LinuxPTP

linuxptp中的clock_adjtime如何影响到PHC

2021-10-22  本文已影响0人  SnC_

1. clock_adjtime映射到sys_clock_adjtime

根据这个patch中的内容,首先,在x86平台中,user space函数clock_adjtime会被映射到__NR_clock_adjtime

在32bit环境中:

/* In /arch/x86/include/asm/unistd_32.h */
 #define __NR_rt_tgsigqueueinfo 335
 #define __NR_perf_event_open   336
 #define __NR_recvmmsg      337
+#define __NR_clock_adjtime 338
/* In /arch/x86/kernel/syscall_table_32.S */
ENTRY(sys_call_table)
    ...
    .long sys_rt_tgsigqueueinfo /* 335 */
    .long sys_perf_event_open
    .long sys_recvmmsg
+   .long sys_clock_adjtime

在64bit环境中,

/* In /arch/x86/include/asm/unistd_64.h */
 __SYSCALL(__NR_perf_event_open, sys_perf_event_open)
 #define __NR_recvmmsg              299
 __SYSCALL(__NR_recvmmsg, sys_recvmmsg)
+#define __NR_clock_adjtime         300
+__SYSCALL(__NR_clock_adjtime, sys_clock_adjtime)

所以,在x86平台下,clock_adjtime被映射到sys_clock_adjtime
在其他平台(如arm, powerpc等)中差不多也是如此。


2. posix clock的描述

根据这个patch里的内容,clock_adjtime函数,是用来操作posix clock的。它接收2个参数,一个是clock id,一个是struct timex。

posix clock的生成需要k_clock。
当posix clock通过dynamic clock creation method生成(如create_posix_clock())时,其clock id是动态生成的。
若通过register_posix_clock()进行注册,则其clock id是自带的。

无论通过哪种方式,它们的作用都是将clock放到posix_clocks[]全局变量中。

int register_posix_clock(const clockid_t id, struct k_clock *clock)
{
  struct k_clock *kc;
  int err = 0;
  mutex_lock(&clocks_mux);
  if (test_bit(id, clocks_map)) {
    pr_err("clock_id %d already registered\n", id);
    err = -EBUSY;
    goto out;
  }
  kc = &posix_clocks[id];
  *kc = *clock;
  kc->id = id;
  set_bit(id, clocks_map);
}
clockid_t create_posix_clock(struct k_clock *clock)
{
    clockid_t id;

    mutex_lock(&clocks_mux);
    id = find_first_zero_bit(clocks_map, MAX_CLOCKS);
    mutex_unlock(&clocks_mux);

    if (id < MAX_CLOCKS) {
        register_posix_clock(id, clock);
        return id;
    }
    return CLOCK_INVALID;
+}

3. ptp clock中的一些操作

根据这个patch中的一些内容,可以得知ptp clock相关的一些信息。

/**
 * ptp_clock_register() - register a PTP hardware clock driver
 *
 * @info:  Structure describing the new clock.
 */
struct ptp_clock *ptp_clock_register(struct ptp_clock_info *info)
{
    struct k_clock clk;
    struct ptp_clock *ptp;
    int err = 0, index, major = MAJOR(ptp_devt);

    /* Find a free clock slot and reserve it. */
    index = find_first_zero_bit(clocks.map, PTP_MAX_CLOCKS);
    set_bit(index, clocks.map);

    /* Initialize a clock structure. */
    ptp = kzalloc(sizeof(struct ptp_clock), GFP_KERNEL);
    ptp->info = info;
    ptp->devid = MKDEV(major, index);
    ptp->index = index;

    /* Create a new device in our class. */
    ptp->dev = device_create(ptp_class, NULL, ptp->devid, ptp,
                 "ptp%d", ptp->index);
    dev_set_drvdata(ptp->dev, ptp);
    ptp_register_chardev(ptp);
    ptp_populate_sysfs(ptp);

