git: 96190b4fef3b - main - LinuxKPI based WiFi drivers: scripts to extract fwget(8) and port details

From: Bjoern A. Zeeb <bz_at_FreeBSD.org>
Date: Thu, 17 Oct 2024 17:38:44 UTC
The branch main has been updated by bz:

URL: https://cgit.FreeBSD.org/src/commit/?id=96190b4fef3b4a0cc3ca0606b0c4e3e69a5e6717

commit 96190b4fef3b4a0cc3ca0606b0c4e3e69a5e6717
Author:     Bjoern A. Zeeb <bz@FreeBSD.org>
AuthorDate: 2024-04-28 20:50:12 +0000
Commit:     Bjoern A. Zeeb <bz@FreeBSD.org>
CommitDate: 2024-10-17 17:26:28 +0000

    LinuxKPI based WiFi drivers: scripts to extract fwget(8) and port details
    
    Add a "zzz_fw_ports_fwget.sh" script to each LinuxKPI based wireless
    driver which in essential are all the same and in detail all different.
    The scripts have been referenced in fwget(8) sources since d33f5a0afa54b
    but were never committed.
    
    The scripts do the full job compared to `single-line-scripts` I tried to
    use before to ease maintainance life.  Some use hacks like calling cpp
    and extracting bits from the output to piece them together over multiple
    files.  It will be left as an exercise for the future to see if what was
    done (a longer while ago) for iwlwifi(4) would be a good idea for some
    other drivers too, to have a FreeBSD-specific sysctl to export some of
    the accumulated data in an easily processable way.
    The scripts are written in the "perl spirit" -- "to get the job done" --
    and not to be nice or neat or efficient.  For that we do not need them
    often enough or in any critical path.  People are welcome to improve
    them if they feel like.
    I've used them for two version updates now and even if ports enforce
    some other (manual) editing to keep support for multiple branches for
    now they worked extremly well.
    
    For the most the scripts extract 2 parts: PCI IDs and firmware name;
    then they add "flavor"s to both and put the information together.
    
    That output is then separated into:
    - fwget(8) lines of PCI ID to port/package
      wifi-firmware-${name}-kmod-${flavor} mappings and
    - distfiles per flavor for the ports.
    - For iwlwififw(4) we also generate the tables for the man page
      (and the wiki) and hopefully the .Sh HARDWARE section for iwlwifi.4
      soon too.
    
    Depending on driver various other checks are done, e.g.,
    - does the PCI ID have one or more firmware files/flavors associated,
    - does the referenced firmware exist in the linux-firmware.git repo,
    - are there duplicates,
    - find the latest version of the firmware API.
    
    Sponsored by:   The FreeBSD Foundation
    Suggested by:   imp (to have automation in D44918)
    MFC after:      3 days
---
 sys/contrib/dev/athk/ath10k/zzz_fw_ports_fwget.sh  | 286 ++++++++++++++++
 sys/contrib/dev/athk/ath11k/zzz_fw_ports_fwget.sh  | 317 +++++++++++++++++
 sys/contrib/dev/athk/ath12k/zzz_fw_ports_fwget.sh  | 310 +++++++++++++++++
 sys/contrib/dev/iwlwifi/zzz_fw_ports_fwget.sh      | 374 +++++++++++++++++++++
 .../dev/mediatek/mt76/zzz_fw_ports_fwget.sh        | 292 ++++++++++++++++
 sys/contrib/dev/rtw88/zzz_fw_ports_fwget.sh        | 145 ++++++++
 sys/contrib/dev/rtw89/zzz_fw_ports_fwget.sh        | 152 +++++++++
 7 files changed, 1876 insertions(+)

