FreeBSD 系统结构手册
上一页 | 第10章 　ISA设备驱动程序 | 下一页

# 10.8 xxx_isa_probe

这个函数探测设备是否存在。如果驱动程序支持自动侦测设备配置的 某些部分（如中断向量或内存地址），则自动侦测必须在此例程中完成。

对于任意其他总线，如果不能侦测到设备，或者侦测到但自检失败， 或者发生某些其他问题，则应当返回一个正值的错误。如果设备不 存在则必须返回值 “`ENXIO`”。 其他错误值可能表示其他条件。零或负值 意味着成功。大多数驱动程序返回零表示成功。

当PnP设备支持多个接口时使用负返回值。例如，不同驱动程序支持 老的兼容接口和较新的高级接口。则两个驱动程序都将侦测设备。 在探测例程中返回较高值的驱动程序获得优先（换句话说，返回0的 驱动程序具有最高的优先级，返回-1的其次，返回-2的更后，等等）。 这样，仅支持老接口的设备将被老驱动程序处理（其应当从探测例程中 返回-1），而同时也支持新接口的设备将由新驱动程序处理（其应当从 探测例程中返回0）。

设备描述符结构xxx_softc由系统在调用探测例程之前分配。如果 探测例程返回错误，描述符会被系统自动取消分配。因此如果出现 探测错误，驱动程序必须保证取消分配探测期间它使用的所有资源， 且确保没有什么能够阻止描述符被安全地取消分配。如果探测成功 完成，描述符将由系统保存并在以后传递给例程 `xxx_isa_attach()`。如果驱动程序返回负值， 就不能保证它将获得最高优先权且其连接例程会被调用。因此这种 情况下它也必须在返回前释放所有的资源，并在需要的时候在连接 例程中重新分配它们。当`xxx_isa_probe()` 返回0时，在返回前释放资源也是一个好主意，而且中规中矩的驱动 程序应当这样做。但在释放资源会存在某些问题的情况下，允许驱动 程序在从探测例程返回0和连接例程的执行之间保持资源。

典型的探测例程以取得设备描述符和单元号开始：

             struct xxx_softc *sc = device_get_softc(dev);
              int unit = device_get_unit(dev);
              int pnperror;
              int error = 0;

              sc->dev = dev; /* 链接回来 */
              sc->unit = unit;

然后检查PnP设备。检查是通过一个包含PnP ID列表的表进行的。此表 包含这个驱动程序支持的PnP ID和以人工可读形式给出的对应这些ID的 设备型号的描述。

            pnperror=ISA_PNP_PROBE(device_get_parent(dev), dev,
            xxx_pnp_ids); if(pnperror == ENXIO) return ENXIO;

ISA_PNP_PROBE的逻辑如下：如果卡（设备单元）没有被作为PnP侦测到， 则返回ENOENT。如果被作为PnP侦测到，但侦测到的ID不匹配表中的 任一ID，则返回ENXIO。最后，如果设备能支持PnP且匹配表中的一个 ID，则返回0，并且由`device_set_desc()`从 表中取得适当的描述进行设置。

如果设备驱动程序仅支持PnP设备，则情况看起来如下：

              if(pnperror != 0)
                  return pnperror;

对于不支持PnP的驱动程序不需要特殊处理，因为驱动程序会传递空的 PnP ID表，且如果在PnP卡上调用会得到ENXIO。

探测例程通常至少需要某些最少量的资源，如I/O端口号，来发现并探测卡。 对于不同的硬件，驱动程序可能会自动发现其他必需的资源。PnP设备的 所有资源由PnP子系统预先设置，因此驱动程序不需要自己发现它们。

通常访问设备所需要的最少信息就是端口号。然后某些设备允许从设备 配置寄存器中取得其余信息（尽管不是所有的设备都这样）。因此首先 我们尝试取得端口起始值：

     sc->port0 = bus_get_resource_start(dev,
            SYS_RES_IOPORT, 0 /*rid*/); if(sc->port0 == 0) return ENXIO;

