Commit 96256460 authored by Greg Kroah-Hartman's avatar Greg Kroah-Hartman

staging: zcache: delete it

zcache is obsolete and not used anymore, Bob Liu has rewritten it and is
submitting it for inclusion through the main -mm tree, as it should have
been done in the first place...

Cc: Bob Liu <lliubbo@gmail.com>
Cc: Konrad Rzeszutek Wilk <konrad.wilk@oracle.com>
Cc: Kyungmin Park <kmpark@infradead.org>
Cc: Wanpeng Li <liwanp@linux.vnet.ibm.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent f21c5394
......@@ -130,8 +130,6 @@ source "drivers/staging/sb105x/Kconfig"
source "drivers/staging/fwserial/Kconfig"
source "drivers/staging/zcache/Kconfig"
source "drivers/staging/goldfish/Kconfig"
source "drivers/staging/netlogic/Kconfig"
......
......@@ -58,7 +58,6 @@ obj-$(CONFIG_DRM_IMX) += imx-drm/
obj-$(CONFIG_DGRP) += dgrp/
obj-$(CONFIG_SB105X) += sb105x/
obj-$(CONFIG_FIREWIRE_SERIAL) += fwserial/
obj-$(CONFIG_ZCACHE) += zcache/
obj-$(CONFIG_GOLDFISH) += goldfish/
obj-$(CONFIG_USB_DWC2) += dwc2/
obj-$(CONFIG_LUSTRE_FS) += lustre/
......
config ZCACHE
tristate "Dynamic compression of swap pages and clean pagecache pages"
depends on CRYPTO=y && SWAP=y && CLEANCACHE && FRONTSWAP
select CRYPTO_LZO
default n
help
Zcache doubles RAM efficiency while providing a significant
performance boosts on many workloads. Zcache uses
compression and an in-kernel implementation of transcendent
memory to store clean page cache pages and swap in RAM,
providing a noticeable reduction in disk I/O.
config ZCACHE_DEBUG
bool "Enable debug statistics"
depends on DEBUG_FS && ZCACHE
default n
help
This is used to provide an debugfs directory with counters of
how zcache is doing. You probably want to set this to 'N'.
config RAMSTER
tristate "Cross-machine RAM capacity sharing, aka peer-to-peer tmem"
depends on CONFIGFS_FS=y && SYSFS=y && !HIGHMEM && ZCACHE
depends on NET
# must ensure struct page is 8-byte aligned
select HAVE_ALIGNED_STRUCT_PAGE if !64BIT
default n
help
RAMster allows RAM on other machines in a cluster to be utilized
dynamically and symmetrically instead of swapping to a local swap
disk, thus improving performance on memory-constrained workloads
while minimizing total RAM across the cluster. RAMster, like
zcache2, compresses swap pages into local RAM, but then remotifies
the compressed pages to another node in the RAMster cluster.
config RAMSTER_DEBUG
bool "Enable ramster debug statistics"
depends on DEBUG_FS && RAMSTER
default n
help
This is used to provide an debugfs directory with counters of
how ramster is doing. You probably want to set this to 'N'.
# Depends on not-yet-upstreamed mm patches to export end_swap_bio_write and
# __add_to_swap_cache, and implement __swap_writepage (which is swap_writepage
# without the frontswap call. When these are in-tree, the dependency on
# BROKEN can be removed
config ZCACHE_WRITEBACK
bool "Allow compressed swap pages to be writtenback to swap disk"
depends on ZCACHE=y && BROKEN
default n
help
Zcache caches compressed swap pages (and other data) in RAM which
often improves performance by avoiding I/O's due to swapping.
In some workloads with very long-lived large processes, it can
instead reduce performance. Writeback decompresses zcache-compressed
pages (in LRU order) when under memory pressure and writes them to
the backing swap disk to ameliorate this problem. Policy driving
writeback is still under development.
zcache-y := zcache-main.o tmem.o zbud.o
zcache-$(CONFIG_ZCACHE_DEBUG) += debug.o
zcache-$(CONFIG_RAMSTER_DEBUG) += ramster/debug.o
zcache-$(CONFIG_RAMSTER) += ramster/ramster.o ramster/r2net.o
zcache-$(CONFIG_RAMSTER) += ramster/nodemanager.o ramster/tcp.o
zcache-$(CONFIG_RAMSTER) += ramster/heartbeat.o ramster/masklog.o
obj-$(CONFIG_ZCACHE) += zcache.o
** ZCACHE PLAN FOR PROMOTION FROM STAGING **
Last updated: Feb 13, 2013
PLAN STEPS
1. merge zcache and ramster to eliminate horrible code duplication
2. converge on a predictable, writeback-capable allocator
3. use debugfs instead of sysfs (per akpm feedback in 2011)
4. zcache side of cleancache/mm WasActive patch
5. zcache side of frontswap exclusive gets
6. zcache must be able to writeback to physical swap disk
(per Andrea Arcangeli feedback in 2011)
7. implement adequate policy for writeback
8. frontswap/cleancache work to allow zcache to be loaded
as a module
9. get core mm developer to review
10. incorporate feedback from review
11. get review/acks from 1-2 additional mm developers
12. incorporate any feedback from additional mm reviews
13. propose location/file-naming in mm tree
14. repeat 9-13 as necessary until akpm is happy and merges
STATUS/OWNERSHIP
1. DONE as part of "new" zcache; in staging/zcache for 3.9
2. DONE as part of "new" zcache (cf zbud.[ch]); in staging/zcache for 3.9
(this was the core of the zcache1 vs zcache2 flail)
3. DONE as part of "new" zcache; in staging/zcache for 3.9
4. DONE (w/caveats) as part of "new" zcache; per cleancache performance
feedback see https://lkml.org/lkml/2011/8/17/351, in
staging/zcache for 3.9; dependent on proposed mm patch, see
https://lkml.org/lkml/2012/1/25/300
5. DONE as part of "new" zcache; performance tuning only,
in staging/zcache for 3.9; dependent on frontswap patch
merged in 3.7 (33c2a174)
6. DONE (w/caveats), prototyped as part of "new" zcache, had
bad memory leak; reimplemented to use sjennings clever tricks
and proposed mm patches with new version in staging/zcache
for 3.9, see https://lkml.org/lkml/2013/2/6/437;
7. PROTOTYPED as part of "new" zcache; in staging/zcache for 3.9;
needs more review (plan to discuss at LSF/MM 2013)
9. IN PROGRESS; owned by Konrad Wilk; Mel Gorman provided
great feedback in August 2012 (unfortunately of "old"
zcache)
11. NOT DONE; owned by Konrad Wilk and Bob Liu
12. TBD (depends on quantity of feedback)
13. PROPOSED; one suggestion proposed by Dan; needs more ideas/feedback
14. TBD (depends on feedback)
WHO NEEDS TO AGREE
Not sure. Seth Jennings is now pursuing a separate but semi-parallel
track. Akpm clearly has to approve for any mm merge to happen. Minchan
Kim has interest but may be happy if/when zram is merged into mm. Konrad
Wilk may be maintainer if akpm decides compression is maintainable
separately from the rest of mm. (More LSF/MM 2013 discussion.)
ZCACHE FUTURE NEW FUNCTIONALITY
A. Support zsmalloc as an alternative high-density allocator
(See https://lkml.org/lkml/2013/1/23/511)
B. Possibly support three zbuds per pageframe when space allows
#include <linux/atomic.h>
#include "debug.h"
#ifdef CONFIG_ZCACHE_DEBUG
#include <linux/debugfs.h>
ssize_t zcache_obj_count;
ssize_t zcache_obj_count_max;
ssize_t zcache_objnode_count;
ssize_t zcache_objnode_count_max;
u64 zcache_eph_zbytes;
u64 zcache_eph_zbytes_max;
u64 zcache_pers_zbytes_max;
ssize_t zcache_eph_pageframes_max;
ssize_t zcache_pers_pageframes_max;
ssize_t zcache_pageframes_alloced;
ssize_t zcache_pageframes_freed;
ssize_t zcache_eph_zpages;
ssize_t zcache_eph_zpages_max;
ssize_t zcache_pers_zpages_max;
ssize_t zcache_flush_total;
ssize_t zcache_flush_found;
ssize_t zcache_flobj_total;
ssize_t zcache_flobj_found;
ssize_t zcache_failed_eph_puts;
ssize_t zcache_failed_pers_puts;
ssize_t zcache_failed_getfreepages;
ssize_t zcache_failed_alloc;
ssize_t zcache_put_to_flush;
ssize_t zcache_compress_poor;
ssize_t zcache_mean_compress_poor;
ssize_t zcache_eph_ate_tail;
ssize_t zcache_eph_ate_tail_failed;
ssize_t zcache_pers_ate_eph;
ssize_t zcache_pers_ate_eph_failed;
ssize_t zcache_evicted_eph_zpages;
ssize_t zcache_evicted_eph_pageframes;
ssize_t zcache_zero_filled_pages;
ssize_t zcache_zero_filled_pages_max;
#define ATTR(x) { .name = #x, .val = &zcache_##x, }
static struct debug_entry {
const char *name;
ssize_t *val;
} attrs[] = {
ATTR(obj_count), ATTR(obj_count_max),
ATTR(objnode_count), ATTR(objnode_count_max),
ATTR(flush_total), ATTR(flush_found),
ATTR(flobj_total), ATTR(flobj_found),
ATTR(failed_eph_puts), ATTR(failed_pers_puts),
ATTR(failed_getfreepages), ATTR(failed_alloc),
ATTR(put_to_flush),
ATTR(compress_poor), ATTR(mean_compress_poor),
ATTR(eph_ate_tail), ATTR(eph_ate_tail_failed),
ATTR(pers_ate_eph), ATTR(pers_ate_eph_failed),
ATTR(evicted_eph_zpages), ATTR(evicted_eph_pageframes),
ATTR(eph_pageframes), ATTR(eph_pageframes_max),
ATTR(pers_pageframes), ATTR(pers_pageframes_max),
ATTR(eph_zpages), ATTR(eph_zpages_max),
ATTR(pers_zpages), ATTR(pers_zpages_max),
ATTR(last_active_file_pageframes),
ATTR(last_inactive_file_pageframes),
ATTR(last_active_anon_pageframes),
ATTR(last_inactive_anon_pageframes),
ATTR(eph_nonactive_puts_ignored),
ATTR(pers_nonactive_puts_ignored),
ATTR(zero_filled_pages),
#ifdef CONFIG_ZCACHE_WRITEBACK
ATTR(outstanding_writeback_pages),
ATTR(writtenback_pages),
#endif
};
#undef ATTR
int zcache_debugfs_init(void)
{
unsigned int i;
struct dentry *root = debugfs_create_dir("zcache", NULL);
if (root == NULL)
return -ENXIO;
for (i = 0; i < ARRAY_SIZE(attrs); i++)
if (!debugfs_create_size_t(attrs[i].name, S_IRUGO, root, attrs[i].val))
goto out;
debugfs_create_u64("eph_zbytes", S_IRUGO, root, &zcache_eph_zbytes);
debugfs_create_u64("eph_zbytes_max", S_IRUGO, root, &zcache_eph_zbytes_max);
debugfs_create_u64("pers_zbytes", S_IRUGO, root, &zcache_pers_zbytes);
debugfs_create_u64("pers_zbytes_max", S_IRUGO, root, &zcache_pers_zbytes_max);
return 0;
out:
return -ENODEV;
}
/* developers can call this in case of ooms, e.g. to find memory leaks */
void zcache_dump(void)
{
unsigned int i;
for (i = 0; i < ARRAY_SIZE(attrs); i++)
pr_debug("zcache: %s=%zu\n", attrs[i].name, *attrs[i].val);
pr_debug("zcache: eph_zbytes=%llu\n", (unsigned long long)zcache_eph_zbytes);
pr_debug("zcache: eph_zbytes_max=%llu\n", (unsigned long long)zcache_eph_zbytes_max);
pr_debug("zcache: pers_zbytes=%llu\n", (unsigned long long)zcache_pers_zbytes);
pr_debug("zcache: pers_zbytes_max=%llu\n", (unsigned long long)zcache_pers_zbytes_max);
}
#endif
#include <linux/bug.h>
#ifdef CONFIG_ZCACHE_DEBUG
/* we try to keep these statistics SMP-consistent */
extern ssize_t zcache_obj_count;
static atomic_t zcache_obj_atomic = ATOMIC_INIT(0);
extern ssize_t zcache_obj_count_max;
static inline void inc_zcache_obj_count(void)
{
zcache_obj_count = atomic_inc_return(&zcache_obj_atomic);
if (zcache_obj_count > zcache_obj_count_max)
zcache_obj_count_max = zcache_obj_count;
}
static inline void dec_zcache_obj_count(void)
{
zcache_obj_count = atomic_dec_return(&zcache_obj_atomic);
BUG_ON(zcache_obj_count < 0);
};
extern ssize_t zcache_objnode_count;
static atomic_t zcache_objnode_atomic = ATOMIC_INIT(0);
extern ssize_t zcache_objnode_count_max;
static inline void inc_zcache_objnode_count(void)
{
zcache_objnode_count = atomic_inc_return(&zcache_objnode_atomic);
if (zcache_objnode_count > zcache_objnode_count_max)
zcache_objnode_count_max = zcache_objnode_count;
};
static inline void dec_zcache_objnode_count(void)
{
zcache_objnode_count = atomic_dec_return(&zcache_objnode_atomic);
BUG_ON(zcache_objnode_count < 0);
};
extern u64 zcache_eph_zbytes;
static atomic_long_t zcache_eph_zbytes_atomic = ATOMIC_INIT(0);
extern u64 zcache_eph_zbytes_max;
static inline void inc_zcache_eph_zbytes(unsigned clen)
{
zcache_eph_zbytes = atomic_long_add_return(clen, &zcache_eph_zbytes_atomic);
if (zcache_eph_zbytes > zcache_eph_zbytes_max)
zcache_eph_zbytes_max = zcache_eph_zbytes;
};
static inline void dec_zcache_eph_zbytes(unsigned zsize)
{
zcache_eph_zbytes = atomic_long_sub_return(zsize, &zcache_eph_zbytes_atomic);
};
extern u64 zcache_pers_zbytes;
static atomic_long_t zcache_pers_zbytes_atomic = ATOMIC_INIT(0);
extern u64 zcache_pers_zbytes_max;
static inline void inc_zcache_pers_zbytes(unsigned clen)
{
zcache_pers_zbytes = atomic_long_add_return(clen, &zcache_pers_zbytes_atomic);
if (zcache_pers_zbytes > zcache_pers_zbytes_max)
zcache_pers_zbytes_max = zcache_pers_zbytes;
}
static inline void dec_zcache_pers_zbytes(unsigned zsize)
{
zcache_pers_zbytes = atomic_long_sub_return(zsize, &zcache_pers_zbytes_atomic);
}
extern ssize_t zcache_eph_pageframes;
static atomic_t zcache_eph_pageframes_atomic = ATOMIC_INIT(0);
extern ssize_t zcache_eph_pageframes_max;
static inline void inc_zcache_eph_pageframes(void)
{
zcache_eph_pageframes = atomic_inc_return(&zcache_eph_pageframes_atomic);
if (zcache_eph_pageframes > zcache_eph_pageframes_max)
zcache_eph_pageframes_max = zcache_eph_pageframes;
};
static inline void dec_zcache_eph_pageframes(void)
{
zcache_eph_pageframes = atomic_dec_return(&zcache_eph_pageframes_atomic);
};
extern ssize_t zcache_pers_pageframes;
static atomic_t zcache_pers_pageframes_atomic = ATOMIC_INIT(0);
extern ssize_t zcache_pers_pageframes_max;
static inline void inc_zcache_pers_pageframes(void)
{
zcache_pers_pageframes = atomic_inc_return(&zcache_pers_pageframes_atomic);
if (zcache_pers_pageframes > zcache_pers_pageframes_max)
zcache_pers_pageframes_max = zcache_pers_pageframes;
}
static inline void dec_zcache_pers_pageframes(void)
{
zcache_pers_pageframes = atomic_dec_return(&zcache_pers_pageframes_atomic);
}
extern ssize_t zcache_pageframes_alloced;
static atomic_t zcache_pageframes_alloced_atomic = ATOMIC_INIT(0);
static inline void inc_zcache_pageframes_alloced(void)
{
zcache_pageframes_alloced = atomic_inc_return(&zcache_pageframes_alloced_atomic);
};
extern ssize_t zcache_pageframes_freed;
static atomic_t zcache_pageframes_freed_atomic = ATOMIC_INIT(0);
static inline void inc_zcache_pageframes_freed(void)
{
zcache_pageframes_freed = atomic_inc_return(&zcache_pageframes_freed_atomic);
}
extern ssize_t zcache_eph_zpages;
static atomic_t zcache_eph_zpages_atomic = ATOMIC_INIT(0);
extern ssize_t zcache_eph_zpages_max;
static inline void inc_zcache_eph_zpages(void)
{
zcache_eph_zpages = atomic_inc_return(&zcache_eph_zpages_atomic);
if (zcache_eph_zpages > zcache_eph_zpages_max)
zcache_eph_zpages_max = zcache_eph_zpages;
}
static inline void dec_zcache_eph_zpages(unsigned zpages)
{
zcache_eph_zpages = atomic_sub_return(zpages, &zcache_eph_zpages_atomic);
}
extern ssize_t zcache_pers_zpages;
static atomic_t zcache_pers_zpages_atomic = ATOMIC_INIT(0);
extern ssize_t zcache_pers_zpages_max;
static inline void inc_zcache_pers_zpages(void)
{
zcache_pers_zpages = atomic_inc_return(&zcache_pers_zpages_atomic);
if (zcache_pers_zpages > zcache_pers_zpages_max)
zcache_pers_zpages_max = zcache_pers_zpages;
}
static inline void dec_zcache_pers_zpages(unsigned zpages)
{
zcache_pers_zpages = atomic_sub_return(zpages, &zcache_pers_zpages_atomic);
}
extern ssize_t zcache_zero_filled_pages;
static atomic_t zcache_zero_filled_pages_atomic = ATOMIC_INIT(0);
extern ssize_t zcache_zero_filled_pages_max;
static inline void inc_zcache_zero_filled_pages(void)
{
zcache_zero_filled_pages = atomic_inc_return(
&zcache_zero_filled_pages_atomic);
if (zcache_zero_filled_pages > zcache_zero_filled_pages_max)
zcache_zero_filled_pages_max = zcache_zero_filled_pages;
}
static inline void dec_zcache_zero_filled_pages(void)
{
zcache_zero_filled_pages = atomic_dec_return(
&zcache_zero_filled_pages_atomic);
}
static inline unsigned long curr_pageframes_count(void)
{
return zcache_pageframes_alloced -
atomic_read(&zcache_pageframes_freed_atomic) -
atomic_read(&zcache_eph_pageframes_atomic) -
atomic_read(&zcache_pers_pageframes_atomic);
};
/* but for the rest of these, counting races are ok */
extern ssize_t zcache_flush_total;
extern ssize_t zcache_flush_found;
extern ssize_t zcache_flobj_total;
extern ssize_t zcache_flobj_found;
extern ssize_t zcache_failed_eph_puts;
extern ssize_t zcache_failed_pers_puts;
extern ssize_t zcache_failed_getfreepages;
extern ssize_t zcache_failed_alloc;
extern ssize_t zcache_put_to_flush;
extern ssize_t zcache_compress_poor;
extern ssize_t zcache_mean_compress_poor;
extern ssize_t zcache_eph_ate_tail;
extern ssize_t zcache_eph_ate_tail_failed;
extern ssize_t zcache_pers_ate_eph;
extern ssize_t zcache_pers_ate_eph_failed;
extern ssize_t zcache_evicted_eph_zpages;
extern ssize_t zcache_evicted_eph_pageframes;
extern ssize_t zcache_last_active_file_pageframes;
extern ssize_t zcache_last_inactive_file_pageframes;
extern ssize_t zcache_last_active_anon_pageframes;
extern ssize_t zcache_last_inactive_anon_pageframes;
static ssize_t zcache_eph_nonactive_puts_ignored;
static ssize_t zcache_pers_nonactive_puts_ignored;
#ifdef CONFIG_ZCACHE_WRITEBACK
extern ssize_t zcache_writtenback_pages;
extern ssize_t zcache_outstanding_writeback_pages;
#endif
static inline void inc_zcache_flush_total(void)
{
zcache_flush_total++;
};
static inline void inc_zcache_flush_found(void)
{
zcache_flush_found++;
};
static inline void inc_zcache_flobj_total(void)
{
zcache_flobj_total++;
};
static inline void inc_zcache_flobj_found(void)
{
zcache_flobj_found++;
};
static inline void inc_zcache_failed_eph_puts(void)
{
zcache_failed_eph_puts++;
};
static inline void inc_zcache_failed_pers_puts(void)
{
zcache_failed_pers_puts++;
};
static inline void inc_zcache_failed_getfreepages(void)
{
zcache_failed_getfreepages++;
};
static inline void inc_zcache_failed_alloc(void)
{
zcache_failed_alloc++;
};
static inline void inc_zcache_put_to_flush(void)
{
zcache_put_to_flush++;
};
static inline void inc_zcache_compress_poor(void)
{
zcache_compress_poor++;
};
static inline void inc_zcache_mean_compress_poor(void)
{
zcache_mean_compress_poor++;
};
static inline void inc_zcache_eph_ate_tail(void)
{
zcache_eph_ate_tail++;
};
static inline void inc_zcache_eph_ate_tail_failed(void)
{
zcache_eph_ate_tail_failed++;
};
static inline void inc_zcache_pers_ate_eph(void)
{
zcache_pers_ate_eph++;
};
static inline void inc_zcache_pers_ate_eph_failed(void)
{
zcache_pers_ate_eph_failed++;
};
static inline void inc_zcache_evicted_eph_zpages(unsigned zpages)
{
zcache_evicted_eph_zpages += zpages;
};
static inline void inc_zcache_evicted_eph_pageframes(void)
{
zcache_evicted_eph_pageframes++;
};
static inline void inc_zcache_eph_nonactive_puts_ignored(void)
{
zcache_eph_nonactive_puts_ignored++;
};
static inline void inc_zcache_pers_nonactive_puts_ignored(void)
{
zcache_pers_nonactive_puts_ignored++;
};
int zcache_debugfs_init(void);
#else
static inline void inc_zcache_obj_count(void) { };
static inline void dec_zcache_obj_count(void) { };
static inline void inc_zcache_objnode_count(void) { };
static inline void dec_zcache_objnode_count(void) { };
static inline void inc_zcache_eph_zbytes(unsigned clen) { };
static inline void dec_zcache_eph_zbytes(unsigned zsize) { };
static inline void inc_zcache_pers_zbytes(unsigned clen) { };
static inline void dec_zcache_pers_zbytes(unsigned zsize) { };
static inline void inc_zcache_eph_pageframes(void) { };
static inline void dec_zcache_eph_pageframes(void) { };
static inline void inc_zcache_pers_pageframes(void) { };
static inline void dec_zcache_pers_pageframes(void) { };
static inline void inc_zcache_pageframes_alloced(void) { };
static inline void inc_zcache_pageframes_freed(void) { };
static inline void inc_zcache_eph_zpages(void) { };
static inline void dec_zcache_eph_zpages(unsigned zpages) { };
static inline void inc_zcache_pers_zpages(void) { };
static inline void dec_zcache_pers_zpages(unsigned zpages) { };
static inline void inc_zcache_zero_filled_pages(void) { };
static inline void dec_zcache_zero_filled_pages(void) { };
static inline unsigned long curr_pageframes_count(void)
{
return 0;
};
static inline int zcache_debugfs_init(void)
{
return 0;
};
static inline void inc_zcache_flush_total(void) { };
static inline void inc_zcache_flush_found(void) { };
static inline void inc_zcache_flobj_total(void) { };
static inline void inc_zcache_flobj_found(void) { };
static inline void inc_zcache_failed_eph_puts(void) { };
static inline void inc_zcache_failed_pers_puts(void) { };
static inline void inc_zcache_failed_getfreepages(void) { };
static inline void inc_zcache_failed_alloc(void) { };
static inline void inc_zcache_put_to_flush(void) { };
static inline void inc_zcache_compress_poor(void) { };
static inline void inc_zcache_mean_compress_poor(void) { };
static inline void inc_zcache_eph_ate_tail(void) { };
static inline void inc_zcache_eph_ate_tail_failed(void) { };
static inline void inc_zcache_pers_ate_eph(void) { };
static inline void inc_zcache_pers_ate_eph_failed(void) { };
static inline void inc_zcache_evicted_eph_zpages(unsigned zpages) { };
static inline void inc_zcache_evicted_eph_pageframes(void) { };
static inline void inc_zcache_eph_nonactive_puts_ignored(void) { };
static inline void inc_zcache_pers_nonactive_puts_ignored(void) { };
#endif
/*
* zcache/ramster.h
*
* Placeholder to resolve ramster references when !CONFIG_RAMSTER
* Real ramster.h lives in ramster subdirectory.
*
* Copyright (c) 2009-2012, Dan Magenheimer, Oracle Corp.
*/
#ifndef _ZCACHE_RAMSTER_H_
#define _ZCACHE_RAMSTER_H_
#ifdef CONFIG_RAMSTER
#include "ramster/ramster.h"
#else
static inline void ramster_init(bool x, bool y, bool z, bool w)
{
}
static inline void ramster_register_pamops(struct tmem_pamops *p)
{
}
static inline int ramster_remotify_pageframe(bool b)
{
return 0;
}
static inline void *ramster_pampd_free(void *v, struct tmem_pool *p,
struct tmem_oid *o, uint32_t u, bool b)
{
return NULL;
}
static inline int ramster_do_preload_flnode(struct tmem_pool *p)
{
return -1;
}
static inline bool pampd_is_remote(void *v)
{
return false;
}
static inline void ramster_count_foreign_pages(bool b, int i)
{
}
static inline void ramster_cpu_up(int cpu)
{
}
static inline void ramster_cpu_down(int cpu)
{
}
#endif
#endif /* _ZCACHE_RAMSTER_H */
#include <linux/atomic.h>
#include "debug.h"
ssize_t ramster_foreign_eph_pages;
ssize_t ramster_foreign_pers_pages;
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
ssize_t ramster_eph_pages_remoted;
ssize_t ramster_pers_pages_remoted;
ssize_t ramster_eph_pages_remote_failed;
ssize_t ramster_pers_pages_remote_failed;
ssize_t ramster_remote_eph_pages_succ_get;
ssize_t ramster_remote_pers_pages_succ_get;
ssize_t ramster_remote_eph_pages_unsucc_get;
ssize_t ramster_remote_pers_pages_unsucc_get;
ssize_t ramster_pers_pages_remote_nomem;
ssize_t ramster_remote_objects_flushed;
ssize_t ramster_remote_object_flushes_failed;
ssize_t ramster_remote_pages_flushed;
ssize_t ramster_remote_page_flushes_failed;
#define ATTR(x) { .name = #x, .val = &ramster_##x, }
static struct debug_entry {
const char *name;
ssize_t *val;
} attrs[] = {
ATTR(eph_pages_remoted),
ATTR(pers_pages_remoted),
ATTR(eph_pages_remote_failed),
ATTR(pers_pages_remote_failed),
ATTR(remote_eph_pages_succ_get),
ATTR(remote_pers_pages_succ_get),
ATTR(remote_eph_pages_unsucc_get),
ATTR(remote_pers_pages_unsucc_get),
ATTR(pers_pages_remote_nomem),
ATTR(remote_objects_flushed),
ATTR(remote_pages_flushed),
ATTR(remote_object_flushes_failed),
ATTR(remote_page_flushes_failed),
ATTR(foreign_eph_pages),
ATTR(foreign_eph_pages_max),
ATTR(foreign_pers_pages),
ATTR(foreign_pers_pages_max),
};
#undef ATTR
int ramster_debugfs_init(void)
{
int i;
struct dentry *root = debugfs_create_dir("ramster", NULL);
if (root == NULL)
return -ENXIO;
for (i = 0; i < ARRAY_SIZE(attrs); i++)
if (!debugfs_create_size_t(attrs[i].name,
S_IRUGO, root, attrs[i].val))
goto out;
return 0;
out:
return -ENODEV;
}
#else
static inline int ramster_debugfs_init(void)
{
return 0;
}
#endif
#include <linux/bug.h>
#ifdef CONFIG_RAMSTER_DEBUG
extern long ramster_flnodes;
static atomic_t ramster_flnodes_atomic = ATOMIC_INIT(0);
static unsigned long ramster_flnodes_max;
static inline void inc_ramster_flnodes(void)
{
ramster_flnodes = atomic_inc_return(&ramster_flnodes_atomic);
if (ramster_flnodes > ramster_flnodes_max)
ramster_flnodes_max = ramster_flnodes;
}
static inline void dec_ramster_flnodes(void)
{
ramster_flnodes = atomic_dec_return(&ramster_flnodes_atomic);
}
extern ssize_t ramster_foreign_eph_pages;
static atomic_t ramster_foreign_eph_pages_atomic = ATOMIC_INIT(0);
static ssize_t ramster_foreign_eph_pages_max;
static inline void inc_ramster_foreign_eph_pages(void)
{
ramster_foreign_eph_pages = atomic_inc_return(
&ramster_foreign_eph_pages_atomic);
if (ramster_foreign_eph_pages > ramster_foreign_eph_pages_max)
ramster_foreign_eph_pages_max = ramster_foreign_eph_pages;
}
static inline void dec_ramster_foreign_eph_pages(void)
{
ramster_foreign_eph_pages = atomic_dec_return(
&ramster_foreign_eph_pages_atomic);
}
extern ssize_t ramster_foreign_pers_pages;
static atomic_t ramster_foreign_pers_pages_atomic = ATOMIC_INIT(0);
static ssize_t ramster_foreign_pers_pages_max;
static inline void inc_ramster_foreign_pers_pages(void)
{
ramster_foreign_pers_pages = atomic_inc_return(
&ramster_foreign_pers_pages_atomic);
if (ramster_foreign_pers_pages > ramster_foreign_pers_pages_max)
ramster_foreign_pers_pages_max = ramster_foreign_pers_pages;
}
static inline void dec_ramster_foreign_pers_pages(void)
{
ramster_foreign_pers_pages = atomic_dec_return(
&ramster_foreign_pers_pages_atomic);
}
extern ssize_t ramster_eph_pages_remoted;
extern ssize_t ramster_pers_pages_remoted;
extern ssize_t ramster_eph_pages_remote_failed;
extern ssize_t ramster_pers_pages_remote_failed;
extern ssize_t ramster_remote_eph_pages_succ_get;
extern ssize_t ramster_remote_pers_pages_succ_get;
extern ssize_t ramster_remote_eph_pages_unsucc_get;
extern ssize_t ramster_remote_pers_pages_unsucc_get;
extern ssize_t ramster_pers_pages_remote_nomem;
extern ssize_t ramster_remote_objects_flushed;
extern ssize_t ramster_remote_object_flushes_failed;
extern ssize_t ramster_remote_pages_flushed;
extern ssize_t ramster_remote_page_flushes_failed;
int ramster_debugfs_init(void);
static inline void inc_ramster_eph_pages_remoted(void)
{
ramster_eph_pages_remoted++;
};
static inline void inc_ramster_pers_pages_remoted(void)
{
ramster_pers_pages_remoted++;
};
static inline void inc_ramster_eph_pages_remote_failed(void)
{
ramster_eph_pages_remote_failed++;
};
static inline void inc_ramster_pers_pages_remote_failed(void)
{
ramster_pers_pages_remote_failed++;
};
static inline void inc_ramster_remote_eph_pages_succ_get(void)
{
ramster_remote_eph_pages_succ_get++;
};
static inline void inc_ramster_remote_pers_pages_succ_get(void)
{
ramster_remote_pers_pages_succ_get++;
};
static inline void inc_ramster_remote_eph_pages_unsucc_get(void)
{
ramster_remote_eph_pages_unsucc_get++;
};
static inline void inc_ramster_remote_pers_pages_unsucc_get(void)
{
ramster_remote_pers_pages_unsucc_get++;
};
static inline void inc_ramster_pers_pages_remote_nomem(void)
{
ramster_pers_pages_remote_nomem++;
};
static inline void inc_ramster_remote_objects_flushed(void)
{
ramster_remote_objects_flushed++;
};
static inline void inc_ramster_remote_object_flushes_failed(void)
{
ramster_remote_object_flushes_failed++;
};
static inline void inc_ramster_remote_pages_flushed(void)
{
ramster_remote_pages_flushed++;
};
static inline void inc_ramster_remote_page_flushes_failed(void)
{
ramster_remote_page_flushes_failed++;
};
#else
static inline void inc_ramster_flnodes(void) { };
static inline void dec_ramster_flnodes(void) { };
static inline void inc_ramster_foreign_eph_pages(void) { };
static inline void dec_ramster_foreign_eph_pages(void) { };
static inline void inc_ramster_foreign_pers_pages(void) { };
static inline void dec_ramster_foreign_pers_pages(void) { };
static inline void inc_ramster_eph_pages_remoted(void) { };
static inline void inc_ramster_pers_pages_remoted(void) { };
static inline void inc_ramster_eph_pages_remote_failed(void) { };
static inline void inc_ramster_pers_pages_remote_failed(void) { };
static inline void inc_ramster_remote_eph_pages_succ_get(void) { };
static inline void inc_ramster_remote_pers_pages_succ_get(void) { };
static inline void inc_ramster_remote_eph_pages_unsucc_get(void) { };
static inline void inc_ramster_remote_pers_pages_unsucc_get(void) { };
static inline void inc_ramster_pers_pages_remote_nomem(void) { };
static inline void inc_ramster_remote_objects_flushed(void) { };
static inline void inc_ramster_remote_object_flushes_failed(void) { };
static inline void inc_ramster_remote_pages_flushed(void) { };
static inline void inc_ramster_remote_page_flushes_failed(void) { };
static inline int ramster_debugfs_init(void)
{
return 0;
}
#endif
/* -*- mode: c; c-basic-offset: 8; -*-
* vim: noexpandtab sw=8 ts=8 sts=0:
*
* Copyright (C) 2004, 2005, 2012 Oracle. All rights reserved.
*
* 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.
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*/
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/configfs.h>
#include "heartbeat.h"
#include "tcp.h"
#include "nodemanager.h"
#include "masklog.h"
/*
* The first heartbeat pass had one global thread that would serialize all hb
* callback calls. This global serializing sem should only be removed once
* we've made sure that all callees can deal with being called concurrently
* from multiple hb region threads.
*/
static DECLARE_RWSEM(r2hb_callback_sem);
/*
* multiple hb threads are watching multiple regions. A node is live
* whenever any of the threads sees activity from the node in its region.
*/
static DEFINE_SPINLOCK(r2hb_live_lock);
static unsigned long r2hb_live_node_bitmap[BITS_TO_LONGS(R2NM_MAX_NODES)];
static struct r2hb_callback {
struct list_head list;
} r2hb_callbacks[R2HB_NUM_CB];
enum r2hb_heartbeat_modes {
R2HB_HEARTBEAT_LOCAL = 0,
R2HB_HEARTBEAT_GLOBAL,
R2HB_HEARTBEAT_NUM_MODES,
};
char *r2hb_heartbeat_mode_desc[R2HB_HEARTBEAT_NUM_MODES] = {
"local", /* R2HB_HEARTBEAT_LOCAL */
"global", /* R2HB_HEARTBEAT_GLOBAL */
};
unsigned int r2hb_dead_threshold = R2HB_DEFAULT_DEAD_THRESHOLD;
unsigned int r2hb_heartbeat_mode = R2HB_HEARTBEAT_LOCAL;
/* Only sets a new threshold if there are no active regions.
*
* No locking or otherwise interesting code is required for reading
* r2hb_dead_threshold as it can't change once regions are active and
* it's not interesting to anyone until then anyway. */
static void r2hb_dead_threshold_set(unsigned int threshold)
{
if (threshold > R2HB_MIN_DEAD_THRESHOLD) {
spin_lock(&r2hb_live_lock);
r2hb_dead_threshold = threshold;
spin_unlock(&r2hb_live_lock);
}
}
static int r2hb_global_hearbeat_mode_set(unsigned int hb_mode)
{
int ret = -1;
if (hb_mode < R2HB_HEARTBEAT_NUM_MODES) {
spin_lock(&r2hb_live_lock);
r2hb_heartbeat_mode = hb_mode;
ret = 0;
spin_unlock(&r2hb_live_lock);
}
return ret;
}
void r2hb_exit(void)
{
}
int r2hb_init(void)
{
int i;
for (i = 0; i < ARRAY_SIZE(r2hb_callbacks); i++)
INIT_LIST_HEAD(&r2hb_callbacks[i].list);
memset(r2hb_live_node_bitmap, 0, sizeof(r2hb_live_node_bitmap));
return 0;
}
/* if we're already in a callback then we're already serialized by the sem */
static void r2hb_fill_node_map_from_callback(unsigned long *map,
unsigned bytes)
{
BUG_ON(bytes < (BITS_TO_LONGS(R2NM_MAX_NODES) * sizeof(unsigned long)));
memcpy(map, &r2hb_live_node_bitmap, bytes);
}
/*
* get a map of all nodes that are heartbeating in any regions
*/
void r2hb_fill_node_map(unsigned long *map, unsigned bytes)
{
/* callers want to serialize this map and callbacks so that they
* can trust that they don't miss nodes coming to the party */
down_read(&r2hb_callback_sem);
spin_lock(&r2hb_live_lock);
r2hb_fill_node_map_from_callback(map, bytes);
spin_unlock(&r2hb_live_lock);
up_read(&r2hb_callback_sem);
}
EXPORT_SYMBOL_GPL(r2hb_fill_node_map);
/*
* heartbeat configfs bits. The heartbeat set is a default set under
* the cluster set in nodemanager.c.
*/
/* heartbeat set */
struct r2hb_hb_group {
struct config_group hs_group;
/* some stuff? */
};
static struct r2hb_hb_group *to_r2hb_hb_group(struct config_group *group)
{
return group ?
container_of(group, struct r2hb_hb_group, hs_group)
: NULL;
}
static struct config_item r2hb_config_item;
static struct config_item *r2hb_hb_group_make_item(struct config_group *group,
const char *name)
{
int ret;
if (strlen(name) > R2HB_MAX_REGION_NAME_LEN) {
ret = -ENAMETOOLONG;
goto free;
}
config_item_put(&r2hb_config_item);
return &r2hb_config_item;
free:
return ERR_PTR(ret);
}
static void r2hb_hb_group_drop_item(struct config_group *group,
struct config_item *item)
{
if (r2hb_global_heartbeat_active()) {
pr_notice("ramster: Heartbeat %s on region %s (%s)\n",
"stopped/aborted", config_item_name(item),
"no region");
}
config_item_put(item);
}
struct r2hb_hb_group_attribute {
struct configfs_attribute attr;
ssize_t (*show)(struct r2hb_hb_group *, char *);
ssize_t (*store)(struct r2hb_hb_group *, const char *, size_t);
};
static ssize_t r2hb_hb_group_show(struct config_item *item,
struct configfs_attribute *attr,
char *page)
{
struct r2hb_hb_group *reg = to_r2hb_hb_group(to_config_group(item));
struct r2hb_hb_group_attribute *r2hb_hb_group_attr =
container_of(attr, struct r2hb_hb_group_attribute, attr);
ssize_t ret = 0;
if (r2hb_hb_group_attr->show)
ret = r2hb_hb_group_attr->show(reg, page);
return ret;
}
static ssize_t r2hb_hb_group_store(struct config_item *item,
struct configfs_attribute *attr,
const char *page, size_t count)
{
struct r2hb_hb_group *reg = to_r2hb_hb_group(to_config_group(item));
struct r2hb_hb_group_attribute *r2hb_hb_group_attr =
container_of(attr, struct r2hb_hb_group_attribute, attr);
ssize_t ret = -EINVAL;
if (r2hb_hb_group_attr->store)
ret = r2hb_hb_group_attr->store(reg, page, count);
return ret;
}
static ssize_t r2hb_hb_group_threshold_show(struct r2hb_hb_group *group,
char *page)
{
return sprintf(page, "%u\n", r2hb_dead_threshold);
}
static ssize_t r2hb_hb_group_threshold_store(struct r2hb_hb_group *group,
const char *page,
size_t count)
{
unsigned long tmp;
char *p = (char *)page;
int err;
err = kstrtoul(p, 10, &tmp);
if (err)
return err;
/* this will validate ranges for us. */
r2hb_dead_threshold_set((unsigned int) tmp);
return count;
}
static
ssize_t r2hb_hb_group_mode_show(struct r2hb_hb_group *group,
char *page)
{
return sprintf(page, "%s\n",
r2hb_heartbeat_mode_desc[r2hb_heartbeat_mode]);
}
static
ssize_t r2hb_hb_group_mode_store(struct r2hb_hb_group *group,
const char *page, size_t count)
{
unsigned int i;
int ret;
size_t len;
len = (page[count - 1] == '\n') ? count - 1 : count;
if (!len)
return -EINVAL;
for (i = 0; i < R2HB_HEARTBEAT_NUM_MODES; ++i) {
if (strnicmp(page, r2hb_heartbeat_mode_desc[i], len))
continue;
ret = r2hb_global_hearbeat_mode_set(i);
if (!ret)
pr_notice("ramster: Heartbeat mode set to %s\n",
r2hb_heartbeat_mode_desc[i]);
return count;
}
return -EINVAL;
}
static struct r2hb_hb_group_attribute r2hb_hb_group_attr_threshold = {
.attr = { .ca_owner = THIS_MODULE,
.ca_name = "dead_threshold",
.ca_mode = S_IRUGO | S_IWUSR },
.show = r2hb_hb_group_threshold_show,
.store = r2hb_hb_group_threshold_store,
};
static struct r2hb_hb_group_attribute r2hb_hb_group_attr_mode = {
.attr = { .ca_owner = THIS_MODULE,
.ca_name = "mode",
.ca_mode = S_IRUGO | S_IWUSR },
.show = r2hb_hb_group_mode_show,
.store = r2hb_hb_group_mode_store,
};
static struct configfs_attribute *r2hb_hb_group_attrs[] = {
&r2hb_hb_group_attr_threshold.attr,
&r2hb_hb_group_attr_mode.attr,
NULL,
};
static struct configfs_item_operations r2hb_hearbeat_group_item_ops = {
.show_attribute = r2hb_hb_group_show,
.store_attribute = r2hb_hb_group_store,
};
static struct configfs_group_operations r2hb_hb_group_group_ops = {
.make_item = r2hb_hb_group_make_item,
.drop_item = r2hb_hb_group_drop_item,
};
static struct config_item_type r2hb_hb_group_type = {
.ct_group_ops = &r2hb_hb_group_group_ops,
.ct_item_ops = &r2hb_hearbeat_group_item_ops,
.ct_attrs = r2hb_hb_group_attrs,
.ct_owner = THIS_MODULE,
};
/* this is just here to avoid touching group in heartbeat.h which the
* entire damn world #includes */
struct config_group *r2hb_alloc_hb_set(void)
{
struct r2hb_hb_group *hs = NULL;
struct config_group *ret = NULL;
hs = kzalloc(sizeof(struct r2hb_hb_group), GFP_KERNEL);
if (hs == NULL)
goto out;
config_group_init_type_name(&hs->hs_group, "heartbeat",
&r2hb_hb_group_type);
ret = &hs->hs_group;
out:
if (ret == NULL)
kfree(hs);
return ret;
}
void r2hb_free_hb_set(struct config_group *group)
{
struct r2hb_hb_group *hs = to_r2hb_hb_group(group);
kfree(hs);
}
/* hb callback registration and issuing */
static struct r2hb_callback *hbcall_from_type(enum r2hb_callback_type type)
{
if (type == R2HB_NUM_CB)
return ERR_PTR(-EINVAL);
return &r2hb_callbacks[type];
}
void r2hb_setup_callback(struct r2hb_callback_func *hc,
enum r2hb_callback_type type,
r2hb_cb_func *func,
void *data,
int priority)
{
INIT_LIST_HEAD(&hc->hc_item);
hc->hc_func = func;
hc->hc_data = data;
hc->hc_priority = priority;
hc->hc_type = type;
hc->hc_magic = R2HB_CB_MAGIC;
}
EXPORT_SYMBOL_GPL(r2hb_setup_callback);
int r2hb_register_callback(const char *region_uuid,
struct r2hb_callback_func *hc)
{
struct r2hb_callback_func *tmp;
struct list_head *iter;
struct r2hb_callback *hbcall;
int ret;
BUG_ON(hc->hc_magic != R2HB_CB_MAGIC);
BUG_ON(!list_empty(&hc->hc_item));
hbcall = hbcall_from_type(hc->hc_type);
if (IS_ERR(hbcall)) {
ret = PTR_ERR(hbcall);
goto out;
}
down_write(&r2hb_callback_sem);
list_for_each(iter, &hbcall->list) {
tmp = list_entry(iter, struct r2hb_callback_func, hc_item);
if (hc->hc_priority < tmp->hc_priority) {
list_add_tail(&hc->hc_item, iter);
break;
}
}
if (list_empty(&hc->hc_item))
list_add_tail(&hc->hc_item, &hbcall->list);
up_write(&r2hb_callback_sem);
ret = 0;
out:
mlog(ML_CLUSTER, "returning %d on behalf of %p for funcs %p\n",
ret, __builtin_return_address(0), hc);
return ret;
}
EXPORT_SYMBOL_GPL(r2hb_register_callback);
void r2hb_unregister_callback(const char *region_uuid,
struct r2hb_callback_func *hc)
{
BUG_ON(hc->hc_magic != R2HB_CB_MAGIC);
mlog(ML_CLUSTER, "on behalf of %p for funcs %p\n",
__builtin_return_address(0), hc);
/* XXX Can this happen _with_ a region reference? */
if (list_empty(&hc->hc_item))
return;
down_write(&r2hb_callback_sem);
list_del_init(&hc->hc_item);
up_write(&r2hb_callback_sem);
}
EXPORT_SYMBOL_GPL(r2hb_unregister_callback);
int r2hb_check_node_heartbeating_from_callback(u8 node_num)
{
unsigned long testing_map[BITS_TO_LONGS(R2NM_MAX_NODES)];
r2hb_fill_node_map_from_callback(testing_map, sizeof(testing_map));
if (!test_bit(node_num, testing_map)) {
mlog(ML_HEARTBEAT,
"node (%u) does not have heartbeating enabled.\n",
node_num);
return 0;
}
return 1;
}
EXPORT_SYMBOL_GPL(r2hb_check_node_heartbeating_from_callback);
void r2hb_stop_all_regions(void)
{
}
EXPORT_SYMBOL_GPL(r2hb_stop_all_regions);
/*
* this is just a hack until we get the plumbing which flips file systems
* read only and drops the hb ref instead of killing the node dead.
*/
int r2hb_global_heartbeat_active(void)
{
return (r2hb_heartbeat_mode == R2HB_HEARTBEAT_GLOBAL);
}
EXPORT_SYMBOL(r2hb_global_heartbeat_active);
/* added for RAMster */
void r2hb_manual_set_node_heartbeating(int node_num)
{
if (node_num < R2NM_MAX_NODES)
set_bit(node_num, r2hb_live_node_bitmap);
}
EXPORT_SYMBOL(r2hb_manual_set_node_heartbeating);
/* -*- mode: c; c-basic-offset: 8; -*-
* vim: noexpandtab sw=8 ts=8 sts=0:
*
* heartbeat.h
*
* Function prototypes
*
* Copyright (C) 2004 Oracle. All rights reserved.
*
* 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.
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*
*/
#ifndef R2CLUSTER_HEARTBEAT_H
#define R2CLUSTER_HEARTBEAT_H
#define R2HB_REGION_TIMEOUT_MS 2000
#define R2HB_MAX_REGION_NAME_LEN 32
/* number of changes to be seen as live */
#define R2HB_LIVE_THRESHOLD 2
/* number of equal samples to be seen as dead */
extern unsigned int r2hb_dead_threshold;
#define R2HB_DEFAULT_DEAD_THRESHOLD 31
/* Otherwise MAX_WRITE_TIMEOUT will be zero... */
#define R2HB_MIN_DEAD_THRESHOLD 2
#define R2HB_MAX_WRITE_TIMEOUT_MS \
(R2HB_REGION_TIMEOUT_MS * (r2hb_dead_threshold - 1))
#define R2HB_CB_MAGIC 0x51d1e4ec
/* callback stuff */
enum r2hb_callback_type {
R2HB_NODE_DOWN_CB = 0,
R2HB_NODE_UP_CB,
R2HB_NUM_CB
};
struct r2nm_node;
typedef void (r2hb_cb_func)(struct r2nm_node *, int, void *);
struct r2hb_callback_func {
u32 hc_magic;
struct list_head hc_item;
r2hb_cb_func *hc_func;
void *hc_data;
int hc_priority;
enum r2hb_callback_type hc_type;
};
struct config_group *r2hb_alloc_hb_set(void);
void r2hb_free_hb_set(struct config_group *group);
void r2hb_setup_callback(struct r2hb_callback_func *hc,
enum r2hb_callback_type type,
r2hb_cb_func *func,
void *data,
int priority);
int r2hb_register_callback(const char *region_uuid,
struct r2hb_callback_func *hc);
void r2hb_unregister_callback(const char *region_uuid,
struct r2hb_callback_func *hc);
void r2hb_fill_node_map(unsigned long *map,
unsigned bytes);
void r2hb_exit(void);
int r2hb_init(void);
int r2hb_check_node_heartbeating_from_callback(u8 node_num);
void r2hb_stop_all_regions(void);
int r2hb_get_all_regions(char *region_uuids, u8 numregions);
int r2hb_global_heartbeat_active(void);
void r2hb_manual_set_node_heartbeating(int);
#endif /* R2CLUSTER_HEARTBEAT_H */
/* -*- mode: c; c-basic-offset: 8; -*-
* vim: noexpandtab sw=8 ts=8 sts=0:
*
* Copyright (C) 2004, 2005, 2012 Oracle. All rights reserved.
*
* 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.
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
#include <linux/string.h>
#include <linux/uaccess.h>
#include "masklog.h"
struct mlog_bits r2_mlog_and_bits = MLOG_BITS_RHS(MLOG_INITIAL_AND_MASK);
EXPORT_SYMBOL_GPL(r2_mlog_and_bits);
struct mlog_bits r2_mlog_not_bits = MLOG_BITS_RHS(0);
EXPORT_SYMBOL_GPL(r2_mlog_not_bits);
static ssize_t mlog_mask_show(u64 mask, char *buf)
{
char *state;
if (__mlog_test_u64(mask, r2_mlog_and_bits))
state = "allow";
else if (__mlog_test_u64(mask, r2_mlog_not_bits))
state = "deny";
else
state = "off";
return snprintf(buf, PAGE_SIZE, "%s\n", state);
}
static ssize_t mlog_mask_store(u64 mask, const char *buf, size_t count)
{
if (!strnicmp(buf, "allow", 5)) {
__mlog_set_u64(mask, r2_mlog_and_bits);
__mlog_clear_u64(mask, r2_mlog_not_bits);
} else if (!strnicmp(buf, "deny", 4)) {
__mlog_set_u64(mask, r2_mlog_not_bits);
__mlog_clear_u64(mask, r2_mlog_and_bits);
} else if (!strnicmp(buf, "off", 3)) {
__mlog_clear_u64(mask, r2_mlog_not_bits);
__mlog_clear_u64(mask, r2_mlog_and_bits);
} else
return -EINVAL;
return count;
}
struct mlog_attribute {
struct attribute attr;
u64 mask;
};
#define to_mlog_attr(_attr) container_of(_attr, struct mlog_attribute, attr)
#define define_mask(_name) { \
.attr = { \
.name = #_name, \
.mode = S_IRUGO | S_IWUSR, \
}, \
.mask = ML_##_name, \
}
static struct mlog_attribute mlog_attrs[MLOG_MAX_BITS] = {
define_mask(TCP),
define_mask(MSG),
define_mask(SOCKET),
define_mask(HEARTBEAT),
define_mask(HB_BIO),
define_mask(DLMFS),
define_mask(DLM),
define_mask(DLM_DOMAIN),
define_mask(DLM_THREAD),
define_mask(DLM_MASTER),
define_mask(DLM_RECOVERY),
define_mask(DLM_GLUE),
define_mask(VOTE),
define_mask(CONN),
define_mask(QUORUM),
define_mask(BASTS),
define_mask(CLUSTER),
define_mask(ERROR),
define_mask(NOTICE),
define_mask(KTHREAD),
};
static struct attribute *mlog_attr_ptrs[MLOG_MAX_BITS] = {NULL, };
static ssize_t mlog_show(struct kobject *obj, struct attribute *attr,
char *buf)
{
struct mlog_attribute *mlog_attr = to_mlog_attr(attr);
return mlog_mask_show(mlog_attr->mask, buf);
}
static ssize_t mlog_store(struct kobject *obj, struct attribute *attr,
const char *buf, size_t count)
{
struct mlog_attribute *mlog_attr = to_mlog_attr(attr);
return mlog_mask_store(mlog_attr->mask, buf, count);
}
static const struct sysfs_ops mlog_attr_ops = {
.show = mlog_show,
.store = mlog_store,
};
static struct kobj_type mlog_ktype = {
.default_attrs = mlog_attr_ptrs,
.sysfs_ops = &mlog_attr_ops,
};
static struct kset mlog_kset = {
.kobj = {.ktype = &mlog_ktype},
};
int r2_mlog_sys_init(struct kset *r2cb_kset)
{
int i = 0;
while (mlog_attrs[i].attr.mode) {
mlog_attr_ptrs[i] = &mlog_attrs[i].attr;
i++;
}
mlog_attr_ptrs[i] = NULL;
kobject_set_name(&mlog_kset.kobj, "logmask");
mlog_kset.kobj.kset = r2cb_kset;
return kset_register(&mlog_kset);
}
void r2_mlog_sys_shutdown(void)
{
kset_unregister(&mlog_kset);
}
/* -*- mode: c; c-basic-offset: 8; -*-
* vim: noexpandtab sw=8 ts=8 sts=0:
*
* Copyright (C) 2005, 2012 Oracle. All rights reserved.
*
* 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.
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*/
#ifndef R2CLUSTER_MASKLOG_H
#define R2CLUSTER_MASKLOG_H
/*
* For now this is a trivial wrapper around printk() that gives the critical
* ability to enable sets of debugging output at run-time. In the future this
* will almost certainly be redirected to relayfs so that it can pay a
* substantially lower heisenberg tax.
*
* Callers associate the message with a bitmask and a global bitmask is
* maintained with help from /proc. If any of the bits match the message is
* output.
*
* We must have efficient bit tests on i386 and it seems gcc still emits crazy
* code for the 64bit compare. It emits very good code for the dual unsigned
* long tests, though, completely avoiding tests that can never pass if the
* caller gives a constant bitmask that fills one of the longs with all 0s. So
* the desire is to have almost all of the calls decided on by comparing just
* one of the longs. This leads to having infrequently given bits that are
* frequently matched in the high bits.
*
* _ERROR and _NOTICE are used for messages that always go to the console and
* have appropriate KERN_ prefixes. We wrap these in our function instead of
* just calling printk() so that this can eventually make its way through
* relayfs along with the debugging messages. Everything else gets KERN_DEBUG.
* The inline tests and macro dance give GCC the opportunity to quite cleverly
* only emit the appropriage printk() when the caller passes in a constant
* mask, as is almost always the case.
*
* All this bitmask nonsense is managed from the files under
* /sys/fs/r2cb/logmask/. Reading the files gives a straightforward
* indication of which bits are allowed (allow) or denied (off/deny).
* ENTRY deny
* EXIT deny
* TCP off
* MSG off
* SOCKET off
* ERROR allow
* NOTICE allow
*
* Writing changes the state of a given bit and requires a strictly formatted
* single write() call:
*
* write(fd, "allow", 5);
*
* Echoing allow/deny/off string into the logmask files can flip the bits
* on or off as expected; here is the bash script for example:
*
* log_mask="/sys/fs/r2cb/log_mask"
* for node in ENTRY EXIT TCP MSG SOCKET ERROR NOTICE; do
* echo allow >"$log_mask"/"$node"
* done
*
* The debugfs.ramster tool can also flip the bits with the -l option:
*
* debugfs.ramster -l TCP allow
*/
/* for task_struct */
#include <linux/sched.h>
/* bits that are frequently given and infrequently matched in the low word */
/* NOTE: If you add a flag, you need to also update masklog.c! */
#define ML_TCP 0x0000000000000001ULL /* net cluster/tcp.c */
#define ML_MSG 0x0000000000000002ULL /* net network messages */
#define ML_SOCKET 0x0000000000000004ULL /* net socket lifetime */
#define ML_HEARTBEAT 0x0000000000000008ULL /* hb all heartbeat tracking */
#define ML_HB_BIO 0x0000000000000010ULL /* hb io tracing */
#define ML_DLMFS 0x0000000000000020ULL /* dlm user dlmfs */
#define ML_DLM 0x0000000000000040ULL /* dlm general debugging */
#define ML_DLM_DOMAIN 0x0000000000000080ULL /* dlm domain debugging */
#define ML_DLM_THREAD 0x0000000000000100ULL /* dlm domain thread */
#define ML_DLM_MASTER 0x0000000000000200ULL /* dlm master functions */
#define ML_DLM_RECOVERY 0x0000000000000400ULL /* dlm master functions */
#define ML_DLM_GLUE 0x0000000000000800ULL /* ramster dlm glue layer */
#define ML_VOTE 0x0000000000001000ULL /* ramster node messaging */
#define ML_CONN 0x0000000000002000ULL /* net connection management */
#define ML_QUORUM 0x0000000000004000ULL /* net connection quorum */
#define ML_BASTS 0x0000000000008000ULL /* dlmglue asts and basts */
#define ML_CLUSTER 0x0000000000010000ULL /* cluster stack */
/* bits that are infrequently given and frequently matched in the high word */
#define ML_ERROR 0x1000000000000000ULL /* sent to KERN_ERR */
#define ML_NOTICE 0x2000000000000000ULL /* setn to KERN_NOTICE */
#define ML_KTHREAD 0x4000000000000000ULL /* kernel thread activity */
#define MLOG_INITIAL_AND_MASK (ML_ERROR|ML_NOTICE)
#ifndef MLOG_MASK_PREFIX
#define MLOG_MASK_PREFIX 0
#endif
/*
* When logging is disabled, force the bit test to 0 for anything other
* than errors and notices, allowing gcc to remove the code completely.
* When enabled, allow all masks.
*/
#if defined(CONFIG_RAMSTER_DEBUG_MASKLOG)
#define ML_ALLOWED_BITS (~0)
#else
#define ML_ALLOWED_BITS (ML_ERROR|ML_NOTICE)
#endif
#define MLOG_MAX_BITS 64
struct mlog_bits {
unsigned long words[MLOG_MAX_BITS / BITS_PER_LONG];
};
extern struct mlog_bits r2_mlog_and_bits, r2_mlog_not_bits;
#if BITS_PER_LONG == 32
#define __mlog_test_u64(mask, bits) \
((u32)(mask & 0xffffffff) & bits.words[0] || \
((u64)(mask) >> 32) & bits.words[1])
#define __mlog_set_u64(mask, bits) do { \
bits.words[0] |= (u32)(mask & 0xffffffff); \
bits.words[1] |= (u64)(mask) >> 32; \
} while (0)
#define __mlog_clear_u64(mask, bits) do { \
bits.words[0] &= ~((u32)(mask & 0xffffffff)); \
bits.words[1] &= ~((u64)(mask) >> 32); \
} while (0)
#define MLOG_BITS_RHS(mask) { \
{ \
[0] = (u32)(mask & 0xffffffff), \
[1] = (u64)(mask) >> 32, \
} \
}
#else /* 32bit long above, 64bit long below */
#define __mlog_test_u64(mask, bits) ((mask) & bits.words[0])
#define __mlog_set_u64(mask, bits) do { \
bits.words[0] |= (mask); \
} while (0)
#define __mlog_clear_u64(mask, bits) do { \
bits.words[0] &= ~(mask); \
} while (0)
#define MLOG_BITS_RHS(mask) { { (mask) } }
#endif
/*
* smp_processor_id() "helpfully" screams when called outside preemptible
* regions in current kernels. sles doesn't have the variants that don't
* scream. just do this instead of trying to guess which we're building
* against.. *sigh*.
*/
#define __mlog_cpu_guess ({ \
unsigned long _cpu = get_cpu(); \
put_cpu(); \
_cpu; \
})
/* In the following two macros, the whitespace after the ',' just
* before ##args is intentional. Otherwise, gcc 2.95 will eat the
* previous token if args expands to nothing.
*/
#define __mlog_printk(level, fmt, args...) \
printk(level "(%s,%u,%lu):%s:%d " fmt, current->comm, \
task_pid_nr(current), __mlog_cpu_guess, \
__PRETTY_FUNCTION__, __LINE__ , ##args)
#define mlog(mask, fmt, args...) do { \
u64 __m = MLOG_MASK_PREFIX | (mask); \
if ((__m & ML_ALLOWED_BITS) && \
__mlog_test_u64(__m, r2_mlog_and_bits) && \
!__mlog_test_u64(__m, r2_mlog_not_bits)) { \
if (__m & ML_ERROR) \
__mlog_printk(KERN_ERR, "ERROR: "fmt , ##args); \
else if (__m & ML_NOTICE) \
__mlog_printk(KERN_NOTICE, fmt , ##args); \
else \
__mlog_printk(KERN_INFO, fmt , ##args); \
} \
} while (0)
#define mlog_errno(st) do { \
int _st = (st); \
if (_st != -ERESTARTSYS && _st != -EINTR && \
_st != AOP_TRUNCATED_PAGE && _st != -ENOSPC) \
mlog(ML_ERROR, "status = %lld\n", (long long)_st); \
} while (0)
#define mlog_bug_on_msg(cond, fmt, args...) do { \
if (cond) { \
mlog(ML_ERROR, "bug expression: " #cond "\n"); \
mlog(ML_ERROR, fmt, ##args); \
BUG(); \
} \
} while (0)
#include <linux/kobject.h>
#include <linux/sysfs.h>
int r2_mlog_sys_init(struct kset *r2cb_subsys);
void r2_mlog_sys_shutdown(void);
#endif /* R2CLUSTER_MASKLOG_H */
/* -*- mode: c; c-basic-offset: 8; -*-
* vim: noexpandtab sw=8 ts=8 sts=0:
*
* Copyright (C) 2004, 2005, 2012 Oracle. All rights reserved.
*
* 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.
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*/
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/configfs.h>
#include "tcp.h"
#include "nodemanager.h"
#include "heartbeat.h"
#include "masklog.h"
/* for now we operate under the assertion that there can be only one
* cluster active at a time. Changing this will require trickling
* cluster references throughout where nodes are looked up */
struct r2nm_cluster *r2nm_single_cluster;
char *r2nm_fence_method_desc[R2NM_FENCE_METHODS] = {
"reset", /* R2NM_FENCE_RESET */
"panic", /* R2NM_FENCE_PANIC */
};
struct r2nm_node *r2nm_get_node_by_num(u8 node_num)
{
struct r2nm_node *node = NULL;
if (node_num >= R2NM_MAX_NODES || r2nm_single_cluster == NULL)
goto out;
read_lock(&r2nm_single_cluster->cl_nodes_lock);
node = r2nm_single_cluster->cl_nodes[node_num];
if (node)
config_item_get(&node->nd_item);
read_unlock(&r2nm_single_cluster->cl_nodes_lock);
out:
return node;
}
EXPORT_SYMBOL_GPL(r2nm_get_node_by_num);
int r2nm_configured_node_map(unsigned long *map, unsigned bytes)
{
struct r2nm_cluster *cluster = r2nm_single_cluster;
BUG_ON(bytes < (sizeof(cluster->cl_nodes_bitmap)));
if (cluster == NULL)
return -EINVAL;
read_lock(&cluster->cl_nodes_lock);
memcpy(map, cluster->cl_nodes_bitmap, sizeof(cluster->cl_nodes_bitmap));
read_unlock(&cluster->cl_nodes_lock);
return 0;
}
EXPORT_SYMBOL_GPL(r2nm_configured_node_map);
static struct r2nm_node *r2nm_node_ip_tree_lookup(struct r2nm_cluster *cluster,
__be32 ip_needle,
struct rb_node ***ret_p,
struct rb_node **ret_parent)
{
struct rb_node **p = &cluster->cl_node_ip_tree.rb_node;
struct rb_node *parent = NULL;
struct r2nm_node *node, *ret = NULL;
while (*p) {
int cmp;
parent = *p;
node = rb_entry(parent, struct r2nm_node, nd_ip_node);
cmp = memcmp(&ip_needle, &node->nd_ipv4_address,
sizeof(ip_needle));
if (cmp < 0)
p = &(*p)->rb_left;
else if (cmp > 0)
p = &(*p)->rb_right;
else {
ret = node;
break;
}
}
if (ret_p != NULL)
*ret_p = p;
if (ret_parent != NULL)
*ret_parent = parent;
return ret;
}
struct r2nm_node *r2nm_get_node_by_ip(__be32 addr)
{
struct r2nm_node *node = NULL;
struct r2nm_cluster *cluster = r2nm_single_cluster;
if (cluster == NULL)
goto out;
read_lock(&cluster->cl_nodes_lock);
node = r2nm_node_ip_tree_lookup(cluster, addr, NULL, NULL);
if (node)
config_item_get(&node->nd_item);
read_unlock(&cluster->cl_nodes_lock);
out:
return node;
}
EXPORT_SYMBOL_GPL(r2nm_get_node_by_ip);
void r2nm_node_put(struct r2nm_node *node)
{
config_item_put(&node->nd_item);
}
EXPORT_SYMBOL_GPL(r2nm_node_put);
void r2nm_node_get(struct r2nm_node *node)
{
config_item_get(&node->nd_item);
}
EXPORT_SYMBOL_GPL(r2nm_node_get);
u8 r2nm_this_node(void)
{
u8 node_num = R2NM_MAX_NODES;
if (r2nm_single_cluster && r2nm_single_cluster->cl_has_local)
node_num = r2nm_single_cluster->cl_local_node;
return node_num;
}
EXPORT_SYMBOL_GPL(r2nm_this_node);
/* node configfs bits */
static struct r2nm_cluster *to_r2nm_cluster(struct config_item *item)
{
return item ?
container_of(to_config_group(item), struct r2nm_cluster,
cl_group)
: NULL;
}
static struct r2nm_node *to_r2nm_node(struct config_item *item)
{
return item ? container_of(item, struct r2nm_node, nd_item) : NULL;
}
static void r2nm_node_release(struct config_item *item)
{
struct r2nm_node *node = to_r2nm_node(item);
kfree(node);
}
static ssize_t r2nm_node_num_read(struct r2nm_node *node, char *page)
{
return sprintf(page, "%d\n", node->nd_num);
}
static struct r2nm_cluster *to_r2nm_cluster_from_node(struct r2nm_node *node)
{
/* through the first node_set .parent
* mycluster/nodes/mynode == r2nm_cluster->r2nm_node_group->r2nm_node */
return to_r2nm_cluster(node->nd_item.ci_parent->ci_parent);
}
enum {
R2NM_NODE_ATTR_NUM = 0,
R2NM_NODE_ATTR_PORT,
R2NM_NODE_ATTR_ADDRESS,
R2NM_NODE_ATTR_LOCAL,
};
static ssize_t r2nm_node_num_write(struct r2nm_node *node, const char *page,
size_t count)
{
struct r2nm_cluster *cluster = to_r2nm_cluster_from_node(node);
unsigned long tmp;
char *p = (char *)page;
int err;
err = kstrtoul(p, 10, &tmp);
if (err)
return err;
if (tmp >= R2NM_MAX_NODES)
return -ERANGE;
/* once we're in the cl_nodes tree networking can look us up by
* node number and try to use our address and port attributes
* to connect to this node.. make sure that they've been set
* before writing the node attribute? */
if (!test_bit(R2NM_NODE_ATTR_ADDRESS, &node->nd_set_attributes) ||
!test_bit(R2NM_NODE_ATTR_PORT, &node->nd_set_attributes))
return -EINVAL; /* XXX */
write_lock(&cluster->cl_nodes_lock);
if (cluster->cl_nodes[tmp])
p = NULL;
else {
cluster->cl_nodes[tmp] = node;
node->nd_num = tmp;
set_bit(tmp, cluster->cl_nodes_bitmap);
}
write_unlock(&cluster->cl_nodes_lock);
if (p == NULL)
return -EEXIST;
return count;
}
static ssize_t r2nm_node_ipv4_port_read(struct r2nm_node *node, char *page)
{
return sprintf(page, "%u\n", ntohs(node->nd_ipv4_port));
}
static ssize_t r2nm_node_ipv4_port_write(struct r2nm_node *node,
const char *page, size_t count)
{
unsigned long tmp;
char *p = (char *)page;
int err;
err = kstrtoul(p, 10, &tmp);
if (err)
return err;
if (tmp == 0)
return -EINVAL;
if (tmp >= (u16)-1)
return -ERANGE;
node->nd_ipv4_port = htons(tmp);
return count;
}
static ssize_t r2nm_node_ipv4_address_read(struct r2nm_node *node, char *page)
{
return sprintf(page, "%pI4\n", &node->nd_ipv4_address);
}
static ssize_t r2nm_node_ipv4_address_write(struct r2nm_node *node,
const char *page,
size_t count)
{
struct r2nm_cluster *cluster = to_r2nm_cluster_from_node(node);
int ret, i;
struct rb_node **p, *parent;
unsigned int octets[4];
__be32 ipv4_addr = 0;
ret = sscanf(page, "%3u.%3u.%3u.%3u", &octets[3], &octets[2],
&octets[1], &octets[0]);
if (ret != 4)
return -EINVAL;
for (i = 0; i < ARRAY_SIZE(octets); i++) {
if (octets[i] > 255)
return -ERANGE;
be32_add_cpu(&ipv4_addr, octets[i] << (i * 8));
}
ret = 0;
write_lock(&cluster->cl_nodes_lock);
if (r2nm_node_ip_tree_lookup(cluster, ipv4_addr, &p, &parent))
ret = -EEXIST;
else {
rb_link_node(&node->nd_ip_node, parent, p);
rb_insert_color(&node->nd_ip_node, &cluster->cl_node_ip_tree);
}
write_unlock(&cluster->cl_nodes_lock);
if (ret)
return ret;
memcpy(&node->nd_ipv4_address, &ipv4_addr, sizeof(ipv4_addr));
return count;
}
static ssize_t r2nm_node_local_read(struct r2nm_node *node, char *page)
{
return sprintf(page, "%d\n", node->nd_local);
}
static ssize_t r2nm_node_local_write(struct r2nm_node *node, const char *page,
size_t count)
{
struct r2nm_cluster *cluster = to_r2nm_cluster_from_node(node);
unsigned long tmp;
char *p = (char *)page;
ssize_t ret;
int err;
err = kstrtoul(p, 10, &tmp);
if (err)
return err;
tmp = !!tmp; /* boolean of whether this node wants to be local */
/* setting local turns on networking rx for now so we require having
* set everything else first */
if (!test_bit(R2NM_NODE_ATTR_ADDRESS, &node->nd_set_attributes) ||
!test_bit(R2NM_NODE_ATTR_NUM, &node->nd_set_attributes) ||
!test_bit(R2NM_NODE_ATTR_PORT, &node->nd_set_attributes))
return -EINVAL; /* XXX */
/* the only failure case is trying to set a new local node
* when a different one is already set */
if (tmp && tmp == cluster->cl_has_local &&
cluster->cl_local_node != node->nd_num)
return -EBUSY;
/* bring up the rx thread if we're setting the new local node. */
if (tmp && !cluster->cl_has_local) {
ret = r2net_start_listening(node);
if (ret)
return ret;
}
if (!tmp && cluster->cl_has_local &&
cluster->cl_local_node == node->nd_num) {
r2net_stop_listening(node);
cluster->cl_local_node = R2NM_INVALID_NODE_NUM;
}
node->nd_local = tmp;
if (node->nd_local) {
cluster->cl_has_local = tmp;
cluster->cl_local_node = node->nd_num;
}
return count;
}
struct r2nm_node_attribute {
struct configfs_attribute attr;
ssize_t (*show)(struct r2nm_node *, char *);
ssize_t (*store)(struct r2nm_node *, const char *, size_t);
};
static struct r2nm_node_attribute r2nm_node_attr_num = {
.attr = { .ca_owner = THIS_MODULE,
.ca_name = "num",
.ca_mode = S_IRUGO | S_IWUSR },
.show = r2nm_node_num_read,
.store = r2nm_node_num_write,
};
static struct r2nm_node_attribute r2nm_node_attr_ipv4_port = {
.attr = { .ca_owner = THIS_MODULE,
.ca_name = "ipv4_port",
.ca_mode = S_IRUGO | S_IWUSR },
.show = r2nm_node_ipv4_port_read,
.store = r2nm_node_ipv4_port_write,
};
static struct r2nm_node_attribute r2nm_node_attr_ipv4_address = {
.attr = { .ca_owner = THIS_MODULE,
.ca_name = "ipv4_address",
.ca_mode = S_IRUGO | S_IWUSR },
.show = r2nm_node_ipv4_address_read,
.store = r2nm_node_ipv4_address_write,
};
static struct r2nm_node_attribute r2nm_node_attr_local = {
.attr = { .ca_owner = THIS_MODULE,
.ca_name = "local",
.ca_mode = S_IRUGO | S_IWUSR },
.show = r2nm_node_local_read,
.store = r2nm_node_local_write,
};
static struct configfs_attribute *r2nm_node_attrs[] = {
[R2NM_NODE_ATTR_NUM] = &r2nm_node_attr_num.attr,
[R2NM_NODE_ATTR_PORT] = &r2nm_node_attr_ipv4_port.attr,
[R2NM_NODE_ATTR_ADDRESS] = &r2nm_node_attr_ipv4_address.attr,
[R2NM_NODE_ATTR_LOCAL] = &r2nm_node_attr_local.attr,
NULL,
};
static int r2nm_attr_index(struct configfs_attribute *attr)
{
int i;
for (i = 0; i < ARRAY_SIZE(r2nm_node_attrs); i++) {
if (attr == r2nm_node_attrs[i])
return i;
}
BUG();
return 0;
}
static ssize_t r2nm_node_show(struct config_item *item,
struct configfs_attribute *attr,
char *page)
{
struct r2nm_node *node = to_r2nm_node(item);
struct r2nm_node_attribute *r2nm_node_attr =
container_of(attr, struct r2nm_node_attribute, attr);
ssize_t ret = 0;
if (r2nm_node_attr->show)
ret = r2nm_node_attr->show(node, page);
return ret;
}
static ssize_t r2nm_node_store(struct config_item *item,
struct configfs_attribute *attr,
const char *page, size_t count)
{
struct r2nm_node *node = to_r2nm_node(item);
struct r2nm_node_attribute *r2nm_node_attr =
container_of(attr, struct r2nm_node_attribute, attr);
ssize_t ret;
int attr_index = r2nm_attr_index(attr);
if (r2nm_node_attr->store == NULL) {
ret = -EINVAL;
goto out;
}
if (test_bit(attr_index, &node->nd_set_attributes))
return -EBUSY;
ret = r2nm_node_attr->store(node, page, count);
if (ret < count)
goto out;
set_bit(attr_index, &node->nd_set_attributes);
out:
return ret;
}
static struct configfs_item_operations r2nm_node_item_ops = {
.release = r2nm_node_release,
.show_attribute = r2nm_node_show,
.store_attribute = r2nm_node_store,
};
static struct config_item_type r2nm_node_type = {
.ct_item_ops = &r2nm_node_item_ops,
.ct_attrs = r2nm_node_attrs,
.ct_owner = THIS_MODULE,
};
/* node set */
struct r2nm_node_group {
struct config_group ns_group;
/* some stuff? */
};
#if 0
static struct r2nm_node_group *to_r2nm_node_group(struct config_group *group)
{
return group ?
container_of(group, struct r2nm_node_group, ns_group)
: NULL;
}
#endif
struct r2nm_cluster_attribute {
struct configfs_attribute attr;
ssize_t (*show)(struct r2nm_cluster *, char *);
ssize_t (*store)(struct r2nm_cluster *, const char *, size_t);
};
static ssize_t r2nm_cluster_attr_write(const char *page, ssize_t count,
unsigned int *val)
{
unsigned long tmp;
char *p = (char *)page;
int err;
err = kstrtoul(p, 10, &tmp);
if (err)
return err;
if (tmp == 0)
return -EINVAL;
if (tmp >= (u32)-1)
return -ERANGE;
*val = tmp;
return count;
}
static ssize_t r2nm_cluster_attr_idle_timeout_ms_read(
struct r2nm_cluster *cluster, char *page)
{
return sprintf(page, "%u\n", cluster->cl_idle_timeout_ms);
}
static ssize_t r2nm_cluster_attr_idle_timeout_ms_write(
struct r2nm_cluster *cluster, const char *page, size_t count)
{
ssize_t ret;
unsigned int val = 0;
ret = r2nm_cluster_attr_write(page, count, &val);
if (ret > 0) {
if (cluster->cl_idle_timeout_ms != val
&& r2net_num_connected_peers()) {
mlog(ML_NOTICE,
"r2net: cannot change idle timeout after "
"the first peer has agreed to it."
" %d connected peers\n",
r2net_num_connected_peers());
ret = -EINVAL;
} else if (val <= cluster->cl_keepalive_delay_ms) {
mlog(ML_NOTICE,
"r2net: idle timeout must be larger "
"than keepalive delay\n");
ret = -EINVAL;
} else {
cluster->cl_idle_timeout_ms = val;
}
}
return ret;
}
static ssize_t r2nm_cluster_attr_keepalive_delay_ms_read(
struct r2nm_cluster *cluster, char *page)
{
return sprintf(page, "%u\n", cluster->cl_keepalive_delay_ms);
}
static ssize_t r2nm_cluster_attr_keepalive_delay_ms_write(
struct r2nm_cluster *cluster, const char *page, size_t count)
{
ssize_t ret;
unsigned int val = 0;
ret = r2nm_cluster_attr_write(page, count, &val);
if (ret > 0) {
if (cluster->cl_keepalive_delay_ms != val
&& r2net_num_connected_peers()) {
mlog(ML_NOTICE,
"r2net: cannot change keepalive delay after"
" the first peer has agreed to it."
" %d connected peers\n",
r2net_num_connected_peers());
ret = -EINVAL;
} else if (val >= cluster->cl_idle_timeout_ms) {
mlog(ML_NOTICE,
"r2net: keepalive delay must be "
"smaller than idle timeout\n");
ret = -EINVAL;
} else {
cluster->cl_keepalive_delay_ms = val;
}
}
return ret;
}
static ssize_t r2nm_cluster_attr_reconnect_delay_ms_read(
struct r2nm_cluster *cluster, char *page)
{
return sprintf(page, "%u\n", cluster->cl_reconnect_delay_ms);
}
static ssize_t r2nm_cluster_attr_reconnect_delay_ms_write(
struct r2nm_cluster *cluster, const char *page, size_t count)
{
return r2nm_cluster_attr_write(page, count,
&cluster->cl_reconnect_delay_ms);
}
static ssize_t r2nm_cluster_attr_fence_method_read(
struct r2nm_cluster *cluster, char *page)
{
ssize_t ret = 0;
if (cluster)
ret = sprintf(page, "%s\n",
r2nm_fence_method_desc[cluster->cl_fence_method]);
return ret;
}
static ssize_t r2nm_cluster_attr_fence_method_write(
struct r2nm_cluster *cluster, const char *page, size_t count)
{
unsigned int i;
if (page[count - 1] != '\n')
goto bail;
for (i = 0; i < R2NM_FENCE_METHODS; ++i) {
if (count != strlen(r2nm_fence_method_desc[i]) + 1)
continue;
if (strncasecmp(page, r2nm_fence_method_desc[i], count - 1))
continue;
if (cluster->cl_fence_method != i) {
pr_info("ramster: Changing fence method to %s\n",
r2nm_fence_method_desc[i]);
cluster->cl_fence_method = i;
}
return count;
}
bail:
return -EINVAL;
}
static struct r2nm_cluster_attribute r2nm_cluster_attr_idle_timeout_ms = {
.attr = { .ca_owner = THIS_MODULE,
.ca_name = "idle_timeout_ms",
.ca_mode = S_IRUGO | S_IWUSR },
.show = r2nm_cluster_attr_idle_timeout_ms_read,
.store = r2nm_cluster_attr_idle_timeout_ms_write,
};
static struct r2nm_cluster_attribute r2nm_cluster_attr_keepalive_delay_ms = {
.attr = { .ca_owner = THIS_MODULE,
.ca_name = "keepalive_delay_ms",
.ca_mode = S_IRUGO | S_IWUSR },
.show = r2nm_cluster_attr_keepalive_delay_ms_read,
.store = r2nm_cluster_attr_keepalive_delay_ms_write,
};
static struct r2nm_cluster_attribute r2nm_cluster_attr_reconnect_delay_ms = {
.attr = { .ca_owner = THIS_MODULE,
.ca_name = "reconnect_delay_ms",
.ca_mode = S_IRUGO | S_IWUSR },
.show = r2nm_cluster_attr_reconnect_delay_ms_read,
.store = r2nm_cluster_attr_reconnect_delay_ms_write,
};
static struct r2nm_cluster_attribute r2nm_cluster_attr_fence_method = {
.attr = { .ca_owner = THIS_MODULE,
.ca_name = "fence_method",
.ca_mode = S_IRUGO | S_IWUSR },
.show = r2nm_cluster_attr_fence_method_read,
.store = r2nm_cluster_attr_fence_method_write,
};
static struct configfs_attribute *r2nm_cluster_attrs[] = {
&r2nm_cluster_attr_idle_timeout_ms.attr,
&r2nm_cluster_attr_keepalive_delay_ms.attr,
&r2nm_cluster_attr_reconnect_delay_ms.attr,
&r2nm_cluster_attr_fence_method.attr,
NULL,
};
static ssize_t r2nm_cluster_show(struct config_item *item,
struct configfs_attribute *attr,
char *page)
{
struct r2nm_cluster *cluster = to_r2nm_cluster(item);
struct r2nm_cluster_attribute *r2nm_cluster_attr =
container_of(attr, struct r2nm_cluster_attribute, attr);
ssize_t ret = 0;
if (r2nm_cluster_attr->show)
ret = r2nm_cluster_attr->show(cluster, page);
return ret;
}
static ssize_t r2nm_cluster_store(struct config_item *item,
struct configfs_attribute *attr,
const char *page, size_t count)
{
struct r2nm_cluster *cluster = to_r2nm_cluster(item);
struct r2nm_cluster_attribute *r2nm_cluster_attr =
container_of(attr, struct r2nm_cluster_attribute, attr);
ssize_t ret;
if (r2nm_cluster_attr->store == NULL) {
ret = -EINVAL;
goto out;
}
ret = r2nm_cluster_attr->store(cluster, page, count);
if (ret < count)
goto out;
out:
return ret;
}
static struct config_item *r2nm_node_group_make_item(struct config_group *group,
const char *name)
{
struct r2nm_node *node = NULL;
if (strlen(name) > R2NM_MAX_NAME_LEN)
return ERR_PTR(-ENAMETOOLONG);
node = kzalloc(sizeof(struct r2nm_node), GFP_KERNEL);
if (node == NULL)
return ERR_PTR(-ENOMEM);
strcpy(node->nd_name, name); /* use item.ci_namebuf instead? */
config_item_init_type_name(&node->nd_item, name, &r2nm_node_type);
spin_lock_init(&node->nd_lock);
mlog(ML_CLUSTER, "r2nm: Registering node %s\n", name);
return &node->nd_item;
}
static void r2nm_node_group_drop_item(struct config_group *group,
struct config_item *item)
{
struct r2nm_node *node = to_r2nm_node(item);
struct r2nm_cluster *cluster =
to_r2nm_cluster(group->cg_item.ci_parent);
r2net_disconnect_node(node);
if (cluster->cl_has_local &&
(cluster->cl_local_node == node->nd_num)) {
cluster->cl_has_local = 0;
cluster->cl_local_node = R2NM_INVALID_NODE_NUM;
r2net_stop_listening(node);
}
/* XXX call into net to stop this node from trading messages */
write_lock(&cluster->cl_nodes_lock);
/* XXX sloppy */
if (node->nd_ipv4_address)
rb_erase(&node->nd_ip_node, &cluster->cl_node_ip_tree);
/* nd_num might be 0 if the node number hasn't been set.. */
if (cluster->cl_nodes[node->nd_num] == node) {
cluster->cl_nodes[node->nd_num] = NULL;
clear_bit(node->nd_num, cluster->cl_nodes_bitmap);
}
write_unlock(&cluster->cl_nodes_lock);
mlog(ML_CLUSTER, "r2nm: Unregistered node %s\n",
config_item_name(&node->nd_item));
config_item_put(item);
}
static struct configfs_group_operations r2nm_node_group_group_ops = {
.make_item = r2nm_node_group_make_item,
.drop_item = r2nm_node_group_drop_item,
};
static struct config_item_type r2nm_node_group_type = {
.ct_group_ops = &r2nm_node_group_group_ops,
.ct_owner = THIS_MODULE,
};
/* cluster */
static void r2nm_cluster_release(struct config_item *item)
{
struct r2nm_cluster *cluster = to_r2nm_cluster(item);
kfree(cluster->cl_group.default_groups);
kfree(cluster);
}
static struct configfs_item_operations r2nm_cluster_item_ops = {
.release = r2nm_cluster_release,
.show_attribute = r2nm_cluster_show,
.store_attribute = r2nm_cluster_store,
};
static struct config_item_type r2nm_cluster_type = {
.ct_item_ops = &r2nm_cluster_item_ops,
.ct_attrs = r2nm_cluster_attrs,
.ct_owner = THIS_MODULE,
};
/* cluster set */
struct r2nm_cluster_group {
struct configfs_subsystem cs_subsys;
/* some stuff? */
};
#if 0
static struct r2nm_cluster_group *
to_r2nm_cluster_group(struct config_group *group)
{
return group ?
container_of(to_configfs_subsystem(group),
struct r2nm_cluster_group, cs_subsys)
: NULL;
}
#endif
static struct config_group *
r2nm_cluster_group_make_group(struct config_group *group,
const char *name)
{
struct r2nm_cluster *cluster = NULL;
struct r2nm_node_group *ns = NULL;
struct config_group *r2hb_group = NULL, *ret = NULL;
void *defs = NULL;
/* this runs under the parent dir's i_mutex; there can be only
* one caller in here at a time */
if (r2nm_single_cluster)
return ERR_PTR(-ENOSPC);
cluster = kzalloc(sizeof(struct r2nm_cluster), GFP_KERNEL);
ns = kzalloc(sizeof(struct r2nm_node_group), GFP_KERNEL);
defs = kcalloc(3, sizeof(struct config_group *), GFP_KERNEL);
r2hb_group = r2hb_alloc_hb_set();
if (cluster == NULL || ns == NULL || r2hb_group == NULL || defs == NULL)
goto out;
config_group_init_type_name(&cluster->cl_group, name,
&r2nm_cluster_type);
config_group_init_type_name(&ns->ns_group, "node",
&r2nm_node_group_type);
cluster->cl_group.default_groups = defs;
cluster->cl_group.default_groups[0] = &ns->ns_group;
cluster->cl_group.default_groups[1] = r2hb_group;
cluster->cl_group.default_groups[2] = NULL;
rwlock_init(&cluster->cl_nodes_lock);
cluster->cl_node_ip_tree = RB_ROOT;
cluster->cl_reconnect_delay_ms = R2NET_RECONNECT_DELAY_MS_DEFAULT;
cluster->cl_idle_timeout_ms = R2NET_IDLE_TIMEOUT_MS_DEFAULT;
cluster->cl_keepalive_delay_ms = R2NET_KEEPALIVE_DELAY_MS_DEFAULT;
cluster->cl_fence_method = R2NM_FENCE_RESET;
ret = &cluster->cl_group;
r2nm_single_cluster = cluster;
out:
if (ret == NULL) {
kfree(cluster);
kfree(ns);
r2hb_free_hb_set(r2hb_group);
kfree(defs);
ret = ERR_PTR(-ENOMEM);
}
return ret;
}
static void r2nm_cluster_group_drop_item(struct config_group *group,
struct config_item *item)
{
struct r2nm_cluster *cluster = to_r2nm_cluster(item);
int i;
struct config_item *killme;
BUG_ON(r2nm_single_cluster != cluster);
r2nm_single_cluster = NULL;
for (i = 0; cluster->cl_group.default_groups[i]; i++) {
killme = &cluster->cl_group.default_groups[i]->cg_item;
cluster->cl_group.default_groups[i] = NULL;
config_item_put(killme);
}
config_item_put(item);
}
static struct configfs_group_operations r2nm_cluster_group_group_ops = {
.make_group = r2nm_cluster_group_make_group,
.drop_item = r2nm_cluster_group_drop_item,
};
static struct config_item_type r2nm_cluster_group_type = {
.ct_group_ops = &r2nm_cluster_group_group_ops,
.ct_owner = THIS_MODULE,
};
static struct r2nm_cluster_group r2nm_cluster_group = {
.cs_subsys = {
.su_group = {
.cg_item = {
.ci_namebuf = "cluster",
.ci_type = &r2nm_cluster_group_type,
},
},
},
};
int r2nm_depend_item(struct config_item *item)
{
return configfs_depend_item(&r2nm_cluster_group.cs_subsys, item);
}
void r2nm_undepend_item(struct config_item *item)
{
configfs_undepend_item(&r2nm_cluster_group.cs_subsys, item);
}
int r2nm_depend_this_node(void)
{
int ret = 0;
struct r2nm_node *local_node;
local_node = r2nm_get_node_by_num(r2nm_this_node());
if (!local_node) {
ret = -EINVAL;
goto out;
}
ret = r2nm_depend_item(&local_node->nd_item);
r2nm_node_put(local_node);
out:
return ret;
}
void r2nm_undepend_this_node(void)
{
struct r2nm_node *local_node;
local_node = r2nm_get_node_by_num(r2nm_this_node());
BUG_ON(!local_node);
r2nm_undepend_item(&local_node->nd_item);
r2nm_node_put(local_node);
}
static void __exit exit_r2nm(void)
{
/* XXX sync with hb callbacks and shut down hb? */
r2net_unregister_hb_callbacks();
configfs_unregister_subsystem(&r2nm_cluster_group.cs_subsys);
r2net_exit();
r2hb_exit();
}
int r2nm_init(void)
{
int ret = -1;
ret = r2hb_init();
if (ret)
goto out;
ret = r2net_init();
if (ret)
goto out_r2hb;
ret = r2net_register_hb_callbacks();
if (ret)
goto out_r2net;
config_group_init(&r2nm_cluster_group.cs_subsys.su_group);
mutex_init(&r2nm_cluster_group.cs_subsys.su_mutex);
ret = configfs_register_subsystem(&r2nm_cluster_group.cs_subsys);
if (ret) {
pr_err("nodemanager: Registration returned %d\n", ret);
goto out_callbacks;
}
if (!ret)
goto out;
configfs_unregister_subsystem(&r2nm_cluster_group.cs_subsys);
out_callbacks:
r2net_unregister_hb_callbacks();
out_r2net:
r2net_exit();
out_r2hb:
r2hb_exit();
out:
return ret;
}
EXPORT_SYMBOL_GPL(r2nm_init);
MODULE_AUTHOR("Oracle");
MODULE_LICENSE("GPL");
#ifndef CONFIG_RAMSTER_MODULE
late_initcall(r2nm_init);
#endif
/* -*- mode: c; c-basic-offset: 8; -*-
* vim: noexpandtab sw=8 ts=8 sts=0:
*
* nodemanager.h
*
* Function prototypes
*
* Copyright (C) 2004 Oracle. All rights reserved.
*
* 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.
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*
*/
#ifndef R2CLUSTER_NODEMANAGER_H
#define R2CLUSTER_NODEMANAGER_H
#include "ramster_nodemanager.h"
/* This totally doesn't belong here. */
#include <linux/configfs.h>
#include <linux/rbtree.h>
enum r2nm_fence_method {
R2NM_FENCE_RESET = 0,
R2NM_FENCE_PANIC,
R2NM_FENCE_METHODS, /* Number of fence methods */
};
struct r2nm_node {
spinlock_t nd_lock;
struct config_item nd_item;
char nd_name[R2NM_MAX_NAME_LEN+1]; /* replace? */
__u8 nd_num;
/* only one address per node, as attributes, for now. */
__be32 nd_ipv4_address;
__be16 nd_ipv4_port;
struct rb_node nd_ip_node;
/* there can be only one local node for now */
int nd_local;
unsigned long nd_set_attributes;
};
struct r2nm_cluster {
struct config_group cl_group;
unsigned cl_has_local:1;
u8 cl_local_node;
rwlock_t cl_nodes_lock;
struct r2nm_node *cl_nodes[R2NM_MAX_NODES];
struct rb_root cl_node_ip_tree;
unsigned int cl_idle_timeout_ms;
unsigned int cl_keepalive_delay_ms;
unsigned int cl_reconnect_delay_ms;
enum r2nm_fence_method cl_fence_method;
/* part of a hack for disk bitmap.. will go eventually. - zab */
unsigned long cl_nodes_bitmap[BITS_TO_LONGS(R2NM_MAX_NODES)];
};
extern struct r2nm_cluster *r2nm_single_cluster;
u8 r2nm_this_node(void);
int r2nm_configured_node_map(unsigned long *map, unsigned bytes);
struct r2nm_node *r2nm_get_node_by_num(u8 node_num);
struct r2nm_node *r2nm_get_node_by_ip(__be32 addr);
void r2nm_node_get(struct r2nm_node *node);
void r2nm_node_put(struct r2nm_node *node);
int r2nm_depend_item(struct config_item *item);
void r2nm_undepend_item(struct config_item *item);
int r2nm_depend_this_node(void);
void r2nm_undepend_this_node(void);
#endif /* R2CLUSTER_NODEMANAGER_H */
/*
* r2net.c
*
* Copyright (c) 2011-2012, Dan Magenheimer, Oracle Corp.
*
* Ramster_r2net provides an interface between zcache and r2net.
*
* FIXME: support more than two nodes
*/
#include <linux/list.h>
#include "tcp.h"
#include "nodemanager.h"
#include "../tmem.h"
#include "../zcache.h"
#include "ramster.h"
#define RAMSTER_TESTING
#define RMSTR_KEY 0x77347734
enum {
RMSTR_TMEM_PUT_EPH = 100,
RMSTR_TMEM_PUT_PERS,
RMSTR_TMEM_ASYNC_GET_REQUEST,
RMSTR_TMEM_ASYNC_GET_AND_FREE_REQUEST,
RMSTR_TMEM_ASYNC_GET_REPLY,
RMSTR_TMEM_FLUSH,
RMSTR_TMEM_FLOBJ,
RMSTR_TMEM_DESTROY_POOL,
};
#define RMSTR_R2NET_MAX_LEN \
(R2NET_MAX_PAYLOAD_BYTES - sizeof(struct tmem_xhandle))
#include "tcp_internal.h"
static struct r2nm_node *r2net_target_node;
static int r2net_target_nodenum;
int r2net_remote_target_node_set(int node_num)
{
int ret = -1;
r2net_target_node = r2nm_get_node_by_num(node_num);
if (r2net_target_node != NULL) {
r2net_target_nodenum = node_num;
r2nm_node_put(r2net_target_node);
ret = 0;
}
return ret;
}
/* FIXME following buffer should be per-cpu, protected by preempt_disable */
static char ramster_async_get_buf[R2NET_MAX_PAYLOAD_BYTES];
static int ramster_remote_async_get_request_handler(struct r2net_msg *msg,
u32 len, void *data, void **ret_data)
{
char *pdata;
struct tmem_xhandle xh;
int found;
size_t size = RMSTR_R2NET_MAX_LEN;
u16 msgtype = be16_to_cpu(msg->msg_type);
bool get_and_free = (msgtype == RMSTR_TMEM_ASYNC_GET_AND_FREE_REQUEST);
unsigned long flags;
xh = *(struct tmem_xhandle *)msg->buf;
if (xh.xh_data_size > RMSTR_R2NET_MAX_LEN)
BUG();
pdata = ramster_async_get_buf;
*(struct tmem_xhandle *)pdata = xh;
pdata += sizeof(struct tmem_xhandle);
local_irq_save(flags);
found = zcache_get_page(xh.client_id, xh.pool_id, &xh.oid, xh.index,
pdata, &size, true, get_and_free ? 1 : -1);
local_irq_restore(flags);
if (found < 0) {
/* a zero size indicates the get failed */
size = 0;
}
if (size > RMSTR_R2NET_MAX_LEN)
BUG();
*ret_data = pdata - sizeof(struct tmem_xhandle);
/* now make caller (r2net_process_message) handle specially */
r2net_force_data_magic(msg, RMSTR_TMEM_ASYNC_GET_REPLY, RMSTR_KEY);
return size + sizeof(struct tmem_xhandle);
}
static int ramster_remote_async_get_reply_handler(struct r2net_msg *msg,
u32 len, void *data, void **ret_data)
{
char *in = (char *)msg->buf;
int datalen = len - sizeof(struct r2net_msg);
int ret = -1;
struct tmem_xhandle *xh = (struct tmem_xhandle *)in;
in += sizeof(struct tmem_xhandle);
datalen -= sizeof(struct tmem_xhandle);
BUG_ON(datalen < 0 || datalen > PAGE_SIZE);
ret = ramster_localify(xh->pool_id, &xh->oid, xh->index,
in, datalen, xh->extra);
#ifdef RAMSTER_TESTING
if (ret == -EEXIST)
pr_err("TESTING ArrgREP, aborted overwrite on racy put\n");
#endif
return ret;
}
int ramster_remote_put_handler(struct r2net_msg *msg,
u32 len, void *data, void **ret_data)
{
struct tmem_xhandle *xh;
char *p = (char *)msg->buf;
int datalen = len - sizeof(struct r2net_msg) -
sizeof(struct tmem_xhandle);
u16 msgtype = be16_to_cpu(msg->msg_type);
bool ephemeral = (msgtype == RMSTR_TMEM_PUT_EPH);
unsigned long flags;
int ret;
xh = (struct tmem_xhandle *)p;
p += sizeof(struct tmem_xhandle);
zcache_autocreate_pool(xh->client_id, xh->pool_id, ephemeral);
local_irq_save(flags);
ret = zcache_put_page(xh->client_id, xh->pool_id, &xh->oid, xh->index,
p, datalen, true, ephemeral);
local_irq_restore(flags);
return ret;
}
int ramster_remote_flush_handler(struct r2net_msg *msg,
u32 len, void *data, void **ret_data)
{
struct tmem_xhandle *xh;
char *p = (char *)msg->buf;
xh = (struct tmem_xhandle *)p;
p += sizeof(struct tmem_xhandle);
(void)zcache_flush_page(xh->client_id, xh->pool_id,
&xh->oid, xh->index);
return 0;
}
int ramster_remote_flobj_handler(struct r2net_msg *msg,
u32 len, void *data, void **ret_data)
{
struct tmem_xhandle *xh;
char *p = (char *)msg->buf;
xh = (struct tmem_xhandle *)p;
p += sizeof(struct tmem_xhandle);
(void)zcache_flush_object(xh->client_id, xh->pool_id, &xh->oid);
return 0;
}
int r2net_remote_async_get(struct tmem_xhandle *xh, bool free, int remotenode,
size_t expect_size, uint8_t expect_cksum,
void *extra)
{
int nodenum, ret = -1, status;
struct r2nm_node *node = NULL;
struct kvec vec[1];
size_t veclen = 1;
u32 msg_type;
struct r2net_node *nn;
node = r2nm_get_node_by_num(remotenode);
if (node == NULL)
goto out;
xh->client_id = r2nm_this_node(); /* which node is getting */
xh->xh_data_cksum = expect_cksum;
xh->xh_data_size = expect_size;
xh->extra = extra;
vec[0].iov_len = sizeof(*xh);
vec[0].iov_base = xh;
node = r2net_target_node;
if (!node)
goto out;
nodenum = r2net_target_nodenum;
r2nm_node_get(node);
nn = r2net_nn_from_num(nodenum);
if (nn->nn_persistent_error || !nn->nn_sc_valid) {
ret = -ENOTCONN;
r2nm_node_put(node);
goto out;
}
if (free)
msg_type = RMSTR_TMEM_ASYNC_GET_AND_FREE_REQUEST;
else
msg_type = RMSTR_TMEM_ASYNC_GET_REQUEST;
ret = r2net_send_message_vec(msg_type, RMSTR_KEY,
vec, veclen, remotenode, &status);
r2nm_node_put(node);
if (ret < 0) {
if (ret == -ENOTCONN || ret == -EHOSTDOWN)
goto out;
if (ret == -EAGAIN)
goto out;
/* FIXME handle bad message possibilities here? */
pr_err("UNTESTED ret<0 in ramster_remote_async_get: ret=%d\n",
ret);
}
ret = status;
out:
return ret;
}
#ifdef RAMSTER_TESTING
/* leave me here to see if it catches a weird crash */
static void ramster_check_irq_counts(void)
{
static int last_hardirq_cnt, last_softirq_cnt, last_preempt_cnt;
int cur_hardirq_cnt, cur_softirq_cnt, cur_preempt_cnt;
cur_hardirq_cnt = hardirq_count() >> HARDIRQ_SHIFT;
if (cur_hardirq_cnt > last_hardirq_cnt) {
last_hardirq_cnt = cur_hardirq_cnt;
if (!(last_hardirq_cnt&(last_hardirq_cnt-1)))
pr_err("RAMSTER TESTING RRP hardirq_count=%d\n",
last_hardirq_cnt);
}
cur_softirq_cnt = softirq_count() >> SOFTIRQ_SHIFT;
if (cur_softirq_cnt > last_softirq_cnt) {
last_softirq_cnt = cur_softirq_cnt;
if (!(last_softirq_cnt&(last_softirq_cnt-1)))
pr_err("RAMSTER TESTING RRP softirq_count=%d\n",
last_softirq_cnt);
}
cur_preempt_cnt = preempt_count() & PREEMPT_MASK;
if (cur_preempt_cnt > last_preempt_cnt) {
last_preempt_cnt = cur_preempt_cnt;
if (!(last_preempt_cnt&(last_preempt_cnt-1)))
pr_err("RAMSTER TESTING RRP preempt_count=%d\n",
last_preempt_cnt);
}
}
#endif
int r2net_remote_put(struct tmem_xhandle *xh, char *data, size_t size,
bool ephemeral, int *remotenode)
{
int nodenum, ret = -1, status;
struct r2nm_node *node = NULL;
struct kvec vec[2];
size_t veclen = 2;
u32 msg_type;
struct r2net_node *nn;
BUG_ON(size > RMSTR_R2NET_MAX_LEN);
xh->client_id = r2nm_this_node(); /* which node is putting */
vec[0].iov_len = sizeof(*xh);
vec[0].iov_base = xh;
vec[1].iov_len = size;
vec[1].iov_base = data;
node = r2net_target_node;
if (!node)
goto out;
nodenum = r2net_target_nodenum;
r2nm_node_get(node);
nn = r2net_nn_from_num(nodenum);
if (nn->nn_persistent_error || !nn->nn_sc_valid) {
ret = -ENOTCONN;
r2nm_node_put(node);
goto out;
}
if (ephemeral)
msg_type = RMSTR_TMEM_PUT_EPH;
else
msg_type = RMSTR_TMEM_PUT_PERS;
#ifdef RAMSTER_TESTING
/* leave me here to see if it catches a weird crash */
ramster_check_irq_counts();
#endif
ret = r2net_send_message_vec(msg_type, RMSTR_KEY, vec, veclen,
nodenum, &status);
if (ret < 0)
ret = -1;
else {
ret = status;
*remotenode = nodenum;
}
r2nm_node_put(node);
out:
return ret;
}
int r2net_remote_flush(struct tmem_xhandle *xh, int remotenode)
{
int ret = -1, status;
struct r2nm_node *node = NULL;
struct kvec vec[1];
size_t veclen = 1;
node = r2nm_get_node_by_num(remotenode);
BUG_ON(node == NULL);
xh->client_id = r2nm_this_node(); /* which node is flushing */
vec[0].iov_len = sizeof(*xh);
vec[0].iov_base = xh;
BUG_ON(irqs_disabled());
BUG_ON(in_softirq());
ret = r2net_send_message_vec(RMSTR_TMEM_FLUSH, RMSTR_KEY,
vec, veclen, remotenode, &status);
r2nm_node_put(node);
return ret;
}
int r2net_remote_flush_object(struct tmem_xhandle *xh, int remotenode)
{
int ret = -1, status;
struct r2nm_node *node = NULL;
struct kvec vec[1];
size_t veclen = 1;
node = r2nm_get_node_by_num(remotenode);
BUG_ON(node == NULL);
xh->client_id = r2nm_this_node(); /* which node is flobjing */
vec[0].iov_len = sizeof(*xh);
vec[0].iov_base = xh;
ret = r2net_send_message_vec(RMSTR_TMEM_FLOBJ, RMSTR_KEY,
vec, veclen, remotenode, &status);
r2nm_node_put(node);
return ret;
}
/*
* Handler registration
*/
static LIST_HEAD(r2net_unreg_list);
static void r2net_unregister_handlers(void)
{
r2net_unregister_handler_list(&r2net_unreg_list);
}
int r2net_register_handlers(void)
{
int status;
status = r2net_register_handler(RMSTR_TMEM_PUT_EPH, RMSTR_KEY,
RMSTR_R2NET_MAX_LEN,
ramster_remote_put_handler,
NULL, NULL, &r2net_unreg_list);
if (status)
goto bail;
status = r2net_register_handler(RMSTR_TMEM_PUT_PERS, RMSTR_KEY,
RMSTR_R2NET_MAX_LEN,
ramster_remote_put_handler,
NULL, NULL, &r2net_unreg_list);
if (status)
goto bail;
status = r2net_register_handler(RMSTR_TMEM_ASYNC_GET_REQUEST, RMSTR_KEY,
RMSTR_R2NET_MAX_LEN,
ramster_remote_async_get_request_handler,
NULL, NULL,
&r2net_unreg_list);
if (status)
goto bail;
status = r2net_register_handler(RMSTR_TMEM_ASYNC_GET_AND_FREE_REQUEST,
RMSTR_KEY, RMSTR_R2NET_MAX_LEN,
ramster_remote_async_get_request_handler,
NULL, NULL,
&r2net_unreg_list);
if (status)
goto bail;
status = r2net_register_handler(RMSTR_TMEM_ASYNC_GET_REPLY, RMSTR_KEY,
RMSTR_R2NET_MAX_LEN,
ramster_remote_async_get_reply_handler,
NULL, NULL,
&r2net_unreg_list);
if (status)
goto bail;
status = r2net_register_handler(RMSTR_TMEM_FLUSH, RMSTR_KEY,
RMSTR_R2NET_MAX_LEN,
ramster_remote_flush_handler,
NULL, NULL,
&r2net_unreg_list);
if (status)
goto bail;
status = r2net_register_handler(RMSTR_TMEM_FLOBJ, RMSTR_KEY,
RMSTR_R2NET_MAX_LEN,
ramster_remote_flobj_handler,
NULL, NULL,
&r2net_unreg_list);
if (status)
goto bail;
pr_info("ramster: r2net handlers registered\n");
bail:
if (status) {
r2net_unregister_handlers();
pr_err("ramster: couldn't register r2net handlers\n");
}
return status;
}
RAMSTER HOW-TO
Author: Dan Magenheimer
Ramster maintainer: Konrad Wilk <konrad.wilk@oracle.com>
This is a HOWTO document for ramster which, as of this writing, is in
the kernel as a subdirectory of zcache in drivers/staging, called ramster.
(Zcache can be built with or without ramster functionality.) If enabled
and properly configured, ramster allows memory capacity load balancing
across multiple machines in a cluster. Further, the ramster code serves
as an example of asynchronous access for zcache (as well as cleancache and
frontswap) that may prove useful for future transcendent memory
implementations, such as KVM and NVRAM. While ramster works today on
any network connection that supports kernel sockets, its features may
become more interesting on future high-speed fabrics/interconnects.
Ramster requires both kernel and userland support. The userland support,
called ramster-tools, is known to work with EL6-based distros, but is a
set of poorly-hacked slightly-modified cluster tools based on ocfs2, which
includes an init file, a config file, and a userland binary that interfaces
to the kernel. This state of userland support reflects the abysmal userland
skills of this suitably-embarrassed author; any help/patches to turn
ramster-tools into more distributable rpms/debs useful for a wider range
of distros would be appreciated. The source RPM that can be used as a
starting point is available at:
http://oss.oracle.com/projects/tmem/files/RAMster/
As a result of this author's ignorance, userland setup described in this
HOWTO assumes an EL6 distro and is described in EL6 syntax. Apologies
if this offends anyone!
Kernel support has only been tested on x86_64. Systems with an active
ocfs2 filesystem should work, but since ramster leverages a lot of
code from ocfs2, there may be latent issues. A kernel configuration that
includes CONFIG_OCFS2_FS should build OK, and should certainly run OK
if no ocfs2 filesystem is mounted.
This HOWTO demonstrates memory capacity load balancing for a two-node
cluster, where one node called the "local" node becomes overcommitted
and the other node called the "remote" node provides additional RAM
capacity for use by the local node. Ramster is capable of more complex
topologies; see the last section titled "ADVANCED RAMSTER TOPOLOGIES".
If you find any terms in this HOWTO unfamiliar or don't understand the
motivation for ramster, the following LWN reading is recommended:
-- Transcendent Memory in a Nutshell (lwn.net/Articles/454795)
-- The future calculus of memory management (lwn.net/Articles/475681)
And since ramster is built on top of zcache, this article may be helpful:
-- In-kernel memory compression (lwn.net/Articles/545244)
Now that you've memorized the contents of those articles, let's get started!
A. PRELIMINARY
1) Install two x86_64 Linux systems that are known to work when
upgraded to a recent upstream Linux kernel version.
On each system:
2) Configure, build and install, then boot Linux, just to ensure it
can be done with an unmodified upstream kernel. Confirm you booted
the upstream kernel with "uname -a".
3) If you plan to do any performance testing or unless you plan to
test only swapping, the "WasActive" patch is also highly recommended.
(Search lkml.org for WasActive, apply the patch, rebuild your kernel.)
For a demo or simple testing, the patch can be ignored.
4) Install ramster-tools as root. An x86_64 rpm for EL6-based systems
can be found at:
http://oss.oracle.com/projects/tmem/files/RAMster/
(Sorry but for now, non-EL6 users must recreate ramster-tools on
their own from source. See above.)
5) Ensure that debugfs is mounted at each boot. Examples below assume it
is mounted at /sys/kernel/debug.
B. BUILDING RAMSTER INTO THE KERNEL
Do the following on each system:
1) Using the kernel configuration mechanism of your choice, change
your config to include:
CONFIG_CLEANCACHE=y
CONFIG_FRONTSWAP=y
CONFIG_STAGING=y
CONFIG_CONFIGFS_FS=y # NOTE: MUST BE y, not m
CONFIG_ZCACHE=y
CONFIG_RAMSTER=y
For a linux-3.10 or later kernel, you should also set:
CONFIG_ZCACHE_DEBUG=y
CONFIG_RAMSTER_DEBUG=y
Before building the kernel please doublecheck your kernel config
file to ensure all of the settings are correct.
2) Build this kernel and change your boot file (e.g. /etc/grub.conf)
so that the new kernel will boot.
3) Add "zcache" and "ramster" as kernel boot parameters for the new kernel.
4) Reboot each system approximately simultaneously.
5) Check dmesg to ensure there are some messages from ramster, prefixed
by "ramster:"
# dmesg | grep ramster
You should also see a lot of files in:
# ls /sys/kernel/debug/zcache
# ls /sys/kernel/debug/ramster
These are mostly counters for various zcache and ramster activities.
You should also see files in:
# ls /sys/kernel/mm/ramster
These are sysfs files that control ramster as we shall see.
Ramster now will act as a single-system zcache on each system
but doesn't yet know anything about the cluster so can't yet do
anything remotely.
C. CONFIGURING THE RAMSTER CLUSTER
This part can be error prone unless you are familiar with clustering
filesystems. We need to describe the cluster in a /etc/ramster.conf
file and the init scripts that parse it are extremely picky about
the syntax.
1) Create a /etc/ramster.conf file and ensure it is identical on both
systems. This file mimics the ocfs2 format and there is a good amount
of documentation that can be searched for ocfs2.conf, but you can use:
cluster:
name = ramster
node_count = 2
node:
name = system1
cluster = ramster
number = 0
ip_address = my.ip.ad.r1
ip_port = 7777
node:
name = system2
cluster = ramster
number = 1
ip_address = my.ip.ad.r2
ip_port = 7777
You must ensure that the "name" field in the file exactly matches
the output of "hostname" on each system; if "hostname" shows a
fully-qualified hostname, ensure the name is fully qualified in
/etc/ramster.conf. Obviously, substitute my.ip.ad.rx with proper
ip addresses.
2) Enable the ramster service and configure it. If you used the
EL6 ramster-tools, this would be:
# chkconfig --add ramster
# service ramster configure
Set "load on boot" to "y", cluster to start is "ramster" (or whatever
name you chose in ramster.conf), heartbeat dead threshold as "500",
network idle timeout as "1000000". Leave the others as default.
3) Reboot both systems. After reboot, try (assuming EL6 ramster-tools):
# service ramster status
You should see "Checking RAMSTER cluster "ramster": Online". If you do
not, something is wrong and ramster will not work. Note that you
should also see that the driver for "configfs" is loaded and mounted,
the driver for ocfs2_dlmfs is not loaded, and some numbers for network
parameters. You will also see "Checking RAMSTER heartbeat: Not active".
That's all OK.
4) Now you need to start the cluster heartbeat; the cluster is not "up"
until all nodes detect a heartbeat. In a real cluster, heartbeat detection
is done via a cluster filesystem, but ramster doesn't require one. Some
hack-y kernel code in ramster can start the heartbeat for you though if
you tell it what nodes are "up". To enable the heartbeat, do:
# echo 0 > /sys/kernel/mm/ramster/manual_node_up
# echo 1 > /sys/kernel/mm/ramster/manual_node_up
This must be done on BOTH nodes and, to avoid timeouts, must be done
approximately concurrently on both nodes. On an EL6 system, it is
convenient to put these lines in /etc/rc.local. To confirm that the
cluster is now up, on both systems do:
# dmesg | grep ramster
You should see ramster "Accepted connection" messages in dmesg on both
nodes after this. Note that if you check userland status again with
# service ramster status
you will still see "Checking RAMSTER heartbeat: Not active". That's
still OK... the ramster kernel heartbeat hack doesn't communicate to
userland.
5) You now must tell each node the node to which it should "remotify" pages.
On this two node cluster, we will assume the "local" node, node 0, has
memory overcommitted and will use ramster to utilize RAM capacity on
the "remote node", node 1. To configure this, on node 0, you do:
# echo 1 > /sys/kernel/mm/ramster/remote_target_nodenum
You should see "ramster: node 1 set as remotification target" in dmesg
on node 0. Again, on EL6, /etc/rc.local is a good place to put this
on node 0 so you don't forget to do it at each boot.
6) One more step: By default, the ramster code does not "remotify" any
pages; this is primarily for testing purposes, but sometimes it is
useful. This may change in the future, but for now, on node 0, you do:
# echo 1 > /sys/kernel/mm/ramster/pers_remotify_enable
# echo 1 > /sys/kernel/mm/ramster/eph_remotify_enable
The first enables remotifying swap (persistent, aka frontswap) pages,
the second enables remotifying of page cache (ephemeral, cleancache)
pages.
On EL6, these lines can also be put in /etc/rc.local (AFTER the
node_up lines), or at the beginning of a script that runs a workload.
7) Note that most testing has been done with both/all machines booted
roughly simultaneously to avoid cluster timeouts. Ideally, you should
do this too unless you are trying to break ramster rather than just
use it. ;-)
D. TESTING RAMSTER
1) Note that ramster has no value unless pages get "remotified". For
swap/frontswap/persistent pages, this doesn't happen unless/until
the workload would cause swapping to occur, at which point pages
are put into frontswap/zcache, and the remotification thread starts
working. To get to the point where the system swaps, you either
need a workload for which the working set exceeds the RAM in the
system; or you need to somehow reduce the amount of RAM one of
the system sees. This latter is easy when testing in a VM, but
harder on physical systems. In some cases, "mem=xxxM" on the
kernel command line restricts memory, but for some values of xxx
the kernel may fail to boot. One may also try creating a fixed
RAMdisk, doing nothing with it, but ensuring that it eats up a fixed
amount of RAM.
2) To see if ramster is working, on the "remote node", node 1, try:
# grep . /sys/kernel/debug/ramster/foreign_*
# # note, that is space-dot-space between grep and the pathname
to monitor the number (and max) ephemeral and persistent pages
that ramster has sent. If these stay at zero, ramster is not working
either because the workload on the local node (node 0) isn't creating
enough memory pressure or because "remotifying" isn't working. On the
local system, node 0, you can watch lots of useful information also.
Try:
grep . /sys/kernel/debug/zcache/*pageframes* \
/sys/kernel/debug/zcache/*zbytes* \
/sys/kernel/debug/zcache/*zpages* \
/sys/kernel/debug/ramster/*remote*
Of particular note are the remote_*_pages_succ_get counters. These
show how many disk reads and/or disk writes have been avoided on the
overcommitted local system by storing pages remotely using ramster.
At the risk of information overload, you can also grep:
/sys/kernel/debug/cleancache/* and /sys/kernel/debug/frontswap/*
These show, for example, how many disk reads and/or disk writes have
been avoided by using zcache to optimize RAM on the local system.
AUTOMATIC SWAP REPATRIATION
You may notice that while the systems are idle, the foreign persistent
page count on the remote machine slowly decreases. This is because
ramster implements "frontswap selfshrinking": When possible, swap
pages that have been remotified are slowly repatriated to the local
machine. This is so that local RAM can be used when possible and
so that, in case of remote machine crash, the probability of loss
of data is reduced.
REBOOTING / POWEROFF
If a system is shut down while some of its swap pages still reside
on a remote system, the system may lock up during the shutdown
sequence. This will occur if the network is shut down before the
swap mechansim is shut down, which is the default ordering on many
distros. To avoid this annoying problem, simply shut off the swap
subsystem before starting the shutdown sequence, e.g.:
# swapoff -a
# reboot
Ideally, this swapoff-before-ifdown ordering should be enforced permanently
using shutdown scripts.
KNOWN PROBLEMS
1) You may periodically see messages such as:
ramster_r2net, message length problem
This is harmless but indicates that a node is sending messages
containing compressed pages that exceed the maximum for zcache
(PAGE_SIZE*15/16). The sender side needs to be fixed.
2) If you see a "No longer connected to node..." message or a "No connection
established with node X after N seconds", it is possible you may
be in an unrecoverable state. If you are certain all of the
appropriate cluster configuration steps described above have been
performed, try rebooting the two servers concurrently to see if
the cluster starts.
Note that "Connection to node... shutdown, state 7" is an intermediate
connection state. As long as you later see "Accepted connection", the
intermediate states are harmless.
3) There are known issues in counting certain values. As a result
you may see periodic warnings from the kernel. Almost always you
will see "ramster: bad accounting for XXX". There are also "WARN_ONCE"
messages. If you see kernel warnings with a tombstone, please report
them. They are harmless but reflect bugs that need to be eventually fixed.
ADVANCED RAMSTER TOPOLOGIES
The kernel code for ramster can support up to eight nodes in a cluster,
but no testing has been done with more than three nodes.
In the example described above, the "remote" node serves as a RAM
overflow for the "local" node. This can be made symmetric by appropriate
settings of the sysfs remote_target_nodenum file. For example, by setting:
# echo 1 > /sys/kernel/mm/ramster/remote_target_nodenum
on node 0, and
# echo 0 > /sys/kernel/mm/ramster/remote_target_nodenum
on node 1, each node can serve as a RAM overflow for the other.
For more than two nodes, a "RAM server" can be configured. For a
three node system, set:
# echo 0 > /sys/kernel/mm/ramster/remote_target_nodenum
on node 1, and
# echo 0 > /sys/kernel/mm/ramster/remote_target_nodenum
on node 2. Then node 0 is a RAM server for node 1 and node 2.
In this implementation of ramster, any remote node is potentially a single
point of failure (SPOF). Though the probability of failure is reduced
by automatic swap repatriation (see above), a proposed future enhancement
to ramster improves high-availability for the cluster by sending a copy
of each page of date to two other nodes. Patches welcome!
/*
* ramster.c
*
* Copyright (c) 2010-2012, Dan Magenheimer, Oracle Corp.
*
* RAMster implements peer-to-peer transcendent memory, allowing a "cluster" of
* kernels to dynamically pool their RAM so that a RAM-hungry workload on one
* machine can temporarily and transparently utilize RAM on another machine
* which is presumably idle or running a non-RAM-hungry workload.
*
* RAMster combines a clustering and messaging foundation based on the ocfs2
* cluster layer with the in-kernel compression implementation of zcache, and
* adds code to glue them together. When a page is "put" to RAMster, it is
* compressed and stored locally. Periodically, a thread will "remotify" these
* pages by sending them via messages to a remote machine. When the page is
* later needed as indicated by a page fault, a "get" is issued. If the data
* is local, it is uncompressed and the fault is resolved. If the data is
* remote, a message is sent to fetch the data and the faulting thread sleeps;
* when the data arrives, the thread awakens, the data is decompressed and
* the fault is resolved.
* As of V5, clusters up to eight nodes are supported; each node can remotify
* pages to one specified node, so clusters can be configured as clients to
* a "memory server". Some simple policy is in place that will need to be
* refined over time. Larger clusters and fault-resistant protocols can also
* be added over time.
*/
#include <linux/module.h>
#include <linux/cpu.h>
#include <linux/highmem.h>
#include <linux/list.h>
#include <linux/lzo.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/atomic.h>
#include <linux/frontswap.h>
#include "../tmem.h"
#include "../zcache.h"
#include "../zbud.h"
#include "ramster.h"
#include "ramster_nodemanager.h"
#include "tcp.h"
#include "debug.h"
#define RAMSTER_TESTING
#ifndef CONFIG_SYSFS
#error "ramster needs sysfs to define cluster nodes to use"
#endif
static bool use_cleancache __read_mostly;
static bool use_frontswap __read_mostly;
static bool use_frontswap_exclusive_gets __read_mostly;
/* These must be sysfs not debugfs as they are checked/used by userland!! */
static unsigned long ramster_interface_revision __read_mostly =
R2NM_API_VERSION; /* interface revision must match userspace! */
static unsigned long ramster_pers_remotify_enable __read_mostly;
static unsigned long ramster_eph_remotify_enable __read_mostly;
static atomic_t ramster_remote_pers_pages = ATOMIC_INIT(0);
#define MANUAL_NODES 8
static bool ramster_nodes_manual_up[MANUAL_NODES] __read_mostly;
static int ramster_remote_target_nodenum __read_mostly = -1;
/* Used by this code. */
long ramster_flnodes;
/* FIXME frontswap selfshrinking knobs in debugfs? */
static LIST_HEAD(ramster_rem_op_list);
static DEFINE_SPINLOCK(ramster_rem_op_list_lock);
static DEFINE_PER_CPU(struct ramster_preload, ramster_preloads);
static DEFINE_PER_CPU(unsigned char *, ramster_remoteputmem1);
static DEFINE_PER_CPU(unsigned char *, ramster_remoteputmem2);
static struct kmem_cache *ramster_flnode_cache __read_mostly;
static struct flushlist_node *ramster_flnode_alloc(struct tmem_pool *pool)
{
struct flushlist_node *flnode = NULL;
struct ramster_preload *kp;
kp = &__get_cpu_var(ramster_preloads);
flnode = kp->flnode;
BUG_ON(flnode == NULL);
kp->flnode = NULL;
inc_ramster_flnodes();
return flnode;
}
/* the "flush list" asynchronously collects pages to remotely flush */
#define FLUSH_ENTIRE_OBJECT ((uint32_t)-1)
static void ramster_flnode_free(struct flushlist_node *flnode,
struct tmem_pool *pool)
{
dec_ramster_flnodes();
BUG_ON(ramster_flnodes < 0);
kmem_cache_free(ramster_flnode_cache, flnode);
}
int ramster_do_preload_flnode(struct tmem_pool *pool)
{
struct ramster_preload *kp;
struct flushlist_node *flnode;
int ret = -ENOMEM;
BUG_ON(!irqs_disabled());
if (unlikely(ramster_flnode_cache == NULL))
BUG();
kp = &__get_cpu_var(ramster_preloads);
flnode = kmem_cache_alloc(ramster_flnode_cache, GFP_ATOMIC);
if (unlikely(flnode == NULL) && kp->flnode == NULL)
BUG(); /* FIXME handle more gracefully, but how??? */
else if (kp->flnode == NULL)
kp->flnode = flnode;
else
kmem_cache_free(ramster_flnode_cache, flnode);
return ret;
}
EXPORT_SYMBOL_GPL(ramster_do_preload_flnode);
/*
* Called by the message handler after a (still compressed) page has been
* fetched from the remote machine in response to an "is_remote" tmem_get
* or persistent tmem_localify. For a tmem_get, "extra" is the address of
* the page that is to be filled to successfully resolve the tmem_get; for
* a (persistent) tmem_localify, "extra" is NULL (as the data is placed only
* in the local zcache). "data" points to "size" bytes of (compressed) data
* passed in the message. In the case of a persistent remote get, if
* pre-allocation was successful (see ramster_repatriate_preload), the page
* is placed into both local zcache and at "extra".
*/
int ramster_localify(int pool_id, struct tmem_oid *oidp, uint32_t index,
char *data, unsigned int size, void *extra)
{
int ret = -ENOENT;
unsigned long flags;
struct tmem_pool *pool;
bool eph, delete = false;
void *pampd, *saved_hb;
struct tmem_obj *obj;
pool = zcache_get_pool_by_id(LOCAL_CLIENT, pool_id);
if (unlikely(pool == NULL))
/* pool doesn't exist anymore */
goto out;
eph = is_ephemeral(pool);
local_irq_save(flags); /* FIXME: maybe only disable softirqs? */
pampd = tmem_localify_get_pampd(pool, oidp, index, &obj, &saved_hb);
if (pampd == NULL) {
/* hmmm... must have been a flush while waiting */
#ifdef RAMSTER_TESTING
pr_err("UNTESTED pampd==NULL in ramster_localify\n");
#endif
if (eph)
inc_ramster_remote_eph_pages_unsucc_get();
else
inc_ramster_remote_pers_pages_unsucc_get();
obj = NULL;
goto finish;
} else if (unlikely(!pampd_is_remote(pampd))) {
/* hmmm... must have been a dup put while waiting */
#ifdef RAMSTER_TESTING
pr_err("UNTESTED dup while waiting in ramster_localify\n");
#endif
if (eph)
inc_ramster_remote_eph_pages_unsucc_get();
else
inc_ramster_remote_pers_pages_unsucc_get();
obj = NULL;
pampd = NULL;
ret = -EEXIST;
goto finish;
} else if (size == 0) {
/* no remote data, delete the local is_remote pampd */
pampd = NULL;
if (eph)
inc_ramster_remote_eph_pages_unsucc_get();
else
BUG();
delete = true;
goto finish;
}
if (pampd_is_intransit(pampd)) {
/*
* a pampd is marked intransit if it is remote and space has
* been allocated for it locally (note, only happens for
* persistent pages, in which case the remote copy is freed)
*/
BUG_ON(eph);
pampd = pampd_mask_intransit_and_remote(pampd);
zbud_copy_to_zbud(pampd, data, size);
} else {
/*
* setting pampd to NULL tells tmem_localify_finish to leave
* pampd alone... meaning it is left pointing to the
* remote copy
*/
pampd = NULL;
obj = NULL;
}
/*
* but in all cases, we decompress direct-to-memory to complete
* the remotify and return success
*/
BUG_ON(extra == NULL);
zcache_decompress_to_page(data, size, (struct page *)extra);
if (eph)
inc_ramster_remote_eph_pages_succ_get();
else
inc_ramster_remote_pers_pages_succ_get();
ret = 0;
finish:
tmem_localify_finish(obj, index, pampd, saved_hb, delete);
zcache_put_pool(pool);
local_irq_restore(flags);
out:
return ret;
}
void ramster_pampd_new_obj(struct tmem_obj *obj)
{
obj->extra = NULL;
}
void ramster_pampd_free_obj(struct tmem_pool *pool, struct tmem_obj *obj,
bool pool_destroy)
{
struct flushlist_node *flnode;
BUG_ON(preemptible());
if (obj->extra == NULL)
return;
if (pool_destroy && is_ephemeral(pool))
/* FIXME don't bother with remote eph data for now */
return;
BUG_ON(!pampd_is_remote(obj->extra));
flnode = ramster_flnode_alloc(pool);
flnode->xh.client_id = pampd_remote_node(obj->extra);
flnode->xh.pool_id = pool->pool_id;
flnode->xh.oid = obj->oid;
flnode->xh.index = FLUSH_ENTIRE_OBJECT;
flnode->rem_op.op = RAMSTER_REMOTIFY_FLUSH_OBJ;
spin_lock(&ramster_rem_op_list_lock);
list_add(&flnode->rem_op.list, &ramster_rem_op_list);
spin_unlock(&ramster_rem_op_list_lock);
}
/*
* Called on a remote persistent tmem_get to attempt to preallocate
* local storage for the data contained in the remote persistent page.
* If successfully preallocated, returns the pampd, marked as remote and
* in_transit. Else returns NULL. Note that the appropriate tmem data
* structure must be locked.
*/
void *ramster_pampd_repatriate_preload(void *pampd, struct tmem_pool *pool,
struct tmem_oid *oidp, uint32_t index,
bool *intransit)
{
int clen = pampd_remote_size(pampd), c;
void *ret_pampd = NULL;
unsigned long flags;
struct tmem_handle th;
BUG_ON(!pampd_is_remote(pampd));
BUG_ON(is_ephemeral(pool));
if (use_frontswap_exclusive_gets)
/* don't need local storage */
goto out;
if (pampd_is_intransit(pampd)) {
/*
* to avoid multiple allocations (and maybe a memory leak)
* don't preallocate if already in the process of being
* repatriated
*/
*intransit = true;
goto out;
}
*intransit = false;
local_irq_save(flags);
th.client_id = pampd_remote_node(pampd);
th.pool_id = pool->pool_id;
th.oid = *oidp;
th.index = index;
ret_pampd = zcache_pampd_create(NULL, clen, true, false, &th);
if (ret_pampd != NULL) {
/*
* a pampd is marked intransit if it is remote and space has
* been allocated for it locally (note, only happens for
* persistent pages, in which case the remote copy is freed)
*/
ret_pampd = pampd_mark_intransit(ret_pampd);
c = atomic_dec_return(&ramster_remote_pers_pages);
WARN_ON_ONCE(c < 0);
} else {
inc_ramster_pers_pages_remote_nomem();
}
local_irq_restore(flags);
out:
return ret_pampd;
}
/*
* Called on a remote tmem_get to invoke a message to fetch the page.
* Might sleep so no tmem locks can be held. "extra" is passed
* all the way through the round-trip messaging to ramster_localify.
*/
int ramster_pampd_repatriate(void *fake_pampd, void *real_pampd,
struct tmem_pool *pool,
struct tmem_oid *oid, uint32_t index,
bool free, void *extra)
{
struct tmem_xhandle xh;
int ret;
if (pampd_is_intransit(real_pampd))
/* have local space pre-reserved, so free remote copy */
free = true;
xh = tmem_xhandle_fill(LOCAL_CLIENT, pool, oid, index);
/* unreliable request/response for now */
ret = r2net_remote_async_get(&xh, free,
pampd_remote_node(fake_pampd),
pampd_remote_size(fake_pampd),
pampd_remote_cksum(fake_pampd),
extra);
return ret;
}
bool ramster_pampd_is_remote(void *pampd)
{
return pampd_is_remote(pampd);
}
int ramster_pampd_replace_in_obj(void *new_pampd, struct tmem_obj *obj)
{
int ret = -1;
if (new_pampd != NULL) {
if (obj->extra == NULL)
obj->extra = new_pampd;
/* enforce that all remote pages in an object reside
* in the same node! */
else if (pampd_remote_node(new_pampd) !=
pampd_remote_node((void *)(obj->extra)))
BUG();
ret = 0;
}
return ret;
}
void *ramster_pampd_free(void *pampd, struct tmem_pool *pool,
struct tmem_oid *oid, uint32_t index, bool acct)
{
bool eph = is_ephemeral(pool);
void *local_pampd = NULL;
int c;
BUG_ON(preemptible());
BUG_ON(!pampd_is_remote(pampd));
WARN_ON(acct == false);
if (oid == NULL) {
/*
* a NULL oid means to ignore this pampd free
* as the remote freeing will be handled elsewhere
*/
} else if (eph) {
/* FIXME remote flush optional but probably good idea */
} else if (pampd_is_intransit(pampd)) {
/* did a pers remote get_and_free, so just free local */
local_pampd = pampd_mask_intransit_and_remote(pampd);
} else {
struct flushlist_node *flnode =
ramster_flnode_alloc(pool);
flnode->xh.client_id = pampd_remote_node(pampd);
flnode->xh.pool_id = pool->pool_id;
flnode->xh.oid = *oid;
flnode->xh.index = index;
flnode->rem_op.op = RAMSTER_REMOTIFY_FLUSH_PAGE;
spin_lock(&ramster_rem_op_list_lock);
list_add(&flnode->rem_op.list, &ramster_rem_op_list);
spin_unlock(&ramster_rem_op_list_lock);
c = atomic_dec_return(&ramster_remote_pers_pages);
WARN_ON_ONCE(c < 0);
}
return local_pampd;
}
EXPORT_SYMBOL_GPL(ramster_pampd_free);
void ramster_count_foreign_pages(bool eph, int count)
{
BUG_ON(count != 1 && count != -1);
if (eph) {
if (count > 0) {
inc_ramster_foreign_eph_pages();
} else {
dec_ramster_foreign_eph_pages();
#ifdef CONFIG_RAMSTER_DEBUG
WARN_ON_ONCE(ramster_foreign_eph_pages < 0);
#endif
}
} else {
if (count > 0) {
inc_ramster_foreign_pers_pages();
} else {
dec_ramster_foreign_pers_pages();
#ifdef CONFIG_RAMSTER_DEBUG
WARN_ON_ONCE(ramster_foreign_pers_pages < 0);
#endif
}
}
}
EXPORT_SYMBOL_GPL(ramster_count_foreign_pages);
/*
* For now, just push over a few pages every few seconds to
* ensure that it basically works
*/
static struct workqueue_struct *ramster_remotify_workqueue;
static void ramster_remotify_process(struct work_struct *work);
static DECLARE_DELAYED_WORK(ramster_remotify_worker,
ramster_remotify_process);
static void ramster_remotify_queue_delayed_work(unsigned long delay)
{
if (!queue_delayed_work(ramster_remotify_workqueue,
&ramster_remotify_worker, delay))
pr_err("ramster_remotify: bad workqueue\n");
}
static void ramster_remote_flush_page(struct flushlist_node *flnode)
{
struct tmem_xhandle *xh;
int remotenode, ret;
preempt_disable();
xh = &flnode->xh;
remotenode = flnode->xh.client_id;
ret = r2net_remote_flush(xh, remotenode);
if (ret >= 0)
inc_ramster_remote_pages_flushed();
else
inc_ramster_remote_page_flushes_failed();
preempt_enable_no_resched();
ramster_flnode_free(flnode, NULL);
}
static void ramster_remote_flush_object(struct flushlist_node *flnode)
{
struct tmem_xhandle *xh;
int remotenode, ret;
preempt_disable();
xh = &flnode->xh;
remotenode = flnode->xh.client_id;
ret = r2net_remote_flush_object(xh, remotenode);
if (ret >= 0)
inc_ramster_remote_objects_flushed();
else
inc_ramster_remote_object_flushes_failed();
preempt_enable_no_resched();
ramster_flnode_free(flnode, NULL);
}
int ramster_remotify_pageframe(bool eph)
{
struct tmem_xhandle xh;
unsigned int size;
int remotenode, ret, zbuds;
struct tmem_pool *pool;
unsigned long flags;
unsigned char cksum;
char *p;
int i, j;
unsigned char *tmpmem[2];
struct tmem_handle th[2];
unsigned int zsize[2];
tmpmem[0] = __get_cpu_var(ramster_remoteputmem1);
tmpmem[1] = __get_cpu_var(ramster_remoteputmem2);
local_bh_disable();
zbuds = zbud_make_zombie_lru(&th[0], &tmpmem[0], &zsize[0], eph);
/* now OK to release lock set in caller */
local_bh_enable();
if (zbuds == 0)
goto out;
BUG_ON(zbuds > 2);
for (i = 0; i < zbuds; i++) {
xh.client_id = th[i].client_id;
xh.pool_id = th[i].pool_id;
xh.oid = th[i].oid;
xh.index = th[i].index;
size = zsize[i];
BUG_ON(size == 0 || size > zbud_max_buddy_size());
for (p = tmpmem[i], cksum = 0, j = 0; j < size; j++)
cksum += *p++;
ret = r2net_remote_put(&xh, tmpmem[i], size, eph, &remotenode);
if (ret != 0) {
/*
* This is some form of a memory leak... if the remote put
* fails, there will never be another attempt to remotify
* this page. But since we've dropped the zv pointer,
* the page may have been freed or the data replaced
* so we can't just "put it back" in the remote op list.
* Even if we could, not sure where to put it in the list
* because there may be flushes that must be strictly
* ordered vs the put. So leave this as a FIXME for now.
* But count them so we know if it becomes a problem.
*/
if (eph)
inc_ramster_eph_pages_remote_failed();
else
inc_ramster_pers_pages_remote_failed();
break;
} else {
if (!eph)
atomic_inc(&ramster_remote_pers_pages);
}
if (eph)
inc_ramster_eph_pages_remoted();
else
inc_ramster_pers_pages_remoted();
/*
* data was successfully remoted so change the local version to
* point to the remote node where it landed
*/
local_bh_disable();
pool = zcache_get_pool_by_id(LOCAL_CLIENT, xh.pool_id);
local_irq_save(flags);
(void)tmem_replace(pool, &xh.oid, xh.index,
pampd_make_remote(remotenode, size, cksum));
local_irq_restore(flags);
zcache_put_pool(pool);
local_bh_enable();
}
out:
return zbuds;
}
static void zcache_do_remotify_flushes(void)
{
struct ramster_remotify_hdr *rem_op;
union remotify_list_node *u;
while (1) {
spin_lock(&ramster_rem_op_list_lock);
if (list_empty(&ramster_rem_op_list)) {
spin_unlock(&ramster_rem_op_list_lock);
goto out;
}
rem_op = list_first_entry(&ramster_rem_op_list,
struct ramster_remotify_hdr, list);
list_del_init(&rem_op->list);
spin_unlock(&ramster_rem_op_list_lock);
u = (union remotify_list_node *)rem_op;
switch (rem_op->op) {
case RAMSTER_REMOTIFY_FLUSH_PAGE:
ramster_remote_flush_page((struct flushlist_node *)u);
break;
case RAMSTER_REMOTIFY_FLUSH_OBJ:
ramster_remote_flush_object((struct flushlist_node *)u);
break;
default:
BUG();
}
}
out:
return;
}
static void ramster_remotify_process(struct work_struct *work)
{
static bool remotify_in_progress;
int i;
BUG_ON(irqs_disabled());
if (remotify_in_progress)
goto requeue;
if (ramster_remote_target_nodenum == -1)
goto requeue;
remotify_in_progress = true;
if (use_cleancache && ramster_eph_remotify_enable) {
for (i = 0; i < 100; i++) {
zcache_do_remotify_flushes();
(void)ramster_remotify_pageframe(true);
}
}
if (use_frontswap && ramster_pers_remotify_enable) {
for (i = 0; i < 100; i++) {
zcache_do_remotify_flushes();
(void)ramster_remotify_pageframe(false);
}
}
remotify_in_progress = false;
requeue:
ramster_remotify_queue_delayed_work(HZ);
}
void ramster_remotify_init(void)
{
unsigned long n = 60UL;
ramster_remotify_workqueue =
create_singlethread_workqueue("ramster_remotify");
ramster_remotify_queue_delayed_work(n * HZ);
}
static ssize_t ramster_manual_node_up_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
int i;
char *p = buf;
for (i = 0; i < MANUAL_NODES; i++)
if (ramster_nodes_manual_up[i])
p += sprintf(p, "%d ", i);
p += sprintf(p, "\n");
return p - buf;
}
static ssize_t ramster_manual_node_up_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
int err;
unsigned long node_num;
err = kstrtoul(buf, 10, &node_num);
if (err) {
pr_err("ramster: bad strtoul?\n");
return -EINVAL;
}
if (node_num >= MANUAL_NODES) {
pr_err("ramster: bad node_num=%lu?\n", node_num);
return -EINVAL;
}
if (ramster_nodes_manual_up[node_num]) {
pr_err("ramster: node %d already up, ignoring\n",
(int)node_num);
} else {
ramster_nodes_manual_up[node_num] = true;
r2net_hb_node_up_manual((int)node_num);
}
return count;
}
static struct kobj_attribute ramster_manual_node_up_attr = {
.attr = { .name = "manual_node_up", .mode = 0644 },
.show = ramster_manual_node_up_show,
.store = ramster_manual_node_up_store,
};
static ssize_t ramster_remote_target_nodenum_show(struct kobject *kobj,
struct kobj_attribute *attr, char *buf)
{
if (ramster_remote_target_nodenum == -1UL)
return sprintf(buf, "unset\n");
else
return sprintf(buf, "%d\n", ramster_remote_target_nodenum);
}
static ssize_t ramster_remote_target_nodenum_store(struct kobject *kobj,
struct kobj_attribute *attr, const char *buf, size_t count)
{
int err;
unsigned long node_num;
err = kstrtoul(buf, 10, &node_num);
if (err) {
pr_err("ramster: bad strtoul?\n");
return -EINVAL;
} else if (node_num == -1UL) {
pr_err("ramster: disabling all remotification, "
"data may still reside on remote nodes however\n");
return -EINVAL;
} else if (node_num >= MANUAL_NODES) {
pr_err("ramster: bad node_num=%lu?\n", node_num);
return -EINVAL;
} else if (!ramster_nodes_manual_up[node_num]) {
pr_err("ramster: node %d not up, ignoring setting "
"of remotification target\n", (int)node_num);
} else if (r2net_remote_target_node_set((int)node_num) >= 0) {
pr_info("ramster: node %d set as remotification target\n",
(int)node_num);
ramster_remote_target_nodenum = (int)node_num;
} else {
pr_err("ramster: bad num to node node_num=%d?\n",
(int)node_num);
return -EINVAL;
}
return count;
}
static struct kobj_attribute ramster_remote_target_nodenum_attr = {
.attr = { .name = "remote_target_nodenum", .mode = 0644 },
.show = ramster_remote_target_nodenum_show,
.store = ramster_remote_target_nodenum_store,
};
#define RAMSTER_SYSFS_RO(_name) \
static ssize_t ramster_##_name##_show(struct kobject *kobj, \
struct kobj_attribute *attr, char *buf) \
{ \
return sprintf(buf, "%lu\n", ramster_##_name); \
} \
static struct kobj_attribute ramster_##_name##_attr = { \
.attr = { .name = __stringify(_name), .mode = 0444 }, \
.show = ramster_##_name##_show, \
}
#define RAMSTER_SYSFS_RW(_name) \
static ssize_t ramster_##_name##_show(struct kobject *kobj, \
struct kobj_attribute *attr, char *buf) \
{ \
return sprintf(buf, "%lu\n", ramster_##_name); \
} \
static ssize_t ramster_##_name##_store(struct kobject *kobj, \
struct kobj_attribute *attr, const char *buf, size_t count) \
{ \
int err; \
unsigned long enable; \
err = kstrtoul(buf, 10, &enable); \
if (err) \
return -EINVAL; \
ramster_##_name = enable; \
return count; \
} \
static struct kobj_attribute ramster_##_name##_attr = { \
.attr = { .name = __stringify(_name), .mode = 0644 }, \
.show = ramster_##_name##_show, \
.store = ramster_##_name##_store, \
}
#define RAMSTER_SYSFS_RO_ATOMIC(_name) \
static ssize_t ramster_##_name##_show(struct kobject *kobj, \
struct kobj_attribute *attr, char *buf) \
{ \
return sprintf(buf, "%d\n", atomic_read(&ramster_##_name)); \
} \
static struct kobj_attribute ramster_##_name##_attr = { \
.attr = { .name = __stringify(_name), .mode = 0444 }, \
.show = ramster_##_name##_show, \
}
RAMSTER_SYSFS_RO(interface_revision);
RAMSTER_SYSFS_RO_ATOMIC(remote_pers_pages);
RAMSTER_SYSFS_RW(pers_remotify_enable);
RAMSTER_SYSFS_RW(eph_remotify_enable);
static struct attribute *ramster_attrs[] = {
&ramster_interface_revision_attr.attr,
&ramster_remote_pers_pages_attr.attr,
&ramster_manual_node_up_attr.attr,
&ramster_remote_target_nodenum_attr.attr,
&ramster_pers_remotify_enable_attr.attr,
&ramster_eph_remotify_enable_attr.attr,
NULL,
};
static struct attribute_group ramster_attr_group = {
.attrs = ramster_attrs,
.name = "ramster",
};
/*
* frontswap selfshrinking
*/
/* In HZ, controls frequency of worker invocation. */
static unsigned int selfshrink_interval __read_mostly = 5;
/* Enable/disable with sysfs. */
static bool frontswap_selfshrinking __read_mostly;
static void selfshrink_process(struct work_struct *work);
static DECLARE_DELAYED_WORK(selfshrink_worker, selfshrink_process);
#ifndef CONFIG_RAMSTER_MODULE
/* Enable/disable with kernel boot option. */
static bool use_frontswap_selfshrink = true;
#endif
/*
* The default values for the following parameters were deemed reasonable
* by experimentation, may be workload-dependent, and can all be
* adjusted via sysfs.
*/
/* Control rate for frontswap shrinking. Higher hysteresis is slower. */
static unsigned int frontswap_hysteresis __read_mostly = 20;
/*
* Number of selfshrink worker invocations to wait before observing that
* frontswap selfshrinking should commence. Note that selfshrinking does
* not use a separate worker thread.
*/
static unsigned int frontswap_inertia __read_mostly = 3;
/* Countdown to next invocation of frontswap_shrink() */
static unsigned long frontswap_inertia_counter;
/*
* Invoked by the selfshrink worker thread, uses current number of pages
* in frontswap (frontswap_curr_pages()), previous status, and control
* values (hysteresis and inertia) to determine if frontswap should be
* shrunk and what the new frontswap size should be. Note that
* frontswap_shrink is essentially a partial swapoff that immediately
* transfers pages from the "swap device" (frontswap) back into kernel
* RAM; despite the name, frontswap "shrinking" is very different from
* the "shrinker" interface used by the kernel MM subsystem to reclaim
* memory.
*/
static void frontswap_selfshrink(void)
{
static unsigned long cur_frontswap_pages;
static unsigned long last_frontswap_pages;
static unsigned long tgt_frontswap_pages;
last_frontswap_pages = cur_frontswap_pages;
cur_frontswap_pages = frontswap_curr_pages();
if (!cur_frontswap_pages ||
(cur_frontswap_pages > last_frontswap_pages)) {
frontswap_inertia_counter = frontswap_inertia;
return;
}
if (frontswap_inertia_counter && --frontswap_inertia_counter)
return;
if (cur_frontswap_pages <= frontswap_hysteresis)
tgt_frontswap_pages = 0;
else
tgt_frontswap_pages = cur_frontswap_pages -
(cur_frontswap_pages / frontswap_hysteresis);
frontswap_shrink(tgt_frontswap_pages);
}
#ifndef CONFIG_RAMSTER_MODULE
static int __init ramster_nofrontswap_selfshrink_setup(char *s)
{
use_frontswap_selfshrink = false;
return 1;
}
__setup("noselfshrink", ramster_nofrontswap_selfshrink_setup);
#endif
static void selfshrink_process(struct work_struct *work)
{
if (frontswap_selfshrinking && frontswap_enabled) {
frontswap_selfshrink();
schedule_delayed_work(&selfshrink_worker,
selfshrink_interval * HZ);
}
}
void ramster_cpu_up(int cpu)
{
unsigned char *p1 = kzalloc(PAGE_SIZE, GFP_KERNEL | __GFP_REPEAT);
unsigned char *p2 = kzalloc(PAGE_SIZE, GFP_KERNEL | __GFP_REPEAT);
BUG_ON(!p1 || !p2);
per_cpu(ramster_remoteputmem1, cpu) = p1;
per_cpu(ramster_remoteputmem2, cpu) = p2;
}
EXPORT_SYMBOL_GPL(ramster_cpu_up);
void ramster_cpu_down(int cpu)
{
struct ramster_preload *kp;
kfree(per_cpu(ramster_remoteputmem1, cpu));
per_cpu(ramster_remoteputmem1, cpu) = NULL;
kfree(per_cpu(ramster_remoteputmem2, cpu));
per_cpu(ramster_remoteputmem2, cpu) = NULL;
kp = &per_cpu(ramster_preloads, cpu);
if (kp->flnode) {
kmem_cache_free(ramster_flnode_cache, kp->flnode);
kp->flnode = NULL;
}
}
EXPORT_SYMBOL_GPL(ramster_cpu_down);
void ramster_register_pamops(struct tmem_pamops *pamops)
{
pamops->free_obj = ramster_pampd_free_obj;
pamops->new_obj = ramster_pampd_new_obj;
pamops->replace_in_obj = ramster_pampd_replace_in_obj;
pamops->is_remote = ramster_pampd_is_remote;
pamops->repatriate = ramster_pampd_repatriate;
pamops->repatriate_preload = ramster_pampd_repatriate_preload;
}
EXPORT_SYMBOL_GPL(ramster_register_pamops);
void ramster_init(bool cleancache, bool frontswap,
bool frontswap_exclusive_gets,
bool frontswap_selfshrink)
{
int ret = 0;
if (cleancache)
use_cleancache = true;
if (frontswap)
use_frontswap = true;
if (frontswap_exclusive_gets)
use_frontswap_exclusive_gets = true;
ramster_debugfs_init();
ret = sysfs_create_group(mm_kobj, &ramster_attr_group);
if (ret)
pr_err("ramster: can't create sysfs for ramster\n");
(void)r2net_register_handlers();
#ifdef CONFIG_RAMSTER_MODULE
ret = r2nm_init();
if (ret)
pr_err("ramster: can't init r2net\n");
frontswap_selfshrinking = frontswap_selfshrink;
#else
frontswap_selfshrinking = use_frontswap_selfshrink;
#endif
INIT_LIST_HEAD(&ramster_rem_op_list);
ramster_flnode_cache = kmem_cache_create("ramster_flnode",
sizeof(struct flushlist_node), 0, 0, NULL);
if (frontswap_selfshrinking) {
pr_info("ramster: Initializing frontswap selfshrink driver.\n");
schedule_delayed_work(&selfshrink_worker,
selfshrink_interval * HZ);
}
ramster_remotify_init();
}
EXPORT_SYMBOL_GPL(ramster_init);
/*
* ramster.h
*
* Peer-to-peer transcendent memory
*
* Copyright (c) 2009-2012, Dan Magenheimer, Oracle Corp.
*/
#ifndef _RAMSTER_RAMSTER_H_
#define _RAMSTER_RAMSTER_H_
#include "../tmem.h"
enum ramster_remotify_op {
RAMSTER_REMOTIFY_FLUSH_PAGE,
RAMSTER_REMOTIFY_FLUSH_OBJ,
};
struct ramster_remotify_hdr {
enum ramster_remotify_op op;
struct list_head list;
};
struct flushlist_node {
struct ramster_remotify_hdr rem_op;
struct tmem_xhandle xh;
};
struct ramster_preload {
struct flushlist_node *flnode;
};
union remotify_list_node {
struct ramster_remotify_hdr rem_op;
struct {
struct ramster_remotify_hdr rem_op;
struct tmem_handle th;
} zbud_hdr;
struct flushlist_node flist;
};
/*
* format of remote pampd:
* bit 0 is reserved for zbud (in-page buddy selection)
* bit 1 == intransit
* bit 2 == is_remote... if this bit is set, then
* bit 3-10 == remotenode
* bit 11-23 == size
* bit 24-31 == cksum
*/
#define FAKE_PAMPD_INTRANSIT_BITS 1
#define FAKE_PAMPD_ISREMOTE_BITS 1
#define FAKE_PAMPD_REMOTENODE_BITS 8
#define FAKE_PAMPD_REMOTESIZE_BITS 13
#define FAKE_PAMPD_CHECKSUM_BITS 8
#define FAKE_PAMPD_INTRANSIT_SHIFT 1
#define FAKE_PAMPD_ISREMOTE_SHIFT (FAKE_PAMPD_INTRANSIT_SHIFT + \
FAKE_PAMPD_INTRANSIT_BITS)
#define FAKE_PAMPD_REMOTENODE_SHIFT (FAKE_PAMPD_ISREMOTE_SHIFT + \
FAKE_PAMPD_ISREMOTE_BITS)
#define FAKE_PAMPD_REMOTESIZE_SHIFT (FAKE_PAMPD_REMOTENODE_SHIFT + \
FAKE_PAMPD_REMOTENODE_BITS)
#define FAKE_PAMPD_CHECKSUM_SHIFT (FAKE_PAMPD_REMOTESIZE_SHIFT + \
FAKE_PAMPD_REMOTESIZE_BITS)
#define FAKE_PAMPD_MASK(x) ((1UL << (x)) - 1)
static inline void *pampd_make_remote(int remotenode, size_t size,
unsigned char cksum)
{
unsigned long fake_pampd = 0;
fake_pampd |= 1UL << FAKE_PAMPD_ISREMOTE_SHIFT;
fake_pampd |= ((unsigned long)remotenode &
FAKE_PAMPD_MASK(FAKE_PAMPD_REMOTENODE_BITS)) <<
FAKE_PAMPD_REMOTENODE_SHIFT;
fake_pampd |= ((unsigned long)size &
FAKE_PAMPD_MASK(FAKE_PAMPD_REMOTESIZE_BITS)) <<
FAKE_PAMPD_REMOTESIZE_SHIFT;
fake_pampd |= ((unsigned long)cksum &
FAKE_PAMPD_MASK(FAKE_PAMPD_CHECKSUM_BITS)) <<
FAKE_PAMPD_CHECKSUM_SHIFT;
return (void *)fake_pampd;
}
static inline unsigned int pampd_remote_node(void *pampd)
{
unsigned long fake_pampd = (unsigned long)pampd;
return (fake_pampd >> FAKE_PAMPD_REMOTENODE_SHIFT) &
FAKE_PAMPD_MASK(FAKE_PAMPD_REMOTENODE_BITS);
}
static inline unsigned int pampd_remote_size(void *pampd)
{
unsigned long fake_pampd = (unsigned long)pampd;
return (fake_pampd >> FAKE_PAMPD_REMOTESIZE_SHIFT) &
FAKE_PAMPD_MASK(FAKE_PAMPD_REMOTESIZE_BITS);
}
static inline unsigned char pampd_remote_cksum(void *pampd)
{
unsigned long fake_pampd = (unsigned long)pampd;
return (fake_pampd >> FAKE_PAMPD_CHECKSUM_SHIFT) &
FAKE_PAMPD_MASK(FAKE_PAMPD_CHECKSUM_BITS);
}
static inline bool pampd_is_remote(void *pampd)
{
unsigned long fake_pampd = (unsigned long)pampd;
return (fake_pampd >> FAKE_PAMPD_ISREMOTE_SHIFT) &
FAKE_PAMPD_MASK(FAKE_PAMPD_ISREMOTE_BITS);
}
static inline bool pampd_is_intransit(void *pampd)
{
unsigned long fake_pampd = (unsigned long)pampd;
return (fake_pampd >> FAKE_PAMPD_INTRANSIT_SHIFT) &
FAKE_PAMPD_MASK(FAKE_PAMPD_INTRANSIT_BITS);
}
/* note that it is a BUG for intransit to be set without isremote also set */
static inline void *pampd_mark_intransit(void *pampd)
{
unsigned long fake_pampd = (unsigned long)pampd;
fake_pampd |= 1UL << FAKE_PAMPD_ISREMOTE_SHIFT;
fake_pampd |= 1UL << FAKE_PAMPD_INTRANSIT_SHIFT;
return (void *)fake_pampd;
}
static inline void *pampd_mask_intransit_and_remote(void *marked_pampd)
{
unsigned long pampd = (unsigned long)marked_pampd;
pampd &= ~(1UL << FAKE_PAMPD_INTRANSIT_SHIFT);
pampd &= ~(1UL << FAKE_PAMPD_ISREMOTE_SHIFT);
return (void *)pampd;
}
extern int r2net_remote_async_get(struct tmem_xhandle *,
bool, int, size_t, uint8_t, void *extra);
extern int r2net_remote_put(struct tmem_xhandle *, char *, size_t,
bool, int *);
extern int r2net_remote_flush(struct tmem_xhandle *, int);
extern int r2net_remote_flush_object(struct tmem_xhandle *, int);
extern int r2net_register_handlers(void);
extern int r2net_remote_target_node_set(int);
extern int ramster_remotify_pageframe(bool);
extern void ramster_init(bool, bool, bool, bool);
extern void ramster_register_pamops(struct tmem_pamops *);
extern int ramster_localify(int, struct tmem_oid *oidp, uint32_t, char *,
unsigned int, void *);
extern void *ramster_pampd_free(void *, struct tmem_pool *, struct tmem_oid *,
uint32_t, bool);
extern void ramster_count_foreign_pages(bool, int);
extern int ramster_do_preload_flnode(struct tmem_pool *);
extern void ramster_cpu_up(int);
extern void ramster_cpu_down(int);
#endif /* _RAMSTER_RAMSTER_H */
/* -*- mode: c; c-basic-offset: 8; -*-
* vim: noexpandtab sw=8 ts=8 sts=0:
*
* ramster_nodemanager.h
*
* Header describing the interface between userspace and the kernel
* for the ramster_nodemanager module.
*
* Copyright (C) 2002, 2004, 2012 Oracle. All rights reserved.
*
* 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.
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*
*/
#ifndef _RAMSTER_NODEMANAGER_H
#define _RAMSTER_NODEMANAGER_H
#define R2NM_API_VERSION 5
#define R2NM_MAX_NODES 255
#define R2NM_INVALID_NODE_NUM 255
/* host name, group name, cluster name all 64 bytes */
#define R2NM_MAX_NAME_LEN 64 /* __NEW_UTS_LEN */
extern int r2nm_init(void);
#endif /* _RAMSTER_NODEMANAGER_H */
/* -*- mode: c; c-basic-offset: 8; -*-
*
* vim: noexpandtab sw=8 ts=8 sts=0:
*
* Copyright (C) 2004 Oracle. All rights reserved.
*
* 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.
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*
* ----
*
* Callers for this were originally written against a very simple synchronus
* API. This implementation reflects those simple callers. Some day I'm sure
* we'll need to move to a more robust posting/callback mechanism.
*
* Transmit calls pass in kernel virtual addresses and block copying this into
* the socket's tx buffers via a usual blocking sendmsg. They'll block waiting
* for a failed socket to timeout. TX callers can also pass in a poniter to an
* 'int' which gets filled with an errno off the wire in response to the
* message they send.
*
* Handlers for unsolicited messages are registered. Each socket has a page
* that incoming data is copied into. First the header, then the data.
* Handlers are called from only one thread with a reference to this per-socket
* page. This page is destroyed after the handler call, so it can't be
* referenced beyond the call. Handlers may block but are discouraged from
* doing so.
*
* Any framing errors (bad magic, large payload lengths) close a connection.
*
* Our sock_container holds the state we associate with a socket. It's current
* framing state is held there as well as the refcounting we do around when it
* is safe to tear down the socket. The socket is only finally torn down from
* the container when the container loses all of its references -- so as long
* as you hold a ref on the container you can trust that the socket is valid
* for use with kernel socket APIs.
*
* Connections are initiated between a pair of nodes when the node with the
* higher node number gets a heartbeat callback which indicates that the lower
* numbered node has started heartbeating. The lower numbered node is passive
* and only accepts the connection if the higher numbered node is heartbeating.
*/
#include <linux/kernel.h>
#include <linux/jiffies.h>
#include <linux/slab.h>
#include <linux/idr.h>
#include <linux/kref.h>
#include <linux/net.h>
#include <linux/export.h>
#include <linux/uaccess.h>
#include <net/tcp.h>
#include "heartbeat.h"
#include "tcp.h"
#include "nodemanager.h"
#define MLOG_MASK_PREFIX ML_TCP
#include "masklog.h"
#include "tcp_internal.h"
#define SC_NODEF_FMT "node %s (num %u) at %pI4:%u"
/*
* In the following two log macros, the whitespace after the ',' just
* before ##args is intentional. Otherwise, gcc 2.95 will eat the
* previous token if args expands to nothing.
*/
#define msglog(hdr, fmt, args...) do { \
typeof(hdr) __hdr = (hdr); \
mlog(ML_MSG, "[mag %u len %u typ %u stat %d sys_stat %d " \
"key %08x num %u] " fmt, \
be16_to_cpu(__hdr->magic), be16_to_cpu(__hdr->data_len), \
be16_to_cpu(__hdr->msg_type), be32_to_cpu(__hdr->status), \
be32_to_cpu(__hdr->sys_status), be32_to_cpu(__hdr->key), \
be32_to_cpu(__hdr->msg_num) , ##args); \
} while (0)
#define sclog(sc, fmt, args...) do { \
typeof(sc) __sc = (sc); \
mlog(ML_SOCKET, "[sc %p refs %d sock %p node %u page %p " \
"pg_off %zu] " fmt, __sc, \
atomic_read(&__sc->sc_kref.refcount), __sc->sc_sock, \
__sc->sc_node->nd_num, __sc->sc_page, __sc->sc_page_off , \
##args); \
} while (0)
static DEFINE_RWLOCK(r2net_handler_lock);
static struct rb_root r2net_handler_tree = RB_ROOT;
static struct r2net_node r2net_nodes[R2NM_MAX_NODES];
/* XXX someday we'll need better accounting */
static struct socket *r2net_listen_sock;
/*
* listen work is only queued by the listening socket callbacks on the
* r2net_wq. teardown detaches the callbacks before destroying the workqueue.
* quorum work is queued as sock containers are shutdown.. stop_listening
* tears down all the node's sock containers, preventing future shutdowns
* and queued quorum work, before canceling delayed quorum work and
* destroying the work queue.
*/
static struct workqueue_struct *r2net_wq;
static struct work_struct r2net_listen_work;
static struct r2hb_callback_func r2net_hb_up, r2net_hb_down;
#define R2NET_HB_PRI 0x1
static struct r2net_handshake *r2net_hand;
static struct r2net_msg *r2net_keep_req, *r2net_keep_resp;
static int r2net_sys_err_translations[R2NET_ERR_MAX] = {
[R2NET_ERR_NONE] = 0,
[R2NET_ERR_NO_HNDLR] = -ENOPROTOOPT,
[R2NET_ERR_OVERFLOW] = -EOVERFLOW,
[R2NET_ERR_DIED] = -EHOSTDOWN,};
/* can't quite avoid *all* internal declarations :/ */
static void r2net_sc_connect_completed(struct work_struct *work);
static void r2net_rx_until_empty(struct work_struct *work);
static void r2net_shutdown_sc(struct work_struct *work);
static void r2net_listen_data_ready(struct sock *sk, int bytes);
static void r2net_sc_send_keep_req(struct work_struct *work);
static void r2net_idle_timer(unsigned long data);
static void r2net_sc_postpone_idle(struct r2net_sock_container *sc);
static void r2net_sc_reset_idle_timer(struct r2net_sock_container *sc);
#ifdef CONFIG_DEBUG_FS
static void r2net_init_nst(struct r2net_send_tracking *nst, u32 msgtype,
u32 msgkey, struct task_struct *task, u8 node)
{
INIT_LIST_HEAD(&nst->st_net_debug_item);
nst->st_task = task;
nst->st_msg_type = msgtype;
nst->st_msg_key = msgkey;
nst->st_node = node;
}
static inline void r2net_set_nst_sock_time(struct r2net_send_tracking *nst)
{
nst->st_sock_time = ktime_get();
}
static inline void r2net_set_nst_send_time(struct r2net_send_tracking *nst)
{
nst->st_send_time = ktime_get();
}
static inline void r2net_set_nst_status_time(struct r2net_send_tracking *nst)
{
nst->st_status_time = ktime_get();
}
static inline void r2net_set_nst_sock_container(struct r2net_send_tracking *nst,
struct r2net_sock_container *sc)
{
nst->st_sc = sc;
}
static inline void r2net_set_nst_msg_id(struct r2net_send_tracking *nst,
u32 msg_id)
{
nst->st_id = msg_id;
}
static inline void r2net_set_sock_timer(struct r2net_sock_container *sc)
{
sc->sc_tv_timer = ktime_get();
}
static inline void r2net_set_data_ready_time(struct r2net_sock_container *sc)
{
sc->sc_tv_data_ready = ktime_get();
}
static inline void r2net_set_advance_start_time(struct r2net_sock_container *sc)
{
sc->sc_tv_advance_start = ktime_get();
}
static inline void r2net_set_advance_stop_time(struct r2net_sock_container *sc)
{
sc->sc_tv_advance_stop = ktime_get();
}
static inline void r2net_set_func_start_time(struct r2net_sock_container *sc)
{
sc->sc_tv_func_start = ktime_get();
}
static inline void r2net_set_func_stop_time(struct r2net_sock_container *sc)
{
sc->sc_tv_func_stop = ktime_get();
}
#else /* CONFIG_DEBUG_FS */
# define r2net_init_nst(a, b, c, d, e)
# define r2net_set_nst_sock_time(a)
# define r2net_set_nst_send_time(a)
# define r2net_set_nst_status_time(a)
# define r2net_set_nst_sock_container(a, b)
# define r2net_set_nst_msg_id(a, b)
# define r2net_set_sock_timer(a)
# define r2net_set_data_ready_time(a)
# define r2net_set_advance_start_time(a)
# define r2net_set_advance_stop_time(a)
# define r2net_set_func_start_time(a)
# define r2net_set_func_stop_time(a)
#endif /* CONFIG_DEBUG_FS */
#ifdef CONFIG_RAMSTER_FS_STATS
static ktime_t r2net_get_func_run_time(struct r2net_sock_container *sc)
{
return ktime_sub(sc->sc_tv_func_stop, sc->sc_tv_func_start);
}
static void r2net_update_send_stats(struct r2net_send_tracking *nst,
struct r2net_sock_container *sc)
{
sc->sc_tv_status_total = ktime_add(sc->sc_tv_status_total,
ktime_sub(ktime_get(),
nst->st_status_time));
sc->sc_tv_send_total = ktime_add(sc->sc_tv_send_total,
ktime_sub(nst->st_status_time,
nst->st_send_time));
sc->sc_tv_acquiry_total = ktime_add(sc->sc_tv_acquiry_total,
ktime_sub(nst->st_send_time,
nst->st_sock_time));
sc->sc_send_count++;
}
static void r2net_update_recv_stats(struct r2net_sock_container *sc)
{
sc->sc_tv_process_total = ktime_add(sc->sc_tv_process_total,
r2net_get_func_run_time(sc));
sc->sc_recv_count++;
}
#else
# define r2net_update_send_stats(a, b)
# define r2net_update_recv_stats(sc)
#endif /* CONFIG_RAMSTER_FS_STATS */
static inline int r2net_reconnect_delay(void)
{
return r2nm_single_cluster->cl_reconnect_delay_ms;
}
static inline int r2net_keepalive_delay(void)
{
return r2nm_single_cluster->cl_keepalive_delay_ms;
}
static inline int r2net_idle_timeout(void)
{
return r2nm_single_cluster->cl_idle_timeout_ms;
}
static inline int r2net_sys_err_to_errno(enum r2net_system_error err)
{
int trans;
BUG_ON(err >= R2NET_ERR_MAX);
trans = r2net_sys_err_translations[err];
/* Just in case we mess up the translation table above */
BUG_ON(err != R2NET_ERR_NONE && trans == 0);
return trans;
}
struct r2net_node *r2net_nn_from_num(u8 node_num)
{
BUG_ON(node_num >= ARRAY_SIZE(r2net_nodes));
return &r2net_nodes[node_num];
}
static u8 r2net_num_from_nn(struct r2net_node *nn)
{
BUG_ON(nn == NULL);
return nn - r2net_nodes;
}
/* ------------------------------------------------------------ */
static int r2net_prep_nsw(struct r2net_node *nn, struct r2net_status_wait *nsw)
{
int ret;
spin_lock(&nn->nn_lock);
ret = idr_alloc(&nn->nn_status_idr, nsw, 0, 0, GFP_ATOMIC);
if (ret >= 0) {
nsw->ns_id = ret;
list_add_tail(&nsw->ns_node_item, &nn->nn_status_list);
}
spin_unlock(&nn->nn_lock);
if (ret >= 0) {
init_waitqueue_head(&nsw->ns_wq);
nsw->ns_sys_status = R2NET_ERR_NONE;
nsw->ns_status = 0;
return 0;
}
return ret;
}
static void r2net_complete_nsw_locked(struct r2net_node *nn,
struct r2net_status_wait *nsw,
enum r2net_system_error sys_status,
s32 status)
{
assert_spin_locked(&nn->nn_lock);
if (!list_empty(&nsw->ns_node_item)) {
list_del_init(&nsw->ns_node_item);
nsw->ns_sys_status = sys_status;
nsw->ns_status = status;
idr_remove(&nn->nn_status_idr, nsw->ns_id);
wake_up(&nsw->ns_wq);
}
}
static void r2net_complete_nsw(struct r2net_node *nn,
struct r2net_status_wait *nsw,
u64 id, enum r2net_system_error sys_status,
s32 status)
{
spin_lock(&nn->nn_lock);
if (nsw == NULL) {
if (id > INT_MAX)
goto out;
nsw = idr_find(&nn->nn_status_idr, id);
if (nsw == NULL)
goto out;
}
r2net_complete_nsw_locked(nn, nsw, sys_status, status);
out:
spin_unlock(&nn->nn_lock);
return;
}
static void r2net_complete_nodes_nsw(struct r2net_node *nn)
{
struct r2net_status_wait *nsw, *tmp;
unsigned int num_kills = 0;
assert_spin_locked(&nn->nn_lock);
list_for_each_entry_safe(nsw, tmp, &nn->nn_status_list, ns_node_item) {
r2net_complete_nsw_locked(nn, nsw, R2NET_ERR_DIED, 0);
num_kills++;
}
mlog(0, "completed %d messages for node %u\n", num_kills,
r2net_num_from_nn(nn));
}
static int r2net_nsw_completed(struct r2net_node *nn,
struct r2net_status_wait *nsw)
{
int completed;
spin_lock(&nn->nn_lock);
completed = list_empty(&nsw->ns_node_item);
spin_unlock(&nn->nn_lock);
return completed;
}
/* ------------------------------------------------------------ */
static void sc_kref_release(struct kref *kref)
{
struct r2net_sock_container *sc = container_of(kref,
struct r2net_sock_container, sc_kref);
BUG_ON(timer_pending(&sc->sc_idle_timeout));
sclog(sc, "releasing\n");
if (sc->sc_sock) {
sock_release(sc->sc_sock);
sc->sc_sock = NULL;
}
r2nm_undepend_item(&sc->sc_node->nd_item);
r2nm_node_put(sc->sc_node);
sc->sc_node = NULL;
r2net_debug_del_sc(sc);
if (sc->sc_page)
__free_page(sc->sc_page);
kfree(sc);
}
static void sc_put(struct r2net_sock_container *sc)
{
sclog(sc, "put\n");
kref_put(&sc->sc_kref, sc_kref_release);
}
static void sc_get(struct r2net_sock_container *sc)
{
sclog(sc, "get\n");
kref_get(&sc->sc_kref);
}
static struct r2net_sock_container *sc_alloc(struct r2nm_node *node)
{
struct r2net_sock_container *sc, *ret = NULL;
struct page *page = NULL;
int status = 0;
page = alloc_page(GFP_NOFS);
sc = kzalloc(sizeof(*sc), GFP_NOFS);
if (sc == NULL || page == NULL)
goto out;
kref_init(&sc->sc_kref);
r2nm_node_get(node);
sc->sc_node = node;
/* pin the node item of the remote node */
status = r2nm_depend_item(&node->nd_item);
if (status) {
mlog_errno(status);
r2nm_node_put(node);
goto out;
}
INIT_WORK(&sc->sc_connect_work, r2net_sc_connect_completed);
INIT_WORK(&sc->sc_rx_work, r2net_rx_until_empty);
INIT_WORK(&sc->sc_shutdown_work, r2net_shutdown_sc);
INIT_DELAYED_WORK(&sc->sc_keepalive_work, r2net_sc_send_keep_req);
init_timer(&sc->sc_idle_timeout);
sc->sc_idle_timeout.function = r2net_idle_timer;
sc->sc_idle_timeout.data = (unsigned long)sc;
sclog(sc, "alloced\n");
ret = sc;
sc->sc_page = page;
r2net_debug_add_sc(sc);
sc = NULL;
page = NULL;
out:
if (page)
__free_page(page);
kfree(sc);
return ret;
}
/* ------------------------------------------------------------ */
static void r2net_sc_queue_work(struct r2net_sock_container *sc,
struct work_struct *work)
{
sc_get(sc);
if (!queue_work(r2net_wq, work))
sc_put(sc);
}
static void r2net_sc_queue_delayed_work(struct r2net_sock_container *sc,
struct delayed_work *work,
int delay)
{
sc_get(sc);
if (!queue_delayed_work(r2net_wq, work, delay))
sc_put(sc);
}
static void r2net_sc_cancel_delayed_work(struct r2net_sock_container *sc,
struct delayed_work *work)
{
if (cancel_delayed_work(work))
sc_put(sc);
}
static atomic_t r2net_connected_peers = ATOMIC_INIT(0);
int r2net_num_connected_peers(void)
{
return atomic_read(&r2net_connected_peers);
}
static void r2net_set_nn_state(struct r2net_node *nn,
struct r2net_sock_container *sc,
unsigned valid, int err)
{
int was_valid = nn->nn_sc_valid;
int was_err = nn->nn_persistent_error;
struct r2net_sock_container *old_sc = nn->nn_sc;
assert_spin_locked(&nn->nn_lock);
if (old_sc && !sc)
atomic_dec(&r2net_connected_peers);
else if (!old_sc && sc)
atomic_inc(&r2net_connected_peers);
/* the node num comparison and single connect/accept path should stop
* an non-null sc from being overwritten with another */
BUG_ON(sc && nn->nn_sc && nn->nn_sc != sc);
mlog_bug_on_msg(err && valid, "err %d valid %u\n", err, valid);
mlog_bug_on_msg(valid && !sc, "valid %u sc %p\n", valid, sc);
if (was_valid && !valid && err == 0)
err = -ENOTCONN;
mlog(ML_CONN, "node %u sc: %p -> %p, valid %u -> %u, err %d -> %d\n",
r2net_num_from_nn(nn), nn->nn_sc, sc, nn->nn_sc_valid, valid,
nn->nn_persistent_error, err);
nn->nn_sc = sc;
nn->nn_sc_valid = valid ? 1 : 0;
nn->nn_persistent_error = err;
/* mirrors r2net_tx_can_proceed() */
if (nn->nn_persistent_error || nn->nn_sc_valid)
wake_up(&nn->nn_sc_wq);
if (!was_err && nn->nn_persistent_error) {
queue_delayed_work(r2net_wq, &nn->nn_still_up,
msecs_to_jiffies(R2NET_QUORUM_DELAY_MS));
}
if (was_valid && !valid) {
pr_notice("ramster: No longer connected to " SC_NODEF_FMT "\n",
old_sc->sc_node->nd_name, old_sc->sc_node->nd_num,
&old_sc->sc_node->nd_ipv4_address,
ntohs(old_sc->sc_node->nd_ipv4_port));
r2net_complete_nodes_nsw(nn);
}
if (!was_valid && valid) {
cancel_delayed_work(&nn->nn_connect_expired);
pr_notice("ramster: %s " SC_NODEF_FMT "\n",
r2nm_this_node() > sc->sc_node->nd_num ?
"Connected to" : "Accepted connection from",
sc->sc_node->nd_name, sc->sc_node->nd_num,
&sc->sc_node->nd_ipv4_address,
ntohs(sc->sc_node->nd_ipv4_port));
}
/* trigger the connecting worker func as long as we're not valid,
* it will back off if it shouldn't connect. This can be called
* from node config teardown and so needs to be careful about
* the work queue actually being up. */
if (!valid && r2net_wq) {
unsigned long delay;
/* delay if we're within a RECONNECT_DELAY of the
* last attempt */
delay = (nn->nn_last_connect_attempt +
msecs_to_jiffies(r2net_reconnect_delay()))
- jiffies;
if (delay > msecs_to_jiffies(r2net_reconnect_delay()))
delay = 0;
mlog(ML_CONN, "queueing conn attempt in %lu jiffies\n", delay);
queue_delayed_work(r2net_wq, &nn->nn_connect_work, delay);
/*
* Delay the expired work after idle timeout.
*
* We might have lots of failed connection attempts that run
* through here but we only cancel the connect_expired work when
* a connection attempt succeeds. So only the first enqueue of
* the connect_expired work will do anything. The rest will see
* that it's already queued and do nothing.
*/
delay += msecs_to_jiffies(r2net_idle_timeout());
queue_delayed_work(r2net_wq, &nn->nn_connect_expired, delay);
}
/* keep track of the nn's sc ref for the caller */
if ((old_sc == NULL) && sc)
sc_get(sc);
if (old_sc && (old_sc != sc)) {
r2net_sc_queue_work(old_sc, &old_sc->sc_shutdown_work);
sc_put(old_sc);
}
}
/* see r2net_register_callbacks() */
static void r2net_data_ready(struct sock *sk, int bytes)
{
void (*ready)(struct sock *sk, int bytes);
read_lock(&sk->sk_callback_lock);
if (sk->sk_user_data) {
struct r2net_sock_container *sc = sk->sk_user_data;
sclog(sc, "data_ready hit\n");
r2net_set_data_ready_time(sc);
r2net_sc_queue_work(sc, &sc->sc_rx_work);
ready = sc->sc_data_ready;
} else {
ready = sk->sk_data_ready;
}
read_unlock(&sk->sk_callback_lock);
ready(sk, bytes);
}
/* see r2net_register_callbacks() */
static void r2net_state_change(struct sock *sk)
{
void (*state_change)(struct sock *sk);
struct r2net_sock_container *sc;
read_lock(&sk->sk_callback_lock);
sc = sk->sk_user_data;
if (sc == NULL) {
state_change = sk->sk_state_change;
goto out;
}
sclog(sc, "state_change to %d\n", sk->sk_state);
state_change = sc->sc_state_change;
switch (sk->sk_state) {
/* ignore connecting sockets as they make progress */
case TCP_SYN_SENT:
case TCP_SYN_RECV:
break;
case TCP_ESTABLISHED:
r2net_sc_queue_work(sc, &sc->sc_connect_work);
break;
default:
pr_info("ramster: Connection to "
SC_NODEF_FMT " shutdown, state %d\n",
sc->sc_node->nd_name, sc->sc_node->nd_num,
&sc->sc_node->nd_ipv4_address,
ntohs(sc->sc_node->nd_ipv4_port), sk->sk_state);
r2net_sc_queue_work(sc, &sc->sc_shutdown_work);
break;
}
out:
read_unlock(&sk->sk_callback_lock);
state_change(sk);
}
/*
* we register callbacks so we can queue work on events before calling
* the original callbacks. our callbacks are careful to test user_data
* to discover when they've reaced with r2net_unregister_callbacks().
*/
static void r2net_register_callbacks(struct sock *sk,
struct r2net_sock_container *sc)
{
write_lock_bh(&sk->sk_callback_lock);
/* accepted sockets inherit the old listen socket data ready */
if (sk->sk_data_ready == r2net_listen_data_ready) {
sk->sk_data_ready = sk->sk_user_data;
sk->sk_user_data = NULL;
}
BUG_ON(sk->sk_user_data != NULL);
sk->sk_user_data = sc;
sc_get(sc);
sc->sc_data_ready = sk->sk_data_ready;
sc->sc_state_change = sk->sk_state_change;
sk->sk_data_ready = r2net_data_ready;
sk->sk_state_change = r2net_state_change;
mutex_init(&sc->sc_send_lock);
write_unlock_bh(&sk->sk_callback_lock);
}
static int r2net_unregister_callbacks(struct sock *sk,
struct r2net_sock_container *sc)
{
int ret = 0;
write_lock_bh(&sk->sk_callback_lock);
if (sk->sk_user_data == sc) {
ret = 1;
sk->sk_user_data = NULL;
sk->sk_data_ready = sc->sc_data_ready;
sk->sk_state_change = sc->sc_state_change;
}
write_unlock_bh(&sk->sk_callback_lock);
return ret;
}
/*
* this is a little helper that is called by callers who have seen a problem
* with an sc and want to detach it from the nn if someone already hasn't beat
* them to it. if an error is given then the shutdown will be persistent
* and pending transmits will be canceled.
*/
static void r2net_ensure_shutdown(struct r2net_node *nn,
struct r2net_sock_container *sc,
int err)
{
spin_lock(&nn->nn_lock);
if (nn->nn_sc == sc)
r2net_set_nn_state(nn, NULL, 0, err);
spin_unlock(&nn->nn_lock);
}
/*
* This work queue function performs the blocking parts of socket shutdown. A
* few paths lead here. set_nn_state will trigger this callback if it sees an
* sc detached from the nn. state_change will also trigger this callback
* directly when it sees errors. In that case we need to call set_nn_state
* ourselves as state_change couldn't get the nn_lock and call set_nn_state
* itself.
*/
static void r2net_shutdown_sc(struct work_struct *work)
{
struct r2net_sock_container *sc =
container_of(work, struct r2net_sock_container,
sc_shutdown_work);
struct r2net_node *nn = r2net_nn_from_num(sc->sc_node->nd_num);
sclog(sc, "shutting down\n");
/* drop the callbacks ref and call shutdown only once */
if (r2net_unregister_callbacks(sc->sc_sock->sk, sc)) {
/* we shouldn't flush as we're in the thread, the
* races with pending sc work structs are harmless */
del_timer_sync(&sc->sc_idle_timeout);
r2net_sc_cancel_delayed_work(sc, &sc->sc_keepalive_work);
sc_put(sc);
kernel_sock_shutdown(sc->sc_sock, SHUT_RDWR);
}
/* not fatal so failed connects before the other guy has our
* heartbeat can be retried */
r2net_ensure_shutdown(nn, sc, 0);
sc_put(sc);
}
/* ------------------------------------------------------------ */
static int r2net_handler_cmp(struct r2net_msg_handler *nmh, u32 msg_type,
u32 key)
{
int ret = memcmp(&nmh->nh_key, &key, sizeof(key));
if (ret == 0)
ret = memcmp(&nmh->nh_msg_type, &msg_type, sizeof(msg_type));
return ret;
}
static struct r2net_msg_handler *
r2net_handler_tree_lookup(u32 msg_type, u32 key, struct rb_node ***ret_p,
struct rb_node **ret_parent)
{
struct rb_node **p = &r2net_handler_tree.rb_node;
struct rb_node *parent = NULL;
struct r2net_msg_handler *nmh, *ret = NULL;
int cmp;
while (*p) {
parent = *p;
nmh = rb_entry(parent, struct r2net_msg_handler, nh_node);
cmp = r2net_handler_cmp(nmh, msg_type, key);
if (cmp < 0)
p = &(*p)->rb_left;
else if (cmp > 0)
p = &(*p)->rb_right;
else {
ret = nmh;
break;
}
}
if (ret_p != NULL)
*ret_p = p;
if (ret_parent != NULL)
*ret_parent = parent;
return ret;
}
static void r2net_handler_kref_release(struct kref *kref)
{
struct r2net_msg_handler *nmh;
nmh = container_of(kref, struct r2net_msg_handler, nh_kref);
kfree(nmh);
}
static void r2net_handler_put(struct r2net_msg_handler *nmh)
{
kref_put(&nmh->nh_kref, r2net_handler_kref_release);
}
/* max_len is protection for the handler func. incoming messages won't
* be given to the handler if their payload is longer than the max. */
int r2net_register_handler(u32 msg_type, u32 key, u32 max_len,
r2net_msg_handler_func *func, void *data,
r2net_post_msg_handler_func *post_func,
struct list_head *unreg_list)
{
struct r2net_msg_handler *nmh = NULL;
struct rb_node **p, *parent;
int ret = 0;
if (max_len > R2NET_MAX_PAYLOAD_BYTES) {
mlog(0, "max_len for message handler out of range: %u\n",
max_len);
ret = -EINVAL;
goto out;
}
if (!msg_type) {
mlog(0, "no message type provided: %u, %p\n", msg_type, func);
ret = -EINVAL;
goto out;
}
if (!func) {
mlog(0, "no message handler provided: %u, %p\n",
msg_type, func);
ret = -EINVAL;
goto out;
}
nmh = kzalloc(sizeof(struct r2net_msg_handler), GFP_NOFS);
if (nmh == NULL) {
ret = -ENOMEM;
goto out;
}
nmh->nh_func = func;
nmh->nh_func_data = data;
nmh->nh_post_func = post_func;
nmh->nh_msg_type = msg_type;
nmh->nh_max_len = max_len;
nmh->nh_key = key;
/* the tree and list get this ref.. they're both removed in
* unregister when this ref is dropped */
kref_init(&nmh->nh_kref);
INIT_LIST_HEAD(&nmh->nh_unregister_item);
write_lock(&r2net_handler_lock);
if (r2net_handler_tree_lookup(msg_type, key, &p, &parent))
ret = -EEXIST;
else {
rb_link_node(&nmh->nh_node, parent, p);
rb_insert_color(&nmh->nh_node, &r2net_handler_tree);
list_add_tail(&nmh->nh_unregister_item, unreg_list);
mlog(ML_TCP, "registered handler func %p type %u key %08x\n",
func, msg_type, key);
/* we've had some trouble with handlers seemingly vanishing. */
mlog_bug_on_msg(r2net_handler_tree_lookup(msg_type, key, &p,
&parent) == NULL,
"couldn't find handler we *just* registered "
"for type %u key %08x\n", msg_type, key);
}
write_unlock(&r2net_handler_lock);
if (ret)
goto out;
out:
if (ret)
kfree(nmh);
return ret;
}
EXPORT_SYMBOL_GPL(r2net_register_handler);
void r2net_unregister_handler_list(struct list_head *list)
{
struct r2net_msg_handler *nmh, *n;
write_lock(&r2net_handler_lock);
list_for_each_entry_safe(nmh, n, list, nh_unregister_item) {
mlog(ML_TCP, "unregistering handler func %p type %u key %08x\n",
nmh->nh_func, nmh->nh_msg_type, nmh->nh_key);
rb_erase(&nmh->nh_node, &r2net_handler_tree);
list_del_init(&nmh->nh_unregister_item);
kref_put(&nmh->nh_kref, r2net_handler_kref_release);
}
write_unlock(&r2net_handler_lock);
}
EXPORT_SYMBOL_GPL(r2net_unregister_handler_list);
static struct r2net_msg_handler *r2net_handler_get(u32 msg_type, u32 key)
{
struct r2net_msg_handler *nmh;
read_lock(&r2net_handler_lock);
nmh = r2net_handler_tree_lookup(msg_type, key, NULL, NULL);
if (nmh)
kref_get(&nmh->nh_kref);
read_unlock(&r2net_handler_lock);
return nmh;
}
/* ------------------------------------------------------------ */
static int r2net_recv_tcp_msg(struct socket *sock, void *data, size_t len)
{
int ret;
mm_segment_t oldfs;
struct kvec vec = {
.iov_len = len,
.iov_base = data,
};
struct msghdr msg = {
.msg_iovlen = 1,
.msg_iov = (struct iovec *)&vec,
.msg_flags = MSG_DONTWAIT,
};
oldfs = get_fs();
set_fs(get_ds());
ret = sock_recvmsg(sock, &msg, len, msg.msg_flags);
set_fs(oldfs);
return ret;
}
static int r2net_send_tcp_msg(struct socket *sock, struct kvec *vec,
size_t veclen, size_t total)
{
int ret;
mm_segment_t oldfs;
struct msghdr msg = {
.msg_iov = (struct iovec *)vec,
.msg_iovlen = veclen,
};
if (sock == NULL) {
ret = -EINVAL;
goto out;
}
oldfs = get_fs();
set_fs(get_ds());
ret = sock_sendmsg(sock, &msg, total);
set_fs(oldfs);
if (ret != total) {
mlog(ML_ERROR, "sendmsg returned %d instead of %zu\n", ret,
total);
if (ret >= 0)
ret = -EPIPE; /* should be smarter, I bet */
goto out;
}
ret = 0;
out:
if (ret < 0)
mlog(0, "returning error: %d\n", ret);
return ret;
}
static void r2net_sendpage(struct r2net_sock_container *sc,
void *kmalloced_virt,
size_t size)
{
struct r2net_node *nn = r2net_nn_from_num(sc->sc_node->nd_num);
ssize_t ret;
while (1) {
mutex_lock(&sc->sc_send_lock);
ret = sc->sc_sock->ops->sendpage(sc->sc_sock,
virt_to_page(kmalloced_virt),
(long)kmalloced_virt & ~PAGE_MASK,
size, MSG_DONTWAIT);
mutex_unlock(&sc->sc_send_lock);
if (ret == size)
break;
if (ret == (ssize_t)-EAGAIN) {
mlog(0, "sendpage of size %zu to " SC_NODEF_FMT
" returned EAGAIN\n", size, sc->sc_node->nd_name,
sc->sc_node->nd_num,
&sc->sc_node->nd_ipv4_address,
ntohs(sc->sc_node->nd_ipv4_port));
cond_resched();
continue;
}
mlog(ML_ERROR, "sendpage of size %zu to " SC_NODEF_FMT
" failed with %zd\n", size, sc->sc_node->nd_name,
sc->sc_node->nd_num, &sc->sc_node->nd_ipv4_address,
ntohs(sc->sc_node->nd_ipv4_port), ret);
r2net_ensure_shutdown(nn, sc, 0);
break;
}
}
static void r2net_init_msg(struct r2net_msg *msg, u16 data_len,
u16 msg_type, u32 key)
{
memset(msg, 0, sizeof(struct r2net_msg));
msg->magic = cpu_to_be16(R2NET_MSG_MAGIC);
msg->data_len = cpu_to_be16(data_len);
msg->msg_type = cpu_to_be16(msg_type);
msg->sys_status = cpu_to_be32(R2NET_ERR_NONE);
msg->status = 0;
msg->key = cpu_to_be32(key);
}
static int r2net_tx_can_proceed(struct r2net_node *nn,
struct r2net_sock_container **sc_ret,
int *error)
{
int ret = 0;
spin_lock(&nn->nn_lock);
if (nn->nn_persistent_error) {
ret = 1;
*sc_ret = NULL;
*error = nn->nn_persistent_error;
} else if (nn->nn_sc_valid) {
kref_get(&nn->nn_sc->sc_kref);
ret = 1;
*sc_ret = nn->nn_sc;
*error = 0;
}
spin_unlock(&nn->nn_lock);
return ret;
}
/* Get a map of all nodes to which this node is currently connected to */
void r2net_fill_node_map(unsigned long *map, unsigned bytes)
{
struct r2net_sock_container *sc;
int node, ret;
BUG_ON(bytes < (BITS_TO_LONGS(R2NM_MAX_NODES) * sizeof(unsigned long)));
memset(map, 0, bytes);
for (node = 0; node < R2NM_MAX_NODES; ++node) {
r2net_tx_can_proceed(r2net_nn_from_num(node), &sc, &ret);
if (!ret) {
set_bit(node, map);
sc_put(sc);
}
}
}
EXPORT_SYMBOL_GPL(r2net_fill_node_map);
int r2net_send_message_vec(u32 msg_type, u32 key, struct kvec *caller_vec,
size_t caller_veclen, u8 target_node, int *status)
{
int ret = 0;
struct r2net_msg *msg = NULL;
size_t veclen, caller_bytes = 0;
struct kvec *vec = NULL;
struct r2net_sock_container *sc = NULL;
struct r2net_node *nn = r2net_nn_from_num(target_node);
struct r2net_status_wait nsw = {
.ns_node_item = LIST_HEAD_INIT(nsw.ns_node_item),
};
struct r2net_send_tracking nst;
/* this may be a general bug fix */
init_waitqueue_head(&nsw.ns_wq);
r2net_init_nst(&nst, msg_type, key, current, target_node);
if (r2net_wq == NULL) {
mlog(0, "attempt to tx without r2netd running\n");
ret = -ESRCH;
goto out;
}
if (caller_veclen == 0) {
mlog(0, "bad kvec array length\n");
ret = -EINVAL;
goto out;
}
caller_bytes = iov_length((struct iovec *)caller_vec, caller_veclen);
if (caller_bytes > R2NET_MAX_PAYLOAD_BYTES) {
mlog(0, "total payload len %zu too large\n", caller_bytes);
ret = -EINVAL;
goto out;
}
if (target_node == r2nm_this_node()) {
ret = -ELOOP;
goto out;
}
r2net_debug_add_nst(&nst);
r2net_set_nst_sock_time(&nst);
wait_event(nn->nn_sc_wq, r2net_tx_can_proceed(nn, &sc, &ret));
if (ret)
goto out;
r2net_set_nst_sock_container(&nst, sc);
veclen = caller_veclen + 1;
vec = kmalloc(sizeof(struct kvec) * veclen, GFP_ATOMIC);
if (vec == NULL) {
mlog(0, "failed to %zu element kvec!\n", veclen);
ret = -ENOMEM;
goto out;
}
msg = kmalloc(sizeof(struct r2net_msg), GFP_ATOMIC);
if (!msg) {
mlog(0, "failed to allocate a r2net_msg!\n");
ret = -ENOMEM;
goto out;
}
r2net_init_msg(msg, caller_bytes, msg_type, key);
vec[0].iov_len = sizeof(struct r2net_msg);
vec[0].iov_base = msg;
memcpy(&vec[1], caller_vec, caller_veclen * sizeof(struct kvec));
ret = r2net_prep_nsw(nn, &nsw);
if (ret)
goto out;
msg->msg_num = cpu_to_be32(nsw.ns_id);
r2net_set_nst_msg_id(&nst, nsw.ns_id);
r2net_set_nst_send_time(&nst);
/* finally, convert the message header to network byte-order
* and send */
mutex_lock(&sc->sc_send_lock);
ret = r2net_send_tcp_msg(sc->sc_sock, vec, veclen,
sizeof(struct r2net_msg) + caller_bytes);
mutex_unlock(&sc->sc_send_lock);
msglog(msg, "sending returned %d\n", ret);
if (ret < 0) {
mlog(0, "error returned from r2net_send_tcp_msg=%d\n", ret);
goto out;
}
/* wait on other node's handler */
r2net_set_nst_status_time(&nst);
wait_event(nsw.ns_wq, r2net_nsw_completed(nn, &nsw) ||
nn->nn_persistent_error || !nn->nn_sc_valid);
r2net_update_send_stats(&nst, sc);
/* Note that we avoid overwriting the callers status return
* variable if a system error was reported on the other
* side. Callers beware. */
ret = r2net_sys_err_to_errno(nsw.ns_sys_status);
if (status && !ret)
*status = nsw.ns_status;
mlog(0, "woken, returning system status %d, user status %d\n",
ret, nsw.ns_status);
out:
r2net_debug_del_nst(&nst); /* must be before dropping sc and node */
if (sc)
sc_put(sc);
kfree(vec);
kfree(msg);
r2net_complete_nsw(nn, &nsw, 0, 0, 0);
return ret;
}
EXPORT_SYMBOL_GPL(r2net_send_message_vec);
int r2net_send_message(u32 msg_type, u32 key, void *data, u32 len,
u8 target_node, int *status)
{
struct kvec vec = {
.iov_base = data,
.iov_len = len,
};
return r2net_send_message_vec(msg_type, key, &vec, 1,
target_node, status);
}
EXPORT_SYMBOL_GPL(r2net_send_message);
static int r2net_send_status_magic(struct socket *sock, struct r2net_msg *hdr,
enum r2net_system_error syserr, int err)
{
struct kvec vec = {
.iov_base = hdr,
.iov_len = sizeof(struct r2net_msg),
};
BUG_ON(syserr >= R2NET_ERR_MAX);
/* leave other fields intact from the incoming message, msg_num
* in particular */
hdr->sys_status = cpu_to_be32(syserr);
hdr->status = cpu_to_be32(err);
/* twiddle the magic */
hdr->magic = cpu_to_be16(R2NET_MSG_STATUS_MAGIC);
hdr->data_len = 0;
msglog(hdr, "about to send status magic %d\n", err);
/* hdr has been in host byteorder this whole time */
return r2net_send_tcp_msg(sock, &vec, 1, sizeof(struct r2net_msg));
}
/*
* "data magic" is a long version of "status magic" where the message
* payload actually contains data to be passed in reply to certain messages
*/
static int r2net_send_data_magic(struct r2net_sock_container *sc,
struct r2net_msg *hdr,
void *data, size_t data_len,
enum r2net_system_error syserr, int err)
{
struct kvec vec[2];
int ret;
vec[0].iov_base = hdr;
vec[0].iov_len = sizeof(struct r2net_msg);
vec[1].iov_base = data;
vec[1].iov_len = data_len;
BUG_ON(syserr >= R2NET_ERR_MAX);
/* leave other fields intact from the incoming message, msg_num
* in particular */
hdr->sys_status = cpu_to_be32(syserr);
hdr->status = cpu_to_be32(err);
hdr->magic = cpu_to_be16(R2NET_MSG_DATA_MAGIC); /* twiddle magic */
hdr->data_len = cpu_to_be16(data_len);
msglog(hdr, "about to send data magic %d\n", err);
/* hdr has been in host byteorder this whole time */
ret = r2net_send_tcp_msg(sc->sc_sock, vec, 2,
sizeof(struct r2net_msg) + data_len);
return ret;
}
/*
* called by a message handler to convert an otherwise normal reply
* message into a "data magic" message
*/
void r2net_force_data_magic(struct r2net_msg *hdr, u16 msgtype, u32 msgkey)
{
hdr->magic = cpu_to_be16(R2NET_MSG_DATA_MAGIC);
hdr->msg_type = cpu_to_be16(msgtype);
hdr->key = cpu_to_be32(msgkey);
}
/* this returns -errno if the header was unknown or too large, etc.
* after this is called the buffer us reused for the next message */
static int r2net_process_message(struct r2net_sock_container *sc,
struct r2net_msg *hdr)
{
struct r2net_node *nn = r2net_nn_from_num(sc->sc_node->nd_num);
int ret = 0, handler_status;
enum r2net_system_error syserr;
struct r2net_msg_handler *nmh = NULL;
void *ret_data = NULL;
int data_magic = 0;
msglog(hdr, "processing message\n");
r2net_sc_postpone_idle(sc);
switch (be16_to_cpu(hdr->magic)) {
case R2NET_MSG_STATUS_MAGIC:
/* special type for returning message status */
r2net_complete_nsw(nn, NULL, be32_to_cpu(hdr->msg_num),
be32_to_cpu(hdr->sys_status),
be32_to_cpu(hdr->status));
goto out;
case R2NET_MSG_KEEP_REQ_MAGIC:
r2net_sendpage(sc, r2net_keep_resp, sizeof(*r2net_keep_resp));
goto out;
case R2NET_MSG_KEEP_RESP_MAGIC:
goto out;
case R2NET_MSG_MAGIC:
break;
case R2NET_MSG_DATA_MAGIC:
/*
* unlike a normal status magic, a data magic DOES
* (MUST) have a handler, so the control flow is
* a little funky here as a result
*/
data_magic = 1;
break;
default:
msglog(hdr, "bad magic\n");
ret = -EINVAL;
goto out;
break;
}
/* find a handler for it */
handler_status = 0;
nmh = r2net_handler_get(be16_to_cpu(hdr->msg_type),
be32_to_cpu(hdr->key));
if (!nmh) {
mlog(ML_TCP, "couldn't find handler for type %u key %08x\n",
be16_to_cpu(hdr->msg_type), be32_to_cpu(hdr->key));
syserr = R2NET_ERR_NO_HNDLR;
goto out_respond;
}
syserr = R2NET_ERR_NONE;
if (be16_to_cpu(hdr->data_len) > nmh->nh_max_len)
syserr = R2NET_ERR_OVERFLOW;
if (syserr != R2NET_ERR_NONE) {
pr_err("ramster_r2net, message length problem\n");
goto out_respond;
}
r2net_set_func_start_time(sc);
sc->sc_msg_key = be32_to_cpu(hdr->key);
sc->sc_msg_type = be16_to_cpu(hdr->msg_type);
handler_status = (nmh->nh_func)(hdr, sizeof(struct r2net_msg) +
be16_to_cpu(hdr->data_len),
nmh->nh_func_data, &ret_data);
if (data_magic) {
/*
* handler handled data sent in reply to request
* so complete the transaction
*/
r2net_complete_nsw(nn, NULL, be32_to_cpu(hdr->msg_num),
be32_to_cpu(hdr->sys_status), handler_status);
goto out;
}
/*
* handler changed magic to DATA_MAGIC to reply to request for data,
* implies ret_data points to data to return and handler_status
* is the number of bytes of data
*/
if (be16_to_cpu(hdr->magic) == R2NET_MSG_DATA_MAGIC) {
ret = r2net_send_data_magic(sc, hdr,
ret_data, handler_status,
syserr, 0);
hdr = NULL;
mlog(0, "sending data reply %d, syserr %d returned %d\n",
handler_status, syserr, ret);
r2net_set_func_stop_time(sc);
r2net_update_recv_stats(sc);
goto out;
}
r2net_set_func_stop_time(sc);
r2net_update_recv_stats(sc);
out_respond:
/* this destroys the hdr, so don't use it after this */
mutex_lock(&sc->sc_send_lock);
ret = r2net_send_status_magic(sc->sc_sock, hdr, syserr,
handler_status);
mutex_unlock(&sc->sc_send_lock);
hdr = NULL;
mlog(0, "sending handler status %d, syserr %d returned %d\n",
handler_status, syserr, ret);
if (nmh) {
BUG_ON(ret_data != NULL && nmh->nh_post_func == NULL);
if (nmh->nh_post_func)
(nmh->nh_post_func)(handler_status, nmh->nh_func_data,
ret_data);
}
out:
if (nmh)
r2net_handler_put(nmh);
return ret;
}
static int r2net_check_handshake(struct r2net_sock_container *sc)
{
struct r2net_handshake *hand = page_address(sc->sc_page);
struct r2net_node *nn = r2net_nn_from_num(sc->sc_node->nd_num);
if (hand->protocol_version != cpu_to_be64(R2NET_PROTOCOL_VERSION)) {
pr_notice("ramster: " SC_NODEF_FMT " Advertised net "
"protocol version %llu but %llu is required. "
"Disconnecting.\n", sc->sc_node->nd_name,
sc->sc_node->nd_num, &sc->sc_node->nd_ipv4_address,
ntohs(sc->sc_node->nd_ipv4_port),
(unsigned long long)be64_to_cpu(hand->protocol_version),
R2NET_PROTOCOL_VERSION);
/* don't bother reconnecting if its the wrong version. */
r2net_ensure_shutdown(nn, sc, -ENOTCONN);
return -1;
}
/*
* Ensure timeouts are consistent with other nodes, otherwise
* we can end up with one node thinking that the other must be down,
* but isn't. This can ultimately cause corruption.
*/
if (be32_to_cpu(hand->r2net_idle_timeout_ms) !=
r2net_idle_timeout()) {
pr_notice("ramster: " SC_NODEF_FMT " uses a network "
"idle timeout of %u ms, but we use %u ms locally. "
"Disconnecting.\n", sc->sc_node->nd_name,
sc->sc_node->nd_num, &sc->sc_node->nd_ipv4_address,
ntohs(sc->sc_node->nd_ipv4_port),
be32_to_cpu(hand->r2net_idle_timeout_ms),
r2net_idle_timeout());
r2net_ensure_shutdown(nn, sc, -ENOTCONN);
return -1;
}
if (be32_to_cpu(hand->r2net_keepalive_delay_ms) !=
r2net_keepalive_delay()) {
pr_notice("ramster: " SC_NODEF_FMT " uses a keepalive "
"delay of %u ms, but we use %u ms locally. "
"Disconnecting.\n", sc->sc_node->nd_name,
sc->sc_node->nd_num, &sc->sc_node->nd_ipv4_address,
ntohs(sc->sc_node->nd_ipv4_port),
be32_to_cpu(hand->r2net_keepalive_delay_ms),
r2net_keepalive_delay());
r2net_ensure_shutdown(nn, sc, -ENOTCONN);
return -1;
}
if (be32_to_cpu(hand->r2hb_heartbeat_timeout_ms) !=
R2HB_MAX_WRITE_TIMEOUT_MS) {
pr_notice("ramster: " SC_NODEF_FMT " uses a heartbeat "
"timeout of %u ms, but we use %u ms locally. "
"Disconnecting.\n", sc->sc_node->nd_name,
sc->sc_node->nd_num, &sc->sc_node->nd_ipv4_address,
ntohs(sc->sc_node->nd_ipv4_port),
be32_to_cpu(hand->r2hb_heartbeat_timeout_ms),
R2HB_MAX_WRITE_TIMEOUT_MS);
r2net_ensure_shutdown(nn, sc, -ENOTCONN);
return -1;
}
sc->sc_handshake_ok = 1;
spin_lock(&nn->nn_lock);
/* set valid and queue the idle timers only if it hasn't been
* shut down already */
if (nn->nn_sc == sc) {
r2net_sc_reset_idle_timer(sc);
atomic_set(&nn->nn_timeout, 0);
r2net_set_nn_state(nn, sc, 1, 0);
}
spin_unlock(&nn->nn_lock);
/* shift everything up as though it wasn't there */
sc->sc_page_off -= sizeof(struct r2net_handshake);
if (sc->sc_page_off)
memmove(hand, hand + 1, sc->sc_page_off);
return 0;
}
/* this demuxes the queued rx bytes into header or payload bits and calls
* handlers as each full message is read off the socket. it returns -error,
* == 0 eof, or > 0 for progress made.*/
static int r2net_advance_rx(struct r2net_sock_container *sc)
{
struct r2net_msg *hdr;
int ret = 0;
void *data;
size_t datalen;
sclog(sc, "receiving\n");
r2net_set_advance_start_time(sc);
if (unlikely(sc->sc_handshake_ok == 0)) {
if (sc->sc_page_off < sizeof(struct r2net_handshake)) {
data = page_address(sc->sc_page) + sc->sc_page_off;
datalen = sizeof(struct r2net_handshake) -
sc->sc_page_off;
ret = r2net_recv_tcp_msg(sc->sc_sock, data, datalen);
if (ret > 0)
sc->sc_page_off += ret;
}
if (sc->sc_page_off == sizeof(struct r2net_handshake)) {
r2net_check_handshake(sc);
if (unlikely(sc->sc_handshake_ok == 0))
ret = -EPROTO;
}
goto out;
}
/* do we need more header? */
if (sc->sc_page_off < sizeof(struct r2net_msg)) {
data = page_address(sc->sc_page) + sc->sc_page_off;
datalen = sizeof(struct r2net_msg) - sc->sc_page_off;
ret = r2net_recv_tcp_msg(sc->sc_sock, data, datalen);
if (ret > 0) {
sc->sc_page_off += ret;
/* only swab incoming here.. we can
* only get here once as we cross from
* being under to over */
if (sc->sc_page_off == sizeof(struct r2net_msg)) {
hdr = page_address(sc->sc_page);
if (be16_to_cpu(hdr->data_len) >
R2NET_MAX_PAYLOAD_BYTES)
ret = -EOVERFLOW;
WARN_ON_ONCE(ret == -EOVERFLOW);
}
}
if (ret <= 0)
goto out;
}
if (sc->sc_page_off < sizeof(struct r2net_msg)) {
/* oof, still don't have a header */
goto out;
}
/* this was swabbed above when we first read it */
hdr = page_address(sc->sc_page);
msglog(hdr, "at page_off %zu\n", sc->sc_page_off);
/* do we need more payload? */
if (sc->sc_page_off - sizeof(struct r2net_msg) <
be16_to_cpu(hdr->data_len)) {
/* need more payload */
data = page_address(sc->sc_page) + sc->sc_page_off;
datalen = (sizeof(struct r2net_msg) +
be16_to_cpu(hdr->data_len)) -
sc->sc_page_off;
ret = r2net_recv_tcp_msg(sc->sc_sock, data, datalen);
if (ret > 0)
sc->sc_page_off += ret;
if (ret <= 0)
goto out;
}
if (sc->sc_page_off - sizeof(struct r2net_msg) ==
be16_to_cpu(hdr->data_len)) {
/* we can only get here once, the first time we read
* the payload.. so set ret to progress if the handler
* works out. after calling this the message is toast */
ret = r2net_process_message(sc, hdr);
if (ret == 0)
ret = 1;
sc->sc_page_off = 0;
}
out:
sclog(sc, "ret = %d\n", ret);
r2net_set_advance_stop_time(sc);
return ret;
}
/* this work func is triggerd by data ready. it reads until it can read no
* more. it interprets 0, eof, as fatal. if data_ready hits while we're doing
* our work the work struct will be marked and we'll be called again. */
static void r2net_rx_until_empty(struct work_struct *work)
{
struct r2net_sock_container *sc =
container_of(work, struct r2net_sock_container, sc_rx_work);
int ret;
do {
ret = r2net_advance_rx(sc);
} while (ret > 0);
if (ret <= 0 && ret != -EAGAIN) {
struct r2net_node *nn = r2net_nn_from_num(sc->sc_node->nd_num);
sclog(sc, "saw error %d, closing\n", ret);
/* not permanent so read failed handshake can retry */
r2net_ensure_shutdown(nn, sc, 0);
}
sc_put(sc);
}
static int r2net_set_nodelay(struct socket *sock)
{
int ret, val = 1;
mm_segment_t oldfs;
oldfs = get_fs();
set_fs(KERNEL_DS);
/*
* Dear unsuspecting programmer,
*
* Don't use sock_setsockopt() for SOL_TCP. It doesn't check its level
* argument and assumes SOL_SOCKET so, say, your TCP_NODELAY will
* silently turn into SO_DEBUG.
*
* Yours,
* Keeper of hilariously fragile interfaces.
*/
ret = sock->ops->setsockopt(sock, SOL_TCP, TCP_NODELAY,
(char __user *)&val, sizeof(val));
set_fs(oldfs);
return ret;
}
static void r2net_initialize_handshake(void)
{
r2net_hand->r2hb_heartbeat_timeout_ms = cpu_to_be32(
R2HB_MAX_WRITE_TIMEOUT_MS);
r2net_hand->r2net_idle_timeout_ms = cpu_to_be32(r2net_idle_timeout());
r2net_hand->r2net_keepalive_delay_ms = cpu_to_be32(
r2net_keepalive_delay());
r2net_hand->r2net_reconnect_delay_ms = cpu_to_be32(
r2net_reconnect_delay());
}
/* ------------------------------------------------------------ */
/* called when a connect completes and after a sock is accepted. the
* rx path will see the response and mark the sc valid */
static void r2net_sc_connect_completed(struct work_struct *work)
{
struct r2net_sock_container *sc =
container_of(work, struct r2net_sock_container,
sc_connect_work);
mlog(ML_MSG, "sc sending handshake with ver %llu id %llx\n",
(unsigned long long)R2NET_PROTOCOL_VERSION,
(unsigned long long)be64_to_cpu(r2net_hand->connector_id));
r2net_initialize_handshake();
r2net_sendpage(sc, r2net_hand, sizeof(*r2net_hand));
sc_put(sc);
}
/* this is called as a work_struct func. */
static void r2net_sc_send_keep_req(struct work_struct *work)
{
struct r2net_sock_container *sc =
container_of(work, struct r2net_sock_container,
sc_keepalive_work.work);
r2net_sendpage(sc, r2net_keep_req, sizeof(*r2net_keep_req));
sc_put(sc);
}
/* socket shutdown does a del_timer_sync against this as it tears down.
* we can't start this timer until we've got to the point in sc buildup
* where shutdown is going to be involved */
static void r2net_idle_timer(unsigned long data)
{
struct r2net_sock_container *sc = (struct r2net_sock_container *)data;
struct r2net_node *nn = r2net_nn_from_num(sc->sc_node->nd_num);
#ifdef CONFIG_DEBUG_FS
unsigned long msecs = ktime_to_ms(ktime_get()) -
ktime_to_ms(sc->sc_tv_timer);
#else
unsigned long msecs = r2net_idle_timeout();
#endif
pr_notice("ramster: Connection to " SC_NODEF_FMT " has been "
"idle for %lu.%lu secs, shutting it down.\n",
sc->sc_node->nd_name, sc->sc_node->nd_num,
&sc->sc_node->nd_ipv4_address, ntohs(sc->sc_node->nd_ipv4_port),
msecs / 1000, msecs % 1000);
/*
* Initialize the nn_timeout so that the next connection attempt
* will continue in r2net_start_connect.
*/
atomic_set(&nn->nn_timeout, 1);
r2net_sc_queue_work(sc, &sc->sc_shutdown_work);
}
static void r2net_sc_reset_idle_timer(struct r2net_sock_container *sc)
{
r2net_sc_cancel_delayed_work(sc, &sc->sc_keepalive_work);
r2net_sc_queue_delayed_work(sc, &sc->sc_keepalive_work,
msecs_to_jiffies(r2net_keepalive_delay()));
r2net_set_sock_timer(sc);
mod_timer(&sc->sc_idle_timeout,
jiffies + msecs_to_jiffies(r2net_idle_timeout()));
}
static void r2net_sc_postpone_idle(struct r2net_sock_container *sc)
{
/* Only push out an existing timer */
if (timer_pending(&sc->sc_idle_timeout))
r2net_sc_reset_idle_timer(sc);
}
/* this work func is kicked whenever a path sets the nn state which doesn't
* have valid set. This includes seeing hb come up, losing a connection,
* having a connect attempt fail, etc. This centralizes the logic which decides
* if a connect attempt should be made or if we should give up and all future
* transmit attempts should fail */
static void r2net_start_connect(struct work_struct *work)
{
struct r2net_node *nn =
container_of(work, struct r2net_node, nn_connect_work.work);
struct r2net_sock_container *sc = NULL;
struct r2nm_node *node = NULL, *mynode = NULL;
struct socket *sock = NULL;
struct sockaddr_in myaddr = {0, }, remoteaddr = {0, };
int ret = 0, stop;
unsigned int timeout;
/* if we're greater we initiate tx, otherwise we accept */
if (r2nm_this_node() <= r2net_num_from_nn(nn))
goto out;
/* watch for racing with tearing a node down */
node = r2nm_get_node_by_num(r2net_num_from_nn(nn));
if (node == NULL) {
ret = 0;
goto out;
}
mynode = r2nm_get_node_by_num(r2nm_this_node());
if (mynode == NULL) {
ret = 0;
goto out;
}
spin_lock(&nn->nn_lock);
/*
* see if we already have one pending or have given up.
* For nn_timeout, it is set when we close the connection
* because of the idle time out. So it means that we have
* at least connected to that node successfully once,
* now try to connect to it again.
*/
timeout = atomic_read(&nn->nn_timeout);
stop = (nn->nn_sc ||
(nn->nn_persistent_error &&
(nn->nn_persistent_error != -ENOTCONN || timeout == 0)));
spin_unlock(&nn->nn_lock);
if (stop)
goto out;
nn->nn_last_connect_attempt = jiffies;
sc = sc_alloc(node);
if (sc == NULL) {
mlog(0, "couldn't allocate sc\n");
ret = -ENOMEM;
goto out;
}
ret = sock_create(PF_INET, SOCK_STREAM, IPPROTO_TCP, &sock);
if (ret < 0) {
mlog(0, "can't create socket: %d\n", ret);
goto out;
}
sc->sc_sock = sock; /* freed by sc_kref_release */
sock->sk->sk_allocation = GFP_ATOMIC;
myaddr.sin_family = AF_INET;
myaddr.sin_addr.s_addr = mynode->nd_ipv4_address;
myaddr.sin_port = htons(0); /* any port */
ret = sock->ops->bind(sock, (struct sockaddr *)&myaddr,
sizeof(myaddr));
if (ret) {
mlog(ML_ERROR, "bind failed with %d at address %pI4\n",
ret, &mynode->nd_ipv4_address);
goto out;
}
ret = r2net_set_nodelay(sc->sc_sock);
if (ret) {
mlog(ML_ERROR, "setting TCP_NODELAY failed with %d\n", ret);
goto out;
}
r2net_register_callbacks(sc->sc_sock->sk, sc);
spin_lock(&nn->nn_lock);
/* handshake completion will set nn->nn_sc_valid */
r2net_set_nn_state(nn, sc, 0, 0);
spin_unlock(&nn->nn_lock);
remoteaddr.sin_family = AF_INET;
remoteaddr.sin_addr.s_addr = node->nd_ipv4_address;
remoteaddr.sin_port = node->nd_ipv4_port;
ret = sc->sc_sock->ops->connect(sc->sc_sock,
(struct sockaddr *)&remoteaddr,
sizeof(remoteaddr),
O_NONBLOCK);
if (ret == -EINPROGRESS)
ret = 0;
out:
if (ret) {
pr_notice("ramster: Connect attempt to " SC_NODEF_FMT
" failed with errno %d\n", sc->sc_node->nd_name,
sc->sc_node->nd_num, &sc->sc_node->nd_ipv4_address,
ntohs(sc->sc_node->nd_ipv4_port), ret);
/* 0 err so that another will be queued and attempted
* from set_nn_state */
if (sc)
r2net_ensure_shutdown(nn, sc, 0);
}
if (sc)
sc_put(sc);
if (node)
r2nm_node_put(node);
if (mynode)
r2nm_node_put(mynode);
return;
}
static void r2net_connect_expired(struct work_struct *work)
{
struct r2net_node *nn =
container_of(work, struct r2net_node, nn_connect_expired.work);
spin_lock(&nn->nn_lock);
if (!nn->nn_sc_valid) {
pr_notice("ramster: No connection established with "
"node %u after %u.%u seconds, giving up.\n",
r2net_num_from_nn(nn),
r2net_idle_timeout() / 1000,
r2net_idle_timeout() % 1000);
r2net_set_nn_state(nn, NULL, 0, -ENOTCONN);
}
spin_unlock(&nn->nn_lock);
}
static void r2net_still_up(struct work_struct *work)
{
}
/* ------------------------------------------------------------ */
void r2net_disconnect_node(struct r2nm_node *node)
{
struct r2net_node *nn = r2net_nn_from_num(node->nd_num);
/* don't reconnect until it's heartbeating again */
spin_lock(&nn->nn_lock);
atomic_set(&nn->nn_timeout, 0);
r2net_set_nn_state(nn, NULL, 0, -ENOTCONN);
spin_unlock(&nn->nn_lock);
if (r2net_wq) {
cancel_delayed_work(&nn->nn_connect_expired);
cancel_delayed_work(&nn->nn_connect_work);
cancel_delayed_work(&nn->nn_still_up);
flush_workqueue(r2net_wq);
}
}
static void r2net_hb_node_down_cb(struct r2nm_node *node, int node_num,
void *data)
{
if (!node)
return;
if (node_num != r2nm_this_node())
r2net_disconnect_node(node);
BUG_ON(atomic_read(&r2net_connected_peers) < 0);
}
static void r2net_hb_node_up_cb(struct r2nm_node *node, int node_num,
void *data)
{
struct r2net_node *nn = r2net_nn_from_num(node_num);
BUG_ON(!node);
/* ensure an immediate connect attempt */
nn->nn_last_connect_attempt = jiffies -
(msecs_to_jiffies(r2net_reconnect_delay()) + 1);
if (node_num != r2nm_this_node()) {
/* believe it or not, accept and node hearbeating testing
* can succeed for this node before we got here.. so
* only use set_nn_state to clear the persistent error
* if that hasn't already happened */
spin_lock(&nn->nn_lock);
atomic_set(&nn->nn_timeout, 0);
if (nn->nn_persistent_error)
r2net_set_nn_state(nn, NULL, 0, 0);
spin_unlock(&nn->nn_lock);
}
}
void r2net_unregister_hb_callbacks(void)
{
r2hb_unregister_callback(NULL, &r2net_hb_up);
r2hb_unregister_callback(NULL, &r2net_hb_down);
}
int r2net_register_hb_callbacks(void)
{
int ret;
r2hb_setup_callback(&r2net_hb_down, R2HB_NODE_DOWN_CB,
r2net_hb_node_down_cb, NULL, R2NET_HB_PRI);
r2hb_setup_callback(&r2net_hb_up, R2HB_NODE_UP_CB,
r2net_hb_node_up_cb, NULL, R2NET_HB_PRI);
ret = r2hb_register_callback(NULL, &r2net_hb_up);
if (ret == 0)
ret = r2hb_register_callback(NULL, &r2net_hb_down);
if (ret)
r2net_unregister_hb_callbacks();
return ret;
}
/* ------------------------------------------------------------ */
static int r2net_accept_one(struct socket *sock)
{
int ret, slen;
struct sockaddr_in sin;
struct socket *new_sock = NULL;
struct r2nm_node *node = NULL;
struct r2nm_node *local_node = NULL;
struct r2net_sock_container *sc = NULL;
struct r2net_node *nn;
BUG_ON(sock == NULL);
ret = sock_create_lite(sock->sk->sk_family, sock->sk->sk_type,
sock->sk->sk_protocol, &new_sock);
if (ret)
goto out;
new_sock->type = sock->type;
new_sock->ops = sock->ops;
ret = sock->ops->accept(sock, new_sock, O_NONBLOCK);
if (ret < 0)
goto out;
new_sock->sk->sk_allocation = GFP_ATOMIC;
ret = r2net_set_nodelay(new_sock);
if (ret) {
mlog(ML_ERROR, "setting TCP_NODELAY failed with %d\n", ret);
goto out;
}
slen = sizeof(sin);
ret = new_sock->ops->getname(new_sock, (struct sockaddr *) &sin,
&slen, 1);
if (ret < 0)
goto out;
node = r2nm_get_node_by_ip(sin.sin_addr.s_addr);
if (node == NULL) {
pr_notice("ramster: Attempt to connect from unknown "
"node at %pI4:%d\n", &sin.sin_addr.s_addr,
ntohs(sin.sin_port));
ret = -EINVAL;
goto out;
}
if (r2nm_this_node() >= node->nd_num) {
local_node = r2nm_get_node_by_num(r2nm_this_node());
pr_notice("ramster: Unexpected connect attempt seen "
"at node '%s' (%u, %pI4:%d) from node '%s' (%u, "
"%pI4:%d)\n", local_node->nd_name, local_node->nd_num,
&(local_node->nd_ipv4_address),
ntohs(local_node->nd_ipv4_port), node->nd_name,
node->nd_num, &sin.sin_addr.s_addr, ntohs(sin.sin_port));
ret = -EINVAL;
goto out;
}
/* this happens all the time when the other node sees our heartbeat
* and tries to connect before we see their heartbeat */
if (!r2hb_check_node_heartbeating_from_callback(node->nd_num)) {
mlog(ML_CONN, "attempt to connect from node '%s' at "
"%pI4:%d but it isn't heartbeating\n",
node->nd_name, &sin.sin_addr.s_addr,
ntohs(sin.sin_port));
ret = -EINVAL;
goto out;
}
nn = r2net_nn_from_num(node->nd_num);
spin_lock(&nn->nn_lock);
if (nn->nn_sc)
ret = -EBUSY;
else
ret = 0;
spin_unlock(&nn->nn_lock);
if (ret) {
pr_notice("ramster: Attempt to connect from node '%s' "
"at %pI4:%d but it already has an open connection\n",
node->nd_name, &sin.sin_addr.s_addr,
ntohs(sin.sin_port));
goto out;
}
sc = sc_alloc(node);
if (sc == NULL) {
ret = -ENOMEM;
goto out;
}
sc->sc_sock = new_sock;
new_sock = NULL;
spin_lock(&nn->nn_lock);
atomic_set(&nn->nn_timeout, 0);
r2net_set_nn_state(nn, sc, 0, 0);
spin_unlock(&nn->nn_lock);
r2net_register_callbacks(sc->sc_sock->sk, sc);
r2net_sc_queue_work(sc, &sc->sc_rx_work);
r2net_initialize_handshake();
r2net_sendpage(sc, r2net_hand, sizeof(*r2net_hand));
out:
if (new_sock)
sock_release(new_sock);
if (node)
r2nm_node_put(node);
if (local_node)
r2nm_node_put(local_node);
if (sc)
sc_put(sc);
return ret;
}
static void r2net_accept_many(struct work_struct *work)
{
struct socket *sock = r2net_listen_sock;
while (r2net_accept_one(sock) == 0)
cond_resched();
}
static void r2net_listen_data_ready(struct sock *sk, int bytes)
{
void (*ready)(struct sock *sk, int bytes);
read_lock(&sk->sk_callback_lock);
ready = sk->sk_user_data;
if (ready == NULL) { /* check for teardown race */
ready = sk->sk_data_ready;
goto out;
}
/* ->sk_data_ready is also called for a newly established child socket
* before it has been accepted and the acceptor has set up their
* data_ready.. we only want to queue listen work for our listening
* socket */
if (sk->sk_state == TCP_LISTEN) {
mlog(ML_TCP, "bytes: %d\n", bytes);
queue_work(r2net_wq, &r2net_listen_work);
}
out:
read_unlock(&sk->sk_callback_lock);
ready(sk, bytes);
}
static int r2net_open_listening_sock(__be32 addr, __be16 port)
{
struct socket *sock = NULL;
int ret;
struct sockaddr_in sin = {
.sin_family = PF_INET,
.sin_addr = { .s_addr = addr },
.sin_port = port,
};
ret = sock_create(PF_INET, SOCK_STREAM, IPPROTO_TCP, &sock);
if (ret < 0) {
pr_err("ramster: Error %d while creating socket\n", ret);
goto out;
}
sock->sk->sk_allocation = GFP_ATOMIC;
write_lock_bh(&sock->sk->sk_callback_lock);
sock->sk->sk_user_data = sock->sk->sk_data_ready;
sock->sk->sk_data_ready = r2net_listen_data_ready;
write_unlock_bh(&sock->sk->sk_callback_lock);
r2net_listen_sock = sock;
INIT_WORK(&r2net_listen_work, r2net_accept_many);
sock->sk->sk_reuse = /* SK_CAN_REUSE FIXME FOR 3.4 */ 1;
ret = sock->ops->bind(sock, (struct sockaddr *)&sin, sizeof(sin));
if (ret < 0) {
pr_err("ramster: Error %d while binding socket at %pI4:%u\n",
ret, &addr, ntohs(port));
goto out;
}
ret = sock->ops->listen(sock, 64);
if (ret < 0)
pr_err("ramster: Error %d while listening on %pI4:%u\n",
ret, &addr, ntohs(port));
out:
if (ret) {
r2net_listen_sock = NULL;
if (sock)
sock_release(sock);
}
return ret;
}
/*
* called from node manager when we should bring up our network listening
* socket. node manager handles all the serialization to only call this
* once and to match it with r2net_stop_listening(). note,
* r2nm_this_node() doesn't work yet as we're being called while it
* is being set up.
*/
int r2net_start_listening(struct r2nm_node *node)
{
int ret = 0;
BUG_ON(r2net_wq != NULL);
BUG_ON(r2net_listen_sock != NULL);
mlog(ML_KTHREAD, "starting r2net thread...\n");
r2net_wq = create_singlethread_workqueue("r2net");
if (r2net_wq == NULL) {
mlog(ML_ERROR, "unable to launch r2net thread\n");
return -ENOMEM; /* ? */
}
ret = r2net_open_listening_sock(node->nd_ipv4_address,
node->nd_ipv4_port);
if (ret) {
destroy_workqueue(r2net_wq);
r2net_wq = NULL;
}
return ret;
}
/* again, r2nm_this_node() doesn't work here as we're involved in
* tearing it down */
void r2net_stop_listening(struct r2nm_node *node)
{
struct socket *sock = r2net_listen_sock;
size_t i;
BUG_ON(r2net_wq == NULL);
BUG_ON(r2net_listen_sock == NULL);
/* stop the listening socket from generating work */
write_lock_bh(&sock->sk->sk_callback_lock);
sock->sk->sk_data_ready = sock->sk->sk_user_data;
sock->sk->sk_user_data = NULL;
write_unlock_bh(&sock->sk->sk_callback_lock);
for (i = 0; i < ARRAY_SIZE(r2net_nodes); i++) {
struct r2nm_node *node = r2nm_get_node_by_num(i);
if (node) {
r2net_disconnect_node(node);
r2nm_node_put(node);
}
}
/* finish all work and tear down the work queue */
mlog(ML_KTHREAD, "waiting for r2net thread to exit....\n");
destroy_workqueue(r2net_wq);
r2net_wq = NULL;
sock_release(r2net_listen_sock);
r2net_listen_sock = NULL;
}
void r2net_hb_node_up_manual(int node_num)
{
struct r2nm_node dummy;
if (r2nm_single_cluster == NULL)
pr_err("ramster: cluster not alive, node_up_manual ignored\n");
else {
r2hb_manual_set_node_heartbeating(node_num);
r2net_hb_node_up_cb(&dummy, node_num, NULL);
}
}
/* ------------------------------------------------------------ */
int r2net_init(void)
{
unsigned long i;
if (r2net_debugfs_init())
return -ENOMEM;
r2net_hand = kzalloc(sizeof(struct r2net_handshake), GFP_KERNEL);
r2net_keep_req = kzalloc(sizeof(struct r2net_msg), GFP_KERNEL);
r2net_keep_resp = kzalloc(sizeof(struct r2net_msg), GFP_KERNEL);
if (!r2net_hand || !r2net_keep_req || !r2net_keep_resp) {
kfree(r2net_hand);
kfree(r2net_keep_req);
kfree(r2net_keep_resp);
return -ENOMEM;
}
r2net_hand->protocol_version = cpu_to_be64(R2NET_PROTOCOL_VERSION);
r2net_hand->connector_id = cpu_to_be64(1);
r2net_keep_req->magic = cpu_to_be16(R2NET_MSG_KEEP_REQ_MAGIC);
r2net_keep_resp->magic = cpu_to_be16(R2NET_MSG_KEEP_RESP_MAGIC);
for (i = 0; i < ARRAY_SIZE(r2net_nodes); i++) {
struct r2net_node *nn = r2net_nn_from_num(i);
atomic_set(&nn->nn_timeout, 0);
spin_lock_init(&nn->nn_lock);
INIT_DELAYED_WORK(&nn->nn_connect_work, r2net_start_connect);
INIT_DELAYED_WORK(&nn->nn_connect_expired,
r2net_connect_expired);
INIT_DELAYED_WORK(&nn->nn_still_up, r2net_still_up);
/* until we see hb from a node we'll return einval */
nn->nn_persistent_error = -ENOTCONN;
init_waitqueue_head(&nn->nn_sc_wq);
idr_init(&nn->nn_status_idr);
INIT_LIST_HEAD(&nn->nn_status_list);
}
return 0;
}
void r2net_exit(void)
{
kfree(r2net_hand);
kfree(r2net_keep_req);
kfree(r2net_keep_resp);
r2net_debugfs_exit();
}
/* -*- mode: c; c-basic-offset: 8; -*-
* vim: noexpandtab sw=8 ts=8 sts=0:
*
* tcp.h
*
* Function prototypes
*
* Copyright (C) 2004 Oracle. All rights reserved.
*
* 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.
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*
*/
#ifndef R2CLUSTER_TCP_H
#define R2CLUSTER_TCP_H
#include <linux/socket.h>
#ifdef __KERNEL__
#include <net/sock.h>
#include <linux/tcp.h>
#else
#include <sys/socket.h>
#endif
#include <linux/inet.h>
#include <linux/in.h>
struct r2net_msg {
__be16 magic;
__be16 data_len;
__be16 msg_type;
__be16 pad1;
__be32 sys_status;
__be32 status;
__be32 key;
__be32 msg_num;
__u8 buf[0];
};
typedef int (r2net_msg_handler_func)(struct r2net_msg *msg, u32 len, void *data,
void **ret_data);
typedef void (r2net_post_msg_handler_func)(int status, void *data,
void *ret_data);
#define R2NET_MAX_PAYLOAD_BYTES (4096 - sizeof(struct r2net_msg))
/* same as hb delay, we're waiting for another node to recognize our hb */
#define R2NET_RECONNECT_DELAY_MS_DEFAULT 2000
#define R2NET_KEEPALIVE_DELAY_MS_DEFAULT 2000
#define R2NET_IDLE_TIMEOUT_MS_DEFAULT 30000
/* TODO: figure this out.... */
static inline int r2net_link_down(int err, struct socket *sock)
{
if (sock) {
if (sock->sk->sk_state != TCP_ESTABLISHED &&
sock->sk->sk_state != TCP_CLOSE_WAIT)
return 1;
}
if (err >= 0)
return 0;
switch (err) {
/* ????????????????????????? */
case -ERESTARTSYS:
case -EBADF:
/* When the server has died, an ICMP port unreachable
* message prompts ECONNREFUSED. */
case -ECONNREFUSED:
case -ENOTCONN:
case -ECONNRESET:
case -EPIPE:
return 1;
}
return 0;
}
enum {
R2NET_DRIVER_UNINITED,
R2NET_DRIVER_READY,
};
int r2net_send_message(u32 msg_type, u32 key, void *data, u32 len,
u8 target_node, int *status);
int r2net_send_message_vec(u32 msg_type, u32 key, struct kvec *vec,
size_t veclen, u8 target_node, int *status);
int r2net_register_handler(u32 msg_type, u32 key, u32 max_len,
r2net_msg_handler_func *func, void *data,
r2net_post_msg_handler_func *post_func,
struct list_head *unreg_list);
void r2net_unregister_handler_list(struct list_head *list);
void r2net_fill_node_map(unsigned long *map, unsigned bytes);
void r2net_force_data_magic(struct r2net_msg *, u16, u32);
void r2net_hb_node_up_manual(int);
struct r2net_node *r2net_nn_from_num(u8);
struct r2nm_node;
int r2net_register_hb_callbacks(void);
void r2net_unregister_hb_callbacks(void);
int r2net_start_listening(struct r2nm_node *node);
void r2net_stop_listening(struct r2nm_node *node);
void r2net_disconnect_node(struct r2nm_node *node);
int r2net_num_connected_peers(void);
int r2net_init(void);
void r2net_exit(void);
struct r2net_send_tracking;
struct r2net_sock_container;
#if 0
int r2net_debugfs_init(void);
void r2net_debugfs_exit(void);
void r2net_debug_add_nst(struct r2net_send_tracking *nst);
void r2net_debug_del_nst(struct r2net_send_tracking *nst);
void r2net_debug_add_sc(struct r2net_sock_container *sc);
void r2net_debug_del_sc(struct r2net_sock_container *sc);
#else
static inline int r2net_debugfs_init(void)
{
return 0;
}
static inline void r2net_debugfs_exit(void)
{
}
static inline void r2net_debug_add_nst(struct r2net_send_tracking *nst)
{
}
static inline void r2net_debug_del_nst(struct r2net_send_tracking *nst)
{
}
static inline void r2net_debug_add_sc(struct r2net_sock_container *sc)
{
}
static inline void r2net_debug_del_sc(struct r2net_sock_container *sc)
{
}
#endif /* CONFIG_DEBUG_FS */
#endif /* R2CLUSTER_TCP_H */
/* -*- mode: c; c-basic-offset: 8; -*-
* vim: noexpandtab sw=8 ts=8 sts=0:
*
* Copyright (C) 2005 Oracle. All rights reserved.
*
* 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.
*
* 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, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 021110-1307, USA.
*/
#ifndef R2CLUSTER_TCP_INTERNAL_H
#define R2CLUSTER_TCP_INTERNAL_H
#define R2NET_MSG_MAGIC ((u16)0xfa55)
#define R2NET_MSG_STATUS_MAGIC ((u16)0xfa56)
#define R2NET_MSG_KEEP_REQ_MAGIC ((u16)0xfa57)
#define R2NET_MSG_KEEP_RESP_MAGIC ((u16)0xfa58)
/*
* "data magic" is a long version of "status magic" where the message
* payload actually contains data to be passed in reply to certain messages
*/
#define R2NET_MSG_DATA_MAGIC ((u16)0xfa59)
/* we're delaying our quorum decision so that heartbeat will have timed
* out truly dead nodes by the time we come around to making decisions
* on their number */
#define R2NET_QUORUM_DELAY_MS \
((r2hb_dead_threshold + 2) * R2HB_REGION_TIMEOUT_MS)
/*
* This version number represents quite a lot, unfortunately. It not
* only represents the raw network message protocol on the wire but also
* locking semantics of the file system using the protocol. It should
* be somewhere else, I'm sure, but right now it isn't.
*
* With version 11, we separate out the filesystem locking portion. The
* filesystem now has a major.minor version it negotiates. Version 11
* introduces this negotiation to the r2dlm protocol, and as such the
* version here in tcp_internal.h should not need to be bumped for
* filesystem locking changes.
*
* New in version 11
* - Negotiation of filesystem locking in the dlm join.
*
* New in version 10:
* - Meta/data locks combined
*
* New in version 9:
* - All votes removed
*
* New in version 8:
* - Replace delete inode votes with a cluster lock
*
* New in version 7:
* - DLM join domain includes the live nodemap
*
* New in version 6:
* - DLM lockres remote refcount fixes.
*
* New in version 5:
* - Network timeout checking protocol
*
* New in version 4:
* - Remove i_generation from lock names for better stat performance.
*
* New in version 3:
* - Replace dentry votes with a cluster lock
*
* New in version 2:
* - full 64 bit i_size in the metadata lock lvbs
* - introduction of "rw" lock and pushing meta/data locking down
*/
#define R2NET_PROTOCOL_VERSION 11ULL
struct r2net_handshake {
__be64 protocol_version;
__be64 connector_id;
__be32 r2hb_heartbeat_timeout_ms;
__be32 r2net_idle_timeout_ms;
__be32 r2net_keepalive_delay_ms;
__be32 r2net_reconnect_delay_ms;
};
struct r2net_node {
/* this is never called from int/bh */
spinlock_t nn_lock;
/* set the moment an sc is allocated and a connect is started */
struct r2net_sock_container *nn_sc;
/* _valid is only set after the handshake passes and tx can happen */
unsigned nn_sc_valid:1;
/* if this is set tx just returns it */
int nn_persistent_error;
/* It is only set to 1 after the idle time out. */
atomic_t nn_timeout;
/* threads waiting for an sc to arrive wait on the wq for generation
* to increase. it is increased when a connecting socket succeeds
* or fails or when an accepted socket is attached. */
wait_queue_head_t nn_sc_wq;
struct idr nn_status_idr;
struct list_head nn_status_list;
/* connects are attempted from when heartbeat comes up until either hb
* goes down, the node is unconfigured, no connect attempts succeed
* before R2NET_CONN_IDLE_DELAY, or a connect succeeds. connect_work
* is queued from set_nn_state both from hb up and from itself if a
* connect attempt fails and so can be self-arming. shutdown is
* careful to first mark the nn such that no connects will be attempted
* before canceling delayed connect work and flushing the queue. */
struct delayed_work nn_connect_work;
unsigned long nn_last_connect_attempt;
/* this is queued as nodes come up and is canceled when a connection is
* established. this expiring gives up on the node and errors out
* transmits */
struct delayed_work nn_connect_expired;
/* after we give up on a socket we wait a while before deciding
* that it is still heartbeating and that we should do some
* quorum work */
struct delayed_work nn_still_up;
};
struct r2net_sock_container {
struct kref sc_kref;
/* the next two are valid for the life time of the sc */
struct socket *sc_sock;
struct r2nm_node *sc_node;
/* all of these sc work structs hold refs on the sc while they are
* queued. they should not be able to ref a freed sc. the teardown
* race is with r2net_wq destruction in r2net_stop_listening() */
/* rx and connect work are generated from socket callbacks. sc
* shutdown removes the callbacks and then flushes the work queue */
struct work_struct sc_rx_work;
struct work_struct sc_connect_work;
/* shutdown work is triggered in two ways. the simple way is
* for a code path calls ensure_shutdown which gets a lock, removes
* the sc from the nn, and queues the work. in this case the
* work is single-shot. the work is also queued from a sock
* callback, though, and in this case the work will find the sc
* still on the nn and will call ensure_shutdown itself.. this
* ends up triggering the shutdown work again, though nothing
* will be done in that second iteration. so work queue teardown
* has to be careful to remove the sc from the nn before waiting
* on the work queue so that the shutdown work doesn't remove the
* sc and rearm itself.
*/
struct work_struct sc_shutdown_work;
struct timer_list sc_idle_timeout;
struct delayed_work sc_keepalive_work;
unsigned sc_handshake_ok:1;
struct page *sc_page;
size_t sc_page_off;
/* original handlers for the sockets */
void (*sc_state_change)(struct sock *sk);
void (*sc_data_ready)(struct sock *sk, int bytes);
u32 sc_msg_key;
u16 sc_msg_type;
#ifdef CONFIG_DEBUG_FS
struct list_head sc_net_debug_item;
ktime_t sc_tv_timer;
ktime_t sc_tv_data_ready;
ktime_t sc_tv_advance_start;
ktime_t sc_tv_advance_stop;
ktime_t sc_tv_func_start;
ktime_t sc_tv_func_stop;
#endif
#ifdef CONFIG_RAMSTER_FS_STATS
ktime_t sc_tv_acquiry_total;
ktime_t sc_tv_send_total;
ktime_t sc_tv_status_total;
u32 sc_send_count;
u32 sc_recv_count;
ktime_t sc_tv_process_total;
#endif
struct mutex sc_send_lock;
};
struct r2net_msg_handler {
struct rb_node nh_node;
u32 nh_max_len;
u32 nh_msg_type;
u32 nh_key;
r2net_msg_handler_func *nh_func;
r2net_msg_handler_func *nh_func_data;
r2net_post_msg_handler_func
*nh_post_func;
struct kref nh_kref;
struct list_head nh_unregister_item;
};
enum r2net_system_error {
R2NET_ERR_NONE = 0,
R2NET_ERR_NO_HNDLR,
R2NET_ERR_OVERFLOW,
R2NET_ERR_DIED,
R2NET_ERR_MAX
};
struct r2net_status_wait {
enum r2net_system_error ns_sys_status;
s32 ns_status;
int ns_id;
wait_queue_head_t ns_wq;
struct list_head ns_node_item;
};
#ifdef CONFIG_DEBUG_FS
/* just for state dumps */
struct r2net_send_tracking {
struct list_head st_net_debug_item;
struct task_struct *st_task;
struct r2net_sock_container *st_sc;
u32 st_id;
u32 st_msg_type;
u32 st_msg_key;
u8 st_node;
ktime_t st_sock_time;
ktime_t st_send_time;
ktime_t st_status_time;
};
#else
struct r2net_send_tracking {
u32 dummy;
};
#endif /* CONFIG_DEBUG_FS */
#endif /* R2CLUSTER_TCP_INTERNAL_H */
/*
* In-kernel transcendent memory (generic implementation)
*
* Copyright (c) 2009-2012, Dan Magenheimer, Oracle Corp.
*
* The primary purpose of Transcedent Memory ("tmem") is to map object-oriented
* "handles" (triples containing a pool id, and object id, and an index), to
* pages in a page-accessible memory (PAM). Tmem references the PAM pages via
* an abstract "pampd" (PAM page-descriptor), which can be operated on by a
* set of functions (pamops). Each pampd contains some representation of
* PAGE_SIZE bytes worth of data. For those familiar with key-value stores,
* the tmem handle is a three-level hierarchical key, and the value is always
* reconstituted (but not necessarily stored) as PAGE_SIZE bytes and is
* referenced in the datastore by the pampd. The hierarchy is required
* to ensure that certain invalidation functions can be performed efficiently
* (i.e. flush all indexes associated with this object_id, or
* flush all objects associated with this pool).
*
* Tmem must support potentially millions of pages and must be able to insert,
* find, and delete these pages at a potential frequency of thousands per
* second concurrently across many CPUs, (and, if used with KVM, across many
* vcpus across many guests). Tmem is tracked with a hierarchy of data
* structures, organized by the elements in the handle-tuple: pool_id,
* object_id, and page index. One or more "clients" (e.g. guests) each
* provide one or more tmem_pools. Each pool, contains a hash table of
* rb_trees of tmem_objs. Each tmem_obj contains a radix-tree-like tree
* of pointers, with intermediate nodes called tmem_objnodes. Each leaf
* pointer in this tree points to a pampd, which is accessible only through
* a small set of callbacks registered by the PAM implementation (see
* tmem_register_pamops). Tmem only needs to memory allocation for objs
* and objnodes and this is done via a set of callbacks that must be
* registered by the tmem host implementation (e.g. see tmem_register_hostops).
*/
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/atomic.h>
#include <linux/export.h>
#if defined(CONFIG_RAMSTER) || defined(CONFIG_RAMSTER_MODULE)
#include <linux/delay.h>
#endif
#include "tmem.h"
/* data structure sentinels used for debugging... see tmem.h */
#define POOL_SENTINEL 0x87658765
#define OBJ_SENTINEL 0x12345678
#define OBJNODE_SENTINEL 0xfedcba09
/*
* A tmem host implementation must use this function to register callbacks
* for memory allocation.
*/
static struct tmem_hostops tmem_hostops;
static void tmem_objnode_tree_init(void);
void tmem_register_hostops(struct tmem_hostops *m)
{
tmem_objnode_tree_init();
tmem_hostops = *m;
}
/*
* A tmem host implementation must use this function to register
* callbacks for a page-accessible memory (PAM) implementation.
*/
static struct tmem_pamops tmem_pamops;
void tmem_register_pamops(struct tmem_pamops *m)
{
tmem_pamops = *m;
}
/*
* Oid's are potentially very sparse and tmem_objs may have an indeterminately
* short life, being added and deleted at a relatively high frequency.
* So an rb_tree is an ideal data structure to manage tmem_objs. But because
* of the potentially huge number of tmem_objs, each pool manages a hashtable
* of rb_trees to reduce search, insert, delete, and rebalancing time.
* Each hashbucket also has a lock to manage concurrent access and no
* searches, inserts, or deletions can be performed unless the lock is held.
* As a result, care must be taken to ensure tmem routines are not called
* recursively; the vast majority of the time, a recursive call may work
* but a deadlock will occur a small fraction of the time due to the
* hashbucket lock.
*
* The following routines manage tmem_objs. In all of these routines,
* the hashbucket lock is already held.
*/
/* Search for object==oid in pool, returns object if found. */
static struct tmem_obj *__tmem_obj_find(struct tmem_hashbucket *hb,
struct tmem_oid *oidp,
struct rb_node **parent,
struct rb_node ***link)
{
struct rb_node *_parent = NULL, **rbnode;
struct tmem_obj *obj = NULL;
rbnode = &hb->obj_rb_root.rb_node;
while (*rbnode) {
BUG_ON(RB_EMPTY_NODE(*rbnode));
_parent = *rbnode;
obj = rb_entry(*rbnode, struct tmem_obj,
rb_tree_node);
switch (tmem_oid_compare(oidp, &obj->oid)) {
case 0: /* equal */
goto out;
case -1:
rbnode = &(*rbnode)->rb_left;
break;
case 1:
rbnode = &(*rbnode)->rb_right;
break;
}
}
if (parent)
*parent = _parent;
if (link)
*link = rbnode;
obj = NULL;
out:
return obj;
}
static struct tmem_obj *tmem_obj_find(struct tmem_hashbucket *hb,
struct tmem_oid *oidp)
{
return __tmem_obj_find(hb, oidp, NULL, NULL);
}
static void tmem_pampd_destroy_all_in_obj(struct tmem_obj *, bool);
/* Free an object that has no more pampds in it. */
static void tmem_obj_free(struct tmem_obj *obj, struct tmem_hashbucket *hb)
{
struct tmem_pool *pool;
BUG_ON(obj == NULL);
ASSERT_SENTINEL(obj, OBJ);
BUG_ON(obj->pampd_count > 0);
pool = obj->pool;
BUG_ON(pool == NULL);
if (obj->objnode_tree_root != NULL) /* may be "stump" with no leaves */
tmem_pampd_destroy_all_in_obj(obj, false);
BUG_ON(obj->objnode_tree_root != NULL);
BUG_ON((long)obj->objnode_count != 0);
atomic_dec(&pool->obj_count);
BUG_ON(atomic_read(&pool->obj_count) < 0);
INVERT_SENTINEL(obj, OBJ);
obj->pool = NULL;
tmem_oid_set_invalid(&obj->oid);
rb_erase(&obj->rb_tree_node, &hb->obj_rb_root);
}
/*
* Initialize, and insert an tmem_object_root (called only if find failed).
*/
static void tmem_obj_init(struct tmem_obj *obj, struct tmem_hashbucket *hb,
struct tmem_pool *pool,
struct tmem_oid *oidp)
{
struct rb_root *root = &hb->obj_rb_root;
struct rb_node **new = NULL, *parent = NULL;
BUG_ON(pool == NULL);
atomic_inc(&pool->obj_count);
obj->objnode_tree_height = 0;
obj->objnode_tree_root = NULL;
obj->pool = pool;
obj->oid = *oidp;
obj->objnode_count = 0;
obj->pampd_count = 0;
#ifdef CONFIG_RAMSTER
if (tmem_pamops.new_obj != NULL)
(*tmem_pamops.new_obj)(obj);
#endif
SET_SENTINEL(obj, OBJ);
if (__tmem_obj_find(hb, oidp, &parent, &new))
BUG();
rb_link_node(&obj->rb_tree_node, parent, new);
rb_insert_color(&obj->rb_tree_node, root);
}
/*
* Tmem is managed as a set of tmem_pools with certain attributes, such as
* "ephemeral" vs "persistent". These attributes apply to all tmem_objs
* and all pampds that belong to a tmem_pool. A tmem_pool is created
* or deleted relatively rarely (for example, when a filesystem is
* mounted or unmounted).
*/
/* flush all data from a pool and, optionally, free it */
static void tmem_pool_flush(struct tmem_pool *pool, bool destroy)
{
struct rb_node *rbnode;
struct tmem_obj *obj;
struct tmem_hashbucket *hb = &pool->hashbucket[0];
int i;
BUG_ON(pool == NULL);
for (i = 0; i < TMEM_HASH_BUCKETS; i++, hb++) {
spin_lock(&hb->lock);
rbnode = rb_first(&hb->obj_rb_root);
while (rbnode != NULL) {
obj = rb_entry(rbnode, struct tmem_obj, rb_tree_node);
rbnode = rb_next(rbnode);
tmem_pampd_destroy_all_in_obj(obj, true);
tmem_obj_free(obj, hb);
(*tmem_hostops.obj_free)(obj, pool);
}
spin_unlock(&hb->lock);
}
if (destroy)
list_del(&pool->pool_list);
}
/*
* A tmem_obj contains a radix-tree-like tree in which the intermediate
* nodes are called tmem_objnodes. (The kernel lib/radix-tree.c implementation
* is very specialized and tuned for specific uses and is not particularly
* suited for use from this code, though some code from the core algorithms has
* been reused, thus the copyright notices below). Each tmem_objnode contains
* a set of pointers which point to either a set of intermediate tmem_objnodes
* or a set of of pampds.
*
* Portions Copyright (C) 2001 Momchil Velikov
* Portions Copyright (C) 2001 Christoph Hellwig
* Portions Copyright (C) 2005 SGI, Christoph Lameter <clameter@sgi.com>
*/
struct tmem_objnode_tree_path {
struct tmem_objnode *objnode;
int offset;
};
/* objnode height_to_maxindex translation */
static unsigned long tmem_objnode_tree_h2max[OBJNODE_TREE_MAX_PATH + 1];
static void tmem_objnode_tree_init(void)
{
unsigned int ht, tmp;
for (ht = 0; ht < ARRAY_SIZE(tmem_objnode_tree_h2max); ht++) {
tmp = ht * OBJNODE_TREE_MAP_SHIFT;
if (tmp >= OBJNODE_TREE_INDEX_BITS)
tmem_objnode_tree_h2max[ht] = ~0UL;
else
tmem_objnode_tree_h2max[ht] =
(~0UL >> (OBJNODE_TREE_INDEX_BITS - tmp - 1)) >> 1;
}
}
static struct tmem_objnode *tmem_objnode_alloc(struct tmem_obj *obj)
{
struct tmem_objnode *objnode;
ASSERT_SENTINEL(obj, OBJ);
BUG_ON(obj->pool == NULL);
ASSERT_SENTINEL(obj->pool, POOL);
objnode = (*tmem_hostops.objnode_alloc)(obj->pool);
if (unlikely(objnode == NULL))
goto out;
objnode->obj = obj;
SET_SENTINEL(objnode, OBJNODE);
memset(&objnode->slots, 0, sizeof(objnode->slots));
objnode->slots_in_use = 0;
obj->objnode_count++;
out:
return objnode;
}
static void tmem_objnode_free(struct tmem_objnode *objnode)
{
struct tmem_pool *pool;
int i;
BUG_ON(objnode == NULL);
for (i = 0; i < OBJNODE_TREE_MAP_SIZE; i++)
BUG_ON(objnode->slots[i] != NULL);
ASSERT_SENTINEL(objnode, OBJNODE);
INVERT_SENTINEL(objnode, OBJNODE);
BUG_ON(objnode->obj == NULL);
ASSERT_SENTINEL(objnode->obj, OBJ);
pool = objnode->obj->pool;
BUG_ON(pool == NULL);
ASSERT_SENTINEL(pool, POOL);
objnode->obj->objnode_count--;
objnode->obj = NULL;
(*tmem_hostops.objnode_free)(objnode, pool);
}
/*
* Lookup index in object and return associated pampd (or NULL if not found).
*/
static void **__tmem_pampd_lookup_in_obj(struct tmem_obj *obj, uint32_t index)
{
unsigned int height, shift;
struct tmem_objnode **slot = NULL;
BUG_ON(obj == NULL);
ASSERT_SENTINEL(obj, OBJ);
BUG_ON(obj->pool == NULL);
ASSERT_SENTINEL(obj->pool, POOL);
height = obj->objnode_tree_height;
if (index > tmem_objnode_tree_h2max[obj->objnode_tree_height])
goto out;
if (height == 0 && obj->objnode_tree_root) {
slot = &obj->objnode_tree_root;
goto out;
}
shift = (height-1) * OBJNODE_TREE_MAP_SHIFT;
slot = &obj->objnode_tree_root;
while (height > 0) {
if (*slot == NULL)
goto out;
slot = (struct tmem_objnode **)
((*slot)->slots +
((index >> shift) & OBJNODE_TREE_MAP_MASK));
shift -= OBJNODE_TREE_MAP_SHIFT;
height--;
}
out:
return slot != NULL ? (void **)slot : NULL;
}
static void *tmem_pampd_lookup_in_obj(struct tmem_obj *obj, uint32_t index)
{
struct tmem_objnode **slot;
slot = (struct tmem_objnode **)__tmem_pampd_lookup_in_obj(obj, index);
return slot != NULL ? *slot : NULL;
}
#ifdef CONFIG_RAMSTER
static void *tmem_pampd_replace_in_obj(struct tmem_obj *obj, uint32_t index,
void *new_pampd, bool no_free)
{
struct tmem_objnode **slot;
void *ret = NULL;
slot = (struct tmem_objnode **)__tmem_pampd_lookup_in_obj(obj, index);
if ((slot != NULL) && (*slot != NULL)) {
void *old_pampd = *(void **)slot;
*(void **)slot = new_pampd;
if (!no_free)
(*tmem_pamops.free)(old_pampd, obj->pool,
NULL, 0, false);
ret = new_pampd;
}
return ret;
}
#endif
static int tmem_pampd_add_to_obj(struct tmem_obj *obj, uint32_t index,
void *pampd)
{
int ret = 0;
struct tmem_objnode *objnode = NULL, *newnode, *slot;
unsigned int height, shift;
int offset = 0;
/* if necessary, extend the tree to be higher */
if (index > tmem_objnode_tree_h2max[obj->objnode_tree_height]) {
height = obj->objnode_tree_height + 1;
if (index > tmem_objnode_tree_h2max[height])
while (index > tmem_objnode_tree_h2max[height])
height++;
if (obj->objnode_tree_root == NULL) {
obj->objnode_tree_height = height;
goto insert;
}
do {
newnode = tmem_objnode_alloc(obj);
if (!newnode) {
ret = -ENOMEM;
goto out;
}
newnode->slots[0] = obj->objnode_tree_root;
newnode->slots_in_use = 1;
obj->objnode_tree_root = newnode;
obj->objnode_tree_height++;
} while (height > obj->objnode_tree_height);
}
insert:
slot = obj->objnode_tree_root;
height = obj->objnode_tree_height;
shift = (height-1) * OBJNODE_TREE_MAP_SHIFT;
while (height > 0) {
if (slot == NULL) {
/* add a child objnode. */
slot = tmem_objnode_alloc(obj);
if (!slot) {
ret = -ENOMEM;
goto out;
}
if (objnode) {
objnode->slots[offset] = slot;
objnode->slots_in_use++;
} else
obj->objnode_tree_root = slot;
}
/* go down a level */
offset = (index >> shift) & OBJNODE_TREE_MAP_MASK;
objnode = slot;
slot = objnode->slots[offset];
shift -= OBJNODE_TREE_MAP_SHIFT;
height--;
}
BUG_ON(slot != NULL);
if (objnode) {
objnode->slots_in_use++;
objnode->slots[offset] = pampd;
} else
obj->objnode_tree_root = pampd;
obj->pampd_count++;
out:
return ret;
}
static void *tmem_pampd_delete_from_obj(struct tmem_obj *obj, uint32_t index)
{
struct tmem_objnode_tree_path path[OBJNODE_TREE_MAX_PATH + 1];
struct tmem_objnode_tree_path *pathp = path;
struct tmem_objnode *slot = NULL;
unsigned int height, shift;
int offset;
BUG_ON(obj == NULL);
ASSERT_SENTINEL(obj, OBJ);
BUG_ON(obj->pool == NULL);
ASSERT_SENTINEL(obj->pool, POOL);
height = obj->objnode_tree_height;
if (index > tmem_objnode_tree_h2max[height])
goto out;
slot = obj->objnode_tree_root;
if (height == 0 && obj->objnode_tree_root) {
obj->objnode_tree_root = NULL;
goto out;
}
shift = (height - 1) * OBJNODE_TREE_MAP_SHIFT;
pathp->objnode = NULL;
do {
if (slot == NULL)
goto out;
pathp++;
offset = (index >> shift) & OBJNODE_TREE_MAP_MASK;
pathp->offset = offset;
pathp->objnode = slot;
slot = slot->slots[offset];
shift -= OBJNODE_TREE_MAP_SHIFT;
height--;
} while (height > 0);
if (slot == NULL)
goto out;
while (pathp->objnode) {
pathp->objnode->slots[pathp->offset] = NULL;
pathp->objnode->slots_in_use--;
if (pathp->objnode->slots_in_use) {
if (pathp->objnode == obj->objnode_tree_root) {
while (obj->objnode_tree_height > 0 &&
obj->objnode_tree_root->slots_in_use == 1 &&
obj->objnode_tree_root->slots[0]) {
struct tmem_objnode *to_free =
obj->objnode_tree_root;
obj->objnode_tree_root =
to_free->slots[0];
obj->objnode_tree_height--;
to_free->slots[0] = NULL;
to_free->slots_in_use = 0;
tmem_objnode_free(to_free);
}
}
goto out;
}
tmem_objnode_free(pathp->objnode); /* 0 slots used, free it */
pathp--;
}
obj->objnode_tree_height = 0;
obj->objnode_tree_root = NULL;
out:
if (slot != NULL)
obj->pampd_count--;
BUG_ON(obj->pampd_count < 0);
return slot;
}
/* Recursively walk the objnode_tree destroying pampds and objnodes. */
static void tmem_objnode_node_destroy(struct tmem_obj *obj,
struct tmem_objnode *objnode,
unsigned int ht)
{
int i;
if (ht == 0)
return;
for (i = 0; i < OBJNODE_TREE_MAP_SIZE; i++) {
if (objnode->slots[i]) {
if (ht == 1) {
obj->pampd_count--;
(*tmem_pamops.free)(objnode->slots[i],
obj->pool, NULL, 0, true);
objnode->slots[i] = NULL;
continue;
}
tmem_objnode_node_destroy(obj, objnode->slots[i], ht-1);
tmem_objnode_free(objnode->slots[i]);
objnode->slots[i] = NULL;
}
}
}
static void tmem_pampd_destroy_all_in_obj(struct tmem_obj *obj,
bool pool_destroy)
{
if (obj->objnode_tree_root == NULL)
return;
if (obj->objnode_tree_height == 0) {
obj->pampd_count--;
(*tmem_pamops.free)(obj->objnode_tree_root,
obj->pool, NULL, 0, true);
} else {
tmem_objnode_node_destroy(obj, obj->objnode_tree_root,
obj->objnode_tree_height);
tmem_objnode_free(obj->objnode_tree_root);
obj->objnode_tree_height = 0;
}
obj->objnode_tree_root = NULL;
#ifdef CONFIG_RAMSTER
if (tmem_pamops.free_obj != NULL)
(*tmem_pamops.free_obj)(obj->pool, obj, pool_destroy);
#endif
}
/*
* Tmem is operated on by a set of well-defined actions:
* "put", "get", "flush", "flush_object", "new pool" and "destroy pool".
* (The tmem ABI allows for subpages and exchanges but these operations
* are not included in this implementation.)
*
* These "tmem core" operations are implemented in the following functions.
*/
/*
* "Put" a page, e.g. associate the passed pampd with the passed handle.
* Tmem_put is complicated by a corner case: What if a page with matching
* handle already exists in tmem? To guarantee coherency, one of two
* actions is necessary: Either the data for the page must be overwritten,
* or the page must be "flushed" so that the data is not accessible to a
* subsequent "get". Since these "duplicate puts" are relatively rare,
* this implementation always flushes for simplicity.
*/
int tmem_put(struct tmem_pool *pool, struct tmem_oid *oidp, uint32_t index,
bool raw, void *pampd_to_use)
{
struct tmem_obj *obj = NULL, *objfound = NULL, *objnew = NULL;
void *pampd = NULL, *pampd_del = NULL;
int ret = -ENOMEM;
struct tmem_hashbucket *hb;
hb = &pool->hashbucket[tmem_oid_hash(oidp)];
spin_lock(&hb->lock);
obj = objfound = tmem_obj_find(hb, oidp);
if (obj != NULL) {
pampd = tmem_pampd_lookup_in_obj(objfound, index);
if (pampd != NULL) {
/* if found, is a dup put, flush the old one */
pampd_del = tmem_pampd_delete_from_obj(obj, index);
BUG_ON(pampd_del != pampd);
(*tmem_pamops.free)(pampd, pool, oidp, index, true);
if (obj->pampd_count == 0) {
objnew = obj;
objfound = NULL;
}
pampd = NULL;
}
} else {
obj = objnew = (*tmem_hostops.obj_alloc)(pool);
if (unlikely(obj == NULL)) {
ret = -ENOMEM;
goto out;
}
tmem_obj_init(obj, hb, pool, oidp);
}
BUG_ON(obj == NULL);
BUG_ON(((objnew != obj) && (objfound != obj)) || (objnew == objfound));
pampd = pampd_to_use;
BUG_ON(pampd_to_use == NULL);
ret = tmem_pampd_add_to_obj(obj, index, pampd);
if (unlikely(ret == -ENOMEM))
/* may have partially built objnode tree ("stump") */
goto delete_and_free;
(*tmem_pamops.create_finish)(pampd, is_ephemeral(pool));
goto out;
delete_and_free:
(void)tmem_pampd_delete_from_obj(obj, index);
if (pampd)
(*tmem_pamops.free)(pampd, pool, NULL, 0, true);
if (objnew) {
tmem_obj_free(objnew, hb);
(*tmem_hostops.obj_free)(objnew, pool);
}
out:
spin_unlock(&hb->lock);
return ret;
}
#ifdef CONFIG_RAMSTER
/*
* For ramster only: The following routines provide a two-step sequence
* to allow the caller to replace a pampd in the tmem data structures with
* another pampd. Here, we lookup the passed handle and, if found, return the
* associated pampd and object, leaving the hashbucket locked and returning
* a reference to it. The caller is expected to immediately call the
* matching tmem_localify_finish routine which will handles the replacement
* and unlocks the hashbucket.
*/
void *tmem_localify_get_pampd(struct tmem_pool *pool, struct tmem_oid *oidp,
uint32_t index, struct tmem_obj **ret_obj,
void **saved_hb)
{
struct tmem_hashbucket *hb;
struct tmem_obj *obj = NULL;
void *pampd = NULL;
hb = &pool->hashbucket[tmem_oid_hash(oidp)];
spin_lock(&hb->lock);
obj = tmem_obj_find(hb, oidp);
if (likely(obj != NULL))
pampd = tmem_pampd_lookup_in_obj(obj, index);
*ret_obj = obj;
*saved_hb = (void *)hb;
/* note, hashbucket remains locked */
return pampd;
}
EXPORT_SYMBOL_GPL(tmem_localify_get_pampd);
void tmem_localify_finish(struct tmem_obj *obj, uint32_t index,
void *pampd, void *saved_hb, bool delete)
{
struct tmem_hashbucket *hb = (struct tmem_hashbucket *)saved_hb;
BUG_ON(!spin_is_locked(&hb->lock));
if (pampd != NULL) {
BUG_ON(obj == NULL);
(void)tmem_pampd_replace_in_obj(obj, index, pampd, 1);
(*tmem_pamops.create_finish)(pampd, is_ephemeral(obj->pool));
} else if (delete) {
BUG_ON(obj == NULL);
(void)tmem_pampd_delete_from_obj(obj, index);
}
spin_unlock(&hb->lock);
}
EXPORT_SYMBOL_GPL(tmem_localify_finish);
/*
* For ramster only. Helper function to support asynchronous tmem_get.
*/
static int tmem_repatriate(void **ppampd, struct tmem_hashbucket *hb,
struct tmem_pool *pool, struct tmem_oid *oidp,
uint32_t index, bool free, char *data)
{
void *old_pampd = *ppampd, *new_pampd = NULL;
bool intransit = false;
int ret = 0;
if (!is_ephemeral(pool))
new_pampd = (*tmem_pamops.repatriate_preload)(
old_pampd, pool, oidp, index, &intransit);
if (intransit)
ret = -EAGAIN;
else if (new_pampd != NULL)
*ppampd = new_pampd;
/* must release the hb->lock else repatriate can't sleep */
spin_unlock(&hb->lock);
if (!intransit)
ret = (*tmem_pamops.repatriate)(old_pampd, new_pampd, pool,
oidp, index, free, data);
if (ret == -EAGAIN) {
/* rare I think, but should cond_resched()??? */
usleep_range(10, 1000);
} else if (ret == -ENOTCONN || ret == -EHOSTDOWN) {
ret = -1;
} else if (ret != 0 && ret != -ENOENT) {
ret = -1;
}
/* note hb->lock has now been unlocked */
return ret;
}
/*
* For ramster only. If a page in tmem matches the handle, replace the
* page so that any subsequent "get" gets the new page. Returns 0 if
* there was a page to replace, else returns -1.
*/
int tmem_replace(struct tmem_pool *pool, struct tmem_oid *oidp,
uint32_t index, void *new_pampd)
{
struct tmem_obj *obj;
int ret = -1;
struct tmem_hashbucket *hb;
hb = &pool->hashbucket[tmem_oid_hash(oidp)];
spin_lock(&hb->lock);
obj = tmem_obj_find(hb, oidp);
if (obj == NULL)
goto out;
new_pampd = tmem_pampd_replace_in_obj(obj, index, new_pampd, 0);
/* if we bug here, pamops wasn't properly set up for ramster */
BUG_ON(tmem_pamops.replace_in_obj == NULL);
ret = (*tmem_pamops.replace_in_obj)(new_pampd, obj);
out:
spin_unlock(&hb->lock);
return ret;
}
EXPORT_SYMBOL_GPL(tmem_replace);
#endif
/*
* "Get" a page, e.g. if a pampd can be found matching the passed handle,
* use a pamops callback to recreated the page from the pampd with the
* matching handle. By tmem definition, when a "get" is successful on
* an ephemeral page, the page is "flushed", and when a "get" is successful
* on a persistent page, the page is retained in tmem. Note that to preserve
* coherency, "get" can never be skipped if tmem contains the data.
* That is, if a get is done with a certain handle and fails, any
* subsequent "get" must also fail (unless of course there is a
* "put" done with the same handle).
*/
int tmem_get(struct tmem_pool *pool, struct tmem_oid *oidp, uint32_t index,
char *data, size_t *sizep, bool raw, int get_and_free)
{
struct tmem_obj *obj;
void *pampd = NULL;
bool ephemeral = is_ephemeral(pool);
int ret = -1;
struct tmem_hashbucket *hb;
bool free = (get_and_free == 1) || ((get_and_free == 0) && ephemeral);
bool lock_held = false;
void **ppampd;
do {
hb = &pool->hashbucket[tmem_oid_hash(oidp)];
spin_lock(&hb->lock);
lock_held = true;
obj = tmem_obj_find(hb, oidp);
if (obj == NULL)
goto out;
ppampd = __tmem_pampd_lookup_in_obj(obj, index);
if (ppampd == NULL)
goto out;
#ifdef CONFIG_RAMSTER
if ((tmem_pamops.is_remote != NULL) &&
tmem_pamops.is_remote(*ppampd)) {
ret = tmem_repatriate(ppampd, hb, pool, oidp,
index, free, data);
/* tmem_repatriate releases hb->lock */
lock_held = false;
*sizep = PAGE_SIZE;
if (ret != -EAGAIN)
goto out;
}
#endif
} while (ret == -EAGAIN);
if (free)
pampd = tmem_pampd_delete_from_obj(obj, index);
else
pampd = tmem_pampd_lookup_in_obj(obj, index);
if (pampd == NULL)
goto out;
if (free) {
if (obj->pampd_count == 0) {
tmem_obj_free(obj, hb);
(*tmem_hostops.obj_free)(obj, pool);
obj = NULL;
}
}
if (free)
ret = (*tmem_pamops.get_data_and_free)(
data, sizep, raw, pampd, pool, oidp, index);
else
ret = (*tmem_pamops.get_data)(
data, sizep, raw, pampd, pool, oidp, index);
if (ret < 0)
goto out;
ret = 0;
out:
if (lock_held)
spin_unlock(&hb->lock);
return ret;
}
/*
* If a page in tmem matches the handle, "flush" this page from tmem such
* that any subsequent "get" does not succeed (unless, of course, there
* was another "put" with the same handle).
*/
int tmem_flush_page(struct tmem_pool *pool,
struct tmem_oid *oidp, uint32_t index)
{
struct tmem_obj *obj;
void *pampd;
int ret = -1;
struct tmem_hashbucket *hb;
hb = &pool->hashbucket[tmem_oid_hash(oidp)];
spin_lock(&hb->lock);
obj = tmem_obj_find(hb, oidp);
if (obj == NULL)
goto out;
pampd = tmem_pampd_delete_from_obj(obj, index);
if (pampd == NULL)
goto out;
(*tmem_pamops.free)(pampd, pool, oidp, index, true);
if (obj->pampd_count == 0) {
tmem_obj_free(obj, hb);
(*tmem_hostops.obj_free)(obj, pool);
}
ret = 0;
out:
spin_unlock(&hb->lock);
return ret;
}
/*
* "Flush" all pages in tmem matching this oid.
*/
int tmem_flush_object(struct tmem_pool *pool, struct tmem_oid *oidp)
{
struct tmem_obj *obj;
struct tmem_hashbucket *hb;
int ret = -1;
hb = &pool->hashbucket[tmem_oid_hash(oidp)];
spin_lock(&hb->lock);
obj = tmem_obj_find(hb, oidp);
if (obj == NULL)
goto out;
tmem_pampd_destroy_all_in_obj(obj, false);
tmem_obj_free(obj, hb);
(*tmem_hostops.obj_free)(obj, pool);
ret = 0;
out:
spin_unlock(&hb->lock);
return ret;
}
/*
* "Flush" all pages (and tmem_objs) from this tmem_pool and disable
* all subsequent access to this tmem_pool.
*/
int tmem_destroy_pool(struct tmem_pool *pool)
{
int ret = -1;
if (pool == NULL)
goto out;
tmem_pool_flush(pool, 1);
ret = 0;
out:
return ret;
}
static LIST_HEAD(tmem_global_pool_list);
/*
* Create a new tmem_pool with the provided flag and return
* a pool id provided by the tmem host implementation.
*/
void tmem_new_pool(struct tmem_pool *pool, uint32_t flags)
{
int persistent = flags & TMEM_POOL_PERSIST;
int shared = flags & TMEM_POOL_SHARED;
struct tmem_hashbucket *hb = &pool->hashbucket[0];
int i;
for (i = 0; i < TMEM_HASH_BUCKETS; i++, hb++) {
hb->obj_rb_root = RB_ROOT;
spin_lock_init(&hb->lock);
}
INIT_LIST_HEAD(&pool->pool_list);
atomic_set(&pool->obj_count, 0);
SET_SENTINEL(pool, POOL);
list_add_tail(&pool->pool_list, &tmem_global_pool_list);
pool->persistent = persistent;
pool->shared = shared;
}
/*
* tmem.h
*
* Transcendent memory
*
* Copyright (c) 2009-2012, Dan Magenheimer, Oracle Corp.
*/
#ifndef _TMEM_H_
#define _TMEM_H_
#include <linux/types.h>
#include <linux/highmem.h>
#include <linux/hash.h>
#include <linux/atomic.h>
/*
* These are defined by the Xen<->Linux ABI so should remain consistent
*/
#define TMEM_POOL_PERSIST 1
#define TMEM_POOL_SHARED 2
#define TMEM_POOL_PRECOMPRESSED 4
#define TMEM_POOL_PAGESIZE_SHIFT 4
#define TMEM_POOL_PAGESIZE_MASK 0xf
#define TMEM_POOL_RESERVED_BITS 0x00ffff00
/*
* sentinels have proven very useful for debugging but can be removed
* or disabled before final merge.
*/
#undef SENTINELS
#ifdef SENTINELS
#define DECL_SENTINEL uint32_t sentinel;
#define SET_SENTINEL(_x, _y) (_x->sentinel = _y##_SENTINEL)
#define INVERT_SENTINEL(_x, _y) (_x->sentinel = ~_y##_SENTINEL)
#define ASSERT_SENTINEL(_x, _y) WARN_ON(_x->sentinel != _y##_SENTINEL)
#define ASSERT_INVERTED_SENTINEL(_x, _y) WARN_ON(_x->sentinel != ~_y##_SENTINEL)
#else
#define DECL_SENTINEL
#define SET_SENTINEL(_x, _y) do { } while (0)
#define INVERT_SENTINEL(_x, _y) do { } while (0)
#define ASSERT_SENTINEL(_x, _y) do { } while (0)
#define ASSERT_INVERTED_SENTINEL(_x, _y) do { } while (0)
#endif
#define ASSERT_SPINLOCK(_l) lockdep_assert_held(_l)
/*
* A pool is the highest-level data structure managed by tmem and
* usually corresponds to a large independent set of pages such as
* a filesystem. Each pool has an id, and certain attributes and counters.
* It also contains a set of hash buckets, each of which contains an rbtree
* of objects and a lock to manage concurrency within the pool.
*/
#define TMEM_HASH_BUCKET_BITS 8
#define TMEM_HASH_BUCKETS (1<<TMEM_HASH_BUCKET_BITS)
struct tmem_hashbucket {
struct rb_root obj_rb_root;
spinlock_t lock;
};
struct tmem_pool {
void *client; /* "up" for some clients, avoids table lookup */
struct list_head pool_list;
uint32_t pool_id;
bool persistent;
bool shared;
atomic_t obj_count;
atomic_t refcount;
struct tmem_hashbucket hashbucket[TMEM_HASH_BUCKETS];
DECL_SENTINEL
};
#define is_persistent(_p) (_p->persistent)
#define is_ephemeral(_p) (!(_p->persistent))
/*
* An object id ("oid") is large: 192-bits (to ensure, for example, files
* in a modern filesystem can be uniquely identified).
*/
struct tmem_oid {
uint64_t oid[3];
};
static inline void tmem_oid_set_invalid(struct tmem_oid *oidp)
{
oidp->oid[0] = oidp->oid[1] = oidp->oid[2] = -1UL;
}
static inline bool tmem_oid_valid(struct tmem_oid *oidp)
{
return oidp->oid[0] != -1UL || oidp->oid[1] != -1UL ||
oidp->oid[2] != -1UL;
}
static inline int tmem_oid_compare(struct tmem_oid *left,
struct tmem_oid *right)
{
int ret;
if (left->oid[2] == right->oid[2]) {
if (left->oid[1] == right->oid[1]) {
if (left->oid[0] == right->oid[0])
ret = 0;
else if (left->oid[0] < right->oid[0])
ret = -1;
else
return 1;
} else if (left->oid[1] < right->oid[1])
ret = -1;
else
ret = 1;
} else if (left->oid[2] < right->oid[2])
ret = -1;
else
ret = 1;
return ret;
}
static inline unsigned tmem_oid_hash(struct tmem_oid *oidp)
{
return hash_long(oidp->oid[0] ^ oidp->oid[1] ^ oidp->oid[2],
TMEM_HASH_BUCKET_BITS);
}
#if defined(CONFIG_RAMSTER) || defined(CONFIG_RAMSTER_MODULE)
struct tmem_xhandle {
uint8_t client_id;
uint8_t xh_data_cksum;
uint16_t xh_data_size;
uint16_t pool_id;
struct tmem_oid oid;
uint32_t index;
void *extra;
};
static inline struct tmem_xhandle tmem_xhandle_fill(uint16_t client_id,
struct tmem_pool *pool,
struct tmem_oid *oidp,
uint32_t index)
{
struct tmem_xhandle xh;
xh.client_id = client_id;
xh.xh_data_cksum = (uint8_t)-1;
xh.xh_data_size = (uint16_t)-1;
xh.pool_id = pool->pool_id;
xh.oid = *oidp;
xh.index = index;
return xh;
}
#endif
/*
* A tmem_obj contains an identifier (oid), pointers to the parent
* pool and the rb_tree to which it belongs, counters, and an ordered
* set of pampds, structured in a radix-tree-like tree. The intermediate
* nodes of the tree are called tmem_objnodes.
*/
struct tmem_objnode;
struct tmem_obj {
struct tmem_oid oid;
struct tmem_pool *pool;
struct rb_node rb_tree_node;
struct tmem_objnode *objnode_tree_root;
unsigned int objnode_tree_height;
unsigned long objnode_count;
long pampd_count;
#if defined(CONFIG_RAMSTER) || defined(CONFIG_RAMSTER_MODULE)
/*
* for current design of ramster, all pages belonging to
* an object reside on the same remotenode and extra is
* used to record the number of the remotenode so a
* flush-object operation can specify it
*/
void *extra; /* for private use by pampd implementation */
#endif
DECL_SENTINEL
};
#define OBJNODE_TREE_MAP_SHIFT 6
#define OBJNODE_TREE_MAP_SIZE (1UL << OBJNODE_TREE_MAP_SHIFT)
#define OBJNODE_TREE_MAP_MASK (OBJNODE_TREE_MAP_SIZE-1)
#define OBJNODE_TREE_INDEX_BITS (8 /* CHAR_BIT */ * sizeof(unsigned long))
#define OBJNODE_TREE_MAX_PATH \
(OBJNODE_TREE_INDEX_BITS/OBJNODE_TREE_MAP_SHIFT + 2)
struct tmem_objnode {
struct tmem_obj *obj;
DECL_SENTINEL
void *slots[OBJNODE_TREE_MAP_SIZE];
unsigned int slots_in_use;
};
struct tmem_handle {
struct tmem_oid oid; /* 24 bytes */
uint32_t index;
uint16_t pool_id;
uint16_t client_id;
};
/* pampd abstract datatype methods provided by the PAM implementation */
struct tmem_pamops {
void (*create_finish)(void *, bool);
int (*get_data)(char *, size_t *, bool, void *, struct tmem_pool *,
struct tmem_oid *, uint32_t);
int (*get_data_and_free)(char *, size_t *, bool, void *,
struct tmem_pool *, struct tmem_oid *,
uint32_t);
void (*free)(void *, struct tmem_pool *,
struct tmem_oid *, uint32_t, bool);
#if defined(CONFIG_RAMSTER) || defined(CONFIG_RAMSTER_MODULE)
void (*new_obj)(struct tmem_obj *);
void (*free_obj)(struct tmem_pool *, struct tmem_obj *, bool);
void *(*repatriate_preload)(void *, struct tmem_pool *,
struct tmem_oid *, uint32_t, bool *);
int (*repatriate)(void *, void *, struct tmem_pool *,
struct tmem_oid *, uint32_t, bool, void *);
bool (*is_remote)(void *);
int (*replace_in_obj)(void *, struct tmem_obj *);
#endif
};
extern void tmem_register_pamops(struct tmem_pamops *m);
/* memory allocation methods provided by the host implementation */
struct tmem_hostops {
struct tmem_obj *(*obj_alloc)(struct tmem_pool *);
void (*obj_free)(struct tmem_obj *, struct tmem_pool *);
struct tmem_objnode *(*objnode_alloc)(struct tmem_pool *);
void (*objnode_free)(struct tmem_objnode *, struct tmem_pool *);
};
extern void tmem_register_hostops(struct tmem_hostops *m);
/* core tmem accessor functions */
extern int tmem_put(struct tmem_pool *, struct tmem_oid *, uint32_t index,
bool, void *);
extern int tmem_get(struct tmem_pool *, struct tmem_oid *, uint32_t index,
char *, size_t *, bool, int);
extern int tmem_flush_page(struct tmem_pool *, struct tmem_oid *,
uint32_t index);
extern int tmem_flush_object(struct tmem_pool *, struct tmem_oid *);
extern int tmem_destroy_pool(struct tmem_pool *);
extern void tmem_new_pool(struct tmem_pool *, uint32_t);
#if defined(CONFIG_RAMSTER) || defined(CONFIG_RAMSTER_MODULE)
extern int tmem_replace(struct tmem_pool *, struct tmem_oid *, uint32_t index,
void *);
extern void *tmem_localify_get_pampd(struct tmem_pool *, struct tmem_oid *,
uint32_t index, struct tmem_obj **,
void **);
extern void tmem_localify_finish(struct tmem_obj *, uint32_t index,
void *, void *, bool);
#endif
#endif /* _TMEM_H */
/*
* zbud.c - Compression buddies allocator
*
* Copyright (c) 2010-2012, Dan Magenheimer, Oracle Corp.
*
* Compression buddies ("zbud") provides for efficiently packing two
* (or, possibly in the future, more) compressed pages ("zpages") into
* a single "raw" pageframe and for tracking both zpages and pageframes
* so that whole pageframes can be easily reclaimed in LRU-like order.
* It is designed to be used in conjunction with transcendent memory
* ("tmem"); for example separate LRU lists are maintained for persistent
* vs. ephemeral pages.
*
* A zbudpage is an overlay for a struct page and thus each zbudpage
* refers to a physical pageframe of RAM. When the caller passes a
* struct page from the kernel's page allocator, zbud "transforms" it
* to a zbudpage which sets/uses a different set of fields than the
* struct-page and thus must "untransform" it back by reinitializing
* certain fields before the struct-page can be freed. The fields
* of a zbudpage include a page lock for controlling access to the
* corresponding pageframe, and there is a size field for each zpage.
* Each zbudpage also lives on two linked lists: a "budlist" which is
* used to support efficient buddying of zpages; and an "lru" which
* is used for reclaiming pageframes in approximately least-recently-used
* order.
*
* A zbudpageframe is a pageframe divided up into aligned 64-byte "chunks"
* which contain the compressed data for zero, one, or two zbuds. Contained
* with the compressed data is a tmem_handle which is a key to allow
* the same data to be found via the tmem interface so the zpage can
* be invalidated (for ephemeral pages) or repatriated to the swap cache
* (for persistent pages). The contents of a zbudpageframe must never
* be accessed without holding the page lock for the corresponding
* zbudpage and, to accomodate highmem machines, the contents may
* only be examined or changes when kmapped. Thus, when in use, a
* kmapped zbudpageframe is referred to in the zbud code as "void *zbpg".
*
* Note that the term "zbud" refers to the combination of a zpage and
* a tmem_handle that is stored as one of possibly two "buddied" zpages;
* it also generically refers to this allocator... sorry for any confusion.
*
* A zbudref is a pointer to a struct zbudpage (which can be cast to a
* struct page), with the LSB either cleared or set to indicate, respectively,
* the first or second zpage in the zbudpageframe. Since a zbudref can be
* cast to a pointer, it is used as the tmem "pampd" pointer and uniquely
* references a stored tmem page and so is the only zbud data structure
* externally visible to zbud.c/zbud.h.
*
* Since we wish to reclaim entire pageframes but zpages may be randomly
* added and deleted to any given pageframe, we approximate LRU by
* promoting a pageframe to MRU when a zpage is added to it, but
* leaving it at the current place in the list when a zpage is deleted
* from it. As a side effect, zpages that are difficult to buddy (e.g.
* very large paages) will be reclaimed faster than average, which seems
* reasonable.
*
* In the current implementation, no more than two zpages may be stored in
* any pageframe and no zpage ever crosses a pageframe boundary. While
* other zpage allocation mechanisms may allow greater density, this two
* zpage-per-pageframe limit both ensures simple reclaim of pageframes
* (including garbage collection of references to the contents of those
* pageframes from tmem data structures) AND avoids the need for compaction.
* With additional complexity, zbud could be modified to support storing
* up to three zpages per pageframe or, to handle larger average zpages,
* up to three zpages per pair of pageframes, but it is not clear if the
* additional complexity would be worth it. So consider it an exercise
* for future developers.
*
* Note also that zbud does no page allocation or freeing. This is so
* that the caller has complete control over and, for accounting, visibility
* into if/when pages are allocated and freed.
*
* Finally, note that zbud limits the size of zpages it can store; the
* caller must check the zpage size with zbud_max_buddy_size before
* storing it, else BUGs will result. User beware.
*/
#include <linux/module.h>
#include <linux/highmem.h>
#include <linux/list.h>
#include <linux/spinlock.h>
#include <linux/pagemap.h>
#include <linux/atomic.h>
#include <linux/bug.h>
#include "tmem.h"
#include "zcache.h"
#include "zbud.h"
/*
* We need to ensure that a struct zbudpage is never larger than a
* struct page. This is checked with a BUG_ON in zbud_init.
*
* The unevictable field indicates that a zbud is being added to the
* zbudpage. Since this is a two-phase process (due to tmem locking),
* this field locks the zbudpage against eviction when a zbud match
* or creation is in process. Since this addition process may occur
* in parallel for two zbuds in one zbudpage, the field is a counter
* that must not exceed two.
*/
struct zbudpage {
union {
struct page page;
struct {
unsigned long space_for_flags;
struct {
unsigned zbud0_size:PAGE_SHIFT;
unsigned zbud1_size:PAGE_SHIFT;
unsigned unevictable:2;
};
struct list_head budlist;
struct list_head lru;
};
};
};
#if (PAGE_SHIFT * 2) + 2 > BITS_PER_LONG
#error "zbud won't work for this arch, PAGE_SIZE is too large"
#endif
struct zbudref {
union {
struct zbudpage *zbudpage;
unsigned long zbudref;
};
};
#define CHUNK_SHIFT 6
#define CHUNK_SIZE (1 << CHUNK_SHIFT)
#define CHUNK_MASK (~(CHUNK_SIZE-1))
#define NCHUNKS (PAGE_SIZE >> CHUNK_SHIFT)
#define MAX_CHUNK (NCHUNKS-1)
/*
* The following functions deal with the difference between struct
* page and struct zbudpage. Note the hack of using the pageflags
* from struct page; this is to avoid duplicating all the complex
* pageflag macros.
*/
static inline void zbudpage_spin_lock(struct zbudpage *zbudpage)
{
struct page *page = (struct page *)zbudpage;
while (unlikely(test_and_set_bit_lock(PG_locked, &page->flags))) {
do {
cpu_relax();
} while (test_bit(PG_locked, &page->flags));
}
}
static inline void zbudpage_spin_unlock(struct zbudpage *zbudpage)
{
struct page *page = (struct page *)zbudpage;
clear_bit(PG_locked, &page->flags);
}
static inline int zbudpage_spin_trylock(struct zbudpage *zbudpage)
{
return trylock_page((struct page *)zbudpage);
}
static inline int zbudpage_is_locked(struct zbudpage *zbudpage)
{
return PageLocked((struct page *)zbudpage);
}
static inline void *kmap_zbudpage_atomic(struct zbudpage *zbudpage)
{
return kmap_atomic((struct page *)zbudpage);
}
/*
* A dying zbudpage is an ephemeral page in the process of being evicted.
* Any data contained in the zbudpage is invalid and we are just waiting for
* the tmem pampds to be invalidated before freeing the page
*/
static inline int zbudpage_is_dying(struct zbudpage *zbudpage)
{
struct page *page = (struct page *)zbudpage;
return test_bit(PG_reclaim, &page->flags);
}
static inline void zbudpage_set_dying(struct zbudpage *zbudpage)
{
struct page *page = (struct page *)zbudpage;
set_bit(PG_reclaim, &page->flags);
}
static inline void zbudpage_clear_dying(struct zbudpage *zbudpage)
{
struct page *page = (struct page *)zbudpage;
clear_bit(PG_reclaim, &page->flags);
}
/*
* A zombie zbudpage is a persistent page in the process of being evicted.
* The data contained in the zbudpage is valid and we are just waiting for
* the tmem pampds to be invalidated before freeing the page
*/
static inline int zbudpage_is_zombie(struct zbudpage *zbudpage)
{
struct page *page = (struct page *)zbudpage;
return test_bit(PG_dirty, &page->flags);
}
static inline void zbudpage_set_zombie(struct zbudpage *zbudpage)
{
struct page *page = (struct page *)zbudpage;
set_bit(PG_dirty, &page->flags);
}
static inline void zbudpage_clear_zombie(struct zbudpage *zbudpage)
{
struct page *page = (struct page *)zbudpage;
clear_bit(PG_dirty, &page->flags);
}
static inline void kunmap_zbudpage_atomic(void *zbpg)
{
kunmap_atomic(zbpg);
}
/*
* zbud "translation" and helper functions
*/
static inline struct zbudpage *zbudref_to_zbudpage(struct zbudref *zref)
{
unsigned long zbud = (unsigned long)zref;
zbud &= ~1UL;
return (struct zbudpage *)zbud;
}
static inline struct zbudref *zbudpage_to_zbudref(struct zbudpage *zbudpage,
unsigned budnum)
{
unsigned long zbud = (unsigned long)zbudpage;
BUG_ON(budnum > 1);
zbud |= budnum;
return (struct zbudref *)zbud;
}
static inline int zbudref_budnum(struct zbudref *zbudref)
{
unsigned long zbud = (unsigned long)zbudref;
return zbud & 1UL;
}
static inline unsigned zbud_max_size(void)
{
return MAX_CHUNK << CHUNK_SHIFT;
}
static inline unsigned zbud_size_to_chunks(unsigned size)
{
BUG_ON(size == 0 || size > zbud_max_size());
return (size + CHUNK_SIZE - 1) >> CHUNK_SHIFT;
}
/* can only be used between kmap_zbudpage_atomic/kunmap_zbudpage_atomic! */
static inline char *zbud_data(void *zbpg,
unsigned budnum, unsigned size)
{
char *p;
BUG_ON(size == 0 || size > zbud_max_size());
p = (char *)zbpg;
if (budnum == 1)
p += PAGE_SIZE - ((size + CHUNK_SIZE - 1) & CHUNK_MASK);
return p;
}
/*
* These are all informative and exposed through debugfs... except for
* the arrays... anyone know how to do that? To avoid confusion for
* debugfs viewers, some of these should also be atomic_long_t, but
* I don't know how to expose atomics via debugfs either...
*/
static ssize_t zbud_eph_pageframes;
static ssize_t zbud_pers_pageframes;
static ssize_t zbud_eph_zpages;
static ssize_t zbud_pers_zpages;
static u64 zbud_eph_zbytes;
static u64 zbud_pers_zbytes;
static ssize_t zbud_eph_evicted_pageframes;
static ssize_t zbud_pers_evicted_pageframes;
static ssize_t zbud_eph_cumul_zpages;
static ssize_t zbud_pers_cumul_zpages;
static u64 zbud_eph_cumul_zbytes;
static u64 zbud_pers_cumul_zbytes;
static ssize_t zbud_eph_cumul_chunk_counts[NCHUNKS];
static ssize_t zbud_pers_cumul_chunk_counts[NCHUNKS];
static ssize_t zbud_eph_buddied_count;
static ssize_t zbud_pers_buddied_count;
static ssize_t zbud_eph_unbuddied_count;
static ssize_t zbud_pers_unbuddied_count;
static ssize_t zbud_eph_zombie_count;
static ssize_t zbud_pers_zombie_count;
static atomic_t zbud_eph_zombie_atomic;
static atomic_t zbud_pers_zombie_atomic;
#ifdef CONFIG_DEBUG_FS
#include <linux/debugfs.h>
#define zdfs debugfs_create_size_t
#define zdfs64 debugfs_create_u64
static int zbud_debugfs_init(void)
{
struct dentry *root = debugfs_create_dir("zbud", NULL);
if (root == NULL)
return -ENXIO;
/*
* would be nice to dump the sizes of the unbuddied
* arrays, like was done with sysfs, but it doesn't
* look like debugfs is flexible enough to do that
*/
zdfs64("eph_zbytes", S_IRUGO, root, &zbud_eph_zbytes);
zdfs64("eph_cumul_zbytes", S_IRUGO, root, &zbud_eph_cumul_zbytes);
zdfs64("pers_zbytes", S_IRUGO, root, &zbud_pers_zbytes);
zdfs64("pers_cumul_zbytes", S_IRUGO, root, &zbud_pers_cumul_zbytes);
zdfs("eph_cumul_zpages", S_IRUGO, root, &zbud_eph_cumul_zpages);
zdfs("eph_evicted_pageframes", S_IRUGO, root,
&zbud_eph_evicted_pageframes);
zdfs("eph_zpages", S_IRUGO, root, &zbud_eph_zpages);
zdfs("eph_pageframes", S_IRUGO, root, &zbud_eph_pageframes);
zdfs("eph_buddied_count", S_IRUGO, root, &zbud_eph_buddied_count);
zdfs("eph_unbuddied_count", S_IRUGO, root, &zbud_eph_unbuddied_count);
zdfs("pers_cumul_zpages", S_IRUGO, root, &zbud_pers_cumul_zpages);
zdfs("pers_evicted_pageframes", S_IRUGO, root,
&zbud_pers_evicted_pageframes);
zdfs("pers_zpages", S_IRUGO, root, &zbud_pers_zpages);
zdfs("pers_pageframes", S_IRUGO, root, &zbud_pers_pageframes);
zdfs("pers_buddied_count", S_IRUGO, root, &zbud_pers_buddied_count);
zdfs("pers_unbuddied_count", S_IRUGO, root, &zbud_pers_unbuddied_count);
zdfs("pers_zombie_count", S_IRUGO, root, &zbud_pers_zombie_count);
return 0;
}
#undef zdfs
#undef zdfs64
#else
static inline int zbud_debugfs_init(void)
{
return 0;
}
#endif
/* protects the buddied list and all unbuddied lists */
static DEFINE_SPINLOCK(zbud_eph_lists_lock);
static DEFINE_SPINLOCK(zbud_pers_lists_lock);
struct zbud_unbuddied {
struct list_head list;
unsigned count;
};
/* list N contains pages with N chunks USED and NCHUNKS-N unused */
/* element 0 is never used but optimizing that isn't worth it */
static struct zbud_unbuddied zbud_eph_unbuddied[NCHUNKS];
static struct zbud_unbuddied zbud_pers_unbuddied[NCHUNKS];
static LIST_HEAD(zbud_eph_lru_list);
static LIST_HEAD(zbud_pers_lru_list);
static LIST_HEAD(zbud_eph_buddied_list);
static LIST_HEAD(zbud_pers_buddied_list);
static LIST_HEAD(zbud_eph_zombie_list);
static LIST_HEAD(zbud_pers_zombie_list);
/*
* Given a struct page, transform it to a zbudpage so that it can be
* used by zbud and initialize fields as necessary.
*/
static inline struct zbudpage *zbud_init_zbudpage(struct page *page, bool eph)
{
struct zbudpage *zbudpage = (struct zbudpage *)page;
BUG_ON(page == NULL);
INIT_LIST_HEAD(&zbudpage->budlist);
INIT_LIST_HEAD(&zbudpage->lru);
zbudpage->zbud0_size = 0;
zbudpage->zbud1_size = 0;
zbudpage->unevictable = 0;
if (eph)
zbud_eph_pageframes++;
else
zbud_pers_pageframes++;
return zbudpage;
}
/* "Transform" a zbudpage back to a struct page suitable to free. */
static inline struct page *zbud_unuse_zbudpage(struct zbudpage *zbudpage,
bool eph)
{
struct page *page = (struct page *)zbudpage;
BUG_ON(!list_empty(&zbudpage->budlist));
BUG_ON(!list_empty(&zbudpage->lru));
BUG_ON(zbudpage->zbud0_size != 0);
BUG_ON(zbudpage->zbud1_size != 0);
BUG_ON(!PageLocked(page));
BUG_ON(zbudpage->unevictable != 0);
BUG_ON(zbudpage_is_dying(zbudpage));
BUG_ON(zbudpage_is_zombie(zbudpage));
if (eph)
zbud_eph_pageframes--;
else
zbud_pers_pageframes--;
zbudpage_spin_unlock(zbudpage);
page_mapcount_reset(page);
init_page_count(page);
page->index = 0;
return page;
}
/* Mark a zbud as unused and do accounting */
static inline void zbud_unuse_zbud(struct zbudpage *zbudpage,
int budnum, bool eph)
{
unsigned size;
BUG_ON(!zbudpage_is_locked(zbudpage));
if (budnum == 0) {
size = zbudpage->zbud0_size;
zbudpage->zbud0_size = 0;
} else {
size = zbudpage->zbud1_size;
zbudpage->zbud1_size = 0;
}
if (eph) {
zbud_eph_zbytes -= size;
zbud_eph_zpages--;
} else {
zbud_pers_zbytes -= size;
zbud_pers_zpages--;
}
}
/*
* Given a zbudpage/budnum/size, a tmem handle, and a kmapped pointer
* to some data, set up the zbud appropriately including data copying
* and accounting. Note that if cdata is NULL, the data copying is
* skipped. (This is useful for lazy writes such as for RAMster.)
*/
static void zbud_init_zbud(struct zbudpage *zbudpage, struct tmem_handle *th,
bool eph, void *cdata,
unsigned budnum, unsigned size)
{
char *to;
void *zbpg;
struct tmem_handle *to_th;
unsigned nchunks = zbud_size_to_chunks(size);
BUG_ON(!zbudpage_is_locked(zbudpage));
zbpg = kmap_zbudpage_atomic(zbudpage);
to = zbud_data(zbpg, budnum, size);
to_th = (struct tmem_handle *)to;
to_th->index = th->index;
to_th->oid = th->oid;
to_th->pool_id = th->pool_id;
to_th->client_id = th->client_id;
to += sizeof(struct tmem_handle);
if (cdata != NULL)
memcpy(to, cdata, size - sizeof(struct tmem_handle));
kunmap_zbudpage_atomic(zbpg);
if (budnum == 0)
zbudpage->zbud0_size = size;
else
zbudpage->zbud1_size = size;
if (eph) {
zbud_eph_cumul_chunk_counts[nchunks]++;
zbud_eph_zpages++;
zbud_eph_cumul_zpages++;
zbud_eph_zbytes += size;
zbud_eph_cumul_zbytes += size;
} else {
zbud_pers_cumul_chunk_counts[nchunks]++;
zbud_pers_zpages++;
zbud_pers_cumul_zpages++;
zbud_pers_zbytes += size;
zbud_pers_cumul_zbytes += size;
}
}
/*
* Given a locked dying zbudpage, read out the tmem handles from the data,
* unlock the page, then use the handles to tell tmem to flush out its
* references
*/
static void zbud_evict_tmem(struct zbudpage *zbudpage)
{
int i, j;
uint32_t pool_id[2], client_id[2];
uint32_t index[2];
struct tmem_oid oid[2];
struct tmem_pool *pool;
void *zbpg;
struct tmem_handle *th;
unsigned size;
/* read out the tmem handles from the data and set aside */
zbpg = kmap_zbudpage_atomic(zbudpage);
for (i = 0, j = 0; i < 2; i++) {
size = (i == 0) ? zbudpage->zbud0_size : zbudpage->zbud1_size;
if (size) {
th = (struct tmem_handle *)zbud_data(zbpg, i, size);
client_id[j] = th->client_id;
pool_id[j] = th->pool_id;
oid[j] = th->oid;
index[j] = th->index;
j++;
zbud_unuse_zbud(zbudpage, i, true);
}
}
kunmap_zbudpage_atomic(zbpg);
zbudpage_spin_unlock(zbudpage);
/* zbudpage is now an unlocked dying... tell tmem to flush pointers */
for (i = 0; i < j; i++) {
pool = zcache_get_pool_by_id(client_id[i], pool_id[i]);
if (pool != NULL) {
tmem_flush_page(pool, &oid[i], index[i]);
zcache_put_pool(pool);
}
}
}
/*
* Externally callable zbud handling routines.
*/
/*
* Return the maximum size compressed page that can be stored (secretly
* setting aside space for the tmem handle.
*/
unsigned int zbud_max_buddy_size(void)
{
return zbud_max_size() - sizeof(struct tmem_handle);
}
/*
* Given a zbud reference, free the corresponding zbud from all lists,
* mark it as unused, do accounting, and if the freeing of the zbud
* frees up an entire pageframe, return it to the caller (else NULL).
*/
struct page *zbud_free_and_delist(struct zbudref *zref, bool eph,
unsigned int *zsize, unsigned int *zpages)
{
unsigned long budnum = zbudref_budnum(zref);
struct zbudpage *zbudpage = zbudref_to_zbudpage(zref);
struct page *page = NULL;
unsigned chunks, bud_size, other_bud_size;
spinlock_t *lists_lock =
eph ? &zbud_eph_lists_lock : &zbud_pers_lists_lock;
struct zbud_unbuddied *unbud =
eph ? zbud_eph_unbuddied : zbud_pers_unbuddied;
spin_lock(lists_lock);
zbudpage_spin_lock(zbudpage);
if (zbudpage_is_dying(zbudpage)) {
/* ignore dying zbudpage... see zbud_evict_pageframe_lru() */
zbudpage_spin_unlock(zbudpage);
spin_unlock(lists_lock);
*zpages = 0;
*zsize = 0;
goto out;
}
if (budnum == 0) {
bud_size = zbudpage->zbud0_size;
other_bud_size = zbudpage->zbud1_size;
} else {
bud_size = zbudpage->zbud1_size;
other_bud_size = zbudpage->zbud0_size;
}
*zsize = bud_size - sizeof(struct tmem_handle);
*zpages = 1;
zbud_unuse_zbud(zbudpage, budnum, eph);
if (other_bud_size == 0) { /* was unbuddied: unlist and free */
chunks = zbud_size_to_chunks(bud_size) ;
if (zbudpage_is_zombie(zbudpage)) {
if (eph)
zbud_pers_zombie_count =
atomic_dec_return(&zbud_eph_zombie_atomic);
else
zbud_pers_zombie_count =
atomic_dec_return(&zbud_pers_zombie_atomic);
zbudpage_clear_zombie(zbudpage);
} else {
BUG_ON(list_empty(&unbud[chunks].list));
list_del_init(&zbudpage->budlist);
unbud[chunks].count--;
}
list_del_init(&zbudpage->lru);
spin_unlock(lists_lock);
if (eph)
zbud_eph_unbuddied_count--;
else
zbud_pers_unbuddied_count--;
page = zbud_unuse_zbudpage(zbudpage, eph);
} else { /* was buddied: move remaining buddy to unbuddied list */
chunks = zbud_size_to_chunks(other_bud_size) ;
if (!zbudpage_is_zombie(zbudpage)) {
list_del_init(&zbudpage->budlist);
list_add_tail(&zbudpage->budlist, &unbud[chunks].list);
unbud[chunks].count++;
}
if (eph) {
zbud_eph_buddied_count--;
zbud_eph_unbuddied_count++;
} else {
zbud_pers_unbuddied_count++;
zbud_pers_buddied_count--;
}
/* don't mess with lru, no need to move it */
zbudpage_spin_unlock(zbudpage);
spin_unlock(lists_lock);
}
out:
return page;
}
/*
* Given a tmem handle, and a kmapped pointer to compressed data of
* the given size, try to find an unbuddied zbudpage in which to
* create a zbud. If found, put it there, mark the zbudpage unevictable,
* and return a zbudref to it. Else return NULL.
*/
struct zbudref *zbud_match_prep(struct tmem_handle *th, bool eph,
void *cdata, unsigned size)
{
struct zbudpage *zbudpage = NULL, *zbudpage2;
unsigned long budnum = 0UL;
unsigned nchunks;
int i, found_good_buddy = 0;
spinlock_t *lists_lock =
eph ? &zbud_eph_lists_lock : &zbud_pers_lists_lock;
struct zbud_unbuddied *unbud =
eph ? zbud_eph_unbuddied : zbud_pers_unbuddied;
size += sizeof(struct tmem_handle);
nchunks = zbud_size_to_chunks(size);
for (i = MAX_CHUNK - nchunks + 1; i > 0; i--) {
spin_lock(lists_lock);
if (!list_empty(&unbud[i].list)) {
list_for_each_entry_safe(zbudpage, zbudpage2,
&unbud[i].list, budlist) {
if (zbudpage_spin_trylock(zbudpage)) {
found_good_buddy = i;
goto found_unbuddied;
}
}
}
spin_unlock(lists_lock);
}
zbudpage = NULL;
goto out;
found_unbuddied:
BUG_ON(!zbudpage_is_locked(zbudpage));
BUG_ON(!((zbudpage->zbud0_size == 0) ^ (zbudpage->zbud1_size == 0)));
if (zbudpage->zbud0_size == 0)
budnum = 0UL;
else if (zbudpage->zbud1_size == 0)
budnum = 1UL;
list_del_init(&zbudpage->budlist);
if (eph) {
list_add_tail(&zbudpage->budlist, &zbud_eph_buddied_list);
unbud[found_good_buddy].count--;
zbud_eph_unbuddied_count--;
zbud_eph_buddied_count++;
/* "promote" raw zbudpage to most-recently-used */
list_del_init(&zbudpage->lru);
list_add_tail(&zbudpage->lru, &zbud_eph_lru_list);
} else {
list_add_tail(&zbudpage->budlist, &zbud_pers_buddied_list);
unbud[found_good_buddy].count--;
zbud_pers_unbuddied_count--;
zbud_pers_buddied_count++;
/* "promote" raw zbudpage to most-recently-used */
list_del_init(&zbudpage->lru);
list_add_tail(&zbudpage->lru, &zbud_pers_lru_list);
}
zbud_init_zbud(zbudpage, th, eph, cdata, budnum, size);
zbudpage->unevictable++;
BUG_ON(zbudpage->unevictable == 3);
zbudpage_spin_unlock(zbudpage);
spin_unlock(lists_lock);
out:
return zbudpage_to_zbudref(zbudpage, budnum);
}
/*
* Given a tmem handle, and a kmapped pointer to compressed data of
* the given size, and a newly allocated struct page, create an unevictable
* zbud in that new page and return a zbudref to it.
*/
struct zbudref *zbud_create_prep(struct tmem_handle *th, bool eph,
void *cdata, unsigned size,
struct page *newpage)
{
struct zbudpage *zbudpage;
unsigned long budnum = 0;
unsigned nchunks;
spinlock_t *lists_lock =
eph ? &zbud_eph_lists_lock : &zbud_pers_lists_lock;
struct zbud_unbuddied *unbud =
eph ? zbud_eph_unbuddied : zbud_pers_unbuddied;
#if 0
/* this may be worth it later to support decompress-in-place? */
static unsigned long counter;
budnum = counter++ & 1; /* alternate using zbud0 and zbud1 */
#endif
if (size > zbud_max_buddy_size())
return NULL;
if (newpage == NULL)
return NULL;
size += sizeof(struct tmem_handle);
nchunks = zbud_size_to_chunks(size) ;
spin_lock(lists_lock);
zbudpage = zbud_init_zbudpage(newpage, eph);
zbudpage_spin_lock(zbudpage);
list_add_tail(&zbudpage->budlist, &unbud[nchunks].list);
if (eph) {
list_add_tail(&zbudpage->lru, &zbud_eph_lru_list);
zbud_eph_unbuddied_count++;
} else {
list_add_tail(&zbudpage->lru, &zbud_pers_lru_list);
zbud_pers_unbuddied_count++;
}
unbud[nchunks].count++;
zbud_init_zbud(zbudpage, th, eph, cdata, budnum, size);
zbudpage->unevictable++;
BUG_ON(zbudpage->unevictable == 3);
zbudpage_spin_unlock(zbudpage);
spin_unlock(lists_lock);
return zbudpage_to_zbudref(zbudpage, budnum);
}
/*
* Finish creation of a zbud by, assuming another zbud isn't being created
* in parallel, marking it evictable.
*/
void zbud_create_finish(struct zbudref *zref, bool eph)
{
struct zbudpage *zbudpage = zbudref_to_zbudpage(zref);
spinlock_t *lists_lock =
eph ? &zbud_eph_lists_lock : &zbud_pers_lists_lock;
spin_lock(lists_lock);
zbudpage_spin_lock(zbudpage);
BUG_ON(zbudpage_is_dying(zbudpage));
zbudpage->unevictable--;
BUG_ON((int)zbudpage->unevictable < 0);
zbudpage_spin_unlock(zbudpage);
spin_unlock(lists_lock);
}
/*
* Given a zbudref and a struct page, decompress the data from
* the zbud into the physical page represented by the struct page
* by upcalling to zcache_decompress
*/
int zbud_decompress(struct page *data_page, struct zbudref *zref, bool eph,
void (*decompress)(char *, unsigned int, char *))
{
struct zbudpage *zbudpage = zbudref_to_zbudpage(zref);
unsigned long budnum = zbudref_budnum(zref);
void *zbpg;
char *to_va, *from_va;
unsigned size;
int ret = -1;
spinlock_t *lists_lock =
eph ? &zbud_eph_lists_lock : &zbud_pers_lists_lock;
spin_lock(lists_lock);
zbudpage_spin_lock(zbudpage);
if (zbudpage_is_dying(zbudpage)) {
/* ignore dying zbudpage... see zbud_evict_pageframe_lru() */
goto out;
}
zbpg = kmap_zbudpage_atomic(zbudpage);
to_va = kmap_atomic(data_page);
if (budnum == 0)
size = zbudpage->zbud0_size;
else
size = zbudpage->zbud1_size;
BUG_ON(size == 0 || size > zbud_max_size());
from_va = zbud_data(zbpg, budnum, size);
from_va += sizeof(struct tmem_handle);
size -= sizeof(struct tmem_handle);
decompress(from_va, size, to_va);
kunmap_atomic(to_va);
kunmap_zbudpage_atomic(zbpg);
ret = 0;
out:
zbudpage_spin_unlock(zbudpage);
spin_unlock(lists_lock);
return ret;
}
/*
* Given a zbudref and a kernel pointer, copy the data from
* the zbud to the kernel pointer.
*/
int zbud_copy_from_zbud(char *to_va, struct zbudref *zref,
size_t *sizep, bool eph)
{
struct zbudpage *zbudpage = zbudref_to_zbudpage(zref);
unsigned long budnum = zbudref_budnum(zref);
void *zbpg;
char *from_va;
unsigned size;
int ret = -1;
spinlock_t *lists_lock =
eph ? &zbud_eph_lists_lock : &zbud_pers_lists_lock;
spin_lock(lists_lock);
zbudpage_spin_lock(zbudpage);
if (zbudpage_is_dying(zbudpage)) {
/* ignore dying zbudpage... see zbud_evict_pageframe_lru() */
goto out;
}
zbpg = kmap_zbudpage_atomic(zbudpage);
if (budnum == 0)
size = zbudpage->zbud0_size;
else
size = zbudpage->zbud1_size;
BUG_ON(size == 0 || size > zbud_max_size());
from_va = zbud_data(zbpg, budnum, size);
from_va += sizeof(struct tmem_handle);
size -= sizeof(struct tmem_handle);
*sizep = size;
memcpy(to_va, from_va, size);
kunmap_zbudpage_atomic(zbpg);
ret = 0;
out:
zbudpage_spin_unlock(zbudpage);
spin_unlock(lists_lock);
return ret;
}
/*
* Given a zbudref and a kernel pointer, copy the data from
* the kernel pointer to the zbud.
*/
int zbud_copy_to_zbud(struct zbudref *zref, char *from_va, bool eph)
{
struct zbudpage *zbudpage = zbudref_to_zbudpage(zref);
unsigned long budnum = zbudref_budnum(zref);
void *zbpg;
char *to_va;
unsigned size;
int ret = -1;
spinlock_t *lists_lock =
eph ? &zbud_eph_lists_lock : &zbud_pers_lists_lock;
spin_lock(lists_lock);
zbudpage_spin_lock(zbudpage);
if (zbudpage_is_dying(zbudpage)) {
/* ignore dying zbudpage... see zbud_evict_pageframe_lru() */
goto out;
}
zbpg = kmap_zbudpage_atomic(zbudpage);
if (budnum == 0)
size = zbudpage->zbud0_size;
else
size = zbudpage->zbud1_size;
BUG_ON(size == 0 || size > zbud_max_size());
to_va = zbud_data(zbpg, budnum, size);
to_va += sizeof(struct tmem_handle);
size -= sizeof(struct tmem_handle);
memcpy(to_va, from_va, size);
kunmap_zbudpage_atomic(zbpg);
ret = 0;
out:
zbudpage_spin_unlock(zbudpage);
spin_unlock(lists_lock);
return ret;
}
/*
* Choose an ephemeral LRU zbudpage that is evictable (not locked), ensure
* there are no references to it remaining, and return the now unused
* (and re-init'ed) struct page and the total amount of compressed
* data that was evicted.
*/
struct page *zbud_evict_pageframe_lru(unsigned int *zsize, unsigned int *zpages)
{
struct zbudpage *zbudpage = NULL, *zbudpage2;
struct zbud_unbuddied *unbud = zbud_eph_unbuddied;
struct page *page = NULL;
bool irqs_disabled = irqs_disabled();
/*
* Since this can be called indirectly from cleancache_put, which
* has interrupts disabled, as well as frontswap_put, which does not,
* we need to be able to handle both cases, even though it is ugly.
*/
if (irqs_disabled)
spin_lock(&zbud_eph_lists_lock);
else
spin_lock_bh(&zbud_eph_lists_lock);
*zsize = 0;
if (list_empty(&zbud_eph_lru_list))
goto unlock_out;
list_for_each_entry_safe(zbudpage, zbudpage2, &zbud_eph_lru_list, lru) {
/* skip a locked zbudpage */
if (unlikely(!zbudpage_spin_trylock(zbudpage)))
continue;
/* skip an unevictable zbudpage */
if (unlikely(zbudpage->unevictable != 0)) {
zbudpage_spin_unlock(zbudpage);
continue;
}
/* got a locked evictable page */
goto evict_page;
}
unlock_out:
/* no unlocked evictable pages, give up */
if (irqs_disabled)
spin_unlock(&zbud_eph_lists_lock);
else
spin_unlock_bh(&zbud_eph_lists_lock);
goto out;
evict_page:
list_del_init(&zbudpage->budlist);
list_del_init(&zbudpage->lru);
zbudpage_set_dying(zbudpage);
/*
* the zbudpage is now "dying" and attempts to read, write,
* or delete data from it will be ignored
*/
if (zbudpage->zbud0_size != 0 && zbudpage->zbud1_size != 0) {
*zsize = zbudpage->zbud0_size + zbudpage->zbud1_size -
(2 * sizeof(struct tmem_handle));
*zpages = 2;
} else if (zbudpage->zbud0_size != 0) {
unbud[zbud_size_to_chunks(zbudpage->zbud0_size)].count--;
*zsize = zbudpage->zbud0_size - sizeof(struct tmem_handle);
*zpages = 1;
} else if (zbudpage->zbud1_size != 0) {
unbud[zbud_size_to_chunks(zbudpage->zbud1_size)].count--;
*zsize = zbudpage->zbud1_size - sizeof(struct tmem_handle);
*zpages = 1;
} else {
BUG();
}
spin_unlock(&zbud_eph_lists_lock);
zbud_eph_evicted_pageframes++;
if (*zpages == 1)
zbud_eph_unbuddied_count--;
else
zbud_eph_buddied_count--;
zbud_evict_tmem(zbudpage);
zbudpage_spin_lock(zbudpage);
zbudpage_clear_dying(zbudpage);
page = zbud_unuse_zbudpage(zbudpage, true);
if (!irqs_disabled)
local_bh_enable();
out:
return page;
}
/*
* Choose a persistent LRU zbudpage that is evictable (not locked), zombify it,
* read the tmem_handle(s) out of it into the passed array, and return the
* number of zbuds. Caller must perform necessary tmem functions and,
* indirectly, zbud functions to fetch any valid data and cause the
* now-zombified zbudpage to eventually be freed. We track the zombified
* zbudpage count so it is possible to observe if there is a leak.
FIXME: describe (ramster) case where data pointers are passed in for memcpy
*/
unsigned int zbud_make_zombie_lru(struct tmem_handle *th, unsigned char **data,
unsigned int *zsize, bool eph)
{
struct zbudpage *zbudpage = NULL, *zbudpag2;
struct tmem_handle *thfrom;
char *from_va;
void *zbpg;
unsigned size;
int ret = 0, i;
spinlock_t *lists_lock =
eph ? &zbud_eph_lists_lock : &zbud_pers_lists_lock;
struct list_head *lru_list =
eph ? &zbud_eph_lru_list : &zbud_pers_lru_list;
spin_lock_bh(lists_lock);
if (list_empty(lru_list))
goto out;
list_for_each_entry_safe(zbudpage, zbudpag2, lru_list, lru) {
/* skip a locked zbudpage */
if (unlikely(!zbudpage_spin_trylock(zbudpage)))
continue;
/* skip an unevictable zbudpage */
if (unlikely(zbudpage->unevictable != 0)) {
zbudpage_spin_unlock(zbudpage);
continue;
}
/* got a locked evictable page */
goto zombify_page;
}
/* no unlocked evictable pages, give up */
goto out;
zombify_page:
/* got an unlocked evictable page, zombify it */
list_del_init(&zbudpage->budlist);
zbudpage_set_zombie(zbudpage);
/* FIXME what accounting do I need to do here? */
list_del_init(&zbudpage->lru);
if (eph) {
list_add_tail(&zbudpage->lru, &zbud_eph_zombie_list);
zbud_eph_zombie_count =
atomic_inc_return(&zbud_eph_zombie_atomic);
} else {
list_add_tail(&zbudpage->lru, &zbud_pers_zombie_list);
zbud_pers_zombie_count =
atomic_inc_return(&zbud_pers_zombie_atomic);
}
/* FIXME what accounting do I need to do here? */
zbpg = kmap_zbudpage_atomic(zbudpage);
for (i = 0; i < 2; i++) {
size = (i == 0) ? zbudpage->zbud0_size : zbudpage->zbud1_size;
if (size) {
from_va = zbud_data(zbpg, i, size);
thfrom = (struct tmem_handle *)from_va;
from_va += sizeof(struct tmem_handle);
size -= sizeof(struct tmem_handle);
if (th != NULL)
th[ret] = *thfrom;
if (data != NULL)
memcpy(data[ret], from_va, size);
if (zsize != NULL)
*zsize++ = size;
ret++;
}
}
kunmap_zbudpage_atomic(zbpg);
zbudpage_spin_unlock(zbudpage);
out:
spin_unlock_bh(lists_lock);
return ret;
}
void zbud_init(void)
{
int i;
zbud_debugfs_init();
BUG_ON((sizeof(struct tmem_handle) * 2 > CHUNK_SIZE));
BUG_ON(sizeof(struct zbudpage) > sizeof(struct page));
for (i = 0; i < NCHUNKS; i++) {
INIT_LIST_HEAD(&zbud_eph_unbuddied[i].list);
INIT_LIST_HEAD(&zbud_pers_unbuddied[i].list);
}
}
/*
* zbud.h
*
* Copyright (c) 2010-2012, Dan Magenheimer, Oracle Corp.
*
*/
#ifndef _ZBUD_H_
#define _ZBUD_H_
#include "tmem.h"
struct zbudref;
extern unsigned int zbud_max_buddy_size(void);
extern struct zbudref *zbud_match_prep(struct tmem_handle *th, bool eph,
void *cdata, unsigned size);
extern struct zbudref *zbud_create_prep(struct tmem_handle *th, bool eph,
void *cdata, unsigned size,
struct page *newpage);
extern void zbud_create_finish(struct zbudref *, bool);
extern int zbud_decompress(struct page *, struct zbudref *, bool,
void (*func)(char *, unsigned int, char *));
extern int zbud_copy_from_zbud(char *, struct zbudref *, size_t *, bool);
extern int zbud_copy_to_zbud(struct zbudref *, char *, bool);
extern struct page *zbud_free_and_delist(struct zbudref *, bool eph,
unsigned int *, unsigned int *);
extern struct page *zbud_evict_pageframe_lru(unsigned int *, unsigned int *);
extern unsigned int zbud_make_zombie_lru(struct tmem_handle *, unsigned char **,
unsigned int *, bool);
extern void zbud_init(void);
#endif /* _ZBUD_H_ */
/*
* zcache.c
*
* Copyright (c) 2010-2012, Dan Magenheimer, Oracle Corp.
* Copyright (c) 2010,2011, Nitin Gupta
*
* Zcache provides an in-kernel "host implementation" for transcendent memory
* ("tmem") and, thus indirectly, for cleancache and frontswap. Zcache uses
* lzo1x compression to improve density and an embedded allocator called
* "zbud" which "buddies" two compressed pages semi-optimally in each physical
* pageframe. Zbud is integrally tied into tmem to allow pageframes to
* be "reclaimed" efficiently.
*/
#include <linux/module.h>
#include <linux/cpu.h>
#include <linux/highmem.h>
#include <linux/list.h>
#include <linux/slab.h>
#include <linux/spinlock.h>
#include <linux/types.h>
#include <linux/string.h>
#include <linux/atomic.h>
#include <linux/math64.h>
#include <linux/crypto.h>
#include <linux/swap.h>
#include <linux/swapops.h>
#include <linux/pagemap.h>
#include <linux/writeback.h>
#include <linux/cleancache.h>
#include <linux/frontswap.h>
#include "tmem.h"
#include "zcache.h"
#include "zbud.h"
#include "ramster.h"
#include "debug.h"
#ifdef CONFIG_RAMSTER
static bool ramster_enabled __read_mostly;
static int disable_frontswap_selfshrink;
#else
#define ramster_enabled false
#define disable_frontswap_selfshrink 0
#endif
#ifndef __PG_WAS_ACTIVE
static inline bool PageWasActive(struct page *page)
{
return true;
}
static inline void SetPageWasActive(struct page *page)
{
}
#endif
#ifdef FRONTSWAP_HAS_EXCLUSIVE_GETS
static bool frontswap_has_exclusive_gets __read_mostly = true;
#else
static bool frontswap_has_exclusive_gets __read_mostly;
static inline void frontswap_tmem_exclusive_gets(bool b)
{
}
#endif
/*
* mark pampd to special value in order that later
* retrieve will identify zero-filled pages
*/
#define ZERO_FILLED 0x2
/* enable (or fix code) when Seth's patches are accepted upstream */
#define zcache_writeback_enabled 0
static bool zcache_enabled __read_mostly;
static bool disable_cleancache __read_mostly;
static bool disable_frontswap __read_mostly;
static bool disable_frontswap_ignore_nonactive __read_mostly;
static bool disable_cleancache_ignore_nonactive __read_mostly;
static char *namestr __read_mostly = "zcache";
#define ZCACHE_GFP_MASK \
(__GFP_FS | __GFP_NORETRY | __GFP_NOWARN | __GFP_NOMEMALLOC)
/* crypto API for zcache */
#ifdef CONFIG_ZCACHE_MODULE
static char *zcache_comp_name = "lzo";
#else
#define ZCACHE_COMP_NAME_SZ CRYPTO_MAX_ALG_NAME
static char zcache_comp_name[ZCACHE_COMP_NAME_SZ] __read_mostly;
#endif
static struct crypto_comp * __percpu *zcache_comp_pcpu_tfms __read_mostly;
enum comp_op {
ZCACHE_COMPOP_COMPRESS,
ZCACHE_COMPOP_DECOMPRESS
};
static inline int zcache_comp_op(enum comp_op op,
const u8 *src, unsigned int slen,
u8 *dst, unsigned int *dlen)
{
struct crypto_comp *tfm;
int ret = -1;
BUG_ON(!zcache_comp_pcpu_tfms);
tfm = *per_cpu_ptr(zcache_comp_pcpu_tfms, get_cpu());
BUG_ON(!tfm);
switch (op) {
case ZCACHE_COMPOP_COMPRESS:
ret = crypto_comp_compress(tfm, src, slen, dst, dlen);
break;
case ZCACHE_COMPOP_DECOMPRESS:
ret = crypto_comp_decompress(tfm, src, slen, dst, dlen);
break;
default:
ret = -EINVAL;
}
put_cpu();
return ret;
}
/*
* policy parameters
*/
/*
* byte count defining poor compression; pages with greater zsize will be
* rejected
*/
static unsigned int zbud_max_zsize __read_mostly = (PAGE_SIZE / 8) * 7;
/*
* byte count defining poor *mean* compression; pages with greater zsize
* will be rejected until sufficient better-compressed pages are accepted
* driving the mean below this threshold
*/
static unsigned int zbud_max_mean_zsize __read_mostly = (PAGE_SIZE / 8) * 5;
/*
* for now, used named slabs so can easily track usage; later can
* either just use kmalloc, or perhaps add a slab-like allocator
* to more carefully manage total memory utilization
*/
static struct kmem_cache *zcache_objnode_cache;
static struct kmem_cache *zcache_obj_cache;
static DEFINE_PER_CPU(struct zcache_preload, zcache_preloads) = { 0, };
/* Used by debug.c */
ssize_t zcache_pers_zpages;
u64 zcache_pers_zbytes;
ssize_t zcache_eph_pageframes;
ssize_t zcache_pers_pageframes;
/* Used by this code. */
ssize_t zcache_last_active_file_pageframes;
ssize_t zcache_last_inactive_file_pageframes;
ssize_t zcache_last_active_anon_pageframes;
ssize_t zcache_last_inactive_anon_pageframes;
#ifdef CONFIG_ZCACHE_WRITEBACK
ssize_t zcache_writtenback_pages;
ssize_t zcache_outstanding_writeback_pages;
#endif
/*
* zcache core code starts here
*/
static struct zcache_client zcache_host;
static struct zcache_client zcache_clients[MAX_CLIENTS];
static inline bool is_local_client(struct zcache_client *cli)
{
return cli == &zcache_host;
}
static struct zcache_client *zcache_get_client_by_id(uint16_t cli_id)
{
struct zcache_client *cli = &zcache_host;
if (cli_id != LOCAL_CLIENT) {
if (cli_id >= MAX_CLIENTS)
goto out;
cli = &zcache_clients[cli_id];
}
out:
return cli;
}
/*
* Tmem operations assume the poolid implies the invoking client.
* Zcache only has one client (the kernel itself): LOCAL_CLIENT.
* RAMster has each client numbered by cluster node, and a KVM version
* of zcache would have one client per guest and each client might
* have a poolid==N.
*/
struct tmem_pool *zcache_get_pool_by_id(uint16_t cli_id, uint16_t poolid)
{
struct tmem_pool *pool = NULL;
struct zcache_client *cli = NULL;
cli = zcache_get_client_by_id(cli_id);
if (cli == NULL)
goto out;
if (!is_local_client(cli))
atomic_inc(&cli->refcount);
if (poolid < MAX_POOLS_PER_CLIENT) {
pool = cli->tmem_pools[poolid];
if (pool != NULL)
atomic_inc(&pool->refcount);
}
out:
return pool;
}
void zcache_put_pool(struct tmem_pool *pool)
{
struct zcache_client *cli = NULL;
if (pool == NULL)
BUG();
cli = pool->client;
atomic_dec(&pool->refcount);
if (!is_local_client(cli))
atomic_dec(&cli->refcount);
}
int zcache_new_client(uint16_t cli_id)
{
struct zcache_client *cli;
int ret = -1;
cli = zcache_get_client_by_id(cli_id);
if (cli == NULL)
goto out;
if (cli->allocated)
goto out;
cli->allocated = 1;
ret = 0;
out:
return ret;
}
/*
* zcache implementation for tmem host ops
*/
static struct tmem_objnode *zcache_objnode_alloc(struct tmem_pool *pool)
{
struct tmem_objnode *objnode = NULL;
struct zcache_preload *kp;
int i;
kp = &__get_cpu_var(zcache_preloads);
for (i = 0; i < ARRAY_SIZE(kp->objnodes); i++) {
objnode = kp->objnodes[i];
if (objnode != NULL) {
kp->objnodes[i] = NULL;
break;
}
}
BUG_ON(objnode == NULL);
inc_zcache_objnode_count();
return objnode;
}
static void zcache_objnode_free(struct tmem_objnode *objnode,
struct tmem_pool *pool)
{
dec_zcache_objnode_count();
kmem_cache_free(zcache_objnode_cache, objnode);
}
static struct tmem_obj *zcache_obj_alloc(struct tmem_pool *pool)
{
struct tmem_obj *obj = NULL;
struct zcache_preload *kp;
kp = &__get_cpu_var(zcache_preloads);
obj = kp->obj;
BUG_ON(obj == NULL);
kp->obj = NULL;
inc_zcache_obj_count();
return obj;
}
static void zcache_obj_free(struct tmem_obj *obj, struct tmem_pool *pool)
{
dec_zcache_obj_count();
kmem_cache_free(zcache_obj_cache, obj);
}
/*
* Compressing zero-filled pages will waste memory and introduce
* serious fragmentation, skip it to avoid overhead.
*/
static bool page_is_zero_filled(struct page *p)
{
unsigned int pos;
char *page;
page = kmap_atomic(p);
for (pos = 0; pos < PAGE_SIZE / sizeof(*page); pos++) {
if (page[pos]) {
kunmap_atomic(page);
return false;
}
}
kunmap_atomic(page);
return true;
}
static void handle_zero_filled_page(void *p)
{
void *user_mem;
struct page *page = (struct page *)p;
user_mem = kmap_atomic(page);
memset(user_mem, 0, PAGE_SIZE);
kunmap_atomic(user_mem);
flush_dcache_page(page);
}
static struct tmem_hostops zcache_hostops = {
.obj_alloc = zcache_obj_alloc,
.obj_free = zcache_obj_free,
.objnode_alloc = zcache_objnode_alloc,
.objnode_free = zcache_objnode_free,
};
static struct page *zcache_alloc_page(void)
{
struct page *page = alloc_page(ZCACHE_GFP_MASK);
if (page != NULL)
inc_zcache_pageframes_alloced();
return page;
}
static void zcache_free_page(struct page *page)
{
long curr_pageframes;
static long max_pageframes, min_pageframes;
if (page == NULL)
BUG();
__free_page(page);
inc_zcache_pageframes_freed();
curr_pageframes = curr_pageframes_count();
if (curr_pageframes > max_pageframes)
max_pageframes = curr_pageframes;
if (curr_pageframes < min_pageframes)
min_pageframes = curr_pageframes;
#ifdef CONFIG_ZCACHE_DEBUG
if (curr_pageframes > 2L || curr_pageframes < -2L) {
/* pr_info here */
}
#endif
}
/*
* zcache implementations for PAM page descriptor ops
*/
/* forward reference */
static void zcache_compress(struct page *from,
void **out_va, unsigned *out_len);
static struct page *zcache_evict_eph_pageframe(void);
static void *zcache_pampd_eph_create(char *data, size_t size, bool raw,
struct tmem_handle *th)
{
void *pampd = NULL, *cdata = data;
unsigned clen = size;
bool zero_filled = false;
struct page *page = (struct page *)(data), *newpage;
if (page_is_zero_filled(page)) {
clen = 0;
zero_filled = true;
inc_zcache_zero_filled_pages();
goto got_pampd;
}
if (!raw) {
zcache_compress(page, &cdata, &clen);
if (clen > zbud_max_buddy_size()) {
inc_zcache_compress_poor();
goto out;
}
} else {
BUG_ON(clen > zbud_max_buddy_size());
}
/* look for space via an existing match first */
pampd = (void *)zbud_match_prep(th, true, cdata, clen);
if (pampd != NULL)
goto got_pampd;
/* no match, now we need to find (or free up) a full page */
newpage = zcache_alloc_page();
if (newpage != NULL)
goto create_in_new_page;
inc_zcache_failed_getfreepages();
/* can't allocate a page, evict an ephemeral page via LRU */
newpage = zcache_evict_eph_pageframe();
if (newpage == NULL) {
inc_zcache_eph_ate_tail_failed();
goto out;
}
inc_zcache_eph_ate_tail();
create_in_new_page:
pampd = (void *)zbud_create_prep(th, true, cdata, clen, newpage);
BUG_ON(pampd == NULL);
inc_zcache_eph_pageframes();
got_pampd:
inc_zcache_eph_zbytes(clen);
inc_zcache_eph_zpages();
if (ramster_enabled && raw && !zero_filled)
ramster_count_foreign_pages(true, 1);
if (zero_filled)
pampd = (void *)ZERO_FILLED;
out:
return pampd;
}
static void *zcache_pampd_pers_create(char *data, size_t size, bool raw,
struct tmem_handle *th)
{
void *pampd = NULL, *cdata = data;
unsigned clen = size;
bool zero_filled = false;
struct page *page = (struct page *)(data), *newpage;
unsigned long zbud_mean_zsize;
unsigned long curr_pers_zpages, total_zsize;
if (data == NULL) {
BUG_ON(!ramster_enabled);
goto create_pampd;
}
if (page_is_zero_filled(page)) {
clen = 0;
zero_filled = true;
inc_zcache_zero_filled_pages();
goto got_pampd;
}
curr_pers_zpages = zcache_pers_zpages;
/* FIXME CONFIG_RAMSTER... subtract atomic remote_pers_pages here? */
if (!raw)
zcache_compress(page, &cdata, &clen);
/* reject if compression is too poor */
if (clen > zbud_max_zsize) {
inc_zcache_compress_poor();
goto out;
}
/* reject if mean compression is too poor */
if ((clen > zbud_max_mean_zsize) && (curr_pers_zpages > 0)) {
total_zsize = zcache_pers_zbytes;
if ((long)total_zsize < 0)
total_zsize = 0;
zbud_mean_zsize = div_u64(total_zsize,
curr_pers_zpages);
if (zbud_mean_zsize > zbud_max_mean_zsize) {
inc_zcache_mean_compress_poor();
goto out;
}
}
create_pampd:
/* look for space via an existing match first */
pampd = (void *)zbud_match_prep(th, false, cdata, clen);
if (pampd != NULL)
goto got_pampd;
/* no match, now we need to find (or free up) a full page */
newpage = zcache_alloc_page();
if (newpage != NULL)
goto create_in_new_page;
/*
* FIXME do the following only if eph is oversized?
* if (zcache_eph_pageframes >
* (global_page_state(NR_LRU_BASE + LRU_ACTIVE_FILE) +
* global_page_state(NR_LRU_BASE + LRU_INACTIVE_FILE)))
*/
inc_zcache_failed_getfreepages();
/* can't allocate a page, evict an ephemeral page via LRU */
newpage = zcache_evict_eph_pageframe();
if (newpage == NULL) {
inc_zcache_pers_ate_eph_failed();
goto out;
}
inc_zcache_pers_ate_eph();
create_in_new_page:
pampd = (void *)zbud_create_prep(th, false, cdata, clen, newpage);
BUG_ON(pampd == NULL);
inc_zcache_pers_pageframes();
got_pampd:
inc_zcache_pers_zpages();
inc_zcache_pers_zbytes(clen);
if (ramster_enabled && raw && !zero_filled)
ramster_count_foreign_pages(false, 1);
if (zero_filled)
pampd = (void *)ZERO_FILLED;
out:
return pampd;
}
/*
* This is called directly from zcache_put_page to pre-allocate space
* to store a zpage.
*/
void *zcache_pampd_create(char *data, unsigned int size, bool raw,
int eph, struct tmem_handle *th)
{
void *pampd = NULL;
struct zcache_preload *kp;
struct tmem_objnode *objnode;
struct tmem_obj *obj;
int i;
BUG_ON(!irqs_disabled());
/* pre-allocate per-cpu metadata */
BUG_ON(zcache_objnode_cache == NULL);
BUG_ON(zcache_obj_cache == NULL);
kp = &__get_cpu_var(zcache_preloads);
for (i = 0; i < ARRAY_SIZE(kp->objnodes); i++) {
objnode = kp->objnodes[i];
if (objnode == NULL) {
objnode = kmem_cache_alloc(zcache_objnode_cache,
ZCACHE_GFP_MASK);
if (unlikely(objnode == NULL)) {
inc_zcache_failed_alloc();
goto out;
}
kp->objnodes[i] = objnode;
}
}
if (kp->obj == NULL) {
obj = kmem_cache_alloc(zcache_obj_cache, ZCACHE_GFP_MASK);
kp->obj = obj;
}
if (unlikely(kp->obj == NULL)) {
inc_zcache_failed_alloc();
goto out;
}
/*
* ok, have all the metadata pre-allocated, now do the data
* but since how we allocate the data is dependent on ephemeral
* or persistent, we split the call here to different sub-functions
*/
if (eph)
pampd = zcache_pampd_eph_create(data, size, raw, th);
else
pampd = zcache_pampd_pers_create(data, size, raw, th);
out:
return pampd;
}
/*
* This is a pamops called via tmem_put and is necessary to "finish"
* a pampd creation.
*/
void zcache_pampd_create_finish(void *pampd, bool eph)
{
if (pampd != (void *)ZERO_FILLED)
zbud_create_finish((struct zbudref *)pampd, eph);
}
/*
* This is passed as a function parameter to zbud_decompress so that
* zbud need not be familiar with the details of crypto. It assumes that
* the bytes from_va and to_va through from_va+size-1 and to_va+size-1 are
* kmapped. It must be successful, else there is a logic bug somewhere.
*/
static void zcache_decompress(char *from_va, unsigned int size, char *to_va)
{
int ret;
unsigned int outlen = PAGE_SIZE;
ret = zcache_comp_op(ZCACHE_COMPOP_DECOMPRESS, from_va, size,
to_va, &outlen);
BUG_ON(ret);
BUG_ON(outlen != PAGE_SIZE);
}
/*
* Decompress from the kernel va to a pageframe
*/
void zcache_decompress_to_page(char *from_va, unsigned int size,
struct page *to_page)
{
char *to_va = kmap_atomic(to_page);
zcache_decompress(from_va, size, to_va);
kunmap_atomic(to_va);
}
/*
* fill the pageframe corresponding to the struct page with the data
* from the passed pampd
*/
static int zcache_pampd_get_data(char *data, size_t *sizep, bool raw,
void *pampd, struct tmem_pool *pool,
struct tmem_oid *oid, uint32_t index)
{
int ret;
bool eph = !is_persistent(pool);
BUG_ON(preemptible());
BUG_ON(eph); /* fix later if shared pools get implemented */
BUG_ON(pampd_is_remote(pampd));
if (pampd == (void *)ZERO_FILLED) {
handle_zero_filled_page(data);
if (!raw)
*sizep = PAGE_SIZE;
return 0;
}
if (raw)
ret = zbud_copy_from_zbud(data, (struct zbudref *)pampd,
sizep, eph);
else {
ret = zbud_decompress((struct page *)(data),
(struct zbudref *)pampd, false,
zcache_decompress);
*sizep = PAGE_SIZE;
}
return ret;
}
/*
* fill the pageframe corresponding to the struct page with the data
* from the passed pampd
*/
static int zcache_pampd_get_data_and_free(char *data, size_t *sizep, bool raw,
void *pampd, struct tmem_pool *pool,
struct tmem_oid *oid, uint32_t index)
{
int ret = 0;
bool eph = !is_persistent(pool), zero_filled = false;
struct page *page = NULL;
unsigned int zsize, zpages;
BUG_ON(preemptible());
BUG_ON(pampd_is_remote(pampd));
if (pampd == (void *)ZERO_FILLED) {
handle_zero_filled_page(data);
zero_filled = true;
zsize = 0;
zpages = 1;
if (!raw)
*sizep = PAGE_SIZE;
dec_zcache_zero_filled_pages();
goto zero_fill;
}
if (raw)
ret = zbud_copy_from_zbud(data, (struct zbudref *)pampd,
sizep, eph);
else {
ret = zbud_decompress((struct page *)(data),
(struct zbudref *)pampd, eph,
zcache_decompress);
*sizep = PAGE_SIZE;
}
page = zbud_free_and_delist((struct zbudref *)pampd, eph,
&zsize, &zpages);
zero_fill:
if (eph) {
if (page)
dec_zcache_eph_pageframes();
dec_zcache_eph_zpages(zpages);
dec_zcache_eph_zbytes(zsize);
} else {
if (page)
dec_zcache_pers_pageframes();
dec_zcache_pers_zpages(zpages);
dec_zcache_pers_zbytes(zsize);
}
if (!is_local_client(pool->client) && !zero_filled)
ramster_count_foreign_pages(eph, -1);
if (page && !zero_filled)
zcache_free_page(page);
return ret;
}
/*
* free the pampd and remove it from any zcache lists
* pampd must no longer be pointed to from any tmem data structures!
*/
static void zcache_pampd_free(void *pampd, struct tmem_pool *pool,
struct tmem_oid *oid, uint32_t index, bool acct)
{
struct page *page = NULL;
unsigned int zsize, zpages;
bool zero_filled = false;
BUG_ON(preemptible());
if (pampd == (void *)ZERO_FILLED) {
zero_filled = true;
zsize = 0;
zpages = 1;
dec_zcache_zero_filled_pages();
}
if (pampd_is_remote(pampd) && !zero_filled) {
BUG_ON(!ramster_enabled);
pampd = ramster_pampd_free(pampd, pool, oid, index, acct);
if (pampd == NULL)
return;
}
if (is_ephemeral(pool)) {
if (!zero_filled)
page = zbud_free_and_delist((struct zbudref *)pampd,
true, &zsize, &zpages);
if (page)
dec_zcache_eph_pageframes();
dec_zcache_eph_zpages(zpages);
dec_zcache_eph_zbytes(zsize);
/* FIXME CONFIG_RAMSTER... check acct parameter? */
} else {
if (!zero_filled)
page = zbud_free_and_delist((struct zbudref *)pampd,
false, &zsize, &zpages);
if (page)
dec_zcache_pers_pageframes();
dec_zcache_pers_zpages(zpages);
dec_zcache_pers_zbytes(zsize);
}
if (!is_local_client(pool->client) && !zero_filled)
ramster_count_foreign_pages(is_ephemeral(pool), -1);
if (page && !zero_filled)
zcache_free_page(page);
}
static struct tmem_pamops zcache_pamops = {
.create_finish = zcache_pampd_create_finish,
.get_data = zcache_pampd_get_data,
.get_data_and_free = zcache_pampd_get_data_and_free,
.free = zcache_pampd_free,
};
/*
* zcache compression/decompression and related per-cpu stuff
*/
static DEFINE_PER_CPU(unsigned char *, zcache_dstmem);
#define ZCACHE_DSTMEM_ORDER 1
static void zcache_compress(struct page *from, void **out_va, unsigned *out_len)
{
int ret;
unsigned char *dmem = __get_cpu_var(zcache_dstmem);
char *from_va;
BUG_ON(!irqs_disabled());
/* no buffer or no compressor so can't compress */
BUG_ON(dmem == NULL);
*out_len = PAGE_SIZE << ZCACHE_DSTMEM_ORDER;
from_va = kmap_atomic(from);
mb();
ret = zcache_comp_op(ZCACHE_COMPOP_COMPRESS, from_va, PAGE_SIZE, dmem,
out_len);
BUG_ON(ret);
*out_va = dmem;
kunmap_atomic(from_va);
}
static int zcache_comp_cpu_up(int cpu)
{
struct crypto_comp *tfm;
tfm = crypto_alloc_comp(zcache_comp_name, 0, 0);
if (IS_ERR(tfm))
return NOTIFY_BAD;
*per_cpu_ptr(zcache_comp_pcpu_tfms, cpu) = tfm;
return NOTIFY_OK;
}
static void zcache_comp_cpu_down(int cpu)
{
struct crypto_comp *tfm;
tfm = *per_cpu_ptr(zcache_comp_pcpu_tfms, cpu);
crypto_free_comp(tfm);
*per_cpu_ptr(zcache_comp_pcpu_tfms, cpu) = NULL;
}
static int zcache_cpu_notifier(struct notifier_block *nb,
unsigned long action, void *pcpu)
{
int ret, i, cpu = (long)pcpu;
struct zcache_preload *kp;
switch (action) {
case CPU_UP_PREPARE:
ret = zcache_comp_cpu_up(cpu);
if (ret != NOTIFY_OK) {
pr_err("%s: can't allocate compressor xform\n",
namestr);
return ret;
}
per_cpu(zcache_dstmem, cpu) = (void *)__get_free_pages(
GFP_KERNEL | __GFP_REPEAT, ZCACHE_DSTMEM_ORDER);
if (ramster_enabled)
ramster_cpu_up(cpu);
break;
case CPU_DEAD:
case CPU_UP_CANCELED:
zcache_comp_cpu_down(cpu);
free_pages((unsigned long)per_cpu(zcache_dstmem, cpu),
ZCACHE_DSTMEM_ORDER);
per_cpu(zcache_dstmem, cpu) = NULL;
kp = &per_cpu(zcache_preloads, cpu);
for (i = 0; i < ARRAY_SIZE(kp->objnodes); i++) {
if (kp->objnodes[i])
kmem_cache_free(zcache_objnode_cache,
kp->objnodes[i]);
}
if (kp->obj) {
kmem_cache_free(zcache_obj_cache, kp->obj);
kp->obj = NULL;
}
if (ramster_enabled)
ramster_cpu_down(cpu);
break;
default:
break;
}
return NOTIFY_OK;
}
static struct notifier_block zcache_cpu_notifier_block = {
.notifier_call = zcache_cpu_notifier
};
/*
* The following code interacts with the zbud eviction and zbud
* zombify code to access LRU pages
*/
static struct page *zcache_evict_eph_pageframe(void)
{
struct page *page;
unsigned int zsize = 0, zpages = 0;
page = zbud_evict_pageframe_lru(&zsize, &zpages);
if (page == NULL)
goto out;
dec_zcache_eph_zbytes(zsize);
dec_zcache_eph_zpages(zpages);
inc_zcache_evicted_eph_zpages(zpages);
dec_zcache_eph_pageframes();
inc_zcache_evicted_eph_pageframes();
out:
return page;
}
#ifdef CONFIG_ZCACHE_WRITEBACK
static atomic_t zcache_outstanding_writeback_pages_atomic = ATOMIC_INIT(0);
static inline void inc_zcache_outstanding_writeback_pages(void)
{
zcache_outstanding_writeback_pages =
atomic_inc_return(&zcache_outstanding_writeback_pages_atomic);
}
static inline void dec_zcache_outstanding_writeback_pages(void)
{
zcache_outstanding_writeback_pages =
atomic_dec_return(&zcache_outstanding_writeback_pages_atomic);
};
static void unswiz(struct tmem_oid oid, u32 index,
unsigned *type, pgoff_t *offset);
/*
* Choose an LRU persistent pageframe and attempt to write it back to
* the backing swap disk by calling frontswap_writeback on both zpages.
*
* This is work-in-progress.
*/
static void zcache_end_swap_write(struct bio *bio, int err)
{
end_swap_bio_write(bio, err);
dec_zcache_outstanding_writeback_pages();
zcache_writtenback_pages++;
}
/*
* zcache_get_swap_cache_page
*
* This is an adaption of read_swap_cache_async()
*
* If success, page is returned in retpage
* Returns 0 if page was already in the swap cache, page is not locked
* Returns 1 if the new page needs to be populated, page is locked
*/
static int zcache_get_swap_cache_page(int type, pgoff_t offset,
struct page *new_page)
{
struct page *found_page;
swp_entry_t entry = swp_entry(type, offset);
int err;
BUG_ON(new_page == NULL);
do {
/*
* First check the swap cache. Since this is normally
* called after lookup_swap_cache() failed, re-calling
* that would confuse statistics.
*/
found_page = find_get_page(&swapper_space, entry.val);
if (found_page)
return 0;
/*
* call radix_tree_preload() while we can wait.
*/
err = radix_tree_preload(GFP_KERNEL);
if (err)
break;
/*
* Swap entry may have been freed since our caller observed it.
*/
err = swapcache_prepare(entry);
if (err == -EEXIST) { /* seems racy */
radix_tree_preload_end();
continue;
}
if (err) { /* swp entry is obsolete ? */
radix_tree_preload_end();
break;
}
/* May fail (-ENOMEM) if radix-tree node allocation failed. */
__set_page_locked(new_page);
SetPageSwapBacked(new_page);
err = __add_to_swap_cache(new_page, entry);
if (likely(!err)) {
radix_tree_preload_end();
lru_cache_add_anon(new_page);
return 1;
}
radix_tree_preload_end();
ClearPageSwapBacked(new_page);
__clear_page_locked(new_page);
/*
* add_to_swap_cache() doesn't return -EEXIST, so we can safely
* clear SWAP_HAS_CACHE flag.
*/
swapcache_free(entry, NULL);
/* FIXME: is it possible to get here without err==-ENOMEM?
* If not, we can dispense with the do loop, use goto retry */
} while (err != -ENOMEM);
return -ENOMEM;
}
/*
* Given a frontswap zpage in zcache (identified by type/offset) and
* an empty page, put the page into the swap cache, use frontswap
* to get the page from zcache into the empty page, then give it
* to the swap subsystem to send to disk (carefully avoiding the
* possibility that frontswap might snatch it back).
* Returns < 0 if error, 0 if successful, and 1 if successful but
* the newpage passed in not needed and should be freed.
*/
static int zcache_frontswap_writeback_zpage(int type, pgoff_t offset,
struct page *newpage)
{
struct page *page = newpage;
int ret;
struct writeback_control wbc = {
.sync_mode = WB_SYNC_NONE,
};
ret = zcache_get_swap_cache_page(type, offset, page);
if (ret < 0)
return ret;
else if (ret == 0) {
/* more uptodate page is already in swapcache */
__frontswap_invalidate_page(type, offset);
return 1;
}
BUG_ON(!frontswap_has_exclusive_gets); /* load must also invalidate */
/* FIXME: how is it possible to get here when page is unlocked? */
__frontswap_load(page);
SetPageUptodate(page); /* above does SetPageDirty, is that enough? */
/* start writeback */
SetPageReclaim(page);
/*
* Return value is ignored here because it doesn't change anything
* for us. Page is returned unlocked.
*/
(void)__swap_writepage(page, &wbc, zcache_end_swap_write);
page_cache_release(page);
inc_zcache_outstanding_writeback_pages();
return 0;
}
/*
* The following is still a magic number... we want to allow forward progress
* for writeback because it clears out needed RAM when under pressure, but
* we don't want to allow writeback to absorb and queue too many GFP_KERNEL
* pages if the swap device is very slow.
*/
#define ZCACHE_MAX_OUTSTANDING_WRITEBACK_PAGES 6400
/*
* Try to allocate two free pages, first using a non-aggressive alloc,
* then by evicting zcache ephemeral (clean pagecache) pages, and last
* by aggressive GFP_KERNEL alloc. We allow zbud to choose a pageframe
* consisting of 1-2 zbuds/zpages, then call the writeback_zpage helper
* function above for each.
*/
static int zcache_frontswap_writeback(void)
{
struct tmem_handle th[2];
int ret = 0;
int nzbuds, writeback_ret;
unsigned type;
struct page *znewpage1 = NULL, *znewpage2 = NULL;
struct page *evictpage1 = NULL, *evictpage2 = NULL;
struct page *newpage1 = NULL, *newpage2 = NULL;
struct page *page1 = NULL, *page2 = NULL;
pgoff_t offset;
znewpage1 = alloc_page(ZCACHE_GFP_MASK);
znewpage2 = alloc_page(ZCACHE_GFP_MASK);
if (znewpage1 == NULL)
evictpage1 = zcache_evict_eph_pageframe();
if (znewpage2 == NULL)
evictpage2 = zcache_evict_eph_pageframe();
if ((evictpage1 == NULL || evictpage2 == NULL) &&
atomic_read(&zcache_outstanding_writeback_pages_atomic) >
ZCACHE_MAX_OUTSTANDING_WRITEBACK_PAGES) {
goto free_and_out;
}
if (znewpage1 == NULL && evictpage1 == NULL)
newpage1 = alloc_page(GFP_KERNEL);
if (znewpage2 == NULL && evictpage2 == NULL)
newpage2 = alloc_page(GFP_KERNEL);
if (newpage1 == NULL || newpage2 == NULL)
goto free_and_out;
/* ok, we have two pageframes pre-allocated, get a pair of zbuds */
nzbuds = zbud_make_zombie_lru(&th[0], NULL, NULL, false);
if (nzbuds == 0) {
ret = -ENOENT;
goto free_and_out;
}
/* process the first zbud */
unswiz(th[0].oid, th[0].index, &type, &offset);
page1 = (znewpage1 != NULL) ? znewpage1 :
((newpage1 != NULL) ? newpage1 : evictpage1);
writeback_ret = zcache_frontswap_writeback_zpage(type, offset, page1);
if (writeback_ret < 0) {
ret = -ENOMEM;
goto free_and_out;
}
if (evictpage1 != NULL)
zcache_pageframes_freed =
atomic_inc_return(&zcache_pageframes_freed_atomic);
if (writeback_ret == 0) {
/* zcache_get_swap_cache_page will free, don't double free */
znewpage1 = NULL;
newpage1 = NULL;
evictpage1 = NULL;
}
if (nzbuds < 2)
goto free_and_out;
/* if there is a second zbud, process it */
unswiz(th[1].oid, th[1].index, &type, &offset);
page2 = (znewpage2 != NULL) ? znewpage2 :
((newpage2 != NULL) ? newpage2 : evictpage2);
writeback_ret = zcache_frontswap_writeback_zpage(type, offset, page2);
if (writeback_ret < 0) {
ret = -ENOMEM;
goto free_and_out;
}
if (evictpage2 != NULL)
zcache_pageframes_freed =
atomic_inc_return(&zcache_pageframes_freed_atomic);
if (writeback_ret == 0) {
znewpage2 = NULL;
newpage2 = NULL;
evictpage2 = NULL;
}
free_and_out:
if (znewpage1 != NULL)
page_cache_release(znewpage1);
if (znewpage2 != NULL)
page_cache_release(znewpage2);
if (newpage1 != NULL)
page_cache_release(newpage1);
if (newpage2 != NULL)
page_cache_release(newpage2);
if (evictpage1 != NULL)
zcache_free_page(evictpage1);
if (evictpage2 != NULL)
zcache_free_page(evictpage2);
return ret;
}
#endif /* CONFIG_ZCACHE_WRITEBACK */
/*
* When zcache is disabled ("frozen"), pools can be created and destroyed,
* but all puts (and thus all other operations that require memory allocation)
* must fail. If zcache is unfrozen, accepts puts, then frozen again,
* data consistency requires all puts while frozen to be converted into
* flushes.
*/
static bool zcache_freeze;
/*
* This zcache shrinker interface reduces the number of ephemeral pageframes
* used by zcache to approximately the same as the total number of LRU_FILE
* pageframes in use, and now also reduces the number of persistent pageframes
* used by zcache to approximately the same as the total number of LRU_ANON
* pageframes in use. FIXME POLICY: Probably the writeback should only occur
* if the eviction doesn't free enough pages.
*/
static int shrink_zcache_memory(struct shrinker *shrink,
struct shrink_control *sc)
{
static bool in_progress;
int ret = -1;
int nr = sc->nr_to_scan;
int nr_evict = 0;
int nr_writeback = 0;
struct page *page;
int file_pageframes_inuse, anon_pageframes_inuse;
if (nr <= 0)
goto skip_evict;
/* don't allow more than one eviction thread at a time */
if (in_progress)
goto skip_evict;
in_progress = true;
/* we are going to ignore nr, and target a different value */
zcache_last_active_file_pageframes =
global_page_state(NR_LRU_BASE + LRU_ACTIVE_FILE);
zcache_last_inactive_file_pageframes =
global_page_state(NR_LRU_BASE + LRU_INACTIVE_FILE);
file_pageframes_inuse = zcache_last_active_file_pageframes +
zcache_last_inactive_file_pageframes;
if (zcache_eph_pageframes > file_pageframes_inuse)
nr_evict = zcache_eph_pageframes - file_pageframes_inuse;
else
nr_evict = 0;
while (nr_evict-- > 0) {
page = zcache_evict_eph_pageframe();
if (page == NULL)
break;
zcache_free_page(page);
}
zcache_last_active_anon_pageframes =
global_page_state(NR_LRU_BASE + LRU_ACTIVE_ANON);
zcache_last_inactive_anon_pageframes =
global_page_state(NR_LRU_BASE + LRU_INACTIVE_ANON);
anon_pageframes_inuse = zcache_last_active_anon_pageframes +
zcache_last_inactive_anon_pageframes;
if (zcache_pers_pageframes > anon_pageframes_inuse)
nr_writeback = zcache_pers_pageframes - anon_pageframes_inuse;
else
nr_writeback = 0;
while (nr_writeback-- > 0) {
#ifdef CONFIG_ZCACHE_WRITEBACK
int writeback_ret;
writeback_ret = zcache_frontswap_writeback();
if (writeback_ret == -ENOMEM)
#endif
break;
}
in_progress = false;
skip_evict:
/* resample: has changed, but maybe not all the way yet */
zcache_last_active_file_pageframes =
global_page_state(NR_LRU_BASE + LRU_ACTIVE_FILE);
zcache_last_inactive_file_pageframes =
global_page_state(NR_LRU_BASE + LRU_INACTIVE_FILE);
ret = zcache_eph_pageframes - zcache_last_active_file_pageframes +
zcache_last_inactive_file_pageframes;
if (ret < 0)
ret = 0;
return ret;
}
static struct shrinker zcache_shrinker = {
.shrink = shrink_zcache_memory,
.seeks = DEFAULT_SEEKS,
};
/*
* zcache shims between cleancache/frontswap ops and tmem
*/
/* FIXME rename these core routines to zcache_tmemput etc? */
int zcache_put_page(int cli_id, int pool_id, struct tmem_oid *oidp,
uint32_t index, void *page,
unsigned int size, bool raw, int ephemeral)
{
struct tmem_pool *pool;
struct tmem_handle th;
int ret = -1;
void *pampd = NULL;
BUG_ON(!irqs_disabled());
pool = zcache_get_pool_by_id(cli_id, pool_id);
if (unlikely(pool == NULL))
goto out;
if (!zcache_freeze) {
ret = 0;
th.client_id = cli_id;
th.pool_id = pool_id;
th.oid = *oidp;
th.index = index;
pampd = zcache_pampd_create((char *)page, size, raw,
ephemeral, &th);
if (pampd == NULL) {
ret = -ENOMEM;
if (ephemeral)
inc_zcache_failed_eph_puts();
else
inc_zcache_failed_pers_puts();
} else {
if (ramster_enabled)
ramster_do_preload_flnode(pool);
ret = tmem_put(pool, oidp, index, 0, pampd);
if (ret < 0)
BUG();
}
zcache_put_pool(pool);
} else {
inc_zcache_put_to_flush();
if (ramster_enabled)
ramster_do_preload_flnode(pool);
if (atomic_read(&pool->obj_count) > 0)
/* the put fails whether the flush succeeds or not */
(void)tmem_flush_page(pool, oidp, index);
zcache_put_pool(pool);
}
out:
return ret;
}
int zcache_get_page(int cli_id, int pool_id, struct tmem_oid *oidp,
uint32_t index, void *page,
size_t *sizep, bool raw, int get_and_free)
{
struct tmem_pool *pool;
int ret = -1;
bool eph;
if (!raw) {
BUG_ON(irqs_disabled());
BUG_ON(in_softirq());
}
pool = zcache_get_pool_by_id(cli_id, pool_id);
eph = is_ephemeral(pool);
if (likely(pool != NULL)) {
if (atomic_read(&pool->obj_count) > 0)
ret = tmem_get(pool, oidp, index, (char *)(page),
sizep, raw, get_and_free);
zcache_put_pool(pool);
}
WARN_ONCE((!is_ephemeral(pool) && (ret != 0)),
"zcache_get fails on persistent pool, "
"bad things are very likely to happen soon\n");
#ifdef RAMSTER_TESTING
if (ret != 0 && ret != -1 && !(ret == -EINVAL && is_ephemeral(pool)))
pr_err("TESTING zcache_get tmem_get returns ret=%d\n", ret);
#endif
return ret;
}
int zcache_flush_page(int cli_id, int pool_id,
struct tmem_oid *oidp, uint32_t index)
{
struct tmem_pool *pool;
int ret = -1;
unsigned long flags;
local_irq_save(flags);
inc_zcache_flush_total();
pool = zcache_get_pool_by_id(cli_id, pool_id);
if (ramster_enabled)
ramster_do_preload_flnode(pool);
if (likely(pool != NULL)) {
if (atomic_read(&pool->obj_count) > 0)
ret = tmem_flush_page(pool, oidp, index);
zcache_put_pool(pool);
}
if (ret >= 0)
inc_zcache_flush_found();
local_irq_restore(flags);
return ret;
}
int zcache_flush_object(int cli_id, int pool_id,
struct tmem_oid *oidp)
{
struct tmem_pool *pool;
int ret = -1;
unsigned long flags;
local_irq_save(flags);
inc_zcache_flobj_total();
pool = zcache_get_pool_by_id(cli_id, pool_id);
if (ramster_enabled)
ramster_do_preload_flnode(pool);
if (likely(pool != NULL)) {
if (atomic_read(&pool->obj_count) > 0)
ret = tmem_flush_object(pool, oidp);
zcache_put_pool(pool);
}
if (ret >= 0)
inc_zcache_flobj_found();
local_irq_restore(flags);
return ret;
}
static int zcache_client_destroy_pool(int cli_id, int pool_id)
{
struct tmem_pool *pool = NULL;
struct zcache_client *cli = NULL;
int ret = -1;
if (pool_id < 0)
goto out;
if (cli_id == LOCAL_CLIENT)
cli = &zcache_host;
else if ((unsigned int)cli_id < MAX_CLIENTS)
cli = &zcache_clients[cli_id];
if (cli == NULL)
goto out;
atomic_inc(&cli->refcount);
pool = cli->tmem_pools[pool_id];
if (pool == NULL)
goto out;
cli->tmem_pools[pool_id] = NULL;
/* wait for pool activity on other cpus to quiesce */
while (atomic_read(&pool->refcount) != 0)
;
atomic_dec(&cli->refcount);
local_bh_disable();
ret = tmem_destroy_pool(pool);
local_bh_enable();
kfree(pool);
if (cli_id == LOCAL_CLIENT)
pr_info("%s: destroyed local pool id=%d\n", namestr, pool_id);
else
pr_info("%s: destroyed pool id=%d, client=%d\n",
namestr, pool_id, cli_id);
out:
return ret;
}
int zcache_new_pool(uint16_t cli_id, uint32_t flags)
{
int poolid = -1;
struct tmem_pool *pool;
struct zcache_client *cli = NULL;
if (cli_id == LOCAL_CLIENT)
cli = &zcache_host;
else if ((unsigned int)cli_id < MAX_CLIENTS)
cli = &zcache_clients[cli_id];
if (cli == NULL)
goto out;
atomic_inc(&cli->refcount);
pool = kmalloc(sizeof(struct tmem_pool), GFP_ATOMIC);
if (pool == NULL)
goto out;
for (poolid = 0; poolid < MAX_POOLS_PER_CLIENT; poolid++)
if (cli->tmem_pools[poolid] == NULL)
break;
if (poolid >= MAX_POOLS_PER_CLIENT) {
pr_info("%s: pool creation failed: max exceeded\n", namestr);
kfree(pool);
poolid = -1;
goto out;
}
atomic_set(&pool->refcount, 0);
pool->client = cli;
pool->pool_id = poolid;
tmem_new_pool(pool, flags);
cli->tmem_pools[poolid] = pool;
if (cli_id == LOCAL_CLIENT)
pr_info("%s: created %s local tmem pool, id=%d\n", namestr,
flags & TMEM_POOL_PERSIST ? "persistent" : "ephemeral",
poolid);
else
pr_info("%s: created %s tmem pool, id=%d, client=%d\n", namestr,
flags & TMEM_POOL_PERSIST ? "persistent" : "ephemeral",
poolid, cli_id);
out:
if (cli != NULL)
atomic_dec(&cli->refcount);
return poolid;
}
static int zcache_local_new_pool(uint32_t flags)
{
return zcache_new_pool(LOCAL_CLIENT, flags);
}
int zcache_autocreate_pool(unsigned int cli_id, unsigned int pool_id, bool eph)
{
struct tmem_pool *pool;
struct zcache_client *cli = NULL;
uint32_t flags = eph ? 0 : TMEM_POOL_PERSIST;
int ret = -1;
BUG_ON(!ramster_enabled);
if (cli_id == LOCAL_CLIENT)
goto out;
if (pool_id >= MAX_POOLS_PER_CLIENT)
goto out;
if (cli_id >= MAX_CLIENTS)
goto out;
cli = &zcache_clients[cli_id];
if ((eph && disable_cleancache) || (!eph && disable_frontswap)) {
pr_err("zcache_autocreate_pool: pool type disabled\n");
goto out;
}
if (!cli->allocated) {
if (zcache_new_client(cli_id)) {
pr_err("zcache_autocreate_pool: can't create client\n");
goto out;
}
cli = &zcache_clients[cli_id];
}
atomic_inc(&cli->refcount);
pool = cli->tmem_pools[pool_id];
if (pool != NULL) {
if (pool->persistent && eph) {
pr_err("zcache_autocreate_pool: type mismatch\n");
goto out;
}
ret = 0;
goto out;
}
pool = kmalloc(sizeof(struct tmem_pool), GFP_KERNEL);
if (pool == NULL)
goto out;
atomic_set(&pool->refcount, 0);
pool->client = cli;
pool->pool_id = pool_id;
tmem_new_pool(pool, flags);
cli->tmem_pools[pool_id] = pool;
pr_info("%s: AUTOcreated %s tmem poolid=%d, for remote client=%d\n",
namestr, flags & TMEM_POOL_PERSIST ? "persistent" : "ephemeral",
pool_id, cli_id);
ret = 0;
out:
if (cli != NULL)
atomic_dec(&cli->refcount);
return ret;
}
/**********
* Two kernel functionalities currently can be layered on top of tmem.
* These are "cleancache" which is used as a second-chance cache for clean
* page cache pages; and "frontswap" which is used for swap pages
* to avoid writes to disk. A generic "shim" is provided here for each
* to translate in-kernel semantics to zcache semantics.
*/
static void zcache_cleancache_put_page(int pool_id,
struct cleancache_filekey key,
pgoff_t index, struct page *page)
{
u32 ind = (u32) index;
struct tmem_oid oid = *(struct tmem_oid *)&key;
if (!disable_cleancache_ignore_nonactive && !PageWasActive(page)) {
inc_zcache_eph_nonactive_puts_ignored();
return;
}
if (likely(ind == index))
(void)zcache_put_page(LOCAL_CLIENT, pool_id, &oid, index,
page, PAGE_SIZE, false, 1);
}
static int zcache_cleancache_get_page(int pool_id,
struct cleancache_filekey key,
pgoff_t index, struct page *page)
{
u32 ind = (u32) index;
struct tmem_oid oid = *(struct tmem_oid *)&key;
size_t size;
int ret = -1;
if (likely(ind == index)) {
ret = zcache_get_page(LOCAL_CLIENT, pool_id, &oid, index,
page, &size, false, 0);
BUG_ON(ret >= 0 && size != PAGE_SIZE);
if (ret == 0)
SetPageWasActive(page);
}
return ret;
}
static void zcache_cleancache_flush_page(int pool_id,
struct cleancache_filekey key,
pgoff_t index)
{
u32 ind = (u32) index;
struct tmem_oid oid = *(struct tmem_oid *)&key;
if (likely(ind == index))
(void)zcache_flush_page(LOCAL_CLIENT, pool_id, &oid, ind);
}
static void zcache_cleancache_flush_inode(int pool_id,
struct cleancache_filekey key)
{
struct tmem_oid oid = *(struct tmem_oid *)&key;
(void)zcache_flush_object(LOCAL_CLIENT, pool_id, &oid);
}
static void zcache_cleancache_flush_fs(int pool_id)
{
if (pool_id >= 0)
(void)zcache_client_destroy_pool(LOCAL_CLIENT, pool_id);
}
static int zcache_cleancache_init_fs(size_t pagesize)
{
BUG_ON(sizeof(struct cleancache_filekey) !=
sizeof(struct tmem_oid));
BUG_ON(pagesize != PAGE_SIZE);
return zcache_local_new_pool(0);
}
static int zcache_cleancache_init_shared_fs(char *uuid, size_t pagesize)
{
/* shared pools are unsupported and map to private */
BUG_ON(sizeof(struct cleancache_filekey) !=
sizeof(struct tmem_oid));
BUG_ON(pagesize != PAGE_SIZE);
return zcache_local_new_pool(0);
}
static struct cleancache_ops zcache_cleancache_ops = {
.put_page = zcache_cleancache_put_page,
.get_page = zcache_cleancache_get_page,
.invalidate_page = zcache_cleancache_flush_page,
.invalidate_inode = zcache_cleancache_flush_inode,
.invalidate_fs = zcache_cleancache_flush_fs,
.init_shared_fs = zcache_cleancache_init_shared_fs,
.init_fs = zcache_cleancache_init_fs
};
struct cleancache_ops *zcache_cleancache_register_ops(void)
{
struct cleancache_ops *old_ops =
cleancache_register_ops(&zcache_cleancache_ops);
return old_ops;
}
/* a single tmem poolid is used for all frontswap "types" (swapfiles) */
static int zcache_frontswap_poolid __read_mostly = -1;
/*
* Swizzling increases objects per swaptype, increasing tmem concurrency
* for heavy swaploads. Later, larger nr_cpus -> larger SWIZ_BITS
* Setting SWIZ_BITS to 27 basically reconstructs the swap entry from
* frontswap_get_page(), but has side-effects. Hence using 8.
*/
#define SWIZ_BITS 8
#define SWIZ_MASK ((1 << SWIZ_BITS) - 1)
#define _oswiz(_type, _ind) ((_type << SWIZ_BITS) | (_ind & SWIZ_MASK))
#define iswiz(_ind) (_ind >> SWIZ_BITS)
static inline struct tmem_oid oswiz(unsigned type, u32 ind)
{
struct tmem_oid oid = { .oid = { 0 } };
oid.oid[0] = _oswiz(type, ind);
return oid;
}
#ifdef CONFIG_ZCACHE_WRITEBACK
static void unswiz(struct tmem_oid oid, u32 index,
unsigned *type, pgoff_t *offset)
{
*type = (unsigned)(oid.oid[0] >> SWIZ_BITS);
*offset = (pgoff_t)((index << SWIZ_BITS) |
(oid.oid[0] & SWIZ_MASK));
}
#endif
static int zcache_frontswap_put_page(unsigned type, pgoff_t offset,
struct page *page)
{
u64 ind64 = (u64)offset;
u32 ind = (u32)offset;
struct tmem_oid oid = oswiz(type, ind);
int ret = -1;
unsigned long flags;
BUG_ON(!PageLocked(page));
if (!disable_frontswap_ignore_nonactive && !PageWasActive(page)) {
inc_zcache_pers_nonactive_puts_ignored();
ret = -ERANGE;
goto out;
}
if (likely(ind64 == ind)) {
local_irq_save(flags);
ret = zcache_put_page(LOCAL_CLIENT, zcache_frontswap_poolid,
&oid, iswiz(ind),
page, PAGE_SIZE, false, 0);
local_irq_restore(flags);
}
out:
return ret;
}
/* returns 0 if the page was successfully gotten from frontswap, -1 if
* was not present (should never happen!) */
static int zcache_frontswap_get_page(unsigned type, pgoff_t offset,
struct page *page)
{
u64 ind64 = (u64)offset;
u32 ind = (u32)offset;
struct tmem_oid oid = oswiz(type, ind);
size_t size;
int ret = -1, get_and_free;
if (frontswap_has_exclusive_gets)
get_and_free = 1;
else
get_and_free = -1;
BUG_ON(!PageLocked(page));
if (likely(ind64 == ind)) {
ret = zcache_get_page(LOCAL_CLIENT, zcache_frontswap_poolid,
&oid, iswiz(ind),
page, &size, false, get_and_free);
BUG_ON(ret >= 0 && size != PAGE_SIZE);
}
return ret;
}
/* flush a single page from frontswap */
static void zcache_frontswap_flush_page(unsigned type, pgoff_t offset)
{
u64 ind64 = (u64)offset;
u32 ind = (u32)offset;
struct tmem_oid oid = oswiz(type, ind);
if (likely(ind64 == ind))
(void)zcache_flush_page(LOCAL_CLIENT, zcache_frontswap_poolid,
&oid, iswiz(ind));
}
/* flush all pages from the passed swaptype */
static void zcache_frontswap_flush_area(unsigned type)
{
struct tmem_oid oid;
int ind;
for (ind = SWIZ_MASK; ind >= 0; ind--) {
oid = oswiz(type, ind);
(void)zcache_flush_object(LOCAL_CLIENT,
zcache_frontswap_poolid, &oid);
}
}
static void zcache_frontswap_init(unsigned ignored)
{
/* a single tmem poolid is used for all frontswap "types" (swapfiles) */
if (zcache_frontswap_poolid < 0)
zcache_frontswap_poolid =
zcache_local_new_pool(TMEM_POOL_PERSIST);
}
static struct frontswap_ops zcache_frontswap_ops = {
.store = zcache_frontswap_put_page,
.load = zcache_frontswap_get_page,
.invalidate_page = zcache_frontswap_flush_page,
.invalidate_area = zcache_frontswap_flush_area,
.init = zcache_frontswap_init
};
struct frontswap_ops *zcache_frontswap_register_ops(void)
{
struct frontswap_ops *old_ops =
frontswap_register_ops(&zcache_frontswap_ops);
return old_ops;
}
/*
* zcache initialization
* NOTE FOR NOW zcache or ramster MUST BE PROVIDED AS A KERNEL BOOT PARAMETER
* OR NOTHING HAPPENS!
*/
#ifndef CONFIG_ZCACHE_MODULE
static int __init enable_zcache(char *s)
{
zcache_enabled = true;
return 1;
}
__setup("zcache", enable_zcache);
static int __init enable_ramster(char *s)
{
zcache_enabled = true;
#ifdef CONFIG_RAMSTER
ramster_enabled = true;
#endif
return 1;
}
__setup("ramster", enable_ramster);
/* allow independent dynamic disabling of cleancache and frontswap */
static int __init no_cleancache(char *s)
{
disable_cleancache = true;
return 1;
}
__setup("nocleancache", no_cleancache);
static int __init no_frontswap(char *s)
{
disable_frontswap = true;
return 1;
}
__setup("nofrontswap", no_frontswap);
static int __init no_frontswap_exclusive_gets(char *s)
{
frontswap_has_exclusive_gets = false;
return 1;
}
__setup("nofrontswapexclusivegets", no_frontswap_exclusive_gets);
static int __init no_frontswap_ignore_nonactive(char *s)
{
disable_frontswap_ignore_nonactive = true;
return 1;
}
__setup("nofrontswapignorenonactive", no_frontswap_ignore_nonactive);
static int __init no_cleancache_ignore_nonactive(char *s)
{
disable_cleancache_ignore_nonactive = true;
return 1;
}
__setup("nocleancacheignorenonactive", no_cleancache_ignore_nonactive);
static int __init enable_zcache_compressor(char *s)
{
strlcpy(zcache_comp_name, s, sizeof(zcache_comp_name));
zcache_enabled = true;
return 1;
}
__setup("zcache=", enable_zcache_compressor);
#endif
static int zcache_comp_init(void)
{
int ret = 0;
/* check crypto algorithm */
#ifdef CONFIG_ZCACHE_MODULE
ret = crypto_has_comp(zcache_comp_name, 0, 0);
if (!ret) {
ret = -1;
goto out;
}
#else
if (*zcache_comp_name != '\0') {
ret = crypto_has_comp(zcache_comp_name, 0, 0);
if (!ret) {
pr_info("zcache: %s not supported\n",
zcache_comp_name);
ret = 1;
goto out;
}
}
if (!ret)
strcpy(zcache_comp_name, "lzo");
ret = crypto_has_comp(zcache_comp_name, 0, 0);
if (!ret) {
ret = 1;
goto out;
}
#endif
pr_info("zcache: using %s compressor\n", zcache_comp_name);
/* alloc percpu transforms */
ret = 0;
zcache_comp_pcpu_tfms = alloc_percpu(struct crypto_comp *);
if (!zcache_comp_pcpu_tfms)
ret = 1;
out:
return ret;
}
static int zcache_init(void)
{
int ret = 0;
#ifdef CONFIG_ZCACHE_MODULE
zcache_enabled = 1;
#endif
if (ramster_enabled) {
namestr = "ramster";
ramster_register_pamops(&zcache_pamops);
}
zcache_debugfs_init();
if (zcache_enabled) {
unsigned int cpu;
tmem_register_hostops(&zcache_hostops);
tmem_register_pamops(&zcache_pamops);
ret = register_cpu_notifier(&zcache_cpu_notifier_block);
if (ret) {
pr_err("%s: can't register cpu notifier\n", namestr);
goto out;
}
ret = zcache_comp_init();
if (ret) {
pr_err("%s: compressor initialization failed\n",
namestr);
goto out;
}
for_each_online_cpu(cpu) {
void *pcpu = (void *)(long)cpu;
zcache_cpu_notifier(&zcache_cpu_notifier_block,
CPU_UP_PREPARE, pcpu);
}
}
zcache_objnode_cache = kmem_cache_create("zcache_objnode",
sizeof(struct tmem_objnode), 0, 0, NULL);
zcache_obj_cache = kmem_cache_create("zcache_obj",
sizeof(struct tmem_obj), 0, 0, NULL);
ret = zcache_new_client(LOCAL_CLIENT);
if (ret) {
pr_err("%s: can't create client\n", namestr);
goto out;
}
zbud_init();
if (zcache_enabled && !disable_cleancache) {
struct cleancache_ops *old_ops;
register_shrinker(&zcache_shrinker);
old_ops = zcache_cleancache_register_ops();
pr_info("%s: cleancache enabled using kernel transcendent "
"memory and compression buddies\n", namestr);
#ifdef CONFIG_ZCACHE_DEBUG
pr_info("%s: cleancache: ignorenonactive = %d\n",
namestr, !disable_cleancache_ignore_nonactive);
#endif
if (old_ops != NULL)
pr_warn("%s: cleancache_ops overridden\n", namestr);
}
if (zcache_enabled && !disable_frontswap) {
struct frontswap_ops *old_ops;
old_ops = zcache_frontswap_register_ops();
if (frontswap_has_exclusive_gets)
frontswap_tmem_exclusive_gets(true);
pr_info("%s: frontswap enabled using kernel transcendent "
"memory and compression buddies\n", namestr);
#ifdef CONFIG_ZCACHE_DEBUG
pr_info("%s: frontswap: excl gets = %d active only = %d\n",
namestr, frontswap_has_exclusive_gets,
!disable_frontswap_ignore_nonactive);
#endif
if (IS_ERR(old_ops) || old_ops) {
if (IS_ERR(old_ops))
return PTR_RET(old_ops);
pr_warn("%s: frontswap_ops overridden\n", namestr);
}
}
if (ramster_enabled)
ramster_init(!disable_cleancache, !disable_frontswap,
frontswap_has_exclusive_gets,
!disable_frontswap_selfshrink);
out:
return ret;
}
#ifdef CONFIG_ZCACHE_MODULE
#ifdef CONFIG_RAMSTER
module_param(ramster_enabled, bool, S_IRUGO);
module_param(disable_frontswap_selfshrink, int, S_IRUGO);
#endif
module_param(disable_cleancache, bool, S_IRUGO);
module_param(disable_frontswap, bool, S_IRUGO);
#ifdef FRONTSWAP_HAS_EXCLUSIVE_GETS
module_param(frontswap_has_exclusive_gets, bool, S_IRUGO);
#endif
module_param(disable_frontswap_ignore_nonactive, bool, S_IRUGO);
module_param(zcache_comp_name, charp, S_IRUGO);
module_init(zcache_init);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Dan Magenheimer <dan.magenheimer@oracle.com>");
MODULE_DESCRIPTION("In-kernel compression of cleancache/frontswap pages");
#else
late_initcall(zcache_init);
#endif
/*
* zcache.h
*
* Copyright (c) 2012, Dan Magenheimer, Oracle Corp.
*/
#ifndef _ZCACHE_H_
#define _ZCACHE_H_
struct zcache_preload {
struct tmem_obj *obj;
struct tmem_objnode *objnodes[OBJNODE_TREE_MAX_PATH];
};
struct tmem_pool;
#define MAX_POOLS_PER_CLIENT 16
#define MAX_CLIENTS 16
#define LOCAL_CLIENT ((uint16_t)-1)
struct zcache_client {
struct tmem_pool *tmem_pools[MAX_POOLS_PER_CLIENT];
bool allocated;
atomic_t refcount;
};
extern struct tmem_pool *zcache_get_pool_by_id(uint16_t cli_id,
uint16_t poolid);
extern void zcache_put_pool(struct tmem_pool *pool);
extern int zcache_put_page(int, int, struct tmem_oid *,
uint32_t, void *,
unsigned int, bool, int);
extern int zcache_get_page(int, int, struct tmem_oid *, uint32_t,
void *, size_t *, bool, int);
extern int zcache_flush_page(int, int, struct tmem_oid *, uint32_t);
extern int zcache_flush_object(int, int, struct tmem_oid *);
extern void zcache_decompress_to_page(char *, unsigned int, struct page *);
#if defined(CONFIG_RAMSTER) || defined(CONFIG_RAMSTER_MODULE)
extern void *zcache_pampd_create(char *, unsigned int, bool, int,
struct tmem_handle *);
int zcache_autocreate_pool(unsigned int cli_id, unsigned int pool_id, bool eph);
#endif
#define MAX_POOLS_PER_CLIENT 16
#define MAX_CLIENTS 16
#define LOCAL_CLIENT ((uint16_t)-1)
#endif /* _ZCACHE_H_ */
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment