Files

645 lines
21 KiB
C

// SPDX-License-Identifier: GPL-2.0
/**
* Copyright (c) 2024-2025 CALCULET Technologies Ltd. All rights reserved.
*/
#include <linux/version.h>
#include <linux/pci.h>
#include <linux/interrupt.h>
#include <linux/sched.h>
#include <linux/pagemap.h>
#include <linux/uaccess.h>
#include <linux/scatterlist.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/kref.h>
#include <linux/crc32.h>
#include <linux/hash.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
#include <linux/sched/signal.h>
#endif
#include "fops.h"
#include "proc.h"
#include "calculet_pci_core.h"
#define DRIVER_NAME "calculet"
#define CALCULET_SOFT_RESET_REG 0x2238108
// 简化的文件上下文
struct calculet_file_private {
struct calculet_pcie_board *board;
pid_t owner_pid;
bool is_valid;
struct kref ref_count;
};
// 自定义混淆函数
static u32 calculet_hash_mix(u32 input, u32 salt)
{
input ^= salt;
input = ((input >> 16) ^ input) * 0x45d9f3b;
input = ((input >> 16) ^ input) * 0x45d9f3b;
input = (input >> 16) ^ input;
return input;
}
void calculet_generate_simple_uuid(const char *serial_number, char *uuid_str)
{
u32 seeds[4];
u8 uuid_bytes[16];
u32 base_hash;
int i;
// 计算基础哈希
base_hash = crc32(0, serial_number, strlen(serial_number));
// 生成4个种子值
seeds[0] = calculet_hash_mix(base_hash, 0x12345678);
seeds[1] = calculet_hash_mix(base_hash, 0x9ABCDEF0);
seeds[2] = calculet_hash_mix(base_hash, 0xFEDCBA98);
seeds[3] = calculet_hash_mix(base_hash, 0x76543210);
// 转换为字节
for (i = 0; i < 4; i++) {
uuid_bytes[i*4] = (seeds[i] >> 24) & 0xFF;
uuid_bytes[i*4+1] = (seeds[i] >> 16) & 0xFF;
uuid_bytes[i*4+2] = (seeds[i] >> 8) & 0xFF;
uuid_bytes[i*4+3] = seeds[i] & 0xFF;
}
// 设置UUID版本和变体位
uuid_bytes[6] = (uuid_bytes[6] & 0x0F) | 0x40; // 版本4
uuid_bytes[8] = (uuid_bytes[8] & 0x3F) | 0x80; // 变体位
// 格式化为UUID字符串
snprintf(uuid_str, 37, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
uuid_bytes[0], uuid_bytes[1], uuid_bytes[2], uuid_bytes[3],
uuid_bytes[4], uuid_bytes[5], uuid_bytes[6], uuid_bytes[7],
uuid_bytes[8], uuid_bytes[9], uuid_bytes[10], uuid_bytes[11],
uuid_bytes[12], uuid_bytes[13], uuid_bytes[14], uuid_bytes[15]);
}
// 文件上下文释放函数
static void calculet_file_private_release(struct kref *ref)
{
struct calculet_file_private *priv = container_of(ref, struct calculet_file_private, ref_count);
if (priv->board) {
calculet_board_mgr_put_board(priv->board);
}
kfree(priv);
}
static void calculet_file_private_get(struct calculet_file_private *priv)
{
if (priv) {
kref_get(&priv->ref_count);
}
}
static void calculet_file_private_put(struct calculet_file_private *priv)
{
if (priv) {
kref_put(&priv->ref_count, calculet_file_private_release);
}
}
int calculet_pcie_fops_open(struct inode *inode, struct file *filp)
{
u32 major = MAJOR(inode->i_rdev);
u32 minor = MINOR(inode->i_rdev);
struct calculet_pcie_board *board;
struct calculet_file_private *priv;
int err = 0;
calculet_log(CALCULET_LOG_DEBUG, "Device open: PID %d, major %d, minor %d",
current->pid, major, minor);
// 分配文件私有数据
priv = kzalloc(sizeof(*priv), GFP_KERNEL);
if (!priv) {
return -ENOMEM;
}
// 初始化文件私有数据
kref_init(&priv->ref_count);
priv->owner_pid = current->pid;
priv->is_valid = true;
// 获取板卡引用
board = calculet_board_mgr_get_board(minor);
if (!board) {
calculet_log(CALCULET_LOG_ERROR, "PCIe board not found for /dev/calculet%d", minor);
err = -ENODEV;
goto error_free_priv;
}
// 检查板卡状态
if (!calculet_board_is_operational(board)) {
calculet_log(CALCULET_LOG_ERROR, "Board %d is not operational", minor);
err = -ENODEV;
goto error_put_board;
}
priv->board = board;
// 添加进程监测
err = calculet_proc_add_process(board, current);
if (err) {
calculet_log(CALCULET_LOG_WARN, "Failed to add process monitoring for PID %d: %d",
current->pid, err);
}
filp->private_data = priv;
calculet_board_log(CALCULET_LOG_DEBUG, board, "Device opened by process %s (PID: %d)",
current->comm, current->pid);
return 0;
error_put_board:
calculet_board_mgr_put_board(board);
error_free_priv:
kfree(priv);
return err;
}
int calculet_pcie_fops_release(struct inode *inode, struct file *filp)
{
struct calculet_file_private *priv = (struct calculet_file_private *)filp->private_data;
if (!priv) {
return 0;
}
// 标记为无效,防止新的操作
priv->is_valid = false;
if (priv->board) {
// 更新进程状态为已关闭
calculet_proc_remove_process(priv->board, priv->owner_pid);
calculet_board_log(CALCULET_LOG_DEBUG, priv->board,
"Device closed by process PID %d", priv->owner_pid);
}
// 释放文件私有数据
calculet_file_private_put(priv);
filp->private_data = NULL;
return 0;
}
static int calculet_msi_ioctl(struct device *dev, struct calculet_dma_controller *controller, unsigned long arg)
{
struct calculet_msi_req *req_num = kmalloc(sizeof(*req_num), GFP_KERNEL);
unsigned long timeout = msecs_to_jiffies(30000); // 30秒超时
struct calculet_dma_channel *channel;
if (!dev || !controller) {
return -EINVAL;
}
if (copy_from_user(req_num, (void __user*)arg, sizeof(*req_num))) {
calculet_dev_log(CALCULET_LOG_ERROR, dev, "copy_from_user fail");
kfree(req_num);
return -EFAULT;
}
channel = &controller->msi_req[req_num->msi_req];
if (wait_event_interruptible_timeout(channel->channel_wq,
atomic_read(&channel->transfer_finish) == 1,
timeout) > 0) {
atomic_set(&channel->transfer_finish, 0);
kfree(req_num);
calculet_dev_log(CALCULET_LOG_DEBUG, dev, "EP MSI request completed successfully");
return 0;
} else {
calculet_dev_log(CALCULET_LOG_ERROR, dev, "EP MSI request timed out");
kfree(req_num);
return -ETIMEDOUT;
}
}
/**
* PCIe软复位主函数
*/
static int calculet_pcie_soft_reset(struct calculet_pcie_board *board)
{
struct calculet_resource *resource = &board->pcie_resources.rt_registers;
struct pci_dev *pdev = board->pDev;
struct pci_bus *bus = pdev->bus;
int ret;
calculet_board_log(CALCULET_LOG_DEBUG, board, "Starting PCIe soft reset");
if (!board || !pdev) {
calculet_log(CALCULET_LOG_ERROR, "Invalid board or device pointer");
return -EINVAL;
}
// 准备全局软复位
ret = calculet_global_soft_reset_prepare(board);
if (ret) {
return ret;
}
/* 1. 触发设备端复位 */
calculet_resource_write32(resource, CALCULET_SOFT_RESET_REG, 1);
/* 2. 等待复位开始 */
msleep(100000); // 减少等待时间
pci_lock_rescan_remove();
pci_stop_and_remove_bus_device(pdev);
pci_rescan_bus(bus->parent);
pci_unlock_rescan_remove();
return 0;
}
static long calculet_get_process_list_ioctl(struct calculet_pcie_board *board, unsigned long arg)
{
int i = 0;
struct calculet_process_info *proc_info;
struct calculet_process_info_list_user *user_list = kmalloc(sizeof(*user_list), GFP_KERNEL);
if (!user_list) {
return -ENOMEM;
}
if (!board || !calculet_board_is_operational(board)) {
kfree(user_list);
return -ENODEV;
}
if (copy_from_user(user_list, (void __user*)arg, sizeof(*user_list))) {
calculet_board_log(CALCULET_LOG_ERROR, board, "copy_from_user fail");
kfree(user_list);
return -EFAULT;
}
// 清零并初始化
memset(user_list, 0, sizeof(*user_list));
user_list->board_index = board->board_index;
mutex_lock(&board->process_list_mutex);
list_for_each_entry(proc_info, &board->process_list, list) {
if (proc_info->is_active && i < MAX_PROCESS_ENTRIES) {
user_list->processes[i].pid = proc_info->pid;
user_list->processes[i].ppid = proc_info->ppid;
user_list->processes[i].uid = proc_info->uid;
strncpy(user_list->processes[i].comm, proc_info->comm, sizeof(user_list->processes[i].comm) - 1);
user_list->processes[i].comm[sizeof(user_list->processes[i].comm) - 1] = '\0';
user_list->processes[i].is_active = proc_info->is_active ? 1 : 0;
i++;
}
}
mutex_unlock(&board->process_list_mutex);
user_list->count = i; // 更新实际数量
if (copy_to_user((void __user*)arg, user_list, sizeof(*user_list))) {
calculet_board_log(CALCULET_LOG_ERROR, board, "copy_to_user fail");
kfree(user_list);
return -EFAULT;
}
kfree(user_list);
calculet_board_log(CALCULET_LOG_DEBUG, board, "Returned %d active processes", i);
return 0;
}
static long calculet_get_board_info_ioctl(struct calculet_pcie_board *board, unsigned long arg)
{
u16 lnksta;
u64 memory_band;
struct calculet_ioc_info *board_info = kmalloc(sizeof(struct calculet_ioc_info), GFP_KERNEL);
if (!board_info) {
return -ENOMEM;
}
if (!board || !calculet_board_is_operational(board)) {
kfree(board_info);
return -ENODEV;
}
if (copy_from_user((void *)board_info, (void __user*)arg, sizeof(struct calculet_ioc_info))) {
calculet_board_log(CALCULET_LOG_ERROR, board, "copy_from_user fail");
kfree(board_info);
return -EFAULT;
}
pcie_capability_read_word(board->pDev, PCI_EXP_LNKSTA, &lnksta);
// 解析速率
board_info->pci_info.link_speed = lnksta & PCI_EXP_LNKSTA_CLS;
// 解析通道宽度
board_info->pci_info.link_width = (lnksta & PCI_EXP_LNKSTA_NLW) >> PCI_EXP_LNKSTA_NLW_SHIFT;
board_info->vendor_id = board->pDev->vendor;
board_info->device_id = board->pDev->device;
board_info->subsystem_vendor = board->pDev->subsystem_vendor;
board_info->subsystem_device = board->pDev->subsystem_device;
board_info->pci_info.domain = pci_domain_nr(board->pDev->bus);
board_info->pci_info.bus = board->pDev->bus->number;
board_info->pci_info.device = PCI_SLOT(board->pDev->devfn);
board_info->pci_info.function = PCI_FUNC(board->pDev->devfn);
// 从bar4读取硬件规格参数
calculet_read_strings(board_info->product_name, &board->pcie_resources.smi_registers, CALCULET_PRODUCT_NAME_REG, 32);
calculet_read_strings(board_info->serial_number, &board->pcie_resources.smi_registers, CALCULET_SERIAL_NUMBER_REG, 32);
calculet_read_strings(board_info->product_number, &board->pcie_resources.smi_registers, CALCULET_PRODUCT_NUMBER_REG, 32);
calculet_read_strings(board_info->firmware_version, &board->pcie_resources.smi_registers, CALCULET_FIRMWARE_VERSION_REG, 12);
calculet_generate_simple_uuid(board_info->serial_number, board_info->calculet_uuid);
// 总显存大小 (0x004C, 8字节)
board_info->total_memory = calculet_resource_read64(&board->pcie_resources.smi_registers, CALCULET_TOTAL_MEMORY_REG);
// 显存带宽 (0x0054, 8字节)
memory_band = calculet_resource_read64(&board->pcie_resources.smi_registers, CALCULET_MEMORY_BANDWIDTH_REG);
memcpy(&board_info->memory_bandwidth, &memory_band, sizeof(double));
// DDR频率 (0x005C, 4字节)
board_info->ddr_frequency = calculet_resource_read32(&board->pcie_resources.smi_registers, CALCULET_DDR_FREQUENCY_REG);
// 板卡 TDP上限 (0x0060, 4字节)
board_info->max_power_limit = calculet_resource_read32(&board->pcie_resources.smi_registers, CALCULET_MAX_POWER_LIMIT_REG);
// KS1温度上限 (0x0064, 4字节)
board_info->temp_limit_ks1 = calculet_resource_read32(&board->pcie_resources.smi_registers, CALCULET_TEMP_LIMIT_REG);
board_info->driver_version.major = CALCULET_DRV_VER_MAJOR;
board_info->driver_version.minor = CALCULET_DRV_VER_MINOR;
snprintf(board_info->driver_version.string, KS1_MAX_STRING_LEN,
"%s", CALCULET_DRV_VER);
if (copy_to_user((void __user*)arg, (void *)board_info, sizeof(struct calculet_ioc_info))) {
calculet_board_log(CALCULET_LOG_ERROR, board, "copy_to_user fail");
kfree(board_info);
return -EFAULT;
}
kfree(board_info);
return 0;
}
static long calculet_bar64_transfer_ioctl(struct calculet_pcie_board *board, unsigned long arg)
{
long err = 0;
struct calculet_bar64_transfer_params transfer;
if (!board || !calculet_board_is_operational(board)) {
return -ENODEV;
}
if (copy_from_user(&transfer, (void __user*)arg, sizeof(transfer))) {
calculet_board_log(CALCULET_LOG_ERROR, board, "copy_from_user fail");
return -EFAULT;
}
err = calculet_pcie_bar64_transfer(&board->pcie_resources, &transfer);
if (err < 0) {
calculet_board_log(CALCULET_LOG_ERROR, board, "bar transfer failed %ld", err);
calculet_board_stats_update_error(board);
}
if (copy_to_user((void __user*)arg, &transfer, sizeof(transfer))) {
calculet_board_log(CALCULET_LOG_ERROR, board, "copy_to_user fail");
return -EFAULT;
}
return err;
}
static long calculet_bar32_transfer_ioctl(struct calculet_pcie_board *board, unsigned long arg)
{
long err = 0;
struct calculet_bar32_transfer_params transfer;
if (!board || !calculet_board_is_operational(board)) {
return -ENODEV;
}
if (copy_from_user(&transfer, (void __user*)arg, sizeof(transfer))) {
calculet_board_log(CALCULET_LOG_ERROR, board, "copy_from_user fail");
return -EFAULT;
}
err = calculet_pcie_bar32_transfer(&board->pcie_resources, &transfer);
if (err < 0) {
calculet_board_log(CALCULET_LOG_ERROR, board, "bar32 transfer failed %ld", err);
calculet_board_stats_update_error(board);
}
if (copy_to_user((void __user*)arg, &transfer, sizeof(transfer))) {
calculet_board_log(CALCULET_LOG_ERROR, board, "copy_to_user fail");
return -EFAULT;
}
return err;
}
static long calculet_get_sn_ioctl(struct calculet_pcie_board *board, unsigned long arg)
{
struct calculet_sn_info sn_info;
if (!board || !calculet_board_is_operational(board)) {
return -ENODEV;
}
if (copy_from_user(&sn_info, (void __user*)arg, sizeof(sn_info))) {
calculet_board_log(CALCULET_LOG_ERROR, board, "copy_from_user fail");
return -EFAULT;
}
calculet_read_strings(sn_info.sn, &board->pcie_resources.smi_registers, CALCULET_SERIAL_NUMBER_REG + sn_info.chip_id * 1024, 32);
if (copy_to_user((void __user*)arg, &sn_info, sizeof(sn_info))) {
calculet_board_log(CALCULET_LOG_ERROR, board, "copy_to_user fail");
return -EFAULT;
}
return 0;
}
static long calculet_general_ioctl(struct calculet_pcie_board *board, unsigned int cmd, unsigned long arg)
{
if (!board || !calculet_board_is_operational(board)) {
return -ENODEV;
}
switch (cmd) {
case CALCULET_BOARD_INFO:
return calculet_get_board_info_ioctl(board, arg);
case CALCULET_BAR64_TRANSFER:
return calculet_bar64_transfer_ioctl(board, arg);
case CALCULET_BAR32_TRANSFER:
return calculet_bar32_transfer_ioctl(board, arg);
case CALCULET_GET_SN:
return calculet_get_sn_ioctl(board, arg);
default:
calculet_board_log(CALCULET_LOG_ERROR, board, "Invalid general ioctl code 0x%x (nr: %d)",
cmd, _IOC_NR(cmd));
return -ENOTTY;
}
}
static long calculet_ep_ioctl(struct calculet_pcie_board *board, unsigned int cmd, unsigned long arg)
{
if (!board || !calculet_board_is_operational(board)) {
return -ENODEV;
}
switch (cmd) {
case CALCULET_EP_MSI_REQ:
return calculet_msi_ioctl(&board->pDev->dev, &board->cdma, arg);
case CALCULET_SOFT_RESET:
return calculet_pcie_soft_reset(board);
default:
calculet_board_log(CALCULET_LOG_ERROR, board, "Invalid ep ioctl code 0x%x (nr: %d)",
cmd, _IOC_NR(cmd));
return -ENOTTY;
}
}
static long calculet_proc_ioctl(struct calculet_pcie_board *board, unsigned int cmd, unsigned long arg)
{
if (!board || !calculet_board_is_operational(board)) {
return -ENODEV;
}
switch (cmd) {
case CALCULET_GET_PROCESS_LIST:
return calculet_get_process_list_ioctl(board, arg);
default:
calculet_board_log(CALCULET_LOG_ERROR, board, "Invalid proc ioctl code 0x%x (nr: %d)",
cmd, _IOC_NR(cmd));
return -ENOTTY;
}
}
long calculet_pcie_fops_unlockedioctl(struct file* filp, unsigned int cmd, unsigned long arg)
{
long err = 0;
struct calculet_file_private *priv = (struct calculet_file_private*)filp->private_data;
struct calculet_pcie_board *board;
if (!priv || !priv->is_valid) {
calculet_log(CALCULET_LOG_ERROR, "Invalid file private data");
return -ENODEV;
}
board = priv->board;
if (!board || !board->pDev) {
calculet_log(CALCULET_LOG_ERROR, "Board is NULL or invalid");
return -ENODEV;
}
// 获取文件私有数据引用
calculet_file_private_get(priv);
// 再次检查板卡状态
if (!calculet_board_is_operational(board)) {
err = -ENODEV;
goto out;
}
// 更新进程访问记录
calculet_proc_update_process_access(board, current->pid);
switch (_IOC_TYPE(cmd)) {
case CALCULET_GENERAL_IOCTL_MAGIC:
err = calculet_general_ioctl(board, cmd, arg);
break;
case CALCULET_DMA_IOCTL_MAGIC:
err = calculet_dma_ioctl(&board->pcie_resources.dma_registers, &board->cdma, cmd, arg);
break;
case CALCULET_PCI_EP_IOCTL_MAGIC:
err = calculet_ep_ioctl(board, cmd, arg);
break;
case CALCULET_PROC_IOCTL_MAGIC:
err = calculet_proc_ioctl(board, cmd, arg);
break;
default:
calculet_board_log(CALCULET_LOG_ERROR, board, "Invalid ioctl type %d", _IOC_TYPE(cmd));
err = -ENOTTY;
}
out:
// 释放文件私有数据引用
calculet_file_private_put(priv);
return err;
}
int calculet_pcie_fops_mmap(struct file* filp, struct vm_area_struct *vma)
{
struct calculet_file_private *priv = (struct calculet_file_private*)filp->private_data;
struct calculet_pcie_board *board;
struct calculet_resource *resource;
unsigned long offset = vma->vm_pgoff << PAGE_SHIFT;
unsigned long size = vma->vm_end - vma->vm_start;
unsigned long pfn;
int bar_num = -1;
if (!priv || !priv->is_valid) {
return -ENODEV;
}
board = priv->board;
if (!board || !calculet_board_is_operational(board)) {
return -ENODEV;
}
calculet_board_log(CALCULET_LOG_DEBUG, board, "mmap called by PID %d", current->pid);
// 根据offset判断映射哪个空间
if (offset == MMAP_BAR2_OFFSET) {
resource = &board->pcie_resources.rt_registers;
bar_num = 2;
size = resource->size;
pfn = resource->phy_address >> PAGE_SHIFT;
// BAR空间:不可缓存
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
if (io_remap_pfn_range(vma, vma->vm_start, pfn,
size, vma->vm_page_prot))
return -EAGAIN;
} else if (offset == MMAP_BAR4_OFFSET) {
bar_num = 4;
size = resource->size;
pfn = resource->phy_address >> PAGE_SHIFT;
vma->vm_page_prot = pgprot_noncached(vma->vm_page_prot);
if (io_remap_pfn_range(vma, vma->vm_start, pfn,
size, vma->vm_page_prot))
return -EAGAIN;
} else if (offset == MMAP_DMA_OFFSET) {
// // DMA buffer映射(这是系统内存,不是IO空间)
// if (size > pdev->dma_size)
// return -EINVAL;
// // DMA内存可以使用write-combine优化
// vma->vm_page_prot = pgprot_writecombine(vma->vm_page_prot);
// // 使用dma_mmap_coherent映射DMA内存
// if (dma_mmap_coherent(&pdev->pci_dev->dev, vma,
// pdev->dma_virt, pdev->dma_handle,
// pdev->dma_size))
// return -EAGAIN;
} else {
printk(KERN_ERR "Invalid mmap offset: 0x%lx\n", offset);
return -EINVAL;
}
printk(KERN_INFO "mmap: offset=0x%lx, size=%lu, bar=%d\n",
offset, size, bar_num);
return 0;
}