|
| 1 | +// SPDX-License-Identifier: GPL-2.0 |
| 2 | +/* Huawei HiNIC PCI Express Linux driver |
| 3 | + * Copyright(c) 2017 Huawei Technologies Co., Ltd |
| 4 | + * |
| 5 | + * This program is free software; you can redistribute it and/or modify it |
| 6 | + * under the terms and conditions of the GNU General Public License, |
| 7 | + * version 2, as published by the Free Software Foundation. |
| 8 | + * |
| 9 | + * This program is distributed in the hope it will be useful, but WITHOUT |
| 10 | + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| 11 | + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| 12 | + * for more details. |
| 13 | + * |
| 14 | + */ |
| 15 | +#include <linux/netlink.h> |
| 16 | +#include <net/devlink.h> |
| 17 | +#include <linux/firmware.h> |
| 18 | + |
| 19 | +#include "hinic_dev.h" |
| 20 | +#include "hinic_port.h" |
| 21 | +#include "hinic_devlink.h" |
| 22 | + |
| 23 | +static bool check_image_valid(struct hinic_devlink_priv *priv, const u8 *buf, |
| 24 | + u32 image_size, struct host_image_st *host_image) |
| 25 | +{ |
| 26 | + struct fw_image_st *fw_image = NULL; |
| 27 | + u32 len = 0; |
| 28 | + u32 i; |
| 29 | + |
| 30 | + fw_image = (struct fw_image_st *)buf; |
| 31 | + |
| 32 | + if (fw_image->fw_magic != HINIC_MAGIC_NUM) { |
| 33 | + dev_err(&priv->hwdev->hwif->pdev->dev, "Wrong fw_magic read from file, fw_magic: 0x%x\n", |
| 34 | + fw_image->fw_magic); |
| 35 | + return false; |
| 36 | + } |
| 37 | + |
| 38 | + if (fw_image->fw_info.fw_section_cnt > MAX_FW_TYPE_NUM) { |
| 39 | + dev_err(&priv->hwdev->hwif->pdev->dev, "Wrong fw_type_num read from file, fw_type_num: 0x%x\n", |
| 40 | + fw_image->fw_info.fw_section_cnt); |
| 41 | + return false; |
| 42 | + } |
| 43 | + |
| 44 | + for (i = 0; i < fw_image->fw_info.fw_section_cnt; i++) { |
| 45 | + len += fw_image->fw_section_info[i].fw_section_len; |
| 46 | + memcpy(&host_image->image_section_info[i], |
| 47 | + &fw_image->fw_section_info[i], |
| 48 | + sizeof(struct fw_section_info_st)); |
| 49 | + } |
| 50 | + |
| 51 | + if (len != fw_image->fw_len || |
| 52 | + (fw_image->fw_len + UPDATEFW_IMAGE_HEAD_SIZE) != image_size) { |
| 53 | + dev_err(&priv->hwdev->hwif->pdev->dev, "Wrong data size read from file\n"); |
| 54 | + return false; |
| 55 | + } |
| 56 | + |
| 57 | + host_image->image_info.up_total_len = fw_image->fw_len; |
| 58 | + host_image->image_info.fw_version = fw_image->fw_version; |
| 59 | + host_image->section_type_num = fw_image->fw_info.fw_section_cnt; |
| 60 | + host_image->device_id = fw_image->device_id; |
| 61 | + |
| 62 | + return true; |
| 63 | +} |
| 64 | + |
| 65 | +static bool check_image_integrity(struct hinic_devlink_priv *priv, |
| 66 | + struct host_image_st *host_image, |
| 67 | + u32 update_type) |
| 68 | +{ |
| 69 | + u32 collect_section_type = 0; |
| 70 | + u32 i, type; |
| 71 | + |
| 72 | + for (i = 0; i < host_image->section_type_num; i++) { |
| 73 | + type = host_image->image_section_info[i].fw_section_type; |
| 74 | + if (collect_section_type & (1U << type)) { |
| 75 | + dev_err(&priv->hwdev->hwif->pdev->dev, "Duplicate section type: %u\n", |
| 76 | + type); |
| 77 | + return false; |
| 78 | + } |
| 79 | + collect_section_type |= (1U << type); |
| 80 | + } |
| 81 | + |
| 82 | + if (update_type == FW_UPDATE_COLD && |
| 83 | + (((collect_section_type & _IMAGE_COLD_SUB_MODULES_MUST_IN) == |
| 84 | + _IMAGE_COLD_SUB_MODULES_MUST_IN) || |
| 85 | + collect_section_type == _IMAGE_CFG_SUB_MODULES_MUST_IN)) |
| 86 | + return true; |
| 87 | + |
| 88 | + if (update_type == FW_UPDATE_HOT && |
| 89 | + (collect_section_type & _IMAGE_HOT_SUB_MODULES_MUST_IN) == |
| 90 | + _IMAGE_HOT_SUB_MODULES_MUST_IN) |
| 91 | + return true; |
| 92 | + |
| 93 | + if (update_type == FW_UPDATE_COLD) |
| 94 | + dev_err(&priv->hwdev->hwif->pdev->dev, "Check file integrity failed, valid: 0x%x or 0x%lx, current: 0x%x\n", |
| 95 | + _IMAGE_COLD_SUB_MODULES_MUST_IN, |
| 96 | + _IMAGE_CFG_SUB_MODULES_MUST_IN, collect_section_type); |
| 97 | + else |
| 98 | + dev_err(&priv->hwdev->hwif->pdev->dev, "Check file integrity failed, valid:0x%x, current: 0x%x\n", |
| 99 | + _IMAGE_HOT_SUB_MODULES_MUST_IN, collect_section_type); |
| 100 | + |
| 101 | + return false; |
| 102 | +} |
| 103 | + |
| 104 | +static int check_image_device_type(struct hinic_devlink_priv *priv, |
| 105 | + u32 image_device_type) |
| 106 | +{ |
| 107 | + struct hinic_comm_board_info board_info = {0}; |
| 108 | + |
| 109 | + if (hinic_get_board_info(priv->hwdev, &board_info)) { |
| 110 | + dev_err(&priv->hwdev->hwif->pdev->dev, "Get board info failed\n"); |
| 111 | + return false; |
| 112 | + } |
| 113 | + |
| 114 | + if (image_device_type == board_info.info.board_type) |
| 115 | + return true; |
| 116 | + |
| 117 | + dev_err(&priv->hwdev->hwif->pdev->dev, "The device type of upgrade file doesn't match the device type of current firmware, please check the upgrade file\n"); |
| 118 | + dev_err(&priv->hwdev->hwif->pdev->dev, "The image device type: 0x%x, firmware device type: 0x%x\n", |
| 119 | + image_device_type, board_info.info.board_type); |
| 120 | + |
| 121 | + return false; |
| 122 | +} |
| 123 | + |
| 124 | +static int hinic_flash_fw(struct hinic_devlink_priv *priv, const u8 *data, |
| 125 | + struct host_image_st *host_image) |
| 126 | +{ |
| 127 | + u32 section_remain_send_len, send_fragment_len, send_pos, up_total_len; |
| 128 | + struct hinic_cmd_update_fw *fw_update_msg = NULL; |
| 129 | + u32 section_type, section_crc, section_version; |
| 130 | + u32 i, len, section_len, section_offset; |
| 131 | + u16 out_size = sizeof(*fw_update_msg); |
| 132 | + int total_len_flag = 0; |
| 133 | + int err; |
| 134 | + |
| 135 | + fw_update_msg = kzalloc(sizeof(*fw_update_msg), GFP_KERNEL); |
| 136 | + if (!fw_update_msg) |
| 137 | + return -ENOMEM; |
| 138 | + |
| 139 | + up_total_len = host_image->image_info.up_total_len; |
| 140 | + |
| 141 | + for (i = 0; i < host_image->section_type_num; i++) { |
| 142 | + len = host_image->image_section_info[i].fw_section_len; |
| 143 | + if (host_image->image_section_info[i].fw_section_type == |
| 144 | + UP_FW_UPDATE_BOOT) { |
| 145 | + up_total_len = up_total_len - len; |
| 146 | + break; |
| 147 | + } |
| 148 | + } |
| 149 | + |
| 150 | + for (i = 0; i < host_image->section_type_num; i++) { |
| 151 | + section_len = |
| 152 | + host_image->image_section_info[i].fw_section_len; |
| 153 | + section_offset = |
| 154 | + host_image->image_section_info[i].fw_section_offset; |
| 155 | + section_remain_send_len = section_len; |
| 156 | + section_type = |
| 157 | + host_image->image_section_info[i].fw_section_type; |
| 158 | + section_crc = host_image->image_section_info[i].fw_section_crc; |
| 159 | + section_version = |
| 160 | + host_image->image_section_info[i].fw_section_version; |
| 161 | + |
| 162 | + if (section_type == UP_FW_UPDATE_BOOT) |
| 163 | + continue; |
| 164 | + |
| 165 | + send_fragment_len = 0; |
| 166 | + send_pos = 0; |
| 167 | + |
| 168 | + while (section_remain_send_len > 0) { |
| 169 | + if (!total_len_flag) { |
| 170 | + fw_update_msg->total_len = up_total_len; |
| 171 | + total_len_flag = 1; |
| 172 | + } else { |
| 173 | + fw_update_msg->total_len = 0; |
| 174 | + } |
| 175 | + |
| 176 | + memset(fw_update_msg->data, 0, MAX_FW_FRAGMENT_LEN); |
| 177 | + |
| 178 | + fw_update_msg->ctl_info.SF = |
| 179 | + (section_remain_send_len == section_len) ? |
| 180 | + true : false; |
| 181 | + fw_update_msg->section_info.FW_section_CRC = section_crc; |
| 182 | + fw_update_msg->fw_section_version = section_version; |
| 183 | + fw_update_msg->ctl_info.flag = UP_TYPE_A; |
| 184 | + |
| 185 | + if (section_type <= UP_FW_UPDATE_UP_DATA_B) { |
| 186 | + fw_update_msg->section_info.FW_section_type = |
| 187 | + (section_type % 2) ? |
| 188 | + UP_FW_UPDATE_UP_DATA : |
| 189 | + UP_FW_UPDATE_UP_TEXT; |
| 190 | + |
| 191 | + fw_update_msg->ctl_info.flag = UP_TYPE_B; |
| 192 | + if (section_type <= UP_FW_UPDATE_UP_DATA_A) |
| 193 | + fw_update_msg->ctl_info.flag = UP_TYPE_A; |
| 194 | + } else { |
| 195 | + fw_update_msg->section_info.FW_section_type = |
| 196 | + section_type - 0x2; |
| 197 | + } |
| 198 | + |
| 199 | + fw_update_msg->setion_total_len = section_len; |
| 200 | + fw_update_msg->section_offset = send_pos; |
| 201 | + |
| 202 | + if (section_remain_send_len <= MAX_FW_FRAGMENT_LEN) { |
| 203 | + fw_update_msg->ctl_info.SL = true; |
| 204 | + fw_update_msg->ctl_info.fragment_len = |
| 205 | + section_remain_send_len; |
| 206 | + send_fragment_len += section_remain_send_len; |
| 207 | + } else { |
| 208 | + fw_update_msg->ctl_info.SL = false; |
| 209 | + fw_update_msg->ctl_info.fragment_len = |
| 210 | + MAX_FW_FRAGMENT_LEN; |
| 211 | + send_fragment_len += MAX_FW_FRAGMENT_LEN; |
| 212 | + } |
| 213 | + |
| 214 | + memcpy(fw_update_msg->data, |
| 215 | + data + UPDATEFW_IMAGE_HEAD_SIZE + |
| 216 | + section_offset + send_pos, |
| 217 | + fw_update_msg->ctl_info.fragment_len); |
| 218 | + |
| 219 | + err = hinic_port_msg_cmd(priv->hwdev, |
| 220 | + HINIC_PORT_CMD_UPDATE_FW, |
| 221 | + fw_update_msg, |
| 222 | + sizeof(*fw_update_msg), |
| 223 | + fw_update_msg, &out_size); |
| 224 | + if (err || !out_size || fw_update_msg->status) { |
| 225 | + dev_err(&priv->hwdev->hwif->pdev->dev, "Failed to update firmware, err: %d, status: 0x%x, out size: 0x%x\n", |
| 226 | + err, fw_update_msg->status, out_size); |
| 227 | + err = fw_update_msg->status ? |
| 228 | + fw_update_msg->status : -EIO; |
| 229 | + kfree(fw_update_msg); |
| 230 | + return err; |
| 231 | + } |
| 232 | + |
| 233 | + send_pos = send_fragment_len; |
| 234 | + section_remain_send_len = section_len - |
| 235 | + send_fragment_len; |
| 236 | + } |
| 237 | + } |
| 238 | + |
| 239 | + kfree(fw_update_msg); |
| 240 | + |
| 241 | + return 0; |
| 242 | +} |
| 243 | + |
| 244 | +static int hinic_firmware_update(struct hinic_devlink_priv *priv, |
| 245 | + const struct firmware *fw, |
| 246 | + struct netlink_ext_ack *extack) |
| 247 | +{ |
| 248 | + struct host_image_st host_image; |
| 249 | + int err; |
| 250 | + |
| 251 | + memset(&host_image, 0, sizeof(struct host_image_st)); |
| 252 | + |
| 253 | + if (!check_image_valid(priv, fw->data, fw->size, &host_image) || |
| 254 | + !check_image_integrity(priv, &host_image, FW_UPDATE_COLD) || |
| 255 | + !check_image_device_type(priv, host_image.device_id)) { |
| 256 | + NL_SET_ERR_MSG_MOD(extack, "Check image failed"); |
| 257 | + return -EINVAL; |
| 258 | + } |
| 259 | + |
| 260 | + dev_info(&priv->hwdev->hwif->pdev->dev, "Flash firmware begin\n"); |
| 261 | + |
| 262 | + err = hinic_flash_fw(priv, fw->data, &host_image); |
| 263 | + if (err) { |
| 264 | + if (err == HINIC_FW_DISMATCH_ERROR) { |
| 265 | + dev_err(&priv->hwdev->hwif->pdev->dev, "Firmware image doesn't match this card, please use newer image, err: %d\n", |
| 266 | + err); |
| 267 | + NL_SET_ERR_MSG_MOD(extack, |
| 268 | + "Firmware image doesn't match this card, please use newer image"); |
| 269 | + } else { |
| 270 | + dev_err(&priv->hwdev->hwif->pdev->dev, "Send firmware image data failed, err: %d\n", |
| 271 | + err); |
| 272 | + NL_SET_ERR_MSG_MOD(extack, "Send firmware image data failed"); |
| 273 | + } |
| 274 | + |
| 275 | + return err; |
| 276 | + } |
| 277 | + |
| 278 | + dev_info(&priv->hwdev->hwif->pdev->dev, "Flash firmware end\n"); |
| 279 | + |
| 280 | + return 0; |
| 281 | +} |
| 282 | + |
| 283 | +static int hinic_devlink_flash_update(struct devlink *devlink, |
| 284 | + const char *file_name, |
| 285 | + const char *component, |
| 286 | + struct netlink_ext_ack *extack) |
| 287 | +{ |
| 288 | + struct hinic_devlink_priv *priv = devlink_priv(devlink); |
| 289 | + const struct firmware *fw; |
| 290 | + int err; |
| 291 | + |
| 292 | + if (component) |
| 293 | + return -EOPNOTSUPP; |
| 294 | + |
| 295 | + err = request_firmware_direct(&fw, file_name, |
| 296 | + &priv->hwdev->hwif->pdev->dev); |
| 297 | + if (err) |
| 298 | + return err; |
| 299 | + |
| 300 | + err = hinic_firmware_update(priv, fw, extack); |
| 301 | + release_firmware(fw); |
| 302 | + |
| 303 | + return err; |
| 304 | +} |
| 305 | + |
| 306 | +static const struct devlink_ops hinic_devlink_ops = { |
| 307 | + .flash_update = hinic_devlink_flash_update, |
| 308 | +}; |
| 309 | + |
| 310 | +struct devlink *hinic_devlink_alloc(void) |
| 311 | +{ |
| 312 | + return devlink_alloc(&hinic_devlink_ops, sizeof(struct hinic_dev)); |
| 313 | +} |
| 314 | + |
| 315 | +void hinic_devlink_free(struct devlink *devlink) |
| 316 | +{ |
| 317 | + devlink_free(devlink); |
| 318 | +} |
| 319 | + |
| 320 | +int hinic_devlink_register(struct devlink *devlink, struct device *dev) |
| 321 | +{ |
| 322 | + return devlink_register(devlink, dev); |
| 323 | +} |
| 324 | + |
| 325 | +void hinic_devlink_unregister(struct devlink *devlink) |
| 326 | +{ |
| 327 | + devlink_unregister(devlink); |
| 328 | +} |
0 commit comments