]>
Commit | Line | Data |
---|---|---|
f1b97b5f SDPP |
1 | // SPDX-License-Identifier: GPL-2.0 |
2 | /* | |
f0f86d39 ASS |
3 | * (C) Copyright 2019 - 2022, Xilinx, Inc. |
4 | * (C) Copyright 2022 - 2023, Advanced Micro Devices, Inc. | |
f1b97b5f SDPP |
5 | */ |
6 | ||
d678a59d | 7 | #include <common.h> |
f1b97b5f SDPP |
8 | #include <cpu_func.h> |
9 | #include <env.h> | |
10 | #include <fdtdec.h> | |
11 | #include <log.h> | |
12 | #include <malloc.h> | |
7a036b67 | 13 | #include <net.h> |
f1b97b5f SDPP |
14 | #include <asm/io.h> |
15 | #include <asm/arch/hardware.h> | |
16 | ||
17 | #include "fru.h" | |
18 | ||
236f2ec4 | 19 | struct fru_table fru_data __section(".data"); |
f1b97b5f SDPP |
20 | |
21 | static u16 fru_cal_area_len(u8 len) | |
22 | { | |
23 | return len * FRU_COMMON_HDR_LEN_MULTIPLIER; | |
24 | } | |
25 | ||
26 | static u8 fru_version(u8 ver) | |
27 | { | |
28 | return ver & FRU_COMMON_HDR_VER_MASK; | |
29 | } | |
30 | ||
31 | static 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 | ||
41 | u8 fru_checksum(u8 *addr, u8 len) | |
42 | { | |
43 | u8 checksum = 0; | |
90e8f2db | 44 | u8 cnt = len; |
f1b97b5f SDPP |
45 | |
46 | while (len--) { | |
90e8f2db ARS |
47 | if (*addr == 0) |
48 | cnt--; | |
49 | ||
f1b97b5f SDPP |
50 | checksum += *addr; |
51 | addr++; | |
52 | } | |
53 | ||
90e8f2db ARS |
54 | /* If all data bytes are 0's return error */ |
55 | if (!cnt) | |
56 | return EINVAL; | |
57 | ||
f1b97b5f SDPP |
58 | return checksum; |
59 | } | |
60 | ||
61 | static int fru_check_type_len(u8 type_len, u8 language, u8 *type) | |
62 | { | |
63 | int len; | |
64 | ||
f1b97b5f SDPP |
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 | ||
4489e0aa MS |
72 | /* Return len */ |
73 | static 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 | ||
90 | int 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 | ||
f1b97b5f SDPP |
169 | static int fru_parse_board(unsigned long addr) |
170 | { | |
171 | u8 i, type; | |
172 | int len; | |
f0f86d39 | 173 | u8 *data, *term, *limit, *next_addr, *eof; |
f1b97b5f SDPP |
174 | |
175 | memcpy(&fru_data.brd.ver, (void *)addr, 6); | |
f0f86d39 ASS |
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 | ||
f1b97b5f SDPP |
183 | addr += 6; |
184 | data = (u8 *)&fru_data.brd.manufacturer_type_len; | |
185 | ||
b8771d0b | 186 | /* Record max structure limit not to write data over allocated space */ |
b6d14c52 | 187 | limit = (u8 *)&fru_data.brd + sizeof(struct fru_board_data); |
b8771d0b | 188 | |
f1b97b5f SDPP |
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); | |
f0f86d39 ASS |
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 | ||
f1b97b5f | 200 | /* |
f0f86d39 ASS |
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'). | |
f1b97b5f | 204 | */ |
f0f86d39 ASS |
205 | if (type == FRU_TYPELEN_TYPE_ASCII8 && len == 1 && |
206 | (*next_addr < 0x20 || *next_addr == 0x7F)) | |
f1b97b5f SDPP |
207 | break; |
208 | ||
b8771d0b MS |
209 | /* Stop when amount of chars is more then fields to record */ |
210 | if (data + len > limit) | |
211 | break; | |
f1b97b5f SDPP |
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 | ||
7a036b67 ARS |
238 | static 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; | |
f3538a3c | 258 | u32 type = FRU_DUT_MACID; |
7a036b67 | 259 | |
f3538a3c MS |
260 | if (CONFIG_IS_ENABLED(FRU_SC)) |
261 | type = FRU_SC_MACID; | |
262 | ||
263 | if (mac->ver == type) { | |
7a036b67 ARS |
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 | ||
f1b97b5f SDPP |
274 | int fru_capture(unsigned long addr) |
275 | { | |
276 | struct fru_common_hdr *hdr; | |
277 | u8 checksum = 0; | |
7a036b67 | 278 | unsigned long multirec_addr = addr; |
f1b97b5f SDPP |
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; | |
952b2e60 | 287 | memset((void *)&fru_data, 0, sizeof(fru_data)); |
5fb093f4 | 288 | memcpy((void *)&fru_data, (void *)hdr, |
f1b97b5f SDPP |
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 | ||
7a036b67 ARS |
300 | if (hdr->off_multirec) { |
301 | multirec_addr += fru_cal_area_len(hdr->off_multirec); | |
302 | fru_parse_multirec(multirec_addr); | |
303 | } | |
304 | ||
f1b97b5f SDPP |
305 | return 0; |
306 | } | |
307 | ||
308 | static 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", | |
4489e0aa MS |
326 | "File ID", |
327 | /* Xilinx spec */ | |
328 | "Revision Number", | |
f1b97b5f SDPP |
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); | |
f0f86d39 ASS |
351 | |
352 | /* Empty record has no len/type filled */ | |
353 | if (!len) { | |
354 | debug("%s not found\n", boardinfo[i]); | |
355 | continue; | |
f1b97b5f SDPP |
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 | ||
f1b97b5f SDPP |
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 | ||
384 | static 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 | ||
422 | int 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 | } |