• 最近项目的几个问题


      最近项目压力比较大,为了赶时间很多代码都得图简便,然而碰到的问题还是需要重新整理一下,即便当时不懂事后也得弄清楚。项目的主要任务是一个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!
    ",index);
     5         return -ENXIO;
     6     }
     7     else {
     8         prev_node = C6678DSP[index];
     9         printk("<C6678DSP>: C6678DSP%d Card Found!
    ",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!
    ");
     6             } else {
     7                 ret = alloc_chrdev_region(&dev, 0, 1, "C6678DSP");
     8                 c6678_major = MAJOR(dev);
     9                 printk("<C6678DSP>:C6678DSP major is not defined!
    ");
    10             }
    11             if (ret <0) {
    12                 printk("<C6678DSP>: alloc C6678DSP device number error
    ");
    13                 return ret;
    14             }
    15             
    16             c6678_setup_cdev(&c6678_cdev, 0);
    17             printk("<C6678DSP>: C6678DSP major is %d. 
    ", 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.
    "):
    23                 return ret;
    24             }
    25 
    26             printk("Node make finished.
    ");
    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就是根据直接读到的原始数据绘制的波形,对应下面的图就是经过傅里叶变换计算得到的频谱图。

          好了就写这么多,睡了。

          此时此刻,竟身披北京国安,与陌生人道晚安。

  • 相关阅读:
    Netty入门(三)之web服务器
    Math对象
    DOM
    BOM
    字符串
    数组
    ajax
    如何安装vue脚手架
    git提交拉取远程仓库
    第九届蓝桥杯,赛后感!!含泪写完。
  • 原文地址:https://www.cnblogs.com/XiaoHDeBlog/p/4929062.html
Copyright © 2020-2023  润新知