Commit 58775a73 authored by Alessandro Rubini's avatar Alessandro Rubini

Merge branch 'wr-clocksource'

parents d1f405a9 1065f286
......@@ -748,6 +748,15 @@ Currently, the package includes the following modules:
@item @i{wr_rtu.ko}: the routing-table interface between the
switching core and the associated user-space daemon.
@item @i{wr_pstats.ko}: exports per-port statistics to /proc/sys.
@item @i{wr_clocksource.ko}: uses WR time as a source for system time.
This driver uses the @sc{wr} counters to make host time flow at the
right speed. The system time is moved to the current value, with an
error not bigger than one second, as soon as @i{ppsi} synchronizes,
and the clocksource grants it won't move ever after, keeping the same
offset. This is considered acceptable, because system time is only
used for logging.
@item @i{at91_softpwm.ko}: a driver that generates a PWM signal for the fan.
@end itemize
......
......@@ -159,16 +159,6 @@ like increasing @t{NR_IRQ} and exporting symbols for externally-loaded
the counters are. This would make the @sc{snmp} code generic, so
no change there would be needed if and when the counters change.
@item We need RTC driver to use WR time from FPGA as a system clock in Linux
running on WR Switch (or to synchronize the system clock). Setting initial time
from NTP is good for Grand Master in the network, but then we should keep all
system clocks in sync with WR timescale. It will become important when people
start gathering logs and SNMP info on a central server and would like to
correlate logs if something bad happens. Let's keep in mind that currently
unplugging fiber from the Slave port resets WR counters (i.e. current time
00:00 01/01/1970). System clock should not follow this, we need to keep
reporting failures timestamped with correct time.
@end itemize
@c ##########################################################################
......
DIRS = wr_vic wr_nic wr_rtu at91_softpwm wr_pstats
DIRS = wr_vic wr_nic wr_rtu at91_softpwm wr_pstats wr_clocksource
# We may "LINUX ?= /usr/src/linux-wrswitch", but it's better to leave it empty
......
obj-m := wr_clocksource.o
# accept WRN_DEBUG from the environment. It turns pr_debug() into printk.
ifdef WRN_DEBUG
ccflags-y += -DDEBUG
endif
# What follows is standard stuff
export ARCH ?= arm
export CROSS_COMPILE ?= $(CROSS_COMPILE_ARM)
all modules:
$(MAKE) CONFIG_DEBUG_SECTION_MISMATCH=y \
-C $(LINUX) SUBDIRS=$(shell /bin/pwd) modules
# looking at preprocessed output is helpful for bug hunting
preprocess:
$(MAKE) CONFIG_DEBUG_SECTION_MISMATCH=y \
-C $(LINUX) SUBDIRS=$(shell /bin/pwd) $(wr-nic-objs:.o=.i)
# We might "$(MAKE) -C $(LINUX)" but "make clean" with no LINUX defined
# is sometimes useful to have
clean:
rm -f *.mod.c *.o *.ko *.i .*cmd Module.symvers modules.order *~
rm -rf .tmp_versions
/* Alessandro Rubini for CERN 2014, GPLv2 or later */
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/vmalloc.h>
#include <linux/clocksource.h>
/* We need these two defined in order to include nic-hardware.h */
#define WR_IS_NODE 0
#define WR_IS_SWITCH 1
/* We have no centralized defines yet: pick frequency and registers base */
#include "../wr_nic/nic-hardware.h"
#define WRCS_FREQUENCY REFCLK_FREQ
#define WRCS_TICK_NS (NSEC_PER_SEC / WRCS_FREQUENCY)
static int wrcs_stats;
module_param(wrcs_stats, int, 0644);
MODULE_PARM_DESC(wrcs_stats, "Count how often the clocksource is being read");
static __iomem struct PPSG_WB *wrcs_ppsg;
static int wrcs_is_registered; /* no need for atomic_t or whatever */
/* If so requested, print statistics once per second */
static inline void wrcs_do_stats(void)
{
static unsigned long nextp;
static int ncalls;
if (!wrcs_stats)
return;
if (!nextp)
nextp = jiffies + HZ;
/* This, when enabled, shows around 400 calls per second */
if (time_after_eq(jiffies, nextp)) {
pr_info("%s: called %i times\n", __func__,
ncalls);
ncalls = 0;
nextp += HZ;
}
ncalls++;
}
static cycle_t wrcs_read(struct clocksource *cs)
{
static uint32_t offset, last, this;
wrcs_do_stats();
/* FIXME: identify a time jump by monitoring the tick counter */
/*
* Turn the counter into a 32-bit one (see cs->mask below).
* We reset at 0x3b9aca0, so without this we should use mask = 0x1f
* and mac_idle = 32 ticks = 512ns. Unaffordable.
*/
this = readl(&wrcs_ppsg->CNTR_NSEC);
if (this < last)
offset += WRCS_FREQUENCY;
last = this;
return offset + this;
}
static struct clocksource wrcs_cs = {
.name = "white-rabbit",
.rating = 450, /* perfect... */
.read = wrcs_read,
/* no enable/disable */
.mask = 0xffffffff, /* We fake a 32-bit thing */
.max_idle_ns = 900 * 1000 * 1000, /* well, 1s... */
.flags = CLOCK_SOURCE_IS_CONTINUOUS,
};
/*
* The timer is used to check when does WR synchronize. When that
* happens, we set time of day and register our clocksource. Time
* jumps after synchronization are not well supported.
*/
static void wrcs_timer_fn(unsigned long unused);
static DEFINE_TIMER(wrcs_timer, wrcs_timer_fn, 0, 0);
static void wrcs_timer_fn(unsigned long unused)
{
uint32_t ticks, tai_l, tai_h;
int64_t tai;
/* Read ppsg, all fields consistently se we can use the value */
do {
tai_l = readl(&wrcs_ppsg->CNTR_UTCLO);
tai_h = readl(&wrcs_ppsg->CNTR_UTCHI);
ticks = readl(&wrcs_ppsg->CNTR_NSEC);
} while (readl(&wrcs_ppsg->CNTR_UTCLO) != tai_l);
tai = (typeof(tai))tai_h << 32 | tai_l;
/* If we are before 2010 (date +%s --date=2010-01-01), try again */
if (tai < 1262300400LL) {
mod_timer(&wrcs_timer, jiffies + HZ);
return;
}
clocksource_register(&wrcs_cs);
wrcs_is_registered = 1;
/* And don't restart the timer */
}
static int wrcs_init(void)
{
wrcs_ppsg = ioremap(FPGA_BASE_PPSG, FPGA_SIZE_PPSG);
if (!wrcs_ppsg) {
pr_err("WR Clocksource: can't remap PPS registers\n");
return -EIO;
}
clocksource_calc_mult_shift(&wrcs_cs, WRCS_FREQUENCY, 1);
/* Fire the timer */
mod_timer(&wrcs_timer, jiffies + HZ);
return 0;
}
static void wrcs_exit(void)
{
del_timer_sync(&wrcs_timer);
if (wrcs_is_registered)
clocksource_unregister(&wrcs_cs);
iounmap(wrcs_ppsg);
}
module_init(wrcs_init);
module_exit(wrcs_exit);
MODULE_LICENSE("GPL");
/* Hack: this is not exported by current kernel. Define a local copy */
void
clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 maxsec)
{
u64 tmp;
u32 sft, sftacc= 32;
/*
* Calculate the shift factor which is limiting the conversion
* range:
*/
tmp = ((u64)maxsec * from) >> 32;
while (tmp) {
tmp >>=1;
sftacc--;
}
/*
* Find the conversion shift/mult pair which has the best
* accuracy and fits the maxsec conversion range:
*/
for (sft = 32; sft > 0; sft--) {
tmp = (u64) to << sft;
tmp += from / 2;
do_div(tmp, from);
if ((tmp >> sftacc) == 0)
break;
}
*mult = tmp;
*shift = sft;
}
......@@ -40,5 +40,6 @@ insmod $WR_HOME/lib/modules/wr_vic.ko
insmod $WR_HOME/lib/modules/wr-nic.ko macaddr=$val
insmod $WR_HOME/lib/modules/wr_rtu.ko
insmod $WR_HOME/lib/modules/wr_pstats.ko pstats_nports=18
insmod $WR_HOME/lib/modules/wr_clocksource.ko
$WR_HOME/sbin/start-daemons.sh
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment