Commit fdaa419b authored by David S. Miller's avatar David S. Miller

Merge branch 'ethernet-ti-cpts-fix-tx-timestamping-timeout'

Grygorii Strashko says:

====================
net: ethernet: ti: cpts: fix tx timestamping timeout

With the low Ethernet connection speed cpdma notification about packet
processing can be received before CPTS TX timestamp event, which is set
when packet actually left CPSW while cpdma notification is sent when packet
pushed in CPSW fifo. As result, when connection is slow and CPU is fast
enough TX timestamping is not working properly.
Issue was discovered using timestamping tool on am57x boards with Ethernet link
speed forced to 100M and on am335x-evm with Ethernet link speed forced to 10M.

Patch3 - This series fixes it by introducing TX SKB queue to store PTP SKBs for
which Ethernet Transmit Event hasn't been received yet and then re-check this
queue with new Ethernet Transmit Events by scheduling CPTS overflow
work more often until TX SKB queue is not empty.

Patch 1,2 - As CPTS overflow work is time critical task it important to ensure
that its scheduling is not delayed. Unfortunately, There could be significant
delay in CPTS work schedule under high system load and on -RT which could cause
CPTS misbehavior due to internal counter overflow and there is no way to tune
CPTS overflow work execution policy and priority manually. The kthread_worker
can be used instead of workqueues, as it creates separate named kthread for
each worker and its its execution policy and priority can be configured
using chrt tool. Instead of modifying CPTS driver itself it was proposed to
it was proposed to add PTP auxiliary worker to the PHC subsystem [1], so
other drivers can benefit from this feature also.

[1] https://www.spinics.net/lists/netdev/msg445392.html

changes in v4:
- fixed memleak in ptp_clock_register()
- undocumented change in cpts_find_ts() moved to separate patch (minor fix)

changes in v3:
- patch 1: added proper error handling in ptp_clock_register.
  minor comments applied.

changes in v2:
- added PTP auxiliary worker to the PHC subsystem

