Linux kernel dpll 子系统

DPLL

PLL - 锁相环是一种电子电路,它将设备的时钟信号与外部时钟信号同步。 有效地使设备以与 PLL 输入上提供的相同的时钟信号节拍运行。

DPLL - 数字锁相环是一种集成电路,除了普通的 PLL 行为外,还包含数字相位检测器,并且可能在环路中具有数字分频器。 因此,DPLL 输入和输出端的频率可以配置。

子系统

dpll 子系统的主要目的是提供通用接口来配置使用任何类型的数字 PLL 的设备,并且可以使用不同的输入信号源进行同步,以及不同类型的输出。 主要接口是基于 NETLINK_GENERIC 的协议,并定义了一个事件监控多播组。

设备对象

单个 dpll 设备对象表示单个数字 PLL 电路和一堆连接的引脚。 它向用户报告支持的操作模式和当前状态,以响应 netlink 命令 DPLL_CMD_DEVICE_GETdo 请求,并列出子系统中注册的 dpll,以响应同一命令的 dump netlink 请求。 更改 dpll 设备的配置是通过 netlink DPLL_CMD_DEVICE_SET 命令的 do 请求完成的。 设备句柄是 DPLL_A_ID,应提供该句柄以获取或设置系统中特定设备的配置。 它可以与 DPLL_CMD_DEVICE_GET dump 请求或 DPLL_CMD_DEVICE_ID_GET do 请求一起获取,其中一个必须提供导致单个设备匹配的属性。

引脚对象

引脚是一个非晶形对象,表示输入或输出,它可以是设备的内部组件,也可以是外部连接的组件。 每个 dpll 的引脚数各不相同,但通常会为单个 dpll 设备提供多个引脚。 引脚的属性、功能和状态在响应 netlink DPLL_CMD_PIN_GET 命令的 do 请求时提供给用户。 也可以使用 DPLL_CMD_PIN_GET 命令的 dump 请求列出系统中注册的所有引脚。 可以通过 netlink DPLL_CMD_PIN_SET 命令的 do 请求来更改引脚的配置。 引脚句柄是 DPLL_A_PIN_ID,应提供该句柄以获取或设置系统中特定引脚的配置。 可以通过 DPLL_CMD_PIN_GET dump 请求或 DPLL_CMD_PIN_ID_GET do 请求来获取,其中用户提供导致单个引脚匹配的属性。

引脚选择

通常,所选引脚(信号驱动 dpll 设备的引脚)可以从 DPLL_A_PIN_STATE 属性获得,并且对于任何 dpll 设备,只能有一个引脚处于 DPLL_PIN_STATE_CONNECTED 状态。

引脚选择可以手动或自动完成,具体取决于硬件功能和活动的 dpll 设备工作模式(DPLL_A_MODE 属性)。 结果是,对于每种模式,可用引脚状态都存在差异,并且对于用户可以为 dpll 设备请求的状态也存在差异。

在手动模式 (DPLL_MODE_MANUAL) 中,用户可以请求或接收以下引脚状态之一

  • DPLL_PIN_STATE_CONNECTED - 引脚用于驱动 dpll 设备

  • DPLL_PIN_STATE_DISCONNECTED - 引脚不用于驱动 dpll 设备

在自动模式 (DPLL_MODE_AUTOMATIC) 中,用户可以请求或接收以下引脚状态之一

  • DPLL_PIN_STATE_SELECTABLE - 应将引脚视为自动选择算法的有效输入

  • DPLL_PIN_STATE_DISCONNECTED - 不应将引脚视为自动选择算法的有效输入

在自动模式 (DPLL_MODE_AUTOMATIC) 中,一旦自动选择算法使用其中一个输入锁定 dpll 设备,用户只能接收引脚状态 DPLL_PIN_STATE_CONNECTED

共享引脚

单个引脚对象可以连接到多个 dpll 设备。 然后有两组配置旋钮

  1. 在引脚上设置 - 配置影响引脚注册到的所有 dpll 设备(即,DPLL_A_PIN_FREQUENCY),

  2. 在引脚-dpll 元组上设置 - 配置仅影响选定的 dpll 设备(即,DPLL_A_PIN_PRIODPLL_A_PIN_STATEDPLL_A_PIN_DIRECTION)。

MUX 类型引脚

