通用计数器接口

简介

计数器设备在各种行业中普遍存在。这些设备的普遍存在需要一个通用的接口和交互及公开标准。此驱动程序 API 尝试通过引入一个用于消费的通用计数器接口来解决现有计数器设备驱动程序中发现的重复代码问题。通用计数器接口使驱动程序能够支持和公开计数器设备中存在的一组通用组件和功能。

原理

计数器设备的设计可能差异很大,但是无论某些设备是正交编码器计数器还是计数器,所有计数器设备都由一组核心组件组成。这组核心组件由所有计数器设备共享,构成了通用计数器接口的本质。

计数器有三个核心组件

  • 信号:要由计数器评估的数据流。

  • 突触:信号与计数以及评估触发器的关联。

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

信号

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

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

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

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

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

突触

突触表示信号与计数的关联。信号数据会影响各自的计数数据,而突触表示这种关系。

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

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

  • 上升沿:低电平状态转换为高电平状态。

  • 下降沿:高电平状态转换为低电平状态。

  • 两个沿:任何状态转换。

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

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

计数

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

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

  • 增加:累积计数递增。

  • 减少:累积计数递减。

  • 脉冲方向:信号 A 上的上升沿更新相应的计数。信号 B 的输入电平确定方向。

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

    • 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 头文件在其代码中使用通用计数器接口。此头文件提供了一些核心数据结构、函数原型和用于定义计数器设备的宏。

struct 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);
    };
};

成员

type

计数器组件数据类型

name

设备特定的组件名称

priv

组件相关数据

{unnamed_union}

匿名

action_read

Synapse(突触)动作模式读取回调。相应Synapse动作模式的读取值应通过 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

Synapse(突触)动作模式写入回调。相应Synapse动作模式的写入值通过 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 参数传递。

struct counter_signal

计数器信号节点

定义:

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

成员

id

用于标识信号的唯一 ID

name

设备特定的信号名称

ext

可选的信号扩展数组

num_ext

ext 中指定的信号扩展的数量

struct counter_synapse

计数器突触节点

定义:

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

成员

actions_list

可用动作模式的数组

num_actions

actions_list 中指定的动作模式的数量

signal

指向关联信号的指针

struct 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

name

设备特定的计数名称

functions_list

可用功能模式的数组

num_functions

functions_list 中指定的功能模式的数量

synapses

用于初始化的突触数组

num_synapses

synapses 中指定的突触数量

ext

可选的计数扩展数组

num_ext

ext 中指定的计数扩展的数量

struct counter_event_node

计数器事件节点

定义:

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

成员

l

当前正在监视的计数器事件列表

event

触发的事件

channel

事件通道

comp_list

触发事件时要监视的组件列表

struct 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);
};

成员

signal_read

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

count_read

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

count_write

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

function_read

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

function_write

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

action_read

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

action_write

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

events_configure

用于配置事件的可选写入回调。可以通过计数器参数的 events_list 成员访问 struct counter_event_node 的列表。

watch_validate

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

struct 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;
};

成员

name

设备的名称

parent

提供计数器的可选父设备

ops

来自驱动程序的回调

signals

信号数组

num_signals

signals 中指定的信号数量

counts

计数数组

num_counts

counts 中指定的计数数量

ext

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

num_ext

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

dev

内部设备结构

chrdev

内部字符设备结构

events_list

当前正在监视的计数器事件列表

events_list_lock

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

next_events_list

下一个正在监视的计数器事件列表

n_events_list_lock

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

events

检测到的计数器事件队列

events_wait

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

events_in_lock

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

events_out_lock

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

ops_exist_lock

用于防止在移除期间使用的锁

void *counter_priv(const struct counter_device *const counter)

访问计数器设备的私有数据

参数

const struct counter_device *const counter

计数器设备

描述

获取计数器设备的私有数据

struct counter_device *counter_alloc(size_t sizeof_priv)

分配一个 counter_device

参数

size_t sizeof_priv

驱动程序私有数据的大小

描述

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

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

int counter_add(struct counter_device *counter)

完成计数器的注册

参数

struct counter_device *counter

要添加的计数器

描述

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

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

void counter_unregister(struct counter_device *const counter)

从系统中注销计数器

参数

struct counter_device *const counter

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

描述

计数器将从系统中注销。

struct counter_device *devm_counter_alloc(struct device *dev, size_t sizeof_priv)

分配一个 counter_device

参数

struct device *dev

用于注册释放回调的设备

size_t sizeof_priv

驱动程序私有数据的大小

描述

这是 counter_add() 的设备管理版本。它注册一个清理回调来处理调用 counter_put()。

int devm_counter_add(struct device *dev, struct counter_device *const counter)

完成计数器的注册

参数

struct device *dev

用于注册释放回调的设备

struct counter_device *const counter

要添加的计数器

描述

这是 counter_add() 的设备管理版本。它注册一个清理回调来处理调用 counter_unregister()

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

将事件排队以供用户空间读取

参数

struct 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_alloc() 分配 counter_device 结构,然后通过将其传递给 counter_add() 函数将其注册到系统,并通过将其传递给 counter_unregister 函数来注销它。这些函数有设备管理变体:devm_counter_alloc()devm_counter_add()

struct 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),
};

这使得查看、添加和修改此驱动程序支持的属性(“方向”、“启用”和“上限”)变得简单,并且可以在不迷失在结构大括号网络中的情况下维护此代码。

回调必须与相应组件或扩展的预期函数类型匹配。这些函数类型在 struct 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 属性的详细分解,请参阅 Documentation/ABI/testing/sysfs-bus-counter 文件。

计数器字符设备

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

Sysfs 接口

通用计数器接口生成多个 sysfs 属性,这些属性位于 /sys/bus/counter/devices/counterX 目录下,其中 X 是相应的计数器设备 ID。有关每个通用计数器接口 sysfs 属性的详细信息,请参阅 Documentation/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 参数指定。调用此函数时,将收集与相应事件关联的计数器数据,并为每个数据生成一个 struct 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 的读取回调函数 -- 为每个返回的数据将作为 struct counter_event 推送到 kfifo,用户空间可以通过对相应字符设备节点执行标准读取操作来检索该数据。

用户空间

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

  • COUNTER_ADD_WATCH_IOCTL

  • COUNTER_ENABLE_EVENTS_IOCTL

  • COUNTER_DISABLE_EVENTS_IOCTL

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

请注意,可以通过将 component.type 成员设置为 COUNTER_COMPONENT_NONE 来在不收集计数器数据的情况下监视事件。使用此配置,计数器字符设备将只填充那些相应的 struct counter_event 元素中的事件时间戳,并忽略组件值。

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

然后,用户空间应用程序可以对计数器字符设备节点执行 read 操作(可以选择先调用 poll),以检索包含所需数据的 struct counter_event 元素。