Files
kernel_arpi/drivers/net/ethernet/stmicro/stmmac/stmmac_hwtstamp.c
Kevin Hao e83acf9391 net: stmmac: Use readl_poll_timeout_atomic() in atomic state
[ Upstream commit 234901de2bc6847eaa0aeb4aba62c31ffb8d3ad6 ]

The init_systime() may be invoked in atomic state. We have observed the
following call trace when running "phc_ctl /dev/ptp0 set" on a Intel
Agilex board.
  BUG: sleeping function called from invalid context at drivers/net/ethernet/stmicro/stmmac/stmmac_hwtstamp.c:74
  in_atomic(): 1, irqs_disabled(): 128, non_block: 0, pid: 381, name: phc_ctl
  preempt_count: 1, expected: 0
  RCU nest depth: 0, expected: 0
  Preemption disabled at:
  [<ffff80000892ef78>] stmmac_set_time+0x34/0x8c
  CPU: 2 PID: 381 Comm: phc_ctl Not tainted 5.18.0-rc2-next-20220414-yocto-standard+ #567
  Hardware name: SoCFPGA Agilex SoCDK (DT)
  Call trace:
   dump_backtrace.part.0+0xc4/0xd0
   show_stack+0x24/0x40
   dump_stack_lvl+0x7c/0xa0
   dump_stack+0x18/0x34
   __might_resched+0x154/0x1c0
   __might_sleep+0x58/0x90
   init_systime+0x78/0x120
   stmmac_set_time+0x64/0x8c
   ptp_clock_settime+0x60/0x9c
   pc_clock_settime+0x6c/0xc0
   __arm64_sys_clock_settime+0x88/0xf0
   invoke_syscall+0x5c/0x130
   el0_svc_common.constprop.0+0x4c/0x100
   do_el0_svc+0x7c/0xa0
   el0_svc+0x58/0xcc
   el0t_64_sync_handler+0xa4/0x130
   el0t_64_sync+0x18c/0x190

So we should use readl_poll_timeout_atomic() here instead of
readl_poll_timeout().

Also adjust the delay time to 10us to fix a "__bad_udelay" build error
reported by "kernel test robot <lkp@intel.com>". I have tested this on
Intel Agilex and NXP S32G boards, there is no delay needed at all.
So the 10us delay should be long enough for most cases.

Fixes: ff8ed73786 ("net: stmmac: use readl_poll_timeout() function in init_systime()")
Signed-off-by: Kevin Hao <haokexin@gmail.com>
Signed-off-by: David S. Miller <davem@davemloft.net>
Signed-off-by: Sasha Levin <sashal@kernel.org>
2022-04-27 14:38:54 +02:00

