blob: 09244181357e5a86266ffe3a32ba62e332262af6 [file] [log] [blame]
/*
* Platform Management FRU Information Storage Definition v1.0
* Document Revision 1.3, March 24, 2015
*
* From: https://www.intel.com/content/www/us/en/servers/ipmi/ipmi-platform-mgt-fru-infostorage-def-v1-0-rev-1-3-spec-update.html
*/
export class FRUParser {
constructor(data) {
this.data = data
}
parseCommon(data) {
// 8. Common Header Format
let version = data[0];
if ((version >> 4) !== 0) throw new Error("Invalid Common Header version");
if ((version & 0b1111) !== 1) throw new Error("Invalid Common Header version");
let res = {};
res.version = version;
res.internalUseStart = data[1] * 8;
res.chassisInfoStart = data[2] * 8;
res.boardInfoStart = data[3] * 8;
res.productInfoStart = data[4] * 8;
res.multiRecordInfoStart = data[5] * 8;
let sum = data.reduce((a, b) => a + b, 0) & 0xff;
if (sum !== 0) throw new Error("Common area checksum error");
return res;
}
parseBoardInfo(data) {
// 11. Board Info Area Format
let res = {};
let version = data[0];
if ((version >> 4) !== 0) throw new Error("Invalid Board Info version");
if ((version & 0b1111) !== 1) throw new Error("Invalid Board Info version");
res.version = version;
let areaLength = data[1] * 8;
if (areaLength > data.length) throw new Error("Invalid Board Info length");
data = data.slice(0, areaLength);
let sum = data.reduce((a, b) => a + b, 0) & 0xff;
if (sum !== 0) throw new Error("Board Info Area checksum error");
let r = new Reader(data);
r.skip(2);
res.language = r.readLanguageCode();
res.manufacturingDate = r.readDateTime();
res.manufacturerName = r.readTypeLength(res.language);
res.productName = r.readTypeLength(res.language);
res.serialNumber = r.readTypeLength(res.language);
res.partNumber = r.readTypeLength(res.language);
res.fruFileID = r.readTypeLength(res.language);
res.custom = r.readTypeLength(res.language);
// Not sure if this is up to standard - the standard seems to say that
// C1 must always appear, but the Dell storage cards I've looked at
// skip it. There's an earlier C1, but that's part of the FRU File ID.
if (res.length > 0) {
if (r.readByte() !== 0xc1) throw new Error("Custom area must end with C1");
}
return res;
}
parseInternalUseDell(data) {
let version = data[0];
if ((version >> 4) !== 0) throw new Error("Invalid Internal Use version");
if ((version & 0b1111) !== 1) throw new Error("Invalid Internal Use version");
if ((new TextDecoder().decode(data.slice(1,5))) !== "DELL") {
throw new Error("Invalid 'DELL' magic in internal area");
}
let sum = data.reduce((a, b) => a + b, 0) & 0xff;
if (sum !== 0) throw new Error("Dell Internal Area checksum error");
return {};
}
parse() {
this.common = this.parseCommon(this.data.slice(0, 8))
if (this.common.boardInfoStart !== 0) {
let data = this.data.slice(this.common.boardInfoStart, this.data.length);
this.boardInfo = this.parseBoardInfo(data);
} else {
this.boardInfo = {};
}
this.internalUse = {};
if (this.common.internalUseStart !== 0) {
let data = this.data.slice(this.common.internalUseStart, this.data.length);
this.internalUse.dell = this.parseInternalUseDell(data);
}
}
stringify() {
let res = [];
res.push(`Version: ${this.common.version}`)
res.push(`Board Info:`)
let bi = this.boardInfo;
res.push(` Language: ${bi.language}`)
if (bi.manufacturingDate !== undefined)
res.push(` Manufacturing Date: ${bi.manufacturingDate}`);
res.push(` Manufacturer Name: ${bi.manufacturerName}`)
res.push(` Product Name: ${bi.productName}`)
res.push(` Serial Number: ${bi.serialNumber}`)
res.push(` PartNumber: ${bi.partNumber}`)
res.push(` FRU File ID: ${bi.fruFileID}`)
if (this.internalUse.dell !== undefined) {
res.push("Internal Use: DELL-specific")
}
return res.join("\n");
}
}
class Reader {
constructor(data) {
this.data = data;
}
skip(n) {
this.data = this.data.slice(n);
}
readByte() {
let num = this.data[0];
this.data = this.data.slice(1);
return num;
}
readLanguageCode() {
let num = this.readByte();
let encoding = num >> 6;
let language = [
"en", "aa", "ab", "af", "am", "ar", "as", "ay", "az", "ba", "be",
"bg", "bh", "bi", "bn", "bo", "br", "ca", "co", "cs", "cy", "da",
"de", "dz", "el", "en", "eo", "es", "et", "eu", "fa", "fi", "fj",
"fo", "fr", "fy", "ga", "gd", "gl", "gn", "gu", "ha", "hi", "hr",
"hu", "hy", "ia", "ie", "ik", "in", "is", "it", "iw", "ja", "ji",
"jw", "ka", "kk", "kl", "km", "kn", "ko", "ks", "ku", "ky", "la",
"ln", "lo", "lt", "lv", "mg", "mi", "mk", "ml", "mn", "mo", "mr",
"ms", "mt", "my", "na", "ne", "nl", "no", "oc", "om", "or", "pa",
"pl", "ps", "pt", "qu", "rm", "rn", "ro", "ru", "rw", "sa", "sd",
"sg", "sh", "di", "sk", "dl", "sm", "sn", "so", "sq", "sr", "ss",
"st", "su", "sv", "sw", "ta", "te", "tg", "th", "ti", "tk", "tl",
"tn", "to", "tr", "ts", "tt", "tw", "uk", "ur", "uz", "vi", "vo",
"wo", "xh", "yo", "zh", "zu",
][num & 0b111111];
return language;
}
readTypeLength(language) {
let tag = this.readByte();
let type = tag >> 6;
let len = tag & 0b111111;
switch (type) {
case 0:
return this.readTLBinary(len);
case 1:
return this.readTLBCDPlus(len);
case 2:
return this.readTL6BASCII(len);
case 3:
return this.readTLString(len, language);
}
}
readTLBinary(len) {
let data = this.data.slice(0, len);
this.data = this.data.slice(len);
return data;
}
readTLBCDPlus(len) {
let data = this.data.slice(0, len);
this.data = this.data.slice(len);
const lookup = "012345689 -.???";
let res = [];
for (const c of data) {
let upper = lookup[c >> 4];
let lower = lookup[c & 0b1111];
if ((upper === "?") || (lower === "?")) {
throw new Error("Invalid BCD Plus data");
}
res.push(upper);
res.push(lower);
}
return res.join("");
}
readTL6BASCII(len) {
let data = this.data.slice(0, len);
this.data = this.data.slice(len);
const lookup =
" !\"#$%&'()*+,-./" +
"0123456789:;<=>?" +
"@ABCDEFGHIJKLMNO" +
"PQRSTUVWXYZ[\\]^_";
let res = [];
let availbits = 0;
let bits = 0;
while ((data.length > 0) || (availbits >= 6)) {
if (availbits < 6) {
bits |= (data[0] << availbits);
availbits += 8;
data = data.slice(1);
}
let n = bits & 0b111111;
availbits -= 6;
bits >>= 6;
res.push(lookup[n]);
}
return res.join("");
}
readTLString(len, language) {
let data = this.data.slice(0, len);
this.data = this.data.slice(len);
// 13. Type/Length Byte Format
// Yikes, Intel.
if (language !== "en") {
throw new Error("Unicode unimplemented");
}
// This should be 'ASCII + Latin 1', but this is a good enough approximation.
return new TextDecoder().decode(data);
}
readDateTime() {
let minutes = this.readByte() | (this.readByte() << 8) | (this.readByte() << 16);
if (minutes !== 0) throw new Error("Datetime parsing not implemented");
return undefined;
}
}
export class HBJ11FRUAssembler {
constructor(serial) {
this.serial = serial;
}
assemble() {
// Strings can be longer in FRU spec, but let's keep it conservative.
if (this.serial.length > 8) {
throw new Error("Serial too long");
}
// Same layout as DELL FRUs, board specific after common, internal use after board specific.
let common = [0x01, 0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0xf4];
// Similar layout to DELL FRUs, 72 bytes.
let board = [
0x01, // Version 1
0x09, // Length (9*8 == 72 bytes)
0x00, 0x00, 0x00, 0x00, // Manufacturing time (unspecified)
// Manufacturer: HELL (we can't use anything longer, as the product
// name below needs to be in this exact byte offset in the ROM for
// the iDRAC to display the name correctly).
0x83, 0x68, 0xc9, 0xb2,
// Product name: bgpwtf SATA Repeater
0xDE, 98, 103, 112, 119, 116, 102, 32, 83, 65, 84, 65, 32, 82, 101, 112, 101, 97, 116, 101, 114,
32, 32, 32, 32, 32, 32, 32, 32, 32, 32, // ... pad above to 30 chars.
];
// Serial number tag/length.
board.push(0xC0 | (this.serial.length));
// Serial number.
for (const c of this.serial) {
board.push(c.charCodeAt());
}
board = board.concat([
0xC7, 72, 66, 74, 49, 49, 65, 48, // Part number: HBJ11A0
0xC1, 0x02, 0xC1, 0x00, // FRU File ID 2, one-byte custom area/end? Weird shit.
]);
if (board.length > 71) {
throw new Error("Board Area too long!");
}
// Pad with zeroes.
board = board.concat(Array(71 - board.length).fill(0));
// Calculate checksum.
let sum = (0xff ^ (board.reduce((a, b) => a + b, 0) & 0xff));
board.push((sum + 1) & 0xff);
// Dell internal use.
let dell = [
0x01, 0x44, 0x45, 0x4c, 0x4c, 0xf7, 0x00, 0x00, 0x01, 0x02, 0x00, 0x00, 0x01, 0x13, 0x58, 0x01,
0x0f, 0x68, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0f, 0x0d, 0x00, 0xe0, 0x01, 0x00, 0x00, 0x01,
0x00, 0x01, 0x01, 0x00,
];
let eeprom = common.concat(board).concat(dell);
// Pad to 256 bytes.
eeprom = eeprom.concat(Array(256 - eeprom.length).fill(0));
return new Uint8Array(eeprom);
}
}