meta-google: ipmi-fru-sh: Add library
This is a useful utility for doing one off FRU rewriting operations,
used when manufactured boards don't have correct eeproms contents.
Change-Id: I73ad4d14e088678f49ccf2a28138aa6356de56a0
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/meta-google/recipes-google/ipmi/ipmi-fru-sh.bb b/meta-google/recipes-google/ipmi/ipmi-fru-sh.bb
new file mode 100644
index 0000000..deaee7a
--- /dev/null
+++ b/meta-google/recipes-google/ipmi/ipmi-fru-sh.bb
@@ -0,0 +1,16 @@
+SUMMARY = "Shell functions for manipulating IPMI formatted EEPROMs"
+PR = "r1"
+LICENSE = "Apache-2.0"
+LIC_FILES_CHKSUM = "file://${COREBASE}/meta/files/common-licenses/Apache-2.0;md5=89aea4e17d99a7cacdbeed46a0096b10"
+
+FILESEXTRAPATHS_prepend := "${THISDIR}/${PN}:"
+SRC_URI += "file://lib.sh"
+S = "${WORKDIR}"
+
+DATA = "${datadir}/ipmi-fru"
+FILES_${PN} += "${DATA}"
+
+do_install_append() {
+ install -d -m0755 ${D}${DATA}
+ install -m0644 lib.sh ${D}${DATA}/
+}
diff --git a/meta-google/recipes-google/ipmi/ipmi-fru-sh/lib.sh b/meta-google/recipes-google/ipmi/ipmi-fru-sh/lib.sh
new file mode 100644
index 0000000..335e0b2
--- /dev/null
+++ b/meta-google/recipes-google/ipmi/ipmi-fru-sh/lib.sh
@@ -0,0 +1,124 @@
+#!/bin/bash
+# Copyright 2021 Google LLC
+#
+# 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.
+
+[ -n "${ipmi_fru_init-}" ] && return
+
+IPMI_FRU_COMMON_HEADER_INTERNAL_OFFSET_IDX=1
+IPMI_FRU_COMMON_HEADER_CHASSIS_OFFSET_IDX=2
+IPMI_FRU_COMMON_HEADER_BOARD_OFFSET_IDX=3
+IPMI_FRU_COMMON_HEADER_PRODUCT_OFFSET_IDX=4
+IPMI_FRU_COMMON_HEADER_MULTI_RECORD_OFFSET_IDX=5
+IPMI_FRU_AREA_HEADER_SIZE_IDX=1
+IPMI_FRU_CHECKSUM_IDX=-1
+
+of_name_to_eeproms() {
+ local names
+ if ! names="$(grep -xl "$1" /sys/bus/i2c/devices/*/of_node/name)"; then
+ echo "Failed to find eeproms with of_name '$1'" >&2
+ return 1
+ fi
+ echo "$names" | sed 's,/of_node/name$,/eeprom,'
+}
+
+of_name_to_eeprom() {
+ local eeproms
+ eeproms="$(of_name_to_eeproms "$1")" || return
+ if (( "$(echo "$eeproms" | wc -l)" != 1 )); then
+ echo "Got more than one eeprom for '$1':" $eeproms >&2
+ return 1
+ fi
+ echo "$eeproms"
+}
+
+checksum() {
+ local -n checksum_arr="$1"
+ local checksum=0
+ for byte in "${checksum_arr[@]}"; do
+ checksum=$((checksum + byte))
+ done
+ echo $((checksum & 0xff))
+}
+
+fix_checksum() {
+ local -n fix_checksum_arr="$1"
+ old_cksum=${fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]}
+ ((fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]-=$(checksum fix_checksum_arr)))
+ ((fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]&=0xff))
+ printf 'Corrected %s checksum from 0x%02X -> 0x%02X\n' \
+ "$1" "${old_cksum}" "${fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]}" >&2
+}
+
+read_bytes() {
+ local file="$1"
+ local offset="$2"
+ local size="$3"
+
+ echo "Reading $file at $offset for $size" >&2
+ dd if="$file" bs=1 count="$size" skip="$offset" 2>/dev/null | \
+ hexdump -v -e '1/1 "%d "'
+}
+
+write_bytes() {
+ local file="$1"
+ local offset="$2"
+ local -n bytes_arr="$3"
+
+ local hexstr
+ hexstr="$(printf '\\x%x' "${bytes_arr[@]}")" || return
+ echo "Writing $file at $offset for ${#bytes_arr[@]}" >&2
+ printf "$hexstr" | dd of="$file" bs=1 seek=$offset 2>/dev/null
+}
+
+read_header() {
+ local eeprom="$1"
+ local -n header_arr="$2"
+
+ header_arr=($(read_bytes "$eeprom" 0 8)) || return
+ echo "Checking $eeprom FRU Header version" >&2
+ # FRU header is always version 1
+ (( header_arr[0] == 1 )) || return
+ echo "Checking $eeprom FRU Header checksum" >&2
+ local sum
+ sum="$(checksum header_arr)" || return
+ # Checksums should be valid
+ (( sum == 0 )) || return 10
+}
+
+read_area() {
+ local eeprom="$1"
+ local offset="$2"
+ local -n area_arr="$3"
+ local area_size="${4-0}"
+
+ offset=$((offset*8))
+ area_arr=($(read_bytes "$eeprom" "$offset" 8)) || return
+ echo "Checking $eeprom $offset FRU Area version" >&2
+ # FRU Area is always version 1
+ (( area_arr[0] == 1 )) || return
+ if (( area_size == 0 )); then
+ area_size=${area_arr[$IPMI_FRU_AREA_HEADER_SIZE_IDX]}
+ fi
+ area_arr=($(read_bytes "$eeprom" "$offset" $((area_size*8)))) || return
+ echo "Checking $eeprom $offset FRU Area checksum" >&2
+ local sum
+ sum="$(checksum area_arr)" || return
+ # Checksums should be valid
+ (( sum == 0 )) || return 10
+}
+
+ipmi_fru_init=1
+return 0 2>/dev/null
+echo "ipmi-fru is a library, not executed directly" >&2
+exit 1