#!/bin/sh
# broadcom-detect - reports the Debian packages supporting the Broadcom
# wireless adapters of the local system, in the manner of nvidia-detect.
#
# Copyright (C) 2026 Q4OS Team <devteam@q4os.org>
#
# This package is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

if [ "$1" = "-h" ] || [ "$1" = "--help" ] ; then
  echo "Usage: broadcom-detect [PCIID]..."
  echo "       Reports the Debian packages supporting the Broadcom wireless adapter"
  echo "       that is installed on the local system (or given as a PCIID parameter)."
  exit 0
fi

unset LC_ALL

IDLISTDIR="${BROADCOM_DETECT_IDS:-/usr/share/broadcom-detect}"
SYSFS="${BROADCOM_DETECT_SYSFS:-/sys}"
RET=1

PKG_WL="broadcom-sta-dkms"
PKG_BRCM="firmware-brcm80211"
PKG_B43="firmware-b43-installer"
PKG_B43LEGACY="firmware-b43legacy-installer"
if grep -qsiE '^ID(_LIKE)?=.*ubuntu' /etc/os-release ; then
  PKG_BRCM="linux-firmware" #ubuntu ships brcm firmware in linux-firmware
  #ubuntu's own bcmwl-kernel-source builds the same wl module from the same
  #broadcom sources, and is what its "Additional Drivers" tool installs. Neither
  #package declares a conflict on the other, so recommending ours on top would
  #leave two dkms sources building one module. Where it is already installed,
  #name it instead and report the driver the system actually uses.
  if dpkg-query -W -f='${Status}' bcmwl-kernel-source 2>/dev/null | grep -q '^install ok installed$' ; then
    PKG_WL="bcmwl-kernel-source"
  fi
fi

WL_ARCH_OK="0"
case "$( dpkg --print-architecture 2>/dev/null )" in
  amd64|i386) WL_ARCH_OK="1" ;;
esac

#in_list <list> <id> [id ...] .. true if any of the ids is a line of <list>.ids
in_list()
{
  LSTFILE="$IDLISTDIR/$1.ids"
  shift
  for XID in "$@" ; do
    if grep -qix "$XID" "$LSTFILE" 2>/dev/null ; then
      return 0
    fi
  done
  return 1
}

#true if this computer has an ethernet controller (class 0200) driven by b44, which
#the broadcom-sta-dkms package blacklists together with b43
b44_ethernet()
{
  if [ -z "$B44_CHECKED" ] ; then
    B44_CHECKED="1"
    B44_FOUND=""
    if ( lspci --version ) > /dev/null 2>&1 ; then
      for XETH in $( lspci -mn 2>/dev/null | awk '{ gsub("\"",""); if (($2 == "0200") && ($3 == "14e4")) { print toupper($3 $4) } }' ) ; do
        if in_list b44 "$XETH" ; then
          B44_FOUND="1"
          break
        fi
      done
    fi
  fi
  [ -n "$B44_FOUND" ]
}

#warn before wl is recommended on a machine whose wired nic needs b44
warn_b44()
{
  if b44_ethernet ; then
    echo "WARNING: The wired Ethernet controller of this computer uses the b44 driver, which the $PKG_WL package blacklists together with b43. Installing it disables the wired network connection as well."
  fi
}

recommend()
{
  echo "It is recommended to install the"
  for XPKG in "$@" ; do
    echo "    $XPKG"
  done
  if [ "$#" -gt "1" ] ; then
    echo "packages."
  else
    echo "package."
  fi
  RET=0
}

#BCM_DETECT <VVVVDDDD> [SSSSssss]
BCM_DETECT()
{
  IDS="$1"
  if [ -n "$2" ] ; then
    IDS="$1:$2 $1"
  fi

  in_list brcmfmac $IDS && FMAC="1" || FMAC="0"
  in_list brcmsmac $IDS && SMAC="1" || SMAC="0"
  in_list broadcom-sta $IDS && WL="1" || WL="0"
  in_list b43 $IDS && B43="1" || B43="0"
  in_list b43legacy $IDS && B43LEGACY="1" || B43LEGACY="0"
  in_list prefer-wl $IDS && PREFER_WL="1" || PREFER_WL="0"

  if [ "$FMAC" = "1" ] ; then
    echo "Your card is supported by the in-kernel brcmfmac driver."
    recommend "$PKG_BRCM"
  elif [ "$SMAC" = "1" ] ; then
    echo "Your card is supported by the in-kernel brcmsmac driver."
    if [ "$WL" = "1" ] ; then
      echo "Your card is also supported by the proprietary Broadcom STA driver (wl)."
    fi
    recommend "$PKG_BRCM"
  elif [ "$WL" = "1" ] && { [ "$B43" = "0" ] || [ "$PREFER_WL" = "1" ] ; } ; then
    if [ "$WL_ARCH_OK" = "1" ] ; then
      echo "Your card is supported by the proprietary Broadcom STA driver (wl)."
      if [ "$B43" = "1" ] ; then
        echo "Your card is also supported by the in-kernel b43 driver, but without 802.11n."
      fi
      warn_b44
      recommend "$PKG_WL"
    elif [ "$B43" = "1" ] ; then
      echo "The Broadcom STA driver (wl) is only available on amd64 and i386."
      echo "Your card is supported by the in-kernel b43 driver, but without 802.11n."
      recommend "$PKG_B43"
    else
      echo "Uh oh. Your card is only supported by the Broadcom STA driver (wl), which is only available on amd64 and i386."
    fi
  elif [ "$B43" = "1" ] ; then
    echo "Your card is supported by the in-kernel b43 driver."
    if [ "$WL" = "1" ] ; then
      echo "Your card is also supported by the proprietary Broadcom STA driver (wl)."
    fi
    if [ "$B43LEGACY" = "1" ] ; then
      echo "Older revisions of your card use the in-kernel b43legacy driver."
      recommend "$PKG_B43" "$PKG_B43LEGACY"
    else
      recommend "$PKG_B43"
    fi
  elif [ "$B43LEGACY" = "1" ] ; then
    echo "Your card is supported by the in-kernel b43legacy driver."
    recommend "$PKG_B43LEGACY"
  else
    echo "Uh oh. Your card is not supported by any driver known to broadcom-detect."
  fi
}

