From 1280c0f8aafc4c09c59c576c8d50f367070b2619 Mon Sep 17 00:00:00 2001 From: Edward Cree Date: Thu, 25 Jan 2018 17:27:40 +0000 Subject: sfc: support second + quarter ns time format for receive datapath The time_format that we stash in the PTP data structure is never referenced, so we can remove it. Instead, store the information needed to interpret sync event timestamps. Also rolls in a couple of other related minor PTP fixes. Based on patches by Bert Kenward and Laurence Evans . Signed-off-by: Edward Cree Signed-off-by: David S. Miller --- drivers/net/ethernet/sfc/ptp.c | 139 +++++++++++++++++++++++++++++++---------- 1 file changed, 106 insertions(+), 33 deletions(-) diff --git a/drivers/net/ethernet/sfc/ptp.c b/drivers/net/ethernet/sfc/ptp.c index 63c2b25f9281..196b0768b2f2 100644 --- a/drivers/net/ethernet/sfc/ptp.c +++ b/drivers/net/ethernet/sfc/ptp.c @@ -233,9 +233,17 @@ struct efx_ptp_timeset { * @config: Current timestamp configuration * @enabled: PTP operation enabled * @mode: Mode in which PTP operating (PTP version) - * @time_format: Time format supported by this NIC * @ns_to_nic_time: Function to convert from scalar nanoseconds to NIC time * @nic_to_kernel_time: Function to convert from NIC to kernel time + * @nic_time.minor_max: Wrap point for NIC minor times + * @nic_time.sync_event_diff_min: Minimum acceptable difference between time + * in packet prefix and last MCDI time sync event i.e. how much earlier than + * the last sync event time a packet timestamp can be. + * @nic_time.sync_event_diff_max: Maximum acceptable difference between time + * in packet prefix and last MCDI time sync event i.e. how much later than + * the last sync event time a packet timestamp can be. + * @nic_time.sync_event_minor_shift: Shift required to make minor time from + * field in MCDI time sync event. * @min_synchronisation_ns: Minimum acceptable corrected sync window * @capabilities: Capabilities flags from the NIC * @ts_corrections.ptp_tx: Required driver correction of PTP packet transmit @@ -292,10 +300,15 @@ struct efx_ptp_data { struct hwtstamp_config config; bool enabled; unsigned int mode; - unsigned int time_format; void (*ns_to_nic_time)(s64 ns, u32 *nic_major, u32 *nic_minor); ktime_t (*nic_to_kernel_time)(u32 nic_major, u32 nic_minor, s32 correction); + struct { + u32 minor_max; + u32 sync_event_diff_min; + u32 sync_event_diff_max; + unsigned int sync_event_minor_shift; + } nic_time; unsigned int min_synchronisation_ns; struct { s32 ptp_tx; @@ -500,6 +513,32 @@ static ktime_t efx_ptp_s27_to_ktime_correction(u32 nic_major, u32 nic_minor, return efx_ptp_s27_to_ktime(nic_major, nic_minor); } +/* For Medford2 platforms the time is in seconds and quarter nanoseconds. */ +static void efx_ptp_ns_to_s_qns(s64 ns, u32 *nic_major, u32 *nic_minor) +{ + struct timespec64 ts = ns_to_timespec64(ns); + + *nic_major = (u32)ts.tv_sec; + *nic_minor = ts.tv_nsec * 4; +} + +static ktime_t efx_ptp_s_qns_to_ktime_correction(u32 nic_major, u32 nic_minor, + s32 correction) +{ + ktime_t kt; + + nic_minor = DIV_ROUND_CLOSEST(nic_minor, 4); + correction = DIV_ROUND_CLOSEST(correction, 4); + + kt = ktime_set(nic_major, nic_minor); + + if (correction >= 0) + kt = ktime_add_ns(kt, (u64)correction); + else + kt = ktime_sub_ns(kt, (u64)-correction); + return kt; +} + struct efx_channel *efx_ptp_channel(struct efx_nic *efx) { return efx->ptp_data ? efx->ptp_data->channel : NULL; @@ -519,7 +558,8 @@ static u32 last_sync_timestamp_major(struct efx_nic *efx) * 48 bits long and provides meta-information in the top 2 bits. */ static ktime_t -efx_ptp_mac_s27_to_ktime_correction(struct efx_nic *efx, +efx_ptp_mac_nic_to_ktime_correction(struct efx_nic *efx, + struct efx_ptp_data *ptp, u32 nic_major, u32 nic_minor, s32 correction) { @@ -531,8 +571,8 @@ efx_ptp_mac_s27_to_ktime_correction(struct efx_nic *efx, nic_major &= 0xffff; nic_major |= (last_sync_timestamp_major(efx) & 0xffff0000); - kt = efx_ptp_s27_to_ktime_correction(nic_major, nic_minor, - correction); + kt = ptp->nic_to_kernel_time(nic_major, nic_minor, + correction); } return kt; } @@ -544,7 +584,7 @@ ktime_t efx_ptp_nic_to_kernel_time(struct efx_tx_queue *tx_queue) ktime_t kt; if (efx_ptp_use_mac_tx_timestamps(efx)) - kt = efx_ptp_mac_s27_to_ktime_correction(efx, + kt = efx_ptp_mac_nic_to_ktime_correction(efx, ptp, tx_queue->completed_timestamp_major, tx_queue->completed_timestamp_minor, ptp->ts_corrections.general_tx); @@ -587,23 +627,49 @@ static int efx_ptp_get_attributes(struct efx_nic *efx) return rc; } - if (fmt == MC_CMD_PTP_OUT_GET_ATTRIBUTES_SECONDS_27FRACTION) { + switch (fmt) { + case MC_CMD_PTP_OUT_GET_ATTRIBUTES_SECONDS_27FRACTION: ptp->ns_to_nic_time = efx_ptp_ns_to_s27; ptp->nic_to_kernel_time = efx_ptp_s27_to_ktime_correction; - } else if (fmt == MC_CMD_PTP_OUT_GET_ATTRIBUTES_SECONDS_NANOSECONDS) { + ptp->nic_time.minor_max = 1 << 27; + ptp->nic_time.sync_event_minor_shift = 19; + break; + case MC_CMD_PTP_OUT_GET_ATTRIBUTES_SECONDS_NANOSECONDS: ptp->ns_to_nic_time = efx_ptp_ns_to_s_ns; ptp->nic_to_kernel_time = efx_ptp_s_ns_to_ktime_correction; - } else { + ptp->nic_time.minor_max = 1000000000; + ptp->nic_time.sync_event_minor_shift = 22; + break; + case MC_CMD_PTP_OUT_GET_ATTRIBUTES_SECONDS_QTR_NANOSECONDS: + ptp->ns_to_nic_time = efx_ptp_ns_to_s_qns; + ptp->nic_to_kernel_time = efx_ptp_s_qns_to_ktime_correction; + ptp->nic_time.minor_max = 4000000000; + ptp->nic_time.sync_event_minor_shift = 24; + break; + default: return -ERANGE; } - /* MC_CMD_PTP_OP_GET_ATTRIBUTES is an extended version of an older - * operation MC_CMD_PTP_OP_GET_TIME_FORMAT that also returns a value - * to use for the minimum acceptable corrected synchronization window. + /* Precalculate acceptable difference between the minor time in the + * packet prefix and the last MCDI time sync event. We expect the + * packet prefix timestamp to be after of sync event by up to one + * sync event interval (0.25s) but we allow it to exceed this by a + * fuzz factor of (0.1s) + */ + ptp->nic_time.sync_event_diff_min = ptp->nic_time.minor_max + - (ptp->nic_time.minor_max / 10); + ptp->nic_time.sync_event_diff_max = (ptp->nic_time.minor_max / 4) + + (ptp->nic_time.minor_max / 10); + + /* MC_CMD_PTP_OP_GET_ATTRIBUTES has been extended twice from an older + * operation MC_CMD_PTP_OP_GET_TIME_FORMAT. The function now may return + * a value to use for the minimum acceptable corrected synchronization + * window and may return further capabilities. * If we have the extra information store it. For older firmware that * does not implement the extended command use the default value. */ - if (rc == 0 && out_len >= MC_CMD_PTP_OUT_GET_ATTRIBUTES_LEN) + if (rc == 0 && + out_len >= MC_CMD_PTP_OUT_GET_ATTRIBUTES_CAPABILITIES_OFST) ptp->min_synchronisation_ns = MCDI_DWORD(outbuf, PTP_OUT_GET_ATTRIBUTES_SYNC_WINDOW_MIN); @@ -1855,9 +1921,20 @@ void efx_ptp_event(struct efx_nic *efx, efx_qword_t *ev) void efx_time_sync_event(struct efx_channel *channel, efx_qword_t *ev) { + struct efx_nic *efx = channel->efx; + struct efx_ptp_data *ptp = efx->ptp_data; + + /* When extracting the sync timestamp minor value, we should discard + * the least significant two bits. These are not required in order + * to reconstruct full-range timestamps and they are optionally used + * to report status depending on the options supplied when subscribing + * for sync events. + */ channel->sync_timestamp_major = MCDI_EVENT_FIELD(*ev, PTP_TIME_MAJOR); channel->sync_timestamp_minor = - MCDI_EVENT_FIELD(*ev, PTP_TIME_MINOR_26_19) << 19; + (MCDI_EVENT_FIELD(*ev, PTP_TIME_MINOR_MS_8BITS) & 0xFC) + << ptp->nic_time.sync_event_minor_shift; + /* if sync events have been disabled then we want to silently ignore * this event, so throw away result. */ @@ -1865,15 +1942,6 @@ void efx_time_sync_event(struct efx_channel *channel, efx_qword_t *ev) SYNC_EVENTS_VALID); } -/* make some assumptions about the time representation rather than abstract it, - * since we currently only support one type of inline timestamping and only on - * EF10. - */ -#define MINOR_TICKS_PER_SECOND 0x8000000 -/* Fuzz factor for sync events to be out of order with RX events */ -#define FUZZ (MINOR_TICKS_PER_SECOND / 10) -#define EXPECTED_SYNC_EVENTS_PER_SECOND 4 - static inline u32 efx_rx_buf_timestamp_minor(struct efx_nic *efx, const u8 *eh) { #if defined(CONFIG_HAVE_EFFICIENT_UNALIGNED_ACCESS) @@ -1891,28 +1959,33 @@ void __efx_rx_skb_attach_timestamp(struct efx_channel *channel, struct sk_buff *skb) { struct efx_nic *efx = channel->efx; + struct efx_ptp_data *ptp = efx->ptp_data; u32 pkt_timestamp_major, pkt_timestamp_minor; u32 diff, carry; struct skb_shared_hwtstamps *timestamps; + if (channel->sync_events_state != SYNC_EVENTS_VALID) + return; + pkt_timestamp_minor = efx_rx_buf_timestamp_minor(efx, skb_mac_header(skb)); /* get the difference between the packet and sync timestamps, * modulo one second */ - diff = (pkt_timestamp_minor - channel->sync_timestamp_minor) & - (MINOR_TICKS_PER_SECOND - 1); + diff = pkt_timestamp_minor - channel->sync_timestamp_minor; + if (pkt_timestamp_minor < channel->sync_timestamp_minor) + diff += ptp->nic_time.minor_max; + /* do we roll over a second boundary and need to carry the one? */ - carry = channel->sync_timestamp_minor + diff > MINOR_TICKS_PER_SECOND ? + carry = (channel->sync_timestamp_minor >= ptp->nic_time.minor_max - diff) ? 1 : 0; - if (diff <= MINOR_TICKS_PER_SECOND / EXPECTED_SYNC_EVENTS_PER_SECOND + - FUZZ) { + if (diff <= ptp->nic_time.sync_event_diff_max) { /* packet is ahead of the sync event by a quarter of a second or * less (allowing for fuzz) */ pkt_timestamp_major = channel->sync_timestamp_major + carry; - } else if (diff >= MINOR_TICKS_PER_SECOND - FUZZ) { + } else if (diff >= ptp->nic_time.sync_event_diff_min) { /* packet is behind the sync event but within the fuzz factor. * This means the RX packet and sync event crossed as they were * placed on the event queue, which can sometimes happen. @@ -1933,10 +2006,10 @@ void __efx_rx_skb_attach_timestamp(struct efx_channel *channel, /* attach the timestamps to the skb */ timestamps = skb_hwtstamps(skb); - timestamps->hwtstamp = efx_ptp_s27_to_ktime_correction( - pkt_timestamp_major, - pkt_timestamp_minor, - efx->ptp_data->ts_corrections.general_rx); + timestamps->hwtstamp = + ptp->nic_to_kernel_time(pkt_timestamp_major, + pkt_timestamp_minor, + ptp->ts_corrections.general_rx); } static int efx_phc_adjfreq(struct ptp_clock_info *ptp, s32 delta) -- cgit v1.2.3