Xilinx 1588驱动分析

最近看到Xilinx 1588相关的驱动,简单的记录一下。


从MPsoc平台的demo中,设备树相关的节点是这个样子的。

timer_1588_v2_0@a0080000 {
    clock-names = "systemtimer_clk", "s_axi_aclk";
    clocks = <0x46 0x43>;
    compatible = "xlnx,timer-1588-2.0";
    interrupt-names = "interrupt";
    interrupt-parent = <0x4>;
    interrupts = <0x0 0x5c 0x4>;
    reg = <0x0 0xa0080000 0x0 0x10000>;
    xlnx,period = <0x3d0900>;
};

奇怪的是,我在linux-xlnx-xilinx-v2019.1分支中并没有找到相关的驱动。于是我在Xilinx的github上找到了对应驱动。这里就对这个驱动简单的分析一下,看看是什么样的原理。


首先还是那个套路,在驱动中,使用module_platform_driver(xlnx_ptp_timer_driver);注册了平台驱动。
xlnx-ptp-timer.c中, timer_1588_of_match[]与设备树compatible属性匹配,所以平台驱动xlnx_ptp_timer_driver的探针会被内核枚举。

r_mem = platform_get_resource(pdev, IORESOURCE_MEM, 0);
if (!r_mem) {
    dev_err(&pdev->dev, "no IO resource defined\n");
    return -ENXIO;
}

timer->baseaddr = devm_ioremap_resource(&pdev->dev, r_mem);
if (IS_ERR(timer->baseaddr)) {
    err = PTR_ERR(timer->baseaddr);
    return err;
}

在probe中,驱动获取了内存资源并做了相应的映射关系。所以这里, timer->baseaddr是0xa0080000的映射。timer->irq = 0x5c,触发类型为高电平。驱动中还固定写死了timer->period = 4000000;所以设备树中传过来的xlnx,period并没有什么卵用。

关键来了

timer->ptp_clock_info = xlnx_ptp_clock_info;
timer->ptp_clock = ptp_clock_register(&timer->ptp_clock_info,
                &pdev->dev);

这就是驱动的核心部分,向内核注册了一个ptp时钟设备, 设备的操作信息在xlnx_ptp_clock_info里。里面包含了adjfreq,adjtime, gettime64,settime64等时间操作的接口。

static struct ptp_clock_info xlnx_ptp_clock_info = {
  .owner    = THIS_MODULE,
  .name    = "Xilinx Timer",
  .max_adj  = 999999999,
  .n_ext_ts  = 0,
  .pps     = 1,
  .adjfreq  = xlnx_ptp_adjfreq,
  .adjtime  = xlnx_ptp_adjtime,
  .gettime64  = xlnx_ptp_gettime,
  .settime64  = xlnx_ptp_settime,
  .enable    = xlnx_ptp_enable,
};

最后,就是注册中断啦,刚才从设备树里辛辛苦苦拿过来的中断号要用上,就是向内核申请这个中断。

err = xlnx_ptp_timer_request_irq(timer);

中断的回调服务函数是xlnx_ptp_timer_isr。

static irqreturn_t xlnx_ptp_timer_isr(int irq, void *priv)
{
  struct xlnx_ptp_timer *timer = priv;
  struct ptp_clock_event event;

  event.type = PTP_CLOCK_PPS;
  ++timer->countpulse;
  if (timer->countpulse >= PULSESIN1PPS) {
    timer->countpulse = 0;
    if ((timer->ptp_clock) && (timer->pps_enable)) {
      ptp_clock_event(timer->ptp_clock, &event);
    }
  }
out:
  out_be32((timer->baseaddr + XTIMER1588_INTERRUPT),  (1 << XTIMER1588_INT_SHIFT) );
  return IRQ_HANDLED;
}

这里就是做了一个计数,然后看看设备有没有被使能,如果被使能了,就使用ptp_clock_event发送一个PTP_CLOCK_PPS事件,这个是内核解耦一贯套路,至于这个事件做了啥,还要分析ptp_clock的核心层。这里我先不去那么深入分析核心层做了什么,毕竟是抽象出来的东西,要用到再去分析。


继续分析xlnx-ptp-timer.c。假设内核已经完成了xlnx-ptp-timer设备驱动的探针操作,就意味着这个驱动可以被使用了。
肯定有人会调用这个驱动的xlnx_ptp_clock_info下的接口。

settime64分析:这个操作看上去比较简单,我就直接从代码上注释了。

