如何在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
相关产品推荐
相关产品推荐

