camera: oot: sensor kernel test and tegracam log

The sensor kernel tests module is a simple test runner whose
responsibility is to dispatch tests and communicate with the
companion userspace binary over Generic Netlink sockets. Tests
may choose to register themselves with this module where they
then can be executed from userspace.

The sensor DT test asserts DT compliance of a given sensor node
with respect to a given TVCF version

A small set of logging utilities have been added to allow
tests the ability to log their results to the kernel log
or to some other handle (typically injected via the sensor
kernel tests module) through a common interface.

Bug 3583587

Change-Id: I22acf9c90fc82fbbdad8ba271dcdbbd6a5898eda
Signed-off-by: Ankur Pawar <ankurp@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/2857293
Reviewed-by: Frank Chen <frankc@nvidia.com>
Reviewed-by: Semi Malinen <smalinen@nvidia.com>
Reviewed-by: Laxman Dewangan <ldewangan@nvidia.com>
GVS: Gerrit_Virtual_Submit <buildbot_gerritrpt@nvidia.com>
This commit is contained in:
Ankur Pawar
2023-02-13 10:50:36 +00:00
committed by mobile promotions
parent da3ddb1fcb
commit 79aa81a101
13 changed files with 2184 additions and 2 deletions

View File

@@ -1,5 +1,5 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved.
# Copyright (c) 2022-2023, NVIDIA CORPORATION. All rights reserved.
LINUXINCLUDE += -I$(srctree.nvidia-oot)/include
LINUXINCLUDE += -I$(srctree.nvidia-oot)/drivers/video/tegra/host
@@ -32,4 +32,4 @@ tegra-camera-objs += fusa-capture/capture-vi-channel.o
tegra-camera-objs += fusa-capture/capture-isp-channel.o
tegra-camera-objs += fusa-capture/capture-isp.o
obj-m += tegra-camera.o
obj-m += tests/

View File

@@ -0,0 +1,14 @@
# SPDX-License-Identifier: GPL-2.0
# Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# Free-standing Tegra Camera Kernel Tests
sensor_kernel_tests-m += sensor_dt_test.o
sensor_kernel_tests-m += sensor_dt_test_nodes.o
# Tegra Camera Kernel Tests Utilities
obj-m += utils/tegracam_log.o
# Sensor Kernel Tests Module
obj-m += sensor_kernel_tests.o
sensor_kernel_tests-m += modules/sensor_kernel_tests_core.o
sensor_kernel_tests-m += modules/sensor_kernel_tests_runner.o

View File

@@ -0,0 +1,473 @@
// SPDX-License-Identifier: GPL-2.0
/*
* sensor_kernel_tests_core - sensor kernel tests module core
*
* Copyright (c) 2018-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
*/
#include <linux/err.h>
#include <linux/init.h>
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/skbuff.h>
#include <linux/version.h>
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <net/netlink.h>
#include <net/genetlink.h>
#include "sensor_kernel_tests_core.h"
#include "sensor_kernel_tests_runner.h"
#define SKT_NL_FAMILY ("SKT_TESTS")
#define SKT_GET_ERR(e) ((e == NULL) ? -EFAULT : PTR_ERR(e))
enum sensor_kernel_tests_attrs {
SKT_ATTR_UNSPECIFIED = 0,
/* Glob pattern for filtering tests */
SKT_ATTR_GLOB,
/* Generic result */
SKT_ATTR_RESULT,
/* Sensor compatible name */
SKT_ATTR_SEN_COMPAT,
/* Sensor name */
SKT_ATTR_SEN_NAME,
/* Tegra V4L2 Camera Framework version */
SKT_ATTR_TVCF_VERSION,
/* A generic null-terminated message */
SKT_ATTR_MSG,
SKT_ATTR_MAX,
};
enum sensor_kernel_tests_cmds {
SKT_CMD_UNSPECIFIED = 0,
/* Query available tests */
SKT_CMD_QUERY_TESTS,
/* Run available tests */
SKT_CMD_RUN_TESTS,
/* Acknowledge a KMD-initiated message */
SKT_CMD_ACK,
SKT_CMD_MAX,
};
/* Generic Netlink Callbacks */
static int skt_query_tests(struct sk_buff *skb, struct genl_info *info);
static int skt_run_tests(struct sk_buff *skb, struct genl_info *info);
static int skt_ack(struct sk_buff *skb, struct genl_info *info);
static struct nla_policy skt_policies[SKT_ATTR_MAX] = {
[SKT_ATTR_UNSPECIFIED] = {
.type = NLA_UNSPEC,
.len = 0 },
[SKT_ATTR_GLOB] = {
.type = NLA_NUL_STRING,
.len = SKT_NL_MAX_STR_LEN },
[SKT_ATTR_RESULT] = {
.type = NLA_S32 },
[SKT_ATTR_SEN_COMPAT] = {
.type = NLA_NUL_STRING,
.len = SKT_NL_MAX_STR_LEN },
[SKT_ATTR_SEN_NAME] = {
.type = NLA_NUL_STRING,
.len = SKT_NL_MAX_STR_LEN },
[SKT_ATTR_TVCF_VERSION] = {
.type = NLA_U32 },
[SKT_ATTR_MSG] = {
.type = NLA_NUL_STRING,
.len = SKT_NL_MAX_STR_LEN },
};
static struct genl_ops sensor_kernel_test_ops[] = {
{
.cmd = SKT_CMD_QUERY_TESTS,
.flags = GENL_CMD_CAP_HASPOL,
.policy = skt_policies,
.doit = skt_query_tests,
},
{
.cmd = SKT_CMD_RUN_TESTS,
.flags = GENL_CMD_CAP_HASPOL,
.policy = skt_policies,
.doit = skt_run_tests,
},
{
.cmd = SKT_CMD_ACK,
.flags = GENL_CMD_CAP_HASPOL,
.policy = skt_policies,
.doit = skt_ack,
},
};
static struct genl_family skt_family = {
.hdrsize = 0,
.name = SKT_NL_FAMILY,
.version = 1,
.maxattr = SKT_ATTR_MAX,
.ops = sensor_kernel_test_ops,
.n_ops = ARRAY_SIZE(sensor_kernel_test_ops),
};
struct skt_core_msg {
struct sk_buff *msg;
void *msg_hdr;
};
struct skt_core_worker {
struct work_struct work;
struct skt_runner_ctx ctx;
};
static struct skt_core_worker test_worker;
static DECLARE_WAIT_QUEUE_HEAD(test_worker_wq);
static bool user_acked;
static inline void skt_core_free_msg(struct skt_core_msg *skt_msg)
{
nlmsg_free(skt_msg->msg);
kfree(skt_msg);
}
static struct skt_core_msg *skt_core_get_msg(const u32 size,
const u32 portid, const u32 seq, const int flags,
const u8 cmd)
{
struct skt_core_msg *skt_msg;
skt_msg = kzalloc(sizeof(*skt_msg), GFP_KERNEL);
if (skt_msg == NULL)
return ERR_PTR(-ENOMEM);
skt_msg->msg = genlmsg_new(size, GFP_KERNEL);
if (skt_msg->msg == NULL) {
kfree(skt_msg);
return ERR_PTR(-ENOMEM);
}
skt_msg->msg_hdr = genlmsg_put(skt_msg->msg, portid, seq, &skt_family,
flags, cmd);
if (skt_msg->msg_hdr == NULL) {
skt_core_free_msg(skt_msg);
return ERR_PTR(-EOVERFLOW);
}
return skt_msg;
}
static inline int skt_core_send_msg(struct skt_core_msg *skt_msg,
struct net *net, const u32 portid)
{
genlmsg_end(skt_msg->msg, skt_msg->msg_hdr);
return genlmsg_unicast(net, skt_msg->msg, portid);
}
static inline int skt_core_append_msg(struct skt_core_msg *skt_msg,
const char *buff)
{
return nla_put_string(skt_msg->msg, SKT_ATTR_MSG, buff);
}
static inline int skt_core_append_result(struct skt_core_msg *skt_msg,
const s32 result)
{
return nla_put_s32(skt_msg->msg, SKT_ATTR_RESULT, result);
}
int skt_core_vlog_msg(const u32 portid, const char *fmt, va_list args)
{
struct skt_core_msg *msg;
char *buff;
int bytes;
int err;
int ret;
if (fmt == NULL)
return -EINVAL;
buff = kmalloc(SKT_NL_MAX_STR_LEN, GFP_KERNEL);
if (buff == NULL)
return -ENOMEM;
bytes = vsnprintf(buff, SKT_NL_MAX_STR_LEN, fmt, args);
if (bytes >= SKT_NL_MAX_STR_LEN)
pr_warn("skt core message truncated\n");
msg = skt_core_get_msg(strlen(buff) + 1, portid,
0, 0, SKT_CMD_RUN_TESTS);
if (IS_ERR_OR_NULL(msg)) {
err = SKT_GET_ERR(msg);
pr_err("Could not get skt msg (%d)\n", err);
kfree(buff);
return err;
}
err = skt_core_append_msg(msg, buff);
kfree(buff);
if (err != 0) {
pr_err("Could not append skt msg (%d)\n", err);
skt_core_free_msg(msg);
return err;
}
err = skt_core_send_msg(msg, &init_net, portid);
if (err != 0)
pr_err("Could not send skt msg (%d)\n", err);
else {
/*
* KMD->UMD communication is buffered. Primitive flow control
* implemented as msg + ACK keeps the socket buffers from
* overflowing if a test(s) dumps more output than userspace
* can consume.
*/
ret = wait_event_interruptible_timeout(test_worker_wq,
(user_acked == true), msecs_to_jiffies(1000));
user_acked = false;
if (ret == 0) {
pr_warn("skt ACK timed out\n");
return -ETIMEDOUT;
}
}
return err;
}
int skt_core_log_msg(const u32 portid, const char *fmt, ...)
{
va_list args;
int err;
if (fmt == NULL)
return -EINVAL;
va_start(args, fmt);
err = skt_core_vlog_msg(portid, fmt, args);
va_end(args);
return err;
}
static int skt_format_test(struct skt_core_msg *msg,
const struct skt_test *test)
{
char *buff;
int bytes = 0;
int err;
buff = kmalloc(SKT_NL_MAX_STR_LEN, GFP_KERNEL);
if (buff == NULL)
return -ENOMEM;
bytes = snprintf(buff, SKT_NL_MAX_STR_LEN, "%s\n - %s\n",
test->name, test->description);
if (bytes >= SKT_NL_MAX_STR_LEN)
pr_warn("skt format test truncated\n");
err = skt_core_append_msg(msg, buff);
kfree(buff);
return err;
}
static int skt_query_tests(struct sk_buff *skb, struct genl_info *info)
{
struct skt_core_msg *msg;
struct skt_test **tests;
int num_tests;
const char *glob = NULL;
int err;
int i;
num_tests = skt_runner_num_tests();
tests = kcalloc(num_tests, sizeof(*tests), GFP_KERNEL);
msg = skt_core_get_msg(NLMSG_GOODSIZE, info->snd_portid,
info->snd_seq, 0, SKT_CMD_QUERY_TESTS);
if (IS_ERR_OR_NULL(msg)) {
err = SKT_GET_ERR(msg);
pr_err("Could not get skt msg (%d)\n", err);
goto query_test_fail;
}
if (info->attrs[SKT_ATTR_GLOB] != NULL)
glob = (const char *)nla_data(info->attrs[SKT_ATTR_GLOB]);
num_tests = skt_runner_query_tests(glob, tests, num_tests);
if (num_tests <= 0) {
err = skt_core_append_msg(msg, "** No tests found **\n");
if (err != 0) {
pr_err("Could not append skt msg (%d)\n", err);
goto query_test_fail;
}
} else {
for (i = 0; i < num_tests; i++) {
err = skt_format_test(msg, tests[i]);
if (err != 0) {
pr_err("Could not format test name (%d)\n",
err);
goto query_test_fail;
}
}
}
err = skt_core_append_result(msg, 0);
if (err != 0) {
pr_err("Could not append skt result (%d)\n", err);
goto query_test_fail;
}
kfree(tests);
err = skt_core_send_msg(msg, genl_info_net(info), info->snd_portid);
if (err != 0)
pr_err("Could not send skt msg (%d)\n", err);
return err;
query_test_fail:
pr_err("skt query test failed (%d)\n", err);
kfree(tests);
skt_core_free_msg(msg);
return err;
}
static void skt_core_test_work(struct work_struct *work)
{
struct skt_core_msg *msg;
struct skt_core_worker *worker;
int result;
int err;
if (work == NULL)
return;
worker = container_of(work, struct skt_core_worker, work);
msg = skt_core_get_msg(NLMSG_GOODSIZE, worker->ctx.dest_portid,
0, 0, SKT_CMD_RUN_TESTS);
if (IS_ERR_OR_NULL(msg)) {
err = SKT_GET_ERR(msg);
pr_err("Could not get skt msg (%d)\n", err);
return;
}
result = skt_runner_run_tests(&worker->ctx);
err = skt_core_append_result(msg, result);
if (err != 0) {
pr_err("Could not append skt result (%d)\n", err);
skt_core_free_msg(msg);
return;
}
err = skt_core_send_msg(msg, &init_net, worker->ctx.dest_portid);
if (err != 0)
pr_err("Could not send skt msg (%d)\n", err);
}
static int skt_run_tests(struct sk_buff *skb, struct genl_info *info)
{
struct skt_runner_ctx *ctx = &test_worker.ctx;
struct skt_core_msg *msg;
int err = 0;
if (info->attrs[SKT_ATTR_GLOB] != NULL) {
nla_strscpy(ctx->glob, info->attrs[SKT_ATTR_GLOB],
SKT_RUNNER_STR_LEN);
} else
ctx->glob[0] = '\0';
if (info->attrs[SKT_ATTR_SEN_COMPAT] != NULL) {
nla_strscpy(ctx->compat, info->attrs[SKT_ATTR_SEN_COMPAT],
SKT_RUNNER_STR_LEN);
} else
ctx->compat[0] = '\0';
if (info->attrs[SKT_ATTR_SEN_NAME] != NULL) {
nla_strscpy(ctx->name, info->attrs[SKT_ATTR_SEN_NAME],
SKT_RUNNER_STR_LEN);
} else
ctx->name[0] = '\0';
if (info->attrs[SKT_ATTR_TVCF_VERSION] != NULL) {
ctx->tvcf_version = nla_get_u32(
info->attrs[SKT_ATTR_TVCF_VERSION]);
}
ctx->dest_portid = info->snd_portid;
user_acked = false;
if (!schedule_work(&test_worker.work))
goto run_tests_fail;
return 0;
run_tests_fail:
pr_err("Could not schedule test work\n");
msg = skt_core_get_msg(NLMSG_GOODSIZE, info->snd_portid,
info->snd_seq, 0, SKT_CMD_RUN_TESTS);
if (IS_ERR_OR_NULL(msg)) {
err = SKT_GET_ERR(msg);
pr_err("Could not get skt msg (%d)\n", err);
return err;
}
err = skt_core_append_msg(msg, "Could not schedule test work\n");
if (err != 0) {
pr_err("Could not append skt msg (%d)\n", err);
goto genl_fail;
}
err = skt_core_append_result(msg, -EBUSY);
if (err != 0) {
pr_err("Could not append skt result (%d)\n", err);
goto genl_fail;
}
err = skt_core_send_msg(msg, genl_info_net(info), info->snd_portid);
if (err != 0)
pr_err("Could not send skt msg (%d)\n", err);
return err;
genl_fail:
pr_err("skt message failed (%d)\n", err);
skt_core_free_msg(msg);
return err;
}
static int skt_ack(struct sk_buff *skb, struct genl_info *info)
{
user_acked = true;
wake_up_interruptible(&test_worker_wq);
return 0;
}
static int __init skt_init(void)
{
int err = 0;
err = genl_register_family(&skt_family);
if (err != 0) {
pr_err("Could not register genetlink family (%d)\n", err);
return err;
}
pr_debug("SKT family ID is %u\n", skt_family.id);
INIT_WORK(&test_worker.work, skt_core_test_work);
return err;
}
static void __exit skt_remove(void)
{
genl_unregister_family(&skt_family);
cancel_work_sync(&test_worker.work);
}
module_init(skt_init);
module_exit(skt_remove);
MODULE_LICENSE("GPL v2");

View File

@@ -0,0 +1,30 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* sensor_kernel_tests_core - sensor kernel tests module core
*
* Copyright (c) 2018-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
*/
#ifndef __SENSOR_KERNEL_TESTS_CORE_H__
#define __SENSOR_KERNEL_TESTS_CORE_H__
#include <linux/stdarg.h>
#define SKT_NL_MAX_STR_LEN (1024U)
/**
* skt_core_(v)log_msg - Log a unicast message to the recipient at portid
* @portid: destination portid
* @fmt: format
* @args: arguments
*
* To be used only under test context, that is, when the recipient is
* an initiator of SKT_CMD_RUN_TESTS.
*
* Returns non-zero on failure.
*/
int skt_core_vlog_msg(const u32 portid, const char *fmt, va_list args);
int skt_core_log_msg(const u32 portid, const char *fmt, ...);
#endif // __SENSOR_KERNEL_TESTS_CORE_H__

