blob: 5b56c65a873c7d700fdeb3b543df77df77480e05 [file] [log] [blame]
/* Copyright (c) 2015, The Linux Foundation. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
* Neither the name of The Linux Foundation nor the names of its
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*===========================================================================
INCLUDE FILES
===========================================================================*/
#include "glink_os_utils.h"
#include "glink.h"
#include "glink_internal.h"
#define FEATURE_CH_MIGRATION_FREE
/*===========================================================================
GLOBAL FUNCTION DECLARATIONS
===========================================================================*/
/*===========================================================================
LOCAL FUNCTION DEFINITIONS
===========================================================================*/
/*===========================================================================
FUNCTION glink_process_negotiation_complete
===========================================================================*/
/**
This is called when negotiation is complete.
It will set the version and call link up callback to notify
@param[in] xport_ctx transport context
@param[in] if_ptr Pointer to interface instance; must be unique
for each edge
@param[in] version negotiated version
@param[in] features negotiated with local side
@return None
@sideeffects None.
@dependencies None.
*/
/*=========================================================================*/
static void glink_process_negotiation_complete
(
glink_transport_if_type *if_ptr,
uint32 version,
uint32 features
)
{
glink_core_xport_ctx_type *xport_ctx = if_ptr->glink_core_priv;
/* Version/Feautre can be negotiated both in ver_req and ver_ack
* Only go through process once in case they are negotiated
* in ver_req before receiving ver_ack */
if (glinki_xport_linkup(if_ptr))
{
return;
}
/* setup core based on transport capabilities*/
xport_ctx->xport_capabilities = if_ptr->set_version( if_ptr,
version,
features );
glink_core_setup(if_ptr);
/* transport is ready to open channels */
glink_os_cs_acquire( &xport_ctx->status_cs );
if_ptr->glink_core_priv->status = GLINK_XPORT_LINK_UP;
glink_os_cs_release(&xport_ctx->status_cs);
/* Scan the notification list to check is we have to let any registered
* clients know that link came online */
glinki_scan_notif_list_and_notify(if_ptr, GLINK_LINK_STATE_UP);
}
/*===========================================================================
FUNCTION glink_clean_channel_ctx
===========================================================================*/
/**
This is called when channel is fully closed.
Clean up the context and redeem channel id if possible.
@param[in] xport_ctx transport context
@param[in] channel_ctx channel context
@return None.
@sideeffects None.
@dependencies This function needs to be called in safe context
which is critical sections locked - channel_q_cs
*/
/*=========================================================================*/
static void glink_clean_channel_ctx
(
glink_core_xport_ctx_type *xport_ctx,
glink_channel_ctx_type *channel_ctx
)
{
/* If logging was enabled for this channel reset the logging filter */
glinki_update_logging_filter(channel_ctx, TRUE);
smem_list_delete(&xport_ctx->open_list, channel_ctx);
if( channel_ctx->lcid == ( xport_ctx->free_lcid - 1 ) )
{
/* If channel being closed is the last opened channel
re-use the lcid of this channel for any new channels */
xport_ctx->free_lcid--;
}
xport_ctx->channel_cleanup(channel_ctx);
}
/*===========================================================================
FUNCTION glink_get_current_version
===========================================================================*/
/**
Get current version/feature. This is necessarily highest version.
@param[in] xport_ctx transport context
@return pointer to version/feature if available.
NULL otherwise.
@sideeffects None.
@dependencies None.
*/
/*=========================================================================*/
static const glink_core_version_type *glink_get_current_version
(
glink_core_xport_ctx_type *xport_ctx
)
{
const glink_core_version_type *ver;
ver = &xport_ctx->version_array[xport_ctx->version_indx];
ASSERT(ver);
return ver;
}
/*===========================================================================
FUNCTION glink_is_local_ch_closed
===========================================================================*/
/**
Check if local channel is fully closed
@param[in] local_state local channel state
@return TRUE, if local channel is closed
@sideeffects None.
@dependencies None.
*/
/*=========================================================================*/
static boolean glink_is_local_ch_closed
(
glink_local_state_type local_state
)
{
return local_state == GLINK_LOCAL_CH_INIT ||
local_state == GLINK_LOCAL_CH_CLOSED;
}
/*===========================================================================
FUNCTION glink_is_remote_ch_closed
===========================================================================*/
/**
Check if remote channel is fully closed.
This doesn't chek GLINK_REMOTE_CH_CLEANUP state as channel ctx shouldn't be
destroyed yet
@param[in] ch_ctx channel ctx
@return TRUE, if remote channel is closed
@sideeffects None.
@dependencies None.
*/
/*=========================================================================*/
static boolean glink_is_remote_ch_closed
(
glink_remote_state_type remote_state
)
{
return remote_state == GLINK_REMOTE_CH_INIT ||
remote_state == GLINK_REMOTE_CH_CLOSED ||
remote_state == GLINK_REMOTE_CH_SSR_RESET;
}
/*===========================================================================
EXTERNAL FUNCTION DEFINITIONS
===========================================================================*/
/*===========================================================================
FUNCTION glink_link_up
===========================================================================*/
/**
Indicates that transport is now ready to start negotiation
using the v0 configuration
@param[in] if_ptr Pointer to interface instance; must be unique
for each edge
@return None
@sideeffects None.
@dependencies None.
*/
/*=========================================================================*/
void glink_link_up
(
glink_transport_if_type *if_ptr
)
{
glink_core_xport_ctx_type *xport_ctx;
const glink_core_version_type *version_array;
xport_ctx = if_ptr->glink_core_priv;
version_array = glink_get_current_version(xport_ctx);
/* Update the transport state */
glink_os_cs_acquire(&if_ptr->glink_core_priv->status_cs);
if_ptr->glink_core_priv->status = GLINK_XPORT_NEGOTIATING;
glink_os_cs_release(&if_ptr->glink_core_priv->status_cs);
/* Start the negtiation */
if_ptr->tx_cmd_version(if_ptr,
version_array->version,
version_array->features);
GLINK_LOG_EVENT_NO_FILTER( GLINK_EVENT_LINK_UP, "",
xport_ctx->xport,
xport_ctx->remote_ss,
GLINK_STATUS_SUCCESS);
}
/*===========================================================================
FUNCTION glink_rx_cmd_version
DESCRIPTION Receive transport version for remote-initiated version
negotiation
ARGUMENTS *if_ptr Pointer to interface instance; must be unique
for each edge
version Remote Version
features Remote Features
RETURN VALUE None.
SIDE EFFECTS None
===========================================================================*/
void glink_rx_cmd_version
(
glink_transport_if_type *if_ptr,
uint32 version,
uint32 features
)
{
const glink_core_version_type *ver;
uint32 negotiated_features;
glink_core_xport_ctx_type *xport_ctx;
xport_ctx = if_ptr->glink_core_priv;
ver = glink_get_current_version(xport_ctx);
/* The version to use must be a subset of supported version and features
* on this host and remote host */
if (version == ver->version)
{
/* Call the transport's negotiation function */
negotiated_features = ver->negotiate_features(if_ptr, ver, features);
if_ptr->tx_cmd_version_ack(if_ptr, version, negotiated_features);
/* If negotiated features match the provided features, version nogetiation
* is complete */
if(negotiated_features == features)
{
glink_process_negotiation_complete( if_ptr, version, features );
}
}
else
{
/* Versions don't match, return ACK with the feature set that we support */
if_ptr->tx_cmd_version_ack(if_ptr, ver->version, ver->features);
}
}
/*===========================================================================
FUNCTION glink_rx_cmd_version_ack
DESCRIPTION Receive ACK to previous glink_transport_if::tx_cmd_version
command
ARGUMENTS *if_ptr Pointer to interface instance; must be unique
for each edge
version Remote Version
features Remote Features
RETURN VALUE None.
SIDE EFFECTS None
===========================================================================*/
void glink_rx_cmd_version_ack
(
glink_transport_if_type *if_ptr,
uint32 version,
uint32 features
)
{
const glink_core_version_type* ver;
uint32 negotiated_features;
glink_core_xport_ctx_type *xport_ctx;
xport_ctx = if_ptr->glink_core_priv;
/* Check to see if version returned by remote end is supported by
* this host. Remote side would have acked only when the version/features
* sent by this host did not match the remote */
ver = glink_get_current_version(xport_ctx);
if (ver->version == version)
{
/* Call the transport's negotiation function */
negotiated_features = ver->negotiate_features(if_ptr, ver, features);
if(negotiated_features == features)
{
glink_process_negotiation_complete( if_ptr, version, features );
}
else
{
/* Continue negotiating */
if_ptr->tx_cmd_version(if_ptr, version, negotiated_features);
}
}
else
{
while (ver->version > version && xport_ctx->version_indx > 0)
{
/* Next time use older version */
xport_ctx->version_indx--;
ver = &xport_ctx->version_array[xport_ctx->version_indx];
}
/* Versions don't match, return ACK with the feature set that we support */
if_ptr->tx_cmd_version(if_ptr, ver->version, ver->features);
}
}
/*===========================================================================
FUNCTION glink_rx_cmd_ch_remote_open
===========================================================================*/
/**
* Receive remote channel open request;
* Calls glink_transport_if:: tx_cmd_ch_remote_open_ack as a result
*
* @param[in] if_ptr Pointer to interface instance; must be unique
for each edge
* @param[in] rcid Remote Channel ID
* @param[in] name String name for logical channel
* @param[in] priority xport priority requested by remote side
*
* @return None
*
* @sideeffects NONE
*/
/*=========================================================================*/
void glink_rx_cmd_ch_remote_open
(
glink_transport_if_type *if_ptr,
uint32 rcid,
const char *name,
glink_xport_priority priority
)
{
glink_channel_ctx_type *remote_ch_ctx = NULL;
glink_channel_ctx_type *allocated_ch_ctx;
glink_err_type status;
GLINK_OS_UNREFERENCED_PARAM( priority );
/* Allocate and initialize channel info structure */
remote_ch_ctx = glink_os_calloc( sizeof( glink_channel_ctx_type ) );
if(remote_ch_ctx == NULL)
{
GLINK_LOG_ERROR_EVENT( GLINK_EVENT_RM_CH_OPEN,
name,
if_ptr->glink_core_priv->xport,
if_ptr->glink_core_priv->remote_ss,
GLINK_STATUS_OUT_OF_RESOURCES );
ASSERT(0);
}
/* Fill in the channel info structure */
glink_os_string_copy(remote_ch_ctx->name, name, sizeof(remote_ch_ctx->name));
remote_ch_ctx->rcid = rcid;
status = glinki_add_ch_to_xport( if_ptr,
remote_ch_ctx,
&allocated_ch_ctx,
FALSE,
if_ptr->glink_priority );
GLINK_LOG_EVENT( allocated_ch_ctx,
GLINK_EVENT_RM_CH_OPEN,
name,
if_ptr->glink_core_priv->xport,
if_ptr->glink_core_priv->remote_ss,
status );
ASSERT(GLINK_STATUS_SUCCESS == status);
}
/*===========================================================================
FUNCTION glink_rx_cmd_ch_open_ack
===========================================================================*/
/**
* This function is invoked by the transport
* in response to glink_transport_if:: tx_cmd_ch_open
*
* @param[in] if_ptr Pointer to interface instance; must be unique
* for each edge
* @param[in] lcid Local Channel ID
* @param[in] migrated_ch_prio Negotiated xport priority from remote side
*
* @return None
*
* @sideeffects NONE
*/
/*=========================================================================*/
void glink_rx_cmd_ch_open_ack
(
glink_transport_if_type *if_ptr,
uint32 lcid,
glink_xport_priority migrated_ch_prio
)
{
glink_channel_ctx_type *open_ch_ctx;
glink_core_xport_ctx_type *xport_ctx;
glink_remote_state_type remote_state;
GLINK_OS_UNREFERENCED_PARAM( migrated_ch_prio );
xport_ctx = if_ptr->glink_core_priv;
/* Move to closed state. Implies we clean up the channel from the
* open list */
glink_os_cs_acquire(&xport_ctx->channel_q_cs);
open_ch_ctx = glinki_find_ch_ctx_by_lcid(xport_ctx, lcid);
if( open_ch_ctx->local_state == GLINK_LOCAL_CH_CLOSING )
{
/* Client called glink_close before gettng open ack.
* Ignore open ack and go for closing sequence */
glink_os_cs_release( &xport_ctx->channel_q_cs );
return;
}
ASSERT(open_ch_ctx->local_state == GLINK_LOCAL_CH_OPENING);
open_ch_ctx->local_state = GLINK_LOCAL_CH_OPENED;
remote_state = open_ch_ctx->remote_state;
glink_os_cs_release(&xport_ctx->channel_q_cs);
if (remote_state == GLINK_REMOTE_CH_OPENED)
{
/* remote channel is open on same xport as current xport.
* change channel state to GLINK_CH_STATE_OPEN and notify client */
open_ch_ctx->notify_state( open_ch_ctx,
open_ch_ctx->priv,
GLINK_CONNECTED );
}
GLINK_LOG_EVENT( open_ch_ctx,
GLINK_EVENT_CH_OPEN_ACK,
open_ch_ctx->name,
xport_ctx->xport,
xport_ctx->remote_ss,
lcid );
}
/*===========================================================================
FUNCTION glink_rx_cmd_ch_close_ack
===========================================================================*/
/**
* This function is invoked by the transport in response to
* glink_transport_if_type:: tx_cmd_ch_close
*
* @param[in] if_ptr Pointer to interface instance; must be unique
* for each edge
* @param[in] lcid Local Channel ID
*
* @return None
*
* @sideeffects NONE
*/
/*=========================================================================*/
void glink_rx_cmd_ch_close_ack
(
glink_transport_if_type *if_ptr,
uint32 lcid
)
{
glink_channel_ctx_type *open_ch_ctx;
glink_core_xport_ctx_type *xport_ctx;
xport_ctx = if_ptr->glink_core_priv;
glink_os_cs_acquire(&xport_ctx->channel_q_cs);
open_ch_ctx = glinki_find_ch_ctx_by_lcid(xport_ctx, lcid);
GLINK_LOG_EVENT( open_ch_ctx,
GLINK_EVENT_CH_CLOSE_ACK,
open_ch_ctx->name,
xport_ctx->xport,
xport_ctx->remote_ss,
lcid);
ASSERT( open_ch_ctx->local_state == GLINK_LOCAL_CH_CLOSING );
open_ch_ctx->local_state = GLINK_LOCAL_CH_CLOSED;
open_ch_ctx->lcid = GLINK_INVALID_CID;
if ( glink_is_remote_ch_closed( open_ch_ctx->remote_state ) )
{
glink_clean_channel_ctx( xport_ctx, open_ch_ctx );
}
glink_os_cs_release(&xport_ctx->channel_q_cs);
open_ch_ctx->notify_state( open_ch_ctx,
open_ch_ctx->priv,
GLINK_LOCAL_DISCONNECTED );
if ( glink_is_remote_ch_closed( open_ch_ctx->remote_state ) )
{
glink_os_free( open_ch_ctx );
}
}
/*===========================================================================
FUNCTION glink_rx_cmd_ch_remote_close
===========================================================================*/
/**
* Remote channel close request; will result in sending
* glink_transport_if_type:: tx_cmd_ch_remote_close_ack
*
* @param[in] if_ptr Pointer to interface instance; must be unique
* for each edge
* @param[in] rcid Remote Channel ID
*
* @return None.
*
* @sideeffects None.
*/
/*=========================================================================*/
void glink_rx_cmd_ch_remote_close
(
glink_transport_if_type *if_ptr,
uint32 rcid
)
{
glink_channel_ctx_type *open_ch_ctx;
glink_core_xport_ctx_type *xport_ctx;
glink_remote_state_type remote_state;
xport_ctx = if_ptr->glink_core_priv;
glink_os_cs_acquire( &xport_ctx->channel_q_cs );
open_ch_ctx = glinki_find_ch_ctx_by_rcid(xport_ctx, rcid);
GLINK_LOG_EVENT( open_ch_ctx,
GLINK_EVENT_CH_REMOTE_CLOSE,
open_ch_ctx->name,
xport_ctx->xport,
xport_ctx->remote_ss,
rcid );
/* It is possible that the remote subsystem sending close might crash
before we handle the close request from it */
if (open_ch_ctx->remote_state == GLINK_REMOTE_CH_SSR_RESET)
{
glink_os_cs_release(&xport_ctx->channel_q_cs);
return;
}
ASSERT( open_ch_ctx->remote_state == GLINK_REMOTE_CH_OPENED );
open_ch_ctx->remote_state = GLINK_REMOTE_CH_CLOSED;
open_ch_ctx->rcid = GLINK_INVALID_CID;
if ( glink_is_local_ch_closed( open_ch_ctx->local_state ) )
{
/* Local side never opened the channel OR it opened it but closed it */
/* Free channel resources */
glink_clean_channel_ctx( xport_ctx, open_ch_ctx );
}
remote_state = open_ch_ctx->remote_state;
glink_os_cs_release(&xport_ctx->channel_q_cs);
/* Send the remote close ACK back to the other side */
if_ptr->tx_cmd_ch_remote_close_ack(if_ptr, rcid);
if ( glink_is_local_ch_closed( open_ch_ctx->local_state ) )
{
/* Destroy channel context only if there isn't any pending intents */
if (remote_state != GLINK_REMOTE_CH_CLEANUP)
{
glink_os_free(open_ch_ctx);
}
}
else
{
/* Inform the client */
open_ch_ctx->notify_state(open_ch_ctx,
open_ch_ctx->priv,
GLINK_REMOTE_DISCONNECTED);
}
}
/*===========================================================================
FUNCTION glink_rx_put_pkt_ctx
===========================================================================*/
/**
* Transport invokes this call to receive a packet fragment (must
* have previously received an rx_cmd_rx_data packet)
*
* @param[in] if_ptr Pointer to interface instance; must be unique
* for each edge
* @param[in] rcid Remote Channel ID
* @param[in] intent_ptr Pointer to the intent fragment
* @param[in] complete True if pkt is complete
*
* @return None.
*
* @sideeffects None.
*/
/*=========================================================================*/
void glink_rx_put_pkt_ctx
(
glink_transport_if_type *if_ptr,
uint32 rcid,
glink_rx_intent_type *intent_ptr,
boolean complete
)
{
glink_channel_ctx_type *open_ch_ctx;
glink_core_xport_ctx_type *xport_ctx;
GLINK_OS_UNREFERENCED_PARAM( complete );
ASSERT(intent_ptr);
xport_ctx = if_ptr->glink_core_priv;
/* Find channel in the open_list */
glink_os_cs_acquire(&xport_ctx->channel_q_cs);
open_ch_ctx = glinki_find_ch_ctx_by_rcid(xport_ctx, rcid);
glink_os_cs_release(&xport_ctx->channel_q_cs);
GLINK_LOG_EVENT( open_ch_ctx,
GLINK_EVENT_CH_PUT_PKT_CTX,
open_ch_ctx->name,
xport_ctx->xport,
xport_ctx->remote_ss,
intent_ptr->iid);
xport_ctx->channel_receive_pkt(open_ch_ctx, intent_ptr);
}
/*===========================================================================
FUNCTION glink_rx_cmd_remote_sigs
DESCRIPTION Transport invokes this call to inform GLink of remote side
changing its control signals
ARGUMENTS *if_ptr Pointer to interface instance; must be unique
for each edge
rcid Remote Channel ID
remote_sigs Remote signal state.
RETURN VALUE None.
SIDE EFFECTS None
===========================================================================*/
void glink_rx_cmd_remote_sigs
(
glink_transport_if_type *if_ptr, /* Pointer to the interface instance */
uint32 rcid, /* Remote channel ID */
uint32 remote_sigs /* Remote control signals */
)
{
glink_core_xport_ctx_type *xport_ctx;
glink_channel_ctx_type *open_ch_ctx;
uint32 prev_sigs;
xport_ctx = if_ptr->glink_core_priv;
glink_os_cs_acquire(&xport_ctx->channel_q_cs);
open_ch_ctx = glinki_find_ch_ctx_by_rcid(xport_ctx, rcid);
glink_os_cs_release( &xport_ctx->channel_q_cs );
if (!glinki_channel_fully_opened(open_ch_ctx))
{
ASSERT(0);
}
/* Found channel, let client know of new remote signal state */
prev_sigs = open_ch_ctx->remote_sigs;
open_ch_ctx->remote_sigs = remote_sigs;
open_ch_ctx->notify_rx_sigs(open_ch_ctx,
open_ch_ctx->priv,
prev_sigs,
remote_sigs);
}