SMBIOS介绍(3):实现

  Linux中实现了SMBIO内核模块,它是通过/proc文件系统,以一种用户可理解的格式或纯粹的二进制格式来访问SMBIOS结构的信息。sourceforge上有这个内核模块的源代码,地址为http://sourceforge.net/projects/smbios/,是在Linux 2.4内核中的实现,它同时也实现了DMI。注意Linux 2.6中的内核驱动程序模块结构与2.4中的基本相同,只是有一些少许的变化,这里就不展开了。
  1、bios.h文件:
  (1)头文件中定义搜索起始地址0xF0000,固定字符串"_SM_"和"_DMI_"。
  (2)定义SMBIOS EPS表smbios_entry_point_struct,SMBIOS结构头部smbios_struct、DMI的EPS表及结构头部。
  (3)定义了模块在proc文件系统中的位置。有两种访问模式,即原始二进制格式(raw),用户可理解格式(cooked),因些访问位置有目录/proc/smbios,/proc/smbios/raw,/proc/smbios/cooked。
  (4)定义搜索EPS表的函数smbios_find_entry_point(void*),以及获取结构长度、在proc中创建或删除节点、从proc文件系统中读取原始的或可理解格式的SMBIOS数据,等等。
  这里的设计体现了初步的数据封装思想,即把数据结构和操作这些数据结构的函数封装在一个文件中。

