Add Qorvo UWB driver

Add Qorvo UWB driver for Qorvo UWB
chipset QM35725s

Change-Id: I77124d7d242863fd8c8a602db7d4cb59aa96ef57
Signed-off-by: zhuangnk1 <zhuangnk1@lenovo.com>
Reviewed-on: https://gerrit.mot.com/2687911
SME-Granted: SME Approvals Granted
SLTApproved: Slta Waiver
Tested-by: Jira Key
Reviewed-by: Tao Sun <suntao2@motorola.com>
Submit-Approved: Jira Key
This commit is contained in:
zhuangnk1 2023-08-21 17:09:48 +08:00 • committed by Ningkai Zhuang
commit 11786f6aae
43 changed files with 9394 additions and 0 deletions

View file

@ -0,0 +1,9 @@
DLKM_DIR := motorola/kernel/modules
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := qm35.ko
LOCAL_MODULE_TAGS := optional
LOCAL_MODULE_PATH := $(KERNEL_MODULES_OUT)
LOCAL_ADDITIONAL_DEPENDENCIES := $(KERNEL_MODULES_OUT)/mmi_info.ko
include $(DLKM_DIR)/AndroidKernelModule.mk

27
drivers/uwb/qm35/Kbuild Normal file
View file

@ -0,0 +1,27 @@
# add -Wall to try to catch everything we can.
EXTRA_CFLAGS += -Wall
EXTRA_CFLAGS += -I$(ANDROID_BUILD_TOP)/motorola/kernel/modules/include
obj-m := qm35.o
qm35-y := \
qm35-spi.o \
qm35_rb.o \
qmrom_spi.o \
libqmrom/src/qmrom_common.o \
libqmrom/src/qm357xx_rom_common.o \
libqmrom/src/qm357xx_rom_b0.o \
libqmrom/src/qm357xx_rom_c0.o \
libqmrom/src/qmrom_log.o \
libfwupdater/src/fwupdater.o \
hsspi.o \
hsspi_uci.o \
hsspi_log.o \
hsspi_coredump.o \
debug.o \
hsspi_test.o
qm35-y += debug_qmrom.o
qm35-y += qm35-trace.o
KBUILD_EXTRA_SYMBOLS += $(CURDIR)/$(KBUILD_EXTMOD)/../../mmi_info/Module.symvers

17
drivers/uwb/qm35/Makefile Normal file
View file

@ -0,0 +1,17 @@
all: modules
modules:
$(MAKE) -C $(KERNEL_SRC) M=$(M) modules $(KBUILD_OPTIONS)
modules_install:
$(MAKE) INSTALL_MOD_STRIP=1 -C $(KERNEL_SRC) M=$(M) modules_install
%:
$(MAKE) -C $(KERNEL_SRC) M=$(M) $@ $(KBUILD_OPTIONS)
clean:
rm -f *.o *.ko *.mod.c *.mod.o *~ .*.cmd Module.symvers
rm -rf .tmp_versions
KBUILD_EXTRA_SYMBOLS := $(M)/../../mmi_info/Module.symvers

513
drivers/uwb/qm35/debug.c Normal file
View file

@ -0,0 +1,513 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2022 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 LOG layer HSSPI Protocol
*/
#include <linux/debugfs.h>
#include <linux/poll.h>
#include <linux/fsnotify.h>
#include <linux/uwb/qmrom.h>
#include <linux/uwb/qmrom_spi.h>
#include "qm35.h"
#include "debug.h"
#include "hsspi_test.h"
//#if IS_ENABLED(CONFIG_QM35_SPI_DEBUG_FW)
extern void debug_rom_code_init(struct debug *debug);
//#endif
static const struct file_operations debug_enable_fops;
static const struct file_operations debug_log_level_fops;
static const struct file_operations debug_test_hsspi_sleep_fops;
static void *priv_from_file(const struct file *filp)
{
return filp->f_path.dentry->d_inode->i_private;
}
static ssize_t debug_enable_write(struct file *filp, const char __user *buff,
size_t count, loff_t *off)
{
struct debug *debug;
u8 enabled;
debug = priv_from_file(filp);
if (kstrtou8_from_user(buff, count, 10, &enabled))
return -EFAULT;
if (debug->trace_ops)
debug->trace_ops->enable_set(debug, enabled == 1 ? 1 : 0);
else
return -ENOSYS;
return count;
}
static ssize_t debug_enable_read(struct file *filp, char __user *buff,
size_t count, loff_t *off)
{
char enabled[2];
struct debug *debug;
debug = priv_from_file(filp);
if (debug->trace_ops)
enabled[0] = debug->trace_ops->enable_get(debug) + '0';
else
return -ENOSYS;
enabled[1] = '\n';
return simple_read_from_buffer(buff, count, off, enabled,
sizeof(enabled));
}
static ssize_t debug_log_level_write(struct file *filp, const char __user *buff,
size_t count, loff_t *off)
{
u8 log_level = 0;
struct log_module *log_module;
log_module = priv_from_file(filp);
if (kstrtou8_from_user(buff, count, 10, &log_level))
return -EFAULT;
if (log_module->debug->trace_ops)
log_module->debug->trace_ops->level_set(log_module->debug,
log_module, log_level);
else
return -ENOSYS;
return count;
}
static ssize_t debug_log_level_read(struct file *filp, char __user *buff,
size_t count, loff_t *off)
{
char log_level[2];
struct log_module *log_module;
log_module = priv_from_file(filp);
if (log_module->debug->trace_ops)
log_level[0] = log_module->debug->trace_ops->level_get(
log_module->debug, log_module) +
'0';
else
return -ENOSYS;
log_level[1] = '\n';
return simple_read_from_buffer(buff, count, off, log_level,
sizeof(log_level));
}
static ssize_t debug_test_hsspi_sleep_write(struct file *filp,
const char __user *buff,
size_t count, loff_t *off)
{
int sleep_inter_frame_ms;
if (kstrtoint_from_user(buff, count, 10, &sleep_inter_frame_ms))
return -EFAULT;
hsspi_test_set_inter_frame_ms(sleep_inter_frame_ms);
return count;
}
static ssize_t debug_traces_read(struct file *filp, char __user *buff,
size_t count, loff_t *off)
{
char *entry;
rb_entry_size_t entry_size;
//struct qm35_ctx *qm35_hdl;
uint16_t ret;
struct debug *debug;
debug = priv_from_file(filp);
//qm35_hdl = container_of(debug, struct qm35_ctx, debug);
if (!debug->trace_ops)
return -ENOSYS;
entry_size = debug->trace_ops->trace_get_next_size(debug);
if (!entry_size) {
if (filp->f_flags & O_NONBLOCK)
return 0;
ret = wait_event_interruptible(
debug->wq,
(entry_size =
debug->trace_ops->trace_get_next_size(debug)));
if (ret)
return ret;
}
if (entry_size > count)
return -EMSGSIZE;
entry = debug->trace_ops->trace_get_next(debug, &entry_size);
if (!entry)
return 0;
ret = copy_to_user(buff, entry, entry_size);
kfree(entry);
return ret ? -EFAULT : entry_size;
}
static __poll_t debug_traces_poll(struct file *filp,
struct poll_table_struct *wait)
{
struct debug *debug;
__poll_t mask = 0;
debug = priv_from_file(filp);
poll_wait(filp, &debug->wq, wait);
if (debug->trace_ops && debug->trace_ops->trace_next_avail(debug))
mask |= POLLIN;
return mask;
}
static int debug_traces_open(struct inode *inodep, struct file *filep)
{
struct debug *debug;
debug = priv_from_file(filep);
mutex_lock(&debug->pv_filp_lock);
if (debug->pv_filp) {
mutex_unlock(&debug->pv_filp_lock);
return -EBUSY;
}
debug->pv_filp = filep;
if (debug->trace_ops)
debug->trace_ops->trace_reset(debug);
mutex_unlock(&debug->pv_filp_lock);
return 0;
}
static int debug_traces_release(struct inode *inodep, struct file *filep)
{
struct debug *debug;
debug = priv_from_file(filep);
mutex_lock(&debug->pv_filp_lock);
debug->pv_filp = NULL;
mutex_unlock(&debug->pv_filp_lock);
return 0;
}
static ssize_t debug_coredump_read(struct file *filep, char __user *buff,
size_t count, loff_t *off)
{
//struct qm35_ctx *qm35_hdl;
struct debug *debug;
char *cd;
size_t cd_len = 0;
debug = priv_from_file(filep);
//qm35_hdl = container_of(debug, struct qm35_ctx, debug);
if (!debug->coredump_ops)
return -ENOSYS;
cd = debug->coredump_ops->coredump_get(debug, &cd_len);
return simple_read_from_buffer(buff, count, off, cd, cd_len);
}
static ssize_t debug_coredump_write(struct file *filp, const char __user *buff,
size_t count, loff_t *off)
{
struct debug *debug;
u8 force;
debug = priv_from_file(filp);
if (kstrtou8_from_user(buff, count, 10, &force))
return -EFAULT;
if (debug->coredump_ops && force != 0)
debug->coredump_ops->coredump_force(debug);
else if (force == 0)
pr_warn("qm35: write non null value to force coredump\n");
else
return -ENOSYS;
return count;
}
static ssize_t debug_hw_reset_write(struct file *filp, const char __user *buff,
size_t count, loff_t *off)
{
struct qm35_ctx *qm35_hdl;
struct debug *debug;
int ret;
u8 reset;
debug = priv_from_file(filp);
qm35_hdl = container_of(debug, struct qm35_ctx, debug);
if (kstrtou8_from_user(buff, count, 10, &reset))
return -EFAULT;
ret = -1;
if (reset != 0) {
pr_info("qm35: resetting chip...\n");
ret = qm35_reset_sync(qm35_hdl);
} else
pr_warn("qm35: write non null value to force a hw reset\n");
if (ret)
return -ENOSYS;
return count;
}
static const struct file_operations debug_enable_fops = {
.owner = THIS_MODULE,
.write = debug_enable_write,
.read = debug_enable_read,
};
static const struct file_operations debug_log_level_fops = {
.owner = THIS_MODULE,
.write = debug_log_level_write,
.read = debug_log_level_read
};
static const struct file_operations debug_test_hsspi_sleep_fops = {
.owner = THIS_MODULE,
.write = debug_test_hsspi_sleep_write
};
static const struct file_operations debug_traces_fops = {
.owner = THIS_MODULE,
.open = debug_traces_open,
.release = debug_traces_release,
.read = debug_traces_read,
.poll = debug_traces_poll,
.llseek = no_llseek,
};
static const struct file_operations debug_coredump_fops = {
.owner = THIS_MODULE,
.read = debug_coredump_read,
.write = debug_coredump_write,
};
static const struct file_operations debug_hw_reset_fops = {
.owner = THIS_MODULE,
.write = debug_hw_reset_write,
};
int debug_create_module_entry(struct debug *debug,
struct log_module *log_module)
{
struct dentry *dir;
struct dentry *file;
dir = debugfs_create_dir(log_module->name, debug->fw_dir);
if (!dir) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/%s\n",
log_module->name);
return -1;
}
file = debugfs_create_file("log_level", 0644, dir, log_module,
&debug_log_level_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/%s/log_level\n",
log_module->name);
return -1;
}
pr_info("qm35 debug: created /sys/kernel/debug/uwb0/%s/log_level\n",
log_module->name);
return 0;
}
void debug_new_trace_available(struct debug *debug)
{
if (debug->pv_filp)
fsnotify_modify(debug->pv_filp);
wake_up_interruptible(&debug->wq);
}
static int debug_devid_show(struct seq_file *s, void *unused)
{
struct debug *debug = (struct debug *)s->private;
uint16_t dev_id;
int rc;
if (debug->trace_ops && debug->trace_ops->get_dev_id) {
rc = debug->trace_ops->get_dev_id(debug, &dev_id);
if (rc < 0)
return -EIO;
seq_printf(s, "deca%04x\n", dev_id);
}
return 0;
}
static int debug_socid_show(struct seq_file *s, void *unused)
{
struct debug *debug = (struct debug *)s->private;
uint8_t soc_id[QM357XX_ROM_SOC_ID_LEN];
int rc;
if (debug->trace_ops && debug->trace_ops->get_soc_id) {
rc = debug->trace_ops->get_soc_id(debug, soc_id);
if (rc < 0)
return -EIO;
seq_printf(s, "%*phN\n", QM357XX_ROM_SOC_ID_LEN, soc_id);
}
return 0;
}
DEFINE_SHOW_ATTRIBUTE(debug_devid);
DEFINE_SHOW_ATTRIBUTE(debug_socid);
void debug_soc_info_available(struct debug *debug)
{
struct dentry *file;
file = debugfs_create_file("dev_id", 0444, debug->chip_dir, debug,
&debug_devid_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/dev_id\n");
goto unregister;
}
file = debugfs_create_file("soc_id", 0444, debug->chip_dir, debug,
&debug_socid_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/soc_id\n");
goto unregister;
}
return;
unregister:
debugfs_remove_recursive(debug->chip_dir);
}
int debug_init_root(struct debug *debug, struct dentry *root)
{
debug->root_dir = debugfs_create_dir("uwb0", root);
if (!debug->root_dir) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0\n");
return -1;
}
return 0;
}
int debug_init(struct debug *debug)
{
struct dentry *file;
init_waitqueue_head(&debug->wq);
mutex_init(&debug->pv_filp_lock);
debug->pv_filp = NULL;
debug->fw_dir = debugfs_create_dir("fw", debug->root_dir);
if (!debug->fw_dir) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw\n");
goto unregister;
}
debug->chip_dir = debugfs_create_dir("chip", debug->root_dir);
if (!debug->chip_dir) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/chip\n");
goto unregister;
}
file = debugfs_create_file("hw_reset", 0444, debug->chip_dir, debug,
&debug_hw_reset_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/chip/hw_reset\n");
goto unregister;
}
file = debugfs_create_file("enable", 0644, debug->fw_dir, debug,
&debug_enable_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/enable\n");
goto unregister;
}
file = debugfs_create_file("traces", 0444, debug->fw_dir, debug,
&debug_traces_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/traces\n");
goto unregister;
}
file = debugfs_create_file("coredump", 0444, debug->fw_dir, debug,
&debug_coredump_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/coredump\n");
goto unregister;
}
file = debugfs_create_file("test_sleep_hsspi_ms", 0200, debug->fw_dir,
debug, &debug_test_hsspi_sleep_fops);
if (!file) {
pr_err("qm35: failed to create /sys/kernel/debug/uwb0/fw/test_sleep_hsspi\n");
goto unregister;
}
//#if IS_ENABLED(CONFIG_QM35_SPI_DEBUG_FW)
debug_rom_code_init(debug);
//#endif
return 0;
unregister:
debug_deinit(debug);
return -1;
}
void debug_deinit(struct debug *debug)
{
wake_up_interruptible(&debug->wq);
debugfs_remove_recursive(debug->root_dir);
}

90
drivers/uwb/qm35/debug.h Normal file
View file

@ -0,0 +1,90 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2022 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 LOG layer HSSPI Protocol
*/
#ifndef __DEBUG_H__
#define __DEBUG_H__
#include <linux/fs.h>
#include <linux/mutex.h>
#include "qm35_rb.h"
struct debug;
struct log_module;
struct debug_trace_ops {
void (*enable_set)(struct debug *dbg, int enable);
int (*enable_get)(struct debug *dbg);
void (*level_set)(struct debug *dbg, struct log_module *log_module,
int lvl);
int (*level_get)(struct debug *dbg, struct log_module *log_module);
char *(*trace_get_next)(struct debug *dbg, rb_entry_size_t *len);
rb_entry_size_t (*trace_get_next_size)(struct debug *dbg);
bool (*trace_next_avail)(struct debug *dbg);
void (*trace_reset)(struct debug *dbg);
int (*get_dev_id)(struct debug *dbg, uint16_t *dev_id);
int (*get_soc_id)(struct debug *dbg, uint8_t *soc_id);
};
struct debug_coredump_ops {
char *(*coredump_get)(struct debug *dbg, size_t *len);
int (*coredump_force)(struct debug *dbg);
};
struct debug {
struct dentry *root_dir;
struct dentry *fw_dir;
struct dentry *chip_dir;
const struct debug_trace_ops *trace_ops;
const struct debug_coredump_ops *coredump_ops;
struct wait_queue_head wq;
struct file *pv_filp;
struct mutex pv_filp_lock;
struct firmware *certificate;
};
// TODO move this from here to a commom place for both log layer and debug
struct log_module {
uint8_t id;
uint8_t lvl;
char name[64];
struct completion *read_done;
struct debug *debug;
};
int debug_init_root(struct debug *debug, struct dentry *root);
int debug_init(struct debug *debug);
void debug_deinit(struct debug *debug);
int debug_create_module_entry(struct debug *debug,
struct log_module *log_module);
void debug_new_trace_available(struct debug *debug);
void debug_soc_info_available(struct debug *debug);
#endif // __DEBUG_H__

View file

@ -0,0 +1,462 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2022 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 LOG layer HSSPI Protocol
*/
#include <linux/version.h>
#include <linux/printk.h>
#include <linux/debugfs.h>
#include <linux/fs.h>
#include <linux/fsnotify.h>
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
#include <linux/kernel_read_file.h>
#endif
#include <linux/poll.h>
#include <linux/vmalloc.h>
#include <linux/uwb/qmrom.h>
#include <linux/uwb/qmrom_log.h>
#include <linux/uwb/qmrom_spi.h>
#include <linux/uwb/qmrom_utils.h>
#include <linux/uwb/fwupdater.h>
#include "qm35.h"
#include "debug.h"
#define FWUPDATER_SPI_SPEED_HZ 20000000
extern int fu_spi_speed_hz;
extern int qmrom_spi_speed_hz;
#define QMROM_RETRIES 10
static void *priv_from_file(const struct file *filp)
{
return filp->f_path.dentry->d_inode->i_private;
}
static struct qm35_ctx *rom_test_prepare(struct file *filp)
{
struct debug *debug = priv_from_file(filp);
struct qm35_ctx *qm35_hdl = container_of(debug, struct qm35_ctx, debug);
qm35_hsspi_stop(qm35_hdl);
qmrom_set_log_device(&qm35_hdl->spi->dev, LOG_DBG);
return qm35_hdl;
}
static void rom_test_unprepare(struct qm35_ctx *qm35_hdl)
{
qmrom_set_log_device(&qm35_hdl->spi->dev, LOG_WARN);
qm35_hsspi_start(qm35_hdl);
}
static struct firmware *file2firmware(const char *filename)
{
struct firmware *firmware =
kmalloc(sizeof(struct firmware), GFP_KERNEL | __GFP_ZERO);
if (!firmware) {
goto fail;
}
#if (LINUX_VERSION_CODE >= KERNEL_VERSION(5, 10, 0))
{
ssize_t bytes_read = kernel_read_file_from_path(
filename, 0, (void **)&firmware->data, INT_MAX,
&firmware->size, READING_FIRMWARE);
if (bytes_read < 0) {
pr_err("kernel_read_file_from_path(%s) returned %d\n",
filename, (int)bytes_read);
goto fail;
}
if (bytes_read != firmware->size) {
pr_err("kernel_read_file_from_path returned %zu; expected %zu\n",
bytes_read, firmware->size);
goto fail;
}
}
#else
{
loff_t size = 0;
int ret = kernel_read_file_from_path(filename,
(void **)&firmware->data,
&size, INT_MAX,
READING_FIRMWARE);
if (ret < 0) {
pr_err("kernel_read_file_from_path(%s) returned %d\n",
filename, ret);
goto fail;
}
firmware->size = (size_t)size;
}
#endif
print_hex_dump(KERN_DEBUG, "Bin file:", DUMP_PREFIX_ADDRESS, 16, 1,
firmware->data, 16, false);
return firmware;
fail:
if (firmware) {
if (firmware->data)
vfree(firmware->data);
kfree(firmware);
}
return NULL;
}
static ssize_t rom_probe(struct file *filp, const char __user *buff,
size_t count, loff_t *off)
{
struct qm35_ctx *qm35_hdl = rom_test_prepare(filp);
struct qmrom_handle *h;
pr_info("Starting the probe test...\n");
h = qmrom_init(&qm35_hdl->spi->dev, qm35_hdl, qm35_hdl->gpio_ss_rdy,
qmrom_spi_speed_hz, QMROM_RETRIES,
qmrom_spi_reset_device, DEVICE_GEN_QM357XX);
if (!h) {
pr_err("qmrom_init failed\n");
goto end;
}
pr_info("chip_revision %#2x\n", h->chip_rev);
pr_info("device version %#02x\n", h->device_version);
if (h->chip_rev != 0xa001) {
pr_info("lcs_state %u\n", h->qm357xx_soc_info.lcs_state);
print_hex_dump(KERN_DEBUG, "soc_id:", DUMP_PREFIX_NONE, 16, 1,
h->qm357xx_soc_info.soc_id,
sizeof(h->qm357xx_soc_info.soc_id), false);
print_hex_dump(KERN_DEBUG, "uuid:", DUMP_PREFIX_NONE, 16, 1,
h->qm357xx_soc_info.uuid,
sizeof(h->qm357xx_soc_info.uuid), false);
}
qmrom_deinit(h);
qmrom_spi_reset_device(qm35_hdl);
qmrom_msleep(100);
end:
rom_test_unprepare(qm35_hdl);
return count;
}
static ssize_t rom_flash_dbg_cert(struct file *filp, const char __user *buff,
size_t count, loff_t *off)
{
struct qm35_ctx *qm35_hdl = rom_test_prepare(filp);
struct firmware *certificate = NULL;
struct qmrom_handle *h = NULL;
char *filename;
int err;
filename = kmalloc(count, GFP_KERNEL);
if (!filename) {
count = -ENOMEM;
goto end;
}
err = copy_from_user(filename, buff, count);
if (err) {
pr_err("copy_from_user failed with error %d\n", err);
count = err;
goto end;
}
filename[count - 1] = '\0';
certificate = file2firmware(filename);
if (!certificate || certificate->size != DEBUG_CERTIFICATE_SIZE) {
pr_err("%s: file retrieval failed, abort (%s)\n", __func__,
certificate ? "wrong size" : "not found");
count = -1;
goto end;
}
/* Flash the debug certificate */
pr_info("Flashing debug certificate %s...\n", filename);
h = qmrom_init(&qm35_hdl->spi->dev, qm35_hdl, qm35_hdl->gpio_ss_rdy,
qmrom_spi_speed_hz, QMROM_RETRIES,
qmrom_spi_reset_device, DEVICE_GEN_QM357XX);
if (!h) {
pr_err("qmrom_init failed\n");
goto end;
}
err = qm357xx_rom_flash_dbg_cert(h, certificate);
if (err)
pr_err("Flashing debug certificate %s failed with %d!\n",
filename, err);
else
pr_info("Flashing debug certificate %s succeeded!\n", filename);
end:
if (h)
qmrom_deinit(h);
if (certificate) {
if (certificate->data)
vfree(certificate->data);
kfree(certificate);
}
qmrom_spi_reset_device(qm35_hdl);
qmrom_msleep(100);
rom_test_unprepare(qm35_hdl);
return count;
}
static ssize_t rom_erase_dbg_cert(struct file *filp, const char __user *buff,
size_t count, loff_t *off)
{
struct qm35_ctx *qm35_hdl = rom_test_prepare(filp);
struct qmrom_handle *h = NULL;
int err;
pr_info("Erasing debug certificate...\n");
h = qmrom_init(&qm35_hdl->spi->dev, qm35_hdl, qm35_hdl->gpio_ss_rdy,
qmrom_spi_speed_hz, QMROM_RETRIES,
qmrom_spi_reset_device, DEVICE_GEN_QM357XX);
if (!h) {
pr_err("qmrom_init failed\n");
goto end;
}
err = qm357xx_rom_erase_dbg_cert(h);
if (err)
pr_err("Erasing debug certificate failed with %d!\n", err);
else
pr_info("Erasing debug certificate succeeded!\n");
end:
if (h)
qmrom_deinit(h);
qmrom_spi_reset_device(qm35_hdl);
qmrom_msleep(100);
rom_test_unprepare(qm35_hdl);
return count;
}
static ssize_t rom_flash_fw(struct file *filp, const char __user *buff,
size_t count, loff_t *off)
{
struct qm35_ctx *qm35_hdl = rom_test_prepare(filp);
struct firmware *fw = NULL;
struct qmrom_handle *h = NULL;
char *filename;
int rc;
filename = kmalloc(count, GFP_KERNEL);
if (!filename) {
count = -ENOMEM;
goto end;
}
rc = copy_from_user(filename, buff, count);
if (rc) {
pr_err("copy_from_user failed with error %d\n", rc);
goto end;
}
filename[count - 1] = '\0';
fw = file2firmware(filename);
if (!fw) {
pr_err("%s: file %s retrieval failed, abort\n", __func__,
filename);
rc = -1;
goto end;
}
pr_info("Flashing image %s (%pK->data %pK)...\n", filename, fw,
fw->data);
h = qmrom_init(&qm35_hdl->spi->dev, qm35_hdl, qm35_hdl->gpio_ss_rdy,
qmrom_spi_speed_hz, QMROM_RETRIES,
qmrom_spi_reset_device, DEVICE_GEN_QM357XX);
if (!h) {
pr_err("qmrom_init failed\n");
rc = -1;
goto end;
}
rc = qm357xx_rom_flash_fw(h, fw);
if (rc)
pr_err("Flashing firmware %s failed with %d!\n", filename, rc);
else
pr_info("Flashing firmware %s succeeded!\n", filename);
end:
kfree(filename);
if (h)
qmrom_deinit(h);
if (fw) {
if (fw->data)
vfree(fw->data);
kfree(fw);
}
rom_test_unprepare(qm35_hdl);
return count;
}
static ssize_t rom_flash_fw_pkg(struct file *filp, const char __user *buff,
size_t count, loff_t *off)
{
struct qm35_ctx *qm35_hdl = rom_test_prepare(filp);
const struct firmware *fw = NULL;
struct qmrom_handle *h = NULL;
char *filename = NULL, *fw_data;
uint32_t fw_size;
int rc;
if (fu_spi_speed_hz == 0)
fu_spi_speed_hz = FWUPDATER_SPI_SPEED_HZ;
filename = kmalloc(count, GFP_KERNEL);
if (!filename) {
count = -ENOMEM;
goto end;
}
rc = copy_from_user(filename, buff, count);
if (rc) {
pr_err("copy_from_user failed with error %d\n", rc);
goto end;
}
filename[count - 1] = '\0';
fw = file2firmware(filename);
if (!fw || !fw->data) {
pr_err("%s: file %s retrieval failed (%s), abort\n", __func__,
filename, fw ? "no data read" : "not found");
rc = -1;
goto end;
}
pr_info("Flashing fw_updater...\n");
h = qmrom_init(&qm35_hdl->spi->dev, qm35_hdl, qm35_hdl->gpio_ss_rdy,
qmrom_spi_speed_hz, QMROM_RETRIES,
qmrom_spi_reset_device, DEVICE_GEN_QM357XX);
if (!h) {
pr_err("qmrom_init failed\n");
rc = -1;
goto end;
}
rc = qm357xx_rom_flash_fw(h, fw);
if (rc) {
pr_err("Flashing fw_updater failed with %d!\n", rc);
goto end;
}
pr_info("Flashing fw_updater succeeded, flashing the fw package now...\n");
qmrom_spi_set_freq(fu_spi_speed_hz);
rc = qm357xx_rom_fw_macro_pkg_get_fw_idx(fw, 1, &fw_size, &fw_data);
if (rc) {
pr_err("%s: FW MACRO PACKAGE corupted = %d\n", __func__, rc);
goto end;
}
if (*(uint32_t *)fw_data == CRYPTO_FIRMWARE_PACK_MAGIC_VALUE) {
run_fwupdater(h, fw_data, fw_size);
} else {
rc = -EINVAL;
pr_err("FW PACKAGE not found - %04x! fw_size = %d\n",
*(uint32_t *)fw_data, fw_size);
goto end;
}
pr_info("FW package flashing done, rebooting the QM...\n");
end:
qmrom_spi_reset_device(qm35_hdl);
if (filename)
kfree(filename);
if (h)
qmrom_deinit(h);
if (fw) {
if (fw->data)
vfree(fw->data);
kfree(fw);
}
rom_test_unprepare(qm35_hdl);
return count;
}
static const struct file_operations rom_probe_fops = { .owner = THIS_MODULE,
.write = rom_probe };
static const struct file_operations rom_flash_dbg_cert_fops = {
.owner = THIS_MODULE,
.write = rom_flash_dbg_cert
};
static const struct file_operations rom_erase_dbg_cert_fops = {
.owner = THIS_MODULE,
.write = rom_erase_dbg_cert
};
static const struct file_operations rom_flash_fw_fops = {
.owner = THIS_MODULE,
.write = rom_flash_fw
};
static const struct file_operations rom_flash_fw_pkg_fops = {
.owner = THIS_MODULE,
.write = rom_flash_fw_pkg
};
void debug_rom_code_init(struct debug *debug)
{
struct dentry *file;
file = debugfs_create_file("rom_probe", 0200, debug->fw_dir, debug,
&rom_probe_fops);
if (!file) {
pr_err("qm35: failed to create uwb0/fw/rom_probe\n");
return;
}
file = debugfs_create_file("rom_flash_dbg_cert", 0200, debug->fw_dir,
debug, &rom_flash_dbg_cert_fops);
if (!file) {
pr_err("qm35: failed to create uwb0/fw/rom_flash_dbg_cert\n");
return;
}
file = debugfs_create_file("rom_erase_dbg_cert", 0200, debug->fw_dir,
debug, &rom_erase_dbg_cert_fops);
if (!file) {
pr_err("qm35: failed to create uwb0/fw/rom_erase_dbg_cert\n");
return;
}
file = debugfs_create_file("rom_flash_fw", 0200, debug->fw_dir, debug,
&rom_flash_fw_fops);
if (!file) {
pr_err("qm35: failed to create uwb0/fw/rom_flash_fw\n");
return;
}
file = debugfs_create_file("rom_flash_fw_pkg", 0200, debug->fw_dir,
debug, &rom_flash_fw_pkg_fops);
if (!file) {
pr_err("qm35: failed to create uwb0/fw/rom_flash_fw_pkg\n");
return;
}
}

