libibmad: add per-port SA key configuration

The libibmad SA query helpers have no way to supply a nonzero SM_Key.
Calls through sa_rpc_call(), including ib_path_query_via() and
ib_node_query_via(), therefore always issue untrusted requests with
SM_Key 0.

This prevents those helpers from querying records that require a
trusted request, such as PKeyTableRecord, and from working with subnet
managers configured to reject untrusted SA requests. Callers currently
have to bypass the helpers and implement the UMAD transaction
themselves.

Add per-port sa_key_set() and sa_key_get() functions following the
existing SMP M_Key API. Store the key in the opaque ibmad_port and copy
it into rpc.mkey when constructing an SA request. Newly opened ports
remain initialized to key 0, preserving existing behavior unless a
caller explicitly sets a key.

Export the new functions under IBMAD_1.6, update the libibmad ABI and
distribution packaging, and document the API.

Signed-off-by: Jonathan Süssemilch Poulain <jpoulain@coreweave.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index be64e26..3ccd87f 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -832,6 +832,7 @@
 add_subdirectory(providers/siw)
 
 add_subdirectory(libibmad)
+add_subdirectory(libibmad/man)
 add_subdirectory(libibnetdisc)
 add_subdirectory(libibnetdisc/man)
 add_subdirectory(infiniband-diags)
diff --git a/debian/libibmad-dev.install b/debian/libibmad-dev.install
index 0083650..0150de4 100644
--- a/debian/libibmad-dev.install
+++ b/debian/libibmad-dev.install
@@ -3,3 +3,5 @@
 usr/lib/*/libibmad*.a
 usr/lib/*/libibmad*.so
 usr/lib/*/pkgconfig/libibmad.pc
+usr/share/man/man3/sa_key_get.3
+usr/share/man/man3/sa_key_set.3
diff --git a/debian/libibmad5.symbols b/debian/libibmad5.symbols
index 55460db..16d2c09 100644
--- a/debian/libibmad5.symbols
+++ b/debian/libibmad5.symbols
@@ -3,6 +3,7 @@
  IBMAD_1.3@IBMAD_1.3 1.3.11
  IBMAD_1.4@IBMAD_1.4 54
  IBMAD_1.5@IBMAD_1.5 56
+ IBMAD_1.6@IBMAD_1.6 66
  bm_call_via@IBMAD_1.3 1.3.11
  cc_config_status_via@IBMAD_1.3 1.3.11
  cc_query_status_via@IBMAD_1.3 1.3.11
@@ -160,3 +161,5 @@
  xdump@IBMAD_1.3 1.3.11
  mad_rpc_open_port2@IBMAD_1.5 56
  mad_rpc_close_port2@IBMAD_1.5 56
+ sa_key_get@IBMAD_1.6 66
+ sa_key_set@IBMAD_1.6 66
diff --git a/libibmad/CMakeLists.txt b/libibmad/CMakeLists.txt
index 27782f4..838ec45 100644
--- a/libibmad/CMakeLists.txt
+++ b/libibmad/CMakeLists.txt
@@ -9,7 +9,7 @@
 
 rdma_library(ibmad libibmad.map
   # See Documentation/versioning.md
-  5 5.5.${PACKAGE_VERSION}
+  5 5.6.${PACKAGE_VERSION}
   bm.c
   cc.c
   dump.c
diff --git a/libibmad/libibmad.map b/libibmad/libibmad.map
index 7d150c0..4065ef4 100644
--- a/libibmad/libibmad.map
+++ b/libibmad/libibmad.map
@@ -168,3 +168,8 @@
 		mad_rpc_close_port2;
 } IBMAD_1.4;
 
+IBMAD_1.6 {
+	global:
+		sa_key_get;
+		sa_key_set;
+} IBMAD_1.5;
diff --git a/libibmad/mad.h b/libibmad/mad.h
index 99d3a5a..24d223e 100644
--- a/libibmad/mad.h
+++ b/libibmad/mad.h
@@ -1612,6 +1612,8 @@
 		  void *buf) __attribute__((deprecated));
 
 /* sa.c new interface */
+void sa_key_set(struct ibmad_port *srcport, uint64_t key);
+uint64_t sa_key_get(const struct ibmad_port *srcport);
 uint8_t *sa_rpc_call(const struct ibmad_port *srcport, void *rcvbuf,
 		     ib_portid_t *portid, ib_sa_call_t *sa, unsigned timeout);
 int ib_path_query_via(const struct ibmad_port *srcport, ibmad_gid_t srcgid,
diff --git a/libibmad/mad_internal.h b/libibmad/mad_internal.h
index 5deac27..82314e7 100644
--- a/libibmad/mad_internal.h
+++ b/libibmad/mad_internal.h
@@ -41,6 +41,7 @@
 	int class_agents[MAX_CLASS];	/* class2agent mapper */
 	int timeout, retries;
 	uint64_t smp_mkey;
+	uint64_t sa_key;
 };
 
 extern struct ibmad_port *ibmp;
