3

I'm trying to get the Linux kernel (and system as a whole) to sync to a monotonically incrementing clock from a FPGA register in memory. The clock is 64-bits wide and divided into two 32-bit sections: a 32-bit seconds register and a 32-bit nanoseconds register which increments in 10s of nanoseconds.

I created an initial implementation based off of the ARM global timer thinking this would be a good starting approach. The driver successful registers (in kernel, not a kernel module), and I can switch the clocksource to the driver, but the timing is off and I get quite a few panics about the scheduler not executing.

My goal is that I can use the typical POSIX APIs to set and get the time from a userspace application without having the userspace involved in reading a register in memory for the time.

Is using the clocksource the right tool for this job? Or do I need something else?

#include <linux/module.h>
#include <linux/init.h>
#include <linux/mod_devicetable.h>
#include <linux/property.h>
#include <linux/platform_device.h>
#include <linux/of_address.h>
#include <linux/of_device.h>
#include <linux/clocksource.h>
#include <linux/slab.h>
#include <linux/sched_clock.h>

/* Meta Information */
MODULE_LICENSE("GPL");

#define FPGA_TIME_DRIVER_NAME "fpga_time"


/* 32-bit seconds register */
#define FPGA_TIME_ADDR_CTRL_TIME_SECONDS         0x00
/* 32-bit subseconds register incrementing in 10s of nanoseconds
 */
#define FPGA_TIME_ADDR_INFO_TIME_SUBSECONDS      0x04

/* Rate at which the time is updated (10ns) */
#define FPGA_TIME_TIMER_TICK_100MHZ              (100 * 1000 * 1000)
/* Rating/priority of the clock source compared to other clocks.
 * Higher rating means it will be preferred over other clock sources.
 */
#define FPGA_TIME_TIMER_RATING                   (100)

static u64 FPGA_TIME_clocksource_read(struct clocksource *cs);

/**
 * @brief The global timer structure that holds the clocksource info and the
 * base register address for reading from memory.
 */
struct clocksource_fpga {
    void __iomem *reg;
    struct clocksource clksrc;
} g_timerInfo = {
    .clksrc = {
        .name = FPGA_TIME_DRIVER_NAME,
        .rating = FPGA_TIME_TIMER_RATING,
        .read = FPGA_TIME_clocksource_read,
        .mask = CLOCKSOURCE_MASK(64),
        .flags = CLOCK_SOURCE_IS_CONTINUOUS
    }
};

static u64 _FPGA_TIME_clocksource_read(void)
{
    u64 counter;
    u32 seconds, subseconds;

    seconds = readl_relaxed(g_timerInfo.reg + FPGA_TIME_ADDR_CTRL_TIME_SECONDS);
    subseconds = readl_relaxed(g_timerInfo.reg + FPGA_TIME_ADDR_INFO_TIME_SUBSECONDS);

    counter = seconds;
    counter <<= 32;
    counter |= (subseconds << 1);

    return counter;
}

static u64 FPGA_TIME_clocksource_read(struct clocksource *cs)
{
    return _FPGA_TIME_clocksource_read();
}

/**
 * @brief Linux scheduler clock read.
 * @return The current time as a 64-bit integer.
 */
#ifdef CONFIG_CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK
static u64 notrace FPGA_TIME_sched_clock_read(void)
{
    return _FPGA_TIME_clocksource_read();
}
#endif

/**
 * @brief Registers the timewarp clocksource and if applicable, registers with
 * the scheduler.
 * @return Returns 0 on success, errno otherwise.
 */
static int timer_clocksource_init(void)
{
    int ret;
#ifdef CONFIG_CLKSRC_ARM_GLOBAL_TIMER_SCHED_CLOCK
    printk(FPGA_TIME_DRIVER_NAME " - Registering the scheduler...\n");
    sched_clock_register(FPGA_TIME_sched_clock_read, 64, FPGA_TIME_TIMER_TICK_100MHZ);
#endif
    ret = clocksource_register_hz(&g_timerInfo.clksrc, FPGA_TIME_TIMER_TICK_100MHZ);
    printk(FPGA_TIME_DRIVER_NAME " - Registering the clocksource returned = %d\n", ret);
    return ret;
}

/**
 * @brief Device driver probed when detected in the device tree.
 * This reads in all the property values and initializes the driver.
 * 
 * @param pdev The plaform device which contains the information about the driver.
 * @return Returns 0 on success, errno otherwise.
 */
static int __init FPGA_TIME_register(struct device_node *np)
{
    printk(FPGA_TIME_DRIVER_NAME " - Loading the driver...\n");

    /* Get the base address and size. */
    g_timerInfo.reg = of_iomap(np, 0);

    return timer_clocksource_init();
}

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.