744
drivers/uwb/qm35/hsspi.c Normal file
View file

@ -0,0 +1,744 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2021 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 HSSPI Protocol
*/
#include <linux/kernel.h>
#include <linux/delay.h>
#include "qm35-trace.h"
#include "hsspi.h"
/* STC HOST flags */
#define STC_HOST_WR BIT(7)
#define STC_HOST_PRD BIT(6)
#define STC_HOST_RD BIT(5)
/* STC SOC flags */
#define STC_SOC_ODW BIT(7)
#define STC_SOC_OA BIT(6)
#define STC_SOC_RDY BIT(5)
#define STC_SOC_ERR BIT(4)
#define SS_READY_TIMEOUT_MS (250)
#define MAX_SUCCESSIVE_ERRORS (5)
#define SPI_CS_SETUP_DELAY_US (5)
#ifdef HSSPI_MANUAL_CS_SETUP
#define HSSPI_MANUAL_CS_SETUP_US SPI_CS_SETUP_DELAY_US
#endif
extern int spi_speed_hz;
struct hsspi_work {
struct list_head list;
enum hsspi_work_type type;
union {
struct {
struct hsspi_block *blk;
struct hsspi_layer *layer;
} tx;
struct completion *completion;
};
};
int test_sleep_after_ss_ready_us = 0;
static inline bool layer_id_is_valid(struct hsspi *hsspi, u8 ul)
{
return (ul < ARRAY_SIZE(hsspi->layers));
}
/**
* get_work() - get a work from the list
*
* @hsspi: &struct hsspi
*
* Return: a &struct hsspi_work
* The work can be:
* - a TX work enqueued by hsspi_send
* - a COMPLETION work used for synchronization (always allocated on stack)
*/
static struct hsspi_work *get_work(struct hsspi *hsspi)
{
struct hsspi_work *hw;
spin_lock(&hsspi->lock);
hw = list_first_entry_or_null(&hsspi->work_list, struct hsspi_work,
list);
if (hw)
list_del(&hw->list);
spin_unlock(&hsspi->lock);
trace_hsspi_get_work(&hsspi->spi->dev, hw ? hw->type : -1);
return hw;
}
/**
* is_txrx_waiting() - is there something to do
*
* @hsspi: &struct hsspi
*
* Return: True if there is a TX work available or if the SS_IRQ flag
* is set. False otherwise.
*/
static bool is_txrx_waiting(struct hsspi *hsspi)
{
enum hsspi_state state;
bool is_empty;
spin_lock(&hsspi->lock);
is_empty = list_empty(&hsspi->work_list);
state = hsspi->state;
spin_unlock(&hsspi->lock);
trace_hsspi_is_txrx_waiting(&hsspi->spi->dev, is_empty, state);
/*
* There is no work in the list but we must check SS_IRQ to
* know if we need to make an empty TX transfer with PRE_READ
* flag set.
*/
return !is_empty || ((state == HSSPI_RUNNING) &&
test_bit(HSSPI_FLAGS_SS_IRQ, hsspi->flags));
}
/**
* hsspi_wait_ss_ready() - waits for ss_ready to be up
*
* @hsspi: &struct hsspi
*
* Return: 0 if ss_ready is up and EAGAIN if not.
*/
static int hsspi_wait_ss_ready(struct hsspi *hsspi)
{
int ret;
if (!test_bit(HSSPI_FLAGS_SS_BUSY, hsspi->flags)) {
/* The ss_ready went low, so the fw is not busy anymore,
* if the ss_ready is high, we can proceed, else,
* either the fw went to sleep or crashed, in any case
* we need to wait for it to be ready again.
*/
clear_bit(HSSPI_FLAGS_SS_READY, hsspi->flags);
if (gpiod_get_value(hsspi->gpio_ss_rdy)) {
return 0;
}
}
/* Check if the QM went to sleep and wake it up if it did */
if (!gpiod_get_value(hsspi->gpio_exton)) {
hsspi->wakeup(hsspi);
}
ret = wait_event_interruptible_timeout(
hsspi->wq_ready,
test_and_clear_bit(HSSPI_FLAGS_SS_READY, hsspi->flags),
msecs_to_jiffies(SS_READY_TIMEOUT_MS));
if (ret == 0) {
dev_warn(&hsspi->spi->dev,
"timed out waiting for ss_ready(%d)\n",
test_bit(HSSPI_FLAGS_SS_READY, hsspi->flags));
return -EAGAIN;
}
if (ret < 0) {
dev_err(&hsspi->spi->dev,
"Error %d while waiting for ss_ready\n", ret);
return ret;
}
/* WA: QM35 C0 have a very short (<100ns) ss_ready toggle
* in the ROM code when waking up from S4. If we transfer immediately,
* the ROM code will enter its command mode and we'll end up
* communicating with the ROM code instead of the firmware.
*/
if (!gpiod_get_value(hsspi->gpio_ss_rdy))
return -EAGAIN;
return 0;
}
#ifdef HSSPI_MANUAL_CS_SETUP
int hsspi_set_cs_level(struct spi_device *spi, int level)
{
struct spi_transfer xfer[] = {
{
.cs_change = !level,
.speed_hz = 1000000,
},
};
return spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
}
#endif
/**
* spi_xfer() - Single SPI transfer
*
* @hsspi: &struct hsspi
* @tx: tx payload
* @rx: rx payload
* @length: payload length
*/
static int spi_xfer(struct hsspi *hsspi, const void *tx, void *rx,
size_t length)
{
struct spi_transfer xfers[2] = {
{
.tx_buf = hsspi->host,
.rx_buf = hsspi->soc,
.len = sizeof(*(hsspi->host)),
.speed_hz = spi_speed_hz,
},
{
.tx_buf = tx,
.rx_buf = rx,
.len = length,
.speed_hz = spi_speed_hz,
},
};
int ret, retry = 5;
hsspi->soc->flags = 0;
hsspi->soc->ul = 0;
hsspi->soc->length = 0;
do {
ret = hsspi_wait_ss_ready(hsspi);
if (ret < 0) {
continue;
}
if (test_sleep_after_ss_ready_us > 0)
usleep_range(test_sleep_after_ss_ready_us,
test_sleep_after_ss_ready_us + 1);
hsspi_set_spi_slave_busy(hsspi);
#ifdef HSSPI_MANUAL_CS_SETUP
hsspi_set_cs_level(hsspi->spi, 0);
udelay(HSSPI_MANUAL_CS_SETUP_US);
#endif
ret = spi_sync_transfer(hsspi->spi, xfers, length ? 2 : 1);
trace_hsspi_spi_xfer(&hsspi->spi->dev, hsspi->host, hsspi->soc,
ret);
if (ret) {
dev_err(&hsspi->spi->dev, "spi_sync_transfer: %d\n",
ret);
continue;
}
if (!(hsspi->soc->flags & STC_SOC_RDY) ||
(hsspi->soc->flags & 0x0f)) {
hsspi->wakeup(hsspi);
ret = -EAGAIN;
continue;
}
/* All looks good! */
break;
} while ((ret == -EAGAIN) && (--retry > 0));
if (!(hsspi->soc->flags & STC_SOC_RDY) || (hsspi->soc->flags & 0x0f)) {
dev_err(&hsspi->spi->dev, "FW not ready (flags %#02x)\n",
hsspi->soc->flags);
}
return ret;
}
static bool check_soc_flag(const struct device *dev, const char *func_name,
u8 soc_flags, bool is_tx)
{
u8 expected;
bool res;
expected = is_tx ? 0x0 : STC_SOC_OA;
res = (soc_flags & (STC_SOC_ERR | STC_SOC_OA)) == expected;
if (!res) {
dev_warn(dev, "%s: bad soc flags %#hhx, expected %#hhx\n",
func_name, soc_flags, expected);
}
return res;
}
/**
* hsspi_rx() - request data from the QM35 on the HSSPI
*
* @hsspi: &struct hsspi
* @ul: upper layer id
* @length: length of data requested
*/
static int hsspi_rx(struct hsspi *hsspi, u8 ul, u16 length)
{
struct hsspi_layer *layer;
struct hsspi_block *blk;
int ret;
hsspi->host->flags = STC_HOST_RD;
hsspi->host->ul = ul;
hsspi->host->length = length;
if (layer_id_is_valid(hsspi, ul)) {
spin_lock(&hsspi->lock);
layer = hsspi->layers[ul];
spin_unlock(&hsspi->lock);
} else
layer = NULL;
blk = layer ? layer->ops->get(layer, length) : NULL;
if (blk) {
ret = spi_xfer(hsspi, NULL, blk->data, blk->size);
layer->ops->received(layer, blk, ret);
} else
ret = spi_xfer(hsspi, NULL, NULL, 0);
if (ret)
return ret;
if (!check_soc_flag(&hsspi->spi->dev, __func__, hsspi->soc->flags,
false)) {
ret = -1;
}
if ((hsspi->soc->ul != ul) || (hsspi->soc->length != length)) {
dev_warn(&hsspi->spi->dev,
"%s: received %hhu %hu but expecting %hhu %hu\n",
__func__, hsspi->soc->ul, hsspi->soc->length, ul,
length);
ret = -1;
}
if (!(hsspi->soc->flags & STC_SOC_ODW) &&
test_and_clear_bit(HSSPI_FLAGS_SS_IRQ, hsspi->flags))
hsspi->odw_cleared(hsspi);
return ret;
}
/**
* hsspi_tx() - send a hsspi block to the QM35 on the HSSPI
*
* @hsspi: &struct hsspi
* @layer: &struct hsspi_layer
* @blk: &struct hsspi_block
*
* It also adds PRD flag if SS_IRQ is set. Therefore it will try a RX
* transfer accordingly.
*/
static int hsspi_tx(struct hsspi *hsspi, struct hsspi_layer *layer,
struct hsspi_block *blk)
{
int ret;
hsspi->host->flags = STC_HOST_WR;
hsspi->host->ul = layer->id;
hsspi->host->length = blk->length;
if (test_bit(HSSPI_FLAGS_SS_IRQ, hsspi->flags))
hsspi->host->flags |= STC_HOST_PRD;
ret = spi_xfer(hsspi, blk->data, NULL, blk->size);
layer->ops->sent(layer, blk, ret);
if (ret)
return ret;
/* Ignore tx check flags */
check_soc_flag(&hsspi->spi->dev, __func__, hsspi->soc->flags, true);
if (hsspi->host->flags & STC_HOST_PRD)
return hsspi_rx(hsspi, hsspi->soc->ul, hsspi->soc->length);
return ret;
}
/**
* hsspi_pre_read() - send a PRE_READ with no TX and do RX
*
* @hsspi: &struct hsspi
*
* This function is a particular case of hsspi_tx with no layer or
* blk.
*
*/
static int hsspi_pre_read(struct hsspi *hsspi)
{
int ret;
hsspi->host->flags = STC_HOST_PRD;
hsspi->host->ul = 0;
hsspi->host->length = 0;
ret = spi_xfer(hsspi, NULL, NULL, 0);
if (ret)
return ret;
/* Ignore pre-read check flags */
check_soc_flag(&hsspi->spi->dev, __func__, hsspi->soc->flags, true);
return hsspi_rx(hsspi, hsspi->soc->ul, hsspi->soc->length);
}
/**
* hsspi_thread_fn() - the thread that manage all SPI transfers
* @data: the &struct hsspi
*
*/
static int hsspi_thread_fn(void *data)
{
struct hsspi *hsspi = data;
static int successive_errors;
successive_errors = 0;
while (1) {
struct hsspi_work *hw;
int ret;
ret = wait_event_interruptible(hsspi->wq,
is_txrx_waiting(hsspi) ||
kthread_should_stop());
if (ret)
return ret;
if (kthread_should_stop())
break;
hw = get_work(hsspi);
if (hw) {
if (hw->type == HSSPI_WORK_COMPLETION) {
complete(hw->completion);
/* on the stack no need to free */
continue;
} else if (hw->type == HSSPI_WORK_TX) {
ret = hsspi_tx(hsspi, hw->tx.layer, hw->tx.blk);
kfree(hw);
} else {
dev_err(&hsspi->spi->dev,
"unknown hsspi_work type: %d\n",
hw->type);
continue;
}
} else
/* If there is no work, we are here because
* SS_IRQ is set.
*/
ret = hsspi_pre_read(hsspi);
if (ret) {
successive_errors++;
if (successive_errors > MAX_SUCCESSIVE_ERRORS) {
dev_err(&hsspi->spi->dev,
"Max successive errors %d reached, likely entered ROM code...\n",
successive_errors);
/* When the device reboots, the ROM code might raise
* ss_ready; if a SPI transfer is requested, the AP
* will initiate the SPI xfer and the ROM code will
* enter its command mode infinite loop...
* No choice but rebooting the device.
*/
hsspi->reset_qm35(hsspi);
successive_errors = 0;
}
} else {
successive_errors = 0;
}
}
return 0;
}
int hsspi_init(struct hsspi *hsspi, struct spi_device *spi)
{
memset(hsspi, 0, sizeof(*hsspi));
spin_lock_init(&hsspi->lock);
INIT_LIST_HEAD(&hsspi->work_list);
hsspi->state = HSSPI_STOPPED;
hsspi->spi = spi;
init_waitqueue_head(&hsspi->wq);
init_waitqueue_head(&hsspi->wq_ready);
hsspi->host = kmalloc(sizeof(*(hsspi->host)), GFP_KERNEL | GFP_DMA);
hsspi->soc = kmalloc(sizeof(*(hsspi->soc)), GFP_KERNEL | GFP_DMA);
hsspi->thread = kthread_create(hsspi_thread_fn, hsspi, "hsspi");
if (IS_ERR(hsspi->thread))
return PTR_ERR(hsspi->thread);
wake_up_process(hsspi->thread);
dev_info(&hsspi->spi->dev, "HSSPI initialized\n");
return 0;
}
void hsspi_set_gpios(struct hsspi *hsspi, struct gpio_desc *gpio_ss_rdy,
struct gpio_desc *gpio_exton)
{
hsspi->gpio_ss_rdy = gpio_ss_rdy;
hsspi->gpio_exton = gpio_exton;
}
int hsspi_deinit(struct hsspi *hsspi)
{
int i;
spin_lock(&hsspi->lock);
if (hsspi->state == HSSPI_RUNNING) {
spin_unlock(&hsspi->lock);
return -EBUSY;
}
for (i = 0; i < ARRAY_SIZE(hsspi->layers); i++) {
if (!hsspi->layers[i])
continue;
dev_err(&hsspi->spi->dev,
"HSSPI upper layer '%s' not unregistered\n",
hsspi->layers[i]->name);
spin_unlock(&hsspi->lock);
return -EBUSY;
}
spin_unlock(&hsspi->lock);
kthread_stop(hsspi->thread);
kfree(hsspi->host);
kfree(hsspi->soc);
dev_info(&hsspi->spi->dev, "HSSPI uninitialized\n");
return 0;
}
int hsspi_register(struct hsspi *hsspi, struct hsspi_layer *layer)
{
int ret = 0;
if (!layer_id_is_valid(hsspi, layer->id))
return -EINVAL;
spin_lock(&hsspi->lock);
if (hsspi->layers[layer->id])
ret = -EBUSY;
else
hsspi->layers[layer->id] = layer;
spin_unlock(&hsspi->lock);
if (ret) {
dev_err(&hsspi->spi->dev, "%s: '%s' ret: %d\n", __func__,
layer->name, ret);
return ret;
}
dev_dbg(&hsspi->spi->dev, "HSSPI upper layer '%s' registered\n",
layer->name);
return 0;
}
int hsspi_unregister(struct hsspi *hsspi, struct hsspi_layer *layer)
{
DECLARE_COMPLETION_ONSTACK(complete);
struct hsspi_work complete_work = {
.type = HSSPI_WORK_COMPLETION,
.completion = &complete,
};
int ret = 0;
if (!layer_id_is_valid(hsspi, layer->id))
return -EINVAL;
spin_lock(&hsspi->lock);
if (hsspi->layers[layer->id] == layer) {
hsspi->layers[layer->id] = NULL;
list_add_tail(&complete_work.list, &hsspi->work_list);
} else
ret = -EINVAL;
spin_unlock(&hsspi->lock);
if (ret) {
dev_err(&hsspi->spi->dev, "%s: '%s' ret: %d\n", __func__,
layer->name, ret);
return ret;
}
wake_up_interruptible(&hsspi->wq);
/* when completed there is no more reference to layer in the
* work_list or in the hsspi_thread_fn
*/
wait_for_completion(&complete);
dev_dbg(&hsspi->spi->dev, "HSSPI upper layer '%s' unregistered\n",
layer->name);
return 0;
}
void hsspi_clear_spi_slave_busy(struct hsspi *hsspi)
{
clear_bit(HSSPI_FLAGS_SS_BUSY, hsspi->flags);
}
void hsspi_set_spi_slave_busy(struct hsspi *hsspi)
{
set_bit(HSSPI_FLAGS_SS_BUSY, hsspi->flags);
}
void hsspi_set_spi_slave_ready(struct hsspi *hsspi)
{
set_bit(HSSPI_FLAGS_SS_READY, hsspi->flags);
wake_up_interruptible(&hsspi->wq_ready);
}
void hsspi_clear_spi_slave_ready(struct hsspi *hsspi)
{
clear_bit(HSSPI_FLAGS_SS_READY, hsspi->flags);
}
void hsspi_set_output_data_waiting(struct hsspi *hsspi)
{
set_bit(HSSPI_FLAGS_SS_IRQ, hsspi->flags);
wake_up_interruptible(&hsspi->wq);
}
int hsspi_init_block(struct hsspi_block *blk, u16 length)
{
void *data;
data = krealloc(blk->data, length, GFP_KERNEL | GFP_DMA);
if (!data)
return -ENOMEM;
blk->data = data;
blk->length = length;
blk->size = length;
return 0;
}
void hsspi_deinit_block(struct hsspi_block *blk)
{
kfree(blk->data);
blk->data = NULL;
}
int hsspi_send(struct hsspi *hsspi, struct hsspi_layer *layer,
struct hsspi_block *blk)
{
struct hsspi_work *tx_work;
int ret = 0;
if (!layer || !blk)
return -EINVAL;
if (!layer_id_is_valid(hsspi, layer->id))
return -EINVAL;
tx_work = kzalloc(sizeof(*tx_work), GFP_KERNEL);
if (!tx_work)
return -ENOMEM;
tx_work->type = HSSPI_WORK_TX;
tx_work->tx.blk = blk;
tx_work->tx.layer = layer;
spin_lock(&hsspi->lock);
if (hsspi->state == HSSPI_RUNNING) {
if (hsspi->layers[layer->id] == layer)
list_add_tail(&tx_work->list, &hsspi->work_list);
else
ret = -EINVAL;
} else
ret = -EAGAIN;
spin_unlock(&hsspi->lock);
if (ret) {
kfree(tx_work);
dev_err(&hsspi->spi->dev, "%s: %d\n", __func__, ret);
return ret;
}
wake_up_interruptible(&hsspi->wq);
dev_dbg(&hsspi->spi->dev, "send %d bytes on HSSPI '%s' layer\n",
blk->length, layer->name);
return 0;
}
void hsspi_start(struct hsspi *hsspi)
{
spin_lock(&hsspi->lock);
hsspi->state = HSSPI_RUNNING;
spin_unlock(&hsspi->lock);
wake_up_interruptible(&hsspi->wq);
dev_dbg(&hsspi->spi->dev, "HSSPI started\n");
}
void hsspi_stop(struct hsspi *hsspi)
{
DECLARE_COMPLETION_ONSTACK(complete);
struct hsspi_work complete_work = {
.type = HSSPI_WORK_COMPLETION,
.completion = &complete,
};
spin_lock(&hsspi->lock);
hsspi->state = HSSPI_STOPPED;
list_add_tail(&complete_work.list, &hsspi->work_list);
spin_unlock(&hsspi->lock);
wake_up_interruptible(&hsspi->wq);
wait_for_completion(&complete);
dev_dbg(&hsspi->spi->dev, "HSSPI stopped\n");
}

325
drivers/uwb/qm35/hsspi.h Normal file
View file

@ -0,0 +1,325 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2021 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 HSSPI Protocol
*/
#ifndef __HSSPI_H__
#define __HSSPI_H__
#include <linux/gpio.h>
#include <linux/kthread.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/spi/spi.h>
#include <linux/wait.h>
enum { UL_RESERVED,
UL_BOOT_FLASH,
UL_UCI_APP,
UL_COREDUMP,
UL_LOG,
UL_TEST_HSSPI,
UL_MAX_IDX };
struct stc_header {
u8 flags;
u8 ul;
u16 length;
} __packed;
enum hsspi_work_type {
HSSPI_WORK_TX = 0,
HSSPI_WORK_COMPLETION,
};
/**
* struct hsspi_block - Memory block used by the HSSPI.
* @data: pointer to some memory
* @length: requested length of the data
* @size: size of the data (could be greater than length)
*
* This structure represents the memory used by the HSSPI driver for
* sending or receiving message. Upper layer must provides the HSSPI
* driver a way to allocate such structure for reception and uses this
* structure for sending function.
*
* The goal here is to prevent useless copy.
*/
struct hsspi_block {
void *data;
u16 length;
u16 size;
};
struct hsspi_layer;
/**
* struct hsspi_layer_ops - Upper layer operations.
*
* @registered: Called when this upper layer is registered.
* @unregistered: Called when unregistered.
*
* @get: Called when the HSSPI driver need some memory for
* reception. This &struct hsspi_block will be give back to the upper
* layer in the received callback.
*
* @received: Called when the HSSPI driver received some data for this
* upper layer. In case of error, status is used to notify the upper
* layer.
*
* @sent: Called when a &struct hsspi_block is sent by the HSSPI
* driver. In case of error, status is used to notify the upper layer.
*
* Operation needed to be implemented by an upper layer. All ops are
* called by the HSSPI driver and are mandatory.
*/
struct hsspi_layer_ops {
int (*registered)(struct hsspi_layer *upper_layer);
void (*unregistered)(struct hsspi_layer *upper_layer);
struct hsspi_block *(*get)(struct hsspi_layer *upper_layer, u16 length);
void (*received)(struct hsspi_layer *upper_layer,
struct hsspi_block *blk, int status);
void (*sent)(struct hsspi_layer *upper_layer, struct hsspi_block *blk,
int status);
};
/**
* struct hsspi_layer - HSSPI upper layer.
* @name: Name of this upper layer.
* @id: id (ul used in the STC header) of this upper layer
* @ops: &struct hsspi_layer_ops
*
* Basic upper layer structure. Inherit from it to implement a
* concrete upper layer.
*/
struct hsspi_layer {
char *name;
u8 id;
const struct hsspi_layer_ops *ops;
};
enum hsspi_flags {
HSSPI_FLAGS_SS_IRQ = 0,
HSSPI_FLAGS_SS_READY = 1,
HSSPI_FLAGS_SS_BUSY = 2,
HSSPI_FLAGS_MAX = 3,
};
enum hsspi_state {
HSSPI_RUNNING = 0,
HSSPI_ERROR = 1,
HSSPI_STOPPED = 2,
};
/**
* struct hsspi - HSSPI driver.
*
* Some things need to be refine:
* 1. a better way to disable/enable ss_irq or ss_ready GPIOs
* 2. be able to change spi speed on the fly (for flashing purpose)
*
* Actually this structure should be abstract.
*
*/
struct hsspi {
spinlock_t lock; /* protect work_list, layers and state */
struct list_head work_list;
struct hsspi_layer *layers[UL_MAX_IDX];
enum hsspi_state state;
DECLARE_BITMAP(flags, HSSPI_FLAGS_MAX);
struct wait_queue_head wq;
struct wait_queue_head wq_ready;
struct task_struct *thread;
// re-enable SS_IRQ
void (*odw_cleared)(struct hsspi *hsspi);
// wakeup QM35
void (*wakeup)(struct hsspi *hsspi);
// reset QM35
void (*reset_qm35)(struct hsspi *hsspi);
struct spi_device *spi;
struct stc_header *host, *soc;
ktime_t next_cs_active_time;
struct gpio_desc *gpio_ss_rdy;
struct gpio_desc *gpio_exton;
};
/**
* hsspi_init() - Initialiaze the HSSPI
* @hsspi: pointer to a &struct hsspi
* @spi: pointer to the &struct spi_device used by the HSSPI for SPI
* transmission
*
* Initialize the HSSPI structure.
*
* Return: 0 if no error or -errno.
*
*/
int hsspi_init(struct hsspi *hsspi, struct spi_device *spi);
void hsspi_set_gpios(struct hsspi *hsspi, struct gpio_desc *gpio_ss_rdy,
struct gpio_desc *gpio_exton);
/**
* hsspi_deinit() - Initialiaze the HSSPI
* @hsspi: pointer to a &struct hsspi
*
* Deinitialize the HSSPI structure.
*
* Return: 0 if no error or -errno
*/
int hsspi_deinit(struct hsspi *hsspi);
/**
* hsspi_register() - Register an upper layer
* @hsspi: pointer to a &struct hsspi
* @layer: pointer to a &struct hsspi_layer
*
* Register an upper layer.
*
* Return: 0 if no error or -errno.
*
*/
int hsspi_register(struct hsspi *hsspi, struct hsspi_layer *layer);
/**
* hsspi_unregister() - Unregister an upper layer
* @hsspi: pointer to a &struct hsspi
* @layer: pointer to a &struct hsspi_layer
*
* Unregister an upper layer.
*
* Return: 0 if no error or -errno.
*
*/
int hsspi_unregister(struct hsspi *hsspi, struct hsspi_layer *layer);
/**
* hsspi_set_spi_slave_ready() - tell the hsspi that the ss_ready is active
* @hsspi: pointer to a &struct hsspi
*
* This function is called in the ss_ready irq handler. It notices the
* HSSPI driver that the QM is ready for transfer.
*/
void hsspi_set_spi_slave_ready(struct hsspi *hsspi);
/**
* hsspi_clear_spi_slave_ready() - tell the hsspi that the ss_ready has
* been lowered meaning that the fw is busy or asleep,
* @hsspi: pointer to a &struct hsspi
*/
void hsspi_clear_spi_slave_ready(struct hsspi *hsspi);
/**
* hsspi_set_spi_slave_busy() - tell the hsspi that the ss_ready has
* not been lowered and raised again meaning that the fw is busy,
* @hsspi: pointer to a &struct hsspi
*/
void hsspi_set_spi_slave_busy(struct hsspi *hsspi);
/**
* hsspi_clear_spi_slave_busy() - tell the hsspi that the ss_ready has
* been lowered and raised again meaning that the fw is not busy anymore,
* @hsspi: pointer to a &struct hsspi
*
* This function is called in the ss_ready irq handler. It notices the
* HSSPI driver that the QM has acknowledged the last SPI xfer.
*/
void hsspi_clear_spi_slave_busy(struct hsspi *hsspi);
/**
* hsspi_set_output_data_waiting() - tell the hsspi that the ss_irq is active
* @hsspi: pointer to a &struct hsspi
*
* This function is called in the ss_irq irq handler. It notices the
* HSSPI dirver that the QM has some date to outpput.
*
* The HSSPI must work with or without the ss_irq gpio. The current
* implementation is far from ideal regarding this requirement.
*
* W/o the gpio we should send repeatly some 0-length write transfer
* in order to check for ODW flag in SOC STC header.
*/
void hsspi_set_output_data_waiting(struct hsspi *hsspi);
/**
* hsspi_init_block() - allocate a block data that suits HSSPI.
*
* @blk: point to a &struct hsspi_block
* @length: block length
*
* Return: 0 or -ENOMEM on error
*/
int hsspi_init_block(struct hsspi_block *blk, u16 length);
/**
* hsspi_deinit_block() - deallocate a block data.
*
* @blk: point to a &struct hsspi_block
*
*/
void hsspi_deinit_block(struct hsspi_block *blk);
/**
* hsspi_send() - send a &struct hsspi_block for a &struct hsspi_layer
* layer.
*
* @hsspi: pointer to a &struct hsspi
* @layer: pointer to a &struct hsspi_layer
* @blk: pointer to a &struct hsspi_block
*
* Send the block `blk` of the upper layer `layer` on the `hsspi`
* driver.
*
* Return: 0 if no error or -errno.
*
*/
int hsspi_send(struct hsspi *hsspi, struct hsspi_layer *layer,
struct hsspi_block *blk);
/**
* hsspi_start() - start the HSSPI
*
* @hsspi: pointer to a &struct hsspi
*
*/
void hsspi_start(struct hsspi *hsspi);
/**
* hsspi_stop() - stop the HSSPI
*
* @hsspi: pointer to a &struct hsspi
*
*/
void hsspi_stop(struct hsspi *hsspi);
#endif // __HSSPI_H__

