eth: fbnic: Add devlink dev flash support
authorLee Trager <lee@trager.us>
Mon, 12 May 2025 18:54:01 +0000 (11:54 -0700)
committerPaolo Abeni <pabeni@redhat.com>
Thu, 15 May 2025 10:59:18 +0000 (12:59 +0200)
Add support to update the CMRT and control firmware as well as the UEFI
driver on fbnic using devlink dev flash.

Make sure the shutdown / quiescence paths like suspend take the devlink
lock to prevent them from interrupting the FW flashing process.

Signed-off-by: Lee Trager <lee@trager.us>
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
Reviewed-by: Jacob Keller <jacob.e.keller@intel.com>
Reviewed-by: Simon Horman <horms@kernel.org>
Link: https://patch.msgid.link/20250512190109.2475614-6-lee@trager.us
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
Documentation/networking/device_drivers/ethernet/meta/fbnic.rst
drivers/net/ethernet/meta/Kconfig
drivers/net/ethernet/meta/fbnic/fbnic_devlink.c
drivers/net/ethernet/meta/fbnic/fbnic_fw.h
drivers/net/ethernet/meta/fbnic/fbnic_pci.c

index 3483e498c08ebcb6d4a0cd42bc800417e1301cb6..f8592dec8851f9d73547d3b5c8415e5f9a6baa35 100644 (file)
@@ -28,6 +28,17 @@ devlink dev info provides version information for all three components. In
 addition to the version the hg commit hash of the build is included as a
 separate entry.
 
+Upgrading Firmware
+------------------
+
+fbnic supports updating firmware using signed PLDM images with devlink dev
+flash. PLDM images are written into the flash. Flashing does not interrupt
+the operation of the device.
+
+On host boot the latest UEFI driver is always used, no explicit activation
+is required. Firmware activation is required to run new control firmware. cmrt
+firmware can only be activated by power cycling the NIC.
+
 Statistics
 ----------
 
index 831921b9d4d5e22d84fa78486621114a56db2b5f..3ba527514f1e9d097bcd139e378f59c9b4612591 100644 (file)
@@ -27,6 +27,7 @@ config FBNIC
        select NET_DEVLINK
        select PAGE_POOL
        select PHYLINK
+       select PLDMFW
        help
          This driver supports Meta Platforms Host Network Interface.
 
index 0072d612215e49856d614a8194f26a3fa5a94a83..71d9461a0d1b0f6e0864b49006542596a812bde2 100644 (file)
@@ -3,10 +3,12 @@
 
 #include <linux/unaligned.h>
 #include <linux/pci.h>
+#include <linux/pldmfw.h>
 #include <linux/types.h>
 #include <net/devlink.h>
 
 #include "fbnic.h"
+#include "fbnic_tlv.h"
 
 #define FBNIC_SN_STR_LEN       24
 
@@ -109,8 +111,264 @@ static int fbnic_devlink_info_get(struct devlink *devlink,
        return 0;
 }
 
