git: 8e55febde6b5 - main - ixl(4): Fix NVM update API

From: Krzysztof Galazka <kgalazka_at_FreeBSD.org>
Date: Fri, 23 Oct 2026 17:57:45 UTC
The branch main has been updated by kgalazka:

URL: https://cgit.FreeBSD.org/src/commit/?id=8e55febde6b54685fe24c8b48ed6720bae97c491

commit 8e55febde6b54685fe24c8b48ed6720bae97c491
Author:     Krzysztof Galazka <kgalazka@FreeBSD.org>
AuthorDate: 2026-10-05 09:22:10 +0000
Commit:     Krzysztof Galazka <kgalazka@FreeBSD.org>
CommitDate: 2026-10-23 17:57:17 +0000

    ixl(4): Fix NVM update API
    
    Driver provides an interface for tools operating on adapters'
    Non-Volatile Memory using SIOC(G|S)DRVSPEC ioctls. It allows
    tools to issue Admin Queue commands to the firmware, which
    may return an error code encoded as a negative integer.
    The driver passed this negative integer as the return value
    of the ioctl handler. This was causing issues because the FreeBSD
    kernel reserves negative error codes in system calls for special
    purposes such as restarting the handler or detecting incorrect
    behavior in VFS operations. Simple remapping of negative values
    to positive integers caused currently available tools to misbehave
    and could break the NVM update process. Introduce a new API version,
    which allows new versions of tools to detect the fixed driver.
    Temporarily keep the old API, but issue a warning that tools need
    to be updated.
    
    Also refactor the code to use centralized cleanup and move the
    debug message from the firmware earlier to make sure it is not masked
    by an error from copyout.
    
    Signed-off-by: Krzysztof Galazka <krzysztof.galazka@intel.com>
    
    Tested by:      Mateusz Moga <mateusz.moga@intel.com>
    MFC after:      2 weeks
    Assisted by:    Copilot GPT 5.4
    Sponsored by:   Intel Corporation
    Differential Revision:  https://reviews.freebsd.org/D57942
---
 sys/dev/ixl/if_ixl.c      |  3 ++-
 sys/dev/ixl/ixl_pf.h      |  8 +++++-
 sys/dev/ixl/ixl_pf_main.c | 66 +++++++++++++++++++++++++++++------------------
 3 files changed, 50 insertions(+), 27 deletions(-)

diff --git a/sys/dev/ixl/if_ixl.c b/sys/dev/ixl/if_ixl.c
index 3822f4b5ace2..b0f89ae9a900 100644
--- a/sys/dev/ixl/if_ixl.c
+++ b/sys/dev/ixl/if_ixl.c
@@ -1983,7 +1983,8 @@ ixl_if_priv_ioctl(if_ctx_t ctx, u_long command, caddr_t data)
 	case SIOCGDRVSPEC:
 	case SIOCSDRVSPEC:
 		/* NVM update command */