/** /文件 bios.h * DMI-BIOS和SM-BIOS的原型及声明 */ #ifndef __BIOS_H__ #define __BIOS_H__ /* * 用来帮助调试的宏 */ #undef PDEBUG /* 取消先前的定义(如果有的话),以防万一 */ #ifdef _DEBUG_ /* 定义调试宏 */ # define PDEBUG(fmt, args...) printk( KERN_DEBUG "smbios: " fmt, ## args) #else # define PDEBUG(fmt, args...) /* 不调试:不做任何事 */ #endif #define BIOS_START_ADDRESS 0xF0000 /* 搜索SM-BIOS和DMI-BIOS的BISO段起始地址 */ #define BIOS_MAP_LENGTH 0x10000 /* 搜索的BIOS区域长度 */ #define SMBIOS_MAGIC_DWORD 0x5F4D535F /* 固定字符串 "_SM_" */ #define DMIBIOS_MAGIC_DWORD 0x494d445f /* 固定字符串 "_DMI" */ #define DMI_STRING "_DMI_" /** 有子类型的SMBIOS结构类型,可扩充! */ #define TYPES_WITH_SUBTYPES 185, 187, 208, 209, 210, 211, 212, 254 #define PROC_BLOCK_SIZE (3*1024) /* proc read函数最大的块大小 */ /** 模式 原始二进制模式/烹调好的(即用户可理解模式) */ #define FILE_MODE_RAW 0 #define FILE_MODE_COOKED 1 /* SMBIOS EPS表 */ typedef struct smbios_entry_point_struct { __u32 anchor_string __attribute__ ((packed)); __u8 entry_point_checksum __attribute__ ((packed)); __u8 entry_point_length __attribute__ ((packed)); __u8 major_version __attribute__ ((packed)); __u8 minor_version __attribute__ ((packed)); __u16 max_struct_size __attribute__ ((packed)); __u8 revision __attribute__ ((packed)); __u8 formated_area[5] __attribute__ ((packed)); __u8 intermediate_string[5] __attribute__ ((packed)); __u8 intermediate_checksum __attribute__ ((packed)); __u16 struct_table_length __attribute__ ((packed)); __u32 struct_table_address __attribute__ ((packed)); __u16 no_of_structures __attribute__ ((packed)); __u8 bcd_revision __attribute__ ((packed)); } smbios_entry_point_struct; /** SM-BIOS和DMI-BIOS结构的头部 */ typedef struct smbios_struct { __u8 type __attribute__ ((packed)); __u8 length __attribute__ ((packed)); __u16 handle __attribute__ ((packed)); __u8 subtype __attribute__ ((packed)); } smbios_struct; /** DMI-BIOS结构的头部 */ typedef struct dmibios_table_entry_struct { __u16 size __attribute__ ((packed)); __u16 handle __attribute__ ((packed)); __u32 procedure __attribute__ ((packed)); } dmibios_table_entry_struct; /** DMI-BIOS的EPS表 */ typedef struct dmibios_entry_point_struct { __u8 signature[10] __attribute__ ((packed)); __u8 revision __attribute__ ((packed)); dmibios_table_entry_struct entry[1] __attribute__ ((packed)); } dmibios_entry_point_struct; /* * 变量 */ extern struct proc_dir_entry * smbios_proc_dir; /* /proc/smbios */ extern struct proc_dir_entry * smbios_raw_proc_dir; /* /proc/smbios/raw */ extern struct proc_dir_entry * smbios_cooked_proc_dir; /* /proc/smbios/cooked */ extern void * smbios_base; /* F-Segment */ extern smbios_entry_point_struct * smbios_entry_point; /* SMBIOS在F-Segment中的起始地址 */ extern dmibios_entry_point_struct * dmibios_entry_point; /* DMIBIOS在F-Segment中的起始地址 */ extern void * smbios_structures_base; /* SMBIOS结构信息的基地址 */ extern unsigned char smbios_types_with_subtypes[]; extern char smbios_version_string[32]; /* 支持的SMBIOS版本,例如V2.31 */ /* 搜索函数 */ smbios_entry_point_struct * smbios_find_entry_point(void * base); dmibios_entry_point_struct * dmibios_find_entry_point(void * base); unsigned char smbios_check_entry_point(void * addr); int smbios_type_has_subtype(unsigned char type); int smbios_get_struct_length(smbios_struct * struct_ptr); int dmibios_get_struct_length(smbios_struct * struct_ptr); int smbios_version_proc (char *page, char **start, off_t off, int count, int *eof, void *data); int bios_read_raw_proc(char * page, char ** start, off_t off, int count, int * eof, void * data); int bios_read_cooked_proc(char *page, char **start, off_t off, int count, int *eof, void *data); int dmibios_read_raw_proc(char * page, char ** start, off_t off, int count, int * eof, void * data); int dmibios_read_cooked_proc(char *page, char **start, off_t off, int count, int *eof, void *data); int smbios_make_dir_entries(struct proc_dir_entry *smbiosdir, struct proc_dir_entry *rawdir, struct proc_dir_entry *cookeddir); int smbios_make_version_entry(struct proc_dir_entry *smbiosdir); int dmibios_make_dir_entries(struct proc_dir_entry * smbiosdir, struct proc_dir_entry * rawdir, struct proc_dir_entry * cookeddir); void smbios_destroy_dir_entries(struct proc_dir_entry * dir); unsigned int smbios_get_readable_name_ext(char *readable_name, smbios_struct *struct_ptr); unsigned int smbios_get_readable_name(char *readable_name, smbios_struct *struct_ptr); int make_file_entries (char *filename, struct proc_dir_entry *dir, smbios_struct *struct_ptr, int mode); #endif /* __BIOS_H__ */

  这里__attribute__ ((packed))是GCC的扩展,其作用就是告诉编译器取消结构在编译过程中的优化对齐,按照实际占用字节数进行对齐。__attribute__关键字主要是用来在函数或数据声明中设置属性。给函数赋予属性的主要目的在于让编译器进行优化。例如函数声明中的__attribute__((noreturn)),就是告诉编译器这个函数不会返回给调用者,以便编译器在优化时去掉不必要的函数返回代码。__attribute__可以设置函数属性、变量属性和类型属性。书写时要放置在声明的尾部,在分号“;”之前。函数属性可以帮助开发者把一些特性添加到函数声明中,从而可以使编译器在错误检查方面的功能更强大。GCC需要使用–Wall编译器来击活该功能,这是控制警告信息的一个很好的方式。这里的packed属性可以使得变量或者结构体成员使用最小的对齐方式,即对变量是一字节对齐,对域(field)是位对齐。
  2、cooking.h和strgdef.h文件: 由于要以用户可理解的方式显示SMBIOS信息,这需要把原始的SMBIOS结构数据映射到一些可理解的字符串上,以显示给用户看。在cooking.h中定义了各个SMBIOS结构以及解析这些结构的函数。有Type 0-Type 13、Type 16、Type 17、Type 19、Type 20、Type 32、Type 127共20个SMBIOS结构数据。这些结构在SMBIOS规范中都有清晰的描述,定义它们是比较直接的。例如Type 0和Type 1的定义如下:

/* 解析成可理解的格式 */ unsigned char * bios_cook_type_0 (smbios_struct * smbiostype, unsigned int *length); unsigned char * bios_cook_type_1 (smbios_struct * smbiostype, unsigned int *length); typedef struct smbios_type_0 { smbios_header header; __u8 vendor __attribute__ ((packed)); __u8 version __attribute__ ((packed)); __u16 startaddr __attribute__ ((packed)); __u8 reldate __attribute__ ((packed)); __u8 romsize __attribute__ ((packed)); __u64 characteristics __attribute__ ((packed)); __u8 ext1 __attribute__ ((packed)); __u8 ext2 __attribute__ ((packed)); } smbios_type_0; typedef struct smbios_type_1 { smbios_header header; __u8 manufacturer __attribute__ ((packed)); __u8 productname __attribute__ ((packed)); __u8 version __attribute__ ((packed)); __u8 serialnumber __attribute__ ((packed)); __u8 uuid[16] __attribute__ ((packed)); __u8 wakeuptype __attribute__ ((packed)); } smbios_type_1;

  bios_cook_type_0()等函数就是把解析后的SMBIOS数据写入到/proc文件中。其他的结构就不列举了,在cooking.h中都有。strgdef.h中定义了各结构数据要解析成的友好信息,如结构中各个域的名称、字符串区域中各个字符串的值等,这在SMBIOS规范中也都有清晰的描述,定义时比较直接。例如对Type 0结构的信息,如下:

/* * Type 0 - Bios */ #define TYPE0_NAME "(BIOS Information)" #define TYPE0_VENDOR "Vendor" #define TYPE0_VERSION "Version" #define TYPE0_ADR_SEG "Starting Adr Seg" #define TYPE0_REL_DATE "Rel. Date" #define TYPE0_ROM_SIZE "ROM Size" #define TYPE0_CHAR "Characteristics" #define TYPE0_CHAR_RES "Reserved" #define TYPE0_CHAR_UNKNOWN "Unknown" #define TYPE0_CHAR_NOTSUP "Not Supported" #define TYPE0_CHAR_ISA "ISA" #define TYPE0_CHAR_MCA "MCA" #define TYPE0_CHAR_EISA "EISA" #define TYPE0_CHAR_PCI "PCI" #define TYPE0_CHAR_PCMCIA "PCMCIA" #define TYPE0_CHAR_PNP "Plug and Play" #define TYPE0_CHAR_APM "Advanced Power Management" #define TYPE0_CHAR_FLASH "Flash" #define TYPE0_CHAR_SHADOWING "Shadowing" #define TYPE0_CHAR_VL "VL-Vesa" #define TYPE0_CHAR_ESCD "ESCD" #define TYPE0_CHAR_BOOTCD "Boot from CD" #define TYPE0_CHAR_SELBOOT "Selectable Boot" #define TYPE0_CHAR_BIOS_IS_SOCKETED "Bios Rom is socketed" #define TYPE0_CHAR_PCMCIA_BOOT "Boot from PCMCIA" #define TYPE0_CHAR_ENH_DISK_DRIVE "Enhanced Disk Drive" #define TYPE0_CHAR_FD_NEC "Int13h - japanese Floppy NEC 1,2 MB" #define TYPE0_CHAR_FD_TOSHIBA "Int13h - japanese Floppy Toshiba 1,2 MB" #define TYPE0_CHAR_360 "Int13h - 360 kB Floppy" #define TYPE0_CHAR_1200 "Int13h - 1,2 MB Floppy" #define TYPE0_CHAR_720 "Int13h - 720 kB Floppy" #define TYPE0_CHAR_2880 "Int13h - 2,88 MB Floppy" #define TYPE0_CHAR_PRINT_SCREEN "Int5h - Print Screen" #define TYPE0_CHAR_KEYBOARD "Int9h - 8042 Keyboard" #define TYPE0_CHAR_SER_SERVICES "Int14h - Serial Services" #define TYPE0_CHAR_PRINT_SERVICES "Int17h - Printer Services" #define TYPE0_CHAR_VIDEO_SERVICES "Int10h - CGA,Mono Video Services" #define TYPE0_CHAR_PC98 "NEC PC-98" #define TYPE0_EXT1_ACPI "ACPI" #define TYPE0_EXT1_USB "USB Legacy" #define TYPE0_EXT1_AGP "AGP" #define TYPE0_EXT1_I2O_BOOT "I2O Boot" #define TYPE0_EXT1_LS120 "LS-120" #define TYPE0_EXT1_ATAPI_ZIP_BOOT "ATAPI ZIP Boot" #define TYPE0_EXT1_1394_BOOT "1394 Boot" #define TYPE0_EXT1_SMART_BATTERY "Smart Battery" #define TYPE0_EXT2_BBS "Bios Boot Spec." #define TYPE0_EXT2_NETWORK_BOOT "Function key initiated Network Service Boot"

  3、实现文件cooking.c和bios.c: 文件cooking.c中的各个函数,如bios_cook_type_0(),bios_cook_type_1(),是对相应SMBIOS结构数据的解析。各个函数做的工作类似,基本流程如下:
  (1)分配足够的空间(char[]型数组file)来存放整个结构的解析数据;
  (2)解析结构头部信息,把strgdef.h中的相应信息用strcpy写入到file中;
  (3)解析各个字符串信息,把strgdef.h中的相应字符串写入到file中;
  (4)用kmalloc分配proc文件的内存,然后把用memcpy把file数组写入到proc文件中。
  bios.c主要是实现对proc文件系统的操作,从内存的BIOS区域中(注意SMBIOS数据在BIOS启动时会被载入到内存的BIOS区域中),读取SMBIOS原始数据或解析成可理解的格式,然后写到proc文件系统中,由于proc文件系统在内存中,因此核心的实现就是把读取的数据用memcpy()直接拷贝到内存的proc区域中。实现的函数有:
  (1)smbios_find_entry_point(void* base):从base开始搜索SMBIOS EPS表,只要搜索到固定字符串"_SM_"即可;
  (2)dmibios_find_entry_point(void* base):从base开始搜索DMI EPS表,只要搜索到固定字符串"_DMI"即可;
  (3)smbios_check_entry_point (void *addr):校验EPS表格;
  (4)smbios_get_struct_length():返回指定类型的SMBIOS结构长度;
  (5)bios_read_raw_proc():当应用程序需要打开本驱动程序创建的proc文件中,本函数就会被内核调用,以获取proc文件中的原始数据;
  (6)dmibios_read_raw_proc():跟前一个类似。
  (7)bios_read_cooked_proc():跟前面类似,只 不过读取的是友好格式的数据;
  (8)smbios_make_version_entry():在proc文件系统中创建smbios_version文件;
  (9)smbios_make_dir_entries():在proc文件系统中创建多个文件,每个类型的SMBIOS结构对应一个文件type[-subtype].instance;
  (10)smbios_destroy_dir_entries():删除指定proc目录下的所有SMBIOS文件;
  (11)smbios_get_readable_name():把SMBIOS类型转换成可理解的格式;
  (12)make_file_entries():在指定的proc目录中创建一个文件。
  4、main.c文件: 包含本驱动模块的框架性函数。

