The dmifs library is written to simplify working with dmi sysfs introduced lastly. This is a short library and can be extended by needs. For now it can be used by dmidecode util to extend it's functionality on dmi-sysfs. It's also supports new smbios_raw entry that holds smbios entry structure raw data and can be used to get information like version, size, etc.
The dmi structure entries can be accessed by iterating dmi_list or just using pointer on contiguous dmi table which can be taken with dmi_get_raw_data().
To get access to dmifs list it's required to get dmifs table first. To get dmifs table use dmi_get_table(), if table is no more needed put it by dmi_put_table();
Reported-by: Leif Lindholm leif.lindholm@linaro.org Signed-off-by: Ivan Khoronzhuk ivan.khoronzhuk@linaro.org --- .gitignore | 5 + Makefile | 23 +++ libdmifs.c | 637 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ libdmifs.h | 56 ++++++ 4 files changed, 721 insertions(+) create mode 100644 .gitignore create mode 100644 Makefile create mode 100644 libdmifs.c create mode 100644 libdmifs.h
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6c08ae0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*.o +libdmifs.a +libdmifs.so +tags +cscope* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..fe5005b --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +CC=$(CROSS_COMPILE)gcc + +all: libdmifs.so libdmifs.a + +libdmifs.so: libdmifs.o + $(CC) -shared -o libdmifs.so $^ + +libdmifs.a: libdmifs_a.o + ar rv $@ $^ + +libdmifs_a.o: libdmifs.c libdmifs.h + $(CC) -c libdmifs.c -o libdmifs_a.o + +libdmifs.o: libdmifs.c libdmifs.h + $(CC) -fPIC -c libdmifs.c + +clean: + rm -f libdmifs.o libdmifs_a.o libdmifs.so libdmifs.a + +distclean: clean + rm -f cscope.* tags + +.PHONY: all clean distclean diff --git a/libdmifs.c b/libdmifs.c new file mode 100644 index 0000000..b2b990f --- /dev/null +++ b/libdmifs.c @@ -0,0 +1,637 @@ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <dirent.h> +#include <unistd.h> +#include <errno.h> +#include <endian.h> +#include "libdmifs.h" + +/* + * maximum number of entries can be calculated based on maximum table + * size and minimum size of structure (4 + 2 NULL symbols). + */ +#define MIN_HEADER_SIZE 4 +#define MIN_DMI_STRUCT_SIZE (MIN_HEADER_SIZE + 2) +#define MAX_ENTRIES_COUNT (~0U/MIN_DMI_STRUCT_SIZE) +#define POSITION_ERR (MAX_ENTRIES_COUNT + 1) + +/* list of dmi entries */ + +/** + * dmilist_insert - inserts new dmi entry to ordered dmi_list, + * sorting according to the position in dmi table + * @dmi_list: pointer on pointer to the first dmi entry in the list + * @new: new entry to be inserted in the ordered dmi_list. + * + * Returns 0 on success and -1 when error. + */ +static int dmilist_insert(struct dmi_entry **dmi_list, struct dmi_entry *new) +{ + struct dmi_entry *old; + struct dmi_entry *cur = *dmi_list; + + if (!cur) { + new->next = NULL; + *dmi_list = new; + return 0; + } + + old = NULL; + while (cur) { + if (cur->position < new->position) { + old = cur; + cur = cur->next; + continue; + } else if (cur->position > new->position) { + if (old) { + old->next = new; + new->next = cur; + return 0; + } + new->next = cur; + *dmi_list = new; + return 0; + } + + fprintf(stderr, "dmitable is broken"); + return -1; + } + + old->next = new; + new->next = NULL; + + return 0; +} + +static void dmilist_free_entry(struct dmi_entry *entry) +{ + free(entry->d_name); + free(entry); +} + +static void dmilist_free(struct dmi_entry **dmi_list) +{ + struct dmi_entry *old; + struct dmi_entry *cur = *dmi_list; + + if (!cur) + return; + + while (cur) { + old = cur; + cur = cur->next; + dmilist_free_entry(old); + } + + *dmi_list = NULL; +} + +/* dmi sysfs attribute reading */ + +/** + * dmi_get_smbios - get smbios data + * @smbios: pointer on array to read raw smbios table in + * + * Returns read data size in bytes on success and 0 when error. + */ +static int dmi_get_smbios(unsigned char *smbios) +{ + FILE *file; + int count = 0; + enum {SMBIOS_SIZE = 32}; + + file = fopen("/sys/firmware/dmi/smbios_raw", "rb"); + if (!file) { + fprintf(stderr, "no "smbios" sysfs entry\n"); + return count; + } + + count = fread(smbios, sizeof(char), SMBIOS_SIZE, file); + if (!feof(file)) { + fprintf(stderr, "Error while reading a file\n"); + goto err; + } + +err: + fclose(file); + return count; +} + +/** + * read_position - reads position of dmi entry as it's inside dmi table + * @dir: appropriate directory of dmi entry position + * + * returns dmi entry position in dmi table on success and POSITION_ERR when + * error occurred. + */ +static unsigned int read_position(char *dir) +{ + FILE *file; + char pos[10]; + unsigned int position; + + file = fopen("position", "r"); + if (!file) { + fprintf(stderr, "no "position" in "%s"\n", dir); + return POSITION_ERR; + } + + if (!fgets(pos, 10, file) && ferror(file)) { + fclose(file); + fprintf(stderr, "Error while working with "position" in %s\n", + dir); + return POSITION_ERR; + } + + fclose(file); + + position = strtoul(pos, NULL, 0); + /* position can't be more than number of entries */ + if (position > MAX_ENTRIES_COUNT) { + fprintf(stderr, "position is incorrect %d\n", position); + return POSITION_ERR; + } + + return position; +} + +/** + * read_raw - reads raw data of dmi entry + * @dir: appropriate directory of dmi entry position + * @data: pointer to the array where raw data will be read + * @max_size: maximum data size possible to read + * + * Returns read data size in bytes on success and 0 when error. + * In the same time return value < 6 is also error as at least header is + * 4 bytes in size + 2 NULLs. + */ +static unsigned int read_raw(char *dir, + unsigned char *buf, unsigned int max_size) +{ + FILE *file; + unsigned int count; + unsigned int size = 0; + + file = fopen("raw", "rb"); + if (!file) { + fprintf(stderr, "no "raw" in "%s"\n", dir); + return 0; + } + + count = fread(buf, sizeof(char), max_size, file); + if (!feof(file)) { + fprintf(stderr, "Error while reading "raw" file\n"); + goto err; + } + + if (count < MIN_DMI_STRUCT_SIZE) { + fprintf(stderr, "DMI header cannot be less than 4 bytes"); + goto err; + } + + /* check if structure is correct */ + if (buf[count - 1] || buf[count - 2]) { + fprintf(stderr, "Bad raw file of "%s"\n", dir); + goto err; + } + + size = count; +err: + fclose(file); + return size; +} + +/** + * add_entry_to_dmilist - generates empty dmi entry and places it to dmi list + * @dmi_list: dmi list where allocated dmi entry will be put + * @dir: the directory name appropriate to dmi entry + * + * Returns 0 on success and -1 on error + */ +static int add_entry_to_dmilist(struct dmi_entry **dmi_list, char *dir) +{ + char *dir_name; + unsigned int position; + struct dmi_entry *entry; + + position = read_position(dir); + if (position == POSITION_ERR) { + fprintf(stderr, + "Cannot read smbios position for "%s"\n", dir); + return -1; + } + + dir_name = malloc(strlen(dir) + 1); + if (!dir_name) { + fprintf(stderr, "Cannot allocate memory for dir\n"); + return -1; + } + strcpy(dir_name, dir); + + entry = malloc(sizeof(struct dmi_entry)); + if (!entry) { + fprintf(stderr, "Cannot allocate memory for dmi struct\n"); + free(dir_name); + return -1; + } + + entry->d_name = dir_name; + entry->position = position; + + if (dmilist_insert(dmi_list, entry)) { + fprintf(stderr, "Cannot insert new dmientry in the list"); + dmilist_free_entry(entry); + return -1; + } + + return 0; +} + +/** + * gather_entry_data - reads data from given dmi directory raw attribute end + * binds it with given dmi entry. + * @entry: dmi entry for what data will be read + * @rdata: pointer to array where data will be read in + * @max_size: maximum data size possible to read for now + * + * Returns read raw data size including two NULLs at the end + * On error returns 0 + */ +static unsigned int gather_entry_data(struct dmi_entry *entry, + unsigned char *rdata, + unsigned int max_size) +{ + unsigned int dsize; + + dsize = read_raw(entry->d_name, rdata, max_size); + if (dsize < MIN_DMI_STRUCT_SIZE) { + fprintf(stderr, + "Cannot read DMI raw for "%s"\n", entry->d_name); + return 0; + } + + entry->h.data = rdata; + entry->h.type = rdata[0]; + entry->h.length = rdata[1]; + entry->h.handle = le16toh(rdata[2] | (rdata[3] << 8)); + + return dsize; +} + +/** + * populate_dmilist - populate dmi entries in dmi list by raw data + * @dmi_list: dmi list with dmi entries to be populated + * @max_size: supposed size of whole dmi table. Can be taken from + * table SMBIOS entry. + * + * Returns dmi raw table size on success, otherwise 0. + */ +static unsigned int populate_dmilist(struct dmi_entry *dmi_list, + unsigned int max_size) +{ + unsigned int dsize; + struct dmi_entry *entry; + unsigned char *raw_table, *tp; + + /* allocate memory for whole dmi table */ + raw_table = malloc(max_size); + if (!raw_table) { + fprintf(stderr, "Cannot allocate memory for DMI table\n"); + return 0; + } + + tp = raw_table; + for (entry = dmi_list; entry; entry = entry->next) { + if (max_size < MIN_DMI_STRUCT_SIZE) { + fprintf(stderr, "Max size of DMI table is reached\n"); + goto err; + } + + if (chdir(entry->d_name)) { + fprintf(stderr, "Cannot change dir to %s\n", + entry->d_name); + goto err; + } + + dsize = gather_entry_data(entry, tp, max_size); + if (dsize < MIN_DMI_STRUCT_SIZE) { + if (chdir("../")) + fprintf(stderr, "Cannot change dir to ../\n"); + goto err; + } + + if (chdir("../")) { + fprintf(stderr, "Cannot change dir to ../\n"); + goto err; + } + + tp += dsize; + max_size -= dsize; + } + + return tp - raw_table; + +err: + fprintf(stderr, "Cannot gather data for dir %s\n", entry->d_name); + free(raw_table); + return 0; +} + +/** + * create_dmilist - creates ordered linked dmi list with empty entries. + * The empty entry is entry with empty dmi header data + * @entry_count: pointer to dmi entry count to be updated while creating. + * + * On success returns dmi_entry pointer on first entry in the list + * Otherwise returns NULL. Updates entry_count. + */ +static struct dmi_entry *create_dmilist(unsigned int *entry_count) +{ + DIR *dmi; + char *cwd; + void *ret = NULL; + struct dirent *dir_entry; + struct dmi_entry *dmi_list = NULL; + + dmi = opendir("."); + if (!dmi) { + fprintf(stderr, "Cannot open cwd\n"); + return ret; + } + + *entry_count = 0; + readdir(dmi); /* miss "." */ + readdir(dmi); /* miss ".." */ + while ((dir_entry = readdir(dmi))) { + if (chdir(dir_entry->d_name)) { + fprintf(stderr, "Cannot change dir to %s\n", + dir_entry->d_name); + goto err; + } + + if (add_entry_to_dmilist(&dmi_list, dir_entry->d_name)) { + fprintf(stderr, "Cannot add "%s" to dmi_list\n", + dir_entry->d_name); + if (chdir("../")) + fprintf(stderr, "Cannot change dir to ../\n"); + goto err; + } + + if (chdir("../")) { + fprintf(stderr, "Cannot change dir to ../\n"); + goto err; + } + + (*entry_count)++; + } + /* now dmilist is complete */ + ret = dmi_list; +err: + if (closedir(dmi)) { + cwd = getcwd(NULL, 1024); + fprintf(stderr, "Cannot close %s", cwd); + free(cwd); + ret = NULL; + } + + if (ret == dmi_list) + return dmi_list; + + dmilist_free(&dmi_list); + return ret; +} + +/* API */ + +/** + * dmi_put_dmilist - frees dmi list alloted by dmi_get_dmilist() + * @dmi_list: dmi list to be free + * + * Returns 0 on success, -1 otherwise. + */ +static int dmi_put_dmilist(struct dmi_entry *dmi_list) +{ + unsigned char *raw_table; + + if (!dmi_list) { + fprintf(stderr, "Cannot free dmi_list NULL pointer\n"); + return -1; + } + + raw_table = dmi_list->h.data; + if (!raw_table) { + fprintf(stderr, "Cannot free raw_table NULL pointer\n"); + return -1; + } + + free(raw_table); + dmilist_free(&dmi_list); + + return 0; +} + +/** + * dmi_get_dmilist - creates ordered dmi list with all dmi entries present + * on dmi sysfs. All raw entries data are situated contiguously in the RAM. + * @entry_count: read count of dmi list entries, that's dmi structs in dmi table + * @dmi_size: read whole size in bytes of dmi table. + * @max_size: supposed size of whole dmi table. Can be taken from SMBIOS + * entry point table. + * + * Returns a pointer to first dmi entry in the list. When list is no longer + * needed it has to be freed with dmi_put_dmilist(). + * In case of error returns NULL. + */ +static struct dmi_entry *dmi_get_dmilist(unsigned int *entry_count, + unsigned int *dmi_size, + unsigned int max_size) +{ + char *cwd; + struct dmi_entry *dmi_list = NULL; + char root[] = "/sys/firmware/dmi/entries/"; + + cwd = getcwd(NULL, 1024); + if (!cwd) { + fprintf(stderr, "Cannot get current directory\n"); + return NULL; + } + + if (chdir(root)) { + fprintf(stderr, "Cannot change dir to %s\n", root); + goto err; + } + + /* + * Create dmi_list then fill it in. Do this in two steps to + * have contiguous raw data for whole table + */ + dmi_list = create_dmilist(entry_count); + if (!dmi_list) { + fprintf(stderr, "Cannot create dmi_list\n"); + goto err; + } + + *dmi_size = populate_dmilist(dmi_list, max_size); + if (*dmi_size == 0) { + dmilist_free(&dmi_list); + fprintf(stderr, "Cannot fill in dmi_list by data from raw\n"); + goto err; + } + +err: + if (chdir(cwd)) { + fprintf(stderr, "Cannot change root dir to %s\n", cwd); + dmi_put_dmilist(dmi_list); + dmi_list = NULL; + } + + free(cwd); + return dmi_list; +} + +static int dmi_checksum(unsigned char *buf, unsigned char len) +{ + int i; + unsigned char sum = 0; + + for (i = 0; i < len; i++) + sum += buf[i]; + + return sum == 0; +} + +/** + * dmi_get_expected_size - read dmi table size from SMBIOS entry point table. + * Verify SMBIOS entry point table checksum. + * + * Returns dmi table size read from SMBIOS entry point table. Return 0 on error. + */ +static unsigned int dmi_get_expected_size(unsigned char *smbios) +{ + if (!memcmp(smbios, "_SM3_", 5) && + dmi_checksum(smbios, smbios[0x06])) + return le32toh(smbios[0x0C] | (smbios[0x0D] << 8) | + (smbios[0x0E] << 16) | (smbios[0x0F] << 24)); + else if (!memcmp(smbios, "_SM_", 4) && + dmi_checksum(smbios, smbios[0x05]) && + dmi_checksum(smbios + 0x10, 0x0F)) + return le16toh(smbios[0x16] | (smbios[0x17] << 8)); + else if (!memcmp(smbios, "_DMI_", 5) && + dmi_checksum(smbios, 0x0F)) + return le16toh(smbios[0x06] | (smbios[0x07] << 8)); + + return 0; +} + +/** + * dmi_get_table - allocate dmi table + * + * Returns pointer on allocated dmi_table struct on success, + * otherwise returns NULL. When table is not needed it has to be + * put by dmi_put_table. + */ +struct dmi_table *dmi_get_table(void) +{ + struct dmi_table *dt; + unsigned int max_dmisize; + + dt = malloc(sizeof(struct dmi_table)); + + if (!dmi_get_smbios(dt->smbios)) + goto err; + + max_dmisize = dmi_get_expected_size(dt->smbios); + if (max_dmisize < MIN_DMI_STRUCT_SIZE) { + fprintf(stderr, "SMBIOS entry point is incorrect\n"); + goto err; + } + + dt->dmi_list = dmi_get_dmilist(&dt->dmi_count, + &dt->dmi_size, max_dmisize + 1); + if (!dt->dmi_list) + goto err; + + return dt; + +err: + free(dt); + return NULL; +} + +/** + * dmi_put_table - used in pair with dmi_get_dmitable + * @dt: dmi table pointer taken by dmi_get_table + */ +int dmi_put_table(struct dmi_table *dt) +{ + int ret = -1; + + if (!dt) { + fprintf(stderr, "pointer for DMI table is incorrect\n"); + return ret; + } + + ret = dmi_put_dmilist(dt->dmi_list); + free(dt); + + return ret; +} + +/** + * dmi_get_raw_data - give a pointer to raw dmi table, all raw dmi entries are + * contiguously situated within this memory in original right order. + * @dmi_list: dmi list only taken by dmi_get_dmilist + */ +void *dmi_get_raw_data(struct dmi_entry *dmi_list) +{ + return dmi_list->h.data; +} + +/** + * dmi_get_entry_by_handle - searches an entry in dmi list by given handle + * @dmi_list: dmi list to search for + * @handle: handle to find entry with + * + * Returns entry with given handle on success and NULL otherwise + */ +struct dmi_entry *dmi_get_entry_by_handle(struct dmi_entry *dmi_list, + int handle) +{ + struct dmi_entry *entry; + + for (entry = dmi_list; entry; entry = entry->next) + if (handle == entry->h.handle) + return entry; + + return NULL; +} + +/** + * dmi_get_next_entry_by_type - searches a next entry in dmi list by type + * @dmi_entry: the dmi_entry in dmi list to search beginnig with + * @type: type of dmi entry to find + */ +struct dmi_entry *dmi_get_next_entry_by_type(struct dmi_entry *dmi_entry, + unsigned char type) +{ + struct dmi_entry *entry; + + for (entry = dmi_entry->next; entry; entry = entry->next) + if (type == entry->h.type) + return entry; + + return NULL; +} + +/** + * dmifs_is_exist - checks if dmi sysfs exists + * + * Returns 1 on success and 0 if no + */ +int dmifs_is_exist(void) +{ + DIR *dir = opendir("/sys/firmware/dmi/entries/"); + + return dir ? 1 : 0; +} diff --git a/libdmifs.h b/libdmifs.h new file mode 100644 index 0000000..218d07f --- /dev/null +++ b/libdmifs.h @@ -0,0 +1,56 @@ +#ifndef LIBDMIFS_H +#define LIBDMIFS_H + +/** + * dmi_hdata - information of dmi entry header + * type: type of dmi entry + * length: length of formated area of dmi entry + * data: pointer to whole raw dmi entry data + */ +struct dmi_hdata { + unsigned char type; + unsigned char length; + unsigned short handle; + unsigned char *data; +}; + +/** + * dmi_entry - holds information about dmi entry + * next: internal in list pointer + * h: dmi header data, including raw data + * position: position within the dmi table + * d_name: dirctory entry name in sysfs + */ +struct dmi_entry { + struct dmi_entry *next; + struct dmi_hdata h; + unsigned int position; + char *d_name; +}; + +/** + * dmi_table - contains whole information about smbios/dmi tables + * @smbios: pointer to raw data of smbios table + * @dmi_count: number of dmi entries in dmi table + * @dmi_size: length of dmi table in bytes + * @dmi_list: single list of dmi table entries, raw data of which contiguously + * situated in memnory. The list can be used to take a pointer to raw data + * of whole dmi table. + */ +struct dmi_table { + unsigned char smbios[32]; + unsigned int dmi_count; + unsigned int dmi_size; + struct dmi_entry *dmi_list; +}; + +int dmifs_is_exist(void); +struct dmi_table *dmi_get_table(void); +int dmi_put_table(struct dmi_table *dt); +void *dmi_get_raw_data(struct dmi_entry *dmi_list); +struct dmi_entry *dmi_get_entry_by_handle(struct dmi_entry *dmi_list, + int handle); +struct dmi_entry *dmi_get_next_entry_by_type(struct dmi_entry *dmi_entry, + unsigned char type); + +#endif