[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <61bc6921-0e87-4cf2-852e-b29a29c4f949@linux.dev>
Date: Mon, 6 Jan 2025 10:36:54 +0000
From: Vadim Fedorenko <vadim.fedorenko@...ux.dev>
To: Jiawen Wu <jiawenwu@...stnetic.com>, andrew+netdev@...n.ch,
davem@...emloft.net, edumazet@...gle.com, kuba@...nel.org,
pabeni@...hat.com, richardcochran@...il.com, linux@...linux.org.uk,
horms@...nel.org, jacob.e.keller@...el.com, netdev@...r.kernel.org
Cc: mengyuanlou@...-swift.com
Subject: Re: [PATCH net-next v2 3/4] net: wangxun: Implement do_aux_work of
ptp_clock_info
On 06/01/2025 08:45, Jiawen Wu wrote:
> Implement watchdog task to detect SYSTIME overflow and error cases of
> Rx/Tx timestamp.
>
> Signed-off-by: Jiawen Wu <jiawenwu@...stnetic.com>
> ---
> drivers/net/ethernet/wangxun/libwx/wx_ptp.c | 211 ++++++++++++++++++
> drivers/net/ethernet/wangxun/libwx/wx_type.h | 2 +
> drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c | 1 +
> .../net/ethernet/wangxun/txgbe/txgbe_phy.c | 1 +
> 4 files changed, 215 insertions(+)
>
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> index 0f683e576b29..0071ba929738 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_ptp.c
> @@ -255,6 +255,215 @@ static void wx_ptp_tx_hwtstamp_work(struct work_struct *work)
> }
> }
>
> +/**
> + * wx_ptp_overflow_check - watchdog task to detect SYSTIME overflow
> + * @wx: pointer to wx struct
> + *
> + * this watchdog task periodically reads the timecounter
> + * in order to prevent missing when the system time registers wrap
> + * around. This needs to be run approximately twice a minute for the fastest
> + * overflowing hardware. We run it for all hardware since it shouldn't have a
> + * large impact.
> + */
> +static void wx_ptp_overflow_check(struct wx *wx)
> +{
> + bool timeout = time_is_before_jiffies(wx->last_overflow_check +
> + WX_OVERFLOW_PERIOD);
> + unsigned long flags;
> +
> + if (timeout) {
> + /* Update the timecounter */
> + spin_lock_irqsave(&wx->tmreg_lock, flags);
> + timecounter_read(&wx->hw_tc);
> + spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +
> + wx->last_overflow_check = jiffies;
> + }
> +}
> +
> +/**
> + * wx_ptp_rx_hang - detect error case when Rx timestamp registers latched
> + * @wx: pointer to wx struct
> + *
> + * this watchdog task is scheduled to detect error case where hardware has
> + * dropped an Rx packet that was timestamped when the ring is full. The
> + * particular error is rare but leaves the device in a state unable to
> + * timestamp any future packets.
> + */
> +static void wx_ptp_rx_hang(struct wx *wx)
> +{
> + struct wx_ring *rx_ring;
> + unsigned long rx_event;
> + u32 tsyncrxctl;
> + int n;
> +
> + tsyncrxctl = rd32(wx, WX_PSR_1588_CTL);
> +
> + /* if we don't have a valid timestamp in the registers, just update the
> + * timeout counter and exit
> + */
> + if (!(tsyncrxctl & WX_PSR_1588_CTL_VALID)) {
> + wx->last_rx_ptp_check = jiffies;
> + return;
> + }
> +
> + /* determine the most recent watchdog or rx_timestamp event */
> + rx_event = wx->last_rx_ptp_check;
> + for (n = 0; n < wx->num_rx_queues; n++) {
> + rx_ring = wx->rx_ring[n];
> + if (time_after(rx_ring->last_rx_timestamp, rx_event))
> + rx_event = rx_ring->last_rx_timestamp;
> + }
> +
> + /* only need to read the high RXSTMP register to clear the lock */
> + if (time_is_before_jiffies(rx_event + 5 * HZ)) {
> + rd32(wx, WX_PSR_1588_STMPH);
> + wx->last_rx_ptp_check = jiffies;
> +
> + wx->rx_hwtstamp_cleared++;
> + dev_warn(&wx->pdev->dev, "clearing RX Timestamp hang");
> + }
> +}
> +
> +/**
> + * wx_ptp_tx_hang - detect error case where Tx timestamp never finishes
> + * @wx: private network wx structure
> + */
> +static void wx_ptp_tx_hang(struct wx *wx)
> +{
> + bool timeout = time_is_before_jiffies(wx->ptp_tx_start +
> + WX_PTP_TX_TIMEOUT);
> +
> + if (!wx->ptp_tx_skb)
> + return;
> +
> + if (!test_bit(WX_STATE_PTP_TX_IN_PROGRESS, wx->state))
> + return;
> +
> + /* If we haven't received a timestamp within the timeout, it is
> + * reasonable to assume that it will never occur, so we can unlock the
> + * timestamp bit when this occurs.
> + */
> + if (timeout) {
> + cancel_work_sync(&wx->ptp_tx_work);
> + wx_ptp_clear_tx_timestamp(wx);
> + wx->tx_hwtstamp_timeouts++;
> + dev_warn(&wx->pdev->dev, "clearing Tx timestamp hang\n");
> + }
> +}
> +
> +static long wx_ptp_do_aux_work(struct ptp_clock_info *ptp)
> +{
> + struct wx *wx = container_of(ptp, struct wx, ptp_caps);
> +
> + wx_ptp_overflow_check(wx);
> + if (unlikely(test_bit(WX_FLAG_RX_HWTSTAMP_IN_REGISTER,
> + wx->flags)))
> + wx_ptp_rx_hang(wx);
> + wx_ptp_tx_hang(wx);
> +
> + return 0;
This means do_aux_work will run only once. If it's not expected
behavior, you have to return delay value to requeue work.
> +}
> +
> +/**
> + * wx_ptp_feature_enable
> + * @ptp: the ptp clock structure
> + * @rq: the requested feature to change
> + * @on: whether to enable or disable the feature
> + *
> + * enable (or disable) ancillary features of the phc subsystem.
> + */
> +static int wx_ptp_feature_enable(struct ptp_clock_info *ptp,
> + struct ptp_clock_request *rq, int on)
> +{
> + struct wx *wx = container_of(ptp, struct wx, ptp_caps);
> +
> + /**
> + * When PPS is enabled, unmask the interrupt for the ClockOut
> + * feature, so that the interrupt handler can send the PPS
> + * event when the clock SDP triggers. Clear mask when PPS is
> + * disabled
> + */
> + if (rq->type != PTP_CLK_REQ_PPS || !wx->ptp_setup_sdp)
> + return -EOPNOTSUPP;
> +
> + if (on)
> + set_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
> + else
> + clear_bit(WX_FLAG_PTP_PPS_ENABLED, wx->flags);
> +
> + wx->ptp_setup_sdp(wx);
> +
> + return 0;
> +}
> +
> +/**
> + * wx_ptp_check_pps_event
> + * @wx: the private wx structure
> + *
> + * This function is called by the interrupt routine when checking for
> + * interrupts. It will check and handle a pps event.
> + */
> +void wx_ptp_check_pps_event(struct wx *wx)
> +{
> + struct cyclecounter *cc = &wx->hw_cc;
> + u32 tsauxc, rem, int_status;
> + u32 trgttiml0, trgttimh0;
> + u32 trgttiml1, trgttimh1;
> + unsigned long flags;
> + u64 ns = 0;
> +
> + /* this check is necessary in case the interrupt was enabled via some
> + * alternative means (ex. debug_fs). Better to check here than
> + * everywhere that calls this function.
> + */
> + if (!wx->ptp_clock)
> + return;
> +
> + int_status = rd32ptp(wx, WX_TSC_1588_INT_ST);
> + if (int_status & WX_TSC_1588_INT_ST_TT1) {
> + /* disable the pin first */
> + wr32ptp(wx, WX_TSC_1588_AUX_CTL, 0);
> + WX_WRITE_FLUSH(wx);
> +
> + tsauxc = WX_TSC_1588_AUX_CTL_PLSG | WX_TSC_1588_AUX_CTL_EN_TT0 |
> + WX_TSC_1588_AUX_CTL_EN_TT1 | WX_TSC_1588_AUX_CTL_EN_TS0;
> +
> + /* Read the current clock time, and save the cycle counter value */
> + spin_lock_irqsave(&wx->tmreg_lock, flags);
> + ns = timecounter_read(&wx->hw_tc);
> + wx->pps_edge_start = wx->hw_tc.cycle_last;
> + spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> + wx->pps_edge_end = wx->pps_edge_start;
> +
> + /* Figure out how far past the next second we are */
> + div_u64_rem(ns, WX_NS_PER_SEC, &rem);
> +
> + /* Figure out how many nanoseconds to add to round the clock edge up
> + * to the next full second
> + */
> + rem = (WX_NS_PER_SEC - rem);
> +
> + /* Adjust the clock edge to align with the next full second. */
> + wx->pps_edge_start += div_u64(((u64)rem << cc->shift), cc->mult);
> + trgttiml0 = (u32)wx->pps_edge_start;
> + trgttimh0 = (u32)(wx->pps_edge_start >> 32);
> +
> + rem += WX_1588_PPS_WIDTH_EM * WX_NS_PER_MSEC;
> + wx->pps_edge_end += div_u64(((u64)rem << cc->shift), cc->mult);
> + trgttiml1 = (u32)wx->pps_edge_end;
> + trgttimh1 = (u32)(wx->pps_edge_end >> 32);
> +
> + wr32ptp(wx, WX_TSC_1588_TRGT_L(0), trgttiml0);
> + wr32ptp(wx, WX_TSC_1588_TRGT_H(0), trgttimh0);
> + wr32ptp(wx, WX_TSC_1588_TRGT_L(1), trgttiml1);
> + wr32ptp(wx, WX_TSC_1588_TRGT_H(1), trgttimh1);
> + wr32ptp(wx, WX_TSC_1588_AUX_CTL, tsauxc);
> + WX_WRITE_FLUSH(wx);
> + }
> +}
> +EXPORT_SYMBOL(wx_ptp_check_pps_event);
> +
> /**
> * wx_ptp_create_clock
> * @wx: the private board structure
> @@ -573,6 +782,8 @@ void wx_ptp_reset(struct wx *wx)
> timecounter_init(&wx->hw_tc, &wx->hw_cc,
> ktime_to_ns(ktime_get_real()));
> spin_unlock_irqrestore(&wx->tmreg_lock, flags);
> +
> + wx->last_overflow_check = jiffies;
> }
> EXPORT_SYMBOL(wx_ptp_reset);
>
> diff --git a/drivers/net/ethernet/wangxun/libwx/wx_type.h b/drivers/net/ethernet/wangxun/libwx/wx_type.h
> index 31b11dba6bf5..1f9ddddea191 100644
> --- a/drivers/net/ethernet/wangxun/libwx/wx_type.h
> +++ b/drivers/net/ethernet/wangxun/libwx/wx_type.h
> @@ -1173,6 +1173,8 @@ struct wx {
> u32 tx_hwtstamp_timeouts;
> u32 tx_hwtstamp_skipped;
> u32 rx_hwtstamp_cleared;
> + unsigned long last_overflow_check;
> + unsigned long last_rx_ptp_check;
> unsigned long ptp_tx_start;
> spinlock_t tmreg_lock; /* spinlock for ptp */
> struct cyclecounter hw_cc;
> diff --git a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
> index c7944e62838a..ea1d7e9a91f3 100644
> --- a/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
> +++ b/drivers/net/ethernet/wangxun/ngbe/ngbe_mdio.c
> @@ -111,6 +111,7 @@ static void ngbe_mac_link_up(struct phylink_config *config,
> wr32(wx, WX_MAC_WDG_TIMEOUT, reg);
>
> wx->speed = speed;
> + wx->last_rx_ptp_check = jiffies;
> if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> wx_ptp_reset_cyclecounter(wx);
> }
> diff --git a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
> index 60e5f3288ad8..7e17d727c2ba 100644
> --- a/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
> +++ b/drivers/net/ethernet/wangxun/txgbe/txgbe_phy.c
> @@ -222,6 +222,7 @@ static void txgbe_mac_link_up(struct phylink_config *config,
> wr32(wx, WX_MAC_WDG_TIMEOUT, wdg);
>
> wx->speed = speed;
> + wx->last_rx_ptp_check = jiffies;
> if (test_bit(WX_STATE_PTP_RUNNING, wx->state))
> wx_ptp_reset_cyclecounter(wx);
> }
Powered by blists - more mailing lists