Discussion:
[Linuxptp-devel] [PATCH V2] ptp and igb: implement POSIX timer (alarm)
Kieran Tyrrell
2016-09-15 12:23:20 UTC
Permalink
Signed-off-by: Kieran Tyrrell <***@sienda.com>
---
diff --git a/drivers/net/ethernet/intel/igb/igb.h b/drivers/net/ethernet/intel/igb/igb.h
index 5387b3a..671c579 100644
--- a/drivers/net/ethernet/intel/igb/igb.h
+++ b/drivers/net/ethernet/intel/igb/igb.h
@@ -352,6 +352,7 @@ struct hwmon_buff {

#define IGB_N_EXTTS 2
#define IGB_N_PEROUT 2
+#define IGB_N_ALARM 2
#define IGB_N_SDP 4
#define IGB_RETA_SIZE 128

@@ -456,6 +457,7 @@ struct igb_adapter {
struct {
struct timespec64 start;
struct timespec64 period;
+ bool enabled;
} perout[IGB_N_PEROUT];

char fw_version[32];
@@ -473,6 +475,8 @@ struct igb_adapter {
int copper_tries;
struct e1000_info ei;
u16 eee_advert;
+
+ int timer_channel;
};

/* flags controlling PTP/1588 function */
@@ -558,6 +562,10 @@ void igb_ptp_rx_pktstamp(struct igb_q_vector *q_vector, unsigned char *va,
int igb_ptp_set_ts_config(struct net_device *netdev, struct ifreq *ifr);
int igb_ptp_get_ts_config(struct net_device *netdev, struct ifreq *ifr);
void igb_set_flag_queue_pairs(struct igb_adapter *, const u32);
+void igb_tt_timer_enable(struct igb_adapter *adapter, bool enable,
+ struct timespec64 *ts);
+void igb_ptp_read_i210(struct igb_adapter *adapter,
+ struct timespec64 *ts);
#ifdef CONFIG_IGB_HWMON
void igb_sysfs_exit(struct igb_adapter *adapter);
int igb_sysfs_init(struct igb_adapter *adapter);
diff --git a/drivers/net/ethernet/intel/igb/igb_main.c b/drivers/net/ethernet/intel/igb/igb_main.c
index 942a89f..aae53e6 100644
--- a/drivers/net/ethernet/intel/igb/igb_main.c
+++ b/drivers/net/ethernet/intel/igb/igb_main.c
@@ -5638,32 +5638,69 @@ static void igb_tsync_interrupt(struct igb_adapter *adapter)
}

if (tsicr & TSINTR_TT0) {
- spin_lock(&adapter->tmreg_lock);
- ts = timespec64_add(adapter->perout[0].start,
- adapter->perout[0].period);
- /* u32 conversion of tv_sec is safe until y2106 */
- wr32(E1000_TRGTTIML0, ts.tv_nsec);
- wr32(E1000_TRGTTIMH0, (u32)ts.tv_sec);
- tsauxc = rd32(E1000_TSAUXC);
- tsauxc |= TSAUXC_EN_TT0;
- wr32(E1000_TSAUXC, tsauxc);
- adapter->perout[0].start = ts;
- spin_unlock(&adapter->tmreg_lock);
- ack |= TSINTR_TT0;
+ if (adapter->timer_channel == 0) {
+ /* this is a timer interrupt */
+ spin_lock(&adapter->tmreg_lock);
+ igb_tt_timer_enable(adapter, false, NULL);
+ wr32(E1000_TSICR, TSINTR_TT0);
+ igb_ptp_read_i210(adapter, &event.alarm_time);
+ spin_unlock(&adapter->tmreg_lock);
+ event.type = PTP_CLOCK_ALARM;
+ event.index = 0;
+ /* ptp_clock_event will return the next time to set */
+ ptp_clock_event(adapter->ptp_clock, &event);
+ spin_lock(&adapter->tmreg_lock);
+ if (timespec64_to_ns(&event.alarm_time) != 0)
+ igb_tt_timer_enable(adapter, true, &event.alarm_time);
+
+ spin_unlock(&adapter->tmreg_lock);
+ } else {
+ /* this is a periodic output interrupt */
+ spin_lock(&adapter->tmreg_lock);
+ ts = timespec64_add(adapter->perout[0].start,
+ adapter->perout[0].period);
+ /* u32 conversion of tv_sec is safe until y2106 */
+ wr32(E1000_TRGTTIML0, ts.tv_nsec);
+ wr32(E1000_TRGTTIMH0, (u32)ts.tv_sec);
+ tsauxc = rd32(E1000_TSAUXC);
+ tsauxc |= TSAUXC_EN_TT0;
+ wr32(E1000_TSAUXC, tsauxc);
+ adapter->perout[0].start = ts;
+ spin_unlock(&adapter->tmreg_lock);
+ ack |= TSINTR_TT0;
+ }
+
}

if (tsicr & TSINTR_TT1) {
- spin_lock(&adapter->tmreg_lock);
- ts = timespec64_add(adapter->perout[1].start,
- adapter->perout[1].period);
- wr32(E1000_TRGTTIML1, ts.tv_nsec);
- wr32(E1000_TRGTTIMH1, (u32)ts.tv_sec);
- tsauxc = rd32(E1000_TSAUXC);
- tsauxc |= TSAUXC_EN_TT1;
- wr32(E1000_TSAUXC, tsauxc);
- adapter->perout[1].start = ts;
- spin_unlock(&adapter->tmreg_lock);
- ack |= TSINTR_TT1;
+ if (adapter->timer_channel == 1) {
+ /* this is a timer interrupt */
+ spin_lock(&adapter->tmreg_lock);
+ igb_tt_timer_enable(adapter, false, NULL);
+ wr32(E1000_TSICR, TSINTR_TT1);
+ igb_ptp_read_i210(adapter, &event.alarm_time);
+ spin_unlock(&adapter->tmreg_lock);
+ event.type = PTP_CLOCK_ALARM;
+ /* ptp_clock_event will return the next time to set */
+ ptp_clock_event(adapter->ptp_clock, &event);
+ spin_lock(&adapter->tmreg_lock);
+ if (timespec64_to_ns(&event.alarm_time) != 0)
+ igb_tt_timer_enable(adapter, true, &event.alarm_time);
+ spin_unlock(&adapter->tmreg_lock);
+ } else {
+ /* this is a periodic output interrupt */
+ spin_lock(&adapter->tmreg_lock);
+ ts = timespec64_add(adapter->perout[1].start,
+ adapter->perout[1].period);
+ wr32(E1000_TRGTTIML1, ts.tv_nsec);
+ wr32(E1000_TRGTTIMH1, (u32)ts.tv_sec);
+ tsauxc = rd32(E1000_TSAUXC);
+ tsauxc |= TSAUXC_EN_TT1;
+ wr32(E1000_TSAUXC, tsauxc);
+ adapter->perout[1].start = ts;
+ spin_unlock(&adapter->tmreg_lock);
+ ack |= TSINTR_TT1;
+ }
}

if (tsicr & TSINTR_AUTT0) {
@@ -5687,7 +5724,8 @@ static void igb_tsync_interrupt(struct igb_adapter *adapter)
}

/* acknowledge the interrupts */
- wr32(E1000_TSICR, ack);
+ if (ack)
+ wr32(E1000_TSICR, ack);
}

static irqreturn_t igb_msix_other(int irq, void *data)
diff --git a/drivers/net/ethernet/intel/igb/igb_ptp.c b/drivers/net/ethernet/intel/igb/igb_ptp.c
index 336c103..fffdb46 100644
--- a/drivers/net/ethernet/intel/igb/igb_ptp.c
+++ b/drivers/net/ethernet/intel/igb/igb_ptp.c
@@ -19,6 +19,7 @@
#include <linux/device.h>
#include <linux/pci.h>
#include <linux/ptp_classify.h>
+#include <linux/posix-timers.h>

#include "igb.h"

@@ -116,8 +117,8 @@ static cycle_t igb_ptp_read_82580(const struct cyclecounter *cc)
}

