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