// SPDX-License-Identifier: GPL-2.0-only/* * Bluetooth supports for Qualcomm Atheros chips * * Copyright (c) 2015 The Linux Foundation. All rights reserved. */#include <linux/module.h>#include <linux/firmware.h>#include <linux/vmalloc.h> #include <net/bluetooth/bluetooth.h>#include <net/bluetooth/hci_core.h> #include "btqca.h" int qca_read_soc_version(struct hci_dev *hdev, struct qca_btsoc_version *ver, enum qca_btsoc_type soc_type){ struct sk_buff *skb; struct edl_event_hdr *edl; char cmd; int err = 0; u8 event_type = HCI_EV_VENDOR; u8 rlen = sizeof(*edl) + sizeof(*ver); u8 rtype = EDL_APP_VER_RES_EVT; bt_dev_dbg(hdev, "QCA Version Request"); /* Unlike other SoC's sending version command response as payload to * VSE event. WCN3991 sends version command response as a payload to * command complete event. */ if (soc_type >= QCA_WCN3991) { event_type = 0; rlen += 1; rtype = EDL_PATCH_VER_REQ_CMD; } cmd = EDL_PATCH_VER_REQ_CMD; skb = __hci_cmd_sync_ev(hdev, EDL_PATCH_CMD_OPCODE, EDL_PATCH_CMD_LEN, &cmd, event_type, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "Reading QCA version information failed (%d)", err); return err; } if (skb->len != rlen) { bt_dev_err(hdev, "QCA Version size mismatch len %d", skb->len); err = -EILSEQ; goto out; } edl = (struct edl_event_hdr *)(skb->data); if (edl->cresp != EDL_CMD_REQ_RES_EVT || edl->rtype != rtype) { bt_dev_err(hdev, "QCA Wrong packet received %d %d", edl->cresp, edl->rtype); err = -EIO; goto out; } if (soc_type >= QCA_WCN3991) memcpy(ver, edl->data + 1, sizeof(*ver)); else memcpy(ver, &edl->data, sizeof(*ver)); bt_dev_info(hdev, "QCA Product ID :0x%08x", le32_to_cpu(ver->product_id)); bt_dev_info(hdev, "QCA SOC Version :0x%08x", le32_to_cpu(ver->soc_id)); bt_dev_info(hdev, "QCA ROM Version :0x%08x", le16_to_cpu(ver->rom_ver)); bt_dev_info(hdev, "QCA Patch Version:0x%08x", le16_to_cpu(ver->patch_ver)); if (ver->soc_id == 0 || ver->rom_ver == 0) err = -EILSEQ; out: kfree_skb(skb); if (err) bt_dev_err(hdev, "QCA Failed to get version (%d)", err); return err;}EXPORT_SYMBOL_GPL(qca_read_soc_version); static int qca_read_fw_build_info(struct hci_dev *hdev){ struct sk_buff *skb; struct edl_event_hdr *edl; char *build_label; char cmd; int build_lbl_len, err = 0; bt_dev_dbg(hdev, "QCA read fw build info"); cmd = EDL_GET_BUILD_INFO_CMD; skb = __hci_cmd_sync_ev(hdev, EDL_PATCH_CMD_OPCODE, EDL_PATCH_CMD_LEN, &cmd, 0, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "Reading QCA fw build info failed (%d)", err); return err; } if (skb->len < sizeof(*edl)) { err = -EILSEQ; goto out; } edl = (struct edl_event_hdr *)(skb->data); if (edl->cresp != EDL_CMD_REQ_RES_EVT || edl->rtype != EDL_GET_BUILD_INFO_CMD) { bt_dev_err(hdev, "QCA Wrong packet received %d %d", edl->cresp, edl->rtype); err = -EIO; goto out; } if (skb->len < sizeof(*edl) + 1) { err = -EILSEQ; goto out; } build_lbl_len = edl->data[0]; if (skb->len < sizeof(*edl) + 1 + build_lbl_len) { err = -EILSEQ; goto out; } build_label = kstrndup(&edl->data[1], build_lbl_len, GFP_KERNEL); if (!build_label) { err = -ENOMEM; goto out; } hci_set_fw_info(hdev, "%s", build_label); bt_dev_info(hdev, "QCA FW build version: %s", build_label); kfree(build_label);out: kfree_skb(skb); return err;} static int qca_send_patch_config_cmd(struct hci_dev *hdev){ const u8 cmd[] = { EDL_PATCH_CONFIG_CMD, 0x01, 0, 0, 0 }; struct sk_buff *skb; struct edl_event_hdr *edl; int err; bt_dev_dbg(hdev, "QCA Patch config"); skb = __hci_cmd_sync_ev(hdev, EDL_PATCH_CMD_OPCODE, sizeof(cmd), cmd, 0, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "Sending QCA Patch config failed (%d)", err); return err; } if (skb->len != 2) { bt_dev_err(hdev, "QCA Patch config cmd size mismatch len %d", skb->len); err = -EILSEQ; goto out; } edl = (struct edl_event_hdr *)(skb->data); if (edl->cresp != EDL_PATCH_CONFIG_RES_EVT || edl->rtype != EDL_PATCH_CONFIG_CMD) { bt_dev_err(hdev, "QCA Wrong packet received %d %d", edl->cresp, edl->rtype); err = -EIO; goto out; } err = 0; out: kfree_skb(skb); return err;} static int qca_read_fw_board_id(struct hci_dev *hdev, u16 *bid){ u8 cmd; struct sk_buff *skb; struct edl_event_hdr *edl; int err = 0; cmd = EDL_GET_BID_REQ_CMD; skb = __hci_cmd_sync_ev(hdev, EDL_PATCH_CMD_OPCODE, EDL_PATCH_CMD_LEN, &cmd, 0, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "Reading QCA board ID failed (%d)", err); return err; } edl = skb_pull_data(skb, sizeof(*edl)); if (!edl) { bt_dev_err(hdev, "QCA read board ID with no header"); err = -EILSEQ; goto out; } if (edl->cresp != EDL_CMD_REQ_RES_EVT || edl->rtype != EDL_GET_BID_REQ_CMD) { bt_dev_err(hdev, "QCA Wrong packet: %d %d", edl->cresp, edl->rtype); err = -EIO; goto out; } if (skb->len < 3) { err = -EILSEQ; goto out; } *bid = (edl->data[1] << 8) + edl->data[2]; bt_dev_dbg(hdev, "%s: bid = %x", __func__, *bid); out: kfree_skb(skb); return err;} int qca_send_pre_shutdown_cmd(struct hci_dev *hdev){ struct sk_buff *skb; int err; bt_dev_dbg(hdev, "QCA pre shutdown cmd"); skb = __hci_cmd_sync_ev(hdev, QCA_PRE_SHUTDOWN_CMD, 0, NULL, HCI_EV_CMD_COMPLETE, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "QCA preshutdown_cmd failed (%d)", err); return err; } kfree_skb(skb); return 0;}EXPORT_SYMBOL_GPL(qca_send_pre_shutdown_cmd); static bool qca_filename_has_extension(const char *filename){ const char *suffix = strrchr(filename, '.'); /* File extensions require a dot, but not as the first or last character */ if (!suffix || suffix == filename || *(suffix + 1) == '\0') return 0; /* Avoid matching directories with names that look like files with extensions */ return !strchr(suffix, '/');} static bool qca_get_alt_nvm_file(char *filename, size_t max_size){ char fwname[64]; const char *suffix; /* nvm file name has an extension, replace with .bin */ if (qca_filename_has_extension(filename)) { suffix = strrchr(filename, '.'); strscpy(fwname, filename, suffix - filename + 1); snprintf(fwname + (suffix - filename), sizeof(fwname) - (suffix - filename), ".bin"); /* If nvm file is already the default one, return false to skip the retry. */ if (strcmp(fwname, filename) == 0) return false; snprintf(filename, max_size, "%s", fwname); return true; } return false;} static int qca_tlv_check_data(struct hci_dev *hdev, struct qca_fw_config *config, u8 *fw_data, size_t fw_size, enum qca_btsoc_type soc_type){ const u8 *data; u32 type_len; u16 tag_id, tag_len; int idx, length; struct tlv_type_hdr *tlv; struct tlv_type_patch *tlv_patch; struct tlv_type_nvm *tlv_nvm; uint8_t nvm_baud_rate = config->user_baud_rate; u8 type; config->dnld_mode = QCA_SKIP_EVT_NONE; config->dnld_type = QCA_SKIP_EVT_NONE; switch (config->type) { case ELF_TYPE_PATCH: if (fw_size < 7) return -EINVAL; config->dnld_mode = QCA_SKIP_EVT_VSE_CC; config->dnld_type = QCA_SKIP_EVT_VSE_CC; bt_dev_dbg(hdev, "File Class : 0x%x", fw_data[4]); bt_dev_dbg(hdev, "Data Encoding : 0x%x", fw_data[5]); bt_dev_dbg(hdev, "File version : 0x%x", fw_data[6]); break; case TLV_TYPE_PATCH: if (fw_size < sizeof(struct tlv_type_hdr) + sizeof(struct tlv_type_patch)) return -EINVAL; tlv = (struct tlv_type_hdr *)fw_data; type_len = le32_to_cpu(tlv->type_len); tlv_patch = (struct tlv_type_patch *)tlv->data; /* For Rome version 1.1 to 3.1, all segment commands * are acked by a vendor specific event (VSE). * For Rome >= 3.2, the download mode field indicates * if VSE is skipped by the controller. * In case VSE is skipped, only the last segment is acked. */ config->dnld_mode = tlv_patch->download_mode; config->dnld_type = config->dnld_mode; BT_DBG("TLV Type\t\t : 0x%x", type_len & 0x000000ff); BT_DBG("Total Length : %d bytes", le32_to_cpu(tlv_patch->total_size)); BT_DBG("Patch Data Length : %d bytes", le32_to_cpu(tlv_patch->data_length)); BT_DBG("Signing Format Version : 0x%x", tlv_patch->format_version); BT_DBG("Signature Algorithm : 0x%x", tlv_patch->signature); BT_DBG("Download mode : 0x%x", tlv_patch->download_mode); BT_DBG("Reserved : 0x%x", tlv_patch->reserved1); BT_DBG("Product ID : 0x%04x", le16_to_cpu(tlv_patch->product_id)); BT_DBG("Rom Build Version : 0x%04x", le16_to_cpu(tlv_patch->rom_build)); BT_DBG("Patch Version : 0x%04x", le16_to_cpu(tlv_patch->patch_version)); BT_DBG("Reserved : 0x%x", le16_to_cpu(tlv_patch->reserved2)); BT_DBG("Patch Entry Address : 0x%x", le32_to_cpu(tlv_patch->entry)); break; case TLV_TYPE_NVM: if (fw_size < sizeof(struct tlv_type_hdr)) return -EINVAL; tlv = (struct tlv_type_hdr *)fw_data; type_len = le32_to_cpu(tlv->type_len); length = type_len >> 8; type = type_len & 0xff; /* Some NVM files have more than one set of tags, only parse * the first set when it has type 2 for now. When there is * more than one set there is an enclosing header of type 4. */ if (type == 4) { if (fw_size < 2 * sizeof(struct tlv_type_hdr)) return -EINVAL; tlv++; type_len = le32_to_cpu(tlv->type_len); length = type_len >> 8; type = type_len & 0xff; } BT_DBG("TLV Type\t\t : 0x%x", type); BT_DBG("Length\t\t : %d bytes", length); if (type != 2) break; if (fw_size < length + (tlv->data - fw_data)) return -EINVAL; idx = 0; data = tlv->data; while (idx + sizeof(struct tlv_type_nvm) <= length) { tlv_nvm = (struct tlv_type_nvm *)(data + idx); tag_id = le16_to_cpu(tlv_nvm->tag_id); tag_len = le16_to_cpu(tlv_nvm->tag_len); if (length < idx + sizeof(struct tlv_type_nvm) + tag_len) return -EINVAL; /* Update NVM tags as needed */ switch (tag_id) { case EDL_TAG_ID_BD_ADDR: if (tag_len != sizeof(bdaddr_t)) return -EINVAL; memcpy(&config->bdaddr, tlv_nvm->data, sizeof(bdaddr_t)); break; case EDL_TAG_ID_HCI: if (tag_len < 3) return -EINVAL; /* HCI transport layer parameters * enabling software inband sleep * onto controller side. */ tlv_nvm->data[0] |= 0x80; /* UART Baud Rate */ if (soc_type >= QCA_WCN3991) tlv_nvm->data[1] = nvm_baud_rate; else tlv_nvm->data[2] = nvm_baud_rate; break; case EDL_TAG_ID_DEEP_SLEEP: if (tag_len < 1) return -EINVAL; /* Sleep enable mask * enabling deep sleep feature on controller. */ tlv_nvm->data[0] |= 0x01; break; } idx += sizeof(struct tlv_type_nvm) + tag_len; } break; default: BT_ERR("Unknown TLV type %d", config->type); return -EINVAL; } return 0;} static int qca_tlv_send_segment(struct hci_dev *hdev, int seg_size, const u8 *data, enum qca_tlv_dnld_mode mode, enum qca_btsoc_type soc_type){ struct sk_buff *skb; struct edl_event_hdr *edl; struct tlv_seg_resp *tlv_resp; u8 cmd[MAX_SIZE_PER_TLV_SEGMENT + 2]; int err = 0; u8 event_type = HCI_EV_VENDOR; u8 rlen = (sizeof(*edl) + sizeof(*tlv_resp)); u8 rtype = EDL_TVL_DNLD_RES_EVT; cmd[0] = EDL_PATCH_TLV_REQ_CMD; cmd[1] = seg_size; memcpy(cmd + 2, data, seg_size); if (mode == QCA_SKIP_EVT_VSE_CC || mode == QCA_SKIP_EVT_VSE) return __hci_cmd_send(hdev, EDL_PATCH_CMD_OPCODE, seg_size + 2, cmd); /* Unlike other SoC's sending version command response as payload to * VSE event. WCN3991 sends version command response as a payload to * command complete event. */ if (soc_type >= QCA_WCN3991) { event_type = 0; rlen = sizeof(*edl); rtype = EDL_PATCH_TLV_REQ_CMD; } skb = __hci_cmd_sync_ev(hdev, EDL_PATCH_CMD_OPCODE, seg_size + 2, cmd, event_type, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "QCA Failed to send TLV segment (%d)", err); return err; } if (skb->len != rlen) { bt_dev_err(hdev, "QCA TLV response size mismatch");Lines 1–500 of 1030. Load more
// SPDX-License-Identifier: GPL-2.0-only /* * Bluetooth supports for Qualcomm Atheros chips * * Copyright (c) 2015 The Linux Foundation. All rights reserved. */ #include <linux/module.h> #include <linux/firmware.h> #include <linux/vmalloc.h> #include <net/bluetooth/bluetooth.h> #include <net/bluetooth/hci_core.h> #include "btqca.h" int qca_read_soc_version(struct hci_dev *hdev, struct qca_btsoc_version *ver, enum qca_btsoc_type soc_type) { struct sk_buff *skb; struct edl_event_hdr *edl; char cmd; int err = 0; u8 event_type = HCI_EV_VENDOR; u8 rlen = sizeof(*edl) + sizeof(*ver); u8 rtype = EDL_APP_VER_RES_EVT; bt_dev_dbg(hdev, "QCA Version Request"); /* Unlike other SoC's sending version command response as payload to * VSE event. WCN3991 sends version command response as a payload to * command complete event. */ if (soc_type >= QCA_WCN3991) { event_type = 0; rlen += 1; rtype = EDL_PATCH_VER_REQ_CMD; } cmd = EDL_PATCH_VER_REQ_CMD; skb = __hci_cmd_sync_ev(hdev, EDL_PATCH_CMD_OPCODE, EDL_PATCH_CMD_LEN, &cmd, event_type, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "Reading QCA version information failed (%d)", err); return err; } if (skb->len != rlen) { bt_dev_err(hdev, "QCA Version size mismatch len %d", skb->len); err = -EILSEQ; goto out; } edl = (struct edl_event_hdr *)(skb->data); if (edl->cresp != EDL_CMD_REQ_RES_EVT || edl->rtype != rtype) { bt_dev_err(hdev, "QCA Wrong packet received %d %d", edl->cresp, edl->rtype); err = -EIO; goto out; } if (soc_type >= QCA_WCN3991) memcpy(ver, edl->data + 1, sizeof(*ver)); else memcpy(ver, &edl->data, sizeof(*ver)); bt_dev_info(hdev, "QCA Product ID :0x%08x", le32_to_cpu(ver->product_id)); bt_dev_info(hdev, "QCA SOC Version :0x%08x", le32_to_cpu(ver->soc_id)); bt_dev_info(hdev, "QCA ROM Version :0x%08x", le16_to_cpu(ver->rom_ver)); bt_dev_info(hdev, "QCA Patch Version:0x%08x", le16_to_cpu(ver->patch_ver)); if (ver->soc_id == 0 || ver->rom_ver == 0) err = -EILSEQ; out: kfree_skb(skb); if (err) bt_dev_err(hdev, "QCA Failed to get version (%d)", err); return err; } EXPORT_SYMBOL_GPL(qca_read_soc_version); static int qca_read_fw_build_info(struct hci_dev *hdev) { struct sk_buff *skb; struct edl_event_hdr *edl; char *build_label; char cmd; int build_lbl_len, err = 0; bt_dev_dbg(hdev, "QCA read fw build info"); cmd = EDL_GET_BUILD_INFO_CMD; skb = __hci_cmd_sync_ev(hdev, EDL_PATCH_CMD_OPCODE, EDL_PATCH_CMD_LEN, &cmd, 0, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "Reading QCA fw build info failed (%d)", err); return err; } if (skb->len < sizeof(*edl)) { err = -EILSEQ; goto out; } edl = (struct edl_event_hdr *)(skb->data); if (edl->cresp != EDL_CMD_REQ_RES_EVT || edl->rtype != EDL_GET_BUILD_INFO_CMD) { bt_dev_err(hdev, "QCA Wrong packet received %d %d", edl->cresp, edl->rtype); err = -EIO; goto out; } if (skb->len < sizeof(*edl) + 1) { err = -EILSEQ; goto out; } build_lbl_len = edl->data[0]; if (skb->len < sizeof(*edl) + 1 + build_lbl_len) { err = -EILSEQ; goto out; } build_label = kstrndup(&edl->data[1], build_lbl_len, GFP_KERNEL); if (!build_label) { err = -ENOMEM; goto out; } hci_set_fw_info(hdev, "%s", build_label); bt_dev_info(hdev, "QCA FW build version: %s", build_label); kfree(build_label); out: kfree_skb(skb); return err; } static int qca_send_patch_config_cmd(struct hci_dev *hdev) { const u8 cmd[] = { EDL_PATCH_CONFIG_CMD, 0x01, 0, 0, 0 }; struct sk_buff *skb; struct edl_event_hdr *edl; int err; bt_dev_dbg(hdev, "QCA Patch config"); skb = __hci_cmd_sync_ev(hdev, EDL_PATCH_CMD_OPCODE, sizeof(cmd), cmd, 0, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "Sending QCA Patch config failed (%d)", err); return err; } if (skb->len != 2) { bt_dev_err(hdev, "QCA Patch config cmd size mismatch len %d", skb->len); err = -EILSEQ; goto out; } edl = (struct edl_event_hdr *)(skb->data); if (edl->cresp != EDL_PATCH_CONFIG_RES_EVT || edl->rtype != EDL_PATCH_CONFIG_CMD) { bt_dev_err(hdev, "QCA Wrong packet received %d %d", edl->cresp, edl->rtype); err = -EIO; goto out; } err = 0; out: kfree_skb(skb); return err; } static int qca_read_fw_board_id(struct hci_dev *hdev, u16 *bid) { u8 cmd; struct sk_buff *skb; struct edl_event_hdr *edl; int err = 0; cmd = EDL_GET_BID_REQ_CMD; skb = __hci_cmd_sync_ev(hdev, EDL_PATCH_CMD_OPCODE, EDL_PATCH_CMD_LEN, &cmd, 0, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "Reading QCA board ID failed (%d)", err); return err; } edl = skb_pull_data(skb, sizeof(*edl)); if (!edl) { bt_dev_err(hdev, "QCA read board ID with no header"); err = -EILSEQ; goto out; } if (edl->cresp != EDL_CMD_REQ_RES_EVT || edl->rtype != EDL_GET_BID_REQ_CMD) { bt_dev_err(hdev, "QCA Wrong packet: %d %d", edl->cresp, edl->rtype); err = -EIO; goto out; } if (skb->len < 3) { err = -EILSEQ; goto out; } *bid = (edl->data[1] << 8) + edl->data[2]; bt_dev_dbg(hdev, "%s: bid = %x", __func__, *bid); out: kfree_skb(skb); return err; } int qca_send_pre_shutdown_cmd(struct hci_dev *hdev) { struct sk_buff *skb; int err; bt_dev_dbg(hdev, "QCA pre shutdown cmd"); skb = __hci_cmd_sync_ev(hdev, QCA_PRE_SHUTDOWN_CMD, 0, NULL, HCI_EV_CMD_COMPLETE, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "QCA preshutdown_cmd failed (%d)", err); return err; } kfree_skb(skb); return 0; } EXPORT_SYMBOL_GPL(qca_send_pre_shutdown_cmd); static bool qca_filename_has_extension(const char *filename) { const char *suffix = strrchr(filename, '.'); /* File extensions require a dot, but not as the first or last character */ if (!suffix || suffix == filename || *(suffix + 1) == '\0') return 0; /* Avoid matching directories with names that look like files with extensions */ return !strchr(suffix, '/'); } static bool qca_get_alt_nvm_file(char *filename, size_t max_size) { char fwname[64]; const char *suffix; /* nvm file name has an extension, replace with .bin */ if (qca_filename_has_extension(filename)) { suffix = strrchr(filename, '.'); strscpy(fwname, filename, suffix - filename + 1); snprintf(fwname + (suffix - filename), sizeof(fwname) - (suffix - filename), ".bin"); /* If nvm file is already the default one, return false to skip the retry. */ if (strcmp(fwname, filename) == 0) return false; snprintf(filename, max_size, "%s", fwname); return true; } return false; } static int qca_tlv_check_data(struct hci_dev *hdev, struct qca_fw_config *config, u8 *fw_data, size_t fw_size, enum qca_btsoc_type soc_type) { const u8 *data; u32 type_len; u16 tag_id, tag_len; int idx, length; struct tlv_type_hdr *tlv; struct tlv_type_patch *tlv_patch; struct tlv_type_nvm *tlv_nvm; uint8_t nvm_baud_rate = config->user_baud_rate; u8 type; config->dnld_mode = QCA_SKIP_EVT_NONE; config->dnld_type = QCA_SKIP_EVT_NONE; switch (config->type) { case ELF_TYPE_PATCH: if (fw_size < 7) return -EINVAL; config->dnld_mode = QCA_SKIP_EVT_VSE_CC; config->dnld_type = QCA_SKIP_EVT_VSE_CC; bt_dev_dbg(hdev, "File Class : 0x%x", fw_data[4]); bt_dev_dbg(hdev, "Data Encoding : 0x%x", fw_data[5]); bt_dev_dbg(hdev, "File version : 0x%x", fw_data[6]); break; case TLV_TYPE_PATCH: if (fw_size < sizeof(struct tlv_type_hdr) + sizeof(struct tlv_type_patch)) return -EINVAL; tlv = (struct tlv_type_hdr *)fw_data; type_len = le32_to_cpu(tlv->type_len); tlv_patch = (struct tlv_type_patch *)tlv->data; /* For Rome version 1.1 to 3.1, all segment commands * are acked by a vendor specific event (VSE). * For Rome >= 3.2, the download mode field indicates * if VSE is skipped by the controller. * In case VSE is skipped, only the last segment is acked. */ config->dnld_mode = tlv_patch->download_mode; config->dnld_type = config->dnld_mode; BT_DBG("TLV Type\t\t : 0x%x", type_len & 0x000000ff); BT_DBG("Total Length : %d bytes", le32_to_cpu(tlv_patch->total_size)); BT_DBG("Patch Data Length : %d bytes", le32_to_cpu(tlv_patch->data_length)); BT_DBG("Signing Format Version : 0x%x", tlv_patch->format_version); BT_DBG("Signature Algorithm : 0x%x", tlv_patch->signature); BT_DBG("Download mode : 0x%x", tlv_patch->download_mode); BT_DBG("Reserved : 0x%x", tlv_patch->reserved1); BT_DBG("Product ID : 0x%04x", le16_to_cpu(tlv_patch->product_id)); BT_DBG("Rom Build Version : 0x%04x", le16_to_cpu(tlv_patch->rom_build)); BT_DBG("Patch Version : 0x%04x", le16_to_cpu(tlv_patch->patch_version)); BT_DBG("Reserved : 0x%x", le16_to_cpu(tlv_patch->reserved2)); BT_DBG("Patch Entry Address : 0x%x", le32_to_cpu(tlv_patch->entry)); break; case TLV_TYPE_NVM: if (fw_size < sizeof(struct tlv_type_hdr)) return -EINVAL; tlv = (struct tlv_type_hdr *)fw_data; type_len = le32_to_cpu(tlv->type_len); length = type_len >> 8; type = type_len & 0xff; /* Some NVM files have more than one set of tags, only parse * the first set when it has type 2 for now. When there is * more than one set there is an enclosing header of type 4. */ if (type == 4) { if (fw_size < 2 * sizeof(struct tlv_type_hdr)) return -EINVAL; tlv++; type_len = le32_to_cpu(tlv->type_len); length = type_len >> 8; type = type_len & 0xff; } BT_DBG("TLV Type\t\t : 0x%x", type); BT_DBG("Length\t\t : %d bytes", length); if (type != 2) break; if (fw_size < length + (tlv->data - fw_data)) return -EINVAL; idx = 0; data = tlv->data; while (idx + sizeof(struct tlv_type_nvm) <= length) { tlv_nvm = (struct tlv_type_nvm *)(data + idx); tag_id = le16_to_cpu(tlv_nvm->tag_id); tag_len = le16_to_cpu(tlv_nvm->tag_len); if (length < idx + sizeof(struct tlv_type_nvm) + tag_len) return -EINVAL; /* Update NVM tags as needed */ switch (tag_id) { case EDL_TAG_ID_BD_ADDR: if (tag_len != sizeof(bdaddr_t)) return -EINVAL; memcpy(&config->bdaddr, tlv_nvm->data, sizeof(bdaddr_t)); break; case EDL_TAG_ID_HCI: if (tag_len < 3) return -EINVAL; /* HCI transport layer parameters * enabling software inband sleep * onto controller side. */ tlv_nvm->data[0] |= 0x80; /* UART Baud Rate */ if (soc_type >= QCA_WCN3991) tlv_nvm->data[1] = nvm_baud_rate; else tlv_nvm->data[2] = nvm_baud_rate; break; case EDL_TAG_ID_DEEP_SLEEP: if (tag_len < 1) return -EINVAL; /* Sleep enable mask * enabling deep sleep feature on controller. */ tlv_nvm->data[0] |= 0x01; break; } idx += sizeof(struct tlv_type_nvm) + tag_len; } break; default: BT_ERR("Unknown TLV type %d", config->type); return -EINVAL; } return 0; } static int qca_tlv_send_segment(struct hci_dev *hdev, int seg_size, const u8 *data, enum qca_tlv_dnld_mode mode, enum qca_btsoc_type soc_type) { struct sk_buff *skb; struct edl_event_hdr *edl; struct tlv_seg_resp *tlv_resp; u8 cmd[MAX_SIZE_PER_TLV_SEGMENT + 2]; int err = 0; u8 event_type = HCI_EV_VENDOR; u8 rlen = (sizeof(*edl) + sizeof(*tlv_resp)); u8 rtype = EDL_TVL_DNLD_RES_EVT; cmd[0] = EDL_PATCH_TLV_REQ_CMD; cmd[1] = seg_size; memcpy(cmd + 2, data, seg_size); if (mode == QCA_SKIP_EVT_VSE_CC || mode == QCA_SKIP_EVT_VSE) return __hci_cmd_send(hdev, EDL_PATCH_CMD_OPCODE, seg_size + 2, cmd); /* Unlike other SoC's sending version command response as payload to * VSE event. WCN3991 sends version command response as a payload to * command complete event. */ if (soc_type >= QCA_WCN3991) { event_type = 0; rlen = sizeof(*edl); rtype = EDL_PATCH_TLV_REQ_CMD; } skb = __hci_cmd_sync_ev(hdev, EDL_PATCH_CMD_OPCODE, seg_size + 2, cmd, event_type, HCI_INIT_TIMEOUT); if (IS_ERR(skb)) { err = PTR_ERR(skb); bt_dev_err(hdev, "QCA Failed to send TLV segment (%d)", err); return err; } if (skb->len != rlen) { bt_dev_err(hdev, "QCA TLV response size mismatch");