View File

@@ -0,0 +1,245 @@
// SPDX-License-Identifier: GPL-2.0
/*
* sensor_kernel_tests_runner - test runner for sensor kernel tests
*
* Copyright (c) 2018-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
*/
#include <linux/glob.h>
#include <linux/of.h>
#include <linux/string.h>
#include <media/tegracam_core.h>
#include <media/tegracam_utils.h>
#include <linux/stdarg.h>
#include "sensor_kernel_tests_runner.h"
#include "sensor_kernel_tests_core.h"
#include "../tegracam_tests.h"
#include "../utils/tegracam_log.h"
#define SKT_TEST_RUN "[ RUN ]"
#define SKT_TEST_OK "[ OK ]"
#define SKT_TEST_FAIL "[ FAIL ]"
#define SKT_TEST_PASSED "[ PASSED ]"
#define SKT_TEST_FAILED "[ FAILED ]"
#define SKT_TEST_SEP "[==========]"
#define SKT_TEST_BLANK "[ ]"
#define SKT_TVCF_VERS_BUFF_SIZE (32U)
static char tvcf_vers_buff[SKT_TVCF_VERS_BUFF_SIZE];
static u32 dest_portid;
static struct skt_test skt_available_tests[] = {
{
.name = "Sensor DT Test",
.description = "Asserts compliance of sensor DT",
.run = sensor_verify_dt,
},
};
int skt_runner_num_tests(void)
{
return ARRAY_SIZE(skt_available_tests);
}
int skt_runner_query_tests(const char *glob,
struct skt_test **tests, const int len)
{
int i;
int ntests = 0;
if (tests == NULL)
return -1;
for (i = 0; i < ARRAY_SIZE(skt_available_tests); i++) {
if (ntests > len)
break;
if ((glob != NULL) &&
!glob_match(glob, skt_available_tests[i].name))
continue;
tests[ntests++] = &skt_available_tests[i];
}
return ntests;
}
static int skt_runner_log(const char *fmt, ...)
{
va_list args;
int ret;
va_start(args, fmt);
ret = skt_core_vlog_msg(dest_portid, fmt, args);
va_end(args);
return ret;
}
static int skt_runner_test_log(const char *fmt, va_list args)
{
int err;
err = skt_core_log_msg(dest_portid, "%s ", SKT_TEST_BLANK);
if (err != 0)
return err;
return skt_core_vlog_msg(dest_portid, fmt, args);
}
static int skt_runner_run_single_test(const struct skt_test *test,
struct device_node *node,
const u32 tvcf_version)
{
int res;
skt_runner_log(SKT_TEST_RUN " %s\n", test->name);
res = test->run(node, tvcf_version);
skt_runner_log("%s %s\n",
(res == 0) ? SKT_TEST_OK : SKT_TEST_FAIL, test->name);
return res;
}
static u32 skt_runner_query_version(struct device_node *node)
{
u32 version;
skt_runner_log("Note: No TVCF version specified - querying TVCF\n");
version = tegracam_query_version(node->name);
if (version == 0) {
version = tegracam_version(1, 0, 0);
skt_runner_log("Note: Sensor %s not registered with TVCF\n",
node->name);
skt_runner_log(" Falling back to TVCF version ");
} else {
skt_runner_log("Note: Sensor %s registered with TVCF\n",
node->name);
skt_runner_log(" Using queried TVCF version ");
}
return version;
}
static int skt_runner_test_node(struct device_node *node,
const u32 req_version, const char *glob)
{
u32 active_version;
int res = 0;
int ntests_ran = 0;
int ntests_passed = 0;
int i;
/* Register callback to send output to userspace */
if (camtest_try_acquire_global_log(skt_runner_test_log) != 0) {
skt_runner_log("Unable to direct tegra camtest output\n");
return -1;
}
if (req_version != 0) {
active_version = req_version;
skt_runner_log("Note: Using user-specified TVCF version ");
} else
active_version = skt_runner_query_version(node);
format_tvcf_version(active_version, tvcf_vers_buff,
SKT_TVCF_VERS_BUFF_SIZE);
skt_runner_log("%s\n", tvcf_vers_buff);
if (glob == NULL) {
skt_runner_log(SKT_TEST_SEP " Starting tests for %s.\n",
node->name);
} else {
skt_runner_log(SKT_TEST_SEP
" Starting tests for %s with filter \"%s\".\n",
node->name, glob);
}
for (i = 0; i < ARRAY_SIZE(skt_available_tests); i++) {
if ((glob != NULL) &&
!glob_match(glob, skt_available_tests[i].name))
continue;
res = skt_runner_run_single_test(&skt_available_tests[i],
node, active_version);
if (res == 0)
ntests_passed++;
ntests_ran++;
}
camtest_release_global_log();
skt_runner_log(SKT_TEST_SEP " %d tests ran.\n", ntests_ran);
skt_runner_log("%s %d of %d test(s) passed.\n\n",
(ntests_ran == ntests_passed) ?
SKT_TEST_PASSED : SKT_TEST_FAILED,
ntests_passed, ntests_ran);
return res;
}
int skt_runner_run_tests(const struct skt_runner_ctx *ctx)
{
int res = 0;
bool node_found = false;
const char *glob;
struct device_node *node;
if (ctx == NULL)
return -1;
dest_portid = ctx->dest_portid;
if ((strlen(ctx->compat) == 0) && (strlen(ctx->name) == 0)) {
skt_runner_log("** Sensor compatible or name required! **\n");
return -1;
}
if (strlen(ctx->glob) == 0)
glob = NULL;
else
glob = ctx->glob;
if (strlen(ctx->compat) != 0) {
for_each_compatible_node(node, NULL, ctx->compat) {
if ((strlen(ctx->name) != 0) &&
!(strcmp(node->name, ctx->name) == 0))
continue;
node_found = true;
res |= skt_runner_test_node(node, ctx->tvcf_version,
glob);
of_node_put(node);
}
} else if (strlen(ctx->name) != 0) {
node = of_find_node_by_name(NULL, ctx->name);
if (node != NULL) {
node_found = true;
res = skt_runner_test_node(node, ctx->tvcf_version,
glob);
of_node_put(node);
}
}
if (!node_found) {
skt_runner_log("** No valid sensors found! **\n");
skt_runner_log("Search Criteria:\n");
skt_runner_log(" sensor compatible: %s\n",
(strlen(ctx->compat) == 0) ?
"N/A" : ctx->compat);
skt_runner_log(" sensor name: %s\n",
(strlen(ctx->name) == 0) ?
"N/A" : ctx->name);
return -1;
}
skt_runner_log("** All tests complete **\n\n");
return res;
}

View File

@@ -0,0 +1,35 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* sensor_kernel_tests_runner - test runner for sensor kernel tests
*
* Copyright (c) 2018-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
*/
#ifndef __SENSOR_KERNEL_TESTS_RUNNER_H__
#define __SENSOR_KERNEL_TESTS_RUNNER_H__
#define SKT_RUNNER_STR_LEN (256U + 1U)
struct device_node;
struct skt_test {
const char *name;
const char *description;
int (*run)(struct device_node *node, const u32 tvcf_version);
};
struct skt_runner_ctx {
char compat[SKT_RUNNER_STR_LEN];
char name[SKT_RUNNER_STR_LEN];
char glob[SKT_RUNNER_STR_LEN];
u32 tvcf_version;
u32 dest_portid;
};
int skt_runner_num_tests(void);
int skt_runner_query_tests(const char *glob, struct skt_test **tests,
const int len);
int skt_runner_run_tests(const struct skt_runner_ctx *ctx);
#endif // __SENSOR_KERNEL_TESTS_RUNNER_H__

View File

