723 lines
20 KiB
C
Executable File
723 lines
20 KiB
C
Executable File
/****************************************************************************
|
|
*
|
|
* Copyright (c) 2016 Samsung Electronics Co., Ltd. All rights reserved.
|
|
*
|
|
****************************************************************************/
|
|
|
|
#include <linux/module.h>
|
|
#include <linux/version.h>
|
|
#include <linux/firmware.h>
|
|
#include <linux/fs.h>
|
|
#if KERNEL_VERSION(4, 15, 0) <= LINUX_VERSION_CODE
|
|
#include <linux/uaccess.h>
|
|
#else
|
|
#include <asm/uaccess.h>
|
|
#endif
|
|
#include <linux/slab.h>
|
|
#include <linux/vmalloc.h>
|
|
#include <scsc/scsc_logring.h>
|
|
#include <scsc/scsc_mx.h>
|
|
|
|
#include "scsc_mx_impl.h"
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
|
MODULE_IMPORT_NS(VFS_internal_I_am_really_a_filesystem_and_am_NOT_a_driver);
|
|
#endif
|
|
|
|
/* Firmware directory definitions */
|
|
|
|
#define SCSC_MULTI_RF_CHIP_ID /* Select FW by RF chip ID, not rev */
|
|
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
|
|
#if defined(CONFIG_SCSC_CORE_FW_LOCATION) && !defined(CONFIG_SCSC_CORE_FW_LOCATION_AUTO)
|
|
#define MX140_FW_BASE_DIR_SYSTEM_ETC_WIFI CONFIG_SCSC_CORE_FW_LOCATION
|
|
#define MX140_FW_BASE_DIR_VENDOR_ETC_WIFI CONFIG_SCSC_CORE_FW_LOCATION
|
|
#else
|
|
#define MX140_FW_BASE_DIR_SYSTEM_ETC_WIFI "/system/etc/wifi"
|
|
#define MX140_FW_BASE_DIR_VENDOR_ETC_WIFI "/vendor/etc/wifi"
|
|
#endif
|
|
#else /* when using request_firmware we need to hardcode the folders */
|
|
#define MX140_FW_BASE_DIR_SYSTEM_ETC_WIFI "/vendor/etc/wifi"
|
|
#define MX140_FW_BASE_DIR_VENDOR_ETC_WIFI "/vendor/etc/wifi"
|
|
#endif
|
|
|
|
/* Paths for vendor utilities, used when CONFIG_SCSC_CORE_FW_LOCATION_AUTO=n */
|
|
#define MX140_EXE_DIR_VENDOR "/vendor/bin" /* Oreo */
|
|
#define MX140_EXE_DIR_SYSTEM "/system/bin" /* Before Oreo */
|
|
|
|
#define MX140_FW_CONF_SUBDIR "conf"
|
|
#define MX140_FW_DEBUG_SUBDIR "debug"
|
|
#define MX140_FW_BIN "mx140.bin"
|
|
#define MX140_FW_PATH_MAX_LENGTH (512)
|
|
|
|
#define MX140_FW_VARIANT_DEFAULT "mx140"
|
|
|
|
/* Table of suffixes to append to f/w name */
|
|
struct fw_suffix {
|
|
char suffix[6];
|
|
u32 hw_ver;
|
|
};
|
|
|
|
#ifdef SCSC_MULTI_RF_CHIP_ID /* Select by chip ID (S611, S612) */
|
|
|
|
/* This scheme has one firmware binary for all revisions of an
|
|
* RF chip ID.
|
|
*/
|
|
|
|
/* Table of known RF h/w IDs */
|
|
static const struct fw_suffix fw_suffixes[] = {
|
|
{ .suffix = "", .hw_ver = 0xff, }, /* plain mx140.bin, always used if found */
|
|
{ .suffix = "_s612", .hw_ver = 0xb1, },
|
|
{ .suffix = "_s611", .hw_ver = 0xb0, },
|
|
};
|
|
|
|
#else /* Select by chip revision (EVT0.0, EVT0.1) */
|
|
|
|
/* This legacy scheme assumes a different fw binary for each revision
|
|
* of an RF chip ID, and those will uniquely identify the
|
|
* right build. This was used for early S5E7570 until a unified
|
|
* binary was available.
|
|
*/
|
|
|
|
/* Table of known RF h/w revs */
|
|
static const struct fw_suffix fw_suffixes[] = {
|
|
{ .suffix = "_11", .hw_ver = 0x11, },
|
|
{ .suffix = "_10", .hw_ver = 0x10, },
|
|
{ .suffix = "_00", .hw_ver = 0x00, },
|
|
{ .suffix = "", .hw_ver = 0xff, }, /* plain mx140.bin, must be last */
|
|
};
|
|
#endif
|
|
|
|
/* Once set, we always load this firmware suffix */
|
|
static int fw_suffix_found = -1;
|
|
|
|
/* Variant of firmware binary to load */
|
|
static char *firmware_variant = MX140_FW_VARIANT_DEFAULT;
|
|
module_param(firmware_variant, charp, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(firmware_variant, "mx140 firmware variant, default mx140");
|
|
|
|
/* RF hardware version of firmware to load. If "auto" this gets replaced with
|
|
* the suffix of FW that got loaded.
|
|
* If "manual" it loads the version specified by firmware_variant, verbatim.
|
|
*/
|
|
static char *firmware_hw_ver = "auto";
|
|
module_param(firmware_hw_ver, charp, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(firmware_hw_ver, "mx140 hw version detect, manual=disable");
|
|
|
|
/* FW base dir readable by usermode script */
|
|
#ifdef CONFIG_SCSC_CORE_FW_LOCATION_AUTO
|
|
static char *fw_base_dir;
|
|
#else
|
|
static char *fw_base_dir = CONFIG_SCSC_CORE_FW_LOCATION;
|
|
#endif
|
|
module_param_named(base_dir, fw_base_dir, charp, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(base_dir, "WLBT FW base directory");
|
|
|
|
/* Firmware and tool (moredump) exe base directory */
|
|
#ifdef CONFIG_SCSC_CORE_FW_LOCATION_AUTO
|
|
static char base_dir[MX140_FW_PATH_MAX_LENGTH]; /* auto detect */
|
|
static char exe_dir[MX140_FW_PATH_MAX_LENGTH]; /* auto detect */
|
|
#else
|
|
static char base_dir[] = CONFIG_SCSC_CORE_FW_LOCATION; /* fixed in defconfig */
|
|
static char exe_dir[] = CONFIG_SCSC_CORE_TOOL_LOCATION; /* fixed in defconfig */
|
|
#endif
|
|
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
|
static char base_dir_request_fw[] = "../etc/wifi"; /* fixed in defconfig */
|
|
#endif
|
|
|
|
|
|
static bool enable_auto_sense;
|
|
module_param(enable_auto_sense, bool, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(enable_auto_sense, "deprecated");
|
|
|
|
static bool use_new_fw_structure = true;
|
|
module_param(use_new_fw_structure, bool, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(use_new_fw_structure, "deprecated");
|
|
|
|
static char *cfg_platform = "default";
|
|
module_param(cfg_platform, charp, S_IRUGO | S_IWUSR);
|
|
MODULE_PARM_DESC(cfg_platform, "HCF config subdirectory");
|
|
|
|
/* Reads a configuration file into memory (f/w profile specific) */
|
|
static int __mx140_file_request_conf(struct scsc_mx *mx,
|
|
const struct firmware **conf,
|
|
const char *platform_dir,
|
|
const char *config_rel_path,
|
|
const char *filename,
|
|
const bool flat)
|
|
|
|
{
|
|
char config_path[MX140_FW_PATH_MAX_LENGTH];
|
|
int r;
|
|
|
|
if (mx140_basedir_file(mx))
|
|
return -ENOENT;
|
|
|
|
if (flat) {
|
|
/* e.g. /etc/wifi/mx140_wlan.hcf */
|
|
|
|
scnprintf(config_path, sizeof(config_path),
|
|
"%s/%s%s_%s",
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
|
base_dir_request_fw,
|
|
#else
|
|
base_dir,
|
|
#endif
|
|
firmware_variant,
|
|
fw_suffixes[fw_suffix_found].suffix,
|
|
filename);
|
|
} else {
|
|
/* e.g. /etc/wifi/mx140/conf/$platform_dir/wlan/wlan.hcf */
|
|
|
|
scnprintf(config_path, sizeof(config_path),
|
|
"%s/%s%s/%s/%s%s%s/%s",
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
|
base_dir_request_fw,
|
|
#else
|
|
base_dir,
|
|
#endif
|
|
firmware_variant,
|
|
fw_suffixes[fw_suffix_found].suffix,
|
|
MX140_FW_CONF_SUBDIR,
|
|
platform_dir,
|
|
(platform_dir[0] != '\0' ? "/" : ""), /* add "/" if platform_dir not empty */
|
|
config_rel_path,
|
|
filename);
|
|
}
|
|
SCSC_TAG_INFO(MX_FILE, "try %s\n", config_path);
|
|
|
|
r = mx140_request_file(mx, config_path, conf);
|
|
|
|
/* Confirm what we read */
|
|
if (r == 0)
|
|
SCSC_TAG_INFO(MX_FILE, "loaded %s\n", config_path);
|
|
|
|
return r;
|
|
}
|
|
|
|
int mx140_file_request_conf(struct scsc_mx *mx,
|
|
const struct firmware **conf,
|
|
const char *config_rel_path,
|
|
const char *filename)
|
|
{
|
|
int r;
|
|
|
|
/* First, if the config subdirectory has been overriden by cfg_platform
|
|
* module parameter, search only in that location.
|
|
*/
|
|
if (strcmp(cfg_platform, "default")) {
|
|
SCSC_TAG_INFO(MX_FILE, "module param cfg_platform = %s\n", cfg_platform);
|
|
return __mx140_file_request_conf(mx, conf, cfg_platform, config_rel_path, filename, false);
|
|
}
|
|
|
|
/* Search in generic location. This is an override.
|
|
* e.g. /etc/wifi/mx140/conf/wlan/wlan.hcf
|
|
*/
|
|
r = __mx140_file_request_conf(mx, conf, "", config_rel_path, filename, false);
|
|
|
|
#if defined CONFIG_SCSC_WLBT_CONFIG_PLATFORM
|
|
/* Then search in platform location
|
|
* e.g. /etc/wifi/mx140/conf/$platform_dir/wlan/wlan.hcf
|
|
*/
|
|
if (r) {
|
|
const char *plat = CONFIG_SCSC_WLBT_CONFIG_PLATFORM;
|
|
|
|
/* Don't bother if plat is empty string */
|
|
if (plat[0] != '\0')
|
|
r = __mx140_file_request_conf(mx, conf, plat, config_rel_path, filename, false);
|
|
}
|
|
#endif
|
|
|
|
/* Finally request "flat" conf, where all hcf files are in FW root dir
|
|
* e.g. /etc/wifi/<firmware-variant>-wlan.hcf
|
|
*/
|
|
if (r)
|
|
r = __mx140_file_request_conf(mx, conf, "", config_rel_path, filename, true);
|
|
|
|
return r;
|
|
}
|
|
|
|
EXPORT_SYMBOL(mx140_file_request_conf);
|
|
|
|
/* Reads a debug configuration file into memory (f/w profile specific) */
|
|
int mx140_file_request_debug_conf(struct scsc_mx *mx, const struct firmware **conf, const char *config_rel_path)
|
|
{
|
|
char config_path[MX140_FW_PATH_MAX_LENGTH];
|
|
|
|
if (mx140_basedir_file(mx))
|
|
return -ENOENT;
|
|
|
|
/* e.g. /etc/wifi/mx140/debug/log_strings.bin */
|
|
|
|
scnprintf(config_path, sizeof(config_path),
|
|
"%s/%s%s/%s/%s",
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
|
base_dir_request_fw,
|
|
#else
|
|
base_dir,
|
|
#endif
|
|
firmware_variant,
|
|
fw_suffixes[fw_suffix_found].suffix,
|
|
MX140_FW_DEBUG_SUBDIR,
|
|
config_rel_path);
|
|
|
|
return mx140_request_file(mx, config_path, conf);
|
|
}
|
|
EXPORT_SYMBOL(mx140_file_request_debug_conf);
|
|
|
|
/* Read device configuration file into memory (whole device specific) */
|
|
int mx140_file_request_device_conf(struct scsc_mx *mx, const struct firmware **conf, const char *config_rel_path)
|
|
{
|
|
char config_path[MX140_FW_PATH_MAX_LENGTH];
|
|
|
|
if (mx140_basedir_file(mx))
|
|
return -ENOENT;
|
|
|
|
/* e.g. /etc/wifi/conf/wlan/mac.txt */
|
|
|
|
snprintf(config_path, sizeof(config_path),
|
|
"%s/%s%s/%s",
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
|
base_dir_request_fw,
|
|
#else
|
|
base_dir,
|
|
#endif
|
|
fw_suffixes[fw_suffix_found].suffix,
|
|
MX140_FW_CONF_SUBDIR,
|
|
config_rel_path);
|
|
|
|
return mx140_request_file(mx, config_path, conf);
|
|
}
|
|
EXPORT_SYMBOL(mx140_file_request_device_conf);
|
|
|
|
/* Release configuration file memory. */
|
|
void mx140_file_release_conf(struct scsc_mx *mx, const struct firmware *conf)
|
|
{
|
|
(void)mx;
|
|
|
|
mx140_release_file(mx, conf);
|
|
}
|
|
EXPORT_SYMBOL(mx140_file_release_conf);
|
|
|
|
static int __mx140_file_download_fw(struct scsc_mx *mx, void *dest, size_t dest_size, u32 *fw_image_size, const char *fw_suffix)
|
|
{
|
|
const struct firmware *firm;
|
|
int r = 0;
|
|
char img_path_name[MX140_FW_PATH_MAX_LENGTH];
|
|
|
|
if (mx140_basedir_file(mx))
|
|
return -ENOENT;
|
|
|
|
SCSC_TAG_INFO(MX_FILE, "firmware_variant=%s (%s)\n", firmware_variant, fw_suffix);
|
|
|
|
/* e.g. /etc/wifi/mx140.bin */
|
|
scnprintf(img_path_name, sizeof(img_path_name),
|
|
"%s/%s%s.bin",
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
|
base_dir_request_fw,
|
|
#else
|
|
base_dir,
|
|
#endif
|
|
firmware_variant,
|
|
fw_suffix);
|
|
|
|
SCSC_TAG_INFO(MX_FILE, "Load WLBT fw %s in shared address %p\n", img_path_name, dest);
|
|
r = mx140_request_file(mx, img_path_name, &firm);
|
|
if (r) {
|
|
SCSC_TAG_ERR(MX_FILE, "Error Loading FW, error %d\n", r);
|
|
return r;
|
|
}
|
|
SCSC_TAG_DEBUG(MX_FILE, "FW Download, size %zu\n", firm->size);
|
|
|
|
if (firm->size > dest_size) {
|
|
SCSC_TAG_ERR(MX_FILE, "firmware image too big for buffer (%zu > %u)", dest_size, *fw_image_size);
|
|
r = -EINVAL;
|
|
} else {
|
|
memcpy(dest, firm->data, firm->size);
|
|
*fw_image_size = firm->size;
|
|
}
|
|
mx140_release_file(mx, firm);
|
|
return r;
|
|
}
|
|
|
|
/* Download firmware binary into a buffer supplied by the caller */
|
|
int mx140_file_download_fw(struct scsc_mx *mx, void *dest, size_t dest_size, u32 *fw_image_size)
|
|
{
|
|
int r;
|
|
int i;
|
|
int manual;
|
|
|
|
/* Override to use the verbatim image only */
|
|
manual = !strcmp(firmware_hw_ver, "manual");
|
|
if (manual) {
|
|
SCSC_TAG_INFO(MX_FILE, "manual hw version\n");
|
|
fw_suffix_found = sizeof(fw_suffixes) / sizeof(fw_suffixes[0]) - 1;
|
|
}
|
|
|
|
SCSC_TAG_DEBUG(MX_FILE, "fw_suffix_found %d\n", fw_suffix_found);
|
|
|
|
/* If we know which f/w suffix to use, select it immediately */
|
|
if (fw_suffix_found != -1) {
|
|
r = __mx140_file_download_fw(mx, dest, dest_size, fw_image_size, fw_suffixes[fw_suffix_found].suffix);
|
|
goto done;
|
|
}
|
|
|
|
/* Otherwise try the list */
|
|
for (i = 0; i < sizeof(fw_suffixes) / sizeof(fw_suffixes[0]); i++) {
|
|
/* Try to find each suffix in turn */
|
|
SCSC_TAG_INFO(MX_FILE, "try %d %s\n", i, fw_suffixes[i].suffix);
|
|
r = __mx140_file_download_fw(mx, dest, dest_size, fw_image_size, fw_suffixes[i].suffix);
|
|
if (r != -ENOENT)
|
|
break;
|
|
}
|
|
|
|
/* Save this for next time */
|
|
if (r == 0)
|
|
fw_suffix_found = i;
|
|
done:
|
|
/* Update firmware_hw_ver to reflect what got auto selected, for moredump */
|
|
if (fw_suffix_found != -1 && !manual) {
|
|
/* User will only read this, so casting away const is safe */
|
|
firmware_hw_ver = (char *)fw_suffixes[fw_suffix_found].suffix;
|
|
}
|
|
return r;
|
|
}
|
|
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
|
|
int __mx140_request_file(struct scsc_mx *mx, char *path, const struct firmware **firmp)
|
|
{
|
|
struct file *f;
|
|
mm_segment_t fs;
|
|
struct kstat stat;
|
|
const int max_read_size = 4096;
|
|
int r, whats_left, to_read, size;
|
|
struct firmware *firm;
|
|
char *buf, *p;
|
|
|
|
SCSC_TAG_DEBUG(MX_FILE, "request %s\n", path);
|
|
|
|
*firmp = NULL;
|
|
|
|
/* Check FS is ready */
|
|
|
|
/* Try to determine base dir */
|
|
r = mx140_basedir_file(mx);
|
|
if (r) {
|
|
SCSC_TAG_ERR(MX_FILE, "detect failed for fw base_dir %d\n", r);
|
|
return r;
|
|
}
|
|
|
|
/* Current segment. */
|
|
fs = get_fs();
|
|
/* Set to kernel segment. */
|
|
set_fs(KERNEL_DS);
|
|
|
|
r = vfs_stat(base_dir, &stat);
|
|
if (r != 0) {
|
|
set_fs(fs);
|
|
SCSC_TAG_ERR(MX_FILE, "vfs_stat() failed for %s\n", base_dir);
|
|
return -EAGAIN;
|
|
}
|
|
|
|
/* Check f/w bin */
|
|
r = vfs_stat(path, &stat);
|
|
if (r != 0) {
|
|
set_fs(fs);
|
|
SCSC_TAG_ERR(MX_FILE, "vfs_stat() failed for %s\n", path);
|
|
return -ENOENT;
|
|
}
|
|
/* Revert to original segment. */
|
|
set_fs(fs);
|
|
|
|
/* Round up for minimum sizes */
|
|
size = (stat.size + 256) & ~255;
|
|
/* Get memory for file contents. */
|
|
buf = vzalloc(size);
|
|
if (!buf) {
|
|
SCSC_TAG_ERR(MX_FILE, "kzalloc(%d) failed for %s\n", size, path);
|
|
return -ENOMEM;
|
|
}
|
|
p = buf;
|
|
/* Get firmware structure. */
|
|
firm = kzalloc(sizeof(*firm), GFP_KERNEL);
|
|
if (!firm) {
|
|
vfree(buf);
|
|
SCSC_TAG_ERR(MX_FILE, "kzalloc(%zu) failed for %s\n", sizeof(*firmp), path);
|
|
return -ENOMEM;
|
|
}
|
|
/* Open the file for reading. */
|
|
f = filp_open(path, O_RDONLY, 0);
|
|
if (IS_ERR(f)) {
|
|
vfree(buf);
|
|
kfree(firm);
|
|
SCSC_TAG_ERR(MX_FILE, "filp_open() failed for %s with %ld\n", path, PTR_ERR(f));
|
|
return -ENOENT;
|
|
}
|
|
|
|
whats_left = stat.size;
|
|
|
|
fs = get_fs();
|
|
set_fs(KERNEL_DS);
|
|
|
|
/* Special case if file length is reported as zero - try to read until it fails.
|
|
* This allows us to read /proc
|
|
*/
|
|
if (whats_left == 0) {
|
|
do {
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
|
r = kernel_read(f, p, max_read_size, &f->f_pos);
|
|
#else
|
|
r = vfs_read(f, p, max_read_size, &f->f_pos);
|
|
#endif
|
|
if (r < 0) {
|
|
SCSC_TAG_INFO(MX_FILE, "No more data %s\n", path);
|
|
break;
|
|
}
|
|
p += r;
|
|
if (r < max_read_size) {
|
|
SCSC_TAG_INFO(MX_FILE, "Read %zd from %s\n", (ptrdiff_t)(p - buf), path);
|
|
break;
|
|
}
|
|
} while (r > 0);
|
|
|
|
goto done;
|
|
}
|
|
|
|
/* Read at most max_read_size in each read. Loop until the whole file has
|
|
* been copied to the local buffer.
|
|
*/
|
|
while (whats_left) {
|
|
to_read = whats_left < max_read_size ? whats_left : max_read_size;
|
|
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 4, 0))
|
|
r = kernel_read(f, p, max_read_size, &f->f_pos);
|
|
#else
|
|
r = vfs_read(f, p, max_read_size, &f->f_pos);
|
|
#endif
|
|
if (r < 0) {
|
|
SCSC_TAG_ERR(MX_FILE, "error reading %s\n", path);
|
|
break;
|
|
}
|
|
if (r == 0 || r < to_read)
|
|
break;
|
|
whats_left -= r;
|
|
p += r;
|
|
}
|
|
done:
|
|
set_fs(fs);
|
|
filp_close(f, NULL);
|
|
|
|
if (r >= 0) {
|
|
r = 0;
|
|
/* Pass to caller. Caller will free allocated memory through
|
|
* mx140_release_file().
|
|
*/
|
|
firm->size = p - buf;
|
|
firm->data = buf;
|
|
*firmp = firm;
|
|
} else {
|
|
vfree(buf);
|
|
kfree(firm);
|
|
}
|
|
return r;
|
|
|
|
}
|
|
#endif
|
|
|
|
int __mx140_request_firmware(struct scsc_mx *mx, char *path, const struct firmware **firmp)
|
|
{
|
|
int ret;
|
|
struct device *dev;
|
|
|
|
SCSC_TAG_DEBUG(MX_FILE, "request %s\n", path);
|
|
|
|
dev = scsc_mx_get_device(mx);
|
|
if (!dev) {
|
|
SCSC_TAG_ERR(MX_FILE, "Error. Device is NULL\n");
|
|
return -EIO;
|
|
}
|
|
ret = request_firmware(firmp, path, dev);
|
|
|
|
SCSC_TAG_DEBUG(MX_FILE, "request %s\n", path);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int mx140_request_file(struct scsc_mx *mx, char *path, const struct firmware **firmp)
|
|
{
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
|
|
return __mx140_request_file(mx, path, firmp);
|
|
#else
|
|
return __mx140_request_firmware(mx, path, firmp);
|
|
#endif
|
|
}
|
|
EXPORT_SYMBOL(mx140_request_file);
|
|
|
|
|
|
int __mx140_release_firmware(struct scsc_mx *mx, const struct firmware *firmp)
|
|
{
|
|
if (!firmp || !firmp->data) {
|
|
SCSC_TAG_ERR(MX_FILE, "firmp=%p\n", firmp);
|
|
return -EINVAL;
|
|
}
|
|
|
|
SCSC_TAG_DEBUG(MX_FILE, "release firmp=%p, data=%p\n", firmp, firmp->data);
|
|
|
|
release_firmware(firmp);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int __mx140_release_file(struct scsc_mx *mx, const struct firmware *firmp)
|
|
{
|
|
if (!firmp || !firmp->data) {
|
|
SCSC_TAG_ERR(MX_FILE, "firmp=%p\n", firmp);
|
|
return -EINVAL;
|
|
}
|
|
|
|
SCSC_TAG_DEBUG(MX_FILE, "release firmp=%p, data=%p\n", firmp, firmp->data);
|
|
|
|
vfree(firmp->data);
|
|
kfree(firmp);
|
|
return 0;
|
|
}
|
|
|
|
int mx140_release_file(struct scsc_mx *mx, const struct firmware *firmp)
|
|
{
|
|
#if (LINUX_VERSION_CODE < KERNEL_VERSION(5, 4, 0))
|
|
return __mx140_release_file(mx, firmp);
|
|
#else
|
|
return __mx140_release_firmware(mx, firmp);
|
|
#endif
|
|
}
|
|
EXPORT_SYMBOL(mx140_release_file);
|
|
|
|
/* Work out correct path for vendor binaries */
|
|
int mx140_exe_path(struct scsc_mx *mx, char *path, size_t len, const char *bin)
|
|
{
|
|
(void)mx;
|
|
|
|
/* Set up when we detect FW path, or statically when
|
|
* auto-detect is off
|
|
*/
|
|
if (exe_dir[0] == '\0')
|
|
return -ENOENT;
|
|
|
|
if (path == NULL)
|
|
return -EINVAL;
|
|
|
|
snprintf(path, len, "%s/%s", exe_dir, bin);
|
|
|
|
SCSC_TAG_DEBUG(MX_FILE, "exe: %s\n", path);
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(mx140_exe_path);
|
|
|
|
/* Try to auto detect f/w directory */
|
|
int mx140_basedir_file(struct scsc_mx *mx)
|
|
{
|
|
struct kstat stat;
|
|
mm_segment_t fs;
|
|
int r = 0;
|
|
|
|
/* Already worked out base dir. This is
|
|
* static if auto-detect is off.
|
|
*/
|
|
if (base_dir[0] != '\0')
|
|
return 0;
|
|
|
|
/* Current segment. */
|
|
fs = get_fs();
|
|
/* Set to kernel segment. */
|
|
set_fs(KERNEL_DS);
|
|
|
|
/* If /system isn't present, assume platform isn't ready yet */
|
|
r = vfs_stat("/system", &stat);
|
|
if (r != 0) {
|
|
SCSC_TAG_ERR(MX_FILE, "/system not mounted yet\n");
|
|
r = -EAGAIN;
|
|
goto done;
|
|
}
|
|
|
|
/* If /vendor isn't present, assume platform isn't ready yet.
|
|
* Android M and N still have /vendor, though we don't use it.
|
|
*/
|
|
r = vfs_stat("/vendor", &stat);
|
|
if (r != 0) {
|
|
SCSC_TAG_ERR(MX_FILE, "/vendor not mounted yet\n");
|
|
r = -EAGAIN;
|
|
goto done;
|
|
}
|
|
|
|
/* Now partitions are mounted, so let's see what's in them. */
|
|
|
|
/* Try /vendor partition (post-Oreo) */
|
|
strlcpy(base_dir, MX140_FW_BASE_DIR_VENDOR_ETC_WIFI, sizeof(base_dir));
|
|
fw_base_dir = MX140_FW_BASE_DIR_VENDOR_ETC_WIFI;
|
|
strlcpy(exe_dir, MX140_EXE_DIR_VENDOR, sizeof(exe_dir));
|
|
#if defined(SCSC_SEP_VERSION) && (SCSC_SEP_VERSION < 8)
|
|
/* Try /system partition (pre-Oreo) */
|
|
strlcpy(base_dir, MX140_FW_BASE_DIR_SYSTEM_ETC_WIFI, sizeof(base_dir));
|
|
fw_base_dir = MX140_FW_BASE_DIR_SYSTEM_ETC_WIFI;
|
|
strlcpy(exe_dir, MX140_EXE_DIR_SYSTEM, sizeof(exe_dir));
|
|
#endif
|
|
|
|
done:
|
|
/* Restore segment */
|
|
set_fs(fs);
|
|
SCSC_TAG_INFO(MX_FILE, "WLBT fw base dir is %s\n", base_dir[0] ? base_dir : "not found");
|
|
|
|
return r;
|
|
}
|
|
|
|
/* Select file for h/w version from filesystem */
|
|
int mx140_file_select_fw(struct scsc_mx *mx, u32 hw_ver)
|
|
{
|
|
int i;
|
|
|
|
SCSC_TAG_INFO(MX_FILE, "select f/w for 0x%04x\n", hw_ver);
|
|
|
|
#ifdef SCSC_MULTI_RF_CHIP_ID
|
|
hw_ver = (hw_ver & 0x00ff); /* LSB is the RF HW ID (e.g. S610) */
|
|
#else
|
|
hw_ver = (hw_ver & 0xff00) >> 8; /* MSB is the RF HW rev (e.g. EVT1.1) */
|
|
#endif
|
|
|
|
for (i = 0; i < sizeof(fw_suffixes) / sizeof(fw_suffixes[0]); i++) {
|
|
if (fw_suffixes[i].hw_ver == hw_ver) {
|
|
fw_suffix_found = i;
|
|
SCSC_TAG_DEBUG(MX_FILE, "f/w for 0x%04x: index %d, suffix '%s'\n",
|
|
hw_ver, i, fw_suffixes[i].suffix);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
SCSC_TAG_ERR(MX_FILE, "No known f/w for 0x%04x, default to catchall\n", hw_ver);
|
|
|
|
/* Enable when a unified FW image is installed */
|
|
#ifdef MX140_UNIFIED_HW_FW
|
|
/* The last f/w is the non-suffixed "<fw>.bin", assume it's compatible */
|
|
fw_suffix_found = i - 1;
|
|
#else
|
|
fw_suffix_found = -1; /* None found */
|
|
#endif
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Query whether this HW is supported by the current FW file set */
|
|
bool mx140_file_supported_hw(struct scsc_mx *mx, u32 hw_ver)
|
|
{
|
|
#ifdef SCSC_MULTI_RF_CHIP_ID
|
|
hw_ver = (hw_ver & 0x00ff); /* LSB is the RF HW ID (e.g. S610) */
|
|
#else
|
|
hw_ver = (hw_ver & 0xff00) >> 8; /* MSB is the RF HW rev (e.g. EVT1.0) */
|
|
#endif
|
|
/* Assume installed 0xff is always compatible, and f/w will panic if it isn't */
|
|
if (fw_suffixes[fw_suffix_found].hw_ver == 0xff)
|
|
return true;
|
|
|
|
/* Does the select f/w match the hw_ver from chip? */
|
|
return (fw_suffixes[fw_suffix_found].hw_ver == hw_ver);
|
|
}
|
|
|