cmd: fru: Add support for FRU commands
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, its supports only English language code and ASCII8 format. Signed-off-by: Siva Durga Prasad Paladugu <siva.durga.paladugu@xilinx.com> Signed-off-by: Michal Simek <michal.simek@xilinx.com>
This commit is contained in:
committed by
Michal Simek
parent
f607c476e7
commit
df532608ed
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
73
cmd/fru.c
Normal file
73
cmd/fru.c
Normal file
@@ -0,0 +1,73 @@
|
||||
// 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;
|
||||
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 <addr>- 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
|
||||
)
|
||||
@@ -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
|
||||
|
||||
|
||||
286
common/fru_ops.c
Normal file
286
common/fru_ops.c
Normal file
@@ -0,0 +1,286 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* (C) Copyright 2019 Xilinx, Inc.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <common.h>
|
||||
#include <fdtdec.h>
|
||||
#include <malloc.h>
|
||||
#include <asm/io.h>
|
||||
#include <asm/arch/hardware.h>
|
||||
#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;
|
||||
}
|
||||
14
include/fru.h
Normal file
14
include/fru.h
Normal file
@@ -0,0 +1,14 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0+ */
|
||||
/*
|
||||
* (C) Copyright 2019 Xilinx, Inc.
|
||||
* Siva Durga Prasad Paladugu <siva.durga.paladugu@xilinx.com>
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __FRU_H
|
||||
#define __FRU_H
|
||||
|
||||
int fru_display(void);
|
||||
int fru_capture(unsigned long addr);
|
||||
|
||||
#endif /* FRU_H */
|
||||
Reference in New Issue
Block a user