Links
v3: https://www.spinics.net/lists/netdev/msg446058.html
v2: https://www.spinics.net/lists/netdev/msg445859.html
v1: https://www.spinics.net/lists/netdev/msg445387.html
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents bc78d646 a93439cc
...@@ -31,9 +31,18 @@ ...@@ -31,9 +31,18 @@
#include "cpts.h" #include "cpts.h"
#define CPTS_SKB_TX_WORK_TIMEOUT 1 /* jiffies */
struct cpts_skb_cb_data {
unsigned long tmo;
};
#define cpts_read32(c, r) readl_relaxed(&c->reg->r) #define cpts_read32(c, r) readl_relaxed(&c->reg->r)
#define cpts_write32(c, v, r) writel_relaxed(v, &c->reg->r) #define cpts_write32(c, v, r) writel_relaxed(v, &c->reg->r)
static int cpts_match(struct sk_buff *skb, unsigned int ptp_class,
u16 ts_seqid, u8 ts_msgtype);
static int event_expired(struct cpts_event *event) static int event_expired(struct cpts_event *event)
{ {
return time_after(jiffies, event->tmo); return time_after(jiffies, event->tmo);
...@@ -77,6 +86,47 @@ static int cpts_purge_events(struct cpts *cpts) ...@@ -77,6 +86,47 @@ static int cpts_purge_events(struct cpts *cpts)
return removed ? 0 : -1; return removed ? 0 : -1;
} }
static bool cpts_match_tx_ts(struct cpts *cpts, struct cpts_event *event)
{
struct sk_buff *skb, *tmp;
u16 seqid;
u8 mtype;
bool found = false;
mtype = (event->high >> MESSAGE_TYPE_SHIFT) & MESSAGE_TYPE_MASK;
seqid = (event->high >> SEQUENCE_ID_SHIFT) & SEQUENCE_ID_MASK;
/* no need to grab txq.lock as access is always done under cpts->lock */
skb_queue_walk_safe(&cpts->txq, skb, tmp) {
struct skb_shared_hwtstamps ssh;
unsigned int class = ptp_classify_raw(skb);
struct cpts_skb_cb_data *skb_cb =
(struct cpts_skb_cb_data *)skb->cb;
if (cpts_match(skb, class, seqid, mtype)) {
u64 ns = timecounter_cyc2time(&cpts->tc, event->low);
memset(&ssh, 0, sizeof(ssh));
ssh.hwtstamp = ns_to_ktime(ns);
skb_tstamp_tx(skb, &ssh);
found = true;
__skb_unlink(skb, &cpts->txq);
dev_consume_skb_any(skb);
dev_dbg(cpts->dev, "match tx timestamp mtype %u seqid %04x\n",
mtype, seqid);
} else if (time_after(jiffies, skb_cb->tmo)) {
/* timeout any expired skbs over 1s */
dev_dbg(cpts->dev,
"expiring tx timestamp mtype %u seqid %04x\n",
mtype, seqid);
__skb_unlink(skb, &cpts->txq);
dev_consume_skb_any(skb);
}
}
return found;
}
/* /*
* Returns zero if matching event type was found. * Returns zero if matching event type was found.
*/ */
...@@ -101,9 +151,15 @@ static int cpts_fifo_read(struct cpts *cpts, int match) ...@@ -101,9 +151,15 @@ static int cpts_fifo_read(struct cpts *cpts, int match)
event->low = lo; event->low = lo;
type = event_type(event); type = event_type(event);
switch (type) { switch (type) {
case CPTS_EV_TX:
if (cpts_match_tx_ts(cpts, event)) {
/* if the new event matches an existing skb,
* then don't queue it
*/
break;
}
case CPTS_EV_PUSH: case CPTS_EV_PUSH:
case CPTS_EV_RX: case CPTS_EV_RX:
case CPTS_EV_TX:
list_del_init(&event->list); list_del_init(&event->list);
list_add_tail(&event->list, &cpts->events); list_add_tail(&event->list, &cpts->events);
break; break;
...@@ -224,6 +280,24 @@ static int cpts_ptp_enable(struct ptp_clock_info *ptp, ...@@ -224,6 +280,24 @@ static int cpts_ptp_enable(struct ptp_clock_info *ptp,
return -EOPNOTSUPP; return -EOPNOTSUPP;
} }
static long cpts_overflow_check(struct ptp_clock_info *ptp)
{
struct cpts *cpts = container_of(ptp, struct cpts, info);
unsigned long delay = cpts->ov_check_period;
struct timespec64 ts;
unsigned long flags;
spin_lock_irqsave(&cpts->lock, flags);
ts = ns_to_timespec64(timecounter_read(&cpts->tc));
if (!skb_queue_empty(&cpts->txq))
delay = CPTS_SKB_TX_WORK_TIMEOUT;
spin_unlock_irqrestore(&cpts->lock, flags);
pr_debug("cpts overflow check at %lld.%09lu\n", ts.tv_sec, ts.tv_nsec);
return (long)delay;
}
static struct ptp_clock_info cpts_info = { static struct ptp_clock_info cpts_info = {
.owner = THIS_MODULE, .owner = THIS_MODULE,
.name = "CTPS timer", .name = "CTPS timer",
...@@ -236,18 +310,9 @@ static struct ptp_clock_info cpts_info = { ...@@ -236,18 +310,9 @@ static struct ptp_clock_info cpts_info = {
.gettime64 = cpts_ptp_gettime, .gettime64 = cpts_ptp_gettime,
.settime64 = cpts_ptp_settime, .settime64 = cpts_ptp_settime,
.enable = cpts_ptp_enable, .enable = cpts_ptp_enable,
.do_aux_work = cpts_overflow_check,
}; };
static void cpts_overflow_check(struct work_struct *work)
{
struct timespec64 ts;
struct cpts *cpts = container_of(work, struct cpts, overflow_work.work);
cpts_ptp_gettime(&cpts->info, &ts);
pr_debug("cpts overflow check at %lld.%09lu\n", ts.tv_sec, ts.tv_nsec);
schedule_delayed_work(&cpts->overflow_work, cpts->ov_check_period);
}
static int cpts_match(struct sk_buff *skb, unsigned int ptp_class, static int cpts_match(struct sk_buff *skb, unsigned int ptp_class,
u16 ts_seqid, u8 ts_msgtype) u16 ts_seqid, u8 ts_msgtype)
{ {
...@@ -299,7 +364,7 @@ static u64 cpts_find_ts(struct cpts *cpts, struct sk_buff *skb, int ev_type) ...@@ -299,7 +364,7 @@ static u64 cpts_find_ts(struct cpts *cpts, struct sk_buff *skb, int ev_type)
return 0; return 0;
spin_lock_irqsave(&cpts->lock, flags); spin_lock_irqsave(&cpts->lock, flags);
cpts_fifo_read(cpts, CPTS_EV_PUSH); cpts_fifo_read(cpts, -1);
list_for_each_safe(this, next, &cpts->events) { list_for_each_safe(this, next, &cpts->events) {
event = list_entry(this, struct cpts_event, list); event = list_entry(this, struct cpts_event, list);
if (event_expired(event)) { if (event_expired(event)) {
...@@ -317,6 +382,19 @@ static u64 cpts_find_ts(struct cpts *cpts, struct sk_buff *skb, int ev_type) ...@@ -317,6 +382,19 @@ static u64 cpts_find_ts(struct cpts *cpts, struct sk_buff *skb, int ev_type)
break; break;
} }
} }
if (ev_type == CPTS_EV_TX && !ns) {
struct cpts_skb_cb_data *skb_cb =
(struct cpts_skb_cb_data *)skb->cb;
/* Not found, add frame to queue for processing later.
* The periodic FIFO check will handle this.
*/
skb_get(skb);
/* get the timestamp for timeouts */
skb_cb->tmo = jiffies + msecs_to_jiffies(100);
__skb_queue_tail(&cpts->txq, skb);
ptp_schedule_worker(cpts->clock, 0);
}
spin_unlock_irqrestore(&cpts->lock, flags); spin_unlock_irqrestore(&cpts->lock, flags);
return ns; return ns;
...@@ -358,6 +436,7 @@ int cpts_register(struct cpts *cpts) ...@@ -358,6 +436,7 @@ int cpts_register(struct cpts *cpts)
{ {
int err, i; int err, i;
skb_queue_head_init(&cpts->txq);
INIT_LIST_HEAD(&cpts->events); INIT_LIST_HEAD(&cpts->events);
INIT_LIST_HEAD(&cpts->pool); INIT_LIST_HEAD(&cpts->pool);
for (i = 0; i < CPTS_MAX_EVENTS; i++) for (i = 0; i < CPTS_MAX_EVENTS; i++)
...@@ -378,7 +457,7 @@ int cpts_register(struct cpts *cpts) ...@@ -378,7 +457,7 @@ int cpts_register(struct cpts *cpts)
} }
cpts->phc_index = ptp_clock_index(cpts->clock); cpts->phc_index = ptp_clock_index(cpts->clock);
schedule_delayed_work(&cpts->overflow_work, cpts->ov_check_period); ptp_schedule_worker(cpts->clock, cpts->ov_check_period);
return 0; return 0;
err_ptp: err_ptp:
...@@ -392,14 +471,15 @@ void cpts_unregister(struct cpts *cpts) ...@@ -392,14 +471,15 @@ void cpts_unregister(struct cpts *cpts)
if (WARN_ON(!cpts->clock)) if (WARN_ON(!cpts->clock))
return; return;
cancel_delayed_work_sync(&cpts->overflow_work);
ptp_clock_unregister(cpts->clock); ptp_clock_unregister(cpts->clock);
cpts->clock = NULL; cpts->clock = NULL;
cpts_write32(cpts, 0, int_enable); cpts_write32(cpts, 0, int_enable);
cpts_write32(cpts, 0, control); cpts_write32(cpts, 0, control);
/* Drop all packet */
skb_queue_purge(&cpts->txq);
clk_disable(cpts->refclk); clk_disable(cpts->refclk);
} }
EXPORT_SYMBOL_GPL(cpts_unregister); EXPORT_SYMBOL_GPL(cpts_unregister);
...@@ -476,7 +556,6 @@ struct cpts *cpts_create(struct device *dev, void __iomem *regs, ...@@ -476,7 +556,6 @@ struct cpts *cpts_create(struct device *dev, void __iomem *regs,
cpts->dev = dev; cpts->dev = dev;
cpts->reg = (struct cpsw_cpts __iomem *)regs; cpts->reg = (struct cpsw_cpts __iomem *)regs;
spin_lock_init(&cpts->lock); spin_lock_init(&cpts->lock);
INIT_DELAYED_WORK(&cpts->overflow_work, cpts_overflow_check);
ret = cpts_of_parse(cpts, node); ret = cpts_of_parse(cpts, node);
if (ret) if (ret)
......
...@@ -119,13 +119,13 @@ struct cpts { ...@@ -119,13 +119,13 @@ struct cpts {
u32 cc_mult; /* for the nominal frequency */ u32 cc_mult; /* for the nominal frequency */
struct cyclecounter cc; struct cyclecounter cc;
struct timecounter tc; struct timecounter tc;
struct delayed_work overflow_work;
int phc_index; int phc_index;
struct clk *refclk; struct clk *refclk;
struct list_head events; struct list_head events;
struct list_head pool; struct list_head pool;
struct cpts_event pool_data[CPTS_MAX_EVENTS]; struct cpts_event pool_data[CPTS_MAX_EVENTS];
unsigned long ov_check_period; unsigned long ov_check_period;
struct sk_buff_head txq;
}; };
void cpts_rx_timestamp(struct cpts *cpts, struct sk_buff *skb); void cpts_rx_timestamp(struct cpts *cpts, struct sk_buff *skb);
......
...@@ -28,6 +28,7 @@ ...@@ -28,6 +28,7 @@
#include <linux/slab.h> #include <linux/slab.h>
#include <linux/syscalls.h> #include <linux/syscalls.h>
#include <linux/uaccess.h> #include <linux/uaccess.h>
#include <uapi/linux/sched/types.h>
#include "ptp_private.h" #include "ptp_private.h"
...@@ -184,6 +185,19 @@ static void delete_ptp_clock(struct posix_clock *pc) ...@@ -184,6 +185,19 @@ static void delete_ptp_clock(struct posix_clock *pc)
kfree(ptp); kfree(ptp);
} }
static void ptp_aux_kworker(struct kthread_work *work)
{
struct ptp_clock *ptp = container_of(work, struct ptp_clock,
aux_work.work);
struct ptp_clock_info *info = ptp->info;
long delay;
delay = info->do_aux_work(info);
if (delay >= 0)
kthread_queue_delayed_work(ptp->kworker, &ptp->aux_work, delay);
}
/* public interface */ /* public interface */
struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info, struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
...@@ -217,6 +231,20 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info, ...@@ -217,6 +231,20 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
mutex_init(&ptp->pincfg_mux); mutex_init(&ptp->pincfg_mux);
init_waitqueue_head(&ptp->tsev_wq); init_waitqueue_head(&ptp->tsev_wq);
if (ptp->info->do_aux_work) {
char *worker_name = kasprintf(GFP_KERNEL, "ptp%d", ptp->index);
kthread_init_delayed_work(&ptp->aux_work, ptp_aux_kworker);
ptp->kworker = kthread_create_worker(0, worker_name ?
worker_name : info->name);
kfree(worker_name);
if (IS_ERR(ptp->kworker)) {
err = PTR_ERR(ptp->kworker);
pr_err("failed to create ptp aux_worker %d\n", err);
goto kworker_err;
}
}
err = ptp_populate_pin_groups(ptp); err = ptp_populate_pin_groups(ptp);
if (err) if (err)
goto no_pin_groups; goto no_pin_groups;
...@@ -259,6 +287,9 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info, ...@@ -259,6 +287,9 @@ struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
no_device: no_device:
ptp_cleanup_pin_groups(ptp); ptp_cleanup_pin_groups(ptp);
no_pin_groups: no_pin_groups:
if (ptp->kworker)
kthread_destroy_worker(ptp->kworker);
kworker_err:
mutex_destroy(&ptp->tsevq_mux); mutex_destroy(&ptp->tsevq_mux);
mutex_destroy(&ptp->pincfg_mux); mutex_destroy(&ptp->pincfg_mux);
ida_simple_remove(&ptp_clocks_map, index); ida_simple_remove(&ptp_clocks_map, index);
...@@ -274,6 +305,11 @@ int ptp_clock_unregister(struct ptp_clock *ptp) ...@@ -274,6 +305,11 @@ int ptp_clock_unregister(struct ptp_clock *ptp)
ptp->defunct = 1; ptp->defunct = 1;
wake_up_interruptible(&ptp->tsev_wq); wake_up_interruptible(&ptp->tsev_wq);
if (ptp->kworker) {
kthread_cancel_delayed_work_sync(&ptp->aux_work);
kthread_destroy_worker(ptp->kworker);
}
/* Release the clock's resources. */ /* Release the clock's resources. */
if (ptp->pps_source) if (ptp->pps_source)
pps_unregister_source(ptp->pps_source); pps_unregister_source(ptp->pps_source);
...@@ -339,6 +375,12 @@ int ptp_find_pin(struct ptp_clock *ptp, ...@@ -339,6 +375,12 @@ int ptp_find_pin(struct ptp_clock *ptp,
} }
EXPORT_SYMBOL(ptp_find_pin); EXPORT_SYMBOL(ptp_find_pin);
int ptp_schedule_worker(struct ptp_clock *ptp, unsigned long delay)
{
return kthread_mod_delayed_work(ptp->kworker, &ptp->aux_work, delay);
}
EXPORT_SYMBOL(ptp_schedule_worker);
/* module operations */ /* module operations */
static void __exit ptp_exit(void) static void __exit ptp_exit(void)
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include <linux/cdev.h> #include <linux/cdev.h>
#include <linux/device.h> #include <linux/device.h>
#include <linux/kthread.h>
#include <linux/mutex.h> #include <linux/mutex.h>
#include <linux/posix-clock.h> #include <linux/posix-clock.h>
#include <linux/ptp_clock.h> #include <linux/ptp_clock.h>
...@@ -56,6 +57,8 @@ struct ptp_clock { ...@@ -56,6 +57,8 @@ struct ptp_clock {
struct attribute_group pin_attr_group; struct attribute_group pin_attr_group;
/* 1st entry is a pointer to the real group, 2nd is NULL terminator */ /* 1st entry is a pointer to the real group, 2nd is NULL terminator */
const struct attribute_group *pin_attr_groups[2]; const struct attribute_group *pin_attr_groups[2];
struct kthread_worker *kworker;
struct kthread_delayed_work aux_work;
}; };
/* /*
......
...@@ -99,6 +99,11 @@ struct system_device_crosststamp; ...@@ -99,6 +99,11 @@ struct system_device_crosststamp;
* parameter func: the desired function to use. * parameter func: the desired function to use.
* parameter chan: the function channel index to use. * parameter chan: the function channel index to use.
* *
* @do_work: Request driver to perform auxiliary (periodic) operations
* Driver should return delay of the next auxiliary work scheduling
* time (>=0) or negative value in case further scheduling
* is not required.
*
* Drivers should embed their ptp_clock_info within a private * Drivers should embed their ptp_clock_info within a private
* structure, obtaining a reference to it using container_of(). * structure, obtaining a reference to it using container_of().
* *
...@@ -126,6 +131,7 @@ struct ptp_clock_info { ...@@ -126,6 +131,7 @@ struct ptp_clock_info {
struct ptp_clock_request *request, int on); struct ptp_clock_request *request, int on);
int (*verify)(struct ptp_clock_info *ptp, unsigned int pin, int (*verify)(struct ptp_clock_info *ptp, unsigned int pin,
enum ptp_pin_function func, unsigned int chan); enum ptp_pin_function func, unsigned int chan);
long (*do_aux_work)(struct ptp_clock_info *ptp);
}; };
struct ptp_clock; struct ptp_clock;
...@@ -211,6 +217,16 @@ extern int ptp_clock_index(struct ptp_clock *ptp); ...@@ -211,6 +217,16 @@ extern int ptp_clock_index(struct ptp_clock *ptp);
int ptp_find_pin(struct ptp_clock *ptp, int ptp_find_pin(struct ptp_clock *ptp,
enum ptp_pin_function func, unsigned int chan); enum ptp_pin_function func, unsigned int chan);
/**
* ptp_schedule_worker() - schedule ptp auxiliary work
*
* @ptp: The clock obtained from ptp_clock_register().
* @delay: number of jiffies to wait before queuing
* See kthread_queue_delayed_work() for more info.
*/
int ptp_schedule_worker(struct ptp_clock *ptp, unsigned long delay);
#else #else
static inline struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info, static inline struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info,
struct device *parent) struct device *parent)
...@@ -225,6 +241,10 @@ static inline int ptp_clock_index(struct ptp_clock *ptp) ...@@ -225,6 +241,10 @@ static inline int ptp_clock_index(struct ptp_clock *ptp)
static inline int ptp_find_pin(struct ptp_clock *ptp, static inline int ptp_find_pin(struct ptp_clock *ptp,
enum ptp_pin_function func, unsigned int chan) enum ptp_pin_function func, unsigned int chan)
{ return -1; } { return -1; }
static inline int ptp_schedule_worker(struct ptp_clock *ptp,
unsigned long delay)
{ return -EOPNOTSUPP; }
#endif #endif
#endif #endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment