blob: b360895661f8bc3769c6f475e38c984314565eef [file] [log] [blame]
/*
* Samsung Exynos SoC series NPU driver
*
* Copyright (c) 2017 Samsung Electronics Co., Ltd.
* http://www.samsung.com/
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*/
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/file.h>
#include <linux/list.h>
#include <linux/uaccess.h>
#include <linux/ctype.h>
#include <linux/string.h>
#include <linux/fs.h>
#include <linux/stat.h>
#include <asm/cacheflush.h>
#include "ncp_header.h"
#include "npu-golden.h"
#include "npu-log.h"
#include "npu-debug.h"
#include "npu-util-comparator.h"
#include "npu-common.h"
#include "npu-session.h"
#define GOLDEN_FILENAME_LEN 2048
typedef enum {
NPU_GOLDEN_STATE_NOT_INITIALIZED,
NPU_GOLDEN_STATE_OPENED,
NPU_GOLDEN_STATE_ADDING_GOLDEN_FILES,
NPU_GOLDEN_STATE_CONFIGURED,
NPU_GOLDEN_STATE_COMPARING,
NPU_GOLDEN_STATE_INVALID,
} npu_golden_state_e;
static const char *state_names[NPU_GOLDEN_STATE_INVALID + 1]
= {"NOT INITIALZED", "OPENED", "ADDING_GOLDEN_FILES", "CONFIGURED", "COMPARING"};
static const u8 transition_map[][STATE_KEEPER_MAX_STATE] = {
/* From - To NOT INITIALZED OPENED ADDING_GOLDEN_FILES CONFIGURED COMPARING INVALID */
/* NOT INITIALZED */ { 1, 1, 0, 0, 0, 0},
/* OPENED */ { 1, 0, 1, 0, 0, 0},
/* ADDING_GOLDEN_FILES */ { 1, 0, 1, 1, 0, 0},
/* CONFIGURED */ { 1, 0, 0, 0, 1, 0},
/* COMPARING */ { 1, 0, 0, 1, 1, 0},
/* INVALID */ { 0, 0, 0, 0, 0, 0},
};
/*
* Trailing message to notify the result is truncated
* due to the limitation of buffer size
*/
static const char *TRUNCATE_MSG = "\nWarning:Result is truncated.\n";
struct golden_file {
u32 av_index;
size_t size;
struct list_head list;
u8 *img_vbuf;
char file_name[GOLDEN_FILENAME_LEN+1];
};
struct parse_buf {
size_t idx;
int (*value_setter)(struct parse_buf *parse_buf);
char token_char[GOLDEN_FILENAME_LEN+1];
};
typedef int (*action_func)(const char ch, struct parse_buf *parse_buf);
typedef enum {
TOKEN_ALPHA, /* Alphabet and some special character */
TOKEN_NUMBER, /* Number */
TOKEN_WS, /* White space */
TOKEN_NL, /* New line */
TOKEN_SHARP, /* '#' sign */
TOKEN_INVALID,
} npu_golden_set_token;
typedef enum {
PS_BEGIN = 0, /* Initial */
PS_HDR, /* Header directive */
PS_HDR_SEP,
PS_HDR_PARAM,
PS_COMMENT,
PS_GOLDEN_IDX,
PS_GOLDEN_SEP,
PS_GOLDEN_FILE,
PS_INVALID,
} npu_golden_set_parsing_state;
struct npu_golden_data {
struct {
u8 frame_id_matching;
u8 net_id_matching;
u8 valid;
u8 updated;
} flags;
struct {
u32 frame_id;
u32 net_id;
} match;
golden_comparator comparator;
golden_is_av_valid is_av_valid;
npu_golden_set_parsing_state parse_state;
struct parse_buf parse_buf;
struct {
u32 line;
u32 col;
} parse_pos;
struct list_head file_list;
};
struct npu_golden_ctx {
/* Flag to check golden is available or not, without acquiring lock */
atomic_t loaded;
struct npu_statekeeper statekeeper;
struct mutex lock;
struct npu_golden_data data;
struct npu_golden_compare_result result;
struct device *dev;
};
struct parsing_action {
npu_golden_set_parsing_state next_state;
action_func func;
};
/* Comparator function configuration map */
struct {
const char *name;
golden_comparator comparator;
golden_is_av_valid is_av_valid;
} COMPARATOR_TABLE[] = {
{ /* SIMPLE: Default */
.name = "SIMPLE",
.comparator = simple_compare,
.is_av_valid = is_av_valid_simple,
},
{ /* REORDERED */
.name = "REORDERED",
.comparator = reordered_compare,
.is_av_valid = is_av_valid_reordered,
},
{ /* Terminator */
.name = NULL,
.comparator = NULL,
.is_av_valid = NULL,
}
};
/* Global storage for golden reference data */
static struct npu_golden_ctx npu_golden_ctx;
/*********************************************************
* Golden file structure manipulation
*/
static struct golden_file *new_golden_file(void)
{
struct golden_file *obj;
BUG_ON(!npu_golden_ctx.dev);
obj = devm_kzalloc(npu_golden_ctx.dev, sizeof(*obj), GFP_KERNEL);
if (obj == NULL) {
npu_err("fail in golden_file memory allocattion");
return NULL;
}
/* new golden_file object is zero-cleared, no memset required */
return obj;
}
static void destroy_golden_file(struct golden_file *obj)
{
BUG_ON(!obj);
BUG_ON(!npu_golden_ctx.dev);
if (obj->img_vbuf) {
vfree(obj->img_vbuf);
}
devm_kfree(npu_golden_ctx.dev, obj);
}
static int load_golden_file(struct golden_file *obj)
{
int ret = 0;
mm_segment_t old_fs = 0;
struct file *fp = NULL;
struct kstat st;
int nread;
loff_t file_offset = 0;
BUG_ON(!obj);
BUG_ON(!npu_golden_ctx.dev);
obj->img_vbuf = NULL;
if (obj->file_name[0] == '\0') {
npu_err("no file name\n");
ret = -EINVAL;
goto err_exit;
}
npu_info("start in load_golden_file %s\n", obj->file_name);
/* Switch to Kernel data segmenet
Ref: https://wiki.kldp.org/wiki.php/%C4%BF%B3%CE%B3%BB%BF%A1%BC%ADReadWrite%BB%E7%BF%EB%C7%CF%B1%E2
*/
old_fs = get_fs();
set_fs(KERNEL_DS);
/* Step 1. Determine file size */
fp = filp_open(obj->file_name, O_RDONLY, 0);
if (!fp) {
npu_err("fail in filp_open %s.\n", obj->file_name);
ret = -EBADFD;
goto err_exit;
} else if (IS_ERR(fp)) {
npu_err("fail in filp_open(%s): errno = %ld\n",
obj->file_name, PTR_ERR(fp));
fp = NULL; /* No need to close fp on err_exit */
ret = -ENOENT;
goto err_exit;
}
/* Get file attributes */
//[MOD]
ret = vfs_getattr(&fp->f_path, &st, STATX_MODE | STATX_TYPE, AT_STATX_SYNC_AS_STAT);
if (ret) {
npu_err("error(%d) in vfs_getattr\n", ret);
goto err_exit;
}
/* Checke whether it is regular file */
if (!S_ISREG(st.mode)) {
npu_err("fail in regular file.\n");
ret = -ENOENT;
goto err_exit;
}
obj->size = st.size;
if (obj->size <= 0) {
npu_err("fail in size check: %s, size=%lu.\n",
obj->file_name, obj->size);
ret = -ENOENT;
goto err_exit;
}
/* Step 2. Allocate memory */
obj->img_vbuf = vzalloc(obj->size);
if (!obj->img_vbuf) {
npu_err("fail in memory allocation: %s, size=%lu.\n",
obj->file_name, obj->size);
ret = -ENOMEM;
goto err_exit;
}
/* Step 3. Read file contents and save to the allocated buffer */
nread = kernel_read(fp, obj->img_vbuf, obj->size, &file_offset);
if (nread != obj->size) {
npu_err("fail(%d) in kernel_read", nread);
ret = -EFAULT;
goto err_exit;
}
ret = 0;
goto ok_exit;
err_exit:
/* Step 4. Clean-up */
/* if an error occurs, image_buf buffer should be de-allocated */
if (obj->img_vbuf)
vfree(obj->img_vbuf);
obj->img_vbuf = NULL;
obj->size = 0;
ok_exit:
if (fp)
filp_close(fp, NULL);
/* Second parameter of filp_close is NULL
Ref: https://stackoverflow.com/questions/32442521/
why-can-the-posix-thread-id-be-null-in-linux-kernel-function-filp-close
*/
set_fs(old_fs);
switch (ret) {
case 0:
npu_info("success in load_golden_file: (%s, %luB length)\n",
obj->file_name, obj->size);
npu_dbg("loaded file on (%pK)\n", obj->img_vbuf);
break;
default:
npu_err("fail(%d) in load_golden_file %s\n",
ret, obj->file_name);
break;
}
return ret;
}
static void destroy_npu_golden(void)
{
struct golden_file *cur;
struct golden_file *tmp;
int cnt = 0;
npu_statekeeper_transition(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_NOT_INITIALIZED);
if (npu_golden_ctx.data.flags.valid == 0) {
npu_warn("not initialized in npu_golden.data\n");
return;
}
list_for_each_entry_safe(cur, tmp, &npu_golden_ctx.data.file_list, list) {
list_del_init(&cur->list);
destroy_golden_file(cur);
cnt++;
}
npu_dbg("%s: removed %d file entries.\n", __func__, cnt);
atomic_set(&npu_golden_ctx.loaded, 0);
npu_golden_ctx.data.flags.valid = 0;
}
/*
* Open :
* Create a new golden data
*/
static int npu_golden_set_open(struct inode *inode, struct file *file)
{
BUG_ON(!file);
mutex_lock(&npu_golden_ctx.lock);
npu_info("start in npu_golden_set_open, npu_golden_ctx = @%pK\n", &npu_golden_ctx);
/* Destroy last golden setting */
if (npu_golden_ctx.data.flags.valid) {
npu_dbg("open for npu_golden_set: Purge existing golden data....\n");
destroy_npu_golden();
}
/* Clear old data */
memset(&npu_golden_ctx.data, 0, sizeof(npu_golden_ctx.data));
/* Set initial value for non-zero value */
npu_golden_ctx.data.parse_state = PS_BEGIN;
npu_golden_ctx.data.parse_pos.line = 1;
npu_golden_ctx.data.parse_pos.col = 1;
npu_golden_ctx.data.flags.valid = 1;
npu_golden_ctx.data.flags.updated = 0;
npu_golden_ctx.data.comparator = COMPARATOR_TABLE[0].comparator; /* Default comparator */
npu_golden_ctx.data.is_av_valid = COMPARATOR_TABLE[0].is_av_valid; /* Default comparator */
INIT_LIST_HEAD(&npu_golden_ctx.data.file_list);
npu_statekeeper_transition(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_OPENED);
npu_info("complete in npu_golden_set_open\n");
mutex_unlock(&npu_golden_ctx.lock);
return 0;
}
/*
* Close : Wrap up the golden data and transition to
* comparing ready state
*/
static int npu_golden_set_close(struct inode *inode, struct file *file)
{
int ret;
struct golden_file *f;
mutex_lock(&npu_golden_ctx.lock);
npu_info("start in npu_golden_set_close\n");
BUG_ON(!file);
if (npu_golden_ctx.data.flags.valid == 0) {
npu_err("not initialized in npu_golden_ctx.data\n");
ret = -EBADFD;
goto err_exit;
}
if (npu_golden_ctx.data.flags.updated == 0) {
// No update after open
npu_err("no golden description\n");
ret = -EINVAL;
goto err_exit;
}
if (npu_golden_ctx.data.parse_state != PS_BEGIN) {
/* Improper parsing */
npu_err("unexpected EOF in description.\n");
ret = -EINVAL;
goto err_exit;
}
/* Load golden files on memory */
list_for_each_entry(f, &npu_golden_ctx.data.file_list, list) {
ret = load_golden_file(f);
if (ret) {
npu_err("fail in load_golden_file on entry(%pK).\n", f);
ret = -EFAULT;
goto err_exit;
}
}
/* Set flag to mark the golden is valid */
atomic_set(&npu_golden_ctx.loaded, 1);
/* Test data is all prepared */
npu_statekeeper_transition(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_CONFIGURED);
npu_info("complete in npu_golden_set_close\n");
ret = 0;
goto ok_exit;
err_exit:
npu_err("error(%d) in npu_golden_set_close]\n", ret);
destroy_npu_golden();
npu_golden_ctx.data.flags.valid = 0;
npu_statekeeper_transition(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_NOT_INITIALIZED);
ok_exit:
mutex_unlock(&npu_golden_ctx.lock);
return ret;
}
static npu_golden_set_token get_token_type(const char ch)
{
switch (ch) {
case '\n':
return TOKEN_NL;
case ' ':
case '\t':
case '\r':
return TOKEN_WS;
case '#':
return TOKEN_SHARP;
case '_':
case '-':
case '/':
case '.':
/* Special character allowed for alphabet */
return TOKEN_ALPHA;
}
/* Range compare */
if (('a' <= ch && ch <= 'z')
|| ('A' <= ch && ch <= 'Z')) {
return TOKEN_ALPHA;
}
if ('0' <= ch && ch <= '9') {
return TOKEN_NUMBER;
}
return TOKEN_INVALID;
}
/* Remove trailing whitespcae from parse_buf */
static void trim_and_term_parse_buf(struct parse_buf *parse_buf)
{
while (parse_buf->idx > 0) {
if (get_token_type(parse_buf->token_char[parse_buf->idx - 1]) != TOKEN_WS) {
break;
}
parse_buf->idx--;
}
parse_buf->token_char[parse_buf->idx] = '\0';
}
static void to_upper_parse_buf(struct parse_buf *parse_buf)
{
size_t i;
for (i = 0; i < parse_buf->idx; ++i) {
parse_buf->token_char[i] = toupper(parse_buf->token_char[i]);
}
}
static inline void reset_parse_buf(struct parse_buf *parse_buf)
{
parse_buf->idx = 0;
parse_buf->token_char[0] = '\0';
}
/***************************************************************************
* Value setter funtion
*/
static int set_frame_id(struct parse_buf *parse_buf)
{
int ret;
long long frame_id;
BUG_ON(!parse_buf);
SKEEPER_EXPECT_STATE(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_ADDING_GOLDEN_FILES);
trim_and_term_parse_buf(parse_buf);
if (parse_buf->idx <= 0) {
return -1;
}
ret = kstrtoll(parse_buf->token_char, 10, &frame_id);
if (ret) {
npu_err("invalid frame id (%s) specified on line (%d)\n",
parse_buf->token_char,
npu_golden_ctx.data.parse_pos.line);
return ret;
}
npu_golden_ctx.data.flags.frame_id_matching = 1;
npu_golden_ctx.data.match.frame_id = frame_id;
npu_info("frame ID matching set: Frame ID = (%u)\n", npu_golden_ctx.data.match.frame_id);
return 0;
}
static int set_net_id(struct parse_buf *parse_buf)
{
int ret;
long long net_id;
BUG_ON(!parse_buf);
SKEEPER_EXPECT_STATE(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_ADDING_GOLDEN_FILES);
trim_and_term_parse_buf(parse_buf);
if (parse_buf->idx <= 0) {
return -1;
}
ret = kstrtoll(parse_buf->token_char, 10, &net_id);
if (ret) {
npu_err("invalid net id [%s] specified on line %d\n",
parse_buf->token_char,
npu_golden_ctx.data.parse_pos.line);
return ret;
}
npu_golden_ctx.data.flags.net_id_matching = 1;
npu_golden_ctx.data.match.net_id = net_id;
npu_info("net ID matching set: Net ID = (%u)\n", npu_golden_ctx.data.match.net_id);
return 0;
}
static int set_comparator(struct parse_buf *parse_buf)
{
int i;
BUG_ON(!parse_buf);
SKEEPER_EXPECT_STATE(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_ADDING_GOLDEN_FILES);
trim_and_term_parse_buf(parse_buf);
to_upper_parse_buf(parse_buf);
if (parse_buf->idx <= 0) {
return -1;
}
/* Find header value */
for (i = 0; COMPARATOR_TABLE[i].name != NULL; i++) {
if (strncmp(parse_buf->token_char, COMPARATOR_TABLE[i].name, parse_buf->idx) == 0) {
npu_info("setting comparator to (%s)\n", COMPARATOR_TABLE[i].name);
npu_golden_ctx.data.comparator = COMPARATOR_TABLE[i].comparator;
npu_golden_ctx.data.is_av_valid = COMPARATOR_TABLE[i].is_av_valid;
goto find_header;
}
}
/* Could not find appropirate header */
npu_err("invalid comparator (%s) found on line (%d)\n", parse_buf->token_char, npu_golden_ctx.data.parse_pos.line);
return -1;
find_header:
return 0;
}
struct {
const char *header;
int (*value_setter)(struct parse_buf *);
} HDR_PARSE_TABLE[] = {
{.header = "FRAME_ID", .value_setter = set_frame_id},
{.header = "NET_ID", .value_setter = set_net_id},
{.header = "COMPARATOR", .value_setter = set_comparator},
{.header = NULL, .value_setter = NULL},
};
/***************************************************************************
* Parse action funtion
*/
/* Do nothing but consume the input character */
static int skip(const char ch, struct parse_buf *parse_buf)
{
BUG_ON(!parse_buf);
SKEEPER_EXPECT_STATE(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_ADDING_GOLDEN_FILES);
return 0;
}
/* Keep the character in parse_buf */
static int append(const char ch, struct parse_buf *parse_buf)
{
BUG_ON(!parse_buf);
SKEEPER_EXPECT_STATE(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_ADDING_GOLDEN_FILES);
if (parse_buf->idx + 1 >= GOLDEN_FILENAME_LEN) {
return -1;
} else {
parse_buf->token_char[parse_buf->idx] = ch;
parse_buf->idx++;
}
return 0;
}
static int set_hdr_name(const char ch, struct parse_buf *parse_buf)
{
int i;
BUG_ON(!parse_buf);
SKEEPER_EXPECT_STATE(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_ADDING_GOLDEN_FILES);
trim_and_term_parse_buf(parse_buf);
to_upper_parse_buf(parse_buf);
if (parse_buf->idx <= 0) {
return -1;
}
/* Find header value */
for (i = 0; HDR_PARSE_TABLE[i].header != NULL; i++) {
if (strncmp(parse_buf->token_char, HDR_PARSE_TABLE[i].header, parse_buf->idx) == 0) {
parse_buf->value_setter = HDR_PARSE_TABLE[i].value_setter;
goto find_header;
}
}
/* Could not find appropirate header */
npu_err("invalid header (%s) found on line (%d)\n", parse_buf->token_char, npu_golden_ctx.data.parse_pos.line);
return -1;
find_header:
/* reset parse_buf */
reset_parse_buf(parse_buf);
return 0;
}
static int set_hdr_param(const char ch, struct parse_buf *parse_buf)
{
int ret;
BUG_ON(!parse_buf);
SKEEPER_EXPECT_STATE(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_ADDING_GOLDEN_FILES);
trim_and_term_parse_buf(parse_buf);
if (parse_buf->value_setter == NULL) {
npu_err("no value setter on line (%d)\n", npu_golden_ctx.data.parse_pos.line);
return -1;
}
ret = parse_buf->value_setter(parse_buf);
if (ret) {
npu_err("value setter failed on line (%d)\n", npu_golden_ctx.data.parse_pos.line);
return ret;
}
parse_buf->value_setter = NULL;
/* reset parse_buf */
reset_parse_buf(parse_buf);
return 0;
}
static int set_golden_idx(const char ch, struct parse_buf *parse_buf)
{
int ret;
long long golden_idx;
struct golden_file *golden_file;
BUG_ON(!parse_buf);
SKEEPER_EXPECT_STATE(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_ADDING_GOLDEN_FILES);
trim_and_term_parse_buf(parse_buf);
if (parse_buf->idx <= 0) {
return -1;
}
ret = kstrtoll(parse_buf->token_char, 10, &golden_idx);
if (ret) {
npu_err("invalid vector index (%s) specified on line %d\n",
parse_buf->token_char,
npu_golden_ctx.data.parse_pos.line);
return ret;
}
/* Create a new file entry */
golden_file = new_golden_file();
if (!golden_file) {
npu_err("fail in new_golden_file\n");
return -ENOMEM;
}
golden_file->av_index = golden_idx;
list_add_tail(&golden_file->list, &npu_golden_ctx.data.file_list);
npu_dbg("adding new_golden_file entry: IDX(%d), Ptr(%pK)\n",
golden_file->av_index, golden_file);
/* reset parse_buf */
reset_parse_buf(parse_buf);
return 0;
}
static int set_golden_file(const char ch, struct parse_buf *parse_buf)
{
struct golden_file *golden_file;
BUG_ON(!parse_buf);
SKEEPER_EXPECT_STATE(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_ADDING_GOLDEN_FILES);
trim_and_term_parse_buf(parse_buf);
/* Retrive last entry from the list -> Added on last set_golden_idx() */
if (list_empty(&npu_golden_ctx.data.file_list)) {
/* Somethings wrong */
npu_err("no file list entry on line (%d)\n", npu_golden_ctx.data.parse_pos.line);
return -1;
}
golden_file = list_last_entry(&npu_golden_ctx.data.file_list, struct golden_file, list);
BUG_ON(!golden_file);
strncpy(golden_file->file_name, parse_buf->token_char, parse_buf->idx);
golden_file->file_name[parse_buf->idx] = '\0';
npu_dbg("golden file entry set: IDX(%d), filename(%s), Ptr(%pK)\n",
golden_file->av_index, golden_file->file_name, golden_file);
/* reset parse_buf */
reset_parse_buf(parse_buf);
return 0;
}
static int reset(const char ch, struct parse_buf *parse_buf)
{
BUG_ON(!parse_buf);
SKEEPER_EXPECT_STATE(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_ADDING_GOLDEN_FILES);
/* reset parse_buf */
reset_parse_buf(parse_buf);
return 0;
}
static const struct parsing_action PARSE_TABLE[][TOKEN_INVALID+1] = {
/*PS_BEGIN*/ {
/*TOKEN_ALPHA,*/ {PS_HDR, append},
/*TOKEN_NUMBER,*/ {PS_GOLDEN_IDX, append},
/*TOKEN_WS,*/ {PS_BEGIN, skip},
/*TOKEN_NL,*/ {PS_BEGIN, skip},
/*TOKEN_SHARP,*/ {PS_COMMENT, skip},
/*TOKEN_INVALID,*/ {PS_INVALID, skip},
},
/*PS_HDR,*/ {
/*TOKEN_ALPHA,*/ {PS_HDR, append},
/*TOKEN_NUMBER,*/ {PS_HDR, append},
/*TOKEN_WS,*/ {PS_HDR_SEP, set_hdr_name},
/*TOKEN_NL,*/ {PS_INVALID, skip},
/*TOKEN_SHARP,*/ {PS_INVALID, skip},
/*TOKEN_INVALID,*/ {PS_INVALID, skip},
},
/*PS_HDR_SEP,*/ {
/*TOKEN_ALPHA,*/ {PS_HDR_PARAM, append},
/*TOKEN_NUMBER,*/ {PS_HDR_PARAM, append},
/*TOKEN_WS,*/ {PS_HDR_SEP, skip},
/*TOKEN_NL,*/ {PS_INVALID, skip},
/*TOKEN_SHARP,*/ {PS_INVALID, skip},
/*TOKEN_INVALID,*/ {PS_INVALID, skip},
},
/*PS_HDR_PARAM,*/ {
/*TOKEN_ALPHA,*/ {PS_HDR_PARAM, append},
/*TOKEN_NUMBER,*/ {PS_HDR_PARAM, append},
/*TOKEN_WS,*/ {PS_HDR_PARAM, append},
/*TOKEN_NL,*/ {PS_BEGIN, set_hdr_param},
/*TOKEN_SHARP,*/ {PS_INVALID, skip},
/*TOKEN_INVALID,*/ {PS_INVALID, skip},
},
/*PS_COMMENT,*/ {
/*TOKEN_ALPHA,*/ {PS_COMMENT, skip},
/*TOKEN_NUMBER,*/ {PS_COMMENT, skip},
/*TOKEN_WS,*/ {PS_COMMENT, skip},
/*TOKEN_NL,*/ {PS_BEGIN, reset},
/*TOKEN_SHARP,*/ {PS_COMMENT, skip},
/*TOKEN_INVALID,*/ {PS_COMMENT, skip},
},
/*PS_GOLDEN_IDX,*/ {
/*TOKEN_ALPHA,*/ {PS_INVALID, skip},
/*TOKEN_NUMBER,*/ {PS_GOLDEN_IDX, append},
/*TOKEN_WS,*/ {PS_GOLDEN_SEP, set_golden_idx},
/*TOKEN_NL,*/ {PS_INVALID, skip},
/*TOKEN_SHARP,*/ {PS_INVALID, skip},
/*TOKEN_INVALID,*/ {PS_INVALID, skip},
},
/*PS_GOLDEN_SEP,*/ {
/*TOKEN_ALPHA,*/ {PS_GOLDEN_FILE, append},
/*TOKEN_NUMBER,*/ {PS_GOLDEN_FILE, append},
/*TOKEN_WS,*/ {PS_GOLDEN_SEP, skip},
/*TOKEN_NL,*/ {PS_INVALID, skip},
/*TOKEN_SHARP,*/ {PS_INVALID, skip},
/*TOKEN_INVALID,*/ {PS_INVALID, skip},
},
/*PS_GOLDEN_FILE*/ {
/*TOKEN_ALPHA,*/ {PS_GOLDEN_FILE, append},
/*TOKEN_NUMBER,*/ {PS_GOLDEN_FILE, append},
/*TOKEN_WS,*/ {PS_GOLDEN_FILE, append},
/*TOKEN_NL,*/ {PS_BEGIN, set_golden_file},
/*TOKEN_SHARP,*/ {PS_INVALID, skip},
/*TOKEN_INVALID,*/ {PS_INVALID, skip},
},
};
int parse_char(const char ch)
{
npu_golden_set_token token_type;
npu_golden_set_parsing_state cur_state = npu_golden_ctx.data.parse_state;
const struct parsing_action *cur_action = NULL;
struct parse_buf *parse_buf = &npu_golden_ctx.data.parse_buf;
token_type = get_token_type(ch);
if (token_type == TOKEN_INVALID) {
/* Invalid character */
npu_err("invalid token at line:%u, col:%u\n",
npu_golden_ctx.data.parse_pos.line,
npu_golden_ctx.data.parse_pos.col);
return -EINVAL;
}
cur_action = &(PARSE_TABLE[cur_state][token_type]);
/* Do action and state transition */
if (cur_action->func) {
if (cur_action->func(ch, parse_buf) != 0) {
/* Invalid character */
npu_err("parsing error at line:%u, col:%u\n",
npu_golden_ctx.data.parse_pos.line,
npu_golden_ctx.data.parse_pos.col);
return -EINVAL;
}
}
npu_golden_ctx.data.parse_state = cur_action->next_state;
if (cur_action->next_state >= PS_INVALID) {
npu_err("parsing error at line:%u, col:%u\n",
npu_golden_ctx.data.parse_pos.line,
npu_golden_ctx.data.parse_pos.col);
return -EINVAL;
}
/* Adjust col and line */
if (token_type == TOKEN_NL) {
npu_golden_ctx.data.parse_pos.line++;
npu_golden_ctx.data.parse_pos.col = 0;
} else {
npu_golden_ctx.data.parse_pos.col++;
}
return 0;
}
/*
* Write : Set golden definition
*/
static ssize_t npu_golden_set_write(struct file *file, const char * __user userbuf, size_t count, loff_t *offp)
{
char ch;
size_t i;
int ret;
mutex_lock(&npu_golden_ctx.lock);
npu_statekeeper_transition(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_ADDING_GOLDEN_FILES);
for (i = 0; i < count; i++) {
get_user(ch, userbuf+i);
ret = parse_char(ch);
if (ret) {
goto err_exit;
}
}
ret = i;
npu_golden_ctx.data.flags.updated = 1;
err_exit:
mutex_unlock(&npu_golden_ctx.lock);
return ret;
}
/* Same implementation but buf is kernel buffer - For IDIOT testing */
__attribute__((unused)) static ssize_t npu_golden_set_write_kern(struct file *file, const char *buf, size_t count, loff_t *offp)
{
char ch;
size_t i;
int ret;
mutex_lock(&npu_golden_ctx.lock);
npu_statekeeper_transition(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_ADDING_GOLDEN_FILES);
for (i = 0; i < count; i++) {
ch = buf[i];
ret = parse_char(ch);
if (ret) {
goto err_exit;
}
}
ret = i;
npu_golden_ctx.data.flags.updated = 1;
err_exit:
mutex_unlock(&npu_golden_ctx.lock);
return ret;
}
static const struct file_operations npu_golden_set_debug_fops = {
.owner = THIS_MODULE,
.open = npu_golden_set_open,
.release = npu_golden_set_close,
.write = npu_golden_set_write,
};
/*
* Open: Check readiness of compare
*/
static int npu_golden_match_open(struct inode *inode, struct file *file)
{
int ret;
BUG_ON(!file);
BUG_ON(!npu_golden_ctx.dev);
mutex_lock(&npu_golden_ctx.lock);
npu_info("start in npu_golden_match_open\n");
if (!SKEEPER_EXPECT_STATE(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_CONFIGURED)
&& !SKEEPER_EXPECT_STATE(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_COMPARING)) {
npu_err("not configured in npu_golden_ctx\n");
ret = -EBADFD;
goto err_exit;
}
if (!atomic_read(&npu_golden_ctx.loaded)) {
npu_err("not loaded in npu_golden_ctx\n");
ret = -EBADFD;
goto err_exit;
}
ret = 0;
err_exit:
mutex_unlock(&npu_golden_ctx.lock);
npu_info("complete in npu_golden_match_open\n");
return ret;
}
/*
* Close : Reset the copied_len to get same result on next read.
*/
static int npu_golden_match_close(struct inode *inode, struct file *file)
{
mutex_lock(&npu_golden_ctx.lock);
npu_golden_ctx.result.copied_len = 0;
mutex_unlock(&npu_golden_ctx.lock);
return 0;
}
/*
* Provide contents of golden matching
*/
static ssize_t npu_golden_match_read(struct file *file, char __user *outbuf, size_t outlen, loff_t *loff)
{
struct npu_golden_compare_result *result = &npu_golden_ctx.result;
size_t copy_len;
int ret;
mutex_lock(&npu_golden_ctx.lock);
if (result->out_buf_cur == 0) {
/* No data available */
npu_dbg("No golden matching result available.");
copy_len = 0;
goto err_exit;
}
if (result->copied_len >= result->out_buf_cur) {
/* Otherwise, EOF */
copy_len = 0;
goto err_exit;
}
copy_len = min(outlen, result->out_buf_cur - result->copied_len);
ret = copy_to_user(outbuf, result->out_buf + result->copied_len, copy_len);
if (ret) {
npu_err("fail in copy_to_user: %d bytes are not copied\n", ret);
}
copy_len -= ret;
result->copied_len += copy_len;
err_exit:
mutex_unlock(&npu_golden_ctx.lock);
return copy_len;
}
static void reset_compare_result(struct npu_golden_compare_result *result)
{
BUG_ON(!result);
result->copied_len = 0;
result->out_buf_cur = 0;
result->out_buf_len = NPU_GOLDEN_RESULT_BUF_LEN;
result->out_buf[0] = '\0';
}
/*
* Compare Golden data with address vector
*/
static int __npu_golden_compare(const struct npu_frame *frame)
{
struct npu_golden_compare_result *result;
struct npu_session *session;
struct golden_file *golden_entry;
struct addr_info *target_entry;
struct ncp_header *ncp_header;
struct memory_vector *memory_vector;
struct npu_fmap_dim dim;
u32 av_len;
int compare_result = 0;
int total_result = 0;
int ret;
int tr_msg_len;
size_t byte_size, dim_size;
BUG_ON(!frame);
BUG_ON(!frame->session);
if (!SKEEPER_EXPECT_STATE(&npu_golden_ctx.statekeeper, NPU_GOLDEN_STATE_CONFIGURED)) {
npu_dbg("invalid state.");
return -EINVAL;
}
result = &npu_golden_ctx.result;
session = frame->session;
ncp_header = (struct ncp_header *)(session->ncp_info.ncp_addr.vaddr);
npu_dbg("golden compare: starting with frame @ %pK, result at %pK\n [cur=%zu len=%zu cop=%zu]",
frame, result->out_buf, result->out_buf_cur, result->out_buf_len, result->copied_len);
if (!ncp_header) {
npu_uferr("not initialized in NCP header\n", frame);
return -EINVAL;
}
if (ncp_header->magic_number1 != NCP_MAGIC1) {
npu_uferr("NCP header magic mismatch: expected(0x%08x), actual(0x%08x).\n",
frame, NCP_MAGIC1, ncp_header->magic_number1);
return -EINVAL;
}
/* Check comparing condition */
if (npu_golden_ctx.data.flags.frame_id_matching) {
if (frame->frame_id != npu_golden_ctx.data.match.frame_id) {
npu_ufdbg("frame ID [frame:%u != Golden:%u] mismatch. skipping.\n",
frame, frame->frame_id, npu_golden_ctx.data.match.frame_id);
return 0;
}
}
if (npu_golden_ctx.data.flags.net_id_matching) {
if (ncp_header->net_id != npu_golden_ctx.data.match.net_id) {
npu_ufdbg("net ID [fFrame:%u != golden:%u] mismatch. skipping.\n",
frame, ncp_header->net_id, npu_golden_ctx.data.match.net_id);
return 0;
}
}
/* Reset comparing buffer */
reset_compare_result(&npu_golden_ctx.result);
/* Retrieve address vector information */
av_len = session->ncp_info.address_vector_cnt;
if (av_len <= 0) {
npu_uferr("Address vector table length [%u] is not properly initialized.\n", frame, av_len);
return -EINVAL;
}
/* Compare each vector */
list_for_each_entry(golden_entry, &npu_golden_ctx.data.file_list, list) {
compare_result = 0;
if (golden_entry->av_index >= av_len) {
add_compare_msg(&npu_golden_ctx.result,
"Golden idx[%u] exceedes total number of address vector[%u]. ",
golden_entry->av_index, av_len);
compare_result++;
goto end_compare;
}
target_entry = find_addr_info(session, golden_entry->av_index, OFM_TYPE, frame->input->index);
if (!target_entry) {
target_entry = find_addr_info(session, golden_entry->av_index, IMB_TYPE, frame->input->index);
}
if (!target_entry) {
add_compare_msg(&npu_golden_ctx.result,
"No matching address vector found for idx[%u]. ",
golden_entry->av_index);
compare_result++;
goto end_compare;
}
/* Size compare */
if (!npu_golden_ctx.data.is_av_valid(golden_entry->size, target_entry->size, result)) {
compare_result++;
goto end_compare;
}
/* Setting featuremap dimension */
memory_vector = (struct memory_vector *)((char *) ncp_header + ncp_header->memory_vector_offset);
dim.axis1 = (size_t)((memory_vector + golden_entry->av_index)->channels);
dim.axis2 = (size_t)((memory_vector + golden_entry->av_index)->height);
dim.axis3 = (size_t)((memory_vector + golden_entry->av_index)->width);
byte_size = (golden_entry->size < target_entry->size) ? golden_entry->size : target_entry->size;
dim_size = dim.axis1 * dim.axis2 * dim.axis3;
if (dim_size == 0 || dim_size != byte_size) {
npu_ufwarn("Axis on MV(%lu x %lu x %lu) does not match with size[%lu] -> Use 1D comparison\n",
frame, dim.axis1, dim.axis2, dim.axis3, byte_size);
dim.axis1 = dim.axis2 = 1;
dim.axis3 = byte_size;
}
npu_ufdbg("av_index(%u), dim.axis1(%zu), dim.axis2(%zu), dim.axis3(%zu)\n",
frame, golden_entry->av_index, dim.axis1, dim.axis2, dim.axis3);
add_compare_msg(&npu_golden_ctx.result, "%u: [%lu x %lu x %lu]:",
golden_entry->av_index,
dim.axis1, dim.axis2, dim.axis3);
/* Buffer pointer validation */
if (!target_entry->vaddr) {
add_compare_msg(&npu_golden_ctx.result,
"KV addr of address vector golden idx[%u] is not valid. ",
golden_entry->av_index);
compare_result++;
goto end_compare;
}
if (!golden_entry->img_vbuf) {
add_compare_msg(&npu_golden_ctx.result,
"Image data of golden idx[%u] is not valid. ",
golden_entry->av_index);
compare_result++;
goto end_compare;
}
/* Cache invalidation */
__dma_map_area(target_entry->vaddr, target_entry->size, DMA_FROM_DEVICE);
npu_ufdbg("golden compare Golden[%pK:%zuB] / Target[%pK:%zuB]\n",
frame, golden_entry->img_vbuf, golden_entry->size,
target_entry->vaddr, target_entry->size);
/* Finally, comparing contents */
if (npu_golden_ctx.data.comparator) {
ret = npu_golden_ctx.data.comparator(0,
golden_entry->img_vbuf, target_entry->vaddr,
dim, result);
compare_result += ret;
npu_ufdbg("result of comparator for golden idx(%u): (%d)\n",
frame, golden_entry->av_index, ret);
} else {
npu_ufdbg("no comparator defined for golden idx(%u). skipping data match.\n",
frame, golden_entry->av_index);
}
end_compare:
if (compare_result > 0) {
add_compare_msg(&npu_golden_ctx.result,
" MISMATCH(%d)\n", compare_result);
} else {
add_compare_msg(&npu_golden_ctx.result, " OK\n");
}
total_result += compare_result;
}
/* If the output buffer is full, left an message at the end of the buffer */
if (result->out_buf_cur == (result->out_buf_len - 1)) {
npu_ufdbg("full in output buffer, truncate the result.\n", frame);
/* Notify that the buffer is truncated */
tr_msg_len = strlen(TRUNCATE_MSG);
strncpy(&result->out_buf[NPU_GOLDEN_RESULT_BUF_LEN - tr_msg_len],
TRUNCATE_MSG, tr_msg_len);
}
return total_result;
}
/*
* Wrapper to npu_golden_compare for locking
*/
int npu_golden_compare(const struct npu_frame *frame)
{
int ret;
if (!atomic_read(&npu_golden_ctx.loaded)) {
npu_uftrace("Golden is not configured.\n", frame);
return 0;
}
mutex_lock(&npu_golden_ctx.lock);
ret = __npu_golden_compare(frame);
mutex_unlock(&npu_golden_ctx.lock);
return ret;
}
static const struct file_operations npu_golden_match_debug_fops = {
.owner = THIS_MODULE,
.open = npu_golden_match_open,
.release = npu_golden_match_close,
.read = npu_golden_match_read,
};
#define SET_GOLDEN_DESC_DEBUGFS_NAME "set-golden-desc"
#define RESULT_GOLDEN_MATCH_DEBUGFS_NAME "result-golden-match"
int register_golden_matcher(struct device *dev)
{
int ret;
npu_info("start in register_golden_matcher\n");
BUG_ON(!dev);
npu_dbg("initialize statekeeper in register_golden_matcher\n");
npu_statekeeper_initialize(&npu_golden_ctx.statekeeper,
NPU_GOLDEN_STATE_INVALID,
state_names, transition_map);
npu_info("Registeration of golden matcher : Register " SET_GOLDEN_DESC_DEBUGFS_NAME " to debugfs.\n");
ret = npu_debug_register(SET_GOLDEN_DESC_DEBUGFS_NAME, &npu_golden_set_debug_fops);
if (ret) {
npu_err("fail(%d) in npu_debug_register for " SET_GOLDEN_DESC_DEBUGFS_NAME "\n", ret);
goto err_exit;
}
npu_info("Registeration of golden matcher: Register " RESULT_GOLDEN_MATCH_DEBUGFS_NAME " to debugfs.\n");
ret = npu_debug_register(RESULT_GOLDEN_MATCH_DEBUGFS_NAME, &npu_golden_match_debug_fops);
if (ret) {
npu_err("fail(%d) in npu_debug_register for " RESULT_GOLDEN_MATCH_DEBUGFS_NAME "\n", ret);
goto err_exit;
}
/* Initialize lock */
atomic_set(&npu_golden_ctx.loaded, 0);
mutex_init(&npu_golden_ctx.lock);
npu_golden_ctx.dev = dev;
npu_info("complete in register_golden_matcher\n");
return 0;
err_exit:
npu_err("fail(%d) in register_golden_matcher\n", ret);
return ret;
}
/* Unit test */
#ifdef CONFIG_VISION_UNITTEST
#define IDIOT_TESTCASE_IMPL "npu-golden.idiot"
#include "idiot-def.h"
#endif