Linux 内核 dpll 子系统

DPLL

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

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

子系统

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

设备对象

单个 dpll 设备对象表示单个数字 PLL 电路和一堆连接的引脚。它响应 netlink 命令 DPLL_CMD_DEVICE_GETdo 请求,向用户报告支持的操作模式和当前状态,并通过同一命令的 dump netlink 请求列出在子系统中注册的 dpll。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 引脚 章节所述。

相位偏移测量和调整

设备可能提供测量引脚上的信号与其父 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

相位调整的属性最小值

DPLL_A_PIN_PHASE_ADJUST_MAX

相位调整的属性最大值

DPLL_A_PIN_PHASE_ADJUST

父 DPLL 设备上配置的相位调整属性值

DPLL_A_PIN_PARENT_DEVICE

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

DPLL_A_PIN_PARENT_ID

父 DPLL 设备 ID

DPLL_A_PIN_PHASE_OFFSET

引脚和父 DPLL 设备之间测得的相位差属性

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

相位调整(以及最小值和最大值)是整数,但测得的相位偏移值是带 3 位小数的分数值,应除以 DPLL_PIN_PHASE_OFFSET_DIVIDER 以获得整数部分,并进行模除以获得小数部分。

嵌入式同步

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

DPLL_A_PIN_ESYNC_FREQUENCY

当前的嵌入式同步频率

DPLL_A_PIN_ESYNC_FREQUENCY_SUPPORTED

嵌套可用的嵌入式同步频率范围

DPLL_A_PIN_FREQUENCY_MIN

频率的属性最小值

DPLL_A_PIN_FREQUENCY_MAX

频率的属性最大值

DPLL_A_PIN_ESYNC_PULSE

嵌入式同步的脉冲类型

配置命令组

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

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

SET 命令格式

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

DPLL_CMD_PIN_SET - 要定位引脚,用户必须提供 DPLL_A_PIN_ID,它是系统中引脚的唯一标识符。还必须添加配置的引脚参数。如果配置了 DPLL_A_PIN_FREQUENCY,这将影响与该引脚连接的所有 DPLL 设备,这就是为什么频率属性不应包含在 DPLL_A_PIN_PARENT_DEVICE 中的原因。其他属性:DPLL_A_PIN_PRIODPLL_A_PIN_STATEDPLL_A_PIN_DIRECTION 必须包含在 DPLL_A_PIN_PARENT_DEVICE 中,因为它们的配置仅与其中一个父 DPLL 相关,该父 DPLL 由 DPLL_A_PIN_PARENT_ID 属性定位,该属性也需要在该嵌套内部。对于 MUX 类型引脚,DPLL_A_PIN_STATE 属性以类似的方式配置,方法是将所需状态包含在 DPLL_A_PIN_PARENT_PIN 嵌套属性中,并将目标父引脚 ID 包含在 DPLL_A_PIN_PARENT_ID 中。

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

配置预定义枚举

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() 获取对它的引用,以及使用它们自己的操作和私有数据注册 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 驱动程序中。操作结构定义如下

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 网络设备恢复的信号驱动 DPLL。这是通过将引脚暴露给网络设备来实现的 - 使用 dpll_netdev_pin_set(struct net_device *dev, struct dpll_pin *dpll_pin) 将引脚附加到网络设备本身。暴露的引脚 ID 句柄 DPLL_A_PIN_ID 然后可被用户识别,因为它附加到 rtnetlink 以响应嵌套属性 IFLA_DPLL_PIN 中的 get RTM_NEWLINK 命令。