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;
              }
          }

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

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