1// SPDX-License-Identifier: GPL-2.0
2/*
3 * (C) Copyright 2019 - 2022, Xilinx, Inc.
4 * (C) Copyright 2022 - 2023, Advanced Micro Devices, Inc.
5 */
6
7#include <common.h>
8#include <cpu_func.h>
9#include <env.h>
10#include <fdtdec.h>
11#include <log.h>
12#include <malloc.h>
13#include <net.h>
14#include <asm/io.h>
15#include <asm/arch/hardware.h>
16
17#include "fru.h"
18
19struct fru_table fru_data __section(".data");
20
21static u16 fru_cal_area_len(u8 len)
22{
23	return len * FRU_COMMON_HDR_LEN_MULTIPLIER;
24}
25
26static u8 fru_version(u8 ver)
27{
28	return ver & FRU_COMMON_HDR_VER_MASK;
29}
30
31static int fru_check_language(u8 code)
32{
33	if (code != FRU_LANG_CODE_ENGLISH && code != FRU_LANG_CODE_ENGLISH_1) {
34		printf("FRU_ERROR: Only English Language is supported\n");
35		return -EINVAL;
36	}
37
38	return 0;
39}
40
41u8 fru_checksum(u8 *addr, u8 len)
42{
43	u8 checksum = 0;
44	u8 cnt = len;
45
46	while (len--) {
47		if (*addr == 0)
48			cnt--;
49
50		checksum += *addr;
51		addr++;
52	}
53
54	/* If all data bytes are 0's return error */
55	if (!cnt)
56		return EINVAL;
57
58	return checksum;
59}
60
61static int fru_check_type_len(u8 type_len, u8 language, u8 *type)
62{
63	int len;
64
65	*type = (type_len & FRU_TYPELEN_CODE_MASK) >> FRU_TYPELEN_TYPE_SHIFT;
66
67	len = type_len & FRU_TYPELEN_LEN_MASK;
68
69	return len;
70}
71
72/* Return len */
73static u8 fru_gen_type_len(u8 *addr, char *name)
74{
75	int len = strlen(name);
76	struct fru_board_info_member *member;
77
78	member = (struct fru_board_info_member *)addr;
79	member->type_len = FRU_TYPELEN_TYPE_ASCII8 << FRU_TYPELEN_TYPE_SHIFT;
80	member->type_len |= len;
81
82	debug("%lx/%lx: Add %s to 0x%lx (len 0x%x)\n", (ulong)addr,
83	      (ulong)&member->type_len,  name, (ulong)&member->name, len);
84	memcpy(&member->name, name, len);
85
86	/* Add +1 for type_len parameter */
87	return 1 + len;
88}
89
90int fru_generate(unsigned long addr, char *manufacturer, char *board_name,
91		 char *serial_no, char *part_no, char *revision)
92{
93	struct fru_common_hdr *header = (struct fru_common_hdr *)addr;
94	struct fru_board_info_header *board_info;
95	u8 *member;
96	u8 len, pad, modulo;
97
98	header->version = 1; /* Only version 1.0 is supported now */
99	header->off_internal = 0; /* not present */
100	header->off_chassis = 0; /* not present */
101	header->off_board = (sizeof(*header)) / 8; /* Starting offset 8 */
102	header->off_product = 0; /* not present */
103	header->off_multirec = 0; /* not present */
104	header->pad = 0;
105	/*
106	 * This unsigned byte can be used to calculate a zero checksum
107	 * for the data area following the header. I.e. the modulo 256 sum of
108	 * the record data bytes plus the checksum byte equals zero.
109	 */
110	header->crc = 0; /* Clear before calculation */
111	header->crc = 0 - fru_checksum((u8 *)header, sizeof(*header));
112
113	/* board info is just right after header */
114	board_info = (void *)((u8 *)header + sizeof(*header));
115
116	debug("header %lx, board_info %lx\n", (ulong)header, (ulong)board_info);
117
118	board_info->ver = 1; /* 1.0 spec */
119	board_info->lang_code = 0; /* English */
120	board_info->time[0] = 0; /* unspecified */
121	board_info->time[1] = 0; /* unspecified */
122	board_info->time[2] = 0; /* unspecified */
123
124	/* Member fields are just after board_info header */
125	member = (u8 *)board_info + sizeof(*board_info);
126
127	len = fru_gen_type_len(member, manufacturer); /* Board Manufacturer */
128	member += len;
129	len = fru_gen_type_len(member, board_name); /* Board Product name */
130	member += len;
131	len = fru_gen_type_len(member, serial_no); /* Board Serial number */
132	member += len;
133	len = fru_gen_type_len(member, part_no); /* Board part number */
134	member += len;
135	len = fru_gen_type_len(member, "U-Boot generator"); /* File ID */
136	member += len;
137	len = fru_gen_type_len(member, revision); /* Revision */
138	member += len;
139
140	*member++ = 0xc1; /* Indication of no more fields */
141
142	len = member - (u8 *)board_info; /* Find current length */
143	len += 1; /* Add checksum there too for calculation */
144
145	modulo = len % 8;
146
147	if (modulo) {
148		/* Do not fill last item which is checksum */
149		for (pad = 0; pad < 8 - modulo; pad++)
150			*member++ = 0;
151
152		/* Increase structure size */
153		len += 8 - modulo;
154	}
155
156	board_info->len = len / 8; /* Size in multiples of 8 bytes */
157
158	*member = 0; /* Clear before calculation */
159	*member = 0 - fru_checksum((u8 *)board_info, len);
160
161	debug("checksum %x(addr %x)\n", *member, len);
162
163	env_set_hex("fru_addr", addr);
164	env_set_hex("filesize", (unsigned long)member - addr + 1);
165
166	return 0;
167}
168
169static int fru_parse_board(unsigned long addr)
170{
171	u8 i, type;
172	int len;
173	u8 *data, *term, *limit, *next_addr, *eof;
174
175	memcpy(&fru_data.brd.ver, (void *)addr, 6);
176
177	/*
178	 * eof marks the last data byte (without checksum). That's why checksum
179	 * is address length - 1 and last data byte is length - 2.
180	 */
181	eof = (u8 *)(fru_data.brd.len * 8 + addr - 2);
182
183	addr += 6;
184	data = (u8 *)&fru_data.brd.manufacturer_type_len;
185
186	/* Record max structure limit not to write data over allocated space */
187	limit = (u8 *)&fru_data.brd + sizeof(struct fru_board_data);
188
189	for (i = 0; ; i++, data += FRU_BOARD_MAX_LEN) {
190		len = fru_check_type_len(*(u8 *)addr, fru_data.brd.lang_code,
191					 &type);
192		next_addr = (u8 *)addr + 1;
193
194		if ((u8 *)addr >= eof) {
195			debug("Reach EOF record: addr %lx, eof %lx\n", addr,
196			      (unsigned long)eof);
197			break;
198		}
199
200		/*
201		 * Stop capture if the type is ASCII and valid field length
202		 * is 1 (0xc1) and next FRU data is less than 0x20 (space " ")
203		 * or it is 0x7f (delete 'DEL').
204		 */
205		if (type == FRU_TYPELEN_TYPE_ASCII8 && len == 1	&&
206		    (*next_addr < 0x20 || *next_addr == 0x7F))
207			break;
208
209		/* Stop when amount of chars is more then fields to record */
210		if (data + len > limit)
211			break;
212		/* This record type/len field */
213		*data++ = *(u8 *)addr;
214
215		/* Add offset to match data */
216		addr += 1;
217
218		/* If len is 0 it means empty field that's why skip writing */
219		if (!len)
220			continue;
221
222		/* Record data field */
223		memcpy(data, (u8 *)addr, len);
224		term = data + (u8)len;
225		*term = 0;
226		addr += len;
227	}
228
229	if (i < FRU_BOARD_AREA_TOTAL_FIELDS) {
230		printf("Board area require minimum %d fields\n",
231		       FRU_BOARD_AREA_TOTAL_FIELDS);
232		return -EINVAL;
233	}
234
235	return 0;
236}
237
238static int fru_parse_multirec(unsigned long addr)
239{
240	struct fru_multirec_hdr mrc;
241	u8 checksum = 0;
242	u8 hdr_len = sizeof(struct fru_multirec_hdr);
243	int mac_len = 0;
244
245	debug("%s: multirec addr %lx\n", __func__, addr);
246
247	do {
248		memcpy(&mrc.rec_type, (void *)addr, hdr_len);
249
250		checksum = fru_checksum((u8 *)addr, hdr_len);
251		if (checksum) {
252			debug("%s header CRC error\n", __func__);
253			return -EINVAL;
254		}
255
256		if (mrc.rec_type == FRU_MULTIREC_TYPE_OEM) {
257			struct fru_multirec_mac *mac = (void *)addr + hdr_len;
258			u32 type = FRU_DUT_MACID;
259
260			if (CONFIG_IS_ENABLED(FRU_SC))
261				type = FRU_SC_MACID;
262
263			if (mac->ver == type) {
264				mac_len = mrc.len - FRU_MULTIREC_MAC_OFFSET;
265				memcpy(&fru_data.mac.macid, mac->macid, mac_len);
266			}
267		}
268		addr += mrc.len + hdr_len;
269	} while (!(mrc.type & FRU_LAST_REC));
270
271	return 0;
272}
273
274int fru_capture(unsigned long addr)
275{
276	struct fru_common_hdr *hdr;
277	u8 checksum = 0;
278	unsigned long multirec_addr = addr;
279
280	checksum = fru_checksum((u8 *)addr, sizeof(struct fru_common_hdr));
281	if (checksum) {
282		printf("%s Common header CRC error\n", __func__);
283		return -EINVAL;
284	}
285
286	hdr = (struct fru_common_hdr *)addr;
287	memset((void *)&fru_data, 0, sizeof(fru_data));
288	memcpy((void *)&fru_data, (void *)hdr,
289	       sizeof(struct fru_common_hdr));
290
291	fru_data.captured = true;
292
293	if (hdr->off_board) {
294		addr += fru_cal_area_len(hdr->off_board);
295		fru_parse_board(addr);
296	}
297
298	env_set_hex("fru_addr", addr);
299
300	if (hdr->off_multirec) {
301		multirec_addr += fru_cal_area_len(hdr->off_multirec);
302		fru_parse_multirec(multirec_addr);
303	}
304
305	return 0;
306}
307
308static int fru_display_board(struct fru_board_data *brd, int verbose)
309{
310	u32 time = 0;
311	u8 type;
312	int len;
313	u8 *data;
314	static const char * const typecode[] = {
315		"Binary/Unspecified",
316		"BCD plus",
317		"6-bit ASCII",
318		"8-bit ASCII",
319		"2-byte UNICODE"
320	};
321	static const char * const boardinfo[] = {
322		"Manufacturer Name",
323		"Product Name",
324		"Serial No",
325		"Part Number",
326		"File ID",
327		/* Xilinx spec */
328		"Revision Number",
329	};
330
331	if (verbose) {
332		printf("*****BOARD INFO*****\n");
333		printf("Version:%d\n", fru_version(brd->ver));
334		printf("Board Area Length:%d\n", fru_cal_area_len(brd->len));
335	}
336
337	if (fru_check_language(brd->lang_code))
338		return -EINVAL;
339
340	time = brd->time[2] << 16 | brd->time[1] << 8 |
341	       brd->time[0];
342
343	if (verbose)
344		printf("Time in Minutes from 0:00hrs 1/1/96: %d\n", time);
345
346	data = (u8 *)&brd->manufacturer_type_len;
347
348	for (u8 i = 0; i < (sizeof(boardinfo) / sizeof(*boardinfo)); i++) {
349		len = fru_check_type_len(*data++, brd->lang_code,
350					 &type);
351
352		/* Empty record has no len/type filled */
353		if (!len) {
354			debug("%s not found\n", boardinfo[i]);
355			continue;
356		}
357
358		if (type <= FRU_TYPELEN_TYPE_ASCII8 &&
359		    (brd->lang_code == FRU_LANG_CODE_ENGLISH ||
360		     brd->lang_code == FRU_LANG_CODE_ENGLISH_1))
361			debug("Type code: %s\n", typecode[type]);
362		else
363			debug("Type code: %s\n", typecode[type + 1]);
364
365		switch (type) {
366		case FRU_TYPELEN_TYPE_BINARY:
367			debug("Length: %d\n", len);
368			printf(" %s: 0x%x\n", boardinfo[i], *data);
369			break;
370		case FRU_TYPELEN_TYPE_ASCII8:
371			debug("Length: %d\n", len);
372			printf(" %s: %s\n", boardinfo[i], data);
373			break;
374		default:
375			debug("Unsupported type %x\n", type);
376		}
377
378		data += FRU_BOARD_MAX_LEN;
379	}
380
381	return 0;
382}
383
384static void fru_display_common_hdr(struct fru_common_hdr *hdr, int verbose)
385{
386	if (!verbose)
387		return;
388
389	printf("*****COMMON HEADER*****\n");
390	printf("Version:%d\n", fru_version(hdr->version));
391	if (hdr->off_internal)
392		printf("Internal Use Area Offset:%d\n",
393		       fru_cal_area_len(hdr->off_internal));
394	else
395		printf("*** No Internal Area ***\n");
396
397	if (hdr->off_chassis)
398		printf("Chassis Info Area Offset:%d\n",
399		       fru_cal_area_len(hdr->off_chassis));
400	else
401		printf("*** No Chassis Info Area ***\n");
402
403	if (hdr->off_board)
404		printf("Board Area Offset:%d\n",
405		       fru_cal_area_len(hdr->off_board));
406	else
407		printf("*** No Board Area ***\n");
408
409	if (hdr->off_product)
410		printf("Product Info Area Offset:%d\n",
411		       fru_cal_area_len(hdr->off_product));
412	else
413		printf("*** No Product Info Area ***\n");
414
415	if (hdr->off_multirec)
416		printf("MultiRecord Area Offset:%d\n",
417		       fru_cal_area_len(hdr->off_multirec));
418	else
419		printf("*** No MultiRecord Area ***\n");
420}
421
422int fru_display(int verbose)
423{
424	if (!fru_data.captured) {
425		printf("FRU data not available please run fru parse\n");
426		return -EINVAL;
427	}
428
429	fru_display_common_hdr(&fru_data.hdr, verbose);
430
431	return fru_display_board(&fru_data.brd, verbose);
432}
433