通用计数器接口

简介

计数器设备广泛应用于各个行业。这些设备的普遍存在需要一个通用的接口以及统一的交互和公开标准。该驱动程序 API 旨在通过引入一个通用的计数器接口,解决现有计数器设备驱动程序中重复代码的问题。通用计数器接口使驱动程序能够支持并公开计数器设备中常见的一组组件和功能。

理论

计数器设备在设计上可能差异很大,但无论某些设备是正交编码器计数器还是累加计数器,所有计数器设备都包含一组核心组件。所有计数器设备共享的这组核心组件构成了通用计数器接口的精髓。

计数器有三个核心组件

  • 信号 (Signal):由计数器评估的数据流。

  • 突触 (Synapse):将信号和评估触发器与计数关联起来。

  • 计数 (Count):连接的突触效应的累积。

信号 (SIGNAL)

信号代表一个数据流。这是由计数器评估的输入数据,用于确定计数数据;例如,旋转编码器的正交信号输出线。并非所有计数器设备都提供用户对信号数据的访问,因此对于驱动程序而言,公开是可选的。

当信号数据可供用户访问时,通用计数器接口提供以下可用信号值

  • SIGNAL_LOW:信号线处于低电平状态。

  • SIGNAL_HIGH:信号线处于高电平状态。

一个信号可以与一个或多个计数关联。

突触 (SYNAPSE)

突触代表信号与计数的关联。信号数据影响相应的计数数据,突触代表这种关系。

突触动作模式指定了触发相应计数的计数功能评估以更新计数数据的信号数据条件。通用计数器接口提供以下可用动作模式

  • 无 (None):信号不触发计数功能。在脉冲-方向计数功能模式下,此信号被评估为方向。

  • 上升沿 (Rising Edge):低电平状态转换为高电平状态。

  • 下降沿 (Falling Edge):高电平状态转换为低电平状态。

  • 双沿 (Both Edges):任何状态转换。

计数器定义为一组输入信号,这些信号与计数数据相关联,而计数数据是根据相应计数功能评估关联输入信号状态而生成的。在通用计数器接口的上下文中,计数器由多个计数 (Counts) 组成,每个计数都与一组信号 (Signals) 相关联,其各自的突触 (Synapse) 实例代表关联计数的计数功能更新条件。

一个突触将一个信号与一个计数关联起来。

计数 (COUNT)

计数代表连接的突触效应的累积;即,一组信号的计数数据。通用计数器接口将计数数据表示为自然数。

计数具有表示计数数据更新行为的计数功能模式。通用计数器接口提供以下可用计数功能模式

  • 增加 (Increase):累积计数递增。

  • 减少 (Decrease):累积计数递减。

  • 脉冲-方向 (Pulse-Direction):信号 A 上的上升沿更新相应的计数。信号 B 的输入电平决定方向。

  • 正交 (Quadrature):一对正交编码信号被评估以确定位置和方向。以下正交模式可用

    • x1 A:如果方向是向前,正交对信号 A 上的上升沿更新相应的计数;如果方向是向后,正交对信号 A 上的下降沿更新相应的计数。正交编码决定方向。

    • x1 B:如果方向是向前,正交对信号 B 上的上升沿更新相应的计数;如果方向是向后,正交对信号 B 上的下降沿更新相应的计数。正交编码决定方向。

    • x2 A:正交对信号 A 上的任何状态转换都会更新相应的计数。正交编码决定方向。

    • x2 B:正交对信号 B 上的任何状态转换都会更新相应的计数。正交编码决定方向。

    • x4:任一正交对信号上的任何状态转换都会更新相应的计数。正交编码决定方向。

一个计数具有一组或多组关联的突触。

范例

最基本的计数器设备可以表示为通过单个突触与单个信号关联的单个计数。例如,一个计数器设备,它仅仅累积源输入线上的上升沿计数

        Count                Synapse        Signal
        -----                -------        ------