#format an id for display ("14E44359" -> "14e4:4359")
fmt_id()
{
  echo "$1" | sed -r 's/(....)(....)/\1:\2/' | tr 'A-F' 'a-f'
}

#print the upper-cased 4-digit hex id stored in a sysfs file ("0x02d0" -> "02D0")
sysfs_id()
{
  sed 's/^0x//' "$1" 2>/dev/null | tr 'a-f' 'A-F'
}

if [ -z "$1" ] ; then

  if ! ( lspci --version ) > /dev/null 2>&1 ; then
    echo "ERROR: The 'lspci' command was not found. Please install the 'pciutils' package." >&2
    exit 1
  fi

  #pci network controllers (class 0280) from broadcom
  BCM_PCI=$( lspci -mn | awk '{ gsub("\"",""); if (($2 == "0280") && ($3 == "14e4")) { print $1 } }' )

  #sdio and usb functions handled by brcmfmac
  BCM_OTHER=""
  for XDEV in "$SYSFS"/bus/sdio/devices/* ; do
    [ -f "$XDEV/vendor" ] || continue
    XID="$( sysfs_id "$XDEV/vendor" )$( sysfs_id "$XDEV/device" )"
    if in_list brcmfmac "$XID" ; then
      BCM_OTHER="$BCM_OTHER sdio:$XID"
    fi
  done
  for XDEV in "$SYSFS"/bus/usb/devices/* ; do
    [ -f "$XDEV/idVendor" ] || continue
    XID="$( sysfs_id "$XDEV/idVendor" )$( sysfs_id "$XDEV/idProduct" )"
    if in_list brcmfmac "$XID" ; then
      BCM_OTHER="$BCM_OTHER usb:$XID"
    fi
  done

  if [ -z "$BCM_PCI" ] && [ -z "$BCM_OTHER" ] ; then
    echo "No Broadcom wireless device detected."
    exit 0
  fi

  echo "Detected Broadcom wireless devices:"
  for XSLOT in $BCM_PCI ; do
    lspci -nn -s $XSLOT
  done
  for XDEV in $BCM_OTHER ; do
    echo "${XDEV%%:*} device [$( fmt_id "${XDEV#*:}" )]"
  done

  for XSLOT in $BCM_PCI ; do
    printf "\nChecking card: %s\n" "$( lspci -s $XSLOT | cut -d: -f3- | sed 's/^ *//' )"
    XVD="$( lspci -mn -s $XSLOT | awk '{ gsub("\"",""); print toupper($3 $4) }' )"
    XSUB="$( lspci -vmmn -s $XSLOT | awk -F'\t' '/^SVendor:/{ v=$2 } /^SDevice:/{ d=$2 } END { if (v != "") print toupper(v d) }' )"
    BCM_DETECT "$XVD" "$XSUB"
  done
  for XDEV in $BCM_OTHER ; do
    printf "\nChecking %s device [%s]\n" "${XDEV%%:*}" "$( fmt_id "${XDEV#*:}" )"
    BCM_DETECT "${XDEV#*:}"
  done

else

  for XARG in "$@" ; do
    PCIID="$( echo "$XARG" | sed -rn 's/^(14e4)?:?([0-9a-fA-F]{4})$/14e4\2/ip' | tr 'a-f' 'A-F' )"
    if [ -z "$PCIID" ] ; then
      echo "Error parsing PCI ID '$XARG'."
      exit 2
    fi
    echo "Checking driver support for PCI ID [$( fmt_id "$PCIID" )]"
    BCM_DETECT "$PCIID"
  done

fi

exit $RET