View file

@ -0,0 +1,349 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2022 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 COREDUMP layer HSSPI Protocol
*/
#include "qm35.h"
#include "hsspi_coredump.h"
#define COREDUMP_HEADER_NTF 0x00
#define COREDUMP_BODY_NTF 0x01
#define COREDUMP_RCV_STATUS 0x02
#define COREDUMP_FORCE_CMD 0x03
#define COREDUMP_RCV_NACK 0x00
#define COREDUMP_RCV_ACK 0x01
#define COREDUMP_RCV_TIMER_TIMEOUT_S 2
struct __packed coredump_common_hdr {
uint8_t cmd_id;
};
struct __packed coredump_hdr_ntf {
uint32_t size;
uint16_t crc;
};
struct __packed coredump_rcv_status {
uint8_t ack;
};
struct coredump_packet *coredump_packet_alloc(u16 length)
{
struct coredump_packet *p;
p = kzalloc(sizeof(*p), GFP_KERNEL);
if (!p)
return NULL;
if (hsspi_init_block(&p->blk, length)) {
kfree(p);
return NULL;
}
return p;
}
void coredump_packet_free(struct coredump_packet *p)
{
hsspi_deinit_block(&p->blk);
kfree(p);
}
static int coredump_send_rcv_status(struct coredump_layer *layer, uint8_t ack)
{
struct coredump_packet *p;
struct coredump_common_hdr hdr;
struct coredump_rcv_status rcv;
struct qm35_ctx *qm35_hdl;
pr_info("qm35: coredump: sending status %s\n",
layer->coredump_status == COREDUMP_RCV_ACK ? "ACK" : "NACK");
qm35_hdl = container_of(layer, struct qm35_ctx, coredump_layer);
p = coredump_packet_alloc(sizeof(hdr) + sizeof(rcv));
if (!p)
return -ENOMEM;
hdr.cmd_id = COREDUMP_RCV_STATUS;
rcv.ack = ack;
memcpy(p->blk.data, &hdr, sizeof(hdr));
memcpy(p->blk.data + sizeof(hdr), &rcv, sizeof(rcv));
return hsspi_send(&qm35_hdl->hsspi, &qm35_hdl->coredump_layer.hlayer,
&p->blk);
}
static uint16_t coredump_get_checksum(struct coredump_layer *layer)
{
uint32_t idx;
uint16_t crc = 0;
for (idx = 0; idx < layer->coredump_data_wr_idx; idx++) {
uint8_t *val;
val = layer->coredump_data + idx;
crc += *val;
}
return crc;
}
static void corredump_on_expired_timer(struct timer_list *timer)
{
struct coredump_layer *layer =
container_of(timer, struct coredump_layer, timer);
pr_warn("qm35: coredump receive timer expired\n");
coredump_send_rcv_status(layer, layer->coredump_status);
}
static int coredump_registered(struct hsspi_layer *hlayer)
{
return 0;
}
static void coredump_unregistered(struct hsspi_layer *hlayer)
{
;
}
static struct hsspi_block *coredump_get(struct hsspi_layer *hlayer, u16 length)
{
struct coredump_packet *p;
p = coredump_packet_alloc(length);
if (!p)
return NULL;
return &p->blk;
}
static void coredump_header_ntf_received(struct coredump_layer *layer,
struct coredump_hdr_ntf chn)
{
void *data;
pr_info("qm35: coredump: receiving coredump with len: %d and crc: 0x%x\n",
chn.size, chn.crc);
layer->coredump_data_wr_idx = 0;
layer->coredump_size = chn.size;
layer->coredump_crc = chn.crc;
layer->coredump_status = COREDUMP_RCV_NACK;
data = krealloc(layer->coredump_data, layer->coredump_size, GFP_KERNEL);
if (ZERO_OR_NULL_PTR(data)) {
layer->coredump_data = NULL;
layer->coredump_size = 0;
layer->coredump_crc = 0;
pr_err("qm35: failed to allocate coredump mem: %px\n", data);
} else {
layer->coredump_data = data;
}
}
static int coredump_body_ntf_received(struct coredump_layer *layer,
uint8_t *cch_body, uint16_t cch_body_size)
{
if (!layer->coredump_data) {
pr_err("qm35: failed to save coredump, mem not allocated\n");
return 1;
}
if (cch_body_size + layer->coredump_data_wr_idx >
layer->coredump_size) {
pr_err("qm35: failed to save coredump, mem overflow: max size: %d, wr_idx: %d, cd size: %d\n",
layer->coredump_size, layer->coredump_data_wr_idx,
cch_body_size);
return 1;
}
memcpy(layer->coredump_data + layer->coredump_data_wr_idx, cch_body,
cch_body_size);
layer->coredump_data_wr_idx += cch_body_size;
return 0;
}
static void coredump_received(struct hsspi_layer *hlayer,
struct hsspi_block *blk, int status)
{
struct coredump_common_hdr cch;
struct coredump_hdr_ntf chn;
uint8_t *cch_body;
uint16_t cch_body_size;
struct coredump_packet *packet =
container_of(blk, struct coredump_packet, blk);
struct coredump_layer *layer =
container_of(hlayer, struct coredump_layer, hlayer);
if (status)
goto out;
if (blk->length < sizeof(struct coredump_common_hdr)) {
pr_err("qm35: coredump packet header too small: %d bytes\n",
blk->length);
goto out;
}
del_timer_sync(&layer->timer);
memcpy(&cch, blk->data, sizeof(cch));
cch_body = blk->data + sizeof(cch);
cch_body_size = blk->length - sizeof(cch);
switch (cch.cmd_id) {
case COREDUMP_HEADER_NTF:
if (cch_body_size < sizeof(chn)) {
pr_err("qm35: coredump packet header ntf too small: %d bytes\n",
cch_body_size);
break;
}
memcpy(&chn, cch_body, sizeof(chn));
coredump_header_ntf_received(layer, chn);
break;
case COREDUMP_BODY_NTF:
pr_info("qm35: coredump: saving coredump data with len: %d [%d/%d]\n",
cch_body_size,
layer->coredump_data_wr_idx + cch_body_size,
layer->coredump_size);
if (coredump_body_ntf_received(layer, cch_body, cch_body_size))
break;
if (layer->coredump_data_wr_idx == layer->coredump_size) {
uint16_t crc = coredump_get_checksum(layer);
pr_info("qm35: coredump: calculated crc: 0x%x, header crc: 0x%x\n",
crc, layer->coredump_crc);
if (crc == layer->coredump_crc)
layer->coredump_status = COREDUMP_RCV_ACK;
coredump_send_rcv_status(layer, layer->coredump_status);
break;
}
mod_timer(&layer->timer,
jiffies + COREDUMP_RCV_TIMER_TIMEOUT_S * HZ);
break;
default:
pr_err("qm35: coredump: wrong cmd id received: 0x%x\n",
cch.cmd_id);
break;
}
out:
coredump_packet_free(packet);
}
static void coredump_sent(struct hsspi_layer *hlayer, struct hsspi_block *blk,
int status)
{
struct coredump_packet *buf =
container_of(blk, struct coredump_packet, blk);
coredump_packet_free(buf);
}
static const struct hsspi_layer_ops coredump_ops = {
.registered = coredump_registered,
.unregistered = coredump_unregistered,
.get = coredump_get,
.received = coredump_received,
.sent = coredump_sent,
};
char *debug_coredump_get(struct debug *dbg, size_t *len)
{
char *data;
struct qm35_ctx *qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
*len = qm35_hdl->coredump_layer.coredump_data_wr_idx;
data = qm35_hdl->coredump_layer.coredump_data;
return data;
}
int debug_coredump_force(struct debug *dbg)
{
struct coredump_packet *p;
struct coredump_common_hdr hdr = { .cmd_id = COREDUMP_FORCE_CMD };
struct qm35_ctx *qm35_hdl;
pr_info("qm35: force coredump");
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
p = coredump_packet_alloc(sizeof(hdr));
if (!p)
return -ENOMEM;
memcpy(p->blk.data, &hdr, sizeof(hdr));
return hsspi_send(&qm35_hdl->hsspi, &qm35_hdl->coredump_layer.hlayer,
&p->blk);
}
static const struct debug_coredump_ops debug_coredump_ops = {
.coredump_get = debug_coredump_get,
.coredump_force = debug_coredump_force,
};
int coredump_layer_init(struct coredump_layer *layer, struct debug *debug)
{
layer->hlayer.name = "QM35 COREDUMP";
layer->hlayer.id = UL_COREDUMP;
layer->hlayer.ops = &coredump_ops;
layer->coredump_data = NULL;
layer->coredump_data_wr_idx = 0;
layer->coredump_size = 0;
layer->coredump_crc = 0;
layer->coredump_status = 0;
timer_setup(&layer->timer, corredump_on_expired_timer, 0);
debug->coredump_ops = &debug_coredump_ops;
return 0;
}
void coredump_layer_deinit(struct coredump_layer *layer)
{
kfree(layer->coredump_data);
}

View file

@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2022 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 COREDUMP layer HSSPI Protocol
*/
#ifndef __HSSPI_COREDUMP_H__
#define __HSSPI_COREDUMP_H__
#include <linux/mutex.h>
#include <linux/sched.h>
#include "hsspi.h"
#include "debug.h"
struct coredump_packet {
struct hsspi_block blk;
};
struct coredump_layer {
struct hsspi_layer hlayer;
void *coredump_data;
uint32_t coredump_data_wr_idx;
uint32_t coredump_size;
uint16_t coredump_crc;
uint8_t coredump_status;
struct timer_list timer;
};
int coredump_layer_init(struct coredump_layer *coredump, struct debug *debug);
void coredump_layer_deinit(struct coredump_layer *coredump);
#endif // __HSSPI_COREDUMP_H__

View file

@ -0,0 +1,463 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2022 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 LOG layer HSSPI Protocol
*/
#include <linux/uwb/qmrom.h>
#include "qm35.h"
#include "hsspi_log.h"
#define LOG_CID_TRACE_NTF 0x0000
#define LOG_CID_SET_LOG_LVL 0x0001
#define LOG_CID_GET_LOG_LVL 0x0002
#define LOG_CID_GET_LOG_SRC 0x0003
#define TRACE_RB_SIZE 0xFFFFF // 1MB
struct __packed log_packet_hdr {
uint16_t cmd_id;
uint16_t b_size;
};
struct __packed log_level_set_cmd {
struct log_packet_hdr hdr;
uint8_t id;
uint8_t lvl;
};
struct __packed log_level_get_cmd {
struct log_packet_hdr hdr;
uint8_t id;
};
struct log_packet *log_packet_alloc(u16 length)
{
struct log_packet *p;
p = kzalloc(sizeof(*p), GFP_KERNEL);
if (!p)
return NULL;
if (hsspi_init_block(&p->blk, length)) {
kfree(p);
return NULL;
}
return p;
}
void log_packet_free(struct log_packet *p)
{
hsspi_deinit_block(&p->blk);
kfree(p);
}
static struct log_packet *encode_get_log_sources_packet(void)
{
struct log_packet *p;
struct log_packet_hdr msg;
p = log_packet_alloc(sizeof(msg));
if (!p)
return p;
msg.cmd_id = LOG_CID_GET_LOG_SRC;
msg.b_size = 0;
memcpy(p->blk.data, &msg, sizeof(msg));
return p;
}
static struct log_packet *encode_set_log_level_packet(uint8_t id, uint8_t lvl)
{
struct log_packet *p;
struct log_level_set_cmd cmd;
p = log_packet_alloc(sizeof(cmd));
if (!p)
return p;
cmd.hdr.cmd_id = LOG_CID_SET_LOG_LVL;
cmd.hdr.b_size = sizeof(cmd) - sizeof(cmd.hdr);
cmd.id = id;
cmd.lvl = lvl;
memcpy(p->blk.data, &cmd, sizeof(cmd));
return p;
}
static struct log_packet *encode_get_log_level_packet(uint8_t id)
{
struct log_packet *p;
struct log_level_get_cmd cmd;
p = log_packet_alloc(sizeof(cmd));
if (!p)
return p;
cmd.hdr.cmd_id = LOG_CID_GET_LOG_LVL;
cmd.hdr.b_size = sizeof(cmd) - sizeof(cmd.hdr);
cmd.id = id;
memcpy(p->blk.data, &cmd, sizeof(cmd));
return p;
}
static int parse_log_sources_response(struct log_layer *layer, uint8_t *data,
uint16_t data_size)
{
struct qm35_ctx *qm35_hdl;
int idx = 0;
qm35_hdl = container_of(layer, struct qm35_ctx, log_layer);
layer->log_modules_count = *data++;
kfree(layer->log_modules);
layer->log_modules = kcalloc(layer->log_modules_count,
sizeof(struct log_module), GFP_KERNEL);
if (!layer->log_modules)
return 1;
for (idx = 0; idx < layer->log_modules_count; idx++) {
layer->log_modules[idx].debug = &qm35_hdl->debug;
layer->log_modules[idx].id = *data++;
layer->log_modules[idx].lvl = *data++;
strcpy(layer->log_modules[idx].name, (char *)data);
data += (strlen((char *)data) + 1);
debug_create_module_entry(&qm35_hdl->debug,
&layer->log_modules[idx]);
}
return 0;
}
static int parse_get_log_lvl_response(struct log_layer *layer, uint8_t *data,
uint16_t data_size)
{
uint8_t src_id, src_lvl;
int idx = 0;
src_id = *data++;
src_lvl = *data;
//qm35_hdl = container_of(layer, struct qm35_ctx, log_layer);
for (idx = 0; idx < layer->log_modules_count; idx++) {
if (layer->log_modules[idx].id == src_id) {
layer->log_modules[idx].lvl = src_lvl;
if (layer->log_modules[idx].read_done)
complete(layer->log_modules[idx].read_done);
}
}
return 0;
}
static int log_registered(struct hsspi_layer *hlayer)
{
return 0;
}
static void log_unregistered(struct hsspi_layer *hlayer)
{
;
}
static struct hsspi_block *log_get(struct hsspi_layer *hlayer, u16 length)
{
struct log_packet *p;
p = log_packet_alloc(length);
if (!p)
return NULL;
return &p->blk;
}
static void log_received(struct hsspi_layer *hlayer, struct hsspi_block *blk,
int status)
{
struct log_layer *layer;
struct log_packet_hdr hdr;
uint8_t *body;
struct qm35_ctx *qm35_hdl;
struct log_packet *p;
p = container_of(blk, struct log_packet, blk);
if (status)
goto out;
layer = container_of(hlayer, struct log_layer, hlayer);
qm35_hdl = container_of(layer, struct qm35_ctx, log_layer);
if (blk->length < sizeof(struct log_packet_hdr)) {
pr_err("qm35: log packet header too small: %d bytes\n",
blk->length);
goto out;
}
memcpy(&hdr, blk->data, sizeof(struct log_packet_hdr));
body = blk->data + sizeof(struct log_packet_hdr);
if (blk->length < sizeof(struct log_packet_hdr) + hdr.b_size) {
pr_err("qm35: incomplete log packet: %d/%d bytes\n",
blk->length, hdr.b_size);
goto out;
}
if (qm35_hdl->log_qm_traces)
pr_info("qm35_log: %.*s\n", hdr.b_size - 2, body);
switch (hdr.cmd_id) {
case LOG_CID_TRACE_NTF:
rb_push(&layer->rb, body, hdr.b_size);
debug_new_trace_available(&qm35_hdl->debug);
break;
case LOG_CID_SET_LOG_LVL:
break;
case LOG_CID_GET_LOG_LVL:
parse_get_log_lvl_response(layer, body, hdr.b_size);
break;
case LOG_CID_GET_LOG_SRC:
parse_log_sources_response(layer, body, hdr.b_size);
break;
default:
break;
}
out:
log_packet_free(p);
}
static void log_sent(struct hsspi_layer *hlayer, struct hsspi_block *blk,
int status)
{
struct log_packet *p = container_of(blk, struct log_packet, blk);
log_packet_free(p);
}
static const struct hsspi_layer_ops log_ops = {
.registered = log_registered,
.unregistered = log_unregistered,
.get = log_get,
.received = log_received,
.sent = log_sent,
};
static void log_enable_set(struct debug *dbg, int enable)
{
struct qm35_ctx *qm35_hdl;
struct log_packet *p;
int ret;
// TODO: what happens if enable is false?
// if (!enable)
// return;
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
if (qm35_hdl->log_layer.enabled) {
pr_warn("qm35: logging already enabled\n");
return;
}
p = encode_get_log_sources_packet();
if (!p) {
pr_err("failed to encode get log sources packet\n");
return;
}
ret = hsspi_send(&qm35_hdl->hsspi, &qm35_hdl->log_layer.hlayer,
&p->blk);
if (ret) {
pr_err("failed to send spi packet\n");
log_packet_free(p);
} else {
qm35_hdl->log_layer.enabled = enable;
}
}
static int log_enable_get(struct debug *dbg)
{
struct qm35_ctx *qm35_hdl;
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
return qm35_hdl->log_layer.enabled;
}
static void log_level_set(struct debug *dbg, struct log_module *log_module,
int lvl)
{
struct qm35_ctx *qm35_hdl;
struct log_packet *p;
int ret;
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
p = encode_set_log_level_packet(log_module->id, lvl);
if (!p) {
pr_err("failed to encode set log level packet\n");
return;
}
ret = hsspi_send(&qm35_hdl->hsspi, &qm35_hdl->log_layer.hlayer,
&p->blk);
if (ret) {
pr_err("failed to send spi packet\n");
log_packet_free(p);
}
}
static int log_level_get(struct debug *dbg, struct log_module *log_module)
{
struct qm35_ctx *qm35_hdl;
struct log_packet *p;
int ret = 0;
DECLARE_COMPLETION_ONSTACK(comp);
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
p = encode_get_log_level_packet(log_module->id);
if (!p) {
pr_err("failed to encode get log level packet\n");
return 0;
}
log_module->read_done = &comp;
ret = hsspi_send(&qm35_hdl->hsspi, &qm35_hdl->log_layer.hlayer,
&p->blk);
if (ret) {
pr_err("failed to send spi packet\n");
log_packet_free(p);
return 0;
}
wait_for_completion(log_module->read_done);
return log_module->lvl;
}
static char *log_trace_get_next(struct debug *dbg, rb_entry_size_t *len)
{
struct qm35_ctx *qm35_hdl;
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
return rb_pop(&qm35_hdl->log_layer.rb, len);
}
static rb_entry_size_t log_trace_get_next_size(struct debug *dbg)
{
struct qm35_ctx *qm35_hdl;
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
return rb_next_size(&qm35_hdl->log_layer.rb);
}
static bool log_trace_next_avail(struct debug *dbg)
{
struct qm35_ctx *qm35_hdl;
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
return rb_can_pop(&qm35_hdl->log_layer.rb);
}
static void log_trace_reset(struct debug *dbg)
{
struct qm35_ctx *qm35_hdl;
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
rb_reset(&qm35_hdl->log_layer.rb);
}
static int get_dev_id(struct debug *dbg, uint16_t *dev_id)
{
struct qm35_ctx *qm35_hdl;
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
return qm_get_dev_id(qm35_hdl, dev_id);
}
static int get_soc_id(struct debug *dbg, uint8_t *soc_id)
{
struct qm35_ctx *qm35_hdl;
qm35_hdl = container_of(dbg, struct qm35_ctx, debug);
return qm_get_soc_id(qm35_hdl, soc_id);
}
static const struct debug_trace_ops debug_trace_ops = {
.enable_set = log_enable_set,
.enable_get = log_enable_get,
.level_set = log_level_set,
.level_get = log_level_get,
.trace_get_next = log_trace_get_next,
.trace_get_next_size = log_trace_get_next_size,
.trace_next_avail = log_trace_next_avail,
.trace_reset = log_trace_reset,
.get_dev_id = get_dev_id,
.get_soc_id = get_soc_id,
};
int log_layer_init(struct log_layer *log, struct debug *debug)
{
int ret;
ret = rb_init(&log->rb, TRACE_RB_SIZE);
if (ret)
return ret;
log->hlayer.name = "QM35 LOG";
log->hlayer.id = UL_LOG;
log->hlayer.ops = &log_ops;
log->enabled = false;
log->log_modules = NULL;
log->log_modules_count = 0;
debug->trace_ops = &debug_trace_ops;
return 0;
}
void log_layer_deinit(struct log_layer *log)
{
kfree(log->log_modules);
rb_deinit(&log->rb);
}

View file

@ -0,0 +1,70 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2022 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 LOG layer HSSPI Protocol
*/
#ifndef __HSSPI_LOG_H__
#define __HSSPI_LOG_H__
#include "hsspi.h"
#include "debug.h"
#include "qm35_rb.h"
struct log_packet {
struct hsspi_block blk;
struct completion *write_done;
};
struct log_layer {
struct hsspi_layer hlayer;
uint8_t log_modules_count;
struct log_module *log_modules;
struct rb rb;
bool enabled;
};
/**
* log_packet_alloc() - Allocate an LOG packet
* @length: length of the LOG packet
*
* Allocate an LOG packet that can be used by the HSSPI driver in
* order to send or receive an LOG packet.
*
* Return: a newly allocated &struct log_packet or NULL
*/
struct log_packet *log_packet_alloc(u16 length);
/**
* log_packet_free() - Free an LOG packet
* @p: pointer to the &struct log_packet to free
*
*/
void log_packet_free(struct log_packet *p);
int log_layer_init(struct log_layer *log, struct debug *debug);
void log_layer_deinit(struct log_layer *log);
#endif // __HSSPI_LOG_H__

View file

@ -0,0 +1,181 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2021 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 UCI layer HSSPI Protocol
*/
#include "hsspi_test.h"
#include "hsspi_uci.h"
#include <linux/printk.h>
#include <linux/delay.h>
int hsspi_test_registered(struct hsspi_layer *upper_layer);
void hsspi_test_unregistered(struct hsspi_layer *upper_layer);
struct hsspi_block *hsspi_test_get(struct hsspi_layer *upper_layer, u16 length);
void hsspi_test_received(struct hsspi_layer *upper_layer,
struct hsspi_block *blk, int status);
void hsspi_test_sent(struct hsspi_layer *upper_layer, struct hsspi_block *blk,
int status);
struct hsspi_layer_ops test_hsspi_layer_ops = {
.registered = hsspi_test_registered,
.unregistered = hsspi_test_unregistered,
.get = hsspi_test_get,
.received = hsspi_test_received,
.sent = hsspi_test_sent,
};
struct hsspi_layer test_hsspi_layer = {
.name = "hsspi_test_layer",
.id = UL_TEST_HSSPI,
.ops = &test_hsspi_layer_ops,
};
static struct hsspi *ghsspi;
int sleep_inter_frame_ms;
extern int test_sleep_after_ss_ready_us;
int hsspi_test_init(struct hsspi *hsspi)
{
ghsspi = hsspi;
return hsspi_register(hsspi, &test_hsspi_layer);
}
void hsspi_test_deinit(struct hsspi *hsspi)
{
hsspi_unregister(hsspi, &test_hsspi_layer);
}
int hsspi_test_registered(struct hsspi_layer *upper_layer)
{
return 0;
}
void hsspi_test_unregistered(struct hsspi_layer *upper_layer)
{
}
static int check_rx(const u8 *rx, int len)
{
int idx = 0, err = 0;
for (; idx < len; idx++) {
if (rx[idx] != (idx & 0xff)) {
pr_err("hsspi test: check_rx rx[%u] != %u\n", idx,
rx[idx]);
print_hex_dump(KERN_DEBUG, "rx:", DUMP_PREFIX_ADDRESS,
16, 1, rx, len, false);
err = -5963;
break;
}
}
return err;
}
struct hsspi_block *hsspi_test_get(struct hsspi_layer *layer, u16 length)
{
struct hsspi_block *blk = kzalloc(sizeof(*blk) + length, GFP_KERNEL);
if (blk) {
blk->data = blk + 1;
blk->size = length;
blk->length = length;
}
return blk;
}
void hsspi_test_set_inter_frame_ms(int ms)
{
sleep_inter_frame_ms = ms;
}
void hsspi_test_received(struct hsspi_layer *layer, struct hsspi_block *blk,
int status)
{
static uint64_t bytes, msgs, errors, bytes0, msgs0, errors0;
static time64_t last_perf_dump;
uint32_t rem;
int error = check_rx(blk->data, blk->length) ? 1 : 0;
time64_t now;
errors += error;
if (!last_perf_dump) {
last_perf_dump = ktime_get_seconds();
}
now = ktime_get_seconds();
/* inject latencies between each message and between the check
* of ss-ready and the xfer.
* The test is expected to fail if
* sleep_inter_frame_ms > CONFIG_PM_RET_SLEEP_DELAY_US
*/
if (sleep_inter_frame_ms > 0) {
static int delay_us = 0;
test_sleep_after_ss_ready_us =
sleep_inter_frame_ms * 1000 - delay_us;
usleep_range(delay_us, delay_us + 1);
delay_us += 100;
if (delay_us > sleep_inter_frame_ms * 1000)
delay_us = 0;
} else {
test_sleep_after_ss_ready_us = 0;
}
bytes += blk->length;
msgs++;
error |= hsspi_send(ghsspi, layer, blk);
div_u64_rem(msgs, 100, &rem);
if (error || (rem == 0))
pr_info("hsspi test: bytes received %llu, msgs %llu, errors %llu\n",
bytes, msgs, errors);
if (now > last_perf_dump) {
uint64_t dbytes = bytes >= bytes0 ? bytes - bytes0 :
~0ULL - bytes0 + bytes;
uint64_t dmsgs = msgs >= msgs0 ? msgs - msgs0 :
~0ULL - msgs0 + msgs;
uint64_t derrors = errors >= errors0 ? errors - errors0 :
~0ULL - errors0 + errors;
pr_info("hsspi test perfs: %llu B/s, %llu msgs/s, %llu errors/s\n",
div_u64(dbytes, (now - last_perf_dump)),
div_u64(dmsgs, (now - last_perf_dump)),
div_u64(derrors, (now - last_perf_dump)));
bytes0 = bytes;
msgs0 = msgs;
errors0 = errors;
last_perf_dump = now;
}
}
void hsspi_test_sent(struct hsspi_layer *layer, struct hsspi_block *blk,
int status)
{
static uint64_t bytes, msgs, errors;
uint32_t rem;
errors += status ? 1 : 0;
msgs++;
bytes += blk->length;
div_u64_rem(msgs, 100, &rem);
if (status || (rem == 0))
pr_info("hsspi test: bytes sent %llu, msgs %llu, errors %llu\n",
bytes, msgs, errors);
kfree(blk);
}

View file

@ -0,0 +1,39 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2022 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 Ringbuffer
*/
#ifndef __HSSPI_TEST_H___
#define __HSSPI_TEST_H___
#include "hsspi.h"
int hsspi_test_init(struct hsspi *hsspi);
void hsspi_test_deinit(struct hsspi *hsspi);
void hsspi_test_set_inter_frame_ms(int ms);
#endif /* __HSSPI_TEST_H___ */

View file

@ -0,0 +1,233 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2021 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 UCI layer HSSPI Protocol
*/
#include "qm35.h"
#include "hsspi_uci.h"
struct uci_packet *uci_packet_alloc(u16 length)
{
struct uci_packet *p;
p = kzalloc(sizeof(*p), GFP_KERNEL);
if (!p)
return NULL;
if (hsspi_init_block(&p->blk, length)) {
kfree(p);
return NULL;
}
p->data = p->blk.data;
p->length = p->blk.length;
return p;
}
void uci_packet_free(struct uci_packet *p)
{
hsspi_deinit_block(&p->blk);
kfree(p);
}
static int uci_registered(struct hsspi_layer *layer)
{
return 0;
}
static void clear_rx_list(struct uci_layer *uci)
{
struct uci_packet *p;
mutex_lock(&uci->lock);
while (!list_empty(&uci->rx_list)) {
p = list_first_entry(&uci->rx_list, struct uci_packet, list);
list_del(&p->list);
uci_packet_free(p);
}
mutex_unlock(&uci->lock);
wake_up_interruptible(&uci->wq);
}
static void uci_unregistered(struct hsspi_layer *hlayer)
{
struct uci_layer *uci = container_of(hlayer, struct uci_layer, hlayer);
clear_rx_list(uci);
}
static struct hsspi_block *uci_get(struct hsspi_layer *hlayer, u16 length)
{
struct uci_packet *p;
p = uci_packet_alloc(length);
if (!p)
return NULL;
return &p->blk;
}
static void uci_sent(struct hsspi_layer *hlayer, struct hsspi_block *blk,
int status)
{
struct uci_packet *p = container_of(blk, struct uci_packet, blk);
p->status = status;
complete(p->write_done);
}
#define UCI_CONTROL_PACKET_PAYLOAD_SIZE_LOCATION (3)
static size_t get_payload_size_from_header(const u8 *header)
{
bool is_data_packet = ((header[0] >> 5) & 0x07) == 0;
bool is_control_packet = !is_data_packet && ((header[0] >> 7) == 0);
if (is_control_packet)
return header[UCI_CONTROL_PACKET_PAYLOAD_SIZE_LOCATION];
return (header[3] << 8) | header[2];
}
#define UCI_PACKET_HEADER_SIZE (4)
static void uci_received(struct hsspi_layer *hlayer, struct hsspi_block *blk,
int status)
{
struct uci_layer *uci = container_of(hlayer, struct uci_layer, hlayer);
struct uci_packet *p = container_of(blk, struct uci_packet, blk);
if (status)
uci_packet_free(p);
else {
struct uci_packet *next;
size_t readn = 0;
size_t payload_size;
while (1) {
if (blk->length - readn < UCI_PACKET_HEADER_SIZE)
// Incomplete UCI header
break;
payload_size = get_payload_size_from_header(
(u8 *)blk->data + readn);
if (blk->length - readn <=
UCI_PACKET_HEADER_SIZE + payload_size)
// blk contains no additional packet
break;
next = kzalloc(sizeof(*next), GFP_KERNEL);
if (!next)
break;
next->data = p->blk.data + readn;
next->length = UCI_PACKET_HEADER_SIZE + payload_size;
readn += next->length;
mutex_lock(&uci->lock);
list_add_tail(&next->list, &uci->rx_list);
mutex_unlock(&uci->lock);
}
p->data = p->blk.data + readn;
p->length = p->blk.length - readn;
mutex_lock(&uci->lock);
list_add_tail(&p->list, &uci->rx_list);
mutex_unlock(&uci->lock);
wake_up_interruptible(&uci->wq);
}
}
static const struct hsspi_layer_ops uci_ops = {
.registered = uci_registered,
.unregistered = uci_unregistered,
.get = uci_get,
.received = uci_received,
.sent = uci_sent,
};
int uci_layer_init(struct uci_layer *uci)
{
uci->hlayer.name = "UCI";
uci->hlayer.id = UL_UCI_APP;
uci->hlayer.ops = &uci_ops;
INIT_LIST_HEAD(&uci->rx_list);
mutex_init(&uci->lock);
init_waitqueue_head(&uci->wq);
return 0;
}
void uci_layer_deinit(struct uci_layer *uci)
{
clear_rx_list(uci);
}
bool uci_layer_has_data_available(struct uci_layer *uci)
{
bool ret;
mutex_lock(&uci->lock);
ret = !list_empty(&uci->rx_list);
mutex_unlock(&uci->lock);
return ret;
}
struct uci_packet *uci_layer_read(struct uci_layer *uci, size_t max_size,
bool non_blocking)
{
struct uci_packet *p;
int ret;
if (!non_blocking) {
ret = wait_event_interruptible(
uci->wq, uci_layer_has_data_available(uci));
if (ret)
return ERR_PTR(ret);
}
mutex_lock(&uci->lock);
p = list_first_entry_or_null(&uci->rx_list, struct uci_packet, list);
if (p) {
if (p->length > max_size)
p = ERR_PTR(-EMSGSIZE);
else
list_del(&p->list);
} else
p = ERR_PTR(-EAGAIN);
mutex_unlock(&uci->lock);
return p;
}

View file

@ -0,0 +1,129 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2021 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 UCI layer HSSPI Protocol
*/
#ifndef __HSSPI_UCI_H__
#define __HSSPI_UCI_H__
#include <linux/completion.h>
#include <linux/list.h>
#include <linux/mutex.h>
#include <linux/wait.h>
#include "hsspi.h"
/**
* struct uci_packet - UCI packet that implements a &struct hsspi_block.
* @blk: &struct hsspi_block
* @write_done: norify when the packet has been really send
* @list: link with &struct uci_layer.rx_list
* @status: status of the transfer
*/
struct uci_packet {
struct hsspi_block blk;
struct completion *write_done;
struct list_head list;
u8 *data;
int length;
int status;
};
/**
* uci_packet_alloc() - Allocate an UCI packet
* @length: length of the UCI packet
*
* Allocate an UCI packet that can be used by the HSSPI driver in
* order to send or receive an UCI packet.
*
* Return: a newly allocated &struct uci_packet or NULL
*/
struct uci_packet *uci_packet_alloc(u16 length);
/**
* uci_packet_free() - Free an UCI packet
* @p: pointer to the &struct uci_packet to free
*
*/
void uci_packet_free(struct uci_packet *p);
/**
* struct uci_layer - Implement an HSSPI Layer
* @hlayer: &struct hsspi_layer
* @rx_list: list of received UCI packets
* @lock: protect the &struct uci_layer.rx_list
* @wq: notify when the &struct uci_layer.rx_list is not empty
*/
struct uci_layer {
struct hsspi_layer hlayer;
struct list_head rx_list;
struct mutex lock;
wait_queue_head_t wq;
};
/**
* uci_layer_init() - Initialize UCI Layer
*
*/
int uci_layer_init(struct uci_layer *uci);
/**
* uci_layer_deinit() - Deinitialize UCI Layer
*
*/
void uci_layer_deinit(struct uci_layer *uci);
/**
* uci_layer_has_data_availal() - checks if the layer has some rx packets
* @uci: pointer to &struct uci_layer
*
* Function that checks if the UCI layer has some data waiting to be read.
*
* Return: true if some data is available, false otherwise.
*/
bool uci_layer_has_data_available(struct uci_layer *uci);
/**
* uci_layer_read() - get a packet from the rx_list
* @uci: pointer to &struct uci_layer
* @max_size: maximum size possible for the UCI packet
* @non_blocking: true if non blocking, false otherwise
*
* This function returns an UCI packet if available in the
* &struct uci_layer.rx_list. The max_size argument logic is due to the way
* the /dev/uci is make. We should ensure that we return an entire
* packet in uci_read, so we must get a packet only if the caller has
* enougth room for it.
*
* Return: a &struct uci_packet if succeed,
* -EINTR if it was interrupted (in blocking mode),
* -ESMGSIZE if the available UCI packet is bigger than max_size,
* -EAGAIN if there is no available UCI packet (in non blocking mode)
*/
struct uci_packet *uci_layer_read(struct uci_layer *uci, size_t max_size,
bool non_blocking);
#endif // __HSSPI_UCI_H__

View file

@ -0,0 +1,245 @@
#ifndef __KERNEL__
#include <stddef.h>
#endif
#include <linux/uwb/qmrom_spi.h>
#include <linux/uwb/qmrom_log.h>
#include <linux/uwb/qmrom_utils.h>
#include <linux/uwb/spi_rom_protocol.h>
#include <linux/uwb/fwupdater.h>
/* Extract from C0 rom code */
#define MAX_CERTIFICATE_SIZE 0x400
#define MAX_CHUNK_SIZE 4096
#define WAIT_SS_RDY_TIMEOUT 100
#define RESULT_RETRIES 3
#define RESULT_CMD_INTERVAL_MS 50
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#ifndef __KERNEL__
_Static_assert(sizeof(struct stc) + MAX_CERTIFICATE_SIZE < MAX_CHUNK_SIZE);
#endif
static int send_data_chunks(struct qmrom_handle *handle, char *data,
size_t size);
int run_fwupdater(struct qmrom_handle *handle, char *fwpkg_bin, size_t size)
{
int rc;
if (size < sizeof(struct fw_pkg_hdr_t) +
sizeof(struct fw_pkg_img_hdr_t) +
CRYPTO_IMAGES_CERT_PKG_SIZE +
CRYPTO_FIRMWARE_CHUNK_MIN_SIZE) {
LOG_ERR("%s cannot extract enough data from fw package binary\n",
__func__);
return -EINVAL;
}
rc = send_data_chunks(handle, fwpkg_bin, size);
if (rc) {
LOG_ERR("Sending image failed with %d\n", rc);
return rc;
}
return 0;
}
static int run_fwupdater_get_status(struct qmrom_handle *handle,
struct stc *hstc, struct stc *sstc,
struct fw_updater_status_t *status)
{
uint32_t i = 0;
bool owa;
while (i++ < RESULT_RETRIES) {
// Poll the QM
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
WAIT_SS_RDY_TIMEOUT);
sstc->all = 0;
hstc->all = 0;
qmrom_spi_transfer(handle->spi_handle, (char *)sstc,
(const char *)hstc, sizeof(hstc));
// LOG_INFO("Poll received:\n");
// hexdump(LOG_INFO, sstc, sizeof(sstc));
/* Line might have been read ready before QM actually handled the previous cmd */
qmrom_msleep(RESULT_CMD_INTERVAL_MS);
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
WAIT_SS_RDY_TIMEOUT);
sstc->all = 0;
hstc->all = 0;
hstc->host_flags.pre_read = 1;
qmrom_spi_transfer(handle->spi_handle, (char *)sstc,
(const char *)hstc, sizeof(hstc));
// LOG_INFO("Pre-Read received:\n");
// hexdump(LOG_INFO, sstc, sizeof(sstc));
/* Stops the loop when QM has a result to share */
owa = sstc->soc_flags.out_waiting;
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
WAIT_SS_RDY_TIMEOUT);
sstc->all = 0;
hstc->all = 0;
hstc->host_flags.read = 1;
hstc->len = sizeof(*status);
qmrom_spi_transfer(handle->spi_handle, (char *)sstc,
(const char *)hstc,
sizeof(hstc) + sizeof(*status));
// LOG_INFO("Read received:\n");
// hexdump(LOG_INFO, sstc, sizeof(hstc) + sizeof(uint32_t));
if (owa) {
memcpy(status, sstc->payload, sizeof(*status));
break;
}
}
if (!owa) {
LOG_ERR("Timedout waiting for result\n");
return -1;
}
return 0;
}
static struct stc *prepare_hstc(bool copy_data, char *tx, char *data, size_t len)
{
struct stc *hstc = (struct stc *)(copy_data ? tx : data);
hstc->all = 0;
hstc->host_flags.write = 1;
hstc->len = len;
if (copy_data)
memcpy(hstc->payload, data, len);
return hstc;
}
static int send_data_chunks(struct qmrom_handle *handle, char *data,
size_t size)
{
struct fw_updater_status_t status;
char *rx, *tx;
struct stc *hstc, *sstc;
size_t to_send;
int rc = 0;
bool copy_data = !qmrom_data_dma_able(data);
qmrom_alloc(rx, MAX_CHUNK_SIZE + sizeof(struct stc));
qmrom_alloc(tx, MAX_CHUNK_SIZE + sizeof(struct stc));
if (!rx || !tx) {
LOG_ERR("Rx/Tx buffers allocation failure\n");
if (rx)
qmrom_free(rx);
if (tx)
qmrom_free(tx);
return -1;
}
hstc = (struct stc *)tx;
sstc = (struct stc *)rx;
/* Sending the fw package header */
LOG_INFO("Sending the fw package header (copy_data is %d)\n", copy_data);
hstc->all = 0;
hstc->host_flags.write = 1;
hstc->len = sizeof(struct fw_pkg_hdr_t);
size -= sizeof(struct fw_pkg_hdr_t);
memcpy(&tx[sizeof(struct stc)], data, sizeof(struct fw_pkg_hdr_t));
/* Move the data to the next offset minus the stc footprint */
data += sizeof(struct fw_pkg_hdr_t) - (copy_data ? 0 : sizeof(struct stc));
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
WAIT_SS_RDY_TIMEOUT);
rc = qmrom_spi_transfer(handle->spi_handle, rx, tx,
sizeof(struct fw_pkg_hdr_t) + sizeof(struct stc));
if (rc)
{
LOG_ERR("First qmrom_spi_transfer failed with %d\n", rc);
return rc;
}
// hexdump(LOG_INFO, tx + sizeof(struct stc), sizeof(struct fw_pkg_hdr_t));
/* Sending the image header */
LOG_INFO("Sending the image header\n");
hstc = prepare_hstc(copy_data, tx, data, sizeof(struct fw_pkg_img_hdr_t));
size -= sizeof(struct fw_pkg_img_hdr_t);
// hexdump(LOG_INFO, data + sizeof(struct stc),
// sizeof(struct fw_pkg_img_hdr_t));
data += sizeof(struct fw_pkg_img_hdr_t);
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
WAIT_SS_RDY_TIMEOUT);
qmrom_spi_transfer(handle->spi_handle, rx, (const char *)hstc,
sizeof(struct fw_pkg_img_hdr_t) +
sizeof(struct stc));
/* Sending the cert chain */
LOG_INFO("Sending the cert chain\n");
#if MAX_CHUNK_SIZE < CRYPTO_IMAGES_CERT_PKG_SIZE
hstc = prepare_hstc(true, tx, data, CRYPTO_IMAGES_CERT_KEY_SIZE);
size -= CRYPTO_IMAGES_CERT_KEY_SIZE;
data += CRYPTO_IMAGES_CERT_KEY_SIZE;
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
WAIT_SS_RDY_TIMEOUT);
qmrom_spi_transfer(handle->spi_handle, rx, tx,
CRYPTO_IMAGES_CERT_KEY_SIZE);
hstc = prepare_hstc(true, tx, data, CRYPTO_IMAGES_CERT_KEY_SIZE);
size -= CRYPTO_IMAGES_CERT_KEY_SIZE;
data += CRYPTO_IMAGES_CERT_KEY_SIZE;
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
WAIT_SS_RDY_TIMEOUT);
qmrom_spi_transfer(handle->spi_handle, rx, tx,
CRYPTO_IMAGES_CERT_KEY_SIZE);
hstc = prepare_hstc(true, tx, data, CRYPTO_IMAGES_CERT_CONTENT_SIZE);
size -= CRYPTO_IMAGES_CERT_CONTENT_SIZE;
data += CRYPTO_IMAGES_CERT_CONTENT_SIZE;
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
WAIT_SS_RDY_TIMEOUT);
qmrom_spi_transfer(handle->spi_handle, rx, tx,
CRYPTO_IMAGES_CERT_CONTENT_SIZE);
#else
hstc = prepare_hstc(copy_data, tx, data, CRYPTO_IMAGES_CERT_PKG_SIZE);
size -= CRYPTO_IMAGES_CERT_PKG_SIZE;
// hexdump(LOG_INFO, data + sizeof(struct stc), 100);
data += CRYPTO_IMAGES_CERT_PKG_SIZE;
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
WAIT_SS_RDY_TIMEOUT);
qmrom_spi_transfer(handle->spi_handle, rx, (const char *)hstc,
CRYPTO_IMAGES_CERT_PKG_SIZE + sizeof(struct stc));
#endif
/* Sending the fw image */
LOG_INFO("Sending the image\n");
do {
to_send = MIN(MAX_CHUNK_SIZE, size);
LOG_DBG("Sending a chunk of %zu bytes\n", to_send);
hstc = prepare_hstc(copy_data, tx, data, to_send);
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
WAIT_SS_RDY_TIMEOUT);
qmrom_spi_transfer(handle->spi_handle, rx, (const char *)hstc,
to_send + sizeof(struct stc));
data += to_send;
size -= to_send;
} while (size);
qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
WAIT_SS_RDY_TIMEOUT);
rc = run_fwupdater_get_status(handle, hstc, sstc, &status);
if (rc) {
LOG_ERR("run_fwupdater_get_status returned %d\n", rc);
} else {
if (status.status) {
LOG_ERR("%s failed, fw updater status %#x (suberror %#x)\n",
__func__, status.status, status.suberror);
rc = status.status;
} else {
LOG_INFO("%s succeeded\n", __func__);
}
}
if (rx)
qmrom_free(rx);
if (tx)
qmrom_free(tx);
return rc;
}

View file