+---------------------+
| Data: Count         |    Rising Edge     ________
| Function: Increase  |  <-------------   / Source \
|                     |                  ____________
+---------------------+

在此示例中,信号是具有脉冲电压的源输入线,而计数是重复递增的持久计数数值。信号通过突触与相应的计数关联。增加功能由突触指定的信号数据条件触发——在本例中是电压输入线上的上升沿条件。总而言之,计数器设备的存在和行为由相应的计数、信号和突触组件恰当地表示:上升沿条件触发累积计数数据上的增加功能。

计数器设备不限于单个信号;实际上,理论上许多信号甚至可以与单个计数关联。例如,正交编码器计数器设备可以根据两条输入线的状态来跟踪位置

           Count                 Synapse     Signal
           -----                 -------     ------
+-------------------------+
| Data: Position          |    Both Edges     ___
| Function: Quadrature x4 |  <------------   / A \
|                         |                 _______
|                         |
|                         |    Both Edges     ___
|                         |  <------------   / B \
|                         |                 _______
+-------------------------+

在此示例中,两个信号(正交编码器线 A 和 B)与单个计数关联:A 或 B 上的上升沿或下降沿触发“正交 x4”功能,该功能确定移动方向并更新相应的位置数据。“正交 x4”功能可能在正交编码器计数器设备的硬件中实现;计数、信号和突触仅仅代表这种硬件行为和功能。

与同一计数关联的信号可以具有不同的突触动作模式条件。例如,一个在非正交脉冲-方向模式下运行的正交编码器计数器设备,可以有一条输入线专用于运动,第二条输入线专用于方向

           Count                   Synapse      Signal
           -----                   -------      ------
+---------------------------+
| Data: Position            |    Rising Edge     ___
| Function: Pulse-Direction |  <-------------   / A \ (Movement)
|                           |                  _______
|                           |
|                           |       None         ___
|                           |  <-------------   / B \ (Direction)
|                           |                  _______
+---------------------------+

只有信号 A 触发“脉冲-方向”更新功能,但仍然需要信号 B 的瞬时状态来确定方向,以便正确更新位置数据。最终,两个信号都通过两个相应的突触与同一计数关联,但只有一个突触具有触发相应计数功能的活动动作模式条件,而另一个则保留为“无”条件动作模式,以表明其相应信号可用于状态评估,尽管它处于非触发模式。

请记住,信号、突触和计数是抽象表示,无需与各自的物理源紧密结合。这使得计数器的用户可以摆脱物理组件的细微差别(例如输入线是差分还是单端),而是专注于数据和过程所代表的核心思想(例如从正交编码数据解释的位置)。

驱动程序 API

驱动程序开发者可以通过包含 include/linux/counter.h 头文件,在其代码中使用通用计数器接口。此头文件提供了用于定义计数器设备的几个核心数据结构、函数原型和宏。

结构体 counter_comp

计数器组件节点

定义:

