Linux UIO驱动框架(一)

2023-05-16

文章目录

  • Linux UIO驱动框架
    • 1. 什么是uio驱动框架
    • 2. uio驱动框架使用
    • 3. uio驱动框架实现原理

Linux UIO驱动框架

1. 什么是uio驱动框架

uio全称为用户空间IO(Userspace I/O),是一种在用户空间编写设备驱动程序的框架。一般而言,Linux的驱动是运行在内核空间的,即设备驱动本身是作为内核源码的一部分进行编译的,这样的驱动程序能够访问系统的所有资源,但是稍有处理不当就容易引起内核奔溃。而uio驱动是在用户空间就行开发的,其本质就是一个应用开发,因此这类驱动就与内核空间隔离开,即使驱动奔溃也不会影响到整个系统。

对于驱动程序来说,一般有两个重要的功能:

  • 读写某一段地址空间
  • 响应外部中断

采用统一编址的处理器,实际上就是将处理器能够访问的地址空间划分一部分出来作为外设寄存器的地址,处理器在访问外设寄存器的时候,就可以通过地址来操作。响应外设中断是驱动程序必须的功能,这个就没什么好说的。

uio驱动就可以完成上述两个主要功能,那么这种应用场景在什么地方呢?我目前接触的应用场景就是使用zynq处理器。zynq处理器内部集成了一个arm核和fpag核,叫做ps端和pl端。这种架构增加了设计的灵活性,因为fpga可以作为一个外设挂载在arm的地址空间上的,想要实现什么外设功能fpag开发完成后,然后向arm提供这个外设寄存器的地址和中断就行,使用uio驱动就可以简化这类设备驱动的开发。

2. uio驱动框架使用

下图为uio驱动框架的整体框图,在内核空间uio驱动的内核代码部分会在/dev目录下生成uio设备节点,节点名称为/dev/uiox,其中x从0开始递增。同时,在sysfs文件下还会生成与uio设备相关的属性,读写这些属性文件就可以获得该uio设备的相关信息。
在这里插入图片描述
关于具体的uio设备操作方法,本文不会涉及后续文章进行补充。本文主要介绍两个操作函数readwrite,因为这两个函数可以帮助理解uio内核驱动框架的实现。

操作uio设备的简单流程为:

  • 调用open函数打开uio设备节点/dev/uio0,得到文件描述符fd
  • 调用write向uio设备写1打开中断,写0会关闭中断
  • 调用read函数读uio设备,此时会阻塞等待uio设备中断,若产生中断则会继续执行

上面三个步骤就实现了uio设备的中断响应功能,下面将将介绍内核实现这些过程的原理,然后就能理解uio设备框架到底做了什么。

3. uio驱动框架实现原理

设备驱动模型整体上可分为三层:

  • 应用层:应用程序调用设备操作接口openreadwrite等来操作设备
  • 核心层:实现字符设备注册那一套操作
  • 驱动相关层:定义设备对象、实现设备操作函数、调用设备对象的注册函数进行注册

在应用层,就是调用驱动框架提供的设备标准操作函数。核心层为驱动框架的主要部分,主要是字符设备注册的那一套流程。驱动相关层就是具体设备的的驱动了,开发也较为简单只要实现相关的操作函数,然后调用注册接口进行注册就完成了驱动开发。

要想分析内核驱动框架,首先需要找到uio内核驱动代码文件是哪一个,这个时候可以去设备树中找到uio设备节点,然后根据compatible的值在驱动代码文件中进行搜索,设备树中uio设备节点列举如下:

/ {
	chosen {
		bootargs = "console=ttyPS0,115200 earlyprintk uio_pdrv_genirq.of_id=generic-uio";
		stdout-path = "serial0:115200n8";
	};
	uio0@0 {
		compatible = "generic-uio";
		status = "okay";
		interrupt-parent = <&intc>;
		interrupts = <0 31 1>;
		reg = <0x43c00000 0x140000>;
	};
};

