最近项目的几个问题
最近项目压力比较大,为了赶时间很多代码都得图简便,然而碰到的问题还是需要重新整理一下,即便当时不懂事后也得弄清楚。项目的主要任务是一个C6678的PCI板卡驱动,用于FFT计算,一个图形界面显示程序显示处理前后结果。设备操作上,需要实时从C6678的内存中读取两个数据,一个是64KB的unsigned short型原始数据,一个是128KB处理后的float型数据。
项目里使用了一个不错的绘图类库:
- QCustomplot:包含了许多基本的绘图操作,可以在其基础上作二次开发。
官网:http://www.qcustomplot.com/
先说说PCI设备驱动的一些重要接口和流程,Linux设备驱动框架不想谈了,随便找本驱动的书都有。首先是设备的探测,在linux系统下使用命令lspci -vv可以看到已插入的板卡的一些信息,包括设备名称及地址空间等。板卡上有4个C6678 DSP,所以能够发现4路设备。
程序中初始化设备驱动,首先调用pci_get_device接口发现PCI设备,因为有4路DSP,所以循环调用了4次,而且这里将板卡当做字符设备处理。
1 for (index=0;index<MAX_NUM;index++) { 2 C6678DSP[index] = pci_get_device(VENDOR_ID , DEVICE_ID , prev_node); 3 if (!C6678DSP[index]) { 4 printk("<C6678DSP>: No C6678DSP%d Card Found!\n",index); 5 return -ENXIO; 6 } 7 else { 8 prev_node = C6678DSP[index]; 9 printk("<C6678DSP>: C6678DSP%d Card Found!\n",index); 10 }
接着第二步,使能设备:
1 if (pci_enable_device(C6678DSP[index])) 2 return -EIO;
第三步,获取设备的起始和结束地址,获取的这个值可以和前面lspci -vv的结果对比看是否一致。
1 base0start=pci_resource_start(C6678DSP[index],0); 2 endsrc0=pci_resource_end(C6678DSP[index],0); 3 base0len=endsrc0-base0start;
第四步,这一步是本项目里面难以理解的一个问题,甚至我自己都有点没弄清。将读取到的起始地址又回写到PCI设备的地址空间中,这点非常不解,一开始我并没有在驱动的初始化里加这两句,因为一般的PCI设备驱动都没见这么做的。但驱动程序加载后看到分配的地址是错误的,后来在别人的提示下加了这两句,果然能够正常工作了,理由是可能BIOS做的不够好,不能正确获取到板卡配置空间的信息,而Linux则可以。
1 pci_write_config_dword(C6678DSP[index],0x10,base0start); 2 pci_write_config_dword(C6678DSP[index],0x14,base1start);
接下来调用ioremap进行内存映射,中间的一些代码我都省略了,不通用。
1 baseAddrDoThingA = ioremap(base0start,base0len); 2 baseAddrDoThingB = ioremap(base1start,base1len);
后面就是字符设备驱动的一些流程了,多说无益。创建设备文件,注册设备。
1 //register major device 2 dev = MKDEV(c6678_major, 0); 3 if (c6678_major) { 4 ret = register_chrdev_region(dev, 1, "C6678DSP"); 5 printk("<C6678DSP>:C6678DSP major is defined!\n"); 6 } else { 7 ret = alloc_chrdev_region(&dev, 0, 1, "C6678DSP"); 8 c6678_major = MAJOR(dev); 9 printk("<C6678DSP>:C6678DSP major is not defined!\n"); 10 } 11 if (ret <0) { 12 printk("<C6678DSP>: alloc C6678DSP device number error\n"); 13 return ret; 14 } 15 16 c6678_setup_cdev(&c6678_cdev, 0); 17 printk("<C6678DSP>: C6678DSP major is %d. \n", c6678_major); 18 19 /* make fs node */ 20 ret = mknod("/dev/C6678", S_IFCHR | S_IRWXU | S_IRWXG | S_IRWXO, dev); 21 if (ret) { 22 printk("<C6678>Mknod error.\n"): 23 return ret; 24 } 25 26 printk("Node make finished.\n"); 27 return 0; 28 29 fail_req_irq: 30 fail_alloc_sendq: 31 cdev_del(&c6678_cdev.cdev); 32 unregister_chrdev_region(dev, 1); 33 return -1; 34 }
这是驱动的初始化,完成以后,就可以编写针对你自己设备的read/write/ioctl等设备使用接口了。
驱动编写完毕后,编译成一个.ko文件,insmod ./C6678.ko后便可以在lsmod及/proc/device/下看到自己的设备了。接下来使用如下命令创建对应文件节点,因为分配的主设备号为248,次设备号位0,字符型设备所以这么写。
1 mknod /dev/C6678 c 248 0
运行了一下读写接口的测试程序,正确无误,接下来就开始编写界面程序了。
然而编写界面程序并不顺利,起初想将直接从硬件读取到的数据放在buffer中,然后直接绘图,但出了个棘手的问题。
来看一段代码,需要从指定文件读64KB的数据到buffer中,进行后续处理。乍一看并没有什么问题,编译一下依然没有什么问题。然而一运行就崩了,而且每次都蹦,试想一下是为什么?
1 void MainWindow::addGraphRaw(int index) 2 { 3 int n = 32768; // number of FFT points in graph 4 QVector<double> x(n), y(n); 5 unsigned short *buffer = new unsigned short [1024*32] ; 6 7 ifstream fin(datafiles[index].toStdString().c_str()); 8 if (!fin.is_open()) { 9 std::cout<<"Cannot find the datafiles."<<std::endl; 10 return ; 11 } 12 13 14 fin.read((char *)buffer, 1024*32*2); 15 16 for (int i=0; i<n; i++) { 17 x[i] = i; 18 y[i] =( (double)buffer[i])/64; 19 }
最开始几次并没有报出什么错误,但后来每次都会弹出一个对话框,报系统SIGBUS信号,导致程序崩溃。SIGBUS信号是好像是内存访问相关的,于是在网上搜索了一下SIGBUS相关的问题。来看看一般是怎么说的(基本都是这种类似的说法):
SIGBUS(Bus error)意味着指针所对应的地址是有效地址,但总线不能正常使用该指针。通常是未对齐的数据访问所致。
SIGSEGV(Segment fault)意味着指针所对应的地址是无效地址,没有物理内存对应该地址。
觉得有点道理,但又不很明白。这个界面程序我在x86的机器上跑不会有任何问题,而在龙芯的机器上一跑就SIGBUS。之后又查到的一种说法:
CPU处于性能方面的考虑,要求对数据进行访问时都必须是地址对齐的。如果发现进行的不是地址对齐的访问,就会发送SIGBUS信号给进程,使进程产生 core dump。RISC包括SPARC(一种微处理器架构)都是这种类型的芯片。x86系列CPU都支持不对齐访问,也提供了开关禁用这个机制。x86架构不要求对齐访问的时候,必定会有性能代价。例如,对int的访问应该是4字节对齐的,即地址应该是4的倍数,对short则是2字节对齐的,地址应该是2的倍数。
摘自:http://blog.csdn.net/klarclm/article/details/8509552
个人觉得这种说法就明白多了,龙芯是MIPS架构,与SPARC存在同样的问题,必须地址对齐,而x86则支持不对齐访问,这也就解释了为什么程序在x86机器上能够正常运行,一到龙芯机器上就SIGBUS。这点也是项目中最诡异的一个点。而且最后为了避免这个问题,不得已将直接读取出来的数据分别保存在一个Raw文件一个Proc文件中,供绘图时再次读取(而不是之前直接用读取的数据绘图,一用就SIGBUS)。当然这也降低了程序的性能,虽然不多。
最后的效果图如下,只开了一路A/D转换作为示例,原始输入波形是一个100MHz,0.5Vpp的正弦波,图1就是根据直接读到的原始数据绘制的波形,对应下面的图就是经过傅里叶变换计算得到的频谱图。
好了就写这么多,睡了。
此时此刻,竟身披北京国安,与陌生人道晚安。