-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for firmware update through the devlink interface. This update copies the firmware object into the device, asks the current firmware to install it, then asks the firmware to select the new firmware for the next boot-up. The install and select steps are launched as asynchronous requests, which are then followed up with status request commands. These status request commands will be answered with an EAGAIN return value and will try again until the request has completed or reached the timeout specified. Signed-off-by: Shannon Nelson <snelson@pensando.io> Acked-by: Jakub Kicinski <kuba@kernel.org> Signed-off-by: David S. Miller <davem@davemloft.net>
- Loading branch information
Shannon Nelson
authored and
David S. Miller
committed
Sep 18, 2020
1 parent
87c905d
commit 30b5191
Showing
5 changed files
with
239 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
// SPDX-License-Identifier: GPL-2.0 | ||
/* Copyright(c) 2020 Pensando Systems, Inc */ | ||
|
||
#include <linux/kernel.h> | ||
#include <linux/types.h> | ||
#include <linux/errno.h> | ||
#include <linux/firmware.h> | ||
|
||
#include "ionic.h" | ||
#include "ionic_dev.h" | ||
#include "ionic_lif.h" | ||
#include "ionic_devlink.h" | ||
|
||
/* The worst case wait for the install activity is about 25 minutes when | ||
* installing a new CPLD, which is very seldom. Normal is about 30-35 | ||
* seconds. Since the driver can't tell if a CPLD update will happen we | ||
* set the timeout for the ugly case. | ||
*/ | ||
#define IONIC_FW_INSTALL_TIMEOUT (25 * 60) | ||
#define IONIC_FW_SELECT_TIMEOUT 30 | ||
|
||
/* Number of periodic log updates during fw file download */ | ||
#define IONIC_FW_INTERVAL_FRACTION 32 | ||
|
||
static void ionic_dev_cmd_firmware_download(struct ionic_dev *idev, u64 addr, | ||
u32 offset, u32 length) | ||
{ | ||
union ionic_dev_cmd cmd = { | ||
.fw_download.opcode = IONIC_CMD_FW_DOWNLOAD, | ||
.fw_download.offset = offset, | ||
.fw_download.addr = addr, | ||
.fw_download.length = length | ||
}; | ||
|
||
ionic_dev_cmd_go(idev, &cmd); | ||
} | ||
|
||
static void ionic_dev_cmd_firmware_install(struct ionic_dev *idev) | ||
{ | ||
union ionic_dev_cmd cmd = { | ||
.fw_control.opcode = IONIC_CMD_FW_CONTROL, | ||
.fw_control.oper = IONIC_FW_INSTALL_ASYNC | ||
}; | ||
|
||
ionic_dev_cmd_go(idev, &cmd); | ||
} | ||
|
||
static void ionic_dev_cmd_firmware_activate(struct ionic_dev *idev, u8 slot) | ||
{ | ||
union ionic_dev_cmd cmd = { | ||
.fw_control.opcode = IONIC_CMD_FW_CONTROL, | ||
.fw_control.oper = IONIC_FW_ACTIVATE_ASYNC, | ||
.fw_control.slot = slot | ||
}; | ||
|
||
ionic_dev_cmd_go(idev, &cmd); | ||
} | ||
|
||
static int ionic_fw_status_long_wait(struct ionic *ionic, | ||
const char *label, | ||
unsigned long timeout, | ||
u8 fw_cmd, | ||
struct netlink_ext_ack *extack) | ||
{ | ||
union ionic_dev_cmd cmd = { | ||
.fw_control.opcode = IONIC_CMD_FW_CONTROL, | ||
.fw_control.oper = fw_cmd, | ||
}; | ||
unsigned long start_time; | ||
unsigned long end_time; | ||
int err; | ||
|
||
start_time = jiffies; | ||
end_time = start_time + (timeout * HZ); | ||
do { | ||
mutex_lock(&ionic->dev_cmd_lock); | ||
ionic_dev_cmd_go(&ionic->idev, &cmd); | ||
err = ionic_dev_cmd_wait(ionic, DEVCMD_TIMEOUT); | ||
mutex_unlock(&ionic->dev_cmd_lock); | ||
|
||
msleep(20); | ||
} while (time_before(jiffies, end_time) && (err == -EAGAIN || err == -ETIMEDOUT)); | ||
|
||
if (err == -EAGAIN || err == -ETIMEDOUT) { | ||
NL_SET_ERR_MSG_MOD(extack, "Firmware wait timed out"); | ||
dev_err(ionic->dev, "DEV_CMD firmware wait %s timed out\n", label); | ||
} else if (err) { | ||
NL_SET_ERR_MSG_MOD(extack, "Firmware wait failed"); | ||
} | ||
|
||
return err; | ||
} | ||
|
||
int ionic_firmware_update(struct ionic_lif *lif, const char *fw_name, | ||
struct netlink_ext_ack *extack) | ||
{ | ||
struct ionic_dev *idev = &lif->ionic->idev; | ||
struct net_device *netdev = lif->netdev; | ||
struct ionic *ionic = lif->ionic; | ||
union ionic_dev_cmd_comp comp; | ||
u32 buf_sz, copy_sz, offset; | ||
const struct firmware *fw; | ||
struct devlink *dl; | ||
int next_interval; | ||
int err = 0; | ||
u8 fw_slot; | ||
|
||
netdev_info(netdev, "Installing firmware %s\n", fw_name); | ||
|
||
dl = priv_to_devlink(ionic); | ||
devlink_flash_update_begin_notify(dl); | ||
devlink_flash_update_status_notify(dl, "Preparing to flash", NULL, 0, 0); | ||
|
||
err = request_firmware(&fw, fw_name, ionic->dev); | ||
if (err) { | ||
NL_SET_ERR_MSG_MOD(extack, "Unable to find firmware file"); | ||
goto err_out; | ||
} | ||
|
||
buf_sz = sizeof(idev->dev_cmd_regs->data); | ||
|
||
netdev_dbg(netdev, | ||
"downloading firmware - size %d part_sz %d nparts %lu\n", | ||
(int)fw->size, buf_sz, DIV_ROUND_UP(fw->size, buf_sz)); | ||
|
||
offset = 0; | ||
next_interval = 0; | ||
while (offset < fw->size) { | ||
if (offset >= next_interval) { | ||
devlink_flash_update_status_notify(dl, "Downloading", NULL, | ||
offset, fw->size); | ||
next_interval = offset + (fw->size / IONIC_FW_INTERVAL_FRACTION); | ||
} | ||
|
||
copy_sz = min_t(unsigned int, buf_sz, fw->size - offset); | ||
mutex_lock(&ionic->dev_cmd_lock); | ||
memcpy_toio(&idev->dev_cmd_regs->data, fw->data + offset, copy_sz); | ||
ionic_dev_cmd_firmware_download(idev, | ||
offsetof(union ionic_dev_cmd_regs, data), | ||
offset, copy_sz); | ||
err = ionic_dev_cmd_wait(ionic, DEVCMD_TIMEOUT); | ||
mutex_unlock(&ionic->dev_cmd_lock); | ||
if (err) { | ||
netdev_err(netdev, | ||
"download failed offset 0x%x addr 0x%lx len 0x%x\n", | ||
offset, offsetof(union ionic_dev_cmd_regs, data), | ||
copy_sz); | ||
NL_SET_ERR_MSG_MOD(extack, "Segment download failed"); | ||
goto err_out; | ||
} | ||
offset += copy_sz; | ||
} | ||
devlink_flash_update_status_notify(dl, "Downloading", NULL, | ||
fw->size, fw->size); | ||
|
||
devlink_flash_update_timeout_notify(dl, "Installing", NULL, | ||
IONIC_FW_INSTALL_TIMEOUT); | ||
|
||
mutex_lock(&ionic->dev_cmd_lock); | ||
ionic_dev_cmd_firmware_install(idev); | ||
err = ionic_dev_cmd_wait(ionic, DEVCMD_TIMEOUT); | ||
ionic_dev_cmd_comp(idev, (union ionic_dev_cmd_comp *)&comp); | ||
fw_slot = comp.fw_control.slot; | ||
mutex_unlock(&ionic->dev_cmd_lock); | ||
if (err) { | ||
NL_SET_ERR_MSG_MOD(extack, "Failed to start firmware install"); | ||
goto err_out; | ||
} | ||
|
||
err = ionic_fw_status_long_wait(ionic, "Installing", | ||
IONIC_FW_INSTALL_TIMEOUT, | ||
IONIC_FW_INSTALL_STATUS, | ||
extack); | ||
if (err) | ||
goto err_out; | ||
|
||
devlink_flash_update_timeout_notify(dl, "Selecting", NULL, | ||
IONIC_FW_SELECT_TIMEOUT); | ||
|
||
mutex_lock(&ionic->dev_cmd_lock); | ||
ionic_dev_cmd_firmware_activate(idev, fw_slot); | ||
err = ionic_dev_cmd_wait(ionic, DEVCMD_TIMEOUT); | ||
mutex_unlock(&ionic->dev_cmd_lock); | ||
if (err) { | ||
NL_SET_ERR_MSG_MOD(extack, "Failed to start firmware select"); | ||
goto err_out; | ||
} | ||
|
||
err = ionic_fw_status_long_wait(ionic, "Selecting", | ||
IONIC_FW_SELECT_TIMEOUT, | ||
IONIC_FW_ACTIVATE_STATUS, | ||
extack); | ||
if (err) | ||
goto err_out; | ||
|
||
netdev_info(netdev, "Firmware update completed\n"); | ||
|
||
err_out: | ||
if (err) | ||
devlink_flash_update_status_notify(dl, "Flash failed", NULL, 0, 0); | ||
else | ||
devlink_flash_update_status_notify(dl, "Flash done", NULL, 0, 0); | ||
release_firmware(fw); | ||
devlink_flash_update_end_notify(dl); | ||
return err; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters