Embedded 版 (精华区)

发信人: Zinux (Linux技工), 信区: Embedded_system
标  题: Linux 2.2 的PCI接口
发信站: 哈工大紫丁香 (2001年10月26日18:37:46 星期五), 站内信件


Linux 2.2 的PCI接口
PCI Management in Linux 2.2
by Alan Cox <alan@lxorguk.ukuu.org.uk>
董晓明 译 <xmdong@263.net>
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

PCI是一种广泛采用的总线标准,它提供了优于其他总线标准(比如EISA)的特性

。在大多数奔腾主板上,PCI是高速、高带宽(32-bit和64-bit)、处理器无关的

总线。对PCI的支持第一次加入Linux中时,其内核接口是PCI BIOS32函数的堆砌
。这样做有几个问题:

* PCI BIOS仅存在于PC上;
* PCI BIOS只代表特定的结构,非PC类机器的某些PCI设置不能用PCI BIOS来描述


* 个别机子的PCI BIOS函数不象预期的那样工作。

Linux 2.2提供了一个通用的PCI接口。Linux x86内核实际上努力直接驱动硬件,

只有当它发现某些东西不能理解时,它才会调用PCI BIOS32函数。
驱动程序可以继续使用老的PCI接口,但是为了兼容将来的内核,可能需要更新。

驱动程序可以继续使用老的PCI接口,但是为了兼容将来的内核,可能需要更新。

如果驱动程序将要跨平台工作,那就更加需要更新了。
多数新、老函数有简单的对应关系。PCI BIOS基于总线号/设备号/功能号的思想
,而新的代码使用pci_bus和pci_dev结构。第一个新PCI函数是:

pci_present()

这个函数检查机器是否存在一条或更多的PCI总线。老内核有一个
pcibios_present()函数,它们的用法完全相同。

确认PCI存在之后,你可以扫描PCI总线来查找设备。PCI设备通过几个配置寄存器

来标识,主要是供应商ID和设备ID。
每个供应商被分配了一个唯一的标识(ID),并且假设供应商给他们的设备(板
子、芯片等)分配唯一的设备ID。PCI的一个好处是它提供了版本和编程接口信息

,因此可以发现板子的变化。

在Linux 2.2中,扫描PCI总线一般用pci_find_device()函数。范例如下:

struct pci_dev *pdev = NULL;
while ((pdev = pci_find_device(PCI_MY_VENDOR,
  PCI_MY_DEVICE, pdev)) != NULL)
{
    /* Found a device */
    /* Found a device */
    setup_device(pdev);
}

pci_find_device()有3个参数:第一个是供应商ID,第二个是设备ID,第三个是
函数的返回值,NULL表示你想从头开始查找。在这个例子中,对找到的设备调用
setup_device()来进行设置。
另一个值得高兴的事情,是PCI为你处理了所有资源配置工作。一般来说PCI 
BIOS
具体做这些工作,但是在其他平台上,这项工作由固件或者体系结构相关的
Linux
代码来做。到你的驱动程序查找PCI卡的时候,它已经被分配了系统资源。
Linux在pci_dev结构中提供了PCI相关的核心信息。同时还允许读写每个卡的PCI
配置空间。当你可以直接查找资源数据时应该小心,对许多系统来说,卡上配置
的数据与内核提供的数据并不相符。因为许多非PC机器有多条PCI总线,PCI总线
以设备卡不知道的方式映射到系统中。

Linux直接提供了IRQ和PCI BARs(基址寄存器)。为了避免代码在非PC平台上出
现意外,你应该总是使用内核提供的数据。下面代码列出了setup_device()例程


Listing One: The setup_device () Function
void setup_device(struct pci_dev *dev)
{
      int io_addr = dev->base_address[0] & PCI_BASE_ADDRESS_IO_MASK;
      int io_addr = dev->base_address[0] & PCI_BASE_ADDRESS_IO_MASK;
      int irq = dev->irq;
      u8 rev;

      pci_read_config_byte(dev, PCI_REVISION_ID, &rev);

      if (rev<64)
            printk("Found a WonderWidget 500 at I/O 0x%04X, IRQ %d.\n",

    io_addr, irq);
      else
            printk("Found a WonderWidget 600 at I/O 0x%04X, IRQ %d.\n",

    io_addr, irq);

      /* Check for a common BIOS problem - if you
       * expect an IRQ you might not get it */
      if (irq==0)
      {
            printk(KERN_ERR "BIOS has not assigned the WonderWidget"
                               " an interrupt.\n");
            return;
      }

      /* Now do the board initialization knowing the resources */
      /* Now do the board initialization knowing the resources */
      init_device(io_addr, irq, rev<64 ? 0 : 1);

      pci_set_master(dev);
}

当你的卡被BIOS配置后,某些特性可能会被屏蔽掉。比如,多数BIOS都会清掉“
master”位,这导致板卡不能随意向主存中拷贝数据。Linux 2.2提供了一个辅助

函数:

pci_set_master(struct pci_dev *)

这个函数会检查是否需要设置标志位,如果需要,则会将“master”位置位。
例子函数setup_device还使用了pci_read_config_byte来读取配置空间数据。内
核提供了一整套与配置空间相关的函数:

pci_read_config_byte,
pci_read_config_word,
和pci_read_config_dword

分别从配置空间获取8,16和32位数据;

pci_write_config_byte,
pci_write_config_byte,
pci_write_config_word,
和pci_write_config_dword

分别向配置空间写入8,16和32位数据。PCI配置空间独立于I/O和内存空间,只能

通过这些函数访问。

最后一组有用的PCI函数以不同的方式扫描PCI总线。pci_find_class查找符合给
定类别(class)的设备。PCI规范把设备分为不同的类别,你可以根据类别查找
设备。例如,为了查找一个USB控制器,可以用

struct pci_dev *pdev = NULL;
while((pdev=pci_find_class
   (PCI_CLASS_SERIAL_USB <<8, pdev))!=NULL)
{
    u8 type;
    pci_read_config_byte(dev,
         PCI_CLASS_PROG, &type);
    if(type!=0)
        continue;
    /* FOUND IT */
}


另一个例子是I2O。这时,供应商ID只用来确定板卡的实际类型(type),偶尔用

来对付特定板卡的bug。
扫描PCI设备的最后一种途径是pci_find_slot,使你按照特定的顺序扫描PCI插槽

和功能。它很少使用,但是,如果你要控制查找某一类型设备时扫描PCI总线的顺

序,你可以用它。这种情况通常出现在你需要遵照主板BIOS报告设备的顺序时,
或者你想使Linux和非Linux驱动程序以相同的顺序报告设备时。传递给
pci_find_slot()的是总线号slot和设备-功能号function(slot<<3 | function
)。

PCI中断和其他注意事项

PCI总线一个重要的概念是共享中断处理,这在ISA总线设备中一般是看不到的。
PCI总线中断也是电平触发的(level-triggered),也就是说,中断一直在那里
,直到设备去清除它。这些特性给驱动程序处理中断加上了一些重要的限制。
驱动程序注册PCI中断时,总是应该带上SA_SHIRQ标志,用来指明中断线是可以共

享的。如果不这样做,那么系统中的其他设备有可能不能正常工作,用户也可能
遇到麻烦。

由于中断是共享的,PCI设备驱动程序和内核都需要与每个中断处理例程进行沟通

的方法。你必须用一个非空(non-NULL)的dev_id来注册共享中断,否则,当你
需要用free_irq来释放一个中断时,内核不能区分不同的中断处理例程。dev_id
被送到中断处理例程,因此它非常重要。例如,你可以这样:
被送到中断处理例程,因此它非常重要。例如,你可以这样:

if (request_irq(dev->irq, dev_interrupt,
               SA_SHIRQ, "wonderwidget",
               dev))
      return -EAGAIN;

结束时,用下面的语句来正确释放中断:

free_irq(dev->irq, dev)

中断处理例程被调用时收到dev参数,这使事情很简单了。你不必搜寻使用该中断

的设备,通常可以这样做:

Listing Two: Using the dev_id
static void dev_interrupt(int irq, void *dev_id, struct pt_regs *regs)
{
     struct wonderwidget *dev = dev_id;
     u32 status;

     /* It is important to exit interrupt handlers
      * that are not for us as fast as possible */


     if((status=inl(dev->port))==0) /* Not our interrupt */
            return;

     if(status&1)
            handle_rx_intr(dev);
     ....
}

你必须总是小心处理中断。永远不要在安装中断处理例程之前产生中断。因为
PCI
中断是电平触发的,如果你产生了中断而又不能处理它,可能会导致死机。这意
味着写初始化代码时必须特别小心,你必须在打开设备的中断之前注册中断处理
例程。同样,关闭时必须在注销中断处理例程之前屏蔽设备的中断。
与ISA总线相比,Linux对PCI总线的支持用到较多的函数,并且要小心处理中断。

作为回报,不需要你的介入,系统把一切都配置好了。

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Linux Magazine 版权所有 2000

--

  puke! 
  技工而已

※ 来源:·哈工大紫丁香 bbs.hit.edu.cn·[FROM: 202.118.239.152]
[百宝箱] [返回首页] [上级目录] [根目录] [返回顶部] [刷新] [返回]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:2.890毫秒