You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在C语言中无重叠读取硬件环形FIFO?

环形FIFO无重叠读取问题及代码修复

我有一款硬件,可通过硬件方式将输入数据写入指定地址的环形FIFO,FIFO基地址类似0xc6dc9cf0,配有环形缓冲区配置寄存器、写指针和读指针。每次写入时硬件会自动递增写指针,当写指针超出设定限制时触发中断。现在需要实现无重叠读取这个环形FIFO,但当前代码运行效果不佳,有时会写入硬件未写入的错误内存数据。注意:当write_pointer >= BUFFER_MAX_SIZE+16时,写指针会归零。

原问题代码

#include <linux/module.h>
#include <linux/init.h>
#include <linux/completion.h>
#include <linux/slab.h>
#include <linux/dmaengine.h>
#include <linux/dma-mapping.h>
#include <linux/iommu-helper.h>
#include <linux/delay.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <linux/vmalloc.h>
#include <linux/kthread.h>
#include <linux/socket.h>
#include <net/sock.h>

#define TSC_BASE_ADDRESS    0x01C06000
#define TSG_BASE_ADDRESS    0x01C06040
#define TSF_BASE_ADDRESS    0x01C06080
#define TSD_BASE_ADDRESS    0x01C06180

#define BUFFER_MAX_SIZE     1048576
void __iomem* tscBaseAddress;

volatile u8 *bufferBaseAddress;

volatile u32 *TSF_CBWPR;
volatile u32 *TSF_CBRPR;
volatile u32 *TSF_CBBAR;
volatile u32 *TSF_CBSZR;

volatile int flag=0;
volatile int record_flag=1;
volatile int read_pointer;
volatile int write_pointer;

int fbuffer_len=0;
unsigned long long offset=0;
struct file *f;

struct task_struct *thread_st;

int thread_fn(void *unused) 
{
    int i=0,len;
    unsigned char *fbuffer=kmalloc(BUFFER_MAX_SIZE+16,GFP_KERNEL);
    while (record_flag)
    {
        while(!flag){};
        flag=0;
        len=write_pointer-read_pointer;
        if(len<0)
            len=(BUFFER_MAX_SIZE-read_pointer)+write_pointer;
        fbuffer_len=len;
        while(len>0)
        {
            fbuffer[i++]=*(bufferBaseAddress+read_pointer);
            len--;
            read_pointer++;
            if(read_pointer>=(BUFFER_MAX_SIZE+16))
                read_pointer=0;
        }
        *TSF_CBRPR=read_pointer;
        file_write(f,offset,fbuffer,fbuffer_len);
        offset+=fbuffer_len;
        i=0;
    }
    record_flag=1;
    return 0;
}

static irqreturn_t tsc_irq_handler(int irq,void *dev_id) 
{
    read_pointer  = *TSF_CBRPR;
    write_pointer = *TSF_CBWPR;
    *TSF_DISR=0x00000001;//clear interrupt
    flag=1;
    return IRQ_HANDLED;
}

static int tsc_init(void)
{
    int ret;
    u32 tmp;
    unsigned long value;
    tscBaseAddress = ioremap(TSC_BASE_ADDRESS, 4096);
    if (tscBaseAddress == NULL)
    {
        printk("Failed to map memory to TSC\n");
        return -1;
    }
    
    TSF_CBBAR     = (volatile u32 *)(tscBaseAddress+0x80+0x50);
    TSF_CBSZR     = (volatile u32 *)(tscBaseAddress+0x80+0x54);
    TSF_CBWPR     = (volatile u32 *)(tscBaseAddress+0x80+0x58);
    TSF_CBRPR     = (volatile u32 *)(tscBaseAddress+0x80+0x5C);
    
    printk("TSC Successfully mapped in memory\n");
    
    ret = request_irq(113, tsc_irq_handler, IRQ_NONE, "TSC Driver", (void *)(tsc_irq_handler));
    if (ret < 0) 
    {
        printk(KERN_ALERT "%s: request_irg failed with %d\n",__func__, ret);
    }
    printk("IRQ TSC done\n");
    //--------------------------------------------------------------------------------
    printk("TSF_CBWPR:%x\n",*TSF_CBWPR);
    *TSF_CBWPR=0x00000000;
    mdelay(100);
    printk("TSF_CBWPR:%x\n",*TSF_CBWPR);
    
    printk("TSF_CBRPR:%x\n",*TSF_CBRPR);
    *TSF_CBRPR=0x00000000;
    mdelay(100);
    printk("TSF_CBRPR:%x\n",*TSF_CBRPR);

    printk("TSF_CBSZR:%x\n",*TSF_CBSZR);
    *TSF_CBSZR=0x02100000;
    mdelay(100);
    printk("TSF_CBSZR:%x\n",*TSF_CBSZR);
    //--------------------------------------------------------------------------------
    printk("TSF_CBBAR:%x\n",*TSF_CBBAR);
    value=(*TSF_CBBAR);
    bufferBaseAddress     = (volatile u8 *)(value);
    //--------------------------------------------------------------------------------
    thread_st = kthread_run(thread_fn, NULL, "my_kthread");
        if (IS_ERR(thread_st)) 
    {
            printk(KERN_ERR "Error creating thread\n");
            return PTR_ERR(thread_st);
        }
    printk(KERN_INFO "Kernel Thread Created\n");
    f=file_open("/root/Desktop/a.ts",O_WRONLY|O_CREAT, 0644);
    if (IS_ERR(f)) 
    {
            printk(KERN_ERR "Failed to create file: %ld\n", PTR_ERR(f));
            return PTR_ERR(f);
        }
    printk("Create file ok\n");
    offset=0;
    return 0;
}