基端口地址被保存在softc结构中，以便将来使用。如果需要经常使用 端口，则每次都调用资源函数将会慢的无法忍受。如果我们没有得到 端口，则返回错误即可。相反，一些设备驱动程序相当聪明，尝试探测 所有可能的端口，如下：

              /* 此设备所有可能的基I/O端口地址表 */
              static struct xxx_allports {
                  u_short port; /* 端口地址 */
                  short used; /* 旗标：此端口是否已被其他单元使用 */
              } xxx_allports = {
                  { 0x300, 0 },
                  { 0x320, 0 },
                  { 0x340, 0 },
                  { 0, 0 } /* 表结束 */
              };

              ...
              int port, i;
              ...

              port =  bus_get_resource_start(dev, SYS_RES_IOPORT, 0 /*rid*/);
              if(port !=0 ) {
                  for(i=0; xxx_allports[i].port!=0; i++) {
                      if(xxx_allports[i].used || xxx_allports[i].port != port)
                          continue;

                      /* 找到了 */
                      xxx_allports[i].used = 1;
                      /* 在已知端口上探测 */
                      return xxx_really_probe(dev, port);
                  }
                  return ENXIO; /* 端口无法识别或已经被使用 */
              }

              /* 仅在需要猜测端口的时候才会到达这儿 */
              for(i=0; xxx_allports[i].port!=0; i++) {
                  if(xxx_allports[i].used)
                      continue;

                  /* 标记为已被使用 - 即使我们在此端口什么也没有发现
                   * 至少我们以后不会再次探测
                   */
                   xxx_allports[i].used = 1;

                  error = xxx_really_probe(dev, xxx_allports[i].port);
                  if(error == 0) /* 在那个端口找到一个设备 */
                      return 0;
              }
              /* 探测过所有可能的地址，但没有可用的 */
              return ENXIO;

当然，做这些事情通常应该使用驱动程序的 `identify()`例程。但可能有一个正当的理由来 说明为什么在函数`probe()`中完成更好：如果 这种探测会让一些其他敏感设备发疯。探测例程按旗标 `sensitive`排序：敏感设备首先被探测，然后是 其他设备。但`identify()`例程在所有探测之前 被调用，因此它们不会考虑敏感设备并可能扰乱这些设备。

现在，我们得到起始端口以后就需要设置端口数（PnP设备除外），因为 内核在配置文件中没有这个信息。

             if(pnperror /* 只对非PnP设备 */
             && bus_set_resource(dev, SYS_RES_IOPORT, 0, sc->port0,
             XXX_PORT_COUNT)<0)
                 return ENXIO;

最后分配并激活一片端口地址空间（特殊值start和end意思是说 “使用我们通过`bus_set_resource()` 设置的那些值”）：

              sc->port0_rid = 0;
              sc->port0_r = bus_alloc_resource(dev, SYS_RES_IOPORT,
              &sc->port0_rid,
                  /*start*/ 0, /*end*/ ~0, /*count*/ 0, RF_ACTIVE);

              if(sc->port0_r == NULL)
                  return ENXIO;

现在可以访问端口映射的寄存器后，我们就可以以某种方式向设备写入 数据并检查设备是否如我们期望的那样作出反应。如果没有，则说明 可能其他的设备在这个地址上，或者这个地址上根本没有设备。

通常驱动程序直到连接例程才会设置中断处理函数。这之前我们替代以 轮询模式进行探测，超时则以`DELAY()`实现。 探测例程必须确保不能永久挂起，设备上的所有等待必须在超时内完成。 如果设备不在这段时间内响应，则可能设备出故障或配置错误，驱动程序 必须返回错误，当确定超时间隔时，给设备一些额外时间以确保可靠： 尽管假定`DELAY()`在任何机器上都延时相同数量的 时间，但随具体CPU的不同，此函数还是有一定的误差幅度。

如果探测例程真的想检查中断是否真的工作，它可以也配置和探测中断。 但不建议这样。

              /* 以严重依赖于具体设备的方式实现 */
              if(error = xxx_probe_ports(sc))
                  goto bad; /* 返回前释放资源 */

依赖于所发现设备的确切型号，函数 `xxx_probe_ports()`也可能设置设备描述。但 如果只支持一种设备型号，则也可以硬编码的形式完成。当然，对于 PnP设备，PnP支持从表中自动设置描述。

              if(pnperror)
                  device_set_desc(dev, "Our device model 1234");

探测例程应当或者通过读取设备配置寄存器来发现所有资源的范围， 或者确保由用户显式设置。我们将假定一个带板上内存的例子。 探测例程应当尽可能是非插入式的，这样分配和检查其余资源功能性 的工作就可以更好地留给连接例程来做。

