PCI裝置驅動程式
一、PCI裝置驅動編寫
PCI匯流排是現在非常流行的計算機匯流排,學會它的驅動設計方法很重要。相信曾經想學習PCI匯流排驅動的人有這麼一個經歷,就是去看那些講解PCI匯流排驅動的書籍和資料的時候,會被裡面繁雜的內容所擊敗,又是什麼配置空間又是什麼列舉的,還沒開始真正的去寫PCI的驅動,到這裡就已經開始打退堂鼓了。其實,只要你認真下去,雖然有些東西看不明白,但是對於你寫PCI的驅動來說,似乎“不那麼重要”。因為,Linux核心對PCI匯流排已經有了完美的支援,你所需要做的內容是非常小的一部份。
Linux下的PCI匯流排,在系統上電的時候會逐一的掃描系統中存在的裝置(包括裝置和橋),匯流排號中斷號都是這個時候分配給裝置的,如果你是初學者,這個過程如果不是很明白,你大可以先略過,去找一個帶有PCI匯流排的開發板,接上PCI的裝置,讓系統重啟掃描一遍,再配合下面會給出的PCI匯流排驅動框架,你就會明白很多。
眾所周知,Linux 2.6核心引入了匯流排驅動模型這一概念,如此,很多基於匯流排的裝置驅動就分成了匯流排驅動和裝置驅動兩部分。其實PCI匯流排驅動跟2.6核心裡面的platform匯流排有類似之處,只不過platform匯流排的匹配方式是名字匹配,也就是裝置名和驅動名一致。PCI匯流排匹配的是id_table;但匹配方式不只一種,最常見的就是廠商號和裝置號。當你載入PCI驅動的時候,驅動程式會把系統中已經存在的裝置的廠商號和裝置號與驅動程式中的對比,如果一致,則會註冊PCI匯流排驅動並進行下一步操作。
下面是我寫的一個PCI匯流排的驅動程式,注意是PCI裝置識別時的驅動程式,這裡並沒有實現具體的功能驅動。PCI裝置的驅動分成兩個部分,一部分是匯流排的,就是PCI裝置識別、呼叫驅動程式probe函式的部分,另一部分就是具體的功能驅動,比如網絡卡。基於PCI匯流排的裝置有很多種,但就PCI匯流排驅動這一塊來說,都大同小異,實現了PCI匯流排驅動之後,再去繼續做具體的裝置驅動。
程式如下(在2.6.31至3.1.4核心都可以執行成功):
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/sched.h>
#include <linux/pci.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <asm/io.h>
#include <linux/ioport.h>
#include <linux/interrupt.h>
#include <linux/irq.h>
//裝置相關
#define MY_VENDOR_ID 0x168c //廠商號
#define MY_DEVICE_ID 0x002a //裝置號
#define MY_PCI_NAME "MYPCIE" //自己起的裝置名
static int debug = 1;
module_param(debug,int,S_IRUGO);
#define DBG(msg...) do{ \
if(debug) \
printk(msg); \
}while(0)
struct pcie_card
{
//埠讀寫變數
int io;
long range,flags;
void __iomem *ioaddr;
int irq;
};
/* 裝置中斷服務*/
static irqreturn_t mypci_interrupt(int irq, void *dev_id)
{
struct pcie_card *mypci = (struct pcie_card *)dev_id;
printk("irq = %d,mypci_irq = %d\n",irq,mypci->irq);
return IRQ_HANDLED;
}
/* 探測PCI裝置*/
static int __init mypci_probe(struct pci_dev *dev, const struct pci_device_id *ent)
{
int retval=0;//, intport, intmask;
struct pcie_card *mypci;
if ( pci_enable_device (dev) )
{
printk (KERN_ERR "IO Error.\n");
return -EIO;
}
/*分配裝置結構*/
mypci = kmalloc(sizeof(struct pcie_card),GFP_KERNEL);
if(!mypci)
{
printk("In %s,kmalloc err!",__func__);
return -ENOMEM;
}
/*設定埠地址及其範圍,指定中斷IRQ*/
mypci->irq = dev->irq;
if(mypci->irq < 0)
{
printk("IRQ is %d, it's invalid!\n",mypci->irq);
goto out_mypci;
}
mypci->io = pci_resource_start(dev, 0);
mypci->range = pci_resource_end(dev, 0) - mypci->io;
mypci->flags = pci_resource_flags(dev,0);
DBG("PCI base addr 0 is io%s.\n",(mypci->flags & IORESOURCE_MEM)? "mem":"port");
/*檢查申請IO埠*/
retval = check_region(mypci->io,mypci->range);
if(retval)
{
printk(KERN_ERR "I/O %d is not free.\n",mypci->io);
goto out_mypci;
}
//request_region(mypci->io,mypci->range, MY_PCI_NAME);
retval = pci_request_regions(dev,MY_PCI_NAME);
if(retval)
{
printk("PCI request regions err!\n");
goto out_mypci;
}
mypci->ioaddr = ioremap(mypci->io,mypci->range);
if(!mypci->ioaddr)
{
printk("ioremap err!\n");
retval = -ENOMEM;
goto out_regions;
}
//申請中斷IRQ並設定中斷服務子函式
retval = request_irq(mypci->irq, mypci_interrupt, IRQF_SHARED, MY_PCI_NAME, mypci);
if(retval)
{
printk (KERN_ERR "Can't get assigned IRQ %d.\n",mypci->irq);
goto out_iounmap;
}
pci_set_drvdata(dev,mypci);
DBG("Probe succeeds.PCIE ioport addr start at %X, mypci->ioaddr is 0x%p,interrupt No. %d.\n",mypci->io,mypci->ioaddr,mypci->irq);
return 0;
out_iounmap:
iounmap(mypci->ioaddr);
out_regions:
pci_release_regions(dev);
out_mypci:
kfree(mypci);
return retval;
}
/* 移除PCI裝置 */
static void __devexit mypci_remove(struct pci_dev *dev)
{
struct pcie_card *mypci = pci_get_drvdata(dev);
free_irq (mypci->irq, mypci);
iounmap(mypci->ioaddr);
//release_region(mypci->io,mypci->range);
pci_release_regions(dev);
kfree(mypci);
DBG("Device is removed successfully.\n");
}
/* 指明驅動程式適用的PCI裝置ID */
static struct pci_device_id mypci_table[] __initdata =
{
{
MY_VENDOR_ID, //廠商ID
MY_DEVICE_ID, //裝置ID
PCI_ANY_ID, //子廠商ID
PCI_ANY_ID, //子裝置ID
},
{0, },
};
MODULE_DEVICE_TABLE(pci, mypci_table);
/* 裝置模組資訊 */
static struct pci_driver mypci_driver_ops =
{
name: MY_PCI_NAME, //裝置模組名稱
id_table: mypci_table, //驅動裝置表
probe: mypci_probe, //查詢並初始化裝置
remove: mypci_remove //解除安裝裝置模組
};
static int __init mypci_init(void)
{
//註冊硬體驅動程式
if ( pci_register_driver(&mypci_driver_ops) )
{
printk (KERN_ERR "Can't register driver!\n");
return -ENODEV;
}
return 0;
}
static void __exit mypci_exit(void)
{
pci_unregister_driver(&mypci_driver_ops);
}
module_init(mypci_init);
module_exit(mypci_exit);
MODULE_LICENSE("GPL");
以上這個程式是我在開發板中插入了一個PCIE的網絡卡裝置,系統重啟之後,載入這個驅動模組,就會進行註冊驅動等一系列的操作。 載入模組後的結果:
[[email protected] /] insmod ar9280.ko
Probe succeeds.PCIE ioport addr start at 98000000, mypci->ioaddr is 0xd4fa0000,interrupt No.17.
看到上面Probe 成功,說明系統找到了我的網絡卡,98000000正是系統PCI匯流排的物理起始地址。
[[email protected] /] cat /proc/interrupts
CPU0
17: 0 UIC Level MYPCIE
18: 24 UIC Level MAL TX EOB
19: 225 UIC Level MAL RX EOB
20: 0 UIC Level MAL SERR
21: 0 UIC Level MAL TX DE
22: 0 UIC Level MAL RX DE
24: 0 UIC Level EMAC
26: 1194 UIC Level serial
BAD: 0
[[email protected] /] cat /proc/iomem //注意:檢視iomem時出現了自己的裝置佔用的iomem,說明是IO記憶體
90000000-97ffffff : /plb/[email protected]0a0000000
98000000-9fffffff : /plb/[email protected]0c0000000
98000000-980fffff : PCI Bus 0001:41
98000000-9800ffff : 0001:41:00.0
98000000-9800ffff : MYPCIE
ef600200-ef600207 : serial
ef600300-ef600307 : serial
fc000000-ffffffff : fc000000.nor_flash
通過上述結果可以看出,PCI匯流排驅動已經載入成功。後續可以繼續做裝置驅動的內容了。
二、PCI中的中斷
下面來講一下PCI中斷: 首先看一下pci 裝置的pin list
扯點題外話,裡面大部分訊號是低電平有效。據說是因為低電平阻抗低,抗干擾能力強。
可以看到,它有四個中斷pin,但是它是放在右邊作為optional 的。
在PCI 裡面,中斷是電平觸發的,低電平有效,如果不是走MSI方式,當Device 有需要的時候,Device driver 會去拉低INTx line. 一旦這個訊號被拉低,它會持續為低,直到Driver 沒有了pending 請求。如果是單功能裝置,那麼只需要用到INT A,多功能裝置可以把INT A, B, C ,D 都用完。
對於多功能裝置而言,上的的邏輯裝置可以使用A, B, C ,D 中的任何一根。
從上面我們可以看出,每個PCI裝置都含有四個IO口INTA# - INTD#,裝置的中斷引腳( INTA# - INTD#)會連線到系統中斷控制器的引腳(1RQO - IRQ15中)上去,這樣當INTA# - INTD#引腳拉低時,就相當於把連線到中斷控制器中的中斷引腳拉低了,從而產生中斷。