219 lines
5.4 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// SPDX-License-Identifier: GPL-2.0-only
/*******************************************************************************
Copyright (C) 2013 Vayavya Labs Pvt Ltd
This implements all the API for managing HW timestamp & PTP.
Author: Rayagond Kokatanur <rayagond@vayavyalabs.com>
Author: Giuseppe Cavallaro <peppe.cavallaro@st.com>
*******************************************************************************/
#include <linux/io.h>
#include <linux/iopoll.h>
#include <linux/delay.h>
#include <linux/ptp_clock_kernel.h>
#include "common.h"
#include "stmmac_ptp.h"
#include "dwmac4.h"
#include "stmmac.h"
static void config_hw_tstamping(void __iomem *ioaddr, u32 data)
{
writel(data, ioaddr + PTP_TCR);
}
static void config_sub_second_increment(void __iomem *ioaddr,
u32 ptp_clock, int gmac4, u32 *ssinc)
{
u32 value = readl(ioaddr + PTP_TCR);
unsigned long data;
u32 reg_value;
/* For GMAC3.x, 4.x versions, in "fine adjustement mode" set sub-second
* increment to twice the number of nanoseconds of a clock cycle.
* The calculation of the default_addend value by the caller will set it
* to mid-range = 2^31 when the remainder of this division is zero,
* which will make the accumulator overflow once every 2 ptp_clock
* cycles, adding twice the number of nanoseconds of a clock cycle :
* 2000000000ULL / ptp_clock.
*/
if (value & PTP_TCR_TSCFUPDT)
data = (2000000000ULL / ptp_clock);
else
data = (1000000000ULL / ptp_clock);
/* 0.465ns accuracy */
if (!(value & PTP_TCR_TSCTRLSSR))
data = (data * 1000) / 465;
data &= PTP_SSIR_SSINC_MASK;
reg_value = data;
if (gmac4)
reg_value <<= GMAC4_PTP_SSIR_SSINC_SHIFT;
writel(reg_value, ioaddr + PTP_SSIR);
if (ssinc)
*ssinc = data;
}
static int init_systime(void __iomem *ioaddr, u32 sec, u32 nsec)
{
u32 value;
writel(sec, ioaddr + PTP_STSUR);
writel(nsec, ioaddr + PTP_STNSUR);
/* issue command to initialize the system time value */
value = readl(ioaddr + PTP_TCR);
value |= PTP_TCR_TSINIT;
writel(value, ioaddr + PTP_TCR);
/* wait for present system time initialize to complete */
return readl_poll_timeout_atomic(ioaddr + PTP_TCR, value,
!(value & PTP_TCR_TSINIT),
10, 100000);
}
static int config_addend(void __iomem *ioaddr, u32 addend)
{
u32 value;
int limit;
writel(addend, ioaddr + PTP_TAR);
/* issue command to update the addend value */
value = readl(ioaddr + PTP_TCR);
value |= PTP_TCR_TSADDREG;
writel(value, ioaddr + PTP_TCR);
/* wait for present addend update to complete */
limit = 10;
while (limit--) {
if (!(readl(ioaddr + PTP_TCR) & PTP_TCR_TSADDREG))
break;
mdelay(10);
}
if (limit < 0)
return -EBUSY;
return 0;
}
static int adjust_systime(void __iomem *ioaddr, u32 sec, u32 nsec,
int add_sub, int gmac4)
{
u32 value;
int limit;
if (add_sub) {
/* If the new sec value needs to be subtracted with
* the system time, then MAC_STSUR reg should be
* programmed with (2^32 <new_sec_value>)
*/
if (gmac4)
sec = -sec;
value = readl(ioaddr + PTP_TCR);
if (value & PTP_TCR_TSCTRLSSR)
nsec = (PTP_DIGITAL_ROLLOVER_MODE - nsec);
else
nsec = (PTP_BINARY_ROLLOVER_MODE - nsec);
}
writel(sec, ioaddr + PTP_STSUR);
value = (add_sub << PTP_STNSUR_ADDSUB_SHIFT) | nsec;
writel(value, ioaddr + PTP_STNSUR);
/* issue command to initialize the system time value */
value = readl(ioaddr + PTP_TCR);
value |= PTP_TCR_TSUPDT;
writel(value, ioaddr + PTP_TCR);
/* wait for present system time adjust/update to complete */
limit = 10;
while (limit--) {
if (!(readl(ioaddr + PTP_TCR) & PTP_TCR_TSUPDT))
break;
mdelay(10);
}
if (limit < 0)
return -EBUSY;
return 0;
}
static void get_systime(void __iomem *ioaddr, u64 *systime)
{
u64 ns, sec0, sec1;
/* Get the TSS value */
sec1 = readl_relaxed(ioaddr + PTP_STSR);
do {
sec0 = sec1;
/* Get the TSSS value */
ns = readl_relaxed(ioaddr + PTP_STNSR);
/* Get the TSS value */
sec1 = readl_relaxed(ioaddr + PTP_STSR);
} while (sec0 != sec1);
if (systime)
*systime = ns + (sec1 * 1000000000ULL);
}
static void get_ptptime(void __iomem *ptpaddr, u64 *ptp_time)
{
u64 ns;
ns = readl(ptpaddr + PTP_ATNR);
ns += readl(ptpaddr + PTP_ATSR) * NSEC_PER_SEC;
*ptp_time = ns;
}
static void timestamp_interrupt(struct stmmac_priv *priv)
{
u32 num_snapshot, ts_status, tsync_int;
struct ptp_clock_event event;
unsigned long flags;
u64 ptp_time;
int i;
tsync_int = readl(priv->ioaddr + GMAC_INT_STATUS) & GMAC_INT_TSIE;
if (!tsync_int)
return;
/* Read timestamp status to clear interrupt from either external
* timestamp or start/end of PPS.
*/
ts_status = readl(priv->ioaddr + GMAC_TIMESTAMP_STATUS);
if (!priv->plat->ext_snapshot_en)
return;
num_snapshot = (ts_status & GMAC_TIMESTAMP_ATSNS_MASK) >>
GMAC_TIMESTAMP_ATSNS_SHIFT;
for (i = 0; i < num_snapshot; i++) {
spin_lock_irqsave(&priv->ptp_lock, flags);
get_ptptime(priv->ptpaddr, &ptp_time);
spin_unlock_irqrestore(&priv->ptp_lock, flags);
event.type = PTP_CLOCK_EXTTS;
event.index = 0;
event.timestamp = ptp_time;
ptp_clock_event(priv->ptp_clock, &event);
}
}
const struct stmmac_hwtimestamp stmmac_ptp = {
.config_hw_tstamping = config_hw_tstamping,
.init_systime = init_systime,
.config_sub_second_increment = config_sub_second_increment,
.config_addend = config_addend,
.adjust_systime = adjust_systime,
.get_systime = get_systime,
.get_ptptime = get_ptptime,
.timestamp_interrupt = timestamp_interrupt,
};