blob: b0a41f46e2c128b8eb2a4a28c96f758ab99d6223 [file] [log] [blame]
/*
* teamd_lw_psr.c - Team port periodic send/receive link watcher
* Copyright (C) 2012-2015 Jiri Pirko <jiri@resnulli.us>
* Copyright (C) 2014 Erik Hugne <erik.hugne@ericsson.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <private/misc.h>
#include "teamd.h"
#include "teamd_link_watch.h"
#include "teamd_config.h"
/*
* Generic periodic send/receive link watch "template"
*/
static const struct timespec lw_psr_default_init_wait = { 0, 1 };
#define LW_PSR_DEFAULT_INTERVAL 1000
#define LW_PSR_DEFAULT_MISSED_MAX 3
#define LW_PERIODIC_CB_NAME "lw_periodic"
static int lw_psr_callback_periodic(struct teamd_context *ctx, int events, void *priv)
{
struct lw_common_port_priv *common_ppriv = priv;
struct lw_psr_port_priv *psr_ppriv = priv;
struct teamd_port *tdport = common_ppriv->tdport;
bool link_up = common_ppriv->link_up;
int err;
if (psr_ppriv->reply_received) {
link_up = true;
psr_ppriv->missed = 0;
} else {
psr_ppriv->missed++;
if (psr_ppriv->missed > psr_ppriv->missed_max && link_up) {
teamd_log_dbg(ctx, "%s: Missed %u replies (max %u).",
tdport->ifname, psr_ppriv->missed,
psr_ppriv->missed_max);
link_up = false;
}
}
err = teamd_link_watch_check_link_up(ctx, tdport,
common_ppriv, link_up);
if (err)
return err;
psr_ppriv->reply_received = false;
return psr_ppriv->ops->send(psr_ppriv);
}
#define LW_SOCKET_CB_NAME "lw_socket"
static int lw_psr_callback_socket(struct teamd_context *ctx, int events, void *priv)
{
struct lw_psr_port_priv *psr_ppriv = priv;
return psr_ppriv->ops->receive(psr_ppriv);
}
static int lw_psr_load_options(struct teamd_context *ctx,
struct teamd_port *tdport,
struct lw_psr_port_priv *psr_ppriv)
{
struct teamd_config_path_cookie *cpcookie = psr_ppriv->common.cpcookie;
int err;
int tmp;
err = teamd_config_int_get(ctx, &tmp, "@.interval", cpcookie);
if (!err) {
if (tmp < 0) {
teamd_log_err("\"interval\" must not be negative number.");
return -EINVAL;
}
} else {
tmp = LW_PSR_DEFAULT_INTERVAL;
}
teamd_log_dbg(ctx, "interval \"%d\".", tmp);
ms_to_timespec(&psr_ppriv->interval, tmp);
err = teamd_config_int_get(ctx, &tmp, "@.init_wait", cpcookie);
if (!err)
ms_to_timespec(&psr_ppriv->init_wait, tmp);
/* if init_wait is set to 0, use default_init_wait */
if (err || !tmp)
psr_ppriv->init_wait = lw_psr_default_init_wait;
teamd_log_dbg(ctx, "init_wait \"%d\".", timespec_to_ms(&psr_ppriv->init_wait));
err = teamd_config_int_get(ctx, &tmp, "@.missed_max", cpcookie);
if (!err) {
if (tmp < 0) {
teamd_log_err("\"missed_max\" must not be negative number.");
return -EINVAL;
}
} else {
tmp = LW_PSR_DEFAULT_MISSED_MAX;
}
teamd_log_dbg(ctx, "missed_max \"%d\".", tmp);
psr_ppriv->missed_max = tmp;
return 0;
}
struct lw_psr_port_priv *
lw_psr_ppriv_get(struct lw_common_port_priv *common_ppriv)
{
return (struct lw_psr_port_priv *) common_ppriv;
}
int lw_psr_port_added(struct teamd_context *ctx, struct teamd_port *tdport,
void *priv, void *creator_priv)
{
struct lw_psr_port_priv *psr_ppriv = priv;
int err;
err = lw_psr_load_options(ctx, tdport, psr_ppriv);
if (err) {
teamd_log_err("Failed to load options.");
return err;
}
err = psr_ppriv->ops->load_options(ctx, tdport, psr_ppriv);
if (err) {
teamd_log_err("Failed to load options.");
return err;
}
err = psr_ppriv->ops->sock_open(psr_ppriv);
if (err) {
teamd_log_err("Failed to create socket.");
return err;
}
err = teamd_loop_callback_fd_add(ctx, LW_SOCKET_CB_NAME, psr_ppriv,
lw_psr_callback_socket,
psr_ppriv->sock,
TEAMD_LOOP_FD_EVENT_READ);
if (err) {
teamd_log_err("Failed add socket callback.");
goto close_sock;
}
err = teamd_loop_callback_timer_add_set(ctx, LW_PERIODIC_CB_NAME,
psr_ppriv,
lw_psr_callback_periodic,
&psr_ppriv->interval,
&psr_ppriv->init_wait);
if (err) {
teamd_log_err("Failed add callback timer");
goto socket_callback_del;
}
err = team_set_port_user_linkup_enabled(ctx->th, tdport->ifindex, true);
if (err) {
teamd_log_err("%s: Failed to enable user linkup.",
tdport->ifname);
goto periodic_callback_del;
}
teamd_loop_callback_enable(ctx, LW_SOCKET_CB_NAME, psr_ppriv);
teamd_loop_callback_enable(ctx, LW_PERIODIC_CB_NAME, psr_ppriv);
return 0;
periodic_callback_del:
teamd_loop_callback_del(ctx, LW_PERIODIC_CB_NAME, psr_ppriv);
socket_callback_del:
teamd_loop_callback_del(ctx, LW_SOCKET_CB_NAME, psr_ppriv);
close_sock:
psr_ppriv->ops->sock_close(psr_ppriv);
return err;
}
void lw_psr_port_removed(struct teamd_context *ctx, struct teamd_port *tdport,
void *priv, void *creator_priv)
{
struct lw_psr_port_priv *psr_ppriv = priv;
teamd_loop_callback_del(ctx, LW_PERIODIC_CB_NAME, psr_ppriv);
teamd_loop_callback_del(ctx, LW_SOCKET_CB_NAME, psr_ppriv);
psr_ppriv->ops->sock_close(psr_ppriv);
}
int lw_psr_state_interval_get(struct teamd_context *ctx,
struct team_state_gsc *gsc,
void *priv)
{
struct lw_common_port_priv *common_ppriv = priv;
struct lw_psr_port_priv *psr_ppriv = lw_psr_ppriv_get(common_ppriv);
gsc->data.int_val = timespec_to_ms(&psr_ppriv->interval);
return 0;
}
int lw_psr_state_init_wait_get(struct teamd_context *ctx,
struct team_state_gsc *gsc,
void *priv)
{
struct lw_common_port_priv *common_ppriv = priv;
struct lw_psr_port_priv *psr_ppriv = lw_psr_ppriv_get(common_ppriv);
gsc->data.int_val = timespec_to_ms(&psr_ppriv->init_wait);
return 0;
}
int lw_psr_state_missed_max_get(struct teamd_context *ctx,
struct team_state_gsc *gsc,
void *priv)
{
struct lw_common_port_priv *common_ppriv = priv;
struct lw_psr_port_priv *psr_ppriv = lw_psr_ppriv_get(common_ppriv);
gsc->data.int_val = psr_ppriv->missed_max;
return 0;
}
int lw_psr_state_missed_get(struct teamd_context *ctx,
struct team_state_gsc *gsc,
void *priv)
{
struct lw_common_port_priv *common_ppriv = priv;
struct lw_psr_port_priv *psr_ppriv = lw_psr_ppriv_get(common_ppriv);
gsc->data.int_val = psr_ppriv->missed;
return 0;
}