[U-Boot] [PATCH 1/3] cmd: fru: Add support for FRU commands

Jean-Jacques Hiblot jjhiblot at ti.com
Tue Oct 22 12:40:47 UTC 2019


On 14/10/2019 15:29, Michal Simek wrote:
> From: Siva Durga Prasad Paladugu <siva.durga.paladugu at xilinx.com>
>
> This patch adds support for fru commands "fru capture" and "fru display".
> The fru capture parses the FRU table present at an address and stores in a
> structure for later use. The fru display prints the content of captured
> structured in a readable format.
>
> As of now, it supports only common header and board area of FRU. Also, it
> supports only English language code and ASCII8 format.
>
> fru_data variable is placed to data section because fru parser can be
> called very early before bss is initialized. And also information needs to
> be shared that's why it is exported via header.
>
> Signed-off-by: Siva Durga Prasad Paladugu <siva.durga.paladugu at xilinx.com>
> Signed-off-by: Michal Simek <michal.simek at xilinx.com>
> ---
>
>   MAINTAINERS      |   3 +
>   cmd/Kconfig      |   8 ++
>   cmd/Makefile     |   1 +
>   cmd/fru.c        |  68 +++++++++++++
>   common/Makefile  |   1 +
>   common/fru_ops.c | 247 +++++++++++++++++++++++++++++++++++++++++++++++
>   include/fru.h    |  64 ++++++++++++
>   7 files changed, 392 insertions(+)
>   create mode 100644 cmd/fru.c
>   create mode 100644 common/fru_ops.c
>   create mode 100644 include/fru.h
>
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 2ef29768555c..420ce26cd0aa 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -404,6 +404,9 @@ M:	Michal Simek <michal.simek at xilinx.com>
>   S:	Maintained
>   T:	git https://gitlab.denx.de/u-boot/custodians/u-boot-microblaze.git
>   F:	arch/arm/mach-versal/
> +F:	cmd/fru.c
> +F:	common/fru_ops.c
> +F:	include/fru.h
>   N:	(?<!uni)versal
>   
>   ARM VERSATILE EXPRESS DRIVERS
> diff --git a/cmd/Kconfig b/cmd/Kconfig
> index 07060c63a7e6..68815e42641e 100644
> --- a/cmd/Kconfig
> +++ b/cmd/Kconfig
> @@ -1688,6 +1688,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 ac843b4b16ad..f790217dd628 100644
> --- a/cmd/Makefile
> +++ b/cmd/Makefile
> @@ -43,6 +43,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 000000000000..d5e96cfe8984
> --- /dev/null
> +++ b/cmd/fru.c
> @@ -0,0 +1,68 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * (C) Copyright 2019 Xilinx, Inc.
> + */
> +
> +#include <common.h>
> +#include <fdtdec.h>
> +#include <malloc.h>
> +#include <fru.h>
> +
> +static int do_fru_capture(cmd_tbl_t *cmdtp, int flag, int argc,
> +			  char *const argv[])
> +{
> +	unsigned long addr;
> +	char *endp;
> +
> +	if (argc < cmdtp->maxargs)
> +		return CMD_RET_USAGE;
> +
> +	addr = simple_strtoul(argv[2], &endp, 16);
> +	if (*argv[1] == 0 || *endp != 0)
> +		return -1;
> +
> +	return fru_capture(addr);
> +}
> +
> +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, "", ""),
> +};

Why not do the capture in "fru display" itself ? And maybe provide the 
ability to display a FRU read from an EEPROM?

> +
> +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 <addr> - Parse and capture FRU table present at address.\n"
> +	"fru 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 302d8beaf356..10cbe9a15ea1 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 000000000000..dd54bd9c0214
> --- /dev/null
> +++ b/common/fru_ops.c
> @@ -0,0 +1,247 @@
> +// SPDX-License-Identifier: GPL-2.0
> +/*
> + * (C) Copyright 2019 Xilinx, Inc.
> + */
> +
> +#include <common.h>
> +#include <fdtdec.h>
> +#include <fru.h>
> +#include <malloc.h>
> +#include <asm/io.h>
> +#include <asm/arch/hardware.h>
> +
> +struct fru_table fru_data  __attribute__((section(".data")));

Isn't it restrictive to have only a single instance of struct fru_table 
? Could there be multiple ?

JJ

> +
> +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 u8 fru_checksum(u8 *addr, u8 len)
> +{
> +	u8 checksum = 0;
> +
> +	while (len--) {
> +		checksum += *addr;
> +		addr++;
> +	}
> +
> +	return checksum;
> +}
> +
> +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, *term;
> +
> +	memcpy(&fru_data.brd.ver, (void *)addr, 6);
> +	addr += 6;
> +	data = (u8 *)&fru_data.brd.manufacturer_type_len;
> +
> +	for (i = 0; ; 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)
> +			break;
> +
> +		/*
> +		 * 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);
> +		term = data + (u8)len;
> +		*term = 0;
> +		addr += len;
> +	}
> +
> +	if (i < FRU_BOARD_AREA_TOTAL_FIELDS) {
> +		printf("Board area require minimum %d fields\n",
> +		       FRU_BOARD_AREA_TOTAL_FIELDS);
> +		return -EINVAL;
> +	}
> +
> +	return 0;
> +}
> +
> +int fru_capture(unsigned long addr)
> +{
> +	struct fru_common_hdr *hdr;
> +	u8 checksum = 0;
> +
> +	checksum = fru_checksum((u8 *)addr, sizeof(struct fru_common_hdr));
> +	if (checksum) {
> +		printf("%s Common header CRC error\n", __func__);
> +		return -EINVAL;
> +	}
> +
> +	hdr = (struct fru_common_hdr *)addr;
> +
> +	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);
> +	}
> +
> +	env_set_hex("fru_addr", 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",
> +		"Serial No",
> +		"Part Number",
> +		"File ID"
> +	};
> +
> +	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.manufacturer_type_len;
> +
> +	for (u8 i = 0; ; i++) {
> +		len = fru_check_type_len(*data++, fru_data.brd.lang_code,
> +					 &type);
> +		if (len == -EINVAL) {
> +			printf("**** EOF for Board Area ****\n");
> +			break;
> +		}
> +
> +		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 (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 000000000000..38fdfad409c4
> --- /dev/null
> +++ b/include/fru.h
> @@ -0,0 +1,64 @@
> +/* SPDX-License-Identifier: GPL-2.0+ */
> +/*
> + * (C) Copyright 2019 Xilinx, Inc.
> + * Siva Durga Prasad Paladugu <siva.durga.paladugu at xilinx.com>
> + */
> +
> +#ifndef __FRU_H
> +#define __FRU_H
> +
> +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 manufacturer_type_len;
> +	u8 manufacturer_name[FRU_BOARD_MAX_LEN];
> +	u8 product_name_type_len;
> +	u8 product_name[FRU_BOARD_MAX_LEN];
> +	u8 serial_number_type_len;
> +	u8 serial_number[FRU_BOARD_MAX_LEN];
> +	u8 part_number_type_len;
> +	u8 part_number[FRU_BOARD_MAX_LEN];
> +	u8 file_id_type_len;
> +	u8 file_id[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
> +
> +/* This should be minimum of fields */
> +#define FRU_BOARD_AREA_TOTAL_FIELDS	5
> +#define FRU_TYPELEN_TYPE_SHIFT		6
> +#define FRU_TYPELEN_TYPE_ASCII8		3
> +
> +int fru_display(void);
> +int fru_capture(unsigned long addr);
> +
> +extern struct fru_table fru_data;
> +
> +#endif /* FRU_H */


More information about the U-Boot mailing list