diff --git a/cmd/Kconfig b/cmd/Kconfig index ea1a325eb3..416e0ec953 100644 --- a/cmd/Kconfig +++ b/cmd/Kconfig @@ -1485,6 +1485,14 @@ config CMD_UUID The two commands are very similar except for the endianness of the output. +config CMD_FRU + bool "FRU information for product" + help + This option enables FRU commands to capture and display FRU + information present in the device. The FRU Information is used + to primarily to provide ‘inventory’ information about the boards + that the FRU Information Device is located on. + endmenu source "cmd/ti/Kconfig" diff --git a/cmd/Makefile b/cmd/Makefile index 15ae4d250f..b7e4138879 100644 --- a/cmd/Makefile +++ b/cmd/Makefile @@ -41,6 +41,7 @@ obj-$(CONFIG_DATAFLASH_MMC_SELECT) += dataflash_mmc_mux.o obj-$(CONFIG_CMD_DATE) += date.o obj-$(CONFIG_CMD_DEMO) += demo.o obj-$(CONFIG_CMD_DM) += dm.o +obj-$(CONFIG_CMD_FRU) += fru.o obj-$(CONFIG_CMD_SOUND) += sound.o ifdef CONFIG_POST obj-$(CONFIG_CMD_DIAG) += diag.o diff --git a/cmd/fru.c b/cmd/fru.c new file mode 100644 index 0000000000..10b6e6ff9a --- /dev/null +++ b/cmd/fru.c @@ -0,0 +1,73 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) Copyright 2019 Xilinx, Inc. + * + */ + +#include +#include +#include +#include + +static int do_fru_capture(cmd_tbl_t *cmdtp, int flag, int argc, + char *const argv[]) +{ + unsigned long addr; + char *endp; + int ret; + + if (argc < 3) + return CMD_RET_USAGE; + + addr = simple_strtoul(argv[2], &endp, 16); + if (*argv[1] == 0 || *endp != 0) + return -1; + + ret = fru_capture(addr); + + return ret; +} + +static int do_fru_display(cmd_tbl_t *cmdtp, int flag, int argc, + char *const argv[]) +{ + fru_display(); + return CMD_RET_SUCCESS; +} + +static cmd_tbl_t cmd_fru_sub[] = { + U_BOOT_CMD_MKENT(capture, 3, 0, do_fru_capture, "", ""), + U_BOOT_CMD_MKENT(display, 2, 0, do_fru_display, "", ""), +}; + +static int do_fru(cmd_tbl_t *cmdtp, int flag, int argc, + char *const argv[]) +{ + cmd_tbl_t *c; + + if (argc < 2) + return CMD_RET_USAGE; + + c = find_cmd_tbl(argv[1], &cmd_fru_sub[0], + ARRAY_SIZE(cmd_fru_sub)); + + if (c) + return c->cmd(c, flag, argc, argv); + + return CMD_RET_USAGE; +} + +/***************************************************/ +#ifdef CONFIG_SYS_LONGHELP +static char fru_help_text[] = + "capture - Parse and capture FRU table present at address.\n" + "display = Displays content of FRU table that was captured using\n" + " fru capture command\n" + ; +#endif + +U_BOOT_CMD( + fru, 3, 1, do_fru, + "FRU table info", + fru_help_text +) diff --git a/common/Makefile b/common/Makefile index 0de60b3ced..bffa974d65 100644 --- a/common/Makefile +++ b/common/Makefile @@ -57,6 +57,7 @@ obj-$(CONFIG_UPDATE_TFTP) += update.o obj-$(CONFIG_DFU_TFTP) += update.o obj-$(CONFIG_USB_KEYBOARD) += usb_kbd.o obj-$(CONFIG_CMDLINE) += cli_readline.o cli_simple.o +obj-$(CONFIG_CMD_FRU) += fru_ops.o endif # !CONFIG_SPL_BUILD diff --git a/common/fru_ops.c b/common/fru_ops.c new file mode 100644 index 0000000000..baa37a87eb --- /dev/null +++ b/common/fru_ops.c @@ -0,0 +1,286 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * (C) Copyright 2019 Xilinx, Inc. + * + */ + +#include +#include +#include +#include +#include +#include "linux/crc8.h" + +DECLARE_GLOBAL_DATA_PTR; + +struct fru_common_hdr { + u8 version; + u8 off_internal; + u8 off_chassis; + u8 off_board; + u8 off_product; + u8 off_multirec; + u8 pad; + u8 crc; +}; + +#define FRU_BOARD_MAX_LEN 32 + +struct fru_board_data { + u8 ver; + u8 len; + u8 lang_code; + u8 time[3]; + u8 manuf_type_len; + u8 manuf_name[FRU_BOARD_MAX_LEN]; + u8 prd_name_type_len; + u8 product_name[FRU_BOARD_MAX_LEN]; + u8 prd_part_type_len; + u8 product_part[FRU_BOARD_MAX_LEN]; + u8 prd_ver_type_len; + u8 product_ver[FRU_BOARD_MAX_LEN]; + u8 prd_serial_type_len; + u8 product_serial[FRU_BOARD_MAX_LEN]; + u8 asset_tag_type_len; + u8 asset_tag[FRU_BOARD_MAX_LEN]; +}; + +struct fru_table { + bool captured; + struct fru_common_hdr hdr; + struct fru_board_data brd; +}; + +#define FRU_TYPELEN_CODE_MASK 0xC0 +#define FRU_TYPELEN_LEN_MASK 0x3F +#define FRU_COMMON_HDR_VER_MASK 0xF +#define FRU_COMMON_HDR_LEN_MULTIPLIER 8 +#define FRU_LANG_CODE_ENGLISH 0 +#define FRU_LANG_CODE_ENGLISH_1 25 +#define FRU_TYPELEN_EOF 0xC1 + +#define FRU_BOARD_AREA_TOTAL_FIELDS 6 +#define FRU_TYPELEN_TYPE_SHIFT 6 +#define FRU_TYPELEN_TYPE_ASCII8 3 + +struct fru_table fru_data; + +static u16 fru_cal_area_len(u8 len) +{ + return len * FRU_COMMON_HDR_LEN_MULTIPLIER; +} + +static u8 fru_version(u8 ver) +{ + return ver & FRU_COMMON_HDR_VER_MASK; +} + +static int fru_check_language(u8 code) +{ + if (code != FRU_LANG_CODE_ENGLISH && code != FRU_LANG_CODE_ENGLISH_1) { + printf("FRU_ERROR: Only English Language is supported\n"); + return -EINVAL; + } + + return code; +} + +static int fru_check_type_len(u8 type_len, u8 language, u8 *type) +{ + int len; + + if (type_len == FRU_TYPELEN_EOF) + return -EINVAL; + + *type = (type_len & FRU_TYPELEN_CODE_MASK) >> FRU_TYPELEN_TYPE_SHIFT; + + len = type_len & FRU_TYPELEN_LEN_MASK; + + return len; +} + +static int fru_parse_board(unsigned long addr) +{ + u8 i, type; + int len; + u8 *data; + + memcpy(&fru_data.brd.ver, (void *)addr, 6); + addr += 6; + data = (u8 *)&fru_data.brd.manuf_type_len; + + for (i = 0; i < FRU_BOARD_AREA_TOTAL_FIELDS; i++, + data += FRU_BOARD_MAX_LEN) { + *data++ = *(u8 *)addr; + len = fru_check_type_len(*(u8 *)addr, fru_data.brd.lang_code, + &type); + /* + * Stop cature if it end of fields + */ + if (len == -EINVAL) + return 0; + + /* + * Dont capture data if type is not ASCII8 + */ + if (type != FRU_TYPELEN_TYPE_ASCII8) + return 0; + + addr += 1; + if (!len) + continue; + memcpy(data, (u8 *)addr, len); + addr += len; + } + + return 0; +} + +int fru_capture(unsigned long addr) +{ + struct fru_common_hdr *hdr; + u8 crc = 0; + u8 len; + u8 *temp = (u8 *)addr; + + hdr = (struct fru_common_hdr *)addr; + len = sizeof(struct fru_common_hdr); + + while(len--) { + crc += *temp; + temp++; + } + + if (crc) { + printf("%s Common header CRC error\n", __func__); + return -EINVAL; + } + + memcpy((void *)&fru_data.hdr, (void *)hdr, + sizeof(struct fru_common_hdr)); + + fru_data.captured = true; + + if (hdr->off_board) { + addr += fru_cal_area_len(hdr->off_board); + fru_parse_board(addr); + } + + return 0; +} + +static int fru_display_board(void) +{ + u32 time = 0; + u8 type; + int len; + u8 *data; + const char *typecode[] = { + "Binary/Unspecified", + "BCD plus", + "6-bit ASCII", + "8-bit ASCII", + "2-byte UNICODE" + }; + const char *boardinfo[] = { + "Manufacturer Name", + "Product Name", + "Product Model/Part No", + "Product Version", + "Product Serial No", + "Asset Tag" + }; + + printf("*****BOARD INFO*****\n"); + printf("Version:%d\n", fru_version(fru_data.brd.ver)); + printf("Board Area Length:%d\n", fru_cal_area_len(fru_data.brd.len)); + + if (fru_check_language(fru_data.brd.lang_code)) + return 0; + + time = fru_data.brd.time[2] << 16 | fru_data.brd.time[1] << 8 | + fru_data.brd.time[0]; + printf("Time in Minutes from 0:00hrs 1/1/96 %d\n", time); + + data = (u8 *)&fru_data.brd.manuf_type_len; + + for (u8 i = 0; i < FRU_BOARD_AREA_TOTAL_FIELDS; i++) { + len = fru_check_type_len(*data++, fru_data.brd.lang_code, + &type); + + if (type <= FRU_TYPELEN_TYPE_ASCII8 && + (fru_data.brd.lang_code == FRU_LANG_CODE_ENGLISH || + fru_data.brd.lang_code == FRU_LANG_CODE_ENGLISH_1)) + printf("Type code: %s\n", typecode[type]); + else + printf("Type code: %s\n", typecode[type + 1]); + if (len == -EINVAL) { + printf("**** EOF for Board Area ****\n"); + return 0; + } + if (type != FRU_TYPELEN_TYPE_ASCII8) { + printf("FRU_ERROR: Only ASCII8 type is supported\n"); + return 0; + } + if (!len) { + printf("%s not found\n", boardinfo[i]); + continue; + } + + printf("Length: %d\n", len); + printf("%s: %s\n", boardinfo[i], data); + data += FRU_BOARD_MAX_LEN; + } + + return 0; +} + +static void fru_display_common_hdr(void) +{ + struct fru_common_hdr *hdr = &fru_data.hdr; + + printf("*****COMMON HEADER*****\n"); + printf("Version:%d\n", fru_version(hdr->version)); + if (hdr->off_internal) + printf("Internal Use Area Offset:%d\n", + fru_cal_area_len(hdr->off_internal)); + else + printf("*** No Internal Area ***\n"); + + if (hdr->off_chassis) + printf("Chassis Info Area Offset:%d\n", + fru_cal_area_len(hdr->off_chassis)); + else + printf("*** No Chassis Info Area ***\n"); + + if (hdr->off_board) + printf("Board Area Offset:%d\n", + fru_cal_area_len(hdr->off_board)); + else + printf("*** No Board Area ***\n"); + + if (hdr->off_product) + printf("Product Info Area Offset:%d\n", + fru_cal_area_len(hdr->off_product)); + else + printf("*** No Product Info Area ***\n"); + + if (hdr->off_multirec) + printf("MultiRecord Area Offset:%d\n", + fru_cal_area_len(hdr->off_multirec)); + else + printf("*** No MultiRecord Area ***\n"); +} + +int fru_display(void) +{ + if (!fru_data.captured) { + printf("FRU data not available please run fru parse\n"); + return -EINVAL; + } + + fru_display_common_hdr(); + fru_display_board(); + + return 0; +} diff --git a/include/fru.h b/include/fru.h new file mode 100644 index 0000000000..92db8107e0 --- /dev/null +++ b/include/fru.h @@ -0,0 +1,14 @@ +/* SPDX-License-Identifier: GPL-2.0+ */ +/* + * (C) Copyright 2019 Xilinx, Inc. + * Siva Durga Prasad Paladugu + * + */ + +#ifndef __FRU_H +#define __FRU_H + +int fru_display(void); +int fru_capture(unsigned long addr); + +#endif /* FRU_H */