/* SYSTIM read access for I210/I211 */
-static void igb_ptp_read_i210(struct igb_adapter *adapter,
- struct timespec64 *ts)
+void igb_ptp_read_i210(struct igb_adapter *adapter,
+ struct timespec64 *ts)
{
struct e1000_hw *hw = &adapter->hw;
u32 sec, nsec;
@@ -515,6 +516,9 @@ static int igb_ptp_feature_enable_i210(struct ptp_clock_info *ptp,
return 0;

case PTP_CLK_REQ_PEROUT:
+ /* cannot use perout and timer at the same time */
+ if (igb->timer_channel == rq->perout.index)
+ return -EBUSY;
if (on) {
pin = ptp_find_pin(igb->ptp_clock, PTP_PF_PEROUT,
rq->perout.index);
@@ -578,7 +582,9 @@ static int igb_ptp_feature_enable_i210(struct ptp_clock_info *ptp,
wr32(freqout, ns);
tsauxc |= tsauxc_mask;
tsim |= tsim_mask;
- }
+ igb->perout[i].enabled = true;
+ } else
+ igb->perout[rq->perout.index].enabled = false;
wr32(E1000_TSAUXC, tsauxc);
wr32(E1000_TSIM, tsim);
spin_unlock_irqrestore(&igb->tmreg_lock, flags);
@@ -594,6 +600,25 @@ static int igb_ptp_feature_enable_i210(struct ptp_clock_info *ptp,
wr32(E1000_TSIM, tsim);
spin_unlock_irqrestore(&igb->tmreg_lock, flags);
return 0;
+
+ case PTP_CLK_REQ_ALARM:
+ /* cannot use perout and timer at the same time */
+ if (igb->perout[rq->alarm.index].enabled)
+ return -EBUSY;
+ if (on && (igb->timer_channel != -1))
+ return -EINVAL;
+ if (!on && (igb->timer_channel != rq->alarm.index))
+ return -EINVAL;
+ if (on)
+ igb->timer_channel = rq->alarm.index;
+ else {
+ /* disable the timer */
+ spin_lock_irqsave(&igb->tmreg_lock, flags);
+ igb_tt_timer_enable(igb, false, NULL);
+ igb->timer_channel = -1;
+ spin_unlock_irqrestore(&igb->tmreg_lock, flags);
+ }
+ return 0;
}

return -EOPNOTSUPP;
@@ -1063,6 +1088,71 @@ int igb_ptp_set_ts_config(struct net_device *netdev, struct ifreq *ifr)
-EFAULT : 0;
}

+/* tmreg_lock should be held for this call */
+void igb_tt_timer_enable(struct igb_adapter *adapter, bool enable,
+ struct timespec64 *ts)
+{
+ u32 tsauxc, tsim, ttimlreg, ttimhreg;
+ struct e1000_hw *hw = &adapter->hw;
+ int channel = adapter->timer_channel;
+
+ tsauxc = rd32(E1000_TSAUXC);
+ tsim = rd32(E1000_TSIM);
+
+ if (enable) {
+ if (channel == 1) {
+ tsauxc |= TSAUXC_EN_TT1;
+ tsim |= TSINTR_TT1;
+ ttimlreg = E1000_TRGTTIML1;
+ ttimhreg = E1000_TRGTTIMH1;
+ }
+ else {
+ tsauxc |= TSAUXC_EN_TT0;
+ tsim |= TSINTR_TT0;
+ ttimlreg = E1000_TRGTTIML0;
+ ttimhreg = E1000_TRGTTIMH0;
+ }
+ /* set trigger time and then enable timer */
+ wr32(ttimlreg, ts->tv_nsec);
+ wr32(ttimhreg, (u32)ts->tv_sec);
+ wr32(E1000_TSAUXC, tsauxc);
+ wr32(E1000_TSIM, tsim);
+ } else {
+ if (channel == 1) {
+ tsauxc &= ~TSAUXC_EN_TT1;
+ tsim &= ~TSINTR_TT1;
+ }
+ else {
+ tsauxc &= ~TSAUXC_EN_TT0;
+ tsim &= ~TSINTR_TT0;
+ }
+ wr32(E1000_TSAUXC, tsauxc);
+ wr32(E1000_TSIM, tsim);
+ }
+}
+
+static int igb_ptp_timer_settime_i210(struct ptp_clock_info *ptp,
+ struct timespec64 *ts)
+{
+ struct igb_adapter *igb =
+ container_of(ptp, struct igb_adapter, ptp_caps);
+ unsigned long irqsaveflags;
+
+ if (igb->timer_channel == -1)
+ return -EBUSY;
+
+ spin_lock_irqsave(&igb->tmreg_lock, irqsaveflags);
+
+ if (timespec64_to_ns(ts) == 0)
+ igb_tt_timer_enable(igb, false, NULL);
+ else
+ igb_tt_timer_enable(igb, true, ts);
+
+ spin_unlock_irqrestore(&igb->tmreg_lock, irqsaveflags);
+
+ return 0;
+}
+
/**
* igb_ptp_init - Initialize PTP functionality
* @adapter: Board private structure
@@ -1125,6 +1215,7 @@ void igb_ptp_init(struct igb_adapter *adapter)
snprintf(adapter->ptp_caps.name, 16, "%pm", netdev->dev_addr);
adapter->ptp_caps.owner = THIS_MODULE;
adapter->ptp_caps.max_adj = 62499999;
+ adapter->ptp_caps.n_alarm = IGB_N_ALARM;
adapter->ptp_caps.n_ext_ts = IGB_N_EXTTS;
adapter->ptp_caps.n_per_out = IGB_N_PEROUT;
adapter->ptp_caps.n_pins = IGB_N_SDP;
@@ -1136,6 +1227,7 @@ void igb_ptp_init(struct igb_adapter *adapter)
adapter->ptp_caps.settime64 = igb_ptp_settime_i210;
adapter->ptp_caps.enable = igb_ptp_feature_enable_i210;
adapter->ptp_caps.verify = igb_ptp_verify_pin;
+ adapter->ptp_caps.timersettime = igb_ptp_timer_settime_i210;
break;
default:
adapter->ptp_clock = NULL;
@@ -1151,6 +1243,7 @@ void igb_ptp_init(struct igb_adapter *adapter)

adapter->tstamp_config.rx_filter = HWTSTAMP_FILTER_NONE;
adapter->tstamp_config.tx_type = HWTSTAMP_TX_OFF;
+ adapter->timer_channel = -1;

igb_ptp_reset(adapter);

diff --git a/drivers/ptp/ptp_chardev.c b/drivers/ptp/ptp_chardev.c
index d637c93..b351c44 100644
--- a/drivers/ptp/ptp_chardev.c
+++ b/drivers/ptp/ptp_chardev.c
@@ -176,6 +176,21 @@ long ptp_ioctl(struct posix_clock *pc, unsigned int cmd, unsigned long arg)
err = ops->enable(ops, &req, enable);
break;

+ case PTP_ALARM_REQUEST:
+ if (copy_from_user(&req.alarm, (void __user *)arg,
+ sizeof(req.alarm))) {
+ err = -EFAULT;
+ break;
+ }
+ if (req.alarm.index >= ops->n_alarm) {
+ err = -EINVAL;
+ break;
+ }
+ req.type = PTP_CLK_REQ_ALARM;
+ enable = req.alarm.flags & PTP_ENABLE_FEATURE ? 1 : 0;
+ err = ops->enable(ops, &req, enable);
+ break;
+
case PTP_ENABLE_PPS:
if (!capable(CAP_SYS_TIME))
return -EPERM;
diff --git a/drivers/ptp/ptp_clock.c b/drivers/ptp/ptp_clock.c
index 2e481b9..28573dc 100644
--- a/drivers/ptp/ptp_clock.c
+++ b/drivers/ptp/ptp_clock.c
@@ -36,6 +36,8 @@
#define PTP_PPS_EVENT PPS_CAPTUREASSERT
#define PTP_PPS_MODE (PTP_PPS_DEFAULTS | PPS_CANWAIT | PPS_TSFMT_TSPEC)

+#define PTP_TIMER_MINIMUM_INTERVAL_NS 100000
+
/* private globals */

static dev_t ptp_devt;
@@ -43,6 +45,56 @@ static struct class *ptp_class;

static DEFINE_IDA(ptp_clocks_map);

+static void alarm_event(struct ptp_clock *ptp, struct ptp_clock_event *event)
+{
+ struct timerqueue_node *next;
+ struct k_itimer *kit;
+ s64 ns_now;
+ bool signal_failed_to_send;
+ unsigned long tq_lock_flags;
+
+ ns_now = timespec64_to_ns(&event->alarm_time);
+
+ spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+
+ next = timerqueue_getnext(&ptp->timerqueue);
+
+ while (next) {
+ if (next->expires.tv64 > ns_now)
+ break;
+
+ kit = container_of(next, struct k_itimer, it.real.timer.node);
+
+ signal_failed_to_send = posix_timer_event(kit, 0);
+
+ /* update the last one that has fired */
+ timerqueue_del(&ptp->timerqueue, &kit->it.real.timer.node);
+ if ((ktime_to_ns(kit->it.real.interval) != 0)
+ && !signal_failed_to_send) {
+ /* this is a periodic timer, set the next fire time */
+ kit->it.real.timer.node.expires =
+ ktime_add(
+ kit->it.real.timer.node.expires,
+ kit->it.real.interval);
+ timerqueue_add(&ptp->timerqueue,
+ &kit->it.real.timer.node);
+ }
+
+ next = timerqueue_getnext(&ptp->timerqueue);
+ }
+
+ /* now set the event time to the next timer fire time */
+ next = timerqueue_getnext(&ptp->timerqueue);
+ if (next) {
+ event->alarm_time = ktime_to_timespec64(next->expires);
+ } else {
+ event->alarm_time.tv_sec = 0;
+ event->alarm_time.tv_nsec = 0;
+ }
+
+ spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+}
+
/* time stamp event queue operations */

static inline int queue_free(struct timestamp_event_queue *q)
@@ -163,12 +215,113 @@ static int ptp_clock_adjtime(struct posix_clock *pc, struct timex *tx)
return err;
}

