can: gs_usb: add RX and TX hardware timestamp support

Add support for hardware timestamps, if the firmware includes it as a
feature via the GS_CAN_FEATURE_HW_TIMESTAMP flag. Check for this
feature during probe, extend the RX expected length if it is and
enable it during open.

The struct classic_can_ts and struct canfd_ts are extended to include
the µs timestamp following data as defined in the firmware. The
timestamp is then captured and set using skb_hwtstamps() on each RX
and TX.

The frame µs timestamp is provided from a 32 bit 1 MHz timer which
rolls over every 4294 seconds, so a cyclecounter, timecounter, and
delayed worker are used to convert the timer into a proper ns
timestamp - same implementation as commit efd8d98dfb ("can:
mcp251xfd: add HW timestamp infrastructure").

Hardware timestamps are added to capabilities as commit
b1f6b93e67 ("can: mcp251xfd: advertise timestamping capabilities and
add ioctl support").

Signed-off-by: John Whittington <git@jbrengineering.co.uk>
Link: https://github.com/candle-usb/candleLight_fw/issues/100
Link: https://lore.kernel.org/all/20220827221548.3291393-3-mkl@pengutronix.de
Co-developed-by: Marc Kleine-Budde <mkl@pengutronix.de>
Signed-off-by: Marc Kleine-Budde <mkl@pengutronix.de>
This commit is contained in:
John Whittington 2022-08-22 12:01:10 +02:00 committed by Marc Kleine-Budde
parent 49c007b9ec
commit 45dfa45f52

View File

@ -10,12 +10,16 @@
*/ */
#include <linux/bitfield.h> #include <linux/bitfield.h>
#include <linux/clocksource.h>
#include <linux/ethtool.h> #include <linux/ethtool.h>
#include <linux/init.h> #include <linux/init.h>
#include <linux/module.h> #include <linux/module.h>
#include <linux/netdevice.h> #include <linux/netdevice.h>
#include <linux/signal.h> #include <linux/signal.h>
#include <linux/timecounter.h>
#include <linux/units.h>
#include <linux/usb.h> #include <linux/usb.h>
#include <linux/workqueue.h>
#include <linux/can.h> #include <linux/can.h>
#include <linux/can/dev.h> #include <linux/can/dev.h>
@ -37,6 +41,14 @@
#define GS_USB_ENDPOINT_IN 1 #define GS_USB_ENDPOINT_IN 1
#define GS_USB_ENDPOINT_OUT 2 #define GS_USB_ENDPOINT_OUT 2
/* Timestamp 32 bit timer runs at 1 MHz (1 µs tick). Worker accounts
* for timer overflow (will be after ~71 minutes)
*/
#define GS_USB_TIMESTAMP_TIMER_HZ (1 * HZ_PER_MHZ)
#define GS_USB_TIMESTAMP_WORK_DELAY_SEC 1800
static_assert(GS_USB_TIMESTAMP_WORK_DELAY_SEC <
CYCLECOUNTER_MASK(32) / GS_USB_TIMESTAMP_TIMER_HZ / 2);
/* Device specific constants */ /* Device specific constants */
enum gs_usb_breq { enum gs_usb_breq {
GS_USB_BREQ_HOST_FORMAT = 0, GS_USB_BREQ_HOST_FORMAT = 0,
@ -199,6 +211,11 @@ struct classic_can {
u8 data[8]; u8 data[8];
} __packed; } __packed;
struct classic_can_ts {
u8 data[8];
__le32 timestamp_us;
} __packed;
struct classic_can_quirk { struct classic_can_quirk {
u8 data[8]; u8 data[8];
u8 quirk; u8 quirk;
@ -208,6 +225,11 @@ struct canfd {
u8 data[64]; u8 data[64];
} __packed; } __packed;
struct canfd_ts {
u8 data[64];
__le32 timestamp_us;
} __packed;
struct canfd_quirk { struct canfd_quirk {
u8 data[64]; u8 data[64];
u8 quirk; u8 quirk;
@ -224,8 +246,10 @@ struct gs_host_frame {
union { union {
DECLARE_FLEX_ARRAY(struct classic_can, classic_can); DECLARE_FLEX_ARRAY(struct classic_can, classic_can);
DECLARE_FLEX_ARRAY(struct classic_can_ts, classic_can_ts);
DECLARE_FLEX_ARRAY(struct classic_can_quirk, classic_can_quirk); DECLARE_FLEX_ARRAY(struct classic_can_quirk, classic_can_quirk);
DECLARE_FLEX_ARRAY(struct canfd, canfd); DECLARE_FLEX_ARRAY(struct canfd, canfd);
DECLARE_FLEX_ARRAY(struct canfd_ts, canfd_ts);
DECLARE_FLEX_ARRAY(struct canfd_quirk, canfd_quirk); DECLARE_FLEX_ARRAY(struct canfd_quirk, canfd_quirk);
}; };
} __packed; } __packed;
@ -259,6 +283,11 @@ struct gs_can {
struct can_bittiming_const bt_const, data_bt_const; struct can_bittiming_const bt_const, data_bt_const;
unsigned int channel; /* channel number */ unsigned int channel; /* channel number */
/* time counter for hardware timestamps */
struct cyclecounter cc;
struct timecounter tc;
struct delayed_work timestamp;
u32 feature; u32 feature;
unsigned int hf_size_tx; unsigned int hf_size_tx;
@ -351,6 +380,87 @@ static int gs_cmd_reset(struct gs_can *gsdev)
return rc; return rc;
} }
static inline int gs_usb_get_timestamp(const struct gs_can *dev,
u32 *timestamp_p)
{
__le32 timestamp;
int rc;
rc = usb_control_msg_recv(interface_to_usbdev(dev->iface),
usb_sndctrlpipe(interface_to_usbdev(dev->iface), 0),
GS_USB_BREQ_TIMESTAMP,
USB_DIR_IN | USB_TYPE_VENDOR | USB_RECIP_INTERFACE,
dev->channel, 0,
&timestamp, sizeof(timestamp),
USB_CTRL_GET_TIMEOUT,
GFP_KERNEL);
if (rc)
return rc;
*timestamp_p = le32_to_cpu(timestamp);
return 0;
}
static u64 gs_usb_timestamp_read(const struct cyclecounter *cc)
{
const struct gs_can *dev;
u32 timestamp = 0;
int err;
dev = container_of(cc, struct gs_can, cc);
err = gs_usb_get_timestamp(dev, &timestamp);
if (err)
netdev_err(dev->netdev,
"Error %d while reading timestamp. HW timestamps may be inaccurate.",
err);
return timestamp;
}
static void gs_usb_timestamp_work(struct work_struct *work)
{
struct delayed_work *delayed_work = to_delayed_work(work);
struct gs_can *dev;
dev = container_of(delayed_work, struct gs_can, timestamp);
timecounter_read(&dev->tc);
schedule_delayed_work(&dev->timestamp,
GS_USB_TIMESTAMP_WORK_DELAY_SEC * HZ);
}
static void gs_usb_skb_set_timestamp(const struct gs_can *dev,
struct sk_buff *skb, u32 timestamp)
{
struct skb_shared_hwtstamps *hwtstamps = skb_hwtstamps(skb);
u64 ns;
ns = timecounter_cyc2time(&dev->tc, timestamp);
hwtstamps->hwtstamp = ns_to_ktime(ns);
}
static void gs_usb_timestamp_init(struct gs_can *dev)
{
struct cyclecounter *cc = &dev->cc;
cc->read = gs_usb_timestamp_read;
cc->mask = CYCLECOUNTER_MASK(32);
cc->shift = 32 - bits_per(NSEC_PER_SEC / GS_USB_TIMESTAMP_TIMER_HZ);
cc->mult = clocksource_hz2mult(GS_USB_TIMESTAMP_TIMER_HZ, cc->shift);
timecounter_init(&dev->tc, &dev->cc, ktime_get_real_ns());
INIT_DELAYED_WORK(&dev->timestamp, gs_usb_timestamp_work);
schedule_delayed_work(&dev->timestamp,
GS_USB_TIMESTAMP_WORK_DELAY_SEC * HZ);
}
static void gs_usb_timestamp_stop(struct gs_can *dev)
{
cancel_delayed_work_sync(&dev->timestamp);
}
static void gs_update_state(struct gs_can *dev, struct can_frame *cf) static void gs_update_state(struct gs_can *dev, struct can_frame *cf)
{ {
struct can_device_stats *can_stats = &dev->can.can_stats; struct can_device_stats *can_stats = &dev->can.can_stats;
@ -376,6 +486,24 @@ static void gs_update_state(struct gs_can *dev, struct can_frame *cf)
} }
} }
static void gs_usb_set_timestamp(const struct gs_can *dev, struct sk_buff *skb,
const struct gs_host_frame *hf)
{
u32 timestamp;
if (!(dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP))
return;
if (hf->flags & GS_CAN_FLAG_FD)
timestamp = le32_to_cpu(hf->canfd_ts->timestamp_us);
else
timestamp = le32_to_cpu(hf->classic_can_ts->timestamp_us);
gs_usb_skb_set_timestamp(dev, skb, timestamp);
return;
}
static void gs_usb_receive_bulk_callback(struct urb *urb) static void gs_usb_receive_bulk_callback(struct urb *urb)
{ {
struct gs_usb *usbcan = urb->context; struct gs_usb *usbcan = urb->context;
@ -443,6 +571,8 @@ static void gs_usb_receive_bulk_callback(struct urb *urb)
gs_update_state(dev, cf); gs_update_state(dev, cf);
} }
gs_usb_set_timestamp(dev, skb, hf);
netdev->stats.rx_packets++; netdev->stats.rx_packets++;
netdev->stats.rx_bytes += hf->can_dlc; netdev->stats.rx_bytes += hf->can_dlc;
@ -465,6 +595,9 @@ static void gs_usb_receive_bulk_callback(struct urb *urb)
goto resubmit_urb; goto resubmit_urb;
} }
skb = dev->can.echo_skb[hf->echo_id];
gs_usb_set_timestamp(dev, skb, hf);
netdev->stats.tx_packets++; netdev->stats.tx_packets++;
netdev->stats.tx_bytes += can_get_echo_skb(netdev, hf->echo_id, netdev->stats.tx_bytes += can_get_echo_skb(netdev, hf->echo_id,
NULL); NULL);
@ -823,6 +956,10 @@ static int gs_can_open(struct net_device *netdev)
if (ctrlmode & CAN_CTRLMODE_3_SAMPLES) if (ctrlmode & CAN_CTRLMODE_3_SAMPLES)
flags |= GS_CAN_MODE_TRIPLE_SAMPLE; flags |= GS_CAN_MODE_TRIPLE_SAMPLE;
/* if hardware supports timestamps, enable it */
if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP)
flags |= GS_CAN_MODE_HW_TIMESTAMP;
/* finally start device */ /* finally start device */
dm->mode = cpu_to_le32(GS_CAN_MODE_START); dm->mode = cpu_to_le32(GS_CAN_MODE_START);
dm->flags = cpu_to_le32(flags); dm->flags = cpu_to_le32(flags);
@ -840,6 +977,10 @@ static int gs_can_open(struct net_device *netdev)
kfree(dm); kfree(dm);
/* start polling timestamp */
if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP)
gs_usb_timestamp_init(dev);
dev->can.state = CAN_STATE_ERROR_ACTIVE; dev->can.state = CAN_STATE_ERROR_ACTIVE;
parent->active_channels++; parent->active_channels++;
@ -858,6 +999,10 @@ static int gs_can_close(struct net_device *netdev)
netif_stop_queue(netdev); netif_stop_queue(netdev);
/* stop polling timestamp */
if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP)
gs_usb_timestamp_stop(dev);
/* Stop polling */ /* Stop polling */
parent->active_channels--; parent->active_channels--;
if (!parent->active_channels) { if (!parent->active_channels) {
@ -890,11 +1035,22 @@ static int gs_can_close(struct net_device *netdev)
return 0; return 0;
} }
static int gs_can_eth_ioctl(struct net_device *netdev, struct ifreq *ifr, int cmd)
{
const struct gs_can *dev = netdev_priv(netdev);
if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP)
return can_eth_ioctl_hwts(netdev, ifr, cmd);
return -EOPNOTSUPP;
}
static const struct net_device_ops gs_usb_netdev_ops = { static const struct net_device_ops gs_usb_netdev_ops = {
.ndo_open = gs_can_open, .ndo_open = gs_can_open,
.ndo_stop = gs_can_close, .ndo_stop = gs_can_close,
.ndo_start_xmit = gs_can_start_xmit, .ndo_start_xmit = gs_can_start_xmit,
.ndo_change_mtu = can_change_mtu, .ndo_change_mtu = can_change_mtu,
.ndo_eth_ioctl = gs_can_eth_ioctl,
}; };
static int gs_usb_set_identify(struct net_device *netdev, bool do_identify) static int gs_usb_set_identify(struct net_device *netdev, bool do_identify)
@ -944,9 +1100,21 @@ static int gs_usb_set_phys_id(struct net_device *dev,
return rc; return rc;
} }
static int gs_usb_get_ts_info(struct net_device *netdev,
struct ethtool_ts_info *info)
{
struct gs_can *dev = netdev_priv(netdev);
/* report if device supports HW timestamps */
if (dev->feature & GS_CAN_FEATURE_HW_TIMESTAMP)
return can_ethtool_op_get_ts_info_hwts(netdev, info);
return ethtool_op_get_ts_info(netdev, info);
}
static const struct ethtool_ops gs_usb_ethtool_ops = { static const struct ethtool_ops gs_usb_ethtool_ops = {
.set_phys_id = gs_usb_set_phys_id, .set_phys_id = gs_usb_set_phys_id,
.get_ts_info = ethtool_op_get_ts_info, .get_ts_info = gs_usb_get_ts_info,
}; };
static struct gs_can *gs_make_candev(unsigned int channel, static struct gs_can *gs_make_candev(unsigned int channel,
@ -1202,15 +1370,13 @@ static int gs_usb_probe(struct usb_interface *intf,
} }
init_usb_anchor(&dev->rx_submitted); init_usb_anchor(&dev->rx_submitted);
/* default to classic CAN, switch to CAN-FD if at least one of
* our channels support CAN-FD.
*/
dev->hf_size_rx = struct_size(hf, classic_can, 1);
usb_set_intfdata(intf, dev); usb_set_intfdata(intf, dev);
dev->udev = udev; dev->udev = udev;
for (i = 0; i < icount; i++) { for (i = 0; i < icount; i++) {
unsigned int hf_size_rx = 0;
dev->canch[i] = gs_make_candev(i, intf, dconf); dev->canch[i] = gs_make_candev(i, intf, dconf);
if (IS_ERR_OR_NULL(dev->canch[i])) { if (IS_ERR_OR_NULL(dev->canch[i])) {
/* save error code to return later */ /* save error code to return later */
@ -1228,8 +1394,21 @@ static int gs_usb_probe(struct usb_interface *intf,
} }
dev->canch[i]->parent = dev; dev->canch[i]->parent = dev;
if (dev->canch[i]->can.ctrlmode_supported & CAN_CTRLMODE_FD) /* set RX packet size based on FD and if hardware
dev->hf_size_rx = struct_size(hf, canfd, 1); * timestamps are supported.
*/
if (dev->canch[i]->can.ctrlmode_supported & CAN_CTRLMODE_FD) {
if (dev->canch[i]->feature & GS_CAN_FEATURE_HW_TIMESTAMP)
hf_size_rx = struct_size(hf, canfd_ts, 1);
else
hf_size_rx = struct_size(hf, canfd, 1);
} else {
if (dev->canch[i]->feature & GS_CAN_FEATURE_HW_TIMESTAMP)
hf_size_rx = struct_size(hf, classic_can_ts, 1);
else
hf_size_rx = struct_size(hf, classic_can, 1);
}
dev->hf_size_rx = max(dev->hf_size_rx, hf_size_rx);
} }
kfree(dconf); kfree(dconf);