最近項目壓力比較大,爲了趕時間不少代碼都得圖簡便,然而碰到的問題仍是須要從新整理一下,即使當時不懂過後也得弄清楚。項目的主要任務是一個C6678的PCI板卡驅動,用於FFT計算,一個圖形界面顯示程序顯示處理先後結果。設備操做上,須要實時從C6678的內存中讀取兩個數據,一個是64KB的unsigned short型原始數據,一個是128KB處理後的float型數據。node
項目裏使用了一個不錯的繪圖類庫:linux
官網: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的結果對比看是否一致。spa
1 base0start=pci_resource_start(C6678DSP[index],0); 2 endsrc0=pci_resource_end(C6678DSP[index],0); 3 base0len=endsrc0-base0start;
第四步,這一步是本項目裏面難以理解的一個問題,甚至我本身都有點沒弄清。將讀取到的起始地址又回寫到PCI設備的地址空間中,這點很是不解,一開始我並無在驅動的初始化里加這兩句,由於通常的PCI設備驅動都沒見這麼作的。但驅動程序加載後看到分配的地址是錯誤的,後來在別人的提示下加了這兩句,果真可以正常工做了,理由是可能BIOS作的不夠好,不能正確獲取到板卡配置空間的信息,而Linux則能夠。.net
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);
後面就是字符設備驅動的一些流程了,多說無益。建立設備文件,註冊設備。code
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就是根據直接讀到的原始數據繪製的波形,對應下面的圖就是通過傅里葉變換計算獲得的頻譜圖。
好了就寫這麼多,睡了。
此時此刻,竟身披北京國安,與陌生人道晚安。