@@ -0,0 +1,929 @@
// SPDX-License-Identifier: GPL-2.0
/*
* sensor_dt_test - sensor device tree test
*
* Copyright (c) 2018-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
*/
#include <linux/device.h>
#include <linux/glob.h>
#include <linux/kref.h>
#include <linux/list.h>
#include <linux/of.h>
#include <linux/slab.h>
#include "media/tegracam_core.h"
#include "media/tegracam_utils.h"
#include "tegracam_tests.h"
#include "utils/tegracam_log.h"
#include "sensor_dt_test.h"
#include "sensor_dt_test_nodes.h"
#define SV_DT_MAX_LINK_DEPTH (8)
#define MODE_TYPE_INVALID (-1)
#define PROP_OK "(OK)"
#define PROP_WARN "(WARN)"
#define PROP_FAIL "(FAIL)"
#define TVCF_VERSION_BUFF_SIZE (32U)
/**
* sv_dt_tvcf_version - decomposed tvcf version
*/
struct sv_dt_tvcf_version {
u8 major;
u8 minor;
u8 patch;
};
#define MAKE_TVCF_VERSION(_major, _minor, _patch) \
{ \
.major = (_major), \
.minor = (_minor), \
.patch = (_patch) \
}
/**
* sv_dt_prop - gold DT property
*
* @name: name of prop
* @links: links to other properties
* @refcount: refcount
* @list: list entry
*/
struct sv_dt_prop {
const char *name;
struct list_head links;
struct kref refcount;
struct list_head list;
};
/**
* sv_dt_link - connection between two properties
*
* @attrs: attribute states for all TVCF versions
* @source: source property
* @sink: sink property
* @list: list entry
*/
struct sv_dt_link {
struct sv_dt_link_attr attrs[TVCF_NVERSIONS];
struct sv_dt_prop *source;
struct sv_dt_prop *sink;
struct list_head list;
};
/**
* sv_dt_node - gold DT node
*
* @name: name of node
* @root: sentinel property for property graph
* @children: child nodes
* @list: list_entry
* @visited: flag for visitation state
*/
struct sv_dt_node {
const char *name;
struct sv_dt_prop root;
struct list_head children;
struct list_head list;
bool visited;
};
/**
* sv_dt_ctx - general test context
*
* @version: TVCF version being tested against
* @mode: capability of mode (bayer, DOL etc.)
*/
struct sv_dt_ctx {
enum sv_dt_link_version version;
u32 mode;
};
static const struct sv_dt_tvcf_version sv_dt_tvcf_versions[TVCF_NVERSIONS] = {
[TVCF_VERSION_V1_0_0] = MAKE_TVCF_VERSION(1, 0, 0),
[TVCF_VERSION_V2_0_0] = MAKE_TVCF_VERSION(2, 0, 0)
};
static struct sv_dt_ctx sv_ctx;
/**
* sv_dt_map_tvcf_version - map input TVCF version to nearest testable version.
*
* @in_tvcf: tvcf version to map
*/
static enum sv_dt_link_version sv_dt_map_tvcf_version(
const u32 in_tvcf)
{
enum sv_dt_link_version lv = 0;
const struct sv_dt_tvcf_version *tv;
u32 map_tvcf;
u32 diff = U32_MAX; // Prime for a large difference
int i;
tv = &sv_dt_tvcf_versions[TVCF_MIN_VERSION];
if (in_tvcf < tegracam_version(tv->major, tv->minor, tv->patch)) {
camtest_log(KERN_WARNING
"Input version is less than minimum\n");
return TVCF_MIN_VERSION;
}
for (i = 0; i < TVCF_NVERSIONS; i++) {
tv = &sv_dt_tvcf_versions[i];
map_tvcf = tegracam_version(tv->major, tv->minor, tv->patch);
if (map_tvcf <= in_tvcf) {
if (in_tvcf - map_tvcf < diff) {
lv = i;
diff = in_tvcf - map_tvcf;
}
}
}
return lv;
}
/**
* sv_dt_query_sensor_mode_type - query "mode_type" of a modeX node
*
* @node: DT mode node
*/
static u32 sv_dt_query_sensor_mode_type(
struct device_node *node)
{
struct property *prop;
const char *type;
int ret;
prop = of_find_property(node, "pixel_t", NULL);
if (prop != NULL)
return MODE_TYPE_BAYER;
prop = of_find_property(node, "mode_type", NULL);
if (prop == NULL)
return MODE_TYPE_ANY;
ret = of_property_read_string(node, "mode_type", &type);
if (ret != 0) {
camtest_log(KERN_ERR, "Unable to query mode type\n");
return MODE_TYPE_INVALID;
}
if (strcmp(type, "bayer") == 0)
return MODE_TYPE_BAYER;
else if (strcmp(type, "bayer_wdr_dol") == 0)
return MODE_TYPE_WDR_DOL;
else if (strcmp(type, "bayer_wdr_pwl") == 0)
return MODE_TYPE_WDR_PWL;
camtest_log(KERN_WARNING
"Unknown sensor mode type - defaulting to ANY\n");
return MODE_TYPE_ANY;
}
static struct sv_dt_prop *__sv_dt_find_prop(struct sv_dt_prop *prop,
const char *prop_name, const int depth)
{
struct sv_dt_link *link;
struct sv_dt_prop *found;
if (depth > SV_DT_MAX_LINK_DEPTH)
return NULL;
if ((prop->name != NULL) && (strcmp(prop->name, prop_name) == 0))
return prop;
list_for_each_entry(link, &prop->links, list) {
found = __sv_dt_find_prop(link->sink, prop_name, depth + 1);
if (found != NULL)
return found;
}
return NULL;
}
/**
* sv_dt_find_prop - find a property in property graph
*
* @node: node which property belongs to
* @prop_name: name of property to find
*
* Note: This DFS is limited to a depth of SV_DT_MAX_LINK_DEPTH. If the
* depth exceeds this amount NULL is returned as if the property
* was not found in a scenario with infinite stack space.
*/
static inline struct sv_dt_prop *sv_dt_find_prop(struct sv_dt_node *node,
const char *prop_name)
{
return __sv_dt_find_prop(&node->root, prop_name, 0);
}
/**
* sv_dt_make_prop - make a gold DT property
*
* @name: name of property
*/
static struct sv_dt_prop *sv_dt_make_prop(const char *name)
{
struct sv_dt_prop *prop;
prop = kzalloc(sizeof(*prop), GFP_KERNEL);
if (prop == NULL)
return NULL;
prop->name = name;
INIT_LIST_HEAD(&prop->links);
INIT_LIST_HEAD(&prop->list);
kref_init(&prop->refcount);
return prop;
}
/**
* sv_dt_put_prop - free a gold DT property
*
* @kref: refcount struct
*
* Note: This is only intended to be used by kref_*()
*/
static void sv_dt_put_prop(struct kref *kref)
{
struct sv_dt_prop *prop =
container_of(kref, struct sv_dt_prop, refcount);
kfree(prop);
}
/**
* sv_dt_free_props - free an entire property graph
*
* @prop: sentinel property to begin traversal from
*/
static void sv_dt_free_props(struct sv_dt_prop *prop)
{
struct sv_dt_link *link;
struct sv_dt_link *temp;
list_for_each_entry_safe(link, temp, &prop->links, list) {
sv_dt_free_props(link->sink);
kref_put(&link->sink->refcount, sv_dt_put_prop);
list_del(&link->list);
kfree(link);
}
}
/**
* sv_dt_free_node - free a gold DT node and its properties
*
* @node: node to free
*/
static inline void sv_dt_free_node(struct sv_dt_node *node)
{
sv_dt_free_props(&node->root);
kfree(node);
}
/**
* sv_dt_make_node - make a gold DT node
*
* @name: name of gold node
* @make_props: fp defining property creation for the node
*/
static struct sv_dt_node *sv_dt_make_node(const char *name,
int (*make_props)(struct sv_dt_node *node))
{
struct sv_dt_node *node;
int err;
node = kzalloc(sizeof(*node), GFP_KERNEL);
if (node == NULL)
return NULL;
node->name = name;
INIT_LIST_HEAD(&node->root.links);
node->root.name = NULL;
INIT_LIST_HEAD(&node->children);
INIT_LIST_HEAD(&node->list);
node->visited = false;
err = make_props(node);
if (err != 0) {
sv_dt_free_node(node);
return NULL;
}
return node;
}
/**
* sv_dt_link_nodes - link a source node to a (child) sink node
*
* @source: source node
* @sink: (child) sink node
*/
static inline void sv_dt_link_nodes(struct sv_dt_node *source,
struct sv_dt_node *sink)
{
list_add_tail(&sink->list, &source->children);
}
/**
* sv_dt_log_prop_reason - format a "reason" for some property relation
*
* @status: status tag
* @dt_prop: actual DT property who is the recipient of @verb
* @link: link of two properties
* @verb: verb of how link source relates to link sink
*/
static void sv_dt_log_prop_reason(const char *status,
struct property *dt_prop,
struct sv_dt_link *link,
const char *verb)
{
if (link->source->name == NULL)
camtest_log(KERN_INFO " %s: prop %s %s\n", status,
(dt_prop == NULL) ? link->sink->name : dt_prop->name,
verb);
else
camtest_log(KERN_INFO " %s: prop %s %s by %s\n", status,
(dt_prop == NULL) ? link->sink->name : dt_prop->name,
verb, link->source->name);
}
/**
* sv_dt_prop_applies_mode_type - check if mode relation of link applies
*
* @link: link
*
* For two properties with a relation defined by @link checks to see if the
* relation is valid for the "mode_type" of the current mode. Practically,
* if the relation exists (ROOT -> SOME_DOL_PROP) this function returns
* true if the link mode type (DOL) matches the mode being tested. If the
* mode being tested is not DOL (in this example) false is returned.
*/
static inline bool sv_dt_prop_applies_mode_type(struct sv_dt_link *link)
{
u32 modes;
if (sv_ctx.version < 0)
return false;
modes = link->attrs[sv_ctx.version].modes;
if ((modes == MODE_TYPE_ANY) || (modes & sv_ctx.mode))
return true;
return false;
}
/**
* sv_dt_invalidate_link - invalidates a link
*
* @dt_node: node being tested
* @link: link
*
* "Invalidation" is the process ensuring a link's sink does not exist.
*/
static int sv_dt_invalidate_link(struct device_node *dt_node,
struct sv_dt_link *link)
{
struct property *dt_prop;
dt_prop = of_find_property(dt_node, link->sink->name, NULL);
if ((dt_prop != NULL) && sv_dt_prop_applies_mode_type(link)) {
sv_dt_log_prop_reason(PROP_FAIL, dt_prop, link, "precluded");
return -1;
}
return 0;
}
/**
* sv_dt_warn_link_mode_type_mismatch - warn on property mode type mismatches
*
* @dt_prop: node being tested
* @link: link
*
* Warns if any property doesn't apply to the current mode type, i.e., having
* a property specific to DOL in a bayer mode_type modeX node.
*/
static void sv_dt_warn_link_mode_type_mismatch(struct property *dt_prop,
struct sv_dt_link *link)
{
if (sv_dt_prop_applies_mode_type(link))
return;
if (sv_ctx.version < 0)
return;
camtest_log(KERN_INFO " " PROP_WARN
": prop %s only valid for mode types:\n",
dt_prop->name);
if (link->attrs[sv_ctx.version].modes & MODE_TYPE_BAYER)
camtest_log(KERN_INFO " -> bayer\n");
if (link->attrs[sv_ctx.version].modes & MODE_TYPE_WDR_PWL)
camtest_log(KERN_INFO " -> wdr_pwl\n");
if (link->attrs[sv_ctx.version].modes & MODE_TYPE_WDR_DOL)
camtest_log(KERN_INFO " -> wdr_dol\n");
}
/**
* sv_dt_match_link - find a matching real DT property to gold property
*
* @dt_node: node being tested
* @link: link
* @link_found: bool that is set to indicate if a match was found
*/
static int sv_dt_match_link(struct device_node *dt_node,
struct sv_dt_link *link,
bool *link_found)
{
struct property *dt_prop;
int status = 0;
if (sv_ctx.version < 0)
return -1;
*link_found = false;
for_each_property_of_node(dt_node, dt_prop) {
if (glob_match(link->sink->name, dt_prop->name)) {
*link_found = true;
sv_dt_warn_link_mode_type_mismatch(dt_prop, link);
switch (link->attrs[sv_ctx.version].type) {
case LTYPE_FORBIDDEN:
sv_dt_log_prop_reason(PROP_FAIL, dt_prop,
link, "forbidden");
status = -1;
break;
case LTYPE_OPTIONAL:
sv_dt_log_prop_reason(PROP_OK, dt_prop,
link, "optional");
break;
case LTYPE_REQUIRED:
sv_dt_log_prop_reason(PROP_OK, dt_prop,
link, "required");
break;
case LTYPE_DEPRECATED:
sv_dt_log_prop_reason(PROP_WARN, dt_prop,
link, "deprecated");
break;
case LTYPE_ALTERNATIVE:
case LTYPE_ALTERNATIVE_DEPRECATED:
/* Processed later */
break;
default:
camtest_log(KERN_ERR "Unknown link type!\n");
return -1;
}
}
}
return status;
}
/**
* sv_dt_link_not_found_ok - returns whether the absence of a link's sink is OK
*
* @link: link
*/
static bool sv_dt_link_not_found_ok(struct sv_dt_link *link)
{
if (!sv_dt_prop_applies_mode_type(link))
return true;
if (sv_ctx.version < 0)
return false;
switch (link->attrs[sv_ctx.version].type) {
case LTYPE_FORBIDDEN:
case LTYPE_OPTIONAL:
return true;
default:
return false;
}
}
static int sv_dt_verify_link(struct device_node *dt_node,
struct sv_dt_link *link);
/**
* sv_dt_masquerade_link - masquerades an imposter link as a real link
*
* @dt_node: node being tested
* @real: real link
* @imposter: imposter who will assume the real link's identity
*
* Masquerading is the process of substituting some property in a graph
* with one of its potential alternatives. In this case, if B is an alternative
* to A and A fails some criteria we perform a temporary node substitution
* of B onto A, i.e. B will masquerade as A for verification purposes.
*/
static int sv_dt_masquerade_link(struct device_node *dt_node,
struct sv_dt_link *real, struct sv_dt_link *imposter)
{
enum sv_dt_link_type ltype;
int status;
if (sv_ctx.version < 0)
return -1;
ltype = imposter->attrs[sv_ctx.version].type;
imposter->source = real->source;
switch (ltype) {
case LTYPE_ALTERNATIVE:
imposter->attrs[sv_ctx.version].type = LTYPE_REQUIRED;
break;
case LTYPE_ALTERNATIVE_DEPRECATED:
imposter->attrs[sv_ctx.version].type = LTYPE_DEPRECATED;
break;
default:
camtest_log(KERN_ERR "Unknown alternative type\n");
return -1;
}
status = sv_dt_verify_link(dt_node, imposter);
imposter->attrs[sv_ctx.version].type = ltype;
imposter->source = real->sink;
return status;
}
/**
* sv_dt_verify_link - verify a link between two properties
*
* @dt_node: node being tested
* @link: link
*/
static int sv_dt_verify_link(struct device_node *dt_node,
struct sv_dt_link *link)
{
LIST_HEAD(alternatives);
struct sv_dt_link *link_next;
struct sv_dt_link *temp;
bool link_found;
int status = 0;
if (sv_ctx.version < 0)
return -1;
/*
* Any LTYPE_ALTERNATIVE* links will be separated out into their
* own list for convenience to be conditionally handled later.
*/
list_for_each_entry_safe(link_next, temp, &link->sink->links, list) {
switch (link_next->attrs[sv_ctx.version].type) {
case LTYPE_ALTERNATIVE:
case LTYPE_ALTERNATIVE_DEPRECATED:
list_move(&link_next->list, &alternatives);
break;
default:
break;
}
}
status = sv_dt_match_link(dt_node, link, &link_found);
if (link_found && (status != 0))
return -1;
if (!link_found && sv_dt_link_not_found_ok(link))
return 0;
else if (!link_found) {
/* An alternative could potentially exist - do not return */
status = -1;
}
if (status == 0) {
/*
* Link was found and all is OK. Start recursing on children
* and invalidate any alternatives.
*/
list_for_each_entry(link_next, &link->sink->links, list)
status |= sv_dt_verify_link(dt_node, link_next);
list_for_each_entry(link_next, &alternatives, list)
status |= sv_dt_invalidate_link(dt_node, link_next);
} else {
/*
* Link is not OK. If there are any alternatives pursue those,
* otherwise fail.
*/
if (list_empty(&alternatives)) {
switch (link->attrs[sv_ctx.version].type) {
case LTYPE_REQUIRED:
sv_dt_log_prop_reason(PROP_FAIL, NULL, link,
"required but not found");
break;
case LTYPE_DEPRECATED:
sv_dt_log_prop_reason(PROP_FAIL, NULL, link,
"deprecated but not found");
break;
default:
break;
}
return -1;
}
/*
* Alternatives exist - reset status and retry validation
* masquerading alternatives as the "true" property that has
* failed.
*/
status = 0;
list_for_each_entry(link_next, &alternatives, list)
status |= sv_dt_masquerade_link(dt_node, link,
link_next);
/*
* Invalidate original dependents from the original property as
* we're looking at alternatives now.
*/
list_for_each_entry(link_next, &link->sink->links, list)
status |= sv_dt_invalidate_link(dt_node, link_next);
}
list_splice(&alternatives, &link->sink->links);
return status;
}
/**
* sv_dt_verify_dt_node - verify a gold DT node
*
* @dt_node: node being tested
* @gnode: golden representation of @dt_node
*/
static int sv_dt_verify_dt_node(struct device_node *dt_node,
const struct sv_dt_node *gnode)
{
struct sv_dt_link *link;
int status = 0;
camtest_log(KERN_INFO "Verifying node \"%s\"\n", dt_node->name);
sv_ctx.mode = sv_dt_query_sensor_mode_type(dt_node);
list_for_each_entry(link, &gnode->root.links, list) {
status |= sv_dt_verify_link(dt_node, link);
}
if (status != 0)
camtest_log(KERN_INFO "Node \"%s\" failed verification\n",
dt_node->name);
else
camtest_log(KERN_INFO "Node \"%s\" passed verification\n",
dt_node->name);
camtest_log(KERN_INFO "\n");
return status;
}
/**
* sv_dt_find_gchild - find a golden child node from a golden parent node
*
* @key: name of golden child node
* @gparent: golden parent node
*/
static struct sv_dt_node *sv_dt_find_gchild(const char *key,
const struct sv_dt_node *gparent)
{
struct sv_dt_node *gchild;
list_for_each_entry(gchild, &gparent->children, list) {
if ((gchild->name != NULL) && (glob_match(gchild->name, key)))
return gchild;
}
return NULL;
}
/**
* sv_dt_walk_dt - verify and walk DT node
*
* @dt_node: real DT node
* @gnode: gold DT node
*/
static int sv_dt_walk_dt(struct device_node *dt_node,
struct sv_dt_node *gnode)
{
struct device_node *dt_next;
struct sv_dt_node *gold_next;
int status = 0;
status = sv_dt_verify_dt_node(dt_node, gnode);
gnode->visited = true;
for_each_child_of_node(dt_node, dt_next) {
gold_next = sv_dt_find_gchild(dt_next->name, gnode);
if (gold_next != NULL)
status |= sv_dt_walk_dt(dt_next, gold_next);
}
return status;
}
/**
* sv_dt_check_unvisited_gnode - check for unvisited golden nodes
*
* @gnode: gold node root
*/
static int sv_dt_check_unvisited_gnode(struct sv_dt_node *gnode)
{
int status = 0;
struct sv_dt_node *gold_next = NULL;
if (!gnode->visited) {
camtest_log(KERN_INFO " Required node \"%s\" not found\n",
gnode->name);
return -1;
}
camtest_log(KERN_INFO " Required node \"%s\" visited\n", gnode->name);
list_for_each_entry(gold_next, &gnode->children, list) {
status |= sv_dt_check_unvisited_gnode(gold_next);
}
return status;
}
/**
* sv_dt_verify_full_dt - entry point for DT verification
*
* @dt_root: real DT root
* @groot: gold DT root
*/
static int sv_dt_verify_full_dt(struct device_node *dt_root,
struct sv_dt_node *groot)
{
int status = 0;
int ret;
camtest_log(KERN_INFO "Sensor DT test starting for root node \"%s\"\n",
dt_root->name);
status = sv_dt_walk_dt(dt_root, groot);
camtest_log(KERN_INFO
"Checking all required nodes have been visited\n");
ret = sv_dt_check_unvisited_gnode(groot);
if (ret == 0)
camtest_log(KERN_INFO "All required nodes visited\n");
else
camtest_log(KERN_INFO "Required nodes missing\n");
status |= ret;
return status;
}
/**
* sv_dt_make_link - make a link
*
* @node: gold DT node who the property will belong to
* @attrs: potential attributes for the link
* @source: source property name
* @sink: sink property name
*
* If the source property name is NULL @node's sentinel property will be
* made the source.
*/
int sv_dt_make_link(struct sv_dt_node *node,
struct sv_dt_link_attr *attrs,
const char *source, const char *sink, ...)
{
struct list_head *link_source;
struct sv_dt_link *link;
struct sv_dt_prop *prop;
if ((node == NULL) || (attrs == NULL) || (sink == NULL))
return -EINVAL;
/*
* If a link source is NULL the link starts at the node's
* "sentinel" property. Otherwise, the link starts from some other
* property deeper in the graph which needs found.
*/
if (source == NULL)
link_source = &node->root.links;
else {
prop = sv_dt_find_prop(node, source);
if (prop == NULL) {
camtest_log(KERN_ERR
"Cannot make prop with unknown source %s\n",
source);
return -1;
}
link_source = &prop->links;
}
link = kzalloc(sizeof(*link), GFP_KERNEL);
if (link == NULL)
return -ENOMEM;
/*
* If the sink is found links can be made immediately. Otherwise
* the sink property needs created.
*/
prop = sv_dt_find_prop(node, sink);
if (prop == NULL) {
prop = sv_dt_make_prop(sink);
if (prop == NULL) {
kfree(link);
return -ENOMEM;
}
} else
kref_get(&prop->refcount);
link->sink = prop;
link->source = container_of(link_source, struct sv_dt_prop, links);
/* Apply attributes to the link */
memcpy(link->attrs, attrs, sizeof(*attrs) * TVCF_NVERSIONS);
list_add_tail(&link->list, link_source);
return 0;
}
EXPORT_SYMBOL_GPL(sv_dt_make_link);
/**
* sv_dt_write_attr - write a link attribute
*
* @attrs: array of attributes to write to
* @version: link version
* @type: link type
* @modes: link mode_type modes
*/
int sv_dt_write_attr(
struct sv_dt_link_attr *attrs,
const enum sv_dt_link_version version,
const enum sv_dt_link_type type,
const u32 modes)
{
if ((version < 0) || (version > TVCF_NVERSIONS))
return -EINVAL;
attrs[version].type = type;
attrs[version].modes = modes;
return 0;
}
EXPORT_SYMBOL_GPL(sv_dt_write_attr);
/**
* sensor_verify_dt - entry point for sensor DT test
*
* @node: root DT node
* @tvcf_version: tvcf framework version
*/
int sensor_verify_dt(struct device_node *node, const u32 tvcf_version)
{
struct sv_dt_node *root_node = NULL;
struct sv_dt_node *modeX_node = NULL;
char tvcf_buff[TVCF_VERSION_BUFF_SIZE];
const struct sv_dt_tvcf_version *sv_tv;
u32 tv;
int err = 0;
if (node == NULL)
return -EINVAL;
sv_ctx.version = sv_dt_map_tvcf_version(tvcf_version);
if (sv_ctx.version == TVCF_VERSION_INVALID || sv_ctx.version < 0) {
camtest_log(KERN_ERR "Invalid TVCF version\n");
return -EINVAL;
}
sv_tv = &sv_dt_tvcf_versions[sv_ctx.version];
tv = tegracam_version(sv_tv->major, sv_tv->minor, sv_tv->patch);
format_tvcf_version(tv, tvcf_buff, TVCF_VERSION_BUFF_SIZE);
camtest_log(KERN_INFO "Testing sensor DT against TVCF version %s\n",
tvcf_buff);
/* Construct DT representation */
root_node = sv_dt_make_node("root", sv_dt_make_root_node_props);
if (root_node == NULL) {
camtest_log(KERN_ERR "Could not create root node\n");
goto sv_fail;
}
modeX_node = sv_dt_make_node("mode[0-9]*", sv_dt_make_modeX_node_props);
if (modeX_node == NULL) {
camtest_log(KERN_ERR "Could not create modeX node\n");
goto sv_root_fail;
}
sv_dt_link_nodes(root_node, modeX_node);
err = sv_dt_verify_full_dt(node, root_node);
sv_dt_free_node(modeX_node);
sv_root_fail:
sv_dt_free_node(root_node);
sv_fail:
if (err == 0)
camtest_log(KERN_INFO "Sensor DT test passed\n");
else
camtest_log(KERN_INFO "Sensor DT test failed\n");
return err;
}
EXPORT_SYMBOL_GPL(sensor_verify_dt);

