android_kernel_samsung_msm8976/drivers/soc/qcom/glink.c

4797 lines
139 KiB
C

/* Copyright (c) 2014-2015, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
* only version 2 as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
#include <linux/err.h>
#include <linux/ipc_logging.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/sched.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/workqueue.h>
#include <linux/rwsem.h>
#include <linux/pm_qos.h>
#include <soc/qcom/glink.h>
#include <soc/qcom/tracer_pkt.h>
#include "glink_core_if.h"
#include "glink_private.h"
#include "glink_xprt_if.h"
/* Number of internal IPC Logging log pages */
#define NUM_LOG_PAGES 15
#define GLINK_PM_QOS_HOLDOFF_MS 10
/**
* struct glink_core_xprt_ctx - transport representation structure
* @xprt_state_lhb0: controls read/write access to transport state
* @list_node: used to chain this transport in a global
* transport list
* @name: name of this transport
* @edge: what this transport connects to
* @id: the id to use for channel migration
* @versions: array of transport versions this implementation
* supports
* @versions_entries: number of entries in @versions
* @local_version_idx: local version index into @versions this
* transport is currently running
* @remote_version_idx: remote version index into @versions this
* transport is currently running
* @l_features: Features negotiated by the local side
* @capabilities: Capabilities of underlying transport
* @ops: transport defined implementation of common
* operations
* @local_state: value from local_channel_state_e representing
* the local state of this transport
* @remote_neg_completed: is the version negotiation with the remote end
* completed
* @xprt_ctx_lock_lhb1 lock to protect @next_lcid and @channels
* @next_lcid: logical channel identifier to assign to the next
* created channel
* @max_cid: maximum number of channel identifiers supported
* @max_iid: maximum number of intent identifiers supported
* @tx_work: work item to process @tx_ready
* @tx_wq: workqueue to run @tx_work
* @channels: list of all existing channels on this transport
* @tx_ready_mutex_lhb2: lock to protect @tx_ready
* @tx_ready: list of all channels ready to transmit
* @pm_qos_req: power management QoS request for TX path
* @qos_req_active: a vote is active with the PM QoS system
* @tx_path_activity: transmit activity has occurred
* @pm_qos_work: removes PM QoS vote due to inactivity
* @xprt_dbgfs_lock_lhb3: debugfs channel structure lock
*/
struct glink_core_xprt_ctx {
struct rwref_lock xprt_state_lhb0;
struct list_head list_node;
char name[GLINK_NAME_SIZE];
char edge[GLINK_NAME_SIZE];
uint16_t id;
const struct glink_core_version *versions;
size_t versions_entries;
uint32_t local_version_idx;
uint32_t remote_version_idx;
uint32_t l_features;
uint32_t capabilities;
struct glink_transport_if *ops;
enum transport_state_e local_state;
bool remote_neg_completed;
spinlock_t xprt_ctx_lock_lhb1;
struct list_head channels;
uint32_t next_lcid;
struct list_head free_lcid_list;
uint32_t max_cid;
uint32_t max_iid;
struct work_struct tx_work;
struct workqueue_struct *tx_wq;
struct mutex tx_ready_mutex_lhb2;
struct list_head tx_ready;
struct pm_qos_request pm_qos_req;
bool qos_req_active;
bool tx_path_activity;
struct delayed_work pm_qos_work;
struct mutex xprt_dbgfs_lock_lhb3;
};
/**
* Channel Context
* @xprt_state_lhb0: controls read/write access to channel state
* @port_list_node: channel list node used by transport "channels" list
* @tx_ready_list_node: channels that have data ready to transmit
* @name: name of the channel
*
* @user_priv: user opaque data type passed into glink_open()
* @notify_rx: RX notification function
* @notify_tx_done: TX-done notification function (remote side is done)
* @notify_state: Channel state (connected / disconnected) notifications
* @notify_rx_intent_req: Request from remote side for an intent
* @notify_rxv: RX notification function (for io buffer chain)
* @notify_rx_sigs: RX signal change notification
* @notify_rx_abort: Channel close RX Intent aborted
* @notify_tx_abort: Channel close TX aborted
* @notify_rx_tracer_pkt: Receive notification for tracer packet
* @notify_remote_rx_intent: Receive notification for remote-queued RX intent
*
* @transport_ptr: Transport this channel uses
* @lcid: Local channel ID
* @rcid: Remote channel ID
* @local_open_state: Local channel state
* @remote_opened: Remote channel state (opened or closed)
* @int_req_ack: Remote side intent request ACK state
* @int_req_ack_complete: Intent tracking completion - received remote ACK
* @int_req_complete: Intent tracking completion - received intent
*
* @local_rx_intent_lst_lock_lhc1: RX intent list lock
* @local_rx_intent_list: Active RX Intents queued by client
* @local_rx_intent_ntfy_list: Client notified, waiting for rx_done()
* @local_rx_intent_free_list: Available intent container structure
*
* @rmt_rx_intent_lst_lock_lhc2: Remote RX intent list lock
* @rmt_rx_intent_list: Remote RX intent list
*
* @max_used_liid: Maximum Local Intent ID used
* @dummy_riid: Dummy remote intent ID
*
* @tx_lists_mutex_lhc3: TX list lock
* @tx_active: Ready to transmit
*
* @tx_pending_rmt_done_lock_lhc4: Remote-done list lock
* @tx_pending_remote_done: Transmitted, waiting for remote done
* @lsigs: Local signals
* @rsigs: Remote signals
* @pending_delete: waiting for channel to be deleted
* @no_migrate: The local client does not want to
* migrate transports
* @local_xprt_req: The transport the local side requested
* @local_xprt_resp: The response to @local_xprt_req
* @remote_xprt_req: The transport the remote side requested
* @remote_xprt_resp: The response to @remote_xprt_req
*/
struct channel_ctx {
struct rwref_lock ch_state_lhc0;
struct list_head port_list_node;
struct list_head tx_ready_list_node;
char name[GLINK_NAME_SIZE];
/* user info */
void *user_priv;
void (*notify_rx)(void *handle, const void *priv, const void *pkt_priv,
const void *ptr, size_t size);
void (*notify_tx_done)(void *handle, const void *priv,
const void *pkt_priv, const void *ptr);
void (*notify_state)(void *handle, const void *priv, unsigned event);
bool (*notify_rx_intent_req)(void *handle, const void *priv,
size_t req_size);
void (*notify_rxv)(void *handle, const void *priv, const void *pkt_priv,
void *iovec, size_t size,
void * (*vbuf_provider)(void *iovec, size_t offset,
size_t *size),
void * (*pbuf_provider)(void *iovec, size_t offset,
size_t *size));
void (*notify_rx_sigs)(void *handle, const void *priv,
uint32_t old_sigs, uint32_t new_sigs);
void (*notify_rx_abort)(void *handle, const void *priv,
const void *pkt_priv);
void (*notify_tx_abort)(void *handle, const void *priv,
const void *pkt_priv);
void (*notify_rx_tracer_pkt)(void *handle, const void *priv,
const void *pkt_priv, const void *ptr, size_t size);
void (*notify_remote_rx_intent)(void *handle, const void *priv,
size_t size);
/* internal port state */
struct glink_core_xprt_ctx *transport_ptr;
uint32_t lcid;
uint32_t rcid;
enum local_channel_state_e local_open_state;
bool remote_opened;
bool int_req_ack;
struct completion int_req_ack_complete;
struct completion int_req_complete;
spinlock_t local_rx_intent_lst_lock_lhc1;
struct list_head local_rx_intent_list;
struct list_head local_rx_intent_ntfy_list;
struct list_head local_rx_intent_free_list;
spinlock_t rmt_rx_intent_lst_lock_lhc2;
struct list_head rmt_rx_intent_list;
uint32_t max_used_liid;
uint32_t dummy_riid;
struct mutex tx_lists_mutex_lhc3;
struct list_head tx_active;
spinlock_t tx_pending_rmt_done_lock_lhc4;
struct list_head tx_pending_remote_done;
uint32_t lsigs;
uint32_t rsigs;
bool pending_delete;
bool no_migrate;
uint16_t local_xprt_req;
uint16_t local_xprt_resp;
uint16_t remote_xprt_req;
uint16_t remote_xprt_resp;
};
static struct glink_core_if core_impl;
static void *log_ctx;
static unsigned glink_debug_mask = QCOM_GLINK_INFO;
module_param_named(debug_mask, glink_debug_mask,
uint, S_IRUGO | S_IWUSR | S_IWGRP);
static unsigned glink_pm_qos;
module_param_named(pm_qos_enable, glink_pm_qos,
uint, S_IRUGO | S_IWUSR | S_IWGRP);
static LIST_HEAD(transport_list);
/*
* Used while notifying the clients about link state events. Since the clients
* need to store the callback information temporarily and since all the
* existing accesses to transport list are in non-IRQ context, defining the
* transport_list_lock as a mutex.
*/
static DEFINE_MUTEX(transport_list_lock_lha0);
struct link_state_notifier_info {
struct list_head list;
char transport[GLINK_NAME_SIZE];
char edge[GLINK_NAME_SIZE];
void (*glink_link_state_notif_cb)(
struct glink_link_state_cb_info *cb_info, void *priv);
void *priv;
};
static LIST_HEAD(link_state_notifier_list);
static DEFINE_MUTEX(link_state_notifier_lock_lha1);
static struct glink_core_xprt_ctx *find_open_transport(const char *edge,
const char *name,
bool initial_xprt,
uint16_t *best_id);
static bool xprt_is_fully_opened(struct glink_core_xprt_ctx *xprt);
static struct channel_ctx *xprt_lcid_to_ch_ctx_get(
struct glink_core_xprt_ctx *xprt_ctx,
uint32_t lcid);
static struct channel_ctx *xprt_rcid_to_ch_ctx_get(
struct glink_core_xprt_ctx *xprt_ctx,
uint32_t rcid);
static void xprt_schedule_tx(struct glink_core_xprt_ctx *xprt_ptr,
struct channel_ctx *ch_ptr,
struct glink_core_tx_pkt *tx_info);
static int xprt_single_threaded_tx(struct glink_core_xprt_ctx *xprt_ptr,
struct channel_ctx *ch_ptr,
struct glink_core_tx_pkt *tx_info);
static void tx_work_func(struct work_struct *work);
static struct channel_ctx *ch_name_to_ch_ctx_create(
struct glink_core_xprt_ctx *xprt_ctx,
const char *name);
static void ch_push_remote_rx_intent(struct channel_ctx *ctx, size_t size,
uint32_t riid);
static int ch_pop_remote_rx_intent(struct channel_ctx *ctx, size_t size,
uint32_t *riid_ptr, size_t *intent_size);
static struct glink_core_rx_intent *ch_push_local_rx_intent(
struct channel_ctx *ctx, const void *pkt_priv, size_t size);
static void ch_remove_local_rx_intent(struct channel_ctx *ctx, uint32_t liid);
static struct glink_core_rx_intent *ch_get_local_rx_intent(
struct channel_ctx *ctx, uint32_t liid);
static void ch_set_local_rx_intent_notified(struct channel_ctx *ctx,
struct glink_core_rx_intent *intent_ptr);
static struct glink_core_rx_intent *ch_get_local_rx_intent_notified(
struct channel_ctx *ctx, const void *ptr);
static void ch_remove_local_rx_intent_notified(struct channel_ctx *ctx,
struct glink_core_rx_intent *liid_ptr, bool reuse);
static struct glink_core_rx_intent *ch_get_free_local_rx_intent(
struct channel_ctx *ctx);
static void ch_purge_intent_lists(struct channel_ctx *ctx);
static void ch_add_rcid(struct glink_core_xprt_ctx *xprt_ctx,
struct channel_ctx *ctx,
uint32_t rcid);
static bool ch_is_fully_opened(struct channel_ctx *ctx);
static bool ch_is_fully_closed(struct channel_ctx *ctx);
struct glink_core_tx_pkt *ch_get_tx_pending_remote_done(struct channel_ctx *ctx,
uint32_t riid);
static void ch_remove_tx_pending_remote_done(struct channel_ctx *ctx,
struct glink_core_tx_pkt *tx_pkt);
static void glink_core_rx_cmd_rx_intent_req_ack(struct glink_transport_if
*if_ptr, uint32_t rcid, bool granted);
static bool glink_core_remote_close_common(struct channel_ctx *ctx);
static void check_link_notifier_and_notify(struct glink_core_xprt_ctx *xprt_ptr,
enum glink_link_state link_state);
static void glink_core_channel_cleanup(struct glink_core_xprt_ctx *xprt_ptr);
static void glink_pm_qos_vote(struct glink_core_xprt_ctx *xprt_ptr);
static void glink_pm_qos_unvote(struct glink_core_xprt_ctx *xprt_ptr);
static void glink_pm_qos_cancel_worker(struct work_struct *work);
static bool ch_update_local_state(struct channel_ctx *ctx,
enum local_channel_state_e lstate);
static bool ch_update_rmt_state(struct channel_ctx *ctx, bool rstate);
/**
* glink_ssr() - Clean up locally for SSR by simulating remote close
* @subsystem: The name of the subsystem being restarted
*
* Call into the transport using the ssr(if_ptr) function to allow it to
* clean up any necessary structures, then simulate a remote close from
* subsystem for all channels on that edge.
*
* Return: Standard error codes.
*/
int glink_ssr(const char *subsystem)
{
int ret = 0;
bool transport_found = false;
struct glink_core_xprt_ctx *xprt_ctx = NULL;
mutex_lock(&transport_list_lock_lha0);
list_for_each_entry(xprt_ctx, &transport_list, list_node) {
if (!strcmp(subsystem, xprt_ctx->edge) &&
xprt_is_fully_opened(xprt_ctx)) {
GLINK_INFO_XPRT(xprt_ctx, "%s: SSR\n", __func__);
xprt_ctx->ops->ssr(xprt_ctx->ops);
transport_found = true;
}
}
mutex_unlock(&transport_list_lock_lha0);
if (!transport_found)
ret = -ENODEV;
return ret;
}
EXPORT_SYMBOL(glink_ssr);
/**
* glink_core_ch_close_ack_common() - handles the common operations during
* close ack.
* @ctx: Pointer to channel instance.
*
* Return: True if the channel is fully closed after the state change,
* false otherwise.
*/
static bool glink_core_ch_close_ack_common(struct channel_ctx *ctx)
{
bool is_fully_closed;
if (ctx == NULL)
return false;
is_fully_closed = ch_update_local_state(ctx, GLINK_CHANNEL_CLOSED);
GLINK_INFO_PERF_CH(ctx,
"%s: local:GLINK_CHANNEL_CLOSING->GLINK_CHANNEL_CLOSED\n",
__func__);
if (ctx->notify_state) {
ctx->notify_state(ctx, ctx->user_priv,
GLINK_LOCAL_DISCONNECTED);
ch_purge_intent_lists(ctx);
GLINK_INFO_PERF_CH(ctx,
"%s: notify state: GLINK_LOCAL_DISCONNECTED\n",
__func__);
}
return is_fully_closed;
}
/**
* glink_core_remote_close_common() - Handles the common operations during
* a remote close.
* @ctx: Pointer to channel instance.
*
* Return: True if the channel is fully closed after the state change,
* false otherwise.
*/
static bool glink_core_remote_close_common(struct channel_ctx *ctx)
{
bool is_fully_closed;
if (ctx == NULL)
return false;
is_fully_closed = ch_update_rmt_state(ctx, false);
ctx->rcid = 0;
if (ctx->local_open_state != GLINK_CHANNEL_CLOSED &&
ctx->local_open_state != GLINK_CHANNEL_CLOSING) {
if (ctx->notify_state)
ctx->notify_state(ctx, ctx->user_priv,
GLINK_REMOTE_DISCONNECTED);
GLINK_INFO_CH(ctx,
"%s: %s: GLINK_REMOTE_DISCONNECTED\n",
__func__, "notify state");
}
if (ctx->local_open_state == GLINK_CHANNEL_CLOSED)
GLINK_INFO_CH(ctx,
"%s: %s, %s\n", __func__,
"Did not send GLINK_REMOTE_DISCONNECTED",
"local state is already CLOSED");
ch_purge_intent_lists(ctx);
return is_fully_closed;
}
/**
* tx_linear_vbuf_provider() - Virtual Buffer Provider for linear buffers
* @iovec: Pointer to the beginning of the linear buffer.
* @offset: Offset into the buffer whose address is needed.
* @size: Pointer to hold the length of the contiguous buffer space.
*
* This function is used when a linear buffer is transmitted.
*
* Return: Address of the buffer which is at offset "offset" from the beginning
* of the buffer.
*/
static void *tx_linear_vbuf_provider(void *iovec, size_t offset, size_t *size)
{
struct glink_core_tx_pkt *tx_info = (struct glink_core_tx_pkt *)iovec;
if (unlikely(!iovec || !size))
return NULL;
if (offset >= tx_info->size)
return NULL;
if (unlikely(OVERFLOW_ADD_UNSIGNED(void *, tx_info->data, offset)))
return NULL;
*size = tx_info->size - offset;
return (void *)tx_info->data + offset;
}
/**
* linearize_vector() - Linearize the vector buffer
* @iovec: Pointer to the vector buffer.
* @size: Size of data in the vector buffer.
* vbuf_provider: Virtual address-space Buffer Provider for the vector.
* pbuf_provider: Physical address-space Buffer Provider for the vector.
*
* This function is used to linearize the vector buffer provided by the
* transport when the client has registered to receive only the vector
* buffer.
*
* Return: address of the linear buffer on success, NULL on failure.
*/
static void *linearize_vector(void *iovec, size_t size,
void * (*vbuf_provider)(void *iovec, size_t offset, size_t *buf_size),
void * (*pbuf_provider)(void *iovec, size_t offset, size_t *buf_size))
{
void *bounce_buf;
void *pdata;
void *vdata;
size_t data_size;
size_t offset = 0;
bounce_buf = kmalloc(size, GFP_KERNEL);
if (!bounce_buf)
return ERR_PTR(-ENOMEM);
do {
if (vbuf_provider) {
vdata = vbuf_provider(iovec, offset, &data_size);
} else {
pdata = pbuf_provider(iovec, offset, &data_size);
vdata = phys_to_virt((unsigned long)pdata);
}
if (!vdata)
break;
if (OVERFLOW_ADD_UNSIGNED(size_t, data_size, offset)) {
GLINK_ERR("%s: overflow data_size %zu + offset %zu\n",
__func__, data_size, offset);
goto err;
}
memcpy(bounce_buf + offset, vdata, data_size);
offset += data_size;
} while (offset < size);
if (offset != size) {
GLINK_ERR("%s: Error size_copied %zu != total_size %zu\n",
__func__, offset, size);
goto err;
}
return bounce_buf;
err:
kfree(bounce_buf);
return NULL;
}
/**
* xprt_lcid_to_ch_ctx_get() - lookup a channel by local id
* @xprt_ctx: Transport to search for a matching channel.
* @lcid: Local channel identifier corresponding to the desired channel.
*
* If the channel is found, the reference count is incremented to ensure the
* lifetime of the channel context. The caller must call rwref_put() when done.
*
* Return: The channel corresponding to @lcid or NULL if a matching channel
* is not found.
*/
static struct channel_ctx *xprt_lcid_to_ch_ctx_get(
struct glink_core_xprt_ctx *xprt_ctx,
uint32_t lcid)
{
struct channel_ctx *entry;
unsigned long flags;
spin_lock_irqsave(&xprt_ctx->xprt_ctx_lock_lhb1, flags);
list_for_each_entry(entry, &xprt_ctx->channels, port_list_node)
if (entry->lcid == lcid) {
rwref_get(&entry->ch_state_lhc0);
spin_unlock_irqrestore(&xprt_ctx->xprt_ctx_lock_lhb1,
flags);
return entry;
}
spin_unlock_irqrestore(&xprt_ctx->xprt_ctx_lock_lhb1, flags);
return NULL;
}
/**
* xprt_rcid_to_ch_ctx_get() - lookup a channel by remote id
* @xprt_ctx: Transport to search for a matching channel.
* @rcid: Remote channel identifier corresponding to the desired channel.
*
* If the channel is found, the reference count is incremented to ensure the
* lifetime of the channel context. The caller must call rwref_put() when done.
*
* Return: The channel corresponding to @rcid or NULL if a matching channel
* is not found.
*/
static struct channel_ctx *xprt_rcid_to_ch_ctx_get(
struct glink_core_xprt_ctx *xprt_ctx,
uint32_t rcid)
{
struct channel_ctx *entry;
unsigned long flags;
spin_lock_irqsave(&xprt_ctx->xprt_ctx_lock_lhb1, flags);
list_for_each_entry(entry, &xprt_ctx->channels, port_list_node)
if (entry->rcid == rcid) {
rwref_get(&entry->ch_state_lhc0);
spin_unlock_irqrestore(&xprt_ctx->xprt_ctx_lock_lhb1,
flags);
return entry;
}
spin_unlock_irqrestore(&xprt_ctx->xprt_ctx_lock_lhb1, flags);
return NULL;
}
/**
* ch_check_duplicate_riid() - Checks for duplicate riid
* @ctx: Local channel context
* @riid: Remote intent ID
*
* This functions check the riid is present in the remote_rx_list or not
*/
bool ch_check_duplicate_riid(struct channel_ctx *ctx, int riid)
{
struct glink_core_rx_intent *intent;
unsigned long flags;
spin_lock_irqsave(&ctx->rmt_rx_intent_lst_lock_lhc2, flags);
list_for_each_entry(intent, &ctx->rmt_rx_intent_list, list) {
if (riid == intent->id) {
spin_unlock_irqrestore(
&ctx->rmt_rx_intent_lst_lock_lhc2, flags);
return true;
}
}
spin_unlock_irqrestore(&ctx->rmt_rx_intent_lst_lock_lhc2, flags);
return false;
}
/**
* ch_pop_remote_rx_intent() - Finds a matching RX intent
* @ctx: Local channel context
* @size: Size of Intent
* @riid_ptr: Pointer to return value of remote intent ID
*
* This functions searches for an RX intent that is >= to the requested size.
*/
int ch_pop_remote_rx_intent(struct channel_ctx *ctx, size_t size,
uint32_t *riid_ptr, size_t *intent_size)
{
struct glink_core_rx_intent *intent;
struct glink_core_rx_intent *intent_tmp;
unsigned long flags;
if (GLINK_MAX_PKT_SIZE < size) {
GLINK_ERR_CH(ctx, "%s: R[]:%zu Invalid size.\n", __func__,
size);
return -EINVAL;
}
if (riid_ptr == NULL)
return -EINVAL;
*riid_ptr = 0;
spin_lock_irqsave(&ctx->rmt_rx_intent_lst_lock_lhc2, flags);
if (ctx->transport_ptr->capabilities & GCAP_INTENTLESS) {
*riid_ptr = ++ctx->dummy_riid;
spin_unlock_irqrestore(&ctx->rmt_rx_intent_lst_lock_lhc2,
flags);
return 0;
}
list_for_each_entry_safe(intent, intent_tmp, &ctx->rmt_rx_intent_list,
list) {
if (intent->intent_size >= size) {
list_del(&intent->list);
GLINK_DBG_CH(ctx,
"%s: R[%u]:%zu Removed remote intent\n",
__func__,
intent->id,
intent->intent_size);
*riid_ptr = intent->id;
*intent_size = intent->intent_size;
kfree(intent);
spin_unlock_irqrestore(
&ctx->rmt_rx_intent_lst_lock_lhc2, flags);
return 0;
}
}
spin_unlock_irqrestore(&ctx->rmt_rx_intent_lst_lock_lhc2, flags);
return -EAGAIN;
}
/**
* ch_push_remote_rx_intent() - Registers a remote RX intent
* @ctx: Local channel context
* @size: Size of Intent
* @riid: Remote intent ID
*
* This functions adds a remote RX intent to the remote RX intent list.
*/
void ch_push_remote_rx_intent(struct channel_ctx *ctx, size_t size,
uint32_t riid)
{
struct glink_core_rx_intent *intent;
unsigned long flags;
if (GLINK_MAX_PKT_SIZE < size) {
GLINK_ERR_CH(ctx, "%s: R[%u]:%zu Invalid size.\n", __func__,
riid, size);
return;
}
if (ch_check_duplicate_riid(ctx, riid)) {
GLINK_ERR_CH(ctx, "%s: R[%d]:%zu Duplicate RIID found\n",
__func__, riid, size);
return;
}
intent = kzalloc(sizeof(struct glink_core_rx_intent), GFP_KERNEL);
if (!intent) {
GLINK_ERR_CH(ctx,
"%s: R[%u]:%zu Memory allocation for intent failed\n",
__func__, riid, size);
return;
}
intent->id = riid;
intent->intent_size = size;
spin_lock_irqsave(&ctx->rmt_rx_intent_lst_lock_lhc2, flags);
list_add_tail(&intent->list, &ctx->rmt_rx_intent_list);
spin_unlock_irqrestore(&ctx->rmt_rx_intent_lst_lock_lhc2, flags);
GLINK_DBG_CH(ctx, "%s: R[%u]:%zu Pushed remote intent\n", __func__,
intent->id,
intent->intent_size);
}
/**
* ch_push_local_rx_intent() - Create an rx_intent
* @ctx: Local channel context
* @pkt_priv: Opaque private pointer provided by client to be returned later
* @size: Size of intent
*
* This functions creates a local intent and adds it to the local
* intent list.
*/
struct glink_core_rx_intent *ch_push_local_rx_intent(struct channel_ctx *ctx,
const void *pkt_priv, size_t size)
{
struct glink_core_rx_intent *intent;
unsigned long flags;
int ret;
if (GLINK_MAX_PKT_SIZE < size) {
GLINK_ERR_CH(ctx,
"%s: L[]:%zu Invalid size\n", __func__, size);
return NULL;
}
intent = ch_get_free_local_rx_intent(ctx);
if (!intent) {
if (ctx->max_used_liid >= ctx->transport_ptr->max_iid) {
GLINK_ERR_CH(ctx,
"%s: All intents are in USE max_iid[%d]",
__func__, ctx->transport_ptr->max_iid);
return NULL;
}
intent = kzalloc(sizeof(struct glink_core_rx_intent),
GFP_KERNEL);
if (!intent) {
GLINK_ERR_CH(ctx,
"%s: Memory Allocation for local rx_intent failed",
__func__);
return NULL;
}
intent->id = ++ctx->max_used_liid;
}
/* transport is responsible for allocating/reserving for the intent */
ret = ctx->transport_ptr->ops->allocate_rx_intent(
ctx->transport_ptr->ops, size, intent);
if (ret < 0) {
/* intent data allocation failure */
GLINK_ERR_CH(ctx, "%s: unable to allocate intent sz[%zu] %d",
__func__, size, ret);
spin_lock_irqsave(&ctx->local_rx_intent_lst_lock_lhc1, flags);
list_add_tail(&intent->list,
&ctx->local_rx_intent_free_list);
spin_unlock_irqrestore(&ctx->local_rx_intent_lst_lock_lhc1,
flags);
return NULL;
}
intent->pkt_priv = pkt_priv;
intent->intent_size = size;
intent->write_offset = 0;
intent->pkt_size = 0;
intent->bounce_buf = NULL;
spin_lock_irqsave(&ctx->local_rx_intent_lst_lock_lhc1, flags);
list_add_tail(&intent->list, &ctx->local_rx_intent_list);
spin_unlock_irqrestore(&ctx->local_rx_intent_lst_lock_lhc1, flags);
GLINK_DBG_CH(ctx, "%s: L[%u]:%zu Pushed intent\n", __func__,
intent->id,
intent->intent_size);
return intent;
}
/**
* ch_remove_local_rx_intent() - Find and remove RX Intent from list
* @ctx: Local channel context
* @liid: Local channel Intent ID
*
* This functions parses the local intent list for a specific channel
* and checks for the intent using the intent ID. If found, the intent
* is deleted from the list.
*/
void ch_remove_local_rx_intent(struct channel_ctx *ctx, uint32_t liid)
{
struct glink_core_rx_intent *intent, *tmp_intent;
unsigned long flags;
if (ctx->transport_ptr->max_iid < liid) {
GLINK_ERR_CH(ctx, "%s: L[%u] Invalid ID.\n", __func__,
liid);
return;
}
spin_lock_irqsave(&ctx->local_rx_intent_lst_lock_lhc1, flags);
list_for_each_entry_safe(intent, tmp_intent, &ctx->local_rx_intent_list,
list) {
if (liid == intent->id) {
list_del(&intent->list);
list_add_tail(&intent->list,
&ctx->local_rx_intent_free_list);
spin_unlock_irqrestore(
&ctx->local_rx_intent_lst_lock_lhc1,
flags);
GLINK_DBG_CH(ctx,
"%s: L[%u]:%zu moved intent to Free/unused list\n",
__func__,
intent->id,
intent->intent_size);
return;
}
}
spin_unlock_irqrestore(&ctx->local_rx_intent_lst_lock_lhc1, flags);
GLINK_ERR_CH(ctx, "%s: L[%u] Intent not found.\n", __func__,
liid);
}
/**
* ch_get_dummy_rx_intent() - Get a dummy rx_intent
* @ctx: Local channel context
* @liid: Local channel Intent ID
*
* This functions parses the local intent list for a specific channel and
* returns either a matching intent or allocates a dummy one if no matching
* intents can be found.
*
* Return: Pointer to the intent if intent is found else NULL
*/
struct glink_core_rx_intent *ch_get_dummy_rx_intent(struct channel_ctx *ctx,
uint32_t liid)
{
struct glink_core_rx_intent *intent;
unsigned long flags;
spin_lock_irqsave(&ctx->local_rx_intent_lst_lock_lhc1, flags);
if (!list_empty(&ctx->local_rx_intent_list)) {
intent = list_first_entry(&ctx->local_rx_intent_list,
struct glink_core_rx_intent, list);
spin_unlock_irqrestore(&ctx->local_rx_intent_lst_lock_lhc1,
flags);
return intent;
}
spin_unlock_irqrestore(&ctx->local_rx_intent_lst_lock_lhc1, flags);
intent = ch_get_free_local_rx_intent(ctx);
if (!intent) {
intent = kzalloc(sizeof(struct glink_core_rx_intent),
GFP_ATOMIC);
if (!intent) {
GLINK_ERR_CH(ctx,
"%s: Memory Allocation for local rx_intent failed",
__func__);
return NULL;
}
intent->id = ++ctx->max_used_liid;
}
intent->intent_size = 0;
intent->write_offset = 0;
intent->pkt_size = 0;
intent->bounce_buf = NULL;
intent->pkt_priv = NULL;
spin_lock_irqsave(&ctx->local_rx_intent_lst_lock_lhc1, flags);
list_add_tail(&intent->list, &ctx->local_rx_intent_list);
spin_unlock_irqrestore(&ctx->local_rx_intent_lst_lock_lhc1, flags);
GLINK_DBG_CH(ctx, "%s: L[%u]:%zu Pushed intent\n", __func__,
intent->id,
intent->intent_size);
return intent;
}
/**
* ch_get_local_rx_intent() - Search for an rx_intent
* @ctx: Local channel context
* @liid: Local channel Intent ID
*
* This functions parses the local intent list for a specific channel
* and checks for the intent using the intent ID. If found, pointer to
* the intent is returned.
*
* Return: Pointer to the intent if intent is found else NULL
*/
struct glink_core_rx_intent *ch_get_local_rx_intent(struct channel_ctx *ctx,
uint32_t liid)
{
struct glink_core_rx_intent *intent;
unsigned long flags;
if (ctx->transport_ptr->max_iid < liid) {
GLINK_ERR_CH(ctx, "%s: L[%u] Invalid ID.\n", __func__,
liid);
return NULL;
}
if (ctx->transport_ptr->capabilities & GCAP_INTENTLESS)
return ch_get_dummy_rx_intent(ctx, liid);
spin_lock_irqsave(&ctx->local_rx_intent_lst_lock_lhc1, flags);
list_for_each_entry(intent, &ctx->local_rx_intent_list, list) {
if (liid == intent->id) {
spin_unlock_irqrestore(
&ctx->local_rx_intent_lst_lock_lhc1, flags);
return intent;
}
}
spin_unlock_irqrestore(&ctx->local_rx_intent_lst_lock_lhc1, flags);
GLINK_ERR_CH(ctx, "%s: L[%u] Intent not found.\n", __func__,
liid);
return NULL;
}
/**
* ch_set_local_rx_intent_notified() - Add a rx intent to local intent
* notified list
* @ctx: Local channel context
* @intent_ptr: Pointer to the local intent
*
* This functions parses the local intent list for a specific channel
* and checks for the intent. If found, the function deletes the intent
* from local_rx_intent list and adds it to local_rx_intent_notified list.
*/
void ch_set_local_rx_intent_notified(struct channel_ctx *ctx,
struct glink_core_rx_intent *intent_ptr)
{
struct glink_core_rx_intent *tmp_intent, *intent;
unsigned long flags;
spin_lock_irqsave(&ctx->local_rx_intent_lst_lock_lhc1, flags);
list_for_each_entry_safe(intent, tmp_intent, &ctx->local_rx_intent_list,
list) {
if (intent == intent_ptr) {
list_del(&intent->list);
list_add_tail(&intent->list,
&ctx->local_rx_intent_ntfy_list);
GLINK_DBG_CH(ctx,
"%s: L[%u]:%zu Moved intent %s",
__func__,
intent_ptr->id,
intent_ptr->intent_size,
"from local to notify list\n");
spin_unlock_irqrestore(
&ctx->local_rx_intent_lst_lock_lhc1,
flags);
return;
}
}
spin_unlock_irqrestore(&ctx->local_rx_intent_lst_lock_lhc1, flags);
GLINK_ERR_CH(ctx, "%s: L[%u] Intent not found.\n", __func__,
intent_ptr->id);
}
/**
* ch_get_local_rx_intent_notified() - Find rx intent in local notified list
* @ctx: Local channel context
* @ptr: Pointer to the rx intent
*
* This functions parses the local intent notify list for a specific channel
* and checks for the intent.
*
* Return: Pointer to the intent if intent is found else NULL.
*/
struct glink_core_rx_intent *ch_get_local_rx_intent_notified(
struct channel_ctx *ctx, const void *ptr)
{
struct glink_core_rx_intent *ptr_intent;
unsigned long flags;
spin_lock_irqsave(&ctx->local_rx_intent_lst_lock_lhc1, flags);
list_for_each_entry(ptr_intent, &ctx->local_rx_intent_ntfy_list,
list) {
if (ptr_intent->data == ptr || ptr_intent->iovec == ptr ||
ptr_intent->bounce_buf == ptr) {
spin_unlock_irqrestore(
&ctx->local_rx_intent_lst_lock_lhc1,
flags);
return ptr_intent;
}
}
spin_unlock_irqrestore(&ctx->local_rx_intent_lst_lock_lhc1, flags);
GLINK_ERR_CH(ctx, "%s: Local intent not found\n", __func__);
return NULL;
}
/**
* ch_remove_local_rx_intent_notified() - Remove a rx intent in local intent
* notified list
* @ctx: Local channel context
* @ptr: Pointer to the rx intent
* @reuse: Reuse the rx intent
*
* This functions parses the local intent notify list for a specific channel
* and checks for the intent. If found, the function deletes the intent
* from local_rx_intent_notified list and adds it to local_rx_intent_free list.
*/
void ch_remove_local_rx_intent_notified(struct channel_ctx *ctx,
struct glink_core_rx_intent *liid_ptr, bool reuse)
{
struct glink_core_rx_intent *ptr_intent, *tmp_intent;
unsigned long flags;
spin_lock_irqsave(&ctx->local_rx_intent_lst_lock_lhc1, flags);
list_for_each_entry_safe(ptr_intent, tmp_intent,
&ctx->local_rx_intent_ntfy_list, list) {
if (ptr_intent == liid_ptr) {
list_del(&ptr_intent->list);
GLINK_DBG_CH(ctx,
"%s: L[%u]:%zu Removed intent from notify list\n",
__func__,
ptr_intent->id,
ptr_intent->intent_size);
kfree(ptr_intent->bounce_buf);
ptr_intent->bounce_buf = NULL;
ptr_intent->write_offset = 0;
ptr_intent->pkt_size = 0;
if (reuse)
list_add_tail(&ptr_intent->list,
&ctx->local_rx_intent_list);
else
list_add_tail(&ptr_intent->list,
&ctx->local_rx_intent_free_list);
spin_unlock_irqrestore(
&ctx->local_rx_intent_lst_lock_lhc1,
flags);
return;
}
}
spin_unlock_irqrestore(&ctx->local_rx_intent_lst_lock_lhc1, flags);
GLINK_ERR_CH(ctx, "%s: L[%u] Intent not found.\n", __func__,
liid_ptr->id);
}
/**
* ch_get_free_local_rx_intent() - Return a rx intent in local intent
* free list
* @ctx: Local channel context
*
* This functions parses the local_rx_intent_free list for a specific channel
* and checks for the free unused intent. If found, the function returns
* the free intent pointer else NULL pointer.
*/
struct glink_core_rx_intent *ch_get_free_local_rx_intent(
struct channel_ctx *ctx)
{
struct glink_core_rx_intent *ptr_intent = NULL;
unsigned long flags;
spin_lock_irqsave(&ctx->local_rx_intent_lst_lock_lhc1, flags);
if (!list_empty(&ctx->local_rx_intent_free_list)) {
ptr_intent = list_first_entry(&ctx->local_rx_intent_free_list,
struct glink_core_rx_intent,
list);
list_del(&ptr_intent->list);
}
spin_unlock_irqrestore(&ctx->local_rx_intent_lst_lock_lhc1, flags);
return ptr_intent;
}
/**
* ch_purge_intent_lists() - Remove all intents for a channel
*
* @ctx: Local channel context
*
* This functions parses the local intent lists for a specific channel and
* removes and frees all intents.
*/
void ch_purge_intent_lists(struct channel_ctx *ctx)
{
struct glink_core_rx_intent *ptr_intent, *tmp_intent;
struct glink_core_tx_pkt *tx_info, *tx_info_temp;
unsigned long flags;
mutex_lock(&ctx->tx_lists_mutex_lhc3);
list_for_each_entry_safe(tx_info, tx_info_temp, &ctx->tx_active,
list_node) {
ctx->notify_tx_abort(ctx, ctx->user_priv,
tx_info->pkt_priv);
list_del(&tx_info->list_node);
kfree(tx_info);
}
mutex_unlock(&ctx->tx_lists_mutex_lhc3);
spin_lock_irqsave(&ctx->local_rx_intent_lst_lock_lhc1, flags);
list_for_each_entry_safe(ptr_intent, tmp_intent,
&ctx->local_rx_intent_list, list) {
ctx->notify_rx_abort(ctx, ctx->user_priv,
ptr_intent->pkt_priv);
list_del(&ptr_intent->list);
kfree(ptr_intent);
}
if (!list_empty(&ctx->local_rx_intent_ntfy_list))
/*
* The client is still processing an rx_notify() call and has
* not yet called glink_rx_done() to return the pointer to us.
* glink_rx_done() will do the appropriate cleanup when this
* call occurs, but log a message here just for internal state
* tracking.
*/
GLINK_INFO_CH(ctx, "%s: waiting on glink_rx_done()\n",
__func__);
list_for_each_entry_safe(ptr_intent, tmp_intent,
&ctx->local_rx_intent_free_list, list) {
list_del(&ptr_intent->list);
kfree(ptr_intent);
}
ctx->max_used_liid = 0;
spin_unlock_irqrestore(&ctx->local_rx_intent_lst_lock_lhc1, flags);
spin_lock_irqsave(&ctx->rmt_rx_intent_lst_lock_lhc2, flags);
list_for_each_entry_safe(ptr_intent, tmp_intent,
&ctx->rmt_rx_intent_list, list) {
list_del(&ptr_intent->list);
kfree(ptr_intent);
}
spin_unlock_irqrestore(&ctx->rmt_rx_intent_lst_lock_lhc2, flags);
}
/**
* ch_get_tx_pending_remote_done() - Lookup for a packet that is waiting for
* the remote-done notification.
* @ctx: Pointer to the channel context
* @riid: riid of transmit packet
*
* This function adds a packet to the tx_pending_remote_done list.
*
* The tx_lists_mutex_lhc3 lock needs to be held while calling this function.
*
* Return: Pointer to the tx packet
*/
struct glink_core_tx_pkt *ch_get_tx_pending_remote_done(
struct channel_ctx *ctx, uint32_t riid)
{
struct glink_core_tx_pkt *tx_pkt;
unsigned long flags;
if (!ctx) {
GLINK_ERR("%s: Invalid context pointer", __func__);
return NULL;
}
spin_lock_irqsave(&ctx->tx_pending_rmt_done_lock_lhc4, flags);
list_for_each_entry(tx_pkt, &ctx->tx_pending_remote_done, list_done) {
if (tx_pkt->riid == riid) {
if (tx_pkt->size_remaining) {
GLINK_ERR_CH(ctx, "%s: R[%u] TX not complete",
__func__, riid);
tx_pkt = NULL;
}
spin_unlock_irqrestore(
&ctx->tx_pending_rmt_done_lock_lhc4, flags);
return tx_pkt;
}
}
spin_unlock_irqrestore(&ctx->tx_pending_rmt_done_lock_lhc4, flags);
GLINK_ERR_CH(ctx, "%s: R[%u] Tx packet for intent not found.\n",
__func__, riid);
return NULL;
}
/**
* ch_remove_tx_pending_remote_done() - Removes a packet transmit context for a
* packet that is waiting for the remote-done notification
* @ctx: Pointer to the channel context
* @tx_pkt: Pointer to the transmit packet
*
* This function parses through tx_pending_remote_done and removes a
* packet that matches with the tx_pkt.
*/
void ch_remove_tx_pending_remote_done(struct channel_ctx *ctx,
struct glink_core_tx_pkt *tx_pkt)
{
struct glink_core_tx_pkt *local_tx_pkt, *tmp_tx_pkt;
unsigned long flags;
if (!ctx || !tx_pkt) {
GLINK_ERR("%s: Invalid input", __func__);
return;
}
spin_lock_irqsave(&ctx->tx_pending_rmt_done_lock_lhc4, flags);
list_for_each_entry_safe(local_tx_pkt, tmp_tx_pkt,
&ctx->tx_pending_remote_done, list_done) {
if (tx_pkt == local_tx_pkt) {
list_del_init(&local_tx_pkt->list_done);
GLINK_DBG_CH(ctx,
"%s: R[%u] Removed Tx packet for intent\n",
__func__,
tx_pkt->riid);
kfree(local_tx_pkt);
spin_unlock_irqrestore(
&ctx->tx_pending_rmt_done_lock_lhc4, flags);
return;
}
}
spin_unlock_irqrestore(&ctx->tx_pending_rmt_done_lock_lhc4, flags);
GLINK_ERR_CH(ctx, "%s: R[%u] Tx packet for intent not found", __func__,
tx_pkt->riid);
}
/**
* glink_add_free_lcid_list() - adds the lcid of a to be deleted channel to
* available lcid list
* @ctx: Pointer to channel context.
*/
static void glink_add_free_lcid_list(struct channel_ctx *ctx)
{
struct channel_lcid *free_lcid;
unsigned long flags;
free_lcid = kzalloc(sizeof(*free_lcid), GFP_KERNEL);
if (!free_lcid) {
GLINK_ERR(
"%s: allocation failed on xprt:edge [%s:%s] for lcid [%d]\n",
__func__, ctx->transport_ptr->name,
ctx->transport_ptr->edge, ctx->lcid);
return;
}
free_lcid->lcid = ctx->lcid;
spin_lock_irqsave(&ctx->transport_ptr->xprt_ctx_lock_lhb1, flags);
list_add_tail(&free_lcid->list_node,
&ctx->transport_ptr->free_lcid_list);
spin_unlock_irqrestore(&ctx->transport_ptr->xprt_ctx_lock_lhb1,
flags);
}
/**
* glink_ch_ctx_release - Free the channel context
* @ch_st_lock: handle to the rwref_lock associated with the chanel
*
* This should only be called when the reference count associated with the
* channel goes to zero.
*/
static void glink_ch_ctx_release(struct rwref_lock *ch_st_lock)
{
struct channel_ctx *ctx = container_of(ch_st_lock, struct channel_ctx,
ch_state_lhc0);
ctx->transport_ptr = NULL;
kfree(ctx);
GLINK_INFO("%s: freed the channel ctx in pid [%d]\n", __func__,
current->pid);
ctx = NULL;
}
/**
* ch_name_to_ch_ctx_create() - lookup a channel by name, create the channel if
* it is not found.
* @xprt_ctx: Transport to search for a matching channel.
* @name: Name of the desired channel.
*
* Return: The channel corresponding to @name, NULL if a matching channel was
* not found AND a new channel could not be created.
*/
static struct channel_ctx *ch_name_to_ch_ctx_create(
struct glink_core_xprt_ctx *xprt_ctx,
const char *name)
{
struct channel_ctx *entry;
struct channel_ctx *ctx;
struct channel_ctx *temp;
unsigned long flags;
struct channel_lcid *flcid;
ctx = kzalloc(sizeof(struct channel_ctx), GFP_KERNEL);
if (!ctx) {
GLINK_ERR_XPRT(xprt_ctx, "%s: Failed to allocated ctx, %s",
"checking if there is one existing\n",
__func__);
goto check_ctx;
}
ctx->local_open_state = GLINK_CHANNEL_CLOSED;
strlcpy(ctx->name, name, GLINK_NAME_SIZE);
rwref_lock_init(&ctx->ch_state_lhc0, glink_ch_ctx_release);
INIT_LIST_HEAD(&ctx->tx_ready_list_node);
init_completion(&ctx->int_req_ack_complete);
init_completion(&ctx->int_req_complete);
INIT_LIST_HEAD(&ctx->local_rx_intent_list);
INIT_LIST_HEAD(&ctx->local_rx_intent_ntfy_list);
INIT_LIST_HEAD(&ctx->local_rx_intent_free_list);
spin_lock_init(&ctx->local_rx_intent_lst_lock_lhc1);
INIT_LIST_HEAD(&ctx->rmt_rx_intent_list);
spin_lock_init(&ctx->rmt_rx_intent_lst_lock_lhc2);
INIT_LIST_HEAD(&ctx->tx_active);
spin_lock_init(&ctx->tx_pending_rmt_done_lock_lhc4);
INIT_LIST_HEAD(&ctx->tx_pending_remote_done);
mutex_init(&ctx->tx_lists_mutex_lhc3);
check_ctx:
rwref_write_get(&xprt_ctx->xprt_state_lhb0);
if (xprt_ctx->local_state != GLINK_XPRT_OPENED) {
kfree(ctx);
rwref_write_put(&xprt_ctx->xprt_state_lhb0);
return NULL;
}
spin_lock_irqsave(&xprt_ctx->xprt_ctx_lock_lhb1, flags);
list_for_each_entry_safe(entry, temp, &xprt_ctx->channels,
port_list_node)
if (!strcmp(entry->name, name) && !entry->pending_delete) {
spin_unlock_irqrestore(&xprt_ctx->xprt_ctx_lock_lhb1,
flags);
kfree(ctx);
rwref_write_put(&xprt_ctx->xprt_state_lhb0);
return entry;
}
if (ctx) {
if (list_empty(&xprt_ctx->free_lcid_list)) {
if (xprt_ctx->next_lcid > xprt_ctx->max_cid) {
/* no more channels available */
GLINK_ERR_XPRT(xprt_ctx,
"%s: unable to exceed %u channels\n",
__func__, xprt_ctx->max_cid);
spin_unlock_irqrestore(
&xprt_ctx->xprt_ctx_lock_lhb1,
flags);
kfree(ctx);
rwref_write_put(&xprt_ctx->xprt_state_lhb0);
return NULL;
} else {
ctx->lcid = xprt_ctx->next_lcid++;
}
} else {
flcid = list_first_entry(&xprt_ctx->free_lcid_list,
struct channel_lcid, list_node);
ctx->lcid = flcid->lcid;
list_del(&flcid->list_node);
kfree(flcid);
}
list_add_tail(&ctx->port_list_node, &xprt_ctx->channels);
GLINK_INFO_PERF_CH_XPRT(ctx, xprt_ctx,
"%s: local:GLINK_CHANNEL_CLOSED\n",
__func__);
}
spin_unlock_irqrestore(&xprt_ctx->xprt_ctx_lock_lhb1, flags);
rwref_write_put(&xprt_ctx->xprt_state_lhb0);
mutex_lock(&xprt_ctx->xprt_dbgfs_lock_lhb3);
if (ctx != NULL)
glink_debugfs_add_channel(ctx, xprt_ctx);
mutex_unlock(&xprt_ctx->xprt_dbgfs_lock_lhb3);
return ctx;
}
/**
* ch_add_rcid() - add a remote channel identifier to an existing channel
* @xprt_ctx: Transport the channel resides on.
* @ctx: Channel receiving the identifier.
* @rcid: The remote channel identifier.
*/
static void ch_add_rcid(struct glink_core_xprt_ctx *xprt_ctx,
struct channel_ctx *ctx,
uint32_t rcid)
{
ctx->rcid = rcid;
}
/**
* ch_update_local_state() - Update the local channel state
* @ctx: Pointer to channel context.
* @lstate: Local channel state.
*
* Return: True if the channel is fully closed as a result of this update,
* false otherwise.
*/
static bool ch_update_local_state(struct channel_ctx *ctx,
enum local_channel_state_e lstate)
{
bool is_fully_closed;
rwref_write_get(&ctx->ch_state_lhc0);
ctx->local_open_state = lstate;
is_fully_closed = ch_is_fully_closed(ctx);
rwref_write_put(&ctx->ch_state_lhc0);
return is_fully_closed;
}
/**
* ch_update_local_state() - Update the local channel state
* @ctx: Pointer to channel context.
* @rstate: Remote Channel state.
*
* Return: True if the channel is fully closed as result of this update,
* false otherwise.
*/
static bool ch_update_rmt_state(struct channel_ctx *ctx, bool rstate)
{
bool is_fully_closed;
rwref_write_get(&ctx->ch_state_lhc0);
ctx->remote_opened = rstate;
is_fully_closed = ch_is_fully_closed(ctx);
rwref_write_put(&ctx->ch_state_lhc0);
return is_fully_closed;
}
/*
* ch_is_fully_opened() - Verify if a channel is open
* ctx: Pointer to channel context
*
* Return: True if open, else flase
*/
static bool ch_is_fully_opened(struct channel_ctx *ctx)
{
if (ctx->remote_opened && ctx->local_open_state == GLINK_CHANNEL_OPENED)
return true;
return false;
}
/*
* ch_is_fully_closed() - Verify if a channel is closed on both sides
* @ctx: Pointer to channel context
* @returns: True if open, else flase
*/
static bool ch_is_fully_closed(struct channel_ctx *ctx)
{
if (!ctx->remote_opened &&
ctx->local_open_state == GLINK_CHANNEL_CLOSED)
return true;
return false;
}
/**
* find_open_transport() - find a specific open transport
* @edge: Edge the transport is on.
* @name: Name of the transport (or NULL if no preference)
* @initial_xprt: The specified transport is the start for migration
* @best_id: The best transport found for this connection
*
* Find an open transport corresponding to the specified @name and @edge. @edge
* is expected to be valid. @name is expected to be NULL (unspecified) or
* valid. If @name is not specified, then the best transport found on the
* specified edge will be returned.
*
* Return: Transport with the specified name on the specified edge, if open.
* NULL if the transport exists, but is not fully open. ENODEV if no such
* transport exists.
*/
static struct glink_core_xprt_ctx *find_open_transport(const char *edge,
const char *name,
bool initial_xprt,
uint16_t *best_id)
{
struct glink_core_xprt_ctx *xprt;
struct glink_core_xprt_ctx *best_xprt;
struct glink_core_xprt_ctx *ret;
bool first = true;
ret = (struct glink_core_xprt_ctx *)ERR_PTR(-ENODEV);
*best_id = USHRT_MAX;
mutex_lock(&transport_list_lock_lha0);
list_for_each_entry(xprt, &transport_list, list_node) {
if (strcmp(edge, xprt->edge))
continue;
if (first) {
first = false;
ret = NULL;
}
if (!xprt_is_fully_opened(xprt))
continue;
if (xprt->id < *best_id) {
*best_id = xprt->id;
best_xprt = xprt;
}
/*
* Braces are required in this instacne because the else will
* attach to the wrong if otherwise.
*/
if (name) {
if (!strcmp(name, xprt->name))
ret = xprt;
} else {
ret = best_xprt;
}
}
mutex_unlock(&transport_list_lock_lha0);
if (IS_ERR_OR_NULL(ret))
return ret;
if (!initial_xprt)
*best_id = ret->id;
return ret;
}
/**
* xprt_is_fully_opened() - check the open status of a transport
* @xprt: Transport being checked.
*
* Return: True if the transport is fully opened, false otherwise.
*/
static bool xprt_is_fully_opened(struct glink_core_xprt_ctx *xprt)
{
if (xprt->remote_neg_completed &&
xprt->local_state == GLINK_XPRT_OPENED)
return true;
return false;
}
/**
* glink_dummy_notify_rx_intent_req() - Dummy RX Request
*
* @handle: Channel handle (ignored)
* @priv: Private data pointer (ignored)
* @req_size: Requested size (ignored)
*
* Dummy RX intent request if client does not implement the optional callback
* function.
*
* Return: False
*/
static bool glink_dummy_notify_rx_intent_req(void *handle, const void *priv,
size_t req_size)
{
return false;
}
/**
* glink_dummy_notify_rx_sigs() - Dummy signal callback
*
* @handle: Channel handle (ignored)
* @priv: Private data pointer (ignored)
* @req_size: Requested size (ignored)
*
* Dummy signal callback if client does not implement the optional callback
* function.
*
* Return: False
*/
static void glink_dummy_notify_rx_sigs(void *handle, const void *priv,
uint32_t old_sigs, uint32_t new_sigs)
{
/* intentionally left blank */
}
/**
* glink_dummy_rx_abort() - Dummy rx abort callback
*
* handle: Channel handle (ignored)
* priv: Private data pointer (ignored)
* pkt_priv: Private intent data pointer (ignored)
*
* Dummy rx abort callback if client does not implement the optional callback
* function.
*/
static void glink_dummy_notify_rx_abort(void *handle, const void *priv,
const void *pkt_priv)
{
/* intentionally left blank */
}
/**
* glink_dummy_tx_abort() - Dummy tx abort callback
*
* @handle: Channel handle (ignored)
* @priv: Private data pointer (ignored)
* @pkt_priv: Private intent data pointer (ignored)
*
* Dummy tx abort callback if client does not implement the optional callback
* function.
*/
static void glink_dummy_notify_tx_abort(void *handle, const void *priv,
const void *pkt_priv)
{
/* intentionally left blank */
}
/**
* dummy_poll() - a dummy poll() for transports that don't define one
* @if_ptr: The transport interface handle for this transport.
* @lcid: The channel to poll.
*
* Return: An error to indicate that this operation is unsupported.
*/
static int dummy_poll(struct glink_transport_if *if_ptr, uint32_t lcid)
{
return -EOPNOTSUPP;
}
/**
* dummy_reuse_rx_intent() - a dummy reuse_rx_intent() for transports that
* don't define one
* @if_ptr: The transport interface handle for this transport.
* @intent: The intent to reuse.
*
* Return: Success.
*/
static int dummy_reuse_rx_intent(struct glink_transport_if *if_ptr,
struct glink_core_rx_intent *intent)
{
return 0;
}
/**
* dummy_mask_rx_irq() - a dummy mask_rx_irq() for transports that don't define
* one
* @if_ptr: The transport interface handle for this transport.
* @lcid: The local channel id for this channel.
* @mask: True to mask the irq, false to unmask.
* @pstruct: Platform defined structure with data necessary for masking.
*
* Return: An error to indicate that this operation is unsupported.
*/
static int dummy_mask_rx_irq(struct glink_transport_if *if_ptr, uint32_t lcid,
bool mask, void *pstruct)
{
return -EOPNOTSUPP;
}
/**
* dummy_wait_link_down() - a dummy wait_link_down() for transports that don't
* define one
* @if_ptr: The transport interface handle for this transport.
*
* Return: An error to indicate that this operation is unsupported.
*/
static int dummy_wait_link_down(struct glink_transport_if *if_ptr)
{
return -EOPNOTSUPP;
}
/**
* dummy_allocate_rx_intent() - a dummy RX intent allocation function that does
* not allocate anything
* @if_ptr: The transport the intent is associated with.
* @size: Size of intent.
* @intent: Pointer to the intent structure.
*
* Return: Success.
*/
static int dummy_allocate_rx_intent(struct glink_transport_if *if_ptr,
size_t size, struct glink_core_rx_intent *intent)
{
return 0;
}
/**
* dummy_tx_cmd_tracer_pkt() - a dummy tracer packet tx cmd for transports
* that don't define one
* @if_ptr: The transport interface handle for this transport.
* @lcid: The channel in which the tracer packet is transmitted.
* @pctx: Context of the packet to be transmitted.
*
* Return: 0.
*/
static int dummy_tx_cmd_tracer_pkt(struct glink_transport_if *if_ptr,
uint32_t lcid, struct glink_core_tx_pkt *pctx)
{
pctx->size_remaining = 0;
return 0;
}
/**
* dummy_deallocate_rx_intent() - a dummy rx intent deallocation function that
* does not deallocate anything
* @if_ptr: The transport the intent is associated with.
* @intent: Pointer to the intent structure.
*
* Return: Success.
*/
static int dummy_deallocate_rx_intent(struct glink_transport_if *if_ptr,
struct glink_core_rx_intent *intent)
{
return 0;
}
/**
* dummy_tx_cmd_local_rx_intent() - dummy local rx intent request
* @if_ptr: The transport to transmit on.
* @lcid: The local channel id to encode.
* @size: The intent size to encode.
* @liid: The local intent id to encode.
*
* Return: Success.
*/
static int dummy_tx_cmd_local_rx_intent(struct glink_transport_if *if_ptr,
uint32_t lcid, size_t size, uint32_t liid)
{
return 0;
}
/**
* dummy_tx_cmd_local_rx_done() - dummy rx done command
* @if_ptr: The transport to transmit on.
* @lcid: The local channel id to encode.
* @liid: The local intent id to encode.
* @reuse: Reuse the consumed intent.
*/
static void dummy_tx_cmd_local_rx_done(struct glink_transport_if *if_ptr,
uint32_t lcid, uint32_t liid, bool reuse)
{
/* intentionally left blank */
}
/**
* dummy_tx() - dummy tx() that does not send anything
* @if_ptr: The transport to transmit on.
* @lcid: The local channel id to encode.
* @pctx: The data to encode.
*
* Return: Number of bytes written i.e. zero.
*/
static int dummy_tx(struct glink_transport_if *if_ptr, uint32_t lcid,
struct glink_core_tx_pkt *pctx)
{
return 0;
}
/**
* dummy_tx_cmd_rx_intent_req() - dummy rx intent request functon
* @if_ptr: The transport to transmit on.
* @lcid: The local channel id to encode.
* @size: The requested intent size to encode.
*
* Return: Success.
*/
static int dummy_tx_cmd_rx_intent_req(struct glink_transport_if *if_ptr,
uint32_t lcid, size_t size)
{
return 0;
}
/**
* dummy_tx_cmd_rx_intent_req_ack() - dummy rx intent request ack
* @if_ptr: The transport to transmit on.
* @lcid: The local channel id to encode.
* @granted: The request response to encode.
*
* Return: Success.
*/
static int dummy_tx_cmd_remote_rx_intent_req_ack(
struct glink_transport_if *if_ptr,
uint32_t lcid, bool granted)
{
return 0;
}
/**
* dummy_tx_cmd_set_sigs() - dummy signals ack transmit function
* @if_ptr: The transport to transmit on.
* @lcid: The local channel id to encode.
* @sigs: The signals to encode.
*
* Return: Success.
*/
static int dummy_tx_cmd_set_sigs(struct glink_transport_if *if_ptr,
uint32_t lcid, uint32_t sigs)
{
return 0;
}
/**
* dummy_tx_cmd_ch_close() - dummy channel close transmit function
* @if_ptr: The transport to transmit on.
* @lcid: The local channel id to encode.
*
* Return: Success.
*/
static int dummy_tx_cmd_ch_close(struct glink_transport_if *if_ptr,
uint32_t lcid)
{
return 0;
}
/**
* dummy_tx_cmd_ch_remote_close_ack() - dummy channel close ack sending function
* @if_ptr: The transport to transmit on.
* @rcid: The remote channel id to encode.
*/
static void dummy_tx_cmd_ch_remote_close_ack(struct glink_transport_if *if_ptr,
uint32_t rcid)
{
/* intentionally left blank */
}
/**
* dummy_tx_cmd_ch_open() - dummy channel open cmd sending function
* @if_ptr: The transport to transmit on.
* @lcid: The local channel id to encode.
* @name: The channel name to encode.
* @req_xprt: The transport the core would like to migrate this channel to.
*
* Return: 0 on success or standard Linux error code.
*/
static int dummy_tx_cmd_ch_open(struct glink_transport_if *if_ptr,
uint32_t lcid, const char *name,
uint16_t req_xprt)
{
return -EOPNOTSUPP;
}
/**
* dummy_tx_cmd_ch_remote_open_ack() - convert a channel open ack cmd to wire
* format and transmit
* @if_ptr: The transport to transmit on.
* @rcid: The remote channel id to encode.
* @xprt_resp: The response to a transport migration request.
*/
static void dummy_tx_cmd_ch_remote_open_ack(struct glink_transport_if *if_ptr,
uint32_t rcid, uint16_t xprt_resp)
{
/* intentionally left blank */
}
/**
* notif_if_up_all_xprts() - Check and notify existing transport state if up
* @notif_info: Data structure containing transport information to be notified.
*
* This function is called when the client registers a notifier to know about
* the state of a transport. This function matches the existing transports with
* the transport in the "notif_info" parameter. When a matching transport is
* found, the callback function in the "notif_info" parameter is called with
* the state of the matching transport.
*
* If an edge or transport is not defined, then all edges and/or transports
* will be matched and will receive up notifications.
*/
static void notif_if_up_all_xprts(
struct link_state_notifier_info *notif_info)
{
struct glink_core_xprt_ctx *xprt_ptr;
struct glink_link_state_cb_info cb_info;
cb_info.link_state = GLINK_LINK_STATE_UP;
mutex_lock(&transport_list_lock_lha0);
list_for_each_entry(xprt_ptr, &transport_list, list_node) {
if (strlen(notif_info->edge) &&
strcmp(notif_info->edge, xprt_ptr->edge))
continue;
if (strlen(notif_info->transport) &&
strcmp(notif_info->transport, xprt_ptr->name))
continue;
if (!xprt_is_fully_opened(xprt_ptr))
continue;
cb_info.transport = xprt_ptr->name;
cb_info.edge = xprt_ptr->edge;
notif_info->glink_link_state_notif_cb(&cb_info,
notif_info->priv);
}
mutex_unlock(&transport_list_lock_lha0);
}
/**
* check_link_notifier_and_notify() - Check and notify clients about link state
* @xprt_ptr: Transport whose state to be notified.
* @link_state: State of the transport to be notified.
*
* This function is called when the state of the transport changes. This
* function matches the transport with the clients that have registered to
* be notified about the state changes. When a matching client notifier is
* found, the callback function in the client notifier is called with the
* new state of the transport.
*/
static void check_link_notifier_and_notify(struct glink_core_xprt_ctx *xprt_ptr,
enum glink_link_state link_state)
{
struct link_state_notifier_info *notif_info;
struct glink_link_state_cb_info cb_info;
cb_info.link_state = link_state;
mutex_lock(&link_state_notifier_lock_lha1);
list_for_each_entry(notif_info, &link_state_notifier_list, list) {
if (strlen(notif_info->edge) &&
strcmp(notif_info->edge, xprt_ptr->edge))
continue;
if (strlen(notif_info->transport) &&
strcmp(notif_info->transport, xprt_ptr->name))
continue;
cb_info.transport = xprt_ptr->name;
cb_info.edge = xprt_ptr->edge;
notif_info->glink_link_state_notif_cb(&cb_info,
notif_info->priv);
}
mutex_unlock(&link_state_notifier_lock_lha1);
}
/**
* Open GLINK channel.
*
* @cfg_ptr: Open configuration structure (the structure is copied before
* glink_open returns). All unused fields should be zero-filled.
*
* This should not be called from link state callback context by clients.
* It is recommended that client should invoke this function from their own
* thread.
*
* Return: Pointer to channel on success, PTR_ERR() with standard Linux
* error code on failure.
*/
void *glink_open(const struct glink_open_config *cfg)
{
struct channel_ctx *ctx = NULL;
struct glink_core_xprt_ctx *transport_ptr;
size_t len;
int ret;
uint16_t best_id;
if (!cfg->edge || !cfg->name) {
GLINK_ERR("%s: !cfg->edge || !cfg->name\n", __func__);
return ERR_PTR(-EINVAL);
}
len = strlen(cfg->edge);
if (len == 0 || len >= GLINK_NAME_SIZE) {
GLINK_ERR("%s: [EDGE] len == 0 || len >= GLINK_NAME_SIZE\n",
__func__);
return ERR_PTR(-EINVAL);
}
len = strlen(cfg->name);
if (len == 0 || len >= GLINK_NAME_SIZE) {
GLINK_ERR("%s: [NAME] len == 0 || len >= GLINK_NAME_SIZE\n",
__func__);
return ERR_PTR(-EINVAL);
}
if (cfg->transport) {
len = strlen(cfg->transport);
if (len == 0 || len >= GLINK_NAME_SIZE) {
GLINK_ERR("%s: [TRANSPORT] len == 0 || %s\n",
__func__,
"len >= GLINK_NAME_SIZE");
return ERR_PTR(-EINVAL);
}
}
/* confirm required notification parameters */
if (!(cfg->notify_rx || cfg->notify_rxv) || !cfg->notify_tx_done
|| !cfg->notify_state
|| ((cfg->options & GLINK_OPT_RX_INTENT_NOTIF)
&& !cfg->notify_remote_rx_intent)) {
GLINK_ERR("%s: Incorrect notification parameters\n", __func__);
return ERR_PTR(-EINVAL);
}
/* find transport */
transport_ptr = find_open_transport(cfg->edge, cfg->transport,
cfg->options & GLINK_OPT_INITIAL_XPORT,
&best_id);
if (IS_ERR_OR_NULL(transport_ptr)) {
GLINK_ERR("%s:%s %s: Error %d - unable to find transport\n",
cfg->transport, cfg->edge, __func__,
(unsigned)PTR_ERR(transport_ptr));
return ERR_PTR(-ENODEV);
}
/*
* look for an existing port structure which can occur in
* reopen and remote-open-first cases
*/
ctx = ch_name_to_ch_ctx_create(transport_ptr, cfg->name);
if (ctx == NULL) {
GLINK_ERR("%s:%s %s: Error - unable to allocate new channel\n",
cfg->transport, cfg->edge, __func__);
return ERR_PTR(-ENOMEM);
}
/* port already exists */
if (ctx->local_open_state != GLINK_CHANNEL_CLOSED) {
/* not ready to be re-opened */
GLINK_INFO_CH_XPRT(ctx, transport_ptr,
"%s: Channel not ready to be re-opened. State: %u\n",
__func__, ctx->local_open_state);
return ERR_PTR(-EBUSY);
}
/* initialize port structure */
ctx->user_priv = cfg->priv;
ctx->notify_rx = cfg->notify_rx;
ctx->notify_tx_done = cfg->notify_tx_done;
ctx->notify_state = cfg->notify_state;
ctx->notify_rx_intent_req = cfg->notify_rx_intent_req;
ctx->notify_rxv = cfg->notify_rxv;
ctx->notify_rx_sigs = cfg->notify_rx_sigs;
ctx->notify_rx_abort = cfg->notify_rx_abort;
ctx->notify_tx_abort = cfg->notify_tx_abort;
ctx->notify_rx_tracer_pkt = cfg->notify_rx_tracer_pkt;
ctx->notify_remote_rx_intent = cfg->notify_remote_rx_intent;
if (!ctx->notify_rx_intent_req)
ctx->notify_rx_intent_req = glink_dummy_notify_rx_intent_req;
if (!ctx->notify_rx_sigs)
ctx->notify_rx_sigs = glink_dummy_notify_rx_sigs;
if (!ctx->notify_rx_abort)
ctx->notify_rx_abort = glink_dummy_notify_rx_abort;
if (!ctx->notify_tx_abort)
ctx->notify_tx_abort = glink_dummy_notify_tx_abort;
ctx->local_xprt_req = best_id;
ctx->no_migrate = cfg->transport &&
!(cfg->options & GLINK_OPT_INITIAL_XPORT);
ctx->transport_ptr = transport_ptr;
ctx->local_open_state = GLINK_CHANNEL_OPENING;
GLINK_INFO_PERF_CH(ctx,
"%s: local:GLINK_CHANNEL_CLOSED->GLINK_CHANNEL_OPENING\n",
__func__);
/* start local-open sequence */
ret = ctx->transport_ptr->ops->tx_cmd_ch_open(ctx->transport_ptr->ops,
ctx->lcid, cfg->name, best_id);
if (ret) {
/* failure to send open command (transport failure) */
ctx->local_open_state = GLINK_CHANNEL_CLOSED;
GLINK_ERR_CH(ctx, "%s: Unable to send open command %d\n",
__func__, ret);
return ERR_PTR(ret);
}
GLINK_INFO_CH(ctx, "%s: Created channel, sent OPEN command. ctx %p\n",
__func__, ctx);
return ctx;
}
EXPORT_SYMBOL(glink_open);
/**
* glink_get_channel_id_for_handle() - Get logical channel ID
*
* @handle: handle of channel
*
* Used internally by G-Link debugfs.
*
* Return: Logical Channel ID or standard Linux error code
*/
int glink_get_channel_id_for_handle(void *handle)
{
struct channel_ctx *ctx = (struct channel_ctx *)handle;
if (ctx == NULL)
return -EINVAL;
return ctx->lcid;
}
EXPORT_SYMBOL(glink_get_channel_id_for_handle);
/**
* glink_get_channel_name_for_handle() - return channel name
*
* @handle: handle of channel
*
* Used internally by G-Link debugfs.
*
* Return: Channel name or NULL
*/
char *glink_get_channel_name_for_handle(void *handle)
{
struct channel_ctx *ctx = (struct channel_ctx *)handle;
if (ctx == NULL)
return NULL;
return ctx->name;
}
EXPORT_SYMBOL(glink_get_channel_name_for_handle);
/**
* glink_delete_ch_from_list() - delete the channel from the list
* @ctx: Pointer to channel context.
* @add_flcid: Boolean value to decide whether the lcid should be added or not.
*
* This function deletes the channel from the list along with the debugfs
* information associated with it. It also adds the channel lcid to the free
* lcid list except if the channel is deleted in case of ssr/unregister case.
* It can only called when channel is fully closed.
*/
static void glink_delete_ch_from_list(struct channel_ctx *ctx, bool add_flcid)
{
unsigned long flags;
spin_lock_irqsave(&ctx->transport_ptr->xprt_ctx_lock_lhb1,
flags);
if (!list_empty(&ctx->port_list_node))
list_del_init(&ctx->port_list_node);
spin_unlock_irqrestore(
&ctx->transport_ptr->xprt_ctx_lock_lhb1,
flags);
if (add_flcid)
glink_add_free_lcid_list(ctx);
mutex_lock(&ctx->transport_ptr->xprt_dbgfs_lock_lhb3);
glink_debugfs_remove_channel(ctx, ctx->transport_ptr);
mutex_unlock(&ctx->transport_ptr->xprt_dbgfs_lock_lhb3);
rwref_put(&ctx->ch_state_lhc0);
}
/**
* glink_close() - Close a previously opened channel.
*
* @handle: handle to close
*
* Once the closing process has been completed, the GLINK_LOCAL_DISCONNECTED
* state event will be sent and the channel can be reopened.
*
* Return: 0 on success; -EINVAL for invalid handle, -EBUSY is close is
* already in progress, standard Linux Error code otherwise.
*/
int glink_close(void *handle)
{
struct glink_core_xprt_ctx *xprt_ctx = NULL;
struct channel_ctx *ctx = (struct channel_ctx *)handle;
int ret;
if (!ctx)
return -EINVAL;
GLINK_INFO_CH(ctx, "%s: Closing channel, ctx: %p\n", __func__, ctx);
if (ctx->local_open_state == GLINK_CHANNEL_CLOSED)
return 0;
if (ctx->local_open_state == GLINK_CHANNEL_CLOSING) {
/* close already pending */
return -EBUSY;
}
mutex_lock(&ctx->transport_ptr->tx_ready_mutex_lhb2);
if (!list_empty(&ctx->tx_ready_list_node))
list_del_init(&ctx->tx_ready_list_node);
mutex_unlock(&ctx->transport_ptr->tx_ready_mutex_lhb2);
GLINK_INFO_PERF_CH(ctx,
"%s: local:%u->GLINK_CHANNEL_CLOSING\n",
__func__, ctx->local_open_state);
ctx->local_open_state = GLINK_CHANNEL_CLOSING;
ctx->pending_delete = true;
if (ctx->transport_ptr->local_state != GLINK_XPRT_DOWN) {
ret = ctx->transport_ptr->ops->tx_cmd_ch_close(
ctx->transport_ptr->ops,
ctx->lcid);
} else if (!strcmp(ctx->transport_ptr->name, "dummy")) {
/*
* This check will avoid any race condition when clients call
* glink_close before the dummy xprt swapping happens in link
* down scenario.
*/
ret = 0;
xprt_ctx = ctx->transport_ptr;
rwref_write_get(&xprt_ctx->xprt_state_lhb0);
glink_core_ch_close_ack_common(ctx);
if (ch_is_fully_closed(ctx)) {
glink_delete_ch_from_list(ctx, false);
rwref_put(&xprt_ctx->xprt_state_lhb0);
if (list_empty(&xprt_ctx->channels))
/* For the xprt reference */
rwref_put(&xprt_ctx->xprt_state_lhb0);
} else {
GLINK_ERR_CH(ctx,
"channel Not closed yet local state [%d] remote_state [%d]\n",
ctx->local_open_state, ctx->remote_opened);
}
rwref_write_put(&xprt_ctx->xprt_state_lhb0);
}
return ret;
}
EXPORT_SYMBOL(glink_close);
/**
* glink_tx_common() - Common TX implementation
*
* @handle: handle returned by glink_open()
* @pkt_priv: opaque data value that will be returned to client with
* notify_tx_done notification
* @data: pointer to the data
* @size: size of data
* @vbuf_provider: Virtual Address-space Buffer Provider for the tx buffer.
* @vbuf_provider: Physical Address-space Buffer Provider for the tx buffer.
* @tx_flags: Flags to indicate transmit options
*
* Return: -EINVAL for invalid handle; -EBUSY if channel isn't ready for
* transmit operation (not fully opened); -EAGAIN if remote side
* has not provided a receive intent that is big enough.
*/
static int glink_tx_common(void *handle, void *pkt_priv,
void *data, void *iovec, size_t size,
void * (*vbuf_provider)(void *iovec, size_t offset, size_t *size),
void * (*pbuf_provider)(void *iovec, size_t offset, size_t *size),
uint32_t tx_flags)
{
struct channel_ctx *ctx = (struct channel_ctx *)handle;
uint32_t riid;
int ret = 0;
struct glink_core_tx_pkt *tx_info;
size_t intent_size;
bool is_atomic = tx_flags & GLINK_TX_SINGLE_THREADED;
if (!ctx)
return -EINVAL;
if (!(vbuf_provider || pbuf_provider))
return -EINVAL;
if (!ch_is_fully_opened(ctx))
return -EBUSY;
if (size > GLINK_MAX_PKT_SIZE)
return -EINVAL;
if (is_atomic && (tx_flags & GLINK_TX_REQ_INTENT))
return -EINVAL;
if (unlikely(tx_flags & GLINK_TX_TRACER_PKT)) {
if (!(ctx->transport_ptr->capabilities & GCAP_TRACER_PKT))
return -EOPNOTSUPP;
tracer_pkt_log_event(data, GLINK_CORE_TX);
}
/* find matching rx intent (first-fit algorithm for now) */
if (ch_pop_remote_rx_intent(ctx, size, &riid, &intent_size)) {
if (!(tx_flags & GLINK_TX_REQ_INTENT)) {
/* no rx intent available */
GLINK_ERR_CH(ctx,
"%s: R[%u]:%zu Intent not present for lcid\n",
__func__, riid, size);
return -EAGAIN;
} else {
/* request intent of correct size */
reinit_completion(&ctx->int_req_ack_complete);
ret = ctx->transport_ptr->ops->tx_cmd_rx_intent_req(
ctx->transport_ptr->ops, ctx->lcid, size);
if (ret) {
GLINK_ERR_CH(ctx,
"%s: Request intent failed %d\n",
__func__, ret);
return ret;
}
/* wait for the remote intent req ack */
wait_for_completion(&ctx->int_req_ack_complete);
if (!ctx->int_req_ack) {
GLINK_ERR_CH(ctx,
"%s: Intent Request with size: %zu %s",
__func__, size,
"not granted for lcid\n");
return -EAGAIN;
}
/* wait for the rx_intent from remote side */
do {
wait_for_completion(&ctx->int_req_complete);
reinit_completion(&ctx->int_req_complete);
} while (ch_pop_remote_rx_intent(ctx, size, &riid,
&intent_size));
}
}
if (!is_atomic) {
mutex_lock(&ctx->transport_ptr->tx_ready_mutex_lhb2);
glink_pm_qos_vote(ctx->transport_ptr);
mutex_unlock(&ctx->transport_ptr->tx_ready_mutex_lhb2);
}
GLINK_INFO_PERF_CH(ctx, "%s: R[%u]:%zu data[%p], size[%zu]. TID %u\n",
__func__, riid, intent_size,
data ? data : iovec, size, current->pid);
tx_info = kzalloc(sizeof(struct glink_core_tx_pkt),
is_atomic ? GFP_ATOMIC : GFP_KERNEL);
if (!tx_info) {
GLINK_ERR_CH(ctx, "%s: No memory for allocation\n", __func__);
ch_push_remote_rx_intent(ctx, intent_size, riid);
return -ENOMEM;
}
INIT_LIST_HEAD(&tx_info->list_done);
tx_info->pkt_priv = pkt_priv;
tx_info->data = data;
tx_info->riid = riid;
tx_info->size = size;
tx_info->size_remaining = size;
tx_info->tracer_pkt = tx_flags & GLINK_TX_TRACER_PKT ? true : false;
tx_info->iovec = iovec ? iovec : (void *)tx_info;
tx_info->vprovider = vbuf_provider;
tx_info->pprovider = pbuf_provider;
tx_info->intent_size = intent_size;
/* schedule packet for transmit */
if ((tx_flags & GLINK_TX_SINGLE_THREADED) &&
(ctx->transport_ptr->capabilities & GCAP_INTENTLESS))
return xprt_single_threaded_tx(ctx->transport_ptr,
ctx, tx_info);
else
xprt_schedule_tx(ctx->transport_ptr, ctx, tx_info);
return 0;
}
/**
* glink_tx() - Transmit packet.
*
* @handle: handle returned by glink_open()
* @pkt_priv: opaque data value that will be returned to client with
* notify_tx_done notification
* @data: pointer to the data
* @size: size of data
* @tx_flags: Flags to specify transmit specific options
*
* Return: -EINVAL for invalid handle; -EBUSY if channel isn't ready for
* transmit operation (not fully opened); -EAGAIN if remote side
* has not provided a receive intent that is big enough.
*/
int glink_tx(void *handle, void *pkt_priv, void *data, size_t size,
uint32_t tx_flags)
{
return glink_tx_common(handle, pkt_priv, data, NULL, size,
tx_linear_vbuf_provider, NULL, tx_flags);
}
EXPORT_SYMBOL(glink_tx);
/**
* glink_queue_rx_intent() - Register an intent to receive data.
*
* @handle: handle returned by glink_open()
* @pkt_priv: opaque data type that is returned when a packet is received
* size: maximum size of data to receive
*
* Return: 0 for success; standard Linux error code for failure case
*/
int glink_queue_rx_intent(void *handle, const void *pkt_priv, size_t size)
{
struct channel_ctx *ctx = (struct channel_ctx *)handle;
struct glink_core_rx_intent *intent_ptr;
int ret = 0;
if (!ctx)
return -EINVAL;
if (!ch_is_fully_opened(ctx)) {
/* Can only queue rx intents if channel is fully opened */
GLINK_ERR_CH(ctx, "%s: Channel is not fully opened\n",
__func__);
return -EBUSY;
}
intent_ptr = ch_push_local_rx_intent(ctx, pkt_priv, size);
if (!intent_ptr) {
GLINK_ERR_CH(ctx,
"%s: Intent pointer allocation failed size[%zu]\n",
__func__, size);
return -ENOMEM;
}
GLINK_DBG_CH(ctx, "%s: L[%u]:%zu\n", __func__, intent_ptr->id,
intent_ptr->intent_size);
if (ctx->transport_ptr->capabilities & GCAP_INTENTLESS)
return ret;
/* notify remote side of rx intent */
ret = ctx->transport_ptr->ops->tx_cmd_local_rx_intent(
ctx->transport_ptr->ops, ctx->lcid, size, intent_ptr->id);
if (ret)
/* unable to transmit, dequeue intent */
ch_remove_local_rx_intent(ctx, intent_ptr->id);
return ret;
}
EXPORT_SYMBOL(glink_queue_rx_intent);
/**
* glink_rx_intent_exists() - Check if an intent exists.
*
* @handle: handle returned by glink_open()
* @size: size of an intent to check or 0 for any intent
*
* Return: TRUE if an intent exists with greater than or equal to the size
* else FALSE
*/
bool glink_rx_intent_exists(void *handle, size_t size)
{
struct channel_ctx *ctx = (struct channel_ctx *)handle;
struct glink_core_rx_intent *intent;
unsigned long flags;
if (!ctx || !ch_is_fully_opened(ctx))
return false;
spin_lock_irqsave(&ctx->local_rx_intent_lst_lock_lhc1, flags);
list_for_each_entry(intent, &ctx->local_rx_intent_list, list) {
if (size <= intent->intent_size) {
spin_unlock_irqrestore(
&ctx->local_rx_intent_lst_lock_lhc1, flags);
return true;
}
}
spin_unlock_irqrestore(&ctx->local_rx_intent_lst_lock_lhc1, flags);
return false;
}
EXPORT_SYMBOL(glink_rx_intent_exists);
/**
* glink_rx_done() - Return receive buffer to remote side.
*
* @handle: handle returned by glink_open()
* @ptr: data pointer provided in the notify_rx() call
* @reuse: if true, receive intent is re-used
*
* Return: 0 for success; standard Linux error code for failure case
*/
int glink_rx_done(void *handle, const void *ptr, bool reuse)
{
struct channel_ctx *ctx = (struct channel_ctx *)handle;
struct glink_core_rx_intent *liid_ptr;
uint32_t id;
int ret = 0;
liid_ptr = ch_get_local_rx_intent_notified(ctx, ptr);
if (IS_ERR_OR_NULL(liid_ptr)) {
/* invalid pointer */
GLINK_ERR_CH(ctx, "%s: Invalid pointer %p\n", __func__, ptr);
return -EINVAL;
}
GLINK_INFO_PERF_CH(ctx, "%s: L[%u]: data[%p]. TID %u\n",
__func__, liid_ptr->id, ptr, current->pid);
id = liid_ptr->id;
if (reuse) {
ret = ctx->transport_ptr->ops->reuse_rx_intent(
ctx->transport_ptr->ops, liid_ptr);
if (ret) {
GLINK_ERR_CH(ctx, "%s: Intent reuse err %d for %p\n",
__func__, ret, ptr);
ret = -ENOBUFS;
reuse = false;
ctx->transport_ptr->ops->deallocate_rx_intent(
ctx->transport_ptr->ops, liid_ptr);
}
} else {
ctx->transport_ptr->ops->deallocate_rx_intent(
ctx->transport_ptr->ops, liid_ptr);
}
ch_remove_local_rx_intent_notified(ctx, liid_ptr, reuse);
/* send rx done */
ctx->transport_ptr->ops->tx_cmd_local_rx_done(ctx->transport_ptr->ops,
ctx->lcid, id, reuse);
return ret;
}
EXPORT_SYMBOL(glink_rx_done);
/**
* glink_txv() - Transmit a packet in vector form.
*
* @handle: handle returned by glink_open()
* @pkt_priv: opaque data value that will be returned to client with
* notify_tx_done notification
* @iovec: pointer to the vector (must remain valid until notify_tx_done
* notification)
* @size: size of data/vector
* @vbuf_provider: Client provided helper function to iterate the vector
* in physical address space
* @pbuf_provider: Client provided helper function to iterate the vector
* in virtual address space
* @tx_flags: Flags to specify transmit specific options
*
* Return: -EINVAL for invalid handle; -EBUSY if channel isn't ready for
* transmit operation (not fully opened); -EAGAIN if remote side has
* not provided a receive intent that is big enough.
*/
int glink_txv(void *handle, void *pkt_priv,
void *iovec, size_t size,
void * (*vbuf_provider)(void *iovec, size_t offset, size_t *size),
void * (*pbuf_provider)(void *iovec, size_t offset, size_t *size),
uint32_t tx_flags)
{
return glink_tx_common(handle, pkt_priv, NULL, iovec, size,
vbuf_provider, pbuf_provider, tx_flags);
}
EXPORT_SYMBOL(glink_txv);
/**
* glink_sigs_set() - Set the local signals for the GLINK channel
*
* @handle: handle returned by glink_open()
* @sigs: modified signal value
*
* Return: 0 for success; standard Linux error code for failure case
*/
int glink_sigs_set(void *handle, uint32_t sigs)
{
struct channel_ctx *ctx = (struct channel_ctx *)handle;
int ret;
if (!ctx)
return -EINVAL;
if (!ch_is_fully_opened(ctx)) {
GLINK_ERR_CH(ctx, "%s: Channel is not fully opened\n",
__func__);
return -EBUSY;
}
ctx->lsigs = sigs;
ret = ctx->transport_ptr->ops->tx_cmd_set_sigs(ctx->transport_ptr->ops,
ctx->lcid, ctx->lsigs);
GLINK_INFO_CH(ctx, "%s: Sent SIGNAL SET command\n", __func__);
return ret;
}
EXPORT_SYMBOL(glink_sigs_set);
/**
* glink_sigs_local_get() - Get the local signals for the GLINK channel
*
* handle: handle returned by glink_open()
* sigs: Pointer to hold the signals
*
* Return: 0 for success; standard Linux error code for failure case
*/
int glink_sigs_local_get(void *handle, uint32_t *sigs)
{
struct channel_ctx *ctx = (struct channel_ctx *)handle;
if (!ctx || !sigs)
return -EINVAL;
if (!ch_is_fully_opened(ctx)) {
GLINK_ERR_CH(ctx, "%s: Channel is not fully opened\n",
__func__);
return -EBUSY;
}
*sigs = ctx->lsigs;
return 0;
}
EXPORT_SYMBOL(glink_sigs_local_get);
/**
* glink_sigs_remote_get() - Get the Remote signals for the GLINK channel
*
* handle: handle returned by glink_open()
* sigs: Pointer to hold the signals
*
* Return: 0 for success; standard Linux error code for failure case
*/
int glink_sigs_remote_get(void *handle, uint32_t *sigs)
{
struct channel_ctx *ctx = (struct channel_ctx *)handle;
if (!ctx || !sigs)
return -EINVAL;
if (!ch_is_fully_opened(ctx)) {
GLINK_ERR_CH(ctx, "%s: Channel is not fully opened\n",
__func__);
return -EBUSY;
}
*sigs = ctx->rsigs;
return 0;
}
EXPORT_SYMBOL(glink_sigs_remote_get);
/**
* glink_register_link_state_cb() - Register for link state notification
* @link_info: Data structure containing the link identification and callback.
* @priv: Private information to be passed with the callback.
*
* This function is used to register a notifier to receive the updates about a
* link's/transport's state. This notifier needs to be registered first before
* an attempt to open a channel.
*
* Return: a reference to the notifier handle.
*/
void *glink_register_link_state_cb(struct glink_link_info *link_info,
void *priv)
{
struct link_state_notifier_info *notif_info;
if (!link_info || !link_info->glink_link_state_notif_cb)
return ERR_PTR(-EINVAL);
notif_info = kzalloc(sizeof(*notif_info), GFP_KERNEL);
if (!notif_info) {
GLINK_ERR("%s: Error allocating link state notifier info\n",
__func__);
return ERR_PTR(-ENOMEM);
}
if (link_info->transport)
strlcpy(notif_info->transport, link_info->transport,
GLINK_NAME_SIZE);
if (link_info->edge)
strlcpy(notif_info->edge, link_info->edge, GLINK_NAME_SIZE);
notif_info->priv = priv;
notif_info->glink_link_state_notif_cb =
link_info->glink_link_state_notif_cb;
mutex_lock(&link_state_notifier_lock_lha1);
list_add_tail(&notif_info->list, &link_state_notifier_list);
mutex_unlock(&link_state_notifier_lock_lha1);
notif_if_up_all_xprts(notif_info);
return notif_info;
}
EXPORT_SYMBOL(glink_register_link_state_cb);
/**
* glink_unregister_link_state_cb() - Unregister the link state notification
* notif_handle: Handle to be unregistered.
*
* This function is used to unregister a notifier to stop receiving the updates
* about a link's/ transport's state.
*/
void glink_unregister_link_state_cb(void *notif_handle)
{
struct link_state_notifier_info *notif_info, *tmp_notif_info;
if (IS_ERR_OR_NULL(notif_handle))
return;
mutex_lock(&link_state_notifier_lock_lha1);
list_for_each_entry_safe(notif_info, tmp_notif_info,
&link_state_notifier_list, list) {
if (notif_info == notif_handle) {
list_del(&notif_info->list);
mutex_unlock(&link_state_notifier_lock_lha1);
kfree(notif_info);
return;
}
}
mutex_unlock(&link_state_notifier_lock_lha1);
return;
}
EXPORT_SYMBOL(glink_unregister_link_state_cb);
/**
* glink_rpm_rx_poll() - Poll and receive any available events
* @handle: Channel handle in which this operation is performed.
*
* This function is used to poll and receive events and packets while the
* receive interrupt from RPM is disabled.
*
* Note that even if a return value > 0 is returned indicating that some events
* were processed, clients should only use the notification functions passed
* into glink_open() to determine if an entire packet has been received since
* some events may be internal details that are not visible to clients.
*
* Return: 0 for no packets available; > 0 for events available; standard
* Linux error codes on failure.
*/
int glink_rpm_rx_poll(void *handle)
{
struct channel_ctx *ctx = (struct channel_ctx *)handle;
if (!ctx)
return -EINVAL;
if (!ch_is_fully_opened(ctx))
return -EBUSY;
if (!ctx->transport_ptr ||
!(ctx->transport_ptr->capabilities & GCAP_INTENTLESS))
return -EOPNOTSUPP;
return ctx->transport_ptr->ops->poll(ctx->transport_ptr->ops,
ctx->lcid);
}
EXPORT_SYMBOL(glink_rpm_rx_poll);
/**
* glink_rpm_mask_rx_interrupt() - Mask or unmask the RPM receive interrupt
* @handle: Channel handle in which this operation is performed.
* @mask: Flag to mask or unmask the interrupt.
* @pstruct: Pointer to any platform specific data.
*
* This function is used to mask or unmask the receive interrupt from RPM.
* "mask" set to true indicates masking the interrupt and when set to false
* indicates unmasking the interrupt.
*
* Return: 0 on success, standard Linux error codes on failure.
*/
int glink_rpm_mask_rx_interrupt(void *handle, bool mask, void *pstruct)
{
struct channel_ctx *ctx = (struct channel_ctx *)handle;
if (!ctx)
return -EINVAL;
if (!ch_is_fully_opened(ctx))
return -EBUSY;
if (!ctx->transport_ptr ||
!(ctx->transport_ptr->capabilities & GCAP_INTENTLESS))
return -EOPNOTSUPP;
return ctx->transport_ptr->ops->mask_rx_irq(ctx->transport_ptr->ops,
ctx->lcid, mask, pstruct);
}
EXPORT_SYMBOL(glink_rpm_mask_rx_interrupt);
/**
* glink_wait_link_down() - Get status of link
* @handle: Channel handle in which this operation is performed
*
* This function will query the transport for its status, to allow clients to
* proceed in cleanup operations.
*/
int glink_wait_link_down(void *handle)
{
struct channel_ctx *ctx = (struct channel_ctx *)handle;
if (!ctx)
return -EINVAL;
if (!ctx->transport_ptr)
return -EOPNOTSUPP;
return ctx->transport_ptr->ops->wait_link_down(ctx->transport_ptr->ops);
}
EXPORT_SYMBOL(glink_wait_link_down);
/**
* glink_xprt_ctx_release - Free the transport context
* @ch_st_lock: handle to the rwref_lock associated with the transport
*
* This should only be called when the reference count associated with the
* transport goes to zero.
*/
void glink_xprt_ctx_release(struct rwref_lock *xprt_st_lock)
{
struct glink_dbgfs xprt_rm_dbgfs;
struct glink_core_xprt_ctx *xprt_ctx = container_of(xprt_st_lock,
struct glink_core_xprt_ctx, xprt_state_lhb0);
GLINK_INFO("%s: freeing transport [%s->%s]context\n", __func__,
xprt_ctx->name,
xprt_ctx->edge);
xprt_rm_dbgfs.curr_name = xprt_ctx->name;
xprt_rm_dbgfs.par_name = "xprt";
glink_debugfs_remove_recur(&xprt_rm_dbgfs);
GLINK_INFO("%s: xprt debugfs removec\n", __func__);
destroy_workqueue(xprt_ctx->tx_wq);
kfree(xprt_ctx);
xprt_ctx = NULL;
}
/**
* glink_dummy_xprt_ctx_release - free the dummy transport context
* @xprt_st_lock: Handle to the rwref_lock associated with the transport.
*
* The release function is called when all the channels on this dummy
* transport are closed and the reference count goes to zero.
*/
static void glink_dummy_xprt_ctx_release(struct rwref_lock *xprt_st_lock)
{
struct glink_core_xprt_ctx *xprt_ctx = container_of(xprt_st_lock,
struct glink_core_xprt_ctx, xprt_state_lhb0);
GLINK_INFO("%s: freeing transport [%s->%s]context\n", __func__,
xprt_ctx->name,
xprt_ctx->edge);
kfree(xprt_ctx);
}
/**
* glink_xprt_name_to_id() - convert transport name to id
* @name: Name of the transport.
* @id: Assigned id.
*
* Return: 0 on success or standard Linux error code.
*/
int glink_xprt_name_to_id(const char *name, uint16_t *id)
{
if (!strcmp(name, "smem")) {
*id = SMEM_XPRT_ID;
return 0;
}
if (!strcmp(name, "smd_trans")) {
*id = SMD_TRANS_XPRT_ID;
return 0;
}
if (!strcmp(name, "lloop")) {
*id = LLOOP_XPRT_ID;
return 0;
}
if (!strcmp(name, "mock")) {
*id = MOCK_XPRT_ID;
return 0;
}
if (!strcmp(name, "mock_low")) {
*id = MOCK_XPRT_LOW_ID;
return 0;
}
if (!strcmp(name, "mock_high")) {
*id = MOCK_XPRT_HIGH_ID;
return 0;
}
return -ENODEV;
}
EXPORT_SYMBOL(glink_xprt_name_to_id);
/**
* glink_core_register_transport() - register a new transport
* @if_ptr: The interface to the transport.
* @cfg: Description and configuration of the transport.
*
* Return: 0 on success, EINVAL for invalid input.
*/
int glink_core_register_transport(struct glink_transport_if *if_ptr,
struct glink_core_transport_cfg *cfg)
{
struct glink_core_xprt_ctx *xprt_ptr;
size_t len;
uint16_t id;
int ret;
if (!if_ptr || !cfg || !cfg->name || !cfg->edge)
return -EINVAL;
len = strlen(cfg->name);
if (len == 0 || len >= GLINK_NAME_SIZE)
return -EINVAL;
len = strlen(cfg->edge);
if (len == 0 || len >= GLINK_NAME_SIZE)
return -EINVAL;
if (cfg->versions_entries < 1)
return -EINVAL;
ret = glink_xprt_name_to_id(cfg->name, &id);
if (ret)
return ret;
xprt_ptr = kzalloc(sizeof(struct glink_core_xprt_ctx), GFP_KERNEL);
if (xprt_ptr == NULL)
return -ENOMEM;
xprt_ptr->id = id;
rwref_lock_init(&xprt_ptr->xprt_state_lhb0,
glink_xprt_ctx_release);
strlcpy(xprt_ptr->name, cfg->name, GLINK_NAME_SIZE);
strlcpy(xprt_ptr->edge, cfg->edge, GLINK_NAME_SIZE);
xprt_ptr->versions = cfg->versions;
xprt_ptr->versions_entries = cfg->versions_entries;
xprt_ptr->local_version_idx = cfg->versions_entries - 1;
xprt_ptr->remote_version_idx = cfg->versions_entries - 1;
xprt_ptr->l_features =
cfg->versions[cfg->versions_entries - 1].features;
if (!if_ptr->poll)
if_ptr->poll = dummy_poll;
if (!if_ptr->mask_rx_irq)
if_ptr->mask_rx_irq = dummy_mask_rx_irq;
if (!if_ptr->reuse_rx_intent)
if_ptr->reuse_rx_intent = dummy_reuse_rx_intent;
if (!if_ptr->wait_link_down)
if_ptr->wait_link_down = dummy_wait_link_down;
if (!if_ptr->tx_cmd_tracer_pkt)
if_ptr->tx_cmd_tracer_pkt = dummy_tx_cmd_tracer_pkt;
xprt_ptr->capabilities = 0;
xprt_ptr->ops = if_ptr;
spin_lock_init(&xprt_ptr->xprt_ctx_lock_lhb1);
xprt_ptr->next_lcid = 1; /* 0 reserved for default unconfigured */
INIT_LIST_HEAD(&xprt_ptr->free_lcid_list);
xprt_ptr->max_cid = cfg->max_cid;
xprt_ptr->max_iid = cfg->max_iid;
xprt_ptr->local_state = GLINK_XPRT_DOWN;
xprt_ptr->remote_neg_completed = false;
INIT_LIST_HEAD(&xprt_ptr->channels);
INIT_LIST_HEAD(&xprt_ptr->tx_ready);
mutex_init(&xprt_ptr->tx_ready_mutex_lhb2);
mutex_init(&xprt_ptr->xprt_dbgfs_lock_lhb3);
INIT_WORK(&xprt_ptr->tx_work, tx_work_func);
xprt_ptr->tx_wq = create_singlethread_workqueue("glink_tx");
if (IS_ERR_OR_NULL(xprt_ptr->tx_wq)) {
GLINK_ERR("%s: unable to allocate workqueue\n", __func__);
kfree(xprt_ptr);
return -ENOMEM;
}
INIT_DELAYED_WORK(&xprt_ptr->pm_qos_work, glink_pm_qos_cancel_worker);
pm_qos_add_request(&xprt_ptr->pm_qos_req, PM_QOS_CPU_DMA_LATENCY,
PM_QOS_DEFAULT_VALUE);
if_ptr->glink_core_priv = xprt_ptr;
if_ptr->glink_core_if_ptr = &core_impl;
mutex_lock(&transport_list_lock_lha0);
list_add_tail(&xprt_ptr->list_node, &transport_list);
mutex_unlock(&transport_list_lock_lha0);
glink_debugfs_add_xprt(xprt_ptr);
return 0;
}
EXPORT_SYMBOL(glink_core_register_transport);
/**
* glink_core_unregister_transport() - unregister a transport
*
* @if_ptr: The interface to the transport.
*/
void glink_core_unregister_transport(struct glink_transport_if *if_ptr)
{
struct glink_core_xprt_ctx *xprt_ptr = if_ptr->glink_core_priv;
GLINK_DBG_XPRT(xprt_ptr, "%s: destroying transport\n", __func__);
if (xprt_ptr->local_state != GLINK_XPRT_DOWN) {
GLINK_ERR_XPRT(xprt_ptr,
"%s: link_down should have been called before this\n",
__func__);
return;
}
mutex_lock(&transport_list_lock_lha0);
list_del(&xprt_ptr->list_node);
mutex_unlock(&transport_list_lock_lha0);
flush_delayed_work(&xprt_ptr->pm_qos_work);
pm_qos_remove_request(&xprt_ptr->pm_qos_req);
rwref_put(&xprt_ptr->xprt_state_lhb0);
}
EXPORT_SYMBOL(glink_core_unregister_transport);
/**
* glink_core_link_up() - transport link-up notification
*
* @if_ptr: pointer to transport interface
*/
static void glink_core_link_up(struct glink_transport_if *if_ptr)
{
struct glink_core_xprt_ctx *xprt_ptr = if_ptr->glink_core_priv;
/* start local negotiation */
xprt_ptr->local_state = GLINK_XPRT_NEGOTIATING;
xprt_ptr->local_version_idx = xprt_ptr->versions_entries - 1;
xprt_ptr->l_features =
xprt_ptr->versions[xprt_ptr->local_version_idx].features;
if_ptr->tx_cmd_version(if_ptr,
xprt_ptr->versions[xprt_ptr->local_version_idx].version,
xprt_ptr->versions[xprt_ptr->local_version_idx].features);
}
/**
* glink_core_link_down() - transport link-down notification
*
* @if_ptr: pointer to transport interface
*/
static void glink_core_link_down(struct glink_transport_if *if_ptr)
{
struct glink_core_xprt_ctx *xprt_ptr = if_ptr->glink_core_priv;
rwref_write_get(&xprt_ptr->xprt_state_lhb0);
xprt_ptr->next_lcid = 1;
xprt_ptr->local_state = GLINK_XPRT_DOWN;
xprt_ptr->local_version_idx = xprt_ptr->versions_entries - 1;
xprt_ptr->remote_version_idx = xprt_ptr->versions_entries - 1;
xprt_ptr->l_features =
xprt_ptr->versions[xprt_ptr->local_version_idx].features;
xprt_ptr->remote_neg_completed = false;
rwref_write_put(&xprt_ptr->xprt_state_lhb0);
GLINK_DBG_XPRT(xprt_ptr,
"%s: Flushing work from tx_wq. Thread: %u\n", __func__,
current->pid);
flush_workqueue(xprt_ptr->tx_wq);
glink_core_channel_cleanup(xprt_ptr);
check_link_notifier_and_notify(xprt_ptr, GLINK_LINK_STATE_DOWN);
}
/**
* glink_create_dummy_xprt_ctx() - create a dummy transport that replaces all
* the transport interface functions with a dummy
* @orig_xprt_ctx: Pointer to the original transport context.
*
* The dummy transport is used only when it is swapped with the actual transport
* pointer in ssr/unregister case.
*
* Return: Pointer to dummy transport context.
*/
static struct glink_core_xprt_ctx *glink_create_dummy_xprt_ctx(
struct glink_core_xprt_ctx *orig_xprt_ctx)
{
struct glink_core_xprt_ctx *xprt_ptr;
struct glink_transport_if *if_ptr;
xprt_ptr = kzalloc(sizeof(*xprt_ptr), GFP_KERNEL);
if (!xprt_ptr)
return ERR_PTR(-ENOMEM);
if_ptr = kmalloc(sizeof(*if_ptr), GFP_KERNEL);
if (!if_ptr) {
kfree(xprt_ptr);
return ERR_PTR(-ENOMEM);
}
rwref_lock_init(&xprt_ptr->xprt_state_lhb0,
glink_dummy_xprt_ctx_release);
strlcpy(xprt_ptr->name, "dummy", GLINK_NAME_SIZE);
strlcpy(xprt_ptr->edge, orig_xprt_ctx->edge, GLINK_NAME_SIZE);
if_ptr->poll = dummy_poll;
if_ptr->mask_rx_irq = dummy_mask_rx_irq;
if_ptr->reuse_rx_intent = dummy_reuse_rx_intent;
if_ptr->wait_link_down = dummy_wait_link_down;
if_ptr->allocate_rx_intent = dummy_allocate_rx_intent;
if_ptr->deallocate_rx_intent = dummy_deallocate_rx_intent;
if_ptr->tx_cmd_local_rx_intent = dummy_tx_cmd_local_rx_intent;
if_ptr->tx_cmd_local_rx_done = dummy_tx_cmd_local_rx_done;
if_ptr->tx = dummy_tx;
if_ptr->tx_cmd_rx_intent_req = dummy_tx_cmd_rx_intent_req;
if_ptr->tx_cmd_remote_rx_intent_req_ack =
dummy_tx_cmd_remote_rx_intent_req_ack;
if_ptr->tx_cmd_set_sigs = dummy_tx_cmd_set_sigs;
if_ptr->tx_cmd_ch_open = dummy_tx_cmd_ch_open;
if_ptr->tx_cmd_ch_remote_open_ack = dummy_tx_cmd_ch_remote_open_ack;
if_ptr->tx_cmd_ch_close = dummy_tx_cmd_ch_close;
if_ptr->tx_cmd_ch_remote_close_ack = dummy_tx_cmd_ch_remote_close_ack;
if_ptr->tx_cmd_tracer_pkt = dummy_tx_cmd_tracer_pkt;
xprt_ptr->ops = if_ptr;
spin_lock_init(&xprt_ptr->xprt_ctx_lock_lhb1);
INIT_LIST_HEAD(&xprt_ptr->free_lcid_list);
xprt_ptr->local_state = GLINK_XPRT_DOWN;
xprt_ptr->remote_neg_completed = false;
INIT_LIST_HEAD(&xprt_ptr->channels);
INIT_LIST_HEAD(&xprt_ptr->tx_ready);
mutex_init(&xprt_ptr->tx_ready_mutex_lhb2);
mutex_init(&xprt_ptr->xprt_dbgfs_lock_lhb3);
return xprt_ptr;
}
/**
* glink_core_channel_cleanup() - cleanup all channels for the transport
*
* @xprt_ptr: pointer to transport context
*
* This function should be called either from link_down or ssr
*/
static void glink_core_channel_cleanup(struct glink_core_xprt_ctx *xprt_ptr)
{
unsigned long flags, d_flags;
struct channel_ctx *ctx, *tmp_ctx;
struct channel_lcid *temp_lcid, *temp_lcid1;
struct glink_core_xprt_ctx *dummy_xprt_ctx;
dummy_xprt_ctx = glink_create_dummy_xprt_ctx(xprt_ptr);
if (IS_ERR_OR_NULL(dummy_xprt_ctx)) {
GLINK_ERR("%s: Dummy Transport creation failed\n", __func__);
return;
}
rwref_get(&dummy_xprt_ctx->xprt_state_lhb0);
spin_lock_irqsave(&xprt_ptr->xprt_ctx_lock_lhb1, flags);
list_for_each_entry_safe(ctx, tmp_ctx, &xprt_ptr->channels,
port_list_node) {
rwref_get(&ctx->ch_state_lhc0);
if (ctx->local_open_state == GLINK_CHANNEL_OPENED ||
ctx->local_open_state == GLINK_CHANNEL_OPENING) {
rwref_get(&dummy_xprt_ctx->xprt_state_lhb0);
spin_lock_irqsave(&dummy_xprt_ctx->xprt_ctx_lock_lhb1,
d_flags);
list_move_tail(&ctx->port_list_node,
&dummy_xprt_ctx->channels);
spin_unlock_irqrestore(
&dummy_xprt_ctx->xprt_ctx_lock_lhb1, d_flags);
ctx->transport_ptr = dummy_xprt_ctx;
} else {
/* local state is in either CLOSED or CLOSING */
spin_unlock_irqrestore(&xprt_ptr->xprt_ctx_lock_lhb1,
flags);
glink_core_remote_close_common(ctx);
if (ctx->local_open_state == GLINK_CHANNEL_CLOSING)
glink_core_ch_close_ack_common(ctx);
/* Channel should be fully closed now. Delete here */
if (ch_is_fully_closed(ctx))
glink_delete_ch_from_list(ctx, false);
spin_lock_irqsave(&xprt_ptr->xprt_ctx_lock_lhb1, flags);
}
rwref_put(&ctx->ch_state_lhc0);
}
list_for_each_entry_safe(temp_lcid, temp_lcid1,
&xprt_ptr->free_lcid_list, list_node) {
list_del(&temp_lcid->list_node);
kfree(&temp_lcid->list_node);
}
spin_unlock_irqrestore(&xprt_ptr->xprt_ctx_lock_lhb1, flags);
spin_lock_irqsave(&dummy_xprt_ctx->xprt_ctx_lock_lhb1, d_flags);
list_for_each_entry_safe(ctx, tmp_ctx, &dummy_xprt_ctx->channels,
port_list_node) {
rwref_get(&ctx->ch_state_lhc0);
spin_unlock_irqrestore(&dummy_xprt_ctx->xprt_ctx_lock_lhb1,
d_flags);
glink_core_remote_close_common(ctx);
spin_lock_irqsave(&dummy_xprt_ctx->xprt_ctx_lock_lhb1,
d_flags);
rwref_put(&ctx->ch_state_lhc0);
}
spin_unlock_irqrestore(&dummy_xprt_ctx->xprt_ctx_lock_lhb1, d_flags);
rwref_put(&dummy_xprt_ctx->xprt_state_lhb0);
}
/**
* glink_core_rx_cmd_version() - receive version/features from remote system
*
* @if_ptr: pointer to transport interface
* @r_version: remote version
* @r_features: remote features
*
* This function is called in response to a remote-initiated version/feature
* negotiation sequence.
*/
static void glink_core_rx_cmd_version(struct glink_transport_if *if_ptr,
uint32_t r_version, uint32_t r_features)
{
struct glink_core_xprt_ctx *xprt_ptr = if_ptr->glink_core_priv;
const struct glink_core_version *versions = xprt_ptr->versions;
bool neg_complete = false;
uint32_t l_version;
if (xprt_is_fully_opened(xprt_ptr)) {
GLINK_ERR_XPRT(xprt_ptr,
"%s: Negotiation already complete\n", __func__);
return;
}
l_version = versions[xprt_ptr->remote_version_idx].version;
GLINK_INFO_XPRT(xprt_ptr,
"%s: [local]%x:%08x [remote]%x:%08x\n", __func__,
l_version, xprt_ptr->l_features, r_version, r_features);
if (l_version > r_version) {
/* Find matching version */
while (true) {
uint32_t rver_idx;
if (xprt_ptr->remote_version_idx == 0) {
/* version negotiation failed */
GLINK_ERR_XPRT(xprt_ptr,
"%s: Transport negotiation failed\n",
__func__);
l_version = 0;
xprt_ptr->l_features = 0;
break;
}
--xprt_ptr->remote_version_idx;
rver_idx = xprt_ptr->remote_version_idx;
if (versions[rver_idx].version <= r_version) {
/* found a potential match */
l_version = versions[rver_idx].version;
xprt_ptr->l_features =
versions[rver_idx].features;
break;
}
}
}
if (l_version == r_version) {
GLINK_INFO_XPRT(xprt_ptr,
"%s: Remote and local version are matched %x:%08x\n",
__func__, r_version, r_features);
if (xprt_ptr->l_features != r_features) {
uint32_t rver_idx = xprt_ptr->remote_version_idx;
xprt_ptr->l_features = versions[rver_idx]
.negotiate_features(if_ptr,
&xprt_ptr->versions[rver_idx],
r_features);
GLINK_INFO_XPRT(xprt_ptr,
"%s: negotiate features %x:%08x\n",
__func__, l_version, xprt_ptr->l_features);
}
neg_complete = true;
}
if_ptr->tx_cmd_version_ack(if_ptr, l_version, xprt_ptr->l_features);
if (neg_complete) {
GLINK_INFO_XPRT(xprt_ptr,
"%s: Remote negotiation complete %x:%08x\n", __func__,
l_version, xprt_ptr->l_features);
if (xprt_ptr->local_state == GLINK_XPRT_OPENED) {
xprt_ptr->capabilities = if_ptr->set_version(if_ptr,
l_version,
xprt_ptr->l_features);
}
if_ptr->glink_core_priv->remote_neg_completed = true;
if (xprt_is_fully_opened(xprt_ptr))
check_link_notifier_and_notify(xprt_ptr,
GLINK_LINK_STATE_UP);
}
}
/**
* glink_core_rx_cmd_version_ack() - receive negotiation ack from remote system
*
* @if_ptr: pointer to transport interface
* @r_version: remote version response
* @r_features: remote features response
*
* This function is called in response to a local-initiated version/feature
* negotiation sequence and is the counter-offer from the remote side based
* upon the initial version and feature set requested.
*/
static void glink_core_rx_cmd_version_ack(struct glink_transport_if *if_ptr,
uint32_t r_version, uint32_t r_features)
{
struct glink_core_xprt_ctx *xprt_ptr = if_ptr->glink_core_priv;
const struct glink_core_version *versions = xprt_ptr->versions;
uint32_t l_version;
bool neg_complete = false;
if (xprt_is_fully_opened(xprt_ptr)) {
GLINK_ERR_XPRT(xprt_ptr,
"%s: Negotiation already complete\n", __func__);
return;
}
l_version = versions[xprt_ptr->local_version_idx].version;
GLINK_INFO_XPRT(xprt_ptr,
"%s: [local]%x:%08x [remote]%x:%08x\n", __func__,
l_version, xprt_ptr->l_features, r_version, r_features);
if (l_version > r_version) {
/* find matching version */
while (true) {
uint32_t lver_idx = xprt_ptr->local_version_idx;
if (xprt_ptr->local_version_idx == 0) {
/* version negotiation failed */
xprt_ptr->local_state = GLINK_XPRT_FAILED;
GLINK_ERR_XPRT(xprt_ptr,
"%s: Transport negotiation failed\n",
__func__);
l_version = 0;
xprt_ptr->l_features = 0;
break;
}
--xprt_ptr->local_version_idx;
lver_idx = xprt_ptr->local_version_idx;
if (versions[lver_idx].version <= r_version) {
/* found a potential match */
l_version = versions[lver_idx].version;
xprt_ptr->l_features =
versions[lver_idx].features;
break;
}
}
} else if (l_version == r_version) {
if (xprt_ptr->l_features != r_features) {
/* version matches, negotiate features */
uint32_t lver_idx = xprt_ptr->local_version_idx;
xprt_ptr->l_features = versions[lver_idx]
.negotiate_features(if_ptr,
&versions[lver_idx],
r_features);
GLINK_INFO_XPRT(xprt_ptr,
"%s: negotiation features %x:%08x\n",
__func__, l_version, xprt_ptr->l_features);
} else {
neg_complete = true;
}
} else {
/*
* r_version > l_version
*
* Remote responded with a version greater than what we
* requested which is invalid and is treated as failure of the
* negotiation algorithm.
*/
GLINK_ERR_XPRT(xprt_ptr,
"%s: [local]%x:%08x [remote]%x:%08x neg failure\n",
__func__, l_version, xprt_ptr->l_features, r_version,
r_features);
xprt_ptr->local_state = GLINK_XPRT_FAILED;
l_version = 0;
xprt_ptr->l_features = 0;
}
if (neg_complete) {
/* negotiation complete */
GLINK_INFO_XPRT(xprt_ptr,
"%s: Local negotiation complete %x:%08x\n",
__func__, l_version, xprt_ptr->l_features);
if (xprt_ptr->remote_neg_completed) {
xprt_ptr->capabilities = if_ptr->set_version(if_ptr,
l_version,
xprt_ptr->l_features);
}
xprt_ptr->local_state = GLINK_XPRT_OPENED;
if (xprt_is_fully_opened(xprt_ptr))
check_link_notifier_and_notify(xprt_ptr,
GLINK_LINK_STATE_UP);
} else {
if_ptr->tx_cmd_version(if_ptr, l_version, xprt_ptr->l_features);
}
}
/**
* find_l_ctx_get() - find a local channel context based on a remote one
* @r_ctx: The remote channel to use as a lookup key.
*
* If the channel is found, the reference count is incremented to ensure the
* lifetime of the channel context. The caller must call rwref_put() when done.
*
* Return: The corresponding local ctx or NULL is not found.
*/
static struct channel_ctx *find_l_ctx_get(struct channel_ctx *r_ctx)
{
struct glink_core_xprt_ctx *xprt;
struct channel_ctx *ctx;
unsigned long flags;
struct channel_ctx *l_ctx = NULL;
mutex_lock(&transport_list_lock_lha0);
list_for_each_entry(xprt, &transport_list, list_node)
if (!strcmp(r_ctx->transport_ptr->edge, xprt->edge)) {
rwref_write_get(&xprt->xprt_state_lhb0);
if (xprt->local_state != GLINK_XPRT_OPENED) {
rwref_write_put(&xprt->xprt_state_lhb0);
continue;
}
spin_lock_irqsave(&xprt->xprt_ctx_lock_lhb1, flags);
list_for_each_entry(ctx, &xprt->channels,
port_list_node)
if (!strcmp(ctx->name, r_ctx->name) &&
ctx->local_xprt_req &&
ctx->local_xprt_resp) {
l_ctx = ctx;
rwref_get(&l_ctx->ch_state_lhc0);
}
spin_unlock_irqrestore(&xprt->xprt_ctx_lock_lhb1,
flags);
rwref_write_put(&xprt->xprt_state_lhb0);
}
mutex_unlock(&transport_list_lock_lha0);
return l_ctx;
}
/**
* find_r_ctx_get() - find a remote channel context based on a local one
* @l_ctx: The local channel to use as a lookup key.
*
* If the channel is found, the reference count is incremented to ensure the
* lifetime of the channel context. The caller must call rwref_put() when done.
*
* Return: The corresponding remote ctx or NULL is not found.
*/
static struct channel_ctx *find_r_ctx_get(struct channel_ctx *l_ctx)
{
struct glink_core_xprt_ctx *xprt;
struct channel_ctx *ctx;
unsigned long flags;
struct channel_ctx *r_ctx = NULL;
mutex_lock(&transport_list_lock_lha0);
list_for_each_entry(xprt, &transport_list, list_node)
if (!strcmp(l_ctx->transport_ptr->edge, xprt->edge)) {
rwref_write_get(&xprt->xprt_state_lhb0);
if (xprt->local_state != GLINK_XPRT_OPENED) {
rwref_write_put(&xprt->xprt_state_lhb0);
continue;
}
spin_lock_irqsave(&xprt->xprt_ctx_lock_lhb1, flags);
list_for_each_entry(ctx, &xprt->channels,
port_list_node)
if (!strcmp(ctx->name, l_ctx->name) &&
ctx->remote_xprt_req &&
ctx->remote_xprt_resp) {
r_ctx = ctx;
rwref_get(&r_ctx->ch_state_lhc0);
}
spin_unlock_irqrestore(&xprt->xprt_ctx_lock_lhb1,
flags);
rwref_write_put(&xprt->xprt_state_lhb0);
}
mutex_unlock(&transport_list_lock_lha0);
return r_ctx;
}
/**
* will_migrate() - will a channel migrate to a different transport
* @l_ctx: The local channel to migrate.
* @r_ctx: The remote channel to migrate.
*
* One of the channel contexts can be NULL if not known, but at least one ctx
* must be provided.
*
* Return: Bool indicating if migration will occur.
*/
static bool will_migrate(struct channel_ctx *l_ctx, struct channel_ctx *r_ctx)
{
uint16_t new_xprt;
bool migrate = false;
if (!r_ctx)
r_ctx = find_r_ctx_get(l_ctx);
else
rwref_get(&r_ctx->ch_state_lhc0);
if (!r_ctx)
return migrate;
if (!l_ctx)
l_ctx = find_l_ctx_get(r_ctx);
else
rwref_get(&l_ctx->ch_state_lhc0);
if (!l_ctx)
goto exit;
if (l_ctx->local_xprt_req == r_ctx->remote_xprt_req &&
l_ctx->local_xprt_req == l_ctx->transport_ptr->id)
goto exit;
if (l_ctx->no_migrate)
goto exit;
if (l_ctx->local_xprt_req > r_ctx->transport_ptr->id)
l_ctx->local_xprt_req = r_ctx->transport_ptr->id;
new_xprt = max(l_ctx->local_xprt_req, r_ctx->remote_xprt_req);
if (new_xprt == l_ctx->transport_ptr->id)
goto exit;
migrate = true;
exit:
if (l_ctx)
rwref_put(&l_ctx->ch_state_lhc0);
if (r_ctx)
rwref_put(&r_ctx->ch_state_lhc0);
return migrate;
}
/**
* ch_migrate() - migrate a channel to a different transport
* @l_ctx: The local channel to migrate.
* @r_ctx: The remote channel to migrate.
*
* One of the channel contexts can be NULL if not known, but at least one ctx
* must be provided.
*
* Return: Bool indicating if migration occurred.
*/
static bool ch_migrate(struct channel_ctx *l_ctx, struct channel_ctx *r_ctx)
{
uint16_t new_xprt;
struct glink_core_xprt_ctx *xprt;
unsigned long flags;
struct channel_lcid *flcid;
uint16_t best_xprt = USHRT_MAX;
struct channel_ctx *ctx_clone;
bool migrated = false;
if (!r_ctx)
r_ctx = find_r_ctx_get(l_ctx);
else
rwref_get(&r_ctx->ch_state_lhc0);
if (!r_ctx)
return migrated;
if (!l_ctx)
l_ctx = find_l_ctx_get(r_ctx);
else
rwref_get(&l_ctx->ch_state_lhc0);
if (!l_ctx) {
rwref_put(&r_ctx->ch_state_lhc0);
return migrated;
}
if (l_ctx->local_xprt_req == r_ctx->remote_xprt_req &&
l_ctx->local_xprt_req == l_ctx->transport_ptr->id)
goto exit;
if (l_ctx->no_migrate)
goto exit;
if (l_ctx->local_xprt_req > r_ctx->transport_ptr->id)
l_ctx->local_xprt_req = r_ctx->transport_ptr->id;
new_xprt = max(l_ctx->local_xprt_req, r_ctx->remote_xprt_req);
if (new_xprt == l_ctx->transport_ptr->id)
goto exit;
ctx_clone = kmalloc(sizeof(*ctx_clone), GFP_KERNEL);
if (!ctx_clone)
goto exit;
mutex_lock(&transport_list_lock_lha0);
list_for_each_entry(xprt, &transport_list, list_node)
if (!strcmp(l_ctx->transport_ptr->edge, xprt->edge))
if (xprt->id == new_xprt)
break;
mutex_unlock(&transport_list_lock_lha0);
spin_lock_irqsave(&l_ctx->transport_ptr->xprt_ctx_lock_lhb1, flags);
list_del_init(&l_ctx->port_list_node);
spin_unlock_irqrestore(&l_ctx->transport_ptr->xprt_ctx_lock_lhb1,
flags);
memcpy(ctx_clone, l_ctx, sizeof(*ctx_clone));
ctx_clone->local_xprt_req = 0;
ctx_clone->local_xprt_resp = 0;
ctx_clone->remote_xprt_req = 0;
ctx_clone->remote_xprt_resp = 0;
ctx_clone->notify_state = NULL;
ctx_clone->local_open_state = GLINK_CHANNEL_CLOSING;
rwref_lock_init(&ctx_clone->ch_state_lhc0, glink_ch_ctx_release);
init_completion(&ctx_clone->int_req_ack_complete);
init_completion(&ctx_clone->int_req_complete);
spin_lock_init(&ctx_clone->local_rx_intent_lst_lock_lhc1);
spin_lock_init(&ctx_clone->rmt_rx_intent_lst_lock_lhc2);
INIT_LIST_HEAD(&ctx_clone->tx_ready_list_node);
INIT_LIST_HEAD(&ctx_clone->local_rx_intent_list);
INIT_LIST_HEAD(&ctx_clone->local_rx_intent_ntfy_list);
INIT_LIST_HEAD(&ctx_clone->local_rx_intent_free_list);
INIT_LIST_HEAD(&ctx_clone->rmt_rx_intent_list);
INIT_LIST_HEAD(&ctx_clone->tx_active);
spin_lock_init(&ctx_clone->tx_pending_rmt_done_lock_lhc4);
INIT_LIST_HEAD(&ctx_clone->tx_pending_remote_done);
mutex_init(&ctx_clone->tx_lists_mutex_lhc3);
spin_lock_irqsave(&l_ctx->transport_ptr->xprt_ctx_lock_lhb1, flags);
list_add_tail(&ctx_clone->port_list_node,
&l_ctx->transport_ptr->channels);
spin_unlock_irqrestore(&l_ctx->transport_ptr->xprt_ctx_lock_lhb1,
flags);
l_ctx->transport_ptr->ops->tx_cmd_ch_close(l_ctx->transport_ptr->ops,
l_ctx->lcid);
l_ctx->transport_ptr = xprt;
l_ctx->local_xprt_req = 0;
l_ctx->local_xprt_resp = 0;
if (new_xprt != r_ctx->transport_ptr->id) {
r_ctx->local_xprt_req = 0;
r_ctx->local_xprt_resp = 0;
r_ctx->remote_xprt_req = 0;
r_ctx->remote_xprt_resp = 0;
l_ctx->remote_xprt_req = 0;
l_ctx->remote_xprt_resp = 0;
l_ctx->remote_opened = false;
rwref_write_get(&xprt->xprt_state_lhb0);
spin_lock_irqsave(&xprt->xprt_ctx_lock_lhb1, flags);
if (list_empty(&xprt->free_lcid_list)) {
l_ctx->lcid = xprt->next_lcid++;
} else {
flcid = list_first_entry(&xprt->free_lcid_list,
struct channel_lcid, list_node);
l_ctx->lcid = flcid->lcid;
list_del(&flcid->list_node);
kfree(flcid);
}
list_add_tail(&l_ctx->port_list_node, &xprt->channels);
spin_unlock_irqrestore(&xprt->xprt_ctx_lock_lhb1, flags);
rwref_write_put(&xprt->xprt_state_lhb0);
} else {
l_ctx->lcid = r_ctx->lcid;
l_ctx->rcid = r_ctx->rcid;
l_ctx->remote_opened = r_ctx->remote_opened;
l_ctx->remote_xprt_req = r_ctx->remote_xprt_req;
l_ctx->remote_xprt_resp = r_ctx->remote_xprt_resp;
glink_delete_ch_from_list(r_ctx, false);
spin_lock_irqsave(&xprt->xprt_ctx_lock_lhb1, flags);
list_add_tail(&l_ctx->port_list_node, &xprt->channels);
spin_unlock_irqrestore(&xprt->xprt_ctx_lock_lhb1, flags);
}
mutex_lock(&transport_list_lock_lha0);
list_for_each_entry(xprt, &transport_list, list_node)
if (!strcmp(l_ctx->transport_ptr->edge, xprt->edge))
if (xprt->id < best_xprt)
best_xprt = xprt->id;
mutex_unlock(&transport_list_lock_lha0);
l_ctx->local_open_state = GLINK_CHANNEL_OPENING;
l_ctx->local_xprt_req = best_xprt;
l_ctx->transport_ptr->ops->tx_cmd_ch_open(l_ctx->transport_ptr->ops,
l_ctx->lcid, l_ctx->name, best_xprt);
migrated = true;
exit:
rwref_put(&l_ctx->ch_state_lhc0);
rwref_put(&r_ctx->ch_state_lhc0);
return migrated;
}
/**
* calculate_xprt_resp() - calculate the response to a remote xprt request
* @r_ctx: The channel the remote xprt request is for.
*
* Return: The calculated response.
*/
static uint16_t calculate_xprt_resp(struct channel_ctx *r_ctx)
{
struct channel_ctx *l_ctx;
l_ctx = find_l_ctx_get(r_ctx);
if (!l_ctx) {
r_ctx->remote_xprt_resp = r_ctx->transport_ptr->id;
} else if (r_ctx->remote_xprt_req == r_ctx->transport_ptr->id) {
r_ctx->remote_xprt_resp = r_ctx->remote_xprt_req;
} else {
if (!l_ctx->local_xprt_req)
r_ctx->remote_xprt_resp = r_ctx->remote_xprt_req;
else if (l_ctx->no_migrate)
r_ctx->remote_xprt_resp = l_ctx->local_xprt_req;
else
r_ctx->remote_xprt_resp = max(l_ctx->local_xprt_req,
r_ctx->remote_xprt_req);
}
if (l_ctx)
rwref_put(&l_ctx->ch_state_lhc0);
return r_ctx->remote_xprt_resp;
}
/**
* glink_core_rx_cmd_ch_remote_open() - Remote-initiated open command
*
* @if_ptr: Pointer to transport instance
* @rcid: Remote Channel ID
* @name: Channel name
* @req_xprt: Requested transport to migrate to
*/
static void glink_core_rx_cmd_ch_remote_open(struct glink_transport_if *if_ptr,
uint32_t rcid, const char *name, uint16_t req_xprt)
{
struct channel_ctx *ctx;
uint16_t xprt_resp;
bool do_migrate;
ctx = ch_name_to_ch_ctx_create(if_ptr->glink_core_priv, name);
if (ctx == NULL) {
GLINK_ERR_XPRT(if_ptr->glink_core_priv,
"%s: invalid rcid %u received, name '%s'\n",
__func__, rcid, name);
return;
}
/* port already exists */
if (ctx->remote_opened) {
GLINK_ERR_CH(ctx,
"%s: Duplicate remote open for rcid %u, name '%s'\n",
__func__, rcid, name);
return;
}
ctx->remote_opened = true;
ch_add_rcid(if_ptr->glink_core_priv, ctx, rcid);
ctx->transport_ptr = if_ptr->glink_core_priv;
ctx->remote_xprt_req = req_xprt;
xprt_resp = calculate_xprt_resp(ctx);
do_migrate = will_migrate(NULL, ctx);
GLINK_INFO_CH(ctx, "%s: remote: CLOSED->OPENED ; xprt req:resp %u:%u\n",
__func__, req_xprt, xprt_resp);
if_ptr->tx_cmd_ch_remote_open_ack(if_ptr, rcid, xprt_resp);
if (!do_migrate && ch_is_fully_opened(ctx))
ctx->notify_state(ctx, ctx->user_priv, GLINK_CONNECTED);
if (do_migrate)
ch_migrate(NULL, ctx);
}
/**
* glink_core_rx_cmd_ch_open_ack() - Receive ack to previously sent open request
*
* if_ptr: Pointer to transport instance
* lcid: Local Channel ID
* @xprt_resp: Response to the transport migration request
*/
static void glink_core_rx_cmd_ch_open_ack(struct glink_transport_if *if_ptr,
uint32_t lcid, uint16_t xprt_resp)
{
struct channel_ctx *ctx;
ctx = xprt_lcid_to_ch_ctx_get(if_ptr->glink_core_priv, lcid);
if (!ctx) {
/* unknown LCID received - this shouldn't happen */
GLINK_ERR_XPRT(if_ptr->glink_core_priv,
"%s: invalid lcid %u received\n", __func__,
(unsigned)lcid);
return;
}
if (ctx->local_open_state != GLINK_CHANNEL_OPENING) {
GLINK_ERR_CH(ctx,
"%s: unexpected open ack receive for lcid. Current state: %u. Thread: %u\n",
__func__, ctx->local_open_state, current->pid);
rwref_put(&ctx->ch_state_lhc0);
return;
}
ctx->local_xprt_resp = xprt_resp;
if (!ch_migrate(ctx, NULL)) {
ctx->local_open_state = GLINK_CHANNEL_OPENED;
GLINK_INFO_PERF_CH(ctx,
"%s: local:GLINK_CHANNEL_OPENING_WAIT->GLINK_CHANNEL_OPENED\n",
__func__);
if (ch_is_fully_opened(ctx)) {
ctx->notify_state(ctx, ctx->user_priv, GLINK_CONNECTED);
GLINK_INFO_PERF_CH(ctx,
"%s: notify state: GLINK_CONNECTED\n",
__func__);
}
}
rwref_put(&ctx->ch_state_lhc0);
}
/**
* glink_core_rx_cmd_ch_remote_close() - Receive remote close command
*
* if_ptr: Pointer to transport instance
* rcid: Remote Channel ID
*/
static void glink_core_rx_cmd_ch_remote_close(
struct glink_transport_if *if_ptr, uint32_t rcid)
{
struct channel_ctx *ctx;
bool is_ch_fully_closed;
ctx = xprt_rcid_to_ch_ctx_get(if_ptr->glink_core_priv, rcid);
if (!ctx) {
/* unknown LCID received - this shouldn't happen */
GLINK_ERR_XPRT(if_ptr->glink_core_priv,
"%s: invalid rcid %u received\n", __func__,
(unsigned)rcid);
return;
}
if (!ctx->remote_opened) {
GLINK_ERR_CH(ctx,
"%s: unexpected remote close receive for rcid %u\n",
__func__, (unsigned)rcid);
rwref_put(&ctx->ch_state_lhc0);
return;
}
GLINK_INFO_CH(ctx, "%s: remote: OPENED->CLOSED\n", __func__);
is_ch_fully_closed = glink_core_remote_close_common(ctx);
ctx->pending_delete = true;
if_ptr->tx_cmd_ch_remote_close_ack(if_ptr, rcid);
if (is_ch_fully_closed) {
glink_delete_ch_from_list(ctx, true);
flush_workqueue(ctx->transport_ptr->tx_wq);
}
rwref_put(&ctx->ch_state_lhc0);
}
/**
* glink_core_rx_cmd_ch_close_ack() - Receive locally-request close ack
*
* if_ptr: Pointer to transport instance
* lcid: Local Channel ID
*/
static void glink_core_rx_cmd_ch_close_ack(struct glink_transport_if *if_ptr,
uint32_t lcid)
{
struct channel_ctx *ctx;
bool is_ch_fully_closed;
ctx = xprt_lcid_to_ch_ctx_get(if_ptr->glink_core_priv, lcid);
if (!ctx) {
/* unknown LCID received - this shouldn't happen */
GLINK_ERR_XPRT(if_ptr->glink_core_priv,
"%s: invalid lcid %u received\n", __func__,
(unsigned)lcid);
return;
}
if (ctx->local_open_state != GLINK_CHANNEL_CLOSING) {
GLINK_ERR_CH(ctx,
"%s: unexpected close ack receive for lcid %u\n",
__func__, (unsigned)lcid);
rwref_put(&ctx->ch_state_lhc0);
return;
}
is_ch_fully_closed = glink_core_ch_close_ack_common(ctx);
if (is_ch_fully_closed) {
glink_delete_ch_from_list(ctx, true);
flush_workqueue(ctx->transport_ptr->tx_wq);
}
rwref_put(&ctx->ch_state_lhc0);
}
/**
* glink_core_remote_rx_intent_put() - Receive remove intent
*
* @if_ptr: Pointer to transport instance
* @rcid: Remote Channel ID
* @riid: Remote Intent ID
* @size: Size of the remote intent ID
*/
static void glink_core_remote_rx_intent_put(struct glink_transport_if *if_ptr,
uint32_t rcid, uint32_t riid, size_t size)
{
struct channel_ctx *ctx;
ctx = xprt_rcid_to_ch_ctx_get(if_ptr->glink_core_priv, rcid);
if (!ctx) {
/* unknown rcid received - this shouldn't happen */
GLINK_ERR_XPRT(if_ptr->glink_core_priv,
"%s: invalid rcid received %u\n", __func__,
(unsigned)rcid);
return;
}
ch_push_remote_rx_intent(ctx, size, riid);
complete_all(&ctx->int_req_complete);
if (ctx->notify_remote_rx_intent)
ctx->notify_remote_rx_intent(ctx, ctx->user_priv, size);
rwref_put(&ctx->ch_state_lhc0);
}
/**
* glink_core_rx_cmd_remote_rx_intent_req() - Receive a request for rx_intent
* from remote side
* if_ptr: Pointer to the transport interface
* rcid: Remote channel ID
* size: size of the intent
*
* The function searches for the local channel to which the request for
* rx_intent has arrived and informs this request to the local channel through
* notify_rx_intent_req callback registered by the local channel.
*/
static void glink_core_rx_cmd_remote_rx_intent_req(
struct glink_transport_if *if_ptr, uint32_t rcid, size_t size)
{
struct channel_ctx *ctx;
bool cb_ret;
ctx = xprt_rcid_to_ch_ctx_get(if_ptr->glink_core_priv, rcid);
if (!ctx) {
GLINK_ERR_XPRT(if_ptr->glink_core_priv,
"%s: invalid rcid received %u\n", __func__,
(unsigned)rcid);
return;
}
if (!ctx->notify_rx_intent_req) {
GLINK_ERR_CH(ctx,
"%s: Notify function not defined for local channel",
__func__);
rwref_put(&ctx->ch_state_lhc0);
return;
}
cb_ret = ctx->notify_rx_intent_req(ctx, ctx->user_priv, size);
if_ptr->tx_cmd_remote_rx_intent_req_ack(if_ptr, ctx->lcid, cb_ret);
rwref_put(&ctx->ch_state_lhc0);
}
/**
* glink_core_rx_cmd_remote_rx_intent_req_ack()- Receive ack from remote side
* for a local rx_intent request
* if_ptr: Pointer to the transport interface
* rcid: Remote channel ID
* size: size of the intent
*
* This function receives the ack for rx_intent request from local channel.
*/
static void glink_core_rx_cmd_rx_intent_req_ack(struct glink_transport_if
*if_ptr, uint32_t rcid, bool granted)
{
struct channel_ctx *ctx;
ctx = xprt_rcid_to_ch_ctx_get(if_ptr->glink_core_priv, rcid);
if (!ctx) {
GLINK_ERR_XPRT(if_ptr->glink_core_priv,
"%s: Invalid rcid received %u\n", __func__,
(unsigned)rcid);
return;
}
ctx->int_req_ack = granted;
complete_all(&ctx->int_req_ack_complete);
rwref_put(&ctx->ch_state_lhc0);
}
/**
* glink_core_rx_get_pkt_ctx() - lookup RX intent structure
*
* if_ptr: Pointer to the transport interface
* rcid: Remote channel ID
* liid: Local RX Intent ID
*
* Note that this function is designed to always be followed by a call to
* glink_core_rx_put_pkt_ctx() to complete an RX operation by the transport.
*
* Return: Pointer to RX intent structure (or NULL if none found)
*/
static struct glink_core_rx_intent *glink_core_rx_get_pkt_ctx(
struct glink_transport_if *if_ptr, uint32_t rcid, uint32_t liid)
{
struct channel_ctx *ctx;
struct glink_core_rx_intent *intent_ptr;
ctx = xprt_rcid_to_ch_ctx_get(if_ptr->glink_core_priv, rcid);
if (!ctx) {
/* unknown LCID received - this shouldn't happen */
GLINK_ERR_XPRT(if_ptr->glink_core_priv,
"%s: invalid rcid received %u\n", __func__,
(unsigned)rcid);
return NULL;
}
/* match pending intent */
intent_ptr = ch_get_local_rx_intent(ctx, liid);
if (intent_ptr == NULL) {
GLINK_ERR_CH(ctx,
"%s: L[%u]: No matching rx intent\n",
__func__, liid);
rwref_put(&ctx->ch_state_lhc0);
return NULL;
}
rwref_put(&ctx->ch_state_lhc0);
return intent_ptr;
}
/**
* glink_core_rx_put_pkt_ctx() - lookup RX intent structure
*
* if_ptr: Pointer to the transport interface
* rcid: Remote channel ID
* intent_ptr: Pointer to the RX intent
* complete: Packet has been completely received
*
* Note that this function should always be preceded by a call to
* glink_core_rx_get_pkt_ctx().
*/
void glink_core_rx_put_pkt_ctx(struct glink_transport_if *if_ptr,
uint32_t rcid, struct glink_core_rx_intent *intent_ptr, bool complete)
{
struct channel_ctx *ctx;
if (!complete) {
GLINK_DBG_XPRT(if_ptr->glink_core_priv,
"%s: rcid[%u] liid[%u] pkt_size[%zu] write_offset[%zu] Fragment received\n",
__func__, rcid, intent_ptr->id,
intent_ptr->pkt_size,
intent_ptr->write_offset);
return;
}
/* packet complete */
ctx = xprt_rcid_to_ch_ctx_get(if_ptr->glink_core_priv, rcid);
if (!ctx) {
/* unknown LCID received - this shouldn't happen */
GLINK_ERR_XPRT(if_ptr->glink_core_priv,
"%s: invalid rcid received %u\n", __func__,
(unsigned)rcid);
return;
}
if (unlikely(intent_ptr->tracer_pkt)) {
tracer_pkt_log_event(intent_ptr->data, GLINK_CORE_RX);
ch_set_local_rx_intent_notified(ctx, intent_ptr);
if (ctx->notify_rx_tracer_pkt)
ctx->notify_rx_tracer_pkt(ctx, ctx->user_priv,
intent_ptr->pkt_priv, intent_ptr->data,
intent_ptr->pkt_size);
rwref_put(&ctx->ch_state_lhc0);
return;
}
GLINK_PERF_CH(ctx, "%s: L[%u]: data[%p] size[%zu]\n",
__func__, intent_ptr->id,
intent_ptr->data ? intent_ptr->data : intent_ptr->iovec,
intent_ptr->write_offset);
if (!intent_ptr->data && !ctx->notify_rxv) {
/* Received a vector, but client can't handle a vector */
intent_ptr->bounce_buf = linearize_vector(intent_ptr->iovec,
intent_ptr->pkt_size,
intent_ptr->vprovider,
intent_ptr->pprovider);
if (IS_ERR_OR_NULL(intent_ptr->bounce_buf)) {
GLINK_ERR_XPRT(if_ptr->glink_core_priv,
"%s: Error %ld linearizing vector\n", __func__,
PTR_ERR(intent_ptr->bounce_buf));
BUG();
rwref_put(&ctx->ch_state_lhc0);
return;
}
}
ch_set_local_rx_intent_notified(ctx, intent_ptr);
if (ctx->notify_rx && (intent_ptr->data || intent_ptr->bounce_buf)) {
ctx->notify_rx(ctx, ctx->user_priv, intent_ptr->pkt_priv,
intent_ptr->data ?
intent_ptr->data : intent_ptr->bounce_buf,
intent_ptr->pkt_size);
} else if (ctx->notify_rxv) {
ctx->notify_rxv(ctx, ctx->user_priv, intent_ptr->pkt_priv,
intent_ptr->iovec, intent_ptr->pkt_size,
intent_ptr->vprovider, intent_ptr->pprovider);
} else {
GLINK_ERR_XPRT(if_ptr->glink_core_priv,
"%s: Unable to process rx data\n", __func__);
BUG();
}
rwref_put(&ctx->ch_state_lhc0);
}
/**
* glink_core_rx_cmd_tx_done() - Receive Transmit Done Command
* @xprt_ptr: Transport to send packet on.
* @rcid: Remote channel ID
* @riid: Remote intent ID
* @reuse: Reuse the consumed intent
*/
void glink_core_rx_cmd_tx_done(struct glink_transport_if *if_ptr,
uint32_t rcid, uint32_t riid, bool reuse)
{
struct channel_ctx *ctx;
struct glink_core_tx_pkt *tx_pkt;
ctx = xprt_rcid_to_ch_ctx_get(if_ptr->glink_core_priv, rcid);
if (!ctx) {
/* unknown RCID received - this shouldn't happen */
GLINK_ERR_XPRT(if_ptr->glink_core_priv,
"%s: invalid rcid %u received\n", __func__,
rcid);
return;
}
mutex_lock(&ctx->tx_lists_mutex_lhc3);
tx_pkt = ch_get_tx_pending_remote_done(ctx, riid);
if (IS_ERR_OR_NULL(tx_pkt)) {
/*
* FUTURE - in the case of a zero-copy transport, this is a
* fatal protocol failure since memory corruption could occur
* in this case. Prevent this by adding code in glink_close()
* to recall any buffers in flight / wait for them to be
* returned.
*/
GLINK_ERR_CH(ctx, "%s: R[%u]: No matching tx\n",
__func__,
(unsigned)riid);
rwref_put(&ctx->ch_state_lhc0);
return;
}
/* notify client */
ctx->notify_tx_done(ctx, ctx->user_priv, tx_pkt->pkt_priv,
tx_pkt->data ? tx_pkt->data : tx_pkt->iovec);
if (reuse)
ch_push_remote_rx_intent(ctx, tx_pkt->intent_size,
tx_pkt->riid);
ch_remove_tx_pending_remote_done(ctx, tx_pkt);
mutex_unlock(&ctx->tx_lists_mutex_lhc3);
rwref_put(&ctx->ch_state_lhc0);
}
/**
* xprt_schedule_tx() - Schedules packet for transmit.
* @xprt_ptr: Transport to send packet on.
* @ch_ptr: Channel to send packet on.
* @tx_info: Packet to transmit.
*/
static void xprt_schedule_tx(struct glink_core_xprt_ctx *xprt_ptr,
struct channel_ctx *ch_ptr,
struct glink_core_tx_pkt *tx_info)
{
mutex_lock(&xprt_ptr->tx_ready_mutex_lhb2);
if (list_empty(&ch_ptr->tx_ready_list_node))
list_add_tail(&ch_ptr->tx_ready_list_node, &xprt_ptr->tx_ready);
mutex_lock(&ch_ptr->tx_lists_mutex_lhc3);
list_add_tail(&tx_info->list_node, &ch_ptr->tx_active);
mutex_unlock(&ch_ptr->tx_lists_mutex_lhc3);
mutex_unlock(&xprt_ptr->tx_ready_mutex_lhb2);
if (unlikely(tx_info->tracer_pkt))
tracer_pkt_log_event((void *)(tx_info->data),
GLINK_QUEUE_TO_SCHEDULER);
queue_work(xprt_ptr->tx_wq, &xprt_ptr->tx_work);
}
/**
* xprt_single_threaded_tx() - Transmit in the context of sender.
* @xprt_ptr: Transport to send packet on.
* @ch_ptr: Channel to send packet on.
* @tx_info: Packet to transmit.
*/
static int xprt_single_threaded_tx(struct glink_core_xprt_ctx *xprt_ptr,
struct channel_ctx *ch_ptr,
struct glink_core_tx_pkt *tx_info)
{
int ret;
unsigned long flags;
spin_lock_irqsave(&ch_ptr->tx_pending_rmt_done_lock_lhc4, flags);
do {
ret = xprt_ptr->ops->tx(ch_ptr->transport_ptr->ops,
ch_ptr->lcid, tx_info);
} while (ret == -EAGAIN);
if (ret < 0 || tx_info->size_remaining) {
GLINK_ERR_CH(ch_ptr, "%s: Error %d writing data\n",
__func__, ret);
kfree(tx_info);
} else {
list_add_tail(&tx_info->list_done,
&ch_ptr->tx_pending_remote_done);
ret = 0;
}
spin_unlock_irqrestore(&ch_ptr->tx_pending_rmt_done_lock_lhc4, flags);
return ret;
}
/**
* tx_work_func() - Transmit worker
* @work: Linux work structure
*/
static void tx_work_func(struct work_struct *work)
{
struct glink_core_xprt_ctx *xprt_ptr =
container_of(work, struct glink_core_xprt_ctx, tx_work);
struct channel_ctx *ch_ptr;
struct glink_core_tx_pkt *tx_info;
int ret;
unsigned long flags;
GLINK_PERF("%s: worker starting\n", __func__);
mutex_lock(&xprt_ptr->tx_ready_mutex_lhb2);
while (!list_empty(&xprt_ptr->tx_ready)) {
glink_pm_qos_vote(xprt_ptr);
ch_ptr = list_first_entry(&xprt_ptr->tx_ready,
struct channel_ctx,
tx_ready_list_node);
mutex_unlock(&xprt_ptr->tx_ready_mutex_lhb2);
mutex_lock(&ch_ptr->tx_lists_mutex_lhc3);
tx_info = list_first_entry(&ch_ptr->tx_active,
struct glink_core_tx_pkt,
list_node);
spin_lock_irqsave(&ch_ptr->tx_pending_rmt_done_lock_lhc4,
flags);
if (list_empty(&tx_info->list_done))
list_add(&tx_info->list_done,
&ch_ptr->tx_pending_remote_done);
spin_unlock_irqrestore(&ch_ptr->tx_pending_rmt_done_lock_lhc4,
flags);
if (unlikely(tx_info->tracer_pkt)) {
tracer_pkt_log_event((void *)(tx_info->data),
GLINK_SCHEDULER_TX);
ret = xprt_ptr->ops->tx_cmd_tracer_pkt(xprt_ptr->ops,
ch_ptr->lcid, tx_info);
} else {
ret = xprt_ptr->ops->tx(xprt_ptr->ops,
ch_ptr->lcid, tx_info);
}
if (ret == -EAGAIN) {
/*
* transport unable to send at the moment and will call
* tx_resume() when it can send again.
*/
mutex_unlock(&ch_ptr->tx_lists_mutex_lhc3);
break;
} else if (ret < 0) {
/*
* General failure code that indicates that the
* transport is unable to recover. In this case, the
* communication failure will be detected at a higher
* level and a subsystem restart of the affected system
* will be triggered.
*/
GLINK_ERR_XPRT(xprt_ptr,
"%s: unrecoverable xprt failure %d\n",
__func__, ret);
}
if (!tx_info->size_remaining) {
spin_lock_irqsave(
&ch_ptr->tx_pending_rmt_done_lock_lhc4, flags);
list_del_init(&tx_info->list_node);
spin_unlock_irqrestore(
&ch_ptr->tx_pending_rmt_done_lock_lhc4, flags);
}
if (list_empty(&ch_ptr->tx_active)) {
mutex_unlock(&ch_ptr->tx_lists_mutex_lhc3);
mutex_lock(&xprt_ptr->tx_ready_mutex_lhb2);
mutex_lock(&ch_ptr->tx_lists_mutex_lhc3);
if (list_empty(&ch_ptr->tx_active))
list_del_init(&ch_ptr->tx_ready_list_node);
mutex_unlock(&xprt_ptr->tx_ready_mutex_lhb2);
}
mutex_unlock(&ch_ptr->tx_lists_mutex_lhc3);
mutex_lock(&xprt_ptr->tx_ready_mutex_lhb2);
list_rotate_left(&xprt_ptr->tx_ready);
}
glink_pm_qos_unvote(xprt_ptr);
mutex_unlock(&xprt_ptr->tx_ready_mutex_lhb2);
GLINK_PERF("%s: worker exiting\n", __func__);
}
static void glink_core_tx_resume(struct glink_transport_if *if_ptr)
{
queue_work(if_ptr->glink_core_priv->tx_wq,
&if_ptr->glink_core_priv->tx_work);
}
/**
* glink_pm_qos_vote() - Add Power Management QoS Vote
* @xprt_ptr: Transport for power vote
*
* Note - must be called with tx_ready_mutex_lhb2 locked.
*/
static void glink_pm_qos_vote(struct glink_core_xprt_ctx *xprt_ptr)
{
if (glink_pm_qos && !xprt_ptr->qos_req_active) {
GLINK_PERF("%s: qos vote %u us\n", __func__, glink_pm_qos);
pm_qos_update_request(&xprt_ptr->pm_qos_req, glink_pm_qos);
xprt_ptr->qos_req_active = true;
}
xprt_ptr->tx_path_activity = true;
}
/**
* glink_pm_qos_unvote() - Schedule Power Management QoS Vote Removal
* @xprt_ptr: Transport for power vote removal
*
* Note - must be called with tx_ready_mutex_lhb2 locked.
*/
static void glink_pm_qos_unvote(struct glink_core_xprt_ctx *xprt_ptr)
{
xprt_ptr->tx_path_activity = false;
if (xprt_ptr->qos_req_active) {
GLINK_PERF("%s: qos unvote\n", __func__);
schedule_delayed_work(&xprt_ptr->pm_qos_work,
msecs_to_jiffies(GLINK_PM_QOS_HOLDOFF_MS));
}
}
/**
* glink_pm_qos_cancel_worker() - Remove Power Management QoS Vote
* @work: Delayed work structure
*
* Removes PM QoS vote if no additional transmit activity has occurred between
* the unvote and when this worker runs.
*/
static void glink_pm_qos_cancel_worker(struct work_struct *work)
{
struct glink_core_xprt_ctx *xprt_ptr;
xprt_ptr = container_of(to_delayed_work(work),
struct glink_core_xprt_ctx, pm_qos_work);
mutex_lock(&xprt_ptr->tx_ready_mutex_lhb2);
if (!xprt_ptr->tx_path_activity) {
/* no more tx activity */
GLINK_PERF("%s: qos off\n", __func__);
pm_qos_update_request(&xprt_ptr->pm_qos_req,
PM_QOS_DEFAULT_VALUE);
xprt_ptr->qos_req_active = false;
}
xprt_ptr->tx_path_activity = false;
mutex_unlock(&xprt_ptr->tx_ready_mutex_lhb2);
}
/**
* glink_core_rx_cmd_remote_sigs() - Receive remote channel signal command
*
* if_ptr: Pointer to transport instance
* rcid: Remote Channel ID
*/
static void glink_core_rx_cmd_remote_sigs(struct glink_transport_if *if_ptr,
uint32_t rcid, uint32_t sigs)
{
struct channel_ctx *ctx;
uint32_t old_sigs;
ctx = xprt_rcid_to_ch_ctx_get(if_ptr->glink_core_priv, rcid);
if (!ctx) {
/* unknown LCID received - this shouldn't happen */
GLINK_ERR_XPRT(if_ptr->glink_core_priv,
"%s: invalid rcid %u received\n", __func__,
(unsigned)rcid);
return;
}
if (!ch_is_fully_opened(ctx)) {
GLINK_ERR_CH(ctx, "%s: Channel is not fully opened\n",
__func__);
rwref_put(&ctx->ch_state_lhc0);
return;
}
old_sigs = ctx->rsigs;
ctx->rsigs = sigs;
if (ctx->notify_rx_sigs) {
ctx->notify_rx_sigs(ctx, ctx->user_priv, old_sigs, ctx->rsigs);
GLINK_INFO_CH(ctx, "%s: notify rx sigs old:0x%x new:0x%x\n",
__func__, old_sigs, ctx->rsigs);
}
rwref_put(&ctx->ch_state_lhc0);
}
static struct glink_core_if core_impl = {
.link_up = glink_core_link_up,
.link_down = glink_core_link_down,
.rx_cmd_version = glink_core_rx_cmd_version,
.rx_cmd_version_ack = glink_core_rx_cmd_version_ack,
.rx_cmd_ch_remote_open = glink_core_rx_cmd_ch_remote_open,
.rx_cmd_ch_open_ack = glink_core_rx_cmd_ch_open_ack,
.rx_cmd_ch_remote_close = glink_core_rx_cmd_ch_remote_close,
.rx_cmd_ch_close_ack = glink_core_rx_cmd_ch_close_ack,
.rx_get_pkt_ctx = glink_core_rx_get_pkt_ctx,
.rx_put_pkt_ctx = glink_core_rx_put_pkt_ctx,
.rx_cmd_remote_rx_intent_put = glink_core_remote_rx_intent_put,
.rx_cmd_remote_rx_intent_req = glink_core_rx_cmd_remote_rx_intent_req,
.rx_cmd_rx_intent_req_ack = glink_core_rx_cmd_rx_intent_req_ack,
.rx_cmd_tx_done = glink_core_rx_cmd_tx_done,
.tx_resume = glink_core_tx_resume,
.rx_cmd_remote_sigs = glink_core_rx_cmd_remote_sigs,
};
/**
* glink_xprt_ctx_iterator_init() - Initializes the transport context list iterator
* @xprt_i: pointer to the transport context iterator.
*
* This function acquires the transport context lock which must then be
* released by glink_xprt_ctx_iterator_end()
*/
void glink_xprt_ctx_iterator_init(struct xprt_ctx_iterator *xprt_i)
{
if (xprt_i == NULL)
return;
mutex_lock(&transport_list_lock_lha0);
xprt_i->xprt_list = &transport_list;
xprt_i->i_curr = list_entry(&transport_list,
struct glink_core_xprt_ctx, list_node);
}
EXPORT_SYMBOL(glink_xprt_ctx_iterator_init);
/**
* glink_xprt_ctx_iterator_end() - Ends the transport context list iteration
* @xprt_i: pointer to the transport context iterator.
*/
void glink_xprt_ctx_iterator_end(struct xprt_ctx_iterator *xprt_i)
{
if (xprt_i == NULL)
return;
xprt_i->xprt_list = NULL;
xprt_i->i_curr = NULL;
mutex_unlock(&transport_list_lock_lha0);
}
EXPORT_SYMBOL(glink_xprt_ctx_iterator_end);
/**
* glink_xprt_ctx_iterator_next() - iterates element by element in transport context list
* @xprt_i: pointer to the transport context iterator.
*
* Return: pointer to the transport context structure
*/
struct glink_core_xprt_ctx *glink_xprt_ctx_iterator_next(
struct xprt_ctx_iterator *xprt_i)
{
struct glink_core_xprt_ctx *xprt_ctx = NULL;
if (xprt_i == NULL)
return xprt_ctx;
if (list_empty(xprt_i->xprt_list))
return xprt_ctx;
list_for_each_entry_continue(xprt_i->i_curr,
xprt_i->xprt_list, list_node) {
xprt_ctx = xprt_i->i_curr;
break;
}
return xprt_ctx;
}
EXPORT_SYMBOL(glink_xprt_ctx_iterator_next);
/**
* glink_get_xprt_name() - get the transport name
* @xprt_ctx: pointer to the transport context.
*
* Return: name of the transport
*/
char *glink_get_xprt_name(struct glink_core_xprt_ctx *xprt_ctx)
{
if (xprt_ctx == NULL)
return NULL;
return xprt_ctx->name;
}
EXPORT_SYMBOL(glink_get_xprt_name);
/**
* glink_get_xprt_name() - get the name of the remote processor/edge
* of the transport
* @xprt_ctx: pointer to the transport context.
*
* Return: Name of the remote processor/edge
*/
char *glink_get_xprt_edge_name(struct glink_core_xprt_ctx *xprt_ctx)
{
if (xprt_ctx == NULL)
return NULL;
return xprt_ctx->edge;
}
EXPORT_SYMBOL(glink_get_xprt_edge_name);
/**
* glink_get_xprt_state() - get the state of the transport
* @xprt_ctx: pointer to the transport context.
*
* Return: Name of the transport state, NULL in case of invalid input
*/
const char *glink_get_xprt_state(struct glink_core_xprt_ctx *xprt_ctx)
{
if (xprt_ctx == NULL)
return NULL;
return glink_get_xprt_state_string(xprt_ctx->local_state);
}
EXPORT_SYMBOL(glink_get_xprt_state);
/**
* glink_get_xprt_version_features() - get the version and feature set
* of local transport in glink
* @xprt_ctx: pointer to the transport context.
*
* Return: pointer to the glink_core_version
*/
const struct glink_core_version *glink_get_xprt_version_features(
struct glink_core_xprt_ctx *xprt_ctx)
{
const struct glink_core_version *ver = NULL;
if (xprt_ctx == NULL)
return ver;
ver = &xprt_ctx->versions[xprt_ctx->local_version_idx];
return ver;
}
EXPORT_SYMBOL(glink_get_xprt_version_features);
/**
* glink_ch_ctx_iterator_init() - Initializes the channel context list iterator
* @ch_iter: pointer to the channel context iterator.
* xprt: pointer to the transport context that holds the channel list
*
* This function acquires the channel context lock which must then be
* released by glink_ch_ctx_iterator_end()
*/
void glink_ch_ctx_iterator_init(struct ch_ctx_iterator *ch_iter,
struct glink_core_xprt_ctx *xprt)
{
unsigned long flags;
if (ch_iter == NULL || xprt == NULL)
return;
spin_lock_irqsave(&xprt->xprt_ctx_lock_lhb1, flags);
ch_iter->ch_list = &(xprt->channels);
ch_iter->i_curr = list_entry(&(xprt->channels),
struct channel_ctx, port_list_node);
ch_iter->ch_list_flags = flags;
}
EXPORT_SYMBOL(glink_ch_ctx_iterator_init);
/**
* glink_ch_ctx_iterator_end() - Ends the channel context list iteration
* @ch_iter: pointer to the channel context iterator.
*/
void glink_ch_ctx_iterator_end(struct ch_ctx_iterator *ch_iter,
struct glink_core_xprt_ctx *xprt)
{
if (ch_iter == NULL || xprt == NULL)
return;
spin_unlock_irqrestore(&xprt->xprt_ctx_lock_lhb1,
ch_iter->ch_list_flags);
ch_iter->ch_list = NULL;
ch_iter->i_curr = NULL;
}
EXPORT_SYMBOL(glink_ch_ctx_iterator_end);
/**
* glink_ch_ctx_iterator_next() - iterates element by element in channel context list
* @c_i: pointer to the channel context iterator.
*
* Return: pointer to the channel context structure
*/
struct channel_ctx *glink_ch_ctx_iterator_next(struct ch_ctx_iterator *c_i)
{
struct channel_ctx *ch_ctx = NULL;
if (c_i == NULL)
return ch_ctx;
if (list_empty(c_i->ch_list))
return ch_ctx;
list_for_each_entry_continue(c_i->i_curr,
c_i->ch_list, port_list_node) {
ch_ctx = c_i->i_curr;
break;
}
return ch_ctx;
}
EXPORT_SYMBOL(glink_ch_ctx_iterator_next);
/**
* glink_get_ch_name() - get the channel name
* @ch_ctx: pointer to the channel context.
*
* Return: name of the channel, NULL in case of invalid input
*/
char *glink_get_ch_name(struct channel_ctx *ch_ctx)
{
if (ch_ctx == NULL)
return NULL;
return ch_ctx->name;
}
EXPORT_SYMBOL(glink_get_ch_name);
/**
* glink_get_ch_edge_name() - get the edge on whcih channel is created
* @ch_ctx: pointer to the channel context.
*
* Return: name of the edge, NULL in case of invalid input
*/
char *glink_get_ch_edge_name(struct channel_ctx *ch_ctx)
{
if (ch_ctx == NULL)
return NULL;
return ch_ctx->transport_ptr->edge;
}
EXPORT_SYMBOL(glink_get_ch_edge_name);
/**
* glink_get_ch_lcid() - get the local channel ID
* @c_i: pointer to the channel context.
*
* Return: local channel id, -EINVAL in case of invalid input
*/
int glink_get_ch_lcid(struct channel_ctx *ch_ctx)
{
if (ch_ctx == NULL)
return -EINVAL;
return ch_ctx->lcid;
}
EXPORT_SYMBOL(glink_get_ch_lcid);
/**
* glink_get_ch_rcid() - get the remote channel ID
* @ch_ctx: pointer to the channel context.
*
* Return: remote channel id, -EINVAL in case of invalid input
*/
int glink_get_ch_rcid(struct channel_ctx *ch_ctx)
{
if (ch_ctx == NULL)
return -EINVAL;
return ch_ctx->rcid;
}
EXPORT_SYMBOL(glink_get_ch_rcid);
/**
* glink_get_ch_lstate() - get the local channel state
* @ch_ctx: pointer to the channel context.
*
* Return: Name of the local channel state, NUll in case of invalid input
*/
const char *glink_get_ch_lstate(struct channel_ctx *ch_ctx)
{
if (ch_ctx == NULL)
return NULL;
return glink_get_ch_state_string(ch_ctx->local_open_state);
}
EXPORT_SYMBOL(glink_get_ch_lstate);
/**
* glink_get_ch_rstate() - get the remote channel state
* @ch_ctx: pointer to the channel context.
*
* Return: true if remote side is opened false otherwise
*/
bool glink_get_ch_rstate(struct channel_ctx *ch_ctx)
{
if (ch_ctx == NULL)
return NULL;
return ch_ctx->remote_opened;
}
EXPORT_SYMBOL(glink_get_ch_rstate);
/**
* glink_get_ch_xprt_name() - get the name of the transport to which
* the channel belongs
* @ch_ctx: pointer to the channel context.
*
* Return: name of the export, NULL in case of invalid input
*/
char *glink_get_ch_xprt_name(struct channel_ctx *ch_ctx)
{
if (ch_ctx == NULL)
return NULL;
return ch_ctx->transport_ptr->name;
}
EXPORT_SYMBOL(glink_get_ch_xprt_name);
/**
* glink_get_tx_pkt_count() - get the total number of packets sent
* through this channel
* @ch_ctx: pointer to the channel context.
*
* Return: number of packets transmitted, -EINVAL in case of invalid input
*/
int glink_get_ch_tx_pkt_count(struct channel_ctx *ch_ctx)
{
if (ch_ctx == NULL)
return -EINVAL;
/* FUTURE: packet stats not yet implemented */
return -ENOSYS;
}
EXPORT_SYMBOL(glink_get_ch_tx_pkt_count);
/**
* glink_get_ch_rx_pkt_count() - get the total number of packets
* recieved at this channel
* @ch_ctx: pointer to the channel context.
*
* Return: number of packets recieved, -EINVAL in case of invalid input
*/
int glink_get_ch_rx_pkt_count(struct channel_ctx *ch_ctx)
{
if (ch_ctx == NULL)
return -EINVAL;
/* FUTURE: packet stats not yet implemented */
return -ENOSYS;
}
EXPORT_SYMBOL(glink_get_ch_rx_pkt_count);
/**
* glink_get_ch_lintents_queued() - get the total number of intents queued
* at local side
* @ch_ctx: pointer to the channel context.
*
* Return: number of intents queued, -EINVAL in case of invalid input
*/
int glink_get_ch_lintents_queued(struct channel_ctx *ch_ctx)
{
struct glink_core_rx_intent *intent;
int ilrx_count = 0;
if (ch_ctx == NULL)
return -EINVAL;
list_for_each_entry(intent, &ch_ctx->local_rx_intent_list, list)
ilrx_count++;
return ilrx_count;
}
EXPORT_SYMBOL(glink_get_ch_lintents_queued);
/**
* glink_get_ch_rintents_queued() - get the total number of intents queued
* from remote side
* @ch_ctx: pointer to the channel context.
*
* Return: number of intents queued, -EINVAL in case of invalid input
*/
int glink_get_ch_rintents_queued(struct channel_ctx *ch_ctx)
{
struct glink_core_rx_intent *intent;
int irrx_count = 0;
if (ch_ctx == NULL)
return -EINVAL;
list_for_each_entry(intent, &ch_ctx->rmt_rx_intent_list, list)
irrx_count++;
return irrx_count;
}
EXPORT_SYMBOL(glink_get_ch_rintents_queued);
/**
* glink_get_ch_intent_info() - get the intent details of a channel
* @ch_ctx: pointer to the channel context.
* ch_ctx_i: pointer to a structure that will contain intent details
*
* This function is used to get all the channel intent details including locks.
*/
void glink_get_ch_intent_info(struct channel_ctx *ch_ctx,
struct glink_ch_intent_info *ch_ctx_i)
{
if (ch_ctx == NULL || ch_ctx_i == NULL)
return;
ch_ctx_i->li_lst_lock = &ch_ctx->local_rx_intent_lst_lock_lhc1;
ch_ctx_i->li_avail_list = &ch_ctx->local_rx_intent_list;
ch_ctx_i->li_used_list = &ch_ctx->local_rx_intent_ntfy_list;
ch_ctx_i->ri_lst_lock = &ch_ctx->rmt_rx_intent_lst_lock_lhc2;
ch_ctx_i->ri_list = &ch_ctx->rmt_rx_intent_list;
}
EXPORT_SYMBOL(glink_get_ch_intent_info);
/**
* glink_get_debug_mask() - Return debug mask attribute
*
* Return: debug mask attribute
*/
unsigned glink_get_debug_mask(void)
{
return glink_debug_mask;
}
EXPORT_SYMBOL(glink_get_debug_mask);
/**
* glink_get_log_ctx() - Return log context for other GLINK modules.
*
* Return: Log context or NULL if none.
*/
void *glink_get_log_ctx(void)
{
return log_ctx;
}
EXPORT_SYMBOL(glink_get_log_ctx);
static int glink_init(void)
{
log_ctx = ipc_log_context_create(NUM_LOG_PAGES, "glink", 0);
if (!log_ctx)
GLINK_ERR("%s: unable to create log context\n", __func__);
glink_debugfs_init();
return 0;
}
arch_initcall(glink_init);
MODULE_DESCRIPTION("MSM Generic Link (G-Link) Transport");
MODULE_LICENSE("GPL v2");