讨论linux PCI驱动的slides

嵌入式技术

1335人已加入

描述

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 总线上的设备类型主要有:

  • 网卡(有线或无线)。
  • SCSI 适配器。
  • 总线控制器:USB、PCMCIA、I2C、FireWire、IDE。
  • 显卡。
  • 声卡。

4. PCI 特性

主要是针对设备驱动开发者。

  • boot 阶段 BIOS 或 linux(如果配置了的话)会自动分配设备的资源(I/O 地址,IRQ 中断线)。PCI/PCIe 的地址分配,参考本号《系统地址映射初始化:基于 PCI 的系统》、《系统地址映射初始化:基于 PCIe 的系统》。
  • 设备驱动只需要读取系统地址空间中的相应配置即可。
  • 大小端:PCI 设备的配置信息是小端的。写驱动的时候要注意(有些内核函数可以提供大小端转化)。

5. 列举 PCI 设备

  1. lspci:列举所有 PCI 设备。

寄存器

  1. lspci -tv:列举 PCI 总线设备树。

寄存器

  1. PCI 设备树与 /sys 中的数据结构对应

寄存器

6. PCI 设备配置

  1. 每个 PCI 设备有一个 256 byte 地址空间长度的配置寄存器。
  2. 可以通过 lspci -x 查看设备的配置:

寄存器

  1. 标准的 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 设备使能

  1. 在访问设备寄存器之前,驱动需要先执行 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;
  ...
}
  1. 调用 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. 这句话没看明白)。
  1. 如果设备可以使用 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 注册中断处理函数

  1. 调用 request_irq() 时需要传入 IRQF_SHARED flag,因为 PCI 中断线是可共享的。
  2. 中断注册同时会使能中断,故在中断注册的时间点上,需要:
  • 确保设备已完成全部初始化工作并准备好处理中断。
  • 确保设备在调用 request_irq() 之前没有 pending 的中断。
  1. 实际调用 request_irq() 的地方取决于设备类型,以及其所属的子系统(比如网络、显示、存储 ...)。
  2. 驱动随后被注册进其所属的子系统。

7.11 PCI 设备关停

在 remove() 函数中,你通常需要对在设备初始化(probe() 函数)中所做工作进行逆操作:

  1. 禁能设备产生中断:如果你不禁能设备中断的话,系统可能会收到 spurious 中断,并最终禁用掉整个中断线。该中断线上的其他设备就遭殃了!
  2. 释放 IRQ。
  3. 停止所有 DMA 活动:需要在关闭 IRQs 之后再做(原 slides 这句话后面还有一个注:could start new DMAs,这句话没看明白)。
  4. 释放 DMA buffers:先释放 streaming buffers,然后再释放 consistent buffers。
  5. 从其他子系统中取消注册。
  6. 通过 io_unmap() 取消映射 I/O 内存及端口。
  7. 通过 pci_disable_device() 关闭设备。
  8. 取消注册 I/O 内存和端口:如果这一步不做的话,会无法重新加载驱动。
打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分