过Linux的人都知道在Linux上硬件的配置过程是非常繁杂的。比如说,对于一块普通的pci网卡,您可能先要知道它的芯片类型,网卡生产厂商,然后猜出它对应的硬件驱动模块,然后再使用modprobe(insmod)插入这个模块,再然后还要生成一系列的配置脚本,最后才能使其正常工作。这还只是一块网卡的配置过程,但是对于不胜枚举的其他硬件,如显卡、声卡、modem、isdn设备、usb设备、pcmcia设备,而它们的配置方法和生成的配置脚本都不尽相同,因此对于一个普通用户要想全部掌握这些配置过程是相当困难的。硬件的自动检测是进行Linux下设备自动配置过程的前提。本文的内容是先从硬件在Linux下的内核描述信息开始,着重介绍如何实现硬件的自动检测。
1:设备检测的一般介绍
一般而言,在Linux下进行设备自动检测是根据设备的总线类型进行的。现在的微机系统上最常见的总线类型有PCI、SERIAL、USB、PCMCIA、PARPORT、ISA、SCSI等。对于检测过程,一般不是通过c语言的库函数直接对设备进行访问,并读取设备的信息,而是通过内核的/proc文件系统进行。这种检测方式,充分利用了内核中关于硬件的多种检测函数,具有高效、稳定的特点,并且在内核版本升级之后,使程序的变化也为最小。对于大多数现在流行的系统硬件,在插入适当的模块之后,内核会在/proc文件系统中生成相应的描述文件。检测过程就是读取这样的文件,并将其信息进行相应的处理,从中提取出设备标识、设备描述、设备工作状态等信息。
由于涉及到对/proc文件系统的访问,并可能在检测过程开始时插入需要的设备模块,所以需要用户以root用户方式执行下面所说的操作。
在检测过程结束的时候,用户一般能够得到设备的唯一标识(制造商标识和设备标识)和设备的当前的状态信息。这时就需要一个设备数据库,这个数据库将设备的唯一标识和对应的设备驱动程序对应起来,然后由此生成/etc/modules.conf中的对应表项及其他配置脚本,完成整个的硬件配置过程。
2 PCI设备的自动检测
2.1 PCI设备简介
对于每个pci设备都由一个总线号、一个设备号和一个功能号确定。PCI设备可以访问三类地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。前两者可由PCI总线上的所有设备共享。PCI的配置空间由256个字节构成,其布局是标准化的。下表显示的是配置空间中前64字节的每个配置寄存器的分布情况:
Vendor ID Device ID
Command Reg Status Reg
Revision ID Class Code
Cache Line Latency Timer Header Type BIST
Base Address 0
Base Address 1
Base Address 2
Base Address 3
Base Address 4
Base Address 5
CardBus CIS pointer
Subsystem Vendor ID Subsystem Device ID
Expansion ROM Base Add
Reserved(PCI Capability List)
Reserved
IRQ Line IRQ Pin Min_Gnt Max_Lat
class code
设备的类型标识,类寄存器是16位的值。它的高八位确定基类,如SCSI设备的分类码位0x0100。Linux系统的定义见 中的声明。现在摘录如下:
#define PCI_CLASS_NOT_DEFINED 0x0000
#define PCI_CLASS_NOT_DEFINED_VGA 0x0001
#define PCI_BASE_CLASS_STORAGE0x01
#define PCI_CLASS_STORAGE_SCSI0x0100
#define PCI_CLASS_STORAGE_IDE 0x0101
#define PCI_CLASS_STORAGE_FLOPPY0x0102
#define PCI_CLASS_STORAGE_IPI 0x0103
#define PCI_CLASS_STORAGE_RAID0x0104
#define PCI_CLASS_STORAGE_OTHER 0x0180
#define PCI_BASE_CLASS_NETWORK0x02
#define PCI_CLASS_NETWORK_ETHERNET0x0200
#define PCI_CLASS_NETWORK_TOKEN_RING0x0201
#define PCI_CLASS_NETWORK_FDDI0x0202
#define PCI_CLASS_NETWORK_ATM 0x0203
#define PCI_CLASS_NETWORK_OTHER 0x0280
#define PCI_BASE_CLASS_DISPLAY0x03
#define PCI_CLASS_DISPLAY_VGA 0x0300
#define PCI_CLASS_DISPLAY_XGA 0x0301
#define PCI_CLASS_DISPLAY_OTHER 0x0380
#define PCI_BASE_CLASS_MULTIMEDIA 0x04
#define PCI_CLASS_MULTIMEDIA_VIDEO0x0400
#define PCI_CLASS_MULTIMEDIA_AUDIO0x0401
#define PCI_CLASS_MULTIMEDIA_OTHER0x0480
#define PCI_BASE_CLASS_MEMORY 0x05
#definePCI_CLASS_MEMORY_RAM 0x0500
#definePCI_CLASS_MEMORY_FLASH 0x0501
#definePCI_CLASS_MEMORY_OTHER 0x0580
#define PCI_BASE_CLASS_BRIDGE 0x06
#definePCI_CLASS_BRIDGE_HOST0x0600
#definePCI_CLASS_BRIDGE_ISA 0x0601
#definePCI_CLASS_BRIDGE_EISA0x0602
#definePCI_CLASS_BRIDGE_MC0x0603
#definePCI_CLASS_BRIDGE_PCI 0x0604
#definePCI_CLASS_BRIDGE_PCMCIA0x0605
#definePCI_CLASS_BRIDGE_NUBUS 0x0606
#definePCI_CLASS_BRIDGE_CARDBUS 0x0607
#definePCI_CLASS_BRIDGE_OTHER 0x0680
#define PCI_BASE_CLASS_COMMUNICATION0x07
#define PCI_CLASS_COMMUNICATION_SERIAL0x0700
#define PCI_CLASS_COMMUNICATION_PARALLEL0x0701
#define PCI_CLASS_COMMUNICATION_OTHER 0x0780
#define PCI_BASE_CLASS_SYSTEM 0x08
#define PCI_CLASS_SYSTEM_PIC0x0800
#define PCI_CLASS_SYSTEM_DMA0x0801
#define PCI_CLASS_SYSTEM_TIMER0x0802
#define PCI_CLASS_SYSTEM_RTC0x0803
#define PCI_CLASS_SYSTEM_OTHER0x0880
#define PCI_BASE_CLASS_INPUT0x09
#define PCI_CLASS_INPUT_KEYBOARD0x0900
#define PCI_CLASS_INPUT_PEN 0x0901
#define PCI_CLASS_INPUT_MOUSE 0x0902
#define PCI_CLASS_INPUT_OTHER 0x0980
#define PCI_BASE_CLASS_DOCKING0x0a
#define PCI_CLASS_DOCKING_GENERIC 0x0a00
#define PCI_CLASS_DOCKING_OTHER 0x0a01
#define PCI_BASE_CLASS_PROCESSOR0x0b
#define PCI_CLASS_PROCESSOR_386 0x0b00
#define PCI_CLASS_PROCESSOR_486 0x0b01
#define PCI_CLASS_PROCESSOR_PENTIUM 0x0b02
#define PCI_CLASS_PROCESSOR_ALPHA 0x0b10
#define PCI_CLASS_PROCESSOR_POWERPC 0x0b20
#define PCI_CLASS_PROCESSOR_CO0x0b40
#define PCI_BASE_CLASS_SERIAL 0x0c
#define PCI_CLASS_SERIAL_FIREWIRE 0x0c00
#define PCI_CLASS_SERIAL_ACCESS 0x0c01
#define PCI_CLASS_SERIAL_SSA0x0c02
#define PCI_CLASS_SERIAL_USB0x0c03
#define PCI_CLASS_SERIAL_FIBER0x0c04
#define PCI_CLASS_SERIAL_SMBUS0x0c05
#define PCI_BASE_CLASS_INTELLIGENT0x0e
#define PCI_CLASS_INTELLIGENT_I2O 0x0e00
#define PCI_CLASS_HOT_SWAP_CONTROLLER 0xff00
#define PCI_CLASS_OTHERS0xff
Base Address 0- Base Address 5
表示此卡占用的内存范围。
IRQ Line
指定的设备所使用的中断号,它在系统启动时由固件复制。
IRQ Pin
PCI卡有4个物理引脚,用于把中断从卡上发送到PCI总线上。为0表示设备不支持中断,非0表示使用哪一个终端引脚。此信息可让中断处理子系统处理来自该设备的中断。
关于pci设备的其他信息,您可以参考PCI Local Bus Specification。
2.2 Linux下PCI设备的检测过程
一个pci设备可映射到最多六个地址区段。每个区段由内存或I/O位置组成。区段的大小和当前位置由配置寄存器报告。
检测pci设备先要打开文件/proc/bus/pci/devices,例如存在下列数据:
0039808671110000000000000000000000000000000000000f0010000000000000000
上面的数据表示一个pci设备,对于第一个数字0039(十六进制表示),高八位表示总线号(bus),对于低三位表示功能号(function),剩余的五位表示设备号(device)。也就是说,0039表示,总线0,设备号为7,功能号为1。由此,内核生成设备对应的文件/proc/bus/pci/bus/device.function。此文件为256个字节,对应读出的设备配置信息,第十字节开始的双字节表示设备的类型。第二个数字80867111表示,设备的vendorid为8086,deviceid为7111。通过查询设备配置数据库可知设备标识80867111表示Intel Corporation|82371AB PIIX4 IDE控制器。第三个数字表示占用的irq。
用户可以通过察看/proc/pci文件,可以获得系统连接的pci设备的基本信息描述,或者用户也可以使用命令lspci(需要安装包pciutils)来察看PCI设备较为详细的描述信息。但是为了使内核显示上述信息,必须要在编译内核时加入PCI设备名数据库,这会使内核增大约80KB。
关于Linux系统PCI设备的进一步信息您可以阅读http://www.linuxdoc.org/HOWTO/PCI-HOWTO.html
3 ISAPNP设备的自动检测
3.1 ISAPNP设备简介
在每个ISAPNP设备加电启动之后,都会进行一个自动配置过程,它的主要步骤如下:
设置所有的isapnp卡为配置模式
每次隔离一个isapnp卡
指定一个句柄并读取卡的资源数据结构
在所有的卡的资源需求和兼容性被决定之后,使用句柄指派每个卡无冲突的资源
激活所有的isapnp卡并使其离开配置模式
pnp软件使用一系列规范定义的命令标识和配置设备,这些命令由三个8位端口发出(不支持16位的配置端口)。写到这些端口中任何一个的打开系统中pnp逻辑的数据序列对所有卡生效。这个数据序列被称作初始键。
所有卡响应同样的I/O端口寻址,因此为了一次只寻址一个卡pnp软件需要一种隔离机制。隔离协议使用独一无二的标号一次区别一个卡。在隔离之后,pnp软件对每个卡指定一个句柄,由它对应确定的pnp卡。
每个卡上支持可读的资源数据结构,用以描述卡上支持的和功能请求的资源。此结构允许每个ISA卡有多个功能,每个功能作为一个逻辑设备来定义。每个逻辑设备提供pnp资源信息,pnp标准寄存器独立配置每个逻辑设备。
隔离操作之后,pnp软件读取每个卡的资源数据结构。在所有的资源的能力和需求已知之后,系统发出一个资源仲裁来裁决每个卡的资源分配并使用命令寄存器指定每个资源类型。
在资源被指派之后,I/O冲突检测机制执行。命令集也包括激活/禁止卡的功能的命令集。在配置之后,pnp卡离开配置模式。如果向卡重新发出初始化键值,则可以重新使能配置模式(防止配置信息被故意删除)。
对于isa pnp设备有三个八位端口被用于存取isapnp卡的配置空间。配置空间由一系列8位寄存器组成。这些寄存器被用于发送命令,检测状态,存取资源数据信息和配置pnp硬件。
Port Name Location Type
ADDRESS 0x0279 (Printer status port) Write-only
WRITE_DATA 0x0A79 (Printer status port + 0x0800) Write-only
READ_DATA Relocatable in range 0x0203 to 0x03FF Read-only
Table 1. 自动配置端口
地址端口(ADDRESS Port)
在访问pnp寄存器之前,首先写寄存器地址到地址端口,紧接着从READ_DATA端口读或者从WRITE_DATA端口写。地址端口也是初始键的写目标。
隔离机制的关键是每个卡都有一个独一无二的编号(serial identifier)。serial identifier是一个72位独一无二、非0的数字,由两个32位域和一个8位校验位组成。头32位是制造商标识,另32位可以是任何值。只要不存在任何两块编号相同的卡。
Figure 1. Shifting of Serial Identifier
在隔离过程结束时,当前控制的卡被指定一个句柄,称为Card Select Number(CSN),用于以后检索此卡。在卡响应其他的命令之前,此卡必须被指定一个CSN。
CSN是一个8位寄存器,加电使所有卡的此寄存器置为0x0。一旦卡的隔离操作结束,此卡的CSN寄存器被指定一个独一无二的值,以使pnp软件能区别所用的卡。isapnp状态如下:
Wait for Key
在加电重设之后或在响应重设与等待命令之后所有卡进入此状态。在此状态下没有命令被激活直到初始键被检测。 Wait for Key在正常系统操作过程中是pnp卡的缺省状态。在配置和激活操作之后,软件应设置所有的卡到此状态。
Sleep
在此状态,pnp卡等待Wake[CSN]命令。此命令会由CSN值设置一个或多个卡进入Isolation或Config状态。在Wake[CSN]命令的写数据位[7:0]的值与卡的CSN匹配时,卡离开Sleep状态。若Wake[CSN]命令写数据为0,所有的没有指定CSN的卡会进入Isolation状态。若Wake[CSN]命令写数据为非0,Wake[CSN]命令的CSN参数与卡上分派的CSN匹配的卡会进入Config状态。
Isolation
在此状态,pnp卡响应对隔离寄存器的读请求。一旦卡被隔离,独一无二的CSN被指定,CSN作为卡的唯一标识用于在Wake[CSN]命令中选择此卡。一旦写入CSN,卡过渡到Config状态。
Config
在此状态的卡会响应所有的配置命令,包括读卡的资源配置信息以及编程卡的资源选择。
关于ISAPNP设备的详细的硬件信息,您可以参考ISAPNP Specification
3.2 Linux系统下ISAPNP设备的自动检测
ISA设备的自动检测是指对支持Plug and Play ISA规范1.0的ISA设备的检测,对不支持此规范的ISA设备,此检测过程是无法工作的。这时的硬件检测和配置是完全要根据用户的个人经验进行。在进行下述检测过程之前,对于基于2.2.X内核的Linux系统,需要用户安装包isapnptools。对于2.4.x内核,则用户只需在编译内核时加入 ISA