@ -0,0 +1,403 @@
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
/*
* Copyright 2022 Qorvo US, Inc.
*
*/
#include <linux/uwb/qmrom.h>
#include <linux/uwb/qmrom_spi.h>
#include <linux/uwb/qmrom_log.h>
#include <linux/uwb/qmrom_utils.h>
#include <linux/uwb/spi_rom_protocol.h>
#define DEFAULT_SPI_CLOCKRATE 3000000
#define CHIP_VERSION_CHIP_REV_PAYLOAD_OFFSET 1
#define CHIP_VERSION_DEV_REV_PAYLOAD_OFFSET 3
#define CHUNK_SIZE_B0 1008
enum B0_CMD {
ROM_CMD_B0_SEC_LOAD_ICV_IMG_TO_RRAM = 0x0,
ROM_CMD_B0_SEC_LOAD_OEM_IMG_TO_RRAM = 0x1,
ROM_CMD_B0_GET_CHIP_VER = 0x2,
ROM_CMD_B0_GET_SOC_INFO = 0x3,
ROM_CMD_B0_ERASE_DBG_CERT = 0x4,
ROM_CMD_B0_WRITE_DBG_CERT = 0x5,
ROM_CMD_B0_SEC_IMAGE_DATA = 0xf,
ROM_CMD_B0_CERT_DATA = 0x10,
ROM_CMD_B0_DEBUG_CERT_SIZE = 0x11,
};
enum B0_RESP {
READY_FOR_CS_LOW_CMD = 0x00,
WRONG_CS_LOW_CMD = 0x01,
WAITING_FOR_NS_RRAM_FILE_SIZE = 0x02,
WAITING_FOR_NS_SRAM_FILE_SIZE = 0x03,
WAITING_FOR_NS_RRAM_FILE_DATA = 0x04,
WAITING_FOR_NS_SRAM_FILE_DATA = 0x05,
WAITING_FOR_SEC_FILE_DATA = 0x06,
ERR_NS_SRAM_OR_RRAM_SIZE_CMD = 0x07,
ERR_SEC_RRAM_SIZE_CMD = 0x08,
ERR_WAITING_FOR_NS_IMAGE_DATA_CMD = 0x09,
ERR_WAITING_FOR_SEC_IMAGE_DATA_CMD = 0x0A,
ERR_IMAGE_SIZE_IS_ZERO = 0x0B,
/* Got more data than expected size */
ERR_IMAGE_SIZE_TOO_BIG = 0x0C,
/* Image must divide in 16 without remainder */
ERR_IMAGE_IS_NOT_16BYTES_MUL = 0x0D,
ERR_GOT_DATA_MORE_THAN_ALLOWED = 0x0E,
/* Remainder is allowed only for last packet */
ERR_RRAM_DATA_REMAINDER_NOT_ALLOWED = 0x0F,
ERR_WAITING_FOR_CERT_DATA_CMD = 0x10,
WAITING_FOR_FIRST_KEY_CERT = 0x11,
WAITING_FOR_SECOND_KEY_CERT = 0x12,
WAITING_FOR_CONTENT_CERT = 0x13,
WAITING_FOR_DEBUG_CERT_DATA = 0x14,
ERR_FIRST_KEY_CERT_OR_FW_VER = 0x15,
ERR_SECOND_KEY_CERT = 0x16,
ERR_CONTENT_CERT_DOWNLOAD_ADDR = 0x17,
/* If the content certificate contains to much images */
ERR_TOO_MANY_IMAGES_IN_CONTENT_CERT = 0x18,
ERR_ADDRESS_NOT_DIVIDED_BY_8 = 0x19,
ERR_IMAGE_BOUNDARIES = 0x1A,
/* Expected ICV type and got OEM */
ERR_CERT_TYPE = 0x1B,
ERR_PRODUCT_ID = 0x1C,
ERR_RRAM_RANGE_OR_WRITE = 0x1D,
WAITING_TO_DEBUG_CERTIFICATE_SIZE = 0x1E,
ERR_DEBUG_CERT_SIZE = 0x1F,
};
static int qm357xx_rom_b0_flash_debug_cert(struct qmrom_handle *handle,
struct firmware *dbg_cert);
static int qm357xx_rom_b0_erase_debug_cert(struct qmrom_handle *handle);
static int
qm357xx_rom_b0_flash_unstitched_fw(struct qmrom_handle *handle,
const struct unstitched_firmware *all_fws);
static void qm357xx_rom_b0_poll_soc(struct qmrom_handle *handle)
{
int retries = handle->comms_retries;
memset(handle->hstc, 0, sizeof(struct stc));
qmrom_msleep(SPI_READY_TIMEOUT_MS);
do {
qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + handle->hstc->len);
} while (retries-- && handle->sstc->raw_flags == 0);
}
static int qm357xx_rom_b0_wait_ready(struct qmrom_handle *handle)
{
int retries = handle->comms_retries;
int rc;
qm357xx_rom_b0_poll_soc(handle);
/* handle->sstc has been updated */
while (retries-- &&
handle->sstc->raw_flags != SPI_SH_READY_CMD_BIT_MASK) {
if (handle->sstc->soc_flags.out_waiting) {
qmrom_pre_read(handle);
} else if (handle->sstc->soc_flags.out_active) {
rc = qmrom_read(handle);
if (rc)
return rc;
} else {
/* error? */
qm357xx_rom_b0_poll_soc(handle);
}
}
return handle->sstc->raw_flags == SPI_SH_READY_CMD_BIT_MASK ?
0 :
SPI_ERR_WAIT_READY_TIMEOUT;
}
static int qm357xx_rom_b0_poll_cmd_resp(struct qmrom_handle *handle)
{
int retries = handle->comms_retries;
qm357xx_rom_b0_poll_soc(handle);
do {
if (handle->sstc->soc_flags.out_waiting) {
qmrom_pre_read(handle);
if (handle->sstc->len > 0xff) {
/* likely the wrong endianness, A0? */
return -1;
}
qmrom_read(handle);
break;
} else
qm357xx_rom_b0_poll_soc(handle);
} while (retries--);
return retries > 0 ? 0 : -1;
}
int qm357xx_rom_b0_probe_device(struct qmrom_handle *handle)
{
int rc, i;
uint8_t *soc_lcs_uuid;
handle->is_be = false;
check_stcs(__func__, __LINE__, handle);
if (handle->spi_speed == 0)
qmrom_spi_set_freq(DEFAULT_SPI_CLOCKRATE);
else
qmrom_spi_set_freq(handle->spi_speed);
rc = qmrom_reboot_bootloader(handle);
if (rc) {
LOG_ERR("%s: cannot reset the device...\n", __func__);
return rc;
}
rc = qm357xx_rom_b0_wait_ready(handle);
if (rc) {
LOG_INFO("%s: maybe not a B0 device\n", __func__);
return rc;
}
rc = qm357xx_rom_write_cmd(handle, ROM_CMD_B0_GET_CHIP_VER);
if (rc)
return rc;
rc = qm357xx_rom_b0_poll_cmd_resp(handle);
if (rc)
return rc;
handle->chip_rev =
SSTC2UINT16(handle, CHIP_VERSION_CHIP_REV_PAYLOAD_OFFSET) &
0xFF;
handle->device_version = bswap_16(
SSTC2UINT16(handle, CHIP_VERSION_DEV_REV_PAYLOAD_OFFSET));
if (handle->chip_rev != CHIP_REVISION_B0) {
LOG_ERR("%s: wrong chip revision 0x%x\n", __func__,
handle->chip_rev);
handle->chip_rev = -1;
return -1;
}
rc = qm357xx_rom_b0_wait_ready(handle);
if (rc) {
LOG_ERR("%s: hmm something went wrong!!!\n", __func__);
return rc;
}
rc = qm357xx_rom_write_cmd(handle, ROM_CMD_B0_GET_SOC_INFO);
if (rc)
return rc;
rc = qm357xx_rom_b0_poll_cmd_resp(handle);
if (rc)
return rc;
/* skip the first byte */
soc_lcs_uuid = &(handle->sstc->payload[1]);
for (i = 0; i < QM357XX_ROM_SOC_ID_LEN; i++)
handle->qm357xx_soc_info.soc_id[i] =
soc_lcs_uuid[QM357XX_ROM_SOC_ID_LEN - i - 1];
soc_lcs_uuid += QM357XX_ROM_SOC_ID_LEN;
handle->qm357xx_soc_info.lcs_state = soc_lcs_uuid[0];
soc_lcs_uuid += 1;
for (i = 0; i < QM357XX_ROM_UUID_LEN; i++)
handle->qm357xx_soc_info.uuid[i] =
soc_lcs_uuid[QM357XX_ROM_UUID_LEN - i - 1];
/* Set device type */
handle->dev_gen = DEVICE_GEN_QM357XX;
/* Set rom ops */
handle->qm357xx_rom_ops.flash_unstitched_fw =
qm357xx_rom_b0_flash_unstitched_fw;
handle->qm357xx_rom_ops.flash_debug_cert =
qm357xx_rom_b0_flash_debug_cert;
handle->qm357xx_rom_ops.erase_debug_cert =
qm357xx_rom_b0_erase_debug_cert;
check_stcs(__func__, __LINE__, handle);
return 0;
}
static int qm357xx_rom_b0_flash_data(struct qmrom_handle *handle,
struct firmware *fw, uint8_t cmd,
uint8_t exp)
{
int rc, sent = 0;
const char *bin_data = (const char *)fw->data;
check_stcs(__func__, __LINE__, handle);
while (sent < fw->size) {
uint32_t tx_bytes = fw->size - sent;
if (tx_bytes > CHUNK_SIZE_B0)
tx_bytes = CHUNK_SIZE_B0;
LOG_DBG("%s: poll soc...\n", __func__);
check_stcs(__func__, __LINE__, handle);
qm357xx_rom_b0_poll_soc(handle);
qmrom_pre_read(handle);
qmrom_read(handle);
if (handle->sstc->payload[0] != exp) {
LOG_ERR("%s: wrong data expected (%#x vs %#x)!!!\n",
__func__, handle->sstc->payload[0] & 0xff, exp);
if (handle->sstc->payload[0] ==
ERR_FIRST_KEY_CERT_OR_FW_VER)
return PEG_ERR_FIRST_KEY_CERT_OR_FW_VER;
else
return SPI_PROTO_WRONG_RESP;
}
LOG_DBG("%s: sending %d command with %" PRIu32 " bytes\n",
__func__, cmd, tx_bytes);
rc = qm357xx_rom_write_size_cmd(handle, cmd, tx_bytes,
bin_data);
if (rc)
return rc;
sent += tx_bytes;
bin_data += tx_bytes;
check_stcs(__func__, __LINE__, handle);
}
return 0;
}
static int
qm357xx_rom_b0_flash_unstitched_fw(struct qmrom_handle *handle,
const struct unstitched_firmware *all_fws)
{
int rc = 0;
uint8_t flash_cmd = handle->qm357xx_soc_info.lcs_state ==
CC_BSV_SECURE_LCS ?
ROM_CMD_B0_SEC_LOAD_OEM_IMG_TO_RRAM :
ROM_CMD_B0_SEC_LOAD_ICV_IMG_TO_RRAM;
if (all_fws->key1_crt->data[HBK_LOC] == HBK_2E_ICV &&
handle->qm357xx_soc_info.lcs_state != CC_BSV_CHIP_MANUFACTURE_LCS) {
LOG_ERR("%s: Trying to flash an ICV fw on a non ICV platform\n",
__func__);
rc = -EINVAL;
goto end;
}
if (all_fws->key1_crt->data[HBK_LOC] == HBK_2E_OEM &&
handle->qm357xx_soc_info.lcs_state != CC_BSV_SECURE_LCS) {
LOG_ERR("%s: Trying to flash an OEM fw on a non OEM platform\n",
__func__);
rc = -EINVAL;
goto end;
}
LOG_DBG("%s: starting...\n", __func__);
check_stcs(__func__, __LINE__, handle);
rc = qm357xx_rom_b0_wait_ready(handle);
if (rc)
goto end;
check_stcs(__func__, __LINE__, handle);
LOG_DBG("%s: sending flash_cmd %u command\n", __func__, flash_cmd);
rc = qm357xx_rom_write_cmd(handle, flash_cmd);
if (rc)
goto end;
check_stcs(__func__, __LINE__, handle);
rc = qm357xx_rom_b0_flash_data(handle, all_fws->key1_crt,
ROM_CMD_B0_CERT_DATA,
WAITING_FOR_FIRST_KEY_CERT);
if (rc)
goto end;
check_stcs(__func__, __LINE__, handle);
rc = qm357xx_rom_b0_flash_data(handle, all_fws->key2_crt,
ROM_CMD_B0_CERT_DATA,
WAITING_FOR_SECOND_KEY_CERT);
if (rc)
goto end;
check_stcs(__func__, __LINE__, handle);
rc = qm357xx_rom_b0_flash_data(handle, all_fws->fw_crt,
ROM_CMD_B0_CERT_DATA,
WAITING_FOR_CONTENT_CERT);
if (rc)
goto end;
check_stcs(__func__, __LINE__, handle);
rc = qm357xx_rom_b0_flash_data(handle, all_fws->fw_img,
ROM_CMD_B0_SEC_IMAGE_DATA,
WAITING_FOR_SEC_FILE_DATA);
if (!rc)
qmrom_msleep(SPI_READY_TIMEOUT_MS);
end:
check_stcs(__func__, __LINE__, handle);
qmrom_free(all_fws->fw_img);
qmrom_free(all_fws->fw_crt);
qmrom_free(all_fws->key1_crt);
qmrom_free(all_fws->key2_crt);
return rc;
}
static int qm357xx_rom_b0_flash_debug_cert(struct qmrom_handle *handle,
struct firmware *dbg_cert)
{
int rc;
LOG_DBG("%s: starting...\n", __func__);
check_stcs(__func__, __LINE__, handle);
rc = qm357xx_rom_b0_wait_ready(handle);
if (rc)
return rc;
check_stcs(__func__, __LINE__, handle);
LOG_DBG("%s: sending ROM_CMD_B0_WRITE_DBG_CERT command\n", __func__);
rc = qm357xx_rom_write_cmd(handle, ROM_CMD_B0_WRITE_DBG_CERT);
if (rc)
return rc;
check_stcs(__func__, __LINE__, handle);
LOG_DBG("%s: poll soc...\n", __func__);
qm357xx_rom_b0_poll_soc(handle);
qmrom_pre_read(handle);
qmrom_read(handle);
check_stcs(__func__, __LINE__, handle);
LOG_DBG("%s: sending ROM_CMD_B0_DEBUG_CERT_SIZE command\n", __func__);
rc = qm357xx_rom_write_size_cmd(handle, ROM_CMD_B0_DEBUG_CERT_SIZE,
sizeof(uint32_t),
(const char *)&dbg_cert->size);
if (handle->sstc->payload[0] != WAITING_TO_DEBUG_CERTIFICATE_SIZE) {
LOG_ERR("%s: wrong debug cert size result (0x%x vs 0x%x)!!!\n",
__func__, handle->sstc->payload[0] & 0xff,
WAITING_TO_DEBUG_CERTIFICATE_SIZE);
return SPI_PROTO_WRONG_RESP;
}
if (rc)
return rc;
rc = qm357xx_rom_b0_flash_data(handle, dbg_cert, ROM_CMD_B0_CERT_DATA,
WAITING_FOR_DEBUG_CERT_DATA);
check_stcs(__func__, __LINE__, handle);
qmrom_msleep(SPI_READY_TIMEOUT_MS);
return 0;
}
static int qm357xx_rom_b0_erase_debug_cert(struct qmrom_handle *handle)
{
int rc;
LOG_INFO("%s: starting...\n", __func__);
check_stcs(__func__, __LINE__, handle);
rc = qm357xx_rom_b0_wait_ready(handle);
if (!rc)
return rc;
LOG_DBG("%s: sending ROM_CMD_B0_ERASE_DBG_CERT command\n", __func__);
rc = qm357xx_rom_write_cmd(handle, ROM_CMD_B0_ERASE_DBG_CERT);
if (rc)
return rc;
qmrom_msleep(SPI_READY_TIMEOUT_MS);
check_stcs(__func__, __LINE__, handle);
return 0;
}

View file

@ -0,0 +1,528 @@
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
/*
* Copyright 2022 Qorvo US, Inc.
*
*/
#include <linux/uwb/qmrom.h>
#include <linux/uwb/qmrom_spi.h>
#include <linux/uwb/qmrom_log.h>
#include <linux/uwb/qmrom_utils.h>
#include <linux/uwb/spi_rom_protocol.h>
#define DEFAULT_SPI_CLOCKRATE 5000000
#define CHIP_VERSION_CHIP_REV_PAYLOAD_OFFSET 4
#define CHIP_VERSION_DEV_REV_PAYLOAD_OFFSET 6
#define CHUNK_SIZE_C0 2040
#define SPI_READY_TIMEOUT_MS_C0 200
#ifdef C0_WRITE_STATS
#include <linux/ktime.h>
#endif
#define SPI_SH_READY_CMD_BIT_MASK_C0 \
(SPI_SH_READY_CMD_BIT_MASK >> 4 | SPI_SH_READY_CMD_BIT_MASK)
enum C0_CMD {
ROM_CMD_C0_SEC_LOAD_ICV_IMG_TO_RRAM = 0x0,
ROM_CMD_C0_SEC_LOAD_OEM_IMG_TO_RRAM = 0x1,
ROM_CMD_C0_GET_CHIP_VER = 0x2,
ROM_CMD_C0_GET_SOC_INFO = 0x3,
ROM_CMD_C0_ERASE_DBG_CERT = 0x4,
ROM_CMD_C0_USE_DIRECT_RRAM_WR = 0X5,
ROM_CMD_C0_USE_INDIRECT_RRAM_WR = 0x6,
ROM_CMD_C0_WRITE_DBG_CERT = 0x7,
ROM_CMD_C0_SEC_IMAGE_DATA = 0x12,
ROM_CMD_C0_CERT_DATA = 0x13,
ROM_CMD_C0_DEBUG_CERT_SIZE = 0x14,
};
enum C0_RESP {
READY_FOR_CS_LOW_CMD = 0x00,
WRONG_CS_LOW_CMD = 0x01,
WAITING_FOR_NS_RRAM_FILE_SIZE = 0x02,
WAITING_FOR_NS_SRAM_FILE_SIZE = 0x03,
WAITING_FOR_NS_RRAM_FILE_DATA = 0x04,
WAITING_FOR_NS_SRAM_FILE_DATA = 0x05,
WAITING_FOR_SEC_FILE_DATA = 0x06,
ERR_NS_SRAM_OR_RRAM_SIZE_CMD = 0x07,
ERR_SEC_RRAM_SIZE_CMD = 0x08,
ERR_WAITING_FOR_NS_IMAGE_DATA_CMD = 0x09,
ERR_WAITING_FOR_SEC_IMAGE_DATA_CMD = 0x0A,
ERR_IMAGE_SIZE_IS_ZERO = 0x0B,
/* Got more data than expected size */
ERR_IMAGE_SIZE_TOO_BIG = 0x0C,
/* Image must divide in 16 without remainder */
ERR_IMAGE_IS_NOT_16BYTES_MUL = 0x0D,
ERR_GOT_DATA_MORE_THAN_ALLOWED = 0x0E,
/* Remainder is allowed only for last packet */
ERR_RRAM_DATA_REMAINDER_NOT_ALLOWED = 0x0F,
ERR_WAITING_FOR_CERT_DATA_CMD = 0x10,
WAITING_FOR_FIRST_KEY_CERT = 0x11,
WAITING_FOR_SECOND_KEY_CERT = 0x12,
WAITING_FOR_CONTENT_CERT = 0x13,
WAITING_FOR_DEBUG_CERT_DATA = 0x14,
ERR_FIRST_KEY_CERT_OR_FW_VER = 0x15,
ERR_SECOND_KEY_CERT = 0x16,
ERR_CONTENT_CERT_DOWNLOAD_ADDR = 0x17,
/* If the content certificate contains to much images */
ERR_TOO_MANY_IMAGES_IN_CONTENT_CERT = 0x18,
ERR_ADDRESS_NOT_DIVIDED_BY_8 = 0x19,
ERR_IMAGE_BOUNDARIES = 0x1A,
/* Expected ICV type and got OEM */
ERR_CERT_TYPE = 0x1B,
ERR_PRODUCT_ID = 0x1C,
ERR_RRAM_RANGE_OR_WRITE = 0x1D,
WAITING_TO_DEBUG_CERTIFICATE_SIZE = 0x1E,
ERR_DEBUG_CERT_SIZE = 0x1F,
};
static int
qm357xx_rom_c0_flash_unstitched_fw(struct qmrom_handle *handle,
const struct unstitched_firmware *all_fws);
static int qm357xx_rom_c0_flash_debug_cert(struct qmrom_handle *handle,
struct firmware *dbg_cert);
static int qm357xx_rom_c0_erase_debug_cert(struct qmrom_handle *handle);
#define qmrom_pre_read_c0(h) \
({ \
int rc; \
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
SPI_READY_TIMEOUT_MS_C0); \
rc = qmrom_pre_read((h)); \
rc; \
})
#define qmrom_read_c0(h) \
({ \
int rc; \
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
SPI_READY_TIMEOUT_MS_C0); \
rc = qmrom_read((h)); \
rc; \
})
#define qm357xx_rom_write_cmd_c0(h, cmd) \
({ \
int rc; \
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
SPI_READY_TIMEOUT_MS_C0); \
rc = qm357xx_rom_write_cmd((h), (cmd)); \
rc; \
})
#define qm357xx_rom_write_cmd32_c0(h, cmd) \
({ \
int rc; \
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
SPI_READY_TIMEOUT_MS_C0); \
rc = qm357xx_rom_write_cmd32((h), (cmd)); \
rc; \
})
#define qm357xx_rom_write_size_cmd_c0(h, cmd, ds, d) \
({ \
int rc; \
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
SPI_READY_TIMEOUT_MS_C0); \
rc = qm357xx_rom_write_size_cmd((h), (cmd), (ds), (d)); \
rc; \
})
#define qm357xx_rom_write_size_cmd32_c0(h, cmd, ds, d) \
({ \
int rc; \
qmrom_spi_wait_for_ready_line((h)->ss_rdy_handle, \
SPI_READY_TIMEOUT_MS_C0); \
rc = qm357xx_rom_write_size_cmd32((h), (cmd), (ds), (d)); \
rc; \
})
static void qm357xx_rom_c0_poll_soc(struct qmrom_handle *handle)
{
int retries = handle->comms_retries;
memset(handle->hstc, 0, sizeof(struct stc));
handle->sstc->raw_flags = 0;
do {
int rc = qmrom_spi_wait_for_ready_line(handle->ss_rdy_handle,
SPI_READY_TIMEOUT_MS_C0);
if (rc) {
LOG_ERR("%s qmrom_spi_wait_for_ready_line failed\n",
__func__);
continue;
}
qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + handle->hstc->len);
} while (retries-- && handle->sstc->raw_flags == 0);
}
static int qm357xx_rom_c0_wait_ready(struct qmrom_handle *handle)
{
int retries = handle->comms_retries;
qm357xx_rom_c0_poll_soc(handle);
/* handle->sstc has been updated */
while (retries-- &&
handle->sstc->raw_flags != SPI_SH_READY_CMD_BIT_MASK_C0) {
if (handle->sstc->soc_flags.out_waiting) {
qmrom_pre_read_c0(handle);
qmrom_read_c0(handle);
} else if (handle->sstc->soc_flags.out_active) {
return qmrom_read_c0(handle);
} else
qm357xx_rom_c0_poll_soc(handle);
}
return handle->sstc->raw_flags == SPI_SH_READY_CMD_BIT_MASK_C0 ?
0 :
SPI_ERR_WAIT_READY_TIMEOUT;
}
static int qm357xx_rom_c0_poll_cmd_resp(struct qmrom_handle *handle)
{
int retries = handle->comms_retries;
qm357xx_rom_c0_poll_soc(handle);
do {
if (handle->sstc->soc_flags.out_waiting) {
qmrom_pre_read_c0(handle);
return qmrom_read_c0(handle);
} else
qm357xx_rom_c0_poll_soc(handle);
} while (retries--);
if (retries <= 0)
LOG_ERR("%s failed after %d replies\n", __func__,
handle->comms_retries);
return retries > 0 ? 0 : -1;
}
int qm357xx_rom_c0_probe_device(struct qmrom_handle *handle)
{
int rc, i;
uint8_t *soc_lcs_uuid;
handle->is_be = false;
if (handle->spi_speed == 0)
qmrom_spi_set_freq(DEFAULT_SPI_CLOCKRATE);
else
qmrom_spi_set_freq(handle->spi_speed);
rc = qmrom_reboot_bootloader(handle);
if (rc) {
LOG_ERR("%s: cannot reset the device...\n", __func__);
return rc;
}
rc = qm357xx_rom_c0_wait_ready(handle);
if (rc) {
LOG_INFO("%s: maybe not a C0 device\n", __func__);
return rc;
}
rc = qm357xx_rom_write_cmd32_c0(handle, ROM_CMD_C0_GET_CHIP_VER);
if (rc)
return rc;
rc = qm357xx_rom_c0_poll_cmd_resp(handle);
if (rc)
return rc;
handle->chip_rev =
SSTC2UINT16(handle, CHIP_VERSION_CHIP_REV_PAYLOAD_OFFSET) &
0xFF;
handle->device_version = bswap_16(
SSTC2UINT16(handle, CHIP_VERSION_DEV_REV_PAYLOAD_OFFSET));
if ((handle->chip_rev != CHIP_REVISION_C0) &&
((handle->chip_rev != CHIP_REVISION_C2))) {
LOG_ERR("%s: wrong chip revision %#x\n", __func__,
handle->chip_rev);
handle->chip_rev = -1;
return -1;
}
rc = qm357xx_rom_c0_wait_ready(handle);
if (rc) {
LOG_ERR("%s: hmm something went wrong!!!\n", __func__);
return rc;
}
rc = qm357xx_rom_write_cmd32_c0(handle, ROM_CMD_C0_GET_SOC_INFO);
if (rc)
return rc;
rc = qm357xx_rom_c0_poll_cmd_resp(handle);
if (rc)
return rc;
/* skip the first 4 bytes */
soc_lcs_uuid = &(handle->sstc->payload[4]);
for (i = 0; i < QM357XX_ROM_SOC_ID_LEN; i++)
handle->qm357xx_soc_info.soc_id[i] =
soc_lcs_uuid[QM357XX_ROM_SOC_ID_LEN - i - 1];
soc_lcs_uuid += QM357XX_ROM_SOC_ID_LEN;
memcpy(&handle->qm357xx_soc_info.lcs_state, soc_lcs_uuid,
sizeof(uint32_t));
soc_lcs_uuid += 4;
for (i = 0; i < QM357XX_ROM_UUID_LEN; i++)
handle->qm357xx_soc_info.uuid[i] =
soc_lcs_uuid[QM357XX_ROM_UUID_LEN - i - 1];
/* Set device type */
handle->dev_gen = DEVICE_GEN_QM357XX;
/* Set rom ops */
handle->qm357xx_rom_ops.flash_unstitched_fw =
qm357xx_rom_c0_flash_unstitched_fw;
handle->qm357xx_rom_ops.flash_debug_cert =
qm357xx_rom_c0_flash_debug_cert;
handle->qm357xx_rom_ops.erase_debug_cert =
qm357xx_rom_c0_erase_debug_cert;
return 0;
}
#ifdef C0_WRITE_STATS
static uint64_t total_bytes, total_time_ns;
static uint32_t max_write_time_ns, min_write_time_ns = ~0;
static void update_write_max_chunk_stats(ktime_t start_time)
{
uint64_t elapsed_time_ns;
total_bytes += CHUNK_SIZE_C0;
elapsed_time_ns = ktime_to_ns(ktime_sub(ktime_get(), start_time));
total_time_ns += elapsed_time_ns;
if (elapsed_time_ns > max_write_time_ns)
max_write_time_ns = elapsed_time_ns;
if (elapsed_time_ns < min_write_time_ns)
min_write_time_ns = elapsed_time_ns;
}
static void dump_stats(void)
{
uint32_t nb_chunks = div_u64(total_bytes, CHUNK_SIZE_C0);
LOG_WARN(
"C0 flashing time stats: %llu bytes over %llu us (chunk size %u, write timings: mean %u us, min %u us, max %u us)\n",
total_bytes, div_u64(total_time_ns, 1000), CHUNK_SIZE_C0,
(uint32_t)(div_u64((div_u64(total_time_ns, nb_chunks)), 1000)),
min_write_time_ns / 1000, max_write_time_ns / 1000);
}
#endif
static int qm357xx_rom_c0_flash_data(struct qmrom_handle *handle,
struct firmware *fw, uint8_t cmd,
uint8_t resp, bool skip_last_check)
{
int rc, sent = 0;
const char *bin_data = (const char *)fw->data;
#ifdef C0_WRITE_STATS
ktime_t start_time;
#endif
while (sent < fw->size) {
uint32_t tx_bytes = fw->size - sent;
if (tx_bytes > CHUNK_SIZE_C0)
tx_bytes = CHUNK_SIZE_C0;
LOG_DBG("%s: sending command %#x with %" PRIu32 " bytes\n",
__func__, cmd, tx_bytes);
#ifdef C0_WRITE_STATS
start_time = ktime_get();
#endif
rc = qm357xx_rom_write_size_cmd32_c0(handle, cmd, tx_bytes,
bin_data);
if (rc)
return rc;
sent += tx_bytes;
bin_data += tx_bytes;
if (skip_last_check && sent == fw->size) {
LOG_INFO("%s: flashing done, quitting now\n", __func__);
break;
}
qm357xx_rom_c0_poll_soc(handle);
#ifdef C0_WRITE_STATS
if (tx_bytes == CHUNK_SIZE_C0)
update_write_max_chunk_stats(start_time);
#endif
qmrom_pre_read_c0(handle);
qmrom_read_c0(handle);
if (handle->sstc->payload[0] != resp) {
LOG_ERR("%s: wrong data result (%#x vs %#x)!!!\n",
__func__, handle->sstc->payload[0] & 0xff,
resp);
if (handle->sstc->payload[0] ==
ERR_FIRST_KEY_CERT_OR_FW_VER)
return PEG_ERR_FIRST_KEY_CERT_OR_FW_VER;
else
return SPI_PROTO_WRONG_RESP;
}
}
qmrom_msleep(SPI_READY_TIMEOUT_MS_C0);
return 0;
}
static int
qm357xx_rom_c0_flash_unstitched_fw(struct qmrom_handle *handle,
const struct unstitched_firmware *all_fws)
{
int rc = 0;
uint8_t flash_cmd = handle->qm357xx_soc_info.lcs_state ==
CC_BSV_SECURE_LCS ?
ROM_CMD_C0_SEC_LOAD_OEM_IMG_TO_RRAM :
ROM_CMD_C0_SEC_LOAD_ICV_IMG_TO_RRAM;
if (all_fws->key1_crt->data[HBK_LOC] == HBK_2E_ICV &&
handle->qm357xx_soc_info.lcs_state != CC_BSV_CHIP_MANUFACTURE_LCS) {
LOG_ERR("%s: Trying to flash an ICV fw on a non ICV platform\n",
__func__);
rc = -EINVAL;
goto end;
}
if (all_fws->key1_crt->data[HBK_LOC] == HBK_2E_OEM &&
handle->qm357xx_soc_info.lcs_state != CC_BSV_SECURE_LCS) {
LOG_ERR("%s: Trying to flash an OEM fw on a non OEM platform\n",
__func__);
rc = -EINVAL;
goto end;
}
LOG_DBG("%s: starting...\n", __func__);
/* Set RRAM write mode */
rc = qm357xx_rom_c0_wait_ready(handle);
if (rc)
goto end;
if (handle->chip_rev != CHIP_REVISION_C2) {
LOG_DBG("%s: sending ROM_CMD_C0_USE_INDIRECT_RRAM_WR command (chip_rev %x)\n",
__func__, handle->chip_rev);
rc = qm357xx_rom_write_cmd32_c0(
handle, ROM_CMD_C0_USE_INDIRECT_RRAM_WR);
if (rc)
goto end;
}
qm357xx_rom_c0_poll_soc(handle);
qmrom_pre_read_c0(handle);
qmrom_read_c0(handle);
qm357xx_rom_c0_poll_soc(handle);
LOG_ERR("%s: sending flash_cmd %u command\n", __func__, flash_cmd);
rc = qm357xx_rom_write_cmd32_c0(handle, flash_cmd);
if (rc)
goto end;
qm357xx_rom_c0_poll_cmd_resp(handle);
if (handle->sstc->payload[0] != WAITING_FOR_FIRST_KEY_CERT) {
LOG_ERR("%s: Waiting for WAITING_FOR_FIRST_KEY_CERT(%#x) but got %#x\n",
__func__, WAITING_FOR_FIRST_KEY_CERT,
handle->sstc->payload[0]);
goto end;
}
qm357xx_rom_c0_poll_soc(handle);
rc = qm357xx_rom_c0_flash_data(handle, all_fws->key1_crt,
ROM_CMD_C0_CERT_DATA,
WAITING_FOR_SECOND_KEY_CERT, false);
if (rc)
goto end;
rc = qm357xx_rom_c0_flash_data(handle, all_fws->key2_crt,
ROM_CMD_C0_CERT_DATA,
WAITING_FOR_CONTENT_CERT, false);
if (rc)
goto end;
rc = qm357xx_rom_c0_flash_data(handle, all_fws->fw_crt,
ROM_CMD_C0_CERT_DATA,
WAITING_FOR_SEC_FILE_DATA, false);
if (rc)
goto end;
rc = qm357xx_rom_c0_flash_data(handle, all_fws->fw_img,
ROM_CMD_C0_SEC_IMAGE_DATA,
WAITING_FOR_SEC_FILE_DATA, true);
#ifdef C0_WRITE_STATS
dump_stats();
#endif
end:
qmrom_free(all_fws->fw_img);
qmrom_free(all_fws->fw_crt);
qmrom_free(all_fws->key1_crt);
qmrom_free(all_fws->key2_crt);
return rc;
}
static int qm357xx_rom_c0_flash_debug_cert(struct qmrom_handle *handle,
struct firmware *dbg_cert)
{
int rc;
LOG_DBG("%s: starting...\n", __func__);
rc = qm357xx_rom_c0_wait_ready(handle);
if (rc)
return rc;
if (handle->chip_rev != CHIP_REVISION_C2) {
LOG_DBG("%s: using ROM_CMD_C0_USE_INDIRECT_RRAM_WR command\n",
__func__);
rc = qm357xx_rom_write_cmd32_c0(
handle, ROM_CMD_C0_USE_INDIRECT_RRAM_WR);
if (rc)
return rc;
}
qm357xx_rom_c0_poll_soc(handle);
qmrom_pre_read_c0(handle);
qmrom_read_c0(handle);
qm357xx_rom_c0_poll_soc(handle);
LOG_DBG("%s: sending ROM_CMD_C0_WRITE_DBG_CERT command\n", __func__);
rc = qm357xx_rom_write_cmd32_c0(handle, ROM_CMD_C0_WRITE_DBG_CERT);
if (rc)
return rc;
qm357xx_rom_c0_poll_cmd_resp(handle);
if (handle->sstc->payload[0] != WAITING_TO_DEBUG_CERTIFICATE_SIZE) {
LOG_ERR("%s: Waiting for WAITING_TO_DEBUG_CERTIFICATE_SIZE(%#x) but got %#x\n",
__func__, WAITING_TO_DEBUG_CERTIFICATE_SIZE,
handle->sstc->payload[0]);
return rc;
}
LOG_DBG("%s: sending ROM_CMD_C0_DEBUG_CERT_SIZE command\n", __func__);
rc = qm357xx_rom_write_size_cmd32_c0(handle, ROM_CMD_C0_DEBUG_CERT_SIZE,
sizeof(uint32_t),
(const char *)&dbg_cert->size);
qm357xx_rom_c0_poll_cmd_resp(handle);
if (handle->sstc->payload[0] != WAITING_FOR_DEBUG_CERT_DATA) {
LOG_ERR("%s: Waiting for WAITING_FOR_DEBUG_CERT_DATA(%#x) but got %#x\n",
__func__, WAITING_FOR_DEBUG_CERT_DATA,
handle->sstc->payload[0]);
return rc;
}
rc = qm357xx_rom_c0_flash_data(handle, dbg_cert, ROM_CMD_C0_CERT_DATA,
WAITING_FOR_DEBUG_CERT_DATA, true);
return 0;
}
static int qm357xx_rom_c0_erase_debug_cert(struct qmrom_handle *handle)
{
int rc;
LOG_DBG("%s: starting...\n", __func__);
rc = qm357xx_rom_c0_wait_ready(handle);
if (rc)
return rc;
LOG_DBG("%s: sending ROM_CMD_C0_ERASE_DBG_CERT command\n", __func__);
rc = qm357xx_rom_write_cmd32_c0(handle, ROM_CMD_C0_ERASE_DBG_CERT);
if (rc)
return rc;
qmrom_msleep(SPI_READY_TIMEOUT_MS_C0);
return 0;
}

