242 lines
6.1 KiB
C
242 lines
6.1 KiB
C
/* Copyright 2013-2014 IBM Corp.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
* implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#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;
|
|
|
|
#define min(x,y) ((x) < (y) ? x : y)
|
|
|
|
/* 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;
|
|
}
|