blob: 940be7fb0dbf0bf0cb007f295add794041e1785c [file] [log] [blame]
William A. Kennington III1b34b592021-03-02 20:24:57 -08001#!/bin/bash
2# Copyright 2021 Google LLC
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16[ -n "${ipmi_fru_init-}" ] && return
17
18IPMI_FRU_COMMON_HEADER_INTERNAL_OFFSET_IDX=1
19IPMI_FRU_COMMON_HEADER_CHASSIS_OFFSET_IDX=2
20IPMI_FRU_COMMON_HEADER_BOARD_OFFSET_IDX=3
21IPMI_FRU_COMMON_HEADER_PRODUCT_OFFSET_IDX=4
22IPMI_FRU_COMMON_HEADER_MULTI_RECORD_OFFSET_IDX=5
23IPMI_FRU_AREA_HEADER_SIZE_IDX=1
24IPMI_FRU_CHECKSUM_IDX=-1
25
26of_name_to_eeproms() {
27 local names
28 if ! names="$(grep -xl "$1" /sys/bus/i2c/devices/*/of_node/name)"; then
29 echo "Failed to find eeproms with of_name '$1'" >&2
30 return 1
31 fi
32 echo "$names" | sed 's,/of_node/name$,/eeprom,'
33}
34
35of_name_to_eeprom() {
36 local eeproms
37 eeproms="$(of_name_to_eeproms "$1")" || return
38 if (( "$(echo "$eeproms" | wc -l)" != 1 )); then
39 echo "Got more than one eeprom for '$1':" $eeproms >&2
40 return 1
41 fi
42 echo "$eeproms"
43}
44
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -070045declare -A IPMI_FRU_EEPROM_FILE=()
46
William A. Kennington IIIa8106142021-10-12 23:25:05 -070047ipmi_fru_device_to_file() {
48 local fdn="$1"
49
50 local json
51 json="$(busctl -j call xyz.openbmc_project.FruDevice \
52 /xyz/openbmc_project/FruDevice/"$fdn" org.freedesktop.DBus.Properties \
53 GetAll s xyz.openbmc_project.FruDevice)" || return 80
54
55 local jqq='.data[0] | (.BUS.data | tostring) + " " + (.ADDRESS.data | tostring)'
56 local busaddr
57 busaddr="$(echo "$json" | jq -r "$jqq")" || return
58
59 # FRU 0 is hardcoded and FruDevice does not report the correct bus for it
60 # Hardcode a workaround for this specifically known bus
61 if [ "$busaddr" = '0 0' ]; then
62 echo "/etc/fru/baseboard.fru.bin"
63 else
64 local dev="$(printf '%d-%04x' $busaddr)"
65 local efile="/sys/bus/i2c/devices/$dev/eeprom"
66 # The at24 eeprom driver is not guaranteed to be bound
67 if [ ! -e "$efile" ]; then
68 echo "$dev" >/sys/bus/i2c/drivers/at24/bind || return
69 fi
70 echo "$efile"
71 fi
72}
73
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -070074ipmi_fru_alloc() {
75 local name="$1"
76 local -n ret="$2"
77
78 # Pick the first free index to return as the allocated entry
79 for (( ret = 0; ret < "${#IPMI_FRU_EEPROM_FILE[@]}"; ++ret )); do
80 [ -n "${IPMI_FRU_EEPROM_FILE[@]+1}" ] || break
81 done
82
83 if [[ "$name" =~ ^of-name:(.*)$ || "$name" =~ ^([^:]*)$ ]]; then
84 local ofn="${BASH_REMATCH[1]}"
85 local file
86 file="$(of_name_to_eeprom "$ofn")" || return
87 IPMI_FRU_EEPROM_FILE["$ret"]="$file"
William A. Kennington IIIa8106142021-10-12 23:25:05 -070088 elif [[ "$name" =~ ^frudev-name:(.*)$ ]]; then
89 local fdn="${BASH_REMATCH[1]}"
90 local start=$SECONDS
91 local file
92 while (( SECONDS - start < 60 )); do
93 local rc=0
94 file="$(ipmi_fru_device_to_file "$fdn")" || rc=$?
95 (( rc == 0 )) && break
96 # Immediately return any errors, 80 is special to signify retry
97 (( rc != 80 )) && return $rc
98 sleep 1
99 done
100 IPMI_FRU_EEPROM_FILE["$ret"]="$file"
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700101 else
102 echo "Invalid IPMI FRU eeprom specification: $name" >&2
103 return 1
104 fi
105}
106
107ipmi_fru_free() {
108 unset 'IPMI_FRU_EEPROM_FILE[$1]'
109}
110
William A. Kennington III1b34b592021-03-02 20:24:57 -0800111checksum() {
112 local -n checksum_arr="$1"
113 local checksum=0
114 for byte in "${checksum_arr[@]}"; do
115 checksum=$((checksum + byte))
116 done
117 echo $((checksum & 0xff))
118}
119
120fix_checksum() {
121 local -n fix_checksum_arr="$1"
122 old_cksum=${fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]}
123 ((fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]-=$(checksum fix_checksum_arr)))
124 ((fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]&=0xff))
125 printf 'Corrected %s checksum from 0x%02X -> 0x%02X\n' \
126 "$1" "${old_cksum}" "${fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]}" >&2
127}
128
129read_bytes() {
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700130 local file="${IPMI_FRU_EEPROM_FILE["$1"]-$1}"
William A. Kennington III1b34b592021-03-02 20:24:57 -0800131 local offset="$2"
132 local size="$3"
133
134 echo "Reading $file at $offset for $size" >&2
135 dd if="$file" bs=1 count="$size" skip="$offset" 2>/dev/null | \
136 hexdump -v -e '1/1 "%d "'
137}
138
139write_bytes() {
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700140 local file="${IPMI_FRU_EEPROM_FILE["$1"]-$1}"
William A. Kennington III1b34b592021-03-02 20:24:57 -0800141 local offset="$2"
142 local -n bytes_arr="$3"
143
144 local hexstr
145 hexstr="$(printf '\\x%x' "${bytes_arr[@]}")" || return
146 echo "Writing $file at $offset for ${#bytes_arr[@]}" >&2
147 printf "$hexstr" | dd of="$file" bs=1 seek=$offset 2>/dev/null
148}
149
150read_header() {
151 local eeprom="$1"
152 local -n header_arr="$2"
153
154 header_arr=($(read_bytes "$eeprom" 0 8)) || return
155 echo "Checking $eeprom FRU Header version" >&2
156 # FRU header is always version 1
157 (( header_arr[0] == 1 )) || return
158 echo "Checking $eeprom FRU Header checksum" >&2
159 local sum
160 sum="$(checksum header_arr)" || return
161 # Checksums should be valid
162 (( sum == 0 )) || return 10
163}
164
165read_area() {
166 local eeprom="$1"
167 local offset="$2"
168 local -n area_arr="$3"
169 local area_size="${4-0}"
170
171 offset=$((offset*8))
172 area_arr=($(read_bytes "$eeprom" "$offset" 8)) || return
173 echo "Checking $eeprom $offset FRU Area version" >&2
174 # FRU Area is always version 1
175 (( area_arr[0] == 1 )) || return
176 if (( area_size == 0 )); then
177 area_size=${area_arr[$IPMI_FRU_AREA_HEADER_SIZE_IDX]}
178 fi
179 area_arr=($(read_bytes "$eeprom" "$offset" $((area_size*8)))) || return
180 echo "Checking $eeprom $offset FRU Area checksum" >&2
181 local sum
182 sum="$(checksum area_arr)" || return
183 # Checksums should be valid
184 (( sum == 0 )) || return 10
185}
186
187ipmi_fru_init=1
188return 0 2>/dev/null
189echo "ipmi-fru is a library, not executed directly" >&2
190exit 1