引脚可以是 MUX 类型,它聚合子引脚并充当引脚多路复用器。 一个或多个引脚注册为 MUX 类型,而不是直接注册到 dpll 设备。 注册为 MUX 类型引脚的引脚为用户提供额外的嵌套属性 DPLL_A_PIN_PARENT_PIN,用于它们注册到的每个父引脚。 如果引脚注册了多个父引脚,则它们的行为类似于多输出多路复用器。 在这种情况下,DPLL_CMD_PIN_GET 的输出将包含多个引脚-父级嵌套属性,其中包含与每个父级相关的当前状态,例如

'pin': [{{
  'clock-id': 282574471561216,
  'module-name': 'ice',
  'capabilities': 4,
  'id': 13,
  'parent-pin': [
  {'parent-id': 2, 'state': 'connected'},
  {'parent-id': 3, 'state': 'disconnected'}
  ],
  'type': 'synce-eth-port'
  }}]

一次只有一个子引脚可以将其信号提供给父 MUX 类型引脚,选择是通过请求更改所需父级上的子引脚状态来完成的,方法是使用 DPLL_A_PIN_PARENT 嵌套属性。 在父引脚上设置状态 netlink 消息格式示例

DPLL_A_PIN_ID

子引脚 ID

DPLL_A_PIN_PARENT_PIN

用于请求与父引脚相关的配置的嵌套属性

DPLL_A_PIN_PARENT_ID

父引脚 ID

DPLL_A_PIN_STATE

父引脚上请求的引脚状态

引脚优先级

某些设备可能提供自动引脚选择模式的功能(DPLL_A_MODE 属性的枚举值 DPLL_MODE_AUTOMATIC)。 通常,自动选择是在硬件级别执行的,这意味着只有直接连接到 dpll 的引脚才能用于自动输入引脚选择。 在自动选择模式下,用户无法手动选择设备的输入引脚,而是用户应为所有直接连接的引脚提供优先级 DPLL_A_PIN_PRIO,设备将选择最高优先级的有效信号并使用它来控制 DPLL 设备。 在父引脚上设置优先级 netlink 消息格式示例

DPLL_A_PIN_ID

配置的引脚 ID

DPLL_A_PIN_PARENT_DEVICE

用于请求与父 dpll 设备相关的配置的嵌套属性

DPLL_A_PIN_PARENT_ID

父 dpll 设备 ID

DPLL_A_PIN_PRIO

在父 dpll 上请求的引脚优先级

MUX 类型引脚的子引脚无法进行自动输入引脚选择,为了配置 MUX 类型引脚的活动输入,用户需要请求父引脚上子引脚的所需引脚状态,如 MUX-type pins 章节所述。

相位偏移测量和调整

设备可以提供测量引脚上的信号及其父 dpll 设备之间的相位差的能力。 如果支持引脚-dpll 相位偏移测量,则应为每个父 dpll 设备提供 DPLL_A_PIN_PHASE_OFFSET 属性。

设备还可以提供调整引脚上信号相位的能力。 如果支持引脚相位调整,则引脚句柄应在 DPLL_CMD_PIN_GET 响应中为用户提供最小值和最大值,属性为 DPLL_A_PIN_PHASE_ADJUST_MINDPLL_A_PIN_PHASE_ADJUST_MAX。 配置的相位调整值由引脚的 DPLL_A_PIN_PHASE_ADJUST 属性提供,并且可以使用相同的属性通过 DPLL_CMD_PIN_SET 命令请求更改值。

DPLL_A_PIN_ID

配置的引脚 ID

DPLL_A_PIN_PHASE_ADJUST_MIN

attr 相位调整的最小值

DPLL_A_PIN_PHASE_ADJUST_MAX

attr 相位调整的最大值

DPLL_A_PIN_PHASE_ADJUST

attr 在父 dpll 设备上配置的相位调整值

DPLL_A_PIN_PARENT_DEVICE

用于请求给定父 dpll 设备上的配置的嵌套属性

DPLL_A_PIN_PARENT_ID

父 dpll 设备 ID

DPLL_A_PIN_PHASE_OFFSET

attr 引脚和父 dpll 设备之间测量的相位差

所有与相位相关的值均以皮秒为单位提供,表示信号相位之间的时间差。 负值表示引脚上的信号的相位在时间上早于 dpll 的信号。 正值表示引脚上的信号的相位在时间上晚于 dpll 的信号。

