Commit b453302f authored by Philippe Gerum's avatar Philippe Gerum
Browse files

drivers/testing: add core heap test module

parent 0fabf4a9
......@@ -87,6 +87,37 @@ struct rttst_swtest_error {
#define RTTST_RTDM_MAGIC_PRIMARY 0xfefbfefb
#define RTTST_RTDM_MAGIC_SECONDARY 0xa5b9a5b9
#define RTTST_HEAPCHECK_ZEROOVRD 1
#define RTTST_HEAPCHECK_SHUFFLE 2
#define RTTST_HEAPCHECK_PATTERN 4
#define RTTST_HEAPCHECK_HOT 8
struct rttst_heap_parms {
__u64 heap_size;
__u64 block_size;
int flags;
int nrstats;
};
struct rttst_heap_stats {
__u64 heap_size;
__u64 user_size;
__u64 block_size;
__s64 alloc_avg_ns;
__s64 alloc_max_ns;
__s64 free_avg_ns;
__s64 free_max_ns;
__u64 maximum_free;
__u64 largest_free;
int nrblocks;
int flags;
};
struct rttst_heap_stathdr {
int nrstats;
struct rttst_heap_stats *buf;
};
#define RTIOC_TYPE_TESTING RTDM_CLASS_TESTING
/*!
......@@ -100,6 +131,8 @@ struct rttst_swtest_error {
#define RTDM_SUBCLASS_SWITCHTEST 2
/** subclase name: "rtdm" */
#define RTDM_SUBCLASS_RTDMTEST 3
/** subclase name: "heapcheck" */
#define RTDM_SUBCLASS_HEAPCHECK 4
/** @} */
/*!
......@@ -153,7 +186,13 @@ struct rttst_swtest_error {
#define RTTST_RTIOC_RTDM_PING_SECONDARY \
_IOR(RTIOC_TYPE_TESTING, 0x43, __u32)
#define RTTST_RTIOC_HEAP_CHECK \
_IOR(RTIOC_TYPE_TESTING, 0x44, struct rttst_heap_parms)
#define RTTST_RTIOC_HEAP_STAT_COLLECT \
_IOR(RTIOC_TYPE_TESTING, 0x45, int)
/** @} */
#endif /* !_RTDM_UAPI_TESTING_H */
......@@ -14,6 +14,12 @@ config XENO_DRIVERS_SWITCHTEST
Kernel-based driver for unit testing context switches and
FPU switches.
config XENO_DRIVERS_HEAPCHECK
tristate "Memory allocator test driver"
default y
help
Kernel-based driver for testing Cobalt's memory allocator.
config XENO_DRIVERS_RTDMTEST
depends on m
tristate "RTDM unit tests driver"
......
......@@ -2,9 +2,12 @@
obj-$(CONFIG_XENO_DRIVERS_TIMERBENCH) += xeno_timerbench.o
obj-$(CONFIG_XENO_DRIVERS_SWITCHTEST) += xeno_switchtest.o
obj-$(CONFIG_XENO_DRIVERS_RTDMTEST) += xeno_rtdmtest.o
obj-$(CONFIG_XENO_DRIVERS_HEAPCHECK) += xeno_heapcheck.o
xeno_timerbench-y := timerbench.o
xeno_switchtest-y := switchtest.o
xeno_rtdmtest-y := rtdmtest.o
xeno_heapcheck-y := heapcheck.o
/*
* Copyright (C) 2018 Philippe Gerum <rpm@xenomai.org>.
*
* Xenomai 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.
*
* Xenomai 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 Xenomai; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA
* 02111-1307, USA.
*/
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/random.h>
#include <cobalt/kernel/assert.h>
#include <cobalt/kernel/heap.h>
#include <rtdm/uapi/testing.h>
#include <rtdm/driver.h>
#define complain(__fmt, __args...) \
printk(XENO_WARNING "heap check: " __fmt "\n", ##__args)
static struct xnheap test_heap = {
.name = "test_heap"
};
enum pattern {
alphabet_series,
digit_series,
binary_series,
};
struct chunk {
void *ptr;
enum pattern pattern;
};
struct runstats {
struct rttst_heap_stats stats;
struct runstats *next;
};
static struct runstats *statistics;
static int nrstats;
static inline void breathe(int loops)
{
if ((loops % 1000) == 0)
rtdm_task_sleep(300000ULL);
}
static inline void do_swap(void *left, void *right, const size_t size)
{
char trans[size];
memcpy(trans, left, size);
memcpy(left, right, size);
memcpy(right, trans, size);
}
static void random_shuffle(void *vbase, size_t nmemb, const size_t size)
{
struct {
char x[size];
} __attribute__((packed)) *base = vbase;
unsigned int j, k;
for (j = nmemb; j > 0; j--) {
k = (unsigned int)(prandom_u32() % nmemb) + 1;
if (j == k)
continue;
do_swap(&base[j - 1], &base[k - 1], size);
}
}
static void fill_pattern(char *p, size_t size, enum pattern pat)
{
unsigned int val, count;
switch (pat) {
case alphabet_series:
val = 'a';
count = 26;
break;
case digit_series:
val = '0';
count = 10;
break;
default:
val = 0;
count = 255;
break;
}
while (size-- > 0) {
*p++ = (char)(val % count);
val++;
}
}
static int check_pattern(const char *p, size_t size, enum pattern pat)
{
unsigned int val, count;
switch (pat) {
case alphabet_series:
val = 'a';
count = 26;
break;
case digit_series:
val = '0';
count = 10;
break;
default:
val = 0;
count = 255;
break;
}
while (size-- > 0) {
if (*p++ != (char)(val % count))
return 0;
val++;
}
return 1;
}
static size_t find_largest_free(size_t free_size, size_t block_size)
{
void *p;
for (;;) {
p = xnheap_alloc(&test_heap, free_size);
if (p) {
xnheap_free(&test_heap, p);
break;
}
if (free_size <= block_size)
break;
free_size -= block_size;
}
return free_size;
}
static int test_seq(size_t heap_size, size_t block_size, int flags)
{
long alloc_sum_ns, alloc_avg_ns, free_sum_ns, free_avg_ns,
alloc_max_ns, free_max_ns, d;
size_t user_size, largest_free, maximum_free, freed;
int ret, n, k, maxblocks, nrblocks;
nanosecs_rel_t start, end;
struct chunk *chunks;
struct runstats *st;
bool done_frag;
void *mem, *p;
maxblocks = heap_size / block_size;
mem = vmalloc(heap_size);
if (mem == NULL)
return -ENOMEM;
ret = xnheap_init(&test_heap, mem, heap_size);
if (ret) {
complain("cannot init heap with size %zu",
heap_size);
goto out;
}
chunks = vmalloc(sizeof(*chunks) * maxblocks);
if (chunks == NULL) {
ret = -ENOMEM;
goto no_chunks;
}
memset(chunks, 0, sizeof(*chunks) * maxblocks);
ret = xnthread_harden();
if (ret)
goto done;
if (xnheap_get_size(&test_heap) != heap_size) {
complain("memory size inconsistency (%zu / %zu bytes)",
heap_size, xnheap_get_size(&test_heap));
goto bad;
}
user_size = 0;
alloc_avg_ns = 0;
free_avg_ns = 0;
alloc_max_ns = 0;
free_max_ns = 0;
maximum_free = 0;
largest_free = 0;
for (n = 0, alloc_sum_ns = 0; ; n++) {
start = rtdm_clock_read_monotonic();
p = xnheap_alloc(&test_heap, block_size);
end = rtdm_clock_read_monotonic();
d = end - start;
if (d > alloc_max_ns)
alloc_max_ns = d;
alloc_sum_ns += d;
if (p == NULL)
break;
user_size += block_size;
if (n >= maxblocks) {
complain("too many blocks fetched"
" (heap=%zu, block=%zu, "
"got more than %d blocks)",
heap_size, block_size, maxblocks);
goto bad;
}
chunks[n].ptr = p;
if (flags & RTTST_HEAPCHECK_PATTERN) {
chunks[n].pattern = (enum pattern)(prandom_u32() % 3);
fill_pattern(chunks[n].ptr, block_size, chunks[n].pattern);
}
breathe(n);
}
nrblocks = n;
if (nrblocks == 0)
goto do_stats;
if ((flags & RTTST_HEAPCHECK_ZEROOVRD) && nrblocks != maxblocks) {
complain("too few blocks fetched, unexpected overhead"
" (heap=%zu, block=%zu, "
"got %d, less than %d blocks)",
heap_size, block_size, nrblocks, maxblocks);
goto bad;
}
breathe(0);
/* Make sure we did not trash any busy block while allocating. */
if (flags & RTTST_HEAPCHECK_PATTERN) {
for (n = 0; n < nrblocks; n++) {
if (!check_pattern(chunks[n].ptr, block_size,
chunks[n].pattern)) {
complain("corrupted block #%d on alloc"
" sequence (pattern %d)",
n, chunks[n].pattern);
goto bad;
}
breathe(n);
}
}
if (flags & RTTST_HEAPCHECK_SHUFFLE)
random_shuffle(chunks, nrblocks, sizeof(*chunks));
/*
* Release all blocks.
*/
for (n = 0, free_sum_ns = 0, freed = 0, done_frag = false;
n < nrblocks; n++) {
start = rtdm_clock_read_monotonic();
xnheap_free(&test_heap, chunks[n].ptr);
end = rtdm_clock_read_monotonic();
d = end - start;
if (d > free_max_ns)
free_max_ns = d;
free_sum_ns += d;
chunks[n].ptr = NULL;
/* Make sure we did not trash busy blocks while freeing. */
if (flags & RTTST_HEAPCHECK_PATTERN) {
for (k = 0; k < nrblocks; k++) {
if (chunks[k].ptr &&
!check_pattern(chunks[k].ptr, block_size,
chunks[k].pattern)) {
complain("corrupted block #%d on release"
" sequence (pattern %d)",
k, chunks[k].pattern);
goto bad;
}
breathe(k);
}
}
freed += block_size;
/*
* Get a sense of the fragmentation for the tested
* allocation pattern, heap and block sizes when half
* of the usable heap size should be available to us.
* NOTE: user_size excludes the overhead, this is
* actually what we managed to get from the current
* heap out of the allocation loop.
*/
if (!done_frag && freed >= user_size / 2) {
/* Calculate the external fragmentation. */
largest_free = find_largest_free(freed, block_size);
maximum_free = freed;
done_frag = true;
}
breathe(n);
}
/*
* If the deallocation mechanism is broken, we might not be
* able to reproduce the same allocation pattern with the same
* outcome, check this.
*/
if (flags & RTTST_HEAPCHECK_HOT) {
for (n = 0, alloc_max_ns = alloc_sum_ns = 0; ; n++) {
start = rtdm_clock_read_monotonic();
p = xnheap_alloc(&test_heap, block_size);
end = rtdm_clock_read_monotonic();
d = end - start;
if (d > alloc_max_ns)
alloc_max_ns = d;
alloc_sum_ns += d;
if (p == NULL)
break;
if (n >= maxblocks) {
complain("too many blocks fetched during hot pass"
" (heap=%zu, block=%zu, "
"got more than %d blocks)",
heap_size, block_size, maxblocks);
goto bad;
}
chunks[n].ptr = p;
breathe(n);
}
if (n != nrblocks) {
complain("inconsistent block count fetched"
" during hot pass (heap=%zu, block=%zu, "
"got %d blocks vs %d during alloc)",
heap_size, block_size, n, nrblocks);
goto bad;
}
for (n = 0, free_max_ns = free_sum_ns = 0; n < nrblocks; n++) {
start = rtdm_clock_read_monotonic();
xnheap_free(&test_heap, chunks[n].ptr);
end = rtdm_clock_read_monotonic();
d = end - start;
if (d > free_max_ns)
free_max_ns = d;
free_sum_ns += d;
breathe(n);
}
}
alloc_avg_ns = alloc_sum_ns / nrblocks;
free_avg_ns = free_sum_ns / nrblocks;
if ((flags & RTTST_HEAPCHECK_ZEROOVRD) && heap_size != user_size) {
complain("unexpected overhead reported");
goto bad;
}
if (xnheap_get_used(&test_heap) > 0) {
complain("memory leakage reported: %zu bytes missing",
xnheap_get_used(&test_heap));
goto bad;
}
do_stats:
xnthread_relax(0, 0);
ret = 0;
/*
* Don't report stats when running a pattern check, timings
* are affected.
*/
if (!(flags & RTTST_HEAPCHECK_PATTERN)) {
st = kmalloc(sizeof(*st), GFP_KERNEL);
if (st == NULL) {
complain("failed allocating memory");
ret = -ENOMEM;
goto out;
}
st->stats.heap_size = heap_size;
st->stats.user_size = user_size;
st->stats.block_size = block_size;
st->stats.nrblocks = nrblocks;
st->stats.alloc_avg_ns = alloc_avg_ns;
st->stats.alloc_max_ns = alloc_max_ns;
st->stats.free_avg_ns = free_avg_ns;
st->stats.free_max_ns = free_max_ns;
st->stats.maximum_free = maximum_free;
st->stats.largest_free = largest_free;
st->stats.flags = flags;
st->next = statistics;
statistics = st;
nrstats++;
}
done:
vfree(chunks);
no_chunks:
xnheap_destroy(&test_heap);
out:
vfree(mem);
return ret;
bad:
xnthread_relax(0, 0);
ret = -EPROTO;
goto done;
}
static int collect_stats(struct rtdm_fd *fd,
struct rttst_heap_stats __user *buf, int nr)
{
struct runstats *p, *next;
int ret, n;
if (nr < 0)
return -EINVAL;
for (p = statistics, n = nr; p && n > 0 && nrstats > 0;
n--, nrstats--, p = next, buf += sizeof(p->stats)) {
ret = rtdm_copy_to_user(fd, buf, &p->stats, sizeof(p->stats));
if (ret)
return ret;
next = p->next;
statistics = next;
kfree(p);
}
return nr - n;
}
static void heapcheck_close(struct rtdm_fd *fd)
{
struct runstats *p, *next;
for (p = statistics; p; p = next) {
next = p->next;
kfree(p);
}
statistics = NULL;
}
static int heapcheck_ioctl(struct rtdm_fd *fd,
unsigned int request, void __user *arg)
{
struct rttst_heap_stathdr sthdr;
struct rttst_heap_parms parms;
int ret;
switch (request) {
case RTTST_RTIOC_HEAP_CHECK:
ret = rtdm_copy_from_user(fd, &parms, arg, sizeof(parms));
if (ret)
return ret;
ret = test_seq(parms.heap_size,
parms.block_size,
parms.flags);
if (ret)
return ret;
parms.nrstats = nrstats;
ret = rtdm_copy_to_user(fd, arg, &parms, sizeof(parms));
break;
case RTTST_RTIOC_HEAP_STAT_COLLECT:
ret = rtdm_copy_from_user(fd, &sthdr, arg, sizeof(sthdr));
if (ret)
return ret;
ret = collect_stats(fd, sthdr.buf, sthdr.nrstats);
if (ret < 0)
return ret;
sthdr.nrstats = ret;
ret = rtdm_copy_to_user(fd, arg, &sthdr, sizeof(sthdr));
break;
default:
ret = -EINVAL;
}
return ret;
}
static struct rtdm_driver heapcheck_driver = {
.profile_info = RTDM_PROFILE_INFO(heap_check,
RTDM_CLASS_TESTING,
RTDM_SUBCLASS_HEAPCHECK,
RTTST_PROFILE_VER),
.device_flags = RTDM_NAMED_DEVICE | RTDM_EXCLUSIVE,
.device_count = 1,
.ops = {
.close = heapcheck_close,
.ioctl_nrt = heapcheck_ioctl,
},
};
static struct rtdm_device heapcheck_device = {
.driver = &heapcheck_driver,
.label = "heapcheck",
};
static int __init heapcheck_init(void)
{
if (!realtime_core_enabled())
return -ENODEV;
return rtdm_dev_register(&heapcheck_device);
}
static void __exit heapcheck_exit(void)
{
rtdm_dev_unregister(&heapcheck_device);
}
module_init(heapcheck_init);
module_exit(heapcheck_exit);
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