内存地址可以在内核配置文件中指定，或者对应某些设备可以在非易失性 配置寄存器中预先配置。如果两种做法均可用却不同，那么应当用 哪个呢？可能用户厌烦在内核配置文件中明确设置地址，但他们知道 自己在干什么，则应当优先使用这个。一个实现的例子可能是这样的：

              /* 首先试图找出配置地址 */
              sc->mem0_p = bus_get_resource_start(dev, SYS_RES_MEMORY, 0 /*rid*/);
              if(sc->mem0_p == 0) { /* 没有，用户没指定 */
                  sc->mem0_p = xxx_read_mem0_from_device_config(sc);

              if(sc->mem0_p == 0)
                      /* 从设备配置寄存器也到不了这儿 */
                      goto bad;
              } else {
                  if(xxx_set_mem0_address_on_device(sc) < 0)
                      goto bad; /* 设备不支持那地址 */
              }

              /* 就像端口，设置内存大小，
               * 对于某些设备，内存大小不是常数，
               * 而应当从设备配置寄存器中读取，以适应设备的不同型号
               * 另一个选择是让用户把内存大小设置为“msize”配置资源，
               * 由ISA总线自动处理
               */
               if(pnperror) { /*仅对非PnP设备 */
                  sc->mem0_size = bus_get_resource_count(dev, SYS_RES_MEMORY, 0 /*rid*/);
                  if(sc->mem0_size == 0) /* 用户没有指定 */
                      sc->mem0_size = xxx_read_mem0_size_from_device_config(sc);

                  if(sc->mem0_size == 0) {
                      /* 假定这是设备非常老的一种型号，没有自动配置特性，
                       * 用户也没有偏好设置，因此假定最低要求的情况
                       * （当然，真实值将根据设备驱动程序而不同）
                       */
                      sc->mem0_size = 8*1024;
                  }

                  if(xxx_set_mem0_size_on_device(sc) < 0)
                      goto bad; /*设备不支持那个大小 */

                  if(bus_set_resource(dev, SYS_RES_MEMORY, /*rid*/0,
                          sc->mem0_p, sc->mem0_size)<0)
                      goto bad;
              } else {
                  sc->mem0_size = bus_get_resource_count(dev, SYS_RES_MEMORY, 0 /*rid*/);
              }

类似, 很容易检查IRQ和DRQ所用的资源。

如果一切进行正常，然后就可以释放所有资源并返回成功。

              xxx_free_resources(sc);
              return 0;

最后，处理棘手情况。所有资源应当在返回前被释放。我们利用这样一个 事实：softc结构在传递给我们以前被零化，因此我们能够找出是否分配了 某些资源：如果分配则这些资源的描述符非零。

              bad:

              xxx_free_resources(sc);
              if(error)
                    return error;
              else /* 确切错误未知 */
                  return ENXIO;

这是完整的探测例程。资源的释放从多个地方完成，因此将它挪到一个 函数中，看起来可能像下面的样子：

    static void
               xxx_free_resources(sc)
                  struct xxx_softc *sc;
              {
                  /* 检查每个资源，如果非0则释放 */

                  /* 中断处理函数 */
                  if(sc->intr_r) {
                      bus_teardown_intr(sc->dev, sc->intr_r, sc->intr_cookie);
                      bus_release_resource(sc->dev, SYS_RES_IRQ, sc->intr_rid,
                          sc->intr_r);
                      sc->intr_r = 0;
                  }

                  /* 我们分配过的所有种类的内存 */
                  if(sc->data_p) {
                      bus_dmamap_unload(sc->data_tag, sc->data_map);
                      sc->data_p = 0;
                  }
                   if(sc->data) { /* sc->data_map等于0有可能合法 */
                      /* the map will also be freed */
                      bus_dmamem_free(sc->data_tag, sc->data, sc->data_map);
                      sc->data = 0;
                  }
                  if(sc->data_tag) {
                      bus_dma_tag_destroy(sc->data_tag);
                      sc->data_tag = 0;
                  }

                  ... 如果有，释放其他的映射和标签 ...

                  if(sc->parent_tag) {
                      bus_dma_tag_destroy(sc->parent_tag);
                      sc->parent_tag = 0;
                  }

                  /* 释放所有总线资源 */
                  if(sc->mem0_r) {
                      bus_release_resource(sc->dev, SYS_RES_MEMORY, sc->mem0_rid,
                          sc->mem0_r);
                      sc->mem0_r = 0;
                  }
                  ...
                  if(sc->port0_r) {
                      bus_release_resource(sc->dev, SYS_RES_IOPORT, sc->port0_rid,
                          sc->port0_r);
                      sc->port0_r = 0;
                  }
              }

上一页 | 起始页 | 下一页
DMA | 上一级 | xxx_isa_attach

本文档和其它文档可从这里下载：ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/.

如果对于FreeBSD有问题，请先阅读文档，如不能解决再联系<questions@FreeBSD.org>.
关于本文档的问题请发信联系 <doc@FreeBSD.org>.