這裏寫自定義目錄標題
pcieuitl中關於cap掃描的有一段代碼很有意思,咋一看,數組可能越界,仔細品味,發現寫的挺巧妙的
static void
pci_scan_trad_caps(struct pci_dev *d)
{
word status = pci_read_word(d, PCI_STATUS);--------(1)
byte been_there[256];-------------------------------------------(2)
int where;
if (!(status & PCI_STATUS_CAP_LIST))-------------------(3)
return;
memset(been_there, 0, 256);----------------------------------(4)
where = pci_read_byte(d, PCI_CAPABILITY_LIST) & ~3;------(5)
while (where)
{
byte id = pci_read_byte(d, where + PCI_CAP_LIST_ID); ------(6)
byte next = pci_read_byte(d, where + PCI_CAP_LIST_NEXT) & ~3;-------(7)
if (been_there[where]++)---------------------------------------------------------(8)
break;
if (id == 0xff)-------------------------------------------------------------------------(9)
break;
pci_add_cap(d, where, id, PCI_CAP_NORMAL);------------------------------(10)
where = next;----------------------------------------------------------------------------(11)
}
}
第(1)步,讀取status reg
第(2)步,在棧上申請了一個256Byte的數組been_there[256]。
第(3)步,判斷是否支持PCIe cap,如果設備支持PCIe cap則繼續。
第(4)步,把256Byte的數組been_there[256]對應的內存清零。
第(5)步,讀取PCI_CAPABILITY_LIST(0x34)獲取第一個PCIe的cap的偏移
第(6)步,讀取改cap對應的capid
第(7)步,讀取next cap對應的偏移
第(8)步,實現兩個功能,這裏是先判斷再++。
功能1.判斷改cap是否爲1,如果爲1就退出,就是防止重複添加cap
功能2.cap對應flag–》been_there[where]++變成1
這裏其實有個小問題,應該把8/9步驟 調換位置更好,防止芯片異常導致的id爲ff
第(9)步,判斷id是否爲ff,ff就是鏈路異常或者completion timeout返回的全1的值
第(10)添加改cap到caplist
第(11)步next cap的偏移賦值給where,然後循環讀
那麼問題來了,1、什麼時候while退出,2、怎麼可以保證數組不越界?
仔細看上面兩個圖,然後品味下協議中的下面一段話
問題1、什麼時候while退出
-----找到last cap時,next 就是00,然後賦值爲where,也就退出了
問題2、爲什麼數組不會越界?
------協議規定了,PCI配置空間是256Byte,PCIe extend 配置空間4K,只要芯片沒有異常,where是無論如何都不會超過0xFF的。那麼芯片異常時呢,按照協議,芯片異常時,可能返回全1,讀取8bit的接口,返回值可能是0xFF,也不會越界。
那pciutils的這段代碼有沒有問題呢,很不幸,即使是大神寫的代碼,異常考慮也有瑕疵
問題1
第(8)和(9)最好是調換一下位置,防止flag置1了,但是cap實際上沒有加入list
問題2
由於next沒有是否爲0xFF,當芯片抽風時,next爲0xFF,id不爲0xFF,會死循環,不知道讀取到哪裏去了。
問題3
where沒有判斷是0xFF,可能導致第(6)步,已經讀偏了
下面是修改後的代碼,稍微顯得囉嗦,但是在芯片異常時,這段代碼也不會出問題。爲啥會想到寫的這麼囉嗦呢,真的是拜某廠所賜。不得不說業界有一個“奇葩”(褒義詞)一樣存在的設備廠商,其中一個環節叫做FIT測試,會對設備上所有的電源時鐘等各種各樣你能想到的芯片和你想不到的芯片注入故障,來看看軟件是否能cover的住。這種方式有好的一方面,也有不好的一方面。
好處就是可以在實驗室有限的幾百臺設備最大可能模擬出客戶現場的各種故障(PS:請相信一個經驗:當你的設備發貨量在10萬+時,任何在實驗室出現過的,那麼就出現過一次,再也不復現的問題,理論分析可能性再低的問題,在客戶現場都會出現)。
壞處就是導致驅動代碼寫的異常囉嗦。
static int
pci_scan_trad_caps(struct pci_dev *d)
{
word status = pci_read_word(d, PCI_STATUS);
if(status == 0xff)
return -1;
byte been_there[256];
int where;
if (!(status & PCI_STATUS_CAP_LIST))
return 0; /*not support Extended Capability list item*/
memset(been_there, 0, 256);
where = pci_read_byte(d, PCI_CAPABILITY_LIST) & ~3;
if (where == 0xff)
return -1;
while (where)
{
byte id = pci_read_byte(d, where + PCI_CAP_LIST_ID);
if (id == 0xff)
return -1;
byte next = pci_read_byte(d, where + PCI_CAP_LIST_NEXT) & ~3;
if (next == 0xff)
return -1;
if (been_there[where]++)
return -1;/*重複的cap,一定讀取reg出了問題*/
pci_add_cap(d, where, id, PCI_CAP_NORMAL);
where = next;
}
return 0;
}