uio0是自己在设备树中添加的节点,本人是在zynq上进行uio驱动开发的,其实采用什么平台都无所谓,原理都一样,你也可以在树莓派的设备树中添加上述节点。compatible表明这个节点要匹配的驱动,interrupts表明这个节点使用的中断信息<中断类型 中断号 中断触发方式>,在arm架构中通用中断控制器(gic)有三种类型的中断,分别为SPI、SGI和PPI。reg属性则表明了这个uio设备使用的地址空间范围<内存起始地址 地址空间长度>。如果直接在内核中搜索"generic-uio"是搜不到任何结果的,这是因为uio驱动的compatible属性是使用内核参数传递进去的,如节点chosen

bootargs = "uio_pdrv_genirq.of_id=generic-uio"

上述的内核参数就是向uio驱动传递的compatible值,打开驱动源码uio_pdrv_genirq.c有如下代码:

#ifdef CONFIG_OF
static struct of_device_id uio_of_genirq_match[] = {
	{ /* This is filled with module_parm */ },
	{ /* Sentinel */ },
};
MODULE_DEVICE_TABLE(of, uio_of_genirq_match);
module_param_string(of_id, uio_of_genirq_match[0].compatible, 128, 0);
MODULE_PARM_DESC(of_id, "Openfirmware id of the device to be handled by uio");
#endif

static struct platform_driver uio_pdrv_genirq = {
	.probe = uio_pdrv_genirq_probe,
	.remove = uio_pdrv_genirq_remove,
	.driver = {
		.name = DRIVER_NAME,
		.pm = &uio_pdrv_genirq_dev_pm_ops,
		.of_match_table = of_match_ptr(uio_of_genirq_match),
	},
};

从上述代码可以看出uio驱动采用的是platform框架,uio_of_genirq_match就是匹配表,module_param_string(of_id, uio_of_genirq_match[0].compatible, 128, 0)可以理解从传递的内核参数中填充匹配表,此时uio节点和uio驱动就能匹配成功,函数uio_pdrv_genirq_probe将会执行。在该函数中主要会进行以下操作:

  1. 申请uio对象
  2. 初始化uio对象,即设备操作函数
  3. 注册uio对象

驱动框架的核心层的逻辑为:提供一个设备抽象数据结构,这个设备抽象数据结构包含一系列设备操作函数,这些函数需要驱动开发者实现,最后调用设备对象的注册函数进行注册。uio驱动框架对uio设备进行了抽象,使用struct uio_info来描述,主要的数据成员如下:

struct uio_info {
	struct uio_device	*uio_dev;
	const char		*name;
	irqreturn_t (*handler)(int irq, struct uio_info *dev_info);
	int (*mmap)(struct uio_info *info, struct vm_area_struct *vma);
	int (*open)(struct uio_info *info, struct inode *inode);
	int (*release)(struct uio_info *info, struct inode *inode);
	int (*irqcontrol)(struct uio_info *info, s32 irq_on);
};

驱动开发者需要做的就是定义一个uio_info,实现其中的函数指针,然后注册,正如uio_pdrv_genirq_probe实现的这样:

uio_pdrv_genirq_probe
	/* 1. 申请uioinfo对象 */
	uioinfo = devm_kzalloc(&pdev->dev, sizeof(*uioinfo), GFP_KERNEL);
	/* 2. 初始化uio设备操作函数 */
	uioinfo->handler = uio_pdrv_genirq_handler;
	uioinfo->irqcontrol = uio_pdrv_genirq_irqcontrol;
	uioinfo->open = uio_pdrv_genirq_open;
	uioinfo->release = uio_pdrv_genirq_release;
	/* 3. 注册uioinfo */
	uio_register_device(&pdev->dev, priv->uioinfo);

可见使用驱动框架开发驱动程序还是比较简单,实现设备的操作函数然后注册就行了。那么对于linux驱动框架来说,框架将简单的部分暴漏给了驱动开发者,那复杂的部分就是由框架的核心层来完成的。

从上面的驱动相关层开发可以引出几个问题:

  • 驱动相关层只是用了接口uio_register_device,/dev/uioX目录下的设备节点怎么生成的?
  • uio设备是一个字符设备,应用程序的open、read、write函数对应的是file_operations中的函数,我们并没有实现file_operations,这个在哪儿实现的?
  • file_operations中的函数是怎么跟uio_info中的函数联系起来的?

首先看看uio_register_device这个函数,因为这个是自己编写的驱动与框架核心层的接口,其调用流程:

uio_register_device
	1. init_waitqueue_head(&idev->wait);
	2. device_create(&uio_class, parent,MKDEV(uio_major, idev->minor), idev,"uio%d", idev->minor);
	3. request_irq(info->irq, uio_interrupt, info->irq_flags, info->name, idev);

可以看见uio_register_device函数里首先初始化了一个等待队列,然后就看到了熟悉的与字符设备相关的函数device_create,这个函数在字符设备开发中是用于自动生成设备节点的,前面说过uio设备节点名称为/dev/uioX,就是这个函数定义的。最后,就是给这个uio设备申请一个中断,并注册中断处理函数。

从这个函数中又引起了几个问题:

  • 自动生成设备节点需要创建类和设备,类是在哪里创建的?
  • uio的字符设备是在哪里添加的?

上面这两个步骤是在系统初始化的时候已经做好了,uio框架有如下代码:

static int __init uio_init(void)
{
	return init_uio_class();
}

static void __exit uio_exit(void)
{
	release_uio_class();
	idr_destroy(&uio_idr);
}

module_init(uio_init);
module_exit(uio_exit);

在函数init_uio_class中就完成了uio设备的字符设备的注册,以及类的创建,调用流程如下:

uio_init
	1. init_uio_class
			uio_major_init
				1. cdev = cdev_alloc
				2. cdev->ops = &uio_fops
				3. cdev_add
	2. class_register(&uio_class)

从上面的流程就可以看出,uio核心层完成了字符设备注册,以及类的创建。字符设备的file_operationsuio_fops,当应用程序调用readwrite函数时就是调用的uio_fops->readuio_fops->write。至此uio框架已经从上至下打通了,后续只要看看uio_fops这里面的函数做了什么就行了。

static const struct file_operations uio_fops = {
	.owner		= THIS_MODULE,
	.open		= uio_open,
	.release	= uio_release,
	.read		= uio_read,
	.write		= uio_write,
	.mmap		= uio_mmap,
	.poll		= uio_poll,
	.fasync		= uio_fasync,
	.llseek		= noop_llseek,
};

在前文说过,调用write向uio设备写1打开中断,写0会关闭中断。调用read函数读uio设备,此时会阻塞等待uio设备中断。uio_write的调用流程:

uio_write
	idev->info->irqcontrol(idev->info, irq_on);

可见,write最终会调用在前文驱动相关层定义的uioinfo->irqcontrol函数,即uio_pdrv_genirq_irqcontrol。这个函数就是根据写入的值使能或禁止uio设备中断:

uio_pdrv_genirq_irqcontrol
	if (irq_on) {
		if (__test_and_clear_bit(UIO_IRQ_DISABLED, &priv->flags))
			enable_irq(dev_info->irq);
	} else {
		if (!__test_and_set_bit(UIO_IRQ_DISABLED, &priv->flags))
			disable_irq_nosync(dev_info->irq);
	}

uio_read的调用流程:

uio_read
	add_wait_queue(&idev->wait, &wait);

调用read函数会将当前线程加入等待队列,实现线程的阻塞,那么唤醒线程的操作肯定就是在uio设备中断里了,这样就实现了read阻塞等待中断,中断产生继续执行的功能了。在调用注册接口uio_register_device已经注册了中断,中断处理函数为uio_interrupt

uio_interrupt
	uio_event_notify
		1. wake_up_interruptible(&idev->wait);
		2. kill_fasync(&idev->async_queue, SIGIO, POLL_IN);

