blob: 0f6de8f73f17d5f26aff048eca6fabd64e51ce48 [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
William A. Kennington IIIa3a61482023-06-02 17:07:38 -070018# shellcheck disable=SC2034
William A. Kennington III1b34b592021-03-02 20:24:57 -080019IPMI_FRU_COMMON_HEADER_INTERNAL_OFFSET_IDX=1
William A. Kennington IIIa3a61482023-06-02 17:07:38 -070020# shellcheck disable=SC2034
William A. Kennington III1b34b592021-03-02 20:24:57 -080021IPMI_FRU_COMMON_HEADER_CHASSIS_OFFSET_IDX=2
William A. Kennington IIIa3a61482023-06-02 17:07:38 -070022# shellcheck disable=SC2034
William A. Kennington III1b34b592021-03-02 20:24:57 -080023IPMI_FRU_COMMON_HEADER_BOARD_OFFSET_IDX=3
William A. Kennington IIIa3a61482023-06-02 17:07:38 -070024# shellcheck disable=SC2034
William A. Kennington III1b34b592021-03-02 20:24:57 -080025IPMI_FRU_COMMON_HEADER_PRODUCT_OFFSET_IDX=4
William A. Kennington IIIa3a61482023-06-02 17:07:38 -070026# shellcheck disable=SC2034
William A. Kennington III1b34b592021-03-02 20:24:57 -080027IPMI_FRU_COMMON_HEADER_MULTI_RECORD_OFFSET_IDX=5
William A. Kennington IIIa3a61482023-06-02 17:07:38 -070028# shellcheck disable=SC2034
William A. Kennington III1b34b592021-03-02 20:24:57 -080029IPMI_FRU_AREA_HEADER_SIZE_IDX=1
William A. Kennington IIIa3a61482023-06-02 17:07:38 -070030# shellcheck disable=SC2034
William A. Kennington III1b34b592021-03-02 20:24:57 -080031IPMI_FRU_CHECKSUM_IDX=-1
32
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070033offset_1bw() {
34 local addr="$1"
35 local off="$2"
36 local extra="${3-0}"
William A. Kennington IIIa3a61482023-06-02 17:07:38 -070037 echo "w$((1+extra))@$addr $(( off & 0xff ))"
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070038}
39
40offset_2bw() {
41 local addr="$1"
42 local off="$2"
43 local extra="${3-0}"
William A. Kennington IIIa3a61482023-06-02 17:07:38 -070044 echo "w$((2+extra))@$addr $(( (off >> 8) & 0xff )) $(( off & 0xff ))"
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070045}
46
William A. Kennington III1b34b592021-03-02 20:24:57 -080047of_name_to_eeproms() {
48 local names
49 if ! names="$(grep -xl "$1" /sys/bus/i2c/devices/*/of_node/name)"; then
50 echo "Failed to find eeproms with of_name '$1'" >&2
51 return 1
52 fi
William A. Kennington IIIa3a61482023-06-02 17:07:38 -070053 echo "${names//of_node\/name/eeprom}"
William A. Kennington III1b34b592021-03-02 20:24:57 -080054}
55
56of_name_to_eeprom() {
57 local eeproms
58 eeproms="$(of_name_to_eeproms "$1")" || return
59 if (( "$(echo "$eeproms" | wc -l)" != 1 )); then
William A. Kennington IIIa3a61482023-06-02 17:07:38 -070060 echo "Got more than one eeprom for '$1': $eeproms" >&2
William A. Kennington III1b34b592021-03-02 20:24:57 -080061 return 1
62 fi
63 echo "$eeproms"
64}
65
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070066# Each element is a `filename`
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -070067declare -A IPMI_FRU_EEPROM_FILE=()
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070068# Each element is a `bus` + `addr` + `offset_bytes_func`
69declare -A IPMI_FRU_EEPROM_BUSADDR=()
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -070070
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070071ipmi_fru_device_alloc() {
William A. Kennington IIIa8106142021-10-12 23:25:05 -070072 local fdn="$1"
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070073 local idx="$2"
William A. Kennington IIIa8106142021-10-12 23:25:05 -070074
75 local json
76 json="$(busctl -j call xyz.openbmc_project.FruDevice \
77 /xyz/openbmc_project/FruDevice/"$fdn" org.freedesktop.DBus.Properties \
78 GetAll s xyz.openbmc_project.FruDevice)" || return 80
79
80 local jqq='.data[0] | (.BUS.data | tostring) + " " + (.ADDRESS.data | tostring)'
81 local busaddr
William A. Kennington IIIa3a61482023-06-02 17:07:38 -070082 # shellcheck disable=SC2207
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070083 busaddr=($(echo "$json" | jq -r "$jqq")) || return
William A. Kennington IIIa8106142021-10-12 23:25:05 -070084
85 # FRU 0 is hardcoded and FruDevice does not report the correct bus for it
86 # Hardcode a workaround for this specifically known bus
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070087 if (( busaddr[0] == 0 && busaddr[1] == 0 )); then
88 IPMI_FRU_EEPROM_FILE["$idx"]=/etc/fru/baseboard.fru.bin
William A. Kennington IIIa8106142021-10-12 23:25:05 -070089 else
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070090 local offset_bw=offset_2bw
William A. Kennington III8dcd4592022-02-07 14:15:07 -080091 local last=0
92 local rsp=0x100
93 local start=$SECONDS
94 # Query the FRU multiple times to ensure the return value is stabilized
95 while (( last != rsp )); do
96 # It shouldn't take > 0.1s to stabilize, limit instability
97 if (( SECONDS - start >= 10 )); then
98 echo "Timed out determining offset for ${busaddr[0]}@${busaddr[1]}" >&2
99 return 1
100 fi
101 last=$rsp
William A. Kennington IIIa3a61482023-06-02 17:07:38 -0700102 # shellcheck disable=SC2046
103 rsp=$(i2ctransfer -f -y "${busaddr[0]}" $(offset_1bw "${busaddr[1]}" 0) r1) || return
William A. Kennington III8dcd4592022-02-07 14:15:07 -0800104 done
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700105 # FRUs never start with 0xff bytes, so we can figure out addressing mode
106 if (( rsp != 0xff )); then
107 offset_bw=offset_1bw
William A. Kennington IIIa8106142021-10-12 23:25:05 -0700108 fi
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700109 IPMI_FRU_EEPROM_BUSADDR["$idx"]="${busaddr[*]} $offset_bw"
William A. Kennington IIIa8106142021-10-12 23:25:05 -0700110 fi
111}
112
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700113ipmi_fru_alloc() {
114 local name="$1"
115 local -n ret="$2"
116
117 # Pick the first free index to return as the allocated entry
118 for (( ret = 0; ret < "${#IPMI_FRU_EEPROM_FILE[@]}"; ++ret )); do
William A. Kennington IIIa3a61482023-06-02 17:07:38 -0700119 [ -n "${IPMI_FRU_EEPROM_FILE[*]+1}" ] || \
120 [ -n "${IPMI_FRU_EEPROM_BUSADDR[*]+1}" ]|| break
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700121 done
122
123 if [[ "$name" =~ ^of-name:(.*)$ || "$name" =~ ^([^:]*)$ ]]; then
124 local ofn="${BASH_REMATCH[1]}"
125 local file
126 file="$(of_name_to_eeprom "$ofn")" || return
127 IPMI_FRU_EEPROM_FILE["$ret"]="$file"
William A. Kennington IIIa8106142021-10-12 23:25:05 -0700128 elif [[ "$name" =~ ^frudev-name:(.*)$ ]]; then
129 local fdn="${BASH_REMATCH[1]}"
130 local start=$SECONDS
131 local file
Yuxiao Zhang3a3b1a32023-05-16 09:54:31 -0700132 while (( SECONDS - start < 300 )); do
William A. Kennington IIIa8106142021-10-12 23:25:05 -0700133 local rc=0
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700134 ipmi_fru_device_alloc "$fdn" "$ret" || rc=$?
William A. Kennington IIIa8106142021-10-12 23:25:05 -0700135 (( rc == 0 )) && break
136 # Immediately return any errors, 80 is special to signify retry
137 (( rc != 80 )) && return $rc
138 sleep 1
139 done
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700140 else
141 echo "Invalid IPMI FRU eeprom specification: $name" >&2
142 return 1
143 fi
144}
145
146ipmi_fru_free() {
147 unset 'IPMI_FRU_EEPROM_FILE[$1]'
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700148 unset 'IPMI_FRU_EEPROM_BUSADDR[$1]'
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700149}
150
William A. Kennington III1b34b592021-03-02 20:24:57 -0800151checksum() {
152 local -n checksum_arr="$1"
153 local checksum=0
154 for byte in "${checksum_arr[@]}"; do
155 checksum=$((checksum + byte))
156 done
157 echo $((checksum & 0xff))
158}
159
160fix_checksum() {
161 local -n fix_checksum_arr="$1"
162 old_cksum=${fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]}
William A. Kennington IIIa3a61482023-06-02 17:07:38 -0700163 ((fix_checksum_arr[IPMI_FRU_CHECKSUM_IDX]-=$(checksum fix_checksum_arr)))
164 ((fix_checksum_arr[IPMI_FRU_CHECKSUM_IDX]&=0xff))
William A. Kennington III1b34b592021-03-02 20:24:57 -0800165 printf 'Corrected %s checksum from 0x%02X -> 0x%02X\n' \
166 "$1" "${old_cksum}" "${fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]}" >&2
167}
168
169read_bytes() {
William A. Kennington IIIa3a61482023-06-02 17:07:38 -0700170 # shellcheck disable=SC2206
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700171 local busaddr=(${IPMI_FRU_EEPROM_BUSADDR["$1"]-})
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700172 local file="${IPMI_FRU_EEPROM_FILE["$1"]-$1}"
William A. Kennington III1b34b592021-03-02 20:24:57 -0800173 local offset="$2"
174 local size="$3"
175
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700176 if (( "${#busaddr[@]}" > 0)); then
177 echo "Reading ${busaddr[*]} at $offset for $size" >&2
William A. Kennington IIIa3a61482023-06-02 17:07:38 -0700178 # shellcheck disable=SC2046
179 i2ctransfer -f -y "${busaddr[0]}" $("${busaddr[2]}" "${busaddr[1]}" "$offset") "r$size"
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700180 else
181 echo "Reading $file at $offset for $size" >&2
182 dd if="$file" bs=1 count="$size" skip="$offset" 2>/dev/null | \
183 hexdump -v -e '1/1 "%d "'
184 fi
William A. Kennington III1b34b592021-03-02 20:24:57 -0800185}
186
187write_bytes() {
William A. Kennington IIIa3a61482023-06-02 17:07:38 -0700188 # shellcheck disable=SC2206
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700189 local busaddr=(${IPMI_FRU_EEPROM_BUSADDR["$1"]-})
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700190 local file="${IPMI_FRU_EEPROM_FILE["$1"]-$1}"
William A. Kennington III1b34b592021-03-02 20:24:57 -0800191 local offset="$2"
192 local -n bytes_arr="$3"
193
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700194 if (( "${#busaddr[@]}" > 0)); then
195 echo "Writing ${busaddr[*]} at $offset for ${#bytes_arr[@]}" >&2
William A. Kennington IIIa3a61482023-06-02 17:07:38 -0700196 # shellcheck disable=SC2046
197 i2ctransfer -f -y "${busaddr[0]}" $("${busaddr[2]}" "${busaddr[1]}" "$offset" "${#bytes_arr[@]}") "${bytes_arr[@]}"
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700198 else
199 local hexstr
200 hexstr="$(printf '\\x%x' "${bytes_arr[@]}")" || return
201 echo "Writing $file at $offset for ${#bytes_arr[@]}" >&2
William A. Kennington IIIa3a61482023-06-02 17:07:38 -0700202 # shellcheck disable=SC2059
203 printf "$hexstr" | dd of="$file" bs=1 seek="$offset" 2>/dev/null
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700204 fi
William A. Kennington III1b34b592021-03-02 20:24:57 -0800205}
206
207read_header() {
208 local eeprom="$1"
209 local -n header_arr="$2"
210
William A. Kennington IIIa3a61482023-06-02 17:07:38 -0700211 # shellcheck disable=SC2207
William A. Kennington III1b34b592021-03-02 20:24:57 -0800212 header_arr=($(read_bytes "$eeprom" 0 8)) || return
213 echo "Checking $eeprom FRU Header version" >&2
214 # FRU header is always version 1
215 (( header_arr[0] == 1 )) || return
216 echo "Checking $eeprom FRU Header checksum" >&2
217 local sum
218 sum="$(checksum header_arr)" || return
219 # Checksums should be valid
220 (( sum == 0 )) || return 10
221}
222
223read_area() {
224 local eeprom="$1"
225 local offset="$2"
226 local -n area_arr="$3"
227 local area_size="${4-0}"
228
229 offset=$((offset*8))
William A. Kennington IIIa3a61482023-06-02 17:07:38 -0700230 # shellcheck disable=SC2207
William A. Kennington III1b34b592021-03-02 20:24:57 -0800231 area_arr=($(read_bytes "$eeprom" "$offset" 8)) || return
232 echo "Checking $eeprom $offset FRU Area version" >&2
233 # FRU Area is always version 1
234 (( area_arr[0] == 1 )) || return
235 if (( area_size == 0 )); then
236 area_size=${area_arr[$IPMI_FRU_AREA_HEADER_SIZE_IDX]}
237 fi
William A. Kennington IIIa3a61482023-06-02 17:07:38 -0700238 # shellcheck disable=SC2207
William A. Kennington III1b34b592021-03-02 20:24:57 -0800239 area_arr=($(read_bytes "$eeprom" "$offset" $((area_size*8)))) || return
240 echo "Checking $eeprom $offset FRU Area checksum" >&2
241 local sum
242 sum="$(checksum area_arr)" || return
243 # Checksums should be valid
244 (( sum == 0 )) || return 10
245}
246
247ipmi_fru_init=1