+static bool
+fbnic_pldm_match_record(struct pldmfw *context, struct pldmfw_record *record)
+{
+       struct pldmfw_desc_tlv *desc;
+       u32 anti_rollback_ver = 0;
+       struct devlink *devlink;
+       struct fbnic_dev *fbd;
+       struct pci_dev *pdev;
+
+       /* First, use the standard PCI matching function */
+       if (!pldmfw_op_pci_match_record(context, record))
+               return false;
+
+       pdev = to_pci_dev(context->dev);
+       fbd = pci_get_drvdata(pdev);
+       devlink = priv_to_devlink(fbd);
+
+       /* If PCI match is successful, check for vendor-specific descriptors */
+       list_for_each_entry(desc, &record->descs, entry) {
+               if (desc->type != PLDM_DESC_ID_VENDOR_DEFINED)
+                       continue;
+
+               if (desc->size < 21 || desc->data[0] != 1 ||
+                   desc->data[1] != 15)
+                       continue;
+
+               if (memcmp(desc->data + 2, "AntiRollbackVer", 15) != 0)
+                       continue;
+
+               anti_rollback_ver = get_unaligned_le32(desc->data + 17);
+               break;
+       }
+
+       /* Compare versions and return error if they do not match */
+       if (anti_rollback_ver < fbd->fw_cap.anti_rollback_version) {
+               char buf[128];
+
+               snprintf(buf, sizeof(buf),
+                        "New firmware anti-rollback version (0x%x) is older than device version (0x%x)!",
+                        anti_rollback_ver, fbd->fw_cap.anti_rollback_version);
+               devlink_flash_update_status_notify(devlink, buf,
+                                                  "Anti-Rollback", 0, 0);
+
+               return false;
+       }
+
+       return true;
+}
+
+static int
+fbnic_flash_start(struct fbnic_dev *fbd, struct pldmfw_component *component)
+{
+       struct fbnic_fw_completion *cmpl;
+       int err;
+
+       cmpl = kzalloc(sizeof(*cmpl), GFP_KERNEL);
+       if (!cmpl)
+               return -ENOMEM;
+
+       fbnic_fw_init_cmpl(cmpl, FBNIC_TLV_MSG_ID_FW_START_UPGRADE_REQ);
+       err = fbnic_fw_xmit_fw_start_upgrade(fbd, cmpl,
+                                            component->identifier,
+                                            component->component_size);
+       if (err)
+               goto cmpl_free;
+
+       /* Wait for firmware to ack firmware upgrade start */
+       if (wait_for_completion_timeout(&cmpl->done, 10 * HZ))
+               err = cmpl->result;
+       else
+               err = -ETIMEDOUT;
+
+       fbnic_fw_clear_cmpl(fbd, cmpl);
+cmpl_free:
+       fbnic_fw_put_cmpl(cmpl);
+
+       return err;
+}
+
+static int
+fbnic_flash_component(struct pldmfw *context,
+                     struct pldmfw_component *component)
+{
+       const u8 *data = component->component_data;
+       const u32 size = component->component_size;
+       struct fbnic_fw_completion *cmpl;
+       const char *component_name;
+       struct devlink *devlink;
+       struct fbnic_dev *fbd;
+       struct pci_dev *pdev;
+       u32 offset = 0;
+       u32 length = 0;
+       char buf[32];
+       int err;
+
+       pdev = to_pci_dev(context->dev);
+       fbd = pci_get_drvdata(pdev);
+       devlink = priv_to_devlink(fbd);
+
+       switch (component->identifier) {
+       case QSPI_SECTION_CMRT:
+               component_name = "boot1";
+               break;
+       case QSPI_SECTION_CONTROL_FW:
+               component_name = "boot2";
+               break;
+       case QSPI_SECTION_OPTION_ROM:
+               component_name = "option-rom";
+               break;
+       default:
+               snprintf(buf, sizeof(buf), "Unknown component ID %u!",
+                        component->identifier);
+               devlink_flash_update_status_notify(devlink, buf, NULL, 0,
+                                                  size);
+               return -EINVAL;
+       }
+
+       /* Once firmware receives the request to start upgrading it responds
+        * with two messages:
+        * 1. An ACK that it received the message and possible error code
+        *    indicating that an upgrade is not currently possible.
+        * 2. A request for the first chunk of data
+        *
+        * Setup completions for write before issuing the start message so
+        * the driver can catch both messages.
+        */
+       cmpl = kzalloc(sizeof(*cmpl), GFP_KERNEL);
+       if (!cmpl)
+               return -ENOMEM;
+
+       fbnic_fw_init_cmpl(cmpl, FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_REQ);
+       err = fbnic_mbx_set_cmpl(fbd, cmpl);
+       if (err)
+               goto cmpl_free;
+
+       devlink_flash_update_timeout_notify(devlink, "Initializing",
+                                           component_name, 15);
+       err = fbnic_flash_start(fbd, component);
+       if (err)
+               goto err_no_msg;
+
+       while (offset < size) {
+               if (!wait_for_completion_timeout(&cmpl->done, 15 * HZ)) {
+                       err = -ETIMEDOUT;
+                       break;
+               }
+
+               err = cmpl->result;
+               if (err)
+                       break;
+
+               /* Verify firmware is requesting the next chunk in the seq. */
+               if (cmpl->u.fw_update.offset != offset + length) {
+                       err = -EFAULT;
+                       break;
+               }
+
+               offset = cmpl->u.fw_update.offset;
+               length = cmpl->u.fw_update.length;
+
+               if (length > TLV_MAX_DATA || offset + length > size) {
+                       err = -EFAULT;
+                       break;
+               }
+
+               devlink_flash_update_status_notify(devlink, "Flashing",
+                                                  component_name,
+                                                  offset, size);
+
+               /* Mailbox will set length to 0 once it receives the finish
+                * message.
+                */
+               if (!length)
+                       continue;
+
+               reinit_completion(&cmpl->done);
+               err = fbnic_fw_xmit_fw_write_chunk(fbd, data, offset, length,
+                                                  0);
+               if (err)
+                       break;
+       }
+
+       if (err) {
+               fbnic_fw_xmit_fw_write_chunk(fbd, NULL, 0, 0, err);
+err_no_msg:
+               snprintf(buf, sizeof(buf), "Mailbox encountered error %d!",
+                        err);
+               devlink_flash_update_status_notify(devlink, buf,
+                                                  component_name, 0, 0);
+       }
+
+       fbnic_fw_clear_cmpl(fbd, cmpl);
+cmpl_free:
+       fbnic_fw_put_cmpl(cmpl);
+
+       return err;
+}
+
+static const struct pldmfw_ops fbnic_pldmfw_ops = {
+       .match_record = fbnic_pldm_match_record,
+       .flash_component = fbnic_flash_component,
+};
+
+static int
+fbnic_devlink_flash_update(struct devlink *devlink,
+                          struct devlink_flash_update_params *params,
+                          struct netlink_ext_ack *extack)
+{
+       struct fbnic_dev *fbd = devlink_priv(devlink);
+       const struct firmware *fw = params->fw;
+       struct device *dev = fbd->dev;
+       struct pldmfw context;
+       char *err_msg;
+       int err;
+
+       context.ops = &fbnic_pldmfw_ops;
+       context.dev = dev;
+
+       err = pldmfw_flash_image(&context, fw);
+       if (err) {
+               switch (err) {
+               case -EINVAL:
+                       err_msg = "Invalid image";
+                       break;
+               case -EOPNOTSUPP:
+                       err_msg = "Unsupported image";
+                       break;
+               case -ENOMEM:
+                       err_msg = "Out of memory";
+                       break;
+               case -EFAULT:
+                       err_msg = "Invalid header";
+                       break;
+               case -ENOENT:
+                       err_msg = "No matching record";
+                       break;
+               case -ENODEV:
+                       err_msg = "No matching device";
+                       break;
+               case -ETIMEDOUT:
+                       err_msg = "Timed out waiting for reply";
+                       break;
+               default:
+                       err_msg = "Unknown error";
+                       break;
+               }
+
+               NL_SET_ERR_MSG_FMT_MOD(extack,
+                                      "Failed to flash PLDM Image: %s (error: %d)",
+                                      err_msg, err);
+       }
+
+       return err;
+}
+
 static const struct devlink_ops fbnic_devlink_ops = {
-       .info_get = fbnic_devlink_info_get,
+       .info_get       = fbnic_devlink_info_get,
+       .flash_update   = fbnic_devlink_flash_update,
 };
 
 void fbnic_devlink_free(struct fbnic_dev *fbd)
