lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [thread-next>] [day] [month] [year] [list]
Message-Id: <20220716120408.450405-1-matej.vasilevski@seznam.cz>
Date:   Sat, 16 Jul 2022 14:04:09 +0200
From:   Matej Vasilevski <matej.vasilevski@...nam.cz>
To:     Appana Durga Kedareswara rao <appana.durga.rao@...inx.com>,
        Naga Sureshkumar Relli <naga.sureshkumar.relli@...inx.com>,
        Wolfgang Grandegger <wg@...ndegger.com>,
        Marc Kleine-Budde <mkl@...gutronix.de>
Cc:     linux-can@...r.kernel.org, netdev@...r.kernel.org,
        Matej Vasilevski <matej.vasilevski@...nam.cz>,
        Martin Jerabek <martin.jerabek01@...il.com>
Subject: [PATCH] can: xilinx_can: add support for RX timestamps on Zynq

This patch adds support for hardware RX timestamps from Xilinx Zynq CAN
controllers. The timestamp is calculated against a timepoint reference
stored when the first CAN message is received.

When CAN bus traffic does not contain long idle pauses (so that
the clocks would drift by a multiple of the counter rollover time),
then the hardware timestamps provide precise relative time between
received messages. This can be used e.g. for latency testing.

Co-authored-by: Martin Jerabek <martin.jerabek01@...il.com>
Signed-off-by: Matej Vasilevski <matej.vasilevski@...nam.cz>
---
 drivers/net/can/xilinx_can.c | 171 ++++++++++++++++++++++++++++++++++-
 1 file changed, 168 insertions(+), 3 deletions(-)

diff --git a/drivers/net/can/xilinx_can.c b/drivers/net/can/xilinx_can.c
index 393b2d9f9d2a..12e3ca856684 100644
--- a/drivers/net/can/xilinx_can.c
+++ b/drivers/net/can/xilinx_can.c
@@ -11,11 +11,14 @@
 
 #include <linux/bitfield.h>
 #include <linux/clk.h>
+#include <linux/clocksource.h>
 #include <linux/errno.h>
 #include <linux/init.h>
 #include <linux/interrupt.h>
 #include <linux/io.h>
 #include <linux/kernel.h>
+#include "linux/ktime.h"
+#include "linux/math64.h"
 #include <linux/module.h>
 #include <linux/netdevice.h>
 #include <linux/of.h>
@@ -165,6 +168,10 @@ enum xcan_reg {
 #define XCAN_FLAG_RX_FIFO_MULTI	0x0010
 #define XCAN_FLAG_CANFD_2	0x0020
 
+#define XCAN_ZYNQ_TSTAMP_BITS	16
+#define XCAN_ZYNQ_TSTAMP_BIT_MASK	GENMASK(15, 0)
+#define XCAN_ZYNQ_TSTAMP_MAX_VAL	BIT(XCAN_ZYNQ_TSTAMP_BITS)
+
 enum xcan_ip_type {
 	XAXI_CAN = 0,
 	XZYNQ_CANPS,
@@ -181,6 +188,11 @@ struct xcan_devtype_data {
 	unsigned int btr_sjw_shift;
 };
 
+struct xcan_timepoint {
+	u64 ktime_ns;
+	u16 ts;
+};
+
 /**
  * struct xcan_priv - This definition define CAN driver instance
  * @can:			CAN private data structure.
@@ -197,6 +209,11 @@ struct xcan_devtype_data {
  * @bus_clk:			Pointer to struct clk
  * @can_clk:			Pointer to struct clk
  * @devtype:			Device type specific constants
+ * @ref_timepoint:		Reference timepoint for synchronizing ktime with HW ticks
+ * @tstamp_rollover_ns:		Timestamping counter rollover time in nanoseconds
+ * @cantime2ns_mul:		Mult. constant for converting from counter time to nanoseconds
+ * @cantime2ns_shr:		Right shift for converting from counter time to nanoseconds
+ * @rx_timestamps_enabled:	Boolean flag indicating whether rx timestamps should be computed
  */
 struct xcan_priv {
 	struct can_priv can;
@@ -214,6 +231,11 @@ struct xcan_priv {
 	struct clk *bus_clk;
 	struct clk *can_clk;
 	struct xcan_devtype_data devtype;
+	struct xcan_timepoint ref_timepoint;
+	u64 tstamp_rollover_ns;
+	u32 cantime2ns_mul;
+	u32 cantime2ns_shr;
+	bool rx_timestamps_enabled;
 };
 
 /* CAN Bittiming constants as per Xilinx CAN specs */
@@ -759,6 +781,50 @@ static netdev_tx_t xcan_start_xmit(struct sk_buff *skb, struct net_device *ndev)
 	return NETDEV_TX_OK;
 }
 
+/**
+ * xcan_timestamp_to_ktime - Converts the hardware timestamp to ktime timestamp
+ * @priv:	Driver private data structure
+ * @ts:		Timestamp from the hardware counter
+ *
+ * Calculating the frame ktime using rollover count and initial timepoint
+ * reference allows to have constant delay between the moment the frame is
+ * timestamped in hardware, and the timestamp reported by the driver.
+ * When CAN bus traffic does not contain long idle pauses (so that the clocks
+ * would drift by a multiple of the counter rollover time), then timestamps
+ * provide precise relative time between received messages.
+ *
+ * Return: hardware timestamp in ktime
+ */
+static ktime_t xcan_timestamp_to_ktime(struct xcan_priv *priv, u16 ts)
+{
+	struct xcan_timepoint *ref = &priv->ref_timepoint;
+	u64 ktime_now_ns;
+	u64 ktime_diff_ns;
+	u16 tstamp_diff;
+	u64 tstamp_diff_ns;
+	u64 time_diff_ns;
+	u64 rollover_count;
+
+	ktime_now_ns = ktime_get_real_ns();
+	if (ref->ktime_ns == 0) {
+		/* first received frame */
+		ref->ktime_ns = ktime_now_ns;
+		ref->ts = ts;
+		return ns_to_ktime(ktime_now_ns);
+	}
+
+	tstamp_diff = ts > ref->ts ? ts - ref->ts : ref->ts - ts;
+	tstamp_diff_ns = mul_u32_u32((u32)tstamp_diff, priv->cantime2ns_mul)
+			 >> priv->cantime2ns_shr;
+	ktime_diff_ns = ktime_now_ns - ref->ktime_ns;
+	time_diff_ns = ktime_diff_ns - tstamp_diff_ns;
+	rollover_count = div_u64(time_diff_ns + (priv->tstamp_rollover_ns / 2),
+				 priv->tstamp_rollover_ns);
+
+	return ns_to_ktime(tstamp_diff_ns + rollover_count * priv->tstamp_rollover_ns)
+	       + ref->ktime_ns;
+}
+
 /**
  * xcan_rx -  Is called from CAN isr to complete the received
  *		frame  processing
@@ -776,7 +842,8 @@ static int xcan_rx(struct net_device *ndev, int frame_base)
 	struct net_device_stats *stats = &ndev->stats;
 	struct can_frame *cf;
 	struct sk_buff *skb;
-	u32 id_xcan, dlc, data[2] = {0, 0};
+	struct skb_shared_hwtstamps *hwts;
+	u32 id_xcan, dlc_reg, dlc, rawts, data[2] = {0, 0};
 
 	skb = alloc_can_skb(ndev, &cf);
 	if (unlikely(!skb)) {
@@ -786,12 +853,19 @@ static int xcan_rx(struct net_device *ndev, int frame_base)
 
 	/* Read a frame from Xilinx zynq CANPS */
 	id_xcan = priv->read_reg(priv, XCAN_FRAME_ID_OFFSET(frame_base));
-	dlc = priv->read_reg(priv, XCAN_FRAME_DLC_OFFSET(frame_base)) >>
-				   XCAN_DLCR_DLC_SHIFT;
+	dlc_reg = priv->read_reg(priv, XCAN_FRAME_DLC_OFFSET(frame_base));
+	dlc = dlc_reg >> XCAN_DLCR_DLC_SHIFT;
 
 	/* Change Xilinx CAN data length format to socketCAN data format */
 	cf->len = can_cc_dlc2len(dlc);
 
+	if (priv->rx_timestamps_enabled) {
+		hwts = skb_hwtstamps(skb);
+		memset(hwts, 0, sizeof(*hwts));
+		rawts = dlc_reg & XCAN_ZYNQ_TSTAMP_BIT_MASK;
+		hwts->hwtstamp = xcan_timestamp_to_ktime(priv, rawts);
+	}
+
 	/* Change Xilinx CAN ID format to socketCAN ID format */
 	if (id_xcan & XCAN_IDR_IDE_MASK) {
 		/* The received frame is an Extended format frame */
@@ -1532,11 +1606,95 @@ static int xcan_get_auto_tdcv(const struct net_device *ndev, u32 *tdcv)
 	return 0;
 }
 
+static int xcan_hwtstamp_set(struct net_device *dev, struct ifreq *ifr)
+{
+	struct xcan_priv *priv = netdev_priv(dev);
+	struct hwtstamp_config cfg;
+
+	if (priv->devtype.cantype != XZYNQ_CANPS)
+		return -EOPNOTSUPP;
+
+	if (copy_from_user(&cfg, ifr->ifr_data, sizeof(cfg)))
+		return -EFAULT;
+
+	if (cfg.flags)
+		return -EINVAL;
+
+	if (cfg.tx_type != HWTSTAMP_TX_OFF)
+		return -ERANGE;
+
+	switch (cfg.rx_filter) {
+	case HWTSTAMP_FILTER_NONE:
+		priv->rx_timestamps_enabled = false;
+		break;
+	case HWTSTAMP_FILTER_ALL:
+		fallthrough;
+	case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
+		fallthrough;
+	case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
+		fallthrough;
+	case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
+		fallthrough;
+	case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
+		fallthrough;
+	case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
+		fallthrough;
+	case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
+		fallthrough;
+	case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
+		fallthrough;
+	case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
+		fallthrough;
+	case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
+		fallthrough;
+	case HWTSTAMP_FILTER_PTP_V2_EVENT:
+		fallthrough;
+	case HWTSTAMP_FILTER_PTP_V2_SYNC:
+		fallthrough;
+	case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
+		priv->rx_timestamps_enabled = true;
+		cfg.rx_filter = HWTSTAMP_FILTER_ALL;
+		break;
+	default:
+		return -ERANGE;
+	}
+
+	return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
+
+static int xcan_hwtstamp_get(struct net_device *dev, struct ifreq *ifr)
+{
+	struct xcan_priv *priv = netdev_priv(dev);
+	struct hwtstamp_config cfg;
+
+	if (priv->devtype.cantype != XZYNQ_CANPS)
+		return -EOPNOTSUPP;
+
+	cfg.flags = 0;
+	cfg.tx_type = HWTSTAMP_TX_OFF;
+	cfg.rx_filter = priv->rx_timestamps_enabled ? HWTSTAMP_FILTER_ALL : HWTSTAMP_FILTER_NONE;
+
+	return copy_to_user(ifr->ifr_data, &cfg, sizeof(cfg)) ? -EFAULT : 0;
+}
+
+static int xcan_ioctl(struct net_device *dev, struct ifreq *ifr, int cmd)
+{
+	switch (cmd) {
+	case SIOCSHWTSTAMP:
+		return xcan_hwtstamp_set(dev, ifr);
+	case SIOCGHWTSTAMP:
+		return xcan_hwtstamp_get(dev, ifr);
+	default:
+		return -EOPNOTSUPP;
+	}
+}
+
 static const struct net_device_ops xcan_netdev_ops = {
 	.ndo_open	= xcan_open,
 	.ndo_stop	= xcan_close,
 	.ndo_start_xmit	= xcan_start_xmit,
 	.ndo_change_mtu	= can_change_mtu,
+	.ndo_eth_ioctl	= xcan_ioctl,
 };
 
 /**
@@ -1854,6 +2012,13 @@ static int xcan_probe(struct platform_device *pdev)
 
 	priv->can.clock.freq = clk_get_rate(priv->can_clk);
 
+	clocks_calc_mult_shift(&priv->cantime2ns_mul, &priv->cantime2ns_shr,
+			       priv->can.clock.freq, NSEC_PER_SEC, 0);
+	priv->rx_timestamps_enabled = false;
+	priv->tstamp_rollover_ns = mul_u32_u32((u32)XCAN_ZYNQ_TSTAMP_MAX_VAL, priv->cantime2ns_mul)
+				   >> priv->cantime2ns_shr;
+	memset(&priv->ref_timepoint, 0, sizeof(priv->ref_timepoint));
+
 	netif_napi_add_weight(ndev, &priv->napi, xcan_rx_poll, rx_max);
 
 	ret = register_candev(ndev);

base-commit: 874bdbfe624e577687c2053a26aab44715c68453
-- 
2.25.1

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