+static int ptp_timer_create(struct posix_clock *pc, struct k_itimer *kit)
+{
+ struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+ unsigned long tq_lock_flags;
+
+ spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+ timerqueue_init(&kit->it.real.timer.node);
+ spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+ return 0;
+}
+
+static int ptp_timer_delete(struct posix_clock *pc, struct k_itimer *kit)
+{
+ struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+ unsigned long tq_lock_flags;
+
+ spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+ if (!RB_EMPTY_NODE(&kit->it.real.timer.node.node))
+ timerqueue_del(&ptp->timerqueue, &kit->it.real.timer.node);
+ spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+ return 0;
+}
+
+static void ptp_timer_gettime(struct posix_clock *pc,
+ struct k_itimer *kit,
+ struct itimerspec *tsp)
+{
+ struct timespec time_now;
+
+ if (ptp_clock_gettime(pc, &time_now) != 0)
+ return;
+
+ tsp->it_interval = ktime_to_timespec(kit->it.real.interval);
+ tsp->it_value = timespec_sub(ktime_to_timespec(
+ kit->it.real.timer.node.expires), time_now);
+}
+
+
+static int ptp_timer_settime(struct posix_clock *pc,
+ struct k_itimer *kit, int flags,
+ struct itimerspec *tsp, struct itimerspec *old)
+{
+ struct ptp_clock *ptp = container_of(pc, struct ptp_clock, clock);
+ int err;
+ unsigned long tq_lock_flags;
+ struct timespec time_now;
+ ktime_t fire_time;
+ struct timerqueue_node *next;
+ struct timespec64 ts;
+
+ if (ptp->info->timersettime == 0)
+ return -EOPNOTSUPP;
+
+ if (old)
+ ptp_timer_gettime(pc, kit, old);
+
+ fire_time = timespec_to_ktime(tsp->it_value);
+
+ if ((fire_time.tv64 != 0) && !(flags & TIMER_ABSTIME)) {
+ err = ptp_clock_gettime(pc, &time_now);
+ if (err)
+ return err;
+ /* convert relative to absolute time */
+ fire_time = ktime_add(fire_time, timespec_to_ktime(time_now));
+ }
+
+ kit->it.real.interval = timespec_to_ktime(tsp->it_interval);
+
+ if ((ktime_to_ns(kit->it.real.interval) != 0)
+ && (ktime_to_ns(kit->it.real.interval) < PTP_TIMER_MINIMUM_INTERVAL_NS))
+ kit->it.real.interval = ns_to_ktime(PTP_TIMER_MINIMUM_INTERVAL_NS);
+
+ spin_lock_irqsave(&ptp->tq_lock, tq_lock_flags);
+
+ kit->it.real.timer.node.expires = fire_time;
+
+ if (!RB_EMPTY_NODE(&kit->it.real.timer.node.node))
+ timerqueue_del(&ptp->timerqueue, &kit->it.real.timer.node);
+
+ if (fire_time.tv64 != 0)
+ timerqueue_add(&ptp->timerqueue, &kit->it.real.timer.node);
+
+ next = timerqueue_getnext(&ptp->timerqueue);
+
+ spin_unlock_irqrestore(&ptp->tq_lock, tq_lock_flags);
+
+ if (next)
+ ts = ktime_to_timespec64(next->expires);
+ else {
+ ts.tv_sec = 0;
+ ts.tv_nsec = 0;
+ }
+ return ptp->info->timersettime(ptp->info, &ts);
+}
+
static struct posix_clock_operations ptp_clock_ops = {
.owner = THIS_MODULE,
.clock_adjtime = ptp_clock_adjtime,
.clock_gettime = ptp_clock_gettime,
.clock_getres = ptp_clock_getres,
.clock_settime = ptp_clock_settime,
+ .timer_create = ptp_timer_create,
+ .timer_delete = ptp_timer_delete,
+ .timer_gettime = ptp_timer_gettime,
+ .timer_settime = ptp_timer_settime,
.ioctl = ptp_ioctl,
.open = ptp_open,
.poll = ptp_poll,
@@ -217,6 +370,8 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
mutex_init(&ptp->tsevq_mux);
mutex_init(&ptp->pincfg_mux);
init_waitqueue_head(&ptp->tsev_wq);
+ spin_lock_init(&ptp->tq_lock);
+ timerqueue_init_head(&ptp->timerqueue);

/* Create a new device in our class. */
ptp->dev = device_create(ptp_class, parent, ptp->devid, ptp,
@@ -293,6 +448,7 @@ void ptp_clock_event(struct ptp_clock *ptp, struct ptp_clock_event *event)
switch (event->type) {

case PTP_CLOCK_ALARM:
+ alarm_event(ptp, event);
break;

case PTP_CLOCK_EXTTS:
diff --git a/drivers/ptp/ptp_private.h b/drivers/ptp/ptp_private.h
index 9c5d414..d491299 100644
--- a/drivers/ptp/ptp_private.h
+++ b/drivers/ptp/ptp_private.h
@@ -54,6 +54,10 @@ struct ptp_clock {
struct device_attribute *pin_dev_attr;
struct attribute **pin_attr;
struct attribute_group pin_attr_group;
+
+ struct timerqueue_head timerqueue;
+ spinlock_t tq_lock;
+ struct work_struct alarm_work;
};

/*
diff --git a/include/linux/ptp_clock_kernel.h b/include/linux/ptp_clock_kernel.h
index 6b15e16..155acc2 100644
--- a/include/linux/ptp_clock_kernel.h
+++ b/include/linux/ptp_clock_kernel.h
@@ -31,10 +31,12 @@ struct ptp_clock_request {
PTP_CLK_REQ_EXTTS,
PTP_CLK_REQ_PEROUT,
PTP_CLK_REQ_PPS,
+ PTP_CLK_REQ_ALARM
} type;
union {
struct ptp_extts_request extts;
struct ptp_perout_request perout;
+ struct ptp_alarm_request alarm;
};
};

@@ -92,6 +94,9 @@ struct system_device_crosststamp;
* parameter func: the desired function to use.
* parameter chan: the function channel index to use.
*
+ * @timersettime: Set the alarm time for the hardware timer.
+ * parameter ts: Time value to set.
+ *
* Drivers should embed their ptp_clock_info within a private
* structure, obtaining a reference to it using container_of().
*
@@ -118,6 +123,7 @@ struct ptp_clock_info {
struct ptp_clock_request *request, int on);
int (*verify)(struct ptp_clock_info *ptp, unsigned int pin,
enum ptp_pin_function func, unsigned int chan);
+ int (*timersettime)(struct ptp_clock_info *ptp, struct timespec64 *ts);
};

struct ptp_clock;
@@ -149,12 +155,14 @@ enum ptp_clock_events {
};

/**
- * struct ptp_clock_event - decribes a PTP hardware clock event
+ * struct ptp_clock_event - describes a PTP hardware clock event
*
* @type: One of the ptp_clock_events enumeration values.
* @index: Identifies the source of the event.
- * @timestamp: When the event occurred (%PTP_CLOCK_EXTTS only).
- * @pps_times: When the event occurred (%PTP_CLOCK_PPSUSR only).
+ * @timestamp: When the event occurred (%PTP_CLOCK_EXTTS only).
+ * @pps_times: When the event occurred (%PTP_CLOCK_PPSUSR only).
+ * @alarm_time: When the event occurred, and on return the time for
+ * the next event (%PTP_CLOCK_ALARM only).
*/

struct ptp_clock_event {
@@ -163,6 +171,7 @@ struct ptp_clock_event {
union {
u64 timestamp;
struct pps_event_time pps_times;
+ struct timespec64 alarm_time;
};
};

diff --git a/include/uapi/linux/ptp_clock.h b/include/uapi/linux/ptp_clock.h
index ac6dded..066424b 100644
--- a/include/uapi/linux/ptp_clock.h
+++ b/include/uapi/linux/ptp_clock.h
@@ -70,6 +70,12 @@ struct ptp_perout_request {
unsigned int rsv[4]; /* Reserved for future use. */
};

+struct ptp_alarm_request {
+ unsigned int index; /* Which channel to configure. */
+ unsigned int flags; /* Bit field for PTP_xxx flags. */
+ unsigned int rsv[2]; /* Reserved for future use. */
+};
+
#define PTP_MAX_SAMPLES 25 /* Maximum allowed offset measurement samples. */

struct ptp_sys_offset {
@@ -135,6 +141,7 @@ struct ptp_pin_desc {
#define PTP_PIN_SETFUNC _IOW(PTP_CLK_MAGIC, 7, struct ptp_pin_desc)
#define PTP_SYS_OFFSET_PRECISE \
_IOWR(PTP_CLK_MAGIC, 8, struct ptp_sys_offset_precise)
+#define PTP_ALARM_REQUEST _IOW(PTP_CLK_MAGIC, 9, struct ptp_alarm_request)

struct ptp_extts_event {
struct ptp_clock_time t; /* Time event occured. */


------------------------------------------------------------------------------
Richard Cochran
2016-10-03 19:08:55 UTC
Permalink
Overall, this is looking much better. I will come back to this with
some comments, but I would like to run some tests first, hopefully
this week or next.

Thanks,
Richard
Richard Cochran
2016-12-06 20:18:09 UTC
Permalink
Post by Richard Cochran
Overall, this is looking much better. I will come back to this with
some comments, but I would like to run some tests first, hopefully
this week or next.
Once again, thanks for providing the proof-of-concept. The patch has
some technical issues, and those would be fixable. However, a little
while ago I ran some tests to see how well this works, and the result
convinced me that the performance of the PHC timers doesn't justify
the implementation effort.

Here is what I did.

I started with Linux 4.1.33-rt38 and backported your patch. In
addition, I hacked the SDPs on the i210 to show up as gpios under
/sys. Then I used a simple program (see below) running at RT priority
to toggle a gpio every 10 milliseconds according to the PTP time.
This gpio output was time stamped on an input to the PTP master.

During the test, I ran hackbench to simulate a load on the host.
I ran ptp4l on the i210 host and synchronized to an external master
connected via a crossover cable. (Prior to running this test, I
verified the host was synchronized to within 200 nanoseconds using the
PPS output.) I used phc2sys to synchronize the Linux system time to
the i210 PHC.

I ran the test program in two ways.

1. using nanosleep(CLOCK_REALTIME)

2. using the PHC timer provided by your patch.

The resulting graphic shows the offsets from the 100 PPS on the gpio
over a ten minute test.

Loading Image...

These are the statistics.

| | nanosleep | PHC Timer |
|--------+---------------+---------------|
| min | +8.933000e+03 | +2.080500e+04 |
| max | +2.962100e+04 | +4.319700e+04 |
| pk-pk | +2.068800e+04 | +2.239200e+04 |
| mean | +1.517014e+04 | +2.845244e+04 |
| stddev | +2.449972e+03 | +3.137972e+03 |

Both results display jitter that is to be expected using a program
running under RT Linux. What is really interesting is the average
offset. There is an addition 13 microsecond delay when using the PHC
timer on the i210, but why?

Remember that reading the PCIe bus costs microseconds for each
operation. The additional overhead of accessing the card over PCIe
completely dwarfs any additional time accuracy.

So the conclusion is, using PHC + phc2sys + nanosleep provides the
best performance that we can offer under Linux for globally
synchronized distributed applications.

Thanks,
Richard

---
/*
* This program tests synchronization to a global time source
* in a distributed system.
*
* Portions taken from the sgd test program.
* Copyright (C) 2015 linutronix GmbH
*
* Portions taken from linux/Documentation/ptp/testptp.c.
* Copyright (C) 2010 OMICRON electronics GmbH
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#define _GNU_SOURCE /*for CPU_SET*/
#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <sched.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define PERIOD_SEC 0
#define PERIOD_NSEC 10000000

static int gpio_fd = -1, running = 1, toggle;

static clockid_t get_clockid(int fd)
{
#define CLOCKFD 3
#define FD_TO_CLOCKID(fd) ((~(clockid_t) (fd) << 3) | CLOCKFD)

return FD_TO_CLOCKID(fd);
}

static void gpio_pulse(void)
{
char c;

c = '1';
write(gpio_fd, &c, sizeof(c));
c = '0';
write(gpio_fd, &c, sizeof(c));
}

static void gpio_toggle(void)
{
static int even;
char c;

if (even) {
c = '1';
write(gpio_fd, &c, sizeof(c));
} else {
c = '0';
write(gpio_fd, &c, sizeof(c));
}
even = 1 - even;
}

static void handle_alarm(int s)
{
if (toggle)
gpio_toggle();
else
gpio_pulse();
}

static int install_handler(int signum, void (*handler)(int))
{
struct sigaction action;
sigset_t mask;

/* Unblock the signal. */
sigemptyset(&mask);
sigaddset(&mask, signum);
sigprocmask(SIG_UNBLOCK, &mask, NULL);

/* Install the signal handler. */
action.sa_handler = handler;
action.sa_flags = 0;
sigemptyset(&action.sa_mask);
sigaction(signum, &action, NULL);

return 0;
}

static int run_nanosleep(clockid_t clkid)
{
struct timespec ts;
int err;

clock_gettime(clkid, &ts);
ts.tv_sec += 2;
ts.tv_nsec = 0;

while (running) {
err = clock_nanosleep(clkid, TIMER_ABSTIME, &ts, NULL);
switch (err) {
case 0:
if (toggle) {
gpio_toggle();
} else {
gpio_pulse();
}
ts.tv_sec += PERIOD_SEC;
ts.tv_nsec += PERIOD_NSEC;
while (ts.tv_nsec > 999999999) {
ts.tv_sec += 1;
ts.tv_nsec -= 1000000000;
}
break;
case EINTR:
continue;
default:
fprintf(stderr, "clock_nanosleep returned %d: %s",
err, strerror(err));
}
}

return 0;
}

static int run_timer(clockid_t clkid)
{
static timer_t timerid;
struct itimerspec timeout;
struct sigevent sigevent;

install_handler(SIGALRM, handle_alarm);

/* Create a timer. */
sigevent.sigev_notify = SIGEV_SIGNAL;
sigevent.sigev_signo = SIGALRM;
if (timer_create(clkid, &sigevent, &timerid)) {
perror("timer_create");
return -1;
}

/* Start the timer. */
memset(&timeout, 0, sizeof(timeout));
timeout.it_interval.tv_sec = PERIOD_SEC;
timeout.it_interval.tv_nsec = PERIOD_NSEC;
clock_gettime(clkid, &timeout.it_value);
timeout.it_value.tv_sec += 2;
timeout.it_value.tv_nsec = 0;

if (timer_settime(timerid, TIMER_ABSTIME, &timeout, NULL)) {
perror("timer_settime");
return -1;
}

while (running) {
pause();
}

timer_delete(timerid);
return 0;
}

static int set_realtime(pthread_t thread, int priority, int cpu)
{
cpu_set_t cpuset;
struct sched_param sp;
int err, policy;

int min = sched_get_priority_min(SCHED_FIFO);
int max = sched_get_priority_max(SCHED_FIFO);

fprintf(stderr, "min %d max %d\n", min, max);

if (priority < 0) {
return 0;
}

err = pthread_getschedparam(thread, &policy, &sp);
if (err) {
fprintf(stderr, "pthread_getschedparam: %s\n", strerror(err));
return -1;
}

sp.sched_priority = priority;

err = pthread_setschedparam(thread, SCHED_FIFO, &sp);
if (err) {
fprintf(stderr, "pthread_setschedparam: %s\n", strerror(err));
return -1;
}

if (cpu < 0) {
return 0;
}
CPU_ZERO(&cpuset);
CPU_SET(cpu, &cpuset);
err = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset);
if (err) {
fprintf(stderr, "pthread_setaffinity_np: %s\n", strerror(err));
return -1;
}

return 0;
}

static void usage(char *progname)
{
fprintf(stderr,
"\n"
"usage: %s [options]\n"
"\n"
" -c [num] run on CPU 'num'\n"
" -d [file] open device 'file'\n"
" omit to use CLOCK_REALTIME instead\n"
" -h prints this message and exits\n"
" -n use clock_nanosleep() instead of timer_settime()\n"
" -o [file] write to gpio 'file'\n"
" -p [num] run with RT priorty 'num'\n"
" -t toggle instead of making a pulse\n"
"\n",
progname);
}

int main(int argc, char *argv[])
{
int c, cpu = -1, fd, err, nanosleep = 0, priority = -1;
char *device = NULL, *gpio = NULL, *progname;
clockid_t clkid;

/* Process the command line arguments. */
progname = strrchr(argv[0], '/');
progname = progname ? 1 + progname : argv[0];
while (EOF != (c = getopt(argc, argv, "c:d:hno:p:t"))) {
switch (c) {
case 'c':
cpu = atoi(optarg);
break;
case 'd':
device = optarg;
break;
case 'h':
usage(progname);
return 0;
case 'n':
nanosleep = 1;
break;
case 'o':
gpio = optarg;
break;
case 'p':
priority = atoi(optarg);
break;
case 't':
toggle = 1;
break;
case '?':
usage(progname);
return -1;
}
}
if (!gpio) {
usage(progname);
return -1;
}
if (set_realtime(pthread_self(), priority, cpu)) {
return -1;
}
gpio_fd = open(gpio, O_RDWR);
if (gpio_fd < 0) {
fprintf(stderr, "cannot open %s: %m\n", gpio);
return -1;
}
if (device) {
fd = open(device, O_RDWR);
if (fd < 0) {
fprintf(stderr, "cannot open %s: %m\n", device);
return -1;
}
clkid = get_clockid(fd);
} else {
clkid = CLOCK_REALTIME;
}

if (nanosleep) {
err = run_nanosleep(clkid);
} else {
err = run_timer(clkid);
}

if (device) {
close(fd);
}
close(gpio_fd);
return err;
}

Loading...