lineage_kernel_xcoverpro/drivers/misc/samsung/scsc/mxfwconfig.c

398 lines
13 KiB
C
Executable File

/*****************************************************************************
*
* Copyright (c) 2018 Samsung Electronics Co., Ltd. All rights reserved
*
****************************************************************************/
#include <scsc/scsc_mx.h>
#include <scsc/scsc_logring.h>
#include "mxfwconfig.h"
#include "miframman.h"
#include "scsc_mx_impl.h"
#include "mxconf.h"
#if IS_ENABLED(CONFIG_SCSC_LOG_COLLECTION)
#include <scsc/scsc_log_collector.h>
#endif
#define MXFWCONFIG_CFG_SUBDIR "common"
#define MXFWCONFIG_CFG_FILE_HW "common.hcf"
#define MXFWCONFIG_CFG_FILE_SW "common_sw.hcf"
static void mxfwconfig_get_dram_ref(struct scsc_mx *mx, struct mxmibref *cfg_ref);
/* CS-213274-SP */
struct __packed mxfwconfig_vldata {
u8 data[6]; /* info + 1 or more data octets when it's a multi-octet entry */
};
/* "info" in data[0] of mxfwconfig_vldata*/
#define MX_VLDATA_INFO_INDEX(x) ((x) & 0xFF) /* When keyval length == 2 */
/* "info" in data[0 or 1] */
#define MX_VLDATA_INFO_MORE(x) ((x) & BIT(7)) /* more octets follow */
#define MX_VLDATA_INFO_SIGN(x) ((x) & BIT(6)) /* sign */
#define MX_VLDATA_INFO_TYPE(x) ((x) & BIT(5)) /* if "more", 1: octet string, 0: integer. Else part of b0-6 of single octet value */
#define MX_VLDATA_INFO_LENGTH(x) ((x) & 0x7F) /* if "more", length of data or string. Else part of b0-6 of single octet value */
struct __packed mxfwconfig_keyval {
u16 psid; /* key ID */
u16 length; /* length of vldata in octets */
struct mxfwconfig_vldata vldata; /* data */
/* u8 pad[0|1]; */ /* padding: 0/1 instances to make data even number of octets */
};
#define MXFWCONFIG_SYSTEM_ERROR_EXCLUDE_FROM_PROMOTION 41 /* lernaSystemErrorExcludeFromPromotion */
u32 mxfwconfig_syserr_no_promote[MXFWCONFIG_MAX_NO_PROMOTE];
static int __mxfwconfig_advance_keyval(struct mxfwconfig_keyval **pkeyval, const u8* conf_data, const size_t conf_len)
{
if ((*pkeyval)->length == 0) {
SCSC_TAG_ERR(MX_CFG, "Zero length\n");
return -EINVAL;
}
SCSC_TAG_DEBUG(MX_CFG, "*pkeyval=%p, ->psid=0x%x, ->length=0x%x, sizeof(psid)=%zu, sizeof(length)=%zu, conf_data=%p, conf_len=%zu\n",
*pkeyval, (*pkeyval)->psid, (*pkeyval)->length, sizeof((*pkeyval)->psid), sizeof((*pkeyval)->length), conf_data, conf_len);
/* Advance */
*pkeyval = (struct mxfwconfig_keyval *)(((u8 *)*pkeyval) +
(((*pkeyval)->length + 1) & 0xfffe) + /* pad to even length */
sizeof((*pkeyval)->psid) +
sizeof((*pkeyval)->length));
/* Sanity: pointer should be even */
if ((uintptr_t)*pkeyval & 1) {
SCSC_TAG_ERR(MX_CFG, "Odd keyval pointer %p\n", *pkeyval);
return -EINVAL;
}
/* Reached end of buffer without finding key? */
if ((ptrdiff_t)*pkeyval >= (ptrdiff_t)conf_data + conf_len) {
SCSC_TAG_DEBUG(MX_CFG, "End of buffer pointer %p >= %p\n", *pkeyval, conf_data + conf_len);
return -ERANGE;
}
return 0;
}
/* Parse System Recovery no-promotion list */
static int mxfwconfig_parse_system_recovery(struct scsc_mx *mx, const u8 *conf_data, const size_t conf_len, u32 *syserr_no_promote, int max_promotes)
{
int r;
struct mxfwconfig_keyval *keyval = (struct mxfwconfig_keyval *)conf_data;
u16 promotes = 0;
/* Default assumption is that promotion list is empty */
syserr_no_promote[0] = 0;
/* Look for MXFWCONFIG_SYSTEM_ERROR_EXCLUDE_FROM_PROMOTION anywhere in file */
while (keyval->psid != MXFWCONFIG_SYSTEM_ERROR_EXCLUDE_FROM_PROMOTION) {
r = __mxfwconfig_advance_keyval(&keyval, conf_data, conf_len);
if (r) {
SCSC_TAG_DEBUG(MX_CFG, "Search for psid%d: %d\n", MXFWCONFIG_SYSTEM_ERROR_EXCLUDE_FROM_PROMOTION, r);
return r;
}
}
/* If list present, L7 SysErrs promote to L8, maintaining legacy panic/moredump behaviour */
while (keyval->psid == MXFWCONFIG_SYSTEM_ERROR_EXCLUDE_FROM_PROMOTION) {
SCSC_TAG_DEBUG(MX_CFG, "PSID=%d, ->length=%d...\n", keyval->psid, keyval->length);
/* If single entry of 0xFFFFFFFF, only host induced L7 SysErrs promote to L8 */
/* 32-bit syserr IDs */
if (keyval->length == 6) { /* 2 len + 4 data */
SCSC_TAG_DEBUG(MX_CFG, "PSID=%d, ->length=%d, vldata.info=0x%x 0x%x, vldata.more=%d, vldata.length=%d\n",
keyval->psid, keyval->length, keyval->vldata.data[0], keyval->vldata.data[1], !!MX_VLDATA_INFO_MORE(keyval->vldata.data[1]), MX_VLDATA_INFO_LENGTH(keyval->vldata.data[1]));
if (MX_VLDATA_INFO_MORE(keyval->vldata.data[1]) && MX_VLDATA_INFO_LENGTH(keyval->vldata.data[1]) == 4) { /* 4 octets */
u32 syserr = (keyval->vldata.data[2] << 24) |
(keyval->vldata.data[3] << 16) |
(keyval->vldata.data[4] << 8) |
(keyval->vldata.data[5] << 0);
SCSC_TAG_DEBUG(MX_CFG, "index=%d: vldata[]=%02x %02x %02x %02x\n",
MX_VLDATA_INFO_INDEX(keyval->vldata.data[0]),
keyval->vldata.data[2], keyval->vldata.data[3], keyval->vldata.data[4], keyval->vldata.data[5]);
if (syserr == 0xFFFFFFFF) {
/* L7 Host Induced promotes to L8 */
syserr_no_promote[0] = 0xFFFFFFFF;
SCSC_TAG_INFO(MX_CFG, "syserr_no_promote[0] = 0xFFFFFFFF\n");
return 0; /* Terminates list */
} else {
syserr_no_promote[promotes++] = syserr;
SCSC_TAG_INFO(MX_CFG, "syserr_no_promote[%d] = 0x%x\n", promotes - 1, syserr);
/* ID of zero terminates the promotion list
* (Tool should not generate 16-bit ID of zero)
*/
if (syserr == 0 || promotes == max_promotes) {
SCSC_TAG_DEBUG(MX_CFG, "Terminated no-promote list (%d)\n", promotes);
return 0;
}
}
}
}
/* If any other entry found, do not promote that syserr to L8 (i.e. handle with fast recovery) */
/* 16-bit syserr IDs */
if (keyval->length == 4) { /* 2 len + 2 data */
SCSC_TAG_DEBUG(MX_CFG, "PSID=%d, ->length=%d, vldata.info=0x%x 0x%x, vldata.more=%d, vldata.length=%d\n",
keyval->psid, keyval->length, keyval->vldata.data[0], keyval->vldata.data[1], !!MX_VLDATA_INFO_MORE(keyval->vldata.data[1]), MX_VLDATA_INFO_LENGTH(keyval->vldata.data[1]));
if (MX_VLDATA_INFO_MORE(keyval->vldata.data[1]) && MX_VLDATA_INFO_LENGTH(keyval->vldata.data[1]) == 2) {
u16 syserr = keyval->vldata.data[2] << 8 | keyval->vldata.data[3];
SCSC_TAG_DEBUG(MX_CFG, "index=%d: vldata[]=%02x %02x\n", MX_VLDATA_INFO_INDEX(keyval->vldata.data[0]), keyval->vldata.data[2], keyval->vldata.data[3]);
syserr_no_promote[promotes++] = syserr;
SCSC_TAG_INFO(MX_CFG, "syserr_no_promote[%d] = 0x%x\n", promotes - 1, syserr);
/* ID of zero terminates the promotion list.
* (Tool should not generate 16-bit ID of zero)
*/
if (syserr == 0 || promotes == max_promotes) {
SCSC_TAG_DEBUG(MX_CFG, "Terminated no promote list (%d)\n", promotes);
return 0;
}
}
}
/* 8-bit syserr IDs */
if (keyval->length == 2) { /* 2 len, data */
u8 syserr = keyval->vldata.data[1];
SCSC_TAG_DEBUG(MX_CFG, "PSID=%d, ->length=%d, vldata.info=0x%x, 0x%0x\n",
keyval->psid, keyval->length, keyval->vldata.data[0], keyval->vldata.data[1]);
SCSC_TAG_DEBUG(MX_CFG, "index=%d: vldata[]=%02x\n", MX_VLDATA_INFO_INDEX(keyval->vldata.data[0]), keyval->vldata.data[1]);
syserr_no_promote[promotes++] = syserr;
SCSC_TAG_INFO(MX_CFG, "syserr_no_promote[%d] = 0x%x\n", promotes - 1, syserr);
/* ID of zero terminates the promotion list */
if (syserr == 0 || promotes == max_promotes) {
SCSC_TAG_DEBUG(MX_CFG, "Terminate no promote list (%d)\n", promotes);
return 0;
}
}
/* Advance */
r = __mxfwconfig_advance_keyval(&keyval, conf_data, conf_len);
if (r) {
SCSC_TAG_DEBUG(MX_CFG, "advance: %d\n", r);
return r;
}
}
return r;
}
/* Load config into non-shared DRAM */
static int mxfwconfig_load_cfg(struct scsc_mx *mx, struct mxfwconfig *cfg, const char *filename)
{
int r = 0;
u32 i;
if (cfg->configs >= SCSC_MX_MAX_COMMON_CFG) {
SCSC_TAG_ERR(MX_CFG, "Too many common config files (%u)\n", cfg->configs);
return -E2BIG;
}
i = cfg->configs++; /* Claim next config slot */
/* Load config file from file system into DRAM */
r = mx140_file_request_conf(mx, &cfg->config[i].fw, MXFWCONFIG_CFG_SUBDIR, filename);
if (r)
return r;
/* Initial size of file */
cfg->config[i].cfg_len = cfg->config[i].fw->size;
cfg->config[i].cfg_data = cfg->config[i].fw->data;
/* Validate file in DRAM */
if (cfg->config[i].cfg_len >= MX_COMMON_HCF_HDR_SIZE && /* Room for header */
/*(cfg->config[i].cfg[6] & 0xF0) == 0x10 && */ /* Curator subsystem */
cfg->config[i].cfg_data[7] == 1) { /* First file format */
int j;
cfg->config[i].cfg_hash = 0;
/* Calculate hash */
for (j = 0; j < MX_COMMON_HASH_SIZE_BYTES; j++) {
cfg->config[i].cfg_hash =
(cfg->config[i].cfg_hash << 8) | cfg->config[i].cfg_data[j + MX_COMMON_HASH_OFFSET];
}
SCSC_TAG_INFO(MX_CFG, "CFG hash: 0x%.04x\n", cfg->config[i].cfg_hash);
/* All good - consume header and continue */
cfg->config[i].cfg_len -= MX_COMMON_HCF_HDR_SIZE;
cfg->config[i].cfg_data += MX_COMMON_HCF_HDR_SIZE;
} else {
SCSC_TAG_ERR(MX_CFG, "Invalid HCF header size %zu\n", cfg->config[i].cfg_len);
/* Caller must call mxfwconfig_unload_cfg() to release the buffer */
return -EINVAL;
}
/* Running shtotal payload */
cfg->shtotal += cfg->config[i].cfg_len;
SCSC_TAG_INFO(MX_CFG, "Loaded common config %s, size %zu, payload size %zu, shared dram total %zu\n",
filename, cfg->config[i].fw->size, cfg->config[i].cfg_len, cfg->shtotal);
/* Parse syserr promotion table */
mxfwconfig_parse_system_recovery(mx,
cfg->config[i].cfg_data,
cfg->config[i].cfg_len,
mxfwconfig_syserr_no_promote,
ARRAY_SIZE(mxfwconfig_syserr_no_promote));
return r;
}
/* Unload config from non-shared DRAM */
static int mxfwconfig_unload_cfg(struct scsc_mx *mx, struct mxfwconfig *cfg, u32 index)
{
if (index >= SCSC_MX_MAX_COMMON_CFG) {
SCSC_TAG_ERR(MX_CFG, "Out of range index (%u)\n", index);
return -E2BIG;
}
if (cfg->config[index].fw) {
SCSC_TAG_DBG3(MX_CFG, "Unload common config %u\n", index);
mx140_file_release_conf(mx, cfg->config[index].fw);
cfg->config[index].fw = NULL;
cfg->config[index].cfg_data = NULL;
cfg->config[index].cfg_len = 0;
}
return 0;
}
/*
* Load Common config files
*/
int mxfwconfig_load(struct scsc_mx *mx, struct mxmibref *cfg_ref)
{
struct mxfwconfig *cfg = scsc_mx_get_mxfwconfig(mx);
struct miframman *miframman = scsc_mx_get_ramman(mx);
int r;
u32 i;
u8 *dest;
/* HW file is optional */
r = mxfwconfig_load_cfg(mx, cfg, MXFWCONFIG_CFG_FILE_HW);
if (r)
goto done;
/* SW file is optional, but not without HW file */
r = mxfwconfig_load_cfg(mx, cfg, MXFWCONFIG_CFG_FILE_SW);
if (r == -EINVAL) {
/* If SW file is corrupt, abandon both HW and SW */
goto done;
}
/* Allocate shared DRAM */
cfg->shdram = miframman_alloc(miframman, cfg->shtotal, 4, MIFRAMMAN_OWNER_COMMON);
if (!cfg->shdram) {
SCSC_TAG_ERR(MX_CFG, "MIF alloc failed for %zu octets\n", cfg->shtotal);
r = -ENOMEM;
goto done;
}
/* Copy files into shared DRAM */
for (i = 0, dest = (u8 *)cfg->shdram;
i < cfg->configs;
i++) {
/* Add to shared DRAM block */
memcpy(dest, cfg->config[i].cfg_data, cfg->config[i].cfg_len);
dest += cfg->config[i].cfg_len;
}
done:
/* Release the files from non-shared DRAM */
for (i = 0; i < cfg->configs; i++)
mxfwconfig_unload_cfg(mx, cfg, i);
/* Configs abandoned on error */
if (r)
cfg->configs = 0;
/* Pass offset of common HCF data.
* FW must ignore if zero length, so set up even if we loaded nothing.
*/
mxfwconfig_get_dram_ref(mx, cfg_ref);
return r;
}
/*
* Unload Common config data
*/
void mxfwconfig_unload(struct scsc_mx *mx)
{
struct mxfwconfig *cfg = scsc_mx_get_mxfwconfig(mx);
struct miframman *miframman = scsc_mx_get_ramman(mx);
/* Free config block in shared DRAM */
if (cfg->shdram) {
SCSC_TAG_INFO(MX_CFG, "Free common config %zu bytes shared DRAM\n", cfg->shtotal);
miframman_free(miframman, cfg->shdram);
cfg->configs = 0;
cfg->shtotal = 0;
cfg->shdram = NULL;
}
}
/*
* Get ref (offset) of config block in shared DRAM
*/
static void mxfwconfig_get_dram_ref(struct scsc_mx *mx, struct mxmibref *cfg_ref)
{
struct mxfwconfig *mxfwconfig = scsc_mx_get_mxfwconfig(mx);
struct scsc_mif_abs *mif = scsc_mx_get_mif_abs(mx);
if (!mxfwconfig->shdram) {
cfg_ref->offset = (scsc_mifram_ref)0;
cfg_ref->size = 0;
} else {
mif->get_mifram_ref(mif, mxfwconfig->shdram, &cfg_ref->offset);
cfg_ref->size = (uint32_t)mxfwconfig->shtotal;
}
SCSC_TAG_INFO(MX_CFG, "cfg_ref: 0x%x, size %u\n", cfg_ref->offset, cfg_ref->size);
}
/*
* Init config file module
*/
int mxfwconfig_init(struct scsc_mx *mx)
{
struct mxfwconfig *cfg = scsc_mx_get_mxfwconfig(mx);
cfg->configs = 0;
cfg->shtotal = 0;
cfg->shdram = NULL;
return 0;
}
/*
* Exit config file module
*/
void mxfwconfig_deinit(struct scsc_mx *mx)
{
struct mxfwconfig *cfg = scsc_mx_get_mxfwconfig(mx);
/* Leaked memory? */
WARN_ON(cfg->configs > 0);
WARN_ON(cfg->shdram);
}