View file

@ -0,0 +1,401 @@
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
/*
* Copyright 2022 Qorvo US, Inc.
*
*/
#include <linux/uwb/qmrom_utils.h>
#include <linux/uwb/qmrom_log.h>
#include <linux/uwb/qmrom_spi.h>
#include <linux/uwb/qmrom.h>
#include <linux/uwb/spi_rom_protocol.h>
#include <linux/uwb/qm357xx_fwpkg.h>
int qm357xx_rom_b0_probe_device(struct qmrom_handle *handle);
int qm357xx_rom_c0_probe_device(struct qmrom_handle *handle);
int qm357xx_rom_write_cmd(struct qmrom_handle *handle, uint8_t cmd)
{
handle->hstc->all = 0;
handle->hstc->host_flags.write = 1;
handle->hstc->ul = 1;
handle->hstc->len = 1;
handle->hstc->payload[0] = cmd;
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + handle->hstc->len);
}
int qm357xx_rom_write_cmd32(struct qmrom_handle *handle, uint32_t cmd)
{
handle->hstc->all = 0;
handle->hstc->host_flags.write = 1;
handle->hstc->ul = 1;
handle->hstc->len = sizeof(cmd);
memcpy(handle->hstc->payload, &cmd, sizeof(cmd));
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + handle->hstc->len);
}
int qm357xx_rom_write_size_cmd(struct qmrom_handle *handle, uint8_t cmd,
uint16_t data_size, const char *data)
{
handle->hstc->all = 0;
handle->hstc->host_flags.write = 1;
handle->hstc->ul = 1;
handle->hstc->len = data_size + 1;
handle->hstc->payload[0] = cmd;
memcpy(&handle->hstc->payload[1], data, data_size);
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + handle->hstc->len);
}
int qm357xx_rom_write_size_cmd32(struct qmrom_handle *handle, uint32_t cmd,
uint16_t data_size, const char *data)
{
handle->hstc->all = 0;
handle->hstc->host_flags.write = 1;
handle->hstc->ul = 1;
handle->hstc->len = data_size + sizeof(cmd);
memcpy(handle->hstc->payload, &cmd, sizeof(cmd));
memcpy(&handle->hstc->payload[sizeof(cmd)], data, data_size);
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + handle->hstc->len);
}
/*
* Unfortunately, B0 and C0 have different
* APIs to get the chip version...
*
*/
int qm357xx_rom_probe_device(struct qmrom_handle *handle)
{
int rc;
/* Test B0 first */
//rc = qm357xx_rom_b0_probe_device(handle);
//if (!rc)
//return rc;
/* Test C0 next */
rc = qm357xx_rom_c0_probe_device(handle);
if (!rc)
return rc;
/* None matched!!! */
return -1;
}
int qm357xx_rom_flash_dbg_cert(struct qmrom_handle *handle,
struct firmware *dbg_cert)
{
if (!handle->qm357xx_rom_ops.flash_debug_cert) {
LOG_ERR("%s: flash debug certificate not support on this device\n",
__func__);
return -EINVAL;
}
return handle->qm357xx_rom_ops.flash_debug_cert(handle, dbg_cert);
}
int qm357xx_rom_erase_dbg_cert(struct qmrom_handle *handle)
{
if (!handle->qm357xx_rom_ops.erase_debug_cert) {
LOG_ERR("%s: erase debug certificate not support on this device\n",
__func__);
return -EINVAL;
}
return handle->qm357xx_rom_ops.erase_debug_cert(handle);
}
int qm357xx_rom_flash_fw(struct qmrom_handle *handle, const struct firmware *fw)
{
int rc = 0;
struct unstitched_firmware all_fws = { 0 };
if (*(uint32_t *)&fw->data[0] ==
CRYPTO_MACRO_FIRMWARE_PACK_MAGIC_VALUE) {
//macro package detected
//write the FW UPDATER
rc = qm357xx_rom_unpack_fw_macro_pkg(fw, &all_fws);
if (rc) {
LOG_ERR("%s: Unpack macro FW package unsuccessful!\n",
__func__);
return rc;
}
} else {
rc = qm357xx_rom_unstitch_fw(fw, &all_fws, handle->chip_rev);
if (rc) {
LOG_ERR("%s: Unstitched fw flashing not supported yet\n",
__func__);
return rc;
}
}
rc = qm357xx_rom_flash_unstitched_fw(handle, &all_fws);
return rc;
}
int qm357xx_rom_flash_unstitched_fw(struct qmrom_handle *handle,
const struct unstitched_firmware *fw)
{
if (!handle->qm357xx_rom_ops.flash_unstitched_fw) {
LOG_ERR("%s: flash un-stitched firmware not supported on this device\n",
__func__);
return -EINVAL;
}
return handle->qm357xx_rom_ops.flash_unstitched_fw(handle, fw);
}
int qm357xx_rom_unstitch_fw(const struct firmware *fw,
struct unstitched_firmware *unstitched_fw,
enum chip_revision_e revision)
{
uint32_t tot_len = 0;
uint32_t fw_img_sz = 0;
uint32_t fw_crt_sz = 0;
uint32_t key1_crt_sz = 0;
uint32_t key2_crt_sz = 0;
uint8_t *p_key1;
uint8_t *p_key2;
uint8_t *p_crt;
uint8_t *p_fw;
int ret = 0;
if (fw->size < 2 * sizeof(key1_crt_sz)) {
LOG_ERR("%s: Not enough data (%zu) to unstitch\n", __func__,
fw->size);
return -EINVAL;
}
LOG_INFO("%s: Unstitching %zu bytes\n", __func__, fw->size);
/* key1 */
key1_crt_sz = *(uint32_t *)&fw->data[tot_len];
if (tot_len + key1_crt_sz + sizeof(key1_crt_sz) > fw->size) {
LOG_ERR("%s: Invalid or corrupted stitched file at offset \
%" PRIu32 " (key1)\n",
__func__, tot_len);
ret = -EINVAL;
goto out;
}
tot_len += sizeof(key1_crt_sz);
p_key1 = (uint8_t *)&fw->data[tot_len];
tot_len += key1_crt_sz;
/* key2 */
key2_crt_sz = *(uint32_t *)&fw->data[tot_len];
if (tot_len + key2_crt_sz + sizeof(key2_crt_sz) > fw->size) {
LOG_ERR("%s: Invalid or corrupted stitched file at offset \
%" PRIu32 " (key2)\n",
__func__, tot_len);
ret = -EINVAL;
goto out;
}
tot_len += sizeof(key2_crt_sz);
p_key2 = (uint8_t *)&fw->data[tot_len];
tot_len += key2_crt_sz;
/* cert */
fw_crt_sz = *(uint32_t *)&fw->data[tot_len];
if (tot_len + fw_crt_sz + sizeof(fw_crt_sz) > fw->size) {
LOG_ERR("%s: Invalid or corrupted stitched file at offset \
%" PRIu32 " (content cert)\n",
__func__, tot_len);
ret = -EINVAL;
goto out;
}
tot_len += sizeof(fw_crt_sz);
p_crt = (uint8_t *)&fw->data[tot_len];
tot_len += fw_crt_sz;
/* fw */
fw_img_sz = *(uint32_t *)&fw->data[tot_len];
if (tot_len + fw_img_sz + sizeof(fw_img_sz) != fw->size) {
LOG_ERR("%s: Invalid or corrupted stitched file at offset \
%" PRIu32 " (firmnware)\n",
__func__, tot_len);
ret = -EINVAL;
goto out;
}
tot_len += sizeof(fw_img_sz);
p_fw = (uint8_t *)&fw->data[tot_len];
qmrom_alloc(unstitched_fw->fw_img, fw_img_sz + sizeof(struct firmware));
if (unstitched_fw->fw_img == NULL) {
ret = -ENOMEM;
goto out;
}
qmrom_alloc(unstitched_fw->fw_crt, fw_crt_sz + sizeof(struct firmware));
if (unstitched_fw->fw_crt == NULL) {
ret = -ENOMEM;
goto err;
}
qmrom_alloc(unstitched_fw->key1_crt,
key1_crt_sz + sizeof(struct firmware));
if (unstitched_fw->key1_crt == NULL) {
ret = -ENOMEM;
goto err;
}
qmrom_alloc(unstitched_fw->key2_crt,
key2_crt_sz + sizeof(struct firmware));
if (unstitched_fw->key2_crt == NULL) {
ret = -ENOMEM;
goto err;
}
unstitched_fw->key1_crt->data =
(const uint8_t *)(unstitched_fw->key1_crt + 1);
unstitched_fw->key2_crt->data =
(const uint8_t *)(unstitched_fw->key2_crt + 1);
unstitched_fw->fw_crt->data =
(const uint8_t *)(unstitched_fw->fw_crt + 1);
unstitched_fw->fw_img->data =
(const uint8_t *)(unstitched_fw->fw_img + 1);
unstitched_fw->key1_crt->size = key1_crt_sz;
unstitched_fw->key2_crt->size = key2_crt_sz;
unstitched_fw->fw_crt->size = fw_crt_sz;
unstitched_fw->fw_img->size = fw_img_sz;
memcpy((void *)unstitched_fw->key1_crt->data, p_key1, key1_crt_sz);
memcpy((void *)unstitched_fw->key2_crt->data, p_key2, key2_crt_sz);
memcpy((void *)unstitched_fw->fw_crt->data, p_crt, fw_crt_sz);
memcpy((void *)unstitched_fw->fw_img->data, p_fw, fw_img_sz);
return 0;
err:
if (unstitched_fw->fw_img)
qmrom_free(unstitched_fw->fw_img);
if (unstitched_fw->fw_crt)
qmrom_free(unstitched_fw->fw_crt);
if (unstitched_fw->key1_crt)
qmrom_free(unstitched_fw->key1_crt);
if (unstitched_fw->key2_crt)
qmrom_free(unstitched_fw->key2_crt);
out:
return ret;
}
int qm357xx_rom_fw_macro_pkg_get_fw_idx(const struct firmware *fw, int idx,
uint32_t *fw_size, char **fw_data)
{
struct fw_macro_pkg_hdr_t *fw_macro_pkg_hdr =
(struct fw_macro_pkg_hdr_t *)&fw->data[0];
struct fw_img_desc_t *fw_pkg;
if (fw_macro_pkg_hdr->nb_descriptors >= 1 &&
idx < fw_macro_pkg_hdr->nb_descriptors) {
fw_pkg = (struct fw_img_desc_t *)(&fw_macro_pkg_hdr
->img_desc[idx]);
if (fw_pkg->offset + fw_pkg->length > fw->size) {
LOG_ERR("Wrong FW PKG offset = %04x; len = %04x; idx = %d!\n",
fw_pkg->offset, fw_pkg->length, idx);
return -EINVAL;
}
} else {
LOG_ERR("%s: No FW pkg found in macro package! nb_descriptors = %d\n",
__func__, fw_macro_pkg_hdr->nb_descriptors);
return -EINVAL;
}
*fw_size = fw_pkg->length;
*fw_data = (char *)&fw->data[fw_pkg->offset];
return 0;
}
int qm357xx_rom_unpack_fw_macro_pkg(const struct firmware *fw,
struct unstitched_firmware *all_fws)
{
int rc = 0;
uint8_t *p_key1;
uint8_t *p_key2;
uint8_t *p_crt;
uint8_t *p_fw;
char *fw_data;
uint32_t fw_size;
struct fw_pkg_img_hdr_t *fw_pkg_img_hdr;
rc = qm357xx_rom_fw_macro_pkg_get_fw_idx(fw, 0, &fw_size, &fw_data);
if (rc) {
LOG_ERR("%s: FW MACRO PACKAGE corupted = %d\n", __func__, rc);
goto err;
}
fw_pkg_img_hdr =
(struct fw_pkg_img_hdr_t *)(fw_data +
sizeof(struct fw_pkg_hdr_t));
if (fw_pkg_img_hdr->magic != CRYPTO_FIRMWARE_IMAGE_MAGIC_VALUE) {
LOG_ERR("%s: Invalid or corrupted file! magic = %04x\n",
__func__, fw_pkg_img_hdr->magic);
return -EINVAL;
}
qmrom_alloc(all_fws->fw_img,
fw_pkg_img_hdr->descs[0].length + sizeof(struct firmware));
if (all_fws->fw_img == NULL) {
rc = -ENOMEM;
goto err;
}
qmrom_alloc(all_fws->key1_crt,
CRYPTO_IMAGES_CERT_KEY_SIZE + sizeof(struct firmware));
if (all_fws->key1_crt == NULL) {
rc = -ENOMEM;
goto err;
}
qmrom_alloc(all_fws->key2_crt,
CRYPTO_IMAGES_CERT_KEY_SIZE + sizeof(struct firmware));
if (all_fws->key2_crt == NULL) {
rc = -ENOMEM;
goto err;
}
qmrom_alloc(all_fws->fw_crt,
CRYPTO_IMAGES_CERT_CONTENT_SIZE + sizeof(struct firmware));
if (all_fws->fw_crt == NULL) {
rc = -ENOMEM;
goto err;
}
all_fws->key1_crt->data = (const uint8_t *)(all_fws->key1_crt + 1);
all_fws->key2_crt->data = (const uint8_t *)(all_fws->key2_crt + 1);
all_fws->fw_crt->data = (const uint8_t *)(all_fws->fw_crt + 1);
all_fws->fw_img->data = (const uint8_t *)(all_fws->fw_img + 1);
all_fws->key1_crt->size = CRYPTO_IMAGES_CERT_KEY_SIZE;
all_fws->key2_crt->size = CRYPTO_IMAGES_CERT_KEY_SIZE;
all_fws->fw_crt->size = CRYPTO_IMAGES_CERT_CONTENT_SIZE;
all_fws->fw_img->size = fw_pkg_img_hdr->descs[0].length;
p_key1 = (uint8_t *)fw_pkg_img_hdr + fw_pkg_img_hdr->cert_chain_offset;
p_key2 = p_key1 + CRYPTO_IMAGES_CERT_KEY_SIZE;
p_crt = p_key2 + CRYPTO_IMAGES_CERT_KEY_SIZE;
p_fw = (uint8_t *)fw_pkg_img_hdr + fw_pkg_img_hdr->descs[0].offset;
memcpy((void *)all_fws->key1_crt->data, p_key1,
all_fws->key1_crt->size);
memcpy((void *)all_fws->key2_crt->data, p_key2,
all_fws->key2_crt->size);
memcpy((void *)all_fws->fw_crt->data, p_crt, all_fws->fw_crt->size);
memcpy((void *)all_fws->fw_img->data, p_fw, all_fws->fw_img->size);
return 0;
err:
if (all_fws->fw_img)
qmrom_free(all_fws->fw_img);
if (all_fws->fw_crt)
qmrom_free(all_fws->fw_crt);
if (all_fws->key1_crt)
qmrom_free(all_fws->key1_crt);
if (all_fws->key2_crt)
qmrom_free(all_fws->key2_crt);
return rc;
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,200 @@
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
/*
* Copyright 2022 Qorvo US, Inc.
*
*/
#include <linux/uwb/qmrom_utils.h>
#include <linux/uwb/qmrom_log.h>
#include <linux/uwb/qmrom_spi.h>
#include <linux/uwb/qmrom.h>
#include <linux/uwb/spi_rom_protocol.h>
#include <linux/gpio.h>
int qm357xx_rom_probe_device(struct qmrom_handle *handle);
int qm358xx_rom_probe_device(struct qmrom_handle *handle);
static void qmrom_free_stcs(struct qmrom_handle *h)
{
if (h->hstc)
qmrom_free(h->hstc);
if (h->sstc)
qmrom_free(h->sstc);
}
#ifdef CHECK_STCS
void check_stcs(const char *func, int line, struct qmrom_handle *h)
{
uint32_t *buff = (uint32_t *)h->hstc;
if (buff[MAX_STC_FRAME_LEN / sizeof(uint32_t)] != 0xfeeddeef) {
LOG_ERR("%s:%d - hstc %pK corrupted\n", func, line,
(void *)h->hstc);
} else {
LOG_ERR("%s:%d - hstc %pK safe\n", func, line, (void *)h->hstc);
}
buff = (uint32_t *)h->sstc;
if (buff[MAX_STC_FRAME_LEN / sizeof(uint32_t)] != 0xfeeddeef) {
LOG_ERR("%s:%d - sstc %pK corrupted\n", func, line,
(void *)h->sstc);
} else {
LOG_ERR("%s:%d - sstc %pK safe\n", func, line, (void *)h->sstc);
}
}
#endif
static int qmrom_allocate_stcs(struct qmrom_handle *h)
{
int rc = 0;
uint8_t *tx_buf = NULL, *rx_buf = NULL;
qmrom_alloc(tx_buf, MAX_STC_FRAME_LEN + sizeof(uint32_t));
if (tx_buf == NULL) {
rc = -ENOMEM;
goto out;
}
qmrom_alloc(rx_buf, MAX_STC_FRAME_LEN + sizeof(uint32_t));
if (rx_buf == NULL) {
qmrom_free(tx_buf);
rc = -ENOMEM;
goto out;
}
#ifdef CHECK_STCS
((uint32_t *)tx_buf)[MAX_STC_FRAME_LEN / sizeof(uint32_t)] = 0xfeeddeef;
((uint32_t *)rx_buf)[MAX_STC_FRAME_LEN / sizeof(uint32_t)] = 0xfeeddeef;
#endif
h->hstc = (struct stc *)tx_buf;
h->sstc = (struct stc *)rx_buf;
return rc;
out:
qmrom_free_stcs(h);
return rc;
}
int qmrom_pre_read(struct qmrom_handle *handle)
{
handle->hstc->all = 0;
handle->hstc->host_flags.pre_read = 1;
handle->hstc->ul = 1;
handle->hstc->len = 0;
handle->hstc->payload[0] = 0;
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + handle->hstc->len);
}
int qmrom_read(struct qmrom_handle *handle)
{
size_t rd_size = handle->sstc->len;
if (rd_size > MAX_STC_FRAME_LEN)
return SPI_ERR_INVALID_STC_LEN;
LOG_DBG("%s: reading %zu bytes...\n", __func__, rd_size);
memset(handle->hstc, 0, sizeof(struct stc) + rd_size);
handle->hstc->host_flags.read = 1;
handle->hstc->ul = 1;
handle->hstc->len = handle->sstc->len;
return qmrom_spi_transfer(handle->spi_handle, (char *)handle->sstc,
(const char *)handle->hstc,
sizeof(struct stc) + rd_size);
}
/*
* Unfortunately, A0, B0 and C0 have different
* APIs to get the chip version...
*
*/
int qmrom_probe_device(struct qmrom_handle *handle,
enum device_generation_e dev_gen_hint)
{
int rc = -1;
if (dev_gen_hint == DEVICE_GEN_QM358XX ||
dev_gen_hint == DEVICE_GEN_UNKNOWN)
rc = qm358xx_rom_probe_device(handle);
if (rc && (dev_gen_hint == DEVICE_GEN_QM357XX ||
dev_gen_hint == DEVICE_GEN_UNKNOWN))
rc = qm357xx_rom_probe_device(handle);
return rc;
}
struct qmrom_handle *qmrom_init(void *spi_handle, void *reset_handle,
void *ss_rdy_handle, int spi_speed,
int comms_retries, reset_device_fn reset,
enum device_generation_e dev_gen_hint)
{
struct qmrom_handle *handle;
int rc;
qmrom_alloc(handle, sizeof(struct qmrom_handle));
if (!handle) {
LOG_ERR("%s: Couldn't allocate %zu bytes...\n", __func__,
sizeof(struct qmrom_handle));
return NULL;
}
rc = qmrom_allocate_stcs(handle);
if (rc) {
LOG_ERR("%s: Couldn't allocate stcs...\n", __func__);
qmrom_free(handle);
return NULL;
}
handle->spi_handle = spi_handle;
handle->reset_handle = reset_handle;
handle->ss_rdy_handle = ss_rdy_handle;
handle->comms_retries = comms_retries;
handle->chip_rev = CHIP_REVISION_UNKNOWN;
handle->device_version = -1;
handle->spi_speed = spi_speed;
handle->dev_ops.reset = reset;
rc = qmrom_probe_device(handle, dev_gen_hint);
if (rc) {
LOG_ERR("%s: qmrom_probe_device returned %d!\n", __func__, rc);
qmrom_free_stcs(handle);
qmrom_free(handle);
return NULL;
}
check_stcs(__func__, __LINE__, handle);
return handle;
}
void qmrom_deinit(struct qmrom_handle *handle)
{
LOG_DBG("Deinitializing %pK\n", (void *)handle);
qmrom_free_stcs(handle);
qmrom_free(handle);
}
int qmrom_reboot_bootloader(struct qmrom_handle *handle)
{
int rc;
qmrom_msleep(500);
rc = qmrom_spi_set_cs_level(handle->spi_handle, 1);
if (rc) {
LOG_ERR("%s: spi_set_cs_level(0) failed with %d\n", __func__,
rc);
return rc;
}
qmrom_msleep(SPI_RST_LOW_DELAY_MS);
handle->dev_ops.reset(handle->reset_handle);
qmrom_msleep(SPI_RST_LOW_DELAY_MS);
rc = qmrom_spi_set_cs_level(handle->spi_handle, 0);
if (rc) {
LOG_ERR("%s: spi_set_cs_level(1) failed with %d\n", __func__,
rc);
return rc;
}
qmrom_msleep(SPI_RST_LOW_DELAY_MS);
return 0;
}

View file

@ -0,0 +1,78 @@
/*
* Copyright 2021 Qorvo US, Inc.
*
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
*
* This file is provided under the Apache License 2.0, or the
* GNU General Public License v2.0.
*
*/
#include <linux/uwb/qmrom_log.h>
#define LOG_PRINT(lvl, ...) \
do { \
switch (lvl) { \
case LOG_ERR: \
LOG_ERR(__VA_ARGS__); \
break; \
case LOG_WARN: \
LOG_WARN(__VA_ARGS__); \
break; \
case LOG_INFO: \
LOG_INFO(__VA_ARGS__); \
break; \
case LOG_DBG: \
LOG_DBG(__VA_ARGS__); \
break; \
default: \
break; \
} \
} while (0)
enum log_level_e __log_level__ = LOG_DBG;
void set_log_level(enum log_level_e lvl)
{
__log_level__ = lvl;
}
#ifdef __KERNEL__
#include <linux/device.h>
struct device *__qmrom_log_dev__ = NULL;
void qmrom_set_log_device(struct device *dev, enum log_level_e lvl)
{
__qmrom_log_dev__ = dev;
__log_level__ = lvl;
}
#endif
void hexdump(enum log_level_e lvl, void *_array, unsigned short length)
{
unsigned char *array = _array;
int i;
if (lvl < __log_level__)
return;
for (i = 0; i < length; i += 16) {
int j = 0;
for (; j < 15 && i + j + 1 < length; j++)
LOG_PRINT(lvl, "0x%02x, ", array[i + j]);
LOG_PRINT(lvl, "0x%02x\n", array[i + j]);
}
}
void hexrawdump(enum log_level_e lvl, void *_array, unsigned short length)
{
unsigned char *array = _array;
int i;
if (lvl < __log_level__)
return;
for (i = 0; i < length; i++) {
LOG_PRINT(lvl, "%02x", array[i]);
}
}

1046
drivers/uwb/qm35/qm35-spi.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,32 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2021 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 trace
*/
#include <linux/module.h>
#define CREATE_TRACE_POINTS
#include "qm35-trace.h"

View file

