|
3 | 3 |
|
4 | 4 | #include <linux/unaligned.h>
|
5 | 5 | #include <linux/pci.h>
|
| 6 | +#include <linux/pldmfw.h> |
6 | 7 | #include <linux/types.h>
|
7 | 8 | #include <net/devlink.h>
|
8 | 9 |
|
9 | 10 | #include "fbnic.h"
|
| 11 | +#include "fbnic_tlv.h" |
10 | 12 |
|
11 | 13 | #define FBNIC_SN_STR_LEN 24
|
12 | 14 |
|
@@ -109,8 +111,264 @@ static int fbnic_devlink_info_get(struct devlink *devlink,
|
109 | 111 | return 0;
|
110 | 112 | }
|
111 | 113 |
|
| 114 | +static bool |
| 115 | +fbnic_pldm_match_record(struct pldmfw *context, struct pldmfw_record *record) |
| 116 | +{ |
| 117 | + struct pldmfw_desc_tlv *desc; |
| 118 | + u32 anti_rollback_ver = 0; |
| 119 | + struct devlink *devlink; |
| 120 | + struct fbnic_dev *fbd; |
| 121 | + struct pci_dev *pdev; |
| 122 | + |
| 123 | + /* First, use the standard PCI matching function */ |
| 124 | + if (!pldmfw_op_pci_match_record(context, record)) |
| 125 | + return false; |
| 126 | + |
| 127 | + pdev = to_pci_dev(context->dev); |
| 128 | + fbd = pci_get_drvdata(pdev); |
| 129 | + devlink = priv_to_devlink(fbd); |
| 130 | + |
| 131 | + /* If PCI match is successful, check for vendor-specific descriptors */ |
| 132 | + list_for_each_entry(desc, &record->descs, entry) { |
| 133 | + if (desc->type != PLDM_DESC_ID_VENDOR_DEFINED) |
| 134 | + continue; |
| 135 | + |
| 136 | + if (desc->size < 21 || desc->data[0] != 1 || |
| 137 | + desc->data[1] != 15) |
| 138 | + continue; |
| 139 | + |
| 140 | + if (memcmp(desc->data + 2, "AntiRollbackVer", 15) != 0) |
| 141 | + continue; |
| 142 | + |
| 143 | + anti_rollback_ver = get_unaligned_le32(desc->data + 17); |
| 144 | + break; |
| 145 | + } |
| 146 | + |
| 147 | + /* Compare versions and return error if they do not match */ |
| 148 | + if (anti_rollback_ver < fbd->fw_cap.anti_rollback_version) { |
| 149 | + char buf[128]; |
| 150 | + |
| 151 | + snprintf(buf, sizeof(buf), |
| 152 | + "New firmware anti-rollback version (0x%x) is older than device version (0x%x)!", |
| 153 | + anti_rollback_ver, fbd->fw_cap.anti_rollback_version); |
| 154 | + devlink_flash_update_status_notify(devlink, buf, |
| 155 | + "Anti-Rollback", 0, 0); |
| 156 | + |
| 157 | + return false; |
| 158 | + } |
| 159 | + |
| 160 | + return true; |
| 161 | +} |
| 162 | + |
| 163 | +static int |
| 164 | +fbnic_flash_start(struct fbnic_dev *fbd, struct pldmfw_component *component) |
| 165 | +{ |
| 166 | + struct fbnic_fw_completion *cmpl; |
| 167 | + int err; |
| 168 | + |
| 169 | + cmpl = kzalloc(sizeof(*cmpl), GFP_KERNEL); |
| 170 | + if (!cmpl) |
| 171 | + return -ENOMEM; |
| 172 | + |
| 173 | + fbnic_fw_init_cmpl(cmpl, FBNIC_TLV_MSG_ID_FW_START_UPGRADE_REQ); |
| 174 | + err = fbnic_fw_xmit_fw_start_upgrade(fbd, cmpl, |
| 175 | + component->identifier, |
| 176 | + component->component_size); |
| 177 | + if (err) |
| 178 | + goto cmpl_free; |
| 179 | + |
| 180 | + /* Wait for firmware to ack firmware upgrade start */ |
| 181 | + if (wait_for_completion_timeout(&cmpl->done, 10 * HZ)) |
| 182 | + err = cmpl->result; |
| 183 | + else |
| 184 | + err = -ETIMEDOUT; |
| 185 | + |
| 186 | + fbnic_fw_clear_cmpl(fbd, cmpl); |
| 187 | +cmpl_free: |
| 188 | + fbnic_fw_put_cmpl(cmpl); |
| 189 | + |
| 190 | + return err; |
| 191 | +} |
| 192 | + |
| 193 | +static int |
| 194 | +fbnic_flash_component(struct pldmfw *context, |
| 195 | + struct pldmfw_component *component) |
| 196 | +{ |
| 197 | + const u8 *data = component->component_data; |
| 198 | + const u32 size = component->component_size; |
| 199 | + struct fbnic_fw_completion *cmpl; |
| 200 | + const char *component_name; |
| 201 | + struct devlink *devlink; |
| 202 | + struct fbnic_dev *fbd; |
| 203 | + struct pci_dev *pdev; |
| 204 | + u32 offset = 0; |
| 205 | + u32 length = 0; |
| 206 | + char buf[32]; |
| 207 | + int err; |
| 208 | + |
| 209 | + pdev = to_pci_dev(context->dev); |
| 210 | + fbd = pci_get_drvdata(pdev); |
| 211 | + devlink = priv_to_devlink(fbd); |
| 212 | + |
| 213 | + switch (component->identifier) { |
| 214 | + case QSPI_SECTION_CMRT: |
| 215 | + component_name = "boot1"; |
| 216 | + break; |
| 217 | + case QSPI_SECTION_CONTROL_FW: |
| 218 | + component_name = "boot2"; |
| 219 | + break; |
| 220 | + case QSPI_SECTION_OPTION_ROM: |
| 221 | + component_name = "option-rom"; |
| 222 | + break; |
| 223 | + default: |
| 224 | + snprintf(buf, sizeof(buf), "Unknown component ID %u!", |
| 225 | + component->identifier); |
| 226 | + devlink_flash_update_status_notify(devlink, buf, NULL, 0, |
| 227 | + size); |
| 228 | + return -EINVAL; |
| 229 | + } |
| 230 | + |
| 231 | + /* Once firmware receives the request to start upgrading it responds |
| 232 | + * with two messages: |
| 233 | + * 1. An ACK that it received the message and possible error code |
| 234 | + * indicating that an upgrade is not currently possible. |
| 235 | + * 2. A request for the first chunk of data |
| 236 | + * |
| 237 | + * Setup completions for write before issuing the start message so |
| 238 | + * the driver can catch both messages. |
| 239 | + */ |
| 240 | + cmpl = kzalloc(sizeof(*cmpl), GFP_KERNEL); |
| 241 | + if (!cmpl) |
| 242 | + return -ENOMEM; |
| 243 | + |
| 244 | + fbnic_fw_init_cmpl(cmpl, FBNIC_TLV_MSG_ID_FW_WRITE_CHUNK_REQ); |
| 245 | + err = fbnic_mbx_set_cmpl(fbd, cmpl); |
| 246 | + if (err) |
| 247 | + goto cmpl_free; |
| 248 | + |
| 249 | + devlink_flash_update_timeout_notify(devlink, "Initializing", |
| 250 | + component_name, 15); |
| 251 | + err = fbnic_flash_start(fbd, component); |
| 252 | + if (err) |
| 253 | + goto err_no_msg; |
| 254 | + |
| 255 | + while (offset < size) { |
| 256 | + if (!wait_for_completion_timeout(&cmpl->done, 15 * HZ)) { |
| 257 | + err = -ETIMEDOUT; |
| 258 | + break; |
| 259 | + } |
| 260 | + |
| 261 | + err = cmpl->result; |
| 262 | + if (err) |
| 263 | + break; |
| 264 | + |
| 265 | + /* Verify firmware is requesting the next chunk in the seq. */ |
| 266 | + if (cmpl->u.fw_update.offset != offset + length) { |
| 267 | + err = -EFAULT; |
| 268 | + break; |
| 269 | + } |
| 270 | + |
| 271 | + offset = cmpl->u.fw_update.offset; |
| 272 | + length = cmpl->u.fw_update.length; |
| 273 | + |
| 274 | + if (length > TLV_MAX_DATA || offset + length > size) { |
| 275 | + err = -EFAULT; |
| 276 | + break; |
| 277 | + } |
| 278 | + |
| 279 | + devlink_flash_update_status_notify(devlink, "Flashing", |
| 280 | + component_name, |
| 281 | + offset, size); |
| 282 | + |
| 283 | + /* Mailbox will set length to 0 once it receives the finish |
| 284 | + * message. |
| 285 | + */ |
| 286 | + if (!length) |
| 287 | + continue; |
| 288 | + |
| 289 | + reinit_completion(&cmpl->done); |
| 290 | + err = fbnic_fw_xmit_fw_write_chunk(fbd, data, offset, length, |
| 291 | + 0); |
| 292 | + if (err) |
| 293 | + break; |
| 294 | + } |
| 295 | + |
| 296 | + if (err) { |
| 297 | + fbnic_fw_xmit_fw_write_chunk(fbd, NULL, 0, 0, err); |
| 298 | +err_no_msg: |
| 299 | + snprintf(buf, sizeof(buf), "Mailbox encountered error %d!", |
| 300 | + err); |
| 301 | + devlink_flash_update_status_notify(devlink, buf, |
| 302 | + component_name, 0, 0); |
| 303 | + } |
| 304 | + |
| 305 | + fbnic_fw_clear_cmpl(fbd, cmpl); |
| 306 | +cmpl_free: |
| 307 | + fbnic_fw_put_cmpl(cmpl); |
| 308 | + |
| 309 | + return err; |
| 310 | +} |
| 311 | + |
| 312 | +static const struct pldmfw_ops fbnic_pldmfw_ops = { |
| 313 | + .match_record = fbnic_pldm_match_record, |
| 314 | + .flash_component = fbnic_flash_component, |
| 315 | +}; |
| 316 | + |
| 317 | +static int |
| 318 | +fbnic_devlink_flash_update(struct devlink *devlink, |
| 319 | + struct devlink_flash_update_params *params, |
| 320 | + struct netlink_ext_ack *extack) |
| 321 | +{ |
| 322 | + struct fbnic_dev *fbd = devlink_priv(devlink); |
| 323 | + const struct firmware *fw = params->fw; |
| 324 | + struct device *dev = fbd->dev; |
| 325 | + struct pldmfw context; |
| 326 | + char *err_msg; |
| 327 | + int err; |
| 328 | + |
| 329 | + context.ops = &fbnic_pldmfw_ops; |
| 330 | + context.dev = dev; |
| 331 | + |
| 332 | + err = pldmfw_flash_image(&context, fw); |
| 333 | + if (err) { |
| 334 | + switch (err) { |
| 335 | + case -EINVAL: |
| 336 | + err_msg = "Invalid image"; |
| 337 | + break; |
| 338 | + case -EOPNOTSUPP: |
| 339 | + err_msg = "Unsupported image"; |
| 340 | + break; |
| 341 | + case -ENOMEM: |
| 342 | + err_msg = "Out of memory"; |
| 343 | + break; |
| 344 | + case -EFAULT: |
| 345 | + err_msg = "Invalid header"; |
| 346 | + break; |
| 347 | + case -ENOENT: |
| 348 | + err_msg = "No matching record"; |
| 349 | + break; |
| 350 | + case -ENODEV: |
| 351 | + err_msg = "No matching device"; |
| 352 | + break; |
| 353 | + case -ETIMEDOUT: |
| 354 | + err_msg = "Timed out waiting for reply"; |
| 355 | + break; |
| 356 | + default: |
| 357 | + err_msg = "Unknown error"; |
| 358 | + break; |
| 359 | + } |
| 360 | + |
| 361 | + NL_SET_ERR_MSG_FMT_MOD(extack, |
| 362 | + "Failed to flash PLDM Image: %s (error: %d)", |
| 363 | + err_msg, err); |
| 364 | + } |
| 365 | + |
| 366 | + return err; |
| 367 | +} |
| 368 | + |
112 | 369 | static const struct devlink_ops fbnic_devlink_ops = {
|
113 |
| - .info_get = fbnic_devlink_info_get, |
| 370 | + .info_get = fbnic_devlink_info_get, |
| 371 | + .flash_update = fbnic_devlink_flash_update, |
114 | 372 | };
|
115 | 373 |
|
116 | 374 | void fbnic_devlink_free(struct fbnic_dev *fbd)
|
|
0 commit comments