blob: d02eeab478c5da947ca9081ba02c2e7214785661 [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
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070026offset_1bw() {
27 local addr="$1"
28 local off="$2"
29 local extra="${3-0}"
30 echo w$((1+extra))@$addr $(( off & 0xff ))
31}
32
33offset_2bw() {
34 local addr="$1"
35 local off="$2"
36 local extra="${3-0}"
37 echo w$((2+extra))@$addr $(( (off >> 8) & 0xff )) $(( off & 0xff ))
38}
39
William A. Kennington III1b34b592021-03-02 20:24:57 -080040of_name_to_eeproms() {
41 local names
42 if ! names="$(grep -xl "$1" /sys/bus/i2c/devices/*/of_node/name)"; then
43 echo "Failed to find eeproms with of_name '$1'" >&2
44 return 1
45 fi
46 echo "$names" | sed 's,/of_node/name$,/eeprom,'
47}
48
49of_name_to_eeprom() {
50 local eeproms
51 eeproms="$(of_name_to_eeproms "$1")" || return
52 if (( "$(echo "$eeproms" | wc -l)" != 1 )); then
53 echo "Got more than one eeprom for '$1':" $eeproms >&2
54 return 1
55 fi
56 echo "$eeproms"
57}
58
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070059# Each element is a `filename`
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -070060declare -A IPMI_FRU_EEPROM_FILE=()
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070061# Each element is a `bus` + `addr` + `offset_bytes_func`
62declare -A IPMI_FRU_EEPROM_BUSADDR=()
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -070063
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070064ipmi_fru_device_alloc() {
William A. Kennington IIIa8106142021-10-12 23:25:05 -070065 local fdn="$1"
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070066 local idx="$2"
William A. Kennington IIIa8106142021-10-12 23:25:05 -070067
68 local json
69 json="$(busctl -j call xyz.openbmc_project.FruDevice \
70 /xyz/openbmc_project/FruDevice/"$fdn" org.freedesktop.DBus.Properties \
71 GetAll s xyz.openbmc_project.FruDevice)" || return 80
72
73 local jqq='.data[0] | (.BUS.data | tostring) + " " + (.ADDRESS.data | tostring)'
74 local busaddr
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070075 busaddr=($(echo "$json" | jq -r "$jqq")) || return
William A. Kennington IIIa8106142021-10-12 23:25:05 -070076
77 # FRU 0 is hardcoded and FruDevice does not report the correct bus for it
78 # Hardcode a workaround for this specifically known bus
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070079 if (( busaddr[0] == 0 && busaddr[1] == 0 )); then
80 IPMI_FRU_EEPROM_FILE["$idx"]=/etc/fru/baseboard.fru.bin
William A. Kennington IIIa8106142021-10-12 23:25:05 -070081 else
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070082 local offset_bw=offset_2bw
83 local rsp
84 rsp=$(i2ctransfer -f -y ${busaddr[0]} $(offset_1bw ${busaddr[1]} 0) r1) || return
85 # FRUs never start with 0xff bytes, so we can figure out addressing mode
86 if (( rsp != 0xff )); then
87 offset_bw=offset_1bw
William A. Kennington IIIa8106142021-10-12 23:25:05 -070088 fi
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070089 IPMI_FRU_EEPROM_BUSADDR["$idx"]="${busaddr[*]} $offset_bw"
William A. Kennington IIIa8106142021-10-12 23:25:05 -070090 fi
91}
92
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -070093ipmi_fru_alloc() {
94 local name="$1"
95 local -n ret="$2"
96
97 # Pick the first free index to return as the allocated entry
98 for (( ret = 0; ret < "${#IPMI_FRU_EEPROM_FILE[@]}"; ++ret )); do
William A. Kennington IIIa7532a62021-10-15 15:55:39 -070099 [ -n "${IPMI_FRU_EEPROM_FILE[@]+1}" ] || \
100 [ -n "${IPMI_FRU_EEPROM_BUSADDR[@]+1}" ]|| break
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700101 done
102
103 if [[ "$name" =~ ^of-name:(.*)$ || "$name" =~ ^([^:]*)$ ]]; then
104 local ofn="${BASH_REMATCH[1]}"
105 local file
106 file="$(of_name_to_eeprom "$ofn")" || return
107 IPMI_FRU_EEPROM_FILE["$ret"]="$file"
William A. Kennington IIIa8106142021-10-12 23:25:05 -0700108 elif [[ "$name" =~ ^frudev-name:(.*)$ ]]; then
109 local fdn="${BASH_REMATCH[1]}"
110 local start=$SECONDS
111 local file
112 while (( SECONDS - start < 60 )); do
113 local rc=0
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700114 ipmi_fru_device_alloc "$fdn" "$ret" || rc=$?
William A. Kennington IIIa8106142021-10-12 23:25:05 -0700115 (( rc == 0 )) && break
116 # Immediately return any errors, 80 is special to signify retry
117 (( rc != 80 )) && return $rc
118 sleep 1
119 done
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700120 else
121 echo "Invalid IPMI FRU eeprom specification: $name" >&2
122 return 1
123 fi
124}
125
126ipmi_fru_free() {
127 unset 'IPMI_FRU_EEPROM_FILE[$1]'
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700128 unset 'IPMI_FRU_EEPROM_BUSADDR[$1]'
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700129}
130
William A. Kennington III1b34b592021-03-02 20:24:57 -0800131checksum() {
132 local -n checksum_arr="$1"
133 local checksum=0
134 for byte in "${checksum_arr[@]}"; do
135 checksum=$((checksum + byte))
136 done
137 echo $((checksum & 0xff))
138}
139
140fix_checksum() {
141 local -n fix_checksum_arr="$1"
142 old_cksum=${fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]}
143 ((fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]-=$(checksum fix_checksum_arr)))
144 ((fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]&=0xff))
145 printf 'Corrected %s checksum from 0x%02X -> 0x%02X\n' \
146 "$1" "${old_cksum}" "${fix_checksum_arr[$IPMI_FRU_CHECKSUM_IDX]}" >&2
147}
148
149read_bytes() {
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700150 local busaddr=(${IPMI_FRU_EEPROM_BUSADDR["$1"]-})
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700151 local file="${IPMI_FRU_EEPROM_FILE["$1"]-$1}"
William A. Kennington III1b34b592021-03-02 20:24:57 -0800152 local offset="$2"
153 local size="$3"
154
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700155 if (( "${#busaddr[@]}" > 0)); then
156 echo "Reading ${busaddr[*]} at $offset for $size" >&2
157 i2ctransfer -f -y ${busaddr[0]} $(${busaddr[2]} ${busaddr[1]} $offset) r$size
158 else
159 echo "Reading $file at $offset for $size" >&2
160 dd if="$file" bs=1 count="$size" skip="$offset" 2>/dev/null | \
161 hexdump -v -e '1/1 "%d "'
162 fi
William A. Kennington III1b34b592021-03-02 20:24:57 -0800163}
164
165write_bytes() {
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700166 local busaddr=(${IPMI_FRU_EEPROM_BUSADDR["$1"]-})
William A. Kennington IIIa3a8ca92021-10-12 23:18:52 -0700167 local file="${IPMI_FRU_EEPROM_FILE["$1"]-$1}"
William A. Kennington III1b34b592021-03-02 20:24:57 -0800168 local offset="$2"
169 local -n bytes_arr="$3"
170
William A. Kennington IIIa7532a62021-10-15 15:55:39 -0700171 if (( "${#busaddr[@]}" > 0)); then
172 echo "Writing ${busaddr[*]} at $offset for ${#bytes_arr[@]}" >&2
173 i2ctransfer -f -y ${busaddr[0]} $(${busaddr[2]} ${busaddr[1]} $offset ${#bytes_arr[@]}) ${bytes_arr[@]}
174 else
175 local hexstr
176 hexstr="$(printf '\\x%x' "${bytes_arr[@]}")" || return
177 echo "Writing $file at $offset for ${#bytes_arr[@]}" >&2
178 printf "$hexstr" | dd of="$file" bs=1 seek=$offset 2>/dev/null
179 fi
William A. Kennington III1b34b592021-03-02 20:24:57 -0800180}
181
182read_header() {
183 local eeprom="$1"
184 local -n header_arr="$2"
185
186 header_arr=($(read_bytes "$eeprom" 0 8)) || return
187 echo "Checking $eeprom FRU Header version" >&2
188 # FRU header is always version 1
189 (( header_arr[0] == 1 )) || return
190 echo "Checking $eeprom FRU Header checksum" >&2
191 local sum
192 sum="$(checksum header_arr)" || return
193 # Checksums should be valid
194 (( sum == 0 )) || return 10
195}
196
197read_area() {
198 local eeprom="$1"
199 local offset="$2"
200 local -n area_arr="$3"
201 local area_size="${4-0}"
202
203 offset=$((offset*8))
204 area_arr=($(read_bytes "$eeprom" "$offset" 8)) || return
205 echo "Checking $eeprom $offset FRU Area version" >&2
206 # FRU Area is always version 1
207 (( area_arr[0] == 1 )) || return
208 if (( area_size == 0 )); then
209 area_size=${area_arr[$IPMI_FRU_AREA_HEADER_SIZE_IDX]}
210 fi
211 area_arr=($(read_bytes "$eeprom" "$offset" $((area_size*8)))) || return
212 echo "Checking $eeprom $offset FRU Area checksum" >&2
213 local sum
214 sum="$(checksum area_arr)" || return
215 # Checksums should be valid
216 (( sum == 0 )) || return 10
217}
218
219ipmi_fru_init=1
220return 0 2>/dev/null
221echo "ipmi-fru is a library, not executed directly" >&2
222exit 1