2019-06-11 18:45:10 +03:00
|
|
|
// SPDX-License-Identifier: BSD-3-Clause OR GPL-2.0
|
|
|
|
|
/* Copyright (c) 2019 Mellanox Technologies. All rights reserved */
|
|
|
|
|
|
|
|
|
|
#include <linux/ptp_clock_kernel.h>
|
|
|
|
|
#include <linux/clocksource.h>
|
|
|
|
|
#include <linux/timecounter.h>
|
|
|
|
|
#include <linux/spinlock.h>
|
|
|
|
|
#include <linux/device.h>
|
2019-06-30 09:04:56 +03:00
|
|
|
#include <linux/rhashtable.h>
|
|
|
|
|
#include <linux/ptp_classify.h>
|
|
|
|
|
#include <linux/if_ether.h>
|
|
|
|
|
#include <linux/if_vlan.h>
|
2019-06-30 09:04:59 +03:00
|
|
|
#include <linux/net_tstamp.h>
|
2019-06-11 18:45:10 +03:00
|
|
|
|
2019-06-30 09:04:54 +03:00
|
|
|
#include "spectrum.h"
|
2019-06-11 18:45:10 +03:00
|
|
|
#include "spectrum_ptp.h"
|
|
|
|
|
#include "core.h"
|
|
|
|
|
|
|
|
|
|
#define MLXSW_SP1_PTP_CLOCK_CYCLES_SHIFT 29
|
|
|
|
|
#define MLXSW_SP1_PTP_CLOCK_FREQ_KHZ 156257 /* 6.4nSec */
|
|
|
|
|
#define MLXSW_SP1_PTP_CLOCK_MASK 64
|
|
|
|
|
|
2019-06-30 09:04:57 +03:00
|
|
|
#define MLXSW_SP1_PTP_HT_GC_INTERVAL 500 /* ms */
|
|
|
|
|
|
|
|
|
|
/* How long, approximately, should the unmatched entries stay in the hash table
|
|
|
|
|
* before they are collected. Should be evenly divisible by the GC interval.
|
|
|
|
|
*/
|
|
|
|
|
#define MLXSW_SP1_PTP_HT_GC_TIMEOUT 1000 /* ms */
|
|
|
|
|
|
2019-06-30 09:04:54 +03:00
|
|
|
struct mlxsw_sp_ptp_state {
|
2019-06-30 09:04:57 +03:00
|
|
|
struct mlxsw_sp *mlxsw_sp;
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
struct rhltable unmatched_ht;
|
2019-06-30 09:04:54 +03:00
|
|
|
spinlock_t unmatched_lock; /* protects the HT */
|
2019-06-30 09:04:57 +03:00
|
|
|
struct delayed_work ht_gc_dw;
|
|
|
|
|
u32 gc_cycle;
|
2019-06-30 09:04:54 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct mlxsw_sp1_ptp_key {
|
|
|
|
|
u8 local_port;
|
|
|
|
|
u8 message_type;
|
|
|
|
|
u16 sequence_id;
|
|
|
|
|
u8 domain_number;
|
|
|
|
|
bool ingress;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
struct mlxsw_sp1_ptp_unmatched {
|
|
|
|
|
struct mlxsw_sp1_ptp_key key;
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
struct rhlist_head ht_node;
|
2019-06-30 09:04:54 +03:00
|
|
|
struct rcu_head rcu;
|
|
|
|
|
struct sk_buff *skb;
|
|
|
|
|
u64 timestamp;
|
2019-06-30 09:04:57 +03:00
|
|
|
u32 gc_cycle;
|
2019-06-30 09:04:54 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const struct rhashtable_params mlxsw_sp1_ptp_unmatched_ht_params = {
|
|
|
|
|
.key_len = sizeof_field(struct mlxsw_sp1_ptp_unmatched, key),
|
|
|
|
|
.key_offset = offsetof(struct mlxsw_sp1_ptp_unmatched, key),
|
|
|
|
|
.head_offset = offsetof(struct mlxsw_sp1_ptp_unmatched, ht_node),
|
|
|
|
|
};
|
|
|
|
|
|
2019-06-11 18:45:10 +03:00
|
|
|
struct mlxsw_sp_ptp_clock {
|
|
|
|
|
struct mlxsw_core *core;
|
|
|
|
|
spinlock_t lock; /* protect this structure */
|
|
|
|
|
struct cyclecounter cycles;
|
|
|
|
|
struct timecounter tc;
|
|
|
|
|
u32 nominal_c_mult;
|
|
|
|
|
struct ptp_clock *ptp;
|
|
|
|
|
struct ptp_clock_info ptp_info;
|
|
|
|
|
unsigned long overflow_period;
|
|
|
|
|
struct delayed_work overflow_work;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static u64 __mlxsw_sp1_ptp_read_frc(struct mlxsw_sp_ptp_clock *clock,
|
|
|
|
|
struct ptp_system_timestamp *sts)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_core *mlxsw_core = clock->core;
|
|
|
|
|
u32 frc_h1, frc_h2, frc_l;
|
|
|
|
|
|
|
|
|
|
frc_h1 = mlxsw_core_read_frc_h(mlxsw_core);
|
|
|
|
|
ptp_read_system_prets(sts);
|
|
|
|
|
frc_l = mlxsw_core_read_frc_l(mlxsw_core);
|
|
|
|
|
ptp_read_system_postts(sts);
|
|
|
|
|
frc_h2 = mlxsw_core_read_frc_h(mlxsw_core);
|
|
|
|
|
|
|
|
|
|
if (frc_h1 != frc_h2) {
|
|
|
|
|
/* wrap around */
|
|
|
|
|
ptp_read_system_prets(sts);
|
|
|
|
|
frc_l = mlxsw_core_read_frc_l(mlxsw_core);
|
|
|
|
|
ptp_read_system_postts(sts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return (u64) frc_l | (u64) frc_h2 << 32;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static u64 mlxsw_sp1_ptp_read_frc(const struct cyclecounter *cc)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_sp_ptp_clock *clock =
|
|
|
|
|
container_of(cc, struct mlxsw_sp_ptp_clock, cycles);
|
|
|
|
|
|
|
|
|
|
return __mlxsw_sp1_ptp_read_frc(clock, NULL) & cc->mask;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
mlxsw_sp1_ptp_phc_adjfreq(struct mlxsw_sp_ptp_clock *clock, int freq_adj)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_core *mlxsw_core = clock->core;
|
|
|
|
|
char mtutc_pl[MLXSW_REG_MTUTC_LEN];
|
|
|
|
|
|
|
|
|
|
mlxsw_reg_mtutc_pack(mtutc_pl, MLXSW_REG_MTUTC_OPERATION_ADJUST_FREQ,
|
|
|
|
|
freq_adj, 0);
|
|
|
|
|
return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mtutc), mtutc_pl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static u64 mlxsw_sp1_ptp_ns2cycles(const struct timecounter *tc, u64 nsec)
|
|
|
|
|
{
|
|
|
|
|
u64 cycles = (u64) nsec;
|
|
|
|
|
|
|
|
|
|
cycles <<= tc->cc->shift;
|
|
|
|
|
cycles = div_u64(cycles, tc->cc->mult);
|
|
|
|
|
|
|
|
|
|
return cycles;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
mlxsw_sp1_ptp_phc_settime(struct mlxsw_sp_ptp_clock *clock, u64 nsec)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_core *mlxsw_core = clock->core;
|
2019-06-18 12:45:35 +00:00
|
|
|
u64 next_sec, next_sec_in_nsec, cycles;
|
2019-06-11 18:45:10 +03:00
|
|
|
char mtutc_pl[MLXSW_REG_MTUTC_LEN];
|
|
|
|
|
char mtpps_pl[MLXSW_REG_MTPPS_LEN];
|
|
|
|
|
int err;
|
|
|
|
|
|
2019-06-18 12:45:35 +00:00
|
|
|
next_sec = div_u64(nsec, NSEC_PER_SEC) + 1;
|
2019-06-11 18:45:10 +03:00
|
|
|
next_sec_in_nsec = next_sec * NSEC_PER_SEC;
|
|
|
|
|
|
2019-06-30 09:04:55 +03:00
|
|
|
spin_lock_bh(&clock->lock);
|
2019-06-11 18:45:10 +03:00
|
|
|
cycles = mlxsw_sp1_ptp_ns2cycles(&clock->tc, next_sec_in_nsec);
|
2019-06-30 09:04:55 +03:00
|
|
|
spin_unlock_bh(&clock->lock);
|
2019-06-11 18:45:10 +03:00
|
|
|
|
|
|
|
|
mlxsw_reg_mtpps_vpin_pack(mtpps_pl, cycles);
|
|
|
|
|
err = mlxsw_reg_write(mlxsw_core, MLXSW_REG(mtpps), mtpps_pl);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
mlxsw_reg_mtutc_pack(mtutc_pl,
|
|
|
|
|
MLXSW_REG_MTUTC_OPERATION_SET_TIME_AT_NEXT_SEC,
|
|
|
|
|
0, next_sec);
|
|
|
|
|
return mlxsw_reg_write(mlxsw_core, MLXSW_REG(mtutc), mtutc_pl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int mlxsw_sp1_ptp_adjfine(struct ptp_clock_info *ptp, long scaled_ppm)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_sp_ptp_clock *clock =
|
|
|
|
|
container_of(ptp, struct mlxsw_sp_ptp_clock, ptp_info);
|
|
|
|
|
int neg_adj = 0;
|
|
|
|
|
u32 diff;
|
|
|
|
|
u64 adj;
|
|
|
|
|
s32 ppb;
|
|
|
|
|
|
|
|
|
|
ppb = scaled_ppm_to_ppb(scaled_ppm);
|
|
|
|
|
|
|
|
|
|
if (ppb < 0) {
|
|
|
|
|
neg_adj = 1;
|
|
|
|
|
ppb = -ppb;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
adj = clock->nominal_c_mult;
|
|
|
|
|
adj *= ppb;
|
|
|
|
|
diff = div_u64(adj, NSEC_PER_SEC);
|
|
|
|
|
|
2019-06-30 09:04:55 +03:00
|
|
|
spin_lock_bh(&clock->lock);
|
2019-06-11 18:45:10 +03:00
|
|
|
timecounter_read(&clock->tc);
|
|
|
|
|
clock->cycles.mult = neg_adj ? clock->nominal_c_mult - diff :
|
|
|
|
|
clock->nominal_c_mult + diff;
|
2019-06-30 09:04:55 +03:00
|
|
|
spin_unlock_bh(&clock->lock);
|
2019-06-11 18:45:10 +03:00
|
|
|
|
|
|
|
|
return mlxsw_sp1_ptp_phc_adjfreq(clock, neg_adj ? -ppb : ppb);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int mlxsw_sp1_ptp_adjtime(struct ptp_clock_info *ptp, s64 delta)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_sp_ptp_clock *clock =
|
|
|
|
|
container_of(ptp, struct mlxsw_sp_ptp_clock, ptp_info);
|
|
|
|
|
u64 nsec;
|
|
|
|
|
|
2019-06-30 09:04:55 +03:00
|
|
|
spin_lock_bh(&clock->lock);
|
2019-06-11 18:45:10 +03:00
|
|
|
timecounter_adjtime(&clock->tc, delta);
|
|
|
|
|
nsec = timecounter_read(&clock->tc);
|
2019-06-30 09:04:55 +03:00
|
|
|
spin_unlock_bh(&clock->lock);
|
2019-06-11 18:45:10 +03:00
|
|
|
|
|
|
|
|
return mlxsw_sp1_ptp_phc_settime(clock, nsec);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int mlxsw_sp1_ptp_gettimex(struct ptp_clock_info *ptp,
|
|
|
|
|
struct timespec64 *ts,
|
|
|
|
|
struct ptp_system_timestamp *sts)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_sp_ptp_clock *clock =
|
|
|
|
|
container_of(ptp, struct mlxsw_sp_ptp_clock, ptp_info);
|
|
|
|
|
u64 cycles, nsec;
|
|
|
|
|
|
2019-06-30 09:04:55 +03:00
|
|
|
spin_lock_bh(&clock->lock);
|
2019-06-11 18:45:10 +03:00
|
|
|
cycles = __mlxsw_sp1_ptp_read_frc(clock, sts);
|
|
|
|
|
nsec = timecounter_cyc2time(&clock->tc, cycles);
|
2019-06-30 09:04:55 +03:00
|
|
|
spin_unlock_bh(&clock->lock);
|
2019-06-11 18:45:10 +03:00
|
|
|
|
|
|
|
|
*ts = ns_to_timespec64(nsec);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int mlxsw_sp1_ptp_settime(struct ptp_clock_info *ptp,
|
|
|
|
|
const struct timespec64 *ts)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_sp_ptp_clock *clock =
|
|
|
|
|
container_of(ptp, struct mlxsw_sp_ptp_clock, ptp_info);
|
|
|
|
|
u64 nsec = timespec64_to_ns(ts);
|
|
|
|
|
|
2019-06-30 09:04:55 +03:00
|
|
|
spin_lock_bh(&clock->lock);
|
2019-06-11 18:45:10 +03:00
|
|
|
timecounter_init(&clock->tc, &clock->cycles, nsec);
|
|
|
|
|
nsec = timecounter_read(&clock->tc);
|
2019-06-30 09:04:55 +03:00
|
|
|
spin_unlock_bh(&clock->lock);
|
2019-06-11 18:45:10 +03:00
|
|
|
|
|
|
|
|
return mlxsw_sp1_ptp_phc_settime(clock, nsec);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct ptp_clock_info mlxsw_sp1_ptp_clock_info = {
|
|
|
|
|
.owner = THIS_MODULE,
|
|
|
|
|
.name = "mlxsw_sp_clock",
|
|
|
|
|
.max_adj = 100000000,
|
|
|
|
|
.adjfine = mlxsw_sp1_ptp_adjfine,
|
|
|
|
|
.adjtime = mlxsw_sp1_ptp_adjtime,
|
|
|
|
|
.gettimex64 = mlxsw_sp1_ptp_gettimex,
|
|
|
|
|
.settime64 = mlxsw_sp1_ptp_settime,
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static void mlxsw_sp1_ptp_clock_overflow(struct work_struct *work)
|
|
|
|
|
{
|
|
|
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
|
|
|
struct mlxsw_sp_ptp_clock *clock;
|
|
|
|
|
|
|
|
|
|
clock = container_of(dwork, struct mlxsw_sp_ptp_clock, overflow_work);
|
|
|
|
|
|
2019-06-30 09:04:55 +03:00
|
|
|
spin_lock_bh(&clock->lock);
|
2019-06-11 18:45:10 +03:00
|
|
|
timecounter_read(&clock->tc);
|
2019-06-30 09:04:55 +03:00
|
|
|
spin_unlock_bh(&clock->lock);
|
2019-06-11 18:45:10 +03:00
|
|
|
mlxsw_core_schedule_dw(&clock->overflow_work, clock->overflow_period);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
struct mlxsw_sp_ptp_clock *
|
|
|
|
|
mlxsw_sp1_ptp_clock_init(struct mlxsw_sp *mlxsw_sp, struct device *dev)
|
|
|
|
|
{
|
|
|
|
|
u64 overflow_cycles, nsec, frac = 0;
|
|
|
|
|
struct mlxsw_sp_ptp_clock *clock;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
clock = kzalloc(sizeof(*clock), GFP_KERNEL);
|
|
|
|
|
if (!clock)
|
|
|
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
|
|
|
|
|
|
spin_lock_init(&clock->lock);
|
|
|
|
|
clock->cycles.read = mlxsw_sp1_ptp_read_frc;
|
|
|
|
|
clock->cycles.shift = MLXSW_SP1_PTP_CLOCK_CYCLES_SHIFT;
|
|
|
|
|
clock->cycles.mult = clocksource_khz2mult(MLXSW_SP1_PTP_CLOCK_FREQ_KHZ,
|
|
|
|
|
clock->cycles.shift);
|
|
|
|
|
clock->nominal_c_mult = clock->cycles.mult;
|
|
|
|
|
clock->cycles.mask = CLOCKSOURCE_MASK(MLXSW_SP1_PTP_CLOCK_MASK);
|
|
|
|
|
clock->core = mlxsw_sp->core;
|
|
|
|
|
|
|
|
|
|
timecounter_init(&clock->tc, &clock->cycles,
|
|
|
|
|
ktime_to_ns(ktime_get_real()));
|
|
|
|
|
|
|
|
|
|
/* Calculate period in seconds to call the overflow watchdog - to make
|
|
|
|
|
* sure counter is checked at least twice every wrap around.
|
|
|
|
|
* The period is calculated as the minimum between max HW cycles count
|
|
|
|
|
* (The clock source mask) and max amount of cycles that can be
|
|
|
|
|
* multiplied by clock multiplier where the result doesn't exceed
|
|
|
|
|
* 64bits.
|
|
|
|
|
*/
|
|
|
|
|
overflow_cycles = div64_u64(~0ULL >> 1, clock->cycles.mult);
|
|
|
|
|
overflow_cycles = min(overflow_cycles, div_u64(clock->cycles.mask, 3));
|
|
|
|
|
|
|
|
|
|
nsec = cyclecounter_cyc2ns(&clock->cycles, overflow_cycles, 0, &frac);
|
|
|
|
|
clock->overflow_period = nsecs_to_jiffies(nsec);
|
|
|
|
|
|
|
|
|
|
INIT_DELAYED_WORK(&clock->overflow_work, mlxsw_sp1_ptp_clock_overflow);
|
|
|
|
|
mlxsw_core_schedule_dw(&clock->overflow_work, 0);
|
|
|
|
|
|
|
|
|
|
clock->ptp_info = mlxsw_sp1_ptp_clock_info;
|
|
|
|
|
clock->ptp = ptp_clock_register(&clock->ptp_info, dev);
|
|
|
|
|
if (IS_ERR(clock->ptp)) {
|
|
|
|
|
err = PTR_ERR(clock->ptp);
|
|
|
|
|
dev_err(dev, "ptp_clock_register failed %d\n", err);
|
|
|
|
|
goto err_ptp_clock_register;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return clock;
|
|
|
|
|
|
|
|
|
|
err_ptp_clock_register:
|
|
|
|
|
cancel_delayed_work_sync(&clock->overflow_work);
|
|
|
|
|
kfree(clock);
|
|
|
|
|
return ERR_PTR(err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mlxsw_sp1_ptp_clock_fini(struct mlxsw_sp_ptp_clock *clock)
|
|
|
|
|
{
|
|
|
|
|
ptp_clock_unregister(clock->ptp);
|
|
|
|
|
cancel_delayed_work_sync(&clock->overflow_work);
|
|
|
|
|
kfree(clock);
|
|
|
|
|
}
|
mlxsw: spectrum: PTP: Hook into packet receive path
When configured, the Spectrum hardware can recognize PTP packets and
trap them to the CPU using dedicated traps, PTP0 and PTP1.
One reason to get PTP packets under dedicated traps is to have a
separate policer suitable for the amount of PTP traffic expected when
switch is operated as a boundary clock. For this, add two new trap
groups, MLXSW_REG_HTGT_TRAP_GROUP_SP_PTP0 and _PTP1, and associate the
two PTP traps with these two groups.
In the driver, specifically for Spectrum-1, event PTP packets will need
to be paired up with their timestamps. Those arrive through a different
set of traps, added later in the patch set. To support this future use,
introduce a new PTP op, ptp_receive.
It is possible to configure which PTP messages should be trapped under
which PTP trap. On Spectrum systems, we will use PTP0 for event
packets (which need timestamping), and PTP1 for control packets (which
do not). Thus configure PTP0 trap with a custom callback that defers to
the ptp_receive op.
Additionally, L2 PTP packets are actually trapped through the LLDP trap,
not through any of the PTP traps. So treat the LLDP trap the same way as
the PTP0 trap. Unlike PTP traps, which are currently still disabled,
LLDP trap is active. Correspondingly, have all the implementations of
the ptp_receive op return true, which the handler treats as a signal to
forward the packet immediately.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-06-30 09:04:51 +03:00
|
|
|
|
2019-06-30 09:04:56 +03:00
|
|
|
static int mlxsw_sp_ptp_parse(struct sk_buff *skb,
|
|
|
|
|
u8 *p_domain_number,
|
|
|
|
|
u8 *p_message_type,
|
|
|
|
|
u16 *p_sequence_id)
|
|
|
|
|
{
|
|
|
|
|
unsigned int ptp_class;
|
2020-08-18 12:32:46 +02:00
|
|
|
struct ptp_header *hdr;
|
2019-06-30 09:04:56 +03:00
|
|
|
|
|
|
|
|
ptp_class = ptp_classify_raw(skb);
|
|
|
|
|
|
|
|
|
|
switch (ptp_class & PTP_CLASS_VMASK) {
|
|
|
|
|
case PTP_CLASS_V1:
|
|
|
|
|
case PTP_CLASS_V2:
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return -ERANGE;
|
|
|
|
|
}
|
|
|
|
|
|
2020-08-18 12:32:46 +02:00
|
|
|
hdr = ptp_parse_header(skb, ptp_class);
|
|
|
|
|
if (!hdr)
|
2019-06-30 09:04:56 +03:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
2020-08-18 12:32:46 +02:00
|
|
|
*p_message_type = ptp_get_msgtype(hdr, ptp_class);
|
|
|
|
|
*p_domain_number = hdr->domain_number;
|
|
|
|
|
*p_sequence_id = be16_to_cpu(hdr->sequence_id);
|
|
|
|
|
|
2019-06-30 09:04:56 +03:00
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Returns NULL on successful insertion, a pointer on conflict, or an ERR_PTR on
|
|
|
|
|
* error.
|
|
|
|
|
*/
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
static int
|
2019-06-30 09:04:56 +03:00
|
|
|
mlxsw_sp1_ptp_unmatched_save(struct mlxsw_sp *mlxsw_sp,
|
|
|
|
|
struct mlxsw_sp1_ptp_key key,
|
|
|
|
|
struct sk_buff *skb,
|
|
|
|
|
u64 timestamp)
|
|
|
|
|
{
|
2019-06-30 09:04:57 +03:00
|
|
|
int cycles = MLXSW_SP1_PTP_HT_GC_TIMEOUT / MLXSW_SP1_PTP_HT_GC_INTERVAL;
|
2019-06-30 09:04:56 +03:00
|
|
|
struct mlxsw_sp_ptp_state *ptp_state = mlxsw_sp->ptp_state;
|
|
|
|
|
struct mlxsw_sp1_ptp_unmatched *unmatched;
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
int err;
|
2019-06-30 09:04:56 +03:00
|
|
|
|
|
|
|
|
unmatched = kzalloc(sizeof(*unmatched), GFP_ATOMIC);
|
|
|
|
|
if (!unmatched)
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
return -ENOMEM;
|
2019-06-30 09:04:56 +03:00
|
|
|
|
|
|
|
|
unmatched->key = key;
|
|
|
|
|
unmatched->skb = skb;
|
|
|
|
|
unmatched->timestamp = timestamp;
|
2019-06-30 09:04:57 +03:00
|
|
|
unmatched->gc_cycle = mlxsw_sp->ptp_state->gc_cycle + cycles;
|
2019-06-30 09:04:56 +03:00
|
|
|
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
err = rhltable_insert(&ptp_state->unmatched_ht, &unmatched->ht_node,
|
|
|
|
|
mlxsw_sp1_ptp_unmatched_ht_params);
|
|
|
|
|
if (err)
|
2019-06-30 09:04:56 +03:00
|
|
|
kfree(unmatched);
|
|
|
|
|
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
return err;
|
2019-06-30 09:04:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static struct mlxsw_sp1_ptp_unmatched *
|
|
|
|
|
mlxsw_sp1_ptp_unmatched_lookup(struct mlxsw_sp *mlxsw_sp,
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
struct mlxsw_sp1_ptp_key key, int *p_length)
|
2019-06-30 09:04:56 +03:00
|
|
|
{
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
struct mlxsw_sp1_ptp_unmatched *unmatched, *last = NULL;
|
|
|
|
|
struct rhlist_head *tmp, *list;
|
|
|
|
|
int length = 0;
|
|
|
|
|
|
|
|
|
|
list = rhltable_lookup(&mlxsw_sp->ptp_state->unmatched_ht, &key,
|
|
|
|
|
mlxsw_sp1_ptp_unmatched_ht_params);
|
|
|
|
|
rhl_for_each_entry_rcu(unmatched, tmp, list, ht_node) {
|
|
|
|
|
last = unmatched;
|
|
|
|
|
length++;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*p_length = length;
|
|
|
|
|
return last;
|
2019-06-30 09:04:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
mlxsw_sp1_ptp_unmatched_remove(struct mlxsw_sp *mlxsw_sp,
|
|
|
|
|
struct mlxsw_sp1_ptp_unmatched *unmatched)
|
|
|
|
|
{
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
return rhltable_remove(&mlxsw_sp->ptp_state->unmatched_ht,
|
|
|
|
|
&unmatched->ht_node,
|
|
|
|
|
mlxsw_sp1_ptp_unmatched_ht_params);
|
2019-06-30 09:04:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/* This function is called in the following scenarios:
|
|
|
|
|
*
|
|
|
|
|
* 1) When a packet is matched with its timestamp.
|
|
|
|
|
* 2) In several situation when it is necessary to immediately pass on
|
|
|
|
|
* an SKB without a timestamp.
|
2019-06-30 09:04:57 +03:00
|
|
|
* 3) From GC indirectly through mlxsw_sp1_ptp_unmatched_finish().
|
|
|
|
|
* This case is similar to 2) above.
|
2019-06-30 09:04:56 +03:00
|
|
|
*/
|
|
|
|
|
static void mlxsw_sp1_ptp_packet_finish(struct mlxsw_sp *mlxsw_sp,
|
|
|
|
|
struct sk_buff *skb, u8 local_port,
|
|
|
|
|
bool ingress,
|
|
|
|
|
struct skb_shared_hwtstamps *hwtstamps)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_sp_port *mlxsw_sp_port;
|
|
|
|
|
|
|
|
|
|
/* Between capturing the packet and finishing it, there is a window of
|
|
|
|
|
* opportunity for the originating port to go away (e.g. due to a
|
|
|
|
|
* split). Also make sure the SKB device reference is still valid.
|
|
|
|
|
*/
|
|
|
|
|
mlxsw_sp_port = mlxsw_sp->ports[local_port];
|
2019-07-02 19:06:47 +00:00
|
|
|
if (!(mlxsw_sp_port && (!skb->dev || skb->dev == mlxsw_sp_port->dev))) {
|
2019-06-30 09:04:56 +03:00
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (ingress) {
|
|
|
|
|
if (hwtstamps)
|
|
|
|
|
*skb_hwtstamps(skb) = *hwtstamps;
|
|
|
|
|
mlxsw_sp_rx_listener_no_mark_func(skb, local_port, mlxsw_sp);
|
|
|
|
|
} else {
|
|
|
|
|
/* skb_tstamp_tx() allows hwtstamps to be NULL. */
|
|
|
|
|
skb_tstamp_tx(skb, hwtstamps);
|
|
|
|
|
dev_kfree_skb_any(skb);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void mlxsw_sp1_packet_timestamp(struct mlxsw_sp *mlxsw_sp,
|
|
|
|
|
struct mlxsw_sp1_ptp_key key,
|
|
|
|
|
struct sk_buff *skb,
|
|
|
|
|
u64 timestamp)
|
|
|
|
|
{
|
|
|
|
|
struct skb_shared_hwtstamps hwtstamps;
|
|
|
|
|
u64 nsec;
|
|
|
|
|
|
|
|
|
|
spin_lock_bh(&mlxsw_sp->clock->lock);
|
|
|
|
|
nsec = timecounter_cyc2time(&mlxsw_sp->clock->tc, timestamp);
|
|
|
|
|
spin_unlock_bh(&mlxsw_sp->clock->lock);
|
|
|
|
|
|
|
|
|
|
hwtstamps.hwtstamp = ns_to_ktime(nsec);
|
|
|
|
|
mlxsw_sp1_ptp_packet_finish(mlxsw_sp, skb,
|
|
|
|
|
key.local_port, key.ingress, &hwtstamps);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
|
mlxsw_sp1_ptp_unmatched_finish(struct mlxsw_sp *mlxsw_sp,
|
|
|
|
|
struct mlxsw_sp1_ptp_unmatched *unmatched)
|
|
|
|
|
{
|
|
|
|
|
if (unmatched->skb && unmatched->timestamp)
|
|
|
|
|
mlxsw_sp1_packet_timestamp(mlxsw_sp, unmatched->key,
|
|
|
|
|
unmatched->skb,
|
|
|
|
|
unmatched->timestamp);
|
|
|
|
|
else if (unmatched->skb)
|
|
|
|
|
mlxsw_sp1_ptp_packet_finish(mlxsw_sp, unmatched->skb,
|
|
|
|
|
unmatched->key.local_port,
|
|
|
|
|
unmatched->key.ingress, NULL);
|
|
|
|
|
kfree_rcu(unmatched, rcu);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 09:04:54 +03:00
|
|
|
static void mlxsw_sp1_ptp_unmatched_free_fn(void *ptr, void *arg)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_sp1_ptp_unmatched *unmatched = ptr;
|
|
|
|
|
|
|
|
|
|
/* This is invoked at a point where the ports are gone already. Nothing
|
|
|
|
|
* to do with whatever is left in the HT but to free it.
|
|
|
|
|
*/
|
|
|
|
|
if (unmatched->skb)
|
|
|
|
|
dev_kfree_skb_any(unmatched->skb);
|
|
|
|
|
kfree_rcu(unmatched, rcu);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 09:04:56 +03:00
|
|
|
static void mlxsw_sp1_ptp_got_piece(struct mlxsw_sp *mlxsw_sp,
|
|
|
|
|
struct mlxsw_sp1_ptp_key key,
|
|
|
|
|
struct sk_buff *skb, u64 timestamp)
|
|
|
|
|
{
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
struct mlxsw_sp1_ptp_unmatched *unmatched;
|
|
|
|
|
int length;
|
2019-06-30 09:04:56 +03:00
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
rcu_read_lock();
|
|
|
|
|
|
|
|
|
|
spin_lock(&mlxsw_sp->ptp_state->unmatched_lock);
|
|
|
|
|
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
unmatched = mlxsw_sp1_ptp_unmatched_lookup(mlxsw_sp, key, &length);
|
2019-06-30 09:04:56 +03:00
|
|
|
if (skb && unmatched && unmatched->timestamp) {
|
|
|
|
|
unmatched->skb = skb;
|
|
|
|
|
} else if (timestamp && unmatched && unmatched->skb) {
|
|
|
|
|
unmatched->timestamp = timestamp;
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
} else {
|
|
|
|
|
/* Either there is no entry to match, or one that is there is
|
|
|
|
|
* incompatible.
|
2019-06-30 09:04:56 +03:00
|
|
|
*/
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
if (length < 100)
|
|
|
|
|
err = mlxsw_sp1_ptp_unmatched_save(mlxsw_sp, key,
|
|
|
|
|
skb, timestamp);
|
|
|
|
|
else
|
|
|
|
|
err = -E2BIG;
|
|
|
|
|
if (err && skb)
|
|
|
|
|
mlxsw_sp1_ptp_packet_finish(mlxsw_sp, skb,
|
|
|
|
|
key.local_port,
|
|
|
|
|
key.ingress, NULL);
|
|
|
|
|
unmatched = NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (unmatched) {
|
|
|
|
|
err = mlxsw_sp1_ptp_unmatched_remove(mlxsw_sp, unmatched);
|
|
|
|
|
WARN_ON_ONCE(err);
|
2019-06-30 09:04:56 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
spin_unlock(&mlxsw_sp->ptp_state->unmatched_lock);
|
|
|
|
|
|
|
|
|
|
if (unmatched)
|
|
|
|
|
mlxsw_sp1_ptp_unmatched_finish(mlxsw_sp, unmatched);
|
|
|
|
|
|
|
|
|
|
rcu_read_unlock();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void mlxsw_sp1_ptp_got_packet(struct mlxsw_sp *mlxsw_sp,
|
|
|
|
|
struct sk_buff *skb, u8 local_port,
|
|
|
|
|
bool ingress)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_sp_port *mlxsw_sp_port;
|
|
|
|
|
struct mlxsw_sp1_ptp_key key;
|
|
|
|
|
u8 types;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
mlxsw_sp_port = mlxsw_sp->ports[local_port];
|
|
|
|
|
if (!mlxsw_sp_port)
|
|
|
|
|
goto immediate;
|
|
|
|
|
|
|
|
|
|
types = ingress ? mlxsw_sp_port->ptp.ing_types :
|
|
|
|
|
mlxsw_sp_port->ptp.egr_types;
|
|
|
|
|
if (!types)
|
|
|
|
|
goto immediate;
|
|
|
|
|
|
|
|
|
|
memset(&key, 0, sizeof(key));
|
|
|
|
|
key.local_port = local_port;
|
|
|
|
|
key.ingress = ingress;
|
|
|
|
|
|
|
|
|
|
err = mlxsw_sp_ptp_parse(skb, &key.domain_number, &key.message_type,
|
|
|
|
|
&key.sequence_id);
|
|
|
|
|
if (err)
|
|
|
|
|
goto immediate;
|
|
|
|
|
|
|
|
|
|
/* For packets whose timestamping was not enabled on this port, don't
|
|
|
|
|
* bother trying to match the timestamp.
|
|
|
|
|
*/
|
|
|
|
|
if (!((1 << key.message_type) & types))
|
|
|
|
|
goto immediate;
|
|
|
|
|
|
|
|
|
|
mlxsw_sp1_ptp_got_piece(mlxsw_sp, key, skb, 0);
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
immediate:
|
|
|
|
|
mlxsw_sp1_ptp_packet_finish(mlxsw_sp, skb, local_port, ingress, NULL);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mlxsw_sp1_ptp_got_timestamp(struct mlxsw_sp *mlxsw_sp, bool ingress,
|
|
|
|
|
u8 local_port, u8 message_type,
|
|
|
|
|
u8 domain_number, u16 sequence_id,
|
|
|
|
|
u64 timestamp)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_sp_port *mlxsw_sp_port;
|
|
|
|
|
struct mlxsw_sp1_ptp_key key;
|
|
|
|
|
u8 types;
|
|
|
|
|
|
|
|
|
|
mlxsw_sp_port = mlxsw_sp->ports[local_port];
|
|
|
|
|
if (!mlxsw_sp_port)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
types = ingress ? mlxsw_sp_port->ptp.ing_types :
|
|
|
|
|
mlxsw_sp_port->ptp.egr_types;
|
|
|
|
|
|
|
|
|
|
/* For message types whose timestamping was not enabled on this port,
|
|
|
|
|
* don't bother with the timestamp.
|
|
|
|
|
*/
|
|
|
|
|
if (!((1 << message_type) & types))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
memset(&key, 0, sizeof(key));
|
|
|
|
|
key.local_port = local_port;
|
|
|
|
|
key.domain_number = domain_number;
|
|
|
|
|
key.message_type = message_type;
|
|
|
|
|
key.sequence_id = sequence_id;
|
|
|
|
|
key.ingress = ingress;
|
|
|
|
|
|
|
|
|
|
mlxsw_sp1_ptp_got_piece(mlxsw_sp, key, NULL, timestamp);
|
|
|
|
|
}
|
|
|
|
|
|
mlxsw: spectrum: PTP: Hook into packet receive path
When configured, the Spectrum hardware can recognize PTP packets and
trap them to the CPU using dedicated traps, PTP0 and PTP1.
One reason to get PTP packets under dedicated traps is to have a
separate policer suitable for the amount of PTP traffic expected when
switch is operated as a boundary clock. For this, add two new trap
groups, MLXSW_REG_HTGT_TRAP_GROUP_SP_PTP0 and _PTP1, and associate the
two PTP traps with these two groups.
In the driver, specifically for Spectrum-1, event PTP packets will need
to be paired up with their timestamps. Those arrive through a different
set of traps, added later in the patch set. To support this future use,
introduce a new PTP op, ptp_receive.
It is possible to configure which PTP messages should be trapped under
which PTP trap. On Spectrum systems, we will use PTP0 for event
packets (which need timestamping), and PTP1 for control packets (which
do not). Thus configure PTP0 trap with a custom callback that defers to
the ptp_receive op.
Additionally, L2 PTP packets are actually trapped through the LLDP trap,
not through any of the PTP traps. So treat the LLDP trap the same way as
the PTP0 trap. Unlike PTP traps, which are currently still disabled,
LLDP trap is active. Correspondingly, have all the implementations of
the ptp_receive op return true, which the handler treats as a signal to
forward the packet immediately.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-06-30 09:04:51 +03:00
|
|
|
void mlxsw_sp1_ptp_receive(struct mlxsw_sp *mlxsw_sp, struct sk_buff *skb,
|
|
|
|
|
u8 local_port)
|
|
|
|
|
{
|
2019-06-30 09:04:56 +03:00
|
|
|
skb_reset_mac_header(skb);
|
|
|
|
|
mlxsw_sp1_ptp_got_packet(mlxsw_sp, skb, local_port, true);
|
mlxsw: spectrum: PTP: Hook into packet receive path
When configured, the Spectrum hardware can recognize PTP packets and
trap them to the CPU using dedicated traps, PTP0 and PTP1.
One reason to get PTP packets under dedicated traps is to have a
separate policer suitable for the amount of PTP traffic expected when
switch is operated as a boundary clock. For this, add two new trap
groups, MLXSW_REG_HTGT_TRAP_GROUP_SP_PTP0 and _PTP1, and associate the
two PTP traps with these two groups.
In the driver, specifically for Spectrum-1, event PTP packets will need
to be paired up with their timestamps. Those arrive through a different
set of traps, added later in the patch set. To support this future use,
introduce a new PTP op, ptp_receive.
It is possible to configure which PTP messages should be trapped under
which PTP trap. On Spectrum systems, we will use PTP0 for event
packets (which need timestamping), and PTP1 for control packets (which
do not). Thus configure PTP0 trap with a custom callback that defers to
the ptp_receive op.
Additionally, L2 PTP packets are actually trapped through the LLDP trap,
not through any of the PTP traps. So treat the LLDP trap the same way as
the PTP0 trap. Unlike PTP traps, which are currently still disabled,
LLDP trap is active. Correspondingly, have all the implementations of
the ptp_receive op return true, which the handler treats as a signal to
forward the packet immediately.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-06-30 09:04:51 +03:00
|
|
|
}
|
mlxsw: pci: PTP: Hook into packet transmit path
On Spectrum-1, timestamps are delivered separately from the packets, and
need to paired up. Therefore, at some point after mlxsw_sp_port_xmit()
is invoked, it is necessary to involve the chip-specific driver code to
allow it to do the necessary bookkeeping and matching.
On Spectrum-2, timestamps are delivered in CQE. For that reason,
position the point of driver involvement into mlxsw_pci_cqe_sdq_handle()
to make it hopefully easier to extend for Spectrum-2 in the future.
To tell the driver what port the packet was sent on, keep tx_info
in SKB control buffer.
Introduce a new driver core interface mlxsw_core_ptp_transmitted(), a
driver callback ptp_transmitted, and a PTP op transmitted. The callee is
responsible for taking care of releasing the SKB passed to the new
interfaces, and correspondingly have the new stub callbacks just call
dev_kfree_skb_any().
Follow-up patches will introduce the actual content into
mlxsw_sp1_ptp_transmitted() in particular.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-06-30 09:04:53 +03:00
|
|
|
|
|
|
|
|
void mlxsw_sp1_ptp_transmitted(struct mlxsw_sp *mlxsw_sp,
|
|
|
|
|
struct sk_buff *skb, u8 local_port)
|
|
|
|
|
{
|
2019-06-30 09:04:56 +03:00
|
|
|
mlxsw_sp1_ptp_got_packet(mlxsw_sp, skb, local_port, false);
|
mlxsw: pci: PTP: Hook into packet transmit path
On Spectrum-1, timestamps are delivered separately from the packets, and
need to paired up. Therefore, at some point after mlxsw_sp_port_xmit()
is invoked, it is necessary to involve the chip-specific driver code to
allow it to do the necessary bookkeeping and matching.
On Spectrum-2, timestamps are delivered in CQE. For that reason,
position the point of driver involvement into mlxsw_pci_cqe_sdq_handle()
to make it hopefully easier to extend for Spectrum-2 in the future.
To tell the driver what port the packet was sent on, keep tx_info
in SKB control buffer.
Introduce a new driver core interface mlxsw_core_ptp_transmitted(), a
driver callback ptp_transmitted, and a PTP op transmitted. The callee is
responsible for taking care of releasing the SKB passed to the new
interfaces, and correspondingly have the new stub callbacks just call
dev_kfree_skb_any().
Follow-up patches will introduce the actual content into
mlxsw_sp1_ptp_transmitted() in particular.
Signed-off-by: Petr Machata <petrm@mellanox.com>
Acked-by: Jiri Pirko <jiri@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-06-30 09:04:53 +03:00
|
|
|
}
|
2019-06-30 09:04:54 +03:00
|
|
|
|
2019-06-30 09:04:57 +03:00
|
|
|
static void
|
|
|
|
|
mlxsw_sp1_ptp_ht_gc_collect(struct mlxsw_sp_ptp_state *ptp_state,
|
|
|
|
|
struct mlxsw_sp1_ptp_unmatched *unmatched)
|
|
|
|
|
{
|
mlxsw: spectrum_ptp: Add counters for GC events
On Spectrum-1, timestamped PTP packets and the corresponding timestamps need to
be kept in caches until both are available, at which point they are matched up
and packets forwarded as appropriate. However, not all packets will ever see
their timestamp, and not all timestamps will ever see their packet. It is
necessary to dispose of such abandoned entries, so a garbage collector was
introduced in commit 5d23e4159772 ("mlxsw: spectrum: PTP: Garbage-collect
unmatched entries").
If these GC events happen often, it is a sign of a problem. However because this
whole mechanism is taking place behind the scenes, there is no direct way to
determine whether garbage collection took place.
Therefore to fix this, on Spectrum-1 only, expose four artificial ethtool
counters for the GC events: GCd timestamps and packets, in TX and RX directions.
Cc: Richard Cochran <richardcochran@gmail.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-28 18:54:37 +03:00
|
|
|
struct mlxsw_sp_ptp_port_dir_stats *stats;
|
|
|
|
|
struct mlxsw_sp_port *mlxsw_sp_port;
|
2019-06-30 09:04:57 +03:00
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
/* If an unmatched entry has an SKB, it has to be handed over to the
|
|
|
|
|
* networking stack. This is usually done from a trap handler, which is
|
|
|
|
|
* invoked in a softirq context. Here we are going to do it in process
|
|
|
|
|
* context. If that were to be interrupted by a softirq, it could cause
|
|
|
|
|
* a deadlock when an attempt is made to take an already-taken lock
|
|
|
|
|
* somewhere along the sending path. Disable softirqs to prevent this.
|
|
|
|
|
*/
|
|
|
|
|
local_bh_disable();
|
|
|
|
|
|
|
|
|
|
spin_lock(&ptp_state->unmatched_lock);
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
err = rhltable_remove(&ptp_state->unmatched_ht, &unmatched->ht_node,
|
|
|
|
|
mlxsw_sp1_ptp_unmatched_ht_params);
|
2019-06-30 09:04:57 +03:00
|
|
|
spin_unlock(&ptp_state->unmatched_lock);
|
|
|
|
|
|
|
|
|
|
if (err)
|
|
|
|
|
/* The packet was matched with timestamp during the walk. */
|
|
|
|
|
goto out;
|
|
|
|
|
|
mlxsw: spectrum_ptp: Add counters for GC events
On Spectrum-1, timestamped PTP packets and the corresponding timestamps need to
be kept in caches until both are available, at which point they are matched up
and packets forwarded as appropriate. However, not all packets will ever see
their timestamp, and not all timestamps will ever see their packet. It is
necessary to dispose of such abandoned entries, so a garbage collector was
introduced in commit 5d23e4159772 ("mlxsw: spectrum: PTP: Garbage-collect
unmatched entries").
If these GC events happen often, it is a sign of a problem. However because this
whole mechanism is taking place behind the scenes, there is no direct way to
determine whether garbage collection took place.
Therefore to fix this, on Spectrum-1 only, expose four artificial ethtool
counters for the GC events: GCd timestamps and packets, in TX and RX directions.
Cc: Richard Cochran <richardcochran@gmail.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-28 18:54:37 +03:00
|
|
|
mlxsw_sp_port = ptp_state->mlxsw_sp->ports[unmatched->key.local_port];
|
|
|
|
|
if (mlxsw_sp_port) {
|
|
|
|
|
stats = unmatched->key.ingress ?
|
|
|
|
|
&mlxsw_sp_port->ptp.stats.rx_gcd :
|
|
|
|
|
&mlxsw_sp_port->ptp.stats.tx_gcd;
|
|
|
|
|
if (unmatched->skb)
|
|
|
|
|
stats->packets++;
|
|
|
|
|
else
|
|
|
|
|
stats->timestamps++;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 09:04:57 +03:00
|
|
|
/* mlxsw_sp1_ptp_unmatched_finish() invokes netif_receive_skb(). While
|
|
|
|
|
* the comment at that function states that it can only be called in
|
|
|
|
|
* soft IRQ context, this pattern of local_bh_disable() +
|
|
|
|
|
* netif_receive_skb(), in process context, is seen elsewhere in the
|
|
|
|
|
* kernel, notably in pktgen.
|
|
|
|
|
*/
|
|
|
|
|
mlxsw_sp1_ptp_unmatched_finish(ptp_state->mlxsw_sp, unmatched);
|
|
|
|
|
|
|
|
|
|
out:
|
|
|
|
|
local_bh_enable();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void mlxsw_sp1_ptp_ht_gc(struct work_struct *work)
|
|
|
|
|
{
|
|
|
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
|
|
|
struct mlxsw_sp1_ptp_unmatched *unmatched;
|
|
|
|
|
struct mlxsw_sp_ptp_state *ptp_state;
|
|
|
|
|
struct rhashtable_iter iter;
|
|
|
|
|
u32 gc_cycle;
|
|
|
|
|
void *obj;
|
|
|
|
|
|
|
|
|
|
ptp_state = container_of(dwork, struct mlxsw_sp_ptp_state, ht_gc_dw);
|
|
|
|
|
gc_cycle = ptp_state->gc_cycle++;
|
|
|
|
|
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
rhltable_walk_enter(&ptp_state->unmatched_ht, &iter);
|
2019-06-30 09:04:57 +03:00
|
|
|
rhashtable_walk_start(&iter);
|
|
|
|
|
while ((obj = rhashtable_walk_next(&iter))) {
|
|
|
|
|
if (IS_ERR(obj))
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
unmatched = obj;
|
|
|
|
|
if (unmatched->gc_cycle <= gc_cycle)
|
|
|
|
|
mlxsw_sp1_ptp_ht_gc_collect(ptp_state, unmatched);
|
|
|
|
|
}
|
|
|
|
|
rhashtable_walk_stop(&iter);
|
|
|
|
|
rhashtable_walk_exit(&iter);
|
|
|
|
|
|
|
|
|
|
mlxsw_core_schedule_dw(&ptp_state->ht_gc_dw,
|
|
|
|
|
MLXSW_SP1_PTP_HT_GC_INTERVAL);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 09:04:58 +03:00
|
|
|
static int mlxsw_sp_ptp_mtptpt_set(struct mlxsw_sp *mlxsw_sp,
|
|
|
|
|
enum mlxsw_reg_mtptpt_trap_id trap_id,
|
|
|
|
|
u16 message_type)
|
|
|
|
|
{
|
|
|
|
|
char mtptpt_pl[MLXSW_REG_MTPTPT_LEN];
|
|
|
|
|
|
|
|
|
|
mlxsw_reg_mtptptp_pack(mtptpt_pl, trap_id, message_type);
|
|
|
|
|
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(mtptpt), mtptpt_pl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int mlxsw_sp1_ptp_set_fifo_clr_on_trap(struct mlxsw_sp *mlxsw_sp,
|
|
|
|
|
bool clr)
|
|
|
|
|
{
|
|
|
|
|
char mogcr_pl[MLXSW_REG_MOGCR_LEN] = {0};
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
err = mlxsw_reg_query(mlxsw_sp->core, MLXSW_REG(mogcr), mogcr_pl);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
mlxsw_reg_mogcr_ptp_iftc_set(mogcr_pl, clr);
|
|
|
|
|
mlxsw_reg_mogcr_ptp_eftc_set(mogcr_pl, clr);
|
|
|
|
|
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(mogcr), mogcr_pl);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 09:04:59 +03:00
|
|
|
static int mlxsw_sp1_ptp_mtpppc_set(struct mlxsw_sp *mlxsw_sp,
|
|
|
|
|
u16 ing_types, u16 egr_types)
|
|
|
|
|
{
|
|
|
|
|
char mtpppc_pl[MLXSW_REG_MTPPPC_LEN];
|
|
|
|
|
|
|
|
|
|
mlxsw_reg_mtpppc_pack(mtpppc_pl, ing_types, egr_types);
|
|
|
|
|
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(mtpppc), mtpppc_pl);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-04 10:07:36 +03:00
|
|
|
struct mlxsw_sp1_ptp_shaper_params {
|
|
|
|
|
u32 ethtool_speed;
|
|
|
|
|
enum mlxsw_reg_qpsc_port_speed port_speed;
|
|
|
|
|
u8 shaper_time_exp;
|
|
|
|
|
u8 shaper_time_mantissa;
|
|
|
|
|
u8 shaper_inc;
|
|
|
|
|
u8 shaper_bs;
|
|
|
|
|
u8 port_to_shaper_credits;
|
|
|
|
|
int ing_timestamp_inc;
|
|
|
|
|
int egr_timestamp_inc;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
static const struct mlxsw_sp1_ptp_shaper_params
|
|
|
|
|
mlxsw_sp1_ptp_shaper_params[] = {
|
2019-07-04 10:07:40 +03:00
|
|
|
{
|
|
|
|
|
.ethtool_speed = SPEED_100,
|
|
|
|
|
.port_speed = MLXSW_REG_QPSC_PORT_SPEED_100M,
|
|
|
|
|
.shaper_time_exp = 4,
|
|
|
|
|
.shaper_time_mantissa = 12,
|
|
|
|
|
.shaper_inc = 9,
|
|
|
|
|
.shaper_bs = 1,
|
|
|
|
|
.port_to_shaper_credits = 1,
|
|
|
|
|
.ing_timestamp_inc = -313,
|
|
|
|
|
.egr_timestamp_inc = 313,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
.ethtool_speed = SPEED_1000,
|
|
|
|
|
.port_speed = MLXSW_REG_QPSC_PORT_SPEED_1G,
|
|
|
|
|
.shaper_time_exp = 0,
|
|
|
|
|
.shaper_time_mantissa = 12,
|
|
|
|
|
.shaper_inc = 6,
|
|
|
|
|
.shaper_bs = 0,
|
|
|
|
|
.port_to_shaper_credits = 1,
|
|
|
|
|
.ing_timestamp_inc = -35,
|
|
|
|
|
.egr_timestamp_inc = 35,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
.ethtool_speed = SPEED_10000,
|
|
|
|
|
.port_speed = MLXSW_REG_QPSC_PORT_SPEED_10G,
|
|
|
|
|
.shaper_time_exp = 0,
|
|
|
|
|
.shaper_time_mantissa = 2,
|
|
|
|
|
.shaper_inc = 14,
|
|
|
|
|
.shaper_bs = 1,
|
|
|
|
|
.port_to_shaper_credits = 1,
|
|
|
|
|
.ing_timestamp_inc = -11,
|
|
|
|
|
.egr_timestamp_inc = 11,
|
|
|
|
|
},
|
|
|
|
|
{
|
|
|
|
|
.ethtool_speed = SPEED_25000,
|
|
|
|
|
.port_speed = MLXSW_REG_QPSC_PORT_SPEED_25G,
|
|
|
|
|
.shaper_time_exp = 0,
|
|
|
|
|
.shaper_time_mantissa = 0,
|
|
|
|
|
.shaper_inc = 11,
|
|
|
|
|
.shaper_bs = 1,
|
|
|
|
|
.port_to_shaper_credits = 1,
|
|
|
|
|
.ing_timestamp_inc = -14,
|
|
|
|
|
.egr_timestamp_inc = 14,
|
|
|
|
|
},
|
2019-07-04 10:07:36 +03:00
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define MLXSW_SP1_PTP_SHAPER_PARAMS_LEN ARRAY_SIZE(mlxsw_sp1_ptp_shaper_params)
|
|
|
|
|
|
|
|
|
|
static int mlxsw_sp1_ptp_shaper_params_set(struct mlxsw_sp *mlxsw_sp)
|
|
|
|
|
{
|
|
|
|
|
const struct mlxsw_sp1_ptp_shaper_params *params;
|
|
|
|
|
char qpsc_pl[MLXSW_REG_QPSC_LEN];
|
|
|
|
|
int i, err;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < MLXSW_SP1_PTP_SHAPER_PARAMS_LEN; i++) {
|
|
|
|
|
params = &mlxsw_sp1_ptp_shaper_params[i];
|
|
|
|
|
mlxsw_reg_qpsc_pack(qpsc_pl, params->port_speed,
|
|
|
|
|
params->shaper_time_exp,
|
|
|
|
|
params->shaper_time_mantissa,
|
|
|
|
|
params->shaper_inc, params->shaper_bs,
|
|
|
|
|
params->port_to_shaper_credits,
|
|
|
|
|
params->ing_timestamp_inc,
|
|
|
|
|
params->egr_timestamp_inc);
|
|
|
|
|
err = mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(qpsc), qpsc_pl);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 09:04:54 +03:00
|
|
|
struct mlxsw_sp_ptp_state *mlxsw_sp1_ptp_init(struct mlxsw_sp *mlxsw_sp)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_sp_ptp_state *ptp_state;
|
2019-06-30 09:04:58 +03:00
|
|
|
u16 message_type;
|
2019-06-30 09:04:54 +03:00
|
|
|
int err;
|
|
|
|
|
|
2019-07-04 10:07:36 +03:00
|
|
|
err = mlxsw_sp1_ptp_shaper_params_set(mlxsw_sp);
|
|
|
|
|
if (err)
|
|
|
|
|
return ERR_PTR(err);
|
|
|
|
|
|
2019-06-30 09:04:54 +03:00
|
|
|
ptp_state = kzalloc(sizeof(*ptp_state), GFP_KERNEL);
|
|
|
|
|
if (!ptp_state)
|
|
|
|
|
return ERR_PTR(-ENOMEM);
|
2019-06-30 09:04:57 +03:00
|
|
|
ptp_state->mlxsw_sp = mlxsw_sp;
|
2019-06-30 09:04:54 +03:00
|
|
|
|
|
|
|
|
spin_lock_init(&ptp_state->unmatched_lock);
|
|
|
|
|
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
err = rhltable_init(&ptp_state->unmatched_ht,
|
|
|
|
|
&mlxsw_sp1_ptp_unmatched_ht_params);
|
2019-06-30 09:04:54 +03:00
|
|
|
if (err)
|
|
|
|
|
goto err_hashtable_init;
|
|
|
|
|
|
2019-06-30 09:04:58 +03:00
|
|
|
/* Delive these message types as PTP0. */
|
2020-11-24 08:44:17 +01:00
|
|
|
message_type = BIT(PTP_MSGTYPE_SYNC) |
|
|
|
|
|
BIT(PTP_MSGTYPE_DELAY_REQ) |
|
|
|
|
|
BIT(PTP_MSGTYPE_PDELAY_REQ) |
|
|
|
|
|
BIT(PTP_MSGTYPE_PDELAY_RESP);
|
2019-06-30 09:04:58 +03:00
|
|
|
err = mlxsw_sp_ptp_mtptpt_set(mlxsw_sp, MLXSW_REG_MTPTPT_TRAP_ID_PTP0,
|
|
|
|
|
message_type);
|
|
|
|
|
if (err)
|
|
|
|
|
goto err_mtptpt_set;
|
|
|
|
|
|
|
|
|
|
/* Everything else is PTP1. */
|
|
|
|
|
message_type = ~message_type;
|
|
|
|
|
err = mlxsw_sp_ptp_mtptpt_set(mlxsw_sp, MLXSW_REG_MTPTPT_TRAP_ID_PTP1,
|
|
|
|
|
message_type);
|
|
|
|
|
if (err)
|
|
|
|
|
goto err_mtptpt1_set;
|
|
|
|
|
|
|
|
|
|
err = mlxsw_sp1_ptp_set_fifo_clr_on_trap(mlxsw_sp, true);
|
|
|
|
|
if (err)
|
|
|
|
|
goto err_fifo_clr;
|
|
|
|
|
|
2019-06-30 09:04:57 +03:00
|
|
|
INIT_DELAYED_WORK(&ptp_state->ht_gc_dw, mlxsw_sp1_ptp_ht_gc);
|
|
|
|
|
mlxsw_core_schedule_dw(&ptp_state->ht_gc_dw,
|
|
|
|
|
MLXSW_SP1_PTP_HT_GC_INTERVAL);
|
2019-06-30 09:04:54 +03:00
|
|
|
return ptp_state;
|
|
|
|
|
|
2019-06-30 09:04:58 +03:00
|
|
|
err_fifo_clr:
|
|
|
|
|
mlxsw_sp_ptp_mtptpt_set(mlxsw_sp, MLXSW_REG_MTPTPT_TRAP_ID_PTP1, 0);
|
|
|
|
|
err_mtptpt1_set:
|
|
|
|
|
mlxsw_sp_ptp_mtptpt_set(mlxsw_sp, MLXSW_REG_MTPTPT_TRAP_ID_PTP0, 0);
|
|
|
|
|
err_mtptpt_set:
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
rhltable_destroy(&ptp_state->unmatched_ht);
|
2019-06-30 09:04:54 +03:00
|
|
|
err_hashtable_init:
|
|
|
|
|
kfree(ptp_state);
|
|
|
|
|
return ERR_PTR(err);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mlxsw_sp1_ptp_fini(struct mlxsw_sp_ptp_state *ptp_state)
|
|
|
|
|
{
|
2019-06-30 09:04:58 +03:00
|
|
|
struct mlxsw_sp *mlxsw_sp = ptp_state->mlxsw_sp;
|
|
|
|
|
|
2019-06-30 09:04:57 +03:00
|
|
|
cancel_delayed_work_sync(&ptp_state->ht_gc_dw);
|
2019-06-30 09:04:59 +03:00
|
|
|
mlxsw_sp1_ptp_mtpppc_set(mlxsw_sp, 0, 0);
|
2019-06-30 09:04:58 +03:00
|
|
|
mlxsw_sp1_ptp_set_fifo_clr_on_trap(mlxsw_sp, false);
|
|
|
|
|
mlxsw_sp_ptp_mtptpt_set(mlxsw_sp, MLXSW_REG_MTPTPT_TRAP_ID_PTP1, 0);
|
|
|
|
|
mlxsw_sp_ptp_mtptpt_set(mlxsw_sp, MLXSW_REG_MTPTPT_TRAP_ID_PTP0, 0);
|
mlxsw: spectrum_ptp: Keep unmatched entries in a linked list
To identify timestamps for matching with their packets, Spectrum-1 uses a
five-tuple of (port, direction, domain number, message type, sequence ID).
If there are several clients from the same domain behind a single port
sending Delay_Req's, the only thing differentiating these packets, as far
as Spectrum-1 is concerned, is the sequence ID. Should sequence IDs between
individual clients be similar, conflicts may arise. That is not a problem
to hardware, which will simply deliver timestamps on a first comes, first
served basis.
However the driver uses a simple hash table to store the unmatched pieces.
When a new conflicting piece arrives, it pushes out the previously stored
one, which if it is a packet, is delivered without timestamp. Later on as
the corresponding timestamps arrive, the first one is mismatched to the
second packet, and the second one is never matched and eventually is GCd.
To correct this issue, instead of using a simple rhashtable, use rhltable
to keep the unmatched entries.
Previously, a found unmatched entry would always be removed from the hash
table. That is not the case anymore--an incompatible entry is left in the
hash table. Therefore removal from the hash table cannot be used to confirm
the validity of the looked-up pointer, instead the lookup would simply need
to be redone. Therefore move it inside the critical section. This
simplifies a lot of the code.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Reported-by: Alex Veber <alexve@mellanox.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-11 10:48:37 +03:00
|
|
|
rhltable_free_and_destroy(&ptp_state->unmatched_ht,
|
|
|
|
|
&mlxsw_sp1_ptp_unmatched_free_fn, NULL);
|
2019-06-30 09:04:54 +03:00
|
|
|
kfree(ptp_state);
|
|
|
|
|
}
|
2019-06-30 09:04:59 +03:00
|
|
|
|
|
|
|
|
int mlxsw_sp1_ptp_hwtstamp_get(struct mlxsw_sp_port *mlxsw_sp_port,
|
|
|
|
|
struct hwtstamp_config *config)
|
|
|
|
|
{
|
|
|
|
|
*config = mlxsw_sp_port->ptp.hwtstamp_config;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int mlxsw_sp_ptp_get_message_types(const struct hwtstamp_config *config,
|
|
|
|
|
u16 *p_ing_types, u16 *p_egr_types,
|
|
|
|
|
enum hwtstamp_rx_filters *p_rx_filter)
|
|
|
|
|
{
|
|
|
|
|
enum hwtstamp_rx_filters rx_filter = config->rx_filter;
|
|
|
|
|
enum hwtstamp_tx_types tx_type = config->tx_type;
|
|
|
|
|
u16 ing_types = 0x00;
|
|
|
|
|
u16 egr_types = 0x00;
|
|
|
|
|
|
|
|
|
|
switch (tx_type) {
|
|
|
|
|
case HWTSTAMP_TX_OFF:
|
|
|
|
|
egr_types = 0x00;
|
|
|
|
|
break;
|
|
|
|
|
case HWTSTAMP_TX_ON:
|
|
|
|
|
egr_types = 0xff;
|
|
|
|
|
break;
|
|
|
|
|
case HWTSTAMP_TX_ONESTEP_SYNC:
|
2019-12-25 18:16:19 -08:00
|
|
|
case HWTSTAMP_TX_ONESTEP_P2P:
|
2019-06-30 09:04:59 +03:00
|
|
|
return -ERANGE;
|
2020-03-30 21:08:20 +03:00
|
|
|
default:
|
|
|
|
|
return -EINVAL;
|
2019-06-30 09:04:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (rx_filter) {
|
|
|
|
|
case HWTSTAMP_FILTER_NONE:
|
|
|
|
|
ing_types = 0x00;
|
|
|
|
|
break;
|
|
|
|
|
case HWTSTAMP_FILTER_PTP_V1_L4_SYNC:
|
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_L4_SYNC:
|
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_L2_SYNC:
|
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_SYNC:
|
|
|
|
|
ing_types = 0x01;
|
|
|
|
|
break;
|
|
|
|
|
case HWTSTAMP_FILTER_PTP_V1_L4_DELAY_REQ:
|
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_L4_DELAY_REQ:
|
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_L2_DELAY_REQ:
|
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_DELAY_REQ:
|
|
|
|
|
ing_types = 0x02;
|
|
|
|
|
break;
|
|
|
|
|
case HWTSTAMP_FILTER_PTP_V1_L4_EVENT:
|
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_L4_EVENT:
|
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_L2_EVENT:
|
|
|
|
|
case HWTSTAMP_FILTER_PTP_V2_EVENT:
|
|
|
|
|
ing_types = 0x0f;
|
|
|
|
|
break;
|
|
|
|
|
case HWTSTAMP_FILTER_ALL:
|
|
|
|
|
ing_types = 0xff;
|
|
|
|
|
break;
|
|
|
|
|
case HWTSTAMP_FILTER_SOME:
|
|
|
|
|
case HWTSTAMP_FILTER_NTP_ALL:
|
|
|
|
|
return -ERANGE;
|
2020-03-30 21:08:20 +03:00
|
|
|
default:
|
|
|
|
|
return -EINVAL;
|
2019-06-30 09:04:59 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
*p_ing_types = ing_types;
|
|
|
|
|
*p_egr_types = egr_types;
|
|
|
|
|
*p_rx_filter = rx_filter;
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int mlxsw_sp1_ptp_mtpppc_update(struct mlxsw_sp_port *mlxsw_sp_port,
|
|
|
|
|
u16 ing_types, u16 egr_types)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
|
|
|
|
|
struct mlxsw_sp_port *tmp;
|
mlxsw: spectrum_ptp: Increase parsing depth when PTP is enabled
Spectrum systems have a configurable limit on how far into the packet they
parse. By default, the limit is 96 bytes.
An IPv6 PTP packet is layered as Ethernet/IPv6/UDP (14+40+8 bytes), and
sequence ID of a PTP event is only available 32 bytes into payload, for a
total of 94 bytes. When an additional 802.1q header is present as
well (such as when ptp4l is running on a VLAN port), the parsing limit is
exceeded. Such packets are not recognized as PTP, and are not timestamped.
Therefore generalize the current VXLAN-specific parsing depth setting to
allow reference-counted requests from other modules as well. Keep it in the
VXLAN module, because the MPRS register also configures UDP destination
port number used for VXLAN, and is thus closely tied to the VXLAN code
anyway.
Then invoke the new interfaces from both VXLAN (in obvious places), as well
as from PTP code, when the (global) timestamping configuration changes from
disabled to enabled or vice versa.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-07-29 18:26:14 +00:00
|
|
|
u16 orig_ing_types = 0;
|
|
|
|
|
u16 orig_egr_types = 0;
|
|
|
|
|
int err;
|
2019-06-30 09:04:59 +03:00
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
/* MTPPPC configures timestamping globally, not per port. Find the
|
|
|
|
|
* configuration that contains all configured timestamping requests.
|
|
|
|
|
*/
|
|
|
|
|
for (i = 1; i < mlxsw_core_max_ports(mlxsw_sp->core); i++) {
|
|
|
|
|
tmp = mlxsw_sp->ports[i];
|
mlxsw: spectrum_ptp: Increase parsing depth when PTP is enabled
Spectrum systems have a configurable limit on how far into the packet they
parse. By default, the limit is 96 bytes.
An IPv6 PTP packet is layered as Ethernet/IPv6/UDP (14+40+8 bytes), and
sequence ID of a PTP event is only available 32 bytes into payload, for a
total of 94 bytes. When an additional 802.1q header is present as
well (such as when ptp4l is running on a VLAN port), the parsing limit is
exceeded. Such packets are not recognized as PTP, and are not timestamped.
Therefore generalize the current VXLAN-specific parsing depth setting to
allow reference-counted requests from other modules as well. Keep it in the
VXLAN module, because the MPRS register also configures UDP destination
port number used for VXLAN, and is thus closely tied to the VXLAN code
anyway.
Then invoke the new interfaces from both VXLAN (in obvious places), as well
as from PTP code, when the (global) timestamping configuration changes from
disabled to enabled or vice versa.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-07-29 18:26:14 +00:00
|
|
|
if (tmp) {
|
|
|
|
|
orig_ing_types |= tmp->ptp.ing_types;
|
|
|
|
|
orig_egr_types |= tmp->ptp.egr_types;
|
|
|
|
|
}
|
2019-06-30 09:04:59 +03:00
|
|
|
if (tmp && tmp != mlxsw_sp_port) {
|
|
|
|
|
ing_types |= tmp->ptp.ing_types;
|
|
|
|
|
egr_types |= tmp->ptp.egr_types;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-30 12:47:52 +01:00
|
|
|
if ((ing_types || egr_types) && !(orig_ing_types || orig_egr_types)) {
|
mlxsw: spectrum_ptp: Increase parsing depth when PTP is enabled
Spectrum systems have a configurable limit on how far into the packet they
parse. By default, the limit is 96 bytes.
An IPv6 PTP packet is layered as Ethernet/IPv6/UDP (14+40+8 bytes), and
sequence ID of a PTP event is only available 32 bytes into payload, for a
total of 94 bytes. When an additional 802.1q header is present as
well (such as when ptp4l is running on a VLAN port), the parsing limit is
exceeded. Such packets are not recognized as PTP, and are not timestamped.
Therefore generalize the current VXLAN-specific parsing depth setting to
allow reference-counted requests from other modules as well. Keep it in the
VXLAN module, because the MPRS register also configures UDP destination
port number used for VXLAN, and is thus closely tied to the VXLAN code
anyway.
Then invoke the new interfaces from both VXLAN (in obvious places), as well
as from PTP code, when the (global) timestamping configuration changes from
disabled to enabled or vice versa.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-07-29 18:26:14 +00:00
|
|
|
err = mlxsw_sp_nve_inc_parsing_depth_get(mlxsw_sp);
|
|
|
|
|
if (err) {
|
|
|
|
|
netdev_err(mlxsw_sp_port->dev, "Failed to increase parsing depth");
|
|
|
|
|
return err;
|
|
|
|
|
}
|
|
|
|
|
}
|
2019-07-30 12:47:52 +01:00
|
|
|
if (!(ing_types || egr_types) && (orig_ing_types || orig_egr_types))
|
mlxsw: spectrum_ptp: Increase parsing depth when PTP is enabled
Spectrum systems have a configurable limit on how far into the packet they
parse. By default, the limit is 96 bytes.
An IPv6 PTP packet is layered as Ethernet/IPv6/UDP (14+40+8 bytes), and
sequence ID of a PTP event is only available 32 bytes into payload, for a
total of 94 bytes. When an additional 802.1q header is present as
well (such as when ptp4l is running on a VLAN port), the parsing limit is
exceeded. Such packets are not recognized as PTP, and are not timestamped.
Therefore generalize the current VXLAN-specific parsing depth setting to
allow reference-counted requests from other modules as well. Keep it in the
VXLAN module, because the MPRS register also configures UDP destination
port number used for VXLAN, and is thus closely tied to the VXLAN code
anyway.
Then invoke the new interfaces from both VXLAN (in obvious places), as well
as from PTP code, when the (global) timestamping configuration changes from
disabled to enabled or vice versa.
Fixes: 8748642751ed ("mlxsw: spectrum: PTP: Support SIOCGHWTSTAMP, SIOCSHWTSTAMP ioctls")
Signed-off-by: Petr Machata <petrm@mellanox.com>
Reviewed-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-07-29 18:26:14 +00:00
|
|
|
mlxsw_sp_nve_inc_parsing_depth_put(mlxsw_sp);
|
|
|
|
|
|
2019-06-30 09:04:59 +03:00
|
|
|
return mlxsw_sp1_ptp_mtpppc_set(mlxsw_sp_port->mlxsw_sp,
|
|
|
|
|
ing_types, egr_types);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-04 10:07:38 +03:00
|
|
|
static bool mlxsw_sp1_ptp_hwtstamp_enabled(struct mlxsw_sp_port *mlxsw_sp_port)
|
|
|
|
|
{
|
|
|
|
|
return mlxsw_sp_port->ptp.ing_types || mlxsw_sp_port->ptp.egr_types;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int
|
|
|
|
|
mlxsw_sp1_ptp_port_shaper_set(struct mlxsw_sp_port *mlxsw_sp_port, bool enable)
|
|
|
|
|
{
|
|
|
|
|
struct mlxsw_sp *mlxsw_sp = mlxsw_sp_port->mlxsw_sp;
|
|
|
|
|
char qeec_pl[MLXSW_REG_QEEC_LEN];
|
|
|
|
|
|
|
|
|
|
mlxsw_reg_qeec_ptps_pack(qeec_pl, mlxsw_sp_port->local_port, enable);
|
|
|
|
|
return mlxsw_reg_write(mlxsw_sp->core, MLXSW_REG(qeec), qeec_pl);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static int mlxsw_sp1_ptp_port_shaper_check(struct mlxsw_sp_port *mlxsw_sp_port)
|
|
|
|
|
{
|
|
|
|
|
bool ptps = false;
|
|
|
|
|
int err, i;
|
2020-01-20 09:52:50 +02:00
|
|
|
u32 speed;
|
2019-07-04 10:07:38 +03:00
|
|
|
|
|
|
|
|
if (!mlxsw_sp1_ptp_hwtstamp_enabled(mlxsw_sp_port))
|
|
|
|
|
return mlxsw_sp1_ptp_port_shaper_set(mlxsw_sp_port, false);
|
|
|
|
|
|
2020-01-20 09:52:50 +02:00
|
|
|
err = mlxsw_sp_port_speed_get(mlxsw_sp_port, &speed);
|
2019-07-04 10:07:38 +03:00
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < MLXSW_SP1_PTP_SHAPER_PARAMS_LEN; i++) {
|
|
|
|
|
if (mlxsw_sp1_ptp_shaper_params[i].ethtool_speed == speed) {
|
|
|
|
|
ptps = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return mlxsw_sp1_ptp_port_shaper_set(mlxsw_sp_port, ptps);
|
|
|
|
|
}
|
|
|
|
|
|
2019-07-04 10:07:39 +03:00
|
|
|
void mlxsw_sp1_ptp_shaper_work(struct work_struct *work)
|
|
|
|
|
{
|
|
|
|
|
struct delayed_work *dwork = to_delayed_work(work);
|
|
|
|
|
struct mlxsw_sp_port *mlxsw_sp_port;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
mlxsw_sp_port = container_of(dwork, struct mlxsw_sp_port,
|
|
|
|
|
ptp.shaper_dw);
|
|
|
|
|
|
|
|
|
|
if (!mlxsw_sp1_ptp_hwtstamp_enabled(mlxsw_sp_port))
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
err = mlxsw_sp1_ptp_port_shaper_check(mlxsw_sp_port);
|
|
|
|
|
if (err)
|
|
|
|
|
netdev_err(mlxsw_sp_port->dev, "Failed to set up PTP shaper\n");
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-30 09:04:59 +03:00
|
|
|
int mlxsw_sp1_ptp_hwtstamp_set(struct mlxsw_sp_port *mlxsw_sp_port,
|
|
|
|
|
struct hwtstamp_config *config)
|
|
|
|
|
{
|
|
|
|
|
enum hwtstamp_rx_filters rx_filter;
|
|
|
|
|
u16 ing_types;
|
|
|
|
|
u16 egr_types;
|
|
|
|
|
int err;
|
|
|
|
|
|
|
|
|
|
err = mlxsw_sp_ptp_get_message_types(config, &ing_types, &egr_types,
|
|
|
|
|
&rx_filter);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
err = mlxsw_sp1_ptp_mtpppc_update(mlxsw_sp_port, ing_types, egr_types);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
|
|
|
|
|
mlxsw_sp_port->ptp.hwtstamp_config = *config;
|
|
|
|
|
mlxsw_sp_port->ptp.ing_types = ing_types;
|
|
|
|
|
mlxsw_sp_port->ptp.egr_types = egr_types;
|
|
|
|
|
|
2019-07-04 10:07:38 +03:00
|
|
|
err = mlxsw_sp1_ptp_port_shaper_check(mlxsw_sp_port);
|
|
|
|
|
if (err)
|
|
|
|
|
return err;
|
|
|
|
|
|
2019-06-30 09:04:59 +03:00
|
|
|
/* Notify the ioctl caller what we are actually timestamping. */
|
|
|
|
|
config->rx_filter = rx_filter;
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
2019-06-30 09:05:00 +03:00
|
|
|
|
|
|
|
|
int mlxsw_sp1_ptp_get_ts_info(struct mlxsw_sp *mlxsw_sp,
|
|
|
|
|
struct ethtool_ts_info *info)
|
|
|
|
|
{
|
|
|
|
|
info->phc_index = ptp_clock_index(mlxsw_sp->clock->ptp);
|
|
|
|
|
|
|
|
|
|
info->so_timestamping = SOF_TIMESTAMPING_TX_HARDWARE |
|
|
|
|
|
SOF_TIMESTAMPING_RX_HARDWARE |
|
|
|
|
|
SOF_TIMESTAMPING_RAW_HARDWARE;
|
|
|
|
|
|
|
|
|
|
info->tx_types = BIT(HWTSTAMP_TX_OFF) |
|
|
|
|
|
BIT(HWTSTAMP_TX_ON);
|
|
|
|
|
|
|
|
|
|
info->rx_filters = BIT(HWTSTAMP_FILTER_NONE) |
|
|
|
|
|
BIT(HWTSTAMP_FILTER_ALL);
|
|
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
mlxsw: spectrum_ptp: Add counters for GC events
On Spectrum-1, timestamped PTP packets and the corresponding timestamps need to
be kept in caches until both are available, at which point they are matched up
and packets forwarded as appropriate. However, not all packets will ever see
their timestamp, and not all timestamps will ever see their packet. It is
necessary to dispose of such abandoned entries, so a garbage collector was
introduced in commit 5d23e4159772 ("mlxsw: spectrum: PTP: Garbage-collect
unmatched entries").
If these GC events happen often, it is a sign of a problem. However because this
whole mechanism is taking place behind the scenes, there is no direct way to
determine whether garbage collection took place.
Therefore to fix this, on Spectrum-1 only, expose four artificial ethtool
counters for the GC events: GCd timestamps and packets, in TX and RX directions.
Cc: Richard Cochran <richardcochran@gmail.com>
Signed-off-by: Petr Machata <petrm@mellanox.com>
Signed-off-by: Ido Schimmel <idosch@mellanox.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
2019-08-28 18:54:37 +03:00
|
|
|
|
|
|
|
|
struct mlxsw_sp_ptp_port_stat {
|
|
|
|
|
char str[ETH_GSTRING_LEN];
|
|
|
|
|
ptrdiff_t offset;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#define MLXSW_SP_PTP_PORT_STAT(NAME, FIELD) \
|
|
|
|
|
{ \
|
|
|
|
|
.str = NAME, \
|
|
|
|
|
.offset = offsetof(struct mlxsw_sp_ptp_port_stats, \
|
|
|
|
|
FIELD), \
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static const struct mlxsw_sp_ptp_port_stat mlxsw_sp_ptp_port_stats[] = {
|
|
|
|
|
MLXSW_SP_PTP_PORT_STAT("ptp_rx_gcd_packets", rx_gcd.packets),
|
|
|
|
|
MLXSW_SP_PTP_PORT_STAT("ptp_rx_gcd_timestamps", rx_gcd.timestamps),
|
|
|
|
|
MLXSW_SP_PTP_PORT_STAT("ptp_tx_gcd_packets", tx_gcd.packets),
|
|
|
|
|
MLXSW_SP_PTP_PORT_STAT("ptp_tx_gcd_timestamps", tx_gcd.timestamps),
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
#undef MLXSW_SP_PTP_PORT_STAT
|
|
|
|
|
|
|
|
|
|
#define MLXSW_SP_PTP_PORT_STATS_LEN \
|
|
|
|
|
ARRAY_SIZE(mlxsw_sp_ptp_port_stats)
|
|
|
|
|
|
|
|
|
|
int mlxsw_sp1_get_stats_count(void)
|
|
|
|
|
{
|
|
|
|
|
return MLXSW_SP_PTP_PORT_STATS_LEN;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mlxsw_sp1_get_stats_strings(u8 **p)
|
|
|
|
|
{
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
for (i = 0; i < MLXSW_SP_PTP_PORT_STATS_LEN; i++) {
|
|
|
|
|
memcpy(*p, mlxsw_sp_ptp_port_stats[i].str,
|
|
|
|
|
ETH_GSTRING_LEN);
|
|
|
|
|
*p += ETH_GSTRING_LEN;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void mlxsw_sp1_get_stats(struct mlxsw_sp_port *mlxsw_sp_port,
|
|
|
|
|
u64 *data, int data_index)
|
|
|
|
|
{
|
|
|
|
|
void *stats = &mlxsw_sp_port->ptp.stats;
|
|
|
|
|
ptrdiff_t offset;
|
|
|
|
|
int i;
|
|
|
|
|
|
|
|
|
|
data += data_index;
|
|
|
|
|
for (i = 0; i < MLXSW_SP_PTP_PORT_STATS_LEN; i++) {
|
|
|
|
|
offset = mlxsw_sp_ptp_port_stats[i].offset;
|
|
|
|
|
*data++ = *(u64 *)(stats + offset);
|
|
|
|
|
}
|
|
|
|
|
}
|