struct counter_comp {
    enum counter_comp_type type;
    const char *name;
    void *priv;
    union {
        int (*action_read)(struct counter_device *counter,struct counter_count *count,struct counter_synapse *synapse, enum counter_synapse_action *action);
        int (*device_u8_read)(struct counter_device *counter, u8 *val);
        int (*count_u8_read)(struct counter_device *counter, struct counter_count *count, u8 *val);
        int (*signal_u8_read)(struct counter_device *counter, struct counter_signal *signal, u8 *val);
        int (*device_u32_read)(struct counter_device *counter, u32 *val);
        int (*count_u32_read)(struct counter_device *counter, struct counter_count *count, u32 *val);
        int (*signal_u32_read)(struct counter_device *counter, struct counter_signal *signal, u32 *val);
        int (*device_u64_read)(struct counter_device *counter, u64 *val);
        int (*count_u64_read)(struct counter_device *counter, struct counter_count *count, u64 *val);
        int (*signal_u64_read)(struct counter_device *counter, struct counter_signal *signal, u64 *val);
        int (*signal_array_u32_read)(struct counter_device *counter,struct counter_signal *signal, size_t idx, u32 *val);
        int (*device_array_u64_read)(struct counter_device *counter, size_t idx, u64 *val);
        int (*count_array_u64_read)(struct counter_device *counter,struct counter_count *count, size_t idx, u64 *val);
        int (*signal_array_u64_read)(struct counter_device *counter,struct counter_signal *signal, size_t idx, u64 *val);
    };
    union {
        int (*action_write)(struct counter_device *counter,struct counter_count *count,struct counter_synapse *synapse, enum counter_synapse_action action);
        int (*device_u8_write)(struct counter_device *counter, u8 val);
        int (*count_u8_write)(struct counter_device *counter, struct counter_count *count, u8 val);
        int (*signal_u8_write)(struct counter_device *counter, struct counter_signal *signal, u8 val);
        int (*device_u32_write)(struct counter_device *counter, u32 val);
        int (*count_u32_write)(struct counter_device *counter, struct counter_count *count, u32 val);
        int (*signal_u32_write)(struct counter_device *counter, struct counter_signal *signal, u32 val);
        int (*device_u64_write)(struct counter_device *counter, u64 val);
        int (*count_u64_write)(struct counter_device *counter, struct counter_count *count, u64 val);
        int (*signal_u64_write)(struct counter_device *counter, struct counter_signal *signal, u64 val);
        int (*signal_array_u32_write)(struct counter_device *counter,struct counter_signal *signal, size_t idx, u32 val);
        int (*device_array_u64_write)(struct counter_device *counter, size_t idx, u64 val);
        int (*count_array_u64_write)(struct counter_device *counter,struct counter_count *count, size_t idx, u64 val);
        int (*signal_array_u64_write)(struct counter_device *counter,struct counter_signal *signal, size_t idx, u64 val);
    };
};

成员

类型

计数器组件数据类型

名称

设备专用组件名称

私有数据

组件相关数据

{unnamed_union}

匿名

action_read

突触动作模式读取回调。相应的突触动作模式的读取值应通过 action 参数传回。

device_u8_read

设备 u8 组件读取回调。相应的设备 u8 组件的读取值应通过 val 参数传回。

count_u8_read

计数 u8 组件读取回调。相应的计数 u8 组件的读取值应通过 val 参数传回。

signal_u8_read

信号 u8 组件读取回调。相应的信号 u8 组件的读取值应通过 val 参数传回。

device_u32_read

设备 u32 组件读取回调。相应的设备 u32 组件的读取值应通过 val 参数传回。

count_u32_read

计数 u32 组件读取回调。相应的计数 u32 组件的读取值应通过 val 参数传回。

signal_u32_read

信号 u32 组件读取回调。相应的信号 u32 组件的读取值应通过 val 参数传回。

device_u64_read

设备 u64 组件读取回调。相应的设备 u64 组件的读取值应通过 val 参数传回。

count_u64_read

计数 u64 组件读取回调。相应的计数 u64 组件的读取值应通过 val 参数传回。

signal_u64_read

信号 u64 组件读取回调。相应的信号 u64 组件的读取值应通过 val 参数传回。

signal_array_u32_read

信号 u32 数组组件读取回调。相应计数 u32 数组组件元素的索引通过 idx 参数传入。相应计数 u32 数组组件元素的读取值应通过 val 参数传回。

device_array_u64_read

设备 u64 数组组件读取回调。相应设备 u64 数组组件元素的索引通过 idx 参数传入。相应设备 u64 数组组件元素的读取值应通过 val 参数传回。

count_array_u64_read

计数 u64 数组组件读取回调。相应计数 u64 数组组件元素的索引通过 idx 参数传入。相应计数 u64 数组组件元素的读取值应通过 val 参数传回。

signal_array_u64_read

信号 u64 数组组件读取回调。相应计数 u64 数组组件元素的索引通过 idx 参数传入。相应计数 u64 数组组件元素的读取值应通过 val 参数传回。

{unnamed_union}

匿名

action_write

