Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 | // SPDX-License-Identifier: GPL-2.0-only /* * Firmware loading. * * Copyright (c) 2017-2020, Silicon Laboratories, Inc. * Copyright (c) 2010, ST-Ericsson */ #include <linux/firmware.h> #include <linux/slab.h> #include <linux/mm.h> #include <linux/bitfield.h> #include "fwio.h" #include "wfx.h" #include "hwio.h" /* Addresses below are in SRAM area */ #define WFX_DNLD_FIFO 0x09004000 #define DNLD_BLOCK_SIZE 0x0400 #define DNLD_FIFO_SIZE 0x8000 /* (32 * DNLD_BLOCK_SIZE) */ /* Download Control Area (DCA) */ #define WFX_DCA_IMAGE_SIZE 0x0900C000 #define WFX_DCA_PUT 0x0900C004 #define WFX_DCA_GET 0x0900C008 #define WFX_DCA_HOST_STATUS 0x0900C00C #define HOST_READY 0x87654321 #define HOST_INFO_READ 0xA753BD99 #define HOST_UPLOAD_PENDING 0xABCDDCBA #define HOST_UPLOAD_COMPLETE 0xD4C64A99 #define HOST_OK_TO_JUMP 0x174FC882 #define WFX_DCA_NCP_STATUS 0x0900C010 #define NCP_NOT_READY 0x12345678 #define NCP_READY 0x87654321 #define NCP_INFO_READY 0xBD53EF99 #define NCP_DOWNLOAD_PENDING 0xABCDDCBA #define NCP_DOWNLOAD_COMPLETE 0xCAFEFECA #define NCP_AUTH_OK 0xD4C64A99 #define NCP_AUTH_FAIL 0x174FC882 #define NCP_PUB_KEY_RDY 0x7AB41D19 #define WFX_DCA_FW_SIGNATURE 0x0900C014 #define FW_SIGNATURE_SIZE 0x40 #define WFX_DCA_FW_HASH 0x0900C054 #define FW_HASH_SIZE 0x08 #define WFX_DCA_FW_VERSION 0x0900C05C #define FW_VERSION_SIZE 0x04 #define WFX_DCA_RESERVED 0x0900C060 #define DCA_RESERVED_SIZE 0x20 #define WFX_STATUS_INFO 0x0900C080 #define WFX_BOOTLOADER_LABEL 0x0900C084 #define BOOTLOADER_LABEL_SIZE 0x3C #define WFX_PTE_INFO 0x0900C0C0 #define PTE_INFO_KEYSET_IDX 0x0D #define PTE_INFO_SIZE 0x10 #define WFX_ERR_INFO 0x0900C0D0 #define ERR_INVALID_SEC_TYPE 0x05 #define ERR_SIG_VERIF_FAILED 0x0F #define ERR_AES_CTRL_KEY 0x10 #define ERR_ECC_PUB_KEY 0x11 #define ERR_MAC_KEY 0x18 #define DCA_TIMEOUT 50 /* milliseconds */ #define WAKEUP_TIMEOUT 200 /* milliseconds */ static const char * const fwio_errors[] = { [ERR_INVALID_SEC_TYPE] = "Invalid section type or wrong encryption", [ERR_SIG_VERIF_FAILED] = "Signature verification failed", [ERR_AES_CTRL_KEY] = "AES control key not initialized", [ERR_ECC_PUB_KEY] = "ECC public key not initialized", [ERR_MAC_KEY] = "MAC key not initialized", }; /* request_firmware() allocate data using vmalloc(). It is not compatible with underlying hardware * that use DMA. Function below detect this case and allocate a bounce buffer if necessary. * * Notice that, in doubt, you can enable CONFIG_DEBUG_SG to ask kernel to detect this problem at * runtime (else, kernel silently fail). * * NOTE: it may also be possible to use 'pages' from struct firmware and avoid bounce buffer */ static int wfx_sram_write_dma_safe(struct wfx_dev *wdev, u32 addr, const u8 *buf, size_t len) { int ret; const u8 *tmp; if (!virt_addr_valid(buf)) { tmp = kmemdup(buf, len, GFP_KERNEL); if (!tmp) return -ENOMEM; } else { tmp = buf; } ret = wfx_sram_buf_write(wdev, addr, tmp, len); if (tmp != buf) kfree(tmp); return ret; } static int get_firmware(struct wfx_dev *wdev, u32 keyset_chip, const struct firmware **fw, int *file_offset) { int keyset_file; char filename[256]; const char *data; int ret; snprintf(filename, sizeof(filename), "%s_%02X.sec", wdev->pdata.file_fw, keyset_chip); ret = firmware_request_nowarn(fw, filename, wdev->dev); if (ret) { dev_info(wdev->dev, "can't load %s, falling back to %s.sec\n", filename, wdev->pdata.file_fw); snprintf(filename, sizeof(filename), "%s.sec", wdev->pdata.file_fw); ret = request_firmware(fw, filename, wdev->dev); if (ret) { dev_err(wdev->dev, "can't load %s\n", filename); *fw = NULL; return ret; } } data = (*fw)->data; if (memcmp(data, "KEYSET", 6) != 0) { /* Legacy firmware format */ *file_offset = 0; keyset_file = 0x90; } else { *file_offset = 8; keyset_file = (hex_to_bin(data[6]) * 16) | hex_to_bin(data[7]); if (keyset_file < 0) { dev_err(wdev->dev, "%s corrupted\n", filename); release_firmware(*fw); *fw = NULL; return -EINVAL; } } if (keyset_file != keyset_chip) { dev_err(wdev->dev, "firmware keyset is incompatible with chip (file: 0x%02X, chip: 0x%02X)\n", keyset_file, keyset_chip); release_firmware(*fw); *fw = NULL; return -ENODEV; } wdev->keyset = keyset_file; return 0; } static int wait_ncp_status(struct wfx_dev *wdev, u32 status) { ktime_t now, start; u32 reg; int ret; start = ktime_get(); for (;;) { ret = wfx_sram_reg_read(wdev, WFX_DCA_NCP_STATUS, ®); if (ret < 0) return -EIO; now = ktime_get(); if (reg == status) break; if (ktime_after(now, ktime_add_ms(start, DCA_TIMEOUT))) return -ETIMEDOUT; } if (ktime_compare(now, start)) dev_dbg(wdev->dev, "chip answer after %lldus\n", ktime_us_delta(now, start)); else dev_dbg(wdev->dev, "chip answer immediately\n"); return 0; } static int upload_firmware(struct wfx_dev *wdev, const u8 *data, size_t len) { int ret; u32 offs, bytes_done = 0; ktime_t now, start; if (len % DNLD_BLOCK_SIZE) { dev_err(wdev->dev, "firmware size is not aligned. Buffer overrun will occur\n"); return -EIO; } offs = 0; while (offs < len) { start = ktime_get(); for (;;) { now = ktime_get(); if (offs + DNLD_BLOCK_SIZE - bytes_done < DNLD_FIFO_SIZE) break; if (ktime_after(now, ktime_add_ms(start, DCA_TIMEOUT))) return -ETIMEDOUT; ret = wfx_sram_reg_read(wdev, WFX_DCA_GET, &bytes_done); if (ret < 0) return ret; } if (ktime_compare(now, start)) dev_dbg(wdev->dev, "answer after %lldus\n", ktime_us_delta(now, start)); ret = wfx_sram_write_dma_safe(wdev, WFX_DNLD_FIFO + (offs % DNLD_FIFO_SIZE), data + offs, DNLD_BLOCK_SIZE); if (ret < 0) return ret; /* The device seems to not support writing 0 in this register during first loop */ offs += DNLD_BLOCK_SIZE; ret = wfx_sram_reg_write(wdev, WFX_DCA_PUT, offs); if (ret < 0) return ret; } return 0; } static void print_boot_status(struct wfx_dev *wdev) { u32 reg; wfx_sram_reg_read(wdev, WFX_STATUS_INFO, ®); if (reg == 0x12345678) return; wfx_sram_reg_read(wdev, WFX_ERR_INFO, ®); if (reg < ARRAY_SIZE(fwio_errors) && fwio_errors[reg]) dev_info(wdev->dev, "secure boot: %s\n", fwio_errors[reg]); else dev_info(wdev->dev, "secure boot: Error %#02x\n", reg); } static int load_firmware_secure(struct wfx_dev *wdev) { const struct firmware *fw = NULL; int header_size; int fw_offset; ktime_t start; u8 *buf; int ret; BUILD_BUG_ON(PTE_INFO_SIZE > BOOTLOADER_LABEL_SIZE); buf = kmalloc(BOOTLOADER_LABEL_SIZE + 1, GFP_KERNEL); if (!buf) return -ENOMEM; wfx_sram_reg_write(wdev, WFX_DCA_HOST_STATUS, HOST_READY); ret = wait_ncp_status(wdev, NCP_INFO_READY); if (ret) goto error; wfx_sram_buf_read(wdev, WFX_BOOTLOADER_LABEL, buf, BOOTLOADER_LABEL_SIZE); buf[BOOTLOADER_LABEL_SIZE] = 0; dev_dbg(wdev->dev, "bootloader: \"%s\"\n", buf); wfx_sram_buf_read(wdev, WFX_PTE_INFO, buf, PTE_INFO_SIZE); ret = get_firmware(wdev, buf[PTE_INFO_KEYSET_IDX], &fw, &fw_offset); if (ret) goto error; header_size = fw_offset + FW_SIGNATURE_SIZE + FW_HASH_SIZE; wfx_sram_reg_write(wdev, WFX_DCA_HOST_STATUS, HOST_INFO_READ); ret = wait_ncp_status(wdev, NCP_READY); if (ret) goto error; wfx_sram_reg_write(wdev, WFX_DNLD_FIFO, 0xFFFFFFFF); /* Fifo init */ wfx_sram_write_dma_safe(wdev, WFX_DCA_FW_VERSION, "\x01\x00\x00\x00", FW_VERSION_SIZE); wfx_sram_write_dma_safe(wdev, WFX_DCA_FW_SIGNATURE, fw->data + fw_offset, FW_SIGNATURE_SIZE); wfx_sram_write_dma_safe(wdev, WFX_DCA_FW_HASH, fw->data + fw_offset + FW_SIGNATURE_SIZE, FW_HASH_SIZE); wfx_sram_reg_write(wdev, WFX_DCA_IMAGE_SIZE, fw->size - header_size); wfx_sram_reg_write(wdev, WFX_DCA_HOST_STATUS, HOST_UPLOAD_PENDING); ret = wait_ncp_status(wdev, NCP_DOWNLOAD_PENDING); if (ret) goto error; start = ktime_get(); ret = upload_firmware(wdev, fw->data + header_size, fw->size - header_size); if (ret) goto error; dev_dbg(wdev->dev, "firmware load after %lldus\n", ktime_us_delta(ktime_get(), start)); wfx_sram_reg_write(wdev, WFX_DCA_HOST_STATUS, HOST_UPLOAD_COMPLETE); ret = wait_ncp_status(wdev, NCP_AUTH_OK); /* Legacy ROM support */ if (ret < 0) ret = wait_ncp_status(wdev, NCP_PUB_KEY_RDY); if (ret < 0) goto error; wfx_sram_reg_write(wdev, WFX_DCA_HOST_STATUS, HOST_OK_TO_JUMP); error: kfree(buf); release_firmware(fw); if (ret) print_boot_status(wdev); return ret; } static int init_gpr(struct wfx_dev *wdev) { int ret, i; static const struct { int index; u32 value; } gpr_init[] = { { 0x07, 0x208775 }, { 0x08, 0x2EC020 }, { 0x09, 0x3C3C3C }, { 0x0B, 0x322C44 }, { 0x0C, 0xA06497 }, }; for (i = 0; i < ARRAY_SIZE(gpr_init); i++) { ret = wfx_igpr_reg_write(wdev, gpr_init[i].index, gpr_init[i].value); if (ret < 0) return ret; dev_dbg(wdev->dev, " index %02x: %08x\n", gpr_init[i].index, gpr_init[i].value); } return 0; } int wfx_init_device(struct wfx_dev *wdev) { int ret; int hw_revision, hw_type; int wakeup_timeout = 50; /* ms */ ktime_t now, start; u32 reg; reg = CFG_DIRECT_ACCESS_MODE | CFG_CPU_RESET | CFG_BYTE_ORDER_ABCD; if (wdev->pdata.use_rising_clk) reg |= CFG_CLK_RISE_EDGE; ret = wfx_config_reg_write(wdev, reg); if (ret < 0) { dev_err(wdev->dev, "bus returned an error during first write access. Host configuration error?\n"); return -EIO; } ret = wfx_config_reg_read(wdev, ®); if (ret < 0) { dev_err(wdev->dev, "bus returned an error during first read access. Bus configuration error?\n"); return -EIO; } if (reg == 0 || reg == ~0) { dev_err(wdev->dev, "chip mute. Bus configuration error or chip wasn't reset?\n"); return -EIO; } dev_dbg(wdev->dev, "initial config register value: %08x\n", reg); hw_revision = FIELD_GET(CFG_DEVICE_ID_MAJOR, reg); if (hw_revision == 0) { dev_err(wdev->dev, "bad hardware revision number: %d\n", hw_revision); return -ENODEV; } hw_type = FIELD_GET(CFG_DEVICE_ID_TYPE, reg); if (hw_type == 1) { dev_notice(wdev->dev, "development hardware detected\n"); wakeup_timeout = 2000; } ret = init_gpr(wdev); if (ret < 0) return ret; ret = wfx_control_reg_write(wdev, CTRL_WLAN_WAKEUP); if (ret < 0) return -EIO; start = ktime_get(); for (;;) { ret = wfx_control_reg_read(wdev, ®); now = ktime_get(); if (reg & CTRL_WLAN_READY) break; if (ktime_after(now, ktime_add_ms(start, wakeup_timeout))) { dev_err(wdev->dev, "chip didn't wake up. Chip wasn't reset?\n"); return -ETIMEDOUT; } } dev_dbg(wdev->dev, "chip wake up after %lldus\n", ktime_us_delta(now, start)); ret = wfx_config_reg_write_bits(wdev, CFG_CPU_RESET, 0); if (ret < 0) return ret; ret = load_firmware_secure(wdev); if (ret < 0) return ret; return wfx_config_reg_write_bits(wdev, CFG_DIRECT_ACCESS_MODE | CFG_IRQ_ENABLE_DATA | CFG_IRQ_ENABLE_WRDY, CFG_IRQ_ENABLE_DATA); } |