本文将基于开源项目RIFFA,分析Linux系统下PCIE设备的内核驱动是如何实现的,为开发Linux PCIE驱动提供一个参考模板,同时也可作为研究PCIE软硬件交互流程的具体实例,加深对PCIE相关知识的理解。
该开源项目的地址为:https://github.com/KastnerRG/riffa
https://jklincn.com/posts/qemu-edu-driver/
https://www.kernel.org/doc/html/latest/PCI/index.html
https://www.qemu.org/docs/master/specs/edu.html
我们考虑的场景是,数据在用户态准备,然后传递给内核PCIE驱动进行处理,所以我们首先介绍一下用户态和内核态的交互方式。
想要做用户态和内核态的交互,比较常见的做法是使用ioctl系统调用。
这一般要求内核驱动实现一个字符设备并给它注册上ioctl的文件操作回调函数。
以下给出驱动编程模板:
C#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "myioctl"
#define MY_MAGIC 'x'
#define MY_IOCTL_GET _IOR(MY_MAGIC, 0, int)
#define MY_IOCTL_SET _IOW(MY_MAGIC, 1, int)
static int device_open = 0;
static int kernel_value = 123;
static long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int ret = 0;
switch (cmd) {
case MY_IOCTL_GET:
if (copy_to_user((int __user *)arg, &kernel_value, sizeof(int))) {
ret = -EFAULT;
}
break;
case MY_IOCTL_SET:
if (copy_from_user(&kernel_value, (int __user *)arg, sizeof(int))) {
ret = -EFAULT;
}
break;
default:
ret = -ENOTTY;
}
return ret;
}
static struct file_operations fops = {
.unlocked_ioctl = my_ioctl, // ioctl的回调函数
};
static int __init my_init(void)
{
register_chrdev(240, DEVICE_NAME, &fops);
printk(KERN_INFO "My device loaded\n");
return 0;
}
static void __exit my_exit(void)
{
unregister_chrdev(240, DEVICE_NAME);
printk(KERN_INFO "My device unloaded\n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
在用户态编程中,可以这样调用ioctl实现与内核驱动的交互:
C#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define MY_MAGIC 'x'
#define MY_IOCTL_GET _IOR(MY_MAGIC, 0, int)
#define MY_IOCTL_SET _IOW(MY_MAGIC, 1, int)
int main()
{
int fd;
int value;
fd = open("/dev/myioctl", O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
// 读取内核值
if (ioctl(fd, MY_IOCTL_GET, &value) == 0) {
printf("Current kernel value: %d\n", value);
}
// 设置新值
value = 456;
if (ioctl(fd, MY_IOCTL_SET, &value) == 0) {
printf("Value set to: %d\n", value);
}
// 验证新值
if (ioctl(fd, MY_IOCTL_GET, &value) == 0) {
printf("New kernel value: %d\n", value);
}
close(fd);
return 0;
}
这样,我们就打通了用户态和内核态的交互,此后,可以在用户程序中发送控制命令给内核驱动,随后内核驱动根据控制命令去对硬件做出合适的操作。
若是要在用户态和内核态之间进行大量的数据交换,可以采用mmap的方式,具体细节不在此赘述了,网络上对该技术有很多讨论。
本文作者:Test
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!