突触动作模式写入回调。相应的突触动作模式的写入值通过 action 参数传入。

device_u8_write

设备 u8 组件写入回调。相应的设备 u8 组件的写入值通过 val 参数传入。

count_u8_write

计数 u8 组件写入回调。相应的计数 u8 组件的写入值通过 val 参数传入。

signal_u8_write

信号 u8 组件写入回调。相应的信号 u8 组件的写入值通过 val 参数传入。

device_u32_write

设备 u32 组件写入回调。相应的设备 u32 组件的写入值通过 val 参数传入。

count_u32_write

计数 u32 组件写入回调。相应的计数 u32 组件的写入值通过 val 参数传入。

signal_u32_write

信号 u32 组件写入回调。相应的信号 u32 组件的写入值通过 val 参数传入。

device_u64_write

设备 u64 组件写入回调。相应的设备 u64 组件的写入值通过 val 参数传入。

count_u64_write

计数 u64 组件写入回调。相应的计数 u64 组件的写入值通过 val 参数传入。

signal_u64_write

信号 u64 组件写入回调。相应的信号 u64 组件的写入值通过 val 参数传入。

signal_array_u32_write

信号 u32 数组组件写入回调。相应信号 u32 数组组件元素的索引通过 idx 参数传入。相应信号 u32 数组组件元素的写入值通过 val 参数传入。

device_array_u64_write

设备 u64 数组组件写入回调。相应设备 u64 数组组件元素的索引通过 idx 参数传入。相应设备 u64 数组组件元素的写入值通过 val 参数传入。

count_array_u64_write

计数 u64 数组组件写入回调。相应计数 u64 数组组件元素的索引通过 idx 参数传入。相应计数 u64 数组组件元素的写入值通过 val 参数传入。

signal_array_u64_write

信号 u64 数组组件写入回调。相应信号 u64 数组组件元素的索引通过 idx 参数传入。相应信号 u64 数组组件元素的写入值通过 val 参数传入。

结构体 counter_signal

计数器信号节点

定义:

struct counter_signal {
    int id;
    const char *name;
    struct counter_comp *ext;
    size_t num_ext;
};

成员

ID

用于识别信号的唯一 ID

名称

设备专用信号名称

扩展

可选的信号扩展数组

扩展数量

ext 中指定的信号扩展数量

结构体 counter_synapse

计数器突触节点

定义:

struct counter_synapse {
    const enum counter_synapse_action *actions_list;
    size_t num_actions;
    struct counter_signal *signal;
};

成员

动作列表

可用动作模式数组

动作数量

actions_list 中指定的动作模式数量

信号

指向关联信号的指针

结构体 counter_count

计数器计数节点

定义:

struct counter_count {
    int id;
    const char *name;
    const enum counter_function *functions_list;
    size_t num_functions;
    struct counter_synapse *synapses;
    size_t num_synapses;
    struct counter_comp *ext;
    size_t num_ext;
};

成员

ID

用于识别计数的唯一 ID

名称

设备专用计数名称

功能列表

可用功能模式数组

功能数量

functions_list 中指定的功能模式数量

突触

用于初始化的突触数组

突触数量

synapses 中指定的突触数量

扩展

可选的计数扩展数组

扩展数量

ext 中指定的计数扩展数量

结构体 counter_event_node

计数器事件节点

定义:

struct counter_event_node {
    struct list_head l;
    u8 event;
    u8 channel;
    struct list_head comp_list;
};

成员

l

当前正在观察的计数器事件列表

事件

触发的事件

通道

事件通道

组件列表

事件触发时要观察的组件列表

结构体 counter_ops

来自驱动程序的回调

定义:

struct counter_ops {
    int (*signal_read)(struct counter_device *counter,struct counter_signal *signal, enum counter_signal_level *level);
    int (*count_read)(struct counter_device *counter, struct counter_count *count, u64 *value);
    int (*count_write)(struct counter_device *counter, struct counter_count *count, u64 value);
    int (*function_read)(struct counter_device *counter,struct counter_count *count, enum counter_function *function);
    int (*function_write)(struct counter_device *counter,struct counter_count *count, enum counter_function function);
    int (*action_read)(struct counter_device *counter,struct counter_count *count,struct counter_synapse *synapse, enum counter_synapse_action *action);
    int (*action_write)(struct counter_device *counter,struct counter_count *count,struct counter_synapse *synapse, enum counter_synapse_action action);
    int (*events_configure)(struct counter_device *counter);
    int (*watch_validate)(struct counter_device *counter, const struct counter_watch *watch);
};

成员

信号读取

信号的可选读取回调。相应信号的读取电平应通过 level 参数传回。

计数读取

计数的读取回调。相应计数的读取值应通过 value 参数传回。

计数写入

计数的 optional 写入回调。相应计数的写入值通过 value 参数传入。

功能读取

计数功能模式的读取回调。相应计数的读取功能模式应通过 function 参数传回。

功能写入

计数功能模式的可选写入回调。相应计数的要写入的功能模式通过 function 参数传入。

action_read

突触动作模式的可选读取回调。相应突触的读取动作模式应通过 action 参数传回。

action_write

突触动作模式的可选写入回调。相应突触的要写入的动作模式通过 action 参数传入。

事件配置

配置事件的可选写入回调。结构体 counter_event_node 列表可以通过 counter 参数的 events_list 成员访问。

观察验证

验证观察的可选回调。计数器组件观察配置通过 watch 参数传入。返回值为 0 表示有效的计数器组件观察配置。

结构体 counter_device

计数器数据结构

定义:

struct counter_device {
    const char *name;
    struct device *parent;
    const struct counter_ops *ops;
    struct counter_signal *signals;
    size_t num_signals;
    struct counter_count *counts;
    size_t num_counts;
    struct counter_comp *ext;
    size_t num_ext;
    struct device dev;
    struct cdev chrdev;
    struct list_head events_list;
    spinlock_t events_list_lock;
    struct list_head next_events_list;
    struct mutex n_events_list_lock;
    struct counter_event *events;
    wait_queue_head_t events_wait;
    spinlock_t events_in_lock;
    struct mutex events_out_lock;
    struct mutex ops_exist_lock;
};

成员

名称

设备名称

父级

提供计数器的可选父设备

操作

来自驱动程序的回调

信号

信号数组

信号数量

signals 中指定的信号数量

计数

计数数组

计数数量

counts 中指定的计数数量

扩展

可选的计数器设备扩展数组

扩展数量

ext 中指定的计数器设备扩展数量

设备

内部设备结构

字符设备

内部字符设备结构

事件列表

当前正在观察的计数器事件列表

事件列表锁

保护计数器事件列表操作的锁

下一个事件列表

下一个正在观察的计数器事件列表

下一个事件列表锁

保护计数器下一个事件列表操作的锁

事件

检测到的计数器事件队列

事件等待

允许阻塞读取计数器事件的等待队列

事件入队锁

保护计数器事件入队操作的锁

事件出队锁

保护计数器事件出队操作的锁

操作存在锁

防止移除期间使用的锁

void *counter_priv(const 结构体 counter_device *const counter)

访问计数器设备私有数据

参数

const 结构体 counter_device *const counter

计数器设备

描述

获取计数器设备私有数据

结构体 counter_device *counter_alloc(size_t sizeof_priv)

分配一个 counter_device

参数

size_t sizeof_priv

驱动程序私有数据的大小

描述

这是计数器注册的第一部分。该结构体是动态分配的,以确保嵌入式 结构体 device 具有正确的生命周期。

如果成功,再次调用 counter_put() 来释放 counter_device

int counter_add(结构体 counter_device *counter)

完成计数器注册

参数

结构体 counter_device *counter

要添加的计数器

描述

这是计数器注册的第二部分。

如果成功,再次调用 counter_unregister() 来释放 counter_device

void counter_unregister(结构体 counter_device *const counter)