相位调整(最小值和最大值)值是整数,但测量的相位偏移值是具有 3 位小数位的分数,并且应使用 DPLL_PIN_PHASE_OFFSET_DIVIDER 进行除以获得整数部分,并进行模运算以获得分数部分。

嵌入式 SYNC

设备可以提供使用嵌入式 SYNC 功能的能力。 它允许将额外的 SYNC 信号嵌入到引脚的基本频率中 - 每次发生 SYNC 信号脉冲时,基本频率信号的一个特殊脉冲。 用户可以配置嵌入式 SYNC 的频率。 嵌入式 SYNC 功能始终与给定的基本频率和硬件功能相关。 根据为引脚配置的当前基本频率,为用户提供支持的嵌入式 SYNC 频率范围。

DPLL_A_PIN_ESYNC_FREQUENCY

当前嵌入式 SYNC 频率

DPLL_A_PIN_ESYNC_FREQUENCY_SUPPORTED

嵌套可用嵌入式 SYNC 频率范围

DPLL_A_PIN_FREQUENCY_MIN

attr 频率的最小值

DPLL_A_PIN_FREQUENCY_MAX

attr 频率的最大值

DPLL_A_PIN_ESYNC_PULSE

嵌入式 SYNC 的脉冲类型

配置命令组

配置命令用于获取有关注册的 dpll 设备(和引脚)的信息,以及设置设备或引脚的配置。 由于 dpll 设备必须被抽象化并反映真实的硬件,因此无法从用户空间通过 netlink 添加新的 dpll 设备,并且每个设备都应由其驱动程序注册。

所有 netlink 命令都需要 GENL_ADMIN_PERM。 这是为了防止来自未经授权的用户空间应用程序的任何垃圾邮件/DoS 攻击。

DPLL_CMD_DEVICE_GETDPLL_CMD_PIN_GET 命令能够进行转储类型的 netlink 请求,在这种情况下,响应的格式与它们的 do 请求的格式相同,但返回系统中注册的每个设备或引脚。

SET 命令格式

DPLL_CMD_DEVICE_SET - 要定位 dpll 设备,用户提供 DPLL_A_ID,它是系统中 dpll 设备的唯一标识符,以及配置的参数 (DPLL_A_MODE)。

通常情况下,可以一次配置多个参数,但内部每个参数更改都会单独调用,且配置顺序不以任何方式保证。

配置预定义枚举

enum dpll_mode

dpll可以支持的工作模式,区分 dpll 如何选择其输入之一以与之同步,DPLL_A_MODE 属性的有效值

常量

DPLL_MODE_MANUAL

输入只能通过向 dpll 发送请求来选择

DPLL_MODE_AUTOMATIC

最高优先级的输入引脚由 dpll 自动选择

enum dpll_lock_status

提供 dpll 设备锁定状态的信息,DPLL_A_LOCK_STATUS 属性的有效值

常量

DPLL_LOCK_STATUS_UNLOCKED

dpll 尚未锁定到任何有效输入(或通过将 DPLL_A_MODE 设置为 DPLL_MODE_DETACHED 强制执行)

DPLL_LOCK_STATUS_LOCKED

dpll 已锁定到有效信号,但没有保持可用

DPLL_LOCK_STATUS_LOCKED_HO_ACQ

dpll 已锁定并且已获取保持

DPLL_LOCK_STATUS_HOLDOVER

dpll 处于保持状态 - 丢失了有效的锁定或通过断开所有引脚强制执行(只有当 dpll 锁定状态已经是 DPLL_LOCK_STATUS_LOCKED_HO_ACQ 时才有可能,如果 dpll 锁定状态不是 DPLL_LOCK_STATUS_LOCKED_HO_ACQ,则 dpll 的锁定状态应保持 DPLL_LOCK_STATUS_UNLOCKED)

enum dpll_lock_status_error

如果之前的状态更改是由于故障引起的,则提供 dpll 设备锁定状态错误的信息。DPLL_A_LOCK_STATUS_ERROR 属性的有效值

常量

DPLL_LOCK_STATUS_ERROR_NONE

dpll 设备锁定状态已更改,没有任何错误

DPLL_LOCK_STATUS_ERROR_UNDEFINED

dpll 设备锁定状态由于未定义的错误而更改。如果驱动程序无法获得合适的精确错误类型,则驱动程序会填充此值。

