最近項目壓力比較大,為了趕時間很多代碼都得圖簡便,然而碰到的問題還是需要重新整理一下,即便當時不懂事后也得弄清楚。項目的主要任務是一個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就是根據直接讀到的原始數據繪制的波形,對應下面的圖就是經過傅里葉變換計算得到的頻譜圖。
好了就寫這么多,睡了。
此時此刻,竟身披北京國安,與陌生人道晚安。