如何在LINUX下实现硬件的自动检测(上)

2019-07-12 20:29发布

级别: 初级 于辰涛 (scu_yct@263.net), 软件工程师, 联想(北京)电脑公司
2001 年 7 月 01 日
用过Linux的人都知道在Linux上硬件的配置过程是非常繁杂的。比如说,对于一块普通的pci网卡,您可能先要知道它的芯片类型,网卡生产厂商,然后猜出它对应的硬件驱动模块,然后再使用modprobe(insmod)插入这个模块,再然后还要生成一系列的配置脚本,最后才能使其正常工作。这还只是一块网卡的配置过程,但是对于不胜枚举的其他硬件,如显卡、声卡、modem、isdn设备、usb设备、pcmcia设备,而它们的配置方法和生成的配置脚本都不尽相同,因此对于一个普通用户要想全部掌握这些配置过程是相当困难的。硬件的自动检测是进行Linux下设备自动配置过程的前提。本文的内容是先从硬件在Linux下的内核描述信息开始,着重介绍如何实现硬件的自动检测。
  一般而言,在Linux下进行设备自动检测是根据设备的总线类型进行的。现在的微机系统上最常见的总线类型有PCI、SERIAL、USB、PCMCIA、PARPORT、ISA、SCSI等。对于检测过程,一般不是通过c语言的库函数直接对设备进行访问,并读取设备的信息,而是通过内核的/proc文件系统进行。这种检测方式,充分利用了内核中关于硬件的多种检测函数,具有高效、稳定的特点,并且在内核版本升级之后,使程序的变化也为最小。对于大多数现在流行的系统硬件,在插入适当的模块之后,内核会在/proc文件系统中生成相应的描述文件。检测过程就是读取这样的文件,并将其信息进行相应的处理,从中提取出设备标识、设备描述、设备工作状态等信息。 由于涉及到对/proc文件系统的访问,并可能在检测过程开始时插入需要的设备模块,所以需要用户以root用户方式执行下面所说的操作。 在检测过程结束的时候,用户一般能够得到设备的唯一标识(制造商标识和设备标识)和设备的当前的状态信息。这时就需要一个设备数据库,这个数据库将设备的唯一标识和对应的设备驱动程序对应起来,然后由此生成/etc/modules.conf中的对应表项及其他配置脚本,完成整个的硬件配置过程。
  对于每个pci设备都由一个总线号、一个设备号和一个功能号确定。PCI设备可以访问三类地址空间:PCI的I/O空间、PCI的存储空间和PCI的配置空间。前两者可由PCI总线上的所有设备共享。PCI的配置空间由256个字节构成,其布局是标准化的。下表显示的是配置空间中前64字节的每个配置寄存器的分布情况: Vendor ID Device IDCommand Reg Status RegRevision ID Class CodeCache Line Latency Timer Header Type BISTBase Address 0Base Address 1Base Address 2Base Address 3Base Address 4Base Address 5CardBus CIS pointerSubsystem Vendor ID Subsystem Device IDExpansion ROM Base AddReserved(PCI Capability List)ReservedIRQ Line IRQ Pin Min_Gnt Max_Latclass code