DPLL_LOCK_STATUS_ERROR_MEDIA_DOWN

dpll 设备锁定状态已更改,因为关联的媒体已关闭。例如,如果 dpll 设备之前已锁定在 PIN_TYPE_SYNCE_ETH_PORT 类型的输入引脚上,则可能会发生这种情况。

DPLL_LOCK_STATUS_ERROR_FRACTIONAL_FREQUENCY_OFFSET_TOO_HIGH

媒体上的 RX 和 TX 符号速率之间的 FFO(分数频率偏移)太高。例如,如果 dpll 设备之前已锁定在 PIN_TYPE_SYNCE_ETH_PORT 类型的输入引脚上,则可能会发生这种情况。

enum dpll_type

dpll的类型,DPLL_A_TYPE 属性的有效值

常量

DPLL_TYPE_PPS

dpll 产生每秒脉冲信号

DPLL_TYPE_EEC

dpll 驱动以太网设备时钟

enum dpll_pin_type

定义引脚的可能类型,DPLL_A_PIN_TYPE 属性的有效值

常量

DPLL_PIN_TYPE_MUX

聚合另一层可选择的引脚

DPLL_PIN_TYPE_EXT

外部输入

DPLL_PIN_TYPE_SYNCE_ETH_PORT

以太网端口 PHY 的恢复时钟

DPLL_PIN_TYPE_INT_OSCILLATOR

设备内部振荡器

DPLL_PIN_TYPE_GNSS

GNSS 恢复时钟

enum dpll_pin_direction

定义引脚的可能方向,DPLL_A_PIN_DIRECTION 属性的有效值

常量

DPLL_PIN_DIRECTION_INPUT

引脚用作信号的输入

DPLL_PIN_DIRECTION_OUTPUT

引脚用于输出信号

enum dpll_pin_state

定义引脚的可能状态,DPLL_A_PIN_STATE 属性的有效值

常量

DPLL_PIN_STATE_CONNECTED

引脚已连接,锁相环的活动输入

DPLL_PIN_STATE_DISCONNECTED

引脚已断开,不被视为有效输入

DPLL_PIN_STATE_SELECTABLE

启用引脚以进行自动输入选择

enum dpll_pin_capabilities

定义引脚的可能功能,DPLL_A_PIN_CAPABILITIES 属性的有效标志

常量

DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE

可以更改引脚方向

DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE

可以更改引脚优先级

DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE

可以更改引脚状态

通知

dpll 设备可以提供有关设备状态更改的通知,即锁定状态更改、输入/输出更改或其他警报。有一个多播组用于通过 netlink 套接字通知用户空间应用程序:DPLL_MCGRP_MONITOR

通知消息

DPLL_CMD_DEVICE_CREATE_NTF

dpll 设备已创建

DPLL_CMD_DEVICE_DELETE_NTF

dpll 设备已删除

DPLL_CMD_DEVICE_CHANGE_NTF

dpll 设备已更改

DPLL_CMD_PIN_CREATE_NTF

dpll 引脚已创建

DPLL_CMD_PIN_DELETE_NTF

dpll 引脚已删除

DPLL_CMD_PIN_CHANGE_NTF

dpll 引脚已更改

事件格式与相应的 get 命令相同。DPLL_CMD_DEVICE_ 事件的格式与 DPLL_CMD_DEVICE_GET 的响应相同。DPLL_CMD_PIN_ 事件的格式与 DPLL_CMD_PIN_GET 的响应相同。

设备驱动程序实现

设备由 dpll_device_get() 调用分配。使用相同参数的第二次调用不会创建新对象,而是为给定参数提供指向先前创建的设备的指针,它还会增加该对象的引用计数。设备由 dpll_device_put() 调用释放,该调用首先减少引用计数,一旦引用计数清除,该对象将被销毁。

设备应实现一组操作并通过 dpll_device_register() 注册设备,此时它对用户可用。多个驱动程序实例可以使用 dpll_device_get() 获取对其的引用,以及使用其自己的 ops 和 priv 注册 dpll 设备。

引脚使用 dpll_pin_get() 单独分配,它的工作方式类似于 dpll_device_get()。函数首先创建对象,然后对于使用相同参数的每个调用,只有对象引用计数增加。此外,dpll_pin_put() 的工作方式类似于 dpll_device_put()。

