EtherCAT主站IGH-- 1 -- IGH之cdev.h/c文件解析

EtherCAT主站IGH-- 1 -- IGH之cdev.h/c文件解析

  • [0 预览](#0 预览)
  • [一 该文件功能](#一 该文件功能)
    • [`cdev.c` 文件功能函数预览](#cdev.c 文件功能函数预览)
  • [二 函数功能介绍](#二 函数功能介绍)
    • [`cdev.c` 中主要函数的作用](#cdev.c 中主要函数的作用)
        • [1. `ec_cdev_init`](#1. ec_cdev_init)
        • [2. `ec_cdev_clear`](#2. ec_cdev_clear)
        • [3. `eccdev_open`](#3. eccdev_open)
        • [4. `eccdev_release`](#4. eccdev_release)
        • [5. `eccdev_ioctl`](#5. eccdev_ioctl)
        • [6. `eccdev_mmap`](#6. eccdev_mmap)
        • [7. `eccdev_vma_fault`](#7. eccdev_vma_fault)
        • [8. `eccdev_vma_nopage`](#8. eccdev_vma_nopage)
  • [三 h文件翻译](#三 h文件翻译)
  • [四 c文件翻译](#四 c文件翻译)
  • 该文档修改记录:
  • 总结

0 预览

一 该文件功能

该文件定义了 EtherCAT 主站的字符设备驱动程序。EtherCAT 是一种实时以太网通信标准,广泛用于工业自动化控制系统。字符设备驱动程序允许用户空间程序与内核空间的 EtherCAT 主站进行交互,通过字符设备文件对 EtherCAT 主站进行操作。

该文件实现了 EtherCAT 主控设备的字符设备驱动程序。此驱动程序允许用户空间程序与 EtherCAT 主设备进行交互,主要功能包括打开设备、释放设备、处理 IO 控制命令、内存映射以及处理虚拟内存区域的缺页错误。

cdev.c 文件功能函数预览

函数 功能和用途 使用场景
ec_cdev_init 初始化 EtherCAT 主控设备的字符设备。 在应用程序启动时进行 EtherCAT 主控设备的初始化。
ec_cdev_clear 清理 EtherCAT 主控设备的字符设备。 在应用程序关闭或设备不再使用时进行清理操作。
eccdev_open 打开字符设备文件,初始化私有数据结构。 在用户空间程序需要访问设备时调用。
eccdev_release 释放字符设备文件,清理私有数据结构。 在用户空间程序关闭设备文件时调用。
eccdev_ioctl 处理来自用户空间的 IO 控制命令。 当用户空间程序发出 ioctl() 命令时调用。
eccdev_mmap 处理设备文件的内存映射请求。 当用户空间程序请求内存映射时调用。
eccdev_vma_fault 处理虚拟内存区域的缺页错误。 当内核处理缺页错误时调用(内核版本 >= 2.6.23)。
eccdev_vma_nopage 处理虚拟内存区域的首次访问缺页错误。 当内核处理首次访问缺页错误时调用(内核版本 < 2.6.23)。

二 函数功能介绍

cdev.c 中主要函数的作用

1. ec_cdev_init
c 复制代码
int ec_cdev_init(ec_cdev_t *cdev, ec_master_t *master, dev_t dev_num)
{
    int ret;

    cdev->master = master;

    cdev_init(&cdev->cdev, &eccdev_fops);
    cdev->cdev.owner = THIS_MODULE;

    ret = cdev_add(&cdev->cdev, MKDEV(MAJOR(dev_num), master->index), 1);
    if (ret) {
        EC_MASTER_ERR(master, "Failed to add character device!\n");
    }

    return ret;
}
  • 功能和用途:初始化 EtherCAT 主控设备的字符设备。
  • 使用场景:在应用程序启动时进行 EtherCAT 主控设备的初始化。
2. ec_cdev_clear
c 复制代码
void ec_cdev_clear(ec_cdev_t *cdev)
{
    cdev_del(&cdev->cdev);
}
  • 功能和用途:清理 EtherCAT 主控设备的字符设备。
  • 使用场景:在应用程序关闭或设备不再使用时进行清理操作。
3. eccdev_open
c 复制代码
int eccdev_open(struct inode *inode, struct file *filp)
{
    ec_cdev_t *cdev = container_of(inode->i_cdev, ec_cdev_t, cdev);
    ec_cdev_priv_t *priv;

    priv = kmalloc(sizeof(ec_cdev_priv_t), GFP_KERNEL);
    if (!priv) {
        EC_MASTER_ERR(cdev->master, "Failed to allocate memory for private data structure.\n");
        return -ENOMEM;
    }

    priv->cdev = cdev;
    priv->ctx.writable = (filp->f_mode & FMODE_WRITE) != 0;
    priv->ctx.requested = 0;
    priv->ctx.process_data = NULL;
    priv->ctx.process_data_size = 0;

    filp->private_data = priv;

#if DEBUG
    EC_MASTER_DBG(cdev->master, 0, "File opened.\n");
#endif
    return 0;
}
  • 功能和用途:打开字符设备文件,初始化私有数据结构。
  • 使用场景:在用户空间程序需要访问设备时调用。
4. eccdev_release
c 复制代码
int eccdev_release(struct inode *inode, struct file *filp)
{
    ec_cdev_priv_t *priv = (ec_cdev_priv_t *) filp->private_data;
    ec_master_t *master = priv->cdev->master;

    if (priv->ctx.requested) {
        ecrt_release_master(master);
    }

    if (priv->ctx.process_data) {
        vfree(priv->ctx.process_data);
    }

#if DEBUG
    EC_MASTER_DBG(master, 0, "File closed.\n");
#endif

    kfree(priv);
    return 0;
}
  • 功能和用途:释放字符设备文件,清理私有数据结构。
  • 使用场景:在用户空间程序关闭设备文件时调用。
5. eccdev_ioctl
c 复制代码
long eccdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    ec_cdev_priv_t *priv = (ec_cdev_priv_t *) filp->private_data;

#if DEBUG
    EC_MASTER_DBG(priv->cdev->master, 0,
            "ioctl(filp = 0x%p, cmd = 0x%08x (0x%02x), arg = 0x%lx)\n",
            filp, cmd, _IOC_NR(cmd), arg);
#endif

    return ec_ioctl(priv->cdev->master, &priv->ctx, cmd, (void __user *) arg);
}
  • 功能和用途:处理来自用户空间的 IO 控制命令。
  • 使用场景:当用户空间程序发出 ioctl() 命令时调用。
6. eccdev_mmap
c 复制代码
int eccdev_mmap(struct file *filp, struct vm_area_struct *vma)
{
    ec_cdev_priv_t *priv = (ec_cdev_priv_t *) filp->private_data;

    EC_MASTER_DBG(priv->cdev->master, 1, "mmap()\n");

    vma->vm_ops = &eccdev_vm_ops;
    vma->vm_flags |= VM_DONTDUMP; /* Pages will not be swapped out */
    vma->vm_private_data = priv;

    return 0;
}
  • 功能和用途:处理设备文件的内存映射请求。
  • 使用场景:当用户空间程序请求内存映射时调用。
7. eccdev_vma_fault
c 复制代码
#if LINUX_VERSION_CODE >= PAGE_FAULT_VERSION

static
#if LINUX_VERSION_CODE > KERNEL_VERSION(5, 0, 0)
vm_fault_t
#else
int
#endif
eccdev_vma_fault(
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 11, 0)
        struct vm_area_struct *vma,
#endif
        struct vm_fault *vmf)
{
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
    struct vm_area_struct *vma = vmf->vma;
#endif
    unsigned long offset = vmf->pgoff << PAGE_SHIFT;
    ec_cdev_priv_t *priv = (ec_cdev_priv_t *) vma->vm_private_data;
    struct page *page;

    if (offset >= priv->ctx.process_data_size) {
        return VM_FAULT_SIGBUS;
    }

    page = vmalloc_to_page(priv->ctx.process_data + offset);
    if (!page) {
        return VM_FAULT_SIGBUS;
    }

    get_page(page);
    vmf->page = page;

    EC_MASTER_DBG(priv->cdev->master, 1, "Vma fault, virtual_address = %p,"
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(4,10,0))
            " offset = %lu, page = %p\n", (void*)vmf->address, offset, page);
#else
            " offset = %lu, page = %p\n", vmf->virtual_address, offset, page);
#endif

    return 0;
}
#else
  • 功能和用途:处理虚拟内存区域的缺页错误。
  • 使用场景:当内核处理缺页错误时调用(内核版本 >= 2.6.23)。
8. eccdev_vma_nopage
c 复制代码
struct page *eccdev_vma_nopage(
        struct vm_area_struct *vma,
        unsigned long address,
        int *type)
{
    unsigned long offset;
    struct page *page = NOPAGE_SIGBUS;
    ec_cdev_priv_t *priv = (ec_cdev_priv_t *) vma->vm_private_data;
    ec_master_t *master = priv->cdev->master;

    offset = (address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT);

    if (offset >= priv->ctx.process_data_size)
        return NOPAGE_SIGBUS;

    page = vmalloc_to_page(priv->ctx.process_data + offset);

    EC_MASTER_DBG(master, 1, "Nopage fault vma, address = %#lx,"
            " offset = %#lx, page = %p\n", address, offset, page);

    get_page(page);
    if (type)
        *type = VM_FAULT_MINOR;

    return page;
}

#endif
  • 功能和用途:处理虚拟内存区域的首次访问缺页错误。
  • **使用场景

**:当内核处理首次访问缺页错误时调用(内核版本 < 2.6.23)。

三 h文件翻译

c 复制代码
/******************************************************************************\
 *
 *  $Id$
 *
 *  版权所有 (C) 2006-2008 Florian Pose, Ingenieurgemeinschaft IgH
 *
 *  本文件是 IgH EtherCAT 主站的一部分。
 *
 *  IgH EtherCAT 主站是免费软件;您可以根据自由软件基金会发布的 GNU 通用公共许可证第2版的条款重新分发和/或修改它。
 *
 *  IgH EtherCAT 主站的分发目的是希望它有用,但没有任何保证;甚至没有适销性或特定用途适用性的隐含保证。详情请参阅 GNU 通用公共许可证。
 *
 *  您应该已经收到了与 IgH EtherCAT 主站一起提供的 GNU 通用公共许可证的副本;如果没有,请写信给自由软件基金会,地址是:51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA。
 *
 *  ---
 *
 *  上述许可证仅适用于源代码。使用 EtherCAT 技术和品牌仅允许在遵守 Beckhoff Automation GmbH 的工业产权和类似权利的情况下使用。
 *****************************************************************************/

/**
   \file
   EtherCAT 主站字符设备。
*/

/*****************************************************************************/

#ifndef __EC_CDEV_H__
#define __EC_CDEV_H__

#include <linux/fs.h>
#include <linux/cdev.h>

#include "globals.h"

/*****************************************************************************/

/** EtherCAT 主站字符设备。
*/
typedef struct {
    ec_master_t *master; /**< 拥有该设备的主站。 */
    struct cdev cdev; /**< 字符设备。 */
} ec_cdev_t;

/*****************************************************************************/

int ec_cdev_init(ec_cdev_t *, ec_master_t *, dev_t);
void ec_cdev_clear(ec_cdev_t *);

/*****************************************************************************/

#endif

四 c文件翻译

c 复制代码
/******************************************************************************\
 *
 *  $Id$
 *
 *  版权所有 (C) 2006-2012 Florian Pose, Ingenieurgemeinschaft IgH
 *
 *  本文件是 IgH EtherCAT 主站的一部分。
 *
 *  IgH EtherCAT 主站是免费软件;您可以根据自由软件基金会发布的 GNU 通用公共许可证第2版的条款重新分发和/或修改它。
 *
 *  IgH EtherCAT 主站的分发目的是希望它有用,但没有任何保证;甚至没有适销性或特定用途适用性的隐含保证。详情请参阅 GNU 通用公共许可证。
 *
 *  您应该已经收到了与 IgH EtherCAT 主站一起提供的 GNU 通用公共许可证的副本;如果没有,请写信给自由软件基金会,地址是:51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA。
 *
 *  ---
 *
 *  上述许可证仅适用于源代码。使用 EtherCAT 技术和品牌仅允许在遵守 Beckhoff Automation GmbH 的工业产权和类似权利的情况下使用。
 *****************************************************************************/

/**
   \file
   EtherCAT 主站字符设备。
*/

/*****************************************************************************/

#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>

#include "cdev.h"
#include "master.h"
#include "slave_config.h"
#include "voe_handler.h"
#include "ethernet.h"
#include "ioctl.h"

/** 设置为 1 以启用设备操作调试。
 */
#define DEBUG 0

/*****************************************************************************/

static int eccdev_open(struct inode *, struct file *);
static int eccdev_release(struct inode *, struct file *);
static long eccdev_ioctl(struct file *, unsigned int, unsigned long);
static int eccdev_mmap(struct file *, struct vm_area_struct *);

/** 这是 .fault 成员在 vm_operations_struct 中可用的内核版本。
 */
#define PAGE_FAULT_VERSION KERNEL_VERSION(2, 6, 23)

#if LINUX_VERSION_CODE >= PAGE_FAULT_VERSION
static
#if LINUX_VERSION_CODE > KERNEL版本 (5, 0, 0)
vm_fault_t
#else
int
#endif
eccdev_vma_fault(
#if LINUX_VERSION_CODE < KERNEL版本 (4, 11, 0)
        struct vm_area_struct *, /**< 虚拟内存区域。 */
#endif
        struct vm_fault *); /**< 错误数据。 */
#else
static struct page *eccdev_vma_nopage(
        struct vm_area_struct *, unsigned long, int *);
#endif

/*****************************************************************************/

/** EtherCAT 字符设备的文件操作回调。
 */
static struct file_operations eccdev_fops = {
    .owner          = THIS_MODULE,
    .open           = eccdev_open,
    .release        = eccdev_release,
    .unlocked_ioctl = eccdev_ioctl,
    .mmap           = eccdev_mmap
};

/** 用 ecdevc_mmap() 检索到的虚拟内存区域的回调。
 */
struct vm_operations_struct eccdev_vm_ops = {
#if LINUX_VERSION_CODE >= PAGE_FAULT_VERSION
    .fault = eccdev_vma_fault
#else
    .nopage = eccdev_vma_nopage
#endif
};

/*****************************************************************************/

/** 文件句柄的私有数据结构。
 */
typedef struct {
    ec_cdev_t *cdev; /**< 字符设备。 */
    ec_ioctl_context_t ctx; /**< 上下文。 */
} ec_cdev_priv_t;

/*****************************************************************************/

/** 构造函数。
 *
 * \return 成功时返回0,否则返回<0
 */
int ec_cdev_init(
        ec_cdev_t *cdev, /**< EtherCAT 主站字符设备。 */
        ec_master_t *master, /**< 父主站。 */
        dev_t dev_num /**< 设备号。 */
        )
{
    int ret;

    cdev->master = master;

    cdev_init(&cdev->cdev, &eccdev_fops);
    cdev->cdev.owner = THIS_MODULE;

    ret = cdev_add(&cdev->cdev,
            MKDEV(MAJOR(dev_num), master->index), 1);
    if (ret) {
        EC_MASTER_ERR(master, "添加字符设备失败!\n");
    }

    return ret;
}

/*****************************************************************************/

/** 析构函数。
 */
void ec_cdev_clear(ec_cdev_t *cdev /**< EtherCAT XML 设备 */)
{
    cdev_del(&cdev->cdev);
}

/******************************************************************************
 * 文件操作
 *****************************************************************************/

/** 打开字符设备时调用。
 */
int eccdev_open(struct inode *inode, struct file *filp)
{
    ec_cdev_t *cdev = container_of(inode->i_cdev, ec_cdev_t, cdev);
    ec_cdev_priv_t *priv;

    priv = kmalloc(sizeof(ec_cdev_priv_t), GFP_KERNEL);
    if (!priv) {
        EC_MASTER_ERR(cdev->master,
                "分配私有数据结构内存失败。\n");
        return -ENOMEM;
    }

    priv->cdev = cdev;
    priv->ctx.writable = (filp->f_mode & FMODE_WRITE) != 0;
    priv->ctx.requested = 0;
    priv->ctx.process_data = NULL;
    priv->ctx.process_data_size = 0;

    filp->private_data = priv;

#if DEBUG
    EC_MASTER_DBG(cdev->master, 0, "文件已打开。\n");
#endif
    return 0;
}

/*****************************************************************************/

/** 关闭字符设备时调用。
 */
int eccdev_release(struct inode *inode, struct file *filp)
{
    ec_cdev_priv_t *priv = (ec_cdev_priv_t *) filp->private_data;
    ec_master_t *master = priv->cdev->master;

    if (priv->ctx.requested) {
        ecrt_release_master(master);
    }

    if (priv->ctx.process_data) {
        vfree(priv->ctx.process_data);
    }

#if DEBUG
    EC_MASTER_DBG(master, 0, "文件已关闭。\n");
#endif

    kfree(priv);
    return 0;
}

/*****************************************************************************/

/** 发出 ioctl() 命令时调用。
 */
long eccdev_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
    ec_cdev_priv_t *priv = (ec_cdev_priv_t *) filp->private_data;

#if DEBUG
    EC_MASTER_DBG(priv->cdev->master, 0,
            "ioctl(filp = 0x%p, cmd = 0x%08x (0x%02x), arg = 0x%lx)\n",
            filp, cmd, _IOC_NR(cmd), arg);
#endif

    return ec_ioctl(priv->cdev->master, &priv->ctx, cmd, (void __user *) arg);
}

/*****************************************************************************/

#ifndef VM_DONTDUMP
/** VM_RESERVED 在 3.7 中消失。
 */
#define VM_DONTDUMP VM_RESERVED
#endif

/** EtherCAT 字符设备的内存映射回调。
 *
 * 实际映射将在虚拟内存区域的 eccdev_vma_nopage() 回调中完成。
 *
 * \return 总是返回零(成功)。
 */
int eccdev_mmap(
        struct file *filp,
        struct vm_area_struct *vma
        )
{
    ec_cdev_priv_t *priv = (ec_cdev_priv_t *) filp->private_data;

    EC_MASTER_DBG(priv->cdev->master, 1, "mmap()\n");

    vma->vm_ops = &eccdev_vm_ops;
    vma->vm_flags |= VM_DONTDUMP; /* 页面不会被换出 */
    vma->vm_private_data = priv;

    return 0;
}

/*****************************************************************************/

#if LINUX_VERSION_CODE >= PAGE_FAULT_VERSION

/** 虚拟内存区域的页面错误回调。
 *
 * 在首次访问使用 ecdev_mmap() 检索到的虚拟内存区域时调用。
 *
 * \return 成功时返回零,否则返回负错误代码。
 */
static
#if LINUX_VERSION_CODE > KERNEL版本 (5, 0, 0)
vm_fault_t
#else
int
#endif
eccdev_vma_fault(
#if LINUX_VERSION_CODE < KERNEL版本 (4, 11, 0)
        struct vm_area_struct *vma, /**< 虚拟内存区域。 */
#endif
        struct vm_fault *vmf /**< 错误数据。 */
        )
{
#if LINUX版本_CODE >= KERNEL版本 (4, 11, 0)
    struct vm_area_struct *vma = vmf->vma;
#endif
    unsigned long offset = vmf->pgoff << PAGE_SHIFT;
    ec_cdev_priv_t *priv = (ec_cdev_priv_t *) vma->vm_private_data;
    struct page *page;

    if (offset >= priv->ctx.process_data_size) {
        return VM_FAULT_SIGBUS;
    }

    page = vmalloc_to_page(priv->ctx.process_data + offset);
    if (!page) {
        return VM_FAULT_SIGBUS;
    }

    get_page(page);
    vmf->page = page;

    EC_MASTER_DBG(priv->cdev->master, 1, "虚拟内存区域错误,虚拟地址 = %p,"
#if (LINUX_VERSION_CODE >= KERNEL版本 (4,10,0))
            " 偏移量 = %lu, 页面 = %p\n", (void*)vmf->address, offset, page);
#else
            " 偏移量 = %lu, 页面 = %p\n", vmf->virtual_address, offset, page);
#endif

    return 0;
}

#else

/** 虚拟内存区域的无页回调。
 *
 * 在首次访问使用 ecdev_mmap() 检索到的虚拟内存区域时调用。
 */
struct page *eccdev_vma_nopage(
        struct vm_area_struct *vma, /**< 内核初始化的虚拟内存区域。 */
        unsigned long address, /**< 请求的虚拟地址。 */
        int *type /**< 类型输出参数。 */
        )
{
    unsigned long offset;
    struct page *page = NOPAGE_SIGBUS;
    ec_cdev_priv_t *priv = (ec_cdev_priv_t *) vma->vm_private_data;
    ec_master_t *master = priv->cdev->master;

    offset = (address - vma->vm_start) + (vma->vm_pgoff << PAGE_SHIFT);

    if (offset >= priv->ctx.process_data_size)
        return NOPAGE_SIGBUS;

    page = vmalloc_to_page(priv->ctx.process_data + offset);

    EC_MASTER_DBG(master, 1, "无页错误 vma,地址 = %#lx,"
            " 偏移量 = %#lx, 页面 = %p\n", address, offset, page);

    get_page(page);
    if (type)
        *type = VM_FAULT_MINOR;

    return page;
}

#endif

/*****************************************************************************/

该文档修改记录:

修改时间 修改说明
2024年6月29日 EtherCAT主站IGH 该 文件解析

总结

以上就是EtherCAT主站IGH文件解析的内容。

有不明白的地方欢迎留言;有建议欢迎留言,我后面编写文档好改进。
创作不容,如果文档对您有帮助,记得给个赞。

相关推荐
真果粒wrdms1 小时前
【sqlite3】联系人管理系统
linux·c语言·数据库·经验分享·笔记·sqlite
托马斯-木1 小时前
const与#define 比较
c语言·内存
小小怪下士的编程小屋1 小时前
FreeRTOS的任务间通信
c语言·stm32·单片机·嵌入式硬件
Q_hd2 小时前
【嵌入式】探索嵌入式世界:在ARM上构建俄罗斯方块游戏的奇妙之旅
linux·c语言·arm开发·游戏
9毫米的幻想3 小时前
【C语言】—— 文件操作(上)
c语言·开发语言·学习
DHDN——19974 小时前
C语言 实现socket服务器客户端通信
服务器·c语言·网络
禁默11 小时前
c语言回顾-内存操作函数
c语言·开发语言·程序人生
人才程序员12 小时前
【C语言】函数无参数有返回值、有参数无返回值、有参数有返回值
linux·c语言·开发语言·c++·stm32·单片机·c
攻城狮7号14 小时前
【第五节】C/C++数据结构之图
c语言·数据结构·c++
XiaoCCCcCCccCcccC14 小时前
C语言中的动态内存管理
c语言·开发语言