meta-google: ipmi-fru: Direct read support

We can't dynamically bind to the at24 driver if the eeprom is not in
the kernel's device tree. Since this support was intended to be used
for dynamic FRUs, it is broken when the kernel has the entries removed.

Change-Id: I99c774191c22d67e518fe9435b1446b80efb5600
Signed-off-by: William A. Kennington III <wak@google.com>
diff --git a/meta-google/recipes-google/ipmi/ipmi-fru-sh/lib.sh b/meta-google/recipes-google/ipmi/ipmi-fru-sh/lib.sh
index 940be7f..d02eeab 100644
--- a/meta-google/recipes-google/ipmi/ipmi-fru-sh/lib.sh
+++ b/meta-google/recipes-google/ipmi/ipmi-fru-sh/lib.sh
@@ -23,6 +23,20 @@
 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
@@ -42,10 +56,14 @@
   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_to_file() {
+ipmi_fru_device_alloc() {
   local fdn="$1"
+  local idx="$2"
 
   local json
   json="$(busctl -j call xyz.openbmc_project.FruDevice \
@@ -54,20 +72,21 @@
 
   local jqq='.data[0] | (.BUS.data | tostring) + " " + (.ADDRESS.data | tostring)'
   local busaddr
-  busaddr="$(echo "$json" | jq -r "$jqq")" || return
+  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' ]; then
-    echo "/etc/fru/baseboard.fru.bin"
+  if (( busaddr[0] == 0 && busaddr[1] == 0 )); then
+    IPMI_FRU_EEPROM_FILE["$idx"]=/etc/fru/baseboard.fru.bin
   else
-    local dev="$(printf '%d-%04x' $busaddr)"
-    local efile="/sys/bus/i2c/devices/$dev/eeprom"
-    # The at24 eeprom driver is not guaranteed to be bound
-    if [ ! -e "$efile" ]; then
-      echo "$dev" >/sys/bus/i2c/drivers/at24/bind || return
+    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
-    echo "$efile"
+    IPMI_FRU_EEPROM_BUSADDR["$idx"]="${busaddr[*]} $offset_bw"
   fi
 }
 
@@ -77,7 +96,8 @@
 
   # 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}" ] || break
+    [ -n "${IPMI_FRU_EEPROM_FILE[@]+1}" ] || \
+      [ -n "${IPMI_FRU_EEPROM_BUSADDR[@]+1}" ]|| break
   done
 
   if [[ "$name" =~ ^of-name:(.*)$ || "$name" =~ ^([^:]*)$ ]]; then
@@ -91,13 +111,12 @@
     local file
     while (( SECONDS - start < 60 )); do
       local rc=0
-      file="$(ipmi_fru_device_to_file "$fdn")" || rc=$?
+      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
-    IPMI_FRU_EEPROM_FILE["$ret"]="$file"
   else
     echo "Invalid IPMI FRU eeprom specification: $name" >&2
     return 1
@@ -106,6 +125,7 @@
 
 ipmi_fru_free() {
   unset 'IPMI_FRU_EEPROM_FILE[$1]'
+  unset 'IPMI_FRU_EEPROM_BUSADDR[$1]'
 }
 
 checksum() {
@@ -127,24 +147,36 @@
 }
 
 read_bytes() {
+  local busaddr=(${IPMI_FRU_EEPROM_BUSADDR["$1"]-})
   local file="${IPMI_FRU_EEPROM_FILE["$1"]-$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 "'
+  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"
 
-  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
+  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() {