引脚可以根据硬件需求注册到父 dpll 设备或父引脚。每个注册都需要注册者提供一组引脚回调,以及用于调用它们的数据私有指针

  • dpll_pin_register() - 将引脚注册到 dpll 设备,

  • dpll_pin_on_pin_register() - 将引脚注册到另一个 MUX 类型引脚。

添加或删除 dpll 设备的通知在子系统本身内创建。关于注册/取消注册引脚的通知也由子系统调用。关于 dpll 设备或引脚的状态更改的通知以两种方式调用

  • 在成功请求 dpll 子系统更改后,子系统调用相应的通知,

  • 由设备驱动程序使用 dpll_device_change_ntf() 或 dpll_pin_change_ntf() 请求,当驱动程序通知状态更改时。

使用 dpll 接口的设备驱动程序不需要实现所有回调操作。但是,有一些需要实现。所需的 dpll 设备级别回调操作

  • .mode_get,

  • .lock_status_get.

所需的引脚级别回调操作

  • .state_on_dpll_get(注册到 dpll 设备的引脚),

  • .state_on_pin_get(注册到父引脚的引脚),

  • .direction_get.

检查每个其他操作处理程序是否存在,如果缺少特定处理程序,则返回 -EOPNOTSUPP

最简单的实现是在 OCP TimeCard 驱动程序中。ops 结构定义如下

static const struct dpll_device_ops dpll_ops = {
        .lock_status_get = ptp_ocp_dpll_lock_status_get,
        .mode_get = ptp_ocp_dpll_mode_get,
        .mode_supported = ptp_ocp_dpll_mode_supported,
};

static const struct dpll_pin_ops dpll_pins_ops = {
        .frequency_get = ptp_ocp_dpll_frequency_get,
        .frequency_set = ptp_ocp_dpll_frequency_set,
        .direction_get = ptp_ocp_dpll_direction_get,
        .direction_set = ptp_ocp_dpll_direction_set,
        .state_on_dpll_get = ptp_ocp_dpll_state_get,
};

然后注册部分看起来像这样

clkid = pci_get_dsn(pdev);
bp->dpll = dpll_device_get(clkid, 0, THIS_MODULE);
if (IS_ERR(bp->dpll)) {
        err = PTR_ERR(bp->dpll);
        dev_err(&pdev->dev, "dpll_device_alloc failed\n");
        goto out;
}

err = dpll_device_register(bp->dpll, DPLL_TYPE_PPS, &dpll_ops, bp);
if (err)
        goto out;

for (i = 0; i < OCP_SMA_NUM; i++) {
        bp->sma[i].dpll_pin = dpll_pin_get(clkid, i, THIS_MODULE, &bp->sma[i].dpll_prop);
        if (IS_ERR(bp->sma[i].dpll_pin)) {
                err = PTR_ERR(bp->dpll);
                goto out_dpll;
        }

        err = dpll_pin_register(bp->dpll, bp->sma[i].dpll_pin, &dpll_pins_ops,
                                &bp->sma[i]);
        if (err) {
                dpll_pin_put(bp->sma[i].dpll_pin);
                goto out_dpll;
        }
}

在错误路径中,我们必须以相反的顺序倒退每个分配

while (i) {
        --i;
        dpll_pin_unregister(bp->dpll, bp->sma[i].dpll_pin, &dpll_pins_ops, &bp->sma[i]);
        dpll_pin_put(bp->sma[i].dpll_pin);
}
dpll_device_put(bp->dpll);

更复杂的示例可以在 Intel 的 ICE 驱动程序或 nVidia 的 mlx5 驱动程序中找到。

SyncE 启用

对于 SyncE 启用,需要允许软件应用程序控制 dpll 设备,该应用程序监视和配置 dpll 设备的输入,以响应 dpll 设备及其输入的当前状态。在这种情况下,dpll 设备输入信号也应可配置为使用从 PHY netdevice 恢复的信号来驱动 dpll。这通过将引脚暴露给 netdevice 来完成 - 使用 dpll_netdev_pin_set(struct net_device *dev, struct dpll_pin *dpll_pin) 将引脚附加到 netdevice 本身。暴露的引脚 id 句柄 DPLL_A_PIN_ID 然后可以被用户识别,因为它附加到 rtnetlink 以响应嵌套属性 IFLA_DPLL_PIN 中的 get RTM_NEWLINK 命令。