blob: 0655658bba4dced5f2c3dad4dcd0b829d8984229 [file] [log] [blame]
/*
* Copyright (C) 2012-2017 ARM Limited or its affiliates.
*
* 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.
*
* This program is distributed 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
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
#include "ssi_config.h"
#include "ssi_driver.h"
#include "cc_crypto_ctx.h"
#include "ssi_sysfs.h"
#ifdef ENABLE_CC_SYSFS
static struct ssi_drvdata *sys_get_drvdata(void);
#ifdef CC_CYCLE_COUNT
#include <asm/timex.h>
struct stat_item {
unsigned int min;
unsigned int max;
cycles_t sum;
unsigned int count;
};
struct stat_name {
const char *op_type_name;
const char *stat_phase_name[MAX_STAT_PHASES];
};
static struct stat_name stat_name_db[MAX_STAT_OP_TYPES] = {
{
/* STAT_OP_TYPE_NULL */
.op_type_name = "NULL",
.stat_phase_name = {NULL},
},
{
.op_type_name = "Encode",
.stat_phase_name[STAT_PHASE_0] = "Init and sanity checks",
.stat_phase_name[STAT_PHASE_1] = "Map buffers",
.stat_phase_name[STAT_PHASE_2] = "Create sequence",
.stat_phase_name[STAT_PHASE_3] = "Send Request",
.stat_phase_name[STAT_PHASE_4] = "HW-Q push",
.stat_phase_name[STAT_PHASE_5] = "Sequence completion",
.stat_phase_name[STAT_PHASE_6] = "HW cycles",
},
{ .op_type_name = "Decode",
.stat_phase_name[STAT_PHASE_0] = "Init and sanity checks",
.stat_phase_name[STAT_PHASE_1] = "Map buffers",
.stat_phase_name[STAT_PHASE_2] = "Create sequence",
.stat_phase_name[STAT_PHASE_3] = "Send Request",
.stat_phase_name[STAT_PHASE_4] = "HW-Q push",
.stat_phase_name[STAT_PHASE_5] = "Sequence completion",
.stat_phase_name[STAT_PHASE_6] = "HW cycles",
},
{ .op_type_name = "Setkey",
.stat_phase_name[STAT_PHASE_0] = "Init and sanity checks",
.stat_phase_name[STAT_PHASE_1] = "Copy key to ctx",
.stat_phase_name[STAT_PHASE_2] = "Create sequence",
.stat_phase_name[STAT_PHASE_3] = "Send Request",
.stat_phase_name[STAT_PHASE_4] = "HW-Q push",
.stat_phase_name[STAT_PHASE_5] = "Sequence completion",
.stat_phase_name[STAT_PHASE_6] = "HW cycles",
},
{
.op_type_name = "Generic",
.stat_phase_name[STAT_PHASE_0] = "Interrupt",
.stat_phase_name[STAT_PHASE_1] = "ISR-to-Tasklet",
.stat_phase_name[STAT_PHASE_2] = "Tasklet start-to-end",
.stat_phase_name[STAT_PHASE_3] = "Tasklet:user_cb()",
.stat_phase_name[STAT_PHASE_4] = "Tasklet:dx_X_complete() - w/o X_complete()",
.stat_phase_name[STAT_PHASE_5] = "",
.stat_phase_name[STAT_PHASE_6] = "HW cycles",
}
};
/*
* Structure used to create a directory
* and its attributes in sysfs.
*/
struct sys_dir {
struct kobject *sys_dir_kobj;
struct attribute_group sys_dir_attr_group;
struct attribute **sys_dir_attr_list;
u32 num_of_attrs;
struct ssi_drvdata *drvdata; /* Associated driver context */
};
/* top level directory structures */
struct sys_dir sys_top_dir;
static DEFINE_SPINLOCK(stat_lock);
/* List of DBs */
static struct stat_item stat_host_db[MAX_STAT_OP_TYPES][MAX_STAT_PHASES];
static struct stat_item stat_cc_db[MAX_STAT_OP_TYPES][MAX_STAT_PHASES];
static void init_db(struct stat_item item[MAX_STAT_OP_TYPES][MAX_STAT_PHASES])
{
unsigned int i, j;
/* Clear db */
for (i = 0; i < MAX_STAT_OP_TYPES; i++) {
for (j = 0; j < MAX_STAT_PHASES; j++) {
item[i][j].min = 0xFFFFFFFF;
item[i][j].max = 0;
item[i][j].sum = 0;
item[i][j].count = 0;
}
}
}
static void update_db(struct stat_item *item, unsigned int result)
{
item->count++;
item->sum += result;
if (result < item->min)
item->min = result;
if (result > item->max)
item->max = result;
}
static void display_db(struct stat_item item[MAX_STAT_OP_TYPES][MAX_STAT_PHASES])
{
unsigned int i, j;
u64 avg;
for (i = STAT_OP_TYPE_ENCODE; i < MAX_STAT_OP_TYPES; i++) {
for (j = 0; j < MAX_STAT_PHASES; j++) {
if (item[i][j].count > 0) {
avg = (u64)item[i][j].sum;
do_div(avg, item[i][j].count);
SSI_LOG_ERR("%s, %s: min=%d avg=%d max=%d sum=%lld count=%d\n",
stat_name_db[i].op_type_name,
stat_name_db[i].stat_phase_name[j],
item[i][j].min, (int)avg,
item[i][j].max,
(long long)item[i][j].sum,
item[i][j].count);
}
}
}
}
/**************************************
* Attributes show functions section *
**************************************/
static ssize_t ssi_sys_stats_host_db_clear(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
init_db(stat_host_db);
return count;
}
static ssize_t ssi_sys_stats_cc_db_clear(struct kobject *kobj,
struct kobj_attribute *attr,
const char *buf, size_t count)
{
init_db(stat_cc_db);
return count;
}
static ssize_t ssi_sys_stat_host_db_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int i, j;
char line[512];
u32 min_cyc, max_cyc;
u64 avg;
ssize_t buf_len, tmp_len = 0;
buf_len = scnprintf(buf, PAGE_SIZE,
"phase\t\t\t\t\t\t\tmin[cy]\tavg[cy]\tmax[cy]\t#samples\n");
if (buf_len < 0)/* scnprintf shouldn't return negative value according to its implementation*/
return buf_len;
for (i = STAT_OP_TYPE_ENCODE; i < MAX_STAT_OP_TYPES; i++) {
for (j = 0; j < MAX_STAT_PHASES - 1; j++) {
if (stat_host_db[i][j].count > 0) {
avg = (u64)stat_host_db[i][j].sum;
do_div(avg, stat_host_db[i][j].count);
min_cyc = stat_host_db[i][j].min;
max_cyc = stat_host_db[i][j].max;
} else {
avg = min_cyc = max_cyc = 0;
}
tmp_len = scnprintf(line, 512,
"%s::%s\t\t\t\t\t%6u\t%6u\t%6u\t%7u\n",
stat_name_db[i].op_type_name,
stat_name_db[i].stat_phase_name[j],
min_cyc, (unsigned int)avg, max_cyc,
stat_host_db[i][j].count);
if (tmp_len < 0)/* scnprintf shouldn't return negative value according to its implementation*/
return buf_len;
if (buf_len + tmp_len >= PAGE_SIZE)
return buf_len;
buf_len += tmp_len;
strncat(buf, line, 512);
}
}
return buf_len;
}
static ssize_t ssi_sys_stat_cc_db_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int i;
char line[256];
u32 min_cyc, max_cyc;
u64 avg;
ssize_t buf_len, tmp_len = 0;
buf_len = scnprintf(buf, PAGE_SIZE,
"phase\tmin[cy]\tavg[cy]\tmax[cy]\t#samples\n");
if (buf_len < 0)/* scnprintf shouldn't return negative value according to its implementation*/
return buf_len;
for (i = STAT_OP_TYPE_ENCODE; i < MAX_STAT_OP_TYPES; i++) {
if (stat_cc_db[i][STAT_PHASE_6].count > 0) {
avg = (u64)stat_cc_db[i][STAT_PHASE_6].sum;
do_div(avg, stat_cc_db[i][STAT_PHASE_6].count);
min_cyc = stat_cc_db[i][STAT_PHASE_6].min;
max_cyc = stat_cc_db[i][STAT_PHASE_6].max;
} else {
avg = min_cyc = max_cyc = 0;
}
tmp_len = scnprintf(line, 256, "%s\t%6u\t%6u\t%6u\t%7u\n",
stat_name_db[i].op_type_name, min_cyc,
(unsigned int)avg, max_cyc,
stat_cc_db[i][STAT_PHASE_6].count);
if (tmp_len < 0)/* scnprintf shouldn't return negative value according to its implementation*/
return buf_len;
if (buf_len + tmp_len >= PAGE_SIZE)
return buf_len;
buf_len += tmp_len;
strncat(buf, line, 256);
}
return buf_len;
}
void update_host_stat(unsigned int op_type, unsigned int phase, cycles_t result)
{
unsigned long flags;
spin_lock_irqsave(&stat_lock, flags);
update_db(&stat_host_db[op_type][phase], (unsigned int)result);
spin_unlock_irqrestore(&stat_lock, flags);
}
void update_cc_stat(
unsigned int op_type,
unsigned int phase,
unsigned int elapsed_cycles)
{
update_db(&stat_cc_db[op_type][phase], elapsed_cycles);
}
void display_all_stat_db(void)
{
SSI_LOG_ERR("\n======= CYCLE COUNT STATS =======\n");
display_db(stat_host_db);
SSI_LOG_ERR("\n======= CC HW CYCLE COUNT STATS =======\n");
display_db(stat_cc_db);
}
#endif /*CC_CYCLE_COUNT*/
static ssize_t ssi_sys_regdump_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
struct ssi_drvdata *drvdata = sys_get_drvdata();
u32 register_value;
void __iomem *cc_base = drvdata->cc_base;
int offset = 0;
register_value = CC_HAL_READ_REGISTER(CC_REG_OFFSET(HOST_RGF, HOST_SIGNATURE));
offset += scnprintf(buf + offset, PAGE_SIZE - offset, "%s \t(0x%lX)\t 0x%08X\n", "HOST_SIGNATURE ", DX_HOST_SIGNATURE_REG_OFFSET, register_value);
register_value = CC_HAL_READ_REGISTER(CC_REG_OFFSET(HOST_RGF, HOST_IRR));
offset += scnprintf(buf + offset, PAGE_SIZE - offset, "%s \t(0x%lX)\t 0x%08X\n", "HOST_IRR ", DX_HOST_IRR_REG_OFFSET, register_value);
register_value = CC_HAL_READ_REGISTER(CC_REG_OFFSET(HOST_RGF, HOST_POWER_DOWN_EN));
offset += scnprintf(buf + offset, PAGE_SIZE - offset, "%s \t(0x%lX)\t 0x%08X\n", "HOST_POWER_DOWN_EN ", DX_HOST_POWER_DOWN_EN_REG_OFFSET, register_value);
register_value = CC_HAL_READ_REGISTER(CC_REG_OFFSET(CRY_KERNEL, AXIM_MON_ERR));
offset += scnprintf(buf + offset, PAGE_SIZE - offset, "%s \t(0x%lX)\t 0x%08X\n", "AXIM_MON_ERR ", DX_AXIM_MON_ERR_REG_OFFSET, register_value);
register_value = CC_HAL_READ_REGISTER(CC_REG_OFFSET(CRY_KERNEL, DSCRPTR_QUEUE_CONTENT));
offset += scnprintf(buf + offset, PAGE_SIZE - offset, "%s \t(0x%lX)\t 0x%08X\n", "DSCRPTR_QUEUE_CONTENT", DX_DSCRPTR_QUEUE_CONTENT_REG_OFFSET, register_value);
return offset;
}
static ssize_t ssi_sys_help_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
char *help_str[] = {
"cat reg_dump ", "Print several of CC register values",
#if defined CC_CYCLE_COUNT
"cat stats_host ", "Print host statistics",
"echo <number> > stats_host", "Clear host statistics database",
"cat stats_cc ", "Print CC statistics",
"echo <number> > stats_cc ", "Clear CC statistics database",
#endif
};
int i = 0, offset = 0;
offset += scnprintf(buf + offset, PAGE_SIZE - offset, "Usage:\n");
for (i = 0; i < ARRAY_SIZE(help_str); i += 2)
offset += scnprintf(buf + offset, PAGE_SIZE - offset, "%s\t\t%s\n", help_str[i], help_str[i + 1]);
return offset;
}
/********************************************************
* SYSFS objects *
********************************************************/
/*
* Structure used to create a directory
* and its attributes in sysfs.
*/
struct sys_dir {
struct kobject *sys_dir_kobj;
struct attribute_group sys_dir_attr_group;
struct attribute **sys_dir_attr_list;
u32 num_of_attrs;
struct ssi_drvdata *drvdata; /* Associated driver context */
};
/* top level directory structures */
static struct sys_dir sys_top_dir;
/* TOP LEVEL ATTRIBUTES */
static struct kobj_attribute ssi_sys_top_level_attrs[] = {
__ATTR(dump_regs, 0444, ssi_sys_regdump_show, NULL),
__ATTR(help, 0444, ssi_sys_help_show, NULL),
#if defined CC_CYCLE_COUNT
__ATTR(stats_host, 0664, ssi_sys_stat_host_db_show, ssi_sys_stats_host_db_clear),
__ATTR(stats_cc, 0664, ssi_sys_stat_cc_db_show, ssi_sys_stats_cc_db_clear),
#endif
};
static struct ssi_drvdata *sys_get_drvdata(void)
{
/* TODO: supporting multiple SeP devices would require avoiding
* global "top_dir" and finding associated "top_dir" by traversing
* up the tree to the kobject which matches one of the top_dir's
*/
return sys_top_dir.drvdata;
}
static int sys_init_dir(struct sys_dir *sys_dir, struct ssi_drvdata *drvdata,
struct kobject *parent_dir_kobj, const char *dir_name,
struct kobj_attribute *attrs, u32 num_of_attrs)
{
int i;
memset(sys_dir, 0, sizeof(struct sys_dir));
sys_dir->drvdata = drvdata;
/* initialize directory kobject */
sys_dir->sys_dir_kobj =
kobject_create_and_add(dir_name, parent_dir_kobj);
if (!(sys_dir->sys_dir_kobj))
return -ENOMEM;
/* allocate memory for directory's attributes list */
sys_dir->sys_dir_attr_list =
kzalloc(sizeof(struct attribute *) * (num_of_attrs + 1),
GFP_KERNEL);
if (!(sys_dir->sys_dir_attr_list)) {
kobject_put(sys_dir->sys_dir_kobj);
return -ENOMEM;
}
sys_dir->num_of_attrs = num_of_attrs;
/* initialize attributes list */
for (i = 0; i < num_of_attrs; ++i)
sys_dir->sys_dir_attr_list[i] = &attrs[i].attr;
/* last list entry should be NULL */
sys_dir->sys_dir_attr_list[num_of_attrs] = NULL;
sys_dir->sys_dir_attr_group.attrs = sys_dir->sys_dir_attr_list;
return sysfs_create_group(sys_dir->sys_dir_kobj,
&sys_dir->sys_dir_attr_group);
}
static void sys_free_dir(struct sys_dir *sys_dir)
{
if (!sys_dir)
return;
kfree(sys_dir->sys_dir_attr_list);
if (sys_dir->sys_dir_kobj)
kobject_put(sys_dir->sys_dir_kobj);
}
int ssi_sysfs_init(struct kobject *sys_dev_obj, struct ssi_drvdata *drvdata)
{
int retval;
#if defined CC_CYCLE_COUNT
/* Init. statistics */
init_db(stat_host_db);
init_db(stat_cc_db);
#endif
SSI_LOG_ERR("setup sysfs under %s\n", sys_dev_obj->name);
/* Initialize top directory */
retval = sys_init_dir(&sys_top_dir, drvdata, sys_dev_obj, "cc_info",
ssi_sys_top_level_attrs,
ARRAY_SIZE(ssi_sys_top_level_attrs));
return retval;
}
void ssi_sysfs_fini(void)
{
sys_free_dir(&sys_top_dir);
}
#endif /*ENABLE_CC_SYSFS*/