summaryrefslogblamecommitdiffstats
path: root/drivers/net/ethernet/intel/fm10k/fm10k_ptp.c
blob: b4945e8abe033a08a59a33e27f808fc41890cf0c (plain) (tree)
1
2
                                              
                                              
























































                                                                            






                                                                       





                                                                       

                                                                 
                                              
         




                                                                    
                                     






















                                                                       
                                                     
                                                                    

                                         


















































































































































































                                                                                
                                                                               










                                                                    
                                    




                                                        
                                                         


                                      
                                      










                                                                    

                                                         


































































































                                                                                

                                                    




































                                                                            
/* Intel Ethernet Switch Host Interface Driver
 * Copyright(c) 2013 - 2015 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;

	/* 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;
	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_shinfo(clone)->tx_flags |= SKBTX_IN_PROGRESS;
		__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)
		dev_kfree_skb(clone);
}

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 free out copy */
	fm10k_systime_to_hwtstamp(interface, &shhwtstamps, systime);
	skb_tstamp_tx(skb, &shhwtstamps);
	dev_kfree_skb_any(skb);
}

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 timespec64 *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_timespec64(now);

	return 0;
}

static int fm10k_ptp_settime(struct ptp_clock_info *ptp,
			     const struct timespec64 *ts)
{
	struct fm10k_intfc *interface;
	unsigned long flags;
	u64 ns = timespec64_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 __always_unused 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->gettime64	= fm10k_ptp_gettime;
	ptp_caps->settime64	= 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);
}