Initial commit: Linux kernel 4.19 for Unplugged OS

This commit is contained in:
ziv committed 2025-10-06 13:52:42 +00:00
commit d30235b04c
80519 files changed
+35800681

No files matched your search

+220
View File
@@ -0,0 +1,220 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0
#
# NAME
# failcmd.sh - run a command with injecting slab/page allocation failures
#
# SYNOPSIS
# failcmd.sh --help
# failcmd.sh [<options>] command [arguments]
#
# DESCRIPTION
# Run command with injecting slab/page allocation failures by fault
# injection.
#
# NOTE: you need to run this script as root.
#
usage()
{
cat >&2 <<EOF
Usage: $0 [options] command [arguments]
OPTIONS
-p percent
--probability=percent
likelihood of failure injection, in percent.
Default value is 1
-t value
--times=value
specifies how many times failures may happen at most.
Default value is 1
--oom-kill-allocating-task=value
set /proc/sys/vm/oom_kill_allocating_task to specified value
before running the command.
Default value is 1
-h, --help
Display a usage message and exit
--interval=value, --space=value, --verbose=value, --task-filter=value,
--stacktrace-depth=value, --require-start=value, --require-end=value,
--reject-start=value, --reject-end=value, --ignore-gfp-wait=value
See Documentation/fault-injection/fault-injection.txt for more
information
failslab options:
--cache-filter=value
fail_page_alloc options:
--ignore-gfp-highmem=value, --min-order=value
ENVIRONMENT
FAILCMD_TYPE
The following values for FAILCMD_TYPE are recognized:
failslab
inject slab allocation failures
fail_page_alloc
inject page allocation failures
If FAILCMD_TYPE is not defined, then failslab is used.
EOF
}
if [ $UID != 0 ]; then
echo must be run as root >&2
exit 1
fi
DEBUGFS=`mount -t debugfs | head -1 | awk '{ print $3}'`
if [ ! -d "$DEBUGFS" ]; then
echo debugfs is not mounted >&2
exit 1
fi
FAILCMD_TYPE=${FAILCMD_TYPE:-failslab}
FAULTATTR=$DEBUGFS/$FAILCMD_TYPE
if [ ! -d $FAULTATTR ]; then
echo $FAILCMD_TYPE is not available >&2
exit 1
fi
LONGOPTS=probability:,interval:,times:,space:,verbose:,task-filter:
LONGOPTS=$LONGOPTS,stacktrace-depth:,require-start:,require-end:
LONGOPTS=$LONGOPTS,reject-start:,reject-end:,oom-kill-allocating-task:,help
if [ $FAILCMD_TYPE = failslab ]; then
LONGOPTS=$LONGOPTS,ignore-gfp-wait:,cache-filter:
elif [ $FAILCMD_TYPE = fail_page_alloc ]; then
LONGOPTS=$LONGOPTS,ignore-gfp-wait:,ignore-gfp-highmem:,min-order:
fi
TEMP=`getopt -o p:i:t:s:v:h --long $LONGOPTS -n 'failcmd.sh' -- "$@"`
if [ $? != 0 ]; then
usage
exit 1
fi
eval set -- "$TEMP"
fault_attr_default()
{
echo N > $FAULTATTR/task-filter
echo 0 > $FAULTATTR/probability
echo 1 > $FAULTATTR/times
}
fault_attr_default
oom_kill_allocating_task_saved=`cat /proc/sys/vm/oom_kill_allocating_task`
restore_values()
{
fault_attr_default
echo $oom_kill_allocating_task_saved \
> /proc/sys/vm/oom_kill_allocating_task
}
#
# Default options
#
declare -i oom_kill_allocating_task=1
declare task_filter=Y
declare -i probability=1
declare -i times=1
while true; do
case "$1" in
-p|--probability)
probability=$2
shift 2
;;
-i|--interval)
echo $2 > $FAULTATTR/interval
shift 2
;;
-t|--times)
times=$2
shift 2
;;
-s|--space)
echo $2 > $FAULTATTR/space
shift 2
;;
-v|--verbose)
echo $2 > $FAULTATTR/verbose
shift 2
;;
--task-filter)
task_filter=$2
shift 2
;;
--stacktrace-depth)
echo $2 > $FAULTATTR/stacktrace-depth
shift 2
;;
--require-start)
echo $2 > $FAULTATTR/require-start
shift 2
;;
--require-end)
echo $2 > $FAULTATTR/require-end
shift 2
;;
--reject-start)
echo $2 > $FAULTATTR/reject-start
shift 2
;;
--reject-end)
echo $2 > $FAULTATTR/reject-end
shift 2
;;
--oom-kill-allocating-task)
oom_kill_allocating_task=$2
shift 2
;;
--ignore-gfp-wait)
echo $2 > $FAULTATTR/ignore-gfp-wait
shift 2
;;
--cache-filter)
echo $2 > $FAULTATTR/cache_filter
shift 2
;;
--ignore-gfp-highmem)
echo $2 > $FAULTATTR/ignore-gfp-highmem
shift 2
;;
--min-order)
echo $2 > $FAULTATTR/min-order
shift 2
;;
-h|--help)
usage
exit 0
shift
;;
--)
shift
break
;;
esac
done
[ -z "$1" ] && exit 0
echo $oom_kill_allocating_task > /proc/sys/vm/oom_kill_allocating_task
echo $task_filter > $FAULTATTR/task-filter
echo $probability > $FAULTATTR/probability
echo $times > $FAULTATTR/times
trap "restore_values" SIGINT SIGTERM EXIT
cmd="echo 1 > /proc/self/make-it-fail && exec $@"
bash -c "$cmd"
+33
View File
@@ -0,0 +1,33 @@
#!/usr/bin/env perl
# SPDX-License-Identifier: GPL-2.0
open (IN,"ktest.pl");
while (<IN>) {
# hashes are now used
if (/\$opt\{"?([A-Z].*?)(\[.*\])?"?\}/ ||
/^\s*"?([A-Z].*?)"?\s*=>\s*/ ||
/set_test_option\("(.*?)"/) {
$opt{$1} = 1;
}
}
close IN;
open (IN, "sample.conf");
while (<IN>) {
if (/^\s*#?\s*([A-Z]\S*)\s*=/) {
$samp{$1} = 1;
}
}
close IN;
foreach $opt (keys %opt) {
if (!defined($samp{$opt})) {
print "opt = $opt\n";
}
}
foreach $samp (keys %samp) {
if (!defined($opt{$samp})) {
print "samp = $samp\n";
}
}
+770
View File
@@ -0,0 +1,770 @@
#!/usr/bin/perl -w
#
# Copyright 2015 - Steven Rostedt, Red Hat Inc.
# Copyright 2017 - Steven Rostedt, VMware, Inc.
#
# Licensed under the terms of the GNU GPL License version 2
#
# usage:
# config-bisect.pl [options] good-config bad-config [good|bad]
#
# Compares a good config to a bad config, then takes half of the diffs
# and produces a config that is somewhere between the good config and
# the bad config. That is, the resulting config will start with the
# good config and will try to make half of the differences of between
# the good and bad configs match the bad config. It tries because of
# dependencies between the two configs it may not be able to change
# exactly half of the configs that are different between the two config
# files.
# Here's a normal way to use it:
#
# $ cd /path/to/linux/kernel
# $ config-bisect.pl /path/to/good/config /path/to/bad/config
# This will now pull in good config (blowing away .config in that directory
# so do not make that be one of the good or bad configs), and then
# build the config with "make oldconfig" to make sure it matches the
# current kernel. It will then store the configs in that result for
# the good config. It does the same for the bad config as well.
# The algorithm will run, merging half of the differences between
# the two configs and building them with "make oldconfig" to make sure
# the result changes (dependencies may reset changes the tool had made).
# It then copies the result of its good config to /path/to/good/config.tmp
# and the bad config to /path/to/bad/config.tmp (just appends ".tmp" to the
# files passed in). And the ".config" that you should test will be in
# directory
# After the first run, determine if the result is good or bad then
# run the same command appending the result
# For good results:
# $ config-bisect.pl /path/to/good/config /path/to/bad/config good
# For bad results:
# $ config-bisect.pl /path/to/good/config /path/to/bad/config bad
# Do not change the good-config or bad-config, config-bisect.pl will
# copy the good-config to a temp file with the same name as good-config
# but with a ".tmp" after it. It will do the same with the bad-config.
# If "good" or "bad" is not stated at the end, it will copy the good and
# bad configs to the .tmp versions. If a .tmp version already exists, it will
# warn before writing over them (-r will not warn, and just write over them).
# If the last config is labeled "good", then it will copy it to the good .tmp
# version. If the last config is labeled "bad", it will copy it to the bad
# .tmp version. It will continue this until it can not merge the two any more
# without the result being equal to either the good or bad .tmp configs.
my $start = 0;
my $val = "";
my $pwd = `pwd`;
chomp $pwd;
my $tree = $pwd;
my $build;
my $output_config;
my $reset_bisect;
sub usage {
print << "EOF"
usage: config-bisect.pl [-l linux-tree][-b build-dir] good-config bad-config [good|bad]
-l [optional] define location of linux-tree (default is current directory)
-b [optional] define location to build (O=build-dir) (default is linux-tree)
good-config the config that is considered good
bad-config the config that does not work
"good" add this if the last run produced a good config
"bad" add this if the last run produced a bad config
If "good" or "bad" is not specified, then it is the start of a new bisect
Note, each run will create copy of good and bad configs with ".tmp" appended.
EOF
;
exit(-1);
}
sub doprint {
print @_;
}
sub dodie {
doprint "CRITICAL FAILURE... ", @_, "\n";
die @_, "\n";
}
sub expand_path {
my ($file) = @_;
if ($file =~ m,^/,) {
return $file;
}
return "$pwd/$file";
}
sub read_prompt {
my ($cancel, $prompt) = @_;
my $ans;
for (;;) {
if ($cancel) {
print "$prompt [y/n/C] ";
} else {
print "$prompt [y/N] ";
}
$ans = <STDIN>;
chomp $ans;
if ($ans =~ /^\s*$/) {
if ($cancel) {
$ans = "c";
} else {
$ans = "n";
}
}
last if ($ans =~ /^y$/i || $ans =~ /^n$/i);
if ($cancel) {
last if ($ans =~ /^c$/i);
print "Please answer either 'y', 'n' or 'c'.\n";
} else {
print "Please answer either 'y' or 'n'.\n";
}
}
if ($ans =~ /^c/i) {
exit;
}
if ($ans !~ /^y$/i) {
return 0;
}
return 1;
}
sub read_yn {
my ($prompt) = @_;
return read_prompt 0, $prompt;
}
sub read_ync {
my ($prompt) = @_;
return read_prompt 1, $prompt;
}
sub run_command {
my ($command, $redirect) = @_;
my $start_time;
my $end_time;
my $dord = 0;
my $pid;
$start_time = time;
doprint("$command ... ");
$pid = open(CMD, "$command 2>&1 |") or
dodie "unable to exec $command";
if (defined($redirect)) {
open (RD, ">$redirect") or
dodie "failed to write to redirect $redirect";
$dord = 1;
}
while (<CMD>) {
print RD if ($dord);
}
waitpid($pid, 0);
my $failed = $?;
close(CMD);
close(RD) if ($dord);
$end_time = time;
my $delta = $end_time - $start_time;
if ($delta == 1) {
doprint "[1 second] ";
} else {
doprint "[$delta seconds] ";
}
if ($failed) {
doprint "FAILED!\n";
} else {
doprint "SUCCESS\n";
}
return !$failed;
}
###### CONFIG BISECT ######
# config_ignore holds the configs that were set (or unset) for
# a good config and we will ignore these configs for the rest
# of a config bisect. These configs stay as they were.
my %config_ignore;
# config_set holds what all configs were set as.
my %config_set;
# config_off holds the set of configs that the bad config had disabled.
# We need to record them and set them in the .config when running
# olddefconfig, because olddefconfig keeps the defaults.
my %config_off;
# config_off_tmp holds a set of configs to turn off for now
my @config_off_tmp;
# config_list is the set of configs that are being tested
my %config_list;
my %null_config;
my %dependency;
my $make;
sub make_oldconfig {
if (!run_command "$make olddefconfig") {
# Perhaps olddefconfig doesn't exist in this version of the kernel
# try oldnoconfig
doprint "olddefconfig failed, trying make oldnoconfig\n";
if (!run_command "$make oldnoconfig") {
doprint "oldnoconfig failed, trying yes '' | make oldconfig\n";
# try a yes '' | oldconfig
run_command "yes '' | $make oldconfig" or
dodie "failed make config oldconfig";
}
}
}
sub assign_configs {
my ($hash, $config) = @_;
doprint "Reading configs from $config\n";
open (IN, $config)
or dodie "Failed to read $config";
while (<IN>) {
chomp;
if (/^((CONFIG\S*)=.*)/) {
${$hash}{$2} = $1;
} elsif (/^(# (CONFIG\S*) is not set)/) {
${$hash}{$2} = $1;
}
}
close(IN);
}
sub process_config_ignore {
my ($config) = @_;
assign_configs \%config_ignore, $config;
}
sub get_dependencies {
my ($config) = @_;
my $arr = $dependency{$config};
if (!defined($arr)) {
return ();
}
my @deps = @{$arr};
foreach my $dep (@{$arr}) {
print "ADD DEP $dep\n";
@deps = (@deps, get_dependencies $dep);
}
return @deps;
}
sub save_config {
my ($pc, $file) = @_;
my %configs = %{$pc};
doprint "Saving configs into $file\n";
open(OUT, ">$file") or dodie "Can not write to $file";
foreach my $config (keys %configs) {
print OUT "$configs{$config}\n";
}
close(OUT);
}
sub create_config {
my ($name, $pc) = @_;
doprint "Creating old config from $name configs\n";
save_config $pc, $output_config;
make_oldconfig;
}
# compare two config hashes, and return configs with different vals.
# It returns B's config values, but you can use A to see what A was.
sub diff_config_vals {
my ($pa, $pb) = @_;
# crappy Perl way to pass in hashes.
my %a = %{$pa};
my %b = %{$pb};
my %ret;
foreach my $item (keys %a) {
if (defined($b{$item}) && $b{$item} ne $a{$item}) {
$ret{$item} = $b{$item};
}
}
return %ret;
}
# compare two config hashes and return the configs in B but not A
sub diff_configs {
my ($pa, $pb) = @_;
my %ret;
# crappy Perl way to pass in hashes.
my %a = %{$pa};
my %b = %{$pb};
foreach my $item (keys %b) {
if (!defined($a{$item})) {
$ret{$item} = $b{$item};
}
}
return %ret;
}
# return if two configs are equal or not
# 0 is equal +1 b has something a does not
# +1 if a and b have a different item.
# -1 if a has something b does not
sub compare_configs {
my ($pa, $pb) = @_;
my %ret;
# crappy Perl way to pass in hashes.
my %a = %{$pa};
my %b = %{$pb};
foreach my $item (keys %b) {
if (!defined($a{$item})) {
return 1;
}
if ($a{$item} ne $b{$item}) {
return 1;
}
}
foreach my $item (keys %a) {
if (!defined($b{$item})) {
return -1;
}
}
return 0;
}
sub process_failed {
my ($config) = @_;
doprint "\n\n***************************************\n";
doprint "Found bad config: $config\n";
doprint "***************************************\n\n";
}
sub process_new_config {
my ($tc, $nc, $gc, $bc) = @_;
my %tmp_config = %{$tc};
my %good_configs = %{$gc};
my %bad_configs = %{$bc};
my %new_configs;
my $runtest = 1;
my $ret;
create_config "tmp_configs", \%tmp_config;
assign_configs \%new_configs, $output_config;
$ret = compare_configs \%new_configs, \%bad_configs;
if (!$ret) {
doprint "New config equals bad config, try next test\n";
$runtest = 0;
}
if ($runtest) {
$ret = compare_configs \%new_configs, \%good_configs;
if (!$ret) {
doprint "New config equals good config, try next test\n";
$runtest = 0;
}
}
%{$nc} = %new_configs;
return $runtest;
}
sub convert_config {
my ($config) = @_;
if ($config =~ /^# (.*) is not set/) {
$config = "$1=n";
}
$config =~ s/^CONFIG_//;
return $config;
}
sub print_config {
my ($sym, $config) = @_;
$config = convert_config $config;
doprint "$sym$config\n";
}
sub print_config_compare {
my ($good_config, $bad_config) = @_;
$good_config = convert_config $good_config;
$bad_config = convert_config $bad_config;
my $good_value = $good_config;
my $bad_value = $bad_config;
$good_value =~ s/(.*)=//;
my $config = $1;
$bad_value =~ s/.*=//;
doprint " $config $good_value -> $bad_value\n";
}
# Pass in:
# $phalf: half of the configs names you want to add
# $oconfigs: The orginial configs to start with
# $sconfigs: The source to update $oconfigs with (from $phalf)
# $which: The name of which half that is updating (top / bottom)
# $type: The name of the source type (good / bad)
sub make_half {
my ($phalf, $oconfigs, $sconfigs, $which, $type) = @_;
my @half = @{$phalf};
my %orig_configs = %{$oconfigs};
my %source_configs = %{$sconfigs};
my %tmp_config = %orig_configs;
doprint "Settings bisect with $which half of $type configs:\n";
foreach my $item (@half) {
doprint "Updating $item to $source_configs{$item}\n";
$tmp_config{$item} = $source_configs{$item};
}
return %tmp_config;
}
sub run_config_bisect {
my ($pgood, $pbad) = @_;
my %good_configs = %{$pgood};
my %bad_configs = %{$pbad};
my %diff_configs = diff_config_vals \%good_configs, \%bad_configs;
my %b_configs = diff_configs \%good_configs, \%bad_configs;
my %g_configs = diff_configs \%bad_configs, \%good_configs;
# diff_arr is what is in both good and bad but are different (y->n)
my @diff_arr = keys %diff_configs;
my $len_diff = $#diff_arr + 1;
# b_arr is what is in bad but not in good (has depends)
my @b_arr = keys %b_configs;
my $len_b = $#b_arr + 1;
# g_arr is what is in good but not in bad
my @g_arr = keys %g_configs;
my $len_g = $#g_arr + 1;
my $runtest = 0;
my %new_configs;
my $ret;
# Look at the configs that are different between good and bad.
# This does not include those that depend on other configs
# (configs depending on other configs that are not set would
# not show up even as a "# CONFIG_FOO is not set"
doprint "# of configs to check: $len_diff\n";
doprint "# of configs showing only in good: $len_g\n";
doprint "# of configs showing only in bad: $len_b\n";
if ($len_diff > 0) {
# Now test for different values
doprint "Configs left to check:\n";
doprint " Good Config\t\t\tBad Config\n";
doprint " -----------\t\t\t----------\n";
foreach my $item (@diff_arr) {
doprint " $good_configs{$item}\t$bad_configs{$item}\n";
}
my $half = int($#diff_arr / 2);
my @tophalf = @diff_arr[0 .. $half];
doprint "Set tmp config to be good config with some bad config values\n";
my %tmp_config = make_half \@tophalf, \%good_configs,
\%bad_configs, "top", "bad";
$runtest = process_new_config \%tmp_config, \%new_configs,
\%good_configs, \%bad_configs;
if (!$runtest) {
doprint "Set tmp config to be bad config with some good config values\n";
my %tmp_config = make_half \@tophalf, \%bad_configs,
\%good_configs, "top", "good";
$runtest = process_new_config \%tmp_config, \%new_configs,
\%good_configs, \%bad_configs;
}
}
if (!$runtest && $len_diff > 0) {
# do the same thing, but this time with bottom half
my $half = int($#diff_arr / 2);
my @bottomhalf = @diff_arr[$half+1 .. $#diff_arr];
doprint "Set tmp config to be good config with some bad config values\n";
my %tmp_config = make_half \@bottomhalf, \%good_configs,
\%bad_configs, "bottom", "bad";
$runtest = process_new_config \%tmp_config, \%new_configs,
\%good_configs, \%bad_configs;
if (!$runtest) {
doprint "Set tmp config to be bad config with some good config values\n";
my %tmp_config = make_half \@bottomhalf, \%bad_configs,
\%good_configs, "bottom", "good";
$runtest = process_new_config \%tmp_config, \%new_configs,
\%good_configs, \%bad_configs;
}
}
if ($runtest) {
make_oldconfig;
doprint "READY TO TEST .config IN $build\n";
return 0;
}
doprint "\n%%%%%%%% FAILED TO FIND SINGLE BAD CONFIG %%%%%%%%\n";
doprint "Hmm, can't make any more changes without making good == bad?\n";
doprint "Difference between good (+) and bad (-)\n";
foreach my $item (keys %bad_configs) {
if (!defined($good_configs{$item})) {
print_config "-", $bad_configs{$item};
}
}
foreach my $item (keys %good_configs) {
next if (!defined($bad_configs{$item}));
if ($good_configs{$item} ne $bad_configs{$item}) {
print_config_compare $good_configs{$item}, $bad_configs{$item};
}
}
foreach my $item (keys %good_configs) {
if (!defined($bad_configs{$item})) {
print_config "+", $good_configs{$item};
}
}
return -1;
}
sub config_bisect {
my ($good_config, $bad_config) = @_;
my $ret;
my %good_configs;
my %bad_configs;
my %tmp_configs;
doprint "Run good configs through make oldconfig\n";
assign_configs \%tmp_configs, $good_config;
create_config "$good_config", \%tmp_configs;
assign_configs \%good_configs, $output_config;
doprint "Run bad configs through make oldconfig\n";
assign_configs \%tmp_configs, $bad_config;
create_config "$bad_config", \%tmp_configs;
assign_configs \%bad_configs, $output_config;
save_config \%good_configs, $good_config;
save_config \%bad_configs, $bad_config;
return run_config_bisect \%good_configs, \%bad_configs;
}
while ($#ARGV >= 0) {
if ($ARGV[0] !~ m/^-/) {
last;
}
my $opt = shift @ARGV;
if ($opt eq "-b") {
$val = shift @ARGV;
if (!defined($val)) {
die "-b requires value\n";
}
$build = $val;
}
elsif ($opt eq "-l") {
$val = shift @ARGV;
if (!defined($val)) {
die "-l requires value\n";
}
$tree = $val;
}
elsif ($opt eq "-r") {
$reset_bisect = 1;
}
elsif ($opt eq "-h") {
usage;
}
else {
die "Unknow option $opt\n";
}
}
$build = $tree if (!defined($build));
$tree = expand_path $tree;
$build = expand_path $build;
if ( ! -d $tree ) {
die "$tree not a directory\n";
}
if ( ! -d $build ) {
die "$build not a directory\n";
}
usage if $#ARGV < 1;
if ($#ARGV == 1) {
$start = 1;
} elsif ($#ARGV == 2) {
$val = $ARGV[2];
if ($val ne "good" && $val ne "bad") {
die "Unknown command '$val', bust be either \"good\" or \"bad\"\n";
}
} else {
usage;
}
my $good_start = expand_path $ARGV[0];
my $bad_start = expand_path $ARGV[1];
my $good = "$good_start.tmp";
my $bad = "$bad_start.tmp";
$make = "make";
if ($build ne $tree) {
$make = "make O=$build"
}
$output_config = "$build/.config";
if ($start) {
if ( ! -f $good_start ) {
die "$good_start not found\n";
}
if ( ! -f $bad_start ) {
die "$bad_start not found\n";
}
if ( -f $good || -f $bad ) {
my $p = "";
if ( -f $good ) {
$p = "$good exists\n";
}
if ( -f $bad ) {
$p = "$p$bad exists\n";
}
if (!defined($reset_bisect)) {
if (!read_yn "${p}Overwrite and start new bisect anyway?") {
exit (-1);
}
}
}
run_command "cp $good_start $good" or die "failed to copy to $good\n";
run_command "cp $bad_start $bad" or die "faield to copy to $bad\n";
} else {
if ( ! -f $good ) {
die "Can not find file $good\n";
}
if ( ! -f $bad ) {
die "Can not find file $bad\n";
}
if ($val eq "good") {
run_command "cp $output_config $good" or die "failed to copy $config to $good\n";
} elsif ($val eq "bad") {
run_command "cp $output_config $bad" or die "failed to copy $config to $bad\n";
}
}
chdir $tree || die "can't change directory to $tree";
my $ret = config_bisect $good, $bad;
if (!$ret) {
exit(0);
}
if ($ret > 0) {
doprint "Cleaning temp files\n";
run_command "rm $good";
run_command "rm $bad";
exit(1);
} else {
doprint "See good and bad configs for details:\n";
doprint "good: $good\n";
doprint "bad: $bad\n";
doprint "%%%%%%%% FAILED TO FIND SINGLE BAD CONFIG %%%%%%%%\n";
}
exit(2);
+32
View File
@@ -0,0 +1,32 @@
This directory contains example configs to use ktest for various tasks.
The configs still need to be customized for your environment, but it
is broken up by task which makes it easier to understand how to set up
ktest.
The configs are based off of real working configs but have been modified
and commented to show more generic use cases that are more helpful for
developers.
crosstests.conf - this config shows an example of testing a git repo against
lots of different architectures. It only does build tests, but makes
it easy to compile test different archs. You can download the arch
cross compilers from:
http://kernel.org/pub/tools/crosstool/files/bin/x86_64/
test.conf - A generic example of a config. This is based on an actual config
used to perform real testing.
kvm.conf - A example of a config that is used to test a virtual guest running
on a host.
snowball.conf - An example config that was used to demo ktest.pl against
a snowball ARM board.
include/ - The include directory holds default configs that can be
included into other configs. This is a real use example that shows how
to reuse configs for various machines or set ups. The files here
are included by other config files, where the other config files define
options and variables that will make the included config work for the
given environment.
@@ -0,0 +1,225 @@
#
# Example config for cross compiling
#
# In this config, it is expected that the tool chains from:
#
# http://kernel.org/pub/tools/crosstool/files/bin/x86_64/
#
# running on a x86_64 system have been downloaded and installed into:
#
# /usr/local/
#
# such that the compiler binaries are something like:
#
# /usr/local/gcc-4.5.2-nolibc/mips-linux/bin/mips-linux-gcc
#
# Some of the archs will use gcc-4.5.1 instead of gcc-4.5.2
# this config uses variables to differentiate them.
#
# Comments describe some of the options, but full descriptions of
# options are described in the samples.conf file.
# ${PWD} is defined by ktest.pl to be the directory that the user
# was in when they executed ktest.pl. It may be better to hardcode the
# path name here. THIS_DIR is the variable used through out the config file
# in case you want to change it.
THIS_DIR := ${PWD}
# Update the BUILD_DIR option to the location of your git repo you want to test.
BUILD_DIR = ${THIS_DIR}/linux.git
# The build will go into this directory. It will be created when you run the test.
OUTPUT_DIR = ${THIS_DIR}/cross-compile
# The build will be compiled with -j8
BUILD_OPTIONS = -j8
# The test will not stop when it hits a failure.
DIE_ON_FAILURE = 0
# If you want to have ktest.pl store the failure somewhere, uncomment this option
# and change the directory where ktest should store the failures.
#STORE_FAILURES = ${THIS_DIR}/failures
# The log file is stored in the OUTPUT_DIR called cross.log
# If you enable this, you need to create the OUTPUT_DIR. It wont be created for you.
LOG_FILE = ${OUTPUT_DIR}/cross.log
# The log file will be cleared each time you run ktest.
CLEAR_LOG = 1
# As some archs do not build with the defconfig, they have been marked
# to be ignored. If you want to test them anyway, change DO_FAILED to 1.
# If a test that has been marked as DO_FAILED passes, then you should change
# that test to be DO_DEFAULT
DO_FAILED := 0
DO_DEFAULT := 1
# By setting both DO_FAILED and DO_DEFAULT to zero, you can pick a single
# arch that you want to test. (uncomment RUN and chose your arch)
#RUN := arm
# At the bottom of the config file exists a bisect test. You can update that
# test and set DO_FAILED and DO_DEFAULT to zero, and uncomment this variable
# to run the bisect on the arch.
#RUN := bisect
# By default all tests will be running gcc 4.5.2. Some tests are using 4.5.1
# and they select that in the test.
# Note: GCC_VER is declared as on option and not a variable ('=' instead of ':=')
# This is important. A variable is used only in the config file and if it is set
# it stays that way for the rest of the config file until it is change again.
# Here we want GCC_VER to remain persistent and change for each test, as it is used in
# the MAKE_CMD. By using '=' instead of ':=' we achieve our goal.
GCC_VER = 4.5.2
MAKE_CMD = PATH=/usr/local/gcc-${GCC_VER}-nolibc/${CROSS}/bin:$PATH CROSS_COMPILE=${CROSS}- make ARCH=${ARCH}
# all tests are only doing builds.
TEST_TYPE = build
# If you want to add configs on top of the defconfig, you can add those configs into
# the add-config file and uncomment this option. This is useful if you want to test
# all cross compiles with PREEMPT set, or TRACING on, etc.
#ADD_CONFIG = ${THIS_DIR}/add-config
# All tests are using defconfig
BUILD_TYPE = defconfig
# The test names will have the arch and cross compiler used. This will be shown in
# the results.
TEST_NAME = ${ARCH} ${CROSS}
# alpha
TEST_START IF ${RUN} == alpha || ${DO_DEFAULT}
# Notice that CROSS and ARCH are also options and not variables (again '=' instead
# of ':='). This is because TEST_NAME and MAKE_CMD wil use them for each test.
# Only options are available during runs. Variables are only present in parsing the
# config file.
CROSS = alpha-linux
ARCH = alpha
# arm
TEST_START IF ${RUN} == arm || ${DO_DEFAULT}
CROSS = arm-unknown-linux-gnueabi
ARCH = arm
# ia64
TEST_START IF ${RUN} == ia64 || ${DO_DEFAULT}
CROSS = ia64-linux
ARCH = ia64
# m68k fails with error?
TEST_START IF ${RUN} == m68k || ${DO_DEFAULT}
CROSS = m68k-linux
ARCH = m68k
# mips64
TEST_START IF ${RUN} == mips || ${RUN} == mips64 || ${DO_DEFAULT}
CROSS = mips64-linux
ARCH = mips
# mips32
TEST_START IF ${RUN} == mips || ${RUN} == mips32 || ${DO_DEFAULT}
CROSS = mips-linux
ARCH = mips
# parisc64 failed?
TEST_START IF ${RUN} == hppa || ${RUN} == hppa64 || ${DO_FAILED}
CROSS = hppa64-linux
ARCH = parisc
# parisc
TEST_START IF ${RUN} == hppa || ${RUN} == hppa32 || ${DO_FAILED}
CROSS = hppa-linux
ARCH = parisc
# ppc
TEST_START IF ${RUN} == ppc || ${RUN} == ppc32 || ${DO_DEFAULT}
CROSS = powerpc-linux
ARCH = powerpc
# ppc64
TEST_START IF ${RUN} == ppc || ${RUN} == ppc64 || ${DO_DEFAULT}
CROSS = powerpc64-linux
ARCH = powerpc
# s390
TEST_START IF ${RUN} == s390 || ${DO_DEFAULT}
CROSS = s390x-linux
ARCH = s390
# sh
TEST_START IF ${RUN} == sh || ${DO_DEFAULT}
CROSS = sh4-linux
ARCH = sh
# sparc64
TEST_START IF ${RUN} == sparc || ${RUN} == sparc64 || ${DO_DEFAULT}
CROSS = sparc64-linux
ARCH = sparc64
# sparc
TEST_START IF ${RUN} == sparc || ${RUN} == sparc32 || ${DO_DEFAULT}
CROSS = sparc-linux
ARCH = sparc
# xtensa failed
TEST_START IF ${RUN} == xtensa || ${DO_FAILED}
CROSS = xtensa-linux
ARCH = xtensa
# UML
TEST_START IF ${RUN} == uml || ${DO_DEFAULT}
MAKE_CMD = make ARCH=um SUBARCH=x86_64
ARCH = uml
CROSS =
TEST_START IF ${RUN} == x86 || ${RUN} == i386 || ${DO_DEFAULT}
MAKE_CMD = make ARCH=i386
ARCH = i386
CROSS =
TEST_START IF ${RUN} == x86 || ${RUN} == x86_64 || ${DO_DEFAULT}
MAKE_CMD = make ARCH=x86_64
ARCH = x86_64
CROSS =
#################################
# This is a bisect if needed. You need to give it a MIN_CONFIG that
# will be the config file it uses. Basically, just copy the created defconfig
# for the arch someplace and point MIN_CONFIG to it.
TEST_START IF ${RUN} == bisect
MIN_CONFIG = ${THIS_DIR}/min-config
CROSS = s390x-linux
ARCH = s390
TEST_TYPE = bisect
BISECT_TYPE = build
BISECT_GOOD = v3.1
BISECT_BAD = v3.2
CHECKOUT = v3.2
#################################
# These defaults are needed to keep ktest.pl from complaining. They are
# ignored because the test does not go pass the build. No install or
# booting of the target images.
DEFAULTS
MACHINE = crosstest
SSH_USER = root
BUILD_TARGET = cross
TARGET_IMAGE = image
POWER_CYCLE = cycle
CONSOLE = console
LOCALVERSION = version
GRUB_MENU = grub
REBOOT_ON_ERROR = 0
POWEROFF_ON_ERROR = 0
POWEROFF_ON_SUCCESS = 0
REBOOT_ON_SUCCESS = 0
@@ -0,0 +1,90 @@
#
# This example shows the bisect tests (git bisect and config bisect)
#
# The config that includes this file may define a RUN_TEST
# variable that will tell this config what test to run.
# (what to set the TEST option to).
#
DEFAULTS IF NOT DEFINED RUN_TEST
# Requires that hackbench is in the PATH
RUN_TEST := ${SSH} hackbench 50
# Set TEST to 'bisect' to do a normal git bisect. You need
# to modify the options below to make it bisect the exact
# commits you are interested in.
#
TEST_START IF ${TEST} == bisect
TEST_TYPE = bisect
# You must set the commit that was considered good (git bisect good)
BISECT_GOOD = v3.3
# You must set the commit that was considered bad (git bisect bad)
BISECT_BAD = HEAD
# It's best to specify the branch to checkout before starting the bisect.
CHECKOUT = origin/master
# This can be build, boot, or test. Here we are doing a bisect
# that requires to run a test to know if the bisect was good or bad.
# The test should exit with 0 on good, non-zero for bad. But see
# the BISECT_RET_* options in samples.conf to override this.
BISECT_TYPE = test
TEST = ${RUN_TEST}
# It is usually a good idea to confirm that the GOOD and the BAD
# commits are truly good and bad respectively. Having BISECT_CHECK
# set to 1 will check both that the good commit works and the bad
# commit fails. If you only want to check one or the other,
# set BISECT_CHECK to 'good' or to 'bad'.
BISECT_CHECK = 1
#BISECT_CHECK = good
#BISECT_CHECK = bad
# Usually it's a good idea to specify the exact config you
# want to use throughout the entire bisect. Here we placed
# it in the directory we called ktest.pl from and named it
# 'config-bisect'.
MIN_CONFIG = ${THIS_DIR}/config-bisect
# By default, if we are doing a BISECT_TYPE = test run but the
# build or boot fails, ktest.pl will do a 'git bisect skip'.
# Uncomment the below option to make ktest stop testing on such
# an error.
#BISECT_SKIP = 0
# Now if you had BISECT_SKIP = 0 and the test fails, you can
# examine what happened and then do 'git bisect log > /tmp/replay'
# Set BISECT_REPLAY to /tmp/replay and ktest.pl will run the
# 'git bisect replay /tmp/replay' before continuing the bisect test.
#BISECT_REPLAY = /tmp/replay
# If you used BISECT_REPLAY after the bisect test failed, you may
# not want to continue the bisect on that commit that failed.
# By setting BISECT_START to a new commit. ktest.pl will checkout
# that commit after it has performed the 'git bisect replay' but
# before it continues running the bisect test.
#BISECT_START = 2545eb6198e7e1ec50daa0cfc64a4cdfecf24ec9
# Now if you don't trust ktest.pl to make the decisions for you, then
# set BISECT_MANUAL to 1. This will cause ktest.pl not to decide
# if the commit was good or bad. Instead, it will ask you to tell
# it if the current commit was good. In the mean time, you could
# take the result, load it on any machine you want. Run several tests,
# or whatever you feel like. Then, when you are happy, you can tell
# ktest if you think it was good or not and ktest.pl will continue
# the git bisect. You can even change what commit it is currently at.
#BISECT_MANUAL = 1
# One of the unique tests that ktest does is the config bisect.
# Currently (which hopefully will be fixed soon), the bad config
# must be a superset of the good config. This is because it only
# searches for a config that causes the target to fail. If the
# good config is not a subset of the bad config, or if the target
# fails because of a lack of a config, then it will not find
# the config for you.
TEST_START IF ${TEST} == config-bisect
TEST_TYPE = config_bisect
# set to build, boot, test
CONFIG_BISECT_TYPE = boot
# Set the config that is considered bad.
CONFIG_BISECT = ${THIS_DIR}/config-bad
# This config is optional. By default it uses the
# MIN_CONFIG as the good config.
CONFIG_BISECT_GOOD = ${THIS_DIR}/config-good
@@ -0,0 +1,157 @@
# This file holds defaults for most the tests. It defines the options that
# are most common to tests that are likely to be shared.
#
# Note, after including this file, a config file may override any option
# with a DEFAULTS OVERRIDE section.
#
# For those cases that use the same machine to boot a 64 bit
# and a 32 bit version. The MACHINE is the DNS name to get to the
# box (usually different if it was 64 bit or 32 bit) but the
# BOX here is defined as a variable that will be the name of the box
# itself. It is useful for calling scripts that will power cycle
# the box, as only one script needs to be created to power cycle
# even though the box itself has multiple operating systems on it.
# By default, BOX and MACHINE are the same.
DEFAULTS IF NOT DEFINED BOX
BOX := ${MACHINE}
# Consider each box as 64 bit box, unless the config including this file
# has defined BITS = 32
DEFAULTS IF NOT DEFINED BITS
BITS := 64
DEFAULTS
# THIS_DIR is used through out the configs and defaults to ${PWD} which
# is the directory that ktest.pl was called from.
THIS_DIR := ${PWD}
# to organize your configs, having each machine save their configs
# into a separate directly is useful.
CONFIG_DIR := ${THIS_DIR}/configs/${MACHINE}
# Reset the log before running each test.
CLEAR_LOG = 1
# As installing kernels usually requires root privilege, default the
# user on the target as root. It is also required that the target
# allows ssh to root from the host without asking for a password.
SSH_USER = root
# For accesing the machine, we will ssh to root@machine.
SSH := ssh ${SSH_USER}@${MACHINE}
# Update this. The default here is ktest will ssh to the target box
# and run a script called 'run-test' located on that box.
TEST = ${SSH} run-test
# Point build dir to the git repo you use
BUILD_DIR = ${THIS_DIR}/linux.git
# Each machine will have its own output build directory.
OUTPUT_DIR = ${THIS_DIR}/build/${MACHINE}
# Yes this config is focused on x86 (but ktest works for other archs too)
BUILD_TARGET = arch/x86/boot/bzImage
TARGET_IMAGE = /boot/vmlinuz-test
# have directory for the scripts to reboot and power cycle the boxes
SCRIPTS_DIR := ${THIS_DIR}/scripts
# You can have each box/machine have a script to power cycle it.
# Name your script <box>-cycle.
POWER_CYCLE = ${SCRIPTS_DIR}/${BOX}-cycle
# This script is used to power off the box.
POWER_OFF = ${SCRIPTS_DIR}/${BOX}-poweroff
# Keep your test kernels separate from your other kernels.
LOCALVERSION = -test
# The /boot/grub/menu.lst is searched for the line:
# title Test Kernel
# and ktest will use that kernel to reboot into.
# For grub2 or other boot loaders, you need to set BOOT_TYPE
# to 'script' and define other ways to load the kernel.
# See snowball.conf example.
#
GRUB_MENU = Test Kernel
# The kernel build will use this option.
BUILD_OPTIONS = -j8
# Keeping the log file with the output dir is convenient.
LOG_FILE = ${OUTPUT_DIR}/${MACHINE}.log
# Each box should have their own minum configuration
# See min-config.conf
MIN_CONFIG = ${CONFIG_DIR}/config-min
# For things like randconfigs, there may be configs you find that
# are already broken, or there may be some configs that you always
# want set. Uncomment ADD_CONFIG and point it to the make config files
# that set the configs you want to keep on (or off) in your build.
# ADD_CONFIG is usually something to add configs to all machines,
# where as, MIN_CONFIG is specific per machine.
#ADD_CONFIG = ${THIS_DIR}/config-broken ${THIS_DIR}/config-general
# To speed up reboots for bisects and patchcheck, instead of
# waiting 60 seconds for the console to be idle, if this line is
# seen in the console output, ktest will know the good kernel has
# finished rebooting and it will be able to continue the tests.
REBOOT_SUCCESS_LINE = ${MACHINE} login:
# The following is different ways to end the test.
# by setting the variable REBOOT to: none, error, fail or
# something else, ktest will power cycle or reboot the target box
# at the end of the tests.
#
# REBOOT := none
# Don't do anything at the end of the test.
#
# REBOOT := error
# Reboot the box if ktest detects an error
#
# REBOOT := fail
# Do not stop on failure, and after all tests are complete
# power off the box (for both success and error)
# This is good to run over a weekend and you don't want to waste
# electricity.
#
DEFAULTS IF ${REBOOT} == none
REBOOT_ON_SUCCESS = 0
REBOOT_ON_ERROR = 0
POWEROFF_ON_ERROR = 0
POWEROFF_ON_SUCCESS = 0
DEFAULTS ELSE IF ${REBOOT} == error
REBOOT_ON_SUCCESS = 0
REBOOT_ON_ERROR = 1
POWEROFF_ON_ERROR = 0
POWEROFF_ON_SUCCESS = 0
DEFAULTS ELSE IF ${REBOOT} == fail
REBOOT_ON_SUCCESS = 0
POWEROFF_ON_ERROR = 1
POWEROFF_ON_SUCCESS = 1
POWEROFF_AFTER_HALT = 120
DIE_ON_FAILURE = 0
# Store the failure information into this directory
# such as the .config, dmesg, and build log.
STORE_FAILURES = ${THIS_DIR}/failures
DEFAULTS ELSE
REBOOT_ON_SUCCESS = 1
REBOOT_ON_ERROR = 1
POWEROFF_ON_ERROR = 0
POWEROFF_ON_SUCCESS = 0
@@ -0,0 +1,60 @@
#
# This file has some examples for creating a MIN_CONFIG.
# (A .config file that is the minimum for a machine to boot, or
# to boot and make a network connection.)
#
# A MIN_CONFIG is very useful as it is the minimum configuration
# needed to boot a given machine. You can debug someone else's
# .config by only setting the configs in your MIN_CONFIG. The closer
# your MIN_CONFIG is to the true minimum set of configs needed to
# boot your machine, the closer the config you test with will be
# to the users config that had the failure.
#
# The make_min_config test allows you to create a MIN_CONFIG that
# is truly the minimum set of configs needed to boot a box.
#
# In this example, the final config will reside in
# ${CONFIG_DIR}/config-new-min and ${CONFIG_DIR}/config-new-min-net.
# Just move one to the location you have set for MIN_CONFIG.
#
# The first test creates a MIN_CONFIG that will be the minimum
# configuration to boot ${MACHINE} and be able to ssh to it.
#
# The second test creates a MIN_CONFIG that will only boot
# the target and most likely will not let you ssh to it. (Notice
# how the second test uses the first test's result to continue with.
# This is because the second test config is a subset of the first).
#
# The ${CONFIG_DIR}/config-skip (and -net) will hold the configs
# that ktest.pl found would not boot the target without them set.
# The config-new-min holds configs that ktest.pl could not test
# directly because another config that was needed to boot the box
# selected them. Sometimes it is possible that this file will hold
# the true minimum configuration. You can test to see if this is
# the case by running the boot test with BOOT_TYPE = allnoconfig and
# setting setting the MIN_CONFIG to ${CONFIG_DIR}/config-skip. If the
# machine still boots, then you can use the config-skip as your MIN_CONFIG.
#
# These tests can run for several hours (and perhaps days).
# It's OK to kill the test with a Ctrl^C. By restarting without
# modifying this config, ktest.pl will notice that the config-new-min(-net)
# exists, and will use that instead as the starting point.
# The USE_OUTPUT_MIN_CONFIG is set to 1 to keep ktest.pl from asking
# you if you want to use the OUTPUT_MIN_CONFIG as the starting point.
# By using the OUTPUT_MIN_CONFIG as the starting point will allow ktest.pl to
# start almost where it left off.
#
TEST_START IF ${TEST} == min-config
TEST_TYPE = make_min_config
OUTPUT_MIN_CONFIG = ${CONFIG_DIR}/config-new-min-net
IGNORE_CONFIG = ${CONFIG_DIR}/config-skip-net
MIN_CONFIG_TYPE = test
TEST = ${SSH} echo hi
USE_OUTPUT_MIN_CONFIG = 1
TEST_START IF ${TEST} == min-config && ${MULTI}
TEST_TYPE = make_min_config
OUTPUT_MIN_CONFIG = ${CONFIG_DIR}/config-new-min
IGNORE_CONFIG = ${CONFIG_DIR}/config-skip
MIN_CONFIG = ${CONFIG_DIR}/config-new-min-net
USE_OUTPUT_MIN_CONFIG = 1
@@ -0,0 +1,111 @@
# patchcheck.conf
#
# This contains a test that takes two git commits and will test each
# commit between the two. The build test will look at what files the
# commit has touched, and if any of those files produce a warning, then
# the build will fail.
# PATCH_START is the commit to begin with and PATCH_END is the commit
# to end with (inclusive). This is similar to doing a git rebase -i PATCH_START~1
# and then testing each commit and doing a git rebase --continue.
# You can use a SHA1, a git tag, or anything that git will accept for a checkout
PATCH_START := HEAD~3
PATCH_END := HEAD
# Use the oldconfig if build_type wasn't defined
DEFAULTS IF NOT DEFINED BUILD_TYPE
DO_BUILD_TYPE := oldconfig
DEFAULTS ELSE
DO_BUILD_TYPE := ${BUILD_TYPE}
DEFAULTS
# Change PATCH_CHECKOUT to be the branch you want to test. The test will
# do a git checkout of this branch before starting. Obviously both
# PATCH_START and PATCH_END must be in this branch (and PATCH_START must
# be contained by PATCH_END).
PATCH_CHECKOUT := test/branch
# Usually it's a good idea to have a set config to use for testing individual
# patches.
PATCH_CONFIG := ${CONFIG_DIR}/config-patchcheck
# Change PATCH_TEST to run some test for each patch. Each commit that is
# tested, after it is built and installed on the test machine, this command
# will be executed. Usually what is done is to ssh to the target box and
# run some test scripts. If you just want to boot test your patches
# comment PATCH_TEST out.
PATCH_TEST := ${SSH} "/usr/local/bin/ktest-test-script"
DEFAULTS IF DEFINED PATCH_TEST
PATCH_TEST_TYPE := test
DEFAULTS ELSE
PATCH_TEST_TYPE := boot
# If for some reason a file has a warning that one of your patches touch
# but you do not care about it, set IGNORE_WARNINGS to that commit(s)
# (space delimited)
#IGNORE_WARNINGS = 39eaf7ef884dcc44f7ff1bac803ca2a1dcf43544 6edb2a8a385f0cdef51dae37ff23e74d76d8a6ce
# Instead of just checking for warnings to files that are changed
# it can be advantageous to check for any new warnings. If a
# header file is changed, it could cause a warning in a file not
# touched by the commit. To detect these kinds of warnings, you
# can use the WARNINGS_FILE option.
#
# If the variable CREATE_WARNINGS_FILE is set, this config will
# enable the WARNINGS_FILE during the patchcheck test. Also,
# before running the patchcheck test, it will create the
# warnings file.
#
DEFAULTS IF DEFINED CREATE_WARNINGS_FILE
WARNINGS_FILE = ${OUTPUT_DIR}/warnings_file
TEST_START IF DEFINED CREATE_WARNINGS_FILE
# WARNINGS_FILE is already set by the DEFAULTS above
TEST_TYPE = make_warnings_file
# Checkout the commit before the patches to test,
# and record all the warnings that exist before the patches
# to test are added
CHECKOUT = ${PATCHCHECK_START}~1
# Force a full build
BUILD_NOCLEAN = 0
BUILD_TYPE = ${DO_BUILD_TYPE}
# If you are running a multi test, and the test failed on the first
# test but on, say the 5th patch. If you want to restart on the
# fifth patch, set PATCH_START1. This will make the first test start
# from this commit instead of the PATCH_START commit.
# Note, do not change this option. Just define PATCH_START1 in the
# top config (the one you pass to ktest.pl), and this will use it,
# otherwise it will just use PATCH_START if PATCH_START1 is not defined.
DEFAULTS IF NOT DEFINED PATCH_START1
PATCH_START1 := ${PATCH_START}
TEST_START IF ${TEST} == patchcheck
TEST_TYPE = patchcheck
MIN_CONFIG = ${PATCH_CONFIG}
TEST = ${PATCH_TEST}
PATCHCHECK_TYPE = ${PATCH_TEST_TYPE}
PATCHCHECK_START = ${PATCH_START1}
PATCHCHECK_END = ${PATCH_END}
CHECKOUT = ${PATCH_CHECKOUT}
BUILD_TYPE = ${DO_BUILD_TYPE}
TEST_START IF ${TEST} == patchcheck && ${MULTI}
TEST_TYPE = patchcheck
MIN_CONFIG = ${PATCH_CONFIG}
TEST = ${PATCH_TEST}
PATCHCHECK_TYPE = ${PATCH_TEST_TYPE}
PATCHCHECK_START = ${PATCH_START}
PATCHCHECK_END = ${PATCH_END}
CHECKOUT = ${PATCH_CHECKOUT}
# Use multi to test different compilers?
MAKE_CMD = CC=gcc-4.5.1 make
BUILD_TYPE = ${DO_BUILD_TYPE}
@@ -0,0 +1,74 @@
#
# This is an example of various tests that you can run
#
# The variable TEST can be of boot, build, randconfig, or test.
#
# Note that TEST is a variable created with ':=' and only exists
# throughout the config processing (not during the tests itself).
#
# The TEST option (defined with '=') is used to tell ktest.pl
# what test to run after a successful boot. The TEST option is
# persistent into the test runs.
#
# The config that includes this file may define a BOOT_TYPE
# variable that tells this config what type of boot test to run.
# If it's not defined, the below DEFAULTS will set the default
# to 'oldconfig'.
#
DEFAULTS IF NOT DEFINED BOOT_TYPE
BOOT_TYPE := oldconfig
# The config that includes this file may define a RUN_TEST
# variable that will tell this config what test to run.
# (what to set the TEST option to).
#
DEFAULTS IF NOT DEFINED RUN_TEST
# Requires that hackbench is in the PATH
RUN_TEST := ${SSH} hackbench 50
# If TEST is set to 'boot' then just build a kernel and boot
# the target.
TEST_START IF ${TEST} == boot
TEST_TYPE = boot
# Notice how we set the BUILD_TYPE option to the BOOT_TYPE variable.
BUILD_TYPE = ${BOOT_TYPE}
# Do not do a make mrproper.
BUILD_NOCLEAN = 1
# If you only want to build the kernel, and perhaps install
# and test it yourself, then just set TEST to build.
TEST_START IF ${TEST} == build
TEST_TYPE = build
BUILD_TYPE = ${BOOT_TYPE}
BUILD_NOCLEAN = 1
# Build, install, boot and test with a randconfg 10 times.
# It is important that you have set MIN_CONFIG in the config
# that includes this file otherwise it is likely that the
# randconfig will not have the necessary configs needed to
# boot your box. This version of the test requires a min
# config that has enough to make sure the target has network
# working.
TEST_START ITERATE 10 IF ${TEST} == randconfig
MIN_CONFIG = ${CONFIG_DIR}/config-min-net
TEST_TYPE = test
BUILD_TYPE = randconfig
TEST = ${RUN_TEST}
# This is the same as above, but only tests to a boot prompt.
# The MIN_CONFIG used here does not need to have networking
# working.
TEST_START ITERATE 10 IF ${TEST} == randconfig && ${MULTI}
TEST_TYPE = boot
BUILD_TYPE = randconfig
MIN_CONFIG = ${CONFIG_DIR}/config-min
MAKE_CMD = make
# This builds, installs, boots and tests the target.
TEST_START IF ${TEST} == test
TEST_TYPE = test
BUILD_TYPE = ${BOOT_TYPE}
TEST = ${RUN_TEST}
BUILD_NOCLEAN = 1
+92
View File
@@ -0,0 +1,92 @@
#
# This config is an example usage of ktest.pl with a kvm guest
#
# The guest is called 'Guest' and this would be something that
# could be run on the host to test a virtual machine target.
MACHINE = Guest
# Use virsh to read the serial console of the guest
CONSOLE = virsh console ${MACHINE}
# Use SIGKILL to terminate virsh console. We can't kill virsh console
# by the default signal, SIGINT.
CLOSE_CONSOLE_SIGNAL = KILL
#*************************************#
# This part is the same as test.conf #
#*************************************#
# The include files will set up the type of test to run. Just set TEST to
# which test you want to run.
#
# TESTS = patchcheck, randconfig, boot, test, config-bisect, bisect, min-config
#
# See the include/*.conf files that define these tests
#
TEST := patchcheck
# Some tests may have more than one test to run. Define MULTI := 1 to run
# the extra tests.
MULTI := 0
# In case you want to differentiate which type of system you are testing
BITS := 64
# REBOOT = none, error, fail, empty
# See include/defaults.conf
REBOOT := empty
# The defaults file will set up various settings that can be used by all
# machine configs.
INCLUDE include/defaults.conf
#*************************************#
# Now we are different from test.conf #
#*************************************#
# The example here assumes that Guest is running a Fedora release
# that uses dracut for its initfs. The POST_INSTALL will be executed
# after the install of the kernel and modules are complete.
#
POST_INSTALL = ${SSH} /sbin/dracut -f /boot/initramfs-test.img $KERNEL_VERSION
# Guests sometimes get stuck on reboot. We wait 3 seconds after running
# the reboot command and then do a full power-cycle of the guest.
# This forces the guest to restart.
#
POWERCYCLE_AFTER_REBOOT = 3
# We do the same after the halt command, but this time we wait 20 seconds.
POWEROFF_AFTER_HALT = 20
# As the defaults.conf file has a POWER_CYCLE option already defined,
# and options can not be defined in the same section more than once
# (all DEFAULTS sections are considered the same). We use the
# DEFAULTS OVERRIDE to tell ktest.pl to ignore the previous defined
# options, for the options set in the OVERRIDE section.
#
DEFAULTS OVERRIDE
# Instead of using the default POWER_CYCLE option defined in
# defaults.conf, we use virsh to cycle it. To do so, we destroy
# the guest, wait 5 seconds, and then start it up again.
# Crude, but effective.
#
POWER_CYCLE = virsh destroy ${MACHINE}; sleep 5; virsh start ${MACHINE}
DEFAULTS
# The following files each handle a different test case.
# Having them included allows you to set up more than one machine and share
# the same tests.
INCLUDE include/patchcheck.conf
INCLUDE include/tests.conf
INCLUDE include/bisect.conf
INCLUDE include/min-config.conf
@@ -0,0 +1,53 @@
# This example was used to boot the snowball ARM board.
# See http://people.redhat.com/srostedt/ktest-embedded-2012/
# PWD is a ktest.pl variable that will result in the process working
# directory that ktest.pl is executed in.
# THIS_DIR is automatically assigned the PWD of the path that generated
# the config file. It is best to use this variable when assigning other
# directory paths within this directory. This allows you to easily
# move the test cases to other locations or to other machines.
#
THIS_DIR := /home/rostedt/work/demo/ktest-embed
LOG_FILE = ${OUTPUT_DIR}/snowball.log
CLEAR_LOG = 1
MAKE_CMD = PATH=/usr/local/gcc-4.5.2-nolibc/arm-unknown-linux-gnueabi/bin:$PATH CROSS_COMPILE=arm-unknown-linux-gnueabi- make ARCH=arm
ADD_CONFIG = ${THIS_DIR}/addconfig
SCP_TO_TARGET = echo "don't do scp"
TFTPBOOT := /var/lib/tftpboot
TFTPDEF := ${TFTPBOOT}/snowball-default
TFTPTEST := ${OUTPUT_DIR}/${BUILD_TARGET}
SWITCH_TO_GOOD = cp ${TFTPDEF} ${TARGET_IMAGE}
SWITCH_TO_TEST = cp ${TFTPTEST} ${TARGET_IMAGE}
# Define each test with TEST_START
# The config options below it will override the defaults
TEST_START SKIP
TEST_TYPE = boot
BUILD_TYPE = u8500_defconfig
BUILD_NOCLEAN = 1
TEST_START
TEST_TYPE = make_min_config
OUTPUT_MIN_CONFIG = ${THIS_DIR}/config.newmin
START_MIN_CONFIG = ${THIS_DIR}/config.orig
IGNORE_CONFIG = ${THIS_DIR}/config.ignore
BUILD_NOCLEAN = 1
DEFAULTS
LOCALVERSION = -test
POWER_CYCLE = echo use the thumb luke; read a
CONSOLE = cat ${THIS_DIR}/snowball-cat
REBOOT_TYPE = script
SSH_USER = root
BUILD_OPTIONS = -j8 uImage
BUILD_DIR = ${THIS_DIR}/linux.git
OUTPUT_DIR = ${THIS_DIR}/snowball-build
MACHINE = snowball
TARGET_IMAGE = /var/lib/tftpboot/snowball-image
BUILD_TARGET = arch/arm/boot/uImage
+62
View File
@@ -0,0 +1,62 @@
#
# Generic config for a machine
#
# Name your machine (the DNS name, what you ssh to)
MACHINE = foo
# BOX can be different than foo, if the machine BOX has
# multiple partitions with different systems installed. For example,
# you may have a i386 and x86_64 installation on a test box.
# If this is the case, MACHINE defines the way to connect to the
# machine, which may be different between which system the machine
# is booting into. BOX is used for the scripts to reboot and power cycle
# the machine, where it does not matter which system the machine boots into.
#
#BOX := bar
# Define a way to read the console
CONSOLE = stty -F /dev/ttyS0 115200 parodd; cat /dev/ttyS0
# The include files will set up the type of test to run. Just set TEST to
# which test you want to run.
#
# TESTS = patchcheck, randconfig, boot, test, config-bisect, bisect, min-config
#
# See the include/*.conf files that define these tests
#
TEST := patchcheck
# Some tests may have more than one test to run. Define MULTI := 1 to run
# the extra tests.
MULTI := 0
# In case you want to differentiate which type of system you are testing
BITS := 64
# REBOOT = none, error, fail, empty
# See include/defaults.conf
REBOOT := empty
# The defaults file will set up various settings that can be used by all
# machine configs.
INCLUDE include/defaults.conf
# In case you need to add a patch for a bisect or something
#PRE_BUILD = patch -p1 < ${THIS_DIR}/fix.patch
# Reset the repo after the build and remove all 'test' modules from the target
# Notice that DO_POST_BUILD is a variable (defined by ':=') and POST_BUILD
# is the option (defined by '=')
DO_POST_BUILD := git reset --hard
POST_BUILD = ${SSH} 'rm -rf /lib/modules/*-test*'; ${DO_POST_BUILD}
# The following files each handle a different test case.
# Having them included allows you to set up more than one machine and share
# the same tests.
INCLUDE include/patchcheck.conf
INCLUDE include/tests.conf
INCLUDE include/bisect.conf
INCLUDE include/min-config.conf
+4425
View File
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+84
View File
@@ -0,0 +1,84 @@
# SPDX-License-Identifier: GPL-2.0
ldflags-y += --wrap=ioremap_wc
ldflags-y += --wrap=memremap
ldflags-y += --wrap=devm_ioremap_nocache
ldflags-y += --wrap=devm_memremap
ldflags-y += --wrap=devm_memunmap
ldflags-y += --wrap=ioremap_nocache
ldflags-y += --wrap=iounmap
ldflags-y += --wrap=memunmap
ldflags-y += --wrap=__devm_request_region
ldflags-y += --wrap=__devm_release_region
ldflags-y += --wrap=__request_region
ldflags-y += --wrap=__release_region
ldflags-y += --wrap=devm_memremap_pages
ldflags-y += --wrap=insert_resource
ldflags-y += --wrap=remove_resource
ldflags-y += --wrap=acpi_evaluate_object
ldflags-y += --wrap=acpi_evaluate_dsm
DRIVERS := ../../../drivers
NVDIMM_SRC := $(DRIVERS)/nvdimm
ACPI_SRC := $(DRIVERS)/acpi/nfit
DAX_SRC := $(DRIVERS)/dax
ccflags-y := -I$(src)/$(NVDIMM_SRC)/
obj-$(CONFIG_LIBNVDIMM) += libnvdimm.o
obj-$(CONFIG_BLK_DEV_PMEM) += nd_pmem.o
obj-$(CONFIG_ND_BTT) += nd_btt.o
obj-$(CONFIG_ND_BLK) += nd_blk.o
obj-$(CONFIG_X86_PMEM_LEGACY) += nd_e820.o
obj-$(CONFIG_ACPI_NFIT) += nfit.o
ifeq ($(CONFIG_DAX),m)
obj-$(CONFIG_DAX) += dax.o
endif
obj-$(CONFIG_DEV_DAX) += device_dax.o
obj-$(CONFIG_DEV_DAX_PMEM) += dax_pmem.o
nfit-y := $(ACPI_SRC)/core.o
nfit-$(CONFIG_X86_MCE) += $(ACPI_SRC)/mce.o
nfit-y += acpi_nfit_test.o
nfit-y += config_check.o
nd_pmem-y := $(NVDIMM_SRC)/pmem.o
nd_pmem-y += pmem-dax.o
nd_pmem-y += pmem_test.o
nd_pmem-y += config_check.o
nd_btt-y := $(NVDIMM_SRC)/btt.o
nd_btt-y += config_check.o
nd_blk-y := $(NVDIMM_SRC)/blk.o
nd_blk-y += config_check.o
nd_e820-y := $(NVDIMM_SRC)/e820.o
nd_e820-y += config_check.o
dax-y := $(DAX_SRC)/super.o
dax-y += config_check.o
device_dax-y := $(DAX_SRC)/device.o
device_dax-y += dax-dev.o
device_dax-y += device_dax_test.o
device_dax-y += config_check.o
dax_pmem-y := $(DAX_SRC)/pmem.o
dax_pmem-y += config_check.o
libnvdimm-y := $(NVDIMM_SRC)/core.o
libnvdimm-y += $(NVDIMM_SRC)/bus.o
libnvdimm-y += $(NVDIMM_SRC)/dimm_devs.o
libnvdimm-y += $(NVDIMM_SRC)/dimm.o
libnvdimm-y += $(NVDIMM_SRC)/region_devs.o
libnvdimm-y += $(NVDIMM_SRC)/region.o
libnvdimm-y += $(NVDIMM_SRC)/namespace_devs.o
libnvdimm-y += $(NVDIMM_SRC)/label.o
libnvdimm-y += $(NVDIMM_SRC)/badrange.o
libnvdimm-$(CONFIG_ND_CLAIM) += $(NVDIMM_SRC)/claim.o
libnvdimm-$(CONFIG_BTT) += $(NVDIMM_SRC)/btt_devs.o
libnvdimm-$(CONFIG_NVDIMM_PFN) += $(NVDIMM_SRC)/pfn_devs.o
libnvdimm-$(CONFIG_NVDIMM_DAX) += $(NVDIMM_SRC)/dax_devs.o
libnvdimm-y += libnvdimm_test.o
libnvdimm-y += config_check.o
obj-m += test/
+8
View File
@@ -0,0 +1,8 @@
# SPDX-License-Identifier: GPL-2.0
KDIR ?= ../../../
default:
$(MAKE) -C $(KDIR) M=$$PWD
install: default
$(MAKE) -C $(KDIR) M=$$PWD modules_install
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright(c) 2018 Intel Corporation. All rights reserved.
#include <linux/module.h>
#include <linux/printk.h>
#include "watermark.h"
nfit_test_watermark(acpi_nfit);
+18
View File
@@ -0,0 +1,18 @@
// SPDX-License-Identifier: GPL-2.0
#include <linux/bug.h>
void check(void)
{
/*
* These kconfig symbols must be set to "m" for nfit_test to
* load and operate.
*/
BUILD_BUG_ON(!IS_MODULE(CONFIG_LIBNVDIMM));
BUILD_BUG_ON(!IS_MODULE(CONFIG_BLK_DEV_PMEM));
BUILD_BUG_ON(!IS_MODULE(CONFIG_ND_BTT));
BUILD_BUG_ON(!IS_MODULE(CONFIG_ND_PFN));
BUILD_BUG_ON(!IS_MODULE(CONFIG_ND_BLK));
BUILD_BUG_ON(!IS_MODULE(CONFIG_ACPI_NFIT));
BUILD_BUG_ON(!IS_MODULE(CONFIG_DEV_DAX));
BUILD_BUG_ON(!IS_MODULE(CONFIG_DEV_DAX_PMEM));
}
+49
View File
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2016, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*/
#include "test/nfit_test.h"
#include <linux/mm.h>
#include "../../../drivers/dax/dax-private.h"
phys_addr_t dax_pgoff_to_phys(struct dev_dax *dev_dax, pgoff_t pgoff,
unsigned long size)
{
struct resource *res;
phys_addr_t addr;
int i;
for (i = 0; i < dev_dax->num_resources; i++) {
res = &dev_dax->res[i];
addr = pgoff * PAGE_SIZE + res->start;
if (addr >= res->start && addr <= res->end)
break;
pgoff -= PHYS_PFN(resource_size(res));
}
if (i < dev_dax->num_resources) {
res = &dev_dax->res[i];
if (addr + size - 1 <= res->end) {
if (get_nfit_res(addr)) {
struct page *page;
if (dev_dax->region->align > PAGE_SIZE)
return -1;
page = vmalloc_to_page((void *)addr);
return PFN_PHYS(page_to_pfn(page));
} else
return addr;
}
}
return -1;
}
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright(c) 2018 Intel Corporation. All rights reserved.
#include <linux/module.h>
#include <linux/printk.h>
#include "watermark.h"
nfit_test_watermark(device_dax);
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright(c) 2018 Intel Corporation. All rights reserved.
#include <linux/module.h>
#include <linux/printk.h>
#include "watermark.h"
nfit_test_watermark(libnvdimm);
+57
View File
@@ -0,0 +1,57 @@
/*
* Copyright (c) 2014-2016, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*/
#include "test/nfit_test.h"
#include <linux/blkdev.h>
#include <pmem.h>
#include <nd.h>
long __pmem_direct_access(struct pmem_device *pmem, pgoff_t pgoff,
long nr_pages, void **kaddr, pfn_t *pfn)
{
resource_size_t offset = PFN_PHYS(pgoff) + pmem->data_offset;
if (unlikely(is_bad_pmem(&pmem->bb, PFN_PHYS(pgoff) / 512,
PFN_PHYS(nr_pages))))
return -EIO;
/*
* Limit dax to a single page at a time given vmalloc()-backed
* in the nfit_test case.
*/
if (get_nfit_res(pmem->phys_addr + offset)) {
struct page *page;
if (kaddr)
*kaddr = pmem->virt_addr + offset;
page = vmalloc_to_page(pmem->virt_addr + offset);
if (pfn)
*pfn = page_to_pfn_t(page);
pr_debug_ratelimited("%s: pmem: %p pgoff: %#lx pfn: %#lx\n",
__func__, pmem, pgoff, page_to_pfn(page));
return 1;
}
if (kaddr)
*kaddr = pmem->virt_addr + offset;
if (pfn)
*pfn = phys_to_pfn_t(pmem->phys_addr + offset, pmem->pfn_flags);
/*
* If badblocks are present, limit known good range to the
* requested range.
*/
if (unlikely(pmem->bb.count))
return nr_pages;
return PHYS_PFN(pmem->size - pmem->pfn_pad - offset);
}
+8
View File
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright(c) 2018 Intel Corporation. All rights reserved.
#include <linux/module.h>
#include <linux/printk.h>
#include "watermark.h"
nfit_test_watermark(pmem);
+9
View File
@@ -0,0 +1,9 @@
# SPDX-License-Identifier: GPL-2.0
ccflags-y := -I$(src)/../../../../drivers/nvdimm/
ccflags-y += -I$(src)/../../../../drivers/acpi/nfit/
obj-m += nfit_test.o
obj-m += nfit_test_iomap.o
nfit_test-y := nfit.o
nfit_test_iomap-y := iomap.o
+403
View File
@@ -0,0 +1,403 @@
/*
* Copyright(c) 2013-2015 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* 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.
*/
#include <linux/memremap.h>
#include <linux/rculist.h>
#include <linux/export.h>
#include <linux/ioport.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/pfn_t.h>
#include <linux/acpi.h>
#include <linux/io.h>
#include <linux/mm.h>
#include "nfit_test.h"
static LIST_HEAD(iomap_head);
static struct iomap_ops {
nfit_test_lookup_fn nfit_test_lookup;
nfit_test_evaluate_dsm_fn evaluate_dsm;
struct list_head list;
} iomap_ops = {
.list = LIST_HEAD_INIT(iomap_ops.list),
};
void nfit_test_setup(nfit_test_lookup_fn lookup,
nfit_test_evaluate_dsm_fn evaluate)
{
iomap_ops.nfit_test_lookup = lookup;
iomap_ops.evaluate_dsm = evaluate;
list_add_rcu(&iomap_ops.list, &iomap_head);
}
EXPORT_SYMBOL(nfit_test_setup);
void nfit_test_teardown(void)
{
list_del_rcu(&iomap_ops.list);
synchronize_rcu();
}
EXPORT_SYMBOL(nfit_test_teardown);
static struct nfit_test_resource *__get_nfit_res(resource_size_t resource)
{
struct iomap_ops *ops;
ops = list_first_or_null_rcu(&iomap_head, typeof(*ops), list);
if (ops)
return ops->nfit_test_lookup(resource);
return NULL;
}
struct nfit_test_resource *get_nfit_res(resource_size_t resource)
{
struct nfit_test_resource *res;
rcu_read_lock();
res = __get_nfit_res(resource);
rcu_read_unlock();
return res;
}
EXPORT_SYMBOL(get_nfit_res);
void __iomem *__nfit_test_ioremap(resource_size_t offset, unsigned long size,
void __iomem *(*fallback_fn)(resource_size_t, unsigned long))
{
struct nfit_test_resource *nfit_res = get_nfit_res(offset);
if (nfit_res)
return (void __iomem *) nfit_res->buf + offset
- nfit_res->res.start;
return fallback_fn(offset, size);
}
void __iomem *__wrap_devm_ioremap_nocache(struct device *dev,
resource_size_t offset, unsigned long size)
{
struct nfit_test_resource *nfit_res = get_nfit_res(offset);
if (nfit_res)
return (void __iomem *) nfit_res->buf + offset
- nfit_res->res.start;
return devm_ioremap_nocache(dev, offset, size);
}
EXPORT_SYMBOL(__wrap_devm_ioremap_nocache);
void *__wrap_devm_memremap(struct device *dev, resource_size_t offset,
size_t size, unsigned long flags)
{
struct nfit_test_resource *nfit_res = get_nfit_res(offset);
if (nfit_res)
return nfit_res->buf + offset - nfit_res->res.start;
return devm_memremap(dev, offset, size, flags);
}
EXPORT_SYMBOL(__wrap_devm_memremap);
static void nfit_test_kill(void *_pgmap)
{
struct dev_pagemap *pgmap = _pgmap;
pgmap->kill(pgmap->ref);
}
void *__wrap_devm_memremap_pages(struct device *dev, struct dev_pagemap *pgmap)
{
resource_size_t offset = pgmap->res.start;
struct nfit_test_resource *nfit_res = get_nfit_res(offset);
if (nfit_res) {
int rc;
rc = devm_add_action_or_reset(dev, nfit_test_kill, pgmap);
if (rc)
return ERR_PTR(rc);
return nfit_res->buf + offset - nfit_res->res.start;
}
return devm_memremap_pages(dev, pgmap);
}
EXPORT_SYMBOL_GPL(__wrap_devm_memremap_pages);
pfn_t __wrap_phys_to_pfn_t(phys_addr_t addr, unsigned long flags)
{
struct nfit_test_resource *nfit_res = get_nfit_res(addr);
if (nfit_res)
flags &= ~PFN_MAP;
return phys_to_pfn_t(addr, flags);
}
EXPORT_SYMBOL(__wrap_phys_to_pfn_t);
void *__wrap_memremap(resource_size_t offset, size_t size,
unsigned long flags)
{
struct nfit_test_resource *nfit_res = get_nfit_res(offset);
if (nfit_res)
return nfit_res->buf + offset - nfit_res->res.start;
return memremap(offset, size, flags);
}
EXPORT_SYMBOL(__wrap_memremap);
void __wrap_devm_memunmap(struct device *dev, void *addr)
{
struct nfit_test_resource *nfit_res = get_nfit_res((long) addr);
if (nfit_res)
return;
return devm_memunmap(dev, addr);
}
EXPORT_SYMBOL(__wrap_devm_memunmap);
void __iomem *__wrap_ioremap_nocache(resource_size_t offset, unsigned long size)
{
return __nfit_test_ioremap(offset, size, ioremap_nocache);
}
EXPORT_SYMBOL(__wrap_ioremap_nocache);
void __iomem *__wrap_ioremap_wc(resource_size_t offset, unsigned long size)
{
return __nfit_test_ioremap(offset, size, ioremap_wc);
}
EXPORT_SYMBOL(__wrap_ioremap_wc);
void __wrap_iounmap(volatile void __iomem *addr)
{
struct nfit_test_resource *nfit_res = get_nfit_res((long) addr);
if (nfit_res)
return;
return iounmap(addr);
}
EXPORT_SYMBOL(__wrap_iounmap);
void __wrap_memunmap(void *addr)
{
struct nfit_test_resource *nfit_res = get_nfit_res((long) addr);
if (nfit_res)
return;
return memunmap(addr);
}
EXPORT_SYMBOL(__wrap_memunmap);
static bool nfit_test_release_region(struct device *dev,
struct resource *parent, resource_size_t start,
resource_size_t n);
static void nfit_devres_release(struct device *dev, void *data)
{
struct resource *res = *((struct resource **) data);
WARN_ON(!nfit_test_release_region(NULL, &iomem_resource, res->start,
resource_size(res)));
}
static int match(struct device *dev, void *__res, void *match_data)
{
struct resource *res = *((struct resource **) __res);
resource_size_t start = *((resource_size_t *) match_data);
return res->start == start;
}
static bool nfit_test_release_region(struct device *dev,
struct resource *parent, resource_size_t start,
resource_size_t n)
{
if (parent == &iomem_resource) {
struct nfit_test_resource *nfit_res = get_nfit_res(start);
if (nfit_res) {
struct nfit_test_request *req;
struct resource *res = NULL;
if (dev) {
devres_release(dev, nfit_devres_release, match,
&start);
return true;
}
spin_lock(&nfit_res->lock);
list_for_each_entry(req, &nfit_res->requests, list)
if (req->res.start == start) {
res = &req->res;
list_del(&req->list);
break;
}
spin_unlock(&nfit_res->lock);
WARN(!res || resource_size(res) != n,
"%s: start: %llx n: %llx mismatch: %pr\n",
__func__, start, n, res);
if (res)
kfree(req);
return true;
}
}
return false;
}
static struct resource *nfit_test_request_region(struct device *dev,
struct resource *parent, resource_size_t start,
resource_size_t n, const char *name, int flags)
{
struct nfit_test_resource *nfit_res;
if (parent == &iomem_resource) {
nfit_res = get_nfit_res(start);
if (nfit_res) {
struct nfit_test_request *req;
struct resource *res = NULL;
if (start + n > nfit_res->res.start
+ resource_size(&nfit_res->res)) {
pr_debug("%s: start: %llx n: %llx overflow: %pr\n",
__func__, start, n,
&nfit_res->res);
return NULL;
}
spin_lock(&nfit_res->lock);
list_for_each_entry(req, &nfit_res->requests, list)
if (start == req->res.start) {
res = &req->res;
break;
}
spin_unlock(&nfit_res->lock);
if (res) {
WARN(1, "%pr already busy\n", res);
return NULL;
}
req = kzalloc(sizeof(*req), GFP_KERNEL);
if (!req)
return NULL;
INIT_LIST_HEAD(&req->list);
res = &req->res;
res->start = start;
res->end = start + n - 1;
res->name = name;
res->flags = resource_type(parent);
res->flags |= IORESOURCE_BUSY | flags;
spin_lock(&nfit_res->lock);
list_add(&req->list, &nfit_res->requests);
spin_unlock(&nfit_res->lock);
if (dev) {
struct resource **d;
d = devres_alloc(nfit_devres_release,
sizeof(struct resource *),
GFP_KERNEL);
if (!d)
return NULL;
*d = res;
devres_add(dev, d);
}
pr_debug("%s: %pr\n", __func__, res);
return res;
}
}
if (dev)
return __devm_request_region(dev, parent, start, n, name);
return __request_region(parent, start, n, name, flags);
}
struct resource *__wrap___request_region(struct resource *parent,
resource_size_t start, resource_size_t n, const char *name,
int flags)
{
return nfit_test_request_region(NULL, parent, start, n, name, flags);
}
EXPORT_SYMBOL(__wrap___request_region);
int __wrap_insert_resource(struct resource *parent, struct resource *res)
{
if (get_nfit_res(res->start))
return 0;
return insert_resource(parent, res);
}
EXPORT_SYMBOL(__wrap_insert_resource);
int __wrap_remove_resource(struct resource *res)
{
if (get_nfit_res(res->start))
return 0;
return remove_resource(res);
}
EXPORT_SYMBOL(__wrap_remove_resource);
struct resource *__wrap___devm_request_region(struct device *dev,
struct resource *parent, resource_size_t start,
resource_size_t n, const char *name)
{
if (!dev)
return NULL;
return nfit_test_request_region(dev, parent, start, n, name, 0);
}
EXPORT_SYMBOL(__wrap___devm_request_region);
void __wrap___release_region(struct resource *parent, resource_size_t start,
resource_size_t n)
{
if (!nfit_test_release_region(NULL, parent, start, n))
__release_region(parent, start, n);
}
EXPORT_SYMBOL(__wrap___release_region);
void __wrap___devm_release_region(struct device *dev, struct resource *parent,
resource_size_t start, resource_size_t n)
{
if (!nfit_test_release_region(dev, parent, start, n))
__devm_release_region(dev, parent, start, n);
}
EXPORT_SYMBOL(__wrap___devm_release_region);
acpi_status __wrap_acpi_evaluate_object(acpi_handle handle, acpi_string path,
struct acpi_object_list *p, struct acpi_buffer *buf)
{
struct nfit_test_resource *nfit_res = get_nfit_res((long) handle);
union acpi_object **obj;
if (!nfit_res || strcmp(path, "_FIT") || !buf)
return acpi_evaluate_object(handle, path, p, buf);
obj = nfit_res->buf;
buf->length = sizeof(union acpi_object);
buf->pointer = *obj;
return AE_OK;
}
EXPORT_SYMBOL(__wrap_acpi_evaluate_object);
union acpi_object * __wrap_acpi_evaluate_dsm(acpi_handle handle, const guid_t *guid,
u64 rev, u64 func, union acpi_object *argv4)
{
union acpi_object *obj = ERR_PTR(-ENXIO);
struct iomap_ops *ops;
rcu_read_lock();
ops = list_first_or_null_rcu(&iomap_head, typeof(*ops), list);
if (ops)
obj = ops->evaluate_dsm(handle, guid, rev, func, argv4);
rcu_read_unlock();
if (IS_ERR(obj))
return acpi_evaluate_dsm(handle, guid, rev, func, argv4);
return obj;
}
EXPORT_SYMBOL(__wrap_acpi_evaluate_dsm);
MODULE_LICENSE("GPL v2");
File diff suppressed because it is too large. Load diff
+249
View File
@@ -0,0 +1,249 @@
/*
* Copyright(c) 2013-2015 Intel Corporation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of version 2 of the GNU General Public License as
* published by the Free Software Foundation.
*
* 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.
*/
#ifndef __NFIT_TEST_H__
#define __NFIT_TEST_H__
#include <linux/acpi.h>
#include <linux/list.h>
#include <linux/uuid.h>
#include <linux/ioport.h>
#include <linux/spinlock_types.h>
struct nfit_test_request {
struct list_head list;
struct resource res;
};
struct nfit_test_resource {
struct list_head requests;
struct list_head list;
struct resource res;
struct device *dev;
spinlock_t lock;
int req_count;
void *buf;
};
#define ND_TRANSLATE_SPA_STATUS_INVALID_SPA 2
#define NFIT_ARS_INJECT_INVALID 2
enum err_inj_options {
ND_ARS_ERR_INJ_OPT_NOTIFY = 0,
};
/* nfit commands */
enum nfit_cmd_num {
NFIT_CMD_TRANSLATE_SPA = 5,
NFIT_CMD_ARS_INJECT_SET = 7,
NFIT_CMD_ARS_INJECT_CLEAR = 8,
NFIT_CMD_ARS_INJECT_GET = 9,
};
struct nd_cmd_translate_spa {
__u64 spa;
__u32 status;
__u8 flags;
__u8 _reserved[3];
__u64 translate_length;
__u32 num_nvdimms;
struct nd_nvdimm_device {
__u32 nfit_device_handle;
__u32 _reserved;
__u64 dpa;
} __packed devices[0];
} __packed;
struct nd_cmd_ars_err_inj {
__u64 err_inj_spa_range_base;
__u64 err_inj_spa_range_length;
__u8 err_inj_options;
__u32 status;
} __packed;
struct nd_cmd_ars_err_inj_clr {
__u64 err_inj_clr_spa_range_base;
__u64 err_inj_clr_spa_range_length;
__u32 status;
} __packed;
struct nd_cmd_ars_err_inj_stat {
__u32 status;
__u32 inj_err_rec_count;
struct nd_error_stat_query_record {
__u64 err_inj_stat_spa_range_base;
__u64 err_inj_stat_spa_range_length;
} __packed record[0];
} __packed;
#define ND_INTEL_SMART 1
#define ND_INTEL_SMART_THRESHOLD 2
#define ND_INTEL_ENABLE_LSS_STATUS 10
#define ND_INTEL_FW_GET_INFO 12
#define ND_INTEL_FW_START_UPDATE 13
#define ND_INTEL_FW_SEND_DATA 14
#define ND_INTEL_FW_FINISH_UPDATE 15
#define ND_INTEL_FW_FINISH_QUERY 16
#define ND_INTEL_SMART_SET_THRESHOLD 17
#define ND_INTEL_SMART_INJECT 18
#define ND_INTEL_SMART_HEALTH_VALID (1 << 0)
#define ND_INTEL_SMART_SPARES_VALID (1 << 1)
#define ND_INTEL_SMART_USED_VALID (1 << 2)
#define ND_INTEL_SMART_MTEMP_VALID (1 << 3)
#define ND_INTEL_SMART_CTEMP_VALID (1 << 4)
#define ND_INTEL_SMART_SHUTDOWN_COUNT_VALID (1 << 5)
#define ND_INTEL_SMART_AIT_STATUS_VALID (1 << 6)
#define ND_INTEL_SMART_PTEMP_VALID (1 << 7)
#define ND_INTEL_SMART_ALARM_VALID (1 << 9)
#define ND_INTEL_SMART_SHUTDOWN_VALID (1 << 10)
#define ND_INTEL_SMART_VENDOR_VALID (1 << 11)
#define ND_INTEL_SMART_SPARE_TRIP (1 << 0)
#define ND_INTEL_SMART_TEMP_TRIP (1 << 1)
#define ND_INTEL_SMART_CTEMP_TRIP (1 << 2)
#define ND_INTEL_SMART_NON_CRITICAL_HEALTH (1 << 0)
#define ND_INTEL_SMART_CRITICAL_HEALTH (1 << 1)
#define ND_INTEL_SMART_FATAL_HEALTH (1 << 2)
#define ND_INTEL_SMART_INJECT_MTEMP (1 << 0)
#define ND_INTEL_SMART_INJECT_SPARE (1 << 1)
#define ND_INTEL_SMART_INJECT_FATAL (1 << 2)
#define ND_INTEL_SMART_INJECT_SHUTDOWN (1 << 3)
struct nd_intel_smart {
__u32 status;
union {
struct {
__u32 flags;
__u8 reserved0[4];
__u8 health;
__u8 spares;
__u8 life_used;
__u8 alarm_flags;
__u16 media_temperature;
__u16 ctrl_temperature;
__u32 shutdown_count;
__u8 ait_status;
__u16 pmic_temperature;
__u8 reserved1[8];
__u8 shutdown_state;
__u32 vendor_size;
__u8 vendor_data[92];
} __packed;
__u8 data[128];
};
} __packed;
struct nd_intel_smart_threshold {
__u32 status;
union {
struct {
__u16 alarm_control;
__u8 spares;
__u16 media_temperature;
__u16 ctrl_temperature;
__u8 reserved[1];
} __packed;
__u8 data[8];
};
} __packed;
struct nd_intel_smart_set_threshold {
__u16 alarm_control;
__u8 spares;
__u16 media_temperature;
__u16 ctrl_temperature;
__u32 status;
} __packed;
struct nd_intel_smart_inject {
__u64 flags;
__u8 mtemp_enable;
__u16 media_temperature;
__u8 spare_enable;
__u8 spares;
__u8 fatal_enable;
__u8 unsafe_shutdown_enable;
__u32 status;
} __packed;
#define INTEL_FW_STORAGE_SIZE 0x100000
#define INTEL_FW_MAX_SEND_LEN 0xFFEC
#define INTEL_FW_QUERY_INTERVAL 250000
#define INTEL_FW_QUERY_MAX_TIME 3000000
#define INTEL_FW_FIS_VERSION 0x0105
#define INTEL_FW_FAKE_VERSION 0xffffffffabcd
enum intel_fw_update_state {
FW_STATE_NEW = 0,
FW_STATE_IN_PROGRESS,
FW_STATE_VERIFY,
FW_STATE_UPDATED,
};
struct nd_intel_fw_info {
__u32 status;
__u32 storage_size;
__u32 max_send_len;
__u32 query_interval;
__u32 max_query_time;
__u8 update_cap;
__u8 reserved[3];
__u32 fis_version;
__u64 run_version;
__u64 updated_version;
} __packed;
struct nd_intel_fw_start {
__u32 status;
__u32 context;
} __packed;
/* this one has the output first because the variable input data size */
struct nd_intel_fw_send_data {
__u32 context;
__u32 offset;
__u32 length;
__u8 data[0];
/* this field is not declared due ot variable data from input */
/* __u32 status; */
} __packed;
struct nd_intel_fw_finish_update {
__u8 ctrl_flags;
__u8 reserved[3];
__u32 context;
__u32 status;
} __packed;
struct nd_intel_fw_finish_query {
__u32 context;
__u32 status;
__u64 updated_fw_rev;
} __packed;
struct nd_intel_lss {
__u8 enable;
__u32 status;
} __packed;
typedef struct nfit_test_resource *(*nfit_test_lookup_fn)(resource_size_t);
typedef union acpi_object *(*nfit_test_evaluate_dsm_fn)(acpi_handle handle,
const guid_t *guid, u64 rev, u64 func,
union acpi_object *argv4);
void __iomem *__wrap_ioremap_nocache(resource_size_t offset,
unsigned long size);
void __wrap_iounmap(volatile void __iomem *addr);
void nfit_test_setup(nfit_test_lookup_fn lookup,
nfit_test_evaluate_dsm_fn evaluate);
void nfit_test_teardown(void);
struct nfit_test_resource *get_nfit_res(resource_size_t resource);
#endif
+21
View File
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright(c) 2018 Intel Corporation. All rights reserved.
#ifndef _TEST_NVDIMM_WATERMARK_H_
#define _TEST_NVDIMM_WATERMARK_H_
int pmem_test(void);
int libnvdimm_test(void);
int acpi_nfit_test(void);
int device_dax_test(void);
/*
* dummy routine for nfit_test to validate it is linking to the properly
* mocked module and not the standard one from the base tree.
*/
#define nfit_test_watermark(x) \
int x##_test(void) \
{ \
pr_debug("%s for nfit_test\n", KBUILD_MODNAME); \
return 0; \
} \
EXPORT_SYMBOL(x##_test)
#endif /* _TEST_NVDIMM_WATERMARK_H_ */
+51
View File
@@ -0,0 +1,51 @@
# SPDX-License-Identifier: GPL-2.0
CFLAGS += -I. -I../../include -g -Og -Wall -D_LGPL_SOURCE -fsanitize=address \
-fsanitize=undefined
LDFLAGS += -fsanitize=address -fsanitize=undefined
LDLIBS+= -lpthread -lurcu
TARGETS = main idr-test multiorder
CORE_OFILES := radix-tree.o idr.o linux.o test.o find_bit.o
OFILES = main.o $(CORE_OFILES) regression1.o regression2.o regression3.o \
tag_check.o multiorder.o idr-test.o iteration_check.o benchmark.o
ifndef SHIFT
SHIFT=3
endif
ifeq ($(BUILD), 32)
CFLAGS += -m32
LDFLAGS += -m32
endif
targets: generated/map-shift.h $(TARGETS)
main: $(OFILES)
idr-test.o: ../../../lib/test_ida.c
idr-test: idr-test.o $(CORE_OFILES)
multiorder: multiorder.o $(CORE_OFILES)
clean:
$(RM) $(TARGETS) *.o radix-tree.c idr.c generated/map-shift.h
vpath %.c ../../lib
$(OFILES): Makefile *.h */*.h generated/map-shift.h \
../../include/linux/*.h \
../../include/asm/*.h \
../../../include/linux/radix-tree.h \
../../../include/linux/idr.h
radix-tree.c: ../../../lib/radix-tree.c
sed -e 's/^static //' -e 's/__always_inline //' -e 's/inline //' < $< > $@
idr.c: ../../../lib/idr.c
sed -e 's/^static //' -e 's/__always_inline //' -e 's/inline //' < $< > $@
generated/map-shift.h:
@if ! grep -qws $(SHIFT) generated/map-shift.h; then \
echo "#define RADIX_TREE_MAP_SHIFT $(SHIFT)" > \
generated/map-shift.h; \
fi
+257
View File
@@ -0,0 +1,257 @@
/*
* benchmark.c:
* Author: Konstantin Khlebnikov <koct9i@gmail.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*/
#include <linux/radix-tree.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <time.h>
#include "test.h"
#define for_each_index(i, base, order) \
for (i = base; i < base + (1 << order); i++)
#define NSEC_PER_SEC 1000000000L
static long long benchmark_iter(struct radix_tree_root *root, bool tagged)
{
volatile unsigned long sink = 0;
struct radix_tree_iter iter;
struct timespec start, finish;
long long nsec;
int l, loops = 1;
void **slot;
#ifdef BENCHMARK
again:
#endif
clock_gettime(CLOCK_MONOTONIC, &start);
for (l = 0; l < loops; l++) {
if (tagged) {
radix_tree_for_each_tagged(slot, root, &iter, 0, 0)
sink ^= (unsigned long)slot;
} else {
radix_tree_for_each_slot(slot, root, &iter, 0)
sink ^= (unsigned long)slot;
}
}
clock_gettime(CLOCK_MONOTONIC, &finish);
nsec = (finish.tv_sec - start.tv_sec) * NSEC_PER_SEC +
(finish.tv_nsec - start.tv_nsec);
#ifdef BENCHMARK
if (loops == 1 && nsec * 5 < NSEC_PER_SEC) {
loops = NSEC_PER_SEC / nsec / 4 + 1;
goto again;
}
#endif
nsec /= loops;
return nsec;
}
static void benchmark_insert(struct radix_tree_root *root,
unsigned long size, unsigned long step, int order)
{
struct timespec start, finish;
unsigned long index;
long long nsec;
clock_gettime(CLOCK_MONOTONIC, &start);
for (index = 0 ; index < size ; index += step)
item_insert_order(root, index, order);
clock_gettime(CLOCK_MONOTONIC, &finish);
nsec = (finish.tv_sec - start.tv_sec) * NSEC_PER_SEC +
(finish.tv_nsec - start.tv_nsec);
printv(2, "Size: %8ld, step: %8ld, order: %d, insertion: %15lld ns\n",
size, step, order, nsec);
}
static void benchmark_tagging(struct radix_tree_root *root,
unsigned long size, unsigned long step, int order)
{
struct timespec start, finish;
unsigned long index;
long long nsec;
clock_gettime(CLOCK_MONOTONIC, &start);
for (index = 0 ; index < size ; index += step)
radix_tree_tag_set(root, index, 0);
clock_gettime(CLOCK_MONOTONIC, &finish);
nsec = (finish.tv_sec - start.tv_sec) * NSEC_PER_SEC +
(finish.tv_nsec - start.tv_nsec);
printv(2, "Size: %8ld, step: %8ld, order: %d, tagging: %17lld ns\n",
size, step, order, nsec);
}
static void benchmark_delete(struct radix_tree_root *root,
unsigned long size, unsigned long step, int order)
{
struct timespec start, finish;
unsigned long index, i;
long long nsec;
clock_gettime(CLOCK_MONOTONIC, &start);
for (index = 0 ; index < size ; index += step)
for_each_index(i, index, order)
item_delete(root, i);
clock_gettime(CLOCK_MONOTONIC, &finish);
nsec = (finish.tv_sec - start.tv_sec) * NSEC_PER_SEC +
(finish.tv_nsec - start.tv_nsec);
printv(2, "Size: %8ld, step: %8ld, order: %d, deletion: %16lld ns\n",
size, step, order, nsec);
}
static void benchmark_size(unsigned long size, unsigned long step, int order)
{
RADIX_TREE(tree, GFP_KERNEL);
long long normal, tagged;
benchmark_insert(&tree, size, step, order);
benchmark_tagging(&tree, size, step, order);
tagged = benchmark_iter(&tree, true);
normal = benchmark_iter(&tree, false);
printv(2, "Size: %8ld, step: %8ld, order: %d, tagged iteration: %8lld ns\n",
size, step, order, tagged);
printv(2, "Size: %8ld, step: %8ld, order: %d, normal iteration: %8lld ns\n",
size, step, order, normal);
benchmark_delete(&tree, size, step, order);
item_kill_tree(&tree);
rcu_barrier();
}
static long long __benchmark_split(unsigned long index,
int old_order, int new_order)
{
struct timespec start, finish;
long long nsec;
RADIX_TREE(tree, GFP_ATOMIC);
item_insert_order(&tree, index, old_order);
clock_gettime(CLOCK_MONOTONIC, &start);
radix_tree_split(&tree, index, new_order);
clock_gettime(CLOCK_MONOTONIC, &finish);
nsec = (finish.tv_sec - start.tv_sec) * NSEC_PER_SEC +
(finish.tv_nsec - start.tv_nsec);
item_kill_tree(&tree);
return nsec;
}
static void benchmark_split(unsigned long size, unsigned long step)
{
int i, j, idx;
long long nsec = 0;
for (idx = 0; idx < size; idx += step) {
for (i = 3; i < 11; i++) {
for (j = 0; j < i; j++) {
nsec += __benchmark_split(idx, i, j);
}
}
}
printv(2, "Size %8ld, step %8ld, split time %10lld ns\n",
size, step, nsec);
}
static long long __benchmark_join(unsigned long index,
unsigned order1, unsigned order2)
{
unsigned long loc;
struct timespec start, finish;
long long nsec;
void *item, *item2 = item_create(index + 1, order1);
RADIX_TREE(tree, GFP_KERNEL);
item_insert_order(&tree, index, order2);
item = radix_tree_lookup(&tree, index);
clock_gettime(CLOCK_MONOTONIC, &start);
radix_tree_join(&tree, index + 1, order1, item2);
clock_gettime(CLOCK_MONOTONIC, &finish);
nsec = (finish.tv_sec - start.tv_sec) * NSEC_PER_SEC +
(finish.tv_nsec - start.tv_nsec);
loc = find_item(&tree, item);
if (loc == -1)
free(item);
item_kill_tree(&tree);
return nsec;
}
static void benchmark_join(unsigned long step)
{
int i, j, idx;
long long nsec = 0;
for (idx = 0; idx < 1 << 10; idx += step) {
for (i = 1; i < 15; i++) {
for (j = 0; j < i; j++) {
nsec += __benchmark_join(idx, i, j);
}
}
}
printv(2, "Size %8d, step %8ld, join time %10lld ns\n",
1 << 10, step, nsec);
}
void benchmark(void)
{
unsigned long size[] = {1 << 10, 1 << 20, 0};
unsigned long step[] = {1, 2, 7, 15, 63, 64, 65,
128, 256, 512, 12345, 0};
int c, s;
printv(1, "starting benchmarks\n");
printv(1, "RADIX_TREE_MAP_SHIFT = %d\n", RADIX_TREE_MAP_SHIFT);
for (c = 0; size[c]; c++)
for (s = 0; step[s]; s++)
benchmark_size(size[c], step[s], 0);
for (c = 0; size[c]; c++)
for (s = 0; step[s]; s++)
benchmark_size(size[c], step[s] << 9, 9);
for (c = 0; size[c]; c++)
for (s = 0; step[s]; s++)
benchmark_split(size[c], step[s]);
for (s = 0; step[s]; s++)
benchmark_join(step[s]);
}
@@ -0,0 +1 @@
#define CONFIG_RADIX_TREE_MULTIORDER 1
+512
View File
@@ -0,0 +1,512 @@
/*
* idr-test.c: Test the IDR API
* Copyright (c) 2016 Matthew Wilcox <willy@infradead.org>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*/
#include <linux/bitmap.h>
#include <linux/idr.h>
#include <linux/slab.h>
#include <linux/kernel.h>
#include <linux/errno.h>
#include "test.h"
#define DUMMY_PTR ((void *)0x12)
int item_idr_free(int id, void *p, void *data)
{
struct item *item = p;
assert(item->index == id);
free(p);
return 0;
}
void item_idr_remove(struct idr *idr, int id)
{
struct item *item = idr_find(idr, id);
assert(item->index == id);
idr_remove(idr, id);
free(item);
}
void idr_alloc_test(void)
{
unsigned long i;
DEFINE_IDR(idr);
assert(idr_alloc_cyclic(&idr, DUMMY_PTR, 0, 0x4000, GFP_KERNEL) == 0);
assert(idr_alloc_cyclic(&idr, DUMMY_PTR, 0x3ffd, 0x4000, GFP_KERNEL) == 0x3ffd);
idr_remove(&idr, 0x3ffd);
idr_remove(&idr, 0);
for (i = 0x3ffe; i < 0x4003; i++) {
int id;
struct item *item;
if (i < 0x4000)
item = item_create(i, 0);
else
item = item_create(i - 0x3fff, 0);
id = idr_alloc_cyclic(&idr, item, 1, 0x4000, GFP_KERNEL);
assert(id == item->index);
}
idr_for_each(&idr, item_idr_free, &idr);
idr_destroy(&idr);
}
void idr_replace_test(void)
{
DEFINE_IDR(idr);
idr_alloc(&idr, (void *)-1, 10, 11, GFP_KERNEL);
idr_replace(&idr, &idr, 10);
idr_destroy(&idr);
}
/*
* Unlike the radix tree, you can put a NULL pointer -- with care -- into
* the IDR. Some interfaces, like idr_find() do not distinguish between
* "present, value is NULL" and "not present", but that's exactly what some
* users want.
*/
void idr_null_test(void)
{
int i;
DEFINE_IDR(idr);
assert(idr_is_empty(&idr));
assert(idr_alloc(&idr, NULL, 0, 0, GFP_KERNEL) == 0);
assert(!idr_is_empty(&idr));
idr_remove(&idr, 0);
assert(idr_is_empty(&idr));
assert(idr_alloc(&idr, NULL, 0, 0, GFP_KERNEL) == 0);
assert(!idr_is_empty(&idr));
idr_destroy(&idr);
assert(idr_is_empty(&idr));
for (i = 0; i < 10; i++) {
assert(idr_alloc(&idr, NULL, 0, 0, GFP_KERNEL) == i);
}
assert(idr_replace(&idr, DUMMY_PTR, 3) == NULL);
assert(idr_replace(&idr, DUMMY_PTR, 4) == NULL);
assert(idr_replace(&idr, NULL, 4) == DUMMY_PTR);
assert(idr_replace(&idr, DUMMY_PTR, 11) == ERR_PTR(-ENOENT));
idr_remove(&idr, 5);
assert(idr_alloc(&idr, NULL, 0, 0, GFP_KERNEL) == 5);
idr_remove(&idr, 5);
for (i = 0; i < 9; i++) {
idr_remove(&idr, i);
assert(!idr_is_empty(&idr));
}
idr_remove(&idr, 8);
assert(!idr_is_empty(&idr));
idr_remove(&idr, 9);
assert(idr_is_empty(&idr));
assert(idr_alloc(&idr, NULL, 0, 0, GFP_KERNEL) == 0);
assert(idr_replace(&idr, DUMMY_PTR, 3) == ERR_PTR(-ENOENT));
assert(idr_replace(&idr, DUMMY_PTR, 0) == NULL);
assert(idr_replace(&idr, NULL, 0) == DUMMY_PTR);
idr_destroy(&idr);
assert(idr_is_empty(&idr));
for (i = 1; i < 10; i++) {
assert(idr_alloc(&idr, NULL, 1, 0, GFP_KERNEL) == i);
}
idr_destroy(&idr);
assert(idr_is_empty(&idr));
}
void idr_nowait_test(void)
{
unsigned int i;
DEFINE_IDR(idr);
idr_preload(GFP_KERNEL);
for (i = 0; i < 3; i++) {
struct item *item = item_create(i, 0);
assert(idr_alloc(&idr, item, i, i + 1, GFP_NOWAIT) == i);
}
idr_preload_end();
idr_for_each(&idr, item_idr_free, &idr);
idr_destroy(&idr);
}
void idr_get_next_test(int base)
{
unsigned long i;
int nextid;
DEFINE_IDR(idr);
idr_init_base(&idr, base);
int indices[] = {4, 7, 9, 15, 65, 128, 1000, 99999, 0};
for(i = 0; indices[i]; i++) {
struct item *item = item_create(indices[i], 0);
assert(idr_alloc(&idr, item, indices[i], indices[i+1],
GFP_KERNEL) == indices[i]);
}
for(i = 0, nextid = 0; indices[i]; i++) {
idr_get_next(&idr, &nextid);
assert(nextid == indices[i]);
nextid++;
}
idr_for_each(&idr, item_idr_free, &idr);
idr_destroy(&idr);
}
int idr_u32_cb(int id, void *ptr, void *data)
{
BUG_ON(id < 0);
BUG_ON(ptr != DUMMY_PTR);
return 0;
}
void idr_u32_test1(struct idr *idr, u32 handle)
{
static bool warned = false;
u32 id = handle;
int sid = 0;
void *ptr;
BUG_ON(idr_alloc_u32(idr, DUMMY_PTR, &id, id, GFP_KERNEL));
BUG_ON(id != handle);
BUG_ON(idr_alloc_u32(idr, DUMMY_PTR, &id, id, GFP_KERNEL) != -ENOSPC);
BUG_ON(id != handle);
if (!warned && id > INT_MAX)
printk("vvv Ignore these warnings\n");
ptr = idr_get_next(idr, &sid);
if (id > INT_MAX) {
BUG_ON(ptr != NULL);
BUG_ON(sid != 0);
} else {
BUG_ON(ptr != DUMMY_PTR);
BUG_ON(sid != id);
}
idr_for_each(idr, idr_u32_cb, NULL);
if (!warned && id > INT_MAX) {
printk("^^^ Warnings over\n");
warned = true;
}
BUG_ON(idr_remove(idr, id) != DUMMY_PTR);
BUG_ON(!idr_is_empty(idr));
}
void idr_u32_test(int base)
{
DEFINE_IDR(idr);
idr_init_base(&idr, base);
idr_u32_test1(&idr, 10);
idr_u32_test1(&idr, 0x7fffffff);
idr_u32_test1(&idr, 0x80000000);
idr_u32_test1(&idr, 0x80000001);
idr_u32_test1(&idr, 0xffe00000);
idr_u32_test1(&idr, 0xffffffff);
}
static inline void *idr_mk_value(unsigned long v)
{
BUG_ON((long)v < 0);
return (void *)((v & 1) | 2 | (v << 1));
}
DEFINE_IDR(find_idr);
static void *idr_throbber(void *arg)
{
time_t start = time(NULL);
int id = *(int *)arg;
rcu_register_thread();
do {
idr_alloc(&find_idr, idr_mk_value(id), id, id + 1, GFP_KERNEL);
idr_remove(&find_idr, id);
} while (time(NULL) < start + 10);
rcu_unregister_thread();
return NULL;
}
void idr_find_test_1(int anchor_id, int throbber_id)
{
pthread_t throbber;
time_t start = time(NULL);
pthread_create(&throbber, NULL, idr_throbber, &throbber_id);
BUG_ON(idr_alloc(&find_idr, idr_mk_value(anchor_id), anchor_id,
anchor_id + 1, GFP_KERNEL) != anchor_id);
do {
int id = 0;
void *entry = idr_get_next(&find_idr, &id);
BUG_ON(entry != idr_mk_value(id));
} while (time(NULL) < start + 11);
pthread_join(throbber, NULL);
idr_remove(&find_idr, anchor_id);
BUG_ON(!idr_is_empty(&find_idr));
}
void idr_find_test(void)
{
idr_find_test_1(100000, 0);
idr_find_test_1(0, 100000);
}
void idr_checks(void)
{
unsigned long i;
DEFINE_IDR(idr);
for (i = 0; i < 10000; i++) {
struct item *item = item_create(i, 0);
assert(idr_alloc(&idr, item, 0, 20000, GFP_KERNEL) == i);
}
assert(idr_alloc(&idr, DUMMY_PTR, 5, 30, GFP_KERNEL) < 0);
for (i = 0; i < 5000; i++)
item_idr_remove(&idr, i);
idr_remove(&idr, 3);
idr_for_each(&idr, item_idr_free, &idr);
idr_destroy(&idr);
assert(idr_is_empty(&idr));
idr_remove(&idr, 3);
idr_remove(&idr, 0);
assert(idr_alloc(&idr, DUMMY_PTR, 0, 0, GFP_KERNEL) == 0);
idr_remove(&idr, 1);
for (i = 1; i < RADIX_TREE_MAP_SIZE; i++)
assert(idr_alloc(&idr, DUMMY_PTR, 0, 0, GFP_KERNEL) == i);
idr_remove(&idr, 1 << 30);
idr_destroy(&idr);
for (i = INT_MAX - 3UL; i < INT_MAX + 1UL; i++) {
struct item *item = item_create(i, 0);
assert(idr_alloc(&idr, item, i, i + 10, GFP_KERNEL) == i);
}
assert(idr_alloc(&idr, DUMMY_PTR, i - 2, i, GFP_KERNEL) == -ENOSPC);
assert(idr_alloc(&idr, DUMMY_PTR, i - 2, i + 10, GFP_KERNEL) == -ENOSPC);
idr_for_each(&idr, item_idr_free, &idr);
idr_destroy(&idr);
idr_destroy(&idr);
assert(idr_is_empty(&idr));
idr_set_cursor(&idr, INT_MAX - 3UL);
for (i = INT_MAX - 3UL; i < INT_MAX + 3UL; i++) {
struct item *item;
unsigned int id;
if (i <= INT_MAX)
item = item_create(i, 0);
else
item = item_create(i - INT_MAX - 1, 0);
id = idr_alloc_cyclic(&idr, item, 0, 0, GFP_KERNEL);
assert(id == item->index);
}
idr_for_each(&idr, item_idr_free, &idr);
idr_destroy(&idr);
assert(idr_is_empty(&idr));
for (i = 1; i < 10000; i++) {
struct item *item = item_create(i, 0);
assert(idr_alloc(&idr, item, 1, 20000, GFP_KERNEL) == i);
}
idr_for_each(&idr, item_idr_free, &idr);
idr_destroy(&idr);
idr_replace_test();
idr_alloc_test();
idr_null_test();
idr_nowait_test();
idr_get_next_test(0);
idr_get_next_test(1);
idr_get_next_test(4);
idr_u32_test(4);
idr_u32_test(1);
idr_u32_test(0);
idr_find_test();
}
#define module_init(x)
#define module_exit(x)
#define MODULE_AUTHOR(x)
#define MODULE_LICENSE(x)
#define dump_stack() assert(0)
void ida_dump(struct ida *);
#include "../../../lib/test_ida.c"
/*
* Check that we get the correct error when we run out of memory doing
* allocations. In userspace, GFP_NOWAIT will always fail an allocation.
* The first test is for not having a bitmap available, and the second test
* is for not being able to allocate a level of the radix tree.
*/
void ida_check_nomem(void)
{
DEFINE_IDA(ida);
int id;
id = ida_alloc_min(&ida, 256, GFP_NOWAIT);
IDA_BUG_ON(&ida, id != -ENOMEM);
id = ida_alloc_min(&ida, 1UL << 30, GFP_NOWAIT);
IDA_BUG_ON(&ida, id != -ENOMEM);
IDA_BUG_ON(&ida, !ida_is_empty(&ida));
}
/*
* Check handling of conversions between exceptional entries and full bitmaps.
*/
void ida_check_conv_user(void)
{
DEFINE_IDA(ida);
unsigned long i;
radix_tree_cpu_dead(1);
for (i = 0; i < 1000000; i++) {
int id = ida_alloc(&ida, GFP_NOWAIT);
if (id == -ENOMEM) {
IDA_BUG_ON(&ida, (i % IDA_BITMAP_BITS) !=
BITS_PER_LONG - 2);
id = ida_alloc(&ida, GFP_KERNEL);
} else {
IDA_BUG_ON(&ida, (i % IDA_BITMAP_BITS) ==
BITS_PER_LONG - 2);
}
IDA_BUG_ON(&ida, id != i);
}
ida_destroy(&ida);
}
void ida_check_random(void)
{
DEFINE_IDA(ida);
DECLARE_BITMAP(bitmap, 2048);
unsigned int i;
time_t s = time(NULL);
repeat:
memset(bitmap, 0, sizeof(bitmap));
for (i = 0; i < 100000; i++) {
int i = rand();
int bit = i & 2047;
if (test_bit(bit, bitmap)) {
__clear_bit(bit, bitmap);
ida_free(&ida, bit);
} else {
__set_bit(bit, bitmap);
IDA_BUG_ON(&ida, ida_alloc_min(&ida, bit, GFP_KERNEL)
!= bit);
}
}
ida_destroy(&ida);
if (time(NULL) < s + 10)
goto repeat;
}
void ida_simple_get_remove_test(void)
{
DEFINE_IDA(ida);
unsigned long i;
for (i = 0; i < 10000; i++) {
assert(ida_simple_get(&ida, 0, 20000, GFP_KERNEL) == i);
}
assert(ida_simple_get(&ida, 5, 30, GFP_KERNEL) < 0);
for (i = 0; i < 10000; i++) {
ida_simple_remove(&ida, i);
}
assert(ida_is_empty(&ida));
ida_destroy(&ida);
}
void user_ida_checks(void)
{
radix_tree_cpu_dead(1);
ida_check_nomem();
ida_check_conv_user();
ida_check_random();
ida_simple_get_remove_test();
radix_tree_cpu_dead(1);
}
static void *ida_random_fn(void *arg)
{
rcu_register_thread();
ida_check_random();
rcu_unregister_thread();
return NULL;
}
void ida_thread_tests(void)
{
pthread_t threads[20];
int i;
for (i = 0; i < ARRAY_SIZE(threads); i++)
if (pthread_create(&threads[i], NULL, ida_random_fn, NULL)) {
perror("creating ida thread");
exit(1);
}
while (i--)
pthread_join(threads[i], NULL);
}
void ida_tests(void)
{
user_ida_checks();
ida_checks();
ida_exit();
ida_thread_tests();
}
int __weak main(void)
{
radix_tree_init();
idr_checks();
ida_tests();
radix_tree_cpu_dead(1);
rcu_barrier();
if (nr_allocated)
printf("nr_allocated = %d\n", nr_allocated);
return 0;
}
+221
View File
@@ -0,0 +1,221 @@
/*
* iteration_check.c: test races having to do with radix tree iteration
* Copyright (c) 2016 Intel Corporation
* Author: Ross Zwisler <ross.zwisler@linux.intel.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*/
#include <linux/radix-tree.h>
#include <pthread.h>
#include "test.h"
#define NUM_THREADS 5
#define MAX_IDX 100
#define TAG 0
#define NEW_TAG 1
static pthread_mutex_t tree_lock = PTHREAD_MUTEX_INITIALIZER;
static pthread_t threads[NUM_THREADS];
static unsigned int seeds[3];
static RADIX_TREE(tree, GFP_KERNEL);
static bool test_complete;
static int max_order;
/* relentlessly fill the tree with tagged entries */
static void *add_entries_fn(void *arg)
{
rcu_register_thread();
while (!test_complete) {
unsigned long pgoff;
int order;
for (pgoff = 0; pgoff < MAX_IDX; pgoff++) {
pthread_mutex_lock(&tree_lock);
for (order = max_order; order >= 0; order--) {
if (item_insert_order(&tree, pgoff, order)
== 0) {
item_tag_set(&tree, pgoff, TAG);
break;
}
}
pthread_mutex_unlock(&tree_lock);
}
}
rcu_unregister_thread();
return NULL;
}
/*
* Iterate over the tagged entries, doing a radix_tree_iter_retry() as we find
* things that have been removed and randomly resetting our iteration to the
* next chunk with radix_tree_iter_resume(). Both radix_tree_iter_retry() and
* radix_tree_iter_resume() cause radix_tree_next_slot() to be called with a
* NULL 'slot' variable.
*/
static void *tagged_iteration_fn(void *arg)
{
struct radix_tree_iter iter;
void **slot;
rcu_register_thread();
while (!test_complete) {
rcu_read_lock();
radix_tree_for_each_tagged(slot, &tree, &iter, 0, TAG) {
void *entry = radix_tree_deref_slot(slot);
if (unlikely(!entry))
continue;
if (radix_tree_deref_retry(entry)) {
slot = radix_tree_iter_retry(&iter);
continue;
}
if (rand_r(&seeds[0]) % 50 == 0) {
slot = radix_tree_iter_resume(slot, &iter);
rcu_read_unlock();
rcu_barrier();
rcu_read_lock();
}
}
rcu_read_unlock();
}
rcu_unregister_thread();
return NULL;
}
/*
* Iterate over the entries, doing a radix_tree_iter_retry() as we find things
* that have been removed and randomly resetting our iteration to the next
* chunk with radix_tree_iter_resume(). Both radix_tree_iter_retry() and
* radix_tree_iter_resume() cause radix_tree_next_slot() to be called with a
* NULL 'slot' variable.
*/
static void *untagged_iteration_fn(void *arg)
{
struct radix_tree_iter iter;
void **slot;
rcu_register_thread();
while (!test_complete) {
rcu_read_lock();
radix_tree_for_each_slot(slot, &tree, &iter, 0) {
void *entry = radix_tree_deref_slot(slot);
if (unlikely(!entry))
continue;
if (radix_tree_deref_retry(entry)) {
slot = radix_tree_iter_retry(&iter);
continue;
}
if (rand_r(&seeds[1]) % 50 == 0) {
slot = radix_tree_iter_resume(slot, &iter);
rcu_read_unlock();
rcu_barrier();
rcu_read_lock();
}
}
rcu_read_unlock();
}
rcu_unregister_thread();
return NULL;
}
/*
* Randomly remove entries to help induce radix_tree_iter_retry() calls in the
* two iteration functions.
*/
static void *remove_entries_fn(void *arg)
{
rcu_register_thread();
while (!test_complete) {
int pgoff;
pgoff = rand_r(&seeds[2]) % MAX_IDX;
pthread_mutex_lock(&tree_lock);
item_delete(&tree, pgoff);
pthread_mutex_unlock(&tree_lock);
}
rcu_unregister_thread();
return NULL;
}
static void *tag_entries_fn(void *arg)
{
rcu_register_thread();
while (!test_complete) {
tag_tagged_items(&tree, &tree_lock, 0, MAX_IDX, 10, TAG,
NEW_TAG);
}
rcu_unregister_thread();
return NULL;
}
/* This is a unit test for a bug found by the syzkaller tester */
void iteration_test(unsigned order, unsigned test_duration)
{
int i;
printv(1, "Running %siteration tests for %d seconds\n",
order > 0 ? "multiorder " : "", test_duration);
max_order = order;
test_complete = false;
for (i = 0; i < 3; i++)
seeds[i] = rand();
if (pthread_create(&threads[0], NULL, tagged_iteration_fn, NULL)) {
perror("create tagged iteration thread");
exit(1);
}
if (pthread_create(&threads[1], NULL, untagged_iteration_fn, NULL)) {
perror("create untagged iteration thread");
exit(1);
}
if (pthread_create(&threads[2], NULL, add_entries_fn, NULL)) {
perror("create add entry thread");
exit(1);
}
if (pthread_create(&threads[3], NULL, remove_entries_fn, NULL)) {
perror("create remove entry thread");
exit(1);
}
if (pthread_create(&threads[4], NULL, tag_entries_fn, NULL)) {
perror("create tag entry thread");
exit(1);
}
sleep(test_duration);
test_complete = true;
for (i = 0; i < NUM_THREADS; i++) {
if (pthread_join(threads[i], NULL)) {
perror("pthread_join");
exit(1);
}
}
item_kill_tree(&tree);
}
+112
View File
@@ -0,0 +1,112 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <pthread.h>
#include <unistd.h>
#include <assert.h>
#include <linux/gfp.h>
#include <linux/poison.h>
#include <linux/slab.h>
#include <linux/radix-tree.h>
#include <urcu/uatomic.h>
int nr_allocated;
int preempt_count;
int kmalloc_verbose;
int test_verbose;
struct kmem_cache {
pthread_mutex_t lock;
int size;
int nr_objs;
void *objs;
void (*ctor)(void *);
};
void *kmem_cache_alloc(struct kmem_cache *cachep, int flags)
{
struct radix_tree_node *node;
if (!(flags & __GFP_DIRECT_RECLAIM))
return NULL;
pthread_mutex_lock(&cachep->lock);
if (cachep->nr_objs) {
cachep->nr_objs--;
node = cachep->objs;
cachep->objs = node->parent;
pthread_mutex_unlock(&cachep->lock);
node->parent = NULL;
} else {
pthread_mutex_unlock(&cachep->lock);
node = malloc(cachep->size);
if (cachep->ctor)
cachep->ctor(node);
}
uatomic_inc(&nr_allocated);
if (kmalloc_verbose)
printf("Allocating %p from slab\n", node);
return node;
}
void kmem_cache_free(struct kmem_cache *cachep, void *objp)
{
assert(objp);
uatomic_dec(&nr_allocated);
if (kmalloc_verbose)
printf("Freeing %p to slab\n", objp);
pthread_mutex_lock(&cachep->lock);
if (cachep->nr_objs > 10) {
memset(objp, POISON_FREE, cachep->size);
free(objp);
} else {
struct radix_tree_node *node = objp;
cachep->nr_objs++;
node->parent = cachep->objs;
cachep->objs = node;
}
pthread_mutex_unlock(&cachep->lock);
}
void *kmalloc(size_t size, gfp_t gfp)
{
void *ret;
if (!(gfp & __GFP_DIRECT_RECLAIM))
return NULL;
ret = malloc(size);
uatomic_inc(&nr_allocated);
if (kmalloc_verbose)
printf("Allocating %p from malloc\n", ret);
if (gfp & __GFP_ZERO)
memset(ret, 0, size);
return ret;
}
void kfree(void *p)
{
if (!p)
return;
uatomic_dec(&nr_allocated);
if (kmalloc_verbose)
printf("Freeing %p to malloc\n", p);
free(p);
}
struct kmem_cache *
kmem_cache_create(const char *name, size_t size, size_t offset,
unsigned long flags, void (*ctor)(void *))
{
struct kmem_cache *ret = malloc(sizeof(*ret));
pthread_mutex_init(&ret->lock, NULL);
ret->size = size;
ret->nr_objs = 0;
ret->objs = NULL;
ret->ctor = ctor;
return ret;
}
+1
View File
@@ -0,0 +1 @@
#include "asm/bug.h"
Whitespace-only changes.
+1
View File
@@ -0,0 +1 @@
#define cpuhp_setup_state_nocalls(a, b, c, d) (0)
+33
View File
@@ -0,0 +1,33 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _GFP_H
#define _GFP_H
#include <linux/types.h>
#define __GFP_BITS_SHIFT 26
#define __GFP_BITS_MASK ((gfp_t)((1 << __GFP_BITS_SHIFT) - 1))
#define __GFP_HIGH 0x20u
#define __GFP_IO 0x40u
#define __GFP_FS 0x80u
#define __GFP_NOWARN 0x200u
#define __GFP_ZERO 0x8000u
#define __GFP_ATOMIC 0x80000u
#define __GFP_ACCOUNT 0x100000u
#define __GFP_DIRECT_RECLAIM 0x400000u
#define __GFP_KSWAPD_RECLAIM 0x2000000u
#define __GFP_RECLAIM (__GFP_DIRECT_RECLAIM|__GFP_KSWAPD_RECLAIM)
#define GFP_ZONEMASK 0x0fu
#define GFP_ATOMIC (__GFP_HIGH|__GFP_ATOMIC|__GFP_KSWAPD_RECLAIM)
#define GFP_KERNEL (__GFP_RECLAIM | __GFP_IO | __GFP_FS)
#define GFP_NOWAIT (__GFP_KSWAPD_RECLAIM)
static inline bool gfpflags_allow_blocking(const gfp_t gfp_flags)
{
return !!(gfp_flags & __GFP_DIRECT_RECLAIM);
}
#endif
+1
View File
@@ -0,0 +1 @@
#include "../../../../include/linux/idr.h"
+1
View File
@@ -0,0 +1 @@
#define __init
+20
View File
@@ -0,0 +1,20 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _KERNEL_H
#define _KERNEL_H
#include "../../include/linux/kernel.h"
#include <string.h>
#include <stdio.h>
#include <limits.h>
#include <linux/compiler.h>
#include <linux/err.h>
#include <linux/bitops.h>
#include <linux/log2.h>
#include "../../../include/linux/kconfig.h"
#define printk printf
#define pr_debug printk
#define pr_cont printk
#endif /* _KERNEL_H */
@@ -0,0 +1 @@
static inline void kmemleak_update_trace(const void *ptr) { }
+11
View File
@@ -0,0 +1,11 @@
/* SPDX-License-Identifier: GPL-2.0 */
#define DECLARE_PER_CPU(type, val) extern type val
#define DEFINE_PER_CPU(type, val) type val
#define __get_cpu_var(var) var
#define this_cpu_ptr(var) var
#define this_cpu_read(var) var
#define this_cpu_xchg(var, val) uatomic_xchg(&var, val)
#define this_cpu_cmpxchg(var, old, new) uatomic_cmpxchg(&var, old, new)
#define per_cpu_ptr(ptr, cpu) ({ (void)(cpu); (ptr); })
#define per_cpu(var, cpu) (*per_cpu_ptr(&(var), cpu))
+15
View File
@@ -0,0 +1,15 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __LINUX_PREEMPT_H
#define __LINUX_PREEMPT_H
extern int preempt_count;
#define preempt_disable() uatomic_inc(&preempt_count)
#define preempt_enable() uatomic_dec(&preempt_count)
static inline int in_interrupt(void)
{
return 0;
}
#endif /* __LINUX_PREEMPT_H */
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _TEST_RADIX_TREE_H
#define _TEST_RADIX_TREE_H
#include "generated/map-shift.h"
#include "../../../../include/linux/radix-tree.h"
extern int kmalloc_verbose;
extern int test_verbose;
static inline void trace_call_rcu(struct rcu_head *head,
void (*func)(struct rcu_head *head))
{
if (kmalloc_verbose)
printf("Delaying free of %p to slab\n", (char *)head -
offsetof(struct radix_tree_node, rcu_head));
call_rcu(head, func);
}
#define printv(verbosity_level, fmt, ...) \
if(test_verbose >= verbosity_level) \
printf(fmt, ##__VA_ARGS__)
#undef call_rcu
#define call_rcu(x, y) trace_call_rcu(x, y)
#endif /* _TEST_RADIX_TREE_H */
+10
View File
@@ -0,0 +1,10 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _RCUPDATE_H
#define _RCUPDATE_H
#include <urcu.h>
#define rcu_dereference_raw(p) rcu_dereference(p)
#define rcu_dereference_protected(p, cond) rcu_dereference(p)
#endif
+27
View File
@@ -0,0 +1,27 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef SLAB_H
#define SLAB_H
#include <linux/types.h>
#include <linux/gfp.h>
#define SLAB_HWCACHE_ALIGN 1
#define SLAB_PANIC 2
#define SLAB_RECLAIM_ACCOUNT 0x00020000UL /* Objects are reclaimable */
void *kmalloc(size_t size, gfp_t);
void kfree(void *);
static inline void *kzalloc(size_t size, gfp_t gfp)
{
return kmalloc(size, gfp | __GFP_ZERO);
}
void *kmem_cache_alloc(struct kmem_cache *cachep, int flags);
void kmem_cache_free(struct kmem_cache *cachep, void *objp);
struct kmem_cache *
kmem_cache_create(const char *name, size_t size, size_t offset,
unsigned long flags, void (*ctor)(void *));
#endif /* SLAB_H */
+2
View File
@@ -0,0 +1,2 @@
#include "generated/map-shift.h"
#include "../../../../include/linux/xarray.h"
+388
View File
@@ -0,0 +1,388 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <assert.h>
#include <limits.h>
#include <linux/slab.h>
#include <linux/radix-tree.h>
#include "test.h"
#include "regression.h"
void __gang_check(unsigned long middle, long down, long up, int chunk, int hop)
{
long idx;
RADIX_TREE(tree, GFP_KERNEL);
middle = 1 << 30;
for (idx = -down; idx < up; idx++)
item_insert(&tree, middle + idx);
item_check_absent(&tree, middle - down - 1);
for (idx = -down; idx < up; idx++)
item_check_present(&tree, middle + idx);
item_check_absent(&tree, middle + up);
if (chunk > 0) {
item_gang_check_present(&tree, middle - down, up + down,
chunk, hop);
item_full_scan(&tree, middle - down, down + up, chunk);
}
item_kill_tree(&tree);
}
void gang_check(void)
{
__gang_check(1UL << 30, 128, 128, 35, 2);
__gang_check(1UL << 31, 128, 128, 32, 32);
__gang_check(1UL << 31, 128, 128, 32, 100);
__gang_check(1UL << 31, 128, 128, 17, 7);
__gang_check(0xffff0000UL, 0, 65536, 17, 7);
__gang_check(0xfffffffeUL, 1, 1, 17, 7);
}
void __big_gang_check(void)
{
unsigned long start;
int wrapped = 0;
start = 0;
do {
unsigned long old_start;
// printf("0x%08lx\n", start);
__gang_check(start, rand() % 113 + 1, rand() % 71,
rand() % 157, rand() % 91 + 1);
old_start = start;
start += rand() % 1000000;
start %= 1ULL << 33;
if (start < old_start)
wrapped = 1;
} while (!wrapped);
}
void big_gang_check(bool long_run)
{
int i;
for (i = 0; i < (long_run ? 1000 : 3); i++) {
__big_gang_check();
printv(2, "%d ", i);
fflush(stdout);
}
}
void add_and_check(void)
{
RADIX_TREE(tree, GFP_KERNEL);
item_insert(&tree, 44);
item_check_present(&tree, 44);
item_check_absent(&tree, 43);
item_kill_tree(&tree);
}
void dynamic_height_check(void)
{
int i;
RADIX_TREE(tree, GFP_KERNEL);
tree_verify_min_height(&tree, 0);
item_insert(&tree, 42);
tree_verify_min_height(&tree, 42);
item_insert(&tree, 1000000);
tree_verify_min_height(&tree, 1000000);
assert(item_delete(&tree, 1000000));
tree_verify_min_height(&tree, 42);
assert(item_delete(&tree, 42));
tree_verify_min_height(&tree, 0);
for (i = 0; i < 1000; i++) {
item_insert(&tree, i);
tree_verify_min_height(&tree, i);
}
i--;
for (;;) {
assert(item_delete(&tree, i));
if (i == 0) {
tree_verify_min_height(&tree, 0);
break;
}
i--;
tree_verify_min_height(&tree, i);
}
item_kill_tree(&tree);
}
void check_copied_tags(struct radix_tree_root *tree, unsigned long start, unsigned long end, unsigned long *idx, int count, int fromtag, int totag)
{
int i;
for (i = 0; i < count; i++) {
/* if (i % 1000 == 0)
putchar('.'); */
if (idx[i] < start || idx[i] > end) {
if (item_tag_get(tree, idx[i], totag)) {
printv(2, "%lu-%lu: %lu, tags %d-%d\n", start,
end, idx[i], item_tag_get(tree, idx[i],
fromtag),
item_tag_get(tree, idx[i], totag));
}
assert(!item_tag_get(tree, idx[i], totag));
continue;
}
if (item_tag_get(tree, idx[i], fromtag) ^
item_tag_get(tree, idx[i], totag)) {
printv(2, "%lu-%lu: %lu, tags %d-%d\n", start, end,
idx[i], item_tag_get(tree, idx[i], fromtag),
item_tag_get(tree, idx[i], totag));
}
assert(!(item_tag_get(tree, idx[i], fromtag) ^
item_tag_get(tree, idx[i], totag)));
}
}
#define ITEMS 50000
void copy_tag_check(void)
{
RADIX_TREE(tree, GFP_KERNEL);
unsigned long idx[ITEMS];
unsigned long start, end, count = 0, tagged, cur, tmp;
int i;
// printf("generating radix tree indices...\n");
start = rand();
end = rand();
if (start > end && (rand() % 10)) {
cur = start;
start = end;
end = cur;
}
/* Specifically create items around the start and the end of the range
* with high probability to check for off by one errors */
cur = rand();
if (cur & 1) {
item_insert(&tree, start);
if (cur & 2) {
if (start <= end)
count++;
item_tag_set(&tree, start, 0);
}
}
if (cur & 4) {
item_insert(&tree, start-1);
if (cur & 8)
item_tag_set(&tree, start-1, 0);
}
if (cur & 16) {
item_insert(&tree, end);
if (cur & 32) {
if (start <= end)
count++;
item_tag_set(&tree, end, 0);
}
}
if (cur & 64) {
item_insert(&tree, end+1);
if (cur & 128)
item_tag_set(&tree, end+1, 0);
}
for (i = 0; i < ITEMS; i++) {
do {
idx[i] = rand();
} while (item_lookup(&tree, idx[i]));
item_insert(&tree, idx[i]);
if (rand() & 1) {
item_tag_set(&tree, idx[i], 0);
if (idx[i] >= start && idx[i] <= end)
count++;
}
/* if (i % 1000 == 0)
putchar('.'); */
}
// printf("\ncopying tags...\n");
tagged = tag_tagged_items(&tree, NULL, start, end, ITEMS, 0, 1);
// printf("checking copied tags\n");
assert(tagged == count);
check_copied_tags(&tree, start, end, idx, ITEMS, 0, 1);
/* Copy tags in several rounds */
// printf("\ncopying tags...\n");
tmp = rand() % (count / 10 + 2);
tagged = tag_tagged_items(&tree, NULL, start, end, tmp, 0, 2);
assert(tagged == count);
// printf("%lu %lu %lu\n", tagged, tmp, count);
// printf("checking copied tags\n");
check_copied_tags(&tree, start, end, idx, ITEMS, 0, 2);
verify_tag_consistency(&tree, 0);
verify_tag_consistency(&tree, 1);
verify_tag_consistency(&tree, 2);
// printf("\n");
item_kill_tree(&tree);
}
static void __locate_check(struct radix_tree_root *tree, unsigned long index,
unsigned order)
{
struct item *item;
unsigned long index2;
item_insert_order(tree, index, order);
item = item_lookup(tree, index);
index2 = find_item(tree, item);
if (index != index2) {
printv(2, "index %ld order %d inserted; found %ld\n",
index, order, index2);
abort();
}
}
static void __order_0_locate_check(void)
{
RADIX_TREE(tree, GFP_KERNEL);
int i;
for (i = 0; i < 50; i++)
__locate_check(&tree, rand() % INT_MAX, 0);
item_kill_tree(&tree);
}
static void locate_check(void)
{
RADIX_TREE(tree, GFP_KERNEL);
unsigned order;
unsigned long offset, index;
__order_0_locate_check();
for (order = 0; order < 20; order++) {
for (offset = 0; offset < (1 << (order + 3));
offset += (1UL << order)) {
for (index = 0; index < (1UL << (order + 5));
index += (1UL << order)) {
__locate_check(&tree, index + offset, order);
}
if (find_item(&tree, &tree) != -1)
abort();
item_kill_tree(&tree);
}
}
if (find_item(&tree, &tree) != -1)
abort();
__locate_check(&tree, -1, 0);
if (find_item(&tree, &tree) != -1)
abort();
item_kill_tree(&tree);
}
static void single_thread_tests(bool long_run)
{
int i;
printv(1, "starting single_thread_tests: %d allocated, preempt %d\n",
nr_allocated, preempt_count);
multiorder_checks();
rcu_barrier();
printv(2, "after multiorder_check: %d allocated, preempt %d\n",
nr_allocated, preempt_count);
locate_check();
rcu_barrier();
printv(2, "after locate_check: %d allocated, preempt %d\n",
nr_allocated, preempt_count);
tag_check();
rcu_barrier();
printv(2, "after tag_check: %d allocated, preempt %d\n",
nr_allocated, preempt_count);
gang_check();
rcu_barrier();
printv(2, "after gang_check: %d allocated, preempt %d\n",
nr_allocated, preempt_count);
add_and_check();
rcu_barrier();
printv(2, "after add_and_check: %d allocated, preempt %d\n",
nr_allocated, preempt_count);
dynamic_height_check();
rcu_barrier();
printv(2, "after dynamic_height_check: %d allocated, preempt %d\n",
nr_allocated, preempt_count);
idr_checks();
ida_tests();
rcu_barrier();
printv(2, "after idr_checks: %d allocated, preempt %d\n",
nr_allocated, preempt_count);
big_gang_check(long_run);
rcu_barrier();
printv(2, "after big_gang_check: %d allocated, preempt %d\n",
nr_allocated, preempt_count);
for (i = 0; i < (long_run ? 2000 : 3); i++) {
copy_tag_check();
printv(2, "%d ", i);
fflush(stdout);
}
rcu_barrier();
printv(2, "after copy_tag_check: %d allocated, preempt %d\n",
nr_allocated, preempt_count);
}
int main(int argc, char **argv)
{
bool long_run = false;
int opt;
unsigned int seed = time(NULL);
while ((opt = getopt(argc, argv, "ls:v")) != -1) {
if (opt == 'l')
long_run = true;
else if (opt == 's')
seed = strtoul(optarg, NULL, 0);
else if (opt == 'v')
test_verbose++;
}
printf("random seed %u\n", seed);
srand(seed);
printf("running tests\n");
rcu_register_thread();
radix_tree_init();
regression1_test();
regression2_test();
regression3_test();
iteration_test(0, 10 + 90 * long_run);
iteration_test(7, 10 + 90 * long_run);
single_thread_tests(long_run);
/* Free any remaining preallocated nodes */
radix_tree_cpu_dead(0);
benchmark();
rcu_barrier();
printv(2, "after rcu_barrier: %d allocated, preempt %d\n",
nr_allocated, preempt_count);
rcu_unregister_thread();
printf("tests completed\n");
exit(0);
}
+719
View File
@@ -0,0 +1,719 @@
/*
* multiorder.c: Multi-order radix tree entry testing
* Copyright (c) 2016 Intel Corporation
* Author: Ross Zwisler <ross.zwisler@linux.intel.com>
* Author: Matthew Wilcox <matthew.r.wilcox@intel.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*/
#include <linux/radix-tree.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <pthread.h>
#include "test.h"
#define for_each_index(i, base, order) \
for (i = base; i < base + (1 << order); i++)
static void __multiorder_tag_test(int index, int order)
{
RADIX_TREE(tree, GFP_KERNEL);
int base, err, i;
/* our canonical entry */
base = index & ~((1 << order) - 1);
printv(2, "Multiorder tag test with index %d, canonical entry %d\n",
index, base);
err = item_insert_order(&tree, index, order);
assert(!err);
/*
* Verify we get collisions for covered indices. We try and fail to
* insert an exceptional entry so we don't leak memory via
* item_insert_order().
*/
for_each_index(i, base, order) {
err = __radix_tree_insert(&tree, i, order,
(void *)(0xA0 | RADIX_TREE_EXCEPTIONAL_ENTRY));
assert(err == -EEXIST);
}
for_each_index(i, base, order) {
assert(!radix_tree_tag_get(&tree, i, 0));
assert(!radix_tree_tag_get(&tree, i, 1));
}
assert(radix_tree_tag_set(&tree, index, 0));
for_each_index(i, base, order) {
assert(radix_tree_tag_get(&tree, i, 0));
assert(!radix_tree_tag_get(&tree, i, 1));
}
assert(tag_tagged_items(&tree, NULL, 0, ~0UL, 10, 0, 1) == 1);
assert(radix_tree_tag_clear(&tree, index, 0));
for_each_index(i, base, order) {
assert(!radix_tree_tag_get(&tree, i, 0));
assert(radix_tree_tag_get(&tree, i, 1));
}
assert(radix_tree_tag_clear(&tree, index, 1));
assert(!radix_tree_tagged(&tree, 0));
assert(!radix_tree_tagged(&tree, 1));
item_kill_tree(&tree);
}
static void __multiorder_tag_test2(unsigned order, unsigned long index2)
{
RADIX_TREE(tree, GFP_KERNEL);
unsigned long index = (1 << order);
index2 += index;
assert(item_insert_order(&tree, 0, order) == 0);
assert(item_insert(&tree, index2) == 0);
assert(radix_tree_tag_set(&tree, 0, 0));
assert(radix_tree_tag_set(&tree, index2, 0));
assert(tag_tagged_items(&tree, NULL, 0, ~0UL, 10, 0, 1) == 2);
item_kill_tree(&tree);
}
static void multiorder_tag_tests(void)
{
int i, j;
/* test multi-order entry for indices 0-7 with no sibling pointers */
__multiorder_tag_test(0, 3);
__multiorder_tag_test(5, 3);
/* test multi-order entry for indices 8-15 with no sibling pointers */
__multiorder_tag_test(8, 3);
__multiorder_tag_test(15, 3);
/*
* Our order 5 entry covers indices 0-31 in a tree with height=2.
* This is broken up as follows:
* 0-7: canonical entry
* 8-15: sibling 1
* 16-23: sibling 2
* 24-31: sibling 3
*/
__multiorder_tag_test(0, 5);
__multiorder_tag_test(29, 5);
/* same test, but with indices 32-63 */
__multiorder_tag_test(32, 5);
__multiorder_tag_test(44, 5);
/*
* Our order 8 entry covers indices 0-255 in a tree with height=3.
* This is broken up as follows:
* 0-63: canonical entry
* 64-127: sibling 1
* 128-191: sibling 2
* 192-255: sibling 3
*/
__multiorder_tag_test(0, 8);
__multiorder_tag_test(190, 8);
/* same test, but with indices 256-511 */
__multiorder_tag_test(256, 8);
__multiorder_tag_test(300, 8);
__multiorder_tag_test(0x12345678UL, 8);
for (i = 1; i < 10; i++)
for (j = 0; j < (10 << i); j++)
__multiorder_tag_test2(i, j);
}
static void multiorder_check(unsigned long index, int order)
{
unsigned long i;
unsigned long min = index & ~((1UL << order) - 1);
unsigned long max = min + (1UL << order);
void **slot;
struct item *item2 = item_create(min, order);
RADIX_TREE(tree, GFP_KERNEL);
printv(2, "Multiorder index %ld, order %d\n", index, order);
assert(item_insert_order(&tree, index, order) == 0);
for (i = min; i < max; i++) {
struct item *item = item_lookup(&tree, i);
assert(item != 0);
assert(item->index == index);
}
for (i = 0; i < min; i++)
item_check_absent(&tree, i);
for (i = max; i < 2*max; i++)
item_check_absent(&tree, i);
for (i = min; i < max; i++)
assert(radix_tree_insert(&tree, i, item2) == -EEXIST);
slot = radix_tree_lookup_slot(&tree, index);
free(*slot);
radix_tree_replace_slot(&tree, slot, item2);
for (i = min; i < max; i++) {
struct item *item = item_lookup(&tree, i);
assert(item != 0);
assert(item->index == min);
}
assert(item_delete(&tree, min) != 0);
for (i = 0; i < 2*max; i++)
item_check_absent(&tree, i);
}
static void multiorder_shrink(unsigned long index, int order)
{
unsigned long i;
unsigned long max = 1 << order;
RADIX_TREE(tree, GFP_KERNEL);
struct radix_tree_node *node;
printv(2, "Multiorder shrink index %ld, order %d\n", index, order);
assert(item_insert_order(&tree, 0, order) == 0);
node = tree.rnode;
assert(item_insert(&tree, index) == 0);
assert(node != tree.rnode);
assert(item_delete(&tree, index) != 0);
assert(node == tree.rnode);
for (i = 0; i < max; i++) {
struct item *item = item_lookup(&tree, i);
assert(item != 0);
assert(item->index == 0);
}
for (i = max; i < 2*max; i++)
item_check_absent(&tree, i);
if (!item_delete(&tree, 0)) {
printv(2, "failed to delete index %ld (order %d)\n", index, order);
abort();
}
for (i = 0; i < 2*max; i++)
item_check_absent(&tree, i);
}
static void multiorder_insert_bug(void)
{
RADIX_TREE(tree, GFP_KERNEL);
item_insert(&tree, 0);
radix_tree_tag_set(&tree, 0, 0);
item_insert_order(&tree, 3 << 6, 6);
item_kill_tree(&tree);
}
void multiorder_iteration(void)
{
RADIX_TREE(tree, GFP_KERNEL);
struct radix_tree_iter iter;
void **slot;
int i, j, err;
printv(1, "Multiorder iteration test\n");
#define NUM_ENTRIES 11
int index[NUM_ENTRIES] = {0, 2, 4, 8, 16, 32, 34, 36, 64, 72, 128};
int order[NUM_ENTRIES] = {1, 1, 2, 3, 4, 1, 0, 1, 3, 0, 7};
for (i = 0; i < NUM_ENTRIES; i++) {
err = item_insert_order(&tree, index[i], order[i]);
assert(!err);
}
for (j = 0; j < 256; j++) {
for (i = 0; i < NUM_ENTRIES; i++)
if (j <= (index[i] | ((1 << order[i]) - 1)))
break;
radix_tree_for_each_slot(slot, &tree, &iter, j) {
int height = order[i] / RADIX_TREE_MAP_SHIFT;
int shift = height * RADIX_TREE_MAP_SHIFT;
unsigned long mask = (1UL << order[i]) - 1;
struct item *item = *slot;
assert((iter.index | mask) == (index[i] | mask));
assert(iter.shift == shift);
assert(!radix_tree_is_internal_node(item));
assert((item->index | mask) == (index[i] | mask));
assert(item->order == order[i]);
i++;
}
}
item_kill_tree(&tree);
}
void multiorder_tagged_iteration(void)
{
RADIX_TREE(tree, GFP_KERNEL);
struct radix_tree_iter iter;
void **slot;
int i, j;
printv(1, "Multiorder tagged iteration test\n");
#define MT_NUM_ENTRIES 9
int index[MT_NUM_ENTRIES] = {0, 2, 4, 16, 32, 40, 64, 72, 128};
int order[MT_NUM_ENTRIES] = {1, 0, 2, 4, 3, 1, 3, 0, 7};
#define TAG_ENTRIES 7
int tag_index[TAG_ENTRIES] = {0, 4, 16, 40, 64, 72, 128};
for (i = 0; i < MT_NUM_ENTRIES; i++)
assert(!item_insert_order(&tree, index[i], order[i]));
assert(!radix_tree_tagged(&tree, 1));
for (i = 0; i < TAG_ENTRIES; i++)
assert(radix_tree_tag_set(&tree, tag_index[i], 1));
for (j = 0; j < 256; j++) {
int k;
for (i = 0; i < TAG_ENTRIES; i++) {
for (k = i; index[k] < tag_index[i]; k++)
;
if (j <= (index[k] | ((1 << order[k]) - 1)))
break;
}
radix_tree_for_each_tagged(slot, &tree, &iter, j, 1) {
unsigned long mask;
struct item *item = *slot;
for (k = i; index[k] < tag_index[i]; k++)
;
mask = (1UL << order[k]) - 1;
assert((iter.index | mask) == (tag_index[i] | mask));
assert(!radix_tree_is_internal_node(item));
assert((item->index | mask) == (tag_index[i] | mask));
assert(item->order == order[k]);
i++;
}
}
assert(tag_tagged_items(&tree, NULL, 0, ~0UL, TAG_ENTRIES, 1, 2) ==
TAG_ENTRIES);
for (j = 0; j < 256; j++) {
int mask, k;
for (i = 0; i < TAG_ENTRIES; i++) {
for (k = i; index[k] < tag_index[i]; k++)
;
if (j <= (index[k] | ((1 << order[k]) - 1)))
break;
}
radix_tree_for_each_tagged(slot, &tree, &iter, j, 2) {
struct item *item = *slot;
for (k = i; index[k] < tag_index[i]; k++)
;
mask = (1 << order[k]) - 1;
assert((iter.index | mask) == (tag_index[i] | mask));
assert(!radix_tree_is_internal_node(item));
assert((item->index | mask) == (tag_index[i] | mask));
assert(item->order == order[k]);
i++;
}
}
assert(tag_tagged_items(&tree, NULL, 1, ~0UL, MT_NUM_ENTRIES * 2, 1, 0)
== TAG_ENTRIES);
i = 0;
radix_tree_for_each_tagged(slot, &tree, &iter, 0, 0) {
assert(iter.index == tag_index[i]);
i++;
}
item_kill_tree(&tree);
}
/*
* Basic join checks: make sure we can't find an entry in the tree after
* a larger entry has replaced it
*/
static void multiorder_join1(unsigned long index,
unsigned order1, unsigned order2)
{
unsigned long loc;
void *item, *item2 = item_create(index + 1, order1);
RADIX_TREE(tree, GFP_KERNEL);
item_insert_order(&tree, index, order2);
item = radix_tree_lookup(&tree, index);
radix_tree_join(&tree, index + 1, order1, item2);
loc = find_item(&tree, item);
if (loc == -1)
free(item);
item = radix_tree_lookup(&tree, index + 1);
assert(item == item2);
item_kill_tree(&tree);
}
/*
* Check that the accounting of exceptional entries is handled correctly
* by joining an exceptional entry to a normal pointer.
*/
static void multiorder_join2(unsigned order1, unsigned order2)
{
RADIX_TREE(tree, GFP_KERNEL);
struct radix_tree_node *node;
void *item1 = item_create(0, order1);
void *item2;
item_insert_order(&tree, 0, order2);
radix_tree_insert(&tree, 1 << order2, (void *)0x12UL);
item2 = __radix_tree_lookup(&tree, 1 << order2, &node, NULL);
assert(item2 == (void *)0x12UL);
assert(node->exceptional == 1);
item2 = radix_tree_lookup(&tree, 0);
free(item2);
radix_tree_join(&tree, 0, order1, item1);
item2 = __radix_tree_lookup(&tree, 1 << order2, &node, NULL);
assert(item2 == item1);
assert(node->exceptional == 0);
item_kill_tree(&tree);
}
/*
* This test revealed an accounting bug for exceptional entries at one point.
* Nodes were being freed back into the pool with an elevated exception count
* by radix_tree_join() and then radix_tree_split() was failing to zero the
* count of exceptional entries.
*/
static void multiorder_join3(unsigned int order)
{
RADIX_TREE(tree, GFP_KERNEL);
struct radix_tree_node *node;
void **slot;
struct radix_tree_iter iter;
unsigned long i;
for (i = 0; i < (1 << order); i++) {
radix_tree_insert(&tree, i, (void *)0x12UL);
}
radix_tree_join(&tree, 0, order, (void *)0x16UL);
rcu_barrier();
radix_tree_split(&tree, 0, 0);
radix_tree_for_each_slot(slot, &tree, &iter, 0) {
radix_tree_iter_replace(&tree, &iter, slot, (void *)0x12UL);
}
__radix_tree_lookup(&tree, 0, &node, NULL);
assert(node->exceptional == node->count);
item_kill_tree(&tree);
}
static void multiorder_join(void)
{
int i, j, idx;
for (idx = 0; idx < 1024; idx = idx * 2 + 3) {
for (i = 1; i < 15; i++) {
for (j = 0; j < i; j++) {
multiorder_join1(idx, i, j);
}
}
}
for (i = 1; i < 15; i++) {
for (j = 0; j < i; j++) {
multiorder_join2(i, j);
}
}
for (i = 3; i < 10; i++) {
multiorder_join3(i);
}
}
static void check_mem(unsigned old_order, unsigned new_order, unsigned alloc)
{
struct radix_tree_preload *rtp = &radix_tree_preloads;
if (rtp->nr != 0)
printv(2, "split(%u %u) remaining %u\n", old_order, new_order,
rtp->nr);
/*
* Can't check for equality here as some nodes may have been
* RCU-freed while we ran. But we should never finish with more
* nodes allocated since they should have all been preloaded.
*/
if (nr_allocated > alloc)
printv(2, "split(%u %u) allocated %u %u\n", old_order, new_order,
alloc, nr_allocated);
}
static void __multiorder_split(int old_order, int new_order)
{
RADIX_TREE(tree, GFP_ATOMIC);
void **slot;
struct radix_tree_iter iter;
unsigned alloc;
struct item *item;
radix_tree_preload(GFP_KERNEL);
assert(item_insert_order(&tree, 0, old_order) == 0);
radix_tree_preload_end();
/* Wipe out the preloaded cache or it'll confuse check_mem() */
radix_tree_cpu_dead(0);
item = radix_tree_tag_set(&tree, 0, 2);
radix_tree_split_preload(old_order, new_order, GFP_KERNEL);
alloc = nr_allocated;
radix_tree_split(&tree, 0, new_order);
check_mem(old_order, new_order, alloc);
radix_tree_for_each_slot(slot, &tree, &iter, 0) {
radix_tree_iter_replace(&tree, &iter, slot,
item_create(iter.index, new_order));
}
radix_tree_preload_end();
item_kill_tree(&tree);
free(item);
}
static void __multiorder_split2(int old_order, int new_order)
{
RADIX_TREE(tree, GFP_KERNEL);
void **slot;
struct radix_tree_iter iter;
struct radix_tree_node *node;
void *item;
__radix_tree_insert(&tree, 0, old_order, (void *)0x12);
item = __radix_tree_lookup(&tree, 0, &node, NULL);
assert(item == (void *)0x12);
assert(node->exceptional > 0);
radix_tree_split(&tree, 0, new_order);
radix_tree_for_each_slot(slot, &tree, &iter, 0) {
radix_tree_iter_replace(&tree, &iter, slot,
item_create(iter.index, new_order));
}
item = __radix_tree_lookup(&tree, 0, &node, NULL);
assert(item != (void *)0x12);
assert(node->exceptional == 0);
item_kill_tree(&tree);
}
static void __multiorder_split3(int old_order, int new_order)
{
RADIX_TREE(tree, GFP_KERNEL);
void **slot;
struct radix_tree_iter iter;
struct radix_tree_node *node;
void *item;
__radix_tree_insert(&tree, 0, old_order, (void *)0x12);
item = __radix_tree_lookup(&tree, 0, &node, NULL);
assert(item == (void *)0x12);
assert(node->exceptional > 0);
radix_tree_split(&tree, 0, new_order);
radix_tree_for_each_slot(slot, &tree, &iter, 0) {
radix_tree_iter_replace(&tree, &iter, slot, (void *)0x16);
}
item = __radix_tree_lookup(&tree, 0, &node, NULL);
assert(item == (void *)0x16);
assert(node->exceptional > 0);
item_kill_tree(&tree);
__radix_tree_insert(&tree, 0, old_order, (void *)0x12);
item = __radix_tree_lookup(&tree, 0, &node, NULL);
assert(item == (void *)0x12);
assert(node->exceptional > 0);
radix_tree_split(&tree, 0, new_order);
radix_tree_for_each_slot(slot, &tree, &iter, 0) {
if (iter.index == (1 << new_order))
radix_tree_iter_replace(&tree, &iter, slot,
(void *)0x16);
else
radix_tree_iter_replace(&tree, &iter, slot, NULL);
}
item = __radix_tree_lookup(&tree, 1 << new_order, &node, NULL);
assert(item == (void *)0x16);
assert(node->count == node->exceptional);
do {
node = node->parent;
if (!node)
break;
assert(node->count == 1);
assert(node->exceptional == 0);
} while (1);
item_kill_tree(&tree);
}
static void multiorder_split(void)
{
int i, j;
for (i = 3; i < 11; i++)
for (j = 0; j < i; j++) {
__multiorder_split(i, j);
__multiorder_split2(i, j);
__multiorder_split3(i, j);
}
}
static void multiorder_account(void)
{
RADIX_TREE(tree, GFP_KERNEL);
struct radix_tree_node *node;
void **slot;
item_insert_order(&tree, 0, 5);
__radix_tree_insert(&tree, 1 << 5, 5, (void *)0x12);
__radix_tree_lookup(&tree, 0, &node, NULL);
assert(node->count == node->exceptional * 2);
radix_tree_delete(&tree, 1 << 5);
assert(node->exceptional == 0);
__radix_tree_insert(&tree, 1 << 5, 5, (void *)0x12);
__radix_tree_lookup(&tree, 1 << 5, &node, &slot);
assert(node->count == node->exceptional * 2);
__radix_tree_replace(&tree, node, slot, NULL, NULL);
assert(node->exceptional == 0);
item_kill_tree(&tree);
}
bool stop_iteration = false;
static void *creator_func(void *ptr)
{
/* 'order' is set up to ensure we have sibling entries */
unsigned int order = RADIX_TREE_MAP_SHIFT - 1;
struct radix_tree_root *tree = ptr;
int i;
for (i = 0; i < 10000; i++) {
item_insert_order(tree, 0, order);
item_delete_rcu(tree, 0);
}
stop_iteration = true;
return NULL;
}
static void *iterator_func(void *ptr)
{
struct radix_tree_root *tree = ptr;
struct radix_tree_iter iter;
struct item *item;
void **slot;
while (!stop_iteration) {
rcu_read_lock();
radix_tree_for_each_slot(slot, tree, &iter, 0) {
item = radix_tree_deref_slot(slot);
if (!item)
continue;
if (radix_tree_deref_retry(item)) {
slot = radix_tree_iter_retry(&iter);
continue;
}
item_sanity(item, iter.index);
}
rcu_read_unlock();
}
return NULL;
}
static void multiorder_iteration_race(void)
{
const int num_threads = sysconf(_SC_NPROCESSORS_ONLN);
pthread_t worker_thread[num_threads];
RADIX_TREE(tree, GFP_KERNEL);
int i;
pthread_create(&worker_thread[0], NULL, &creator_func, &tree);
for (i = 1; i < num_threads; i++)
pthread_create(&worker_thread[i], NULL, &iterator_func, &tree);
for (i = 0; i < num_threads; i++)
pthread_join(worker_thread[i], NULL);
item_kill_tree(&tree);
}
void multiorder_checks(void)
{
int i;
for (i = 0; i < 20; i++) {
multiorder_check(200, i);
multiorder_check(0, i);
multiorder_check((1UL << i) + 1, i);
}
for (i = 0; i < 15; i++)
multiorder_shrink((1UL << (i + RADIX_TREE_MAP_SHIFT)), i);
multiorder_insert_bug();
multiorder_tag_tests();
multiorder_iteration();
multiorder_tagged_iteration();
multiorder_join();
multiorder_split();
multiorder_account();
multiorder_iteration_race();
radix_tree_cpu_dead(0);
}
int __weak main(void)
{
radix_tree_init();
multiorder_checks();
return 0;
}
+9
View File
@@ -0,0 +1,9 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __REGRESSION_H__
#define __REGRESSION_H__
void regression1_test(void);
void regression2_test(void);
void regression3_test(void);
#endif
+221
View File
@@ -0,0 +1,221 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Regression1
* Description:
* Salman Qazi describes the following radix-tree bug:
*
* In the following case, we get can get a deadlock:
*
* 0. The radix tree contains two items, one has the index 0.
* 1. The reader (in this case find_get_pages) takes the rcu_read_lock.
* 2. The reader acquires slot(s) for item(s) including the index 0 item.
* 3. The non-zero index item is deleted, and as a consequence the other item
* is moved to the root of the tree. The place where it used to be is queued
* for deletion after the readers finish.
* 3b. The zero item is deleted, removing it from the direct slot, it remains in
* the rcu-delayed indirect node.
* 4. The reader looks at the index 0 slot, and finds that the page has 0 ref
* count
* 5. The reader looks at it again, hoping that the item will either be freed
* or the ref count will increase. This never happens, as the slot it is
* looking at will never be updated. Also, this slot can never be reclaimed
* because the reader is holding rcu_read_lock and is in an infinite loop.
*
* The fix is to re-use the same "indirect" pointer case that requires a slot
* lookup retry into a general "retry the lookup" bit.
*
* Running:
* This test should run to completion in a few seconds. The above bug would
* cause it to hang indefinitely.
*
* Upstream commit:
* Not yet
*/
#include <linux/kernel.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <linux/radix-tree.h>
#include <linux/rcupdate.h>
#include <stdlib.h>
#include <pthread.h>
#include <stdio.h>
#include <assert.h>
#include "regression.h"
static RADIX_TREE(mt_tree, GFP_KERNEL);
static pthread_mutex_t mt_lock = PTHREAD_MUTEX_INITIALIZER;
struct page {
pthread_mutex_t lock;
struct rcu_head rcu;
int count;
unsigned long index;
};
static struct page *page_alloc(void)
{
struct page *p;
p = malloc(sizeof(struct page));
p->count = 1;
p->index = 1;
pthread_mutex_init(&p->lock, NULL);
return p;
}
static void page_rcu_free(struct rcu_head *rcu)
{
struct page *p = container_of(rcu, struct page, rcu);
assert(!p->count);
pthread_mutex_destroy(&p->lock);
free(p);
}
static void page_free(struct page *p)
{
call_rcu(&p->rcu, page_rcu_free);
}
static unsigned find_get_pages(unsigned long start,
unsigned int nr_pages, struct page **pages)
{
unsigned int i;
unsigned int ret;
unsigned int nr_found;
rcu_read_lock();
restart:
nr_found = radix_tree_gang_lookup_slot(&mt_tree,
(void ***)pages, NULL, start, nr_pages);
ret = 0;
for (i = 0; i < nr_found; i++) {
struct page *page;
repeat:
page = radix_tree_deref_slot((void **)pages[i]);
if (unlikely(!page))
continue;
if (radix_tree_exception(page)) {
if (radix_tree_deref_retry(page)) {
/*
* Transient condition which can only trigger
* when entry at index 0 moves out of or back
* to root: none yet gotten, safe to restart.
*/
assert((start | i) == 0);
goto restart;
}
/*
* No exceptional entries are inserted in this test.
*/
assert(0);
}
pthread_mutex_lock(&page->lock);
if (!page->count) {
pthread_mutex_unlock(&page->lock);
goto repeat;
}
/* don't actually update page refcount */
pthread_mutex_unlock(&page->lock);
/* Has the page moved? */
if (unlikely(page != *((void **)pages[i]))) {
goto repeat;
}
pages[ret] = page;
ret++;
}
rcu_read_unlock();
return ret;
}
static pthread_barrier_t worker_barrier;
static void *regression1_fn(void *arg)
{
rcu_register_thread();
if (pthread_barrier_wait(&worker_barrier) ==
PTHREAD_BARRIER_SERIAL_THREAD) {
int j;
for (j = 0; j < 1000000; j++) {
struct page *p;
p = page_alloc();
pthread_mutex_lock(&mt_lock);
radix_tree_insert(&mt_tree, 0, p);
pthread_mutex_unlock(&mt_lock);
p = page_alloc();
pthread_mutex_lock(&mt_lock);
radix_tree_insert(&mt_tree, 1, p);
pthread_mutex_unlock(&mt_lock);
pthread_mutex_lock(&mt_lock);
p = radix_tree_delete(&mt_tree, 1);
pthread_mutex_lock(&p->lock);
p->count--;
pthread_mutex_unlock(&p->lock);
pthread_mutex_unlock(&mt_lock);
page_free(p);
pthread_mutex_lock(&mt_lock);
p = radix_tree_delete(&mt_tree, 0);
pthread_mutex_lock(&p->lock);
p->count--;
pthread_mutex_unlock(&p->lock);
pthread_mutex_unlock(&mt_lock);
page_free(p);
}
} else {
int j;
for (j = 0; j < 100000000; j++) {
struct page *pages[10];
find_get_pages(0, 10, pages);
}
}
rcu_unregister_thread();
return NULL;
}
static pthread_t *threads;
void regression1_test(void)
{
int nr_threads;
int i;
long arg;
/* Regression #1 */
printv(1, "running regression test 1, should finish in under a minute\n");
nr_threads = 2;
pthread_barrier_init(&worker_barrier, NULL, nr_threads);
threads = malloc(nr_threads * sizeof(pthread_t *));
for (i = 0; i < nr_threads; i++) {
arg = i;
if (pthread_create(&threads[i], NULL, regression1_fn, (void *)arg)) {
perror("pthread_create");
exit(1);
}
}
for (i = 0; i < nr_threads; i++) {
if (pthread_join(threads[i], NULL)) {
perror("pthread_join");
exit(1);
}
}
free(threads);
printv(1, "regression test 1, done\n");
}
+123
View File
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Regression2
* Description:
* Toshiyuki Okajima describes the following radix-tree bug:
*
* In the following case, we can get a hangup on
* radix_radix_tree_gang_lookup_tag_slot.
*
* 0. The radix tree contains RADIX_TREE_MAP_SIZE items. And the tag of
* a certain item has PAGECACHE_TAG_DIRTY.
* 1. radix_tree_range_tag_if_tagged(, start, end, , PAGECACHE_TAG_DIRTY,
* PAGECACHE_TAG_TOWRITE) is called to add PAGECACHE_TAG_TOWRITE tag
* for the tag which has PAGECACHE_TAG_DIRTY. However, there is no tag with
* PAGECACHE_TAG_DIRTY within the range from start to end. As the result,
* There is no tag with PAGECACHE_TAG_TOWRITE but the root tag has
* PAGECACHE_TAG_TOWRITE.
* 2. An item is added into the radix tree and then the level of it is
* extended into 2 from 1. At that time, the new radix tree node succeeds
* the tag status of the root tag. Therefore the tag of the new radix tree
* node has PAGECACHE_TAG_TOWRITE but there is not slot with
* PAGECACHE_TAG_TOWRITE tag in the child node of the new radix tree node.
* 3. The tag of a certain item is cleared with PAGECACHE_TAG_DIRTY.
* 4. All items within the index range from 0 to RADIX_TREE_MAP_SIZE - 1 are
* released. (Only the item which index is RADIX_TREE_MAP_SIZE exist in the
* radix tree.) As the result, the slot of the radix tree node is NULL but
* the tag which corresponds to the slot has PAGECACHE_TAG_TOWRITE.
* 5. radix_tree_gang_lookup_tag_slot(PAGECACHE_TAG_TOWRITE) calls
* __lookup_tag. __lookup_tag returns with 0. And __lookup_tag doesn't
* change the index that is the input and output parameter. Because the 1st
* slot of the radix tree node is NULL, but the tag which corresponds to
* the slot has PAGECACHE_TAG_TOWRITE.
* Therefore radix_tree_gang_lookup_tag_slot tries to get some items by
* calling __lookup_tag, but it cannot get any items forever.
*
* The fix is to change that radix_tree_tag_if_tagged doesn't tag the root tag
* if it doesn't set any tags within the specified range.
*
* Running:
* This test should run to completion immediately. The above bug would cause it
* to hang indefinitely.
*
* Upstream commit:
* Not yet
*/
#include <linux/kernel.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <linux/radix-tree.h>
#include <stdlib.h>
#include <stdio.h>
#include "regression.h"
#include "test.h"
#define PAGECACHE_TAG_DIRTY 0
#define PAGECACHE_TAG_WRITEBACK 1
#define PAGECACHE_TAG_TOWRITE 2
static RADIX_TREE(mt_tree, GFP_KERNEL);
unsigned long page_count = 0;
struct page {
unsigned long index;
};
static struct page *page_alloc(void)
{
struct page *p;
p = malloc(sizeof(struct page));
p->index = page_count++;
return p;
}
void regression2_test(void)
{
int i;
struct page *p;
int max_slots = RADIX_TREE_MAP_SIZE;
unsigned long int start, end;
struct page *pages[1];
printv(1, "running regression test 2 (should take milliseconds)\n");
/* 0. */
for (i = 0; i <= max_slots - 1; i++) {
p = page_alloc();
radix_tree_insert(&mt_tree, i, p);
}
radix_tree_tag_set(&mt_tree, max_slots - 1, PAGECACHE_TAG_DIRTY);
/* 1. */
start = 0;
end = max_slots - 2;
tag_tagged_items(&mt_tree, NULL, start, end, 1,
PAGECACHE_TAG_DIRTY, PAGECACHE_TAG_TOWRITE);
/* 2. */
p = page_alloc();
radix_tree_insert(&mt_tree, max_slots, p);
/* 3. */
radix_tree_tag_clear(&mt_tree, max_slots - 1, PAGECACHE_TAG_DIRTY);
/* 4. */
for (i = max_slots - 1; i >= 0; i--)
free(radix_tree_delete(&mt_tree, i));
/* 5. */
// NOTE: start should not be 0 because radix_tree_gang_lookup_tag_slot
// can return.
start = 1;
end = max_slots - 2;
radix_tree_gang_lookup_tag_slot(&mt_tree, (void ***)pages, start, end,
PAGECACHE_TAG_TOWRITE);
/* We remove all the remained nodes */
free(radix_tree_delete(&mt_tree, max_slots));
BUG_ON(!radix_tree_empty(&mt_tree));
printv(1, "regression test 2, done\n");
}
+118
View File
@@ -0,0 +1,118 @@
// SPDX-License-Identifier: GPL-2.0
/*
* Regression3
* Description:
* Helper radix_tree_iter_retry resets next_index to the current index.
* In following radix_tree_next_slot current chunk size becomes zero.
* This isn't checked and it tries to dereference null pointer in slot.
*
* Helper radix_tree_iter_resume reset slot to NULL and next_index to index + 1,
* for tagger iteraction it also must reset cached tags in iterator to abort
* next radix_tree_next_slot and go to slow-path into radix_tree_next_chunk.
*
* Running:
* This test should run to completion immediately. The above bug would
* cause it to segfault.
*
* Upstream commit:
* Not yet
*/
#include <linux/kernel.h>
#include <linux/gfp.h>
#include <linux/slab.h>
#include <linux/radix-tree.h>
#include <stdlib.h>
#include <stdio.h>
#include "regression.h"
void regression3_test(void)
{
RADIX_TREE(root, GFP_KERNEL);
void *ptr0 = (void *)4ul;
void *ptr = (void *)8ul;
struct radix_tree_iter iter;
void **slot;
bool first;
printv(1, "running regression test 3 (should take milliseconds)\n");
radix_tree_insert(&root, 0, ptr0);
radix_tree_tag_set(&root, 0, 0);
first = true;
radix_tree_for_each_tagged(slot, &root, &iter, 0, 0) {
printv(2, "tagged %ld %p\n", iter.index, *slot);
if (first) {
radix_tree_insert(&root, 1, ptr);
radix_tree_tag_set(&root, 1, 0);
first = false;
}
if (radix_tree_deref_retry(*slot)) {
printv(2, "retry at %ld\n", iter.index);
slot = radix_tree_iter_retry(&iter);
continue;
}
}
radix_tree_delete(&root, 1);
first = true;
radix_tree_for_each_slot(slot, &root, &iter, 0) {
printv(2, "slot %ld %p\n", iter.index, *slot);
if (first) {
radix_tree_insert(&root, 1, ptr);
first = false;
}
if (radix_tree_deref_retry(*slot)) {
printv(2, "retry at %ld\n", iter.index);
slot = radix_tree_iter_retry(&iter);
continue;
}
}
radix_tree_delete(&root, 1);
first = true;
radix_tree_for_each_contig(slot, &root, &iter, 0) {
printv(2, "contig %ld %p\n", iter.index, *slot);
if (first) {
radix_tree_insert(&root, 1, ptr);
first = false;
}
if (radix_tree_deref_retry(*slot)) {
printv(2, "retry at %ld\n", iter.index);
slot = radix_tree_iter_retry(&iter);
continue;
}
}
radix_tree_for_each_slot(slot, &root, &iter, 0) {
printv(2, "slot %ld %p\n", iter.index, *slot);
if (!iter.index) {
printv(2, "next at %ld\n", iter.index);
slot = radix_tree_iter_resume(slot, &iter);
}
}
radix_tree_for_each_contig(slot, &root, &iter, 0) {
printv(2, "contig %ld %p\n", iter.index, *slot);
if (!iter.index) {
printv(2, "next at %ld\n", iter.index);
slot = radix_tree_iter_resume(slot, &iter);
}
}
radix_tree_tag_set(&root, 0, 0);
radix_tree_tag_set(&root, 1, 0);
radix_tree_for_each_tagged(slot, &root, &iter, 0, 0) {
printv(2, "tagged %ld %p\n", iter.index, *slot);
if (!iter.index) {
printv(2, "next at %ld\n", iter.index);
slot = radix_tree_iter_resume(slot, &iter);
}
}
radix_tree_delete(&root, 0);
radix_tree_delete(&root, 1);
printv(1, "regression test 3 passed\n");
}
+380
View File
@@ -0,0 +1,380 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <linux/slab.h>
#include <linux/radix-tree.h>
#include "test.h"
static void
__simple_checks(struct radix_tree_root *tree, unsigned long index, int tag)
{
unsigned long first = 0;
int ret;
item_check_absent(tree, index);
assert(item_tag_get(tree, index, tag) == 0);
item_insert(tree, index);
assert(item_tag_get(tree, index, tag) == 0);
item_tag_set(tree, index, tag);
ret = item_tag_get(tree, index, tag);
assert(ret != 0);
ret = tag_tagged_items(tree, NULL, first, ~0UL, 10, tag, !tag);
assert(ret == 1);
ret = item_tag_get(tree, index, !tag);
assert(ret != 0);
ret = item_delete(tree, index);
assert(ret != 0);
item_insert(tree, index);
ret = item_tag_get(tree, index, tag);
assert(ret == 0);
ret = item_delete(tree, index);
assert(ret != 0);
ret = item_delete(tree, index);
assert(ret == 0);
}
void simple_checks(void)
{
unsigned long index;
RADIX_TREE(tree, GFP_KERNEL);
for (index = 0; index < 10000; index++) {
__simple_checks(&tree, index, 0);
__simple_checks(&tree, index, 1);
}
verify_tag_consistency(&tree, 0);
verify_tag_consistency(&tree, 1);
printv(2, "before item_kill_tree: %d allocated\n", nr_allocated);
item_kill_tree(&tree);
rcu_barrier();
printv(2, "after item_kill_tree: %d allocated\n", nr_allocated);
}
/*
* Check that tags propagate correctly when extending a tree.
*/
static void extend_checks(void)
{
RADIX_TREE(tree, GFP_KERNEL);
item_insert(&tree, 43);
assert(item_tag_get(&tree, 43, 0) == 0);
item_tag_set(&tree, 43, 0);
assert(item_tag_get(&tree, 43, 0) == 1);
item_insert(&tree, 1000000);
assert(item_tag_get(&tree, 43, 0) == 1);
item_insert(&tree, 0);
item_tag_set(&tree, 0, 0);
item_delete(&tree, 1000000);
assert(item_tag_get(&tree, 43, 0) != 0);
item_delete(&tree, 43);
assert(item_tag_get(&tree, 43, 0) == 0); /* crash */
assert(item_tag_get(&tree, 0, 0) == 1);
verify_tag_consistency(&tree, 0);
item_kill_tree(&tree);
}
/*
* Check that tags propagate correctly when contracting a tree.
*/
static void contract_checks(void)
{
struct item *item;
int tmp;
RADIX_TREE(tree, GFP_KERNEL);
tmp = 1<<RADIX_TREE_MAP_SHIFT;
item_insert(&tree, tmp);
item_insert(&tree, tmp+1);
item_tag_set(&tree, tmp, 0);
item_tag_set(&tree, tmp, 1);
item_tag_set(&tree, tmp+1, 0);
item_delete(&tree, tmp+1);
item_tag_clear(&tree, tmp, 1);
assert(radix_tree_gang_lookup_tag(&tree, (void **)&item, 0, 1, 0) == 1);
assert(radix_tree_gang_lookup_tag(&tree, (void **)&item, 0, 1, 1) == 0);
assert(item_tag_get(&tree, tmp, 0) == 1);
assert(item_tag_get(&tree, tmp, 1) == 0);
verify_tag_consistency(&tree, 0);
item_kill_tree(&tree);
}
/*
* Stupid tag thrasher
*
* Create a large linear array corresponding to the tree. Each element in
* the array is coherent with each node in the tree
*/
enum {
NODE_ABSENT = 0,
NODE_PRESENT = 1,
NODE_TAGGED = 2,
};
#define THRASH_SIZE (1000 * 1000)
#define N 127
#define BATCH 33
static void gang_check(struct radix_tree_root *tree,
char *thrash_state, int tag)
{
struct item *items[BATCH];
int nr_found;
unsigned long index = 0;
unsigned long last_index = 0;
while ((nr_found = radix_tree_gang_lookup_tag(tree, (void **)items,
index, BATCH, tag))) {
int i;
for (i = 0; i < nr_found; i++) {
struct item *item = items[i];
while (last_index < item->index) {
assert(thrash_state[last_index] != NODE_TAGGED);
last_index++;
}
assert(thrash_state[last_index] == NODE_TAGGED);
last_index++;
}
index = items[nr_found - 1]->index + 1;
}
}
static void do_thrash(struct radix_tree_root *tree, char *thrash_state, int tag)
{
int insert_chunk;
int delete_chunk;
int tag_chunk;
int untag_chunk;
int total_tagged = 0;
int total_present = 0;
for (insert_chunk = 1; insert_chunk < THRASH_SIZE; insert_chunk *= N)
for (delete_chunk = 1; delete_chunk < THRASH_SIZE; delete_chunk *= N)
for (tag_chunk = 1; tag_chunk < THRASH_SIZE; tag_chunk *= N)
for (untag_chunk = 1; untag_chunk < THRASH_SIZE; untag_chunk *= N) {
int i;
unsigned long index;
int nr_inserted = 0;
int nr_deleted = 0;
int nr_tagged = 0;
int nr_untagged = 0;
int actual_total_tagged;
int actual_total_present;
for (i = 0; i < insert_chunk; i++) {
index = rand() % THRASH_SIZE;
if (thrash_state[index] != NODE_ABSENT)
continue;
item_check_absent(tree, index);
item_insert(tree, index);
assert(thrash_state[index] != NODE_PRESENT);
thrash_state[index] = NODE_PRESENT;
nr_inserted++;
total_present++;
}
for (i = 0; i < delete_chunk; i++) {
index = rand() % THRASH_SIZE;
if (thrash_state[index] == NODE_ABSENT)
continue;
item_check_present(tree, index);
if (item_tag_get(tree, index, tag)) {
assert(thrash_state[index] == NODE_TAGGED);
total_tagged--;
} else {
assert(thrash_state[index] == NODE_PRESENT);
}
item_delete(tree, index);
assert(thrash_state[index] != NODE_ABSENT);
thrash_state[index] = NODE_ABSENT;
nr_deleted++;
total_present--;
}
for (i = 0; i < tag_chunk; i++) {
index = rand() % THRASH_SIZE;
if (thrash_state[index] != NODE_PRESENT) {
if (item_lookup(tree, index))
assert(item_tag_get(tree, index, tag));
continue;
}
item_tag_set(tree, index, tag);
item_tag_set(tree, index, tag);
assert(thrash_state[index] != NODE_TAGGED);
thrash_state[index] = NODE_TAGGED;
nr_tagged++;
total_tagged++;
}
for (i = 0; i < untag_chunk; i++) {
index = rand() % THRASH_SIZE;
if (thrash_state[index] != NODE_TAGGED)
continue;
item_check_present(tree, index);
assert(item_tag_get(tree, index, tag));
item_tag_clear(tree, index, tag);
item_tag_clear(tree, index, tag);
assert(thrash_state[index] != NODE_PRESENT);
thrash_state[index] = NODE_PRESENT;
nr_untagged++;
total_tagged--;
}
actual_total_tagged = 0;
actual_total_present = 0;
for (index = 0; index < THRASH_SIZE; index++) {
switch (thrash_state[index]) {
case NODE_ABSENT:
item_check_absent(tree, index);
break;
case NODE_PRESENT:
item_check_present(tree, index);
assert(!item_tag_get(tree, index, tag));
actual_total_present++;
break;
case NODE_TAGGED:
item_check_present(tree, index);
assert(item_tag_get(tree, index, tag));
actual_total_present++;
actual_total_tagged++;
break;
}
}
gang_check(tree, thrash_state, tag);
printv(2, "%d(%d) %d(%d) %d(%d) %d(%d) / "
"%d(%d) present, %d(%d) tagged\n",
insert_chunk, nr_inserted,
delete_chunk, nr_deleted,
tag_chunk, nr_tagged,
untag_chunk, nr_untagged,
total_present, actual_total_present,
total_tagged, actual_total_tagged);
}
}
static void thrash_tags(void)
{
RADIX_TREE(tree, GFP_KERNEL);
char *thrash_state;
thrash_state = malloc(THRASH_SIZE);
memset(thrash_state, 0, THRASH_SIZE);
do_thrash(&tree, thrash_state, 0);
verify_tag_consistency(&tree, 0);
item_kill_tree(&tree);
free(thrash_state);
}
static void leak_check(void)
{
RADIX_TREE(tree, GFP_KERNEL);
item_insert(&tree, 1000000);
item_delete(&tree, 1000000);
item_kill_tree(&tree);
}
static void __leak_check(void)
{
RADIX_TREE(tree, GFP_KERNEL);
printv(2, "%d: nr_allocated=%d\n", __LINE__, nr_allocated);
item_insert(&tree, 1000000);
printv(2, "%d: nr_allocated=%d\n", __LINE__, nr_allocated);
item_delete(&tree, 1000000);
printv(2, "%d: nr_allocated=%d\n", __LINE__, nr_allocated);
item_kill_tree(&tree);
printv(2, "%d: nr_allocated=%d\n", __LINE__, nr_allocated);
}
static void single_check(void)
{
struct item *items[BATCH];
RADIX_TREE(tree, GFP_KERNEL);
int ret;
unsigned long first = 0;
item_insert(&tree, 0);
item_tag_set(&tree, 0, 0);
ret = radix_tree_gang_lookup_tag(&tree, (void **)items, 0, BATCH, 0);
assert(ret == 1);
ret = radix_tree_gang_lookup_tag(&tree, (void **)items, 1, BATCH, 0);
assert(ret == 0);
verify_tag_consistency(&tree, 0);
verify_tag_consistency(&tree, 1);
ret = tag_tagged_items(&tree, NULL, first, 10, 10, 0, 1);
assert(ret == 1);
ret = radix_tree_gang_lookup_tag(&tree, (void **)items, 0, BATCH, 1);
assert(ret == 1);
item_tag_clear(&tree, 0, 0);
ret = radix_tree_gang_lookup_tag(&tree, (void **)items, 0, BATCH, 0);
assert(ret == 0);
item_kill_tree(&tree);
}
void radix_tree_clear_tags_test(void)
{
unsigned long index;
struct radix_tree_node *node;
struct radix_tree_iter iter;
void **slot;
RADIX_TREE(tree, GFP_KERNEL);
item_insert(&tree, 0);
item_tag_set(&tree, 0, 0);
__radix_tree_lookup(&tree, 0, &node, &slot);
radix_tree_clear_tags(&tree, node, slot);
assert(item_tag_get(&tree, 0, 0) == 0);
for (index = 0; index < 1000; index++) {
item_insert(&tree, index);
item_tag_set(&tree, index, 0);
}
radix_tree_for_each_slot(slot, &tree, &iter, 0) {
radix_tree_clear_tags(&tree, iter.node, slot);
assert(item_tag_get(&tree, iter.index, 0) == 0);
}
item_kill_tree(&tree);
}
void tag_check(void)
{
single_check();
extend_checks();
contract_checks();
rcu_barrier();
printv(2, "after extend_checks: %d allocated\n", nr_allocated);
__leak_check();
leak_check();
rcu_barrier();
printv(2, "after leak_check: %d allocated\n", nr_allocated);
simple_checks();
rcu_barrier();
printv(2, "after simple_checks: %d allocated\n", nr_allocated);
thrash_tags();
rcu_barrier();
printv(2, "after thrash_tags: %d allocated\n", nr_allocated);
radix_tree_clear_tags_test();
}
+334
View File
@@ -0,0 +1,334 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/bitops.h>
#include "test.h"
struct item *
item_tag_set(struct radix_tree_root *root, unsigned long index, int tag)
{
return radix_tree_tag_set(root, index, tag);
}
struct item *
item_tag_clear(struct radix_tree_root *root, unsigned long index, int tag)
{
return radix_tree_tag_clear(root, index, tag);
}
int item_tag_get(struct radix_tree_root *root, unsigned long index, int tag)
{
return radix_tree_tag_get(root, index, tag);
}
int __item_insert(struct radix_tree_root *root, struct item *item)
{
return __radix_tree_insert(root, item->index, item->order, item);
}
struct item *item_create(unsigned long index, unsigned int order)
{
struct item *ret = malloc(sizeof(*ret));
ret->index = index;
ret->order = order;
return ret;
}
int item_insert_order(struct radix_tree_root *root, unsigned long index,
unsigned order)
{
struct item *item = item_create(index, order);
int err = __item_insert(root, item);
if (err)
free(item);
return err;
}
int item_insert(struct radix_tree_root *root, unsigned long index)
{
return item_insert_order(root, index, 0);
}
void item_sanity(struct item *item, unsigned long index)
{
unsigned long mask;
assert(!radix_tree_is_internal_node(item));
assert(item->order < BITS_PER_LONG);
mask = (1UL << item->order) - 1;
assert((item->index | mask) == (index | mask));
}
int item_delete(struct radix_tree_root *root, unsigned long index)
{
struct item *item = radix_tree_delete(root, index);
if (item) {
item_sanity(item, index);
free(item);
return 1;
}
return 0;
}
static void item_free_rcu(struct rcu_head *head)
{
struct item *item = container_of(head, struct item, rcu_head);
free(item);
}
int item_delete_rcu(struct radix_tree_root *root, unsigned long index)
{
struct item *item = radix_tree_delete(root, index);
if (item) {
item_sanity(item, index);
call_rcu(&item->rcu_head, item_free_rcu);
return 1;
}
return 0;
}
void item_check_present(struct radix_tree_root *root, unsigned long index)
{
struct item *item;
item = radix_tree_lookup(root, index);
assert(item != NULL);
item_sanity(item, index);
}
struct item *item_lookup(struct radix_tree_root *root, unsigned long index)
{
return radix_tree_lookup(root, index);
}
void item_check_absent(struct radix_tree_root *root, unsigned long index)
{
struct item *item;
item = radix_tree_lookup(root, index);
assert(item == NULL);
}
/*
* Scan only the passed (start, start+nr] for present items
*/
void item_gang_check_present(struct radix_tree_root *root,
unsigned long start, unsigned long nr,
int chunk, int hop)
{
struct item *items[chunk];
unsigned long into;
for (into = 0; into < nr; ) {
int nfound;
int nr_to_find = chunk;
int i;
if (nr_to_find > (nr - into))
nr_to_find = nr - into;
nfound = radix_tree_gang_lookup(root, (void **)items,
start + into, nr_to_find);
assert(nfound == nr_to_find);
for (i = 0; i < nfound; i++)
assert(items[i]->index == start + into + i);
into += hop;
}
}
/*
* Scan the entire tree, only expecting present items (start, start+nr]
*/
void item_full_scan(struct radix_tree_root *root, unsigned long start,
unsigned long nr, int chunk)
{
struct item *items[chunk];
unsigned long into = 0;
unsigned long this_index = start;
int nfound;
int i;
// printf("%s(0x%08lx, 0x%08lx, %d)\n", __FUNCTION__, start, nr, chunk);
while ((nfound = radix_tree_gang_lookup(root, (void **)items, into,
chunk))) {
// printf("At 0x%08lx, nfound=%d\n", into, nfound);
for (i = 0; i < nfound; i++) {
assert(items[i]->index == this_index);
this_index++;
}
// printf("Found 0x%08lx->0x%08lx\n",
// items[0]->index, items[nfound-1]->index);
into = this_index;
}
if (chunk)
assert(this_index == start + nr);
nfound = radix_tree_gang_lookup(root, (void **)items,
this_index, chunk);
assert(nfound == 0);
}
/* Use the same pattern as tag_pages_for_writeback() in mm/page-writeback.c */
int tag_tagged_items(struct radix_tree_root *root, pthread_mutex_t *lock,
unsigned long start, unsigned long end, unsigned batch,
unsigned iftag, unsigned thentag)
{
unsigned long tagged = 0;
struct radix_tree_iter iter;
void **slot;
if (batch == 0)
batch = 1;
if (lock)
pthread_mutex_lock(lock);
radix_tree_for_each_tagged(slot, root, &iter, start, iftag) {
if (iter.index > end)
break;
radix_tree_iter_tag_set(root, &iter, thentag);
tagged++;
if ((tagged % batch) != 0)
continue;
slot = radix_tree_iter_resume(slot, &iter);
if (lock) {
pthread_mutex_unlock(lock);
rcu_barrier();
pthread_mutex_lock(lock);
}
}
if (lock)
pthread_mutex_unlock(lock);
return tagged;
}
/* Use the same pattern as find_swap_entry() in mm/shmem.c */
unsigned long find_item(struct radix_tree_root *root, void *item)
{
struct radix_tree_iter iter;
void **slot;
unsigned long found = -1;
unsigned long checked = 0;
radix_tree_for_each_slot(slot, root, &iter, 0) {
if (*slot == item) {
found = iter.index;
break;
}
checked++;
if ((checked % 4) != 0)
continue;
slot = radix_tree_iter_resume(slot, &iter);
}
return found;
}
static int verify_node(struct radix_tree_node *slot, unsigned int tag,
int tagged)
{
int anyset = 0;
int i;
int j;
slot = entry_to_node(slot);
/* Verify consistency at this level */
for (i = 0; i < RADIX_TREE_TAG_LONGS; i++) {
if (slot->tags[tag][i]) {
anyset = 1;
break;
}
}
if (tagged != anyset) {
printf("tag: %u, shift %u, tagged: %d, anyset: %d\n",
tag, slot->shift, tagged, anyset);
for (j = 0; j < RADIX_TREE_MAX_TAGS; j++) {
printf("tag %d: ", j);
for (i = 0; i < RADIX_TREE_TAG_LONGS; i++)
printf("%016lx ", slot->tags[j][i]);
printf("\n");
}
return 1;
}
assert(tagged == anyset);
/* Go for next level */
if (slot->shift > 0) {
for (i = 0; i < RADIX_TREE_MAP_SIZE; i++)
if (slot->slots[i])
if (verify_node(slot->slots[i], tag,
!!test_bit(i, slot->tags[tag]))) {
printf("Failure at off %d\n", i);
for (j = 0; j < RADIX_TREE_MAX_TAGS; j++) {
printf("tag %d: ", j);
for (i = 0; i < RADIX_TREE_TAG_LONGS; i++)
printf("%016lx ", slot->tags[j][i]);
printf("\n");
}
return 1;
}
}
return 0;
}
void verify_tag_consistency(struct radix_tree_root *root, unsigned int tag)
{
struct radix_tree_node *node = root->rnode;
if (!radix_tree_is_internal_node(node))
return;
verify_node(node, tag, !!root_tag_get(root, tag));
}
void item_kill_tree(struct radix_tree_root *root)
{
struct radix_tree_iter iter;
void **slot;
struct item *items[32];
int nfound;
radix_tree_for_each_slot(slot, root, &iter, 0) {
if (radix_tree_exceptional_entry(*slot))
radix_tree_delete(root, iter.index);
}
while ((nfound = radix_tree_gang_lookup(root, (void **)items, 0, 32))) {
int i;
for (i = 0; i < nfound; i++) {
void *ret;
ret = radix_tree_delete(root, items[i]->index);
assert(ret == items[i]);
free(items[i]);
}
}
assert(radix_tree_gang_lookup(root, (void **)items, 0, 32) == 0);
assert(root->rnode == NULL);
}
void tree_verify_min_height(struct radix_tree_root *root, int maxindex)
{
unsigned shift;
struct radix_tree_node *node = root->rnode;
if (!radix_tree_is_internal_node(node)) {
assert(maxindex == 0);
return;
}
node = entry_to_node(node);
assert(maxindex <= node_maxindex(node));
shift = node->shift;
if (shift > 0)
assert(maxindex > shift_maxindex(shift - RADIX_TREE_MAP_SHIFT));
else
assert(maxindex > 0);
}
+65
View File
@@ -0,0 +1,65 @@
/* SPDX-License-Identifier: GPL-2.0 */
#include <linux/gfp.h>
#include <linux/types.h>
#include <linux/radix-tree.h>
#include <linux/rcupdate.h>
struct item {
struct rcu_head rcu_head;
unsigned long index;
unsigned int order;
};
struct item *item_create(unsigned long index, unsigned int order);
int __item_insert(struct radix_tree_root *root, struct item *item);
int item_insert(struct radix_tree_root *root, unsigned long index);
void item_sanity(struct item *item, unsigned long index);
int item_insert_order(struct radix_tree_root *root, unsigned long index,
unsigned order);
int item_delete(struct radix_tree_root *root, unsigned long index);
int item_delete_rcu(struct radix_tree_root *root, unsigned long index);
struct item *item_lookup(struct radix_tree_root *root, unsigned long index);
void item_check_present(struct radix_tree_root *root, unsigned long index);
void item_check_absent(struct radix_tree_root *root, unsigned long index);
void item_gang_check_present(struct radix_tree_root *root,
unsigned long start, unsigned long nr,
int chunk, int hop);
void item_full_scan(struct radix_tree_root *root, unsigned long start,
unsigned long nr, int chunk);
void item_kill_tree(struct radix_tree_root *root);
int tag_tagged_items(struct radix_tree_root *, pthread_mutex_t *,
unsigned long start, unsigned long end, unsigned batch,
unsigned iftag, unsigned thentag);
unsigned long find_item(struct radix_tree_root *, void *item);
void tag_check(void);
void multiorder_checks(void);
void iteration_test(unsigned order, unsigned duration);
void benchmark(void);
void idr_checks(void);
void ida_tests(void);
struct item *
item_tag_set(struct radix_tree_root *root, unsigned long index, int tag);
struct item *
item_tag_clear(struct radix_tree_root *root, unsigned long index, int tag);
int item_tag_get(struct radix_tree_root *root, unsigned long index, int tag);
void tree_verify_min_height(struct radix_tree_root *root, int maxindex);
void verify_tag_consistency(struct radix_tree_root *root, unsigned int tag);
extern int nr_allocated;
/* Normally private parts of lib/radix-tree.c */
struct radix_tree_node *entry_to_node(void *ptr);
void radix_tree_dump(struct radix_tree_root *root);
int root_tag_get(struct radix_tree_root *root, unsigned int tag);
unsigned long node_maxindex(struct radix_tree_node *);
unsigned long shift_maxindex(unsigned int shift);
int radix_tree_cpu_dead(unsigned int cpu);
struct radix_tree_preload {
unsigned nr;
struct radix_tree_node *nodes;
};
extern struct radix_tree_preload radix_tree_preloads;
+30
View File
@@ -0,0 +1,30 @@
CFLAGS += -I. -I../../include -g -O2 -Wall -fsanitize=address
LDFLAGS += -fsanitize=address -fsanitize=undefined
TARGETS = main
OFILES = main.o scatterlist.o
ifeq ($(BUILD), 32)
CFLAGS += -m32
LDFLAGS += -m32
endif
targets: include $(TARGETS)
main: $(OFILES)
clean:
$(RM) $(TARGETS) $(OFILES) scatterlist.c linux/scatterlist.h linux/highmem.h linux/kmemleak.h asm/io.h
@rmdir asm
scatterlist.c: ../../../lib/scatterlist.c
@sed -e 's/^static //' -e 's/__always_inline //' -e 's/inline //' < $< > $@
.PHONY: include
include: ../../../include/linux/scatterlist.h
@mkdir -p linux
@mkdir -p asm
@touch asm/io.h
@touch linux/highmem.h
@touch linux/kmemleak.h
@cp $< linux/scatterlist.h
+125
View File
@@ -0,0 +1,125 @@
#ifndef _LINUX_MM_H
#define _LINUX_MM_H
#include <assert.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <limits.h>
#include <stdio.h>
typedef unsigned long dma_addr_t;
#define unlikely
#define BUG_ON(x) assert(!(x))
#define WARN_ON(condition) ({ \
int __ret_warn_on = !!(condition); \
unlikely(__ret_warn_on); \
})
#define WARN_ON_ONCE(condition) ({ \
int __ret_warn_on = !!(condition); \
if (unlikely(__ret_warn_on)) \
assert(0); \
unlikely(__ret_warn_on); \
})
#define PAGE_SIZE (4096)
#define PAGE_SHIFT (12)
#define PAGE_MASK (~(PAGE_SIZE-1))
#define __ALIGN_KERNEL(x, a) __ALIGN_KERNEL_MASK(x, (typeof(x))(a) - 1)
#define __ALIGN_KERNEL_MASK(x, mask) (((x) + (mask)) & ~(mask))
#define ALIGN(x, a) __ALIGN_KERNEL((x), (a))
#define PAGE_ALIGN(addr) ALIGN(addr, PAGE_SIZE)
#define offset_in_page(p) ((unsigned long)(p) & ~PAGE_MASK)
#define virt_to_page(x) ((void *)x)
#define page_address(x) ((void *)x)
static inline unsigned long page_to_phys(struct page *page)
{
assert(0);
return 0;
}
#define page_to_pfn(page) ((unsigned long)(page) / PAGE_SIZE)
#define pfn_to_page(pfn) (void *)((pfn) * PAGE_SIZE)
#define nth_page(page,n) pfn_to_page(page_to_pfn((page)) + (n))
#define __min(t1, t2, min1, min2, x, y) ({ \
t1 min1 = (x); \
t2 min2 = (y); \
(void) (&min1 == &min2); \
min1 < min2 ? min1 : min2; })
#define ___PASTE(a,b) a##b
#define __PASTE(a,b) ___PASTE(a,b)
#define __UNIQUE_ID(prefix) __PASTE(__PASTE(__UNIQUE_ID_, prefix), __COUNTER__)
#define min(x, y) \
__min(typeof(x), typeof(y), \
__UNIQUE_ID(min1_), __UNIQUE_ID(min2_), \
x, y)
#define min_t(type, x, y) \
__min(type, type, \
__UNIQUE_ID(min1_), __UNIQUE_ID(min2_), \
x, y)
#define preemptible() (1)
static inline void *kmap(struct page *page)
{
assert(0);
return NULL;
}
static inline void *kmap_atomic(struct page *page)
{
assert(0);
return NULL;
}
static inline void kunmap(void *addr)
{
assert(0);
}
static inline void kunmap_atomic(void *addr)
{
assert(0);
}
static inline unsigned long __get_free_page(unsigned int flags)
{
return (unsigned long)malloc(PAGE_SIZE);
}
static inline void free_page(unsigned long page)
{
free((void *)page);
}
static inline void *kmalloc(unsigned int size, unsigned int flags)
{
return malloc(size);
}
#define kfree(x) free(x)
#define kmemleak_alloc(a, b, c, d)
#define kmemleak_free(a)
#define PageSlab(p) (0)
#define flush_kernel_dcache_page(p)
#endif
+79
View File
@@ -0,0 +1,79 @@
#include <stdio.h>
#include <assert.h>
#include <linux/scatterlist.h>
#define MAX_PAGES (64)
static void set_pages(struct page **pages, const unsigned *array, unsigned num)
{
unsigned int i;
assert(num < MAX_PAGES);
for (i = 0; i < num; i++)
pages[i] = (struct page *)(unsigned long)
((1 + array[i]) * PAGE_SIZE);
}
#define pfn(...) (unsigned []){ __VA_ARGS__ }
int main(void)
{
const unsigned int sgmax = SCATTERLIST_MAX_SEGMENT;
struct test {
int alloc_ret;
unsigned num_pages;
unsigned *pfn;
unsigned size;
unsigned int max_seg;
unsigned int expected_segments;
} *test, tests[] = {
{ -EINVAL, 1, pfn(0), PAGE_SIZE, PAGE_SIZE + 1, 1 },
{ -EINVAL, 1, pfn(0), PAGE_SIZE, 0, 1 },
{ -EINVAL, 1, pfn(0), PAGE_SIZE, sgmax + 1, 1 },
{ 0, 1, pfn(0), PAGE_SIZE, sgmax, 1 },
{ 0, 1, pfn(0), 1, sgmax, 1 },
{ 0, 2, pfn(0, 1), 2 * PAGE_SIZE, sgmax, 1 },
{ 0, 2, pfn(1, 0), 2 * PAGE_SIZE, sgmax, 2 },
{ 0, 3, pfn(0, 1, 2), 3 * PAGE_SIZE, sgmax, 1 },
{ 0, 3, pfn(0, 2, 1), 3 * PAGE_SIZE, sgmax, 3 },
{ 0, 3, pfn(0, 1, 3), 3 * PAGE_SIZE, sgmax, 2 },
{ 0, 3, pfn(1, 2, 4), 3 * PAGE_SIZE, sgmax, 2 },
{ 0, 3, pfn(1, 3, 4), 3 * PAGE_SIZE, sgmax, 2 },
{ 0, 4, pfn(0, 1, 3, 4), 4 * PAGE_SIZE, sgmax, 2 },
{ 0, 5, pfn(0, 1, 3, 4, 5), 5 * PAGE_SIZE, sgmax, 2 },
{ 0, 5, pfn(0, 1, 3, 4, 6), 5 * PAGE_SIZE, sgmax, 3 },
{ 0, 5, pfn(0, 1, 2, 3, 4), 5 * PAGE_SIZE, sgmax, 1 },
{ 0, 5, pfn(0, 1, 2, 3, 4), 5 * PAGE_SIZE, 2 * PAGE_SIZE, 3 },
{ 0, 6, pfn(0, 1, 2, 3, 4, 5), 6 * PAGE_SIZE, 2 * PAGE_SIZE, 3 },
{ 0, 6, pfn(0, 2, 3, 4, 5, 6), 6 * PAGE_SIZE, 2 * PAGE_SIZE, 4 },
{ 0, 6, pfn(0, 1, 3, 4, 5, 6), 6 * PAGE_SIZE, 2 * PAGE_SIZE, 3 },
{ 0, 0, NULL, 0, 0, 0 },
};
unsigned int i;
for (i = 0, test = tests; test->expected_segments; test++, i++) {
struct page *pages[MAX_PAGES];
struct sg_table st;
int ret;
set_pages(pages, test->pfn, test->num_pages);
ret = __sg_alloc_table_from_pages(&st, pages, test->num_pages,
0, test->size, test->max_seg,
GFP_KERNEL);
assert(ret == test->alloc_ret);
if (test->alloc_ret)
continue;
assert(st.nents == test->expected_segments);
assert(st.orig_nents == test->expected_segments);
sg_free_table(&st);
}
assert(i == (sizeof(tests) / sizeof(tests[0])) - 1);
return 0;
}
+167
View File
@@ -0,0 +1,167 @@
# SPDX-License-Identifier: GPL-2.0
TARGETS = android
TARGETS += bpf
TARGETS += breakpoints
TARGETS += capabilities
TARGETS += cgroup
TARGETS += cpufreq
TARGETS += cpu-hotplug
TARGETS += efivarfs
TARGETS += exec
TARGETS += filesystems
TARGETS += firmware
TARGETS += ftrace
TARGETS += futex
TARGETS += gpio
TARGETS += intel_pstate
TARGETS += ipc
TARGETS += kcmp
TARGETS += kvm
TARGETS += lib
TARGETS += membarrier
TARGETS += memfd
TARGETS += memory-hotplug
TARGETS += mount
TARGETS += mqueue
TARGETS += net
TARGETS += netfilter
TARGETS += nsfs
TARGETS += powerpc
TARGETS += proc
TARGETS += pstore
TARGETS += ptrace
TARGETS += rseq
TARGETS += rtc
TARGETS += seccomp
TARGETS += sigaltstack
TARGETS += size
TARGETS += sparc64
TARGETS += splice
TARGETS += static_keys
TARGETS += sync
TARGETS += sysctl
ifneq (1, $(quicktest))
TARGETS += timers
endif
TARGETS += user
TARGETS += vm
TARGETS += x86
TARGETS += zram
#Please keep the TARGETS list alphabetically sorted
# Run "make quicktest=1 run_tests" or
# "make quicktest=1 kselftest" from top level Makefile
TARGETS_HOTPLUG = cpu-hotplug
TARGETS_HOTPLUG += memory-hotplug
# Clear LDFLAGS and MAKEFLAGS if called from main
# Makefile to avoid test build failures when test
# Makefile doesn't have explicit build rules.
ifeq (1,$(MAKELEVEL))
override LDFLAGS =
override MAKEFLAGS =
endif
ifneq ($(KBUILD_SRC),)
override LDFLAGS =
endif
BUILD := $(O)
ifndef BUILD
BUILD := $(KBUILD_OUTPUT)
endif
ifndef BUILD
BUILD := $(shell pwd)
endif
# KSFT_TAP_LEVEL is used from KSFT framework to prevent nested TAP header
# printing from tests. Applicable to run_tests case where run_tests adds
# TAP header prior running tests and when a test program invokes another
# with system() call. Export it here to cover override RUN_TESTS defines.
export KSFT_TAP_LEVEL=`echo 1`
export BUILD
all:
@for TARGET in $(TARGETS); do \
BUILD_TARGET=$$BUILD/$$TARGET; \
mkdir $$BUILD_TARGET -p; \
make OUTPUT=$$BUILD_TARGET -C $$TARGET;\
done;
run_tests: all
@for TARGET in $(TARGETS); do \
BUILD_TARGET=$$BUILD/$$TARGET; \
make OUTPUT=$$BUILD_TARGET -C $$TARGET run_tests;\
done;
hotplug:
@for TARGET in $(TARGETS_HOTPLUG); do \
BUILD_TARGET=$$BUILD/$$TARGET; \
make OUTPUT=$$BUILD_TARGET -C $$TARGET;\
done;
run_hotplug: hotplug
@for TARGET in $(TARGETS_HOTPLUG); do \
BUILD_TARGET=$$BUILD/$$TARGET; \
make OUTPUT=$$BUILD_TARGET -C $$TARGET run_full_test;\
done;
clean_hotplug:
@for TARGET in $(TARGETS_HOTPLUG); do \
BUILD_TARGET=$$BUILD/$$TARGET; \
make OUTPUT=$$BUILD_TARGET -C $$TARGET clean;\
done;
run_pstore_crash:
make -C pstore run_crash
INSTALL_PATH ?= install
INSTALL_PATH := $(abspath $(INSTALL_PATH))
ALL_SCRIPT := $(INSTALL_PATH)/run_kselftest.sh
install:
ifdef INSTALL_PATH
@# Ask all targets to install their files
mkdir -p $(INSTALL_PATH)
@for TARGET in $(TARGETS); do \
BUILD_TARGET=$$BUILD/$$TARGET; \
make OUTPUT=$$BUILD_TARGET -C $$TARGET INSTALL_PATH=$(INSTALL_PATH)/$$TARGET install; \
done;
@# Ask all targets to emit their test scripts
echo "#!/bin/sh" > $(ALL_SCRIPT)
echo "BASE_DIR=\$$(realpath \$$(dirname \$$0))" >> $(ALL_SCRIPT)
echo "cd \$$BASE_DIR" >> $(ALL_SCRIPT)
echo "ROOT=\$$PWD" >> $(ALL_SCRIPT)
echo "if [ \"\$$1\" = \"--summary\" ]; then" >> $(ALL_SCRIPT)
echo " OUTPUT=\$$BASE_DIR/output.log" >> $(ALL_SCRIPT)
echo " cat /dev/null > \$$OUTPUT" >> $(ALL_SCRIPT)
echo "else" >> $(ALL_SCRIPT)
echo " OUTPUT=/dev/stdout" >> $(ALL_SCRIPT)
echo "fi" >> $(ALL_SCRIPT)
echo "export KSFT_TAP_LEVEL=1" >> $(ALL_SCRIPT)
echo "export skip=4" >> $(ALL_SCRIPT)
for TARGET in $(TARGETS); do \
BUILD_TARGET=$$BUILD/$$TARGET; \
echo "echo ; echo TAP version 13" >> $(ALL_SCRIPT); \
echo "echo Running tests in $$TARGET" >> $(ALL_SCRIPT); \
echo "echo ========================================" >> $(ALL_SCRIPT); \
echo "[ -w /dev/kmsg ] && echo \"kselftest: Running tests in $$TARGET\" >> /dev/kmsg" >> $(ALL_SCRIPT); \
echo "cd $$TARGET" >> $(ALL_SCRIPT); \
make -s --no-print-directory OUTPUT=$$BUILD_TARGET -C $$TARGET emit_tests >> $(ALL_SCRIPT); \
echo "cd \$$ROOT" >> $(ALL_SCRIPT); \
done;
chmod u+x $(ALL_SCRIPT)
else
$(error Error: set INSTALL_PATH to use install)
endif
clean:
@for TARGET in $(TARGETS); do \
BUILD_TARGET=$$BUILD/$$TARGET; \
make OUTPUT=$$BUILD_TARGET -C $$TARGET clean;\
done;
.PHONY: all run_tests hotplug run_hotplug clean_hotplug run_pstore_crash install clean
+38
View File
@@ -0,0 +1,38 @@
SUBDIRS := ion
TEST_PROGS := run.sh
.PHONY: all clean
include ../lib.mk
all:
@for DIR in $(SUBDIRS); do \
BUILD_TARGET=$(OUTPUT)/$$DIR; \
mkdir $$BUILD_TARGET -p; \
make OUTPUT=$$BUILD_TARGET -C $$DIR $@;\
#SUBDIR test prog name should be in the form: SUBDIR_test.sh \
TEST=$$DIR"_test.sh"; \
if [ -e $$DIR/$$TEST ]; then \
rsync -a $$DIR/$$TEST $$BUILD_TARGET/; \
fi \
done
override define INSTALL_RULE
mkdir -p $(INSTALL_PATH)
install -t $(INSTALL_PATH) $(TEST_PROGS) $(TEST_PROGS_EXTENDED) $(TEST_FILES)
@for SUBDIR in $(SUBDIRS); do \
BUILD_TARGET=$(OUTPUT)/$$SUBDIR; \
mkdir $$BUILD_TARGET -p; \
$(MAKE) OUTPUT=$$BUILD_TARGET -C $$SUBDIR INSTALL_PATH=$(INSTALL_PATH)/$$SUBDIR install; \
done;
endef
override define CLEAN
@for DIR in $(SUBDIRS); do \
BUILD_TARGET=$(OUTPUT)/$$DIR; \
mkdir $$BUILD_TARGET -p; \
make OUTPUT=$$BUILD_TARGET -C $$DIR $@;\
done
endef
+5
View File
@@ -0,0 +1,5 @@
CONFIG_ANDROID=y
CONFIG_STAGING=y
CONFIG_ION=y
CONFIG_ION_SYSTEM_HEAP=y
CONFIG_DRM_VGEM=y
@@ -0,0 +1,19 @@
INCLUDEDIR := -I. -I../../../../../drivers/staging/android/uapi/ -I../../../../../usr/include/
CFLAGS := $(CFLAGS) $(INCLUDEDIR) -Wall -O2 -g
TEST_GEN_FILES := ionapp_export ionapp_import ionmap_test
all: $(TEST_GEN_FILES)
$(TEST_GEN_FILES): ipcsocket.c ionutils.c
TEST_PROGS := ion_test.sh
KSFT_KHDR_INSTALL := 1
top_srcdir = ../../../../..
include ../../lib.mk
$(OUTPUT)/ionapp_export: ionapp_export.c ipcsocket.c ionutils.c
$(OUTPUT)/ionapp_import: ionapp_import.c ipcsocket.c ionutils.c
$(OUTPUT)/ionmap_test: ionmap_test.c ionutils.c
+101
View File
@@ -0,0 +1,101 @@
ION BUFFER SHARING UTILITY
==========================
File: ion_test.sh : Utility to test ION driver buffer sharing mechanism.
Author: Pintu Kumar <pintu.ping@gmail.com>
Introduction:
-------------
This is a test utility to verify ION buffer sharing in user space
between 2 independent processes.
It uses unix domain socket (with SCM_RIGHTS) as IPC to transfer an FD to
another process to share the same buffer.
This utility demonstrates how ION buffer sharing can be implemented between
two user space processes, using various heap types.
The following heap types are supported by ION driver.
ION_HEAP_TYPE_SYSTEM (0)
ION_HEAP_TYPE_SYSTEM_CONTIG (1)
ION_HEAP_TYPE_CARVEOUT (2)
ION_HEAP_TYPE_CHUNK (3)
ION_HEAP_TYPE_DMA (4)
By default only the SYSTEM and SYSTEM_CONTIG heaps are supported.
Each heap is associated with the respective heap id.
This utility is designed in the form of client/server program.
The server part (ionapp_export) is the exporter of the buffer.
It is responsible for creating an ION client, allocating the buffer based on
the heap id, writing some data to this buffer and then exporting the FD
(associated with this buffer) to another process using socket IPC.
This FD is called as buffer FD (which is different than the ION client FD).
The client part (ionapp_import) is the importer of the buffer.
It retrives the FD from the socket data and installs into its address space.
This new FD internally points to the same kernel buffer.
So first it reads the data that is stored in this buffer and prints it.
Then it writes the different size of data (it could be different data) to the
same buffer.
Finally the buffer FD must be closed by both the exporter and importer.
Thus the same kernel buffer is shared among two user space processes using
ION driver and only one time allocation.
Prerequisite:
-------------
This utility works only if /dev/ion interface is present.
The following configs needs to be enabled in kernel to include ion driver.
CONFIG_ANDROID=y
CONFIG_STAGING=y
CONFIG_ION=y
CONFIG_ION_SYSTEM_HEAP=y
This utility requires to be run as root user.
Compile and test:
-----------------
This utility is made to be run as part of kselftest framework in kernel.
To compile and run using kselftest you can simply do the following from the
kernel top directory.
linux$ make TARGETS=android kselftest
Or you can also use:
linux$ make -C tools/testing/selftests TARGETS=android run_tests
Using the selftest it can directly execute the ion_test.sh script to test the
buffer sharing using ion system heap.
Currently the heap size is hard coded as just 10 bytes inside this script.
You need to be a root user to run under selftest.
You can also compile and test manually using the following steps:
ion$ make
These will generate 2 executable: ionapp_export, ionapp_import
Now you can run the export and import manually by specifying the heap type
and the heap size.
You can also directly execute the shell script to run the test automatically.
Simply use the following command to run the test.
ion$ sudo ./ion_test.sh
Test Results:
-------------
The utility is verified on Ubuntu-32 bit system with Linux Kernel 4.14.
Here is the snapshot of the test result using kselftest.
linux# make TARGETS=android kselftest
heap_type: 0, heap_size: 10
--------------------------------------
heap type: 0
heap id: 1
heap name: ion_system_heap
--------------------------------------
Fill buffer content:
0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd
Sharing fd: 6, Client fd: 5
<ion_close_buffer_fd>: buffer release successfully....
Received buffer fd: 4
Read buffer content:
0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0x0 0x0 0x0 0x0 0x0 0x0
0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0 0x0
Fill buffer content:
0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd
0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd 0xfd
0xfd 0xfd
<ion_close_buffer_fd>: buffer release successfully....
ion_test.sh: heap_type: 0 - [PASS]
ion_test.sh: done
+143
View File
@@ -0,0 +1,143 @@
/*
* ion.h
*
* Copyright (C) 2011 Google, Inc.
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
/* This file is copied from drivers/staging/android/uapi/ion.h
* This local copy is required for the selftest to pass, when build
* outside the kernel source tree.
* Please keep this file in sync with its original file until the
* ion driver is moved outside the staging tree.
*/
#ifndef _UAPI_LINUX_ION_H
#define _UAPI_LINUX_ION_H
#include <linux/ioctl.h>
#include <linux/types.h>
/**
* enum ion_heap_types - list of all possible types of heaps
* @ION_HEAP_TYPE_SYSTEM: memory allocated via vmalloc
* @ION_HEAP_TYPE_SYSTEM_CONTIG: memory allocated via kmalloc
* @ION_HEAP_TYPE_CARVEOUT: memory allocated from a prereserved
* carveout heap, allocations are physically
* contiguous
* @ION_HEAP_TYPE_DMA: memory allocated via DMA API
* @ION_NUM_HEAPS: helper for iterating over heaps, a bit mask
* is used to identify the heaps, so only 32
* total heap types are supported
*/
enum ion_heap_type {
ION_HEAP_TYPE_SYSTEM,
ION_HEAP_TYPE_SYSTEM_CONTIG,
ION_HEAP_TYPE_CARVEOUT,
ION_HEAP_TYPE_CHUNK,
ION_HEAP_TYPE_DMA,
ION_HEAP_TYPE_CUSTOM, /*
* must be last so device specific heaps always
* are at the end of this enum
*/
};
#define ION_NUM_HEAP_IDS (sizeof(unsigned int) * 8)
/**
* allocation flags - the lower 16 bits are used by core ion, the upper 16
* bits are reserved for use by the heaps themselves.
*/
/*
* mappings of this buffer should be cached, ion will do cache maintenance
* when the buffer is mapped for dma
*/
#define ION_FLAG_CACHED 1
/**
* DOC: Ion Userspace API
*
* create a client by opening /dev/ion
* most operations handled via following ioctls
*
*/
/**
* struct ion_allocation_data - metadata passed from userspace for allocations
* @len: size of the allocation
* @heap_id_mask: mask of heap ids to allocate from
* @flags: flags passed to heap
* @handle: pointer that will be populated with a cookie to use to
* refer to this allocation
*
* Provided by userspace as an argument to the ioctl
*/
struct ion_allocation_data {
__u64 len;
__u32 heap_id_mask;
__u32 flags;
__u32 fd;
__u32 unused;
};
#define MAX_HEAP_NAME 32
/**
* struct ion_heap_data - data about a heap
* @name - first 32 characters of the heap name
* @type - heap type
* @heap_id - heap id for the heap
*/
struct ion_heap_data {
char name[MAX_HEAP_NAME];
__u32 type;
__u32 heap_id;
__u32 reserved0;
__u32 reserved1;
__u32 reserved2;
};
/**
* struct ion_heap_query - collection of data about all heaps
* @cnt - total number of heaps to be copied
* @heaps - buffer to copy heap data
*/
struct ion_heap_query {
__u32 cnt; /* Total number of heaps to be copied */
__u32 reserved0; /* align to 64bits */
__u64 heaps; /* buffer to be populated */
__u32 reserved1;
__u32 reserved2;
};
#define ION_IOC_MAGIC 'I'
/**
* DOC: ION_IOC_ALLOC - allocate memory
*
* Takes an ion_allocation_data struct and returns it with the handle field
* populated with the opaque handle for the allocation.
*/
#define ION_IOC_ALLOC _IOWR(ION_IOC_MAGIC, 0, \
struct ion_allocation_data)
/**
* DOC: ION_IOC_HEAP_QUERY - information about available heaps
*
* Takes an ion_heap_query structure and populates information about
* available Ion heaps.
*/
#define ION_IOC_HEAP_QUERY _IOWR(ION_IOC_MAGIC, 8, \
struct ion_heap_query)
#endif /* _UAPI_LINUX_ION_H */
+58
View File
@@ -0,0 +1,58 @@
#!/bin/bash
heapsize=4096
TCID="ion_test.sh"
errcode=0
# Kselftest framework requirement - SKIP code is 4.
ksft_skip=4
run_test()
{
heaptype=$1
./ionapp_export -i $heaptype -s $heapsize &
sleep 1
./ionapp_import
if [ $? -ne 0 ]; then
echo "$TCID: heap_type: $heaptype - [FAIL]"
errcode=1
else
echo "$TCID: heap_type: $heaptype - [PASS]"
fi
sleep 1
echo ""
}
check_root()
{
uid=$(id -u)
if [ $uid -ne 0 ]; then
echo $TCID: must be run as root >&2
exit $ksft_skip
fi
}
check_device()
{
DEVICE=/dev/ion
if [ ! -e $DEVICE ]; then
echo $TCID: No $DEVICE device found >&2
echo $TCID: May be CONFIG_ION is not set >&2
exit $ksft_skip
fi
}
main_function()
{
check_device
check_root
# ION_SYSTEM_HEAP TEST
run_test 0
# ION_SYSTEM_CONTIG_HEAP TEST
run_test 1
}
main_function
echo "$TCID: done"
exit $errcode
@@ -0,0 +1,136 @@
/*
* ionapp_export.c
*
* It is a user space utility to create and export android
* ion memory buffer fd to another process using unix domain socket as IPC.
* This acts like a server for ionapp_import(client).
* So, this server has to be started first before the client.
*
* Copyright (C) 2017 Pintu Kumar <pintu.ping@gmail.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/time.h>
#include "ionutils.h"
#include "ipcsocket.h"
void print_usage(int argc, char *argv[])
{
printf("Usage: %s [-h <help>] [-i <heap id>] [-s <size in bytes>]\n",
argv[0]);
}
int main(int argc, char *argv[])
{
int opt, ret, status, heapid;
int sockfd, client_fd, shared_fd;
unsigned char *map_buf;
unsigned long map_len, heap_type, heap_size, flags;
struct ion_buffer_info info;
struct socket_info skinfo;
if (argc < 2) {
print_usage(argc, argv);
return -1;
}
heap_size = 0;
flags = 0;
heap_type = ION_HEAP_TYPE_SYSTEM;
while ((opt = getopt(argc, argv, "hi:s:")) != -1) {
switch (opt) {
case 'h':
print_usage(argc, argv);
exit(0);
break;
case 'i':
heapid = atoi(optarg);
switch (heapid) {
case 0:
heap_type = ION_HEAP_TYPE_SYSTEM;
break;
case 1:
heap_type = ION_HEAP_TYPE_SYSTEM_CONTIG;
break;
default:
printf("ERROR: heap type not supported\n");
exit(1);
}
break;
case 's':
heap_size = atoi(optarg);
break;
default:
print_usage(argc, argv);
exit(1);
break;
}
}
if (heap_size <= 0) {
printf("heap_size cannot be 0\n");
print_usage(argc, argv);
exit(1);
}
printf("heap_type: %ld, heap_size: %ld\n", heap_type, heap_size);
info.heap_type = heap_type;
info.heap_size = heap_size;
info.flag_type = flags;
/* This is server: open the socket connection first */
/* Here; 1 indicates server or exporter */
status = opensocket(&sockfd, SOCKET_NAME, 1);
if (status < 0) {
fprintf(stderr, "<%s>: Failed opensocket.\n", __func__);
goto err_socket;
}
skinfo.sockfd = sockfd;
ret = ion_export_buffer_fd(&info);
if (ret < 0) {
fprintf(stderr, "FAILED: ion_get_buffer_fd\n");
goto err_export;
}
client_fd = info.ionfd;
shared_fd = info.buffd;
map_buf = info.buffer;
map_len = info.buflen;
write_buffer(map_buf, map_len);
/* share ion buf fd with other user process */
printf("Sharing fd: %d, Client fd: %d\n", shared_fd, client_fd);
skinfo.datafd = shared_fd;
skinfo.buflen = map_len;
ret = socket_send_fd(&skinfo);
if (ret < 0) {
fprintf(stderr, "FAILED: socket_send_fd\n");
goto err_send;
}
err_send:
err_export:
ion_close_buffer_fd(&info);
err_socket:
closesocket(sockfd, SOCKET_NAME);
return 0;
}
@@ -0,0 +1,88 @@
/*
* ionapp_import.c
*
* It is a user space utility to receive android ion memory buffer fd
* over unix domain socket IPC that can be exported by ionapp_export.
* This acts like a client for ionapp_export.
*
* Copyright (C) 2017 Pintu Kumar <pintu.ping@gmail.com>
*
* This software is licensed under the terms of the GNU General Public
* License version 2, as published by the Free Software Foundation, and
* may be copied, distributed, and modified under those terms.
*
* 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.
*
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "ionutils.h"
#include "ipcsocket.h"
int main(void)
{
int ret, status;
int sockfd, shared_fd;
unsigned char *map_buf;
unsigned long map_len;
struct ion_buffer_info info;
struct socket_info skinfo;
/* This is the client part. Here 0 means client or importer */
status = opensocket(&sockfd, SOCKET_NAME, 0);
if (status < 0) {
fprintf(stderr, "No exporter exists...\n");
ret = status;
goto err_socket;
}
skinfo.sockfd = sockfd;
ret = socket_receive_fd(&skinfo);
if (ret < 0) {
fprintf(stderr, "Failed: socket_receive_fd\n");
goto err_recv;
}
shared_fd = skinfo.datafd;
printf("Received buffer fd: %d\n", shared_fd);
if (shared_fd <= 0) {
fprintf(stderr, "ERROR: improper buf fd\n");
ret = -1;
goto err_fd;
}
memset(&info, 0, sizeof(info));
info.buffd = shared_fd;
info.buflen = ION_BUFFER_LEN;
ret = ion_import_buffer_fd(&info);
if (ret < 0) {
fprintf(stderr, "Failed: ion_use_buffer_fd\n");
goto err_import;
}
map_buf = info.buffer;
map_len = info.buflen;
read_buffer(map_buf, map_len);
/* Write probably new data to the same buffer again */
map_len = ION_BUFFER_LEN;
write_buffer(map_buf, map_len);
err_import:
ion_close_buffer_fd(&info);
err_fd:
err_recv:
err_socket:
closesocket(sockfd, SOCKET_NAME);
return ret;
}
@@ -0,0 +1,136 @@
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/dma-buf.h>
#include <drm/drm.h>
#include "ion.h"
#include "ionutils.h"
int check_vgem(int fd)
{
drm_version_t version = { 0 };
char name[5];
int ret;
version.name_len = 4;
version.name = name;
ret = ioctl(fd, DRM_IOCTL_VERSION, &version);
if (ret)
return 1;
return strcmp(name, "vgem");
}
int open_vgem(void)
{
int i, fd;
const char *drmstr = "/dev/dri/card";
fd = -1;
for (i = 0; i < 16; i++) {
char name[80];
sprintf(name, "%s%u", drmstr, i);
fd = open(name, O_RDWR);
if (fd < 0)
continue;
if (check_vgem(fd)) {
close(fd);
continue;
} else {
break;
}
}
return fd;
}
int import_vgem_fd(int vgem_fd, int dma_buf_fd, uint32_t *handle)
{
struct drm_prime_handle import_handle = { 0 };
int ret;
import_handle.fd = dma_buf_fd;
import_handle.flags = 0;
import_handle.handle = 0;
ret = ioctl(vgem_fd, DRM_IOCTL_PRIME_FD_TO_HANDLE, &import_handle);
if (ret == 0)
*handle = import_handle.handle;
return ret;
}
void close_handle(int vgem_fd, uint32_t handle)
{
struct drm_gem_close close = { 0 };
close.handle = handle;
ioctl(vgem_fd, DRM_IOCTL_GEM_CLOSE, &close);
}
int main()
{
int ret, vgem_fd;
struct ion_buffer_info info;
uint32_t handle = 0;
struct dma_buf_sync sync = { 0 };
info.heap_type = ION_HEAP_TYPE_SYSTEM;
info.heap_size = 4096;
info.flag_type = ION_FLAG_CACHED;
ret = ion_export_buffer_fd(&info);
if (ret < 0) {
printf("ion buffer alloc failed\n");
return -1;
}
vgem_fd = open_vgem();
if (vgem_fd < 0) {
ret = vgem_fd;
printf("Failed to open vgem\n");
goto out_ion;
}
ret = import_vgem_fd(vgem_fd, info.buffd, &handle);
if (ret < 0) {
printf("Failed to import buffer\n");
goto out_vgem;
}
sync.flags = DMA_BUF_SYNC_START | DMA_BUF_SYNC_RW;
ret = ioctl(info.buffd, DMA_BUF_IOCTL_SYNC, &sync);
if (ret)
printf("sync start failed %d\n", errno);
memset(info.buffer, 0xff, 4096);
sync.flags = DMA_BUF_SYNC_END | DMA_BUF_SYNC_RW;
ret = ioctl(info.buffd, DMA_BUF_IOCTL_SYNC, &sync);
if (ret)
printf("sync end failed %d\n", errno);
close_handle(vgem_fd, handle);
ret = 0;
out_vgem:
close(vgem_fd);
out_ion:
ion_close_buffer_fd(&info);
printf("done.\n");
return ret;
}
@@ -0,0 +1,253 @@
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
//#include <stdint.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include "ionutils.h"
#include "ipcsocket.h"
void write_buffer(void *buffer, unsigned long len)
{
int i;
unsigned char *ptr = (unsigned char *)buffer;
if (!ptr) {
fprintf(stderr, "<%s>: Invalid buffer...\n", __func__);
return;
}
printf("Fill buffer content:\n");
memset(ptr, 0xfd, len);
for (i = 0; i < len; i++)
printf("0x%x ", ptr[i]);
printf("\n");
}
void read_buffer(void *buffer, unsigned long len)
{
int i;
unsigned char *ptr = (unsigned char *)buffer;
if (!ptr) {
fprintf(stderr, "<%s>: Invalid buffer...\n", __func__);
return;
}
printf("Read buffer content:\n");
for (i = 0; i < len; i++)
printf("0x%x ", ptr[i]);
printf("\n");
}
int ion_export_buffer_fd(struct ion_buffer_info *ion_info)
{
int i, ret, ionfd, buffer_fd;
unsigned int heap_id;
unsigned long maplen;
unsigned char *map_buffer;
struct ion_allocation_data alloc_data;
struct ion_heap_query query;
struct ion_heap_data heap_data[MAX_HEAP_COUNT];
if (!ion_info) {
fprintf(stderr, "<%s>: Invalid ion info\n", __func__);
return -1;
}
/* Create an ION client */
ionfd = open(ION_DEVICE, O_RDWR);
if (ionfd < 0) {
fprintf(stderr, "<%s>: Failed to open ion client: %s\n",
__func__, strerror(errno));
return -1;
}
memset(&query, 0, sizeof(query));
query.cnt = MAX_HEAP_COUNT;
query.heaps = (unsigned long int)&heap_data[0];
/* Query ION heap_id_mask from ION heap */
ret = ioctl(ionfd, ION_IOC_HEAP_QUERY, &query);
if (ret < 0) {
fprintf(stderr, "<%s>: Failed: ION_IOC_HEAP_QUERY: %s\n",
__func__, strerror(errno));
goto err_query;
}
heap_id = MAX_HEAP_COUNT + 1;
for (i = 0; i < query.cnt; i++) {
if (heap_data[i].type == ion_info->heap_type) {
heap_id = heap_data[i].heap_id;
break;
}
}
if (heap_id > MAX_HEAP_COUNT) {
fprintf(stderr, "<%s>: ERROR: heap type does not exists\n",
__func__);
goto err_heap;
}
alloc_data.len = ion_info->heap_size;
alloc_data.heap_id_mask = 1 << heap_id;
alloc_data.flags = ion_info->flag_type;
/* Allocate memory for this ION client as per heap_type */
ret = ioctl(ionfd, ION_IOC_ALLOC, &alloc_data);
if (ret < 0) {
fprintf(stderr, "<%s>: Failed: ION_IOC_ALLOC: %s\n",
__func__, strerror(errno));
goto err_alloc;
}
/* This will return a valid buffer fd */
buffer_fd = alloc_data.fd;
maplen = alloc_data.len;
if (buffer_fd < 0 || maplen <= 0) {
fprintf(stderr, "<%s>: Invalid map data, fd: %d, len: %ld\n",
__func__, buffer_fd, maplen);
goto err_fd_data;
}
/* Create memory mapped buffer for the buffer fd */
map_buffer = (unsigned char *)mmap(NULL, maplen, PROT_READ|PROT_WRITE,
MAP_SHARED, buffer_fd, 0);
if (map_buffer == MAP_FAILED) {
fprintf(stderr, "<%s>: Failed: mmap: %s\n",
__func__, strerror(errno));
goto err_mmap;
}
ion_info->ionfd = ionfd;
ion_info->buffd = buffer_fd;
ion_info->buffer = map_buffer;
ion_info->buflen = maplen;
return 0;
munmap(map_buffer, maplen);
err_fd_data:
err_mmap:
/* in case of error: close the buffer fd */
if (buffer_fd)
close(buffer_fd);
err_query:
err_heap:
err_alloc:
/* In case of error: close the ion client fd */
if (ionfd)
close(ionfd);
return -1;
}
int ion_import_buffer_fd(struct ion_buffer_info *ion_info)
{
int buffd;
unsigned char *map_buf;
unsigned long map_len;
if (!ion_info) {
fprintf(stderr, "<%s>: Invalid ion info\n", __func__);
return -1;
}
map_len = ion_info->buflen;
buffd = ion_info->buffd;
if (buffd < 0 || map_len <= 0) {
fprintf(stderr, "<%s>: Invalid map data, fd: %d, len: %ld\n",
__func__, buffd, map_len);
goto err_buffd;
}
map_buf = (unsigned char *)mmap(NULL, map_len, PROT_READ|PROT_WRITE,
MAP_SHARED, buffd, 0);
if (map_buf == MAP_FAILED) {
printf("<%s>: Failed - mmap: %s\n",
__func__, strerror(errno));
goto err_mmap;
}
ion_info->buffer = map_buf;
ion_info->buflen = map_len;
return 0;
err_mmap:
if (buffd)
close(buffd);
err_buffd:
return -1;
}
void ion_close_buffer_fd(struct ion_buffer_info *ion_info)
{
if (ion_info) {
/* unmap the buffer properly in the end */
munmap(ion_info->buffer, ion_info->buflen);
/* close the buffer fd */
if (ion_info->buffd > 0)
close(ion_info->buffd);
/* Finally, close the client fd */
if (ion_info->ionfd > 0)
close(ion_info->ionfd);
}
}
int socket_send_fd(struct socket_info *info)
{
int status;
int fd, sockfd;
struct socketdata skdata;
if (!info) {
fprintf(stderr, "<%s>: Invalid socket info\n", __func__);
return -1;
}
sockfd = info->sockfd;
fd = info->datafd;
memset(&skdata, 0, sizeof(skdata));
skdata.data = fd;
skdata.len = sizeof(skdata.data);
status = sendtosocket(sockfd, &skdata);
if (status < 0) {
fprintf(stderr, "<%s>: Failed: sendtosocket\n", __func__);
return -1;
}
return 0;
}
int socket_receive_fd(struct socket_info *info)
{
int status;
int fd, sockfd;
struct socketdata skdata;
if (!info) {
fprintf(stderr, "<%s>: Invalid socket info\n", __func__);
return -1;
}
sockfd = info->sockfd;
memset(&skdata, 0, sizeof(skdata));
status = receivefromsocket(sockfd, &skdata);
if (status < 0) {
fprintf(stderr, "<%s>: Failed: receivefromsocket\n", __func__);
return -1;
}
fd = (int)skdata.data;
info->datafd = fd;
return status;
}
@@ -0,0 +1,55 @@
#ifndef __ION_UTILS_H
#define __ION_UTILS_H
#include "ion.h"
#define SOCKET_NAME "ion_socket"
#define ION_DEVICE "/dev/ion"
#define ION_BUFFER_LEN 4096
#define MAX_HEAP_COUNT ION_HEAP_TYPE_CUSTOM
struct socket_info {
int sockfd;
int datafd;
unsigned long buflen;
};
struct ion_buffer_info {
int ionfd;
int buffd;
unsigned int heap_type;
unsigned int flag_type;
unsigned long heap_size;
unsigned long buflen;
unsigned char *buffer;
};
/* This is used to fill the data into the mapped buffer */
void write_buffer(void *buffer, unsigned long len);
/* This is used to read the data from the exported buffer */
void read_buffer(void *buffer, unsigned long len);
/* This is used to create an ION buffer FD for the kernel buffer
* So you can export this same buffer to others in the form of FD
*/
int ion_export_buffer_fd(struct ion_buffer_info *ion_info);
/* This is used to import or map an exported FD.
* So we point to same buffer without making a copy. Hence zero-copy.
*/
int ion_import_buffer_fd(struct ion_buffer_info *ion_info);
/* This is used to close all references for the ION client */
void ion_close_buffer_fd(struct ion_buffer_info *ion_info);
/* This is used to send FD to another process using socket IPC */
int socket_send_fd(struct socket_info *skinfo);
/* This is used to receive FD from another process using socket IPC */
int socket_receive_fd(struct socket_info *skinfo);
#endif
@@ -0,0 +1,227 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/un.h>
#include <errno.h>
#include "ipcsocket.h"
int opensocket(int *sockfd, const char *name, int connecttype)
{
int ret, temp = 1;
if (!name || strlen(name) > MAX_SOCK_NAME_LEN) {
fprintf(stderr, "<%s>: Invalid socket name.\n", __func__);
return -1;
}
ret = socket(PF_LOCAL, SOCK_STREAM, 0);
if (ret < 0) {
fprintf(stderr, "<%s>: Failed socket: <%s>\n",
__func__, strerror(errno));
return ret;
}
*sockfd = ret;
if (setsockopt(*sockfd, SOL_SOCKET, SO_REUSEADDR,
(char *)&temp, sizeof(int)) < 0) {
fprintf(stderr, "<%s>: Failed setsockopt: <%s>\n",
__func__, strerror(errno));
goto err;
}
sprintf(sock_name, "/tmp/%s", name);
if (connecttype == 1) {
/* This is for Server connection */
struct sockaddr_un skaddr;
int clientfd;
socklen_t sklen;
unlink(sock_name);
memset(&skaddr, 0, sizeof(skaddr));
skaddr.sun_family = AF_LOCAL;
strcpy(skaddr.sun_path, sock_name);
ret = bind(*sockfd, (struct sockaddr *)&skaddr,
SUN_LEN(&skaddr));
if (ret < 0) {
fprintf(stderr, "<%s>: Failed bind: <%s>\n",
__func__, strerror(errno));
goto err;
}
ret = listen(*sockfd, 5);
if (ret < 0) {
fprintf(stderr, "<%s>: Failed listen: <%s>\n",
__func__, strerror(errno));
goto err;
}
memset(&skaddr, 0, sizeof(skaddr));
sklen = sizeof(skaddr);
ret = accept(*sockfd, (struct sockaddr *)&skaddr,
(socklen_t *)&sklen);
if (ret < 0) {
fprintf(stderr, "<%s>: Failed accept: <%s>\n",
__func__, strerror(errno));
goto err;
}
clientfd = ret;
*sockfd = clientfd;
} else {
/* This is for client connection */
struct sockaddr_un skaddr;
memset(&skaddr, 0, sizeof(skaddr));
skaddr.sun_family = AF_LOCAL;
strcpy(skaddr.sun_path, sock_name);
ret = connect(*sockfd, (struct sockaddr *)&skaddr,
SUN_LEN(&skaddr));
if (ret < 0) {
fprintf(stderr, "<%s>: Failed connect: <%s>\n",
__func__, strerror(errno));
goto err;
}
}
return 0;
err:
if (*sockfd)
close(*sockfd);
return ret;
}
int sendtosocket(int sockfd, struct socketdata *skdata)
{
int ret, buffd;
unsigned int len;
char cmsg_b[CMSG_SPACE(sizeof(int))];
struct cmsghdr *cmsg;
struct msghdr msgh;
struct iovec iov;
struct timeval timeout;
fd_set selFDs;
if (!skdata) {
fprintf(stderr, "<%s>: socketdata is NULL\n", __func__);
return -1;
}
FD_ZERO(&selFDs);
FD_SET(0, &selFDs);
FD_SET(sockfd, &selFDs);
timeout.tv_sec = 20;
timeout.tv_usec = 0;
ret = select(sockfd+1, NULL, &selFDs, NULL, &timeout);
if (ret < 0) {
fprintf(stderr, "<%s>: Failed select: <%s>\n",
__func__, strerror(errno));
return -1;
}
if (FD_ISSET(sockfd, &selFDs)) {
buffd = skdata->data;
len = skdata->len;
memset(&msgh, 0, sizeof(msgh));
msgh.msg_control = &cmsg_b;
msgh.msg_controllen = CMSG_LEN(len);
iov.iov_base = "OK";
iov.iov_len = 2;
msgh.msg_iov = &iov;
msgh.msg_iovlen = 1;
cmsg = CMSG_FIRSTHDR(&msgh);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(len);
memcpy(CMSG_DATA(cmsg), &buffd, len);
ret = sendmsg(sockfd, &msgh, MSG_DONTWAIT);
if (ret < 0) {
fprintf(stderr, "<%s>: Failed sendmsg: <%s>\n",
__func__, strerror(errno));
return -1;
}
}
return 0;
}
int receivefromsocket(int sockfd, struct socketdata *skdata)
{
int ret, buffd;
unsigned int len = 0;
char cmsg_b[CMSG_SPACE(sizeof(int))];
struct cmsghdr *cmsg;
struct msghdr msgh;
struct iovec iov;
fd_set recvFDs;
char data[32];
if (!skdata) {
fprintf(stderr, "<%s>: socketdata is NULL\n", __func__);
return -1;
}
FD_ZERO(&recvFDs);
FD_SET(0, &recvFDs);
FD_SET(sockfd, &recvFDs);
ret = select(sockfd+1, &recvFDs, NULL, NULL, NULL);
if (ret < 0) {
fprintf(stderr, "<%s>: Failed select: <%s>\n",
__func__, strerror(errno));
return -1;
}
if (FD_ISSET(sockfd, &recvFDs)) {
len = sizeof(buffd);
memset(&msgh, 0, sizeof(msgh));
msgh.msg_control = &cmsg_b;
msgh.msg_controllen = CMSG_LEN(len);
iov.iov_base = data;
iov.iov_len = sizeof(data)-1;
msgh.msg_iov = &iov;
msgh.msg_iovlen = 1;
cmsg = CMSG_FIRSTHDR(&msgh);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
cmsg->cmsg_len = CMSG_LEN(len);
ret = recvmsg(sockfd, &msgh, MSG_DONTWAIT);
if (ret < 0) {
fprintf(stderr, "<%s>: Failed recvmsg: <%s>\n",
__func__, strerror(errno));
return -1;
}
memcpy(&buffd, CMSG_DATA(cmsg), len);
skdata->data = buffd;
skdata->len = len;
}
return 0;
}
int closesocket(int sockfd, char *name)
{
char sockname[MAX_SOCK_NAME_LEN];
if (sockfd)
close(sockfd);
sprintf(sockname, "/tmp/%s", name);
unlink(sockname);
shutdown(sockfd, 2);
return 0;
}
@@ -0,0 +1,35 @@
#ifndef _IPCSOCKET_H
#define _IPCSOCKET_H
#define MAX_SOCK_NAME_LEN 64
char sock_name[MAX_SOCK_NAME_LEN];
/* This structure is responsible for holding the IPC data
* data: hold the buffer fd
* len: just the length of 32-bit integer fd
*/
struct socketdata {
int data;
unsigned int len;
};
/* This API is used to open the IPC socket connection
* name: implies a unique socket name in the system
* connecttype: implies server(0) or client(1)
*/
int opensocket(int *sockfd, const char *name, int connecttype);
/* This is the API to send socket data over IPC socket */
int sendtosocket(int sockfd, struct socketdata *data);
/* This is the API to receive socket data over IPC socket */
int receivefromsocket(int sockfd, struct socketdata *data);
/* This is the API to close the socket connection */
int closesocket(int sockfd, char *name);
#endif
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
(cd ion; ./ion_test.sh)
+12
View File
@@ -0,0 +1,12 @@
# SPDX-License-Identifier: GPL-2.0
# ARCH can be overridden by the user for cross compiling
ARCH ?= $(shell uname -m 2>/dev/null || echo not)
ifneq (,$(filter $(ARCH),aarch64 arm64))
CFLAGS += -I../../../../usr/include/
TEST_GEN_PROGS := tags_test
TEST_PROGS := run_tags_test.sh
endif
include ../lib.mk
+12
View File
@@ -0,0 +1,12 @@
#!/bin/sh
# SPDX-License-Identifier: GPL-2.0
echo "--------------------"
echo "running tags test"
echo "--------------------"
./tags_test
if [ $? -ne 0 ]; then
echo "[FAIL]"
else
echo "[PASS]"
fi
+31
View File
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: GPL-2.0
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <sys/prctl.h>
#include <sys/utsname.h>
#define SHIFT_TAG(tag) ((uint64_t)(tag) << 56)
#define SET_TAG(ptr, tag) (((uint64_t)(ptr) & ~SHIFT_TAG(0xff)) | \
SHIFT_TAG(tag))
int main(void)
{
static int tbi_enabled = 0;
unsigned long tag = 0;
struct utsname *ptr;
int err;
if (prctl(PR_SET_TAGGED_ADDR_CTRL, PR_TAGGED_ADDR_ENABLE, 0, 0, 0) == 0)
tbi_enabled = 1;
ptr = (struct utsname *)malloc(sizeof(*ptr));
if (tbi_enabled)
tag = 0x42;
ptr = (struct utsname *)SET_TAG(ptr, tag);
err = uname(ptr);
free(ptr);
return err;
}
+145
View File
@@ -0,0 +1,145 @@
# SPDX-License-Identifier: GPL-2.0
LIBDIR := ../../../lib
BPFDIR := $(LIBDIR)/bpf
APIDIR := ../../../include/uapi
GENDIR := ../../../../include/generated
GENHDR := $(GENDIR)/autoconf.h
ifneq ($(wildcard $(GENHDR)),)
GENFLAGS := -DHAVE_GENHDR
endif
CFLAGS += -Wall -O2 -I$(APIDIR) -I$(LIBDIR) -I$(BPFDIR) -I$(GENDIR) $(GENFLAGS) -I../../../include
LDLIBS += -lcap -lelf -lrt -lpthread
TEST_CUSTOM_PROGS = $(OUTPUT)/urandom_read
all: $(TEST_CUSTOM_PROGS)
$(TEST_CUSTOM_PROGS): $(OUTPUT)/%: %.c
$(CC) -o $(TEST_CUSTOM_PROGS) -static $< -Wl,--build-id
# Order correspond to 'make run_tests' order
TEST_GEN_PROGS = test_verifier test_tag test_maps test_lru_map test_lpm_map test_progs \
test_align test_verifier_log test_dev_cgroup test_tcpbpf_user \
test_sock test_btf test_sockmap test_lirc_mode2_user get_cgroup_id_user \
test_socket_cookie test_cgroup_storage test_select_reuseport
TEST_GEN_FILES = test_pkt_access.o test_xdp.o test_l4lb.o test_tcp_estats.o test_obj_id.o \
test_pkt_md_access.o test_xdp_redirect.o test_xdp_meta.o sockmap_parse_prog.o \
sockmap_verdict_prog.o dev_cgroup.o sample_ret0.o test_tracepoint.o \
test_l4lb_noinline.o test_xdp_noinline.o test_stacktrace_map.o \
sample_map_ret0.o test_tcpbpf_kern.o test_stacktrace_build_id.o \
sockmap_tcp_msg_prog.o connect4_prog.o connect6_prog.o test_adjust_tail.o \
test_btf_haskv.o test_btf_nokv.o test_sockmap_kern.o test_tunnel_kern.o \
test_get_stack_rawtp.o test_sockmap_kern.o test_sockhash_kern.o \
test_lwt_seg6local.o sendmsg4_prog.o sendmsg6_prog.o test_lirc_mode2_kern.o \
get_cgroup_id_kern.o socket_cookie_prog.o test_select_reuseport_kern.o \
test_skb_cgroup_id_kern.o
# Order correspond to 'make run_tests' order
TEST_PROGS := test_kmod.sh \
test_libbpf.sh \
test_xdp_redirect.sh \
test_xdp_meta.sh \
test_offload.py \
test_sock_addr.sh \
test_tunnel.sh \
test_lwt_seg6local.sh \
test_lirc_mode2.sh \
test_skb_cgroup_id.sh
# Compile but not part of 'make run_tests'
TEST_GEN_PROGS_EXTENDED = test_libbpf_open test_sock_addr test_skb_cgroup_id_user
include ../lib.mk
BPFOBJ := $(OUTPUT)/libbpf.a
$(TEST_GEN_PROGS): $(BPFOBJ)
$(TEST_GEN_PROGS_EXTENDED): $(OUTPUT)/libbpf.a
$(OUTPUT)/test_dev_cgroup: cgroup_helpers.c
$(OUTPUT)/test_skb_cgroup_id_user: cgroup_helpers.c
$(OUTPUT)/test_sock: cgroup_helpers.c
$(OUTPUT)/test_sock_addr: cgroup_helpers.c
$(OUTPUT)/test_socket_cookie: cgroup_helpers.c
$(OUTPUT)/test_sockmap: cgroup_helpers.c
$(OUTPUT)/test_tcpbpf_user: cgroup_helpers.c
$(OUTPUT)/test_progs: trace_helpers.c
$(OUTPUT)/get_cgroup_id_user: cgroup_helpers.c
$(OUTPUT)/test_cgroup_storage: cgroup_helpers.c
.PHONY: force
# force a rebuild of BPFOBJ when its dependencies are updated
force:
$(BPFOBJ): force
$(MAKE) -C $(BPFDIR) OUTPUT=$(OUTPUT)/
CLANG ?= clang
LLC ?= llc
LLVM_OBJCOPY ?= llvm-objcopy
BTF_PAHOLE ?= pahole
PROBE := $(shell $(LLC) -march=bpf -mcpu=probe -filetype=null /dev/null 2>&1)
# Let newer LLVM versions transparently probe the kernel for availability
# of full BPF instruction set.
ifeq ($(PROBE),)
CPU ?= probe
else
CPU ?= generic
endif
# Get Clang's default includes on this system, as opposed to those seen by
# '-target bpf'. This fixes "missing" files on some architectures/distros,
# such as asm/byteorder.h, asm/socket.h, asm/sockios.h, sys/cdefs.h etc.
#
# Use '-idirafter': Don't interfere with include mechanics except where the
# build would have failed anyways.
CLANG_SYS_INCLUDES := $(shell $(CLANG) -v -E - </dev/null 2>&1 \
| sed -n '/<...> search starts here:/,/End of search list./{ s| \(/.*\)|-idirafter \1|p }')
CLANG_FLAGS = -I. -I./include/uapi -I../../../include/uapi \
$(CLANG_SYS_INCLUDES) \
-Wno-compare-distinct-pointer-types
$(OUTPUT)/test_l4lb_noinline.o: CLANG_FLAGS += -fno-inline
$(OUTPUT)/test_xdp_noinline.o: CLANG_FLAGS += -fno-inline
BTF_LLC_PROBE := $(shell $(LLC) -march=bpf -mattr=help 2>&1 | grep dwarfris)
BTF_PAHOLE_PROBE := $(shell $(BTF_PAHOLE) --help 2>&1 | grep BTF)
BTF_OBJCOPY_PROBE := $(shell $(LLVM_OBJCOPY) --help 2>&1 | grep -i 'usage.*llvm')
ifneq ($(BTF_LLC_PROBE),)
ifneq ($(BTF_PAHOLE_PROBE),)
ifneq ($(BTF_OBJCOPY_PROBE),)
CLANG_FLAGS += -g
LLC_FLAGS += -mattr=dwarfris
DWARF2BTF = y
endif
endif
endif
# Have one program compiled without "-target bpf" to test whether libbpf loads
# it successfully
$(OUTPUT)/test_xdp.o: test_xdp.c
$(CLANG) $(CLANG_FLAGS) \
-O2 -emit-llvm -c $< -o - | \
$(LLC) -march=bpf -mcpu=$(CPU) $(LLC_FLAGS) -filetype=obj -o $@
ifeq ($(DWARF2BTF),y)
$(BTF_PAHOLE) -J $@
endif
$(OUTPUT)/%.o: %.c
$(CLANG) $(CLANG_FLAGS) \
-O2 -target bpf -emit-llvm -c $< -o - | \
$(LLC) -march=bpf -mcpu=$(CPU) $(LLC_FLAGS) -filetype=obj -o $@
ifeq ($(DWARF2BTF),y)
$(BTF_PAHOLE) -J $@
endif
EXTRA_CLEAN := $(TEST_CUSTOM_PROGS)
+57
View File
@@ -0,0 +1,57 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __BPF_ENDIAN__
#define __BPF_ENDIAN__
#include <linux/swab.h>
/* LLVM's BPF target selects the endianness of the CPU
* it compiles on, or the user specifies (bpfel/bpfeb),
* respectively. The used __BYTE_ORDER__ is defined by
* the compiler, we cannot rely on __BYTE_ORDER from
* libc headers, since it doesn't reflect the actual
* requested byte order.
*
* Note, LLVM's BPF target has different __builtin_bswapX()
* semantics. It does map to BPF_ALU | BPF_END | BPF_TO_BE
* in bpfel and bpfeb case, which means below, that we map
* to cpu_to_be16(). We could use it unconditionally in BPF
* case, but better not rely on it, so that this header here
* can be used from application and BPF program side, which
* use different targets.
*/
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
# define __bpf_ntohs(x) __builtin_bswap16(x)
# define __bpf_htons(x) __builtin_bswap16(x)
# define __bpf_constant_ntohs(x) ___constant_swab16(x)
# define __bpf_constant_htons(x) ___constant_swab16(x)
# define __bpf_ntohl(x) __builtin_bswap32(x)
# define __bpf_htonl(x) __builtin_bswap32(x)
# define __bpf_constant_ntohl(x) ___constant_swab32(x)
# define __bpf_constant_htonl(x) ___constant_swab32(x)
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
# define __bpf_ntohs(x) (x)
# define __bpf_htons(x) (x)
# define __bpf_constant_ntohs(x) (x)
# define __bpf_constant_htons(x) (x)
# define __bpf_ntohl(x) (x)
# define __bpf_htonl(x) (x)
# define __bpf_constant_ntohl(x) (x)
# define __bpf_constant_htonl(x) (x)
#else
# error "Fix your compiler's __BYTE_ORDER__?!"
#endif
#define bpf_htons(x) \
(__builtin_constant_p(x) ? \
__bpf_constant_htons(x) : __bpf_htons(x))
#define bpf_ntohs(x) \
(__builtin_constant_p(x) ? \
__bpf_constant_ntohs(x) : __bpf_ntohs(x))
#define bpf_htonl(x) \
(__builtin_constant_p(x) ? \
__bpf_constant_htonl(x) : __bpf_htonl(x))
#define bpf_ntohl(x) \
(__builtin_constant_p(x) ? \
__bpf_constant_ntohl(x) : __bpf_ntohl(x))
#endif /* __BPF_ENDIAN__ */
+336
View File
@@ -0,0 +1,336 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __BPF_HELPERS_H
#define __BPF_HELPERS_H
/* helper macro to place programs, maps, license in
* different sections in elf_bpf file. Section names
* are interpreted by elf_bpf loader
*/
#define SEC(NAME) __attribute__((section(NAME), used))
/* helper functions called from eBPF programs written in C */
static void *(*bpf_map_lookup_elem)(void *map, void *key) =
(void *) BPF_FUNC_map_lookup_elem;
static int (*bpf_map_update_elem)(void *map, void *key, void *value,
unsigned long long flags) =
(void *) BPF_FUNC_map_update_elem;
static int (*bpf_map_delete_elem)(void *map, void *key) =
(void *) BPF_FUNC_map_delete_elem;
static int (*bpf_probe_read)(void *dst, int size, void *unsafe_ptr) =
(void *) BPF_FUNC_probe_read;
static unsigned long long (*bpf_ktime_get_ns)(void) =
(void *) BPF_FUNC_ktime_get_ns;
static int (*bpf_trace_printk)(const char *fmt, int fmt_size, ...) =
(void *) BPF_FUNC_trace_printk;
static void (*bpf_tail_call)(void *ctx, void *map, int index) =
(void *) BPF_FUNC_tail_call;
static unsigned long long (*bpf_get_smp_processor_id)(void) =
(void *) BPF_FUNC_get_smp_processor_id;
static unsigned long long (*bpf_get_current_pid_tgid)(void) =
(void *) BPF_FUNC_get_current_pid_tgid;
static unsigned long long (*bpf_get_current_uid_gid)(void) =
(void *) BPF_FUNC_get_current_uid_gid;
static int (*bpf_get_current_comm)(void *buf, int buf_size) =
(void *) BPF_FUNC_get_current_comm;
static unsigned long long (*bpf_perf_event_read)(void *map,
unsigned long long flags) =
(void *) BPF_FUNC_perf_event_read;
static int (*bpf_clone_redirect)(void *ctx, int ifindex, int flags) =
(void *) BPF_FUNC_clone_redirect;
static int (*bpf_redirect)(int ifindex, int flags) =
(void *) BPF_FUNC_redirect;
static int (*bpf_redirect_map)(void *map, int key, int flags) =
(void *) BPF_FUNC_redirect_map;
static int (*bpf_perf_event_output)(void *ctx, void *map,
unsigned long long flags, void *data,
int size) =
(void *) BPF_FUNC_perf_event_output;
static int (*bpf_get_stackid)(void *ctx, void *map, int flags) =
(void *) BPF_FUNC_get_stackid;
static int (*bpf_probe_write_user)(void *dst, void *src, int size) =
(void *) BPF_FUNC_probe_write_user;
static int (*bpf_current_task_under_cgroup)(void *map, int index) =
(void *) BPF_FUNC_current_task_under_cgroup;
static int (*bpf_skb_get_tunnel_key)(void *ctx, void *key, int size, int flags) =
(void *) BPF_FUNC_skb_get_tunnel_key;
static int (*bpf_skb_set_tunnel_key)(void *ctx, void *key, int size, int flags) =
(void *) BPF_FUNC_skb_set_tunnel_key;
static int (*bpf_skb_get_tunnel_opt)(void *ctx, void *md, int size) =
(void *) BPF_FUNC_skb_get_tunnel_opt;
static int (*bpf_skb_set_tunnel_opt)(void *ctx, void *md, int size) =
(void *) BPF_FUNC_skb_set_tunnel_opt;
static unsigned long long (*bpf_get_prandom_u32)(void) =
(void *) BPF_FUNC_get_prandom_u32;
static int (*bpf_xdp_adjust_head)(void *ctx, int offset) =
(void *) BPF_FUNC_xdp_adjust_head;
static int (*bpf_xdp_adjust_meta)(void *ctx, int offset) =
(void *) BPF_FUNC_xdp_adjust_meta;
static int (*bpf_get_socket_cookie)(void *ctx) =
(void *) BPF_FUNC_get_socket_cookie;
static int (*bpf_setsockopt)(void *ctx, int level, int optname, void *optval,
int optlen) =
(void *) BPF_FUNC_setsockopt;
static int (*bpf_getsockopt)(void *ctx, int level, int optname, void *optval,
int optlen) =
(void *) BPF_FUNC_getsockopt;
static int (*bpf_sock_ops_cb_flags_set)(void *ctx, int flags) =
(void *) BPF_FUNC_sock_ops_cb_flags_set;
static int (*bpf_sk_redirect_map)(void *ctx, void *map, int key, int flags) =
(void *) BPF_FUNC_sk_redirect_map;
static int (*bpf_sk_redirect_hash)(void *ctx, void *map, void *key, int flags) =
(void *) BPF_FUNC_sk_redirect_hash;
static int (*bpf_sock_map_update)(void *map, void *key, void *value,
unsigned long long flags) =
(void *) BPF_FUNC_sock_map_update;
static int (*bpf_sock_hash_update)(void *map, void *key, void *value,
unsigned long long flags) =
(void *) BPF_FUNC_sock_hash_update;
static int (*bpf_perf_event_read_value)(void *map, unsigned long long flags,
void *buf, unsigned int buf_size) =
(void *) BPF_FUNC_perf_event_read_value;
static int (*bpf_perf_prog_read_value)(void *ctx, void *buf,
unsigned int buf_size) =
(void *) BPF_FUNC_perf_prog_read_value;
static int (*bpf_override_return)(void *ctx, unsigned long rc) =
(void *) BPF_FUNC_override_return;
static int (*bpf_msg_redirect_map)(void *ctx, void *map, int key, int flags) =
(void *) BPF_FUNC_msg_redirect_map;
static int (*bpf_msg_redirect_hash)(void *ctx,
void *map, void *key, int flags) =
(void *) BPF_FUNC_msg_redirect_hash;
static int (*bpf_msg_apply_bytes)(void *ctx, int len) =
(void *) BPF_FUNC_msg_apply_bytes;
static int (*bpf_msg_cork_bytes)(void *ctx, int len) =
(void *) BPF_FUNC_msg_cork_bytes;
static int (*bpf_msg_pull_data)(void *ctx, int start, int end, int flags) =
(void *) BPF_FUNC_msg_pull_data;
static int (*bpf_bind)(void *ctx, void *addr, int addr_len) =
(void *) BPF_FUNC_bind;
static int (*bpf_xdp_adjust_tail)(void *ctx, int offset) =
(void *) BPF_FUNC_xdp_adjust_tail;
static int (*bpf_skb_get_xfrm_state)(void *ctx, int index, void *state,
int size, int flags) =
(void *) BPF_FUNC_skb_get_xfrm_state;
static int (*bpf_sk_select_reuseport)(void *ctx, void *map, void *key, __u32 flags) =
(void *) BPF_FUNC_sk_select_reuseport;
static int (*bpf_get_stack)(void *ctx, void *buf, int size, int flags) =
(void *) BPF_FUNC_get_stack;
static int (*bpf_fib_lookup)(void *ctx, struct bpf_fib_lookup *params,
int plen, __u32 flags) =
(void *) BPF_FUNC_fib_lookup;
static int (*bpf_lwt_push_encap)(void *ctx, unsigned int type, void *hdr,
unsigned int len) =
(void *) BPF_FUNC_lwt_push_encap;
static int (*bpf_lwt_seg6_store_bytes)(void *ctx, unsigned int offset,
void *from, unsigned int len) =
(void *) BPF_FUNC_lwt_seg6_store_bytes;
static int (*bpf_lwt_seg6_action)(void *ctx, unsigned int action, void *param,
unsigned int param_len) =
(void *) BPF_FUNC_lwt_seg6_action;
static int (*bpf_lwt_seg6_adjust_srh)(void *ctx, unsigned int offset,
unsigned int len) =
(void *) BPF_FUNC_lwt_seg6_adjust_srh;
static int (*bpf_rc_repeat)(void *ctx) =
(void *) BPF_FUNC_rc_repeat;
static int (*bpf_rc_keydown)(void *ctx, unsigned int protocol,
unsigned long long scancode, unsigned int toggle) =
(void *) BPF_FUNC_rc_keydown;
static unsigned long long (*bpf_get_current_cgroup_id)(void) =
(void *) BPF_FUNC_get_current_cgroup_id;
static void *(*bpf_get_local_storage)(void *map, unsigned long long flags) =
(void *) BPF_FUNC_get_local_storage;
static unsigned long long (*bpf_skb_cgroup_id)(void *ctx) =
(void *) BPF_FUNC_skb_cgroup_id;
static unsigned long long (*bpf_skb_ancestor_cgroup_id)(void *ctx, int level) =
(void *) BPF_FUNC_skb_ancestor_cgroup_id;
/* llvm builtin functions that eBPF C program may use to
* emit BPF_LD_ABS and BPF_LD_IND instructions
*/
struct sk_buff;
unsigned long long load_byte(void *skb,
unsigned long long off) asm("llvm.bpf.load.byte");
unsigned long long load_half(void *skb,
unsigned long long off) asm("llvm.bpf.load.half");
unsigned long long load_word(void *skb,
unsigned long long off) asm("llvm.bpf.load.word");
/* a helper structure used by eBPF C program
* to describe map attributes to elf_bpf loader
*/
struct bpf_map_def {
unsigned int type;
unsigned int key_size;
unsigned int value_size;
unsigned int max_entries;
unsigned int map_flags;
unsigned int inner_map_idx;
unsigned int numa_node;
};
#define BPF_ANNOTATE_KV_PAIR(name, type_key, type_val) \
struct ____btf_map_##name { \
type_key key; \
type_val value; \
}; \
struct ____btf_map_##name \
__attribute__ ((section(".maps." #name), used)) \
____btf_map_##name = { }
static int (*bpf_skb_load_bytes)(void *ctx, int off, void *to, int len) =
(void *) BPF_FUNC_skb_load_bytes;
static int (*bpf_skb_load_bytes_relative)(void *ctx, int off, void *to, int len, __u32 start_header) =
(void *) BPF_FUNC_skb_load_bytes_relative;
static int (*bpf_skb_store_bytes)(void *ctx, int off, void *from, int len, int flags) =
(void *) BPF_FUNC_skb_store_bytes;
static int (*bpf_l3_csum_replace)(void *ctx, int off, int from, int to, int flags) =
(void *) BPF_FUNC_l3_csum_replace;
static int (*bpf_l4_csum_replace)(void *ctx, int off, int from, int to, int flags) =
(void *) BPF_FUNC_l4_csum_replace;
static int (*bpf_csum_diff)(void *from, int from_size, void *to, int to_size, int seed) =
(void *) BPF_FUNC_csum_diff;
static int (*bpf_skb_under_cgroup)(void *ctx, void *map, int index) =
(void *) BPF_FUNC_skb_under_cgroup;
static int (*bpf_skb_change_head)(void *, int len, int flags) =
(void *) BPF_FUNC_skb_change_head;
static int (*bpf_skb_pull_data)(void *, int len) =
(void *) BPF_FUNC_skb_pull_data;
/* Scan the ARCH passed in from ARCH env variable (see Makefile) */
#if defined(__TARGET_ARCH_x86)
#define bpf_target_x86
#define bpf_target_defined
#elif defined(__TARGET_ARCH_s930x)
#define bpf_target_s930x
#define bpf_target_defined
#elif defined(__TARGET_ARCH_arm64)
#define bpf_target_arm64
#define bpf_target_defined
#elif defined(__TARGET_ARCH_mips)
#define bpf_target_mips
#define bpf_target_defined
#elif defined(__TARGET_ARCH_powerpc)
#define bpf_target_powerpc
#define bpf_target_defined
#elif defined(__TARGET_ARCH_sparc)
#define bpf_target_sparc
#define bpf_target_defined
#else
#undef bpf_target_defined
#endif
/* Fall back to what the compiler says */
#ifndef bpf_target_defined
#if defined(__x86_64__)
#define bpf_target_x86
#elif defined(__s390x__)
#define bpf_target_s930x
#elif defined(__aarch64__)
#define bpf_target_arm64
#elif defined(__mips__)
#define bpf_target_mips
#elif defined(__powerpc__)
#define bpf_target_powerpc
#elif defined(__sparc__)
#define bpf_target_sparc
#endif
#endif
#if defined(bpf_target_x86)
#define PT_REGS_PARM1(x) ((x)->di)
#define PT_REGS_PARM2(x) ((x)->si)
#define PT_REGS_PARM3(x) ((x)->dx)
#define PT_REGS_PARM4(x) ((x)->cx)
#define PT_REGS_PARM5(x) ((x)->r8)
#define PT_REGS_RET(x) ((x)->sp)
#define PT_REGS_FP(x) ((x)->bp)
#define PT_REGS_RC(x) ((x)->ax)
#define PT_REGS_SP(x) ((x)->sp)
#define PT_REGS_IP(x) ((x)->ip)
#elif defined(bpf_target_s390x)
#define PT_REGS_PARM1(x) ((x)->gprs[2])
#define PT_REGS_PARM2(x) ((x)->gprs[3])
#define PT_REGS_PARM3(x) ((x)->gprs[4])
#define PT_REGS_PARM4(x) ((x)->gprs[5])
#define PT_REGS_PARM5(x) ((x)->gprs[6])
#define PT_REGS_RET(x) ((x)->gprs[14])
#define PT_REGS_FP(x) ((x)->gprs[11]) /* Works only with CONFIG_FRAME_POINTER */
#define PT_REGS_RC(x) ((x)->gprs[2])
#define PT_REGS_SP(x) ((x)->gprs[15])
#define PT_REGS_IP(x) ((x)->psw.addr)
#elif defined(bpf_target_arm64)
#define PT_REGS_PARM1(x) ((x)->regs[0])
#define PT_REGS_PARM2(x) ((x)->regs[1])
#define PT_REGS_PARM3(x) ((x)->regs[2])
#define PT_REGS_PARM4(x) ((x)->regs[3])
#define PT_REGS_PARM5(x) ((x)->regs[4])
#define PT_REGS_RET(x) ((x)->regs[30])
#define PT_REGS_FP(x) ((x)->regs[29]) /* Works only with CONFIG_FRAME_POINTER */
#define PT_REGS_RC(x) ((x)->regs[0])
#define PT_REGS_SP(x) ((x)->sp)
#define PT_REGS_IP(x) ((x)->pc)
#elif defined(bpf_target_mips)
#define PT_REGS_PARM1(x) ((x)->regs[4])
#define PT_REGS_PARM2(x) ((x)->regs[5])
#define PT_REGS_PARM3(x) ((x)->regs[6])
#define PT_REGS_PARM4(x) ((x)->regs[7])
#define PT_REGS_PARM5(x) ((x)->regs[8])
#define PT_REGS_RET(x) ((x)->regs[31])
#define PT_REGS_FP(x) ((x)->regs[30]) /* Works only with CONFIG_FRAME_POINTER */
#define PT_REGS_RC(x) ((x)->regs[1])
#define PT_REGS_SP(x) ((x)->regs[29])
#define PT_REGS_IP(x) ((x)->cp0_epc)
#elif defined(bpf_target_powerpc)
#define PT_REGS_PARM1(x) ((x)->gpr[3])
#define PT_REGS_PARM2(x) ((x)->gpr[4])
#define PT_REGS_PARM3(x) ((x)->gpr[5])
#define PT_REGS_PARM4(x) ((x)->gpr[6])
#define PT_REGS_PARM5(x) ((x)->gpr[7])
#define PT_REGS_RC(x) ((x)->gpr[3])
#define PT_REGS_SP(x) ((x)->sp)
#define PT_REGS_IP(x) ((x)->nip)
#elif defined(bpf_target_sparc)
#define PT_REGS_PARM1(x) ((x)->u_regs[UREG_I0])
#define PT_REGS_PARM2(x) ((x)->u_regs[UREG_I1])
#define PT_REGS_PARM3(x) ((x)->u_regs[UREG_I2])
#define PT_REGS_PARM4(x) ((x)->u_regs[UREG_I3])
#define PT_REGS_PARM5(x) ((x)->u_regs[UREG_I4])
#define PT_REGS_RET(x) ((x)->u_regs[UREG_I7])
#define PT_REGS_RC(x) ((x)->u_regs[UREG_I0])
#define PT_REGS_SP(x) ((x)->u_regs[UREG_FP])
/* Should this also be a bpf_target check for the sparc case? */
#if defined(__arch64__)
#define PT_REGS_IP(x) ((x)->tpc)
#else
#define PT_REGS_IP(x) ((x)->pc)
#endif
#endif
#ifdef bpf_target_powerpc
#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ (ip) = (ctx)->link; })
#define BPF_KRETPROBE_READ_RET_IP BPF_KPROBE_READ_RET_IP
#elif bpf_target_sparc
#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ (ip) = PT_REGS_RET(ctx); })
#define BPF_KRETPROBE_READ_RET_IP BPF_KPROBE_READ_RET_IP
#else
#define BPF_KPROBE_READ_RET_IP(ip, ctx) ({ \
bpf_probe_read(&(ip), sizeof(ip), (void *)PT_REGS_RET(ctx)); })
#define BPF_KRETPROBE_READ_RET_IP(ip, ctx) ({ \
bpf_probe_read(&(ip), sizeof(ip), \
(void *)(PT_REGS_FP(ctx) + sizeof(ip))); })
#endif
#endif
+80
View File
@@ -0,0 +1,80 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __BPF_RAND__
#define __BPF_RAND__
#include <stdint.h>
#include <stdlib.h>
#include <time.h>
static inline uint64_t bpf_rand_mask(uint64_t mask)
{
return (((uint64_t)(uint32_t)rand()) |
((uint64_t)(uint32_t)rand() << 32)) & mask;
}
#define bpf_rand_ux(x, m) \
static inline uint64_t bpf_rand_u##x(int shift) \
{ \
return bpf_rand_mask((m)) << shift; \
}
bpf_rand_ux( 8, 0xffULL)
bpf_rand_ux(16, 0xffffULL)
bpf_rand_ux(24, 0xffffffULL)
bpf_rand_ux(32, 0xffffffffULL)
bpf_rand_ux(40, 0xffffffffffULL)
bpf_rand_ux(48, 0xffffffffffffULL)
bpf_rand_ux(56, 0xffffffffffffffULL)
bpf_rand_ux(64, 0xffffffffffffffffULL)
static inline void bpf_semi_rand_init(void)
{
srand(time(NULL));
}
static inline uint64_t bpf_semi_rand_get(void)
{
switch (rand() % 39) {
case 0: return 0x000000ff00000000ULL | bpf_rand_u8(0);
case 1: return 0xffffffff00000000ULL | bpf_rand_u16(0);
case 2: return 0x00000000ffff0000ULL | bpf_rand_u16(0);
case 3: return 0x8000000000000000ULL | bpf_rand_u32(0);
case 4: return 0x00000000f0000000ULL | bpf_rand_u32(0);
case 5: return 0x0000000100000000ULL | bpf_rand_u24(0);
case 6: return 0x800ff00000000000ULL | bpf_rand_u32(0);
case 7: return 0x7fffffff00000000ULL | bpf_rand_u32(0);
case 8: return 0xffffffffffffff00ULL ^ bpf_rand_u32(24);
case 9: return 0xffffffffffffff00ULL | bpf_rand_u8(0);
case 10: return 0x0000000010000000ULL | bpf_rand_u32(0);
case 11: return 0xf000000000000000ULL | bpf_rand_u8(0);
case 12: return 0x0000f00000000000ULL | bpf_rand_u8(8);
case 13: return 0x000000000f000000ULL | bpf_rand_u8(16);
case 14: return 0x0000000000000f00ULL | bpf_rand_u8(32);
case 15: return 0x00fff00000000f00ULL | bpf_rand_u8(48);
case 16: return 0x00007fffffffffffULL ^ bpf_rand_u32(1);
case 17: return 0xffff800000000000ULL | bpf_rand_u8(4);
case 18: return 0xffff800000000000ULL | bpf_rand_u8(20);
case 19: return (0xffffffc000000000ULL + 0x80000ULL) | bpf_rand_u32(0);
case 20: return (0xffffffc000000000ULL - 0x04000000ULL) | bpf_rand_u32(0);
case 21: return 0x0000000000000000ULL | bpf_rand_u8(55) | bpf_rand_u32(20);
case 22: return 0xffffffffffffffffULL ^ bpf_rand_u8(3) ^ bpf_rand_u32(40);
case 23: return 0x0000000000000000ULL | bpf_rand_u8(bpf_rand_u8(0) % 64);
case 24: return 0x0000000000000000ULL | bpf_rand_u16(bpf_rand_u8(0) % 64);
case 25: return 0xffffffffffffffffULL ^ bpf_rand_u8(bpf_rand_u8(0) % 64);
case 26: return 0xffffffffffffffffULL ^ bpf_rand_u40(bpf_rand_u8(0) % 64);
case 27: return 0x0000800000000000ULL;
case 28: return 0x8000000000000000ULL;
case 29: return 0x0000000000000000ULL;
case 30: return 0xffffffffffffffffULL;
case 31: return bpf_rand_u16(bpf_rand_u8(0) % 64);
case 32: return bpf_rand_u24(bpf_rand_u8(0) % 64);
case 33: return bpf_rand_u32(bpf_rand_u8(0) % 64);
case 34: return bpf_rand_u40(bpf_rand_u8(0) % 64);
case 35: return bpf_rand_u48(bpf_rand_u8(0) % 64);
case 36: return bpf_rand_u56(bpf_rand_u8(0) % 64);
case 37: return bpf_rand_u64(bpf_rand_u8(0) % 64);
default: return bpf_rand_u64(0);
}
}
#endif /* __BPF_RAND__ */
+28
View File
@@ -0,0 +1,28 @@
#include <sys/resource.h>
#include <stdio.h>
static __attribute__((constructor)) void bpf_rlimit_ctor(void)
{
struct rlimit rlim_old, rlim_new = {
.rlim_cur = RLIM_INFINITY,
.rlim_max = RLIM_INFINITY,
};
getrlimit(RLIMIT_MEMLOCK, &rlim_old);
/* For the sake of running the test cases, we temporarily
* set rlimit to infinity in order for kernel to focus on
* errors from actual test cases and not getting noise
* from hitting memlock limits. The limit is on per-process
* basis and not a global one, hence destructor not really
* needed here.
*/
if (setrlimit(RLIMIT_MEMLOCK, &rlim_new) < 0) {
perror("Unable to lift memlock rlimit");
/* Trying out lower limit, but expect potential test
* case failures from this!
*/
rlim_new.rlim_cur = rlim_old.rlim_cur + (1UL << 20);
rlim_new.rlim_max = rlim_old.rlim_max + (1UL << 20);
setrlimit(RLIMIT_MEMLOCK, &rlim_new);
}
}
+61
View File
@@ -0,0 +1,61 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __BPF_UTIL__
#define __BPF_UTIL__
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
static inline unsigned int bpf_num_possible_cpus(void)
{
static const char *fcpu = "/sys/devices/system/cpu/possible";
unsigned int start, end, possible_cpus = 0;
char buff[128];
FILE *fp;
int len, n, i, j = 0;
fp = fopen(fcpu, "r");
if (!fp) {
printf("Failed to open %s: '%s'!\n", fcpu, strerror(errno));
exit(1);
}
if (!fgets(buff, sizeof(buff), fp)) {
printf("Failed to read %s!\n", fcpu);
exit(1);
}
len = strlen(buff);
for (i = 0; i <= len; i++) {
if (buff[i] == ',' || buff[i] == '\0') {
buff[i] = '\0';
n = sscanf(&buff[j], "%u-%u", &start, &end);
if (n <= 0) {
printf("Failed to retrieve # possible CPUs!\n");
exit(1);
} else if (n == 1) {
end = start;
}
possible_cpus += end - start + 1;
j = i + 1;
}
}
fclose(fp);
return possible_cpus;
}
#define __bpf_percpu_val_align __attribute__((__aligned__(8)))
#define BPF_DECLARE_PERCPU(type, name) \
struct { type v; /* padding */ } __bpf_percpu_val_align \
name[bpf_num_possible_cpus()]
#define bpf_percpu(name, cpu) name[(cpu)].v
#ifndef ARRAY_SIZE
# define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
#endif
#endif /* __BPF_UTIL__ */
@@ -0,0 +1,235 @@
// SPDX-License-Identifier: GPL-2.0
#define _GNU_SOURCE
#include <sched.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <linux/limits.h>
#include <stdio.h>
#include <stdlib.h>
#include <linux/sched.h>
#include <fcntl.h>
#include <unistd.h>
#include <ftw.h>
#include "cgroup_helpers.h"
/*
* To avoid relying on the system setup, when setup_cgroup_env is called
* we create a new mount namespace, and cgroup namespace. The cgroup2
* root is mounted at CGROUP_MOUNT_PATH
*
* Unfortunately, most people don't have cgroupv2 enabled at this point in time.
* It's easier to create our own mount namespace and manage it ourselves.
*
* We assume /mnt exists.
*/
#define WALK_FD_LIMIT 16
#define CGROUP_MOUNT_PATH "/mnt"
#define CGROUP_WORK_DIR "/cgroup-test-work-dir"
#define format_cgroup_path(buf, path) \
snprintf(buf, sizeof(buf), "%s%s%s", CGROUP_MOUNT_PATH, \
CGROUP_WORK_DIR, path)
/**
* setup_cgroup_environment() - Setup the cgroup environment
*
* After calling this function, cleanup_cgroup_environment should be called
* once testing is complete.
*
* This function will print an error to stderr and return 1 if it is unable
* to setup the cgroup environment. If setup is successful, 0 is returned.
*/
int setup_cgroup_environment(void)
{
char cgroup_workdir[PATH_MAX - 24];
format_cgroup_path(cgroup_workdir, "");
if (unshare(CLONE_NEWNS)) {
log_err("unshare");
return 1;
}
if (mount("none", "/", NULL, MS_REC | MS_PRIVATE, NULL)) {
log_err("mount fakeroot");
return 1;
}
if (mount("none", CGROUP_MOUNT_PATH, "cgroup2", 0, NULL) && errno != EBUSY) {
log_err("mount cgroup2");
return 1;
}
/* Cleanup existing failed runs, now that the environment is setup */
cleanup_cgroup_environment();
if (mkdir(cgroup_workdir, 0777) && errno != EEXIST) {
log_err("mkdir cgroup work dir");
return 1;
}
return 0;
}
static int nftwfunc(const char *filename, const struct stat *statptr,
int fileflags, struct FTW *pfwt)
{
if ((fileflags & FTW_D) && rmdir(filename))
log_err("Removing cgroup: %s", filename);
return 0;
}
static int join_cgroup_from_top(char *cgroup_path)
{
char cgroup_procs_path[PATH_MAX + 1];
pid_t pid = getpid();
int fd, rc = 0;
snprintf(cgroup_procs_path, sizeof(cgroup_procs_path),
"%s/cgroup.procs", cgroup_path);
fd = open(cgroup_procs_path, O_WRONLY);
if (fd < 0) {
log_err("Opening Cgroup Procs: %s", cgroup_procs_path);
return 1;
}
if (dprintf(fd, "%d\n", pid) < 0) {
log_err("Joining Cgroup");
rc = 1;
}
close(fd);
return rc;
}
/**
* join_cgroup() - Join a cgroup
* @path: The cgroup path, relative to the workdir, to join
*
* This function expects a cgroup to already be created, relative to the cgroup
* work dir, and it joins it. For example, passing "/my-cgroup" as the path
* would actually put the calling process into the cgroup
* "/cgroup-test-work-dir/my-cgroup"
*
* On success, it returns 0, otherwise on failure it returns 1.
*/
int join_cgroup(const char *path)
{
char cgroup_path[PATH_MAX + 1];
format_cgroup_path(cgroup_path, path);
return join_cgroup_from_top(cgroup_path);
}
/**
* cleanup_cgroup_environment() - Cleanup Cgroup Testing Environment
*
* This is an idempotent function to delete all temporary cgroups that
* have been created during the test, including the cgroup testing work
* directory.
*
* At call time, it moves the calling process to the root cgroup, and then
* runs the deletion process. It is idempotent, and should not fail, unless
* a process is lingering.
*
* On failure, it will print an error to stderr, and try to continue.
*/
void cleanup_cgroup_environment(void)
{
char cgroup_workdir[PATH_MAX + 1];
format_cgroup_path(cgroup_workdir, "");
join_cgroup_from_top(CGROUP_MOUNT_PATH);
nftw(cgroup_workdir, nftwfunc, WALK_FD_LIMIT, FTW_DEPTH | FTW_MOUNT);
}
/**
* create_and_get_cgroup() - Create a cgroup, relative to workdir, and get the FD
* @path: The cgroup path, relative to the workdir, to join
*
* This function creates a cgroup under the top level workdir and returns the
* file descriptor. It is idempotent.
*
* On success, it returns the file descriptor. On failure it returns 0.
* If there is a failure, it prints the error to stderr.
*/
int create_and_get_cgroup(const char *path)
{
char cgroup_path[PATH_MAX + 1];
int fd;
format_cgroup_path(cgroup_path, path);
if (mkdir(cgroup_path, 0777) && errno != EEXIST) {
log_err("mkdiring cgroup %s .. %s", path, cgroup_path);
return 0;
}
fd = open(cgroup_path, O_RDONLY);
if (fd < 0) {
log_err("Opening Cgroup");
return 0;
}
return fd;
}
/**
* get_cgroup_id() - Get cgroup id for a particular cgroup path
* @path: The cgroup path, relative to the workdir, to join
*
* On success, it returns the cgroup id. On failure it returns 0,
* which is an invalid cgroup id.
* If there is a failure, it prints the error to stderr.
*/
unsigned long long get_cgroup_id(const char *path)
{
int dirfd, err, flags, mount_id, fhsize;
union {
unsigned long long cgid;
unsigned char raw_bytes[8];
} id;
char cgroup_workdir[PATH_MAX + 1];
struct file_handle *fhp, *fhp2;
unsigned long long ret = 0;
format_cgroup_path(cgroup_workdir, path);
dirfd = AT_FDCWD;
flags = 0;
fhsize = sizeof(*fhp);
fhp = calloc(1, fhsize);
if (!fhp) {
log_err("calloc");
return 0;
}
err = name_to_handle_at(dirfd, cgroup_workdir, fhp, &mount_id, flags);
if (err >= 0 || fhp->handle_bytes != 8) {
log_err("name_to_handle_at");
goto free_mem;
}
fhsize = sizeof(struct file_handle) + fhp->handle_bytes;
fhp2 = realloc(fhp, fhsize);
if (!fhp2) {
log_err("realloc");
goto free_mem;
}
err = name_to_handle_at(dirfd, cgroup_workdir, fhp2, &mount_id, flags);
fhp = fhp2;
if (err < 0) {
log_err("name_to_handle_at");
goto free_mem;
}
memcpy(id.raw_bytes, fhp->f_handle, 8);
ret = id.cgid;
free_mem:
free(fhp);
return ret;
}
@@ -0,0 +1,18 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef __CGROUP_HELPERS_H
#define __CGROUP_HELPERS_H
#include <errno.h>
#include <string.h>
#define clean_errno() (errno == 0 ? "None" : strerror(errno))
#define log_err(MSG, ...) fprintf(stderr, "(%s:%d: errno: %s) " MSG "\n", \
__FILE__, __LINE__, clean_errno(), ##__VA_ARGS__)
int create_and_get_cgroup(const char *path);
int join_cgroup(const char *path);
int setup_cgroup_environment(void);
void cleanup_cgroup_environment(void);
unsigned long long get_cgroup_id(const char *path);
#endif
+20
View File
@@ -0,0 +1,20 @@
CONFIG_BPF=y
CONFIG_BPF_SYSCALL=y
CONFIG_NET_CLS_BPF=m
CONFIG_BPF_EVENTS=y
CONFIG_TEST_BPF=m
CONFIG_CGROUP_BPF=y
CONFIG_NETDEVSIM=m
CONFIG_NET_CLS_ACT=y
CONFIG_NET_SCHED=y
CONFIG_NET_SCH_INGRESS=y
CONFIG_NET_IPIP=y
CONFIG_IPV6=y
CONFIG_NET_IPGRE_DEMUX=y
CONFIG_NET_IPGRE=y
CONFIG_IPV6_GRE=y
CONFIG_CRYPTO_USER_API_HASH=m
CONFIG_CRYPTO_HMAC=m
CONFIG_CRYPTO_SHA256=m
CONFIG_VXLAN=y
CONFIG_GENEVE=y
@@ -0,0 +1,45 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 Facebook
#include <string.h>
#include <linux/stddef.h>
#include <linux/bpf.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <sys/socket.h>
#include "bpf_helpers.h"
#include "bpf_endian.h"
#define SRC_REWRITE_IP4 0x7f000004U
#define DST_REWRITE_IP4 0x7f000001U
#define DST_REWRITE_PORT4 4444
int _version SEC("version") = 1;
SEC("cgroup/connect4")
int connect_v4_prog(struct bpf_sock_addr *ctx)
{
struct sockaddr_in sa;
/* Rewrite destination. */
ctx->user_ip4 = bpf_htonl(DST_REWRITE_IP4);
ctx->user_port = bpf_htons(DST_REWRITE_PORT4);
if (ctx->type == SOCK_DGRAM || ctx->type == SOCK_STREAM) {
///* Rewrite source. */
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = bpf_htons(0);
sa.sin_addr.s_addr = bpf_htonl(SRC_REWRITE_IP4);
if (bpf_bind(ctx, (struct sockaddr *)&sa, sizeof(sa)) != 0)
return 0;
}
return 1;
}
char _license[] SEC("license") = "GPL";
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 Facebook
#include <string.h>
#include <linux/stddef.h>
#include <linux/bpf.h>
#include <linux/in.h>
#include <linux/in6.h>
#include <sys/socket.h>
#include "bpf_helpers.h"
#include "bpf_endian.h"
#define SRC_REWRITE_IP6_0 0
#define SRC_REWRITE_IP6_1 0
#define SRC_REWRITE_IP6_2 0
#define SRC_REWRITE_IP6_3 6
#define DST_REWRITE_IP6_0 0
#define DST_REWRITE_IP6_1 0
#define DST_REWRITE_IP6_2 0
#define DST_REWRITE_IP6_3 1
#define DST_REWRITE_PORT6 6666
int _version SEC("version") = 1;
SEC("cgroup/connect6")
int connect_v6_prog(struct bpf_sock_addr *ctx)
{
struct sockaddr_in6 sa;
/* Rewrite destination. */
ctx->user_ip6[0] = bpf_htonl(DST_REWRITE_IP6_0);
ctx->user_ip6[1] = bpf_htonl(DST_REWRITE_IP6_1);
ctx->user_ip6[2] = bpf_htonl(DST_REWRITE_IP6_2);
ctx->user_ip6[3] = bpf_htonl(DST_REWRITE_IP6_3);
ctx->user_port = bpf_htons(DST_REWRITE_PORT6);
if (ctx->type == SOCK_DGRAM || ctx->type == SOCK_STREAM) {
/* Rewrite source. */
memset(&sa, 0, sizeof(sa));
sa.sin6_family = AF_INET6;
sa.sin6_port = bpf_htons(0);
sa.sin6_addr.s6_addr32[0] = bpf_htonl(SRC_REWRITE_IP6_0);
sa.sin6_addr.s6_addr32[1] = bpf_htonl(SRC_REWRITE_IP6_1);
sa.sin6_addr.s6_addr32[2] = bpf_htonl(SRC_REWRITE_IP6_2);
sa.sin6_addr.s6_addr32[3] = bpf_htonl(SRC_REWRITE_IP6_3);
if (bpf_bind(ctx, (struct sockaddr *)&sa, sizeof(sa)) != 0)
return 0;
}
return 1;
}
char _license[] SEC("license") = "GPL";
+60
View File
@@ -0,0 +1,60 @@
/* Copyright (c) 2017 Facebook
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of version 2 of the GNU General Public
* License as published by the Free Software Foundation.
*/
#include <linux/bpf.h>
#include <linux/version.h>
#include "bpf_helpers.h"
SEC("cgroup/dev")
int bpf_prog1(struct bpf_cgroup_dev_ctx *ctx)
{
short type = ctx->access_type & 0xFFFF;
#ifdef DEBUG
short access = ctx->access_type >> 16;
char fmt[] = " %d:%d \n";
switch (type) {
case BPF_DEVCG_DEV_BLOCK:
fmt[0] = 'b';
break;
case BPF_DEVCG_DEV_CHAR:
fmt[0] = 'c';
break;
default:
fmt[0] = '?';
break;
}
if (access & BPF_DEVCG_ACC_READ)
fmt[8] = 'r';
if (access & BPF_DEVCG_ACC_WRITE)
fmt[9] = 'w';
if (access & BPF_DEVCG_ACC_MKNOD)
fmt[10] = 'm';
bpf_trace_printk(fmt, sizeof(fmt), ctx->major, ctx->minor);
#endif
/* Allow access to /dev/zero and /dev/random.
* Forbid everything else.
*/
if (ctx->major != 1 || type != BPF_DEVCG_DEV_CHAR)
return 0;
switch (ctx->minor) {
case 5: /* 1:5 /dev/zero */
case 9: /* 1:9 /dev/urandom */
return 1;
}
return 0;
}
char _license[] SEC("license") = "GPL";
__u32 _version SEC("version") = LINUX_VERSION_CODE;
@@ -0,0 +1,40 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 Facebook
#include <linux/bpf.h>
#include "bpf_helpers.h"
struct bpf_map_def SEC("maps") cg_ids = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(__u64),
.max_entries = 1,
};
struct bpf_map_def SEC("maps") pidmap = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(__u32),
.max_entries = 1,
};
SEC("tracepoint/syscalls/sys_enter_nanosleep")
int trace(void *ctx)
{
__u32 pid = bpf_get_current_pid_tgid();
__u32 key = 0, *expected_pid;
__u64 *val;
expected_pid = bpf_map_lookup_elem(&pidmap, &key);
if (!expected_pid || *expected_pid != pid)
return 0;
val = bpf_map_lookup_elem(&cg_ids, &key);
if (val)
*val = bpf_get_current_cgroup_id();
return 0;
}
char _license[] SEC("license") = "GPL";
__u32 _version SEC("version") = 1; /* ignored by tracepoints, required by libbpf.a */
@@ -0,0 +1,149 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 Facebook
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <fcntl.h>
#include <syscall.h>
#include <unistd.h>
#include <linux/perf_event.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <linux/bpf.h>
#include <bpf/bpf.h>
#include <bpf/libbpf.h>
#include "cgroup_helpers.h"
#include "bpf_rlimit.h"
#define CHECK(condition, tag, format...) ({ \
int __ret = !!(condition); \
if (__ret) { \
printf("%s:FAIL:%s ", __func__, tag); \
printf(format); \
} else { \
printf("%s:PASS:%s\n", __func__, tag); \
} \
__ret; \
})
static int bpf_find_map(const char *test, struct bpf_object *obj,
const char *name)
{
struct bpf_map *map;
map = bpf_object__find_map_by_name(obj, name);
if (!map)
return -1;
return bpf_map__fd(map);
}
#define TEST_CGROUP "/test-bpf-get-cgroup-id/"
int main(int argc, char **argv)
{
const char *probe_name = "syscalls/sys_enter_nanosleep";
const char *file = "get_cgroup_id_kern.o";
int err, bytes, efd, prog_fd, pmu_fd;
int cgroup_fd, cgidmap_fd, pidmap_fd;
struct perf_event_attr attr = {};
struct bpf_object *obj;
__u64 kcgid = 0, ucgid;
__u32 key = 0, pid;
int exit_code = 1;
char buf[256];
err = setup_cgroup_environment();
if (CHECK(err, "setup_cgroup_environment", "err %d errno %d\n", err,
errno))
return 1;
cgroup_fd = create_and_get_cgroup(TEST_CGROUP);
if (CHECK(cgroup_fd < 0, "create_and_get_cgroup", "err %d errno %d\n",
cgroup_fd, errno))
goto cleanup_cgroup_env;
err = join_cgroup(TEST_CGROUP);
if (CHECK(err, "join_cgroup", "err %d errno %d\n", err, errno))
goto cleanup_cgroup_env;
err = bpf_prog_load(file, BPF_PROG_TYPE_TRACEPOINT, &obj, &prog_fd);
if (CHECK(err, "bpf_prog_load", "err %d errno %d\n", err, errno))
goto cleanup_cgroup_env;
cgidmap_fd = bpf_find_map(__func__, obj, "cg_ids");
if (CHECK(cgidmap_fd < 0, "bpf_find_map", "err %d errno %d\n",
cgidmap_fd, errno))
goto close_prog;
pidmap_fd = bpf_find_map(__func__, obj, "pidmap");
if (CHECK(pidmap_fd < 0, "bpf_find_map", "err %d errno %d\n",
pidmap_fd, errno))
goto close_prog;
pid = getpid();
bpf_map_update_elem(pidmap_fd, &key, &pid, 0);
snprintf(buf, sizeof(buf),
"/sys/kernel/debug/tracing/events/%s/id", probe_name);
efd = open(buf, O_RDONLY, 0);
if (CHECK(efd < 0, "open", "err %d errno %d\n", efd, errno))
goto close_prog;
bytes = read(efd, buf, sizeof(buf));
close(efd);
if (CHECK(bytes <= 0 || bytes >= sizeof(buf), "read",
"bytes %d errno %d\n", bytes, errno))
goto close_prog;
attr.config = strtol(buf, NULL, 0);
attr.type = PERF_TYPE_TRACEPOINT;
attr.sample_type = PERF_SAMPLE_RAW;
attr.sample_period = 1;
attr.wakeup_events = 1;
/* attach to this pid so the all bpf invocations will be in the
* cgroup associated with this pid.
*/
pmu_fd = syscall(__NR_perf_event_open, &attr, getpid(), -1, -1, 0);
if (CHECK(pmu_fd < 0, "perf_event_open", "err %d errno %d\n", pmu_fd,
errno))
goto close_prog;
err = ioctl(pmu_fd, PERF_EVENT_IOC_ENABLE, 0);
if (CHECK(err, "perf_event_ioc_enable", "err %d errno %d\n", err,
errno))
goto close_pmu;
err = ioctl(pmu_fd, PERF_EVENT_IOC_SET_BPF, prog_fd);
if (CHECK(err, "perf_event_ioc_set_bpf", "err %d errno %d\n", err,
errno))
goto close_pmu;
/* trigger some syscalls */
sleep(1);
err = bpf_map_lookup_elem(cgidmap_fd, &key, &kcgid);
if (CHECK(err, "bpf_map_lookup_elem", "err %d errno %d\n", err, errno))
goto close_pmu;
ucgid = get_cgroup_id(TEST_CGROUP);
if (CHECK(kcgid != ucgid, "compare_cgroup_id",
"kern cgid %llx user cgid %llx", kcgid, ucgid))
goto close_pmu;
exit_code = 0;
printf("%s:PASS\n", argv[0]);
close_pmu:
close(pmu_fd);
close_prog:
bpf_object__close(obj);
cleanup_cgroup_env:
cleanup_cgroup_environment();
return exit_code;
}
+1
View File
@@ -0,0 +1 @@
/* dummy .h to trick /usr/include/features.h to work with 'clang -target bpf' */
@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: GPL-2.0 */
#ifndef _UAPI_LINUX_TYPES_H
#define _UAPI_LINUX_TYPES_H
#include <asm-generic/int-ll64.h>
/* copied from linux:include/uapi/linux/types.h */
#define __bitwise
typedef __u16 __bitwise __le16;
typedef __u16 __bitwise __be16;
typedef __u32 __bitwise __le32;
typedef __u32 __bitwise __be32;
typedef __u64 __bitwise __le64;
typedef __u64 __bitwise __be64;
typedef __u16 __bitwise __sum16;
typedef __u32 __bitwise __wsum;
#define __aligned_u64 __u64 __attribute__((aligned(8)))
#define __aligned_be64 __be64 __attribute__((aligned(8)))
#define __aligned_le64 __le64 __attribute__((aligned(8)))
#endif /* _UAPI_LINUX_TYPES_H */
@@ -0,0 +1,34 @@
/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */
#include <linux/bpf.h>
#include "bpf_helpers.h"
struct bpf_map_def SEC("maps") htab = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(__u32),
.value_size = sizeof(long),
.max_entries = 2,
};
struct bpf_map_def SEC("maps") array = {
.type = BPF_MAP_TYPE_ARRAY,
.key_size = sizeof(__u32),
.value_size = sizeof(long),
.max_entries = 2,
};
/* Sample program which should always load for testing control paths. */
SEC(".text") int func()
{
__u64 key64 = 0;
__u32 key = 0;
long *value;
value = bpf_map_lookup_elem(&htab, &key);
if (!value)
return 1;
value = bpf_map_lookup_elem(&array, &key64);
if (!value)
return 1;
return 0;
}
@@ -0,0 +1,7 @@
/* SPDX-License-Identifier: (GPL-2.0 OR BSD-2-Clause) */
/* Sample program which should always load for testing control paths. */
int func()
{
return 0;
}
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 Facebook
#include <linux/stddef.h>
#include <linux/bpf.h>
#include <sys/socket.h>
#include "bpf_helpers.h"
#include "bpf_endian.h"
#define SRC1_IP4 0xAC100001U /* 172.16.0.1 */
#define SRC2_IP4 0x00000000U
#define SRC_REWRITE_IP4 0x7f000004U
#define DST_IP4 0xC0A801FEU /* 192.168.1.254 */
#define DST_REWRITE_IP4 0x7f000001U
#define DST_PORT 4040
#define DST_REWRITE_PORT4 4444
int _version SEC("version") = 1;
SEC("cgroup/sendmsg4")
int sendmsg_v4_prog(struct bpf_sock_addr *ctx)
{
if (ctx->type != SOCK_DGRAM)
return 0;
/* Rewrite source. */
if (ctx->msg_src_ip4 == bpf_htonl(SRC1_IP4) ||
ctx->msg_src_ip4 == bpf_htonl(SRC2_IP4)) {
ctx->msg_src_ip4 = bpf_htonl(SRC_REWRITE_IP4);
} else {
/* Unexpected source. Reject sendmsg. */
return 0;
}
/* Rewrite destination. */
if ((ctx->user_ip4 >> 24) == (bpf_htonl(DST_IP4) >> 24) &&
ctx->user_port == bpf_htons(DST_PORT)) {
ctx->user_ip4 = bpf_htonl(DST_REWRITE_IP4);
ctx->user_port = bpf_htons(DST_REWRITE_PORT4);
} else {
/* Unexpected source. Reject sendmsg. */
return 0;
}
return 1;
}
char _license[] SEC("license") = "GPL";
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 Facebook
#include <linux/stddef.h>
#include <linux/bpf.h>
#include <sys/socket.h>
#include "bpf_helpers.h"
#include "bpf_endian.h"
#define SRC_REWRITE_IP6_0 0
#define SRC_REWRITE_IP6_1 0
#define SRC_REWRITE_IP6_2 0
#define SRC_REWRITE_IP6_3 6
#define DST_REWRITE_IP6_0 0
#define DST_REWRITE_IP6_1 0
#define DST_REWRITE_IP6_2 0
#define DST_REWRITE_IP6_3 1
#define DST_REWRITE_PORT6 6666
int _version SEC("version") = 1;
SEC("cgroup/sendmsg6")
int sendmsg_v6_prog(struct bpf_sock_addr *ctx)
{
if (ctx->type != SOCK_DGRAM)
return 0;
/* Rewrite source. */
if (ctx->msg_src_ip6[3] == bpf_htonl(1) ||
ctx->msg_src_ip6[3] == bpf_htonl(0)) {
ctx->msg_src_ip6[0] = bpf_htonl(SRC_REWRITE_IP6_0);
ctx->msg_src_ip6[1] = bpf_htonl(SRC_REWRITE_IP6_1);
ctx->msg_src_ip6[2] = bpf_htonl(SRC_REWRITE_IP6_2);
ctx->msg_src_ip6[3] = bpf_htonl(SRC_REWRITE_IP6_3);
} else {
/* Unexpected source. Reject sendmsg. */
return 0;
}
/* Rewrite destination. */
if (ctx->user_ip6[0] == bpf_htonl(0xFACEB00C)) {
ctx->user_ip6[0] = bpf_htonl(DST_REWRITE_IP6_0);
ctx->user_ip6[1] = bpf_htonl(DST_REWRITE_IP6_1);
ctx->user_ip6[2] = bpf_htonl(DST_REWRITE_IP6_2);
ctx->user_ip6[3] = bpf_htonl(DST_REWRITE_IP6_3);
ctx->user_port = bpf_htons(DST_REWRITE_PORT6);
} else {
/* Unexpected destination. Reject sendmsg. */
return 0;
}
return 1;
}
char _license[] SEC("license") = "GPL";
@@ -0,0 +1,60 @@
// SPDX-License-Identifier: GPL-2.0
// Copyright (c) 2018 Facebook
#include <linux/bpf.h>
#include <sys/socket.h>
#include "bpf_helpers.h"
#include "bpf_endian.h"
struct bpf_map_def SEC("maps") socket_cookies = {
.type = BPF_MAP_TYPE_HASH,
.key_size = sizeof(__u64),
.value_size = sizeof(__u32),
.max_entries = 1 << 8,
};
SEC("cgroup/connect6")
int set_cookie(struct bpf_sock_addr *ctx)
{
__u32 cookie_value = 0xFF;
__u64 cookie_key;
if (ctx->family != AF_INET6 || ctx->user_family != AF_INET6)
return 1;
cookie_key = bpf_get_socket_cookie(ctx);
if (bpf_map_update_elem(&socket_cookies, &cookie_key, &cookie_value, 0))
return 0;
return 1;
}
SEC("sockops")
int update_cookie(struct bpf_sock_ops *ctx)
{
__u32 new_cookie_value;
__u32 *cookie_value;
__u64 cookie_key;
if (ctx->family != AF_INET6)
return 1;
if (ctx->op != BPF_SOCK_OPS_TCP_CONNECT_CB)
return 1;
cookie_key = bpf_get_socket_cookie(ctx);
cookie_value = bpf_map_lookup_elem(&socket_cookies, &cookie_key);
if (!cookie_value)
return 1;
new_cookie_value = (ctx->local_port << 8) | *cookie_value;
bpf_map_update_elem(&socket_cookies, &cookie_key, &new_cookie_value, 0);
return 1;
}
int _version SEC("version") = 1;
char _license[] SEC("license") = "GPL";
Loaded 100 of 80519 files, more files were not shown because too many files have changed in this diff. Show more