这个函数探测设备是否存在。如果驱动程序支持自动侦测设备配置的 某些部分(如中断向量或内存地址),则自动侦测必须在此例程中完成。
对于任意其他总线,如果不能侦测到设备,或者侦测到但自检失败, 或者发生某些其他问题,则应当返回一个正值的错误。如果设备不 存在则必须返回值 “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>.