diff --git a/sys/contrib/dev/athk/ath10k/zzz_fw_ports_fwget.sh b/sys/contrib/dev/athk/ath10k/zzz_fw_ports_fwget.sh
new file mode 100644
index 000000000000..71a11a890a48
--- /dev/null
+++ b/sys/contrib/dev/athk/ath10k/zzz_fw_ports_fwget.sh
@@ -0,0 +1,286 @@
+#!/bin/sh
+#-
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2024 The FreeBSD Foundation
+#
+# This software was developed by Björn Zeeb
+# under sponsorship from the FreeBSD Foundation.
+#
+# This is neither efficient nor elegant but we need it few times
+# a year and it does the job.
+#
+#
+# USAGE: please check out the correct tag/hash for ports in the
+# linux-firmware.git repository you point this script to.
+#
+
+set -e
+
+DRIVER=ath10k
+CHECKFILE=qmi_wlfw_v01.c
+
+################################################################################
+#
+# Check pre-reqs
+#
+if [ $# -ne 1 ]; then
+	printf "USAGE: %s /path/to/linux-firmware.git\n" $0 >&2
+	exit 1
+fi
+
+if [ ! -e ${CHECKFILE} ]; then
+	printf "ERROR: run from %s driver directory; no %s.c here\n" ${DRIVER} ${CHECKFILE} >&2
+	exit 1
+fi
+
+LFWDIR=${1}
+if test ! -d ${LFWDIR} -o ! -e ${LFWDIR}/WHENCE; then
+	printf "ERROR: cannot find linux-firmware.git at '%s'\n" ${LFWDIR} >&2
+	exit 1
+fi
+
+################################################################################
+#
+# Helper functions.
+#
+# This uses a hack (cpp) to expand some macros for us and parses out the result
+# which is the PCI device ID or the firmware directory for that.
+# Checking MODULE_FIRMWARE was pointless as it had too many "dead" entries:
+# NOTICE: no firmware file found for 'ath10k/QCA6174/hw2.1/firmware-4.bin'
+# NOTICE: no firmware file found for 'ath10k/QCA6174/hw3.0/firmware-5.bin'
+# NOTICE: no firmware file found for 'ath10k/QCA9887/hw1.0/board-2.bin'
+# NOTICE: no firmware file found for 'ath10k/QCA988X/hw2.0/board-2.bin'
+# NOTICE: no firmware file found for 'ath10k/QCA988X/hw2.0/firmware-2.bin'
+# NOTICE: no firmware file found for 'ath10k/QCA988X/hw2.0/firmware-3.bin'
+#
+list_fw()
+{
+	# List of already seen flavor (firmware directory).
+	sfwl=""
+
+	# List of "supported" device IDs (ignoring Ubiquity).
+	devidl=$(cpp pci.c 2> /dev/null | awk '/PCI_VDEVICE\(ATHEROS,/ { gsub("^.*, \\(", ""); gsub("\\)\\) },$", ""); print tolower($0); }')
+
+	# List of (device ID) -> (firware directory) mappings.
+	cpp core.c 2> /dev/null | egrep -E '\.(dev_id|dir) = ' | awk '{ if (/dev_id/) { printf "%s", $0; } else { print; } }' | grep -v 'dev_id = 0,' | sort | uniq | \
+	awk '{
+		gsub("^.*\\(", "");
+		gsub("),.* = ", "\t");
+		gsub(",$", "");
+		gsub(/"/, "");
+		gsub(" ", "");
+		print;
+	}' | \
+	while read did fwd; do
+
+		x=""
+		for d in ${devidl}; do
+			if test "${did}" == "${d}"; then
+				x="${d}"
+				break
+			fi
+		done
+		if test "${x}" == ""; then
+			# Device ID not in the list of PCI IDs we support.
+			# At least the Ubiquity one we hit here.
+			#printf "Device ID %s (%s) not in PCI ID list; skipping\n" ${did} ${fwd} >&2
+			continue
+		fi
+
+		if test ! -d ${LFWDIR}/${fwd}; then
+			# Leave this on as it MUST not happen.
+			printf "Firmware dir %s (for %s) does not exist; skipping\n" ${fwd} ${did} >&2
+			continue
+		fi
+
+		flav=$(echo "${fwd}" | awk -v drv=${DRIVER} '{
+			# Ports FLAVOR names are [a-z0-9_].  If needed add more mangling magic here.
+			gsub("^" drv "/", "");
+			gsub("/", "_");
+			gsub("\\.", "");
+			print tolower($0);
+		}')
+
+		# Print this first or otherwise if two device IDs have the same firmware
+		# we may not see that.
+		echo "FWGET ${did} ${flav}"
+
+		x=""
+		for zf in ${sfwl}; do
+			if test "${zf}" == "${flav}"; then
+				x="${zf}"
+				break
+			fi
+		done
+		if test "${x}" != ""; then
+			#printf "Flavor %s (firmware directory %s) already seen; skipping\n" ${flav} ${fwd} >&2
+			continue
+		fi
+		sfwl="${sfwl} ${flav}"
+
+		#echo "==> ${did} -> ${fwd} -> ${flav}"
+
+		lx=$(cd ${LFWDIR} && find ${fwd} -type f \! -name "*sdio*" -a \! -name "*.txt" -print)
+
+		# Get a count so we can automatically add \\ apart from the last line.
+		fn=$(echo "${lx}" | wc -w | awk '{ print $1 }')
+
+		#echo "==> ${flav} :: ${fn} :: ${lx}" >&2
+
+		if test ${fn} -gt 0; then
+
+			echo "FWS ${flav}"
+			echo "DISTFILES_${flav}= \\"
+			for fz in ${lx}; do echo "${fz}"; done | \
+			awk -v fn=$fn -v fwg=${flav} -v drv=${DRIVER} '{
+				if (FNR == fn) { x="" } else { x=" \\" };
+				gsub("^" drv "/", "${FWSUBDIR}/");
+				printf "\t%s${DISTURL_SUFFIX}%s\n", $0, x;
+			}'
+
+			# Check for "lic" files.
+			lx=$(cd ${LFWDIR} && find ${fwd} -type f \! -name "*sdio*" -a -name "*.txt" -print)
+
+			# Get a count so we can automatically add \\ apart from the last line.
+			fn=$(echo "${lx}" | wc -w | awk '{ print $1 }')
+
+			if test ${fn} -gt 0; then
+				echo "FWL ${flav}"
+				echo "DISTFILES_${flav}_lic= \\"
+				for fz in ${lx}; do echo "${fz}"; done | \
+				awk -v fn=$fn -v fwg=${flav} -v drv=${DRIVER} '{
+					if (FNR == fn) { x="" } else { x=" \\" };
+					gsub("^" drv "/", "${FWSUBDIR}/");
+					printf "\t%s${DISTURL_SUFFIX}%s\n", $0, x;
+				}'
+			fi
+		fi
+	done
+}
+
+################################################################################
+#
+# Generate the PORTS file template.
+#
+
+fwsl=$(list_fw | grep ^FWS | awk '{ print $2 }')
+# Get a count so we can automatically add \\ apart from the last line.
+fn=$(echo "${fwsl}" | wc -w | awk '{ print $1 }')
+
+if test ${fn} -gt 0; then
+
+	portsfile=$(mktemp -p /tmp ${DRIVER}-fwport.XXXXXX)
+
+	:> ${portsfile}
+	(
+	echo "FWSUBS= \\"
+	for sz in ${fwsl}; do echo "${sz}"; done | \
+	awk -v fn=$fn '{
+		if (FNR == fn) { x="" } else { x=" \\" };
+		printf "\t%s%s\n", $0, x;
+	}'
+
+	echo
+	list_fw | grep -v ^FWS | grep -v ^FWL | grep -v ^FWGET
+
+	echo
+	echo "DISTFILES_\${FWDRV}= \\"
+	for sz in ${fwsl}; do echo "${sz}"; done | \
+	awk -v fn=$fn '{
+		if (FNR == fn) { x="" } else { x=" \\" };
+		printf "\t${DISTFILES_%s}%s\n", $0, x;
+	}'
+
+	fwsl=$(list_fw | grep ^FWL | awk '{ print $2 }')
+	# Get a count so we can automatically add \\ apart from the last line.
+	fn=$(echo "${fwsl}" | wc -w | awk '{ print $1 }')
+	if test ${fn} -gt 0; then
+		echo "DISTFILES_\${FWDRV}_lic= \\"
+		for sz in ${fwsl}; do echo "${sz}"; done | \
+		awk -v fn=$fn '{
+			if (FNR == fn) { x="" } else { x=" \\" };
+			printf "\t${DISTFILES_%s_lic}%s\n", $0, x;
+		}'
+	else
+		echo "DISTFILES_\${FWDRV}_lic="
+	fi
+
+	) >> ${portsfile}
+
+	printf "INFO: wifi-firmware-%s-kmod template at %s\n" ${DRIVER} ${portsfile} >&2
+fi
+
+################################################################################
+#
+# Generate the fwget(8) case pattern table (PCI device ID -> fw port flavor).
+#
+
+fwgetfile=$(mktemp -p /tmp ${DRIVER}-fwget.XXXXXX)
+:> ${fwgetfile}
+
+fwsl=$(list_fw | grep ^FWGET | sort)
+# Get a count so we can automatically add \\ apart from the last line.
+fn=$(echo "${fwsl}" | grep -c FWGET | awk '{ print $1 }')
+
+if test ${fn} -gt 0; then
+
+	# We need to check for same ID with multiple firmware.
+	# The list ist sorted by ID so duplicates are next to each other.
+	cs=$(echo "${fwsl}" | awk '{ print $2 }' | uniq -c | awk '{ print $1 }')
+
+	#echo "==> cs=${cs}" >&2
+
+	for n in ${cs}; do
+
+		# Skip FWGET
+		fwsl=${fwsl#*[[:space:]]}
+		# get device ID
+		did=${fwsl%%[[:space:]]*}
+		fwsl=${fwsl#*[[:space:]]}
+		# get flavor
+		flav=${fwsl%%[[:space:]]*}
+		fwsl=${fwsl#*[[:space:]]}
+
+		# printf "===> did %s flav %s\n" ${did} ${flav} >&2
+
+		if test ${n} -eq 1; then
+			echo "${did} ${flav}" | awk -v drv=${DRIVER} '{
+				printf "\t%s)\taddpkg \"wifi-firmware-%s-kmod-%s\"; return 1 ;;\n",
+				    tolower($1), drv, tolower($2);
+			}' >> ${fwgetfile}
+		else
+			echo "${did} ${flav}" | awk -v drv=${DRIVER} '{
+				printf "\t%s)\taddpkg \"wifi-firmware-%s-kmod-%s\"\n",
+				    tolower($1), drv, tolower($2);
+			}' >> ${fwgetfile}
+
+			i=1
+			while test ${i} -lt ${n}; do
+				# Skip FWGET
+				fwsl=${fwsl#*[[:space:]]}
+				# get device ID
+				did=${fwsl%%[[:space:]]*}
+				fwsl=${fwsl#*[[:space:]]}
+				# get flavor
+				flav=${fwsl%%[[:space:]]*}
+				fwsl=${fwsl#*[[:space:]]}
+
+				#printf "===> did %s flav %s\n" ${did} ${flav} >&2
+
+				echo "${did} ${flav}" | awk -v drv=${DRIVER} '{
+					printf "\t\taddpkg \"wifi-firmware-%s-kmod-%s\"\n",
+					    drv, tolower($2);
+				}' >> ${fwgetfile}
+
+				i=$((i + 1))
+			done
+
+			printf "\t\treturn 1 ;;\n" >> ${fwgetfile}
+		fi
+	done
+fi
+
+printf "INFO: fwget pci_network_qca %s template at %s\n" ${DRIVER} ${fwgetfile} >&2
+
+# end
diff --git a/sys/contrib/dev/athk/ath11k/zzz_fw_ports_fwget.sh b/sys/contrib/dev/athk/ath11k/zzz_fw_ports_fwget.sh
new file mode 100644
index 000000000000..9de14ed45442
--- /dev/null
+++ b/sys/contrib/dev/athk/ath11k/zzz_fw_ports_fwget.sh
@@ -0,0 +1,317 @@
+#!/bin/sh
+#-
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2024 The FreeBSD Foundation
+#
+# This software was developed by Björn Zeeb
+# under sponsorship from the FreeBSD Foundation.
+#
+# This is neither efficient nor elegant but we need it few times
+# a year and it does the job.
+#
+#
+# USAGE: please check out the correct tag/hash for ports in the
+# linux-firmware.git repository you point this script to.
+#
+
+set -e
+
+DRIVER=ath11k
+CHECKFILE=debugfs_htt_stats.c
+
+################################################################################
+#
+# Check pre-reqs
+#
+if [ $# -ne 1 ]; then
+	printf "USAGE: %s /path/to/linux-firmware.git\n" $0 >&2
+	exit 1
+fi
+
+if [ ! -e ${CHECKFILE} ]; then
+	printf "ERROR: run from %s driver directory; no %s.c here\n" ${DRIVER} ${CHECKFILE} >&2
+	exit 1
+fi
+
+LFWDIR=${1}
+if test ! -d ${LFWDIR} -o ! -e ${LFWDIR}/WHENCE; then
+	printf "ERROR: cannot find linux-firmware.git at '%s'\n" ${LFWDIR} >&2
+	exit 1
+fi
+LFWDIR=${LFWDIR}/${DRIVER}
+
+################################################################################
+#
+# Helper functions.
+#
+# This uses a hack (cpp) to expand some macros for us and parses out the result
+# which is the PCI device ID or the firmware directory for that.
+#
+# Driver is there, the firmware not yet...?
+# ==> 0x1101 -> ATH11K_HW_QCA6390_HW20 -> QCA6390/hw2.0
+# ==> 0x1104 -> ATH11K_HW_QCN9074_HW10 -> QCN9074/hw1.0
+# ==> 0x1103 -> ATH11K_HW_WCN6855_HW20 -> WCN6855/hw2.0
+# ==> 0x1103 -> ATH11K_HW_WCN6855_HW21 -> WCN6855/hw2.1
+# Firmware dir WCN6855/hw2.1 (for 0x1103) does not exist; skipping
+#
+list_fw()
+{
+	# List of already seen flavor (firmware directory).
+	sfwl=""
+
+	# List of "supported" device IDs.
+	devidl=$(cpp pci.c 2> /dev/null | awk '/PCI_VDEVICE\(QCOM,/ { gsub("^.*, ", ""); gsub("\\) },$", ""); print tolower($0); }')
+	# Turn them into a regex.
+	didreg=$(echo "${devidl}" | xargs -J % echo -n % | sed -e 's, ,|,g')
+	# List the device ID cases along with their hw_rev which we can go and take to lookup fw.
+	hwrevs=$(cpp pci.c 2> /dev/null | egrep -E "(case (${didreg})|ab->hw_rev = )" | awk '{
+		if (FNR > 1 && /case/) {
+			printf "\n";
+		}
+		gsub("^.*case[[:space:]]*", "");
+		gsub("[[:space:]]*ab->hw_rev = ", " ");
+		gsub("[:;]", "");
+		printf "%s", $0;
+	}')
+
+	# hwrevs is a list of (device IDs) -> (1..n hardware revisions) mappings.
+	#echo "==> ${devidl} :: ${didreg} :: ${hwrevs}" >&2
+
+	# List of (hardware revision) -> (firware directory) mappings.
+	l=$(cpp core.c 2> /dev/null | egrep -E '\.(hw_rev|dir) = ' | awk '{ if (/hw_rev/) { printf "%s", $0; } else { print; } }' | sort | uniq | \
+	awk '{
+		gsub("^.*hw_rev = ", "");
+		gsub(",.* = ", "\t");
+		gsub(",$", "");
+		gsub(/"/, "");
+		gsub(" ", "");
+		gsub("\t", " ");
+		print;
+	}')
+	#echo "===> ${l}" >&2
+
+	ll=$(echo "${l}" | wc -w | awk '{ print $1 }')
+	while test "${ll}" -gt 1; do
+		hwr=${l%%[[:space:]]*}
+		l=${l#*[[:space:]]}
+		fwd=${l%%[[:space:]]*}
+		l=${l#*[[:space:]]}
+
+		#echo "=+=> ${hwr} -> ${fwd}" >&2
+		eval fwd_${hwr}=${fwd}
+		ll=$(echo "${l}" | wc -w | awk '{ print $1 }')
+	done
+
+	echo "${hwrevs}" | \
+	while read did hwrl; do
+		hwrn=$(echo "${hwrl}" | wc -w | awk '{ print $1 }')
+		if test ${hwrn} -lt 1; then
+			printf "Device ID %s has no hardware revisions (%s); skipping\n" "${did}" ${hwrn} >&2
+			continue
+		fi
+
+		for hwrx in ${hwrl}; do
+
+			eval fwd=\${fwd_${hwrx}}
+			#echo "===> ${did} -> ${hwrx} -> ${fwd}" >&2
+
+			if test ! -d ${LFWDIR}/${fwd}; then
+				#printf "Firmware dir %s (for %s) does not exist; skipping\n" ${fwd} ${did} >&2
+				continue
+			fi
+
+			flav=$(echo "${fwd}" | awk -v drv=${DRIVER} '{
+				# Ports FLAVOR names are [a-z0-9_].  If needed add more mangling magic here.
+				gsub("^" drv "/", "");
+				gsub("/", "_");
+				gsub("\\.", "");
+				print tolower($0);
+			}')
+
+			# Print this first or otherwise if two device IDs have the same firmware
+			# we may not see that.
+			echo "FWGET ${did} ${flav}"
+
+			x=""
+			for zf in ${sfwl}; do
+				if test "${zf}" == "${flav}"; then
+					x="${zf}"
+					break
+				fi
+			done
+			if test "${x}" != ""; then
+				#printf "Flavor %s (firmware directory %s) already seen; skipping\n" ${flav} ${fwd} >&2
+				continue
+			fi
+			sfwl="${sfwl} ${flav}"
+
+			#echo "==> ${did} -> ${fwd} -> ${flav}"
+
+			lx=$(cd ${LFWDIR} && find ${fwd} -type f \! -name "*sdio*" -a \! -name "*.txt" -print)
+
+			# Get a count so we can automatically add \\ apart from the last line.
+			fn=$(echo "${lx}" | wc -w | awk '{ print $1 }')
+
+			#echo "==> ${flav} :: ${fn} :: ${lx}" >&2
+
+			if test ${fn} -gt 0; then
+
+				echo "FWS ${flav}"
+				echo "DISTFILES_${flav}= \\"
+				for fz in ${lx}; do echo "${fz}"; done | \
+				awk -v fn=$fn -v fwg=${flav} -v drv=${DRIVER} '{
+					if (FNR == fn) { x="" } else { x=" \\" };
+					#gsub("^" drv "/", "${FWSUBDIR}/");
+					gsub("^", "${FWSUBDIR}/");
+					printf "\t%s${DISTURL_SUFFIX}%s\n", $0, x;
+				}'
+
+				# Check for "lic" files.
+				lx=$(cd ${LFWDIR} && find ${fwd} -type f \! -name "*sdio*" -a -name "*.txt" -print)
+
+				# Get a count so we can automatically add \\ apart from the last line.
+				fn=$(echo "${lx}" | wc -w | awk '{ print $1 }')
+
+				if test ${fn} -gt 0; then
+					echo "FWL ${flav}"
+					echo "DISTFILES_${flav}_lic= \\"
+					for fz in ${lx}; do echo "${fz}"; done | \
+					awk -v fn=$fn -v fwg=${flav} -v drv=${DRIVER} '{
+						if (FNR == fn) { x="" } else { x=" \\" };
+						#gsub("^" drv "/", "${FWSUBDIR}/");
+						gsub("^", "${FWSUBDIR}/");
+						printf "\t%s${DISTURL_SUFFIX}%s\n", $0, x;
+					}'
+				fi
+			fi
+		done
+
+	done
+}
+
+################################################################################
+#
+# Generate the PORTS file template.
+#
+
+fwsl=$(list_fw | grep ^FWS | awk '{ print $2 }')
+# Get a count so we can automatically add \\ apart from the last line.
+fn=$(echo "${fwsl}" | wc -w | awk '{ print $1 }')
+
+if test ${fn} -gt 0; then
+
+	portsfile=$(mktemp -p /tmp ${DRIVER}-fwport.XXXXXX)
+
+	:> ${portsfile}
+	(
+	echo "FWSUBS= \\"
+	for sz in ${fwsl}; do echo "${sz}"; done | \
+	awk -v fn=$fn '{
+		if (FNR == fn) { x="" } else { x=" \\" };
+		printf "\t%s%s\n", $0, x;
+	}'
+
+	echo
+	list_fw | grep -v ^FWS | grep -v ^FWL | grep -v ^FWGET
+
+	echo
+	echo "DISTFILES_\${FWDRV}= \\"
+	for sz in ${fwsl}; do echo "${sz}"; done | \
+	awk -v fn=$fn '{
+		if (FNR == fn) { x="" } else { x=" \\" };
+		printf "\t${DISTFILES_%s}%s\n", $0, x;
+	}'
+
+	fwsl=$(list_fw | grep ^FWL | awk '{ print $2 }')
+	# Get a count so we can automatically add \\ apart from the last line.
+	fn=$(echo "${fwsl}" | wc -w | awk '{ print $1 }')
+	if test ${fn} -gt 0; then
+		echo "DISTFILES_\${FWDRV}_lic= \\"
+		for sz in ${fwsl}; do echo "${sz}"; done | \
+		awk -v fn=$fn '{
+			if (FNR == fn) { x="" } else { x=" \\" };
+			printf "\t${DISTFILES_%s_lic}%s\n", $0, x;
+		}'
+	else
+		echo "DISTFILES_\${FWDRV}_lic="
+	fi
+
+	) >> ${portsfile}
+
+	printf "INFO: wifi-firmware-%s-kmod template at %s\n" ${DRIVER} ${portsfile} >&2
+fi
+
+################################################################################
+#
+# Generate the fwget(8) case pattern table (PCI device ID -> fw port flavor).
+#
+
+fwgetfile=$(mktemp -p /tmp ${DRIVER}-fwget.XXXXXX)
+:> ${fwgetfile}
+
+fwsl=$(list_fw | grep ^FWGET | sort)
+# Get a count so we can automatically add \\ apart from the last line.
+fn=$(echo "${fwsl}" | grep -c FWGET | awk '{ print $1 }')
+
+if test ${fn} -gt 0; then
+
+	# We need to check for same ID with multiple firmware.
+	# The list ist sorted by ID so duplicates are next to each other.
+	cs=$(echo "${fwsl}" | awk '{ print $2 }' | uniq -c | awk '{ print $1 }')
+
+	#echo "==> cs=${cs}" >&2
+
+	for n in ${cs}; do
+
+		# Skip FWGET
+		fwsl=${fwsl#*[[:space:]]}
+		# get device ID
+		did=${fwsl%%[[:space:]]*}
+		fwsl=${fwsl#*[[:space:]]}
+		# get flavor
+		flav=${fwsl%%[[:space:]]*}
+		fwsl=${fwsl#*[[:space:]]}
+
+		# printf "===> did %s flav %s\n" ${did} ${flav} >&2
+
+		if test ${n} -eq 1; then
+			echo "${did} ${flav}" | awk -v drv=${DRIVER} '{
+				printf "\t%s)\taddpkg \"wifi-firmware-%s-kmod-%s\"; return 1 ;;\n",
+				    tolower($1), drv, tolower($2);
+			}' >> ${fwgetfile}
+		else
+			echo "${did} ${flav}" | awk -v drv=${DRIVER} '{
+				printf "\t%s)\taddpkg \"wifi-firmware-%s-kmod-%s\"\n",
+				    tolower($1), drv, tolower($2);
+			}' >> ${fwgetfile}
+
+			i=1
+			while test ${i} -lt ${n}; do
+				# Skip FWGET
+				fwsl=${fwsl#*[[:space:]]}
+				# get device ID
+				did=${fwsl%%[[:space:]]*}
+				fwsl=${fwsl#*[[:space:]]}
+				# get flavor
+				flav=${fwsl%%[[:space:]]*}
+				fwsl=${fwsl#*[[:space:]]}
+
+				#printf "===> did %s flav %s\n" ${did} ${flav} >&2
+
+				echo "${did} ${flav}" | awk -v drv=${DRIVER} '{
+					printf "\t\taddpkg \"wifi-firmware-%s-kmod-%s\"\n",
+					    drv, tolower($2);
+				}' >> ${fwgetfile}
+
+				i=$((i + 1))
+			done
+
+			printf "\t\treturn 1 ;;\n" >> ${fwgetfile}
+		fi
+	done
+fi
+
+printf "INFO: fwget pci_network_qca %s template at %s\n" ${DRIVER} ${fwgetfile} >&2
+
+# end
diff --git a/sys/contrib/dev/athk/ath12k/zzz_fw_ports_fwget.sh b/sys/contrib/dev/athk/ath12k/zzz_fw_ports_fwget.sh
new file mode 100644
index 000000000000..500f5de05cc4
--- /dev/null
+++ b/sys/contrib/dev/athk/ath12k/zzz_fw_ports_fwget.sh
@@ -0,0 +1,310 @@
+#!/bin/sh
+#-
+# SPDX-License-Identifier: BSD-2-Clause
+#
+# Copyright (c) 2024 The FreeBSD Foundation
+#
+# This software was developed by Björn Zeeb
+# under sponsorship from the FreeBSD Foundation.
+#
+# This is neither efficient nor elegant but we need it few times
+# a year and it does the job.
+#
+#
+# USAGE: please check out the correct tag/hash for ports in the
+# linux-firmware.git repository you point this script to.
+#
+
+set -e
+
+DRIVER=ath12k
+CHECKFILE=dp_mon.c
+
+################################################################################
+#
+# Check pre-reqs
+#
+if [ $# -ne 1 ]; then
+	printf "USAGE: %s /path/to/linux-firmware.git\n" $0 >&2
+	exit 1
+fi
+
+if [ ! -e ${CHECKFILE} ]; then
+	printf "ERROR: run from %s driver directory; no %s.c here\n" ${DRIVER} ${CHECKFILE} >&2
+	exit 1
+fi
+
+LFWDIR=${1}
+if test ! -d ${LFWDIR} -o ! -e ${LFWDIR}/WHENCE; then
+	printf "ERROR: cannot find linux-firmware.git at '%s'\n" ${LFWDIR} >&2
+	exit 1
+fi
+LFWDIR=${LFWDIR}/${DRIVER}
+
+################################################################################
+#
+# Helper functions.
+#
+# This uses a hack (cpp) to expand some macros for us and parses out the result
+# which is the PCI device ID or the firmware directory for that.
+#
+list_fw()
+{
+	# List of already seen flavor (firmware directory).
+	sfwl=""
+
+	# List of "supported" device IDs.
+	devidl=$(cpp pci.c 2> /dev/null | awk '/PCI_VDEVICE\(QCOM,/ { gsub("^.*, ", ""); gsub("\\) },$", ""); print tolower($0); }')
+	# Turn them into a regex.
+	didreg=$(echo "${devidl}" | xargs -J % echo -n % | sed -e 's, ,|,g')
+	# List the device ID cases along with their hw_rev which we can go and take to lookup fw.
+	hwrevs=$(cpp pci.c 2> /dev/null | egrep -E "(case (${didreg})|ab->hw_rev = )" | awk '{
+		if (FNR > 1 && /case/) {
+			printf "\n";
+		}
+		gsub("^.*case[[:space:]]*", "");
+		gsub("[[:space:]]*ab->hw_rev = ", " ");
+		gsub("[:;]", "");
+		printf "%s", $0;
+	}')
+
+	# hwrevs is a list of (device IDs) -> (1..n hardware revisions) mappings.
+	#echo "==> ${devidl} :: ${didreg} :: ${hwrevs}" >&2
+
+	# List of (hardware revision) -> (firware directory) mappings.
+	l=$(cpp hw.c 2> /dev/null | egrep -E '\.(hw_rev|dir) = ' | awk '{ if (/hw_rev/) { printf "%s", $0; } else { print; } }' | sort | uniq | \
+	awk '{
+		gsub("^.*hw_rev = ", "");
+		gsub(",.* = ", "\t");
+		gsub(",$", "");
+		gsub(/"/, "");
+		gsub(" ", "");
+		gsub("\t", " ");
+		print;
+	}')
+	#echo "===> ${l}" >&2
+
+	ll=$(echo "${l}" | wc -w | awk '{ print $1 }')
+	while test "${ll}" -gt 1; do
+		hwr=${l%%[[:space:]]*}
+		l=${l#*[[:space:]]}
+		fwd=${l%%[[:space:]]*}
+		l=${l#*[[:space:]]}
+
+		#echo "=+=> ${hwr} -> ${fwd}" >&2
+		eval fwd_${hwr}=${fwd}
+		ll=$(echo "${l}" | wc -w | awk '{ print $1 }')
+	done
+
+	echo "${hwrevs}" | \
+	while read did hwrl; do
+		hwrn=$(echo "${hwrl}" | wc -w | awk '{ print $1 }')
+		if test ${hwrn} -lt 1; then
+			printf "Device ID %s has no hardware revisions (%s); skipping\n" "${did}" ${hwrn} >&2
+			continue
+		fi
+
+		for hwrx in ${hwrl}; do
+
+			eval fwd=\${fwd_${hwrx}}
+			#echo "===> ${did} -> ${hwrx} -> ${fwd}" >&2
+
+			if test ! -d ${LFWDIR}/${fwd}; then
+				#printf "Firmware dir %s (for %s) does not exist; skipping\n" ${fwd} ${did} >&2
+				continue
+			fi
+
+			flav=$(echo "${fwd}" | awk -v drv=${DRIVER} '{
+				# Ports FLAVOR names are [a-z0-9_].  If needed add more mangling magic here.
+				gsub("^" drv "/", "");
+				gsub("/", "_");
+				gsub("\\.", "");
+				print tolower($0);
+			}')
+
+			# Print this first or otherwise if two device IDs have the same firmware
+			# we may not see that.
+			echo "FWGET ${did} ${flav}"
+
+			x=""
+			for zf in ${sfwl}; do
+				if test "${zf}" == "${flav}"; then
+					x="${zf}"
+					break
+				fi
+			done
+			if test "${x}" != ""; then
+				#printf "Flavor %s (firmware directory %s) already seen; skipping\n" ${flav} ${fwd} >&2
+				continue
+			fi
+			sfwl="${sfwl} ${flav}"
+
+			#echo "==> ${did} -> ${fwd} -> ${flav}"
+
+			lx=$(cd ${LFWDIR} && find ${fwd} -type f \! -name "*sdio*" -a \! -name "*.txt" -print)
+
+			# Get a count so we can automatically add \\ apart from the last line.
+			fn=$(echo "${lx}" | wc -w | awk '{ print $1 }')
+
+			#echo "==> ${flav} :: ${fn} :: ${lx}" >&2
+
+			if test ${fn} -gt 0; then
+
+				echo "FWS ${flav}"
+				echo "DISTFILES_${flav}= \\"
+				for fz in ${lx}; do echo "${fz}"; done | \
+				awk -v fn=$fn -v fwg=${flav} -v drv=${DRIVER} '{
+					if (FNR == fn) { x="" } else { x=" \\" };
+					#gsub("^" drv "/", "${FWSUBDIR}/");
+					gsub("^", "${FWSUBDIR}/");
+					printf "\t%s${DISTURL_SUFFIX}%s\n", $0, x;
+				}'
+
+				# Check for "lic" files.
+				lx=$(cd ${LFWDIR} && find ${fwd} -type f \! -name "*sdio*" -a -name "*.txt" -print)
+
+				# Get a count so we can automatically add \\ apart from the last line.
+				fn=$(echo "${lx}" | wc -w | awk '{ print $1 }')
+
+				if test ${fn} -gt 0; then
+					echo "FWL ${flav}"
+					echo "DISTFILES_${flav}_lic= \\"
+					for fz in ${lx}; do echo "${fz}"; done | \
+					awk -v fn=$fn -v fwg=${flav} -v drv=${DRIVER} '{
+						if (FNR == fn) { x="" } else { x=" \\" };
+						#gsub("^" drv "/", "${FWSUBDIR}/");
+						gsub("^", "${FWSUBDIR}/");
+						printf "\t%s${DISTURL_SUFFIX}%s\n", $0, x;
+					}'
+				fi
+			fi
+		done
+
+	done
+}
+
+################################################################################
+#
+# Generate the PORTS file template.
+#
+
+fwsl=$(list_fw | grep ^FWS | awk '{ print $2 }')
+# Get a count so we can automatically add \\ apart from the last line.
+fn=$(echo "${fwsl}" | wc -w | awk '{ print $1 }')
+
+if test ${fn} -gt 0; then
+
+	portsfile=$(mktemp -p /tmp ${DRIVER}-fwport.XXXXXX)
+
+	:> ${portsfile}
+	(
+	echo "FWSUBS= \\"
+	for sz in ${fwsl}; do echo "${sz}"; done | \
+	awk -v fn=$fn '{
+		if (FNR == fn) { x="" } else { x=" \\" };
+		printf "\t%s%s\n", $0, x;
+	}'
+
+	echo
+	list_fw | grep -v ^FWS | grep -v ^FWL | grep -v ^FWGET
+
+	echo
+	echo "DISTFILES_\${FWDRV}= \\"
+	for sz in ${fwsl}; do echo "${sz}"; done | \
+	awk -v fn=$fn '{
+		if (FNR == fn) { x="" } else { x=" \\" };
+		printf "\t${DISTFILES_%s}%s\n", $0, x;
+	}'
+
+	fwsl=$(list_fw | grep ^FWL | awk '{ print $2 }')
+	# Get a count so we can automatically add \\ apart from the last line.
+	fn=$(echo "${fwsl}" | wc -w | awk '{ print $1 }')
+	if test ${fn} -gt 0; then
+		echo "DISTFILES_\${FWDRV}_lic= \\"
+		for sz in ${fwsl}; do echo "${sz}"; done | \
+		awk -v fn=$fn '{
+			if (FNR == fn) { x="" } else { x=" \\" };
+			printf "\t${DISTFILES_%s_lic}%s\n", $0, x;
+		}'
+	else
+		echo "DISTFILES_\${FWDRV}_lic="
+	fi
+
+	) >> ${portsfile}
+
+	printf "INFO: wifi-firmware-%s-kmod template at %s\n" ${DRIVER} ${portsfile} >&2
+fi
+
+################################################################################
+#
+# Generate the fwget(8) case pattern table (PCI device ID -> fw port flavor).
+#
+
+fwgetfile=$(mktemp -p /tmp ${DRIVER}-fwget.XXXXXX)
+:> ${fwgetfile}
+
+fwsl=$(list_fw | grep ^FWGET | sort)
+# Get a count so we can automatically add \\ apart from the last line.
+fn=$(echo "${fwsl}" | grep -c FWGET | awk '{ print $1 }')
+
+if test ${fn} -gt 0; then
+
+	# We need to check for same ID with multiple firmware.
+	# The list ist sorted by ID so duplicates are next to each other.
+	cs=$(echo "${fwsl}" | awk '{ print $2 }' | uniq -c | awk '{ print $1 }')
+
+	#echo "==> cs=${cs}" >&2
+
+	for n in ${cs}; do
+
+		# Skip FWGET
+		fwsl=${fwsl#*[[:space:]]}
+		# get device ID
+		did=${fwsl%%[[:space:]]*}
+		fwsl=${fwsl#*[[:space:]]}
+		# get flavor
+		flav=${fwsl%%[[:space:]]*}
+		fwsl=${fwsl#*[[:space:]]}
+
+		# printf "===> did %s flav %s\n" ${did} ${flav} >&2
+
+		if test ${n} -eq 1; then
+			echo "${did} ${flav}" | awk -v drv=${DRIVER} '{
+				printf "\t%s)\taddpkg \"wifi-firmware-%s-kmod-%s\"; return 1 ;;\n",
+				    tolower($1), drv, tolower($2);
+			}' >> ${fwgetfile}
+		else
+			echo "${did} ${flav}" | awk -v drv=${DRIVER} '{
+				printf "\t%s)\taddpkg \"wifi-firmware-%s-kmod-%s\"\n",
+				    tolower($1), drv, tolower($2);
+			}' >> ${fwgetfile}
+
+			i=1
+			while test ${i} -lt ${n}; do
+				# Skip FWGET
+				fwsl=${fwsl#*[[:space:]]}
+				# get device ID
+				did=${fwsl%%[[:space:]]*}
+				fwsl=${fwsl#*[[:space:]]}
+				# get flavor
+				flav=${fwsl%%[[:space:]]*}
+				fwsl=${fwsl#*[[:space:]]}
+
+				#printf "===> did %s flav %s\n" ${did} ${flav} >&2
+
+				echo "${did} ${flav}" | awk -v drv=${DRIVER} '{
+					printf "\t\taddpkg \"wifi-firmware-%s-kmod-%s\"\n",
+					    drv, tolower($2);
+				}' >> ${fwgetfile}
+
+				i=$((i + 1))
+			done
+
+			printf "\t\treturn 1 ;;\n" >> ${fwgetfile}
*** 994 LINES SKIPPED ***