diff --git a/libibmad/man/CMakeLists.txt b/libibmad/man/CMakeLists.txt
new file mode 100644
index 0000000..8497d12
--- /dev/null
+++ b/libibmad/man/CMakeLists.txt
@@ -0,0 +1,7 @@
+rdma_man_pages(
+  sa_key_set.3.md
+  )
+
+rdma_alias_man_pages(
+  sa_key_set.3 sa_key_get.3
+  )
diff --git a/libibmad/man/sa_key_set.3.md b/libibmad/man/sa_key_set.3.md
new file mode 100644
index 0000000..159f731
--- /dev/null
+++ b/libibmad/man/sa_key_set.3.md
@@ -0,0 +1,49 @@
+---
+date: 2026-09-24
+footer: libibmad
+header: "Libibmad Programmer's Manual"
+layout: page
+license: 'Licensed under the OpenIB.org BSD license (FreeBSD Variant) - See COPYING.md'
+section: 3
+title: SA_KEY_SET
+---
+
+# NAME
+
+sa_key_set, sa_key_get - set or get the SM_Key used for SA requests
+
+# SYNOPSIS
+
+```c
+#include <infiniband/mad.h>
+
+void sa_key_set(struct ibmad_port *srcport, uint64_t key);
+
+uint64_t sa_key_get(const struct ibmad_port *srcport);
+```
+
+# DESCRIPTION
+
+**sa_key_set()** sets the Subnet Administration (SA) SM_Key associated with
+*srcport*. Subsequent requests sent through **sa_rpc_call()**, including the
+requests made by **ib_path_query_via()** and **ib_node_query_via()**, encode
+*key* in the SM_Key field of the SA header.
+
+An **ibmad_port** returned by **mad_rpc_open_port()** or
+**mad_rpc_open_port2()** initially has an SA SM_Key of 0. This preserves the
+untrusted-request behavior used when **sa_key_set()** is not called.
+
+**sa_key_get()** returns the SA SM_Key currently associated with *srcport*.
+
+The SA SM_Key is separate from the SMP M_Key configured by
+**smp_mkey_set()**.
+
+# RETURN VALUE
+
+**sa_key_set()** has no return value.
+
+**sa_key_get()** returns the configured SA SM_Key in host byte order.
+
+# AUTHORS
+
+Jonathan Süssemilch Poulain <jpoulain@coreweave.com>
diff --git a/libibmad/sa.c b/libibmad/sa.c
index 9dfec93..d2675aa 100644
--- a/libibmad/sa.c
+++ b/libibmad/sa.c
@@ -41,6 +41,16 @@
 #undef DEBUG
 #define DEBUG 	if (ibdebug)	IBWARN
 
+void sa_key_set(struct ibmad_port *srcport, uint64_t key)
+{
+	srcport->sa_key = key;
+}
+
+uint64_t sa_key_get(const struct ibmad_port *srcport)
+{
+	return srcport->sa_key;
+}
+
 uint8_t *sa_rpc_call(const struct ibmad_port *ibmad_port, void *rcvbuf,
 		     ib_portid_t * portid, ib_sa_call_t * sa, unsigned timeout)
 {
@@ -64,6 +74,7 @@
 	rpc.datasz = IB_SA_DATA_SIZE;
 	rpc.dataoffs = IB_SA_DATA_OFFS;
 	rpc.trid = sa->trid;
+	rpc.mkey = ibmad_port->sa_key;
 
 	portid->qp = 1;
 	if (!portid->qkey)
diff --git a/redhat/rdma-core.spec b/redhat/rdma-core.spec
index 461cd64..d0610fe 100644
--- a/redhat/rdma-core.spec
+++ b/redhat/rdma-core.spec
@@ -470,6 +470,7 @@
 %{_mandir}/man3/ibv_*
 %{_mandir}/man3/ionic_dv*
 %{_mandir}/man3/rdma*
+%{_mandir}/man3/sa_key*
 %{_mandir}/man3/umad*
 %{_mandir}/man3/*_to_ibv_rate.*
 %{_mandir}/man7/rdma_cm.*
diff --git a/suse/rdma-core.spec b/suse/rdma-core.spec
index 72eebd6..a694289 100644
--- a/suse/rdma-core.spec
+++ b/suse/rdma-core.spec
@@ -678,6 +678,7 @@
 %{_mandir}/man3/ibnd_*
 %{_mandir}/man3/ibv_*
 %{_mandir}/man3/rdma*
+%{_mandir}/man3/sa_key*
 %{_mandir}/man3/umad*
 %{_mandir}/man3/*_to_ibv_rate.*
 %{_mandir}/man7/rdma_cm.*