@ -0,0 +1,129 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2021 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 trace
*/
#undef TRACE_SYSTEM
#define TRACE_SYSTEM qm35
#if !defined(_QM35_TRACE_H) || defined(TRACE_HEADER_MULTI_READ)
#define _QM35_TRACE_H
#include <linux/tracepoint.h>
#include "hsspi.h"
#define show_work_type(type) \
__print_symbolic(type, \
{ \
HSSPI_WORK_TX, \
"TX", \
}, \
{ \
HSSPI_WORK_COMPLETION, \
"COMPLETION", \
}, \
{ \
-1, \
"no", \
})
TRACE_DEFINE_ENUM(HSSPI_WORK_TX);
TRACE_DEFINE_ENUM(HSSPI_WORK_COMPLETION);
TRACE_EVENT(hsspi_get_work, TP_PROTO(const struct device *dev, int type),
TP_ARGS(dev, type),
TP_STRUCT__entry(__string(dev, dev_name(dev)) __field(int, type)),
TP_fast_assign(__assign_str(dev, dev_name(dev));
__entry->type = type;),
TP_printk("[%s]: %s work", __get_str(dev),
show_work_type(__entry->type)));
#define show_hsspi_state(state) \
__print_symbolic(state, \
{ \
HSSPI_RUNNING, \
"running", \
}, \
{ \
HSSPI_ERROR, \
"error", \
}, \
{ \
HSSPI_STOPPED, \
"stopped", \
})
TRACE_DEFINE_ENUM(HSSPI_RUNNING);
TRACE_DEFINE_ENUM(HSSPI_ERROR);
TRACE_DEFINE_ENUM(HSSPI_STOPPED);
TRACE_EVENT(hsspi_is_txrx_waiting,
TP_PROTO(const struct device *dev, bool is_empty,
enum hsspi_state state),
TP_ARGS(dev, is_empty, state),
TP_STRUCT__entry(__string(dev, dev_name(dev))
__field(bool, is_empty)
__field(enum hsspi_state, state)),
TP_fast_assign(__assign_str(dev, dev_name(dev));
__entry->is_empty = is_empty;
__entry->state = state;),
TP_printk("[%s]: is_empty: %d state: %s", __get_str(dev),
__entry->is_empty, show_hsspi_state(__entry->state)));
#define STC_ENTRY(header) \
__field(u8, header##flags) __field(u8, header##ul) \
__field(u16, header##length)
#define STC_ASSIGN(header, var) \
__entry->header##flags = var->flags; \
__entry->header##ul = var->ul; \
__entry->header##length = var->length;
#define STC_FMT "flags:0x%hhx ul:%hhd len:%hd"
#define STC_ARG(header) \
__entry->header##flags, __entry->header##ul, __entry->header##length
TRACE_EVENT(hsspi_spi_xfer,
TP_PROTO(const struct device *dev, const struct stc_header *host,
struct stc_header *soc, int ret),
TP_ARGS(dev, host, soc, ret),
TP_STRUCT__entry(__string(dev, dev_name(dev)) STC_ENTRY(host)
STC_ENTRY(soc) __field(int, ret)),
TP_fast_assign(__assign_str(dev, dev_name(dev));
STC_ASSIGN(host, host); STC_ASSIGN(soc, soc);
__entry->ret = ret;),
TP_printk("[%s]: host " STC_FMT " | soc " STC_FMT " rc=%d",
__get_str(dev), STC_ARG(host), STC_ARG(soc),
__entry->ret));
#endif /* _QM35_TRACE_H */
/* This part must be outside protection */
#undef TRACE_INCLUDE_PATH
#define TRACE_INCLUDE_PATH ../../../../motorola/kernel/modules/drivers/uwb/qm35
// clang-format off
#define TRACE_INCLUDE_FILE qm35-trace
// clang-format on
#include <trace/define_trace.h>

100
drivers/uwb/qm35/qm35.h Normal file
View file

@ -0,0 +1,100 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __QM35_H___
#define __QM35_H___
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/spi/spi.h>
#include <linux/spinlock.h>
#include <linux/miscdevice.h>
#include "uci_ioctls.h"
#include "hsspi.h"
#include "hsspi_uci.h"
#include "hsspi_coredump.h"
#include "hsspi_log.h"
#include "debug.h"
#define DEBUG_CERTIFICATE_SIZE 2560
#define QM_RESET_LOW_MS 2
/*
* value found using a SALAE
*/
#define QM_BOOT_MS 450
#define QM_BEFORE_RESET_MS 450
#define DRV_VERSION "7.1.0-rc2"
struct regulator;
/**
* struct qm35_ctx - QM35 driver context
*
*/
struct qm35_ctx {
unsigned int state;
struct miscdevice uci_dev;
struct spi_device *spi;
struct gpio_desc *gpio_csn;
struct gpio_desc *gpio_reset;
struct gpio_desc *gpio_ss_rdy;
struct gpio_desc *gpio_ss_irq;
struct gpio_desc *gpio_exton;
struct gpio_desc *gpio_wakeup;
spinlock_t lock;
bool out_data_wait;
bool out_active;
bool soc_error;
struct hsspi hsspi;
struct uci_layer uci_layer;
struct coredump_layer coredump_layer;
struct log_layer log_layer;
struct debug debug;
struct regulator *vdd1;
struct regulator *vdd2;
struct regulator *vdd3;
struct regulator *vdd4;
bool regulators_enabled;
bool log_qm_traces;
};
static inline unsigned int qm35_get_state(struct qm35_ctx *qm35_hdl)
{
return qm35_hdl->state;
}
static inline void qm35_set_state(struct qm35_ctx *qm35_hdl, int state)
{
unsigned long flags;
spin_lock_irqsave(&qm35_hdl->lock, flags);
qm35_hdl->state = state;
spin_unlock_irqrestore(&qm35_hdl->lock, flags);
}
static inline int qm35_reset(struct qm35_ctx *qm35_hdl, int timeout_ms)
{
if (qm35_hdl->gpio_reset) {
qm35_set_state(qm35_hdl, QM35_CTRL_STATE_RESET);
gpiod_set_value(qm35_hdl->gpio_reset, 1);
usleep_range(timeout_ms * 1000, timeout_ms * 1000);
gpiod_set_value(qm35_hdl->gpio_reset, 0);
qm35_set_state(qm35_hdl, QM35_CTRL_STATE_UNKNOWN);
return 0;
}
return -ENODEV;
}
int qm35_reset_sync(struct qm35_ctx *qm35_hdl);
int qm_get_dev_id(struct qm35_ctx *qm35_hdl, uint16_t *dev_id);
int qm_get_soc_id(struct qm35_ctx *qm35_hdl, uint8_t *soc_id);
void qm35_hsspi_start(struct qm35_ctx *qm35_hdl);
void qm35_hsspi_stop(struct qm35_ctx *qm35_hdl);
void qmrom_set_fwname(const char *name);
#endif /* __QM35_H___ */

230
drivers/uwb/qm35/qm35_rb.c Normal file
View file

@ -0,0 +1,230 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2022 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 Ringbuffer
*/
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/sched.h>
#include "qm35_rb.h"
static bool __rb_can_pop(struct rb *rb, uint32_t *tail)
{
// if tail equals with the head index, no data can be popped
return *tail != rb->head;
}
bool rb_can_pop(struct rb *rb)
{
bool can_pop = false;
mutex_lock(&rb->lock);
can_pop = __rb_can_pop(rb, &rb->rdtail);
mutex_unlock(&rb->lock);
return can_pop;
}
static rb_entry_size_t __rb_next_size(struct rb *rb, uint32_t *tail)
{
rb_entry_size_t next_packet_size = 0;
// check if something is available to pop from the ring buffer
if (!__rb_can_pop(rb, tail))
return 0;
// read next packet size
memcpy(&next_packet_size, rb->buf + *tail, sizeof(next_packet_size));
if (next_packet_size == 0) {
if (*tail == 0)
return 0;
// if the next packet size is 0x00, it is an
// indication that we hit the end marker so we should
// start reading from the beginning
*tail = 0;
memcpy(&next_packet_size, rb->buf + *tail,
sizeof(next_packet_size));
}
return next_packet_size;
}
rb_entry_size_t rb_next_size(struct rb *rb)
{
rb_entry_size_t next_packet_size = 0;
mutex_lock(&rb->lock);
next_packet_size = __rb_next_size(rb, &rb->rdtail);
mutex_unlock(&rb->lock);
return next_packet_size;
}
static char *__rb_pop(struct rb *rb, rb_entry_size_t *len, uint32_t *tail)
{
char *trace;
*len = __rb_next_size(rb, tail);
if (*len == 0)
return 0;
// advance ptr with sizeof(next_entry_size)
*tail += sizeof(*len);
// allocate memory for data
trace = kmalloc(*len, GFP_KERNEL);
// get the data
memcpy(trace, rb->buf + *tail, *len);
*tail += *len;
return trace;
}
char *rb_pop(struct rb *rb, rb_entry_size_t *len)
{
char *entry;
mutex_lock(&rb->lock);
entry = __rb_pop(rb, len, &rb->rdtail);
mutex_unlock(&rb->lock);
return entry;
}
static bool __rb_skip(struct rb *rb)
{
rb_entry_size_t next_entry_size;
// read next packet size
memcpy(&next_entry_size, rb->buf + rb->tail, sizeof(next_entry_size));
if (next_entry_size == 0) {
if (rb->tail == 0)
return false;
// if the next packet size is 0x00, it is an
// indication that we hit the end marker so we should
// start reading from the beginning
rb->tail = 0;
memcpy(&next_entry_size, rb->buf + rb->tail,
sizeof(next_entry_size));
}
// advance ptr with sizeof(next_entry_size)
rb->tail += sizeof(next_entry_size) + next_entry_size;
return true;
}
int rb_push(struct rb *rb, const char *data, rb_entry_size_t len)
{
rb_entry_size_t entry_size;
uint32_t available_to_end;
// doesn't make sense to push a packet with the payload len 0.
if (len == 0)
return 1;
mutex_lock(&rb->lock);
// calculate how much data we want to store
// we add the size of the trace with the size of the size of the trace
// because we want to store the size as well for reading
entry_size = sizeof(len) + len;
// available data to the end of the ring buffer
available_to_end = rb->size - rb->head;
// we check that by writing the buffer still has 2 bytes left
// to write the buffer end marker 0x0000 if not, we just add
// the marker and start from beginning
if (available_to_end <= entry_size + sizeof(rb_entry_size_t)) {
memset(rb->buf + rb->head, 0, sizeof(rb_entry_size_t));
if (rb->tail >= rb->head)
rb->tail = 0;
if (rb->rdtail >= rb->head)
rb->rdtail = 0;
rb->head = 0;
}
// check if writing the new data will cause the head index to
// overrun the tail index
while ((rb->head <= rb->tail) && (rb->head + entry_size >= rb->tail)) {
bool equal_tails = rb->tail == rb->rdtail;
// need to adjust the tail to point to the next log
// not overwritten
bool skipped = __rb_skip(rb);
if (equal_tails)
rb->rdtail = rb->tail;
if (!skipped)
break;
}
// copy the size first
memcpy(rb->buf + rb->head, &len, sizeof(len));
// move the head by sizeof(trace->size) to be in place for copying the data
rb->head += sizeof(len);
// copy the data
memcpy(rb->buf + rb->head, data, len);
// update the head ptr to the next write
rb->head += len;
mutex_unlock(&rb->lock);
return 0;
}
void rb_reset(struct rb *rb)
{
mutex_lock(&rb->lock);
rb->rdtail = rb->tail;
mutex_unlock(&rb->lock);
}
int rb_init(struct rb *rb, uint32_t size)
{
rb->buf = kmalloc(size, GFP_KERNEL);
if (!rb->buf)
return -ENOMEM;
mutex_init(&rb->lock);
rb->head = 0;
rb->tail = 0;
rb->rdtail = 0;
rb->size = size;
return 0;
}
void rb_deinit(struct rb *rb)
{
kfree(rb->buf);
}

View file

@ -0,0 +1,55 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2022 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 Ringbuffer
*/
#ifndef __QM35_RB_H__
#define __QM35_RB_H__
#include <linux/types.h>
typedef uint16_t rb_entry_size_t;
struct rb {
uint8_t *buf;
uint32_t size;
uint32_t head;
uint32_t tail;
uint32_t rdtail;
struct mutex lock;
};
bool rb_can_pop(struct rb *rb);
rb_entry_size_t rb_next_size(struct rb *rb);
char *rb_pop(struct rb *rb, rb_entry_size_t *len);
int rb_push(struct rb *rb, const char *data, rb_entry_size_t len);
void rb_reset(struct rb *rb);
int rb_init(struct rb *rb, uint32_t size);
void rb_deinit(struct rb *rb);
#endif // __QM35_RB_H__

View file

@ -0,0 +1,192 @@
// SPDX-License-Identifier: GPL-2.0
/*
* This file is part of the QM35 UCI stack for linux.
*
* Copyright (c) 2021 Qorvo US, Inc.
*
* This software is provided under the GNU General Public License, version 2
* (GPLv2), as well as under a Qorvo commercial license.
*
* You may choose to use this software under the terms of the GPLv2 License,
* version 2 ("GPLv2"), as published by the Free Software Foundation.
* You should have received a copy of the GPLv2 along with this program. If
* not, see <http://www.gnu.org/licenses/>.
*
* This program is distributed under the GPLv2 in the hope that it will be
* useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GPLv2 for more
* details.
*
* If you cannot meet the requirements of the GPLv2, you may not use this
* software for any purpose without first obtaining a commercial license from
* Qorvo.
* Please contact Qorvo to inquire about licensing terms.
*
* QM35 FW ROM protocol SPI ops
*/
#include <linux/spi/spi.h>
#include <linux/uwb/qmrom_spi.h>
#include <linux/uwb/spi_rom_protocol.h>
#include "qm35.h"
/* TODO Compile QM358XX code */
int qm358xx_rom_probe_device(struct qmrom_handle *handle)
{
return -1;
}
static const char *fwname = NULL;
static unsigned int speed_hz;
extern int trace_spi_xfers;
void qmrom_set_fwname(const char *name)
{
fwname = name;
}
int qmrom_spi_transfer(void *handle, char *rbuf, const char *wbuf, size_t size)
{
struct spi_device *spi = (struct spi_device *)handle;
int rc;
struct spi_transfer xfer[] = {
{
.tx_buf = wbuf,
.rx_buf = rbuf,
.len = size,
.speed_hz = qmrom_spi_get_freq(),
},
};
rc = spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
if (trace_spi_xfers) {
print_hex_dump(KERN_DEBUG, "qm35 tx:", DUMP_PREFIX_NONE, 16, 1,
wbuf, size, false);
print_hex_dump(KERN_DEBUG, "qm35 rx:", DUMP_PREFIX_NONE, 16, 1,
rbuf, size, false);
}
return rc;
}
#if 0
int qmrom_spi_set_cs_level(void *handle, int level)
{
struct spi_device *spi = (struct spi_device *)handle;
uint8_t dummy = 0;
struct spi_transfer xfer[] = {
{
.tx_buf = &dummy,
.len = 1,
.cs_change = !level,
.speed_hz = qmrom_spi_get_freq(),
},
};
return spi_sync_transfer(spi, xfer, ARRAY_SIZE(xfer));
}
#else
extern void qm35_set_csn_level(int level);
int qmrom_spi_set_cs_level(void *handle, int level)
{
qm35_set_csn_level(level);
return 0;
}
#endif
int qmrom_spi_reset_device(void *reset_handle)
{
struct qm35_ctx *qm35_hdl = (struct qm35_ctx *)reset_handle;
return qm35_reset(qm35_hdl, SPI_RST_LOW_DELAY_MS);
}
const struct firmware *qmrom_spi_get_firmware(void *handle,
struct qmrom_handle *qmrom_h,
bool *is_macro_pkg,
bool use_prod_fw)
{
const struct firmware *fw;
struct spi_device *spi = handle;
const char *fw_name;
int ret;
uint32_t lcs_state = qmrom_h->qm357xx_soc_info.lcs_state;
if (!fwname) {
if (lcs_state != CC_BSV_SECURE_LCS) {
dev_warn(&spi->dev, "LCS state is not secure.");
}
if (use_prod_fw)
fw_name = "qm35_fw_pkg_prod.bin";
else if (qmrom_h->chip_rev >= CHIP_REVISION_C0)
fw_name = "qm35_fw_pkg.bin";
else
fw_name = "qm35_b0_fw_pkg.bin";
*is_macro_pkg = true;
} else {
fw_name = fwname;
}
dev_info(&spi->dev, "Requesting fw %s!\n", fw_name);
ret = request_firmware(&fw, fw_name, &spi->dev);
if (ret) {
if (lcs_state != CC_BSV_SECURE_LCS) {
dev_warn(&spi->dev, "LCS state is not secure.");
}
/* Didn't get the macro package, try the stitched image */
*is_macro_pkg = false;
if (use_prod_fw)
fw_name = "qm35_oem_prod.bin";
else
fw_name = "qm35_oem.bin";
dev_info(&spi->dev, "Requesting fw %s!\n", fw_name);
ret = request_firmware(&fw, fw_name, &spi->dev);
if (!ret) {
dev_info(&spi->dev, "Firmware size is %zu!\n",
fw->size);
return fw;
}
release_firmware(fw);
dev_err(&spi->dev,
"request_firmware failed (ret=%d) for '%s'\n", ret,
fw_name);
return NULL;
}
dev_info(&spi->dev, "Firmware size is %zu!\n", fw->size);
return fw;
}
void qmrom_spi_release_firmware(const struct firmware *fw)
{
release_firmware(fw);
}
int qmrom_spi_wait_for_ready_line(void *handle, unsigned int timeout_ms)
{
int count_down = (int)timeout_ms;
while (!gpiod_get_value(handle) && (--count_down >= 0)) {
udelay(1000);
}
return gpiod_get_value(handle) ? 0 : -1;
}
void qmrom_spi_set_freq(unsigned int freq)
{
speed_hz = freq;
}
unsigned int qmrom_spi_get_freq()
{
return speed_hz;
}

View file

@ -0,0 +1,26 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __UCI_IOCTLS_H___
#define __UCI_IOCTLS_H___
#include <asm/ioctl.h>
#define UCI_DEV_NAME "uci"
#define UCI_IOC_TYPE 'U'
#define QM35_CTRL_RESET _IOR(UCI_IOC_TYPE, 1, unsigned int)
#define QM35_CTRL_GET_STATE _IOR(UCI_IOC_TYPE, 2, unsigned int)
#define QM35_CTRL_FW_UPLOAD _IOR(UCI_IOC_TYPE, 3, unsigned int)
#define QM35_CTRL_POWER _IOW(UCI_IOC_TYPE, 4, unsigned int)
/* qm35 states */
enum { QM35_CTRL_STATE_UNKNOWN = 0x0000,
QM35_CTRL_STATE_OFF = 0x0001,
QM35_CTRL_STATE_RESET = 0x0002,
QM35_CTRL_STATE_COREDUMP = 0x0004,
QM35_CTRL_STATE_READY = 0x0008,
QM35_CTRL_STATE_FW_DOWNLOADING = 0x0010,
QM35_CTRL_STATE_UCI_APP = 0x0020,
};
#endif /* __UCI_IOCTLS_H___ */

View file

@ -0,0 +1,10 @@
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
/*
* Copyright 2022 Qorvo US, Inc.
*
*/
#ifndef BYTESWAP_H
#define BYTESWAP_H
#define bswap_16 __builtin_bswap16
#endif

View file

@ -0,0 +1,62 @@
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
/*
* Copyright 2021 Qorvo US, Inc.
*
*/
#ifndef __FWUPDATER_H__
#define __FWUPDATER_H__
#ifndef __KERNEL__
#include <stdint.h>
#include <stddef.h>
#else
#include <linux/types.h>
#endif
#include "qmrom.h"
#include "qm357xx_fwpkg.h"
enum fw_pkg_error_e {
FW_PKG_SUCESS = 0,
FW_PKG_DOWNLOAD_ERROR,
FW_PKG_MAGIC_NUM_INVALID,
FW_PKG_VERSION_INVALID,
FW_PKG_ENCRYPTION_MODE_INVALID,
FW_PKG_IMG_HDR_MAGIC_NUM_INVALID,
FW_PKG_IMG_HDR_VERSION_INVALID,
FW_PKG_IMG_HDR_CERT_SIZE_INVALID,
FW_PKG_IMG_HDR_IMG_NUM_INVALID,
FW_PKG_PLD_CHK_MAGIC_NUM_INVALID,
FW_PKG_PLD_CHK_VERSION_INVALID,
FW_PKG_PLD_CHK_LENGTH_INVALID,
FW_PKG_HDR_CRYPTO_ERROR,
FW_PKG_HDR_CRYPTO_INVALID,
FW_PKG_HDR_PAYLOAD_SIZE_INVALID,
FW_PKG_IMG_HDR_SIZE_INVALID,
FW_PKG_IMG_HDR_CRYPTO_ERROR,
FW_PKG_IMG_HDR_NUMDESCS_INVALID,
FW_PKG_CERT_CHAIN_SIZE_INVALID,
FW_PKG_CERT_CHAIN_INVALID,
FW_PKG_CERT_INSTALLED_FW_VERSION_INVALID,
FW_PKG_CERT_KEY1_CHECK_INVALID,
FW_PKG_CERT_KEY2_CHECK_INVALID,
FW_PKG_CERT_CONTENT_CHECK_INVALID,
FW_PKG_CERT_CHAIN_CRYPTO_ERROR,
FW_PKG_CERT_CHAIN_IMG_RANGE_INVALID,
FW_PKG_CERT_CHAIN_WRITE_ERROR,
FW_PKG_IMG_CHUNK_CRYPTO_ERROR,
FW_PKG_IMG_CHUNK_WRITE_ERROR,
FW_PKG_IMG_FIXUP_FAILED
};
/*! Firmware Update Status fields */
struct fw_updater_status_t {
uint32_t status;
uint32_t suberror;
} __attribute__((packed));
void run_fwupdater_unit_tests(void *spi_handle);
int run_fwupdater(struct qmrom_handle *handle, char *fwpkg_bin, size_t size);
#endif /* __FWUPDATER_H__ */

View file

@ -0,0 +1,132 @@
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
/*
* Copyright 2021 Qorvo US, Inc.
*
*/
#ifndef __QM357XX_FWPKG_H__
#define __QM357XX_FWPKG_H__
/*! Pkg version word conversion macro */
#define PKG_VER_TO_U32(maj, min) (((maj) << 16) | ((min) << 24))
/*! Pkg magic word conversion macro */
#define MAGIC_STR_TO_U32(x) \
((uint32_t)((x[3]) | (x[2] << 8) | (x[1] << 16) | (x[0] << 24)))
// Package size defines
#define CRYPTO_FIRMWARE_PACK_ENC_DATA_SIZE 16
#define CRYPTO_FIRMWARE_PACK_FW_VERSION_SIZE 32
#define CRYPTO_FIRMWARE_PACK_TAG_SIZE 16
// Firmware image size define
#define CRYPTO_FW_PKG_IMG_HDR_IMG_NUM_MAX 8
// Field values for macro firmware package
#define CRYPTO_MACRO_FIRMWARE_PACK_MAGIC_VALUE MAGIC_STR_TO_U32("FWMP")
// Field values for firmware package
#define CRYPTO_FIRMWARE_PACK_MAGIC_VALUE MAGIC_STR_TO_U32("CFWP")
#define CRYPTO_FIRMWARE_PACK_VERSION PKG_VER_TO_U32(1, 0)
#define CRYPTO_FIRMWARE_PACK_ENC_MODE_NOT_ENCRYPTED 0x0
#define CRYPTO_FIRMWARE_PACK_ENC_ALGO_128BIT_AES_CTR 0x00
#define CRYPTO_FIRMWARE_PACK_ENC_KEY_L2_SIZE 64
// Field values for firmware image
#define CRYPTO_FIRMWARE_IMAGE_MAGIC_VALUE MAGIC_STR_TO_U32("IMGS")
#define CRYPTO_FIRMWARE_IMAGE_VERSION PKG_VER_TO_U32(1, 0)
// Field values for firmware chunks
#define CRYPTO_FIRMWARE_CHUNK_MAGIC_VALUE MAGIC_STR_TO_U32("CFWC")
#define CRYPTO_FIRMWARE_CHUNK_VERSION PKG_VER_TO_U32(1, 0)
#define CRYPTO_FIRMWARE_CHUNK_MIN_SIZE 16
// fw certificate sizes
#define CRYPTO_IMAGES_CERT_KEY_SIZE 840
#define CRYPTO_IMAGES_CERT_CONTENT_SIZE 868
#define CRYPTO_IMAGES_CERT_PKG_SIZE \
(2 * CRYPTO_IMAGES_CERT_KEY_SIZE + CRYPTO_IMAGES_CERT_CONTENT_SIZE)
#define CRYPTO_IMAGES_NB_CERTS 3
#define CRYPTO_IMAGES_MAX_NB_IMAGES 8
#define CRYPTO_FIRMWARE_IMAGE_HDR_TOTAL_SIZE \
(sizeof(struct fw_pkg_img_hdr_t) + \
CRYPTO_IMAGES_NB_CERTS * sizeof(struct fw_img_desc_t))
/*! Encryption mode enum. */
enum fw_pkg_enc_mode_e {
CRYPTO_FIRMWARE_PACK_ENC_MODE_NONE = 0x0,
CRYPTO_FIRMWARE_PACK_ENC_MODE_ENCRYPTED = 0x1
};
/*! Package type enum. */
enum fw_pkg_package_type_e {
CRYPTO_FIRMWARE_PACK_PACKAGE_TYPE_ICV = 0x01,
CRYPTO_FIRMWARE_PACK_PACKAGE_TYPE_OEM = 0x02
};
/*! IV type enum. */
enum fw_pkg_iv_type_e {
CRYPTO_FIRMWARE_PACK_IV_TYPE_HDR = 0x00,
CRYPTO_FIRMWARE_PACK_IV_TYPE_IMG = 0x01
};
/*! Firmware Package Header fields */
struct fw_pkg_hdr_t {
uint32_t magic; /**< Magic number. */
uint32_t version; /**< Version. */
uint8_t package_type; /**< Package type. */
uint8_t enc_mode; /**< Encryption mode. */
uint8_t enc_algo; /**< Encryption algorithm. */
uint8_t reserved; /**< Reserved for alignment. */
uint8_t enc_data
[CRYPTO_FIRMWARE_PACK_ENC_DATA_SIZE]; /**< Encryption data. */
uint8_t enc_key_l2
[CRYPTO_FIRMWARE_PACK_ENC_KEY_L2_SIZE]; /**< Code encryption L2 key data. */
uint8_t fw_version
[CRYPTO_FIRMWARE_PACK_FW_VERSION_SIZE]; /**< Firwmare version included in the package. */
uint32_t payload_len; /**< Payload length. */
uint8_t tag[CRYPTO_FIRMWARE_PACK_TAG_SIZE]; /**< AES-CMAC Tag. */
} __attribute__((packed));
/*! Firmware Image Metadata fields */
struct fw_img_desc_t {
uint32_t offset; /**< Offset. */
uint32_t length; /**< Length. */
} __attribute__((packed));
/*! Firmware Image Header fields */
struct fw_pkg_img_hdr_t {
uint32_t magic; /**< Magic number. */
uint32_t version; /**< Version number. */
uint32_t cert_chain_offset; /**< Certificate chain offset. */
uint16_t cert_chain_length; /**< Certificate chain length. */
uint8_t num_descs; /**< Number of images descriptors. */
uint8_t reserved; /**< Reserved data. */
struct fw_img_desc_t descs
[CRYPTO_FW_PKG_IMG_HDR_IMG_NUM_MAX]; /**< Firmware image metadata. */
} __attribute__((packed));
/*! Firmware Chunk fields */
struct fw_pkg_payload_chunk_t {
uint32_t magic; /**< Magic number. */
uint32_t version; /**< Version number. */
uint32_t length; /**< Length. */
uint8_t payload[]; /**< Payload data pointer. */
} __attribute__((packed));
/*! Firmware Macro Package Header fields */
struct fw_macro_pkg_hdr_t {
uint32_t magic; /**< Magic number. */
uint32_t version; /**< Version. */
uint8_t nb_descriptors;
uint8_t reserved[3];
struct fw_img_desc_t img_desc[];
} __attribute__((packed));
#define MACRO_PKG_HASH_SIZE (32)
#define COMPUTE_FW_MACRO_PKG_HDR_SIZE(ndescs) \
(sizeof(struct fw_macro_pkg_hdr_t) + \
ndescs * sizeof(struct fw_img_desc_t) + MACRO_PKG_HASH_SIZE)
#endif /* __QM357XX_FWPKG_H__ */

View file

@ -0,0 +1,93 @@
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
/*
* Copyright 2021 Qorvo US, Inc.
*
*/
#ifndef __QM357XX_ROM_H__
#define __QM357XX_ROM_H__
#define PEG_ERR_TIMEOUT PEG_ERR_BASE - 1
#define PEG_ERR_ROM_NOT_READY PEG_ERR_BASE - 2
#define PEG_ERR_SEND_CERT_WRITE PEG_ERR_BASE - 3
#define PEG_ERR_WRONG_REVISION PEG_ERR_BASE - 4
#define PEG_ERR_FIRST_KEY_CERT_OR_FW_VER PEG_ERR_BASE - 5
#define HBK_LOC 12
typedef enum {
HBK_2E_ICV = 0,
HBK_2E_OEM = 1,
HBK_1E_ICV_OEM = 2,
} hbk_t;
#define ROM_VERSION_A0 0x01a0
#define ROM_VERSION_B0 0xb000
#define QM357XX_ROM_SOC_ID_LEN 0x20
#define QM357XX_ROM_UUID_LEN 0x10
/* Life cycle state definitions. */
/*! Defines the CM life-cycle state value. */
#define CC_BSV_CHIP_MANUFACTURE_LCS 0x0
/*! Defines the DM life-cycle state value. */
#define CC_BSV_DEVICE_MANUFACTURE_LCS 0x1
/*! Defines the Secure life-cycle state value. */
#define CC_BSV_SECURE_LCS 0x5
/*! Defines the RMA life-cycle state value. */
#define CC_BSV_RMA_LCS 0x7
struct unstitched_firmware {
struct firmware *fw_img;
struct firmware *fw_crt;
struct firmware *key1_crt;
struct firmware *key2_crt;
};
/* Those functions allow the libqmrom to call
* revision specific functions
*/
typedef int (*flash_fw_fn)(struct qmrom_handle *handle,
const struct firmware *fw);
typedef int (*flash_unstitched_fw_fn)(struct qmrom_handle *handle,
const struct unstitched_firmware *fw);
typedef int (*flash_debug_cert_fn)(struct qmrom_handle *handle,
struct firmware *dbg_cert);
typedef int (*erase_debug_cert_fn)(struct qmrom_handle *handle);
struct qm357xx_rom_code_ops {
flash_fw_fn flash_fw;
flash_unstitched_fw_fn flash_unstitched_fw;
flash_debug_cert_fn flash_debug_cert;
erase_debug_cert_fn erase_debug_cert;
};
struct qm357xx_soc_infos {
uint8_t soc_id[QM357XX_ROM_SOC_ID_LEN];
uint8_t uuid[QM357XX_ROM_UUID_LEN];
uint32_t lcs_state;
};
int qm357xx_rom_unstitch_fw(const struct firmware *fw,
struct unstitched_firmware *unstitched_fw,
enum chip_revision_e revision);
int qm357xx_rom_fw_macro_pkg_get_fw_idx(const struct firmware *fw, int idx,
uint32_t *fw_size, char **fw_fata);
int qm357xx_rom_unpack_fw_macro_pkg(const struct firmware *fw,
struct unstitched_firmware *all_fws);
int qm357xx_rom_flash_dbg_cert(struct qmrom_handle *handle,
struct firmware *dbg_cert);
int qm357xx_rom_erase_dbg_cert(struct qmrom_handle *handle);
int qm357xx_rom_flash_fw(struct qmrom_handle *handle,
const struct firmware *fw);
int qm357xx_rom_flash_unstitched_fw(struct qmrom_handle *handle,
const struct unstitched_firmware *fw);
int qm357xx_rom_write_cmd(struct qmrom_handle *handle, uint8_t cmd);
int qm357xx_rom_write_cmd32(struct qmrom_handle *handle, uint32_t cmd);
int qm357xx_rom_write_size_cmd(struct qmrom_handle *handle, uint8_t cmd,
uint16_t data_size, const char *data);
int qm357xx_rom_write_size_cmd32(struct qmrom_handle *handle, uint32_t cmd,
uint16_t data_size, const char *data);
#endif /* __QM357XX_ROM_H__ */

View file

@ -0,0 +1,174 @@
/*
* Copyright 2021 Qorvo US, Inc.
*
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
*
* This file is provided under the Apache License 2.0, or the
* GNU General Public License v2.0.
*
*/
#ifndef _QM358XX_FIRMWARE_PACK_HANDLING_H_
#define _QM358XX_FIRMWARE_PACK_HANDLING_H_
#ifdef __cplusplus
extern "C" {
#endif
#include <stdint.h>
/*!
@addtogroup secureboot_firmware_pack_handling
@{
*/
/*! Cert magic word conversion macro */
#define MAGIC_STR_TO_U32(x) ((x[3]) | (x[2] << 8) | (x[1] << 16) | (x[0] << 24))
/*! Cert version word conversion macro */
#define CERT_VER_TO_U32(maj, min) (((maj) << 16) | ((min) << 24))
// Field values for firmware package
#define CRYPTO_FIRMWARE_PACK_MAGIC_VALUE MAGIC_STR_TO_U32("CFWP")
// Package size defines
#define CRYPTO_FIRMWARE_PACK_ENC_KEY_L2_ENC_SIZE 64u
#define CRYPTO_FIRMWARE_PACK_ENC_DATA_SIZE 16u
#define CRYPTO_FIRMWARE_PACK_FW_VERSION_SIZE 32u
#define CRYPTO_FIRMWARE_PACK_TAG_SIZE 16u
// Firmware image size define
#define CRYPTO_FW_PKG_IMG_HDR_IMG_NUM_MAX 8u
#define CRYPTO_FIRMWARE_PACK_HEADER_SIZE sizeof(fw_pkg_hdr_t)
#define CRYPTO_FIRMWARE_IMAGE_CERT_SIZE sizeof(fw_pkg_img_hdr_t)
// Field values for firmware chunks
#define CRYPTO_FIRMWARE_CHUNK_MAGIC_VALUE MAGIC_STR_TO_U32("CFWC")
#define CRYPTO_FIRMWARE_CHUNK_VERSION CERT_VER_TO_U32(1, 0)
#define CRYPTO_FIRMWARE_CHUNK_MIN_SIZE 16u
#define CRYPTO_FIRMWARE_CHUNK_HDR_SIZE sizeof(fw_pkg_payload_chunk_hdr_t)
/*! Firmware Package Header fields */
typedef struct {
uint32_t magic; /**< Magic number. */
uint32_t version; /**< Version. */
uint8_t package_type; /**< Package type. */
uint8_t enc_mode; /**< Encryption mode. */
uint8_t enc_algo; /**< Encryption algorithm. */
union {
uint8_t enc_key_l2_cfg; /**< Code encryption L2 key config. */
struct {
uint8_t use : 1; /**< Use Kce_l2 for decryption. */
uint8_t lock : 1; /**< Lock using Kce_l2 for decryption. */
uint8_t : 6;
} enc_key_l2_cfg_b;
};
uint8_t enc_data
[CRYPTO_FIRMWARE_PACK_ENC_DATA_SIZE]; /**< Encryption data. */
uint8_t enc_key_l2
[CRYPTO_FIRMWARE_PACK_ENC_KEY_L2_ENC_SIZE]; /**< Code encryption L2 key data. */
uint8_t fw_version
[CRYPTO_FIRMWARE_PACK_FW_VERSION_SIZE]; /**< Firwmare version included in the package. */
uint32_t payload_len; /**< Payload length. */
uint8_t tag[CRYPTO_FIRMWARE_PACK_TAG_SIZE]; /**< AES-CMAC Tag. */
} fw_pkg_hdr_t;
/*! Firmware Image Metadata fields */
typedef struct {
uint32_t offset; /**< Offset. */
uint32_t length; /**< Length. */
} fw_image_metadata_t;
/*! Firmware Image Header fields */
typedef struct {
uint32_t magic; /**< Magic number. */
uint32_t version; /**< Version number. */
uint32_t cert_chain_offset; /**< Certificate chain offset. */
uint16_t cert_chain_length; /**< Certificate chain length. */
uint8_t img_num; /**< Number of images. */
uint8_t reserved; /**< Reserved data. */
fw_image_metadata_t metadata
[CRYPTO_FW_PKG_IMG_HDR_IMG_NUM_MAX]; /**< Firmware image metadata. */
} fw_pkg_img_hdr_t;
/*! Firmware Chunk fields */
typedef struct {
uint32_t magic; /**< Magic number. */
uint32_t version; /**< Version number. */
uint32_t length; /**< Length. */
} fw_pkg_payload_chunk_hdr_t;
#define CRYPTO_IMAGES_CERT_PUBLIC_KEY_SIZE 384
#define CRYPTO_KEY_HASH_TRUNC_SIZE_BYTES 16u
#define CRYPTO_SHA256_SIZE_BYTES 32u
#define CRYPTO_IMAGES_CERT_MIN_IMAGE_NUMBER 1u
#define CRYPTO_IMAGES_CERT_PKG_SIZE sizeof(cert_chain_struct)
/*! Certificate or file header struct */
typedef struct {
uint32_t magic; /**< Magic value. */
uint32_t version; /**< Certificate version. */
uint32_t size; /**< Certificate size. */
} cert_common_header_struct;
/*! Key certificate fields */
typedef struct {
cert_common_header_struct
header; /**< Pointer to key certificate header data. */
uint8_t rsa_public_key
[CRYPTO_IMAGES_CERT_PUBLIC_KEY_SIZE]; /**< RSA public key data. */
uint32_t context_hash_id; /**< Context and hash ID combined in one uint32. */
uint32_t customer_id; /**< Customer ID. */
uint32_t product_id; /**< Product ID. */
uint8_t sec_version; /**< Security version number. */
uint8_t reserved[3]; /**< Reserved data. */
uint32_t arb_version; /**< Anti-rollback version number. */
uint8_t hash[CRYPTO_KEY_HASH_TRUNC_SIZE_BYTES]; /**< Content cert public key hash. */
uint8_t rsa_signature
[CRYPTO_IMAGES_CERT_PUBLIC_KEY_SIZE]; /**< RSA signature data. */
} cert_key_struct;
/*! Content of metadata fields */
typedef struct {
uint8_t img_hash[CRYPTO_SHA256_SIZE_BYTES]; /**< Image hash result data. */
uint32_t flash_addr; /**< Flash (RRAM) address. */
uint32_t max_len; /**< Maximum length. */
uint32_t len; /**< Length. */
uint32_t load_verify_scheme; /**< Schema to load and verify firmware image. */
uint32_t load_addr; /**< SRAM load address. */
} cert_content_img_metadata_t;
/*! Content certificate fields */
typedef struct {
cert_common_header_struct
header; /**< Pointer to content certificate header data. */
uint8_t rsa_public_key
[CRYPTO_IMAGES_CERT_PUBLIC_KEY_SIZE]; /**< RSA public key data. */
uint32_t context; /**< Context. */
uint32_t customer_id; /**< Customer ID. */
uint32_t product_id; /**< Product ID. */
uint8_t sec_version; /**< Security version number. */
uint8_t reserved[3]; /**< Reserved data. */
uint32_t arb_version; /**< Anti-rollback version number. */
uint32_t img_num; /**< Number of images. */
cert_content_img_metadata_t img_metadata
[CRYPTO_IMAGES_CERT_MIN_IMAGE_NUMBER]; /**< Image metadata. */
} cert_content_struct;
/*! Certificate chain fields */
typedef struct {
cert_key_struct cert_key; /**< Pointer to key certificate data. */
cert_content_struct
cert_content; /**< Pointer to content certificate data. */
} cert_chain_struct;
/**
@}
*/
#ifdef __cplusplus
}
#endif
#endif /* _QM358XX_FIRMWARE_PACK_HANDLING_H_ */

View file

@ -0,0 +1,70 @@
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
/*
* Copyright 2021 Qorvo US, Inc.
*
*/
#ifndef __QM358XX_ROM_H__
#define __QM358XX_ROM_H__
#define QM358XX_ROM_UUID_LEN 0x10
/* Life cycle state definitions. */
#define LCS_TEST (0x00) /* Previously: CC_BSV_CHIP_MANUFACTURE_LCS */
#define LCS_OEM (0x01) /* Previously: CC_BSV_DEVICE_MANUFACTURE_LCS */
#define LCS_USER (0x02) /* Previously: CC_BSV_SECURE_LCS */
#define LCS_RMA (0x03) /* Previously: CC_BSV_RMA_LCS */
typedef int (*gen_secrets_fn)(struct qmrom_handle *handle);
typedef int (*load_asset_pkg_fn)(struct qmrom_handle *handle,
const struct firmware *asset_pkg);
typedef int (*load_fw_pkg_fn)(struct qmrom_handle *handle,
const struct firmware *fw_pkg);
typedef int (*load_secure_dbg_pkg_fn)(struct qmrom_handle *handle,
struct firmware *dbg_pkg);
typedef int (*erase_secure_dbg_pkg_fn)(struct qmrom_handle *handle);
typedef int (*run_test_mode_fn)(struct qmrom_handle *handle);
typedef int (*load_sram_fw_fn)(struct qmrom_handle *handle,
const struct firmware *sram_fw);
struct qm358xx_rom_code_ops {
gen_secrets_fn gen_secrets;
load_asset_pkg_fn load_asset_pkg;
load_fw_pkg_fn load_fw_pkg;
load_secure_dbg_pkg_fn load_secure_dbg_pkg;
erase_secure_dbg_pkg_fn erase_secure_dbg_pkg;
run_test_mode_fn run_test_mode;
load_sram_fw_fn load_sram_fw;
};
struct qm358xx_soc_infos {
uint8_t uuid[QM358XX_ROM_UUID_LEN];
uint32_t customer_id;
uint32_t product_id;
uint32_t arb_ver_icv;
uint32_t arb_ver_oem;
uint8_t lcs_state;
uint8_t working_model;
uint8_t business_ctx;
uint8_t secure_debug;
uint8_t secure_boot_oem;
uint8_t fw_encryption_oem;
uint8_t lock_debug;
uint8_t sec_ver_icv;
uint8_t sec_ver_oem;
uint8_t enc_l2_lock;
};
int qm358xx_rom_gen_secrets(struct qmrom_handle *handle);
int qm358xx_rom_load_asset_pkg(struct qmrom_handle *handle,
struct firmware *asset_pkg);
int qm358xx_rom_load_fw_pkg(struct qmrom_handle *handle,
struct firmware *fw_pkg);
int qm358xx_rom_load_secure_dbg_pkg(struct qmrom_handle *handle,
struct firmware *dbg_pkg);
int qm358xx_rom_erase_secure_dbg_pkg(struct qmrom_handle *handle);
int qm358xx_rom_run_test_mode(struct qmrom_handle *handle);
int qm358xx_rom_load_sram_fw(struct qmrom_handle *handle,
struct firmware *sram_fw);
#endif /* __QM358XX_ROM_H__ */

118
include/linux/uwb/qmrom.h Normal file
View file

@ -0,0 +1,118 @@
// SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
/*
* Copyright 2021 Qorvo US, Inc.
*
*/
#ifndef __QMROM_H__
#define __QMROM_H__
#ifndef __KERNEL__
#include <errno.h>
#include <stdint.h>
#include <string.h>
#include <stdint.h>
#include <stdbool.h>
#include <byteswap.h>
#include <inttypes.h>
#else
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/string.h>
#define bswap_16 be16_to_cpu
#define PRIu32 "u"
#endif
#include "qmrom_error.h"
#undef CHECK_STCS
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
enum chip_revision_e {
CHIP_REVISION_A0 = 0xA0,
CHIP_REVISION_B0 = 0xB0,
CHIP_REVISION_C0 = 0xC0,
CHIP_REVISION_C2 = 0xC2,
CHIP_REVISION_UNKNOWN = 0xFF
};
enum device_generation_e {
DEVICE_GEN_QM357XX,
DEVICE_GEN_QM358XX,
DEVICE_GEN_UNKNOWN = 0xFF
};
struct qmrom_handle;
#include "qm357xx_rom.h"
#include "qm358xx_rom.h"
#define SSTC2UINT32(handle, offset) \
({ \
uint32_t tmp = 0xbeefdeed; \
if ((handle)->sstc->len >= (offset) + sizeof(tmp)) \
memcpy(&tmp, &(handle)->sstc->payload[(offset)], \
sizeof(tmp)); \
tmp; \
})
#define SSTC2UINT16(handle, offset) \
({ \
uint16_t tmp = 0xbeed; \
if ((handle)->sstc->len >= (offset) + sizeof(tmp)) \
memcpy(&tmp, &(handle)->sstc->payload[(offset)], \
sizeof(tmp)); \
tmp; \
})
/* Those functions allow the libqmrom to call
* device specific functions
*/
typedef int (*reset_device_fn)(void *handle);
struct device_ops {
reset_device_fn reset;
};
struct qmrom_handle {
void *spi_handle;
void *reset_handle;
void *ss_rdy_handle;
int comms_retries;
enum device_generation_e dev_gen;
enum chip_revision_e chip_rev;
uint16_t device_version;
struct device_ops dev_ops;
int spi_speed;
union {
struct qm357xx_rom_code_ops qm357xx_rom_ops;
struct qm358xx_rom_code_ops qm358xx_rom_ops;
};
struct stc *hstc;
struct stc *sstc;
union {
struct qm357xx_soc_infos qm357xx_soc_info;
struct qm358xx_soc_infos qm358xx_soc_info;
};
bool is_be;
};
struct qmrom_handle *qmrom_init(void *spi_handle, void *reset_handle,
void *ss_rdy_handle, int spi_speed,
int comms_retries, reset_device_fn reset,
enum device_generation_e dev_gen_hint);
void qmrom_deinit(struct qmrom_handle *handle);
int qmrom_reboot_bootloader(struct qmrom_handle *handle);
int qmrom_pre_read(struct qmrom_handle *handle);
int qmrom_read(struct qmrom_handle *handle);
#ifdef CHECK_STCS
void check_stcs(const char *func, int line, struct qmrom_handle *h);
#else
#define check_stcs(f, l, h)
#endif
#endif /* __QMROM_H__ */

View file

@ -0,0 +1,21 @@
/*
* Copyright 2021 Qorvo US, Inc.
*
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
*
* This file is provided under the Apache License 2.0, or the
* GNU General Public License v2.0.
*
*/
#ifndef __QMROM_ERROR_H__
#define __QMROM_ERROR_H__
#define SPI_ERR_BASE -8000
#define PEG_ERR_BASE -9000
#define SPI_PROTO_ERR_BASE -10000
#define IS_PTR_ERROR(ptr) (((intptr_t)ptr) <= 0)
#define PTR2ERROR(ptr) ((int)((intptr_t)ptr))
#define ERROR2PTR(error) ((void *)((intptr_t)error))
#endif /* __QMROM_ERROR_H__ */

View file

@ -0,0 +1,94 @@
/*
* Copyright 2021 Qorvo US, Inc.
*
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
*
* This file is provided under the Apache License 2.0, or the
* GNU General Public License v2.0.
*
*/
#ifndef __QMROM_LOGGER_H__
#define __QMROM_LOGGER_H__
enum log_level_e {
LOG_QUIET = 0,
LOG_ERR,
LOG_WARN,
LOG_INFO,
LOG_DBG,
LOG_LVL_MAX = LOG_DBG,
};
extern enum log_level_e __log_level__;
void set_log_level(enum log_level_e lvl);
static inline int is_debug_mode(void)
{
return __log_level__ >= LOG_DBG;
}
static inline int is_log_level_allowed(enum log_level_e lvl)
{
return (__log_level__ >= lvl);
}
void hexdump(enum log_level_e lvl, void *address, unsigned short length);
void hexrawdump(enum log_level_e lvl, void *address, unsigned short length);
#ifndef __KERNEL__
#include <stdio.h>
#define LOG_ERR(...) \
do { \
if (__log_level__ > LOG_QUIET) \
fprintf(stderr, __VA_ARGS__); \
} while (0)
#define LOG_WARN(...) \
do { \
if (__log_level__ > LOG_ERR) \
fprintf(stderr, __VA_ARGS__); \
} while (0)
#define LOG_INFO(...) \
do { \
if (__log_level__ > LOG_WARN) \
fprintf(stdout, __VA_ARGS__); \
} while (0)
#define LOG_DBG(...) \
do { \
if (__log_level__ > LOG_INFO) \
fprintf(stdout, __VA_ARGS__); \
} while (0)
#else
#include <linux/device.h>
extern struct device *__qmrom_log_dev__;
void qmrom_set_log_device(struct device *dev, enum log_level_e lvl);
#define LOG_ERR(...) \
do { \
if (__qmrom_log_dev__) \
if (__log_level__ > LOG_QUIET) \
dev_err(__qmrom_log_dev__, __VA_ARGS__); \
} while (0)
#define LOG_WARN(...) \
do { \
if (__qmrom_log_dev__) \
if (__log_level__ > LOG_ERR) \
dev_warn(__qmrom_log_dev__, __VA_ARGS__); \
} while (0)
#define LOG_INFO(...) \
do { \
if (__qmrom_log_dev__) \
if (__log_level__ > LOG_WARN) \
dev_info(__qmrom_log_dev__, __VA_ARGS__); \
} while (0)
#define LOG_DBG(...) \
do { \
if (__qmrom_log_dev__) \
if (__log_level__ > LOG_INFO) \
dev_dbg(__qmrom_log_dev__, __VA_ARGS__); \
} while (0)
#endif
#endif /* __QMROM_LOGGER_H__ */

View file

@ -0,0 +1,62 @@
/*
* Copyright 2021 Qorvo US, Inc.
*
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
*
* This file is provided under the Apache License 2.0, or the
* GNU General Public License v2.0.
*
*/
#ifndef __QMROM_SPI_H__
#define __QMROM_SPI_H__
#include <stddef.h>
#include "qmrom_error.h"
#include "qmrom.h"
#ifndef __KERNEL__
struct firmware {
size_t size;
const uint8_t *data;
};
#else
#include <linux/firmware.h>
#endif
#define DEFAULT_SPI_LATENCY_MS 2
#define SPI_ERR_NOCHAN SPI_ERR_BASE - 1
#define SPI_ERR_INFNOTFOUND SPI_ERR_BASE - 2
#define SPI_ERR_NOMEM SPI_ERR_BASE - 3
#define SPI_ERR_READ_INCOMPLETE SPI_ERR_BASE - 4
#define SPI_ERR_GPIO_WRITE_CMD_INCOMPLETE SPI_ERR_BASE - 5
#define SPI_ERR_GPIO_READ_CMD_INCOMPLETE SPI_ERR_BASE - 6
#define SPI_ERR_READY_LINE_TIMEOUT SPI_ERR_BASE - 7
#define SPI_ERR_WRITE_INCOMPLETE SPI_ERR_BASE - 8
#define SPI_ERR_RW_INCOMPLETE SPI_ERR_BASE - 9
#define SPI_ERR_INVALID_STC_LEN SPI_ERR_BASE - 10
#define SPI_ERR_WAIT_READY_TIMEOUT SPI_ERR_BASE - 11
/*Make sure that the error ranges don't overlap */
#define SPI_ERR_LIB_BASE (SPI_ERR_BASE - 500)
#define SPI_ERR_LIB(rc) (SPI_ERR_LIB_BASE - rc)
void *qmrom_spi_init(int spi_interface_index);
void qmrom_spi_uninit(void *handle);
int qmrom_spi_read(void *handle, char *buffer, size_t size);
int qmrom_spi_write(void *handle, const char *buffer, size_t size);
int qmrom_spi_transfer(void *handle, char *rbuf, const char *wbuf, size_t size);
int qmrom_spi_set_cs_level(void *handle, int level);
int qmrom_spi_reset_device(void *reset_handle);
const struct firmware *qmrom_spi_get_firmware(void *handle,
struct qmrom_handle *qmrom_h,
bool *is_fw_updater,
bool use_prod_fw);
const struct firmware *qmrom_spi_get_firmware_package(void *handle,
int lcs_state);
void qmrom_spi_release_firmware(const struct firmware *fw);
int qmrom_spi_wait_for_ready_line(void *handle, unsigned int timeout_ms);
void qmrom_spi_set_freq(unsigned int freq);
unsigned int qmrom_spi_get_freq(void);
#endif /* __QMROM_SPI_H__ */

View file

@ -0,0 +1,56 @@
/*
* Copyright 2021 Qorvo US, Inc.
*
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
*
* This file is provided under the Apache License 2.0, or the
* GNU General Public License v2.0.
*
*/
#ifndef __QMROM_UTILS_H__
#define __QMROM_UTILS_H__
#ifndef __KERNEL__
#include <stdlib.h>
#include <stdint.h>
#ifndef __linux__
extern void usleep(unsigned int us);
#else
#include <unistd.h>
#endif
#define qmrom_msleep(ms) \
do { \
usleep(ms * 1000); \
} while (0)
#define qmrom_alloc(ptr, size) \
do { \
ptr = calloc(1, size); \
} while (0)
#define qmrom_free free
#define qmrom_data_dma_able(d) true
#else
#include <linux/delay.h>
#include <linux/slab.h>
#include <linux/mm.h>
#define qmrom_msleep(ms) \
do { \
usleep_range(ms * 1000, ms * 1000); \
} while (0)
#define qmrom_alloc(ptr, size) \
do { \
ptr = kzalloc(size, GFP_KERNEL | GFP_DMA); \
} while (0)
#define qmrom_free kfree
#define qmrom_data_dma_able(d) !is_vmalloc_addr(d)
#endif
#endif /* __QMROM_UTILS_H__ */

View file

@ -0,0 +1,92 @@
/*
* Copyright 2021 Qorvo US, Inc.
*
* SPDX-License-Identifier: GPL-2.0 OR Apache-2.0
*
* This file is provided under the Apache License 2.0, or the
* GNU General Public License v2.0.
*
*/
#ifndef __SPI_ROM_PROTOCOL_H__
#define __SPI_ROM_PROTOCOL_H__
#include "qmrom_error.h"
#include "qmrom.h"
#ifndef __KERNEL__
#include <stdint.h>
#else
#include <linux/types.h>
#endif
#define SPI_PROTO_WRONG_RESP SPI_PROTO_ERR_BASE - 1
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
struct stc {
union {
struct {
union {
struct {
uint8_t reserved : 5;
uint8_t read : 1; // Read indication: Set to one by the host to tell the SOC SPI driver that this transaction is a read, otherwise it is set to zero.
uint8_t pre_read : 1; // Pre-read indication: Set to one by the host to tell the SOC SPI driver that the next transaction will be a read, otherwise it is set to zero
uint8_t write : 1; // Write indication: Set to one by the host when it is doing a write transaction. Set to zero when the host is not doing a write.
} host_flags;
struct {
uint8_t reserved : 4;
uint8_t err : 1; // Error indication: This is set to one by the SOC SPI driver to tell the HOST that it has detected some error in the SPI protocol.
uint8_t ready : 1; //
uint8_t out_active : 1; // Output active indication: Set to one by the SOC SPI driver to tell the HOST that it is outputting data on MISO line and expecting that the host is doing a read transaction at this time. This is set to zero for all other transactions.
uint8_t out_waiting : 1; // Output data waiting indication: Set to one by the SOC SPI driver to tell the HOST there is data awaiting reading. This is set to zero when there is no data pending output.
} soc_flags;
uint8_t raw_flags;
};
uint8_t ul;
uint16_t len;
};
uint32_t all;
};
uint8_t payload[0];
} __attribute__((packed));
#pragma GCC diagnostic pop
/* Host to Soc (HS) masks */
#define SPI_HS_WRITE_CMD_BIT_MASK 0x80
#define SPI_HS_PRD_CMD_BIT_MASK 0x40
#define SPI_HS_RD_CMD_BIT_MASK 0x20
/* Soc to Host (SH) masks */
#define SPI_SH_ODW_BIT_MASK 0x80
#define SPI_SH_ACTIVE_BIT_MASK 0x40
#define SPI_SH_READY_CMD_BIT_MASK 0x20
#define SPI_DEVICE_READY_FLAGS SPI_SH_ODW_BIT_MASK
/* Communication parameters */
#define MAX_STC_FRAME_LEN 2048
#define MAX_STC_PAYLOAD_LEN (MAX_STC_FRAME_LEN - sizeof(struct stc))
#define SPI_NUM_READS_FOR_READY 1
#define SPI_NUM_FAILS_RETRY 4
#define SPI_ET_PROTOCOL 5
#define SPI_RST_LOW_DELAY_MS 20
#define SPI_INTERCMD_DELAY_MS 1
#define SPI_DEVICE_POLL_RETRY 10
#define SPI_READY_TIMEOUT_MS 50
#define SPI_ET_VERSION_LOCATION 0x601f0000
/* ROM boot proto */
#define SPI_ROM_READ_VERSION_SIZE_A0 3
#define SPI_ROM_READ_IMAGE_CERT_SIZE_A0 3
#define SPI_ROM_READ_IMAGE_SIZE_SIZE_A0 3
#define SPI_ROM_READ_VERSION_SIZE_B0 5
#define SPI_ROM_READ_INFO_SIZE_B0 50
#define SPI_ROM_READ_IMAGE_CERT_SIZE_B0 1
#define SPI_ROM_READ_IMAGE_SIZE_SIZE_B0 1
#define SPI_ROM_WRITE_KEY_CERT_SIZE 6
#define SPI_ROM_WRITE_IMAGE_CERT_SIZE 40
#define SPI_ROM_WRITE_IMAGE_SIZE_SIZE 4
#define SPI_ROM_DBG_CERT_SIZE_SIZE 5
#endif /* __SPI_ROM_PROTOCOL_H__ */