设备的类型标识,类寄存器是16位的值。它的高八位确定基类,如SCSI设备的分类码位0x0100。Linux系统的定义见中的声明。现在摘录如下: #define PCI_CLASS_NOT_DEFINED 0x0000 #define PCI_CLASS_NOT_DEFINED_VGA 0x0001 #define PCI_BASE_CLASS_STORAGE 0x01 #define PCI_CLASS_STORAGE_SCSI 0x0100 #define PCI_CLASS_STORAGE_IDE 0x0101 #define PCI_CLASS_STORAGE_FLOPPY 0x0102 #define PCI_CLASS_STORAGE_IPI 0x0103 #define PCI_CLASS_STORAGE_RAID 0x0104 #define PCI_CLASS_STORAGE_OTHER 0x0180 #define PCI_BASE_CLASS_NETWORK 0x02 #define PCI_CLASS_NETWORK_ETHERNET 0x0200 #define PCI_CLASS_NETWORK_TOKEN_RING 0x0201 #define PCI_CLASS_NETWORK_FDDI 0x0202 #define PCI_CLASS_NETWORK_ATM 0x0203 #define PCI_CLASS_NETWORK_OTHER 0x0280 #define PCI_BASE_CLASS_DISPLAY 0x03 #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_VIDEO 0x0400 #define PCI_CLASS_MULTIMEDIA_AUDIO 0x0401 #define PCI_CLASS_MULTIMEDIA_OTHER 0x0480 #define PCI_BASE_CLASS_MEMORY 0x05 #define PCI_CLASS_MEMORY_RAM 0x0500 #define PCI_CLASS_MEMORY_FLASH 0x0501 #define PCI_CLASS_MEMORY_OTHER 0x0580 #define PCI_BASE_CLASS_BRIDGE 0x06 #define PCI_CLASS_BRIDGE_HOST 0x0600 #define PCI_CLASS_BRIDGE_ISA 0x0601 #define PCI_CLASS_BRIDGE_EISA 0x0602 #define PCI_CLASS_BRIDGE_MC 0x0603 #define PCI_CLASS_BRIDGE_PCI 0x0604 #define PCI_CLASS_BRIDGE_PCMCIA 0x0605 #define PCI_CLASS_BRIDGE_NUBUS 0x0606 #define PCI_CLASS_BRIDGE_CARDBUS 0x0607 #define PCI_CLASS_BRIDGE_OTHER 0x0680 #define PCI_BASE_CLASS_COMMUNICATION 0x07 #define PCI_CLASS_COMMUNICATION_SERIAL 0x0700 #define PCI_CLASS_COMMUNICATION_PARALLEL 0x0701 #define PCI_CLASS_COMMUNICATION_OTHER 0x0780 #define PCI_BASE_CLASS_SYSTEM 0x08 #define PCI_CLASS_SYSTEM_PIC 0x0800 #define PCI_CLASS_SYSTEM_DMA 0x0801 #define PCI_CLASS_SYSTEM_TIMER 0x0802 #define PCI_CLASS_SYSTEM_RTC 0x0803 #define PCI_CLASS_SYSTEM_OTHER 0x0880 #define PCI_BASE_CLASS_INPUT 0x09 #define PCI_CLASS_INPUT_KEYBOARD 0x0900 #define PCI_CLASS_INPUT_PEN 0x0901 #define PCI_CLASS_INPUT_MOUSE 0x0902 #define PCI_CLASS_INPUT_OTHER 0x0980 #define PCI_BASE_CLASS_DOCKING 0x0a #define PCI_CLASS_DOCKING_GENERIC 0x0a00 #define PCI_CLASS_DOCKING_OTHER 0x0a01 #define PCI_BASE_CLASS_PROCESSOR 0x0b #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_CO 0x0b40 #define PCI_BASE_CLASS_SERIAL 0x0c #define PCI_CLASS_SERIAL_FIREWIRE 0x0c00 #define PCI_CLASS_SERIAL_ACCESS 0x0c01 #define PCI_CLASS_SERIAL_SSA 0x0c02 #define PCI_CLASS_SERIAL_USB 0x0c03 #define PCI_CLASS_SERIAL_FIBER 0x0c04 #define PCI_CLASS_SERIAL_SMBUS 0x0c05 #define PCI_BASE_CLASS_INTELLIGENT 0x0e #define PCI_CLASS_INTELLIGENT_I2O 0x0e00 #define PCI_CLASS_HOT_SWAP_CONTROLLER 0xff00 #define PCI_CLASS_OTHERS 0xff
Base Address 0- Base Address 5
表示此卡占用的内存范围。 IRQ Line
指定的设备所使用的中断号,它在系统启动时由固件复制。 IRQ Pin
PCI卡有4个物理引脚,用于把中断从卡上发送到PCI总线上。为0表示设备不支持中断,非0表示使用哪一个终端引脚。此信息可让中断处理子系统处理来自该设备的中断。 关于pci设备的其他信息,您可以参考PCI Local Bus Specification。   一个pci设备可映射到最多六个地址区段。每个区段由内存或I/O位置组成。区段的大小和当前位置由配置寄存器报告。 检测pci设备先要打开文件/proc/bus/pci/devices,例如存在下列数据: 003980867111 0000000000000000000000000000000000000f0010000000000000000
上面的数据表示一个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
  在每个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-onlyWRITE_DATA 0x0A79 (Printer status port + 0x0800) Write-onlyREAD_DATA Relocatable in range 0x0203 to 0x03FF Read-onlyTable 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   ISA设备的自动检测是指对支持Plug and Play ISA规范1.0的ISA设备的检测,对不支持此规范的ISA设备,此检测过程是无法工作的。这时的硬件检测和配置是完全要根据用户的个人经验进行。在进行下述检测过程之前,对于基于2.2.X内核的Linux系统,需要用户安装包isapnptools。对于2.4.x内核,则用户只需在编译内核时加入 ISA Plug and Play support即可。在检测开始之前,必须由用户手动插入模块(modprobe)isapnp.o或者isa-pnp.o(由内核版本决定)。在用户插入上述模块之后,内核会自动生成/proc下对应的描述文件。这些描述文件包括/proc/isapnp,/proc/bus/isapnp/devices,/proc/bus/isapnp/ csn。 例如,在系统中存在OPL3-SA3类型的声卡时,未挂接设备驱动程序的情况下,/proc/isapnp文件内容如下: Card 1 'YMH0020:OPL3-SA3 Sound Board' PnP version 1.0 Logical device 0 'YMH0021:Unknown' Device is not active Active DMA 0,0 Resources 0 Priority preferred Port 0x220-0x220, align 0xf, size 0x10, 16-bit address decoding Port 0x530-0x530, align 0x7, size 0x8, 16-bit address decoding Port 0x388-0x388, align 0x7, size 0x8, 16-bit address decoding Port 0x330-0x330, align 0x1, size 0x2, 16-bit address decoding Port 0x370-0x370, align 0x1, size 0x2, 16-bit address decoding IRQ 5 High-Edge DMA 0 8-bit byte-count type-A DMA 1 8-bit byte-count type-A Alternate resources 0:1 Priority acceptable Port 0x240-0x240, align 0xf, size 0x10, 16-bit address decoding Port 0xe80-0xe80, align 0x7, size 0x8, 16-bit address decoding Port 0x388-0x388, align 0x7, size 0x8, 16-bit address decoding Port 0x300-0x300, align 0x1, size 0x2, 16-bit address decoding Port 0x100-0xffe, align 0x1, size 0x2, 16-bit address decoding IRQ 5,7,2/9,10,11 High-Edge DMA 0,1,3 8-bit byte-count type-A DMA 0,1,3 8-bit byte-count type-A Alternate resources 0:2 Priority functional Port 0x220-0x280, align 0xf, size 0x10, 16-bit address decoding Port 0x530-0xf48, align 0x7, size 0x8, 16-bit address decoding Port 0x388-0x3f8, align 0x7, size 0x8, 16-bit address decoding Port 0x300-0x334, align 0x1, size 0x2, 16-bit address decoding Port 0x100-0xffe, align 0x1, size 0x2, 16-bit address decoding IRQ 3,5,7,2/9,10,11 High-Edge DMA 0,1,3 8-bit byte-count type-A DMA 0,1,3 8-bit byte-count type-A Logical device 1 'YMH0022:Unknown' Compatible device PNPb02f Device is not active Active DMA 0,0 Resources 0 Priority preferred Port 0x201-0x201, align 0x0, size 0x1, 16-bit address decoding Alternate resources 0:1 Priority functional Port 0x201-0x211, align 0xf, size 0x1, 16-bit address decoding
上述信息的第一行表示此ISA设备的板卡数,其后的YMH0020表示板卡标识。以" Logical Device"为行首的小节代表不同的逻辑设备(一块isapnp卡上可以存在多个逻辑设备)。" Device is not active "表示此设备的驱动程序未加载,在设备的驱动程序加载后此处显示" Device is active "。在装入设备对应的驱动程序后,其后的字段以"Active"开始表示设备所占用的资源。例如 Active port 0x240,0xe80,0x388,0x300,0x100(占用的端口号) Active IRQ 5 [0x2] (占用的中断号) Active DMA 1,3 (占用的DMA)
此文件再往下的部分列出的是此卡可以占用的地址空间,一般是一组资源配置方案,上面的例子就包含三组可选的资源配置方案,分别是缺省方案、可选配置方案1(Alternate resources 0:1)和可选配置方案2(Alternate resources 0:2)。
  存储设备主要是指硬盘,光驱等能够进行告诉数据存取的设备。我们最常见的存储设备按照总线类型分类主要包括ide,scsi这两种。对于ide设备,Linux系统一般是使用hd*来表示,*是按照连接的总线位置顺序编号。一般而言,我们现在所使用的微机有两个ide接口,并且每个接口上只能连接两个设备,一个为主设备而另一个为从设备。对于连接在ide0上的主设备它的设备名为hda,而对于在ide0上的从设备它的设备名为hdb。以此类推,连接在ide1上的主、从设备的设备名分别为hdc和hdd。 一般而言,现有的linux发行版都将对ide设备的支持加入内核,所以在系统启动之后,相应的设备模块已经在内核中了,所以检测过程只需对/proc/ide下相应的文件进行访问就行了。对于scsi设备,对它们的支持一般都不打入内核,所以要想使scsi设备在Linux下生效必须先插入对应的设备驱动模块。这就要求用户在创建lilo时必须在/etc/lilo.conf中加入一行"initrd=",它连接的由命令mkinitrd生成系统启动映像。例如,在使用Adaptec,AIC-7850的scsi芯片组时,使用模块aic7xxx,此时需要进行如下步骤:
mkinitrd --ifneeded /boot/initrd-2.4.3.img 2.4.3 --with=aic7xxx
然后编辑/etc/lilo.conf,加入行: initrd=/boot/initrd-2.4.3.img
重新运行lilo,启动后,这时就会在目录/proc/scsi下出现设备的对应描述文件。只要检索这些文件就可以完成对设备的检测。
  • ide设备的检测过程
    1. 查找/proc/ide/下名为的hd*(*为通配符,可为a,b,…)的目录(目录名为设备名)。
    2. 打开文件/proc/ide/hd*/media,此文件的只由一行构成,其内容为cdrom、disk、tape或floppy,此文件的内容表示此IDE设备的类型。
    3. 打开文件/proc/ide/hd*/model,由此文件得到详细的设备描述信息。例如:ST313021A,表示希捷硬盘,型号为ST313021A。
    4. 打开文件/proc/ide/hd*/geometry,这个文件描述的是硬盘的几何信息。例如 physical 16383/16/63
      logical 1583/255/63(cylinders/heads/sectors)
      表示这块ide硬盘的柱面数,头数和扇区数
    5. 由命令mknod path, b, major, minor,创建设备结点。对于从/dev/hda到/dev/hdh的设备结点的主次设备号为: /dev/hda major = 3, minor = 0 /dev/hdb major = 3, minor = 64 /dev/hdc major = 22, minor = 0 /dev/hdd major = 22, minor = 64 /dev/hde major = 33, minor = 0 /dev/hdf major = 33, minor = 64 /dev/hdg major = 34, minor = 0 /dev/hdh major = 34, minor = 64
  • scsi设备的检测过程
    1. 读入/proc/scsi/scsi文件,其文件结构: Attached devices: Host: scsi**** Channel: **** Id: **** Lun: **** Vendor: XXXXXXX Model:xxxxxx Rev: Type: Direct-Access(类型为hd,设备名为sd*) Sequential-Access(类型为tape,设备名为st*) CD-ROM (类型为cdrom,设备名为scd*) Scanner (类型为scanner,设备名为sg*) Printer (类型为printer,设备名为sg*) 其他设备(设备名为sg*)
待续
    于辰涛,联想(北京)电脑公司软件工程师。目前主要从事Linux系统安装程序的开发工作,主要研究兴趣是操作系统的工作机制和开发底层系统程序。您可以通过电子邮件 scu_yct@263.net 跟他联系。