/** /文件 main.c * smbios内核模块的内核接口函数 */ #ifndef __KERNEL__ # define __KERNEL__ #endif #ifndef MODULE # define MODULE #endif #define __NO_VERSION__ /* 在module.h中不要定义kernel_version */ #include <linux/module.h> #include <linux/version.h> char kernel_version[] = UTS_RELEASE; #include <linux/kernel.h> /* printk() */ #include <linux/errno.h> /* 错误码 */ #include <linux/types.h> /* size_t */ #include <linux/proc_fs.h> #include <asm/io.h> /* ioremap() */ #include "strgdef.h" /* 所有解析成的字符串定义 */ #include "bios.h" /* 本地定义 */ EXPORT_NO_SYMBOLS; /** /fn 函数int init_module (void) * /brief 模块初始化,成功返回0,否则返回一个错误码 */ int init_module (void) { int err = 0; PDEBUG ("starting module initialization/n"); /* * 映射SMBIOS存储段:ioremap把一个物理地址映射到虚拟地址,例如把BIOS起始地址映射成Bios F-段, * 即返回的起始虚拟地址smbios_base */ if (!(smbios_base = ioremap (BIOS_START_ADDRESS, BIOS_MAP_LENGTH))) { PDEBUG ("ioremap() for entry point failed/n"); err = -ENXIO; goto ioremap_for_entry_point_failed; } PDEBUG ("BIOS base set to 0x%p/n", smbios_base); /* * 搜索SMBIOS或DMIBIOS入口点(EPS表起始地址) */ if (!(smbios_entry_point = smbios_find_entry_point (smbios_base))) { PDEBUG ("SM-BIOS entry point not found/n"); if (!(dmibios_entry_point = dmibios_find_entry_point (smbios_base))) { PDEBUG ("DMI-BIOS entry point not found. Aborting.../n"); err = -ENXIO; goto find_entry_point_failed; } } /* * 对SM-BIOS:检查指向DMI结构的指针是否存在。中间字符串_DMI_不是以'/0'结尾,因此strncmp()传入大小为sizeof(DMI_STRING)-1 */ if (smbios_entry_point) { if (strncmp((char *) &(smbios_entry_point->intermediate_string), DMI_STRING, sizeof (DMI_STRING) - 1)) { PDEBUG ("Pointer to DMI structures not found!/n"); err = -ENXIO; goto check_dmi_failed; } } /* * 映射SMBIOS结构的物理地址段,返回的smbios_structures_base包含了SMBIOS结构数据起始地址 */ if (smbios_entry_point) { if (!(smbios_structures_base = ioremap (smbios_entry_point->struct_table_address, (unsigned long) smbios_entry_point->struct_table_length))) { PDEBUG ("ioremap() for structures table failed/n"); err = -ENXIO; goto ioremap_for_structures_table_failed; } } /* * 另一方面,如果我们有专有的DMI Bios,则smbios_structures_base会包含指向表格的指针,这个表格包含了到每个sm/dmi bios结构的偏移 */ if (dmibios_entry_point) { if (!(smbios_structures_base = dmibios_entry_point->entry)) { PDEBUG ("invalid structure table entry%p,%p/n", smbios_structures_base, dmibios_entry_point->entry); err = -ENXIO; goto ioremap_for_structures_table_failed; } } PDEBUG ("DMI structures base set to 0x%p/n", smbios_structures_base); /* * 创建/proc节点 */ /* 创建/proc/smbios目录 */ if (!(smbios_proc_dir = create_proc_entry (PROC_DIR_STRING, S_IFDIR, &proc_root))) { err = -ENOMEM; PDEBUG ("failed to create /proc/smbios directory entry/n"); goto create_smbios_dir_failed; } PDEBUG ("/proc/smbios directory created./n"); /* 创建/proc/smbios/raw目录 */ if (!(smbios_raw_proc_dir = create_proc_entry (PROC_DIR_STRING_RAW, S_IFDIR, smbios_proc_dir))) { err = -ENOMEM; PDEBUG ("failed to create /proc/smbios/raw directory entry/n"); goto create_smbios_raw_dir_failed; } PDEBUG ("/proc/smbios/raw directory created./n"); /* 创建/proc/smbios/cooked目录 */ if (!(smbios_cooked_proc_dir = create_proc_entry (PROC_DIR_STRING_COOKED, S_IFDIR, smbios_proc_dir))) { err = -ENOMEM; PDEBUG ("failed to create /proc/smbios/cooked directory entry/n"); goto create_smbios_cooked_dir_failed; } PDEBUG ("/proc/smbios/cooked directory created./n"); /* 创建版本文件 */ if (smbios_entry_point) { if ((err = smbios_make_version_entry (smbios_proc_dir))) goto smbios_make_version_entry_failed; } /* 创建各个SMBIOS结构对应的文件 */ if (smbios_entry_point) { if ((err = smbios_make_dir_entries (smbios_proc_dir, smbios_raw_proc_dir, smbios_cooked_proc_dir))) goto make_smbios_dir_entries_failed; } /* 创建各个DMI BIOS结构对应的文件 */ if (dmibios_entry_point) { if ((err = dmibios_make_dir_entries (smbios_proc_dir, smbios_raw_proc_dir, smbios_cooked_proc_dir))) goto make_smbios_dir_entries_failed; } PDEBUG ("module loaded succesfully/n"); return 0; /* * 发生错误时就会到达这里,需要对发生错误之前做的操作进行回滚处理(如申请了资源,则需要进行释放) */ make_smbios_dir_entries_failed: /* remove /proc/smbios/cooked files */ smbios_destroy_dir_entries (smbios_cooked_proc_dir); /* remove /proc/smbios/raw files */ smbios_destroy_dir_entries (smbios_raw_proc_dir); /* remove /proc/smbios files */ smbios_destroy_dir_entries (smbios_proc_dir); smbios_make_version_entry_failed: /* remove /proc/smbios/cooked directory */ remove_proc_entry(PROC_DIR_STRING_COOKED, smbios_proc_dir); create_smbios_cooked_dir_failed : /* remove /proc/smbios/raw directory */ remove_proc_entry(PROC_DIR_STRING_RAW, smbios_proc_dir); create_smbios_raw_dir_failed: /* remove /proc/smbios directory */ remove_proc_entry(PROC_DIR_STRING, &proc_root); create_smbios_dir_failed: /* unmap the virtual to physical memory binding */ if (smbios_entry_point) iounmap (smbios_structures_base); ioremap_for_structures_table_failed: check_dmi_failed: find_entry_point_failed: /* unmap the virtual to physical memory binding */ iounmap (smbios_base); ioremap_for_entry_point_failed: return err; } /** /fn 函数int cleanup_module (void) * /brief 模块清理 */ void cleanup_module (void) { /* 删除/proc/smbios/cooked下的各个文件 */ smbios_destroy_dir_entries (smbios_cooked_proc_dir); /* 删除/proc/smbios/raw下的各个文件 */ smbios_destroy_dir_entries (smbios_raw_proc_dir); /* 删除/proc/smbios下的文件 */ smbios_destroy_dir_entries (smbios_proc_dir); /* 删除/proc/smbios/cooked目录 */ remove_proc_entry(PROC_DIR_STRING_COOKED, smbios_proc_dir); /* 删除/proc/smbios/raw目录 */ remove_proc_entry(PROC_DIR_STRING_RAW, smbios_proc_dir); /* 删除/proc/smbios目录 */ remove_proc_entry(PROC_DIR_STRING, &proc_root); /* 取消虚拟地址到物理地址的映射 */ if (smbios_entry_point) iounmap (smbios_structures_base); iounmap (smbios_base); PDEBUG ("module unloaded/n"); }
  (1)init_module():映射SMBIOS存储段地址(映射到虚拟地址上)、搜索SMBIOS或DMI的EPS表、映射SMBIOS结构的物理地址、创建目录结点/proc/smbios/raw和/proc/smbios/cooked、在其中创建各个类型的SMBIOS文件。
  (2)cleanup_module():删除proc中的各个SMBIOS文件、删除相应目录、解除地址映射。

你可能感兴趣的:(struct,String,Module,ext,table,编译器)