index 0ab6ae3859e4ed2a4eba32624c9a936843ce580e..6baac10fd68888a2a8d166cf43c4f09f53e016b6 100644 (file)
@@ -100,6 +100,15 @@ do {                                                                       \
 #define fbnic_mk_fw_ver_str(_rev_id, _str) \
        fbnic_mk_full_fw_ver_str(_rev_id, "", "", _str, sizeof(_str))
 
+enum {
+       QSPI_SECTION_CMRT                       = 0,
+       QSPI_SECTION_CONTROL_FW                 = 1,
+       QSPI_SECTION_UCODE                      = 2,
+       QSPI_SECTION_OPTION_ROM                 = 3,
+       QSPI_SECTION_USER                       = 4,
+       QSPI_SECTION_INVALID,
+};
+
 #define FW_HEARTBEAT_PERIOD            (10 * HZ)
 
 enum {
index 70a852b3e99d0c2064a4feee0fad8a3c95661f2b..249d3ef862d5a97f19e271da26245a6c6625d306 100644 (file)
@@ -6,6 +6,7 @@
 #include <linux/pci.h>
 #include <linux/rtnetlink.h>
 #include <linux/types.h>
+#include <net/devlink.h>
 
 #include "fbnic.h"
 #include "fbnic_drvinfo.h"
@@ -388,8 +389,12 @@ static int fbnic_pm_suspend(struct device *dev)
        rtnl_unlock();
 
 null_uc_addr:
+       devl_lock(priv_to_devlink(fbd));
+
        fbnic_fw_free_mbx(fbd);
 
+       devl_unlock(priv_to_devlink(fbd));
+
        /* Free the IRQs so they aren't trying to occupy sleeping CPUs */
        fbnic_free_irqs(fbd);
 
@@ -420,11 +425,15 @@ static int __fbnic_pm_resume(struct device *dev)
 
        fbd->mac->init_regs(fbd);
 
+       devl_lock(priv_to_devlink(fbd));
+
        /* Re-enable mailbox */
        err = fbnic_fw_request_mbx(fbd);
        if (err)
                goto err_free_irqs;
 
+       devl_unlock(priv_to_devlink(fbd));
+
        /* No netdev means there isn't a network interface to bring up */
        if (fbnic_init_failure(fbd))
                return 0;