mirror of
git://nv-tegra.nvidia.com/linux-nv-oot.git
synced 2025-12-22 17:25:35 +03:00
Add detailed Doxygen documentation across all files in the RTCPU subsystem. This documentation effort follows SWUD Doxygen guidelines and provides complete API documentation for the entire RTCPU codebase. Use Cursor Agent to generate SWUD doxygen headers Jira CAMERASW-31120 Change-Id: I3095ff2e321402e72f0f8f5762bad798ea0ecf19 Signed-off-by: Mohit Ingale <mohiti@nvidia.com> Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3332472 Reviewed-by: Semi Malinen <smalinen@nvidia.com> Reviewed-by: Vincent Chung <vincentc@nvidia.com> GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com> Reviewed-by: Frank Chen <frankc@nvidia.com>
884 lines
30 KiB
C
884 lines
30 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
// SPDX-FileCopyrightText: Copyright (c) 2022-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
|
|
|
|
#include <nvidia/conftest.h>
|
|
|
|
#include <linux/tegra-capture-ivc.h>
|
|
|
|
#include <linux/completion.h>
|
|
#include <linux/module.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/of.h>
|
|
#include <linux/of_device.h>
|
|
#include <linux/pm_runtime.h>
|
|
#include <soc/tegra/ivc_ext.h>
|
|
#include <linux/tegra-ivc-bus.h>
|
|
#include <linux/nospec.h>
|
|
#include <linux/kthread.h>
|
|
#include <linux/sched.h>
|
|
#include <linux/version.h>
|
|
#include <asm/barrier.h>
|
|
|
|
#include <trace/events/tegra_capture.h>
|
|
|
|
#include "capture-ivc-priv.h"
|
|
|
|
/**
|
|
* @brief Transmit a message over IVC channel with mutex protection
|
|
*
|
|
* This function transmits a message over the specified IVC channel with mutex
|
|
* protection.
|
|
* - Validates that the channel is valid and ready
|
|
* - Acquires the write lock to ensure exclusive access to the channel using
|
|
* @ref mutex_lock_interruptible()
|
|
* - Waits for the channel to be available for writing using @ref wait_event_interruptible()
|
|
* - Calls @ref tegra_ivc_write() to write the message to the channel
|
|
* - Releases the write lock using @ref mutex_unlock()
|
|
*
|
|
* @param[in] civc Pointer to the capture IVC context
|
|
* Valid value: non-NULL
|
|
* @param[in] req Pointer to the message to be transmitted
|
|
* Valid value: non-NULL
|
|
* @param[in] len Length of the message in bytes
|
|
* Valid range: > 0
|
|
*
|
|
* @retval -EIO If channel is NULL or not ready
|
|
* @retval -ERESTARTSYS If signal received while waiting for the write queue
|
|
* @retval (int) Return code propagated from @ref mutex_lock_interruptible() or
|
|
* @ref tegra_ivc_write() or @ref wait_event_interruptible()
|
|
*/
|
|
static int tegra_capture_ivc_tx_(struct tegra_capture_ivc *civc,
|
|
const void *req, size_t len)
|
|
{
|
|
struct tegra_ivc_channel *chan;
|
|
int ret;
|
|
|
|
chan = civc->chan;
|
|
if (chan == NULL || WARN_ON(!chan->is_ready))
|
|
return -EIO;
|
|
|
|
ret = mutex_lock_interruptible(&civc->ivc_wr_lock);
|
|
if (unlikely(ret == -EINTR))
|
|
return -ERESTARTSYS;
|
|
if (unlikely(ret))
|
|
return ret;
|
|
|
|
ret = wait_event_interruptible(civc->write_q,
|
|
tegra_ivc_can_write(&chan->ivc));
|
|
if (likely(ret == 0))
|
|
ret = tegra_ivc_write(&chan->ivc, NULL, req, len);
|
|
|
|
mutex_unlock(&civc->ivc_wr_lock);
|
|
|
|
if (unlikely(ret < 0))
|
|
dev_err(&chan->dev, "tegra_ivc_write: error %d\n", ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Transmit a message over IVC channel with tracing
|
|
*
|
|
* This function wraps @ref tegra_capture_ivc_tx_() to provide tracing
|
|
* functionality for IVC transmissions.
|
|
* - Gets the channel name using @ref dev_name()
|
|
* - If len is less than the header length, sets the header to zero and
|
|
* copies the message header using @ref memcpy()
|
|
* - Otherwise, copies the header from the message using @ref memcpy()
|
|
* - Calls @ref tegra_capture_ivc_tx_() to transmit the message
|
|
* - Records the transmission via trace events for debugging and monitoring
|
|
* using @ref trace_capture_ivc_send() or @ref trace_capture_ivc_send_error()
|
|
* based on the return value of @ref tegra_capture_ivc_tx_()
|
|
* @param[in] civc Pointer to the capture IVC context
|
|
* Valid value: non-NULL
|
|
* @param[in] req Pointer to the message to be transmitted
|
|
* Valid value: non-NULL
|
|
* @param[in] len Length of the message in bytes
|
|
* Valid range: > 0
|
|
*
|
|
* @retval (int) Value returned by @ref tegra_capture_ivc_tx_()
|
|
*/
|
|
static int tegra_capture_ivc_tx(struct tegra_capture_ivc *civc,
|
|
const void *req, size_t len)
|
|
{
|
|
int ret;
|
|
struct tegra_capture_ivc_msg_header hdr;
|
|
size_t hdrlen = sizeof(hdr);
|
|
char const *ch_name = "NULL";
|
|
|
|
if (civc->chan)
|
|
ch_name = dev_name(&civc->chan->dev);
|
|
|
|
if (len < hdrlen) {
|
|
memset(&hdr, 0, hdrlen);
|
|
memcpy(&hdr, req, len);
|
|
} else {
|
|
memcpy(&hdr, req, hdrlen);
|
|
}
|
|
|
|
ret = tegra_capture_ivc_tx_(civc, req, len);
|
|
|
|
if (ret < 0)
|
|
trace_capture_ivc_send_error(ch_name, hdr.msg_id, hdr.channel_id, ret);
|
|
else
|
|
trace_capture_ivc_send(ch_name, hdr.msg_id, hdr.channel_id);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Submit a control message over the capture-control IVC channel
|
|
*
|
|
* This function submits a control message to the capture-control IVC channel.
|
|
* - Verifies that the capture-control IVC context is initialized
|
|
* - Calls @ref tegra_capture_ivc_tx() to transmit the control message
|
|
*
|
|
* @param[in] control_desc Pointer to the control message descriptor
|
|
* Valid value: non-NULL
|
|
* @param[in] len Length of the control message in bytes
|
|
* Valid range: > 0
|
|
*
|
|
* @retval (int) Value returned by @ref tegra_capture_ivc_tx()
|
|
* @retval -ENODEV If the control IVC context is not initialized
|
|
*/
|
|
int tegra_capture_ivc_control_submit(const void *control_desc, size_t len)
|
|
{
|
|
if (WARN_ON(__scivc_control == NULL))
|
|
return -ENODEV;
|
|
|
|
return tegra_capture_ivc_tx(__scivc_control, control_desc, len);
|
|
}
|
|
EXPORT_SYMBOL(tegra_capture_ivc_control_submit);
|
|
|
|
/**
|
|
* @brief Submit a capture message over the capture IVC channel
|
|
*
|
|
* This function submits a capture message to the capture IVC channel.
|
|
* - Verifies that the capture IVC context is initialized
|
|
* - Calls @ref tegra_capture_ivc_tx() to transmit the capture message
|
|
*
|
|
* @param[in] capture_desc Pointer to the capture message descriptor
|
|
* Valid value: non-NULL
|
|
* @param[in] len Length of the capture message in bytes
|
|
* Valid range: > 0
|
|
*
|
|
* @retval (int) Value returned by @ref tegra_capture_ivc_tx()
|
|
* @retval -ENODEV If the capture IVC context is not initialized
|
|
*/
|
|
int tegra_capture_ivc_capture_submit(const void *capture_desc, size_t len)
|
|
{
|
|
if (WARN_ON(__scivc_capture == NULL))
|
|
return -ENODEV;
|
|
|
|
return tegra_capture_ivc_tx(__scivc_capture, capture_desc, len);
|
|
}
|
|
EXPORT_SYMBOL(tegra_capture_ivc_capture_submit);
|
|
|
|
/**
|
|
* @brief Register a callback function for capture-control IVC responses
|
|
*
|
|
* This function registers a callback function that will be invoked when a
|
|
* response is received on the capture-control IVC channel.
|
|
* - Validates input parameters
|
|
* - Gets a runtime reference to the IVC channel using @ref tegra_ivc_channel_runtime_get()
|
|
* - Acquires the callback context list lock using @ref spin_lock()
|
|
* - Checks if the list is empty using @ref list_empty()
|
|
* - If the list is empty, releases the lock using @ref spin_unlock().
|
|
* - Otherwise, acquires an available callback context from the list using @ref list_first_entry()
|
|
* - Removes the callback context from the list using @ref list_del()
|
|
* - Releases the callback context list lock using @ref spin_unlock()
|
|
* - Acquires the callback context lock using @ref mutex_lock()
|
|
* - Checks if the callback function is already registered using @ref cb_ctx->cb_func
|
|
* - If the callback function is already registered, releases the callback context lock using
|
|
* @ref mutex_unlock()
|
|
* - Otherwise, associates the callback function with the context using @ref cb_ctx->cb_func
|
|
* - Releases the callback context lock using @ref mutex_unlock()
|
|
* - Adds back the channel using @ref tegra_ivc_channel_runtime_put()
|
|
*
|
|
* @param[in] control_resp_cb Callback function to be invoked for responses
|
|
* Valid value: non-NULL
|
|
* @param[out] trans_id Pointer to store the transaction ID
|
|
* Valid value: non-NULL
|
|
* @param[in] priv_context Private context to be passed to the callback
|
|
* Valid value: any value
|
|
*
|
|
* @retval 0 On successful registration
|
|
* @retval -EINVAL If input validation fails
|
|
* @retval -ENODEV If control IVC context is not initialized
|
|
* @retval -EAGAIN If no callback contexts are available
|
|
* @retval -EIO If an internal error occurs during registration
|
|
*/
|
|
int tegra_capture_ivc_register_control_cb(
|
|
tegra_capture_ivc_cb_func control_resp_cb,
|
|
uint32_t *trans_id, const void *priv_context)
|
|
{
|
|
struct tegra_capture_ivc *civc;
|
|
struct tegra_capture_ivc_cb_ctx *cb_ctx;
|
|
size_t ctx_id;
|
|
int ret;
|
|
|
|
/* Check if inputs are valid */
|
|
if (WARN(control_resp_cb == NULL, "callback function is NULL"))
|
|
return -EINVAL;
|
|
if (WARN(trans_id == NULL, "return value trans_id is NULL"))
|
|
return -EINVAL;
|
|
if (WARN_ON(!__scivc_control))
|
|
return -ENODEV;
|
|
|
|
civc = __scivc_control;
|
|
|
|
ret = tegra_ivc_channel_runtime_get(civc->chan);
|
|
if (unlikely(ret < 0))
|
|
return ret;
|
|
|
|
spin_lock(&civc->avl_ctx_list_lock);
|
|
if (unlikely(list_empty(&civc->avl_ctx_list))) {
|
|
spin_unlock(&civc->avl_ctx_list_lock);
|
|
ret = -EAGAIN;
|
|
goto fail;
|
|
}
|
|
|
|
|
|
cb_ctx = list_first_entry(&civc->avl_ctx_list,
|
|
struct tegra_capture_ivc_cb_ctx, node);
|
|
|
|
list_del(&cb_ctx->node);
|
|
spin_unlock(&civc->avl_ctx_list_lock);
|
|
|
|
ctx_id = cb_ctx - &civc->cb_ctx[0];
|
|
|
|
if (WARN(ctx_id < TRANS_ID_START_IDX ||
|
|
ctx_id >= ARRAY_SIZE(civc->cb_ctx),
|
|
"invalid cb_ctx %zu", ctx_id)) {
|
|
ret = -EIO;
|
|
goto fail;
|
|
}
|
|
|
|
mutex_lock(&civc->cb_ctx_lock);
|
|
|
|
if (WARN(cb_ctx->cb_func != NULL, "cb_ctx is busy")) {
|
|
ret = -EIO;
|
|
goto locked_fail;
|
|
}
|
|
|
|
*trans_id = (uint32_t)ctx_id;
|
|
cb_ctx->cb_func = control_resp_cb;
|
|
cb_ctx->priv_context = priv_context;
|
|
|
|
mutex_unlock(&civc->cb_ctx_lock);
|
|
|
|
return 0;
|
|
|
|
locked_fail:
|
|
mutex_unlock(&civc->cb_ctx_lock);
|
|
fail:
|
|
tegra_ivc_channel_runtime_put(civc->chan);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(tegra_capture_ivc_register_control_cb);
|
|
|
|
/**
|
|
* @brief Update callback context from transaction ID to channel ID
|
|
*
|
|
* This function updates the callback context from a temporary transaction ID to
|
|
* a permanent channel ID. This is typically called after the RTCPU has assigned
|
|
* a channel ID in response to a channel setup request.
|
|
* - Validates input parameters
|
|
* - Gets the channel ID and transaction ID using @ref array_index_nospec()
|
|
* - Gets the capture IVC context using @ref __scivc_control
|
|
* - Locks the callback context lock using @ref mutex_lock()
|
|
* - Validates the transaction context using @ref civc->cb_ctx[trans_id].cb_func
|
|
* - Validates the channel context using @ref civc->cb_ctx[chan_id].cb_func
|
|
* - Moves the callback function from the transaction context to the channel context
|
|
* - Adds the transaction context back to the available list
|
|
* - Releases the callback context lock using @ref mutex_unlock()
|
|
* - Locks the callback context list lock using @ref spin_lock()
|
|
* - Adds the transaction context back to the available list using @ref list_add_tail()
|
|
* - Releases the callback context list lock using @ref spin_unlock()
|
|
*
|
|
* @param[in] chan_id Channel ID assigned by RTCPU
|
|
* Valid range: [0, @ref NUM_CAPTURE_CHANNELS-1]
|
|
* @param[in] trans_id Transaction ID previously returned by
|
|
* @ref tegra_capture_ivc_register_control_cb()
|
|
* Valid range: [@ref TRANS_ID_START_IDX, @ref TOTAL_CHANNELS-1]
|
|
*
|
|
* @retval 0 On successful notification
|
|
* @retval -EINVAL If input validation fails
|
|
* @retval -ENODEV If control IVC context is not initialized
|
|
* @retval -EBADF If transaction context is idle
|
|
* @retval -EBUSY If channel context is busy
|
|
*/
|
|
int tegra_capture_ivc_notify_chan_id(uint32_t chan_id, uint32_t trans_id)
|
|
{
|
|
struct tegra_capture_ivc *civc;
|
|
|
|
if (WARN(chan_id >= NUM_CAPTURE_CHANNELS, "invalid chan_id"))
|
|
return -EINVAL;
|
|
if (WARN(trans_id < TRANS_ID_START_IDX ||
|
|
trans_id >= TOTAL_CHANNELS, "invalid trans_id"))
|
|
return -EINVAL;
|
|
if (WARN_ON(!__scivc_control))
|
|
return -ENODEV;
|
|
|
|
chan_id = array_index_nospec(chan_id, NUM_CAPTURE_CHANNELS);
|
|
trans_id = array_index_nospec(trans_id, TOTAL_CHANNELS);
|
|
|
|
civc = __scivc_control;
|
|
|
|
mutex_lock(&civc->cb_ctx_lock);
|
|
|
|
if (WARN(civc->cb_ctx[trans_id].cb_func == NULL,
|
|
"transaction context at %u is idle", trans_id)) {
|
|
mutex_unlock(&civc->cb_ctx_lock);
|
|
return -EBADF;
|
|
}
|
|
|
|
if (WARN(civc->cb_ctx[chan_id].cb_func != NULL,
|
|
"channel context at %u is busy", chan_id)) {
|
|
mutex_unlock(&civc->cb_ctx_lock);
|
|
return -EBUSY;
|
|
}
|
|
|
|
/* Update cb_ctx index */
|
|
civc->cb_ctx[chan_id].cb_func = civc->cb_ctx[trans_id].cb_func;
|
|
civc->cb_ctx[chan_id].priv_context =
|
|
civc->cb_ctx[trans_id].priv_context;
|
|
|
|
/* Reset trans_id cb_ctx fields */
|
|
civc->cb_ctx[trans_id].cb_func = NULL;
|
|
civc->cb_ctx[trans_id].priv_context = NULL;
|
|
|
|
mutex_unlock(&civc->cb_ctx_lock);
|
|
|
|
spin_lock(&civc->avl_ctx_list_lock);
|
|
list_add_tail(&civc->cb_ctx[trans_id].node, &civc->avl_ctx_list);
|
|
spin_unlock(&civc->avl_ctx_list_lock);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_capture_ivc_notify_chan_id);
|
|
|
|
/**
|
|
* @brief Register a callback function for capture IVC status indications
|
|
*
|
|
* This function registers a callback function that will be invoked when a
|
|
* status indication is received on the capture IVC channel for a specific
|
|
* channel ID.
|
|
* - Validates input parameters
|
|
* - Gets a runtime reference to the IVC channel using @ref tegra_ivc_channel_runtime_get()
|
|
* - Gets lock to the callback context lock using @ref mutex_lock()
|
|
* - Checks if the channel ID already has a registered callback using @ref cb_ctx->cb_func
|
|
* - If the channel ID already has a registered callback, releases the callback context lock using
|
|
* @ref mutex_unlock()
|
|
* - Otherwise, associates the callback function with the channel ID using @ref cb_ctx->cb_func
|
|
* - Releases the callback context lock using @ref mutex_unlock()
|
|
* - Releases the runtime reference to the IVC channel using @ref tegra_ivc_channel_runtime_put()
|
|
*
|
|
* @param[in] capture_status_ind_cb Callback function to be invoked for status indications
|
|
* Valid value: non-NULL
|
|
* @param[in] chan_id Channel ID for which to register the callback
|
|
* Valid range: [0, @ref NUM_CAPTURE_CHANNELS-1]
|
|
* @param[in] priv_context Private context to be passed to the callback
|
|
* Valid value: any value
|
|
*
|
|
* @retval 0 On successful registration
|
|
* @retval -EINVAL If input validation fails
|
|
* @retval -ENODEV If capture IVC context is not initialized
|
|
* @retval -EBUSY If channel ID already has a registered callback
|
|
*/
|
|
int tegra_capture_ivc_register_capture_cb(
|
|
tegra_capture_ivc_cb_func capture_status_ind_cb,
|
|
uint32_t chan_id, const void *priv_context)
|
|
{
|
|
struct tegra_capture_ivc *civc;
|
|
int ret;
|
|
|
|
if (WARN(capture_status_ind_cb == NULL, "callback function is NULL"))
|
|
return -EINVAL;
|
|
|
|
if (WARN(chan_id >= NUM_CAPTURE_CHANNELS,
|
|
"invalid channel id %u", chan_id))
|
|
return -EINVAL;
|
|
chan_id = array_index_nospec(chan_id, NUM_CAPTURE_CHANNELS);
|
|
|
|
if (!__scivc_capture)
|
|
return -ENODEV;
|
|
|
|
civc = __scivc_capture;
|
|
|
|
ret = tegra_ivc_channel_runtime_get(civc->chan);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
mutex_lock(&civc->cb_ctx_lock);
|
|
|
|
if (WARN(civc->cb_ctx[chan_id].cb_func != NULL,
|
|
"capture channel %u is busy", chan_id)) {
|
|
ret = -EBUSY;
|
|
goto fail;
|
|
}
|
|
|
|
civc->cb_ctx[chan_id].cb_func = capture_status_ind_cb;
|
|
civc->cb_ctx[chan_id].priv_context = priv_context;
|
|
mutex_unlock(&civc->cb_ctx_lock);
|
|
|
|
return 0;
|
|
fail:
|
|
mutex_unlock(&civc->cb_ctx_lock);
|
|
tegra_ivc_channel_runtime_put(civc->chan);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(tegra_capture_ivc_register_capture_cb);
|
|
|
|
/**
|
|
* @brief Unregister a control callback function for a specified ID
|
|
*
|
|
* This function unregisters a previously registered callback function from
|
|
* either a transaction ID or a channel ID.
|
|
* - Validates the input ID
|
|
* - Gets the capture IVC context using @ref __scivc_control
|
|
* - Gets the id using @ref array_index_nospec()
|
|
* - Locks the callback context lock using @ref mutex_lock()
|
|
* - Clears the callback function and context from the specified ID using @ref cb_ctx->cb_func
|
|
* - Acquires the callback context list lock using @ref spin_lock()
|
|
* - If the ID is a transaction ID, adds it back to the available list using @ref list_add_tail()
|
|
* - Releases the callback context list lock using @ref spin_unlock()
|
|
* - Releases the callback context lock using @ref mutex_unlock()
|
|
* - Releases the runtime reference to the IVC channel using @ref tegra_ivc_channel_runtime_put()
|
|
*
|
|
* @param[in] id Transaction ID or channel ID to unregister
|
|
* Valid range: [0, @ref TOTAL_CHANNELS-1]
|
|
*
|
|
* @retval 0 On successful unregistration
|
|
* @retval -EINVAL If ID validation fails
|
|
* @retval -ENODEV If control IVC context is not initialized
|
|
* @retval -EBADF If the specified ID has no registered callback
|
|
*/
|
|
int tegra_capture_ivc_unregister_control_cb(uint32_t id)
|
|
{
|
|
struct tegra_capture_ivc *civc;
|
|
|
|
/* id could be temporary trans_id or rtcpu-allocated chan_id */
|
|
if (WARN(id >= TOTAL_CHANNELS, "invalid id %u", id))
|
|
return -EINVAL;
|
|
if (WARN_ON(!__scivc_control))
|
|
return -ENODEV;
|
|
|
|
id = array_index_nospec(id, TOTAL_CHANNELS);
|
|
|
|
civc = __scivc_control;
|
|
|
|
mutex_lock(&civc->cb_ctx_lock);
|
|
|
|
if (WARN(civc->cb_ctx[id].cb_func == NULL,
|
|
"control channel %u is idle", id)) {
|
|
mutex_unlock(&civc->cb_ctx_lock);
|
|
return -EBADF;
|
|
}
|
|
|
|
civc->cb_ctx[id].cb_func = NULL;
|
|
civc->cb_ctx[id].priv_context = NULL;
|
|
|
|
mutex_unlock(&civc->cb_ctx_lock);
|
|
|
|
/*
|
|
* If it's trans_id, client encountered an error before or during
|
|
* chan_id update, in that case the corresponding cb_ctx
|
|
* needs to be added back in the avilable cb_ctx list.
|
|
*/
|
|
if (id >= TRANS_ID_START_IDX) {
|
|
spin_lock(&civc->avl_ctx_list_lock);
|
|
list_add_tail(&civc->cb_ctx[id].node, &civc->avl_ctx_list);
|
|
spin_unlock(&civc->avl_ctx_list_lock);
|
|
}
|
|
|
|
tegra_ivc_channel_runtime_put(civc->chan);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_capture_ivc_unregister_control_cb);
|
|
|
|
/**
|
|
* @brief Unregister a capture callback function for a specified channel ID
|
|
*
|
|
* This function unregisters a previously registered capture callback function.
|
|
* - Validates the channel ID
|
|
* - Gets the capture IVC context using @ref __scivc_capture
|
|
* - Locks the callback context lock using @ref mutex_lock()
|
|
* - Clears the callback function and context from the specified channel ID using
|
|
* @ref cb_ctx->cb_func
|
|
* - Releases the callback context lock using @ref mutex_unlock()
|
|
* - Releases the runtime reference to the IVC channel using @ref tegra_ivc_channel_runtime_put()
|
|
*
|
|
* @param[in] chan_id Channel ID to unregister
|
|
* Valid range: [0, @ref NUM_CAPTURE_CHANNELS-1]
|
|
*
|
|
* @retval 0 On successful unregistration
|
|
* @retval -EINVAL If channel ID validation fails
|
|
* @retval -ENODEV If capture IVC context is not initialized
|
|
* @retval -EBADF If the specified channel ID has no registered callback
|
|
*/
|
|
int tegra_capture_ivc_unregister_capture_cb(uint32_t chan_id)
|
|
{
|
|
struct tegra_capture_ivc *civc;
|
|
|
|
if (chan_id >= NUM_CAPTURE_CHANNELS)
|
|
return -EINVAL;
|
|
|
|
if (!__scivc_capture)
|
|
return -ENODEV;
|
|
|
|
chan_id = array_index_nospec(chan_id, NUM_CAPTURE_CHANNELS);
|
|
|
|
civc = __scivc_capture;
|
|
|
|
mutex_lock(&civc->cb_ctx_lock);
|
|
|
|
if (WARN(civc->cb_ctx[chan_id].cb_func == NULL,
|
|
"capture channel %u is idle", chan_id)) {
|
|
mutex_unlock(&civc->cb_ctx_lock);
|
|
return -EBADF;
|
|
}
|
|
|
|
civc->cb_ctx[chan_id].cb_func = NULL;
|
|
civc->cb_ctx[chan_id].priv_context = NULL;
|
|
|
|
mutex_unlock(&civc->cb_ctx_lock);
|
|
|
|
tegra_ivc_channel_runtime_put(civc->chan);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(tegra_capture_ivc_unregister_capture_cb);
|
|
|
|
/**
|
|
* @brief Process an IVC message by invoking the appropriate callback
|
|
*
|
|
* This inline function processes an incoming IVC message by invoking the registered
|
|
* callback function for the specified channel ID.
|
|
* - Checks if a callback function is registered for the channel ID using
|
|
* @ref civc->cb_ctx[id].cb_func
|
|
* - If registered, invokes the callback with the message and private context using
|
|
* @ref civc->cb_ctx[id].cb_func()
|
|
*
|
|
* @param[in] civc Pointer to the capture IVC context
|
|
* Valid value: non-NULL
|
|
* @param[in] id Channel ID associated with the message
|
|
* Valid range: [0, @ref TOTAL_CHANNELS-1]
|
|
* @param[in] msg Pointer to the received message
|
|
* Valid value: non-NULL
|
|
*/
|
|
static inline void tegra_capture_ivc_recv_msg(
|
|
struct tegra_capture_ivc *civc,
|
|
uint32_t id,
|
|
const void *msg)
|
|
{
|
|
struct device *dev = &civc->chan->dev;
|
|
|
|
/* Check if callback function available */
|
|
if (unlikely(!civc->cb_ctx[id].cb_func)) {
|
|
dev_dbg(dev, "No callback for id %u\n", id);
|
|
} else {
|
|
/* Invoke client callback. */
|
|
civc->cb_ctx[id].cb_func(msg, civc->cb_ctx[id].priv_context);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Process all pending received IVC messages
|
|
*
|
|
* This inline function processes all pending messages in the IVC receive queue.
|
|
* - Loops while messages are available for reading using @ref tegra_ivc_can_read()
|
|
* - Retrieves the next message from the queue using @ref tegra_ivc_read_get_next_frame()
|
|
* - Extracts the channel ID from the message header using @ref hdr->channel_id
|
|
* - Records the message reception via trace events using @ref trace_capture_ivc_recv()
|
|
* - Gets the id using @ref array_index_nospec()
|
|
* - Validates the channel ID and dispatches the message to the appropriate callback using
|
|
* @ref tegra_capture_ivc_recv_msg()
|
|
* - Advances the IVC read queue to the next message using @ref tegra_ivc_read_advance()
|
|
*
|
|
* @param[in] civc Pointer to the capture IVC context
|
|
* Valid value: non-NULL
|
|
*/
|
|
static inline void tegra_capture_ivc_recv(struct tegra_capture_ivc *civc)
|
|
{
|
|
struct tegra_ivc *ivc = &civc->chan->ivc;
|
|
struct device *dev = &civc->chan->dev;
|
|
const void *msg;
|
|
const struct tegra_capture_ivc_msg_header *hdr;
|
|
uint32_t id;
|
|
|
|
while (tegra_ivc_can_read(ivc)) {
|
|
#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) /* Linux 6.2 */
|
|
struct iosys_map map;
|
|
int err;
|
|
err = tegra_ivc_read_get_next_frame(ivc, &map);
|
|
if (err) {
|
|
dev_err(dev, "Failed to get next frame for read\n");
|
|
return;
|
|
}
|
|
msg = map.vaddr;
|
|
#else
|
|
msg = tegra_ivc_read_get_next_frame(ivc);
|
|
#endif
|
|
hdr = msg;
|
|
id = hdr->channel_id;
|
|
|
|
trace_capture_ivc_recv(dev_name(dev), hdr->msg_id, id);
|
|
|
|
/* Check if message is valid */
|
|
if (id < TOTAL_CHANNELS) {
|
|
id = array_index_nospec(id, TOTAL_CHANNELS);
|
|
tegra_capture_ivc_recv_msg(civc, id, msg);
|
|
} else {
|
|
dev_WARN(dev, "Invalid rtcpu channel id %u", id);
|
|
}
|
|
|
|
tegra_ivc_read_advance(ivc);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief Worker function to process IVC notifications
|
|
*
|
|
* This function is invoked when an IVC notification is received from the RTCPU.
|
|
* - Retrieves the capture IVC context from the work structure using @ref container_of()
|
|
* - Acquires a runtime PM reference to prevent suspended operation using
|
|
* @ref pm_runtime_get_if_in_use()
|
|
* - If channel is not ready, logs a warning using @ref WARN_ON()
|
|
* - Verifies that the channel is ready using @ref chan->is_ready
|
|
* - Calls @ref tegra_capture_ivc_recv() to process all pending messages
|
|
* - Releases the runtime PM reference using @ref pm_runtime_put()
|
|
*
|
|
* @param[in] work Pointer to the kthread work structure
|
|
* Valid value: non-NULL
|
|
*/
|
|
static void tegra_capture_ivc_worker(struct kthread_work *work)
|
|
{
|
|
struct tegra_capture_ivc *civc;
|
|
struct tegra_ivc_channel *chan;
|
|
|
|
civc = container_of(work, struct tegra_capture_ivc, work);
|
|
chan = civc->chan;
|
|
|
|
/*
|
|
* Do not process IVC events if worker gets woken up while
|
|
* this channel is suspended. There is a Christmas tree
|
|
* notify when RCE resumes and IVC bus gets set up.
|
|
*/
|
|
if (pm_runtime_get_if_in_use(&chan->dev) > 0) {
|
|
WARN_ON(!chan->is_ready);
|
|
|
|
tegra_capture_ivc_recv(civc);
|
|
|
|
pm_runtime_put(&chan->dev);
|
|
} else {
|
|
dev_dbg(&chan->dev, "extra wakeup");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @brief IVC notification callback from the IVC subsystem
|
|
*
|
|
* This function is called by the IVC subsystem when a notification is received
|
|
* from the remote processor (RTCPU).
|
|
* - Retrieves the capture IVC context from the channel driver data using
|
|
* @ref tegra_ivc_channel_get_drvdata()
|
|
* - Records the notification via trace events using @ref trace_capture_ivc_notify()
|
|
* - Wakes up any threads waiting to write to the IVC channel using @ref wake_up()
|
|
* - Queues the worker to process any received messages using @ref kthread_queue_work()
|
|
*
|
|
* @param[in] chan Pointer to the IVC channel that received the notification
|
|
* Valid value: non-NULL
|
|
*/
|
|
static void tegra_capture_ivc_notify(struct tegra_ivc_channel *chan)
|
|
{
|
|
struct tegra_capture_ivc *civc = tegra_ivc_channel_get_drvdata(chan);
|
|
|
|
trace_capture_ivc_notify(dev_name(&chan->dev));
|
|
|
|
/* Only 1 thread can wait on write_q, rest wait for write_lock */
|
|
wake_up(&civc->write_q);
|
|
kthread_queue_work(&civc->ivc_worker, &civc->work);
|
|
}
|
|
|
|
#define NV(x) "nvidia," #x
|
|
|
|
/**
|
|
* @brief Probe function for the tegra capture IVC driver
|
|
*
|
|
* This function is called when a matching IVC channel is found.
|
|
* - Allocates and initializes the capture IVC context using @ref devm_kzalloc()
|
|
* - Reads the service type from device tree using @ref of_property_read_string()
|
|
* - Stores the channel reference in the IVC context (civc->chan = chan)
|
|
* - Initializes synchronization primitives using @ref mutex_init()
|
|
* - Initializes the work structure for processing IVC notifications using @ref kthread_init_work()
|
|
* - Initializes the worker thread using @ref kthread_init_worker()
|
|
* - Creates a kernel worker thread using @ref kthread_create()
|
|
* - Sets the worker thread to FIFO scheduling using @ref sched_set_fifo_low()
|
|
* - Wakes up the worker thread using @ref wake_up_process()
|
|
* - Initializes the IVC write queue using @ref init_waitqueue_head()
|
|
* - Initializes the spinlock for available context list using @ref spin_lock_init()
|
|
* - Initializes the transaction context list using @ref INIT_LIST_HEAD()
|
|
* - Locks the callback context lock using @ref mutex_lock()
|
|
* - Adds the transaction contexts to the available list using @ref list_add_tail()
|
|
* - Unlocks the callback context lock using @ref mutex_unlock()
|
|
* - Associates the context with the IVC channel using @ref tegra_ivc_channel_set_drvdata()
|
|
* - Checks if the service type is "capture-control" using @ref strcmp()
|
|
* - Verifies that no control channel already exists using @ref WARN_ON()
|
|
* - Registers the context as a control service by setting @ref __scivc_control
|
|
* - If not control, checks if service type is "capture" using @ref strcmp()
|
|
* - Verifies that no capture channel already exists using @ref WARN_ON()
|
|
* - Registers the context as a capture service by setting @ref __scivc_capture
|
|
* - Returns error if service type is neither control nor capture
|
|
* - Stops the worker thread using @ref kthread_stop() if an error occurs
|
|
*
|
|
* @param[in] chan Pointer to the IVC channel to be probed
|
|
* Valid value: non-NULL
|
|
*
|
|
* @retval 0 On successful probe
|
|
* @retval -ENOMEM If memory allocation fails
|
|
* @retval -EEXIST If a channel for the same service already exists
|
|
* @retval -EINVAL If the service type is invalid or missing
|
|
* @retval (int) Value returned by @ref of_property_read_string() or @ref kthread_create()
|
|
*/
|
|
static int tegra_capture_ivc_probe(struct tegra_ivc_channel *chan)
|
|
{
|
|
struct device *dev = &chan->dev;
|
|
struct tegra_capture_ivc *civc;
|
|
const char *service;
|
|
int ret;
|
|
uint32_t i;
|
|
|
|
civc = devm_kzalloc(dev, (sizeof(*civc)), GFP_KERNEL);
|
|
if (unlikely(civc == NULL))
|
|
return -ENOMEM;
|
|
|
|
ret = of_property_read_string(dev->of_node, NV(service),
|
|
&service);
|
|
if (unlikely(ret)) {
|
|
dev_err(dev, "missing <%s> property\n", NV(service));
|
|
return ret;
|
|
}
|
|
|
|
civc->chan = chan;
|
|
|
|
mutex_init(&civc->cb_ctx_lock);
|
|
mutex_init(&civc->ivc_wr_lock);
|
|
|
|
/* Initialize kworker */
|
|
kthread_init_work(&civc->work, tegra_capture_ivc_worker);
|
|
|
|
kthread_init_worker(&civc->ivc_worker);
|
|
|
|
civc->ivc_kthread = kthread_create(&kthread_worker_fn,
|
|
&civc->ivc_worker, service);
|
|
if (IS_ERR(civc->ivc_kthread)) {
|
|
dev_err(dev, "Cannot allocate ivc worker thread\n");
|
|
ret = PTR_ERR(civc->ivc_kthread);
|
|
goto err;
|
|
}
|
|
sched_set_fifo_low(civc->ivc_kthread);
|
|
wake_up_process(civc->ivc_kthread);
|
|
|
|
/* Initialize wait queue */
|
|
init_waitqueue_head(&civc->write_q);
|
|
|
|
/* transaction-id list of available callback contexts */
|
|
spin_lock_init(&civc->avl_ctx_list_lock);
|
|
INIT_LIST_HEAD(&civc->avl_ctx_list);
|
|
|
|
/* Add the transaction cb-contexts to the available list */
|
|
mutex_lock(&civc->cb_ctx_lock);
|
|
for (i = TRANS_ID_START_IDX; i < ARRAY_SIZE(civc->cb_ctx); i++)
|
|
list_add_tail(&civc->cb_ctx[i].node, &civc->avl_ctx_list);
|
|
mutex_unlock(&civc->cb_ctx_lock);
|
|
|
|
tegra_ivc_channel_set_drvdata(chan, civc);
|
|
|
|
if (!strcmp("capture-control", service)) {
|
|
if (WARN_ON(__scivc_control != NULL)) {
|
|
ret = -EEXIST;
|
|
goto err_service;
|
|
}
|
|
__scivc_control = civc;
|
|
} else if (!strcmp("capture", service)) {
|
|
if (WARN_ON(__scivc_capture != NULL)) {
|
|
ret = -EEXIST;
|
|
goto err_service;
|
|
}
|
|
__scivc_capture = civc;
|
|
} else {
|
|
dev_err(dev, "Unknown ivc channel %s\n", service);
|
|
ret = -EINVAL;
|
|
goto err_service;
|
|
}
|
|
|
|
return 0;
|
|
|
|
err_service:
|
|
kthread_stop(civc->ivc_kthread);
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* @brief Remove function for the tegra capture IVC driver
|
|
*
|
|
* This function is called when an IVC channel is being removed.
|
|
* - Gets the capture IVC context from the channel driver data using
|
|
* @ref tegra_ivc_channel_get_drvdata()
|
|
* - Ensures any pending work on the worker thread is complete using
|
|
* @ref kthread_flush_worker()
|
|
* - Stops the worker thread using @ref kthread_stop()
|
|
* - Unregisters the IVC context from the global context using
|
|
* @ref __scivc_control or @ref __scivc_capture
|
|
*
|
|
* @param[in] chan Pointer to the IVC channel to be removed
|
|
* Valid value: non-NULL
|
|
*/
|
|
static void tegra_capture_ivc_remove(struct tegra_ivc_channel *chan)
|
|
{
|
|
struct tegra_capture_ivc *civc = tegra_ivc_channel_get_drvdata(chan);
|
|
|
|
kthread_flush_worker(&civc->ivc_worker);
|
|
kthread_stop(civc->ivc_kthread);
|
|
|
|
if (__scivc_control == civc)
|
|
__scivc_control = NULL;
|
|
else if (__scivc_capture == civc)
|
|
__scivc_capture = NULL;
|
|
else
|
|
dev_warn(&chan->dev, "Unknown ivc channel\n");
|
|
}
|
|
|
|
static struct of_device_id tegra_capture_ivc_channel_of_match[] = {
|
|
{ .compatible = "nvidia,tegra186-camera-ivc-protocol-capture-control" },
|
|
{ .compatible = "nvidia,tegra186-camera-ivc-protocol-capture" },
|
|
{ },
|
|
};
|
|
MODULE_DEVICE_TABLE(of, tegra_capture_ivc_channel_of_match);
|
|
|
|
static const struct tegra_ivc_channel_ops tegra_capture_ivc_ops = {
|
|
.probe = tegra_capture_ivc_probe,
|
|
.remove = tegra_capture_ivc_remove,
|
|
.notify = tegra_capture_ivc_notify,
|
|
};
|
|
|
|
static struct tegra_ivc_driver tegra_capture_ivc_driver = {
|
|
.driver = {
|
|
.name = "tegra-capture-ivc",
|
|
.bus = &tegra_ivc_bus_type,
|
|
.owner = THIS_MODULE,
|
|
.of_match_table = tegra_capture_ivc_channel_of_match,
|
|
},
|
|
.dev_type = &tegra_ivc_channel_type,
|
|
.ops.channel = &tegra_capture_ivc_ops,
|
|
};
|
|
|
|
tegra_ivc_subsys_driver_default(tegra_capture_ivc_driver);
|
|
MODULE_AUTHOR("Sudhir Vyas <svyas@nvidia.com>");
|
|
MODULE_DESCRIPTION("NVIDIA Tegra Capture IVC driver");
|
|
MODULE_LICENSE("GPL v2");
|