/**
@file rundown_protection.cpp
@brief Rundown Protection implementation for Linux kernel
@details Copyright (c) 2022 Acronis International GmbH
@author Denis Kopyrin (Denis.Kopyrin@acronis.com)
@since $Id: $
*/
#include "rundown_protection.h"
#include "debug.h"
#define RP_RUNDOWN_ACTIVE 1
#define RP_RUNDOWN_COUNT_SHIFT 1
#define RP_RUNDOWN_COUNT_INC (1 << RP_RUNDOWN_COUNT_SHIFT)
#define RP_LOG IPRINTF
static inline
void rundown_wait_block_init(rundown_wait_block_t *rwb, rundown_protection_finalizer_t finalizer, void *finalizer_ctx) {
atomic64_set(&rwb->ref_cnt, 0);
init_waitqueue_head(&rwb->wait_queue);
rwb->finalizer = finalizer;
rwb->finalizer_ctx = finalizer_ctx;
}
static inline
void rundown_wait_block_wait(rundown_wait_block_t *rwb) {
wait_event(rwb->wait_queue, 0 == atomic64_read(&rwb->ref_cnt));
}
static inline
long rundown_wait_block_wait_timeout(rundown_wait_block_t *rwb, unsigned long timeout_jiffies) {
return wait_event_timeout(rwb->wait_queue, 0 == atomic64_read(&rwb->ref_cnt), timeout_jiffies);
}
static inline
int64_t rundown_wait_block_decrement_count_and_notify(rundown_wait_block_t *rwb) {
int64_t leftCount = atomic64_sub_return(1, &rwb->ref_cnt);
if (0 == leftCount) {
wake_up(&rwb->wait_queue);
if (rwb->finalizer) {
IPRINTF("Invoking finalizer");
rwb->finalizer(rwb->finalizer_ctx);
IPRINTF("Finalizer completed");
}
}
return leftCount;
}
static inline
void rundown_wait_block_add_count(rundown_wait_block_t *rwb, int64_t cnt) {
atomic64_add(cnt, &rwb->ref_cnt);
}
static inline
int64_t rundown_wait_block_get_count(rundown_wait_block_t *rwb) {
return atomic64_read(&rwb->ref_cnt);
}
void simple_rundown_protection_init(simple_rundown_protection_t *srp, rundown_protection_finalizer_t finalizer, void *finalizer_ctx, bool ready) {
atomic64_set(&srp->data, ready ? 0 : RP_RUNDOWN_ACTIVE);
rundown_wait_block_init(&srp->wait_block, finalizer, finalizer_ctx);
}
bool simple_rundown_protection_lock(simple_rundown_protection_t *srp) {
int64_t old_value, new_value;
do {
old_value = atomic64_read(&srp->data);
if (old_value & RP_RUNDOWN_ACTIVE)
return false;
new_value = old_value + RP_RUNDOWN_COUNT_INC;
} while(old_value != atomic64_cmpxchg(&srp->data, old_value, new_value));
return true;
}
void simple_rundown_protection_unlock(simple_rundown_protection_t *srp) {
int64_t value;
while (true) {
value = atomic64_read(&srp->data);
if (!(value & RP_RUNDOWN_ACTIVE)) {
// active
int64_t new_value = value - RP_RUNDOWN_COUNT_INC;
if (value == atomic64_cmpxchg(&srp->data, value, new_value))
return;
} else {
// rundown
int64_t cnt;
RP_LOG("releasing count...");
cnt = rundown_wait_block_decrement_count_and_notify(&srp->wait_block);
RP_LOG("released count, left=%lld", cnt);
return;
}
}
}
void simple_rundown_protection_set_rundown_active(simple_rundown_protection_t *srp) {
int64_t count;
int64_t value = atomic64_cmpxchg(&srp->data, 0, RP_RUNDOWN_ACTIVE);
RP_LOG("");
if (value & RP_RUNDOWN_ACTIVE)
return;
do {
value = atomic64_read(&srp->data);
} while(value != atomic64_cmpxchg(&srp->data, value, RP_RUNDOWN_ACTIVE));
count = value >> RP_RUNDOWN_COUNT_SHIFT;
rundown_wait_block_add_count(&srp->wait_block, count);
RP_LOG("switched block count, left to wait for %lld unlocks", count);
}
void simple_rundown_protection_wait_for_rundown(simple_rundown_protection_t *srp) {
RP_LOG("waiting...");
rundown_wait_block_wait(&srp->wait_block);
RP_LOG("waiting done");
}
static const char *convert_wait_result_to_string(long ret) {
if (0 == ret) {
return "timeout";
} else if (ret < 0) {
return "interrupted";
} else {
return "ok";
}
}
bool simple_rundown_protection_wait_for_rundown_timeout(simple_rundown_protection_t *srp, unsigned long timeout_jiffies) {
long res;
RP_LOG("waiting...");
res = rundown_wait_block_wait_timeout(&srp->wait_block, timeout_jiffies);
RP_LOG("waiting done : %s[%ld]", convert_wait_result_to_string(res), res);
return res > 0;
}
void simple_rundown_protection_set_ready(simple_rundown_protection_t *srp) {
atomic64_set(&srp->data, 0);
}
int64_t simple_rundown_protection_get_pending_count(simple_rundown_protection_t *srp) {
return rundown_wait_block_get_count(&srp->wait_block);
}
|