-		if (ifd->ifd_cmd == I40E_NVM_ACCESS) {
+		if (ifd->ifd_cmd == I40E_NVM_ACCESS ||
+		    ifd->ifd_cmd == I40E_NVM_ACCESS2) {
 			error = priv_check(curthread, PRIV_DRIVER);
 			if (error)
 				break;
diff --git a/sys/dev/ixl/ixl_pf.h b/sys/dev/ixl/ixl_pf.h
index d244a2e36a38..3bc51e4c7459 100644
--- a/sys/dev/ixl/ixl_pf.h
+++ b/sys/dev/ixl/ixl_pf.h
@@ -93,6 +93,7 @@ enum ixl_state {
 	IXL_STATE_EEE_ENABLED	= 10,
 	IXL_STATE_LINK_ACTIVE_ON_DOWN = 11,
 	IXL_STATE_LINK_POLLING = 12,
+	IXL_STATE_NVMUPD_API_WARNED = 13,
 };
 
 #define IXL_PF_IN_RECOVERY_MODE(pf)	\
@@ -200,6 +201,12 @@ struct ixl_pf {
  */
 #define I40E_NVM_ACCESS \
      (((((((('E' << 4) + '1') << 4) + 'K') << 4) + 'G') << 4) | 5)
+/*
+ * New version of tools API. Omitting '6' to not conflict
+ * with debug dump supported by other drivers.
+ */
+#define I40E_NVM_ACCESS2 \
+     (((((((('E' << 4) + '1') << 4) + 'K') << 4) + 'G') << 4) | 7)
 
 #define IXL_DEFAULT_PHY_INT_MASK \
      ((~(I40E_AQ_EVENT_LINK_UPDOWN | I40E_AQ_EVENT_MODULE_QUAL_FAIL \
@@ -310,7 +317,6 @@ void	ixl_clear_state(volatile u32 *s, enum ixl_state bit);
 bool	ixl_test_state(volatile u32 *s, enum ixl_state bit);
 u32	ixl_testandset_state(volatile u32 *s, enum ixl_state bit);
 int	ixl_setup_interface(device_t, struct ixl_pf *);
-void	ixl_print_nvm_cmd(device_t, struct i40e_nvm_access *);
 
 void	ixl_handle_que(void *context, int pending);
 
diff --git a/sys/dev/ixl/ixl_pf_main.c b/sys/dev/ixl/ixl_pf_main.c
index d2a2fb74fe69..cc71ec8a2270 100644
--- a/sys/dev/ixl/ixl_pf_main.c
+++ b/sys/dev/ixl/ixl_pf_main.c
@@ -50,6 +50,7 @@ static u_int	ixl_add_maddr(void *, struct sockaddr_dl *, u_int);
 static u_int	ixl_match_maddr(void *, struct sockaddr_dl *, u_int);
 static char *	ixl_switch_element_string(struct sbuf *, u8, u16);
 static enum ixl_fw_mode ixl_get_fw_mode(struct ixl_pf *);
+static void	ixl_print_nvm_cmd(device_t, int, struct i40e_nvm_access *);
 
 /* Sysctls */
 static int	ixl_sysctl_set_advertise(SYSCTL_HANDLER_ARGS);
@@ -3123,8 +3124,8 @@ ixl_sysctl_show_fw(SYSCTL_HANDLER_ARGS)
 	return (0);
 }
 
-void
-ixl_print_nvm_cmd(device_t dev, struct i40e_nvm_access *nvma)
+static void
+ixl_print_nvm_cmd(device_t dev, int apiver, struct i40e_nvm_access *nvma)
 {
 	u8 nvma_ptr = nvma->config & 0xFF;
 	u8 nvma_flags = (nvma->config & 0xF00) >> 8;
@@ -3134,7 +3135,7 @@ ixl_print_nvm_cmd(device_t dev, struct i40e_nvm_access *nvma)
 	case I40E_NVM_READ:
 		if (nvma_ptr == 0xF && nvma_flags == 0xF &&
 		    nvma->offset == 0 && nvma->data_size == 1) {
-			device_printf(dev, "NVMUPD: Get Driver Status Command\n");
+			device_printf(dev, "NVMUPD: API: v%d Get Driver Status Command\n", apiver);
 			return;
 		}
 		cmd_str = "READ ";
@@ -3143,12 +3144,12 @@ ixl_print_nvm_cmd(device_t dev, struct i40e_nvm_access *nvma)
 		cmd_str = "WRITE";
 		break;
 	default:
-		device_printf(dev, "NVMUPD: unknown command: 0x%08x\n", nvma->command);
+		device_printf(dev, "NVMUPD: API: v%d unknown command: 0x%08x\n", apiver, nvma->command);
 		return;
 	}
 	device_printf(dev,
-	    "NVMUPD: cmd: %s ptr: 0x%02x flags: 0x%01x offset: 0x%08x data_s: 0x%08x\n",
-	    cmd_str, nvma_ptr, nvma_flags, nvma->offset, nvma->data_size);
+	    "NVMUPD: API: v%d cmd: %s ptr: 0x%02x flags: 0x%01x offset: 0x%08x data_s: 0x%08x\n",
+	    apiver, cmd_str, nvma_ptr, nvma_flags, nvma->offset, nvma->data_size);
 }
 
 int
@@ -3159,7 +3160,7 @@ ixl_handle_nvmupd_cmd(struct ixl_pf *pf, struct ifdrv *ifd)
 	device_t dev = pf->dev;
 	enum i40e_status_code status = 0;
 	size_t nvma_size, ifd_len, exp_len;
-	int err, perrno;
+	int err, perrno, apiver;
 
 	DEBUGFUNC("ixl_handle_nvmupd_cmd");
 
@@ -3175,20 +3176,26 @@ ixl_handle_nvmupd_cmd(struct ixl_pf *pf, struct ifdrv *ifd)
 		    __func__, ifd_len, nvma_size);
 		device_printf(dev, "%s: data pointer: %p\n", __func__,
 		    ifd->ifd_data);
-		return (EINVAL);
+		err = EINVAL;
+		goto out;
 	}
 
+	if (ifd->ifd_cmd == I40E_NVM_ACCESS &&
+	    !ixl_testandset_state(&pf->state, IXL_STATE_NVMUPD_API_WARNED))
+		device_printf(dev,
+		    "Using deprecated tools API, please update the tool\n");
+
 	nvma = malloc(ifd_len, M_IXL, M_WAITOK);
 	err = copyin(ifd->ifd_data, nvma, ifd_len);
 	if (err) {
 		device_printf(dev, "%s: Cannot get request from user space\n",
 		    __func__);
-		free(nvma, M_IXL);
-		return (err);
+		goto out_free;
 	}
 
+	apiver = ifd->ifd_cmd == I40E_NVM_ACCESS2 ? 2 : 1;
 	if (pf->dbg_mask & IXL_DBG_NVMUPD)
-		ixl_print_nvm_cmd(dev, nvma);
+		ixl_print_nvm_cmd(dev, apiver, nvma);
 
 	if (IXL_PF_IS_RESETTING(pf)) {
 		int count = 0;
@@ -3203,16 +3210,16 @@ ixl_handle_nvmupd_cmd(struct ixl_pf *pf, struct ifdrv *ifd)
 		device_printf(dev,
 		    "%s: timeout waiting for EMP reset to finish\n",
 		    __func__);
-		free(nvma, M_IXL);
-		return (-EBUSY);
+		perrno = -EBUSY;
+		goto out_sanitize;
 	}
 
 	if (nvma->data_size < 1 || nvma->data_size > 4096) {
 		device_printf(dev,
 		    "%s: invalid request, data size not in supported range\n",
 		    __func__);
-		free(nvma, M_IXL);
-		return (EINVAL);
+		err = EINVAL;
+		goto out_free;
 	}
 
 	/*
@@ -3230,8 +3237,7 @@ ixl_handle_nvmupd_cmd(struct ixl_pf *pf, struct ifdrv *ifd)
 		if (err) {
 			device_printf(dev, "%s: Cannot get request from user space\n",
 					__func__);
-			free(nvma, M_IXL);
-			return (err);
+			goto out_free;
 		}
 	}
 
@@ -3240,27 +3246,37 @@ ixl_handle_nvmupd_cmd(struct ixl_pf *pf, struct ifdrv *ifd)
 	status = i40e_nvmupd_command(hw, nvma, nvma->data, &perrno);
 	// IXL_PF_UNLOCK(pf);
 
+	/* Let the nvmupdate report errors, show them only when debug is enabled */
+	if (status != 0 && (pf->dbg_mask & IXL_DBG_NVMUPD) != 0)
+		device_printf(dev, "i40e_nvmupd_command API: v%d status %s, perrno %d\n",
+		    apiver, i40e_stat_str(hw, status), perrno);
+
 	err = copyout(nvma, ifd->ifd_data, ifd_len);
-	free(nvma, M_IXL);
 	if (err) {
 		device_printf(dev, "%s: Cannot return data to user space\n",
 				__func__);
-		return (err);
+		goto out_free;
 	}
 
-	/* Let the nvmupdate report errors, show them only when debug is enabled */
-	if (status != 0 && (pf->dbg_mask & IXL_DBG_NVMUPD) != 0)
-		device_printf(dev, "i40e_nvmupd_command status %s, perrno %d\n",
-		    i40e_stat_str(hw, status), perrno);
+out_sanitize:
+	if (ifd->ifd_cmd == I40E_NVM_ACCESS2) {
+		err = -perrno;
+		goto out_free;
+	}
 
 	/*
 	 * -EPERM is actually ERESTART, which the kernel interprets as it needing
 	 * to run this ioctl again. So use -EACCES for -EPERM instead.
 	 */
 	if (perrno == -EPERM)
-		return (-EACCES);
+		err = -EACCES;
 	else
-		return (perrno);
+		err = perrno;
+
+out_free:
+	free(nvma, M_IXL);
+out:
+	return (err);
 }
 
 int