从系统中注销计数器

参数

结构体 counter_device *const counter

指向要注销的计数器的指针

描述

该计数器已从系统中注销。

结构体 counter_device *devm_counter_alloc(结构体 device *dev, size_t sizeof_priv)

分配一个 counter_device

参数

结构体 device *dev

要注册释放回调的设备

size_t sizeof_priv

驱动程序私有数据的大小

描述

这是 counter_add() 的设备管理版本。它注册一个清理回调,以负责调用 counter_put()

int devm_counter_add(结构体 device *dev, 结构体 counter_device *const counter)

完成计数器注册

参数

结构体 device *dev

要注册释放回调的设备

结构体 counter_device *const counter

要添加的计数器

描述

这是 counter_add() 的设备管理版本。它注册一个清理回调,以负责调用 counter_unregister()

void counter_push_event(结构体 counter_device *const counter, const u8 event, const u8 channel)

为用户空间读取排队事件

参数

结构体 counter_device *const counter

指向计数器结构体的指针

const u8 event

触发的事件

const u8 channel

事件通道

注意

如果没有人在观察相应的事件,它将被静默丢弃。

驱动程序实现

为了支持计数器设备,驱动程序必须首先通过 counter_signal 结构体分配可用计数器信号。这些信号应存储为一个数组,并在计数器注册到系统之前,将其设置为已分配 counter_device 结构体的 signals 数组成员。

计数器计数可以通过 counter_count 结构体分配,相应的计数器信号关联(突触)通过 counter_synapse 结构体建立。关联的 counter_synapse 结构体存储为一个数组,并设置为相应 counter_count 结构体的 synapses 数组成员。这些 counter_count 结构体在计数器注册到系统之前,被设置为已分配 counter_device 结构体的 counts 数组成员。

必须向 counter_device 结构体提供驱动程序回调,以便与设备通信:读取和写入各种信号和计数,并分别设置和获取各种突触和计数的“动作模式”和“功能模式”。

一个 counter_device 结构体使用 counter_alloc() 分配,然后通过将其传递给 counter_add() 函数注册到系统,并通过将其传递给 counter_unregister 函数注销。这些函数还有设备管理版本:devm_counter_alloc()devm_counter_add()

结构体 counter_comp 结构体用于定义信号、突触和计数的计数器扩展。

“type”成员指定此扩展处理的高级数据类型(例如 BOOL、COUNT_DIRECTION 等)。然后,计数器设备驱动程序可以通过回调设置“*_read”和“*_write”联合成员,以使用原生 C 数据类型(即 u8、u64 等)处理该数据。

为驱动程序开发者提供了诸如 COUNTER_COMP_COUNT_U64 等便捷宏。特别是,驱动程序开发者应使用所提供的宏来定义标准计数器子系统属性,以维护用户空间的一致接口。例如,计数器设备驱动程序可以定义以下几个标准属性

struct counter_comp count_ext[] = {
        COUNTER_COMP_DIRECTION(count_direction_read),
        COUNTER_COMP_ENABLE(count_enable_read, count_enable_write),
        COUNTER_COMP_CEILING(count_ceiling_read, count_ceiling_write),
};

这使得查看、添加和修改此驱动程序支持的属性(“direction”、“enable”和“ceiling”)变得简单,并且可以在不陷入复杂的结构体大括号中也能维护此代码。

回调必须与相应组件或扩展预期的函数类型匹配。这些函数类型在 结构体 counter_comp 结构体中定义为“*_read”和“*_write”联合成员。

上述示例中提到的扩展的相应回调原型将是

int count_direction_read(struct counter_device *counter,
                         struct counter_count *count,
                         enum counter_count_direction *direction);
int count_enable_read(struct counter_device *counter,
                      struct counter_count *count, u8 *enable);
int count_enable_write(struct counter_device *counter,
                       struct counter_count *count, u8 enable);
int count_ceiling_read(struct counter_device *counter,
                       struct counter_count *count, u64 *ceiling);
