#!/bin/sh
set -e

# This is a nasty kluge, but it seems to work. Better check the output when
# upgrading to a new release of the nvidia driver, though.

if [ "$#" -ne 4 ]; then
  >&2 printf 'USAGE: %s modulename packagename README.txt nv-kernel.o\n' "$0"
  exit 1
fi

device_ids() {
  local modname="$1"; shift
  local pkgname="$1"; shift
  local readme="$1"; shift
  local object="$1"; shift

  local ret=1

  local symbols="$(mktemp)"
  local readme_list="$(mktemp)"
  local object_list="$(mktemp)"
  local diff="$(mktemp)"

  sed -nr '/^Appendix .\. Supported NVIDIA /,/legacy/ {
    s/.*   ([0-9a-fA-F]{4}) ([0-9a-fA-F]{4}) ([0-9a-fA-F]{4})(   .*|$)/\1 \2 \3/p
  }' "$readme" | tr A-F a-f | sort | uniq >"$readme_list"

  sed -nr '/^Appendix .\. Supported NVIDIA /,/legacy/ {
    s/.*   ([0-9a-fA-F]{4}) ([0-9a-fA-F]{4})(   .*|$)/\1 \2/p
  }' "$readme" | tr A-F a-f | sort | uniq >"$readme_list"

  sed -nr '/^Appendix .\. Supported NVIDIA /,/legacy/ {
    s/.*   ([0-9a-fA-F]{4})(   .*|$)/\1/p
  }' "$readme" | tr A-F a-f | sort | uniq >>"$readme_list"

  local readme_length="$(grep -Ec . "$readme_list")"

  objdump --section=.rodata --syms "$object" |
  sed -nr '/SYMBOL TABLE/,/^$/ {
    s/^([0-9a-f]+)\s+l\s+O\s+\S+\s+([0-9a-f]+)\s+\S+.*/\2 \1/p
  }' |
  sort -nr >"$symbols" # The list is probably among the larger symbols.

  while read length start; do
    [ "$((0x$length))" -gt 0 ] || continue

    objdump --section=.rodata --full-contents \
      --start-address="0x$start" \
      --stop-address="$((0x$start+0x$length))" "$object" |
    sed -nr 's/^ [0-9a-f]+ ([0-9a-f]{2})([0-9a-f]{2}).*/\2\1/p' |
    sort | uniq | (grep -vx 0000 || :) >"$object_list"

    local object_length="$(grep -Ec . "$object_list")"

    diff -u "$readme_list" "$object_list" | tail -n +3 >"$diff"
    local num_deletions="$(grep -Ec '^-' "$diff")"
    local num_additions="$(grep -Ec '^\+' "$diff")"

    # Some thresholds for now.
    if [ "$num_deletions" -eq 0 ] &&
       [ "$num_additions" -le "$(($readme_length*3/2))" ]; then
      >&2 printf 'DEBUG: readme:%d object:%d deletions:%d additions:%d\n' \
        "$readme_length" "$object_length" "$num_deletions" "$num_additions"
      ret=0
      break
    fi
  done <"$symbols"

  if [ "$ret" -eq 0 ]; then
    printf '%s\n' '# List generated by nvidia_supported. Do not edit manually.'

    while read id; do
      printf 'alias pci:v%08Xd%08Xsv*sd*bc03sc*i* %s %s\n' \
        0x10de "0x$id" "$modname" "$pkgname"
    done <"$object_list"

  else
    >&2 printf '%s\n' 'Failed to find the list. Using README.txt to get the list'

    # We failed to extract the ids from the blob. Use the ones in README.txt
    # as a fallback
    printf '%s\n' '# List generated by nvidia_supported. Do not edit manually.'
    while read id sid; do
      if [ -z "$sid" ]; then
        printf 'alias pci:v%08Xd%08Xsv*sd*bc03sc*i* %s %s\n' \
          0x10de "0x$id" "$modname" "$pkgname"
      else
        printf 'alias pci:v%08Xd%08Xsv*sd%08Xbc03sc*i* %s %s\n' \
          0x10de "0x$id" "0x$sid" "$modname" "$pkgname"
      fi
    done <"$readme_list"

    ret=0
  fi

  rm -f "$symbols" "$readme_list" "$object_list" "$diff"

  return "$ret"
}

device_ids "$@"

# vim:set et sw=2 sts=2:
