Files
calculet-npu-research-archive/source/driver-source/source/cal-pcie-0.9.0/dma.c
T

966 lines
34 KiB
C

// SPDX-License-Identifier: GPL-2.0
/**
* Copyright (c) 2024-2025 CALCULET Technologies Ltd. All rights reserved.
*/
#include "dma.h"
#include "board_mgr.h"
#include "utils.h"
#include <linux/sched.h>
#include <linux/version.h>
#include <linux/slab.h>
#include <linux/dma-mapping.h>
#include <linux/mm.h>
#include <linux/uaccess.h>
#include <asm/cacheflush.h>
#if LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0)
#include <linux/dma-map-ops.h>
#else
#include <linux/dma-mapping.h>
#endif
static int calculet_set_dma_mask(struct device *dev)
{
int err = -EINVAL;
/* Check and configure DMA length */
if (!(err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(64)))) {
calculet_dev_log(CALCULET_LOG_INFO, dev, "Enabled 64 bit dma");
} else if (!(err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(48)))) {
calculet_dev_log(CALCULET_LOG_DEBUG, dev, "Enabled 48 bit dma");
} else if (!(err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(40)))) {
calculet_dev_log(CALCULET_LOG_DEBUG, dev, "Enabled 40 bit dma");
} else if (!(err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(36)))) {
calculet_dev_log(CALCULET_LOG_DEBUG, dev, "Enabled 36 bit dma");
} else if (!(err = dma_set_mask_and_coherent(dev, DMA_BIT_MASK(32)))) {
calculet_dev_log(CALCULET_LOG_INFO, dev, "Enabled 32 bit dma");
} else {
calculet_dev_log(CALCULET_LOG_ERROR, dev, "Error enabling dma %d", err);
return err;
}
return 0;
}
// DMA通道状态管理函数(简化,减少不必要的锁)
void calculet_dma_channel_set_state(struct calculet_dma_channel *channel,
enum calculet_dma_channel_state state)
{
if (channel && channel->state != state) {
calculet_log(CALCULET_LOG_DEBUG, "DMA channel %d state change: %d -> %d",
channel->channel_index, channel->state, state);
WRITE_ONCE(channel->state, state);
channel->last_used = ktime_get();
}
}
enum calculet_dma_channel_state calculet_dma_channel_get_state(struct calculet_dma_channel *channel)
{
return channel ? READ_ONCE(channel->state) : DMA_CHANNEL_ERROR;
}
bool calculet_dma_channel_is_available(struct calculet_dma_channel *channel)
{
if (!channel) return false;
enum calculet_dma_channel_state state = calculet_dma_channel_get_state(channel);
return (state == DMA_CHANNEL_IDLE);
}
// 通道分配和释放(简化,使用原子操作)
int calculet_dma_allocate_channel(struct calculet_dma_controller *controller,
enum calculet_transfer_direction direction,
int *channel_id)
{
unsigned long *bitmap;
struct calculet_dma_channel *channels;
int max_channels;
int id;
if (!controller || !channel_id) {
return -EINVAL;
}
if (direction == TRANSFER_H2C_DIRECTION) {
bitmap = &controller->read_channels_bitmap;
channels = controller->dma_rdch_s;
max_channels = CALCULET_READ_CHANNELS;
} else {
bitmap = &controller->write_channels_bitmap;
channels = controller->dma_wrch_s;
max_channels = CALCULET_WRITE_CHANNELS;
}
// 查找可用通道(无锁版本)
do {
id = find_first_zero_bit(bitmap, max_channels);
if (id >= max_channels) {
return -EBUSY;
}
// 检查通道状态
if (!calculet_dma_channel_is_available(&channels[id])) {
continue;
}
} while (test_and_set_bit(id, bitmap));
calculet_dma_channel_set_state(&channels[id], DMA_CHANNEL_BUSY);
*channel_id = id;
calculet_log(CALCULET_LOG_DEBUG, "Allocated %s channel %d",
direction == TRANSFER_H2C_DIRECTION ? "H2C" : "C2H", id);
return 0;
}
void calculet_dma_release_channel(struct calculet_dma_controller *controller,
enum calculet_transfer_direction direction,
int channel_id)
{
unsigned long *bitmap;
struct calculet_dma_channel *channels;
int max_channels;
if (!controller) {
return;
}
if (direction == TRANSFER_H2C_DIRECTION) {
bitmap = &controller->read_channels_bitmap;
channels = controller->dma_rdch_s;
max_channels = CALCULET_READ_CHANNELS;
} else {
bitmap = &controller->write_channels_bitmap;
channels = controller->dma_wrch_s;
max_channels = CALCULET_WRITE_CHANNELS;
}
if (channel_id >= 0 && channel_id < max_channels) {
clear_bit(channel_id, bitmap);
calculet_dma_channel_set_state(&channels[channel_id], DMA_CHANNEL_IDLE);
calculet_log(CALCULET_LOG_DEBUG, "Released %s channel %d",
direction == TRANSFER_H2C_DIRECTION ? "H2C" : "C2H", channel_id);
}
}
// 传输上下文管理(简化)
struct calculet_dma_transfer_context* calculet_dma_create_transfer_context(pid_t pid)
{
struct calculet_dma_transfer_context *ctx;
ctx = kzalloc(sizeof(*ctx), GFP_KERNEL);
if (!ctx) {
return NULL;
}
init_completion(&ctx->completion);
ctx->result = 0;
ctx->start_time = ktime_get();
ctx->requester_pid = pid;
atomic_set(&ctx->ref_count, 1);
return ctx;
}
void calculet_dma_destroy_transfer_context(struct calculet_dma_transfer_context *ctx)
{
if (ctx) {
kfree(ctx);
}
}
void calculet_dma_transfer_context_get(struct calculet_dma_transfer_context *ctx)
{
if (ctx) {
atomic_inc(&ctx->ref_count);
}
}
void calculet_dma_transfer_context_put(struct calculet_dma_transfer_context *ctx)
{
if (ctx && atomic_dec_and_test(&ctx->ref_count)) {
calculet_dma_destroy_transfer_context(ctx);
}
}
int calculet_dma_controller_init(struct calculet_dma_controller *controller, struct device *dev, bool poll_en)
{
int i;
int err = 0;
struct calculet_dma_poll *poll;
if (!dev) {
calculet_log(CALCULET_LOG_ERROR, "Device pointer is NULL");
return -EINVAL;
}
controller->dev = dev;
controller->controller_enabled = false;
// 初始化控制器级别的锁和状态
mutex_init(&controller->controller_mutex);
mutex_init(&controller->allocation_mutex);
atomic_set(&controller->active_transfers, 0);
// 初始化通道分配位图
controller->read_channels_bitmap = 0;
controller->write_channels_bitmap = 0;
/* Check and configure DMA length */
err = calculet_set_dma_mask(dev);
if (0 > err) {
return err;
}
if (get_dma_ops(controller->dev)) {
calculet_dev_log(CALCULET_LOG_DEBUG, controller->dev, "Using specialized dma_ops=%ps", get_dma_ops(controller->dev));
}
else {
calculet_dev_log(CALCULET_LOG_WARN, dev, "No specialized dma_ops found");
}
// 初始化读通道(简化初始化)
for(i = 0; i < CALCULET_READ_CHANNELS; i++) {
struct calculet_dma_channel *channel = &controller->dma_rdch_s[i];
memset(channel, 0, sizeof(*channel));
channel->channel_index = i;
channel->state = DMA_CHANNEL_IDLE;
spin_lock_init(&channel->state_lock);
mutex_init(&channel->buffer_mutex);
mutex_init(&channel->transfer_mutex);
atomic_set(&channel->buffer_users, 0);
atomic_set(&channel->transfer_finish, 0);
atomic64_set(&channel->transfer_count, 0);
atomic64_set(&channel->transfer_bytes, 0);
init_waitqueue_head(&channel->channel_wq);
}
// 初始化写通道
for(i = 0; i < CALCULET_WRITE_CHANNELS; i++) {
struct calculet_dma_channel *channel = &controller->dma_wrch_s[i];
memset(channel, 0, sizeof(*channel));
channel->channel_index = i;
channel->state = DMA_CHANNEL_IDLE;
spin_lock_init(&channel->state_lock);
mutex_init(&channel->buffer_mutex);
mutex_init(&channel->transfer_mutex);
atomic_set(&channel->buffer_users, 0);
atomic_set(&channel->transfer_finish, 0);
atomic64_set(&channel->transfer_count, 0);
atomic64_set(&channel->transfer_bytes, 0);
init_waitqueue_head(&channel->channel_wq);
}
// 初始化轮询模式
if(poll_en) {
/* 分配 DMA Kthread 结构体内存 */
if(!controller->dma_kthread) {
controller->dma_kthread = devm_kzalloc(dev, sizeof(struct calculet_dma_kthread), GFP_KERNEL);
if (!controller->dma_kthread) {
return -ENOMEM;
}
}
/* 分配 DMA Poll 结构体内存 */
if (!controller->dma_kthread->dma_poll) {
controller->dma_kthread->dma_poll = devm_kzalloc(dev, sizeof(struct calculet_dma_poll), GFP_KERNEL);
if (!controller->dma_kthread->dma_poll) {
return -ENOMEM;
}
}
poll = controller->dma_kthread->dma_poll;
poll->status_region = dma_alloc_coherent(dev, sizeof(uint32_t), &poll->status_region_phys, GFP_KERNEL);
if (!poll->status_region) {
calculet_dev_log(CALCULET_LOG_ERROR, dev, "Failed to allocate coherent memory for status region");
return -ENOMEM;
}
spin_lock_init(&poll->poll_lock);
atomic_set(&poll->poll_active, 0);
for (i = 0; i < CALCULET_TOTAL_CHANNELS; i++) {
poll->channel_data[i] = i + 100;
}
// 初始化内核线程
atomic_set(&controller->dma_kthread->should_stop, 0);
init_completion(&controller->dma_kthread->thread_completion);
}
controller->controller_enabled = true;
calculet_log(CALCULET_LOG_DEBUG, "DMA controller initialized (poll_mode: %s)",
poll_en ? "enabled" : "disabled");
return 0;
}
void calculet_dma_controller_deinit(struct calculet_dma_controller *controller, bool poll_en)
{
int i;
struct calculet_dma_poll *poll;
if (!controller) {
return;
}
controller->controller_enabled = false;
// 等待所有活动传输完成
while (atomic_read(&controller->active_transfers) > 0) {
msleep(10);
}
// 清理读通道
for(i = 0; i < CALCULET_READ_CHANNELS; i++) {
struct calculet_dma_channel *channel = &controller->dma_rdch_s[i];
// 设置通道为禁用状态
calculet_dma_channel_set_state(channel, DMA_CHANNEL_DISABLED);
// 释放缓冲区
mutex_lock(&channel->buffer_mutex);
if (channel->kernel_buf) {
dma_free_coherent(controller->dev, channel->dma_len,
channel->kernel_buf, channel->dma_handle);
channel->kernel_buf = NULL;
channel->dma_len = 0;
}
mutex_unlock(&channel->buffer_mutex);
// 清理传输上下文
if (channel->current_transfer) {
calculet_dma_transfer_context_put(channel->current_transfer);
channel->current_transfer = NULL;
}
}
// 清理写通道
for(i = 0; i < CALCULET_WRITE_CHANNELS; i++) {
struct calculet_dma_channel *channel = &controller->dma_wrch_s[i];
// 设置通道为禁用状态
calculet_dma_channel_set_state(channel, DMA_CHANNEL_DISABLED);
// 释放缓冲区
mutex_lock(&channel->buffer_mutex);
if (channel->kernel_buf) {
dma_free_coherent(controller->dev, channel->dma_len,
channel->kernel_buf, channel->dma_handle);
channel->kernel_buf = NULL;
channel->dma_len = 0;
}
mutex_unlock(&channel->buffer_mutex);
// 清理传输上下文
if (channel->current_transfer) {
calculet_dma_transfer_context_put(channel->current_transfer);
channel->current_transfer = NULL;
}
}
// 清理轮询相关资源
if(poll_en && controller->dma_kthread) {
poll = controller->dma_kthread->dma_poll;
if (poll) {
for (i = 0; i < CALCULET_TOTAL_CHANNELS; i++) {
poll->channel_data[i] = 0;
}
if (poll->status_region) {
dma_free_coherent(controller->dev, sizeof(uint32_t),
poll->status_region, poll->status_region_phys);
poll->status_region = NULL;
}
}
}
calculet_log(CALCULET_LOG_DEBUG, "DMA controller deinitialized");
}
// DMA参数验证
int calculet_dma_validate_transfer_params(struct calculet_dma_transfer_channels_params *params)
{
if (!params) {
return -EINVAL;
}
// 检查传输方向
if (params->dma_transfer_direction != TRANSFER_H2C_DIRECTION &&
params->dma_transfer_direction != TRANSFER_C2H_DIRECTION) {
return -EINVAL;
}
// 检查通道ID
if (params->dma_transfer_direction == TRANSFER_H2C_DIRECTION) {
if (params->dma_channel_id < 0 || params->dma_channel_id >= CALCULET_READ_CHANNELS) {
return -EINVAL;
}
} else {
if (params->dma_channel_id < 0 || params->dma_channel_id >= CALCULET_WRITE_CHANNELS) {
return -EINVAL;
}
}
// 检查传输大小
if (params->dma_channel_size == 0 || params->dma_channel_size > (32 * 1024 * 1024)) {
return -EINVAL;
}
// 检查地址对齐
if (params->device_address & 0x3) {
calculet_log(CALCULET_LOG_WARN, "Device address 0x%llx is not 4-byte aligned",
params->device_address);
}
return 0;
}
// 安全的DMA传输函数
long calculet_dma_safe_transfer(struct calculet_dma_controller *controller,
struct calculet_dma_transfer_channels_params *params,
struct calculet_resource *resource)
{
struct calculet_dma_channel *channel;
struct calculet_dma_transfer_context *ctx;
long ret;
int ch_id;
if (!controller || !params || !resource) {
return -EINVAL;
}
// 验证传输参数
ret = calculet_dma_validate_transfer_params(params);
if (ret) {
return ret;
}
// 检查控制器状态
if (!controller->controller_enabled) {
return -ENODEV;
}
ch_id = params->dma_channel_id;
// 获取通道
if (params->dma_transfer_direction == TRANSFER_H2C_DIRECTION) {
if (ch_id >= CALCULET_READ_CHANNELS) {
return -EINVAL;
}
channel = &controller->dma_rdch_s[ch_id];
} else {
if (ch_id >= CALCULET_WRITE_CHANNELS) {
return -EINVAL;
}
channel = &controller->dma_wrch_s[ch_id];
}
// 检查通道状态
if (!calculet_dma_channel_is_available(channel)) {
return -EBUSY;
}
// 创建传输上下文
ctx = calculet_dma_create_transfer_context(current->pid);
if (!ctx) {
return -ENOMEM;
}
// 设置通道参数
channel->dma_channel_size = params->dma_channel_size;
channel->buffer = params->host_address;
channel->dst_address = params->device_address;
channel->current_transfer = ctx;
// 增加活动传输计数
atomic_inc(&controller->active_transfers);
// 执行传输
if (params->dma_transfer_direction == TRANSFER_H2C_DIRECTION) {
ret = calculet_dma_h2c_transfer(controller->dev, channel, resource);
} else {
ret = calculet_dma_c2h_transfer(controller->dev, channel, resource);
}
// 清理
channel->current_transfer = NULL;
calculet_dma_transfer_context_put(ctx);
atomic_dec(&controller->active_transfers);
return ret;
}
static long calculet_block_dma_transfer_ioctl(struct calculet_resource *resource, struct calculet_dma_controller *controller, unsigned long arg)
{
struct calculet_dma_transfer_channels_params input;
long ret = -1;
if (copy_from_user(&input, (void *)arg, sizeof(input))) {
calculet_dev_log(CALCULET_LOG_ERROR, controller->dev, "copy_from_user fail");
return -EFAULT;
}
// 使用安全的传输函数
ret = calculet_dma_safe_transfer(controller, &input, resource);
return ret;
}
int calculet_dma_poll_ioctl(struct calculet_dma_controller *controller)
{
struct calculet_dma_poll *poll;
int region_data = 0;
int i;
if (!controller || !controller->dma_kthread || !controller->dma_kthread->dma_poll) {
return -EINVAL;
}
poll = controller->dma_kthread->dma_poll;
spin_lock(&poll->poll_lock);
region_data = *poll->status_region;
if(region_data != 0) {
for(i = 0; i < CALCULET_TOTAL_CHANNELS; i++)
{
if(poll->channel_data[i] == region_data) {
if(i < CALCULET_TOTAL_CHANNELS/2) {
atomic_set(&controller->dma_rdch_s[i].transfer_finish, 1);
wake_up_interruptible(&controller->dma_rdch_s[i].channel_wq);
break;
}
else {
atomic_set(&controller->dma_wrch_s[i - CALCULET_READ_CHANNELS].transfer_finish, 1);
wake_up_interruptible(&controller->dma_wrch_s[i - CALCULET_READ_CHANNELS].channel_wq);
break;
}
}
}
*poll->status_region = 0;
}
spin_unlock(&poll->poll_lock);
return region_data;
}
long calculet_dma_ioctl(struct calculet_resource *resource, struct calculet_dma_controller *controller, unsigned int cmd, unsigned long arg)
{
if (!resource || !controller) {
return -EINVAL;
}
switch(cmd) {
case CALCULET_BLOCK_DMA_TRANSFER:
return calculet_block_dma_transfer_ioctl(resource, controller, arg);
case CALCULET_INTERRUPT_POLL:
return calculet_dma_poll_ioctl(controller);
default:
calculet_dev_log(CALCULET_LOG_ERROR, controller->dev, "Invalid DMA ioctl code 0x%x (nr: %d)", cmd, _IOC_NR(cmd));
return -ENOTTY;
}
}
//block dma api
long calculet_dma_h2c_transfer(struct device *dev, struct calculet_dma_channel *channel, struct calculet_resource *resource)
{
int ch = channel->channel_index;
unsigned long timeout = msecs_to_jiffies(100000); // 设置超时时间为100秒
struct calculet_pcie_board *board = dev_get_drvdata(dev);
long ret = 0;
int transfer_result;
if (!board || !calculet_board_is_operational(board)) {
return -ENODEV;
}
CALCULET_PERF_START(h2c_transfer);
// 获取传输互斥锁
if (mutex_lock_interruptible(&channel->transfer_mutex)) {
return -ERESTARTSYS;
}
// 缓冲区管理
mutex_lock(&channel->buffer_mutex);
if(channel->dma_channel_size <= DMA_CHANNEL_MAX_SIZE) {
if(channel->kernel_buf == NULL) {
channel->kernel_buf = dma_alloc_coherent(dev, DMA_CHANNEL_MAX_SIZE, &channel->dma_handle, GFP_KERNEL);
if (!channel->kernel_buf) {
calculet_dev_log(CALCULET_LOG_ERROR, dev, "Failed to allocate DMA buffer");
ret = -ENOMEM;
goto error_unlock_buffer;
}
}
} else {
calculet_dev_log(CALCULET_LOG_ERROR, dev, "H2C transfer size %ld is too large", channel->dma_channel_size);
ret = -EINVAL;
goto error_unlock_buffer;
}
// if(channel->dma_len != channel->dma_channel_size) {
// if(channel->kernel_buf == NULL) {
// channel->kernel_buf = dma_alloc_coherent(dev, channel->dma_channel_size, &channel->dma_handle, GFP_KERNEL);
// if (!channel->kernel_buf) {
// calculet_dev_log(CALCULET_LOG_ERROR, dev, "Failed to allocate DMA buffer");
// ret = -ENOMEM;
// goto error_unlock_buffer;
// }
// } else {
// dma_free_coherent(dev, channel->dma_len, channel->kernel_buf, channel->dma_handle);
// channel->kernel_buf = dma_alloc_coherent(dev, channel->dma_channel_size, &channel->dma_handle, GFP_KERNEL);
// if (!channel->kernel_buf) {
// calculet_dev_log(CALCULET_LOG_ERROR, dev, "Failed to allocate DMA buffer");
// ret = -ENOMEM;
// goto error_unlock_buffer;
// }
// }
// channel->dma_len = channel->dma_channel_size;
// }
atomic_inc(&channel->buffer_users);
mutex_unlock(&channel->buffer_mutex);
// 从用户空间拷贝数据到DMA一致性内存
if (copy_from_user(channel->kernel_buf, (void __user *)channel->buffer, channel->dma_channel_size)) {
calculet_dev_log(CALCULET_LOG_ERROR, dev, "Failed to copy_from_user");
ret = -EFAULT;
goto error_cleanup;
}
// 配置DMA寄存器
iowrite32(0x10, (u8*)resource->address + DMA_RDCH_CONTROL1_OFF(ch));
iowrite32(PCI_DMA_L(channel->dma_channel_size), (u8*)resource->address + DMA_RDCH_SIZE_OFF(ch));
iowrite32(PCI_DMA_L(channel->dma_handle), (u8*)resource->address + DMA_RDCH_SAR_LOW_OFF(ch));
iowrite32(PCI_DMA_H(channel->dma_handle), (u8*)resource->address + DMA_RDCH_SAR_HIGH_OFF(ch));
iowrite32(PCI_DMA_L(channel->dst_address), (u8*)resource->address + DMA_RDCH_DAR_LOW_OFF(ch));
iowrite32(PCI_DMA_H(channel->dst_address), (u8*)resource->address + DMA_RDCH_DAR_HIGH_OFF(ch));
// 重置完成标志
atomic_set(&channel->transfer_finish, 0);
// 使用资源结构体中的锁
mutex_lock(&resource->dma_global_lock);
iowrite32(ch, (u8*)resource->address + DMA_READ_DOORBELL_OFF);
mutex_unlock(&resource->dma_global_lock);
// 等待传输完成或超时
transfer_result = wait_event_interruptible_timeout(channel->channel_wq,
atomic_read(&channel->transfer_finish) != 0,
timeout);
if (transfer_result > 0) {
int finish_status = atomic_read(&channel->transfer_finish);
if (finish_status == 1) {
// 传输成功
calculet_board_stats_update_dma_h2c(board, channel->dma_channel_size);
calculet_dma_update_channel_stats(channel, channel->dma_channel_size, true);
ret = 0;
} else {
// 传输错误
calculet_dev_log(CALCULET_LOG_ERROR, dev, "H2C transfer failed with status %d", finish_status);
calculet_board_stats_update_error(board);
calculet_dma_update_channel_stats(channel, 0, false);
ret = finish_status;
}
} else if (transfer_result == 0) {
// 超时
calculet_dev_log(CALCULET_LOG_ERROR, dev, "H2C transfer timed out");
calculet_board_stats_update_error(board);
calculet_dma_update_channel_stats(channel, 0, false);
ret = -ETIMEDOUT;
} else {
// 被信号中断
calculet_dev_log(CALCULET_LOG_ERROR, dev, "H2C transfer interrupted");
ret = -ERESTARTSYS;
}
CALCULET_PERF_END(h2c_transfer, "channel %d, size %ld, result %ld",
ch, channel->dma_channel_size, ret);
calculet_dev_log(CALCULET_LOG_DEBUG, dev, "H2C transfer: channel %d, src 0x%llx, dst 0x%llx, size %ld, result %ld",
ch, channel->dma_handle, channel->dst_address, channel->dma_channel_size, ret);
error_cleanup:
mutex_lock(&channel->buffer_mutex);
if (atomic_dec_and_test(&channel->buffer_users)) {
// 如果发生错误,释放缓冲区
if (ret != 0 && channel->kernel_buf) {
dma_free_coherent(dev, channel->dma_channel_size, channel->kernel_buf, channel->dma_handle);
channel->kernel_buf = NULL;
channel->dma_len = 0;
}
}
error_unlock_buffer:
mutex_unlock(&channel->buffer_mutex);
mutex_unlock(&channel->transfer_mutex);
return ret;
}
long calculet_dma_c2h_transfer(struct device *dev, struct calculet_dma_channel *channel, struct calculet_resource *resource)
{
int ch = channel->channel_index;
unsigned long timeout = msecs_to_jiffies(100000); // 设置超时时间为100秒
struct calculet_pcie_board *board = dev_get_drvdata(dev);
long ret = 0;
int transfer_result;
if (!board || !calculet_board_is_operational(board)) {
return -ENODEV;
}
CALCULET_PERF_START(c2h_transfer);
// 获取传输互斥锁
if (mutex_lock_interruptible(&channel->transfer_mutex)) {
return -ERESTARTSYS;
}
// 缓冲区管理
mutex_lock(&channel->buffer_mutex);
if(channel->dma_channel_size <= DMA_CHANNEL_MAX_SIZE) {
if(channel->kernel_buf == NULL) {
channel->kernel_buf = dma_alloc_coherent(dev, DMA_CHANNEL_MAX_SIZE, &channel->dma_handle, GFP_KERNEL);
if (!channel->kernel_buf) {
calculet_dev_log(CALCULET_LOG_ERROR, dev, "Failed to allocate DMA buffer");
ret = -ENOMEM;
goto error_unlock_buffer;
}
}
} else {
calculet_dev_log(CALCULET_LOG_ERROR, dev, "C2H transfer size %ld is too large", channel->dma_channel_size);
ret = -EINVAL;
goto error_unlock_buffer;
}
// if(channel->dma_len != channel->dma_channel_size) {
// if(channel->kernel_buf == NULL) {
// channel->kernel_buf = dma_alloc_coherent(dev, channel->dma_channel_size, &channel->dma_handle, GFP_KERNEL);
// if (!channel->kernel_buf) {
// calculet_dev_log(CALCULET_LOG_ERROR, dev, "Failed to allocate DMA buffer");
// ret = -ENOMEM;
// goto error_unlock_buffer;
// }
// } else {
// dma_free_coherent(dev, channel->dma_len, channel->kernel_buf, channel->dma_handle);
// channel->kernel_buf = dma_alloc_coherent(dev, channel->dma_channel_size, &channel->dma_handle, GFP_KERNEL);
// if (!channel->kernel_buf) {
// calculet_dev_log(CALCULET_LOG_ERROR, dev, "Failed to allocate DMA buffer");
// ret = -ENOMEM;
// goto error_unlock_buffer;
// }
// }
// channel->dma_len = channel->dma_channel_size;
// }
atomic_inc(&channel->buffer_users);
mutex_unlock(&channel->buffer_mutex);
// 配置DMA寄存器
iowrite32(0x10, (u8*)resource->address + DMA_WRCH_CONTROL1_OFF(ch));
iowrite32(PCI_DMA_L(channel->dma_channel_size), (u8*)resource->address + DMA_WRCH_SIZE_OFF(ch));
iowrite32(PCI_DMA_L(channel->dst_address), (u8*)resource->address + DMA_WRCH_SAR_LOW_OFF(ch));
iowrite32(PCI_DMA_H(channel->dst_address), (u8*)resource->address + DMA_WRCH_SAR_HIGH_OFF(ch));
iowrite32(PCI_DMA_L(channel->dma_handle), (u8*)resource->address + DMA_WRCH_DAR_LOW_OFF(ch));
iowrite32(PCI_DMA_H(channel->dma_handle), (u8*)resource->address + DMA_WRCH_DAR_HIGH_OFF(ch));
// 重置完成标志
atomic_set(&channel->transfer_finish, 0);
// 使用资源结构体中的锁
mutex_lock(&resource->dma_global_lock);
iowrite32(ch, (u8*)resource->address + DMA_WRITE_DOORBELL_OFF);
mutex_unlock(&resource->dma_global_lock);
// 等待传输完成或超时
transfer_result = wait_event_interruptible_timeout(channel->channel_wq,
atomic_read(&channel->transfer_finish) != 0,
timeout);
if (transfer_result > 0) {
int finish_status = atomic_read(&channel->transfer_finish);
if (finish_status == 1) {
// 传输成功,将数据从DMA一致性内存复制回用户空间
if (copy_to_user((void __user *)channel->buffer, channel->kernel_buf, channel->dma_channel_size)) {
calculet_dev_log(CALCULET_LOG_ERROR, dev, "Failed to copy_to_user");
calculet_board_stats_update_error(board);
calculet_dma_update_channel_stats(channel, 0, false);
ret = -EFAULT;
} else {
calculet_board_stats_update_dma_c2h(board, channel->dma_channel_size);
calculet_dma_update_channel_stats(channel, channel->dma_channel_size, true);
ret = 0;
}
} else {
// 传输错误
calculet_dev_log(CALCULET_LOG_ERROR, dev, "C2H transfer failed with status %d", finish_status);
calculet_board_stats_update_error(board);
calculet_dma_update_channel_stats(channel, 0, false);
ret = finish_status;
}
} else if (transfer_result == 0) {
// 超时
calculet_dev_log(CALCULET_LOG_ERROR, dev, "C2H transfer timed out");
calculet_board_stats_update_error(board);
calculet_dma_update_channel_stats(channel, 0, false);
ret = -ETIMEDOUT;
} else {
// 被信号中断
calculet_dev_log(CALCULET_LOG_ERROR, dev, "C2H transfer interrupted");
ret = -ERESTARTSYS;
}
CALCULET_PERF_END(c2h_transfer, "channel %d, size %ld, result %ld",
ch, channel->dma_channel_size, ret);
calculet_dev_log(CALCULET_LOG_DEBUG, dev, "C2H transfer: channel %d, src 0x%llx, dst 0x%llx, size %ld, result %ld",
ch, channel->dst_address, channel->dma_handle, channel->dma_channel_size, ret);
mutex_lock(&channel->buffer_mutex);
if (atomic_dec_and_test(&channel->buffer_users)) {
// 如果发生错误,释放缓冲区
if (ret != 0 && channel->kernel_buf) {
dma_free_coherent(dev, channel->dma_channel_size, channel->kernel_buf, channel->dma_handle);
channel->kernel_buf = NULL;
channel->dma_len = 0;
}
}
error_unlock_buffer:
mutex_unlock(&channel->buffer_mutex);
mutex_unlock(&channel->transfer_mutex);
return ret;
}
irqreturn_t calculet_irqhandler(int irq, void *dev_id)
{
irqreturn_t return_value = IRQ_HANDLED;
struct calculet_dma_channel *channel = (struct calculet_dma_channel *)dev_id;
struct calculet_pcie_board *board;
if (!channel) {
return IRQ_NONE;
}
calculet_log(CALCULET_LOG_DEBUG, "IRQ %d for channel %d (vector %d)",
irq, channel->channel_index, channel->vector_index);
// 设置传输完成标志
atomic_set(&channel->transfer_finish, 1);
// 唤醒等待的线程
wake_up_interruptible(&channel->channel_wq);
// 更新中断统计(简化版本)
board = container_of(channel, struct calculet_pcie_board, cdma.dma_rdch_s[channel->channel_index]);
if (!board) {
board = container_of(channel, struct calculet_pcie_board, cdma.dma_wrch_s[channel->channel_index]);
}
if (board) {
calculet_board_stats_update_interrupt(board);
}
return return_value;
}
int calculet_poll_handler(void *data)
{
struct calculet_dma_controller *controller = (struct calculet_dma_controller *)data;
struct calculet_dma_poll *poll = controller->dma_kthread->dma_poll;
int region_data = 0;
int i;
atomic_set(&poll->poll_active, 1);
while(!kthread_should_stop() && !atomic_read(&controller->dma_kthread->should_stop))
{
spin_lock(&poll->poll_lock);
region_data = *poll->status_region;
if(region_data != 0) {
for(i = 0; i < CALCULET_TOTAL_CHANNELS; i++)
{
if(poll->channel_data[i] == region_data) {
if(i < CALCULET_TOTAL_CHANNELS/2) {
atomic_set(&controller->dma_rdch_s[i].transfer_finish, 1);
wake_up_interruptible(&controller->dma_rdch_s[i].channel_wq);
break;
}
else {
atomic_set(&controller->dma_wrch_s[i - CALCULET_READ_CHANNELS].transfer_finish, 1);
wake_up_interruptible(&controller->dma_wrch_s[i - CALCULET_READ_CHANNELS].channel_wq);
break;
}
}
}
*poll->status_region = 0;
}
spin_unlock(&poll->poll_lock);
schedule();
}
atomic_set(&poll->poll_active, 0);
complete(&controller->dma_kthread->thread_completion);
calculet_log(CALCULET_LOG_DEBUG, "Poll handler thread exiting");
return 0;
}
// 统计信息更新(简化版本)
void calculet_dma_update_channel_stats(struct calculet_dma_channel *channel,
size_t bytes, bool success)
{
if (!channel) {
return;
}
atomic64_inc(&channel->transfer_count);
if (success) {
atomic64_add(bytes, &channel->transfer_bytes);
}
channel->last_used = ktime_get();
}
// 控制器状态转储
void calculet_dma_dump_controller_state(struct calculet_dma_controller *controller)
{
int i;
if (!controller) {
return;
}
calculet_log(CALCULET_LOG_DEBUG, "DMA Controller State Dump");
calculet_log(CALCULET_LOG_DEBUG, " Enabled: %s", controller->controller_enabled ? "YES" : "NO");
calculet_log(CALCULET_LOG_DEBUG, " Active Transfers: %d", atomic_read(&controller->active_transfers));
calculet_log(CALCULET_LOG_DEBUG, " Read Channels Bitmap: 0x%lx", controller->read_channels_bitmap);
calculet_log(CALCULET_LOG_DEBUG, " Write Channels Bitmap: 0x%lx", controller->write_channels_bitmap);
// 转储读通道状态
calculet_log(CALCULET_LOG_DEBUG, " Read Channels:");
for (i = 0; i < CALCULET_READ_CHANNELS; i++) {
struct calculet_dma_channel *ch = &controller->dma_rdch_s[i];
calculet_log(CALCULET_LOG_DEBUG, " CH%d: state=%d, transfers=%lld, bytes=%lld",
i, calculet_dma_channel_get_state(ch),
atomic64_read(&ch->transfer_count),
atomic64_read(&ch->transfer_bytes));
}
// 转储写通道状态
calculet_log(CALCULET_LOG_DEBUG, " Write Channels:");
for (i = 0; i < CALCULET_WRITE_CHANNELS; i++) {
struct calculet_dma_channel *ch = &controller->dma_wrch_s[i];
calculet_log(CALCULET_LOG_DEBUG, " CH%d: state=%d, transfers=%lld, bytes=%lld",
i, calculet_dma_channel_get_state(ch),
atomic64_read(&ch->transfer_count),
atomic64_read(&ch->transfer_bytes));
}
}