#!/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 offset_1bw() { local addr="$1" local off="$2" local extra="${3-0}" echo w$((1+extra))@$addr $(( off & 0xff )) } offset_2bw() { local addr="$1" local off="$2" local extra="${3-0}" echo w$((2+extra))@$addr $(( (off >> 8) & 0xff )) $(( off & 0xff )) } 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" } # Each element is a `filename` declare -A IPMI_FRU_EEPROM_FILE=() # Each element is a `bus` + `addr` + `offset_bytes_func` declare -A IPMI_FRU_EEPROM_BUSADDR=() ipmi_fru_device_alloc() { local fdn="$1" local idx="$2" local json json="$(busctl -j call xyz.openbmc_project.FruDevice \ /xyz/openbmc_project/FruDevice/"$fdn" org.freedesktop.DBus.Properties \ GetAll s xyz.openbmc_project.FruDevice)" || return 80 local jqq='.data[0] | (.BUS.data | tostring) + " " + (.ADDRESS.data | tostring)' local busaddr busaddr=($(echo "$json" | jq -r "$jqq")) || return # FRU 0 is hardcoded and FruDevice does not report the correct bus for it # Hardcode a workaround for this specifically known bus if (( busaddr[0] == 0 && busaddr[1] == 0 )); then IPMI_FRU_EEPROM_FILE["$idx"]=/etc/fru/baseboard.fru.bin else local offset_bw=offset_2bw local rsp rsp=$(i2ctransfer -f -y ${busaddr[0]} $(offset_1bw ${busaddr[1]} 0) r1) || return # FRUs never start with 0xff bytes, so we can figure out addressing mode if (( rsp != 0xff )); then offset_bw=offset_1bw fi IPMI_FRU_EEPROM_BUSADDR["$idx"]="${busaddr[*]} $offset_bw" fi } ipmi_fru_alloc() { local name="$1" local -n ret="$2" # Pick the first free index to return as the allocated entry for (( ret = 0; ret < "${#IPMI_FRU_EEPROM_FILE[@]}"; ++ret )); do [ -n "${IPMI_FRU_EEPROM_FILE[@]+1}" ] || \ [ -n "${IPMI_FRU_EEPROM_BUSADDR[@]+1}" ]|| break done if [[ "$name" =~ ^of-name:(.*)$ || "$name" =~ ^([^:]*)$ ]]; then local ofn="${BASH_REMATCH[1]}" local file file="$(of_name_to_eeprom "$ofn")" || return IPMI_FRU_EEPROM_FILE["$ret"]="$file" elif [[ "$name" =~ ^frudev-name:(.*)$ ]]; then local fdn="${BASH_REMATCH[1]}" local start=$SECONDS local file while (( SECONDS - start < 60 )); do local rc=0 ipmi_fru_device_alloc "$fdn" "$ret" || rc=$? (( rc == 0 )) && break # Immediately return any errors, 80 is special to signify retry (( rc != 80 )) && return $rc sleep 1 done else echo "Invalid IPMI FRU eeprom specification: $name" >&2 return 1 fi } ipmi_fru_free() { unset 'IPMI_FRU_EEPROM_FILE[$1]' unset 'IPMI_FRU_EEPROM_BUSADDR[$1]' } 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 busaddr=(${IPMI_FRU_EEPROM_BUSADDR["$1"]-}) local file="${IPMI_FRU_EEPROM_FILE["$1"]-$1}" local offset="$2" local size="$3" if (( "${#busaddr[@]}" > 0)); then echo "Reading ${busaddr[*]} at $offset for $size" >&2 i2ctransfer -f -y ${busaddr[0]} $(${busaddr[2]} ${busaddr[1]} $offset) r$size else 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 "' fi } write_bytes() { local busaddr=(${IPMI_FRU_EEPROM_BUSADDR["$1"]-}) local file="${IPMI_FRU_EEPROM_FILE["$1"]-$1}" local offset="$2" local -n bytes_arr="$3" if (( "${#busaddr[@]}" > 0)); then echo "Writing ${busaddr[*]} at $offset for ${#bytes_arr[@]}" >&2 i2ctransfer -f -y ${busaddr[0]} $(${busaddr[2]} ${busaddr[1]} $offset ${#bytes_arr[@]}) ${bytes_arr[@]} else 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 fi } 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