diff --git a/drivers/media/platform/tegra/camera/Makefile b/drivers/media/platform/tegra/camera/Makefile index 0251d084..f0b4c1e2 100644 --- a/drivers/media/platform/tegra/camera/Makefile +++ b/drivers/media/platform/tegra/camera/Makefile @@ -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/ diff --git a/drivers/media/platform/tegra/camera/tests/Makefile b/drivers/media/platform/tegra/camera/tests/Makefile new file mode 100644 index 00000000..aa3649a4 --- /dev/null +++ b/drivers/media/platform/tegra/camera/tests/Makefile @@ -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 diff --git a/drivers/media/platform/tegra/camera/tests/modules/sensor_kernel_tests_core.c b/drivers/media/platform/tegra/camera/tests/modules/sensor_kernel_tests_core.c new file mode 100644 index 00000000..2db1d814 --- /dev/null +++ b/drivers/media/platform/tegra/camera/tests/modules/sensor_kernel_tests_core.c @@ -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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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"); diff --git a/drivers/media/platform/tegra/camera/tests/modules/sensor_kernel_tests_core.h b/drivers/media/platform/tegra/camera/tests/modules/sensor_kernel_tests_core.h new file mode 100644 index 00000000..a891c294 --- /dev/null +++ b/drivers/media/platform/tegra/camera/tests/modules/sensor_kernel_tests_core.h @@ -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 + +#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__ diff --git a/drivers/media/platform/tegra/camera/tests/modules/sensor_kernel_tests_runner.c b/drivers/media/platform/tegra/camera/tests/modules/sensor_kernel_tests_runner.c new file mode 100644 index 00000000..ef2117ad --- /dev/null +++ b/drivers/media/platform/tegra/camera/tests/modules/sensor_kernel_tests_runner.c @@ -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 +#include +#include +#include +#include +#include + +#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; +} diff --git a/drivers/media/platform/tegra/camera/tests/modules/sensor_kernel_tests_runner.h b/drivers/media/platform/tegra/camera/tests/modules/sensor_kernel_tests_runner.h new file mode 100644 index 00000000..8e088906 --- /dev/null +++ b/drivers/media/platform/tegra/camera/tests/modules/sensor_kernel_tests_runner.h @@ -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__ diff --git a/drivers/media/platform/tegra/camera/tests/sensor_dt_test.c b/drivers/media/platform/tegra/camera/tests/sensor_dt_test.c new file mode 100644 index 00000000..fff82a59 --- /dev/null +++ b/drivers/media/platform/tegra/camera/tests/sensor_dt_test.c @@ -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 +#include +#include +#include +#include +#include + +#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); diff --git a/drivers/media/platform/tegra/camera/tests/sensor_dt_test.h b/drivers/media/platform/tegra/camera/tests/sensor_dt_test.h new file mode 100644 index 00000000..dbb8a660 --- /dev/null +++ b/drivers/media/platform/tegra/camera/tests/sensor_dt_test.h @@ -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 +#include + +/** + * 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__ diff --git a/drivers/media/platform/tegra/camera/tests/sensor_dt_test_nodes.c b/drivers/media/platform/tegra/camera/tests/sensor_dt_test_nodes.c new file mode 100644 index 00000000..bc6b5b13 --- /dev/null +++ b/drivers/media/platform/tegra/camera/tests/sensor_dt_test_nodes.c @@ -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 + +#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; +} diff --git a/drivers/media/platform/tegra/camera/tests/sensor_dt_test_nodes.h b/drivers/media/platform/tegra/camera/tests/sensor_dt_test_nodes.h new file mode 100644 index 00000000..0ca99223 --- /dev/null +++ b/drivers/media/platform/tegra/camera/tests/sensor_dt_test_nodes.h @@ -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__ diff --git a/drivers/media/platform/tegra/camera/tests/tegracam_tests.h b/drivers/media/platform/tegra/camera/tests/tegracam_tests.h new file mode 100644 index 00000000..93a8c26d --- /dev/null +++ b/drivers/media/platform/tegra/camera/tests/tegracam_tests.h @@ -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__ diff --git a/drivers/media/platform/tegra/camera/tests/utils/tegracam_log.c b/drivers/media/platform/tegra/camera/tests/utils/tegracam_log.c new file mode 100644 index 00000000..0c2ff5be --- /dev/null +++ b/drivers/media/platform/tegra/camera/tests/utils/tegracam_log.c @@ -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 +#include +#include +#include +#include + +#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"); diff --git a/drivers/media/platform/tegra/camera/tests/utils/tegracam_log.h b/drivers/media/platform/tegra/camera/tests/utils/tegracam_log.h new file mode 100644 index 00000000..a2ffd511 --- /dev/null +++ b/drivers/media/platform/tegra/camera/tests/utils/tegracam_log.h @@ -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 + +/** + * 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__