static void tsc_exit(void)
{
    free_irq(113,(void *)(tsc_irq_handler));
    if (client_socket) 
        sock_release(client_socket);
        if (listen_socket) 
        sock_release(listen_socket);
    flag=1;
    record_flag=0;
    file_close(f);
}

问题根源分析

  • 指针同步失效:中断中读取的指针是瞬间值,线程处理过程中硬件可能持续写入,导致指针偏移,读取到未写入的无效数据。
  • 内存缓存一致性问题:直接访问bufferBaseAddress可能读取CPU缓存中的旧数据,未同步硬件写入的最新内容。
  • 同步机制不可靠:用全局变量flag做线程唤醒,未使用原子操作或内存屏障,存在竞态条件。
  • 指针边界处理错误:计算数据长度时未考虑FIFO实际总长度(BUFFER_MAX_SIZE+16),且更新硬件读指针的时机过早,可能导致硬件覆盖未读取数据。

修复方案

1. 替换同步机制为Completion

用内核completion替代全局flag,实现可靠的线程唤醒:

// 全局变量添加
static struct completion irq_completion;

// 初始化函数中添加
init_completion(&irq_completion);

// 中断处理函数修改
static irqreturn_t tsc_irq_handler(int irq,void *dev_id) 
{
    writel(0x00000001, tscBaseAddress + 0x80 + 0xXX); // 替换为TSF_DISR的实际偏移地址
    complete(&irq_completion);
    return IRQ_HANDLED;
}

// 线程中等待唤醒
wait_for_completion_interruptible(&irq_completion);
reinit_completion(&irq_completion);

2. 确保指针读取的原子性与数据可见性

使用READ_ONCE/WRITE_ONCE宏保证指针读写原子性,添加内存屏障同步硬件数据:

// 读取指针时
u32 current_wp = READ_ONCE(*TSF_CBWPR);
u32 current_rp = READ_ONCE(read_pointer);
smp_mb(); // 确保硬件写入的数据已同步到内存

// 更新硬件指针时
WRITE_ONCE(*TSF_CBRPR, current_rp);
smp_wmb(); // 确保指针更新已同步到硬件

3. 修正数据长度计算逻辑

基于FIFO实际总长度(BUFFER_MAX_SIZE+16)计算有效数据,避免越界:

u32 fifo_total_size = BUFFER_MAX_SIZE + 16;
int len;

if (current_wp >= current_rp) {
    len = current_wp - current_rp;
} else {
    len = (fifo_total_size - current_rp) + current_wp;
}

4. 正确访问硬件缓冲区

用ioread8替代直接指针访问,或使用ioremap_nocache映射内存,避免缓存一致性问题:

// 替换直接读取
fbuffer[i++] = ioread8(bufferBaseAddress + current_rp);

// 或初始化时替换ioremap
tscBaseAddress = ioremap_nocache(TSC_BASE_ADDRESS, 4096);

5. 调整硬件读指针更新时机

读取完所有数据后再更新硬件读指针,防止硬件覆盖未读取的数据:

// 读取完所有数据后再更新
WRITE_ONCE(read_pointer, current_rp);
WRITE_ONCE(*TSF_CBRPR, current_rp);

修复后核心代码示例

static struct completion irq_completion;
volatile u32 read_pointer;
volatile u32 write_pointer;

int thread_fn(void *unused) 
{
    int i=0, len;
    u32 fifo_total_size = BUFFER_MAX_SIZE + 16;
    unsigned char *fbuffer = kmalloc(fifo_total_size, GFP_KERNEL);
    if (!fbuffer) {
        printk(KERN_ERR "Failed to allocate fbuffer\n");
        return -ENOMEM;
    }

    while (record_flag)
    {
        wait_for_completion_interruptible(&irq_completion);
        reinit_completion(&irq_completion);

        u32 current_wp = READ_ONCE(*TSF_CBWPR);
        u32 current_rp = READ_ONCE(read_pointer);
        smp_mb();

        // 计算有效数据长度
        if (current_wp >= current_rp) {
            len = current_wp - current_rp;
        } else {
            len = (fifo_total_size - current_rp) + current_wp;
        }
        if (len <= 0) continue;
        fbuffer_len = len;
        i = 0;

        while (len > 0)
        {
            fbuffer[i++] = ioread8(bufferBaseAddress + current_rp);
            len--;
            current_rp++;
            if (current_rp >= fifo_total_size) {
                current_rp = 0;
            }
        }

        // 更新指针
        WRITE_ONCE(read_pointer, current_rp);
        WRITE_ONCE(*TSF_CBRPR, current_rp);
        smp_wmb();

        file_write(f, offset, fbuffer, fbuffer_len);
        offset += fbuffer_len;
    }
    kfree(fbuffer);
    record_flag = 1;
    return 0;
}

static int tsc_init(void)
{
    // ... 原有初始化代码 ...
    init_completion(&irq_completion);
    // ... 原有初始化代码 ...
}

内容的提问来源于stack exchange,提问作者pirozy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 13:00:54