/*
* UWB reservation management.
*
* Copyright (C) 2008 Cambridge Silicon Radio Ltd.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License 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.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <linux/kernel.h>
#include <linux/uwb.h>
#include <linux/slab.h>
#include <linux/random.h>
#include <linux/export.h>
#include "uwb-internal.h"
static void uwb_rsv_timer(unsigned long arg);
static const char *rsv_states[] = {
[UWB_RSV_STATE_NONE] = "none ",
[UWB_RSV_STATE_O_INITIATED] = "o initiated ",
[UWB_RSV_STATE_O_PENDING] = "o pending ",
[UWB_RSV_STATE_O_MODIFIED] = "o modified ",
[UWB_RSV_STATE_O_ESTABLISHED] = "o established ",
[UWB_RSV_STATE_O_TO_BE_MOVED] = "o to be moved ",
[UWB_RSV_STATE_O_MOVE_EXPANDING] = "o move expanding",
[UWB_RSV_STATE_O_MOVE_COMBINING] = "o move combining",
[UWB_RSV_STATE_O_MOVE_REDUCING] = "o move reducing ",
[UWB_RSV_STATE_T_ACCEPTED] = "t accepted ",
[UWB_RSV_STATE_T_CONFLICT] = "t conflict ",
[UWB_RSV_STATE_T_PENDING] = "t pending ",
[UWB_RSV_STATE_T_DENIED] = "t denied ",
[UWB_RSV_STATE_T_RESIZED] = "t resized ",
[UWB_RSV_STATE_T_EXPANDING_ACCEPTED] = "t expanding acc ",
[UWB_RSV_STATE_T_EXPANDING_CONFLICT] = "t expanding conf",
[UWB_RSV_STATE_T_EXPANDING_PENDING] = "t expanding pend",
[UWB_RSV_STATE_T_EXPANDING_DENIED] = "t expanding den ",
};
static const char *rsv_types[] = {
[UWB_DRP_TYPE_ALIEN_BP] = "alien-bp",
[UWB_DRP_TYPE_HARD] = "hard",
[UWB_DRP_TYPE_SOFT] = "soft",
[UWB_DRP_TYPE_PRIVATE] = "private",
[UWB_DRP_TYPE_PCA] = "pca",
};
bool uwb_rsv_has_two_drp_ies(struct uwb_rsv *rsv)
{
static const bool has_two_drp_ies[] = {
[UWB_RSV_STATE_O_INITIATED] = false,
[UWB_RSV_STATE_O_PENDING] = false,
[UWB_RSV_STATE_O_MODIFIED] = false,
[UWB_RSV_STATE_O_ESTABLISHED] = false,
[UWB_RSV_STATE_O_TO_BE_MOVED] = false,
[UWB_RSV_STATE_O_MOVE_COMBINING] = false,
[UWB_RSV_STATE_O_MOVE_REDUCING] = false,
[UWB_RSV_STATE_O_MOVE_EXPANDING] = true,
[UWB_RSV_STATE_T_ACCEPTED] = false,
[UWB_RSV_STATE_T_CONFLICT] = false,
[UWB_RSV_STATE_T_PENDING] = false,
[UWB_RSV_STATE_T_DENIED] = false,
[UWB_RSV_STATE_T_RESIZED] = false,
[UWB_RSV_STATE_T_EXPANDING_ACCEPTED] = true,
[UWB_RSV_STATE_T_EXPANDING_CONFLICT] = true,
[UWB_RSV_STATE_T_EXPANDING_PENDING] = true,
[UWB_RSV_STATE_T_EXPANDING_DENIED] = true,
};
return has_two_drp_ies[rsv->state];
}
/**
* uwb_rsv_state_str - return a string for a reservation state
* @state: the reservation state.
*/
const char *uwb_rsv_state_str(enum uwb_rsv_state state)
{
if (state < UWB_RSV_STATE_NONE || state >= UWB_RSV_STATE_LAST)
return "unknown";
return rsv_states[state];
}
EXPORT_SYMBOL_GPL(uwb_rsv_state_str);
/**
* uwb_rsv_type_str - return a string for a reservation type
* @type: the reservation type
*/
const char *uwb_rsv_type_str(enum uwb_drp_type type)
{
if (type < UWB_DRP_TYPE_ALIEN_BP || type > UWB_DRP_TYPE_PCA)
return "invalid";
return rsv_types[type];
}
EXPORT_SYMBOL_GPL(uwb_rsv_type_str);
void uwb_rsv_dump(char *text, struct uwb_rsv *rsv)
{
struct device *dev = &rsv->rc->uwb_dev.dev;
struct uwb_dev_addr devaddr;
char owner[UWB_ADDR_STRSIZE], target[UWB_ADDR_STRSIZE];
uwb_dev_addr_print(owner, sizeof(owner), &rsv->owner->dev_addr);
if (rsv->target.type == UWB_RSV_TARGET_DEV)
devaddr = rsv->target.dev->dev_addr;
else
devaddr = rsv->target.devaddr;
uwb_dev_addr_print(target, sizeof(target), &devaddr);
dev_dbg(dev, "rsv %s %s -> %s: %s\n",
text, owner, target, uwb_rsv_state_str(rsv->state));
}
static void uwb_rsv_release(struct kref *kref)
{
struct uwb_rsv *rsv = container_of(kref, struct uwb_rsv, kref);
kfree(rsv);
}
void uwb_rsv_get(struct uwb_rsv *rsv)
{
kref_get(&rsv->kref);
}
void uwb_rsv_put(struct uwb_rsv *rsv)
{
kref_put(&rsv->kref, uwb_rsv_release);
}
/*
* Get a free stream index for a reservation.
*
* If the target is a DevAddr (e.g., a WUSB cluster reservation) then
* the stream is allocated from a pool of per-RC stream indexes,
* otherwise a unique stream index for the target is selected.
*/
static int uwb_rsv_get_stream(struct uwb_rsv *rsv)
{
struct uwb_rc *rc = rsv->rc;
struct device *dev = &rc->uwb_dev.dev;
unsigned long *streams_bm;
int stream;
switch (rsv->target.type) {
case UWB_RSV_TARGET_DEV:
streams_bm = rsv->target.dev->streams;
break;
case UWB_RSV_TARGET_DEVADDR:
streams_bm = rc->uwb_dev.streams;
break;
default:
return -EINVAL;
}
stream = find_first_zero_bit(streams_bm, UWB_NUM_STREAMS);
if (stream >= UWB_NUM_STREAMS) {
dev_err(dev, "%s: no available stream found\n", __func__);
return -EBUSY;
}
rsv->stream = stream;
set_bit(stream, streams_bm);
dev_dbg(dev, "get stream %d\n", rsv->stream);
return 0;
}
static void uwb_rsv_put_stream(struct uwb_rsv *rsv)
{
struct uwb_rc *rc = rsv->rc;
struct device *dev = &rc->uwb_dev.dev;
unsigned long *streams_bm;
switch (rsv->target.type) {
case UWB_RSV_TARGET_DEV:
streams_bm = rsv->target.dev->streams;
break;
case UWB_RSV_TARGET_DEVADDR:
streams_bm = rc->uwb_dev.streams;
break;
default:
return;
}
clear_bit(rsv->stream, streams_bm);
dev_dbg(dev, "put stream %d\n", rsv->stream);
}
void uwb_rsv_backoff_win_timer(unsigned long arg)
{
struct uwb_drp_backoff_win *bow = (struct uwb_drp_backoff_win *)arg;
struct uwb_rc *rc = container_of(bow, struct uwb_rc, bow);
struct device *dev = &rc->uwb_dev.dev;
bow->can_reserve_extra_mases = true;
if (bow->total_expired <= 4) {
bow->total_expired++;
} else {
/* after 4 backoff window has expired we can exit from
* the backoff procedure */
bow->total_expired = 0;
bow->window = UWB_DRP_BACKOFF_WIN_MIN >> 1;
}
dev_dbg(dev, "backoff_win_timer total_expired=%d, n=%d\n", bow->total_expired, bow->n);
/* try to relocate all the "to be moved" relocations */
uwb_rsv_handle_drp_avail_change(rc);
}
void uwb_rsv_backoff_win_increment(struct uwb_rc *rc)
{
struct uwb_drp_backoff_win *bow = &rc->bow;
struct device *dev = &rc->uwb_dev.dev;
unsigned timeout_us;
dev_dbg(dev, "backoff_win_increment: window=%d\n", bow->window);
bow->can_reserve_extra_mases = false;
if((bow->window << 1) == UWB_DRP_BACKOFF_WIN_MAX)
return;
bow->window <<= 1;
bow->n = prandom_u32() & (bow->window - 1);
dev_dbg(dev, "new_window=%d, n=%d\n", bow->window, bow->n);
/* reset the timer associated variables */
timeout_us = bow->n * UWB_SUPERFRAME_LENGTH_US;
bow->total_expired = 0;
mod_timer(&bow->timer, jiffies + usecs_to_jiffies(timeout_us));
}
static void uwb_rsv_stroke_timer(struct uwb_rsv *rsv)
{
int sframes = UWB_MAX_LOST_BEACONS;
/*
* Multicast reservations can become established within 1
* super frame and should not be terminated if no response is
* received.
*/
if (rsv->state == UWB_RSV_STATE_NONE) {
sframes = 0;
} else if (rsv->is_multicast) {
if (rsv->state == UWB_RSV_STATE_O_INITIATED
|| rsv->state == UWB_RSV_STATE_O_MOVE_EXPANDING
|| rsv->state == UWB_RSV_STATE_O_MOVE_COMBINING
|| rsv->state == UWB_RSV_STATE_O_MOVE_REDUCING)
sframes = 1;
if (rsv->state == UWB_RSV_STATE_O_ESTABLISHED)
sframes = 0;
}
if (sframes