可见,跟猜想的一样,在中断中唤醒线程并且发了异步通知。至此,这个uio框架的核心逻辑已经介绍清楚了,uio驱动的其他功能实现逻辑也是相同的,在此不过多介绍。下图为uio框架的整体框图,应该更为清晰。
在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Linux UIO驱动框架(一) 的相关文章

  • Laravel 5.4.* - 运行 artisan 时“”目录不存在

    我有一个全新安装的Ubuntu服务器14 04我目前正在尝试安装我的Laravel项目上 我已将整个存储库移至所需的文件夹中 但是当我运行命令时 php artisan 在该目录中 终端返回一个错误 指出 InvalidArgumentEx
  • 在 Linux 上使用命令行 PHP 检查互联网连接

    我在 Linux 上使用命令行 PHP 来打开蓝牙拨号连接 并且我需要一种快速的方法来检查互联网连接是否处于活动状态 嗯 不一定要脏 但要快 使用exec运行外部命令不是问题 我正在考虑 ping 一些稳定的服务器 例如谷歌 但我想知道是否
  • 从哪个 Linux 内核/libc 版本开始,Java Runtime.exec() 在内存方面是安全的?

    在工作中 我们的目标平台之一是运行 Linux 的资源受限的迷你服务器 内核 2 6 13 基于旧 Fedora Core 的自定义发行版 该应用程序是用 Java Sun JDK 1 6 04 编写的 Linux OOM Killer 配
  • 如何在ubuntu下使用nasm(汇编)从键盘读取单个字符输入?

    我在ubuntu下使用nasm 顺便说一句 我需要从用户的键盘获取单个输入字符 就像当程序询问您 y n 时 因此当按下按键并且不按 Enter 键时 我需要读取输入的字符 我用谷歌搜索了很多 但我发现的所有内容都与这条线有关 int 21
  • 如何在Linux下生成系统范围的唯一ID

    我正在使用多进程 Linux 系统 需要生成唯一的 ID 安全性不是考虑因素 因此 ID 生成器从零开始递增就可以了 而且它只是在本地计算机内 不涉及网络 显然 实现这一点并不难 但我只是想知道是否已经提供了任何东西 最好是轻量级的 这听起
  • 如何从存储在 char* 指针中的 name 调用 c 函数?

    我想通过函数的名称动态调用函数 例如 假设有以下函数和字符串 void do fork printf Fork called n char pFunc do fork 现在我需要打电话do fork 就在 pFunc 那么这可能吗 欢迎 C
  • 如何将动态链接的应用程序转换为静态链接的应用程序?

    我有一个应用程序 例如 gedit 它是动态链接的 但我没有源代码 所以我不能按我喜欢的方式编译它 我想要做的是将其静态链接并将其移动到没有运行该应用程序所需的库的系统 那么是否可以做到以及如何做到呢 理论上是可能的 您基本上必须执行与动态
  • 如何从子进程为父进程设置环境变量?

    如何从子进程为父进程设置环境变量 例如 我有父进程和子进程 子进程继承自父进程环境变量 TMP VARIABLE 777 如何将子进程中 TMP VARIABLE 的值更改为 999 使其值对父进程可见 因为 TMP VARIABLE 99
  • posix 和 linux 特定函数的 C++ 包装器 [关闭]

    Closed 这个问题正在寻求书籍 工具 软件库等的推荐 不满足堆栈溢出指南 help closed questions 目前不接受答案 您知道有什么好的库将 posix 和 linux 函数和结构 例如套接字或文件描述符 包装到 C 类中
  • 如何使用Python distutils?

    我用 python 编写了一个快速程序 将 gtk GUI 添加到 cli 程序中 我想知道如何使用 distutils 创建安装程序 因为它只是命令行应用程序的 GUI 前端 所以它只能在 nix 中工作 所以我不担心它是跨平台的 我的主
  • 有人可以解释一下以下内存分配 C 程序的性能行为吗?

    在我的机器上 时间 A 和时间 B 交换取决于是否A是 定义或未定义 这会改变两个的顺序 callocs 被称为 我最初将此归因于寻呼系统 奇怪的是 当mmap被用来代替calloc 情况更加奇怪 两个循环花费的时间相同 正如预期的那样 作
  • 如何更改解释器路径并将命令行参数传递给 Linux 上的“可执行”共享库?

    这是 可执行 共享库的最小示例 假设文件名 mini c Interpreter path is different on some systems definitely different for 32 Bit machines cons
  • 使用vim,如何快速刷新正在处理的网页?

    我已经使用 VIM 几个星期了 同时处理各种网络语言 我真的很喜欢它 我发现必须点击或单击浏览器并刷新页面才能看到代码更改的效果 这很麻烦 更烦人的是 因为我使用的是 Virtual Box 而且我倾向于在主机系统上处理 PDF 文件 因此
  • Motif 库的水平绘制的 RowColumn 类 (C)?

    我正在使用 Motif Library 来完成我的工作 如果有人不熟悉这个库 您可以在这里找到文件列表https packages ubuntu com xenial amd64 libmotif dev filelist https pa
  • 32 位 x86 汇编中堆栈对齐的职责

    我试图清楚地了解谁 调用者或被调用者 负责堆栈对齐 64 位汇编的情况相当清楚 它是由caller 请参阅系统 V AMD64 ABI 第 3 2 2 节栈帧 输入参数区域的末尾应按 16 对齐 32 如果 m256 在堆栈 字节边界上传递
  • 检测目录中是否有某些内容被修改,如果是,则备份 - 否则不执行任何操作

    我有一个 数据 目录 我通过 shell 脚本定期同步到远程 NAS 但是 我想让这变得更有效率 我想在运行 rsync 之前检测 数据 中是否发生了变化 这样我就不会不必要地唤醒 NAS 上的驱动器 我正在考虑修改 shell 脚本以获取
  • 共享库 RPATH 和二进制 RPATH 优先级

    如果共享库链接到二进制文件 并且共享库还依赖于其他库 则共享库的 RPATH 和二进制文件的 RPATH 的优先级 链接器搜索顺序 是什么 二进制文件的 RPATH 是否可以覆盖共享库中的 RPATH 我在共享库RPATH中设置的 ORIG
  • H.323,如何制作一个没有媒体的简单环。该脚本遵循 Q.931 设置,但仍然无法正常工作

    谁能帮我解决这个问题吗 当我发送此请求时 我在wireshark中看到数据包将发送到1720 tcp端口中的SJPhone 但 SJPhone 仍然没有响铃 我想让它响起 无论媒体 我非常感谢您的支持 我一定缺少消息协议细节来实现这个 请给
  • 在Linux上如何找到当前目录的所有直接子目录?

    在Linux上如何找到当前目录的所有直接子目录 最简单的方法是通过编写来利用 shell 通配功能echo 如果你喜欢使用ls 例如要应用格式 排序选项 请使其ls d 解释 斜杠确保仅考虑目录 而不考虑文件 Option d 列出目录本身
  • 为什么在setsid()之前fork()

    Why fork before setsid 守护进程 基本上 如果我想将一个进程与其控制终端分离并使其成为进程组领导者 我使用setsid 之前没有分叉就这样做是行不通的 Why 首先 setsid 将使您的进程成为进程组的领导者 但它也

随机推荐

  • vue 列表进行拖拽排序

    文章为记录项目 需引入插件vuedraggable handle 61 34 mover 34 为绑定拖拽图标的类名 xff0c 即可只能在图标上才可拖拽 lt el form item label 61 34 34 gt lt ul cl
  • vue elementui表单验证

    this refs form validateField 39 type 39 只为项目记录 这个代码为对部分表单字段进行校验的方法
  • element ui分开的开始结束日期验证

    废话不多说直接上代码 lt el form v show 61 34 showSearch 34 ref 61 34 queryForm 34 model 61 34 queryParams 34 inline 61 34 true 34
  • ant.design pro表格序号自定义,翻页也可按顺序来

    title 39 序号 39 dataIndex 39 index 39 valueType 39 indexBorder 39 width 48 hideInSearch true render text record index 61
  • ant.design pro 发布时间对应两个参数值

    title 39 时间 39 dataIndex 39 deployTime 39 valueType 39 dateRange 39 hideInSearch false render record 61 gt lt span gt re
  • 微信小程序图片水印添加

    js getCanvasOne url var mycenter 61 0 文字左右居中显示 var myheight 61 0 文字高度 const that 61 this const query 61 wx createSelecto
  • anaconda出现CondaHTTPError问题解决办法

    一 condarc xff08 conda 配置文件 xff09 Configuration Conda documentation condarc以点开头 xff0c 一般表示 conda 应用程序的配置文件 xff0c 在用户的家目录
  • 使用kalibr标定imu

    这种方法需要在ubuntu中安装matlab 本人只标定的imu 没有和摄像头联合标定 xff0c 方法和imu utils类似 xff0c 先用ros记录imu数据 xff0c 在通过kalibr来计算随机游走误差和高斯白噪声误差 1 首
  • 联合标定双目相机和imu,使用工具Kalibr

    文章目录 imu标定 xff0c 产生数据写入imu yaml中 xff0c 见下文 xff0c imu yaml文件要用于联合标定 双目相机标定 xff0c 产生数据文件用于联合标定 xff0c 文件名类似camchain homeubu
  • matlab从txt文件中提取出有效信息

    背景 从一份txt文件中筛选出有效信息 xff0c txt文件有非常多行 xff0c 依靠关键字筛选出有效行 xff0c 并从行中提取有效信息 test txt文件例如 xff1a aaa 1 2 3 valid 0 1 0 2 0 3 a
  • Python 基础 第一天

    print 34 Hello World 34 print 34 你好 xff0c 世界 34 在 Python 中以单下划线 xff08 xff09 开头命名的标识符 表示不能直接访问的类属性 xff0c 以双下划线 xff08 xff0
  • Python 基础 第二天

    import random import math 集合 xff08 set xff09 是一个无序的不重复元素序列 可以使用 或 set 函数创建集合 值得注意的是 一个空集合必须用set xff0c 使用 创建时会创建一个空字典 bas
  • Django 第六天

    Django高级扩展 静态文件 xff1a css xff0c js xff0c 图片 xff0c Json文件 xff0c 字体文件等 配置settings py xff1a STATICFILES DIRS span class tok
  • Python 爬虫 小练习

    获得某易云音乐 对应歌单下的所有歌曲的歌曲 专辑图片 歌手图片 lrc歌词 span class token keyword import span requests span class token keyword from span b
  • 免费GPU

    中国移动免费GPU资源 九天 毕昇还属于内侧阶段 xff0c 没有充值入口 没有GPU算力的同学可以体验一下 xff0c 不算广告 xff0c 纯属安利羊毛 引言 最近想跑一个模型 xff0c 但突然发现手头没有可用的算力了 然后朋友推荐了
  • 分布式 ROS PX4 GAZEBO 多机仿真 服务器-客户端模式

    这是一个目录 最终目标环境配置要求具体实施方案UAV0配置UAV1配置 执行 最终目标 实现主从机器多机仿真 xff0c 模拟真机部署 具体方案如下 xff1a 设定一台计算机为通信汇集节点 xff0c 处理所有无人机位姿 移动控制等 xf
  • 算法训练 最短路

    算法训练 最短路 问题描述 给定一个n个顶点 xff0c m条边的有向图 xff08 其中某些边权可能为负 xff0c 但保证没有负环 xff09 请你计算从1号点到其他点的最短路 xff08 顶点从1到n编号 xff09 输入格式 第一行
  • Ubuntu18开启ssh服务

    Ubuntu默认不会开启ssh服务 所以我们无法对Ubuntu进行远程连接 xff0c 这对Ubuntu的运维造成了很大不便 本文详细讲解如何在Ubuntu18下开启ssh服务 关键指令 root用户下 xff0c 非root用户所有指令前
  • alembic 常用命令有哪些?

    上节为大家讲解了alembic如何使用 xff1f 本节就为大家介绍一下alembic常用命令有哪些 xff1f alembic常用命令如下 xff1a 在讲这些命令用法之前 xff0c 我们先建立一个alembic demo数据库 然后再
  • Linux UIO驱动框架(一)

    文章目录 Linux UIO驱动框架1 什么是uio驱动框架2 uio驱动框架使用3 uio驱动框架实现原理 Linux UIO驱动框架 1 什么是uio驱动框架 uio全称为用户空间IO Userspace I O xff0c 是一种在用