Files
linux-nv-oot/drivers/platform/tegra/dce/dce-ipc.c
anupamg 9f31ca7c42 DCE-KMD: Update DCE OS abstraction - Part 5
Modules covered in this CL: dce-os-ivc

This is not a functional CL. It does the following:
1) Move os/include/linux-kmd/os-ivc.h to
   os/linux/include/dce-os-ivc.h

2) s/os_ivc/dce_os_ivc/g

3) Delete old intermediate header os/include/os-ivc.h and include
   <dce-os-ivc.h> directly.

JIRA TDS-16126

Change-Id: Ib6264a39910dbb4a107fd2261005c5e593b4b9b7
Signed-off-by: anupamg <anupamg@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-nv-oot/+/3228545
GVS: buildbot_gerritrpt <buildbot_gerritrpt@nvidia.com>
Reviewed-by: Arun Swain <arswain@nvidia.com>
Reviewed-by: Mahesh Kumar <mahkumar@nvidia.com>
2025-07-24 10:19:12 +00:00

761 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* SPDX-FileCopyrightText: Copyright (c) 2019-2024 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
*/
#include <dce.h>
#include <dce-ipc.h>
#include <dce-os-utils.h>
#include <interface/dce-interface.h>
#include <interface/dce-ipc-header.h>
#include <os-dce-events.h>
static struct dce_ipc_channel ivc_channels[DCE_IPC_CH_KMD_TYPE_MAX] = {
[DCE_IPC_CH_KMD_TYPE_ADMIN] = {
.flags = DCE_IPC_CHANNEL_VALID
| DCE_IPC_CHANNEL_MSG_HEADER,
.ch_type = DCE_IPC_CH_KMD_TYPE_ADMIN,
.ipc_type = DCE_IPC_TYPE_ADMIN,
.signal = {
.to_d = {
.type = DCE_IPC_SIGNAL_MAILBOX,
.sema_num = DCE_NUM_SEMA_REGS,
.sema_bit = 0U,
.form = {
.mbox = {
.mb_type = DCE_MAILBOX_ADMIN_INTERFACE,
.mb_num = DCE_MBOX_TO_DCE_ADMIN,
},
},
.signal = NULL,
.next = NULL,
},
.from_d = {
.type = DCE_IPC_SIGNAL_MAILBOX,
.sema_num = DCE_NUM_SEMA_REGS,
.sema_bit = 0U,
.form = {
.mbox = {
.mb_type = DCE_MAILBOX_ADMIN_INTERFACE,
.mb_num = DCE_MBOX_FROM_DCE_ADMIN,
},
},
.signal = NULL,
.next = NULL,
},
},
.q_info = {
.nframes = DCE_ADMIN_CMD_MAX_NFRAMES,
.frame_sz = DCE_ADMIN_CMD_MAX_FSIZE,
},
},
[DCE_IPC_CH_KMD_TYPE_RM] = {
.flags = DCE_IPC_CHANNEL_VALID
| DCE_IPC_CHANNEL_MSG_HEADER,
.ch_type = DCE_IPC_CH_KMD_TYPE_RM,
.ipc_type = DCE_IPC_TYPE_DISPRM,
.signal = {
.to_d = {
.type = DCE_IPC_SIGNAL_MAILBOX,
.sema_num = DCE_NUM_SEMA_REGS,
.sema_bit = 0U,
.form = {
.mbox = {
.mb_type = DCE_MAILBOX_DISPRM_INTERFACE,
.mb_num = DCE_MBOX_TO_DCE_RM,
},
},
.signal = NULL,
.next = NULL,
},
.from_d = {
.type = DCE_IPC_SIGNAL_MAILBOX,
.sema_num = DCE_NUM_SEMA_REGS,
.sema_bit = 0U,
.form = {
.mbox = {
.mb_type = DCE_MAILBOX_DISPRM_INTERFACE,
.mb_num = DCE_MBOX_FROM_DCE_RM,
},
},
.signal = NULL,
.next = NULL,
},
},
.q_info = {
.nframes = DCE_DISPRM_CMD_MAX_NFRAMES,
.frame_sz = DCE_DISPRM_CMD_MAX_FSIZE,
},
},
[DCE_IPC_CH_KMD_TYPE_RM_NOTIFY] = {
.flags = DCE_IPC_CHANNEL_VALID
| DCE_IPC_CHANNEL_MSG_HEADER,
.ch_type = DCE_IPC_CH_KMD_TYPE_RM_NOTIFY,
.ipc_type = DCE_IPC_TYPE_RM_NOTIFY,
.signal = {
.to_d = {
.type = DCE_IPC_SIGNAL_MAILBOX,
.sema_num = DCE_NUM_SEMA_REGS,
.sema_bit = 0U,
.form = {
.mbox = {
.mb_type = DCE_MAILBOX_DISPRM_NOTIFY_INTERFACE,
.mb_num = DCE_MBOX_FROM_DCE_RM_EVENT_NOTIFY,
},
},
.signal = NULL,
.next = NULL,
},
.from_d = {
.type = DCE_IPC_SIGNAL_MAILBOX,
.sema_num = DCE_NUM_SEMA_REGS,
.sema_bit = 0U,
.form = {
.mbox = {
.mb_type = DCE_MAILBOX_DISPRM_NOTIFY_INTERFACE,
.mb_num = DCE_MBOX_TO_DCE_RM_EVENT_NOTIFY,
},
},
.signal = NULL,
.next = NULL,
},
},
.q_info = {
.nframes = DCE_DISPRM_EVENT_NOTIFY_CMD_MAX_NFRAMES,
.frame_sz = DCE_DISPRM_EVENT_NOTIFY_CMD_MAX_FSIZE,
},
},
};
static int _dce_ipc_wait(struct tegra_dce *d, u32 w_type, u32 ch_type)
{
int ret = 0;
struct dce_ipc_channel *ch;
if (ch_type >= DCE_IPC_CH_KMD_TYPE_MAX) {
dce_os_err(d, "Invalid Channel Type : [%d]", ch_type);
return -EINVAL;
}
ch = d->d_ipc.ch[ch_type];
if (ch == NULL) {
dce_os_err(d, "Invalid Channel Data for type : [%d]", ch_type);
ret = -EINVAL;
goto out;
}
ch->w_type = w_type;
dce_os_mutex_unlock(&ch->lock);
if (ch_type == DCE_IPC_TYPE_ADMIN)
ret = dce_admin_ipc_wait(d);
else
ret = dce_client_ipc_wait(d, ch_type);
dce_os_mutex_lock(&ch->lock);
ch->w_type = DCE_IPC_WAIT_TYPE_INVALID;
out:
return ret;
}
u32 dce_ipc_get_cur_wait_type(struct tegra_dce *d, u32 ch_type)
{
uint32_t w_type;
struct dce_ipc_channel *ch;
if (ch_type >= DCE_IPC_CH_KMD_TYPE_MAX) {
dce_os_err(d, "Invalid Channel Type : [%d]", ch_type);
return -EINVAL;
}
ch = d->d_ipc.ch[ch_type];
if (ch == NULL) {
dce_os_err(d, "Invalid Channel Data for type : [%d]", ch_type);
return -EINVAL;
}
dce_os_mutex_lock(&ch->lock);
w_type = ch->w_type;
dce_os_mutex_unlock(&ch->lock);
return w_type;
}
/**
* dce_ipc_channel_init_unlocked - Initializes the underlying IPC channel to
* be used for all bi-directional messaging.
* @d : Pointer to struct tegra_dce.
* @type : Type of interface for which this channel is needed.
*
* Note: This function is not thread safe and should be called only once
* during initialization.
*
* Return : 0 if successful.
*/
int dce_ipc_channel_init_unlocked(struct tegra_dce *d, u32 ch_type)
{
u32 q_sz;
u32 msg_sz;
int ret = 0;
struct dce_ipc_region *r;
struct dce_ipc_channel *ch;
struct dce_ipc_queue_info *q_info;
if (ch_type >= DCE_IPC_CH_KMD_TYPE_MAX) {
dce_os_err(d, "Invalid ivc channel ch_type : [%d]", ch_type);
ret = -EINVAL;
goto out;
}
ch = &ivc_channels[ch_type];
if (!ch) {
dce_os_err(d, "Invalid ivc channel for this ch_type : [%d]",
ch_type);
ret = -ENOMEM;
goto out;
}
ret = dce_os_mutex_init(&ch->lock);
if (ret) {
dce_os_err(d, "dce lock initialization failed for mailbox");
goto out;
}
if ((ch->flags & DCE_IPC_CHANNEL_VALID) == 0U) {
dce_os_info(d, "Invalid Channel State [0x%x] for ch_type [%d]",
ch->flags, ch_type);
goto out_lock_destroy;
}
ch->d = d;
ret = dce_ipc_init_signaling(d, ch);
if (ret) {
dce_os_err(d, "Signaling init failed");
goto out_lock_destroy;
}
q_info = &ch->q_info;
msg_sz = dce_os_ivc_align(q_info->frame_sz);
q_sz = dce_os_ivc_total_queue_size(msg_sz * q_info->nframes);
r = &d->d_ipc.region;
if (!r->base) {
ret = -ENOMEM;
goto out_lock_destroy;
}
ret = dce_os_ivc_init(&ch->d_ivc,
(char *)r->base + r->s_offset, (char *)r->base + r->s_offset + q_sz,
r->iova + r->s_offset, r->iova + r->s_offset + q_sz,
q_info->nframes, msg_sz);
if (ret) {
dce_os_err(d, "IVC creation failed");
goto out_lock_destroy;
}
ch->flags |= DCE_IPC_CHANNEL_INITIALIZED;
q_info->rx_iova = r->iova + r->s_offset;
q_info->tx_iova = r->iova + r->s_offset + q_sz;
trace_ivc_channel_init_complete(d, ch);
d->d_ipc.ch[ch_type] = ch;
r->s_offset += (2 * q_sz);
out_lock_destroy:
if (ret)
dce_os_mutex_destroy(&ch->lock);
out:
return ret;
}
/**
* dce_ipc_channel_deinit_unlocked - Releases resources for a ivc channel
*
* @d : Pointer to tegra_dce struct.
* @id : Channel Id.
*
* Note: This function is not thread safe and should be called only once
* during de-initialization.
*/
void dce_ipc_channel_deinit_unlocked(struct tegra_dce *d, u32 ch_type)
{
struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type];
if (ch == NULL || (ch->flags & DCE_IPC_CHANNEL_INITIALIZED) == 0U) {
dce_os_info(d, "Invalid IVC Channel [%d]", ch_type);
return;
}
dce_os_mutex_lock(&ch->lock);
dce_ipc_deinit_signaling(d, ch);
ch->flags &= ~DCE_IPC_CHANNEL_INITIALIZED;
ch->flags &= ~DCE_IPC_CHANNEL_SYNCED;
d->d_ipc.ch[ch_type] = NULL;
dce_os_mutex_unlock(&ch->lock);
dce_os_mutex_destroy(&ch->lock);
}
struct tegra_dce *dce_ipc_get_dce_from_ch(u32 ch_type)
{
struct tegra_dce *d = NULL;
struct dce_ipc_channel *ch = NULL;
if (ch_type >= DCE_IPC_CH_KMD_TYPE_MAX)
goto out;
ch = &ivc_channels[ch_type];
dce_os_mutex_lock(&ch->lock);
d = ch->d;
dce_os_mutex_unlock(&ch->lock);
out:
return d;
}
/**
* dce_ipc_channel_ready - Checks if channel is ready to use
*
* @d : Pointer to tegra_dce struct.
* @id : Channel Id.
*
* Return : true if channel ready to use.
*/
bool dce_ipc_channel_is_ready(struct tegra_dce *d, u32 ch_type)
{
bool is_est;
struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type];
dce_os_mutex_lock(&ch->lock);
is_est = (dce_os_ivc_notified(&ch->d_ivc) ? false : true);
ch->signal.notify(d, &ch->signal.to_d);
dce_os_mutex_unlock(&ch->lock);
return is_est;
}
/**
* dce_ipc_channel_synced - Checks if channel is in synced state
*
* @d : Pointer to tegra_dce struct.
* @id : Channel Id.
*
* Return : true if channel is in synced state.
*/
bool dce_ipc_channel_is_synced(struct tegra_dce *d, u32 ch_type)
{
bool ret;
struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type];
dce_os_mutex_lock(&ch->lock);
ret = (ch->flags & DCE_IPC_CHANNEL_SYNCED) ? true : false;
dce_os_mutex_unlock(&ch->lock);
return ret;
}
/**
* dce_ipc_channel_reset - Resets the channel and completes
* the handshake with the remote.
*
* @d : Pointer to tegra_dce struct.
* @id : Channel Id.
*
* Return : void
*/
void dce_ipc_channel_reset(struct tegra_dce *d, u32 ch_type)
{
struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type];
dce_os_mutex_lock(&ch->lock);
dce_os_ivc_reset(&ch->d_ivc);
trace_ivc_channel_reset_triggered(d, ch);
ch->flags &= ~DCE_IPC_CHANNEL_SYNCED;
ch->signal.notify(d, &ch->signal.to_d);
dce_os_mutex_unlock(&ch->lock);
do {
if (dce_ipc_channel_is_ready(d, ch_type) == true)
break;
} while (true);
dce_os_mutex_lock(&ch->lock);
ch->flags |= DCE_IPC_CHANNEL_SYNCED;
trace_ivc_channel_reset_complete(d, ch);
dce_os_mutex_unlock(&ch->lock);
}
/**
* dce_ipc_get_next_write_buff - waits for the next write frame.
*
* @ch : Pointer to the pertinent channel.
*
* Return : 0 if successful
*/
static int _dce_ipc_get_next_write_buff(struct dce_ipc_channel *ch)
{
int err = 0;
if (ch != NULL)
err = dce_os_ivc_get_next_write_frame(&ch->d_ivc, &ch->obuff);
else
err = -EINVAL;
return err;
}
/**
* dce_ipc_write_channel - Writes to an ivc channel.
*
* @ch : Pointer to the pertinent channel.
* @data : Pointer to the data to be written.
* @size : Size of the data to be written.
*
* Return : 0 if successful.
*/
static int _dce_ipc_write_channel(struct dce_ipc_channel *ch,
const void *data, size_t size)
{
struct dce_ipc_header *hdr;
/**
* Add actual length information to the top
* of the IVC frame
*/
#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) /* Linux v6.2 */
if ((ch->flags & DCE_IPC_CHANNEL_MSG_HEADER) != 0U) {
iosys_map_wr_field(&ch->obuff, 0, struct dce_ipc_header, length,
size);
iosys_map_incr(&ch->obuff, sizeof(*hdr));
}
if (data && size > 0)
iosys_map_memcpy_to(&ch->obuff, 0, data, size);
#else
if ((ch->flags & DCE_IPC_CHANNEL_MSG_HEADER) != 0U) {
hdr = (struct dce_ipc_header *)ch->obuff;
hdr->length = (uint32_t)size;
ch->obuff = (void *)(hdr + 1U);
}
if (data && size > 0)
memcpy(ch->obuff, data, size);
#endif
return dce_os_ivc_write_advance(&ch->d_ivc);
}
/**
* dce_ipc_send_message - Sends messages over ipc.
*
* @d : Pointer to tegra_dce struct.
* @id : Channel Id.
* @data : Pointer to the data to be written.
* @size : Size of the data to be written.
*
* Return : 0 if successful.
*/
int dce_ipc_send_message(struct tegra_dce *d, u32 ch_type,
const void *data, size_t size)
{
int ret = 0;
struct dce_ipc_channel *ch
= d->d_ipc.ch[ch_type];
dce_os_mutex_lock(&ch->lock);
trace_ivc_send_req_received(d, ch);
ret = _dce_ipc_get_next_write_buff(ch);
if (ret) {
dce_os_err(ch->d, "Error getting next free buf to write");
goto out;
}
ret = _dce_ipc_write_channel(ch, data, size);
if (ret) {
dce_os_err(ch->d, "Error writing to channel");
goto out;
}
ch->signal.notify(d, &ch->signal.to_d);
trace_ivc_send_complete(d, ch);
out:
dce_os_mutex_unlock(&ch->lock);
return ret;
}
/**
* dce_ipc_get_next_read_buff - waits for the next write frame.
*
* @ch : Pointer to the pertinent channel.
*
* Return : 0 if successful
*/
static int _dce_ipc_get_next_read_buff(struct dce_ipc_channel *ch)
{
int err = 0;
if (ch != NULL)
err = dce_os_ivc_get_next_read_frame(&ch->d_ivc, &ch->ibuff);
else
err = -EINVAL;
return err;
}
/**
* dce_ipc_read_channel - Writes to an ivc channel.
*
* @ch : Pointer to the pertinent channel.
* @data : Pointer to the data to be read.
* @size : Size of the data to be read.
*
* Return : 0 if successful.
*/
static int _dce_ipc_read_channel(struct dce_ipc_channel *ch,
void *data, size_t size)
{
struct dce_ipc_header *hdr;
/**
* Get actual length information from the top
* of the IVC frame
*/
#if defined(NV_TEGRA_IVC_STRUCT_HAS_IOSYS_MAP) /* Linux v6.2 */
if ((ch->flags & DCE_IPC_CHANNEL_MSG_HEADER) != 0U) {
iosys_map_wr_field(&ch->ibuff, 0, struct dce_ipc_header, length,
size);
iosys_map_incr(&ch->ibuff, sizeof(*hdr));
}
if (data && size > 0)
iosys_map_memcpy_from(data, &ch->ibuff, 0, size);
#else
if ((ch->flags & DCE_IPC_CHANNEL_MSG_HEADER) != 0U) {
hdr = (struct dce_ipc_header *)ch->ibuff;
size = (size_t)(hdr->length);
ch->ibuff = (void *)(hdr + 1U);
}
if (data && size > 0)
memcpy(data, ch->ibuff, size);
#endif
return dce_os_ivc_read_advance(&ch->d_ivc);
}
/**
* dce_ipc_read_message - Reads messages over ipc.
*
* @d : Pointer to tegra_dce struct.
* @id : Channel Id.
* @data : Pointer to the data to be read.
* @size : Size of the data to be read.
*
* Return : 0 if successful.
*/
int dce_ipc_read_message(struct tegra_dce *d, u32 ch_type,
void *data, size_t size)
{
int ret = 0;
struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type];
dce_os_mutex_lock(&ch->lock);
trace_ivc_receive_req_received(d, ch);
ret = _dce_ipc_get_next_read_buff(ch);
if (ret) {
dce_os_debug(ch->d, "No Msg to read");
goto out;
}
ret = _dce_ipc_read_channel(ch, data, size);
if (ret) {
dce_os_err(ch->d, "Error reading from channel");
goto out;
}
trace_ivc_receive_req_complete(d, ch);
out:
dce_os_mutex_unlock(&ch->lock);
return ret;
}
/**
* dce_ipc_send_message_sync - Sends messages on a channel
* synchronously and waits for an ack.
*
* @d : Pointer to tegra_dce struct.
* @id : Channel Id.
* @msg : Pointer to the message to be sent/received.
*
* Return : 0 if successful
*/
int dce_ipc_send_message_sync(struct tegra_dce *d, u32 ch_type,
struct dce_ipc_message *msg)
{
int ret = 0;
struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type];
ret = dce_ipc_send_message(d, ch_type, msg->tx.data, msg->tx.size);
if (ret) {
dce_os_err(ch->d, "Error in sending message to DCE");
goto done;
}
dce_os_mutex_lock(&ch->lock);
ret = _dce_ipc_wait(ch->d, DCE_IPC_WAIT_TYPE_RPC, ch_type);
dce_os_mutex_unlock(&ch->lock);
if (ret) {
dce_os_err(ch->d, "Error in waiting for ack");
goto done;
}
trace_ivc_wait_complete(d, ch);
ret = dce_ipc_read_message(d, ch_type, msg->rx.data, msg->rx.size);
if (ret) {
dce_os_err(ch->d, "Error in reading DCE msg for ch_type [%d]",
ch_type);
goto done;
}
done:
return ret;
}
/**
* dce_ipc_get_channel_info - Provides information about frames details
*
* @d : Pointer to tegra_dce struct.
* @ch_index : Channel Index.
* @q_info : Pointer to struct dce_ipc_queue_info
*
* Return : 0 if successful
*/
int dce_ipc_get_channel_info(struct tegra_dce *d,
struct dce_ipc_queue_info *q_info, u32 ch_index)
{
struct dce_ipc_channel *ch = d->d_ipc.ch[ch_index];
if (ch == NULL)
return -ENOMEM;
dce_os_mutex_lock(&ch->lock);
memcpy(q_info, &ch->q_info, sizeof(ch->q_info));
dce_os_mutex_unlock(&ch->lock);
return 0;
}
/**
* dce_ipc_get_region_iova_info - Provides iova details for ipc region
*
* @d : Pointer to tegra_dce struct.
* @iova : Iova start address.
* @size : Iova size
*
* Return : 0 if successful
*/
int dce_ipc_get_region_iova_info(struct tegra_dce *d, u64 *iova, u32 *size)
{
struct dce_ipc_region *r = &d->d_ipc.region;
if (!r->base)
return -ENOMEM;
*iova = r->iova;
*size = r->size;
return 0;
}
/*
* dce_ipc_handle_notification - Handles the notification from remote
*
* @d : Pointer to tegra_dce struct
* @id : Channel Index
*
* Return : True if the worker thread needs to wake up
*/
bool dce_ipc_is_data_available(struct tegra_dce *d, u32 ch_type)
{
bool ret = false;
void *frame;
int err = 0;
struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type];
dce_os_mutex_lock(&ch->lock);
err = dce_os_ivc_get_next_read_frame(&ch->d_ivc, &frame);
if (err == 0)
ret = true;
dce_os_mutex_unlock(&ch->lock);
return ret;
}
/*
* dce_ipc_get_ipc_type - Returns the ipc_type for the channel.
*
* @d : Pointer to tegra_dce struct
* @id : Channel Index
*
* Return : True if the worker thread needs to wake up
*/
uint32_t dce_ipc_get_ipc_type(struct tegra_dce *d, u32 ch_type)
{
uint32_t ipc_type;
struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type];
dce_os_mutex_lock(&ch->lock);
ipc_type = ch->ipc_type;
dce_os_mutex_unlock(&ch->lock);
return ipc_type;
}