int count_ceiling_write(struct counter_device *counter,
                        struct counter_count *count, u64 ceiling);

确定要创建的扩展类型是一个作用域问题。

  • 信号扩展是暴露信号特定信息/控制的属性。这些类型的属性将存在于 sysfs 中信号的目录下。

    例如,如果信号具有反转功能,可以有一个名为“invert”的信号扩展来切换该功能:/sys/bus/counter/devices/counterX/signalY/invert

  • 计数扩展是暴露计数特定信息/控制的属性。这些类型的属性将存在于 sysfs 中计数的目录下。

    例如,如果要暂停/取消暂停计数的更新,可以有一个名为“enable”的计数扩展来切换此功能:/sys/bus/counter/devices/counterX/countY/enable

  • 设备扩展是暴露不特定于某个计数或信号的信息/控制的属性。您可以在此处放置全局功能或其他杂项功能。

    例如,如果您的设备具有过温传感器,可以通过一个名为“error_overtemp”的设备扩展报告芯片过热:/sys/bus/counter/devices/counterX/error_overtemp

子系统架构

计数器驱动程序以原生方式(即 u8u64 等)传递和接收数据,共享计数器模块处理 sysfs 接口之间的转换。这保证了所有计数器驱动程序的标准用户空间接口,并通过通用设备驱动程序 ABI 启用了通用计数器字符设备接口。

计数器驱动程序如何向下传递计数值的高级视图示例如下。驱动程序回调首先注册到计数器核心组件,供计数器用户空间接口组件使用

Driver callbacks registration:
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                +----------------------------+
                | Counter device driver      |
                +----------------------------+
                | Processes data from device |
                +----------------------------+
                        |
                 -------------------
                / driver callbacks /
                -------------------
                        |
                        V
                +----------------------+
                | Counter core         |
                +----------------------+
                | Routes device driver |
                | callbacks to the     |
                | userspace interfaces |
                +----------------------+
                        |
                 -------------------
                / driver callbacks /
                -------------------
                        |
        +---------------+---------------+
        |                               |
        V                               V
+--------------------+          +---------------------+
| Counter sysfs      |          | Counter chrdev      |
+--------------------+          +---------------------+
| Translates to the  |          | Translates to the   |
| standard Counter   |          | standard Counter    |
| sysfs output       |          | character device    |
+--------------------+          +---------------------+

此后,数据可以在计数器设备驱动程序和计数器用户空间接口之间直接传输

Count data request:
~~~~~~~~~~~~~~~~~~~
                 ----------------------
                / Counter device       \
                +----------------------+
                | Count register: 0x28 |
                +----------------------+
                        |
                 -----------------
                / raw count data /
                -----------------
                        |
                        V
                +----------------------------+
                | Counter device driver      |
                +----------------------------+
                | Processes data from device |
                |----------------------------|
                | Type: u64                  |
                | Value: 42                  |
                +----------------------------+
                        |
                 ----------
                / u64     /
                ----------
                        |
        +---------------+---------------+
        |                               |
        V                               V
+--------------------+          +---------------------+
| Counter sysfs      |          | Counter chrdev      |
+--------------------+          +---------------------+
| Translates to the  |          | Translates to the   |
| standard Counter   |          | standard Counter    |
| sysfs output       |          | character device    |
|--------------------|          |---------------------|
| Type: const char * |          | Type: u64           |
| Value: "42"        |          | Value: 42           |
+--------------------+          +---------------------+
        |                               |
 ---------------                 -----------------------
/ const char * /                / struct counter_event /
---------------                 -----------------------
        |                               |
        |                               V
        |                       +-----------+
        |                       | read      |
        |                       +-----------+
        |                       \ Count: 42 /
        |                        -----------
        |
        V
+--------------------------------------------------+
| `/sys/bus/counter/devices/counterX/countY/count` |
+--------------------------------------------------+
\ Count: "42"                                      /
 --------------------------------------------------

涉及四个主要组件

计数器设备驱动程序

