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