/*
* Copyright (c) 2013-2014 Richard Braun.
*
* 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 3 of the License, or
* (at your option) any later version.
*
* 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 .
*
*
* The method used by this module is described in the expired US patent
* 4809168, "Passive Serialization in a Multitasking Environment". It is
* similar to "Classic RCU (Read-Copy Update)" as found in Linux 2.6, with
* the notable difference that RCU actively starts grace periods, where
* passive serialization waits for two sequential "multiprocess checkpoints"
* (renamed global checkpoints in this implementation) to occur.
*
* It is used instead of RCU because of patents that may not allow writing
* an implementation not based on the Linux code (see
* http://lists.lttng.org/pipermail/lttng-dev/2013-May/020305.html). As
* patents expire, this module could be reworked to become a true RCU
* implementation. In the mean time, the module interface was carefully
* designed to be similar to RCU.
*
* TODO Gracefully handle large amounts of deferred works.
*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
/*
* Initial global checkpoint ID.
*
* Set to a high value to make sure overflows are correctly handled.
*/
#define LLSYNC_INITIAL_GCID ((unsigned int)-10)
/*
* Number of pending works beyond which to issue a warning.
*/
#define LLSYNC_NR_PENDING_WORKS_WARN 10000
struct llsync_data llsync_data;
struct llsync_cpu_data llsync_cpu_data __percpu;
struct llsync_waiter {
struct work work;
struct mutex lock;
struct condition cond;
int done;
};
static bool llsync_is_ready __read_mostly = false;
bool
llsync_ready(void)
{
return llsync_is_ready;
}
void __init
llsync_setup(void)
{
struct llsync_cpu_data *cpu_data;
unsigned int i;
spinlock_init(&llsync_data.lock);
work_queue_init(&llsync_data.queue0);
work_queue_init(&llsync_data.queue1);
syscnt_register(&llsync_data.sc_global_checkpoints,
"llsync_global_checkpoints");
syscnt_register(&llsync_data.sc_periodic_checkins,
"llsync_periodic_checkins");
syscnt_register(&llsync_data.sc_failed_periodic_checkins,
"llsync_failed_periodic_checkins");
llsync_data.gcid.value = LLSYNC_INITIAL_GCID;
for (i = 0; i < cpu_count(); i++) {
cpu_data = percpu_ptr(llsync_cpu_data, i);
work_queue_init(&cpu_data->queue0);
}
llsync_is_ready = true;
}
static void
llsync_process_global_checkpoint(void)
{
struct work_queue queue;
unsigned int nr_works;
assert(cpumap_find_first(&llsync_data.pending_checkpoints) == -1);
assert(llsync_data.nr_pending_checkpoints == 0);
nr_works = work_queue_nr_works(&llsync_data.queue0)
+ work_queue_nr_works(&llsync_data.queue1);
/* TODO Handle hysteresis */
if (!llsync_data.no_warning && (nr_works >= LLSYNC_NR_PENDING_WORKS_WARN)) {
llsync_data.no_warning = 1;
printf("llsync: warning: large number of pending works\n");
}
if (llsync_data.nr_registered_cpus == 0) {
work_queue_concat(&llsync_data.queue1, &llsync_data.queue0);
work_queue_init(&llsync_data.queue0);
} else {
cpumap_copy(&llsync_data.pending_checkpoints, &llsync_data.registered_cpus);
llsync_data.nr_pending_checkpoints = llsync_data.nr_registered_cpus;
}
work_queue_transfer(&queue, &llsync_data.queue1);
work_queue_transfer(&llsync_data.queue1, &llsync_data.queue0);
work_queue_init(&llsync_data.queue0);
if (work_queue_nr_works(&queue) != 0) {
work_queue_schedule(&queue, 0);
}
llsync_data.gcid.value++;
syscnt_inc(&llsync_data.sc_global_checkpoints);
}
static void
llsync_flush_works(struct llsync_cpu_data *cpu_data)
{
if (work_queue_nr_works(&cpu_data->queue0) == 0) {
return;
}
work_queue_concat(&llsync_data.queue0, &cpu_data->queue0);
work_queue_init(&cpu_data->queue0);
}
static void
llsync_commit_checkpoint(unsigned int cpu)
{
int pending;
pending = cpumap_test(&llsync_data.pending_checkpoints, cpu);
if (!pending) {
return;
}
cpumap_clear(&llsync_data.pending_checkpoints, cpu);
llsync_data.nr_pending_checkpoints--;
if (llsync_data.nr_pending_checkpoints == 0) {
llsync_process_global_checkpoint();
}
}
void
llsync_register(void)
{
struct llsync_cpu_data *cpu_data;
unsigned long flags;
unsigned int cpu;
cpu = cpu_id();
cpu_data = llsync_get_cpu_data();
spinlock_lock_intr_save(&llsync_data.lock, &flags);
assert(!cpu_data->registered);
assert(work_queue_nr_works(&cpu_data->queue0) == 0);
cpu_data->registered = 1;
cpu_data->gcid = llsync_data.gcid.value;
assert(!cpumap_test(&llsync_data.registered_cpus, cpu));
cpumap_set(&llsync_data.registered_cpus, cpu);
llsync_data.nr_registered_cpus++;
assert(!cpumap_test(&llsync_data.pending_checkpoints, cpu));
if ((llsync_data.nr_registered_cpus == 1)
&& (llsync_data.nr_pending_checkpoints == 0)) {
llsync_process_global_checkpoint();
}
spinlock_unlock_intr_restore(&llsync_data.lock, flags);
}
void
llsync_unregister(void)
{
struct llsync_cpu_data *cpu_data;
unsigned long flags;
unsigned int cpu;
cpu = cpu_id();
cpu_data = llsync_get_cpu_data();
spinlock_lock_intr_save(&llsync_data.lock, &flags);
llsync_flush_works(cpu_data);
assert(cpu_data->registered);
cpu_data->registered = 0;
assert(cpumap_test(&llsync_data.registered_cpus, cpu));
cpumap_clear(&llsync_data.registered_cpus, cpu);
llsync_data.nr_registered_cpus--;
/*
* Processor registration qualifies as a checkpoint. Since unregistering
* a processor also disables commits until it's registered again, perform
* one now.
*/
llsync_commit_checkpoint(cpu);
spinlock_unlock_intr_restore(&llsync_data.lock, flags);
}
void
llsync_report_periodic_event(void)
{
struct llsync_cpu_data *cpu_data;
unsigned int gcid;
assert(!cpu_intr_enabled());
assert(!thread_preempt_enabled());
cpu_data = llsync_get_cpu_data();
if (!cpu_data->registered) {
assert(work_queue_nr_works(&cpu_data->queue0) == 0);
return;
}
spinlock_lock(&llsync_data.lock);
llsync_flush_works(cpu_data);
gcid = llsync_data.gcid.value;
assert((gcid - cpu_data->gcid) <= 1);
/*
* If the local copy of the global checkpoint ID matches the true
* value, the current processor has checked in.
*
* Otherwise, there were no checkpoint since the last global checkpoint.
* Check whether this periodic event occurred during a read-side critical
* section, and if not, trigger a checkpoint.
*/
if (cpu_data->gcid == gcid) {
llsync_commit_checkpoint(cpu_id());
} else {
if (thread_llsync_in_read_cs()) {
syscnt_inc(&llsync_data.sc_failed_periodic_checkins);
} else {
cpu_data->gcid = gcid;
syscnt_inc(&llsync_data.sc_periodic_checkins);
llsync_commit_checkpoint(cpu_id());
}
}
spinlock_unlock(&llsync_data.lock);
}
void
llsync_defer(struct work *work)
{
struct llsync_cpu_data *cpu_data;
unsigned long flags;
thread_preempt_disable();
cpu_intr_save(&flags);
cpu_data = llsync_get_cpu_data();
work_queue_push(&cpu_data->queue0, work);
cpu_intr_restore(flags);
thread_preempt_enable();
}
static void
llsync_signal(struct work *work)
{
struct llsync_waiter *waiter;
waiter = structof(work, struct llsync_waiter, work);
mutex_lock(&waiter->lock);
waiter->done = 1;
condition_signal(&waiter->cond);
mutex_unlock(&waiter->lock);
}
void
llsync_wait(void)
{
struct llsync_waiter waiter;
work_init(&waiter.work, llsync_signal);
mutex_init(&waiter.lock);
condition_init(&waiter.cond);
waiter.done = 0;
llsync_defer(&waiter.work);
mutex_lock(&waiter.lock);
while (!waiter.done) {
condition_wait(&waiter.cond, &waiter.lock);
}
mutex_unlock(&waiter.lock);
}