与硬件设备通信以读取/写入数据;例如,正交编码器、计时器等的计数器驱动程序。

计数器核心

将计数器设备驱动程序注册到系统,以便在用户空间交互期间调用相应的回调。

计数器 sysfs

将计数器数据转换为标准计数器 sysfs 接口格式,反之亦然。

有关可用通用计数器接口 sysfs 属性的详细分类,请参阅 ABI 文件 testing/sysfs-bus-counter

计数器字符设备

将计数器事件转换为标准计数器字符设备;数据通过标准字符设备读取调用传输,而计数器事件通过 ioctl 调用配置。

Sysfs 接口

通用计数器接口生成了几个 sysfs 属性,它们位于 /sys/bus/counter/devices/counterX 目录下,其中 X 是相应的计数器设备 ID。有关每个通用计数器接口 sysfs 属性的详细信息,请参阅 ABI 文件 testing/sysfs-bus-counter

通过这些 sysfs 属性,程序和脚本可以与相应计数器设备的通用计数器范例中的计数、信号和突触进行交互。

计数器字符设备

计数器字符设备节点在 /dev 目录下创建为 counterX,其中 X 是相应的计数器设备 ID。标准计数器数据类型的定义通过用户空间 include/uapi/linux/counter.h 文件公开。

计数器事件

计数器设备驱动程序可以通过利用 counter_push_event 函数来支持计数器事件

void counter_push_event(struct counter_device *const counter, const u8 event,
                        const u8 channel);

事件 ID 由 event 参数指定;事件通道 ID 由 channel 参数指定。当调用此函数时,将收集与相应事件关联的计数器数据,并为每个数据生成一个 结构体 counter_event 并推送到用户空间。

计数器事件可以由用户配置,以报告各种感兴趣的计数器数据。这可以被概念化为要执行的计数器组件读取调用列表。例如

COUNTER_EVENT_OVERFLOW

COUNTER_EVENT_INDEX

通道 0

通道 0

  • 计数 0

  • 计数 1

  • 信号 3

  • 计数 4 扩展 2

  • 信号 5 扩展 0

  • 信号 0

  • 信号 0 扩展 0

  • 扩展 4

通道 1

  • 信号 4

  • 信号 4 扩展 0

  • 计数 7

例如,当调用 counter_push_event(counter, COUNTER_EVENT_INDEX, 1) 时,它将遍历 COUNTER_EVENT_INDEX 事件通道 1 的列表,并执行信号 4、信号 4 扩展 0 和计数 7 的读取回调——为每个数据返回的数据作为 结构体 counter_event 推送到一个 kfifo 中,用户空间可以通过对相应字符设备节点执行标准读取操作来检索它。

用户空间

用户空间应用程序可以通过对计数器字符设备节点执行 ioctl 操作来配置计数器事件。以下 ioctl 代码由用户空间 linux/counter.h 头文件支持和提供

  • COUNTER_ADD_WATCH_IOCTL

  • COUNTER_ENABLE_EVENTS_IOCTL

  • COUNTER_DISABLE_EVENTS_IOCTL

为了配置事件以收集计数器数据,用户首先使用相关事件 ID、事件通道 ID 以及要读取的所需计数器组件的信息填充 结构体 counter_watch,然后通过 COUNTER_ADD_WATCH_IOCTL ioctl 命令传递它。

请注意,通过将 component.type 成员设置为 COUNTER_COMPONENT_NONE,可以观察事件而不收集计数器数据。在此配置下,计数器字符设备将简单地填充这些相应 结构体 counter_event 元素的事件时间戳,并忽略组件值。

COUNTER_ADD_WATCH_IOCTL 命令将缓冲这些计数器观察。准备就绪后,可以使用 COUNTER_ENABLE_EVENTS_IOCTL ioctl 命令来激活这些计数器观察。

用户空间应用程序随后可以对计数器字符设备节点执行 read 操作(可选地首先调用 poll),以检索带有所需数据的 结构体 counter_event 元素。