blob: 2fe7a762408e4f7340b63f7ca58918658782f6dc [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0 OR BSD-3-Clause
/*
* Copyright (C) 2012-2014, 2021 Intel Corporation
* Copyright (C) 2015 Intel Deutschland GmbH
*/
#include <linux/firmware.h>
#include "iwl-trans.h"
#include "xvt.h"
#include "iwl-eeprom-parse.h"
#include "iwl-eeprom-read.h"
#include "iwl-nvm-parse.h"
#include "iwl-prph.h"
#include "fw-api.h"
/* Default NVM size to read */
#define IWL_NVM_DEFAULT_CHUNK_SIZE (2*1024)
#define IWL_MAX_NVM_SECTION_SIZE 7000
#define NVM_WRITE_OPCODE 1
enum wkp_nvm_offsets {
/* NVM HW-Section offset (in words) definitions */
HW_ADDR = 0x15,
};
enum ext_nvm_offsets {
/* NVM HW-Section offset (in words) definitions */
MAC_ADDRESS_OVERRIDE_EXT_NVM = 1,
};
/*
* prepare the NVM host command w/ the pointers to the nvm buffer
* and send it to fw
*/
static int iwl_nvm_write_chunk(struct iwl_xvt *xvt, u16 section,
u16 offset, u16 length, const u8 *data)
{
struct iwl_nvm_access_cmd nvm_access_cmd = {
.offset = cpu_to_le16(offset),
.length = cpu_to_le16(length),
.type = cpu_to_le16(section),
.op_code = NVM_WRITE_OPCODE,
};
struct iwl_host_cmd cmd = {
.id = NVM_ACCESS_CMD,
.len = { sizeof(struct iwl_nvm_access_cmd), length },
.flags = CMD_SEND_IN_RFKILL,
.data = { &nvm_access_cmd, data },
/* data may come from vmalloc, so use _DUP */
.dataflags = { 0, IWL_HCMD_DFL_DUP },
};
return iwl_xvt_send_cmd(xvt, &cmd);
}
static int iwl_nvm_write_section(struct iwl_xvt *xvt, u16 section,
const u8 *data, u16 length)
{
int offset = 0;
/* copy data in chunks of 2k (and remainder if any) */
while (offset < length) {
int chunk_size, ret;
chunk_size = min(IWL_NVM_DEFAULT_CHUNK_SIZE,
length - offset);
ret = iwl_nvm_write_chunk(xvt, section, offset,
chunk_size, data + offset);
if (ret < 0)
return ret;
offset += chunk_size;
}
return 0;
}
#define MAX_NVM_FILE_LEN 16384
/*
* HOW TO CREATE THE NVM FILE FORMAT:
* ------------------------------
* 1. create hex file, format:
* 3800 -> header
* 0000 -> header
* 5a40 -> data
*
* rev - 6 bit (word1)
* len - 10 bit (word1)
* id - 4 bit (word2)
* rsv - 12 bit (word2)
*
* 2. flip 8bits with 8 bits per line to get the right NVM file format
*
* 3. create binary file from the hex file
*
* 4. save as "iNVM_xxx.bin" under /lib/firmware
*/
static int iwl_xvt_load_external_nvm(struct iwl_xvt *xvt)
{
int ret, section_size;
u16 section_id;
const struct firmware *fw_entry;
const struct {
__le16 word1;
__le16 word2;
u8 data[];
} *file_sec;
const u8 *eof;
const __le32 *dword_buff;
const u8 *hw_addr;
#define NVM_WORD1_LEN(x) (8 * (x & 0x03FF))
#define NVM_WORD2_ID(x) (x >> 12)
#define EXT_NVM_WORD2_LEN(x) (2 * (((x) & 0xFF) << 8 | (x) >> 8))
#define EXT_NVM_WORD1_ID(x) ((x) >> 4)
#define NVM_HEADER_0 (0x2A504C54)
#define NVM_HEADER_1 (0x4E564D2A)
#define NVM_HEADER_SIZE (4 * sizeof(u32))
/*
* Obtain NVM image via request_firmware. Since we already used
* request_firmware_nowait() for the firmware binary load and only
* get here after that we assume the NVM request can be satisfied
* synchronously.
*/
ret = request_firmware(&fw_entry, iwlwifi_mod_params.nvm_file,
xvt->trans->dev);
if (ret) {
IWL_WARN(xvt, "WARNING: %s isn't available %d\n",
iwlwifi_mod_params.nvm_file, ret);
return 0;
}
IWL_INFO(xvt, "Loaded NVM file %s (%zu bytes)\n",
iwlwifi_mod_params.nvm_file, fw_entry->size);
if (fw_entry->size > MAX_NVM_FILE_LEN) {
IWL_ERR(xvt, "NVM file too large\n");
ret = -EINVAL;
goto out;
}
eof = fw_entry->data + fw_entry->size;
dword_buff = (const __le32 *)fw_entry->data;
/* some NVM file will contain a header.
* The header is identified by 2 dwords header as follows:
* dword[0] = 0x2A504C54
* dword[1] = 0x4E564D2A
*
* This header must be skipped when providing the NVM data to the FW.
*/
if (fw_entry->size > NVM_HEADER_SIZE &&
dword_buff[0] == cpu_to_le32(NVM_HEADER_0) &&
dword_buff[1] == cpu_to_le32(NVM_HEADER_1)) {
file_sec = (const void *)(fw_entry->data + NVM_HEADER_SIZE);
IWL_INFO(xvt, "NVM Version %08X\n", le32_to_cpu(dword_buff[2]));
IWL_INFO(xvt, "NVM Manufacturing date %08X\n",
le32_to_cpu(dword_buff[3]));
} else {
file_sec = (const void *)fw_entry->data;
}
while (true) {
if (file_sec->data > eof) {
IWL_ERR(xvt,
"ERROR - NVM file too short for section header\n");
ret = -EINVAL;
break;
}
/* check for EOF marker */
if (!file_sec->word1 && !file_sec->word2) {
ret = 0;
break;
}
if (xvt->trans->cfg->nvm_type != IWL_NVM_EXT) {
section_size =
2 * NVM_WORD1_LEN(le16_to_cpu(file_sec->word1));
section_id = NVM_WORD2_ID(le16_to_cpu(file_sec->word2));
} else {
section_size = 2 * EXT_NVM_WORD2_LEN(
le16_to_cpu(file_sec->word2));
section_id = EXT_NVM_WORD1_ID(
le16_to_cpu(file_sec->word1));
}
if (section_size > IWL_MAX_NVM_SECTION_SIZE) {
IWL_ERR(xvt, "ERROR - section too large (%d)\n",
section_size);
ret = -EINVAL;
break;
}
if (!section_size) {
IWL_ERR(xvt, "ERROR - section empty\n");
ret = -EINVAL;
break;
}
if (file_sec->data + section_size > eof) {
IWL_ERR(xvt,
"ERROR - NVM file too short for section (%d bytes)\n",
section_size);
ret = -EINVAL;
break;
}
if (section_id == xvt->cfg->nvm_hw_section_num) {
hw_addr = (const u8 *)((const __le16 *)file_sec->data +
HW_ADDR);
/* The byte order is little endian 16 bit, meaning 214365 */
xvt->nvm_hw_addr[0] = hw_addr[1];
xvt->nvm_hw_addr[1] = hw_addr[0];
xvt->nvm_hw_addr[2] = hw_addr[3];
xvt->nvm_hw_addr[3] = hw_addr[2];
xvt->nvm_hw_addr[4] = hw_addr[5];
xvt->nvm_hw_addr[5] = hw_addr[4];
}
if (section_id == NVM_SECTION_TYPE_MAC_OVERRIDE) {
xvt->is_nvm_mac_override = true;
hw_addr = (const u8 *)((const __le16 *)file_sec->data +
MAC_ADDRESS_OVERRIDE_EXT_NVM);
/*
* Store the MAC address from MAO section.
* No byte swapping is required in MAO section.
*/
memcpy(xvt->nvm_hw_addr, hw_addr, ETH_ALEN);
}
ret = iwl_nvm_write_section(xvt, section_id, file_sec->data,
section_size);
if (ret < 0) {
IWL_ERR(xvt, "iwl_mvm_send_cmd failed: %d\n", ret);
break;
}
/* advance to the next section */
file_sec = (const void *)(file_sec->data + section_size);
}
out:
release_firmware(fw_entry);
return ret;
}
int iwl_xvt_nvm_init(struct iwl_xvt *xvt)
{
int ret;
xvt->is_nvm_mac_override = false;
/* load external NVM if configured */
if (iwlwifi_mod_params.nvm_file) {
/* move to External NVM flow */
ret = iwl_xvt_load_external_nvm(xvt);
if (ret)
return ret;
}
return 0;
}