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