// PCIE 设备的读写demo 模块.
// 逐行分析代码的执行逻辑 使用这个提示词来让ai解释代码.
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h> //sudo apt install linux-source
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#define DEVICE_NAME "my_pcie_dev"
#define CLASS_NAME "pcie"
static int major_number;
static struct class* pcie_class = NULL;
static struct device* pcie_device = NULL;
// 设备私有数据结构
struct my_device_data {
char buffer[256];
int buffer_size;
};//最多存256的char, buffer_size表示实际上存多少.
// 文件打开函数
static int my_open(struct inode *inode, struct file* file)
{
struct my_device_data *dev_data;
printk(KERN_INFO "PCIe device opened\n");
// 分配设备私有数据
dev_data = kmalloc(sizeof(struct my_device_data), GFP_KERNEL);//GFP_KERNEL 表示内存不够时候可以睡眠等待.
if (!dev_data) {
return -ENOMEM;
}
dev_data->buffer_size = 0;
memset(dev_data->buffer, 0, sizeof(dev_data->buffer));
// 将设备数据关联到文件结构
file->private_data = dev_data;
return 0;//返回0表示成功
}
// 文件释放/关闭函数
static int my_release(struct inode *inode, struct file *file)
{
struct my_device_data *dev_data = file->private_data;
printk(KERN_INFO "PCIe device closed\n");
// 释放设备私有数据
if (dev_data) {
kfree(dev_data);
file->private_data = NULL;
}
return 0;
}
// 读操作函数 - 从设备读取数据到用户空间
static ssize_t my_read(struct file *file, char __user *user_buffer,
size_t count, loff_t *offset) // loff_t 后面t 表示底层是一个int类型.
{
struct my_device_data *dev_data = file->private_data;
int bytes_to_read;
printk(KERN_INFO "Read operation requested, count=%zu, offset=%lld\n",
count, *offset); //内核打印命令 用dmeg查看.
if (!dev_data) {
return -ENODEV;
}
// 计算实际可读取的字节数
if (*offset >= dev_data->buffer_size) {
return 0; // 已到达缓冲区末尾, 读取的字符数是0.所以返回0表示读取的字符数.
}
bytes_to_read = dev_data->buffer_size - *offset;
if (count < bytes_to_read) {
bytes_to_read = count;
}
// 将数据从内核空间复制到用户空间
if (copy_to_user(user_buffer,
dev_data->buffer + *offset,
bytes_to_read)) {
return -EFAULT; // 复制失败
}
*offset += bytes_to_read; // 更新偏移量
printk(KERN_INFO "Successfully read %d bytes\n", bytes_to_read);
return bytes_to_read;
}
// Linux 内核的标记;
// __user: 指向用户空间的指针
// __kernel: 指向内核空间的指针
// __iomem: 指向 I/O 内存的指针
// 写操作函数 - 从用户空间写入数据到设备
static ssize_t my_write(struct file *file, const char __user *user_buffer,
size_t count, loff_t *offset) // 所以user_buffer是一个指向用户空间的指针, 他的标记是用户空间.
{
struct my_device_data *dev_data = file->private_data;
int bytes_to_write;
printk(KERN_INFO "Write operation requested, count=%zu\n", count);
if (!dev_data) {
return -ENODEV;
}
// 检查缓冲区边界, 计算可以写入的字节数
if (*offset + count > sizeof(dev_data->buffer)) {
bytes_to_write = sizeof(dev_data->buffer) - *offset;
if (bytes_to_write <= 0) {
printk(KERN_WARNING "Buffer full, cannot write more data\n");
return -ENOSPC;
}
} else {
bytes_to_write = count;
}
// 将数据从用户空间复制到内核空间 //复制函数一般是第一个变量是dst, 第二个是src,第三个是字节数. 因为目的地是未来要到的位置, 所以更重要, 所以写在第一个位置, 原始位置是老的位置不那么重要, 过时了马上, 所以写第二个位置.
if (copy_from_user(dev_data->buffer + *offset,
user_buffer,
bytes_to_write)) {
return -EFAULT; // 复制失败
}
*offset += bytes_to_write;
if (dev_data->buffer_size < *offset) {//如果offset过大,超过buffer最大值了.那么就用最大值定义offset即可.
dev_data->buffer_size = *offset;
}
printk(KERN_INFO "Successfully wrote %d bytes\n", bytes_to_write);
return bytes_to_write;
}
// ioctl 操作函数(可选)- 用于控制命令 //io control : 该函数通过传入设备相关请求码实现对硬件特性的控制
static long my_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
struct my_device_data *dev_data = file->private_data;
printk(KERN_INFO "ioctl called: cmd=0x%x, arg=0x%lx\n", cmd, arg);
switch (cmd) {
case 1: // 清空缓冲区
if (dev_data) {
dev_data->buffer_size = 0;
memset(dev_data->buffer, 0, sizeof(dev_data->buffer));
printk(KERN_INFO "Buffer cleared\n");
}
break;
case 2: // 获取缓冲区大小 //arg强制转化类型为用户空间的指针.//所以最后arg这个指针指向一个int, 他的值是buffer_size
if (copy_to_user((void __user *)arg,
&dev_data->buffer_size,
sizeof(dev_data->buffer_size))) {
return -EFAULT;
}
break;
default:
return -ENOTTY; // 不支持的命令
}
return 0;
}
// 定义文件操作结构体
static const struct file_operations fops = {
.owner = THIS_MODULE, // 指向当前模块
.open = my_open, // 打开文件时调用
.release = my_release, // 关闭文件时调用
.read = my_read, // 读取文件时调用
.write = my_write, // 写入文件时调用
.unlocked_ioctl = my_ioctl, // ioctl 操作时调用
.llseek = no_llseek, // 禁用 seek 操作(对于设备文件通常是这样)
};
// 模块初始化函数
static int __init pcie_module_init(void)
{
// 注册字符设备,获取主设备号
major_number = register_chrdev(0, DEVICE_NAME, &fops);
//传入 0 表示让内核自动分配一个可用的主设备号
//major_number :返回分配到的主设备号(如果参数中传入 0)
if (major_number < 0) {
printk(KERN_ALERT "Failed to register device with error %d\n", major_number);
return major_number;
}
printk(KERN_INFO "PCIe device registered with major number %d\n", major_number);
// 创建设备类
pcie_class = class_create(THIS_MODULE, CLASS_NAME); //在 /sys/class/pcie/ 目录下创建设备类目录
if (IS_ERR(pcie_class)) {
unregister_chrdev(major_number, DEVICE_NAME);
printk(KERN_ALERT "Failed to create device class\n");
return PTR_ERR(pcie_class);
}
// 创建设备节点
pcie_device = device_create(pcie_class, NULL,
MKDEV(major_number, 0),
NULL, DEVICE_NAME); //device_create 函数自动在 /dev 目录创建设备文件
if (IS_ERR(pcie_device)) {
class_destroy(pcie_class);
unregister_chrdev(major_number, DEVICE_NAME);
printk(KERN_ALERT "Failed to create device\n");
return PTR_ERR(pcie_device);
}
printk(KERN_INFO "PCIe device created successfully\n");
return 0;
}
// 模块退出函数
static void __exit pcie_module_exit(void)
{
device_destroy(pcie_class, MKDEV(major_number, 0));
class_unregister(pcie_class);
class_destroy(pcie_class);
unregister_chrdev(major_number, DEVICE_NAME);
printk(KERN_INFO "PCIe device driver unloaded\n");
}
module_init(pcie_module_init);
module_exit(pcie_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Qwen");
MODULE_DESCRIPTION("PCIe Device Driver with File Operations Example");
MODULE_VERSION("1.0");