464 lines
13 KiB
C
464 lines
13 KiB
C
/* Intel Ethernet Switch Host Interface Driver
|
|
* Copyright(c) 2013 - 2014 Intel Corporation.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it
|
|
* under the terms and conditions of the GNU General Public License,
|
|
* version 2, as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope 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.
|
|
*
|
|
* The full GNU General Public License is included in this distribution in
|
|
* the file called "COPYING".
|
|
*
|
|
* Contact Information:
|
|
* e1000-devel Mailing List <e1000-devel@lists.sourceforge.net>
|
|
* Intel Corporation, 5200 N.E. Elam Young Parkway, Hillsboro, OR 97124-6497
|
|
*/
|
|
|
|
#include <linux/ptp_classify.h>
|
|
#include <linux/ptp_clock_kernel.h>
|
|
|
|
#include "fm10k.h"
|
|
|
|
#define FM10K_TS_TX_TIMEOUT (HZ * 15)
|
|
|
|
void fm10k_systime_to_hwtstamp(struct fm10k_intfc *interface,
|
|
struct skb_shared_hwtstamps *hwtstamp,
|
|
u64 systime)
|
|
{
|
|
unsigned long flags;
|
|
|
|
read_lock_irqsave(&interface->systime_lock, flags);
|
|
systime += interface->ptp_adjust;
|
|
read_unlock_irqrestore(&interface->systime_lock, flags);
|
|
|
|
hwtstamp->hwtstamp = ns_to_ktime(systime);
|
|
}
|
|
|
|
static struct sk_buff *fm10k_ts_tx_skb(struct fm10k_intfc *interface,
|
|
__le16 dglort)
|
|
{
|
|
struct sk_buff_head *list = &interface->ts_tx_skb_queue;
|
|
struct sk_buff *skb;
|
|
|
|
skb_queue_walk(list, skb) {
|
|
if (FM10K_CB(skb)->fi.w.dglort == dglort)
|
|
return skb;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
void fm10k_ts_tx_enqueue(struct fm10k_intfc *interface, struct sk_buff *skb)
|
|
{
|
|
struct sk_buff_head *list = &interface->ts_tx_skb_queue;
|
|
struct sk_buff *clone;
|
|
unsigned long flags;
|
|
__le16 dglort;
|
|
|
|
/* create clone for us to return on the Tx path */
|
|
clone = skb_clone_sk(skb);
|
|
if (!clone)
|
|
return;
|
|
|
|
FM10K_CB(clone)->ts_tx_timeout = jiffies + FM10K_TS_TX_TIMEOUT;
|
|
dglort = FM10K_CB(clone)->fi.w.dglort;
|
|
|
|
spin_lock_irqsave(&list->lock, flags);
|
|
|
|
/* attempt to locate any buffers with the same dglort,
|
|
* if none are present then insert skb in tail of list
|
|
*/
|
|
skb = fm10k_ts_tx_skb(interface, FM10K_CB(clone)->fi.w.dglort);
|
|
if (!skb)
|
|
__skb_queue_tail(list, clone);
|
|
|
|
spin_unlock_irqrestore(&list->lock, flags);
|
|
|
|
/* if list is already has one then we just free the clone */
|
|
if (skb)
|
|
kfree_skb(skb);
|
|
else
|
|
skb_shinfo(clone)->tx_flags |= SKBTX_IN_PROGRESS;
|
|
}
|
|
|
|
void fm10k_ts_tx_hwtstamp(struct fm10k_intfc *interface, __le16 dglort,
|
|
u64 systime)
|
|
{
|
|
struct skb_shared_hwtstamps shhwtstamps;
|
|
struct sk_buff_head *list = &interface->ts_tx_skb_queue;
|
|
struct sk_buff *skb;
|
|
unsigned long flags;
|
|
|
|
spin_lock_irqsave(&list->lock, flags);
|
|
|
|
/* attempt to locate and pull the sk_buff out of the list */
|
|
skb = fm10k_ts_tx_skb(interface, dglort);
|
|
if (skb)
|
|
__skb_unlink(skb, list);
|
|
|
|
spin_unlock_irqrestore(&list->lock, flags);
|
|
|
|
/* if not found do nothing */
|
|
if (!skb)
|
|
return;
|
|
|
|
/* timestamp the sk_buff and return it to the socket */
|
|
fm10k_systime_to_hwtstamp(interface, &shhwtstamps, systime);
|
|
skb_complete_tx_timestamp(skb, &shhwtstamps);
|
|
}
|
|
|
|
void fm10k_ts_tx_subtask(struct fm10k_intfc *interface)
|
|
{
|
|
struct sk_buff_head *list = &interface->ts_tx_skb_queue;
|
|
struct sk_buff *skb, *tmp;
|
|
unsigned long flags;
|
|
|
|
/* If we're down or resetting, just bail */
|
|
if (test_bit(__FM10K_DOWN, &interface->state) ||
|
|
test_bit(__FM10K_RESETTING, &interface->state))
|
|
return;
|
|
|
|
spin_lock_irqsave(&list->lock, flags);
|
|
|
|
/* walk though the list and flush any expired timestamp packets */
|
|
skb_queue_walk_safe(list, skb, tmp) {
|
|
if (!time_is_after_jiffies(FM10K_CB(skb)->ts_tx_timeout))
|
|
continue;
|
|
__skb_unlink(skb, list);
|
|
kfree_skb(skb);
|
|
interface->tx_hwtstamp_timeouts++;
|
|
}
|
|
|
|
spin_unlock_irqrestore(&list->lock, flags);
|
|
}
|
|
|
|
static u64 fm10k_systime_read(struct fm10k_intfc *interface)
|
|
{
|
|
struct fm10k_hw *hw = &interface->hw;
|
|
|
|
return hw->mac.ops.read_systime(hw);
|
|
}
|
|
|
|
void fm10k_ts_reset(struct fm10k_intfc *interface)
|
|
{
|
|
s64 ns = ktime_to_ns(ktime_get_real());
|
|
unsigned long flags;
|
|
|
|
/* reinitialize the clock */
|
|
write_lock_irqsave(&interface->systime_lock, flags);
|
|
interface->ptp_adjust = fm10k_systime_read(interface) - ns;
|
|
write_unlock_irqrestore(&interface->systime_lock, flags);
|
|
}
|
|
|
|
void fm10k_ts_init(struct fm10k_intfc *interface)
|
|
{
|
|
/* Initialize lock protecting systime access */
|
|
rwlock_init(&interface->systime_lock);
|
|
|
|
/* Initialize skb queue for pending timestamp requests */
|
|
skb_queue_head_init(&interface->ts_tx_skb_queue);
|
|
|
|
/* reset the clock to current kernel time */
|
|
fm10k_ts_reset(interface);
|
|
}
|
|
|
|
/**
|
|
* fm10k_get_ts_config - get current hardware timestamping configuration
|
|
* @netdev: network interface device structure
|
|
* @ifreq: ioctl data
|
|
*
|
|
* This function returns the current timestamping settings. Rather than
|
|
* attempt to deconstruct registers to fill in the values, simply keep a copy
|
|
* of the old settings around, and return a copy when requested.
|
|
*/
|
|
int fm10k_get_ts_config(struct net_device *netdev, struct ifreq *ifr)
|
|
{
|
|
struct fm10k_intfc *interface = netdev_priv(netdev);
|
|
struct hwtstamp_config *config = &interface->ts_config;
|
|
|
|
return copy_to_user(ifr->ifr_data, config, sizeof(*config)) ?
|
|
-EFAULT : 0;
|
|
}
|
|
|
|
/**
|
|
* fm10k_set_ts_config - control hardware time stamping
|
|
* @netdev: network interface device structure
|
|
* @ifreq: ioctl data
|
|
*
|
|
* Outgoing time stamping can be enabled and disabled. Play nice and
|
|
* disable it when requested, although it shouldn't cause any overhead
|
|
* when no packet needs it. At most one packet in the queue may be
|
|
* marked for time stamping, otherwise it would be impossible to tell
|
|
* for sure to which packet the hardware time stamp belongs.
|
|
*
|
|
* Incoming time stamping has to be configured via the hardware
|
|
* filters. Not all combinations are supported, in particular event
|
|
* type has to be specified. Matching the kind of event packet is
|
|
* not supported, with the exception of "all V2 events regardless of
|
|
* level 2 or 4".
|
|
*
|
|
* Since hardware always timestamps Path delay packets when timestamping V2
|
|
* packets, regardless of the type specified in the register, only use V2
|
|
* Event mode. This more accurately tells the user what the hardware is going
|
|
* to do anyways.
|
|
*/
|
|
int fm10k_set_ts_config(struct net_device *netdev, struct ifreq *ifr)
|
|
{
|
|
struct fm10k_intfc *interface = netdev_priv(netdev);
|
|
struct hwtstamp_config ts_config;
|
|
|
|
if (copy_from_user(&ts_config, ifr->ifr_data, sizeof(ts_config)))
|
|
return -EFAULT;
|
|
|
|
/* reserved for future extensions */
|
|
if (ts_config.flags)
|
|
return -EINVAL;
|
|
|
|
switch (ts_config.tx_type) {
|
|
case HWTSTAMP_TX_OFF:
|
|
break;
|
|
case HWTSTAMP_TX_ON:
|
|
/* we likely need some check here to see if this is supported */
|
|
break;
|
|
default:
|
|
return -ERANGE;
|
|
}
|
|
|
|
switch (ts_config.rx_filter) {
|
|
case HWTSTAMP_FILTER_NONE:
|
|
interface->flags &= ~FM10K_FLAG_RX_TS_ENABLED;
|
|
break;
|
|
case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
|
|
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
|
|
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
|
|
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
|
|
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
|
|
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
|
|
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
|
|
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
|
|
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
|
|
case HWTSTAMP_FILTER_PTP_V2_EVENT:
|
|
case HWTSTAMP_FILTER_PTP_V2_SYNC:
|
|
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
|
|
case HWTSTAMP_FILTER_ALL:
|
|
interface->flags |= FM10K_FLAG_RX_TS_ENABLED;
|
|
ts_config.rx_filter = HWTSTAMP_FILTER_ALL;
|
|
break;
|
|
default:
|
|
return -ERANGE;
|
|
}
|
|
|
|
/* save these settings for future reference */
|
|
interface->ts_config = ts_config;
|
|
|
|
return copy_to_user(ifr->ifr_data, &ts_config, sizeof(ts_config)) ?
|
|
-EFAULT : 0;
|
|
}
|
|
|
|
static int fm10k_ptp_adjfreq(struct ptp_clock_info *ptp, s32 ppb)
|
|
{
|
|
struct fm10k_intfc *interface;
|
|
struct fm10k_hw *hw;
|
|
int err;
|
|
|
|
interface = container_of(ptp, struct fm10k_intfc, ptp_caps);
|
|
hw = &interface->hw;
|
|
|
|
err = hw->mac.ops.adjust_systime(hw, ppb);
|
|
|
|
/* the only error we should see is if the value is out of range */
|
|
return (err == FM10K_ERR_PARAM) ? -ERANGE : err;
|
|
}
|
|
|
|
static int fm10k_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
|
|
{
|
|
struct fm10k_intfc *interface;
|
|
unsigned long flags;
|
|
|
|
interface = container_of(ptp, struct fm10k_intfc, ptp_caps);
|
|
|
|
write_lock_irqsave(&interface->systime_lock, flags);
|
|
interface->ptp_adjust += delta;
|
|
write_unlock_irqrestore(&interface->systime_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fm10k_ptp_gettime(struct ptp_clock_info *ptp, struct timespec *ts)
|
|
{
|
|
struct fm10k_intfc *interface;
|
|
unsigned long flags;
|
|
u64 now;
|
|
|
|
interface = container_of(ptp, struct fm10k_intfc, ptp_caps);
|
|
|
|
read_lock_irqsave(&interface->systime_lock, flags);
|
|
now = fm10k_systime_read(interface) + interface->ptp_adjust;
|
|
read_unlock_irqrestore(&interface->systime_lock, flags);
|
|
|
|
*ts = ns_to_timespec(now);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fm10k_ptp_settime(struct ptp_clock_info *ptp,
|
|
const struct timespec *ts)
|
|
{
|
|
struct fm10k_intfc *interface;
|
|
unsigned long flags;
|
|
u64 ns = timespec_to_ns(ts);
|
|
|
|
interface = container_of(ptp, struct fm10k_intfc, ptp_caps);
|
|
|
|
write_lock_irqsave(&interface->systime_lock, flags);
|
|
interface->ptp_adjust = fm10k_systime_read(interface) - ns;
|
|
write_unlock_irqrestore(&interface->systime_lock, flags);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int fm10k_ptp_enable(struct ptp_clock_info *ptp,
|
|
struct ptp_clock_request *rq, int on)
|
|
{
|
|
struct ptp_clock_time *t = &rq->perout.period;
|
|
struct fm10k_intfc *interface;
|
|
struct fm10k_hw *hw;
|
|
u64 period;
|
|
u32 step;
|
|
|
|
/* we can only support periodic output */
|
|
if (rq->type != PTP_CLK_REQ_PEROUT)
|
|
return -EINVAL;
|
|
|
|
/* verify the requested channel is there */
|
|
if (rq->perout.index >= ptp->n_per_out)
|
|
return -EINVAL;
|
|
|
|
/* we cannot enforce start time as there is no
|
|
* mechanism for that in the hardware, we can only control
|
|
* the period.
|
|
*/
|
|
|
|
/* we cannot support periods greater than 4 seconds due to reg limit */
|
|
if (t->sec > 4 || t->sec < 0)
|
|
return -ERANGE;
|
|
|
|
interface = container_of(ptp, struct fm10k_intfc, ptp_caps);
|
|
hw = &interface->hw;
|
|
|
|
/* we simply cannot support the operation if we don't have BAR4 */
|
|
if (!hw->sw_addr)
|
|
return -ENOTSUPP;
|
|
|
|
/* convert to unsigned 64b ns, verify we can put it in a 32b register */
|
|
period = t->sec * 1000000000LL + t->nsec;
|
|
|
|
/* determine the minimum size for period */
|
|
step = 2 * (fm10k_read_reg(hw, FM10K_SYSTIME_CFG) &
|
|
FM10K_SYSTIME_CFG_STEP_MASK);
|
|
|
|
/* verify the value is in range supported by hardware */
|
|
if ((period && (period < step)) || (period > U32_MAX))
|
|
return -ERANGE;
|
|
|
|
/* notify hardware of request to being sending pulses */
|
|
fm10k_write_sw_reg(hw, FM10K_SW_SYSTIME_PULSE(rq->perout.index),
|
|
(u32)period);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static struct ptp_pin_desc fm10k_ptp_pd[2] = {
|
|
{
|
|
.name = "IEEE1588_PULSE0",
|
|
.index = 0,
|
|
.func = PTP_PF_PEROUT,
|
|
.chan = 0
|
|
},
|
|
{
|
|
.name = "IEEE1588_PULSE1",
|
|
.index = 1,
|
|
.func = PTP_PF_PEROUT,
|
|
.chan = 1
|
|
}
|
|
};
|
|
|
|
static int fm10k_ptp_verify(struct ptp_clock_info *ptp, unsigned int pin,
|
|
enum ptp_pin_function func, unsigned int chan)
|
|
{
|
|
/* verify the requested pin is there */
|
|
if (pin >= ptp->n_pins || !ptp->pin_config)
|
|
return -EINVAL;
|
|
|
|
/* enforce locked channels, no changing them */
|
|
if (chan != ptp->pin_config[pin].chan)
|
|
return -EINVAL;
|
|
|
|
/* we want to keep the functions locked as well */
|
|
if (func != ptp->pin_config[pin].func)
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void fm10k_ptp_register(struct fm10k_intfc *interface)
|
|
{
|
|
struct ptp_clock_info *ptp_caps = &interface->ptp_caps;
|
|
struct device *dev = &interface->pdev->dev;
|
|
struct ptp_clock *ptp_clock;
|
|
|
|
snprintf(ptp_caps->name, sizeof(ptp_caps->name),
|
|
"%s", interface->netdev->name);
|
|
ptp_caps->owner = THIS_MODULE;
|
|
/* This math is simply the inverse of the math in
|
|
* fm10k_adjust_systime_pf applied to an adjustment value
|
|
* of 2^30 - 1 which is the maximum value of the register:
|
|
* max_ppb == ((2^30 - 1) * 5^9) / 2^31
|
|
*/
|
|
ptp_caps->max_adj = 976562;
|
|
ptp_caps->adjfreq = fm10k_ptp_adjfreq;
|
|
ptp_caps->adjtime = fm10k_ptp_adjtime;
|
|
ptp_caps->gettime = fm10k_ptp_gettime;
|
|
ptp_caps->settime = fm10k_ptp_settime;
|
|
|
|
/* provide pins if BAR4 is accessible */
|
|
if (interface->sw_addr) {
|
|
/* enable periodic outputs */
|
|
ptp_caps->n_per_out = 2;
|
|
ptp_caps->enable = fm10k_ptp_enable;
|
|
|
|
/* enable clock pins */
|
|
ptp_caps->verify = fm10k_ptp_verify;
|
|
ptp_caps->n_pins = 2;
|
|
ptp_caps->pin_config = fm10k_ptp_pd;
|
|
}
|
|
|
|
ptp_clock = ptp_clock_register(ptp_caps, dev);
|
|
if (IS_ERR(ptp_clock)) {
|
|
ptp_clock = NULL;
|
|
dev_err(dev, "ptp_clock_register failed\n");
|
|
} else {
|
|
dev_info(dev, "registered PHC device %s\n", ptp_caps->name);
|
|
}
|
|
|
|
interface->ptp_clock = ptp_clock;
|
|
}
|
|
|
|
void fm10k_ptp_unregister(struct fm10k_intfc *interface)
|
|
{
|
|
struct ptp_clock *ptp_clock = interface->ptp_clock;
|
|
struct device *dev = &interface->pdev->dev;
|
|
|
|
if (!ptp_clock)
|
|
return;
|
|
|
|
interface->ptp_clock = NULL;
|
|
|
|
ptp_clock_unregister(ptp_clock);
|
|
dev_info(dev, "removed PHC %s\n", interface->ptp_caps.name);
|
|
}
|