| /* |
| * 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*/ |
| |