linuxptp中的clock_adjtime如何影响到PHC
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);
}
所以现在可以整理一下整个流程的顺序了:
- 设备启动,初始化struct ptp_clock_info,注册各种操作函数(adjtime, adjfreq等)。
- 调用ptp_clock_register(),通过ptp_clock_info创建k_clock以及ptp_clock,并将两者关联起来。同时也得知了clock_id。
- 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];
}