ath10k: implement firmware IE container support
[cascardo/linux.git] / drivers / net / wireless / ath / ath10k / core.c
index b8f72f2..7b5dd09 100644 (file)
@@ -318,7 +318,7 @@ static void ath10k_core_free_firmware_files(struct ath10k *ar)
        ar->firmware_len = 0;
 }
 
-static int ath10k_core_fetch_firmware_files(struct ath10k *ar)
+static int ath10k_core_fetch_firmware_api_1(struct ath10k *ar)
 {
        int ret = 0;
 
@@ -379,6 +379,188 @@ err:
        return ret;
 }
 
+static int ath10k_core_fetch_firmware_api_n(struct ath10k *ar, const char *name)
+{
+       size_t magic_len, len, ie_len;
+       int ie_id, i, index, bit, ret;
+       struct ath10k_fw_ie *hdr;
+       const u8 *data;
+       __le32 *timestamp;
+
+       /* first fetch the firmware file (firmware-*.bin) */
+       ar->firmware = ath10k_fetch_fw_file(ar, ar->hw_params.fw.dir, name);
+       if (IS_ERR(ar->firmware)) {
+               ath10k_err("Could not fetch firmware file '%s': %ld\n",
+                          name, PTR_ERR(ar->firmware));
+               return PTR_ERR(ar->firmware);
+       }
+
+       data = ar->firmware->data;
+       len = ar->firmware->size;
+
+       /* magic also includes the null byte, check that as well */
+       magic_len = strlen(ATH10K_FIRMWARE_MAGIC) + 1;
+
+       if (len < magic_len) {
+               ath10k_err("firmware image too small to contain magic: %d\n",
+                          len);
+               return -EINVAL;
+       }
+
+       if (memcmp(data, ATH10K_FIRMWARE_MAGIC, magic_len) != 0) {
+               ath10k_err("Invalid firmware magic\n");
+               return -EINVAL;
+       }
+
+       /* jump over the padding */
+       magic_len = ALIGN(magic_len, 4);
+
+       len -= magic_len;
+       data += magic_len;
+
+       /* loop elements */
+       while (len > sizeof(struct ath10k_fw_ie)) {
+               hdr = (struct ath10k_fw_ie *)data;
+
+               ie_id = le32_to_cpu(hdr->id);
+               ie_len = le32_to_cpu(hdr->len);
+
+               len -= sizeof(*hdr);
+               data += sizeof(*hdr);
+
+               if (len < ie_len) {
+                       ath10k_err("Invalid length for FW IE %d (%d < %d)\n",
+                                  ie_id, len, ie_len);
+                       return -EINVAL;
+               }
+
+               switch (ie_id) {
+               case ATH10K_FW_IE_FW_VERSION:
+                       if (ie_len > sizeof(ar->hw->wiphy->fw_version) - 1)
+                               break;
+
+                       memcpy(ar->hw->wiphy->fw_version, data, ie_len);
+                       ar->hw->wiphy->fw_version[ie_len] = '\0';
+
+                       ath10k_dbg(ATH10K_DBG_BOOT,
+                                  "found fw version %s\n",
+                                   ar->hw->wiphy->fw_version);
+                       break;
+               case ATH10K_FW_IE_TIMESTAMP:
+                       if (ie_len != sizeof(u32))
+                               break;
+
+                       timestamp = (__le32 *)data;
+
+                       ath10k_dbg(ATH10K_DBG_BOOT, "found fw timestamp %d\n",
+                                  le32_to_cpup(timestamp));
+                       break;
+               case ATH10K_FW_IE_FEATURES:
+                       ath10k_dbg(ATH10K_DBG_BOOT,
+                                  "found firmware features ie (%zd B)\n",
+                                  ie_len);
+
+                       for (i = 0; i < ATH10K_FW_FEATURE_COUNT; i++) {
+                               index = i / 8;
+                               bit = i % 8;
+
+                               if (index == ie_len)
+                                       break;
+
+                               if (data[index] & (1 << bit))
+                                       __set_bit(i, ar->fw_features);
+                       }
+
+                       ath10k_dbg_dump(ATH10K_DBG_BOOT, "features", "",
+                                       ar->fw_features,
+                                       sizeof(ar->fw_features));
+                       break;
+               case ATH10K_FW_IE_FW_IMAGE:
+                       ath10k_dbg(ATH10K_DBG_BOOT,
+                                  "found fw image ie (%zd B)\n",
+                                  ie_len);
+
+                       ar->firmware_data = data;
+                       ar->firmware_len = ie_len;
+
+                       break;
+               case ATH10K_FW_IE_OTP_IMAGE:
+                       ath10k_dbg(ATH10K_DBG_BOOT,
+                                  "found otp image ie (%zd B)\n",
+                                  ie_len);
+
+                       ar->otp_data = data;
+                       ar->otp_len = ie_len;
+
+                       break;
+               default:
+                       ath10k_warn("Unknown FW IE: %u\n",
+                                   le32_to_cpu(hdr->id));
+                       break;
+               }
+
+               /* jump over the padding */
+               ie_len = ALIGN(ie_len, 4);
+
+               len -= ie_len;
+               data += ie_len;
+       };
+
+       if (!ar->firmware_data || !ar->firmware_len) {
+               ath10k_warn("No ATH10K_FW_IE_FW_IMAGE found from %s, skipping\n",
+                           name);
+               ret = -ENOMEDIUM;
+               goto err;
+       }
+
+       /* now fetch the board file */
+       if (ar->hw_params.fw.board == NULL) {
+               ath10k_err("board data file not defined");
+               ret = -EINVAL;
+               goto err;
+       }
+
+       ar->board = ath10k_fetch_fw_file(ar,
+                                        ar->hw_params.fw.dir,
+                                        ar->hw_params.fw.board);
+       if (IS_ERR(ar->board)) {
+               ret = PTR_ERR(ar->board);
+               ath10k_err("could not fetch board data (%d)\n", ret);
+               goto err;
+       }
+
+       ar->board_data = ar->board->data;
+       ar->board_len = ar->board->size;
+
+       return 0;
+
+err:
+       ath10k_core_free_firmware_files(ar);
+       return ret;
+}
+
+static int ath10k_core_fetch_firmware_files(struct ath10k *ar)
+{
+       int ret;
+
+       ret = ath10k_core_fetch_firmware_api_n(ar, ATH10K_FW_API2_FILE);
+       if (ret == 0) {
+               ar->fw_api = 2;
+               goto out;
+       }
+
+       ret = ath10k_core_fetch_firmware_api_1(ar);
+       if (ret)
+               return ret;
+
+       ar->fw_api = 1;
+
+out:
+       ath10k_dbg(ATH10K_DBG_BOOT, "using fw api %d\n", ar->fw_api);
+
+       return 0;
+}
+
 static int ath10k_init_download_firmware(struct ath10k *ar)
 {
        int ret;