/* Management of Tx window, Tx resend, ACKs and out-of-sequence reception
*
* Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
* Written by David Howells (dhowells@redhat.com)
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version
* 2 of the License, or (at your option) any later version.
*/
#include <linux/module.h>
#include <linux/circ_buf.h>
#include <linux/net.h>
#include <linux/skbuff.h>
#include <linux/slab.h>
#include <linux/udp.h>
#include <net/sock.h>
#include <net/af_rxrpc.h>
#include "ar-internal.h"
static unsigned int rxrpc_ack_defer = 1;
static const char *const rxrpc_acks[] = {
"---", "REQ", "DUP", "OOS", "WIN", "MEM", "PNG", "PNR", "DLY", "IDL",
"-?-"
};
static const s8 rxrpc_ack_priority[] = {
[0] = 0,
[RXRPC_ACK_DELAY] = 1,
[RXRPC_ACK_REQUESTED] = 2,
[RXRPC_ACK_IDLE] = 3,
[RXRPC_ACK_PING_RESPONSE] = 4,
[RXRPC_ACK_DUPLICATE] = 5,
[RXRPC_ACK_OUT_OF_SEQUENCE] = 6,
[RXRPC_ACK_EXCEEDS_WINDOW] = 7,
[RXRPC_ACK_NOSPACE] = 8,
};
/*
* propose an ACK be sent
*/
void __rxrpc_propose_ACK(struct rxrpc_call *call, u8 ack_reason,
__be32 serial, bool immediate)
{
unsigned long expiry;
s8 prior = rxrpc_ack_priority[ack_reason];
ASSERTCMP(prior, >, 0);
_enter("{%d},%s,%%%x,%u",
call->debug_id, rxrpc_acks[ack_reason], ntohl(serial),
immediate);
if (prior < rxrpc_ack_priority[call->ackr_reason]) {
if (immediate)
goto cancel_timer;
return;
}
/* update DELAY, IDLE, REQUESTED and PING_RESPONSE ACK serial
* numbers */
if (prior == rxrpc_ack_priority[call->ackr_reason]) {
if (prior <= 4)
call->ackr_serial = serial;
if (immediate)
goto cancel_timer;
return;
}
call->ackr_reason = ack_reason;
call->ackr_serial = serial;
switch (ack_reason) {
case RXRPC_ACK_DELAY:
_debug("run delay timer");
call->ack_timer.expires = jiffies + rxrpc_ack_timeout * HZ;
add_timer(&call->ack_timer);
return;
case RXRPC_ACK_IDLE:
if (!immediate) {
_debug("run defer timer");
expiry = 1;
goto run_timer;
}
goto cancel_timer;
case RXRPC_ACK_REQUESTED:
if (!rxrpc_ack_defer)
goto cancel_timer;
if (!immediate || serial == cpu_to_be32(1)) {
_debug("run defer timer");
expiry = rxrpc_ack_defer;
goto run_timer;
}
default:
_debug("immediate ACK");
goto cancel_timer;
}
run_timer:
expiry += jiffies;
if (!timer_pending(&call->ack_timer) ||
time_after(call->ack_timer.expires, expiry))
mod_timer(&call->ack_timer, expiry);
return;
cancel_timer:
_debug("cancel timer %%%u", ntohl(serial));
try_to_del_timer_sync(&call->ack_timer);
read_lock_bh(&call->state_lock);
if (call->state <= RXRPC_CALL_COMPLETE &&
!test_and_set_bit(RXRPC_CALL_ACK, &call->events))
rxrpc_queue_call(call);
read_unlock_bh(&call->state_lock);
}
/*
* propose an ACK be sent, locking the call structure
*/
void rxrpc_propose_ACK(struct rxrpc_call *call, u8 ack_reason,
__be32 serial, bool immediate)
{
s8 prior = rxrpc_ack_priority[ack_reason];
if (prior > rxrpc_ack_priority[call->ackr_reason]) {
spin_lock_bh(&call->lock);
__rxrpc_propose_ACK(call, ack_reason, serial, immediate);
spin_unlock_bh(&call->lock);
}
}
/*
* set the resend timer
*/
static void rxrpc_set_resend(struct rxrpc_call *call, u8 resend,
unsigned long resend_at)
{
read_lock_bh(&call->state_lock);
if (call->state >= RXRPC_CALL_COMPLETE)
resend = 0;
if (resend & 1) {
_debug("SET RESEND");
set_bit(RXRPC_CALL_RESEND, &call->events);
}
if (resend & 2) {
_debug("MODIFY RESEND TIMER");
set_bit(RXRPC_CALL_RUN_RTIMER, &call->flags);
mod_timer(&call->resend_timer, resend_at);
} else {
_debug("KILL RESEND TIMER");
del_timer_sync(&call->resend_timer);
clear_bit(RXRPC_CALL_RESEND_TIMER,
|