blob: 780f60efa96515c0335d8ac11d27abe12016dc00 [file] [log] [blame]
/*
<:copyright-BRCM:2016:DUAL/GPL:standard
Broadcom Proprietary and Confidential.(c) 2016 Broadcom
All Rights Reserved
Unless you and Broadcom execute a separate written software license
agreement governing use of this software, this software is licensed
to you under the terms of the GNU General Public License version 2
(the "GPL"), available at http://www.broadcom.com/licenses/GPLv2.php,
with the following added to such license:
As a special exception, the copyright holders of this software give
you permission to link this software with independent modules, and
to copy and distribute the resulting executable under terms of your
choice, provided that you also meet, for each linked independent
module, the terms and conditions of the license of that module.
An independent module is a module which is not derived from this
software. The special exception does not apply to any modifications
of the software.
Not withstanding the above, under no circumstances may you combine
this software in any way with any other Broadcom software provided
under a license other than the GPL, without Broadcom's express prior
written consent.
:>
*/
#include <bcmos_system.h>
#include <bcmolt_msg.h>
#include <bcmolt_api.h>
#include <bcmolt_model_types.h>
#include <bcmolt_model_ids.h>
#include "bcm_keep_alive.h"
keep_alive_msg_orig2str_t keep_alive_msg_orig2str[] =
{
{BCM_KEEP_ALIVE_MSG_ORIG_DEVICE, "device"},
{BCM_KEEP_ALIVE_MSG_ORIG_HOST, "host"},
{-1}
};
static void keep_alive_tx_handler(bcmos_keep_alive_info *ka_info);
static bcmos_timer_rc keep_alive_timer_handler(bcmos_timer *timer, long data)
{
bcmos_keep_alive_info *ka_info;
uint32_t last_recv_ka_ts;
uint32_t ka_interval;
uint32_t ka_tolerance;
uint32_t time_diff_from_prev_ka;
uint32_t ka_process_start_ts;
uint32_t prev_ka_ts;
ka_info = (bcmos_keep_alive_info *)data;
last_recv_ka_ts = ka_info->last_recv_ka_timestamp;
ka_interval = ka_info->ka_interval;
ka_tolerance = ka_info->ka_tolerance;
ka_process_start_ts = ka_info->ka_process_start_ts;
/* The first keep alive message that arrives changes last_recv_ka_ts to a value different than zero.
* If no keep alive message has arrived yet then the previous keep alive time stamp is the time the process has
* started. */
prev_ka_ts = (last_recv_ka_ts ? last_recv_ka_ts : ka_process_start_ts);
/* Time difference between current time and the previous keep alive message */
time_diff_from_prev_ka = bcmos_timestamp() - prev_ka_ts;
/* Check if keep alive message has been received in the past keep alive interval, both part of the equation can't overflow:
* The left part is subtraction of two unsigned integers and the right part is restricted by ka_interval max value and ka_tolerance max value -
* their multiplication can't exceed max unsigned integer */
if (time_diff_from_prev_ka > ka_interval * (ka_tolerance + 1))
{
if (last_recv_ka_ts)
{
BCMOS_TRACE_INFO(
"Didn't receive keep alive message for %u seconds - about to disconnect\n",
(bcmos_timestamp() - last_recv_ka_ts) / BCMOS_MICROSECONDS_IN_SECONDS);
}
else
{
BCMOS_TRACE_INFO(
"Didn't receive keep alive message since keep alive process started %u seconds ago - "
"about to disconnect\n",
(bcmos_timestamp() - ka_process_start_ts) / BCMOS_MICROSECONDS_IN_SECONDS);
}
if (ka_info->disconnect_handler(ka_info->device) != BCM_ERR_OK)
{
if (ka_info->orig == BCM_KEEP_ALIVE_MSG_ORIG_HOST)
BCMOS_TRACE_ERR("Error while trying to send disconnect message (device=%d)\n", ka_info->device);
else
BCMOS_TRACE_ERR("Error while trying to invoke disconnect from the host procedure\n");
}
return BCMOS_TIMER_STOP;
}
/* Send Keep alive message to the remote side */
keep_alive_tx_handler(ka_info);
return BCMOS_TIMER_OK;
}
static void keep_alive_tx_handler(bcmos_keep_alive_info *ka_info)
{
bcmos_keep_alive_msg ka_msg = {};
bcmolt_device_key key = {};
if (ka_info->orig == BCM_KEEP_ALIVE_MSG_ORIG_HOST)
{
BCMOLT_OPER_INIT(&ka_msg.host, device, host_keep_alive, key);
ka_msg.host.hdr.hdr.type = BCMOLT_MSG_TYPE_SET;
BCMOLT_OPER_PROP_SET(&ka_msg.host, device, host_keep_alive, sequence_number, ka_info->last_sent_ka_seq++);
BCMOLT_OPER_PROP_SET(&ka_msg.host, device, host_keep_alive, time_stamp, bcmos_timestamp());
}
else
{
BCMOLT_AUTO_INIT(&ka_msg.device, device, device_keep_alive);
ka_msg.device.hdr.hdr.type = BCMOLT_MSG_TYPE_SET;
ka_msg.device.data.sequence_number = ka_info->last_sent_ka_seq++;
ka_msg.device.data.time_stamp = bcmos_timestamp();
}
if (ka_info->send_handler(ka_info->device, (bcmolt_msg *)&ka_msg) != BCM_ERR_OK)
{
BCMOS_TRACE_INFO("Error while trying to send keep alive sequence number %u\n", ka_info->last_sent_ka_seq);
}
}
void keep_alive_rx_handler(const bcmos_keep_alive_data_msg *ka_data_msg, bcmos_keep_alive_info *ka_info)
{
uint32_t seq_diff;
uint32_t rx_msg_seq;
uint32_t rx_msg_ts;
/* Tricky part - rx_msg_seq is NOT from the originator but from the other side */
if (ka_info->orig == BCM_KEEP_ALIVE_MSG_ORIG_HOST)
{
rx_msg_seq = ka_data_msg->device.sequence_number;
rx_msg_ts = ka_data_msg->device.time_stamp;
}
else
{
rx_msg_seq = ka_data_msg->host.sequence_number;
rx_msg_ts = ka_data_msg->host.time_stamp;
}
/* If last_recv_ka_seq and last_recv_ka_timestamp are both zeros then it's the first message */
if (ka_info->last_recv_ka_seq || ka_info->last_recv_ka_timestamp)
{
/* All part of the equation are unsigned integers so warp around is already taken care of */
seq_diff = rx_msg_seq - ka_info->last_recv_ka_seq;
/* In the normal case seq_diff should be one */
if (seq_diff > 1UL)
{
BCMOS_TRACE_INFO(
"Current keep alive sequence number is %u while previous keep alive sequence number was %u\n",
rx_msg_seq,
ka_info->last_recv_ka_seq);
BCMOS_TRACE_INFO(
"Current time stamp is %u while previous time stamp was %u, receive timestamp is %u\n",
bcmos_timestamp(),
ka_info->last_recv_ka_timestamp,rx_msg_ts);
if (seq_diff <= ka_info->ka_tolerance)
{
BCMOS_TRACE_INFO(
"Missed %u keep alive messages (keep alive tolerance is to lose %d messages)\n",
seq_diff,
ka_info->ka_tolerance);
}
else
{
BCMOS_TRACE_ERR(
"Missed %u keep alive messages (keep alive tolerance is to lose %d messages - "
"about to disconnect)\n",
seq_diff,
ka_info->ka_tolerance);
stop_keep_alive_process(ka_info);
if (ka_info->disconnect_handler(ka_info->device) != BCM_ERR_OK)
{
if (ka_info->orig == BCM_KEEP_ALIVE_MSG_ORIG_HOST)
{
BCMOS_TRACE_ERR(
"Error while trying to send disconnect message (device=%d)\n",
ka_info->device);
}
else
{
BCMOS_TRACE_ERR("Error while trying to send disconnect from the host message\n");
}
}
}
}
}
/* Stroe at the recieving side's database, the sequence number of the last message that came */
ka_info->last_recv_ka_seq = rx_msg_seq;
/* The time stamp is taken from the clock at the receiving side , not what came in the message */
ka_info->last_recv_ka_timestamp = bcmos_timestamp();
}
bcmos_errno create_keep_alive_timer(bcmos_keep_alive_info *ka_info, bcmos_module_id module_id)
{
bcmos_timer_parm timer_params = {};
bcmos_errno rc = BCM_ERR_OK;
ka_info->last_recv_ka_timestamp = 0;
ka_info->last_recv_ka_seq = 0;
snprintf(ka_info->ka_timer.name, sizeof(ka_info->ka_timer.name), "keep_alive_t%u", ka_info->device);
timer_params.name = ka_info->ka_timer.name;
timer_params.owner = module_id;
timer_params.periodic = BCMOS_TRUE;
timer_params.handler = keep_alive_timer_handler;
timer_params.data = (long)ka_info;
rc = bcmos_timer_create(&ka_info->ka_timer.timer, &timer_params);
if (rc != BCM_ERR_OK)
{
BCMOS_TRACE_ERR(
"keep alive timer creation failed, bcmos_timer_create returned error %s (%d)\n",
bcmos_strerror(rc),
rc);
}
return rc;
}
void start_keep_alive_process(bcmos_keep_alive_info *ka_info)
{
ka_info->last_recv_ka_timestamp = 0;
ka_info->last_recv_ka_seq = 0;
ka_info->ka_process_start_ts = bcmos_timestamp();
bcmos_timer_start(&ka_info->ka_timer.timer, ka_info->ka_interval);
}
void stop_keep_alive_process(bcmos_keep_alive_info *ka_info)
{
bcmos_timer_stop(&ka_info->ka_timer.timer);
}
#ifdef __KERNEL__
EXPORT_SYMBOL(create_keep_alive_timer);
EXPORT_SYMBOL(keep_alive_rx_handler);
MODULE_DESCRIPTION("BCM device keep-alive support");
MODULE_LICENSE("Dual BSD/GPL");
#endif