    /* Register a new PPS source. */
    if (info->pps) {
        struct pps_source_info pps;
        memset(&pps, 0, sizeof(pps));
        snprintf(pps.name, PPS_MAX_NAME_LEN, "ptp%d", index);
        pps.mode = PTP_PPS_MODE;
        pps.owner = info->owner;
        ptp->pps_source = pps_register_source(&pps, PTP_PPS_DEFAULTS);

    /* Create a posix clock. */
    memset(&clk, 0, sizeof(clk));
    clk.clock_getres    = ptp_clock_getres;
    clk.clock_set       = ptp_clock_set;
    clk.clock_get       = ptp_clock_get;
    clk.clock_adj       = ptp_clock_adj;
    clk.timer_create    = ptp_timer_create;
    clk.nsleep      = ptp_nsleep;
    clk.nsleep_restart  = ptp_nsleep_restart;
    clk.timer_set       = ptp_timer_set;
    clk.timer_del       = ptp_timer_del;
    clk.timer_get       = ptp_timer_get;

    snprintf(clk.name, KCLOCK_MAX_NAME, "ptp%d", index);

    ptp->clock_id = create_posix_clock(&clk);

    /* Prevent this module from unloading. */
    try_module_get(info->owner);

    /* Clock is ready, add it into the list. */
    list_add(&ptp->list, &clocks.list);
    clocks.data[ptp->clock_id] = ptp;

    return ptp;
}

可以看出,ptp_clock_register创建了ptp_clock以及k_clock,并将k_clock关联到ptp_clock。
也可以看出,是先有的ptp_clock_info,再有的ptp_clock。

而k_clock中注册了一些函数,但这些都是标准接口,最终是会调用到ptp_clock_info中注册的函数,比如下面这个

/* In /drivers/ptp/ptp_clock.c */
static int ptp_clock_adj(const clockid_t clkid, struct timex *tx)
{
    struct ptp_clock *ptp;
    struct ptp_clock_info *ops;

    ptp = clockid_to_ptpclock(clkid);
    ops = ptp->info;

    if (tx->modes & ADJ_SETOFFSET) {
        struct timespec ts;
        ts.tv_sec = tx->time.tv_sec;
        ts.tv_nsec = tx->modes & ADJ_NANO ? tx->time.tv_usec :
            tx->time.tv_usec * 1000;
        err = ops->adjtime(ops->priv, &ts);
    } else if (tx->modes & ADJ_FREQUENCY) {
        s64 ppb = 1 + tx->freq;
        ppb *= 125;
        ppb >>= 13;
        err = ops->adjfreq(ops->priv, (s32)ppb);
    }
    return err;
}

ptp_clock_info,是在设备驱动的代码里被事先定义好的。
它们要么是全局变量(如struct ptp_clock_info ptp_caps),要么在adapter初始化ptp功能时被赋值。

/* In /drivers\net\ethernet\intel\igb\igb_ptp.c */
void igb_ptp_init(struct igb_adapter *adapter)
{
    struct e1000_hw *hw = &adapter->hw;
    struct net_device *netdev = adapter->netdev;

    switch (hw->mac.type)
    {
    case e1000_82576:
        snprintf(adapter->ptp_caps.name, 16, "%pm", netdev->dev_addr);
        adapter->ptp_caps.owner = THIS_MODULE;
        adapter->ptp_caps.max_adj = 1000000000;
        adapter->ptp_caps.n_ext_ts = 0;
        adapter->ptp_caps.pps = 0;
        adapter->ptp_caps.adjfreq = igb_ptp_adjfreq_82576;
        adapter->ptp_caps.adjtime = igb_ptp_adjtime_82576;
        adapter->ptp_caps.gettime = igb_ptp_gettime_82576;
        adapter->ptp_caps.settime = igb_ptp_settime_82576;
        adapter->ptp_caps.enable = igb_ptp_enable;
        adapter->cc.read = igb_ptp_read_82576;
        adapter->cc.mask = CLOCKSOURCE_MASK(64);
        adapter->cc.mult = 1;
        adapter->cc.shift = IGB_82576_TSYNC_SHIFT;
        /* Dial the nominal frequency. */
        wr32(E1000_TIMINCA, INCPERIOD_82576 | INCVALUE_82576);
        break;
   ...
    }

    adapter->ptp_clock = ptp_clock_register(&adapter->ptp_caps,
                                            &adapter->pdev->dev);
}

所以现在可以整理一下整个流程的顺序了:

  1. 设备启动,初始化struct ptp_clock_info,注册各种操作函数(adjtime, adjfreq等)。
  2. 调用ptp_clock_register(),通过ptp_clock_info创建k_clock以及ptp_clock,并将两者关联起来。同时也得知了clock_id。
  3. clock_adjtime操作posix_clock,也就是操作k_clock。

现在的问题是,sys_clock_adjtime是如何调用到ptp_clock_adj的?


以下是在linux kernel中找到的一些代码片段

SYSCALL_DEFINE2(clock_adjtime, const clockid_t, which_clock,
        struct timex __user *, utx)
{
    struct k_clock *kc = clockid_to_kclock(which_clock);
    struct timex ktx;

    if (copy_from_user(&ktx, utx, sizeof(ktx)))
        return -EFAULT;

    err = kc->clock_adj(which_clock, &ktx);
    return err;
static struct k_clock *clockid_to_kclock(const clockid_t id)
{
    if (id < 0)
        return (id & CLOCKFD_MASK) == CLOCKFD ?
            &clock_posix_dynamic : &clock_posix_cpu;
    return &posix_clocks[id];
}
上一篇下一篇

猜你喜欢

热点阅读