static int xlnx_ptp_settime(struct ptp_clock_info *ptp,
			    const struct timespec64 *ts)
{
	struct xlnx_ptp_timer *timer = container_of(ptp, struct xlnx_ptp_timer,
						    ptp_clock_info);
	struct timespec64 delta, tod;
	struct timespec64 offset;
	unsigned long flags;

	// 进入临界状态,禁止内核抢占,关闭中断并保存中断当前状态
	spin_lock_irqsave(&timer->reg_lock, flags);
	
	// 对timer的offset进行清0操作
	/* First zero the offset */
	offset.tv_sec = 0;
	offset.tv_nsec = 0;
	xlnx_rtc_offset_write(timer, &offset);

	// 获取当前时间值,保存在tod中。
	/* Get the current timer value */
	xlnx_tod_read(timer, &tod);

	// 用这个传入的ts时间减去当前时间tod, 得到一个差值delta
	/* Subtract the current reported time from our desired time */
	delta = timespec64_sub(*ts, tod);

	// delta还是不能出现负数。。。。
	/* Don't write a negative offset */
	if (delta.tv_sec <= 0) {
		delta.tv_sec = 0;
		if (delta.tv_nsec < 0)
			delta.tv_nsec = 0;
	}

	// 用delta更新offset
	xlnx_rtc_offset_write(timer, &delta);
	// 退出临界状态,恢复内核抢占,恢复中断和中断状态
	spin_unlock_irqrestore(&timer->reg_lock, flags);
	return 0;
}

我们重点看看xlnx_rtc_offset_write和xlnx_tod_read做了啥见不得人的勾当。

static void xlnx_rtc_offset_write(struct xlnx_ptp_timer *timer,
          const struct timespec *ts)
{

  out_be32((timer->baseaddr + XTIMER1588_RTC_OFFSET_SEC_H), (u32) ((ts->tv_sec>>32)&0xFFFFFFFF) );
  out_be32((timer->baseaddr + XTIMER1588_RTC_OFFSET_SEC_L), (u32) ((ts->tv_sec    )&0xFFFFFFFF) );
  out_be32((timer->baseaddr + XTIMER1588_RTC_OFFSET_NS),    (u32) ts->tv_nsec );

  // printk(KERN_INFO "xlnx_rtc_offset_write:ts.tv_sec:%lld, ts.tv_nsec:%d \n",ts->tv_sec,ts->tv_nsec);
}

static void xlnx_tod_read(struct xlnx_ptp_timer *timer, struct timespec64 *ts)
{
  u32 sec, nsec;
  nsec = in_be32(timer->baseaddr + XTIMER1588_CURRENT_RTC_NS);
  sec = in_be32(timer->baseaddr + XTIMER1588_CURRENT_RTC_SEC_L);

#ifdef XTIMER1588_ENABLE_DEBUG_MESS
  printk(KERN_INFO "ptp_xlnx_1588:xlnx_tod_read=%d.%09d\n", sec, nsec);
#endif

  ts->tv_sec = 0; // Clear to be safe. Change sec read to 64 above.
  ts->tv_sec = (u64) sec; // Linux now uses timespec64 as the function res
  ts->tv_nsec = nsec;
}

好像也没干啥,看上去就是读写了某个寄存器而已啊,out_be32这个东西就是写,in_be32就是读,头文件中都有定义。

#define in_be32(offset)		__raw_readl(offset)
#define out_be32(offset, val)	__raw_writel(val, offset)

那寄存器是哪个?timer->baseaddr是什么?回头看一下,最开始分析的时候,探针操作的时候驱动就从设备树中获取了这个地址,它对应着一个映射后的虚拟地址,这个不用管,只需要知道,timer->baseaddr对应的物理地址是0xa0080000。所以写那个寄存器对应驱动中的宏就一清二楚了。


gettime64分析:这个就更简单了,就是直接读了当前时间寄存器,然后返回时间值。

static int xlnx_ptp_gettime(struct ptp_clock_info *ptp, struct timespec64 *ts)
{
  unsigned long flags;
  struct xlnx_ptp_timer *timer = container_of(ptp,
    struct xlnx_ptp_timer, ptp_clock_info);

  spin_lock_irqsave(&timer->reg_lock, flags);

  xlnx_tod_read(timer, ts);

  spin_unlock_irqrestore(&timer->reg_lock, flags);

#ifdef XTIMER1588_ENABLE_DEBUG_MESS
  printk(KERN_INFO "ptp_xlnx_1588:gettime ts.tv_sec:%d, ts.tv_nsec:%d\n",ts->tv_sec,ts->tv_nsec);
#endif

  return 0;
}

adjtime分析:核心部分如下,其他都是花哨。

1、// 将传过来的delta转换成timespec结构类型的值

  then = ns_to_timespec(delta);

2、// 获取当前的offset值,并把刚才的delta转换之后的值加到当前的offset上

  xlnx_rtc_offset_read(timer, &offset);
  offset.tv_sec = 0;  
  offset = timespec_add(offset, then);

3、// 更新offset寄存器

  out_be32((timer->baseaddr + XTIMER1588_RTC_OFFSET_SEC_H), (u32) ((offset.tv_sec>>32) & 0xffffffff));
  out_be32((timer->baseaddr + XTIMER1588_RTC_OFFSET_SEC_L), (u32) ((offset.tv_sec    ) & 0xffffffff));
  out_be32((timer->baseaddr + XTIMER1588_RTC_OFFSET_NS), offset.tv_nsec );

这就是Xilinx 1588部分的驱动,寄存器部分和Xilinx的IP核有很大的联系,这些地址的偏移都是根据IP core而确定的。至于PTP部分,应该是统一的接口,有机会看看PTP部分的原理实现。

你可能感兴趣的:(linux)