2. PCI 总线族
下面的总线都属于 PCI 族:
- PCI:32 bit 总线,33 或 66 MHz。
- MiniPCI:插槽更小,用于笔记本电脑。
- CardBus:外部卡槽,用于笔记本电脑。
- PIX Extended(PCI-X):比 PCI 插槽要宽,64 bit,但支持插入一个标准 PCI 卡。
- PCI Express(PCIe or PCI-E):PCI 的当前代,用串行接口取代并行接口。
- PCI Express Mini Card:应用于较新的笔记本,取代 MiniPCI。
- Express Card:应用于较新的笔记本,取代 CardBus。
这些技术都是互相兼容的,内核驱动可以使用同一套。内核不需要感知硬件到底用的是什么插槽和总线变种。
3. PCI 设备类型
PCI 总线上的设备类型主要有:
4. PCI 特性
主要是针对设备驱动开发者。
- boot 阶段 BIOS 或 linux(如果配置了的话)会自动分配设备的资源(I/O 地址,IRQ 中断线)。PCI/PCIe 的地址分配,参考本号《系统地址映射初始化:基于 PCI 的系统》、《系统地址映射初始化:基于 PCIe 的系统》。
- 设备驱动只需要读取系统地址空间中的相应配置即可。
- 大小端:PCI 设备的配置信息是小端的。写驱动的时候要注意(有些内核函数可以提供大小端转化)。
5. 列举 PCI 设备
- lspci:列举所有 PCI 设备。
- lspci -tv:列举 PCI 总线设备树。
- PCI 设备树与 /sys 中的数据结构对应
6. PCI 设备配置
- 每个 PCI 设备有一个 256 byte 地址空间长度的配置寄存器。
- 可以通过 lspci -x 查看设备的配置:
- 标准的 PCI 配置信息:
- 偏移 0:Vendor ID。
- 偏移 2:Device ID。
- 偏移 10:Class ID(网卡、显卡、桥 ...)。
- 偏移 16 - 39:Base Address Registers(BAR)0 到 5。
- 偏移 44:SubVendor ID。
- 偏移 46:SubDevice ID。
- 偏移 64 及以上:设备制造商。
这些偏移的定义,在内核的 include/linux/pci_regs.h。
7. linux 驱动
7.1 注册所支持的设备
drivers/net/ne2k-pci.c:
static struct pci_device_id ne2k_pci_tbl[] = {
{ 0x10ec, 0x8029, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_RealTek_RTL_8029 },
{ 0x1050, 0x0940, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Winbond_89C940 },
{ 0x11f6, 0x1401, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Compex_RL2000 },
{ 0x8e2e, 0x3000, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_KTI_ET32P2 },
{ 0x4a14, 0x5000, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_NetVin_NV5000SC },
{ 0x1106, 0x0926, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Via_86C926 },
{ 0x10bd, 0x0e34, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_SureCom_NE34 },
{ 0x1050, 0x5a5a, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Winbond_W89C940F },
{ 0x12c3, 0x0058, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Holtek_HT80232 },
{ 0x12c3, 0x5598, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Holtek_HT80229 },
{ 0x8c4a, 0x1980, PCI_ANY_ID, PCI_ANY_ID, 0, 0, CH_Winbond_89C940_8c4a },
{ 0, }
};
MODULE_DEVICE_TABLE(pci, ne2k_pci_tbl);
7.2 注册驱动
注册驱动的操作函数,以及驱动所支持的设备表:
static struct pci_driver ne2k_driver = {
.name = DRV_NAME,
.probe = ne2k_pci_init_one,
.remove = __devexit_p(ne2k_pci_remove_one),
.id_table = ne2k_pci_tbl,
#ifdef CONFIG_PM
.suspend = ne2k_pci_suspend,
.resume = ne2k_pci_resume,
#endif /* CONFIG_PM */
};
static int __init ne2k_pci_init(void)
{
return pci_register_driver(&ne2k_driver);
}
static void __exit ne2k_pci_cleanup(void)
{
pci_unregister_driver (&ne2k_driver);
}
- 驱动的操作函数以及所支持的设备,会在模块加载时被载入。
- 如果找到一个匹配的设备,PCI 框架代码会调用驱动的 probe() 函数。
- 非常类似 USB 设备驱动!
7.3 驱动操作函数修饰
- __init:模块初始化函数。这些代码会在驱动初始化完之后被丢弃。
- __exit:模块退出函数。如果是静态编译(编到内核中)的驱动,会忽略之。
- __devinit:probe 函数以及所有初始化函数。如果使能了 CONFIG_HOTPLUG 内核配置,则此类函数就是个正常的函数,否则等同 __init。
- __devinitconst:用于设备 ID 表。
- __devexit:移除时会调用的函数。同 __devinit。
- 所有引用 __devinit 函数地址的地方,都应该使用 __devexit_p(fun) 进行声明修饰。如果代码被丢弃的话,此修饰会将函数地址替换为 NULL。
示例:
static struct pci_driver ne2k_driver = {
.name = DRV_NAME,
.probe = ne2k_pci_init_one,
.remove = __devexit_p(ne2k_pci_remove_one),
.id_table = ne2k_pci_tbl,
...
};
7.4 设备初始化步骤
- 使能设备。
- 请求 I/O 端口以及 I/O 内存资源。
- 设置 DMA mask size(coherent 及 streaming DMA 皆需要)。
- 分配并初始化共享控制数据(pci_allocate_coherent())。
- 初始化设备寄存器(如果需要的话)。
- 注册 IRQ 处理函数(request_irq())。
- 注册进其他子系统(网络、显示、存储,等等)。
- 使能 DMA 处理引擎。
7.5 设备使能
- 在访问设备寄存器之前,驱动需要先执行 pci_enable_device(),这会导致:
- 如果设备在 suspend 状态,则唤醒之。
- 分配设备的 I/O 和内存区域(如果 BIOS 没有搞定的话)。
- 为设备分配一个 IRQ(如果 BIOS 没有搞定的话)。
pci_enable_device() 可能会失败,所以需要检查其返回值!
pci_enable_device(drivers/net/ne2k-pci.c)示例:
static int __devinit ne2k_pci_init_one
(struct pci_dev *pdev, const struct pci_device_id *ent)
{
...
i = pci_enable_device (pdev);
if (i)
return i;
...
}
- 调用 pci_set_master() 使能 DMA,这会导致:
- 通过设置 PCI_COMMAND 寄存器中的 bus master bit 来使能 DMA。完成后设备便可在地址总线上扮演一个 master 的角色。
- 如果 BIOS 设置了伪造的值,则修复延迟定时器的值(Fix the latency timer value if it's set to something bogus by the BIOS. 这句话没看明白)。
- 如果设备可以使用 PCI Memory-Write-Invalidate transaction(写整个 cache lines),你还可以调用 pci_set_mwi():
- 此函数使能 Memory-Write-Invalidate 的 PCI_COMMAND bit。
- 此函数还确保对 cache line size 寄存器进行正确的设置。
7.6 访问配置寄存器
访问 I/O 内存和端口信息。
#include < linux/pci.h >
/* 读接口 */
int pci_read_config_byte(struct pci_dev *dev, int where, u8 *val);
int pci_read_config_word(struct pci_dev *dev, int where, u16 *val);
int pci_read_config_dword(struct pci_dev *dev, int where, u32 *val);
/* 读示例:drivers/net/cassini.c */
pci_read_config_word(cp >pdev, PCI_STATUS, &cfg);
/* 写接口 */
int pci_write_config_byte(struct pci_dev *dev, int where, u8 val);
int pci_write_config_word(struct pci_dev *dev, int where, u16 val);
int pci_write_config_dword(struct pci_dev *dev, int where, u32 val);
/* 写示例:drivers/net/s2io.c */
/* Clear "detected parity error" bit
pci_write_config_word(sp >pdev, PCI_STATUS, 0x8000);
- 每个 PCI 设备最多有 6 个 I/O 或内存区域,通过 BAR0 到 BAR5 来描述。
- 访问 I/O 区域的基地址:
#include < linux/pci.h >
long iobase = pci_resource_start(pdev, bar);
- 访问 I/O 区域的大小:
long iosize = pci_resource_len(pdev, bar);
- 预留 I/O 区域:
request_region(iobase, iosize, “my driver”);
或者简单点:
pci_request_region(pdev, bar, “my driver”);
或者更简单点:
pci_request_regions(pdev, “my driver”);
示例代码(drivers/net/ne2k-pci.c):
ioaddr = pci_resource_start (pdev, 0);
irq = pdev- >irq;
if (!ioaddr || ((pci_resource_flags (pdev, 0) & IORESOURCE_IO) == 0))
{
dev_err(&pdev- >dev, "no I/O resource at PCI BAR #0\\n");
return -ENODEV;
}
if (request_region (ioaddr, NE_IO_EXTENT, DRV_NAME) == NULL) {
dev_err(&pdev- >dev, "I/O resource 0x%x @ 0x%lx busy\\n", NE_IO_EXTENT, ioaddr);
return -EBUSY;
}
7.7 设置 DMA mask size
- 对于拥有超过(或少于)(那不就是 whatever 么?) 32 bit 总线 master capability 的设备,对此设备调用 pci_dma_set_mask() 声明之。
- 特别是对于使用 64 bit DMA 的 PCI-X 和 PCIe 兼容设备来说,驱动必须调用此函数
- 如果设备可以直接寻址系统 RAM 中高于 4G 物理地址的“缓存一致性内存”,则通过 pci_set_consistent_dma_mask() 注册之。
示例(drivers/net/wireless/ipw2200.c):
err = pci_set_dma_mask(pdev, DMA_32BIT_MASK);
if (!err)
err = pci_set_consistent_dma_mask(pdev, DMA_32BIT_MASK);
if (err) {
printk(KERN_WARNING DRV_NAME ": No suitable DMA available.\\n");
goto out_pci_disable_device;
}
7.8 分配缓存一致性的 DMA buffers
至此,已完成 DMA mask size 的分配。
- 如果你准备使用缓存一致性的 buffers,则分配之。
- 详情参考内核 Documentation/DMA-API.txt。
7.9 初始化设备寄存器
如果设备需要的话:
- 设置一些 "capability" 域。
- 进行一些 vendor specific 的初始化工作或复位。
示例:清除 pending 的中断。
7.10 注册中断处理函数
- 调用 request_irq() 时需要传入 IRQF_SHARED flag,因为 PCI 中断线是可共享的。
- 中断注册同时会使能中断,故在中断注册的时间点上,需要:
- 确保设备已完成全部初始化工作并准备好处理中断。
- 确保设备在调用 request_irq() 之前没有 pending 的中断。
- 实际调用 request_irq() 的地方取决于设备类型,以及其所属的子系统(比如网络、显示、存储 ...)。
- 驱动随后被注册进其所属的子系统。
7.11 PCI 设备关停
在 remove() 函数中,你通常需要对在设备初始化(probe() 函数)中所做工作进行逆操作:
- 禁能设备产生中断:如果你不禁能设备中断的话,系统可能会收到 spurious 中断,并最终禁用掉整个中断线。该中断线上的其他设备就遭殃了!
- 释放 IRQ。
- 停止所有 DMA 活动:需要在关闭 IRQs 之后再做(原 slides 这句话后面还有一个注:could start new DMAs,这句话没看明白)。
- 释放 DMA buffers:先释放 streaming buffers,然后再释放 consistent buffers。
- 从其他子系统中取消注册。
- 通过 io_unmap() 取消映射 I/O 内存及端口。
- 通过 pci_disable_device() 关闭设备。
- 取消注册 I/O 内存和端口:如果这一步不做的话,会无法重新加载驱动。
评论
查看更多