1696 lines
42 KiB
C
1696 lines
42 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/* Marvell OcteonTX CPT driver
|
|
*
|
|
* Copyright (C) 2019 Marvell International Ltd.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License version 2 as
|
|
* published by the Free Software Foundation.
|
|
*/
|
|
|
|
#include <linux/ctype.h>
|
|
#include <linux/firmware.h>
|
|
#include "otx_cpt_common.h"
|
|
#include "otx_cptpf_ucode.h"
|
|
#include "otx_cptpf.h"
|
|
|
|
#define CSR_DELAY 30
|
|
/* Tar archive defines */
|
|
#define TAR_MAGIC "ustar"
|
|
#define TAR_MAGIC_LEN 6
|
|
#define TAR_BLOCK_LEN 512
|
|
#define REGTYPE '0'
|
|
#define AREGTYPE '\0'
|
|
|
|
/* tar header as defined in POSIX 1003.1-1990. */
|
|
struct tar_hdr_t {
|
|
char name[100];
|
|
char mode[8];
|
|
char uid[8];
|
|
char gid[8];
|
|
char size[12];
|
|
char mtime[12];
|
|
char chksum[8];
|
|
char typeflag;
|
|
char linkname[100];
|
|
char magic[6];
|
|
char version[2];
|
|
char uname[32];
|
|
char gname[32];
|
|
char devmajor[8];
|
|
char devminor[8];
|
|
char prefix[155];
|
|
};
|
|
|
|
struct tar_blk_t {
|
|
union {
|
|
struct tar_hdr_t hdr;
|
|
char block[TAR_BLOCK_LEN];
|
|
};
|
|
};
|
|
|
|
struct tar_arch_info_t {
|
|
struct list_head ucodes;
|
|
const struct firmware *fw;
|
|
};
|
|
|
|
static struct otx_cpt_bitmap get_cores_bmap(struct device *dev,
|
|
struct otx_cpt_eng_grp_info *eng_grp)
|
|
{
|
|
struct otx_cpt_bitmap bmap = { {0} };
|
|
bool found = false;
|
|
int i;
|
|
|
|
if (eng_grp->g->engs_num > OTX_CPT_MAX_ENGINES) {
|
|
dev_err(dev, "unsupported number of engines %d on octeontx\n",
|
|
eng_grp->g->engs_num);
|
|
return bmap;
|
|
}
|
|
|
|
for (i = 0; i < OTX_CPT_MAX_ETYPES_PER_GRP; i++) {
|
|
if (eng_grp->engs[i].type) {
|
|
bitmap_or(bmap.bits, bmap.bits,
|
|
eng_grp->engs[i].bmap,
|
|
eng_grp->g->engs_num);
|
|
bmap.size = eng_grp->g->engs_num;
|
|
found = true;
|
|
}
|
|
}
|
|
|
|
if (!found)
|
|
dev_err(dev, "No engines reserved for engine group %d\n",
|
|
eng_grp->idx);
|
|
return bmap;
|
|
}
|
|
|
|
static int is_eng_type(int val, int eng_type)
|
|
{
|
|
return val & (1 << eng_type);
|
|
}
|
|
|
|
static int dev_supports_eng_type(struct otx_cpt_eng_grps *eng_grps,
|
|
int eng_type)
|
|
{
|
|
return is_eng_type(eng_grps->eng_types_supported, eng_type);
|
|
}
|
|
|
|
static void set_ucode_filename(struct otx_cpt_ucode *ucode,
|
|
const char *filename)
|
|
{
|
|
strscpy(ucode->filename, filename, OTX_CPT_UCODE_NAME_LENGTH);
|
|
}
|
|
|
|
static char *get_eng_type_str(int eng_type)
|
|
{
|
|
char *str = "unknown";
|
|
|
|
switch (eng_type) {
|
|
case OTX_CPT_SE_TYPES:
|
|
str = "SE";
|
|
break;
|
|
|
|
case OTX_CPT_AE_TYPES:
|
|
str = "AE";
|
|
break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
static char *get_ucode_type_str(int ucode_type)
|
|
{
|
|
char *str = "unknown";
|
|
|
|
switch (ucode_type) {
|
|
case (1 << OTX_CPT_SE_TYPES):
|
|
str = "SE";
|
|
break;
|
|
|
|
case (1 << OTX_CPT_AE_TYPES):
|
|
str = "AE";
|
|
break;
|
|
}
|
|
return str;
|
|
}
|
|
|
|
static int get_ucode_type(struct otx_cpt_ucode_hdr *ucode_hdr, int *ucode_type)
|
|
{
|
|
char tmp_ver_str[OTX_CPT_UCODE_VER_STR_SZ];
|
|
u32 i, val = 0;
|
|
u8 nn;
|
|
|
|
strscpy(tmp_ver_str, ucode_hdr->ver_str, OTX_CPT_UCODE_VER_STR_SZ);
|
|
for (i = 0; i < strlen(tmp_ver_str); i++)
|
|
tmp_ver_str[i] = tolower(tmp_ver_str[i]);
|
|
|
|
nn = ucode_hdr->ver_num.nn;
|
|
if (strnstr(tmp_ver_str, "se-", OTX_CPT_UCODE_VER_STR_SZ) &&
|
|
(nn == OTX_CPT_SE_UC_TYPE1 || nn == OTX_CPT_SE_UC_TYPE2 ||
|
|
nn == OTX_CPT_SE_UC_TYPE3))
|
|
val |= 1 << OTX_CPT_SE_TYPES;
|
|
if (strnstr(tmp_ver_str, "ae", OTX_CPT_UCODE_VER_STR_SZ) &&
|
|
nn == OTX_CPT_AE_UC_TYPE)
|
|
val |= 1 << OTX_CPT_AE_TYPES;
|
|
|
|
*ucode_type = val;
|
|
|
|
if (!val)
|
|
return -EINVAL;
|
|
if (is_eng_type(val, OTX_CPT_AE_TYPES) &&
|
|
is_eng_type(val, OTX_CPT_SE_TYPES))
|
|
return -EINVAL;
|
|
return 0;
|
|
}
|
|
|
|
static int is_mem_zero(const char *ptr, int size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (ptr[i])
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
static int cpt_set_ucode_base(struct otx_cpt_eng_grp_info *eng_grp, void *obj)
|
|
{
|
|
struct otx_cpt_device *cpt = (struct otx_cpt_device *) obj;
|
|
dma_addr_t dma_addr;
|
|
struct otx_cpt_bitmap bmap;
|
|
int i;
|
|
|
|
bmap = get_cores_bmap(&cpt->pdev->dev, eng_grp);
|
|
if (!bmap.size)
|
|
return -EINVAL;
|
|
|
|
if (eng_grp->mirror.is_ena)
|
|
dma_addr =
|
|
eng_grp->g->grp[eng_grp->mirror.idx].ucode[0].align_dma;
|
|
else
|
|
dma_addr = eng_grp->ucode[0].align_dma;
|
|
|
|
/*
|
|
* Set UCODE_BASE only for the cores which are not used,
|
|
* other cores should have already valid UCODE_BASE set
|
|
*/
|
|
for_each_set_bit(i, bmap.bits, bmap.size)
|
|
if (!eng_grp->g->eng_ref_cnt[i])
|
|
writeq((u64) dma_addr, cpt->reg_base +
|
|
OTX_CPT_PF_ENGX_UCODE_BASE(i));
|
|
return 0;
|
|
}
|
|
|
|
static int cpt_detach_and_disable_cores(struct otx_cpt_eng_grp_info *eng_grp,
|
|
void *obj)
|
|
{
|
|
struct otx_cpt_device *cpt = (struct otx_cpt_device *) obj;
|
|
struct otx_cpt_bitmap bmap = { {0} };
|
|
int timeout = 10;
|
|
int i, busy;
|
|
u64 reg;
|
|
|
|
bmap = get_cores_bmap(&cpt->pdev->dev, eng_grp);
|
|
if (!bmap.size)
|
|
return -EINVAL;
|
|
|
|
/* Detach the cores from group */
|
|
reg = readq(cpt->reg_base + OTX_CPT_PF_GX_EN(eng_grp->idx));
|
|
for_each_set_bit(i, bmap.bits, bmap.size) {
|
|
if (reg & (1ull << i)) {
|
|
eng_grp->g->eng_ref_cnt[i]--;
|
|
reg &= ~(1ull << i);
|
|
}
|
|
}
|
|
writeq(reg, cpt->reg_base + OTX_CPT_PF_GX_EN(eng_grp->idx));
|
|
|
|
/* Wait for cores to become idle */
|
|
do {
|
|
busy = 0;
|
|
usleep_range(10000, 20000);
|
|
if (timeout-- < 0)
|
|
return -EBUSY;
|
|
|
|
reg = readq(cpt->reg_base + OTX_CPT_PF_EXEC_BUSY);
|
|
for_each_set_bit(i, bmap.bits, bmap.size)
|
|
if (reg & (1ull << i)) {
|
|
busy = 1;
|
|
break;
|
|
}
|
|
} while (busy);
|
|
|
|
/* Disable the cores only if they are not used anymore */
|
|
reg = readq(cpt->reg_base + OTX_CPT_PF_EXE_CTL);
|
|
for_each_set_bit(i, bmap.bits, bmap.size)
|
|
if (!eng_grp->g->eng_ref_cnt[i])
|
|
reg &= ~(1ull << i);
|
|
writeq(reg, cpt->reg_base + OTX_CPT_PF_EXE_CTL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int cpt_attach_and_enable_cores(struct otx_cpt_eng_grp_info *eng_grp,
|
|
void *obj)
|
|
{
|
|
struct otx_cpt_device *cpt = (struct otx_cpt_device *) obj;
|
|
struct otx_cpt_bitmap bmap;
|
|
u64 reg;
|
|
int i;
|
|
|
|
bmap = get_cores_bmap(&cpt->pdev->dev, eng_grp);
|
|
if (!bmap.size)
|
|
return -EINVAL;
|
|
|
|
/* Attach the cores to the group */
|
|
reg = readq(cpt->reg_base + OTX_CPT_PF_GX_EN(eng_grp->idx));
|
|
for_each_set_bit(i, bmap.bits, bmap.size) {
|
|
if (!(reg & (1ull << i))) {
|
|
eng_grp->g->eng_ref_cnt[i]++;
|
|
reg |= 1ull << i;
|
|
}
|
|
}
|
|
writeq(reg, cpt->reg_base + OTX_CPT_PF_GX_EN(eng_grp->idx));
|
|
|
|
/* Enable the cores */
|
|
reg = readq(cpt->reg_base + OTX_CPT_PF_EXE_CTL);
|
|
for_each_set_bit(i, bmap.bits, bmap.size)
|
|
reg |= 1ull << i;
|
|
writeq(reg, cpt->reg_base + OTX_CPT_PF_EXE_CTL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int process_tar_file(struct device *dev,
|
|
struct tar_arch_info_t *tar_arch, char *filename,
|
|
const u8 *data, u32 size)
|
|
{
|
|
struct tar_ucode_info_t *tar_info;
|
|
struct otx_cpt_ucode_hdr *ucode_hdr;
|
|
int ucode_type, ucode_size;
|
|
unsigned int code_length;
|
|
|
|
/*
|
|
* If size is less than microcode header size then don't report
|
|
* an error because it might not be microcode file, just process
|
|
* next file from archive
|
|
*/
|
|
if (size < sizeof(struct otx_cpt_ucode_hdr))
|
|
return 0;
|
|
|
|
ucode_hdr = (struct otx_cpt_ucode_hdr *) data;
|
|
/*
|
|
* If microcode version can't be found don't report an error
|
|
* because it might not be microcode file, just process next file
|
|
*/
|
|
if (get_ucode_type(ucode_hdr, &ucode_type))
|
|
return 0;
|
|
|
|
code_length = ntohl(ucode_hdr->code_length);
|
|
if (code_length >= INT_MAX / 2) {
|
|
dev_err(dev, "Invalid code_length %u\n", code_length);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ucode_size = code_length * 2;
|
|
if (!ucode_size || (size < round_up(ucode_size, 16) +
|
|
sizeof(struct otx_cpt_ucode_hdr) + OTX_CPT_UCODE_SIGN_LEN)) {
|
|
dev_err(dev, "Ucode %s invalid size\n", filename);
|
|
return -EINVAL;
|
|
}
|
|
|
|
tar_info = kzalloc(sizeof(struct tar_ucode_info_t), GFP_KERNEL);
|
|
if (!tar_info)
|
|
return -ENOMEM;
|
|
|
|
tar_info->ucode_ptr = data;
|
|
set_ucode_filename(&tar_info->ucode, filename);
|
|
memcpy(tar_info->ucode.ver_str, ucode_hdr->ver_str,
|
|
OTX_CPT_UCODE_VER_STR_SZ);
|
|
tar_info->ucode.ver_num = ucode_hdr->ver_num;
|
|
tar_info->ucode.type = ucode_type;
|
|
tar_info->ucode.size = ucode_size;
|
|
list_add_tail(&tar_info->list, &tar_arch->ucodes);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void release_tar_archive(struct tar_arch_info_t *tar_arch)
|
|
{
|
|
struct tar_ucode_info_t *curr, *temp;
|
|
|
|
if (!tar_arch)
|
|
return;
|
|
|
|
list_for_each_entry_safe(curr, temp, &tar_arch->ucodes, list) {
|
|
list_del(&curr->list);
|
|
kfree(curr);
|
|
}
|
|
|
|
if (tar_arch->fw)
|
|
release_firmware(tar_arch->fw);
|
|
kfree(tar_arch);
|
|
}
|
|
|
|
static struct tar_ucode_info_t *get_uc_from_tar_archive(
|
|
struct tar_arch_info_t *tar_arch,
|
|
int ucode_type)
|
|
{
|
|
struct tar_ucode_info_t *curr, *uc_found = NULL;
|
|
|
|
list_for_each_entry(curr, &tar_arch->ucodes, list) {
|
|
if (!is_eng_type(curr->ucode.type, ucode_type))
|
|
continue;
|
|
|
|
if (!uc_found) {
|
|
uc_found = curr;
|
|
continue;
|
|
}
|
|
|
|
switch (ucode_type) {
|
|
case OTX_CPT_AE_TYPES:
|
|
break;
|
|
|
|
case OTX_CPT_SE_TYPES:
|
|
if (uc_found->ucode.ver_num.nn == OTX_CPT_SE_UC_TYPE2 ||
|
|
(uc_found->ucode.ver_num.nn == OTX_CPT_SE_UC_TYPE3
|
|
&& curr->ucode.ver_num.nn == OTX_CPT_SE_UC_TYPE1))
|
|
uc_found = curr;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return uc_found;
|
|
}
|
|
|
|
static void print_tar_dbg_info(struct tar_arch_info_t *tar_arch,
|
|
char *tar_filename)
|
|
{
|
|
struct tar_ucode_info_t *curr;
|
|
|
|
pr_debug("Tar archive filename %s\n", tar_filename);
|
|
pr_debug("Tar archive pointer %p, size %ld\n", tar_arch->fw->data,
|
|
tar_arch->fw->size);
|
|
list_for_each_entry(curr, &tar_arch->ucodes, list) {
|
|
pr_debug("Ucode filename %s\n", curr->ucode.filename);
|
|
pr_debug("Ucode version string %s\n", curr->ucode.ver_str);
|
|
pr_debug("Ucode version %d.%d.%d.%d\n",
|
|
curr->ucode.ver_num.nn, curr->ucode.ver_num.xx,
|
|
curr->ucode.ver_num.yy, curr->ucode.ver_num.zz);
|
|
pr_debug("Ucode type (%d) %s\n", curr->ucode.type,
|
|
get_ucode_type_str(curr->ucode.type));
|
|
pr_debug("Ucode size %d\n", curr->ucode.size);
|
|
pr_debug("Ucode ptr %p\n", curr->ucode_ptr);
|
|
}
|
|
}
|
|
|
|
static struct tar_arch_info_t *load_tar_archive(struct device *dev,
|
|
char *tar_filename)
|
|
{
|
|
struct tar_arch_info_t *tar_arch = NULL;
|
|
struct tar_blk_t *tar_blk;
|
|
unsigned int cur_size;
|
|
size_t tar_offs = 0;
|
|
size_t tar_size;
|
|
int ret;
|
|
|
|
tar_arch = kzalloc(sizeof(struct tar_arch_info_t), GFP_KERNEL);
|
|
if (!tar_arch)
|
|
return NULL;
|
|
|
|
INIT_LIST_HEAD(&tar_arch->ucodes);
|
|
|
|
/* Load tar archive */
|
|
ret = request_firmware(&tar_arch->fw, tar_filename, dev);
|
|
if (ret)
|
|
goto release_tar_arch;
|
|
|
|
if (tar_arch->fw->size < TAR_BLOCK_LEN) {
|
|
dev_err(dev, "Invalid tar archive %s\n", tar_filename);
|
|
goto release_tar_arch;
|
|
}
|
|
|
|
tar_size = tar_arch->fw->size;
|
|
tar_blk = (struct tar_blk_t *) tar_arch->fw->data;
|
|
if (strncmp(tar_blk->hdr.magic, TAR_MAGIC, TAR_MAGIC_LEN - 1)) {
|
|
dev_err(dev, "Unsupported format of tar archive %s\n",
|
|
tar_filename);
|
|
goto release_tar_arch;
|
|
}
|
|
|
|
while (1) {
|
|
/* Read current file size */
|
|
ret = kstrtouint(tar_blk->hdr.size, 8, &cur_size);
|
|
if (ret)
|
|
goto release_tar_arch;
|
|
|
|
if (tar_offs + cur_size > tar_size ||
|
|
tar_offs + 2*TAR_BLOCK_LEN > tar_size) {
|
|
dev_err(dev, "Invalid tar archive %s\n", tar_filename);
|
|
goto release_tar_arch;
|
|
}
|
|
|
|
tar_offs += TAR_BLOCK_LEN;
|
|
if (tar_blk->hdr.typeflag == REGTYPE ||
|
|
tar_blk->hdr.typeflag == AREGTYPE) {
|
|
ret = process_tar_file(dev, tar_arch,
|
|
tar_blk->hdr.name,
|
|
&tar_arch->fw->data[tar_offs],
|
|
cur_size);
|
|
if (ret)
|
|
goto release_tar_arch;
|
|
}
|
|
|
|
tar_offs += (cur_size/TAR_BLOCK_LEN) * TAR_BLOCK_LEN;
|
|
if (cur_size % TAR_BLOCK_LEN)
|
|
tar_offs += TAR_BLOCK_LEN;
|
|
|
|
/* Check for the end of the archive */
|
|
if (tar_offs + 2*TAR_BLOCK_LEN > tar_size) {
|
|
dev_err(dev, "Invalid tar archive %s\n", tar_filename);
|
|
goto release_tar_arch;
|
|
}
|
|
|
|
if (is_mem_zero(&tar_arch->fw->data[tar_offs],
|
|
2*TAR_BLOCK_LEN))
|
|
break;
|
|
|
|
/* Read next block from tar archive */
|
|
tar_blk = (struct tar_blk_t *) &tar_arch->fw->data[tar_offs];
|
|
}
|
|
|
|
print_tar_dbg_info(tar_arch, tar_filename);
|
|
return tar_arch;
|
|
release_tar_arch:
|
|
release_tar_archive(tar_arch);
|
|
return NULL;
|
|
}
|
|
|
|
static struct otx_cpt_engs_rsvd *find_engines_by_type(
|
|
struct otx_cpt_eng_grp_info *eng_grp,
|
|
int eng_type)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < OTX_CPT_MAX_ETYPES_PER_GRP; i++) {
|
|
if (!eng_grp->engs[i].type)
|
|
continue;
|
|
|
|
if (eng_grp->engs[i].type == eng_type)
|
|
return &eng_grp->engs[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int otx_cpt_uc_supports_eng_type(struct otx_cpt_ucode *ucode, int eng_type)
|
|
{
|
|
return is_eng_type(ucode->type, eng_type);
|
|
}
|
|
EXPORT_SYMBOL_GPL(otx_cpt_uc_supports_eng_type);
|
|
|
|
int otx_cpt_eng_grp_has_eng_type(struct otx_cpt_eng_grp_info *eng_grp,
|
|
int eng_type)
|
|
{
|
|
struct otx_cpt_engs_rsvd *engs;
|
|
|
|
engs = find_engines_by_type(eng_grp, eng_type);
|
|
|
|
return (engs != NULL ? 1 : 0);
|
|
}
|
|
EXPORT_SYMBOL_GPL(otx_cpt_eng_grp_has_eng_type);
|
|
|
|
static void print_ucode_info(struct otx_cpt_eng_grp_info *eng_grp,
|
|
char *buf, int size)
|
|
{
|
|
if (eng_grp->mirror.is_ena) {
|
|
scnprintf(buf, size, "%s (shared with engine_group%d)",
|
|
eng_grp->g->grp[eng_grp->mirror.idx].ucode[0].ver_str,
|
|
eng_grp->mirror.idx);
|
|
} else {
|
|
scnprintf(buf, size, "%s", eng_grp->ucode[0].ver_str);
|
|
}
|
|
}
|
|
|
|
static void print_engs_info(struct otx_cpt_eng_grp_info *eng_grp,
|
|
char *buf, int size, int idx)
|
|
{
|
|
struct otx_cpt_engs_rsvd *mirrored_engs = NULL;
|
|
struct otx_cpt_engs_rsvd *engs;
|
|
int len, i;
|
|
|
|
buf[0] = '\0';
|
|
for (i = 0; i < OTX_CPT_MAX_ETYPES_PER_GRP; i++) {
|
|
engs = &eng_grp->engs[i];
|
|
if (!engs->type)
|
|
continue;
|
|
if (idx != -1 && idx != i)
|
|
continue;
|
|
|
|
if (eng_grp->mirror.is_ena)
|
|
mirrored_engs = find_engines_by_type(
|
|
&eng_grp->g->grp[eng_grp->mirror.idx],
|
|
engs->type);
|
|
if (i > 0 && idx == -1) {
|
|
len = strlen(buf);
|
|
scnprintf(buf+len, size-len, ", ");
|
|
}
|
|
|
|
len = strlen(buf);
|
|
scnprintf(buf+len, size-len, "%d %s ", mirrored_engs ?
|
|
engs->count + mirrored_engs->count : engs->count,
|
|
get_eng_type_str(engs->type));
|
|
if (mirrored_engs) {
|
|
len = strlen(buf);
|
|
scnprintf(buf+len, size-len,
|
|
"(%d shared with engine_group%d) ",
|
|
engs->count <= 0 ? engs->count +
|
|
mirrored_engs->count : mirrored_engs->count,
|
|
eng_grp->mirror.idx);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void print_ucode_dbg_info(struct otx_cpt_ucode *ucode)
|
|
{
|
|
pr_debug("Ucode info\n");
|
|
pr_debug("Ucode version string %s\n", ucode->ver_str);
|
|
pr_debug("Ucode version %d.%d.%d.%d\n", ucode->ver_num.nn,
|
|
ucode->ver_num.xx, ucode->ver_num.yy, ucode->ver_num.zz);
|
|
pr_debug("Ucode type %s\n", get_ucode_type_str(ucode->type));
|
|
pr_debug("Ucode size %d\n", ucode->size);
|
|
pr_debug("Ucode virt address %16.16llx\n", (u64)ucode->align_va);
|
|
pr_debug("Ucode phys address %16.16llx\n", ucode->align_dma);
|
|
}
|
|
|
|
static void cpt_print_engines_mask(struct otx_cpt_eng_grp_info *eng_grp,
|
|
struct device *dev, char *buf, int size)
|
|
{
|
|
struct otx_cpt_bitmap bmap;
|
|
u32 mask[2];
|
|
|
|
bmap = get_cores_bmap(dev, eng_grp);
|
|
if (!bmap.size) {
|
|
scnprintf(buf, size, "unknown");
|
|
return;
|
|
}
|
|
bitmap_to_arr32(mask, bmap.bits, bmap.size);
|
|
scnprintf(buf, size, "%8.8x %8.8x", mask[1], mask[0]);
|
|
}
|
|
|
|
|
|
static void print_dbg_info(struct device *dev,
|
|
struct otx_cpt_eng_grps *eng_grps)
|
|
{
|
|
char engs_info[2*OTX_CPT_UCODE_NAME_LENGTH];
|
|
struct otx_cpt_eng_grp_info *mirrored_grp;
|
|
char engs_mask[OTX_CPT_UCODE_NAME_LENGTH];
|
|
struct otx_cpt_eng_grp_info *grp;
|
|
struct otx_cpt_engs_rsvd *engs;
|
|
u32 mask[4];
|
|
int i, j;
|
|
|
|
pr_debug("Engine groups global info\n");
|
|
pr_debug("max SE %d, max AE %d\n",
|
|
eng_grps->avail.max_se_cnt, eng_grps->avail.max_ae_cnt);
|
|
pr_debug("free SE %d\n", eng_grps->avail.se_cnt);
|
|
pr_debug("free AE %d\n", eng_grps->avail.ae_cnt);
|
|
|
|
for (i = 0; i < OTX_CPT_MAX_ENGINE_GROUPS; i++) {
|
|
grp = &eng_grps->grp[i];
|
|
pr_debug("engine_group%d, state %s\n", i, grp->is_enabled ?
|
|
"enabled" : "disabled");
|
|
if (grp->is_enabled) {
|
|
mirrored_grp = &eng_grps->grp[grp->mirror.idx];
|
|
pr_debug("Ucode0 filename %s, version %s\n",
|
|
grp->mirror.is_ena ?
|
|
mirrored_grp->ucode[0].filename :
|
|
grp->ucode[0].filename,
|
|
grp->mirror.is_ena ?
|
|
mirrored_grp->ucode[0].ver_str :
|
|
grp->ucode[0].ver_str);
|
|
}
|
|
|
|
for (j = 0; j < OTX_CPT_MAX_ETYPES_PER_GRP; j++) {
|
|
engs = &grp->engs[j];
|
|
if (engs->type) {
|
|
print_engs_info(grp, engs_info,
|
|
2*OTX_CPT_UCODE_NAME_LENGTH, j);
|
|
pr_debug("Slot%d: %s\n", j, engs_info);
|
|
bitmap_to_arr32(mask, engs->bmap,
|
|
eng_grps->engs_num);
|
|
pr_debug("Mask: %8.8x %8.8x %8.8x %8.8x\n",
|
|
mask[3], mask[2], mask[1], mask[0]);
|
|
} else
|
|
pr_debug("Slot%d not used\n", j);
|
|
}
|
|
if (grp->is_enabled) {
|
|
cpt_print_engines_mask(grp, dev, engs_mask,
|
|
OTX_CPT_UCODE_NAME_LENGTH);
|
|
pr_debug("Cmask: %s\n", engs_mask);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int update_engines_avail_count(struct device *dev,
|
|
struct otx_cpt_engs_available *avail,
|
|
struct otx_cpt_engs_rsvd *engs, int val)
|
|
{
|
|
switch (engs->type) {
|
|
case OTX_CPT_SE_TYPES:
|
|
avail->se_cnt += val;
|
|
break;
|
|
|
|
case OTX_CPT_AE_TYPES:
|
|
avail->ae_cnt += val;
|
|
break;
|
|
|
|
default:
|
|
dev_err(dev, "Invalid engine type %d\n", engs->type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int update_engines_offset(struct device *dev,
|
|
struct otx_cpt_engs_available *avail,
|
|
struct otx_cpt_engs_rsvd *engs)
|
|
{
|
|
switch (engs->type) {
|
|
case OTX_CPT_SE_TYPES:
|
|
engs->offset = 0;
|
|
break;
|
|
|
|
case OTX_CPT_AE_TYPES:
|
|
engs->offset = avail->max_se_cnt;
|
|
break;
|
|
|
|
default:
|
|
dev_err(dev, "Invalid engine type %d\n", engs->type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int release_engines(struct device *dev, struct otx_cpt_eng_grp_info *grp)
|
|
{
|
|
int i, ret = 0;
|
|
|
|
for (i = 0; i < OTX_CPT_MAX_ETYPES_PER_GRP; i++) {
|
|
if (!grp->engs[i].type)
|
|
continue;
|
|
|
|
if (grp->engs[i].count > 0) {
|
|
ret = update_engines_avail_count(dev, &grp->g->avail,
|
|
&grp->engs[i],
|
|
grp->engs[i].count);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
grp->engs[i].type = 0;
|
|
grp->engs[i].count = 0;
|
|
grp->engs[i].offset = 0;
|
|
grp->engs[i].ucode = NULL;
|
|
bitmap_zero(grp->engs[i].bmap, grp->g->engs_num);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int do_reserve_engines(struct device *dev,
|
|
struct otx_cpt_eng_grp_info *grp,
|
|
struct otx_cpt_engines *req_engs)
|
|
{
|
|
struct otx_cpt_engs_rsvd *engs = NULL;
|
|
int i, ret;
|
|
|
|
for (i = 0; i < OTX_CPT_MAX_ETYPES_PER_GRP; i++) {
|
|
if (!grp->engs[i].type) {
|
|
engs = &grp->engs[i];
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!engs)
|
|
return -ENOMEM;
|
|
|
|
engs->type = req_engs->type;
|
|
engs->count = req_engs->count;
|
|
|
|
ret = update_engines_offset(dev, &grp->g->avail, engs);
|
|
if (ret)
|
|
return ret;
|
|
|
|
if (engs->count > 0) {
|
|
ret = update_engines_avail_count(dev, &grp->g->avail, engs,
|
|
-engs->count);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int check_engines_availability(struct device *dev,
|
|
struct otx_cpt_eng_grp_info *grp,
|
|
struct otx_cpt_engines *req_eng)
|
|
{
|
|
int avail_cnt = 0;
|
|
|
|
switch (req_eng->type) {
|
|
case OTX_CPT_SE_TYPES:
|
|
avail_cnt = grp->g->avail.se_cnt;
|
|
break;
|
|
|
|
case OTX_CPT_AE_TYPES:
|
|
avail_cnt = grp->g->avail.ae_cnt;
|
|
break;
|
|
|
|
default:
|
|
dev_err(dev, "Invalid engine type %d\n", req_eng->type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (avail_cnt < req_eng->count) {
|
|
dev_err(dev,
|
|
"Error available %s engines %d < than requested %d\n",
|
|
get_eng_type_str(req_eng->type),
|
|
avail_cnt, req_eng->count);
|
|
return -EBUSY;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int reserve_engines(struct device *dev, struct otx_cpt_eng_grp_info *grp,
|
|
struct otx_cpt_engines *req_engs, int req_cnt)
|
|
{
|
|
int i, ret;
|
|
|
|
/* Validate if a number of requested engines is available */
|
|
for (i = 0; i < req_cnt; i++) {
|
|
ret = check_engines_availability(dev, grp, &req_engs[i]);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
|
|
/* Reserve requested engines for this engine group */
|
|
for (i = 0; i < req_cnt; i++) {
|
|
ret = do_reserve_engines(dev, grp, &req_engs[i]);
|
|
if (ret)
|
|
return ret;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t eng_grp_info_show(struct device *dev,
|
|
struct device_attribute *attr,
|
|
char *buf)
|
|
{
|
|
char ucode_info[2*OTX_CPT_UCODE_NAME_LENGTH];
|
|
char engs_info[2*OTX_CPT_UCODE_NAME_LENGTH];
|
|
char engs_mask[OTX_CPT_UCODE_NAME_LENGTH];
|
|
struct otx_cpt_eng_grp_info *eng_grp;
|
|
int ret;
|
|
|
|
eng_grp = container_of(attr, struct otx_cpt_eng_grp_info, info_attr);
|
|
mutex_lock(&eng_grp->g->lock);
|
|
|
|
print_engs_info(eng_grp, engs_info, 2*OTX_CPT_UCODE_NAME_LENGTH, -1);
|
|
print_ucode_info(eng_grp, ucode_info, 2*OTX_CPT_UCODE_NAME_LENGTH);
|
|
cpt_print_engines_mask(eng_grp, dev, engs_mask,
|
|
OTX_CPT_UCODE_NAME_LENGTH);
|
|
ret = scnprintf(buf, PAGE_SIZE,
|
|
"Microcode : %s\nEngines: %s\nEngines mask: %s\n",
|
|
ucode_info, engs_info, engs_mask);
|
|
|
|
mutex_unlock(&eng_grp->g->lock);
|
|
return ret;
|
|
}
|
|
|
|
static int create_sysfs_eng_grps_info(struct device *dev,
|
|
struct otx_cpt_eng_grp_info *eng_grp)
|
|
{
|
|
eng_grp->info_attr.show = eng_grp_info_show;
|
|
eng_grp->info_attr.store = NULL;
|
|
eng_grp->info_attr.attr.name = eng_grp->sysfs_info_name;
|
|
eng_grp->info_attr.attr.mode = 0440;
|
|
sysfs_attr_init(&eng_grp->info_attr.attr);
|
|
return device_create_file(dev, &eng_grp->info_attr);
|
|
}
|
|
|
|
static void ucode_unload(struct device *dev, struct otx_cpt_ucode *ucode)
|
|
{
|
|
if (ucode->va) {
|
|
dma_free_coherent(dev, ucode->size + OTX_CPT_UCODE_ALIGNMENT,
|
|
ucode->va, ucode->dma);
|
|
ucode->va = NULL;
|
|
ucode->align_va = NULL;
|
|
ucode->dma = 0;
|
|
ucode->align_dma = 0;
|
|
ucode->size = 0;
|
|
}
|
|
|
|
memset(&ucode->ver_str, 0, OTX_CPT_UCODE_VER_STR_SZ);
|
|
memset(&ucode->ver_num, 0, sizeof(struct otx_cpt_ucode_ver_num));
|
|
set_ucode_filename(ucode, "");
|
|
ucode->type = 0;
|
|
}
|
|
|
|
static int copy_ucode_to_dma_mem(struct device *dev,
|
|
struct otx_cpt_ucode *ucode,
|
|
const u8 *ucode_data)
|
|
{
|
|
u32 i;
|
|
|
|
/* Allocate DMAable space */
|
|
ucode->va = dma_alloc_coherent(dev, ucode->size +
|
|
OTX_CPT_UCODE_ALIGNMENT,
|
|
&ucode->dma, GFP_KERNEL);
|
|
if (!ucode->va) {
|
|
dev_err(dev, "Unable to allocate space for microcode\n");
|
|
return -ENOMEM;
|
|
}
|
|
ucode->align_va = PTR_ALIGN(ucode->va, OTX_CPT_UCODE_ALIGNMENT);
|
|
ucode->align_dma = PTR_ALIGN(ucode->dma, OTX_CPT_UCODE_ALIGNMENT);
|
|
|
|
memcpy((void *) ucode->align_va, (void *) ucode_data +
|
|
sizeof(struct otx_cpt_ucode_hdr), ucode->size);
|
|
|
|
/* Byte swap 64-bit */
|
|
for (i = 0; i < (ucode->size / 8); i++)
|
|
((__be64 *)ucode->align_va)[i] =
|
|
cpu_to_be64(((u64 *)ucode->align_va)[i]);
|
|
/* Ucode needs 16-bit swap */
|
|
for (i = 0; i < (ucode->size / 2); i++)
|
|
((__be16 *)ucode->align_va)[i] =
|
|
cpu_to_be16(((u16 *)ucode->align_va)[i]);
|
|
return 0;
|
|
}
|
|
|
|
static int ucode_load(struct device *dev, struct otx_cpt_ucode *ucode,
|
|
const char *ucode_filename)
|
|
{
|
|
struct otx_cpt_ucode_hdr *ucode_hdr;
|
|
const struct firmware *fw;
|
|
unsigned int code_length;
|
|
int ret;
|
|
|
|
set_ucode_filename(ucode, ucode_filename);
|
|
ret = request_firmware(&fw, ucode->filename, dev);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ucode_hdr = (struct otx_cpt_ucode_hdr *) fw->data;
|
|
memcpy(ucode->ver_str, ucode_hdr->ver_str, OTX_CPT_UCODE_VER_STR_SZ);
|
|
ucode->ver_num = ucode_hdr->ver_num;
|
|
code_length = ntohl(ucode_hdr->code_length);
|
|
if (code_length >= INT_MAX / 2) {
|
|
dev_err(dev, "Ucode invalid code_length %u\n", code_length);
|
|
ret = -EINVAL;
|
|
goto release_fw;
|
|
}
|
|
ucode->size = code_length * 2;
|
|
if (!ucode->size || (fw->size < round_up(ucode->size, 16)
|
|
+ sizeof(struct otx_cpt_ucode_hdr) + OTX_CPT_UCODE_SIGN_LEN)) {
|
|
dev_err(dev, "Ucode %s invalid size\n", ucode_filename);
|
|
ret = -EINVAL;
|
|
goto release_fw;
|
|
}
|
|
|
|
ret = get_ucode_type(ucode_hdr, &ucode->type);
|
|
if (ret) {
|
|
dev_err(dev, "Microcode %s unknown type 0x%x\n",
|
|
ucode->filename, ucode->type);
|
|
goto release_fw;
|
|
}
|
|
|
|
ret = copy_ucode_to_dma_mem(dev, ucode, fw->data);
|
|
if (ret)
|
|
goto release_fw;
|
|
|
|
print_ucode_dbg_info(ucode);
|
|
release_fw:
|
|
release_firmware(fw);
|
|
return ret;
|
|
}
|
|
|
|
static int enable_eng_grp(struct otx_cpt_eng_grp_info *eng_grp,
|
|
void *obj)
|
|
{
|
|
int ret;
|
|
|
|
ret = cpt_set_ucode_base(eng_grp, obj);
|
|
if (ret)
|
|
return ret;
|
|
|
|
ret = cpt_attach_and_enable_cores(eng_grp, obj);
|
|
return ret;
|
|
}
|
|
|
|
static int disable_eng_grp(struct device *dev,
|
|
struct otx_cpt_eng_grp_info *eng_grp,
|
|
void *obj)
|
|
{
|
|
int i, ret;
|
|
|
|
ret = cpt_detach_and_disable_cores(eng_grp, obj);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Unload ucode used by this engine group */
|
|
ucode_unload(dev, &eng_grp->ucode[0]);
|
|
|
|
for (i = 0; i < OTX_CPT_MAX_ETYPES_PER_GRP; i++) {
|
|
if (!eng_grp->engs[i].type)
|
|
continue;
|
|
|
|
eng_grp->engs[i].ucode = &eng_grp->ucode[0];
|
|
}
|
|
|
|
ret = cpt_set_ucode_base(eng_grp, obj);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void setup_eng_grp_mirroring(struct otx_cpt_eng_grp_info *dst_grp,
|
|
struct otx_cpt_eng_grp_info *src_grp)
|
|
{
|
|
/* Setup fields for engine group which is mirrored */
|
|
src_grp->mirror.is_ena = false;
|
|
src_grp->mirror.idx = 0;
|
|
src_grp->mirror.ref_count++;
|
|
|
|
/* Setup fields for mirroring engine group */
|
|
dst_grp->mirror.is_ena = true;
|
|
dst_grp->mirror.idx = src_grp->idx;
|
|
dst_grp->mirror.ref_count = 0;
|
|
}
|
|
|
|
static void remove_eng_grp_mirroring(struct otx_cpt_eng_grp_info *dst_grp)
|
|
{
|
|
struct otx_cpt_eng_grp_info *src_grp;
|
|
|
|
if (!dst_grp->mirror.is_ena)
|
|
return;
|
|
|
|
src_grp = &dst_grp->g->grp[dst_grp->mirror.idx];
|
|
|
|
src_grp->mirror.ref_count--;
|
|
dst_grp->mirror.is_ena = false;
|
|
dst_grp->mirror.idx = 0;
|
|
dst_grp->mirror.ref_count = 0;
|
|
}
|
|
|
|
static void update_requested_engs(struct otx_cpt_eng_grp_info *mirrored_eng_grp,
|
|
struct otx_cpt_engines *engs, int engs_cnt)
|
|
{
|
|
struct otx_cpt_engs_rsvd *mirrored_engs;
|
|
int i;
|
|
|
|
for (i = 0; i < engs_cnt; i++) {
|
|
mirrored_engs = find_engines_by_type(mirrored_eng_grp,
|
|
engs[i].type);
|
|
if (!mirrored_engs)
|
|
continue;
|
|
|
|
/*
|
|
* If mirrored group has this type of engines attached then
|
|
* there are 3 scenarios possible:
|
|
* 1) mirrored_engs.count == engs[i].count then all engines
|
|
* from mirrored engine group will be shared with this engine
|
|
* group
|
|
* 2) mirrored_engs.count > engs[i].count then only a subset of
|
|
* engines from mirrored engine group will be shared with this
|
|
* engine group
|
|
* 3) mirrored_engs.count < engs[i].count then all engines
|
|
* from mirrored engine group will be shared with this group
|
|
* and additional engines will be reserved for exclusively use
|
|
* by this engine group
|
|
*/
|
|
engs[i].count -= mirrored_engs->count;
|
|
}
|
|
}
|
|
|
|
static struct otx_cpt_eng_grp_info *find_mirrored_eng_grp(
|
|
struct otx_cpt_eng_grp_info *grp)
|
|
{
|
|
struct otx_cpt_eng_grps *eng_grps = grp->g;
|
|
int i;
|
|
|
|
for (i = 0; i < OTX_CPT_MAX_ENGINE_GROUPS; i++) {
|
|
if (!eng_grps->grp[i].is_enabled)
|
|
continue;
|
|
if (eng_grps->grp[i].ucode[0].type)
|
|
continue;
|
|
if (grp->idx == i)
|
|
continue;
|
|
if (!strncasecmp(eng_grps->grp[i].ucode[0].ver_str,
|
|
grp->ucode[0].ver_str,
|
|
OTX_CPT_UCODE_VER_STR_SZ))
|
|
return &eng_grps->grp[i];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct otx_cpt_eng_grp_info *find_unused_eng_grp(
|
|
struct otx_cpt_eng_grps *eng_grps)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < OTX_CPT_MAX_ENGINE_GROUPS; i++) {
|
|
if (!eng_grps->grp[i].is_enabled)
|
|
return &eng_grps->grp[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static int eng_grp_update_masks(struct device *dev,
|
|
struct otx_cpt_eng_grp_info *eng_grp)
|
|
{
|
|
struct otx_cpt_engs_rsvd *engs, *mirrored_engs;
|
|
struct otx_cpt_bitmap tmp_bmap = { {0} };
|
|
int i, j, cnt, max_cnt;
|
|
int bit;
|
|
|
|
for (i = 0; i < OTX_CPT_MAX_ETYPES_PER_GRP; i++) {
|
|
engs = &eng_grp->engs[i];
|
|
if (!engs->type)
|
|
continue;
|
|
if (engs->count <= 0)
|
|
continue;
|
|
|
|
switch (engs->type) {
|
|
case OTX_CPT_SE_TYPES:
|
|
max_cnt = eng_grp->g->avail.max_se_cnt;
|
|
break;
|
|
|
|
case OTX_CPT_AE_TYPES:
|
|
max_cnt = eng_grp->g->avail.max_ae_cnt;
|
|
break;
|
|
|
|
default:
|
|
dev_err(dev, "Invalid engine type %d\n", engs->type);
|
|
return -EINVAL;
|
|
}
|
|
|
|
cnt = engs->count;
|
|
WARN_ON(engs->offset + max_cnt > OTX_CPT_MAX_ENGINES);
|
|
bitmap_zero(tmp_bmap.bits, eng_grp->g->engs_num);
|
|
for (j = engs->offset; j < engs->offset + max_cnt; j++) {
|
|
if (!eng_grp->g->eng_ref_cnt[j]) {
|
|
bitmap_set(tmp_bmap.bits, j, 1);
|
|
cnt--;
|
|
if (!cnt)
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (cnt)
|
|
return -ENOSPC;
|
|
|
|
bitmap_copy(engs->bmap, tmp_bmap.bits, eng_grp->g->engs_num);
|
|
}
|
|
|
|
if (!eng_grp->mirror.is_ena)
|
|
return 0;
|
|
|
|
for (i = 0; i < OTX_CPT_MAX_ETYPES_PER_GRP; i++) {
|
|
engs = &eng_grp->engs[i];
|
|
if (!engs->type)
|
|
continue;
|
|
|
|
mirrored_engs = find_engines_by_type(
|
|
&eng_grp->g->grp[eng_grp->mirror.idx],
|
|
engs->type);
|
|
WARN_ON(!mirrored_engs && engs->count <= 0);
|
|
if (!mirrored_engs)
|
|
continue;
|
|
|
|
bitmap_copy(tmp_bmap.bits, mirrored_engs->bmap,
|
|
eng_grp->g->engs_num);
|
|
if (engs->count < 0) {
|
|
bit = find_first_bit(mirrored_engs->bmap,
|
|
eng_grp->g->engs_num);
|
|
bitmap_clear(tmp_bmap.bits, bit, -engs->count);
|
|
}
|
|
bitmap_or(engs->bmap, engs->bmap, tmp_bmap.bits,
|
|
eng_grp->g->engs_num);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int delete_engine_group(struct device *dev,
|
|
struct otx_cpt_eng_grp_info *eng_grp)
|
|
{
|
|
int i, ret;
|
|
|
|
if (!eng_grp->is_enabled)
|
|
return -EINVAL;
|
|
|
|
if (eng_grp->mirror.ref_count) {
|
|
dev_err(dev, "Can't delete engine_group%d as it is used by engine_group(s):",
|
|
eng_grp->idx);
|
|
for (i = 0; i < OTX_CPT_MAX_ENGINE_GROUPS; i++) {
|
|
if (eng_grp->g->grp[i].mirror.is_ena &&
|
|
eng_grp->g->grp[i].mirror.idx == eng_grp->idx)
|
|
pr_cont(" %d", i);
|
|
}
|
|
pr_cont("\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* Removing engine group mirroring if enabled */
|
|
remove_eng_grp_mirroring(eng_grp);
|
|
|
|
/* Disable engine group */
|
|
ret = disable_eng_grp(dev, eng_grp, eng_grp->g->obj);
|
|
if (ret)
|
|
return ret;
|
|
|
|
/* Release all engines held by this engine group */
|
|
ret = release_engines(dev, eng_grp);
|
|
if (ret)
|
|
return ret;
|
|
|
|
device_remove_file(dev, &eng_grp->info_attr);
|
|
eng_grp->is_enabled = false;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int validate_1_ucode_scenario(struct device *dev,
|
|
struct otx_cpt_eng_grp_info *eng_grp,
|
|
struct otx_cpt_engines *engs, int engs_cnt)
|
|
{
|
|
int i;
|
|
|
|
/* Verify that ucode loaded supports requested engine types */
|
|
for (i = 0; i < engs_cnt; i++) {
|
|
if (!otx_cpt_uc_supports_eng_type(&eng_grp->ucode[0],
|
|
engs[i].type)) {
|
|
dev_err(dev,
|
|
"Microcode %s does not support %s engines\n",
|
|
eng_grp->ucode[0].filename,
|
|
get_eng_type_str(engs[i].type));
|
|
return -EINVAL;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void update_ucode_ptrs(struct otx_cpt_eng_grp_info *eng_grp)
|
|
{
|
|
struct otx_cpt_ucode *ucode;
|
|
|
|
if (eng_grp->mirror.is_ena)
|
|
ucode = &eng_grp->g->grp[eng_grp->mirror.idx].ucode[0];
|
|
else
|
|
ucode = &eng_grp->ucode[0];
|
|
WARN_ON(!eng_grp->engs[0].type);
|
|
eng_grp->engs[0].ucode = ucode;
|
|
}
|
|
|
|
static int create_engine_group(struct device *dev,
|
|
struct otx_cpt_eng_grps *eng_grps,
|
|
struct otx_cpt_engines *engs, int engs_cnt,
|
|
void *ucode_data[], int ucodes_cnt,
|
|
bool use_uc_from_tar_arch)
|
|
{
|
|
struct otx_cpt_eng_grp_info *mirrored_eng_grp;
|
|
struct tar_ucode_info_t *tar_info;
|
|
struct otx_cpt_eng_grp_info *eng_grp;
|
|
int i, ret = 0;
|
|
|
|
if (ucodes_cnt > OTX_CPT_MAX_ETYPES_PER_GRP)
|
|
return -EINVAL;
|
|
|
|
/* Validate if requested engine types are supported by this device */
|
|
for (i = 0; i < engs_cnt; i++)
|
|
if (!dev_supports_eng_type(eng_grps, engs[i].type)) {
|
|
dev_err(dev, "Device does not support %s engines\n",
|
|
get_eng_type_str(engs[i].type));
|
|
return -EPERM;
|
|
}
|
|
|
|
/* Find engine group which is not used */
|
|
eng_grp = find_unused_eng_grp(eng_grps);
|
|
if (!eng_grp) {
|
|
dev_err(dev, "Error all engine groups are being used\n");
|
|
return -ENOSPC;
|
|
}
|
|
|
|
/* Load ucode */
|
|
for (i = 0; i < ucodes_cnt; i++) {
|
|
if (use_uc_from_tar_arch) {
|
|
tar_info = (struct tar_ucode_info_t *) ucode_data[i];
|
|
eng_grp->ucode[i] = tar_info->ucode;
|
|
ret = copy_ucode_to_dma_mem(dev, &eng_grp->ucode[i],
|
|
tar_info->ucode_ptr);
|
|
} else
|
|
ret = ucode_load(dev, &eng_grp->ucode[i],
|
|
(char *) ucode_data[i]);
|
|
if (ret)
|
|
goto err_ucode_unload;
|
|
}
|
|
|
|
/* Validate scenario where 1 ucode is used */
|
|
ret = validate_1_ucode_scenario(dev, eng_grp, engs, engs_cnt);
|
|
if (ret)
|
|
goto err_ucode_unload;
|
|
|
|
/* Check if this group mirrors another existing engine group */
|
|
mirrored_eng_grp = find_mirrored_eng_grp(eng_grp);
|
|
if (mirrored_eng_grp) {
|
|
/* Setup mirroring */
|
|
setup_eng_grp_mirroring(eng_grp, mirrored_eng_grp);
|
|
|
|
/*
|
|
* Update count of requested engines because some
|
|
* of them might be shared with mirrored group
|
|
*/
|
|
update_requested_engs(mirrored_eng_grp, engs, engs_cnt);
|
|
}
|
|
|
|
/* Reserve engines */
|
|
ret = reserve_engines(dev, eng_grp, engs, engs_cnt);
|
|
if (ret)
|
|
goto err_ucode_unload;
|
|
|
|
/* Update ucode pointers used by engines */
|
|
update_ucode_ptrs(eng_grp);
|
|
|
|
/* Update engine masks used by this group */
|
|
ret = eng_grp_update_masks(dev, eng_grp);
|
|
if (ret)
|
|
goto err_release_engs;
|
|
|
|
/* Create sysfs entry for engine group info */
|
|
ret = create_sysfs_eng_grps_info(dev, eng_grp);
|
|
if (ret)
|
|
goto err_release_engs;
|
|
|
|
/* Enable engine group */
|
|
ret = enable_eng_grp(eng_grp, eng_grps->obj);
|
|
if (ret)
|
|
goto err_release_engs;
|
|
|
|
/*
|
|
* If this engine group mirrors another engine group
|
|
* then we need to unload ucode as we will use ucode
|
|
* from mirrored engine group
|
|
*/
|
|
if (eng_grp->mirror.is_ena)
|
|
ucode_unload(dev, &eng_grp->ucode[0]);
|
|
|
|
eng_grp->is_enabled = true;
|
|
if (eng_grp->mirror.is_ena)
|
|
dev_info(dev,
|
|
"Engine_group%d: reuse microcode %s from group %d\n",
|
|
eng_grp->idx, mirrored_eng_grp->ucode[0].ver_str,
|
|
mirrored_eng_grp->idx);
|
|
else
|
|
dev_info(dev, "Engine_group%d: microcode loaded %s\n",
|
|
eng_grp->idx, eng_grp->ucode[0].ver_str);
|
|
|
|
return 0;
|
|
|
|
err_release_engs:
|
|
release_engines(dev, eng_grp);
|
|
err_ucode_unload:
|
|
ucode_unload(dev, &eng_grp->ucode[0]);
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t ucode_load_store(struct device *dev,
|
|
struct device_attribute *attr,
|
|
const char *buf, size_t count)
|
|
{
|
|
struct otx_cpt_engines engs[OTX_CPT_MAX_ETYPES_PER_GRP] = { {0} };
|
|
char *ucode_filename[OTX_CPT_MAX_ETYPES_PER_GRP];
|
|
char tmp_buf[OTX_CPT_UCODE_NAME_LENGTH] = { 0 };
|
|
char *start, *val, *err_msg, *tmp;
|
|
struct otx_cpt_eng_grps *eng_grps;
|
|
int grp_idx = 0, ret = -EINVAL;
|
|
bool has_se, has_ie, has_ae;
|
|
int del_grp_idx = -1;
|
|
int ucode_idx = 0;
|
|
|
|
if (strlen(buf) > OTX_CPT_UCODE_NAME_LENGTH)
|
|
return -EINVAL;
|
|
|
|
eng_grps = container_of(attr, struct otx_cpt_eng_grps, ucode_load_attr);
|
|
err_msg = "Invalid engine group format";
|
|
strscpy(tmp_buf, buf, OTX_CPT_UCODE_NAME_LENGTH);
|
|
start = tmp_buf;
|
|
|
|
has_se = has_ie = has_ae = false;
|
|
|
|
for (;;) {
|
|
val = strsep(&start, ";");
|
|
if (!val)
|
|
break;
|
|
val = strim(val);
|
|
if (!*val)
|
|
continue;
|
|
|
|
if (!strncasecmp(val, "engine_group", 12)) {
|
|
if (del_grp_idx != -1)
|
|
goto err_print;
|
|
tmp = strim(strsep(&val, ":"));
|
|
if (!val)
|
|
goto err_print;
|
|
if (strlen(tmp) != 13)
|
|
goto err_print;
|
|
if (kstrtoint((tmp + 12), 10, &del_grp_idx))
|
|
goto err_print;
|
|
val = strim(val);
|
|
if (strncasecmp(val, "null", 4))
|
|
goto err_print;
|
|
if (strlen(val) != 4)
|
|
goto err_print;
|
|
} else if (!strncasecmp(val, "se", 2) && strchr(val, ':')) {
|
|
if (has_se || ucode_idx)
|
|
goto err_print;
|
|
tmp = strim(strsep(&val, ":"));
|
|
if (!val)
|
|
goto err_print;
|
|
if (strlen(tmp) != 2)
|
|
goto err_print;
|
|
if (kstrtoint(strim(val), 10, &engs[grp_idx].count))
|
|
goto err_print;
|
|
engs[grp_idx++].type = OTX_CPT_SE_TYPES;
|
|
has_se = true;
|
|
} else if (!strncasecmp(val, "ae", 2) && strchr(val, ':')) {
|
|
if (has_ae || ucode_idx)
|
|
goto err_print;
|
|
tmp = strim(strsep(&val, ":"));
|
|
if (!val)
|
|
goto err_print;
|
|
if (strlen(tmp) != 2)
|
|
goto err_print;
|
|
if (kstrtoint(strim(val), 10, &engs[grp_idx].count))
|
|
goto err_print;
|
|
engs[grp_idx++].type = OTX_CPT_AE_TYPES;
|
|
has_ae = true;
|
|
} else {
|
|
if (ucode_idx > 1)
|
|
goto err_print;
|
|
if (!strlen(val))
|
|
goto err_print;
|
|
if (strnstr(val, " ", strlen(val)))
|
|
goto err_print;
|
|
ucode_filename[ucode_idx++] = val;
|
|
}
|
|
}
|
|
|
|
/* Validate input parameters */
|
|
if (del_grp_idx == -1) {
|
|
if (!(grp_idx && ucode_idx))
|
|
goto err_print;
|
|
|
|
if (ucode_idx > 1 && grp_idx < 2)
|
|
goto err_print;
|
|
|
|
if (grp_idx > OTX_CPT_MAX_ETYPES_PER_GRP) {
|
|
err_msg = "Error max 2 engine types can be attached";
|
|
goto err_print;
|
|
}
|
|
|
|
} else {
|
|
if (del_grp_idx < 0 ||
|
|
del_grp_idx >= OTX_CPT_MAX_ENGINE_GROUPS) {
|
|
dev_err(dev, "Invalid engine group index %d\n",
|
|
del_grp_idx);
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
|
|
if (!eng_grps->grp[del_grp_idx].is_enabled) {
|
|
dev_err(dev, "Error engine_group%d is not configured\n",
|
|
del_grp_idx);
|
|
ret = -EINVAL;
|
|
return ret;
|
|
}
|
|
|
|
if (grp_idx || ucode_idx)
|
|
goto err_print;
|
|
}
|
|
|
|
mutex_lock(&eng_grps->lock);
|
|
|
|
if (eng_grps->is_rdonly) {
|
|
dev_err(dev, "Disable VFs before modifying engine groups\n");
|
|
ret = -EACCES;
|
|
goto err_unlock;
|
|
}
|
|
|
|
if (del_grp_idx == -1)
|
|
/* create engine group */
|
|
ret = create_engine_group(dev, eng_grps, engs, grp_idx,
|
|
(void **) ucode_filename,
|
|
ucode_idx, false);
|
|
else
|
|
/* delete engine group */
|
|
ret = delete_engine_group(dev, &eng_grps->grp[del_grp_idx]);
|
|
if (ret)
|
|
goto err_unlock;
|
|
|
|
print_dbg_info(dev, eng_grps);
|
|
err_unlock:
|
|
mutex_unlock(&eng_grps->lock);
|
|
return ret ? ret : count;
|
|
err_print:
|
|
dev_err(dev, "%s\n", err_msg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
int otx_cpt_try_create_default_eng_grps(struct pci_dev *pdev,
|
|
struct otx_cpt_eng_grps *eng_grps,
|
|
int pf_type)
|
|
{
|
|
struct tar_ucode_info_t *tar_info[OTX_CPT_MAX_ETYPES_PER_GRP] = {};
|
|
struct otx_cpt_engines engs[OTX_CPT_MAX_ETYPES_PER_GRP] = {};
|
|
struct tar_arch_info_t *tar_arch = NULL;
|
|
char *tar_filename;
|
|
int i, ret = 0;
|
|
|
|
mutex_lock(&eng_grps->lock);
|
|
|
|
/*
|
|
* We don't create engine group for kernel crypto if attempt to create
|
|
* it was already made (when user enabled VFs for the first time)
|
|
*/
|
|
if (eng_grps->is_first_try)
|
|
goto unlock_mutex;
|
|
eng_grps->is_first_try = true;
|
|
|
|
/* We create group for kcrypto only if no groups are configured */
|
|
for (i = 0; i < OTX_CPT_MAX_ENGINE_GROUPS; i++)
|
|
if (eng_grps->grp[i].is_enabled)
|
|
goto unlock_mutex;
|
|
|
|
switch (pf_type) {
|
|
case OTX_CPT_AE:
|
|
case OTX_CPT_SE:
|
|
tar_filename = OTX_CPT_UCODE_TAR_FILE_NAME;
|
|
break;
|
|
|
|
default:
|
|
dev_err(&pdev->dev, "Unknown PF type %d\n", pf_type);
|
|
ret = -EINVAL;
|
|
goto unlock_mutex;
|
|
}
|
|
|
|
tar_arch = load_tar_archive(&pdev->dev, tar_filename);
|
|
if (!tar_arch)
|
|
goto unlock_mutex;
|
|
|
|
/*
|
|
* If device supports SE engines and there is SE microcode in tar
|
|
* archive try to create engine group with SE engines for kernel
|
|
* crypto functionality (symmetric crypto)
|
|
*/
|
|
tar_info[0] = get_uc_from_tar_archive(tar_arch, OTX_CPT_SE_TYPES);
|
|
if (tar_info[0] &&
|
|
dev_supports_eng_type(eng_grps, OTX_CPT_SE_TYPES)) {
|
|
|
|
engs[0].type = OTX_CPT_SE_TYPES;
|
|
engs[0].count = eng_grps->avail.max_se_cnt;
|
|
|
|
ret = create_engine_group(&pdev->dev, eng_grps, engs, 1,
|
|
(void **) tar_info, 1, true);
|
|
if (ret)
|
|
goto release_tar_arch;
|
|
}
|
|
/*
|
|
* If device supports AE engines and there is AE microcode in tar
|
|
* archive try to create engine group with AE engines for asymmetric
|
|
* crypto functionality.
|
|
*/
|
|
tar_info[0] = get_uc_from_tar_archive(tar_arch, OTX_CPT_AE_TYPES);
|
|
if (tar_info[0] &&
|
|
dev_supports_eng_type(eng_grps, OTX_CPT_AE_TYPES)) {
|
|
|
|
engs[0].type = OTX_CPT_AE_TYPES;
|
|
engs[0].count = eng_grps->avail.max_ae_cnt;
|
|
|
|
ret = create_engine_group(&pdev->dev, eng_grps, engs, 1,
|
|
(void **) tar_info, 1, true);
|
|
if (ret)
|
|
goto release_tar_arch;
|
|
}
|
|
|
|
print_dbg_info(&pdev->dev, eng_grps);
|
|
release_tar_arch:
|
|
release_tar_archive(tar_arch);
|
|
unlock_mutex:
|
|
mutex_unlock(&eng_grps->lock);
|
|
return ret;
|
|
}
|
|
|
|
void otx_cpt_set_eng_grps_is_rdonly(struct otx_cpt_eng_grps *eng_grps,
|
|
bool is_rdonly)
|
|
{
|
|
mutex_lock(&eng_grps->lock);
|
|
|
|
eng_grps->is_rdonly = is_rdonly;
|
|
|
|
mutex_unlock(&eng_grps->lock);
|
|
}
|
|
|
|
void otx_cpt_disable_all_cores(struct otx_cpt_device *cpt)
|
|
{
|
|
int grp, timeout = 100;
|
|
u64 reg;
|
|
|
|
/* Disengage the cores from groups */
|
|
for (grp = 0; grp < OTX_CPT_MAX_ENGINE_GROUPS; grp++) {
|
|
writeq(0, cpt->reg_base + OTX_CPT_PF_GX_EN(grp));
|
|
udelay(CSR_DELAY);
|
|
}
|
|
|
|
reg = readq(cpt->reg_base + OTX_CPT_PF_EXEC_BUSY);
|
|
while (reg) {
|
|
udelay(CSR_DELAY);
|
|
reg = readq(cpt->reg_base + OTX_CPT_PF_EXEC_BUSY);
|
|
if (timeout--) {
|
|
dev_warn(&cpt->pdev->dev, "Cores still busy\n");
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Disable the cores */
|
|
writeq(0, cpt->reg_base + OTX_CPT_PF_EXE_CTL);
|
|
}
|
|
|
|
void otx_cpt_cleanup_eng_grps(struct pci_dev *pdev,
|
|
struct otx_cpt_eng_grps *eng_grps)
|
|
{
|
|
struct otx_cpt_eng_grp_info *grp;
|
|
int i, j;
|
|
|
|
mutex_lock(&eng_grps->lock);
|
|
if (eng_grps->is_ucode_load_created) {
|
|
device_remove_file(&pdev->dev,
|
|
&eng_grps->ucode_load_attr);
|
|
eng_grps->is_ucode_load_created = false;
|
|
}
|
|
|
|
/* First delete all mirroring engine groups */
|
|
for (i = 0; i < OTX_CPT_MAX_ENGINE_GROUPS; i++)
|
|
if (eng_grps->grp[i].mirror.is_ena)
|
|
delete_engine_group(&pdev->dev, &eng_grps->grp[i]);
|
|
|
|
/* Delete remaining engine groups */
|
|
for (i = 0; i < OTX_CPT_MAX_ENGINE_GROUPS; i++)
|
|
delete_engine_group(&pdev->dev, &eng_grps->grp[i]);
|
|
|
|
/* Release memory */
|
|
for (i = 0; i < OTX_CPT_MAX_ENGINE_GROUPS; i++) {
|
|
grp = &eng_grps->grp[i];
|
|
for (j = 0; j < OTX_CPT_MAX_ETYPES_PER_GRP; j++) {
|
|
kfree(grp->engs[j].bmap);
|
|
grp->engs[j].bmap = NULL;
|
|
}
|
|
}
|
|
|
|
mutex_unlock(&eng_grps->lock);
|
|
}
|
|
|
|
int otx_cpt_init_eng_grps(struct pci_dev *pdev,
|
|
struct otx_cpt_eng_grps *eng_grps, int pf_type)
|
|
{
|
|
struct otx_cpt_eng_grp_info *grp;
|
|
int i, j, ret = 0;
|
|
|
|
mutex_init(&eng_grps->lock);
|
|
eng_grps->obj = pci_get_drvdata(pdev);
|
|
eng_grps->avail.se_cnt = eng_grps->avail.max_se_cnt;
|
|
eng_grps->avail.ae_cnt = eng_grps->avail.max_ae_cnt;
|
|
|
|
eng_grps->engs_num = eng_grps->avail.max_se_cnt +
|
|
eng_grps->avail.max_ae_cnt;
|
|
if (eng_grps->engs_num > OTX_CPT_MAX_ENGINES) {
|
|
dev_err(&pdev->dev,
|
|
"Number of engines %d > than max supported %d\n",
|
|
eng_grps->engs_num, OTX_CPT_MAX_ENGINES);
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
for (i = 0; i < OTX_CPT_MAX_ENGINE_GROUPS; i++) {
|
|
grp = &eng_grps->grp[i];
|
|
grp->g = eng_grps;
|
|
grp->idx = i;
|
|
|
|
snprintf(grp->sysfs_info_name, OTX_CPT_UCODE_NAME_LENGTH,
|
|
"engine_group%d", i);
|
|
for (j = 0; j < OTX_CPT_MAX_ETYPES_PER_GRP; j++) {
|
|
grp->engs[j].bmap =
|
|
kcalloc(BITS_TO_LONGS(eng_grps->engs_num),
|
|
sizeof(long), GFP_KERNEL);
|
|
if (!grp->engs[j].bmap) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (pf_type) {
|
|
case OTX_CPT_SE:
|
|
/* OcteonTX 83XX SE CPT PF has only SE engines attached */
|
|
eng_grps->eng_types_supported = 1 << OTX_CPT_SE_TYPES;
|
|
break;
|
|
|
|
case OTX_CPT_AE:
|
|
/* OcteonTX 83XX AE CPT PF has only AE engines attached */
|
|
eng_grps->eng_types_supported = 1 << OTX_CPT_AE_TYPES;
|
|
break;
|
|
|
|
default:
|
|
dev_err(&pdev->dev, "Unknown PF type %d\n", pf_type);
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
eng_grps->ucode_load_attr.show = NULL;
|
|
eng_grps->ucode_load_attr.store = ucode_load_store;
|
|
eng_grps->ucode_load_attr.attr.name = "ucode_load";
|
|
eng_grps->ucode_load_attr.attr.mode = 0220;
|
|
sysfs_attr_init(&eng_grps->ucode_load_attr.attr);
|
|
ret = device_create_file(&pdev->dev,
|
|
&eng_grps->ucode_load_attr);
|
|
if (ret)
|
|
goto err;
|
|
eng_grps->is_ucode_load_created = true;
|
|
|
|
print_dbg_info(&pdev->dev, eng_grps);
|
|
return ret;
|
|
err:
|
|
otx_cpt_cleanup_eng_grps(pdev, eng_grps);
|
|
return ret;
|
|
}
|