View File

@@ -0,0 +1,188 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* sensor_dt_test - sensor device tree test
*
* Copyright (c) 2018-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
*/
#ifndef __SENSOR_DT_TEST_H__
#define __SENSOR_DT_TEST_H__
#include <linux/string.h>
#include <linux/types.h>
/**
* TVCF versions to be tested against.
*
* The semantics of the test versioning is such that:
* + Any version, V, where V is of MAJOR.MINOR.PATCH, will be
* matched against floor(V) where floor(V) is the greatest
* TVCF version that is less than OR equal to V.
*
* For example, given some input version V and sensor dt test
* names support for versions 1.0.0, 2.0.0, and 2.5.0:
* -> floor(V = 1.0.0) = 1.0.0
* -> floor(V = 1.8.2) = 1.0.0
* -> floor(V = 2.0.5) = 2.0.0
* -> floor(V = 3.1.9) = 2.5.0
*
* Given this, the sensor DT test can support any number of
* TVCF versions WITHOUT modifications granted there are no
* DT modifications between versions. Said another way, only
* versions incompatible with other versions need explicitly
* defined.
*
* To add support for a TVCF version that requires specific DT
* checks:
* 1) Add version to `enum sv_dt_link_version`
* 2) Add version to `struct sv_dt_tvcf_version`
* 3) Add version to `MAKE_LINK_ALL_FULL` macro
* 4) Update properties accordingly
*/
enum sv_dt_link_version {
TVCF_VERSION_V1_0_0 = 0,
TVCF_VERSION_V2_0_0,
TVCF_NVERSIONS,
};
#define TVCF_MIN_VERSION (TVCF_VERSION_V1_0_0)
#define TVCF_VERSION_INVALID (-1)
/**
* Allows a DT property to be tested only with a specific mode type.
*/
#define MODE_TYPE_ANY (0U)
#define MODE_TYPE_BAYER (1 << 0U)
#define MODE_TYPE_WDR_DOL (1 << 1U)
#define MODE_TYPE_WDR_PWL (1 << 2U)
#define MODE_TYPE_MAX (1 << 31U)
/**
* Relations between a source property X, and a sink property Y.
*
* Only ONE relation may exist for each property pair for each
* supported TVCF version. Any property can form any number of
* unique property pair relations for each supported TVCF version.
* Relations are uni-directional from X to Y in all cases.
*
* Unless explicitly stated any relation between X and Y is
* LTYPE_FORBIDDEN. For example, if a LTYPE_REQUIRED relations exists
* for X and Y for TVCF version 2.5.0 any other relation between
* X and Y for TVCF != 2.5.0 is LTYPE_FORBIDDEN.
*
* LTYPE_FORBIDDEN: The presence of X forbids the presence of Y.
* The converse IS NOT true - if X is not present
* Y can assume any state.
* LTYPE_OPTIONAL: The presence of X makes Y optional.
* LTYPE_REQUIRED: The presence of X requires the presence of Y.
* This type has special meaning when X has a
* LTYPE_ALTERNATIVE* relation to some property Z.
* If X does not exist but Z does any LTYPE_REQUIRED
* relations from X (i.e. to Y) become LTYPE_FORBIDDEN.
* In other words, if an alternative to X is found,
* X and all of its dependencies will be invalidated.
* LTYPE_DEPRECATED: The presence of X deprecates Y. This is not a failure
* condition but a warning will be posted.
* LTYPE_ALTERNATIVE: Y is an alternative property to X. If X exists then
* Y (and its dependents) are invalidated. If X does not
* exist but Y does Y is validated in place of X. Then, X
* and any of its dependents are invalidated.
* LTYPE_ALT*_DEPRECATED: Same behavior as LTYPE_ALTERNATIVE except a warning
* is posted. In other words, Y is an alternative to X
* but Y is deprecated.
*/
enum sv_dt_link_type {
LTYPE_FORBIDDEN = 0,
LTYPE_OPTIONAL,
LTYPE_REQUIRED,
LTYPE_DEPRECATED,
LTYPE_ALTERNATIVE,
LTYPE_ALTERNATIVE_DEPRECATED
};
struct sv_dt_link_attr {
u32 modes;
enum sv_dt_link_type type;
};
#define MAKE_ATTRS(_n) \
struct sv_dt_link_attr _n[TVCF_NVERSIONS]
struct sv_dt_node;
int sv_dt_make_link(struct sv_dt_node *node,
struct sv_dt_link_attr *attrs,
const char *source, const char *sink, ...);
int sv_dt_write_attr(
struct sv_dt_link_attr *attrs,
const enum sv_dt_link_version version,
const enum sv_dt_link_type type,
const u32 modes);
/**
* Make a link attribute by its full specification.
*
* @attrs: link attributes.
* @version: sensor driver version attribute applies towards.
* @status: status of attribute.
* @modes: mode types attribute applies towards.
*/
#define MAKE_LATTR_FULL(attrs, version, status, modes) \
sv_dt_write_attr(attrs, version, status, modes)
/**
* Make a link attribute that applies to any mode type.
*
* @attrs: link attributes
* @version: sensor driver version attribute applies towards.
* @status: status of attribute.
*/
#define MAKE_LATTR(attrs, version, status) \
MAKE_LATTR_FULL(attrs, version, status, MODE_TYPE_ANY)
/**
* Make a DT link by its full specification.
*
* @node: node link will directly (or indirectly) belong to.
* @attrs: link attributes.
* @source: name of source prop (or NULL if none)
* @sink: name of prop to be added
* @...: variable list of link attributes to make i.e. MAKE_LATTR*
*/
#define MAKE_LINK_FULL(node, attrs, source, sink, ...) \
do { \
memset(attrs, 0, sizeof(*attrs) * TVCF_NVERSIONS); \
if (sv_dt_make_link(node, attrs, source, sink, \
##__VA_ARGS__) != 0) \
goto make_link_fail; \
} while (0)
/**
* Make a DT link with type and mode(s) that applies to all sensor
* driver versions.
*
* @node: node link will directly (or indirectly) belong to.
* @attrs: link attributes.
* @source: name of source prop (or NULL if one)
* @sink: name of prop to be added
* @type: link type
* @modes: mode types attribute applies towards.
*/
#define MAKE_LINK_ALL_FULL(node, attrs, source, sink, type, modes) \
MAKE_LINK_FULL(node, attrs, source, sink, \
MAKE_LATTR_FULL(attrs, TVCF_VERSION_V1_0_0, type, modes), \
MAKE_LATTR_FULL(attrs, TVCF_VERSION_V2_0_0, type, modes) \
)
/**
* Make a DT link with type that applies to all sensor driver versions
* and all mode types.
*
* @node: node link will directly (or indirectly) belong to.
* @attrs: link attributes.
* @source: name of source prop (or NULL if one)
* @sink: name of prop to be added
* @type: link type
*/
#define MAKE_LINK_ALL(node, attrs, source, sink, type) \
MAKE_LINK_ALL_FULL(node, attrs, source, sink, type, MODE_TYPE_ANY)
#endif // __SENSOR_DT_TEST_H__

View File

@@ -0,0 +1,130 @@
// SPDX-License-Identifier: GPL-2.0
/*
* sensor_dt_test_nodes - sensor device tree test node definitions
*
* Copyright (c) 2018-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
*/
#include <linux/errno.h>
#include "sensor_dt_test.h"
#include "sensor_dt_test_nodes.h"
int sv_dt_make_root_node_props(struct sv_dt_node *node)
{
MAKE_ATTRS(attrs);
int err = 0;
if (node == NULL)
return -EINVAL;
MAKE_LINK_ALL(node, attrs, NULL, "compatible", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "reg", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "mclk", LTYPE_OPTIONAL);
MAKE_LINK_ALL(node, attrs, NULL, "*-gpio", LTYPE_OPTIONAL);
MAKE_LINK_ALL(node, attrs, NULL, "*-supply", LTYPE_OPTIONAL);
MAKE_LINK_ALL(node, attrs, NULL, "*-reg", LTYPE_OPTIONAL);
MAKE_LINK_ALL(node, attrs, NULL, "physical_w", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "physical_h", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "sensor_model", LTYPE_OPTIONAL);
MAKE_LINK_ALL(node, attrs, NULL,
"post_crop_frame_drop", LTYPE_OPTIONAL);
MAKE_LINK_ALL(node, attrs, NULL, "use_decibel_gain", LTYPE_OPTIONAL);
MAKE_LINK_ALL(node, attrs, NULL, "delayed_gain", LTYPE_OPTIONAL);
MAKE_LINK_ALL(node, attrs, NULL,
"user_sensor_mode_id", LTYPE_OPTIONAL);
make_link_fail:
return err;
}
int sv_dt_make_modeX_node_props(struct sv_dt_node *node)
{
MAKE_ATTRS(attrs);
int err = 0;
if (node == NULL)
return -EINVAL;
MAKE_LINK_ALL(node, attrs, NULL, "mclk_khz", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "num_lanes", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "tegra_sinterface", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "discontinuous_clk", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "cil_settletime", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "dpcm_enable", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "active_h", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "active_w", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "readout_orientation", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "pixel_t", LTYPE_DEPRECATED);
MAKE_LINK_ALL(node, attrs, "pixel_t", "mode_type", LTYPE_ALTERNATIVE);
MAKE_LINK_ALL(node, attrs, "pixel_t", "csi_pixel_bit_depth",
LTYPE_ALTERNATIVE);
MAKE_LINK_ALL(node, attrs, "pixel_t", "pixel_phase", LTYPE_ALTERNATIVE);
MAKE_LINK_ALL(node, attrs, NULL, "line_length", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "mclk_multiplier", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "pix_clk_hz", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "inherent_gain", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "min_hdr_ratio", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "max_hdr_ratio", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "embedded_metadata_height",
LTYPE_REQUIRED);
MAKE_LINK_ALL_FULL(node, attrs, "pixel_t", "dynamic_pixel_bit_depth",
LTYPE_ALTERNATIVE,
MODE_TYPE_WDR_PWL | MODE_TYPE_WDR_DOL);
MAKE_LINK_ALL_FULL(node, attrs, "pixel_t", "num_control_point",
LTYPE_ALTERNATIVE, MODE_TYPE_WDR_PWL);
MAKE_LINK_ALL_FULL(node, attrs, "pixel_t", "control_point_x_[0-9]*",
LTYPE_ALTERNATIVE, MODE_TYPE_WDR_PWL);
MAKE_LINK_ALL_FULL(node, attrs, "pixel_t", "control_point_y_[0-9]*",
LTYPE_ALTERNATIVE, MODE_TYPE_WDR_PWL);
MAKE_LINK_ALL_FULL(node, attrs, NULL, "num_of_exposure",
LTYPE_REQUIRED, MODE_TYPE_WDR_DOL);
MAKE_LINK_ALL_FULL(node, attrs, NULL, "num_of_ignored_lines",
LTYPE_REQUIRED, MODE_TYPE_WDR_DOL);
MAKE_LINK_ALL_FULL(node, attrs, NULL, "num_of_lines_offset_[0-9]*",
LTYPE_REQUIRED, MODE_TYPE_WDR_DOL);
MAKE_LINK_ALL_FULL(node, attrs, NULL, "num_of_ignored_pixels",
LTYPE_REQUIRED, MODE_TYPE_WDR_DOL);
MAKE_LINK_ALL_FULL(node, attrs, NULL, "num_of_left_margin_pixels",
LTYPE_REQUIRED, MODE_TYPE_WDR_DOL);
MAKE_LINK_ALL_FULL(node, attrs, NULL, "num_of_right_margin_pixels",
LTYPE_REQUIRED, MODE_TYPE_WDR_DOL);
MAKE_LINK_ALL(node, attrs, NULL, "min_gain_val", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "max_gain_val", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "min_exp_time", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "max_exp_time", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "min_framerate", LTYPE_REQUIRED);
MAKE_LINK_ALL(node, attrs, NULL, "max_framerate", LTYPE_REQUIRED);
MAKE_LINK_FULL(node, attrs, NULL, "gain_factor",
MAKE_LATTR(attrs, TVCF_VERSION_V2_0_0, LTYPE_REQUIRED));
MAKE_LINK_FULL(node, attrs, NULL, "default_gain",
MAKE_LATTR(attrs, TVCF_VERSION_V2_0_0, LTYPE_REQUIRED));
MAKE_LINK_FULL(node, attrs, NULL, "step_gain_val",
MAKE_LATTR(attrs, TVCF_VERSION_V2_0_0, LTYPE_REQUIRED));
MAKE_LINK_FULL(node, attrs, NULL, "exposure_factor",
MAKE_LATTR(attrs, TVCF_VERSION_V2_0_0, LTYPE_REQUIRED));
MAKE_LINK_FULL(node, attrs, NULL, "default_exp_time",
MAKE_LATTR(attrs, TVCF_VERSION_V2_0_0, LTYPE_REQUIRED));
MAKE_LINK_FULL(node, attrs, NULL, "step_exp_time",
MAKE_LATTR(attrs, TVCF_VERSION_V2_0_0, LTYPE_REQUIRED));
MAKE_LINK_FULL(node, attrs, NULL, "framerate_factor",
MAKE_LATTR(attrs, TVCF_VERSION_V2_0_0, LTYPE_REQUIRED));
MAKE_LINK_FULL(node, attrs, NULL, "default_framerate",
MAKE_LATTR(attrs, TVCF_VERSION_V2_0_0, LTYPE_REQUIRED));
MAKE_LINK_FULL(node, attrs, NULL, "step_framerate",
MAKE_LATTR(attrs, TVCF_VERSION_V2_0_0, LTYPE_REQUIRED));
make_link_fail:
return err;
}

View File

@@ -0,0 +1,17 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* sensor_dt_test_nodes - sensor device tree test node definitions
*
* Copyright (c) 2018-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
*/
#ifndef __SENSOR_DT_TEST_NODES_H__
#define __SENSOR_DT_TEST_NODES_H__
struct sv_dt_node;
int sv_dt_make_root_node_props(struct sv_dt_node *node);
int sv_dt_make_modeX_node_props(struct sv_dt_node *node);
#endif // __SENSOR_DT_TEST_NODES_H__

View File

@@ -0,0 +1,16 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* tegracam_tests - tegra camera kernel tests
*
* Copyright (c) 2018-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
*/
#ifndef __TEGRACAM_TESTS_H__
#define __TEGRACAM_TESTS_H__
/*
* Tegra Camera Kernel Tests
*/
int sensor_verify_dt(struct device_node *node, const u32 tvcf_version);
#endif // __TEGRACAM_TESTS_H__

View File

@@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-2.0
/*
* tegracam_log - tegra camera test logging utility
*
* Copyright (c) 2018-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
*/
#include <linux/errno.h>
#include <linux/mutex.h>
#include <linux/printk.h>
#include <linux/module.h>
#include <linux/stdarg.h>
#include "tegracam_log.h"
static DEFINE_MUTEX(global_log_mutex);
static int (*global_log)(const char *fmt, va_list args);
int camtest_log(const char *fmt, ...)
{
va_list args;
int ret;
if (fmt == NULL)
return -EINVAL;
va_start(args, fmt);
if (global_log == NULL)
ret = vprintk(fmt, args);
else
ret = global_log(printk_skip_level(fmt), args);
va_end(args);
return ret;
}
EXPORT_SYMBOL_GPL(camtest_log);
int camtest_try_acquire_global_log(int (*log)(const char *fmt, va_list args))
{
if (log == NULL)
return -EINVAL;
if (!mutex_trylock(&global_log_mutex))
return -EBUSY;
global_log = log;
return 0;
}
EXPORT_SYMBOL_GPL(camtest_try_acquire_global_log);
void camtest_release_global_log(void)
{
global_log = NULL;
mutex_unlock(&global_log_mutex);
}
EXPORT_SYMBOL_GPL(camtest_release_global_log);
MODULE_LICENSE("GPL");

View File

@@ -0,0 +1,46 @@
/* SPDX-License-Identifier: GPL-2.0 */
/*
* tegracam_log - tegra camera test logging utility
*
* Copyright (c) 2018-2023 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*
*/
#ifndef __TEGRACAM_LOG_H__
#define __TEGRACAM_LOG_H__
#include <linux/stdarg.h>
/**
* camtest_log - Log a message from a kernel camera test.
*
* By default messages are routed to printk (note kernel
* levels such as KERN_* are supported). However, output can
* be redirected by acquiring the global log handle. If some
* entity owns the global log handle output will be directed
* there instead.
*
* @fmt: format and args
*/
int camtest_log(const char *fmt, ...);
/**
* camtest_try_acquire_global_log - Try to set global camera test log
*
* Allows an entity to redirect all camtest_log() output to a custom
* implementation. Only one owner of the global log may exist at one time.
* If 0 is returned acquisition of the log handle was successful.
* Otherwise -EBUSY is returned. This is a NON-blocking call.
*
* A call to camtest_release_global_log() must be performed when output no
* longer needs to be redirected.
*
* @log: custom log handler
*/
int camtest_try_acquire_global_log(int (*log)(const char *fmt, va_list args));
/**
* camtest_release_global_log - Relinquish control of global camera test log
*/
void camtest_release_global_log(void);
#endif // __TEGRACAM_LOG_H__