1 // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
2 /*
3  * Fill out firmware related FRUs (Field Replaceable Units)
4  *
5  * Copyright 2013-2019 IBM Corp.
6  */
7 
8 #include <skiboot.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include <ipmi.h>
12 #include <lock.h>
13 #include <opal.h>
14 #include <device.h>
15 
16 struct product_info {
17 	char *manufacturer;
18 	char *product;
19 	char *part_no;
20 	char *version;
21 	char *serial_no;
22 	char *asset_tag;
23 };
24 
25 struct common_header {
26 	u8 version;
27 	u8 internal_offset;
28 	u8 chassis_offset;
29 	u8 board_offset;
30 	u8 product_offset;
31 	u8 multirecord_offset;
32 	u8 pad;
33 	u8 checksum;
34 } __packed;
35 
36 /* The maximum amount of FRU data we can store. */
37 #define FRU_DATA_SIZE 256
38 
39 /* We allocate two bytes at these locations in the data array to track
40  * state. */
41 #define WRITE_INDEX 256
42 #define REMAINING 257
43 
44 /* The ASCII string encoding used only has 5 bits to encode length
45  * hence the maximum is 31 characters. */
46 #define MAX_STR_LEN 31
47 
48 static u8 fru_dev_id = 0;
49 
fru_insert_string(u8 * buf,char * str)50 static int fru_insert_string(u8 *buf, char *str)
51 {
52 	int len = strlen(str);
53 
54 	/* The ASCII type/length format only supports a string length
55 	 * between 2 and 31 characters. Zero characters is ok though
56 	 * as it indicates no data present. */
57 	if (len == 1 || len > MAX_STR_LEN)
58 		return OPAL_PARAMETER;
59 
60 	buf[0] = 0xc0 | len;
61 	memcpy(&buf[1], str, len);
62 
63 	return len + 1;
64 }
65 
fru_checksum(u8 * buf,int len)66 static u8 fru_checksum(u8 *buf, int len)
67 {
68 	int i;
69 	u8 checksum = 0;
70 
71 	for(i = 0; i < len; i++) {
72 		checksum += buf[i];
73 	}
74 	checksum = ~checksum + 1;
75 	return checksum;
76 }
77 
78 #define FRU_INSERT_STRING(x, y)						\
79 	({ rc = fru_insert_string(x, y);				\
80 	 { if (rc < 1) return OPAL_PARAMETER; } rc; })
81 
fru_fill_product_info(u8 * buf,struct product_info * info,size_t size)82 static int fru_fill_product_info(u8 *buf, struct product_info *info, size_t size)
83 {
84 	size_t total_size = 11;
85 	int index = 0;
86 	int rc;
87 
88 	total_size += strlen(info->manufacturer);
89 	total_size += strlen(info->product);
90 	total_size += strlen(info->part_no);
91 	total_size += strlen(info->version);
92 	total_size += strlen(info->serial_no);
93 	total_size += strlen(info->asset_tag);
94 	total_size += (8 - (total_size % 8)) % 8;
95 	if (total_size > size)
96 		return OPAL_PARAMETER;
97 
98 	buf[index++] = 0x1;		/* Version */
99 	buf[index++] = total_size / 8;	/* Size */
100 	buf[index++] = 0;		/* Language code (English) */
101 
102 	index += FRU_INSERT_STRING(&buf[index], info->manufacturer);
103 	index += FRU_INSERT_STRING(&buf[index], info->product);
104 	index += FRU_INSERT_STRING(&buf[index], info->part_no);
105 	index += FRU_INSERT_STRING(&buf[index], info->version);
106 	index += FRU_INSERT_STRING(&buf[index], info->serial_no);
107 	index += FRU_INSERT_STRING(&buf[index], info->asset_tag);
108 
109 	buf[index++] = 0xc1;		/* End of data marker */
110 	memset(&buf[index], 0, total_size - index - 1);
111 	index += total_size - index - 1;
112 	buf[index] = fru_checksum(buf, index);
113 	assert(index == total_size - 1);
114 
115 	return total_size;
116 }
117 
fru_add(u8 * buf,int size)118 static int fru_add(u8 *buf, int size)
119 {
120 	int len;
121 	struct common_header common_hdr;
122 	char *short_version;
123 	struct product_info info = {
124 		.manufacturer = (char *) "IBM",
125 		.product = (char *) "skiboot",
126 		.part_no = (char *) "",
127 		.serial_no = (char *) "",
128 		.asset_tag = (char *) "",
129 	};
130 
131 	if (size < sizeof(common_hdr))
132 		return OPAL_PARAMETER;
133 
134 	/* We currently only support adding the version number at the
135 	 * product information offset. We choose an offset of 64 bytes
136 	 * because that's what the standard recommends. */
137 	common_hdr.version = 1;
138 	common_hdr.internal_offset = 0;
139 	common_hdr.chassis_offset = 0;
140 	common_hdr.board_offset = 0;
141 	common_hdr.product_offset = 64/8;
142 	common_hdr.multirecord_offset = 0;
143 	common_hdr.pad = 0;
144 	common_hdr.checksum = fru_checksum((u8 *) &common_hdr, sizeof(common_hdr) - 1);
145 	memcpy(buf, &common_hdr, sizeof(common_hdr));
146 
147 	short_version = strdup(version);
148 	info.version = short_version;
149 	if (!strncmp(version, "skiboot-", 8))
150 		info.version = &short_version[8];
151 
152 	if (strlen(info.version) >= MAX_STR_LEN) {
153 		if (info.version[MAX_STR_LEN] != '\0')
154 			info.version[MAX_STR_LEN - 1] = '+';
155 		info.version[MAX_STR_LEN] = '\0';
156 	}
157 
158 	len = fru_fill_product_info(&buf[64], &info, size - 64);
159 	free(short_version);
160 	if (len < 0)
161 		return OPAL_PARAMETER;
162 
163 	return len + 64;
164 }
165 
fru_write_complete(struct ipmi_msg * msg)166 static void fru_write_complete(struct ipmi_msg *msg)
167 {
168 	u8 write_count = msg->data[0];
169 	u16 offset;
170 
171 	msg->data[WRITE_INDEX] += write_count;
172 	msg->data[REMAINING] -= write_count;
173 	if (msg->data[REMAINING] == 0)
174 		goto out;
175 
176 	offset = msg->data[WRITE_INDEX];
177 	ipmi_init_msg(msg, IPMI_DEFAULT_INTERFACE, IPMI_WRITE_FRU,
178 		      fru_write_complete, NULL,
179 		      MIN(msg->data[REMAINING] + 3, IPMI_MAX_REQ_SIZE), 2);
180 
181 	memmove(&msg->data[3], &msg->data[offset + 3], msg->req_size - 3);
182 
183 	msg->data[0] = fru_dev_id;     		/* FRU Device ID */
184 	msg->data[1] = offset & 0xff;		/* Offset LSB */
185 	msg->data[2] = (offset >> 8) & 0xff;	/* Offset MSB */
186 
187 	ipmi_queue_msg(msg);
188 
189 	return;
190 
191 out:
192 	ipmi_free_msg(msg);
193 }
194 
fru_write(void)195 static int fru_write(void)
196 {
197 	struct ipmi_msg *msg;
198 	int len;
199 
200 	/* We allocate FRU_DATA_SIZE + 5 bytes for the message:
201 	 * - 3 bytes for the the write FRU command header
202 	 * - FRU_DATA_SIZE bytes for FRU data
203 	 * - 2 bytes for offset & bytes remaining count
204 	 */
205 	msg = ipmi_mkmsg(IPMI_DEFAULT_INTERFACE, IPMI_WRITE_FRU,
206 			 fru_write_complete, NULL, NULL, FRU_DATA_SIZE + 5, 2);
207 	if (!msg)
208 		return OPAL_RESOURCE;
209 
210 	msg->data[0] = fru_dev_id;	/* FRU Device ID */
211 	msg->data[1] = 0x0;		/* Offset LSB (we always write a new common header) */
212 	msg->data[2] = 0x0;		/* Offset MSB */
213 	len = fru_add(&msg->data[3], FRU_DATA_SIZE);
214 
215 	if (len < 0)
216 		return len;
217 
218 	/* Three bytes for the actual FRU Data Command */
219 	msg->data[WRITE_INDEX] = 0;
220 	msg->data[REMAINING] = len;
221 	msg->req_size = MIN(len + 3, IPMI_MAX_REQ_SIZE);
222 	return ipmi_queue_msg(msg);
223 }
224 
ipmi_fru_init(u8 dev_id)225 void ipmi_fru_init(u8 dev_id)
226 {
227 	fru_dev_id = dev_id;
228 	fru_write();
229 
230 	return;
231 }
232