Merge 96f4263568 ("Merge tag 'rust-6.2' of https://github.com/Rust-for-Linux/linux") into android-mainline
Steps on the way to 6.2-rc1 Change-Id: I6b8404556e70204fee5a5bce822d6a8ea71ef03c Signed-off-by: Greg Kroah-Hartman <gregkh@google.com>
This commit is contained in:
@@ -405,37 +405,55 @@ Unit Tests for amd-pstate
|
||||
|
||||
1. Test case decriptions
|
||||
|
||||
1). Basic tests
|
||||
|
||||
Test prerequisite and basic functions for the ``amd-pstate`` driver.
|
||||
|
||||
+---------+--------------------------------+------------------------------------------------------------------------------------+
|
||||
| Index | Functions | Description |
|
||||
+=========+================================+====================================================================================+
|
||||
| 0 | amd_pstate_ut_acpi_cpc_valid || Check whether the _CPC object is present in SBIOS. |
|
||||
| 1 | amd_pstate_ut_acpi_cpc_valid || Check whether the _CPC object is present in SBIOS. |
|
||||
| | || |
|
||||
| | || The detail refer to `Processor Support <processor_support_>`_. |
|
||||
+---------+--------------------------------+------------------------------------------------------------------------------------+
|
||||
| 1 | amd_pstate_ut_check_enabled || Check whether AMD P-State is enabled. |
|
||||
| 2 | amd_pstate_ut_check_enabled || Check whether AMD P-State is enabled. |
|
||||
| | || |
|
||||
| | || AMD P-States and ACPI hardware P-States always can be supported in one processor. |
|
||||
| | | But AMD P-States has the higher priority and if it is enabled with |
|
||||
| | | :c:macro:`MSR_AMD_CPPC_ENABLE` or ``cppc_set_enable``, it will respond to the |
|
||||
| | | request from AMD P-States. |
|
||||
+---------+--------------------------------+------------------------------------------------------------------------------------+
|
||||
| 2 | amd_pstate_ut_check_perf || Check if the each performance values are reasonable. |
|
||||
| 3 | amd_pstate_ut_check_perf || Check if the each performance values are reasonable. |
|
||||
| | || highest_perf >= nominal_perf > lowest_nonlinear_perf > lowest_perf > 0. |
|
||||
+---------+--------------------------------+------------------------------------------------------------------------------------+
|
||||
| 3 | amd_pstate_ut_check_freq || Check if the each frequency values and max freq when set support boost mode |
|
||||
| 4 | amd_pstate_ut_check_freq || Check if the each frequency values and max freq when set support boost mode |
|
||||
| | | are reasonable. |
|
||||
| | || max_freq >= nominal_freq > lowest_nonlinear_freq > min_freq > 0 |
|
||||
| | || If boost is not active but supported, this maximum frequency will be larger than |
|
||||
| | | the one in ``cpuinfo``. |
|
||||
+---------+--------------------------------+------------------------------------------------------------------------------------+
|
||||
|
||||
2). Tbench test
|
||||
|
||||
Test and monitor the cpu changes when running tbench benchmark under the specified governor.
|
||||
These changes include desire performance, frequency, load, performance, energy etc.
|
||||
The specified governor is ondemand or schedutil.
|
||||
Tbench can also be tested on the ``acpi-cpufreq`` kernel driver for comparison.
|
||||
|
||||
3). Gitsource test
|
||||
|
||||
Test and monitor the cpu changes when running gitsource benchmark under the specified governor.
|
||||
These changes include desire performance, frequency, load, time, energy etc.
|
||||
The specified governor is ondemand or schedutil.
|
||||
Gitsource can also be tested on the ``acpi-cpufreq`` kernel driver for comparison.
|
||||
|
||||
#. How to execute the tests
|
||||
|
||||
We use test module in the kselftest frameworks to implement it.
|
||||
We create amd-pstate-ut module and tie it into kselftest.(for
|
||||
We create ``amd-pstate-ut`` module and tie it into kselftest.(for
|
||||
details refer to Linux Kernel Selftests [4]_).
|
||||
|
||||
1. Build
|
||||
1). Build
|
||||
|
||||
+ open the :c:macro:`CONFIG_X86_AMD_PSTATE` configuration option.
|
||||
+ set the :c:macro:`CONFIG_X86_AMD_PSTATE_UT` configuration option to M.
|
||||
@@ -445,23 +463,159 @@ Unit Tests for amd-pstate
|
||||
$ cd linux
|
||||
$ make -C tools/testing/selftests
|
||||
|
||||
#. Installation & Steps ::
|
||||
+ make perf ::
|
||||
|
||||
$ cd tools/perf/
|
||||
$ make
|
||||
|
||||
|
||||
2). Installation & Steps ::
|
||||
|
||||
$ make -C tools/testing/selftests install INSTALL_PATH=~/kselftest
|
||||
$ cp tools/perf/perf /usr/bin/perf
|
||||
$ sudo ./kselftest/run_kselftest.sh -c amd-pstate
|
||||
TAP version 13
|
||||
1..1
|
||||
# selftests: amd-pstate: amd-pstate-ut.sh
|
||||
# amd-pstate-ut: ok
|
||||
ok 1 selftests: amd-pstate: amd-pstate-ut.sh
|
||||
|
||||
#. Results ::
|
||||
3). Specified test case ::
|
||||
|
||||
$ dmesg | grep "amd_pstate_ut" | tee log.txt
|
||||
[12977.570663] amd_pstate_ut: 1 amd_pstate_ut_acpi_cpc_valid success!
|
||||
[12977.570673] amd_pstate_ut: 2 amd_pstate_ut_check_enabled success!
|
||||
[12977.571207] amd_pstate_ut: 3 amd_pstate_ut_check_perf success!
|
||||
[12977.571212] amd_pstate_ut: 4 amd_pstate_ut_check_freq success!
|
||||
$ cd ~/kselftest/amd-pstate
|
||||
$ sudo ./run.sh -t basic
|
||||
$ sudo ./run.sh -t tbench
|
||||
$ sudo ./run.sh -t tbench -m acpi-cpufreq
|
||||
$ sudo ./run.sh -t gitsource
|
||||
$ sudo ./run.sh -t gitsource -m acpi-cpufreq
|
||||
$ ./run.sh --help
|
||||
./run.sh: illegal option -- -
|
||||
Usage: ./run.sh [OPTION...]
|
||||
[-h <help>]
|
||||
[-o <output-file-for-dump>]
|
||||
[-c <all: All testing,
|
||||
basic: Basic testing,
|
||||
tbench: Tbench testing,
|
||||
gitsource: Gitsource testing.>]
|
||||
[-t <tbench time limit>]
|
||||
[-p <tbench process number>]
|
||||
[-l <loop times for tbench>]
|
||||
[-i <amd tracer interval>]
|
||||
[-m <comparative test: acpi-cpufreq>]
|
||||
|
||||
|
||||
4). Results
|
||||
|
||||
+ basic
|
||||
|
||||
When you finish test, you will get the following log info ::
|
||||
|
||||
$ dmesg | grep "amd_pstate_ut" | tee log.txt
|
||||
[12977.570663] amd_pstate_ut: 1 amd_pstate_ut_acpi_cpc_valid success!
|
||||
[12977.570673] amd_pstate_ut: 2 amd_pstate_ut_check_enabled success!
|
||||
[12977.571207] amd_pstate_ut: 3 amd_pstate_ut_check_perf success!
|
||||
[12977.571212] amd_pstate_ut: 4 amd_pstate_ut_check_freq success!
|
||||
|
||||
+ tbench
|
||||
|
||||
When you finish test, you will get selftest.tbench.csv and png images.
|
||||
The selftest.tbench.csv file contains the raw data and the drop of the comparative test.
|
||||
The png images shows the performance, energy and performan per watt of each test.
|
||||
Open selftest.tbench.csv :
|
||||
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ Governor | Round | Des-perf | Freq | Load | Performance | Energy | Performance Per Watt |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ Unit | | | GHz | | MB/s | J | MB/J |
|
||||
+=================================================+==============+==========+=========+==========+=============+=========+======================+
|
||||
+ amd-pstate-ondemand | 1 | | | | 2504.05 | 1563.67 | 158.5378 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-ondemand | 2 | | | | 2243.64 | 1430.32 | 155.2941 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-ondemand | 3 | | | | 2183.88 | 1401.32 | 154.2860 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-ondemand | Average | | | | 2310.52 | 1465.1 | 156.1268 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-schedutil | 1 | 165.329 | 1.62257 | 99.798 | 2136.54 | 1395.26 | 151.5971 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-schedutil | 2 | 166 | 1.49761 | 99.9993 | 2100.56 | 1380.5 | 150.6377 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-schedutil | 3 | 166 | 1.47806 | 99.9993 | 2084.12 | 1375.76 | 149.9737 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-schedutil | Average | 165.776 | 1.53275 | 99.9322 | 2107.07 | 1383.84 | 150.7399 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-ondemand | 1 | | | | 2529.9 | 1564.4 | 160.0997 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-ondemand | 2 | | | | 2249.76 | 1432.97 | 155.4297 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-ondemand | 3 | | | | 2181.46 | 1406.88 | 153.5060 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-ondemand | Average | | | | 2320.37 | 1468.08 | 156.4741 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-schedutil | 1 | | | | 2137.64 | 1385.24 | 152.7723 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-schedutil | 2 | | | | 2107.05 | 1372.23 | 152.0138 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-schedutil | 3 | | | | 2085.86 | 1365.35 | 151.2433 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-schedutil | Average | | | | 2110.18 | 1374.27 | 152.0136 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-ondemand VS acpi-cpufreq-schedutil | Comprison(%) | | | | -9.0584 | -6.3899 | -2.8506 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-ondemand VS amd-pstate-schedutil | Comprison(%) | | | | 8.8053 | -5.5463 | -3.4503 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-ondemand VS amd-pstate-ondemand | Comprison(%) | | | | -0.4245 | -0.2029 | -0.2219 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-schedutil VS amd-pstate-schedutil | Comprison(%) | | | | -0.1473 | 0.6963 | -0.8378 |
|
||||
+-------------------------------------------------+--------------+----------+---------+----------+-------------+---------+----------------------+
|
||||
|
||||
+ gitsource
|
||||
|
||||
When you finish test, you will get selftest.gitsource.csv and png images.
|
||||
The selftest.gitsource.csv file contains the raw data and the drop of the comparative test.
|
||||
The png images shows the performance, energy and performan per watt of each test.
|
||||
Open selftest.gitsource.csv :
|
||||
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ Governor | Round | Des-perf | Freq | Load | Time | Energy | Performance Per Watt |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ Unit | | | GHz | | s | J | 1/J |
|
||||
+=================================================+==============+==========+==========+==========+=============+=========+======================+
|
||||
+ amd-pstate-ondemand | 1 | 50.119 | 2.10509 | 23.3076 | 475.69 | 865.78 | 0.001155027 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-ondemand | 2 | 94.8006 | 1.98771 | 56.6533 | 467.1 | 839.67 | 0.001190944 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-ondemand | 3 | 76.6091 | 2.53251 | 43.7791 | 467.69 | 855.85 | 0.001168429 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-ondemand | Average | 73.8429 | 2.20844 | 41.2467 | 470.16 | 853.767 | 0.001171279 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-schedutil | 1 | 165.919 | 1.62319 | 98.3868 | 464.17 | 866.8 | 0.001153668 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-schedutil | 2 | 165.97 | 1.31309 | 99.5712 | 480.15 | 880.4 | 0.001135847 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-schedutil | 3 | 165.973 | 1.28448 | 99.9252 | 481.79 | 867.02 | 0.001153375 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-schedutil | Average | 165.954 | 1.40692 | 99.2944 | 475.37 | 871.407 | 0.001147569 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-ondemand | 1 | | | | 2379.62 | 742.96 | 0.001345967 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-ondemand | 2 | | | | 441.74 | 817.49 | 0.001223256 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-ondemand | 3 | | | | 455.48 | 820.01 | 0.001219497 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-ondemand | Average | | | | 425.613 | 793.487 | 0.001260260 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-schedutil | 1 | | | | 459.69 | 838.54 | 0.001192548 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-schedutil | 2 | | | | 466.55 | 830.89 | 0.001203528 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-schedutil | 3 | | | | 470.38 | 837.32 | 0.001194286 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-schedutil | Average | | | | 465.54 | 835.583 | 0.001196769 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-ondemand VS acpi-cpufreq-schedutil | Comprison(%) | | | | 9.3810 | 5.3051 | -5.0379 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ amd-pstate-ondemand VS amd-pstate-schedutil | Comprison(%) | 124.7392 | -36.2934 | 140.7329 | 1.1081 | 2.0661 | -2.0242 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-ondemand VS amd-pstate-ondemand | Comprison(%) | | | | 10.4665 | 7.5968 | -7.0605 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
+ acpi-cpufreq-schedutil VS amd-pstate-schedutil | Comprison(%) | | | | 2.1115 | 4.2873 | -4.1110 |
|
||||
+-------------------------------------------------+--------------+----------+----------+----------+-------------+---------+----------------------+
|
||||
|
||||
Reference
|
||||
===========
|
||||
|
||||
@@ -80,8 +80,8 @@ have the number 1 and the number then must increase by 1 for each additional
|
||||
subtest within the same test at the same nesting level.
|
||||
|
||||
The description is a description of the test, generally the name of
|
||||
the test, and can be any string of words (can't include #). The
|
||||
description is optional, but recommended.
|
||||
the test, and can be any string of characters other than # or a
|
||||
newline. The description is optional, but recommended.
|
||||
|
||||
The directive and any diagnostic data is optional. If either are present, they
|
||||
must follow a hash sign, "#".
|
||||
|
||||
@@ -4,16 +4,17 @@
|
||||
KUnit Architecture
|
||||
==================
|
||||
|
||||
The KUnit architecture can be divided into two parts:
|
||||
The KUnit architecture is divided into two parts:
|
||||
|
||||
- `In-Kernel Testing Framework`_
|
||||
- `kunit_tool (Command Line Test Harness)`_
|
||||
- `kunit_tool (Command-line Test Harness)`_
|
||||
|
||||
In-Kernel Testing Framework
|
||||
===========================
|
||||
|
||||
The kernel testing library supports KUnit tests written in C using
|
||||
KUnit. KUnit tests are kernel code. KUnit does several things:
|
||||
KUnit. These KUnit tests are kernel code. KUnit performs the following
|
||||
tasks:
|
||||
|
||||
- Organizes tests
|
||||
- Reports test results
|
||||
@@ -22,19 +23,17 @@ KUnit. KUnit tests are kernel code. KUnit does several things:
|
||||
Test Cases
|
||||
----------
|
||||
|
||||
The fundamental unit in KUnit is the test case. The KUnit test cases are
|
||||
grouped into KUnit suites. A KUnit test case is a function with type
|
||||
signature ``void (*)(struct kunit *test)``.
|
||||
These test case functions are wrapped in a struct called
|
||||
struct kunit_case.
|
||||
The test case is the fundamental unit in KUnit. KUnit test cases are organised
|
||||
into suites. A KUnit test case is a function with type signature
|
||||
``void (*)(struct kunit *test)``. These test case functions are wrapped in a
|
||||
struct called struct kunit_case.
|
||||
|
||||
.. note:
|
||||
``generate_params`` is optional for non-parameterized tests.
|
||||
|
||||
Each KUnit test case gets a ``struct kunit`` context
|
||||
object passed to it that tracks a running test. The KUnit assertion
|
||||
macros and other KUnit utilities use the ``struct kunit`` context
|
||||
object. As an exception, there are two fields:
|
||||
Each KUnit test case receives a ``struct kunit`` context object that tracks a
|
||||
running test. The KUnit assertion macros and other KUnit utilities use the
|
||||
``struct kunit`` context object. As an exception, there are two fields:
|
||||
|
||||
- ``->priv``: The setup functions can use it to store arbitrary test
|
||||
user data.
|
||||
@@ -77,12 +76,13 @@ Executor
|
||||
|
||||
The KUnit executor can list and run built-in KUnit tests on boot.
|
||||
The Test suites are stored in a linker section
|
||||
called ``.kunit_test_suites``. For code, see:
|
||||
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/asm-generic/vmlinux.lds.h?h=v5.15#n945.
|
||||
called ``.kunit_test_suites``. For the code, see ``KUNIT_TABLE()`` macro
|
||||
definition in
|
||||
`include/asm-generic/vmlinux.lds.h <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/include/asm-generic/vmlinux.lds.h?h=v6.0#n950>`_.
|
||||
The linker section consists of an array of pointers to
|
||||
``struct kunit_suite``, and is populated by the ``kunit_test_suites()``
|
||||
macro. To run all tests compiled into the kernel, the KUnit executor
|
||||
iterates over the linker section array.
|
||||
macro. The KUnit executor iterates over the linker section array in order to
|
||||
run all the tests that are compiled into the kernel.
|
||||
|
||||
.. kernel-figure:: kunit_suitememorydiagram.svg
|
||||
:alt: KUnit Suite Memory
|
||||
@@ -90,17 +90,17 @@ iterates over the linker section array.
|
||||
KUnit Suite Memory Diagram
|
||||
|
||||
On the kernel boot, the KUnit executor uses the start and end addresses
|
||||
of this section to iterate over and run all tests. For code, see:
|
||||
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/lib/kunit/executor.c
|
||||
|
||||
of this section to iterate over and run all tests. For the implementation of the
|
||||
executor, see
|
||||
`lib/kunit/executor.c <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/lib/kunit/executor.c>`_.
|
||||
When built as a module, the ``kunit_test_suites()`` macro defines a
|
||||
``module_init()`` function, which runs all the tests in the compilation
|
||||
unit instead of utilizing the executor.
|
||||
|
||||
In KUnit tests, some error classes do not affect other tests
|
||||
or parts of the kernel, each KUnit case executes in a separate thread
|
||||
context. For code, see:
|
||||
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/lib/kunit/try-catch.c?h=v5.15#n58
|
||||
context. See the ``kunit_try_catch_run()`` function in
|
||||
`lib/kunit/try-catch.c <https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/lib/kunit/try-catch.c?h=v5.15#n58>`_.
|
||||
|
||||
Assertion Macros
|
||||
----------------
|
||||
@@ -111,37 +111,36 @@ All expectations/assertions are formatted as:
|
||||
|
||||
- ``{EXPECT|ASSERT}`` determines whether the check is an assertion or an
|
||||
expectation.
|
||||
In the event of a failure, the testing flow differs as follows:
|
||||
|
||||
- For an expectation, if the check fails, marks the test as failed
|
||||
and logs the failure.
|
||||
- For expectations, the test is marked as failed and the failure is logged.
|
||||
|
||||
- An assertion, on failure, causes the test case to terminate
|
||||
immediately.
|
||||
- Failing assertions, on the other hand, result in the test case being
|
||||
terminated immediately.
|
||||
|
||||
- Assertions call function:
|
||||
- Assertions call the function:
|
||||
``void __noreturn kunit_abort(struct kunit *)``.
|
||||
|
||||
- ``kunit_abort`` calls function:
|
||||
- ``kunit_abort`` calls the function:
|
||||
``void __noreturn kunit_try_catch_throw(struct kunit_try_catch *try_catch)``.
|
||||
|
||||
- ``kunit_try_catch_throw`` calls function:
|
||||
- ``kunit_try_catch_throw`` calls the function:
|
||||
``void kthread_complete_and_exit(struct completion *, long) __noreturn;``
|
||||
and terminates the special thread context.
|
||||
|
||||
- ``<op>`` denotes a check with options: ``TRUE`` (supplied property
|
||||
has the boolean value “true”), ``EQ`` (two supplied properties are
|
||||
has the boolean value "true"), ``EQ`` (two supplied properties are
|
||||
equal), ``NOT_ERR_OR_NULL`` (supplied pointer is not null and does not
|
||||
contain an “err” value).
|
||||
contain an "err" value).
|
||||
|
||||
- ``[_MSG]`` prints a custom message on failure.
|
||||
|
||||
Test Result Reporting
|
||||
---------------------
|
||||
KUnit prints test results in KTAP format. KTAP is based on TAP14, see:
|
||||
https://github.com/isaacs/testanything.github.io/blob/tap14/tap-version-14-specification.md.
|
||||
KTAP (yet to be standardized format) works with KUnit and Kselftest.
|
||||
The KUnit executor prints KTAP results to dmesg, and debugfs
|
||||
(if configured).
|
||||
KUnit prints the test results in KTAP format. KTAP is based on TAP14, see
|
||||
Documentation/dev-tools/ktap.rst.
|
||||
KTAP works with KUnit and Kselftest. The KUnit executor prints KTAP results to
|
||||
dmesg, and debugfs (if configured).
|
||||
|
||||
Parameterized Tests
|
||||
-------------------
|
||||
@@ -150,33 +149,35 @@ Each KUnit parameterized test is associated with a collection of
|
||||
parameters. The test is invoked multiple times, once for each parameter
|
||||
value and the parameter is stored in the ``param_value`` field.
|
||||
The test case includes a KUNIT_CASE_PARAM() macro that accepts a
|
||||
generator function.
|
||||
The generator function is passed the previous parameter and returns the next
|
||||
parameter. It also provides a macro to generate common-case generators based on
|
||||
arrays.
|
||||
generator function. The generator function is passed the previous parameter
|
||||
and returns the next parameter. It also includes a macro for generating
|
||||
array-based common-case generators.
|
||||
|
||||
kunit_tool (Command Line Test Harness)
|
||||
kunit_tool (Command-line Test Harness)
|
||||
======================================
|
||||
|
||||
kunit_tool is a Python script ``(tools/testing/kunit/kunit.py)``
|
||||
that can be used to configure, build, exec, parse and run (runs other
|
||||
commands in order) test results. You can either run KUnit tests using
|
||||
kunit_tool or can include KUnit in kernel and parse manually.
|
||||
``kunit_tool`` is a Python script, found in ``tools/testing/kunit/kunit.py``. It
|
||||
is used to configure, build, execute, parse test results and run all of the
|
||||
previous commands in correct order (i.e., configure, build, execute and parse).
|
||||
You have two options for running KUnit tests: either build the kernel with KUnit
|
||||
enabled and manually parse the results (see
|
||||
Documentation/dev-tools/kunit/run_manual.rst) or use ``kunit_tool``
|
||||
(see Documentation/dev-tools/kunit/run_wrapper.rst).
|
||||
|
||||
- ``configure`` command generates the kernel ``.config`` from a
|
||||
``.kunitconfig`` file (and any architecture-specific options).
|
||||
For some architectures, additional config options are specified in the
|
||||
``qemu_config`` Python script
|
||||
(For example: ``tools/testing/kunit/qemu_configs/powerpc.py``).
|
||||
The Python scripts available in ``qemu_configs`` folder
|
||||
(for example, ``tools/testing/kunit/qemu configs/powerpc.py``) contains
|
||||
additional configuration options for specific architectures.
|
||||
It parses both the existing ``.config`` and the ``.kunitconfig`` files
|
||||
and ensures that ``.config`` is a superset of ``.kunitconfig``.
|
||||
If this is not the case, it will combine the two and run
|
||||
``make olddefconfig`` to regenerate the ``.config`` file. It then
|
||||
verifies that ``.config`` is now a superset. This checks if all
|
||||
Kconfig dependencies are correctly specified in ``.kunitconfig``.
|
||||
``kunit_config.py`` includes the parsing Kconfigs code. The code which
|
||||
runs ``make olddefconfig`` is a part of ``kunit_kernel.py``. You can
|
||||
invoke this command via: ``./tools/testing/kunit/kunit.py config`` and
|
||||
to ensure that ``.config`` is a superset of ``.kunitconfig``.
|
||||
If not, it will combine the two and run ``make olddefconfig`` to regenerate
|
||||
the ``.config`` file. It then checks to see if ``.config`` has become a superset.
|
||||
This verifies that all the Kconfig dependencies are correctly specified in the
|
||||
file ``.kunitconfig``. The ``kunit_config.py`` script contains the code for parsing
|
||||
Kconfigs. The code which runs ``make olddefconfig`` is part of the
|
||||
``kunit_kernel.py`` script. You can invoke this command through:
|
||||
``./tools/testing/kunit/kunit.py config`` and
|
||||
generate a ``.config`` file.
|
||||
- ``build`` runs ``make`` on the kernel tree with required options
|
||||
(depends on the architecture and some options, for example: build_dir)
|
||||
@@ -184,8 +185,8 @@ kunit_tool or can include KUnit in kernel and parse manually.
|
||||
To build a KUnit kernel from the current ``.config``, you can use the
|
||||
``build`` argument: ``./tools/testing/kunit/kunit.py build``.
|
||||
- ``exec`` command executes kernel results either directly (using
|
||||
User-mode Linux configuration), or via an emulator such
|
||||
as QEMU. It reads results from the log via standard
|
||||
User-mode Linux configuration), or through an emulator such
|
||||
as QEMU. It reads results from the log using standard
|
||||
output (stdout), and passes them to ``parse`` to be parsed.
|
||||
If you already have built a kernel with built-in KUnit tests,
|
||||
you can run the kernel and display the test results with the ``exec``
|
||||
|
||||
@@ -16,7 +16,6 @@ KUnit - Linux Kernel Unit Testing
|
||||
api/index
|
||||
style
|
||||
faq
|
||||
tips
|
||||
running_tips
|
||||
|
||||
.. warning::
|
||||
@@ -109,14 +108,11 @@ Read also :ref:`kinds-of-tests`.
|
||||
How do I use it?
|
||||
================
|
||||
|
||||
* Documentation/dev-tools/kunit/start.rst - for KUnit new users.
|
||||
* Documentation/dev-tools/kunit/architecture.rst - KUnit architecture.
|
||||
* Documentation/dev-tools/kunit/run_wrapper.rst - run kunit_tool.
|
||||
* Documentation/dev-tools/kunit/run_manual.rst - run tests without kunit_tool.
|
||||
* Documentation/dev-tools/kunit/usage.rst - write tests.
|
||||
* Documentation/dev-tools/kunit/tips.rst - best practices with
|
||||
examples.
|
||||
* Documentation/dev-tools/kunit/api/index.rst - KUnit APIs
|
||||
used for testing.
|
||||
* Documentation/dev-tools/kunit/faq.rst - KUnit common questions and
|
||||
answers.
|
||||
You can find a step-by-step guide to writing and running KUnit tests in
|
||||
Documentation/dev-tools/kunit/start.rst
|
||||
|
||||
Alternatively, feel free to look through the rest of the KUnit documentation,
|
||||
or to experiment with tools/testing/kunit/kunit.py and the example test under
|
||||
lib/kunit/kunit-example-test.c
|
||||
|
||||
Happy testing!
|
||||
|
||||
@@ -294,13 +294,11 @@ Congrats! You just wrote your first KUnit test.
|
||||
Next Steps
|
||||
==========
|
||||
|
||||
* Documentation/dev-tools/kunit/architecture.rst - KUnit architecture.
|
||||
* Documentation/dev-tools/kunit/run_wrapper.rst - run kunit_tool.
|
||||
* Documentation/dev-tools/kunit/run_manual.rst - run tests without kunit_tool.
|
||||
* Documentation/dev-tools/kunit/usage.rst - write tests.
|
||||
* Documentation/dev-tools/kunit/tips.rst - best practices with
|
||||
examples.
|
||||
* Documentation/dev-tools/kunit/api/index.rst - KUnit APIs
|
||||
used for testing.
|
||||
* Documentation/dev-tools/kunit/faq.rst - KUnit common questions and
|
||||
answers.
|
||||
If you're interested in using some of the more advanced features of kunit.py,
|
||||
take a look at Documentation/dev-tools/kunit/run_wrapper.rst
|
||||
|
||||
If you'd like to run tests without using kunit.py, check out
|
||||
Documentation/dev-tools/kunit/run_manual.rst
|
||||
|
||||
For more information on writing KUnit tests (including some common techniques
|
||||
for testing different things), see Documentation/dev-tools/kunit/usage.rst
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
============================
|
||||
Tips For Writing KUnit Tests
|
||||
============================
|
||||
|
||||
Exiting early on failed expectations
|
||||
------------------------------------
|
||||
|
||||
``KUNIT_EXPECT_EQ`` and friends will mark the test as failed and continue
|
||||
execution. In some cases, it's unsafe to continue and you can use the
|
||||
``KUNIT_ASSERT`` variant to exit on failure.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void example_test_user_alloc_function(struct kunit *test)
|
||||
{
|
||||
void *object = alloc_some_object_for_me();
|
||||
|
||||
/* Make sure we got a valid pointer back. */
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, object);
|
||||
do_something_with_object(object);
|
||||
}
|
||||
|
||||
Allocating memory
|
||||
-----------------
|
||||
|
||||
Where you would use ``kzalloc``, you should prefer ``kunit_kzalloc`` instead.
|
||||
KUnit will ensure the memory is freed once the test completes.
|
||||
|
||||
This is particularly useful since it lets you use the ``KUNIT_ASSERT_EQ``
|
||||
macros to exit early from a test without having to worry about remembering to
|
||||
call ``kfree``.
|
||||
|
||||
Example:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void example_test_allocation(struct kunit *test)
|
||||
{
|
||||
char *buffer = kunit_kzalloc(test, 16, GFP_KERNEL);
|
||||
/* Ensure allocation succeeded. */
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, buffer);
|
||||
|
||||
KUNIT_ASSERT_STREQ(test, buffer, "");
|
||||
}
|
||||
|
||||
|
||||
Testing static functions
|
||||
------------------------
|
||||
|
||||
If you don't want to expose functions or variables just for testing, one option
|
||||
is to conditionally ``#include`` the test file at the end of your .c file, e.g.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/* In my_file.c */
|
||||
|
||||
static int do_interesting_thing();
|
||||
|
||||
#ifdef CONFIG_MY_KUNIT_TEST
|
||||
#include "my_kunit_test.c"
|
||||
#endif
|
||||
|
||||
Injecting test-only code
|
||||
------------------------
|
||||
|
||||
Similarly to the above, it can be useful to add test-specific logic.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/* In my_file.h */
|
||||
|
||||
#ifdef CONFIG_MY_KUNIT_TEST
|
||||
/* Defined in my_kunit_test.c */
|
||||
void test_only_hook(void);
|
||||
#else
|
||||
void test_only_hook(void) { }
|
||||
#endif
|
||||
|
||||
This test-only code can be made more useful by accessing the current kunit
|
||||
test, see below.
|
||||
|
||||
Accessing the current test
|
||||
--------------------------
|
||||
|
||||
In some cases, you need to call test-only code from outside the test file, e.g.
|
||||
like in the example above or if you're providing a fake implementation of an
|
||||
ops struct.
|
||||
There is a ``kunit_test`` field in ``task_struct``, so you can access it via
|
||||
``current->kunit_test``.
|
||||
|
||||
Here's a slightly in-depth example of how one could implement "mocking":
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include <linux/sched.h> /* for current */
|
||||
|
||||
struct test_data {
|
||||
int foo_result;
|
||||
int want_foo_called_with;
|
||||
};
|
||||
|
||||
static int fake_foo(int arg)
|
||||
{
|
||||
struct kunit *test = current->kunit_test;
|
||||
struct test_data *test_data = test->priv;
|
||||
|
||||
KUNIT_EXPECT_EQ(test, test_data->want_foo_called_with, arg);
|
||||
return test_data->foo_result;
|
||||
}
|
||||
|
||||
static void example_simple_test(struct kunit *test)
|
||||
{
|
||||
/* Assume priv is allocated in the suite's .init */
|
||||
struct test_data *test_data = test->priv;
|
||||
|
||||
test_data->foo_result = 42;
|
||||
test_data->want_foo_called_with = 1;
|
||||
|
||||
/* In a real test, we'd probably pass a pointer to fake_foo somewhere
|
||||
* like an ops struct, etc. instead of calling it directly. */
|
||||
KUNIT_EXPECT_EQ(test, fake_foo(1), 42);
|
||||
}
|
||||
|
||||
|
||||
Note: here we're able to get away with using ``test->priv``, but if you wanted
|
||||
something more flexible you could use a named ``kunit_resource``, see
|
||||
Documentation/dev-tools/kunit/api/test.rst.
|
||||
|
||||
Failing the current test
|
||||
------------------------
|
||||
|
||||
But sometimes, you might just want to fail the current test. In that case, we
|
||||
have ``kunit_fail_current_test(fmt, args...)`` which is defined in ``<kunit/test-bug.h>`` and
|
||||
doesn't require pulling in ``<kunit/test.h>``.
|
||||
|
||||
E.g. say we had an option to enable some extra debug checks on some data structure:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include <kunit/test-bug.h>
|
||||
|
||||
#ifdef CONFIG_EXTRA_DEBUG_CHECKS
|
||||
static void validate_my_data(struct data *data)
|
||||
{
|
||||
if (is_valid(data))
|
||||
return;
|
||||
|
||||
kunit_fail_current_test("data %p is invalid", data);
|
||||
|
||||
/* Normal, non-KUnit, error reporting code here. */
|
||||
}
|
||||
#else
|
||||
static void my_debug_function(void) { }
|
||||
#endif
|
||||
|
||||
|
||||
Customizing error messages
|
||||
--------------------------
|
||||
|
||||
Each of the ``KUNIT_EXPECT`` and ``KUNIT_ASSERT`` macros have a ``_MSG`` variant.
|
||||
These take a format string and arguments to provide additional context to the automatically generated error messages.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
char some_str[41];
|
||||
generate_sha1_hex_string(some_str);
|
||||
|
||||
/* Before. Not easy to tell why the test failed. */
|
||||
KUNIT_EXPECT_EQ(test, strlen(some_str), 40);
|
||||
|
||||
/* After. Now we see the offending string. */
|
||||
KUNIT_EXPECT_EQ_MSG(test, strlen(some_str), 40, "some_str='%s'", some_str);
|
||||
|
||||
Alternatively, one can take full control over the error message by using ``KUNIT_FAIL()``, e.g.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/* Before */
|
||||
KUNIT_EXPECT_EQ(test, some_setup_function(), 0);
|
||||
|
||||
/* After: full control over the failure message. */
|
||||
if (some_setup_function())
|
||||
KUNIT_FAIL(test, "Failed to setup thing for testing");
|
||||
|
||||
Next Steps
|
||||
==========
|
||||
* Optional: see the Documentation/dev-tools/kunit/usage.rst page for a more
|
||||
in-depth explanation of KUnit.
|
||||
@@ -112,11 +112,45 @@ terminates the test case if the condition is not satisfied. For example:
|
||||
KUNIT_EXPECT_LE(test, a[i], a[i + 1]);
|
||||
}
|
||||
|
||||
In this example, the method under test should return pointer to a value. If the
|
||||
pointer returns null or an errno, we want to stop the test since the following
|
||||
expectation could crash the test case. `ASSERT_NOT_ERR_OR_NULL(...)` allows us
|
||||
to bail out of the test case if the appropriate conditions are not satisfied to
|
||||
complete the test.
|
||||
In this example, we need to be able to allocate an array to test the ``sort()``
|
||||
function. So we use ``KUNIT_ASSERT_NOT_ERR_OR_NULL()`` to abort the test if
|
||||
there's an allocation error.
|
||||
|
||||
.. note::
|
||||
In other test frameworks, ``ASSERT`` macros are often implemented by calling
|
||||
``return`` so they only work from the test function. In KUnit, we stop the
|
||||
current kthread on failure, so you can call them from anywhere.
|
||||
|
||||
Customizing error messages
|
||||
--------------------------
|
||||
|
||||
Each of the ``KUNIT_EXPECT`` and ``KUNIT_ASSERT`` macros have a ``_MSG``
|
||||
variant. These take a format string and arguments to provide additional
|
||||
context to the automatically generated error messages.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
char some_str[41];
|
||||
generate_sha1_hex_string(some_str);
|
||||
|
||||
/* Before. Not easy to tell why the test failed. */
|
||||
KUNIT_EXPECT_EQ(test, strlen(some_str), 40);
|
||||
|
||||
/* After. Now we see the offending string. */
|
||||
KUNIT_EXPECT_EQ_MSG(test, strlen(some_str), 40, "some_str='%s'", some_str);
|
||||
|
||||
Alternatively, one can take full control over the error message by using
|
||||
``KUNIT_FAIL()``, e.g.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
/* Before */
|
||||
KUNIT_EXPECT_EQ(test, some_setup_function(), 0);
|
||||
|
||||
/* After: full control over the failure message. */
|
||||
if (some_setup_function())
|
||||
KUNIT_FAIL(test, "Failed to setup thing for testing");
|
||||
|
||||
|
||||
Test Suites
|
||||
~~~~~~~~~~~
|
||||
@@ -546,24 +580,6 @@ By reusing the same ``cases`` array from above, we can write the test as a
|
||||
{}
|
||||
};
|
||||
|
||||
Exiting Early on Failed Expectations
|
||||
------------------------------------
|
||||
|
||||
We can use ``KUNIT_EXPECT_EQ`` to mark the test as failed and continue
|
||||
execution. In some cases, it is unsafe to continue. We can use the
|
||||
``KUNIT_ASSERT`` variant to exit on failure.
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
void example_test_user_alloc_function(struct kunit *test)
|
||||
{
|
||||
void *object = alloc_some_object_for_me();
|
||||
|
||||
/* Make sure we got a valid pointer back. */
|
||||
KUNIT_ASSERT_NOT_ERR_OR_NULL(test, object);
|
||||
do_something_with_object(object);
|
||||
}
|
||||
|
||||
Allocating Memory
|
||||
-----------------
|
||||
|
||||
@@ -625,17 +641,23 @@ as shown in next section: *Accessing The Current Test*.
|
||||
Accessing The Current Test
|
||||
--------------------------
|
||||
|
||||
In some cases, we need to call test-only code from outside the test file.
|
||||
For example, see example in section *Injecting Test-Only Code* or if
|
||||
we are providing a fake implementation of an ops struct. Using
|
||||
``kunit_test`` field in ``task_struct``, we can access it via
|
||||
``current->kunit_test``.
|
||||
In some cases, we need to call test-only code from outside the test file. This
|
||||
is helpful, for example, when providing a fake implementation of a function, or
|
||||
to fail any current test from within an error handler.
|
||||
We can do this via the ``kunit_test`` field in ``task_struct``, which we can
|
||||
access using the ``kunit_get_current_test()`` function in ``kunit/test-bug.h``.
|
||||
|
||||
The example below includes how to implement "mocking":
|
||||
``kunit_get_current_test()`` is safe to call even if KUnit is not enabled. If
|
||||
KUnit is not enabled, was built as a module (``CONFIG_KUNIT=m``), or no test is
|
||||
running in the current task, it will return ``NULL``. This compiles down to
|
||||
either a no-op or a static key check, so will have a negligible performance
|
||||
impact when no test is running.
|
||||
|
||||
The example below uses this to implement a "mock" implementation of a function, ``foo``:
|
||||
|
||||
.. code-block:: c
|
||||
|
||||
#include <linux/sched.h> /* for current */
|
||||
#include <kunit/test-bug.h> /* for kunit_get_current_test */
|
||||
|
||||
struct test_data {
|
||||
int foo_result;
|
||||
@@ -644,7 +666,7 @@ The example below includes how to implement "mocking":
|
||||
|
||||
static int fake_foo(int arg)
|
||||
{
|
||||
struct kunit *test = current->kunit_test;
|
||||
struct kunit *test = kunit_get_current_test();
|
||||
struct test_data *test_data = test->priv;
|
||||
|
||||
KUNIT_EXPECT_EQ(test, test_data->want_foo_called_with, arg);
|
||||
@@ -675,7 +697,7 @@ Each test can have multiple resources which have string names providing the same
|
||||
flexibility as a ``priv`` member, but also, for example, allowing helper
|
||||
functions to create resources without conflicting with each other. It is also
|
||||
possible to define a clean up function for each resource, making it easy to
|
||||
avoid resource leaks. For more information, see Documentation/dev-tools/kunit/api/test.rst.
|
||||
avoid resource leaks. For more information, see Documentation/dev-tools/kunit/api/resource.rst.
|
||||
|
||||
Failing The Current Test
|
||||
------------------------
|
||||
@@ -703,3 +725,9 @@ structures as shown below:
|
||||
static void my_debug_function(void) { }
|
||||
#endif
|
||||
|
||||
``kunit_fail_current_test()`` is safe to call even if KUnit is not enabled. If
|
||||
KUnit is not enabled, was built as a module (``CONFIG_KUNIT=m``), or no test is
|
||||
running in the current task, it will do nothing. This compiles down to either a
|
||||
no-op or a static key check, so will have a negligible performance impact when
|
||||
no test is running.
|
||||
|
||||
|
||||
@@ -11,6 +11,7 @@ more additions are needed here:
|
||||
:maxdepth: 1
|
||||
|
||||
rtla/index
|
||||
rv/index
|
||||
|
||||
.. only:: subproject and html
|
||||
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
INSTALL ?= install
|
||||
RM ?= rm -f
|
||||
RMDIR ?= rmdir --ignore-fail-on-non-empty
|
||||
|
||||
PREFIX ?= /usr/share
|
||||
MANDIR ?= $(PREFIX)/man
|
||||
MAN1DIR = $(MANDIR)/man1
|
||||
|
||||
MAN1_RST = $(wildcard rv*.rst)
|
||||
|
||||
_DOC_MAN1 = $(patsubst %.rst,%.1,$(MAN1_RST))
|
||||
DOC_MAN1 = $(addprefix $(OUTPUT),$(_DOC_MAN1))
|
||||
|
||||
RST2MAN_DEP := $(shell command -v rst2man 2>/dev/null)
|
||||
RST2MAN_OPTS += --verbose
|
||||
|
||||
TEST_RST2MAN = $(shell sh -c "rst2man --version > /dev/null 2>&1 || echo n")
|
||||
|
||||
$(OUTPUT)%.1: %.rst
|
||||
ifndef RST2MAN_DEP
|
||||
$(info ********************************************)
|
||||
$(info ** NOTICE: rst2man not found)
|
||||
$(info **)
|
||||
$(info ** Consider installing the latest rst2man from your)
|
||||
$(info ** distribution, e.g., 'dnf install python3-docutils' on Fedora,)
|
||||
$(info ** or from source:)
|
||||
$(info **)
|
||||
$(info ** https://docutils.sourceforge.io/docs/dev/repository.html )
|
||||
$(info **)
|
||||
$(info ********************************************)
|
||||
$(error NOTICE: rst2man required to generate man pages)
|
||||
endif
|
||||
rst2man $(RST2MAN_OPTS) $< > $@
|
||||
|
||||
man1: $(DOC_MAN1)
|
||||
man: man1
|
||||
|
||||
clean:
|
||||
$(RM) $(DOC_MAN1)
|
||||
|
||||
install: man
|
||||
$(INSTALL) -d -m 755 $(DESTDIR)$(MAN1DIR)
|
||||
$(INSTALL) -m 644 $(DOC_MAN1) $(DESTDIR)$(MAN1DIR)
|
||||
|
||||
uninstall:
|
||||
$(RM) $(addprefix $(DESTDIR)$(MAN1DIR)/,$(_DOC_MAN1))
|
||||
$(RMDIR) $(DESTDIR)$(MAN1DIR)
|
||||
|
||||
.PHONY: man man1 clean install uninstall
|
||||
.DEFAULT_GOAL := man
|
||||
@@ -0,0 +1,16 @@
|
||||
REPORTING BUGS
|
||||
==============
|
||||
|
||||
Report bugs to <linux-kernel@vger.kernel.org>
|
||||
and <linux-trace-devel@vger.kernel.org>
|
||||
|
||||
LICENSE
|
||||
=======
|
||||
|
||||
**rv** is Free Software licensed under the GNU GPLv2
|
||||
|
||||
COPYING
|
||||
=======
|
||||
|
||||
Copyright \(C) 2022 Red Hat, Inc. Free use of this software is granted under
|
||||
the terms of the GNU Public License (GPL).
|
||||
@@ -0,0 +1,21 @@
|
||||
**-h**, **--help**
|
||||
|
||||
Print the monitor's options and the available reactors list.
|
||||
|
||||
**-r**, **--reactor** *reactor*
|
||||
|
||||
Enables the *reactor*. See **-h** for a list of available reactors.
|
||||
|
||||
**-s**, **--self**
|
||||
|
||||
When tracing (**-t**), also print the events that happened during the **rv**
|
||||
command itself. If the **rv** command itself generates too many events,
|
||||
the tool might get busy processing its own events only.
|
||||
|
||||
**-t**, **--trace**
|
||||
|
||||
Trace monitor's events and error.
|
||||
|
||||
**-v**, **--verbose**
|
||||
|
||||
Print debug messages.
|
||||
@@ -0,0 +1,24 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
==============================
|
||||
Runtime verification (rv) tool
|
||||
==============================
|
||||
|
||||
**rv** tool provides the interface for a collection of runtime verification
|
||||
(rv) monitors.
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
rv
|
||||
rv-list
|
||||
rv-mon
|
||||
rv-mon-wip
|
||||
rv-mon-wwnr
|
||||
|
||||
.. only:: subproject and html
|
||||
|
||||
Indices
|
||||
=======
|
||||
|
||||
* :ref:`genindex`
|
||||
@@ -0,0 +1,43 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
=======
|
||||
rv-list
|
||||
=======
|
||||
-----------------------
|
||||
List available monitors
|
||||
-----------------------
|
||||
|
||||
:Manual section: 1
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
**rv list** [*OPTIONS*]
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
The **rv list** command prints all available monitors. These monitors
|
||||
can be enabled using the **rv mon** command.
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
**-h**, **--help**
|
||||
|
||||
Print help menu.
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
|
||||
**rv**\(1), **rv-mon**\(1)
|
||||
|
||||
Linux kernel *RV* documentation:
|
||||
<https://www.kernel.org/doc/html/latest/trace/rv/index.html>
|
||||
|
||||
AUTHOR
|
||||
======
|
||||
|
||||
Written by Daniel Bristot de Oliveira <bristot@kernel.org>
|
||||
|
||||
.. include:: common_appendix.rst
|
||||
@@ -0,0 +1,44 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
==========
|
||||
rv-mon-wip
|
||||
==========
|
||||
----------------------------
|
||||
Wakeup In Preemptive monitor
|
||||
----------------------------
|
||||
|
||||
:Manual section: 1
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
**rv mon wip** [*OPTIONS*]
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
The wakeup in preemptive (**wip**) monitor is a sample per-cpu monitor that
|
||||
checks if the wakeup events always take place with preemption disabled.
|
||||
|
||||
See kernel documentation for further information about this monitor:
|
||||
<https://docs.kernel.org/trace/rv/monitor_wip.html>
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
.. include:: common_ikm.rst
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
|
||||
**rv**\(1), **rv-mon**\(1)
|
||||
|
||||
Linux kernel *RV* documentation:
|
||||
<https://www.kernel.org/doc/html/latest/trace/rv/index.html>
|
||||
|
||||
AUTHOR
|
||||
======
|
||||
|
||||
Written by Daniel Bristot de Oliveira <bristot@kernel.org>
|
||||
|
||||
.. include:: common_appendix.rst
|
||||
@@ -0,0 +1,43 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
===========
|
||||
rv-mon-wwnr
|
||||
===========
|
||||
--------------------------------
|
||||
Wakeup While Not Running monitor
|
||||
--------------------------------
|
||||
|
||||
:Manual section: 1
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
**rv mon wip** [*OPTIONS*]
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
The wakeup while not running (**wwnr**) is a per-task sample monitor.
|
||||
|
||||
See kernel documentation for further information about this monitor:
|
||||
<https://docs.kernel.org/trace/rv/monitor_wwnr.html>
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
.. include:: common_ikm.rst
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
|
||||
**rv**\(1), **rv-mon**\(1)
|
||||
|
||||
Linux kernel *RV* documentation:
|
||||
<https://www.kernel.org/doc/html/latest/trace/rv/index.html>
|
||||
|
||||
AUTHOR
|
||||
======
|
||||
|
||||
Written by Daniel Bristot de Oliveira <bristot@kernel.org>
|
||||
|
||||
.. include:: common_appendix.rst
|
||||
@@ -0,0 +1,55 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
=======
|
||||
rv-list
|
||||
=======
|
||||
-----------------------
|
||||
List available monitors
|
||||
-----------------------
|
||||
|
||||
:Manual section: 1
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
**rv mon** [*-h*] **monitor_name** [*-h*] [*MONITOR OPTIONS*]
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
The **rv mon** command runs the monitor named *monitor_name*. Each monitor
|
||||
has its own set of options. The **rv list** command shows all available
|
||||
monitors.
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
**-h**, **--help**
|
||||
|
||||
Print help menu.
|
||||
|
||||
AVAILABLE MONITORS
|
||||
==================
|
||||
|
||||
The **rv** tool provides the interface for a set of monitors. Use the
|
||||
**rv list** command to list all available monitors.
|
||||
|
||||
Each monitor has its own set of options. See man **rv-mon**-*monitor_name*
|
||||
for details about each specific monitor. Also, running **rv mon**
|
||||
**monitor_name** **-h** display the help menu with the available
|
||||
options.
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
|
||||
**rv**\(1), **rv-mon**\(1)
|
||||
|
||||
Linux kernel *RV* documentation:
|
||||
<https://www.kernel.org/doc/html/latest/trace/rv/index.html>
|
||||
|
||||
AUTHOR
|
||||
======
|
||||
|
||||
Written by Daniel Bristot de Oliveira <bristot@kernel.org>
|
||||
|
||||
.. include:: common_appendix.rst
|
||||
@@ -0,0 +1,63 @@
|
||||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
==
|
||||
rv
|
||||
==
|
||||
--------------------
|
||||
Runtime Verification
|
||||
--------------------
|
||||
|
||||
:Manual section: 1
|
||||
|
||||
SYNOPSIS
|
||||
========
|
||||
|
||||
**rv** *COMMAND* [*OPTIONS*]
|
||||
|
||||
DESCRIPTION
|
||||
===========
|
||||
|
||||
Runtime Verification (**RV**) is a lightweight (yet rigorous) method
|
||||
for formal verification with a practical approach for complex systems.
|
||||
Instead of relying on a fine-grained model of a system (e.g., a
|
||||
re-implementation a instruction level), RV works by analyzing the trace
|
||||
of the system's actual execution, comparing it against a formal
|
||||
specification of the system behavior.
|
||||
|
||||
The **rv** tool provides the interface for a collection of runtime
|
||||
verification (rv) monitors.
|
||||
|
||||
COMMANDS
|
||||
========
|
||||
|
||||
**list**
|
||||
|
||||
List all available monitors.
|
||||
|
||||
**mon**
|
||||
|
||||
Run monitor.
|
||||
|
||||
OPTIONS
|
||||
=======
|
||||
|
||||
**-h**, **--help**
|
||||
|
||||
Display the help text.
|
||||
|
||||
For other options, see the man page for the corresponding command.
|
||||
|
||||
SEE ALSO
|
||||
========
|
||||
|
||||
**rv-list**\(1), **rv-mon**\(1)
|
||||
|
||||
Linux kernel *RV* documentation:
|
||||
<https://www.kernel.org/doc/html/latest/trace/rv/index.html>
|
||||
|
||||
AUTHOR
|
||||
======
|
||||
|
||||
Daniel Bristot de Oliveira <bristot@kernel.org>
|
||||
|
||||
.. include:: common_appendix.rst
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-License-Identifier: GPL-1.0-or-later
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
/*
|
||||
* AMD Processor P-state Frequency Driver Unit Test
|
||||
*
|
||||
|
||||
@@ -315,7 +315,7 @@ static void drm_test_fb_xrgb8888_to_gray8(struct kunit *test)
|
||||
iosys_map_set_vaddr(&src, xrgb8888);
|
||||
|
||||
drm_fb_xrgb8888_to_gray8(&dst, &result->dst_pitch, &src, &fb, ¶ms->clip);
|
||||
KUNIT_EXPECT_EQ(test, memcmp(buf, result->expected, dst_size), 0);
|
||||
KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size);
|
||||
}
|
||||
|
||||
static void drm_test_fb_xrgb8888_to_rgb332(struct kunit *test)
|
||||
@@ -345,7 +345,7 @@ static void drm_test_fb_xrgb8888_to_rgb332(struct kunit *test)
|
||||
iosys_map_set_vaddr(&src, xrgb8888);
|
||||
|
||||
drm_fb_xrgb8888_to_rgb332(&dst, &result->dst_pitch, &src, &fb, ¶ms->clip);
|
||||
KUNIT_EXPECT_EQ(test, memcmp(buf, result->expected, dst_size), 0);
|
||||
KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size);
|
||||
}
|
||||
|
||||
static void drm_test_fb_xrgb8888_to_rgb565(struct kunit *test)
|
||||
@@ -375,10 +375,10 @@ static void drm_test_fb_xrgb8888_to_rgb565(struct kunit *test)
|
||||
iosys_map_set_vaddr(&src, xrgb8888);
|
||||
|
||||
drm_fb_xrgb8888_to_rgb565(&dst, &result->dst_pitch, &src, &fb, ¶ms->clip, false);
|
||||
KUNIT_EXPECT_EQ(test, memcmp(buf, result->expected, dst_size), 0);
|
||||
KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size);
|
||||
|
||||
drm_fb_xrgb8888_to_rgb565(&dst, &result->dst_pitch, &src, &fb, ¶ms->clip, true);
|
||||
KUNIT_EXPECT_EQ(test, memcmp(buf, result->expected_swab, dst_size), 0);
|
||||
KUNIT_EXPECT_MEMEQ(test, buf, result->expected_swab, dst_size);
|
||||
}
|
||||
|
||||
static void drm_test_fb_xrgb8888_to_rgb888(struct kunit *test)
|
||||
@@ -408,7 +408,7 @@ static void drm_test_fb_xrgb8888_to_rgb888(struct kunit *test)
|
||||
iosys_map_set_vaddr(&src, xrgb8888);
|
||||
|
||||
drm_fb_xrgb8888_to_rgb888(&dst, &result->dst_pitch, &src, &fb, ¶ms->clip);
|
||||
KUNIT_EXPECT_EQ(test, memcmp(buf, result->expected, dst_size), 0);
|
||||
KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size);
|
||||
}
|
||||
|
||||
static void drm_test_fb_xrgb8888_to_xrgb2101010(struct kunit *test)
|
||||
@@ -439,7 +439,7 @@ static void drm_test_fb_xrgb8888_to_xrgb2101010(struct kunit *test)
|
||||
|
||||
drm_fb_xrgb8888_to_xrgb2101010(&dst, &result->dst_pitch, &src, &fb, ¶ms->clip);
|
||||
buf = le32buf_to_cpu(test, buf, dst_size / sizeof(u32));
|
||||
KUNIT_EXPECT_EQ(test, memcmp(buf, result->expected, dst_size), 0);
|
||||
KUNIT_EXPECT_MEMEQ(test, buf, result->expected, dst_size);
|
||||
}
|
||||
|
||||
static struct kunit_case drm_format_helper_test_cases[] = {
|
||||
|
||||
+26
-48
@@ -90,19 +90,6 @@ void kunit_unary_assert_format(const struct kunit_assert *assert,
|
||||
const struct va_format *message,
|
||||
struct string_stream *stream);
|
||||
|
||||
/**
|
||||
* KUNIT_INIT_UNARY_ASSERT_STRUCT() - Initializes &struct kunit_unary_assert.
|
||||
* @cond: A string representation of the expression asserted true or false.
|
||||
* @expect_true: True if of type KUNIT_{EXPECT|ASSERT}_TRUE, false otherwise.
|
||||
*
|
||||
* Initializes a &struct kunit_unary_assert. Intended to be used in
|
||||
* KUNIT_EXPECT_* and KUNIT_ASSERT_* macros.
|
||||
*/
|
||||
#define KUNIT_INIT_UNARY_ASSERT_STRUCT(cond, expect_true) { \
|
||||
.condition = cond, \
|
||||
.expected_true = expect_true \
|
||||
}
|
||||
|
||||
/**
|
||||
* struct kunit_ptr_not_err_assert - An expectation/assertion that a pointer is
|
||||
* not NULL and not a -errno.
|
||||
@@ -123,20 +110,6 @@ void kunit_ptr_not_err_assert_format(const struct kunit_assert *assert,
|
||||
const struct va_format *message,
|
||||
struct string_stream *stream);
|
||||
|
||||
/**
|
||||
* KUNIT_INIT_PTR_NOT_ERR_ASSERT_STRUCT() - Initializes a
|
||||
* &struct kunit_ptr_not_err_assert.
|
||||
* @txt: A string representation of the expression passed to the expectation.
|
||||
* @val: The actual evaluated pointer value of the expression.
|
||||
*
|
||||
* Initializes a &struct kunit_ptr_not_err_assert. Intended to be used in
|
||||
* KUNIT_EXPECT_* and KUNIT_ASSERT_* macros.
|
||||
*/
|
||||
#define KUNIT_INIT_PTR_NOT_ERR_STRUCT(txt, val) { \
|
||||
.text = txt, \
|
||||
.value = val \
|
||||
}
|
||||
|
||||
/**
|
||||
* struct kunit_binary_assert_text - holds strings for &struct
|
||||
* kunit_binary_assert and friends to try and make the structs smaller.
|
||||
@@ -173,27 +146,6 @@ void kunit_binary_assert_format(const struct kunit_assert *assert,
|
||||
const struct va_format *message,
|
||||
struct string_stream *stream);
|
||||
|
||||
/**
|
||||
* KUNIT_INIT_BINARY_ASSERT_STRUCT() - Initializes a binary assert like
|
||||
* kunit_binary_assert, kunit_binary_ptr_assert, etc.
|
||||
*
|
||||
* @text_: Pointer to a kunit_binary_assert_text.
|
||||
* @left_val: The actual evaluated value of the expression in the left slot.
|
||||
* @right_val: The actual evaluated value of the expression in the right slot.
|
||||
*
|
||||
* Initializes a binary assert like kunit_binary_assert,
|
||||
* kunit_binary_ptr_assert, etc. This relies on these structs having the same
|
||||
* fields but with different types for left_val/right_val.
|
||||
* This is ultimately used by binary assertion macros like KUNIT_EXPECT_EQ, etc.
|
||||
*/
|
||||
#define KUNIT_INIT_BINARY_ASSERT_STRUCT(text_, \
|
||||
left_val, \
|
||||
right_val) { \
|
||||
.text = text_, \
|
||||
.left_value = left_val, \
|
||||
.right_value = right_val \
|
||||
}
|
||||
|
||||
/**
|
||||
* struct kunit_binary_ptr_assert - An expectation/assertion that compares two
|
||||
* pointer values (for example, KUNIT_EXPECT_PTR_EQ(test, foo, bar)).
|
||||
@@ -240,4 +192,30 @@ void kunit_binary_str_assert_format(const struct kunit_assert *assert,
|
||||
const struct va_format *message,
|
||||
struct string_stream *stream);
|
||||
|
||||
/**
|
||||
* struct kunit_mem_assert - An expectation/assertion that compares two
|
||||
* memory blocks.
|
||||
* @assert: The parent of this type.
|
||||
* @text: Holds the textual representations of the operands and comparator.
|
||||
* @left_value: The actual evaluated value of the expression in the left slot.
|
||||
* @right_value: The actual evaluated value of the expression in the right slot.
|
||||
* @size: Size of the memory block analysed in bytes.
|
||||
*
|
||||
* Represents an expectation/assertion that compares two memory blocks. For
|
||||
* example, to expect that the first three bytes of foo is equal to the
|
||||
* first three bytes of bar, you can use the expectation
|
||||
* KUNIT_EXPECT_MEMEQ(test, foo, bar, 3);
|
||||
*/
|
||||
struct kunit_mem_assert {
|
||||
struct kunit_assert assert;
|
||||
const struct kunit_binary_assert_text *text;
|
||||
const void *left_value;
|
||||
const void *right_value;
|
||||
const size_t size;
|
||||
};
|
||||
|
||||
void kunit_mem_assert_format(const struct kunit_assert *assert,
|
||||
const struct va_format *message,
|
||||
struct string_stream *stream);
|
||||
|
||||
#endif /* _KUNIT_ASSERT_H */
|
||||
|
||||
@@ -9,16 +9,63 @@
|
||||
#ifndef _KUNIT_TEST_BUG_H
|
||||
#define _KUNIT_TEST_BUG_H
|
||||
|
||||
#define kunit_fail_current_test(fmt, ...) \
|
||||
__kunit_fail_current_test(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
|
||||
#if IS_BUILTIN(CONFIG_KUNIT)
|
||||
|
||||
#include <linux/jump_label.h> /* For static branch */
|
||||
#include <linux/sched.h>
|
||||
|
||||
/* Static key if KUnit is running any tests. */
|
||||
DECLARE_STATIC_KEY_FALSE(kunit_running);
|
||||
|
||||
/**
|
||||
* kunit_get_current_test() - Return a pointer to the currently running
|
||||
* KUnit test.
|
||||
*
|
||||
* If a KUnit test is running in the current task, returns a pointer to its
|
||||
* associated struct kunit. This pointer can then be passed to any KUnit
|
||||
* function or assertion. If no test is running (or a test is running in a
|
||||
* different task), returns NULL.
|
||||
*
|
||||
* This function is safe to call even when KUnit is disabled. If CONFIG_KUNIT
|
||||
* is not enabled, it will compile down to nothing and will return quickly no
|
||||
* test is running.
|
||||
*/
|
||||
static inline struct kunit *kunit_get_current_test(void)
|
||||
{
|
||||
if (!static_branch_unlikely(&kunit_running))
|
||||
return NULL;
|
||||
|
||||
return current->kunit_test;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* kunit_fail_current_test() - If a KUnit test is running, fail it.
|
||||
*
|
||||
* If a KUnit test is running in the current task, mark that test as failed.
|
||||
*
|
||||
* This macro will only work if KUnit is built-in (though the tests
|
||||
* themselves can be modules). Otherwise, it compiles down to nothing.
|
||||
*/
|
||||
#define kunit_fail_current_test(fmt, ...) do { \
|
||||
if (static_branch_unlikely(&kunit_running)) { \
|
||||
__kunit_fail_current_test(__FILE__, __LINE__, \
|
||||
fmt, ##__VA_ARGS__); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
extern __printf(3, 4) void __kunit_fail_current_test(const char *file, int line,
|
||||
const char *fmt, ...);
|
||||
|
||||
#else
|
||||
|
||||
static inline struct kunit *kunit_get_current_test(void) { return NULL; }
|
||||
|
||||
/* We define this with an empty helper function so format string warnings work */
|
||||
#define kunit_fail_current_test(fmt, ...) \
|
||||
__kunit_fail_current_test(__FILE__, __LINE__, fmt, ##__VA_ARGS__)
|
||||
|
||||
static inline __printf(3, 4) void __kunit_fail_current_test(const char *file, int line,
|
||||
const char *fmt, ...)
|
||||
{
|
||||
|
||||
+106
-12
@@ -16,6 +16,7 @@
|
||||
#include <linux/container_of.h>
|
||||
#include <linux/err.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/jump_label.h>
|
||||
#include <linux/kconfig.h>
|
||||
#include <linux/kref.h>
|
||||
#include <linux/list.h>
|
||||
@@ -27,6 +28,9 @@
|
||||
|
||||
#include <asm/rwonce.h>
|
||||
|
||||
/* Static key: true if any KUnit tests are currently running */
|
||||
DECLARE_STATIC_KEY_FALSE(kunit_running);
|
||||
|
||||
struct kunit;
|
||||
|
||||
/* Size of log associated with test. */
|
||||
@@ -515,22 +519,25 @@ void kunit_do_failed_assertion(struct kunit *test,
|
||||
fmt, \
|
||||
##__VA_ARGS__)
|
||||
|
||||
/* Helper to safely pass around an initializer list to other macros. */
|
||||
#define KUNIT_INIT_ASSERT(initializers...) { initializers }
|
||||
|
||||
#define KUNIT_UNARY_ASSERTION(test, \
|
||||
assert_type, \
|
||||
condition, \
|
||||
expected_true, \
|
||||
condition_, \
|
||||
expected_true_, \
|
||||
fmt, \
|
||||
...) \
|
||||
do { \
|
||||
if (likely(!!(condition) == !!expected_true)) \
|
||||
if (likely(!!(condition_) == !!expected_true_)) \
|
||||
break; \
|
||||
\
|
||||
_KUNIT_FAILED(test, \
|
||||
assert_type, \
|
||||
kunit_unary_assert, \
|
||||
kunit_unary_assert_format, \
|
||||
KUNIT_INIT_UNARY_ASSERT_STRUCT(#condition, \
|
||||
expected_true), \
|
||||
KUNIT_INIT_ASSERT(.condition = #condition_, \
|
||||
.expected_true = expected_true_), \
|
||||
fmt, \
|
||||
##__VA_ARGS__); \
|
||||
} while (0)
|
||||
@@ -590,9 +597,9 @@ do { \
|
||||
assert_type, \
|
||||
assert_class, \
|
||||
format_func, \
|
||||
KUNIT_INIT_BINARY_ASSERT_STRUCT(&__text, \
|
||||
__left, \
|
||||
__right), \
|
||||
KUNIT_INIT_ASSERT(.text = &__text, \
|
||||
.left_value = __left, \
|
||||
.right_value = __right), \
|
||||
fmt, \
|
||||
##__VA_ARGS__); \
|
||||
} while (0)
|
||||
@@ -651,9 +658,42 @@ do { \
|
||||
assert_type, \
|
||||
kunit_binary_str_assert, \
|
||||
kunit_binary_str_assert_format, \
|
||||
KUNIT_INIT_BINARY_ASSERT_STRUCT(&__text, \
|
||||
__left, \
|
||||
__right), \
|
||||
KUNIT_INIT_ASSERT(.text = &__text, \
|
||||
.left_value = __left, \
|
||||
.right_value = __right), \
|
||||
fmt, \
|
||||
##__VA_ARGS__); \
|
||||
} while (0)
|
||||
|
||||
#define KUNIT_MEM_ASSERTION(test, \
|
||||
assert_type, \
|
||||
left, \
|
||||
op, \
|
||||
right, \
|
||||
size_, \
|
||||
fmt, \
|
||||
...) \
|
||||
do { \
|
||||
const void *__left = (left); \
|
||||
const void *__right = (right); \
|
||||
const size_t __size = (size_); \
|
||||
static const struct kunit_binary_assert_text __text = { \
|
||||
.operation = #op, \
|
||||
.left_text = #left, \
|
||||
.right_text = #right, \
|
||||
}; \
|
||||
\
|
||||
if (likely(memcmp(__left, __right, __size) op 0)) \
|
||||
break; \
|
||||
\
|
||||
_KUNIT_FAILED(test, \
|
||||
assert_type, \
|
||||
kunit_mem_assert, \
|
||||
kunit_mem_assert_format, \
|
||||
KUNIT_INIT_ASSERT(.text = &__text, \
|
||||
.left_value = __left, \
|
||||
.right_value = __right, \
|
||||
.size = __size), \
|
||||
fmt, \
|
||||
##__VA_ARGS__); \
|
||||
} while (0)
|
||||
@@ -673,7 +713,7 @@ do { \
|
||||
assert_type, \
|
||||
kunit_ptr_not_err_assert, \
|
||||
kunit_ptr_not_err_assert_format, \
|
||||
KUNIT_INIT_PTR_NOT_ERR_STRUCT(#ptr, __ptr), \
|
||||
KUNIT_INIT_ASSERT(.text = #ptr, .value = __ptr), \
|
||||
fmt, \
|
||||
##__VA_ARGS__); \
|
||||
} while (0)
|
||||
@@ -928,6 +968,60 @@ do { \
|
||||
fmt, \
|
||||
##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* KUNIT_EXPECT_MEMEQ() - Expects that the first @size bytes of @left and @right are equal.
|
||||
* @test: The test context object.
|
||||
* @left: An arbitrary expression that evaluates to the specified size.
|
||||
* @right: An arbitrary expression that evaluates to the specified size.
|
||||
* @size: Number of bytes compared.
|
||||
*
|
||||
* Sets an expectation that the values that @left and @right evaluate to are
|
||||
* equal. This is semantically equivalent to
|
||||
* KUNIT_EXPECT_TRUE(@test, !memcmp((@left), (@right), (@size))). See
|
||||
* KUNIT_EXPECT_TRUE() for more information.
|
||||
*
|
||||
* Although this expectation works for any memory block, it is not recommended
|
||||
* for comparing more structured data, such as structs. This expectation is
|
||||
* recommended for comparing, for example, data arrays.
|
||||
*/
|
||||
#define KUNIT_EXPECT_MEMEQ(test, left, right, size) \
|
||||
KUNIT_EXPECT_MEMEQ_MSG(test, left, right, size, NULL)
|
||||
|
||||
#define KUNIT_EXPECT_MEMEQ_MSG(test, left, right, size, fmt, ...) \
|
||||
KUNIT_MEM_ASSERTION(test, \
|
||||
KUNIT_EXPECTATION, \
|
||||
left, ==, right, \
|
||||
size, \
|
||||
fmt, \
|
||||
##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* KUNIT_EXPECT_MEMNEQ() - Expects that the first @size bytes of @left and @right are not equal.
|
||||
* @test: The test context object.
|
||||
* @left: An arbitrary expression that evaluates to the specified size.
|
||||
* @right: An arbitrary expression that evaluates to the specified size.
|
||||
* @size: Number of bytes compared.
|
||||
*
|
||||
* Sets an expectation that the values that @left and @right evaluate to are
|
||||
* not equal. This is semantically equivalent to
|
||||
* KUNIT_EXPECT_TRUE(@test, memcmp((@left), (@right), (@size))). See
|
||||
* KUNIT_EXPECT_TRUE() for more information.
|
||||
*
|
||||
* Although this expectation works for any memory block, it is not recommended
|
||||
* for comparing more structured data, such as structs. This expectation is
|
||||
* recommended for comparing, for example, data arrays.
|
||||
*/
|
||||
#define KUNIT_EXPECT_MEMNEQ(test, left, right, size) \
|
||||
KUNIT_EXPECT_MEMNEQ_MSG(test, left, right, size, NULL)
|
||||
|
||||
#define KUNIT_EXPECT_MEMNEQ_MSG(test, left, right, size, fmt, ...) \
|
||||
KUNIT_MEM_ASSERTION(test, \
|
||||
KUNIT_EXPECTATION, \
|
||||
left, !=, right, \
|
||||
size, \
|
||||
fmt, \
|
||||
##__VA_ARGS__)
|
||||
|
||||
/**
|
||||
* KUNIT_EXPECT_NULL() - Expects that @ptr is null.
|
||||
* @test: The test context object.
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
/*
|
||||
* KUnit API to allow symbols to be conditionally visible during KUnit
|
||||
* testing
|
||||
*
|
||||
* Copyright (C) 2022, Google LLC.
|
||||
* Author: Rae Moar <rmoar@google.com>
|
||||
*/
|
||||
|
||||
#ifndef _KUNIT_VISIBILITY_H
|
||||
#define _KUNIT_VISIBILITY_H
|
||||
|
||||
#if IS_ENABLED(CONFIG_KUNIT)
|
||||
/**
|
||||
* VISIBLE_IF_KUNIT - A macro that sets symbols to be static if
|
||||
* CONFIG_KUNIT is not enabled. Otherwise if CONFIG_KUNIT is enabled
|
||||
* there is no change to the symbol definition.
|
||||
*/
|
||||
#define VISIBLE_IF_KUNIT
|
||||
/**
|
||||
* EXPORT_SYMBOL_IF_KUNIT(symbol) - Exports symbol into
|
||||
* EXPORTED_FOR_KUNIT_TESTING namespace only if CONFIG_KUNIT is
|
||||
* enabled. Must use MODULE_IMPORT_NS(EXPORTED_FOR_KUNIT_TESTING)
|
||||
* in test file in order to use symbols.
|
||||
*/
|
||||
#define EXPORT_SYMBOL_IF_KUNIT(symbol) EXPORT_SYMBOL_NS(symbol, \
|
||||
EXPORTED_FOR_KUNIT_TESTING)
|
||||
#else
|
||||
#define VISIBLE_IF_KUNIT static
|
||||
#define EXPORT_SYMBOL_IF_KUNIT(symbol)
|
||||
#endif
|
||||
|
||||
#endif /* _KUNIT_VISIBILITY_H */
|
||||
@@ -2823,6 +2823,22 @@ config RUST_OVERFLOW_CHECKS
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
config RUST_BUILD_ASSERT_ALLOW
|
||||
bool "Allow unoptimized build-time assertions"
|
||||
depends on RUST
|
||||
help
|
||||
Controls how are `build_error!` and `build_assert!` handled during build.
|
||||
|
||||
If calls to them exist in the binary, it may indicate a violated invariant
|
||||
or that the optimizer failed to verify the invariant during compilation.
|
||||
|
||||
This should not happen, thus by default the build is aborted. However,
|
||||
as an escape hatch, you can choose Y here to ignore them during build
|
||||
and let the check be carried at runtime (with `panic!` being called if
|
||||
the check fails).
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
endmenu # "Rust"
|
||||
|
||||
source "Documentation/Kconfig"
|
||||
|
||||
+60
-2
@@ -127,13 +127,15 @@ void kunit_binary_assert_format(const struct kunit_assert *assert,
|
||||
binary_assert->text->right_text);
|
||||
if (!is_literal(stream->test, binary_assert->text->left_text,
|
||||
binary_assert->left_value, stream->gfp))
|
||||
string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == %lld\n",
|
||||
string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == %lld (0x%llx)\n",
|
||||
binary_assert->text->left_text,
|
||||
binary_assert->left_value,
|
||||
binary_assert->left_value);
|
||||
if (!is_literal(stream->test, binary_assert->text->right_text,
|
||||
binary_assert->right_value, stream->gfp))
|
||||
string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == %lld",
|
||||
string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s == %lld (0x%llx)",
|
||||
binary_assert->text->right_text,
|
||||
binary_assert->right_value,
|
||||
binary_assert->right_value);
|
||||
kunit_assert_print_msg(message, stream);
|
||||
}
|
||||
@@ -204,3 +206,59 @@ void kunit_binary_str_assert_format(const struct kunit_assert *assert,
|
||||
kunit_assert_print_msg(message, stream);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(kunit_binary_str_assert_format);
|
||||
|
||||
/* Adds a hexdump of a buffer to a string_stream comparing it with
|
||||
* a second buffer. The different bytes are marked with <>.
|
||||
*/
|
||||
static void kunit_assert_hexdump(struct string_stream *stream,
|
||||
const void *buf,
|
||||
const void *compared_buf,
|
||||
const size_t len)
|
||||
{
|
||||
size_t i;
|
||||
const u8 *buf1 = buf;
|
||||
const u8 *buf2 = compared_buf;
|
||||
|
||||
string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT);
|
||||
|
||||
for (i = 0; i < len; ++i) {
|
||||
if (!(i % 16) && i)
|
||||
string_stream_add(stream, "\n" KUNIT_SUBSUBTEST_INDENT);
|
||||
|
||||
if (buf1[i] != buf2[i])
|
||||
string_stream_add(stream, "<%02x>", buf1[i]);
|
||||
else
|
||||
string_stream_add(stream, " %02x ", buf1[i]);
|
||||
}
|
||||
}
|
||||
|
||||
void kunit_mem_assert_format(const struct kunit_assert *assert,
|
||||
const struct va_format *message,
|
||||
struct string_stream *stream)
|
||||
{
|
||||
struct kunit_mem_assert *mem_assert;
|
||||
|
||||
mem_assert = container_of(assert, struct kunit_mem_assert,
|
||||
assert);
|
||||
|
||||
string_stream_add(stream,
|
||||
KUNIT_SUBTEST_INDENT "Expected %s %s %s, but\n",
|
||||
mem_assert->text->left_text,
|
||||
mem_assert->text->operation,
|
||||
mem_assert->text->right_text);
|
||||
|
||||
string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s ==\n",
|
||||
mem_assert->text->left_text);
|
||||
kunit_assert_hexdump(stream, mem_assert->left_value,
|
||||
mem_assert->right_value, mem_assert->size);
|
||||
|
||||
string_stream_add(stream, "\n");
|
||||
|
||||
string_stream_add(stream, KUNIT_SUBSUBTEST_INDENT "%s ==\n",
|
||||
mem_assert->text->right_text);
|
||||
kunit_assert_hexdump(stream, mem_assert->right_value,
|
||||
mem_assert->left_value, mem_assert->size);
|
||||
|
||||
kunit_assert_print_msg(message, stream);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(kunit_mem_assert_format);
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ static int debugfs_print_results(struct seq_file *seq, void *v)
|
||||
kunit_suite_for_each_test_case(suite, test_case)
|
||||
debugfs_print_result(seq, suite, test_case);
|
||||
|
||||
seq_printf(seq, "%s %d - %s\n",
|
||||
seq_printf(seq, "%s %d %s\n",
|
||||
kunit_status_to_ok_not_ok(success), 1, suite->name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -166,7 +166,7 @@ static void kunit_exec_run_tests(struct suite_set *suite_set)
|
||||
{
|
||||
size_t num_suites = suite_set->end - suite_set->start;
|
||||
|
||||
pr_info("TAP version 14\n");
|
||||
pr_info("KTAP version 1\n");
|
||||
pr_info("1..%zu\n", num_suites);
|
||||
|
||||
__kunit_test_suites_init(suite_set->start, num_suites);
|
||||
@@ -177,8 +177,8 @@ static void kunit_exec_list_tests(struct suite_set *suite_set)
|
||||
struct kunit_suite * const *suites;
|
||||
struct kunit_case *test_case;
|
||||
|
||||
/* Hack: print a tap header so kunit.py can find the start of KUnit output. */
|
||||
pr_info("TAP version 14\n");
|
||||
/* Hack: print a ktap header so kunit.py can find the start of KUnit output. */
|
||||
pr_info("KTAP version 1\n");
|
||||
|
||||
for (suites = suite_set->start; suites < suite_set->end; suites++)
|
||||
kunit_suite_for_each_test_case((*suites), test_case) {
|
||||
|
||||
@@ -86,6 +86,9 @@ static void example_mark_skipped_test(struct kunit *test)
|
||||
*/
|
||||
static void example_all_expect_macros_test(struct kunit *test)
|
||||
{
|
||||
const u32 array1[] = { 0x0F, 0xFF };
|
||||
const u32 array2[] = { 0x1F, 0xFF };
|
||||
|
||||
/* Boolean assertions */
|
||||
KUNIT_EXPECT_TRUE(test, true);
|
||||
KUNIT_EXPECT_FALSE(test, false);
|
||||
@@ -109,6 +112,10 @@ static void example_all_expect_macros_test(struct kunit *test)
|
||||
KUNIT_EXPECT_STREQ(test, "hi", "hi");
|
||||
KUNIT_EXPECT_STRNEQ(test, "hi", "bye");
|
||||
|
||||
/* Memory block assertions */
|
||||
KUNIT_EXPECT_MEMEQ(test, array1, array1, sizeof(array1));
|
||||
KUNIT_EXPECT_MEMNEQ(test, array1, array2, sizeof(array1));
|
||||
|
||||
/*
|
||||
* There are also ASSERT variants of all of the above that abort test
|
||||
* execution if they fail. Useful for memory allocations, etc.
|
||||
|
||||
@@ -131,11 +131,6 @@ bool string_stream_is_empty(struct string_stream *stream)
|
||||
return list_empty(&stream->fragments);
|
||||
}
|
||||
|
||||
struct string_stream_alloc_context {
|
||||
struct kunit *test;
|
||||
gfp_t gfp;
|
||||
};
|
||||
|
||||
struct string_stream *alloc_string_stream(struct kunit *test, gfp_t gfp)
|
||||
{
|
||||
struct string_stream *stream;
|
||||
|
||||
+12
-3
@@ -20,6 +20,8 @@
|
||||
#include "string-stream.h"
|
||||
#include "try-catch-impl.h"
|
||||
|
||||
DEFINE_STATIC_KEY_FALSE(kunit_running);
|
||||
|
||||
#if IS_BUILTIN(CONFIG_KUNIT)
|
||||
/*
|
||||
* Fail the current test and print an error message to the log.
|
||||
@@ -149,6 +151,7 @@ EXPORT_SYMBOL_GPL(kunit_suite_num_test_cases);
|
||||
|
||||
static void kunit_print_suite_start(struct kunit_suite *suite)
|
||||
{
|
||||
kunit_log(KERN_INFO, suite, KUNIT_SUBTEST_INDENT "KTAP version 1\n");
|
||||
kunit_log(KERN_INFO, suite, KUNIT_SUBTEST_INDENT "# Subtest: %s",
|
||||
suite->name);
|
||||
kunit_log(KERN_INFO, suite, KUNIT_SUBTEST_INDENT "1..%zd",
|
||||
@@ -175,13 +178,13 @@ static void kunit_print_ok_not_ok(void *test_or_suite,
|
||||
* representation.
|
||||
*/
|
||||
if (suite)
|
||||
pr_info("%s %zd - %s%s%s\n",
|
||||
pr_info("%s %zd %s%s%s\n",
|
||||
kunit_status_to_ok_not_ok(status),
|
||||
test_number, description, directive_header,
|
||||
(status == KUNIT_SKIPPED) ? directive : "");
|
||||
else
|
||||
kunit_log(KERN_INFO, test,
|
||||
KUNIT_SUBTEST_INDENT "%s %zd - %s%s%s",
|
||||
KUNIT_SUBTEST_INDENT "%s %zd %s%s%s",
|
||||
kunit_status_to_ok_not_ok(status),
|
||||
test_number, description, directive_header,
|
||||
(status == KUNIT_SKIPPED) ? directive : "");
|
||||
@@ -542,6 +545,8 @@ int kunit_run_tests(struct kunit_suite *suite)
|
||||
/* Get initial param. */
|
||||
param_desc[0] = '\0';
|
||||
test.param_value = test_case->generate_params(NULL, param_desc);
|
||||
kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT
|
||||
"KTAP version 1\n");
|
||||
kunit_log(KERN_INFO, &test, KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT
|
||||
"# Subtest: %s", test_case->name);
|
||||
|
||||
@@ -555,7 +560,7 @@ int kunit_run_tests(struct kunit_suite *suite)
|
||||
|
||||
kunit_log(KERN_INFO, &test,
|
||||
KUNIT_SUBTEST_INDENT KUNIT_SUBTEST_INDENT
|
||||
"%s %d - %s",
|
||||
"%s %d %s",
|
||||
kunit_status_to_ok_not_ok(test.status),
|
||||
test.param_index + 1, param_desc);
|
||||
|
||||
@@ -612,10 +617,14 @@ int __kunit_test_suites_init(struct kunit_suite * const * const suites, int num_
|
||||
return 0;
|
||||
}
|
||||
|
||||
static_branch_inc(&kunit_running);
|
||||
|
||||
for (i = 0; i < num_suites; i++) {
|
||||
kunit_init_suite(suites[i]);
|
||||
kunit_run_tests(suites[i]);
|
||||
}
|
||||
|
||||
static_branch_dec(&kunit_running);
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__kunit_test_suites_init);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
#include <kunit/test.h>
|
||||
#include <kunit/test-bug.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
|
||||
@@ -39,6 +39,7 @@
|
||||
#include <linux/memcontrol.h>
|
||||
#include <linux/random.h>
|
||||
#include <kunit/test.h>
|
||||
#include <kunit/test-bug.h>
|
||||
#include <linux/sort.h>
|
||||
|
||||
#include <linux/debugfs.h>
|
||||
@@ -618,7 +619,7 @@ static bool slab_add_kunit_errors(void)
|
||||
{
|
||||
struct kunit_resource *resource;
|
||||
|
||||
if (likely(!current->kunit_test))
|
||||
if (!kunit_get_current_test())
|
||||
return false;
|
||||
|
||||
resource = kunit_find_named_resource(current->kunit_test, "slab_errors");
|
||||
|
||||
@@ -71,11 +71,11 @@ static void dev_addr_test_basic(struct kunit *test)
|
||||
|
||||
memset(addr, 2, sizeof(addr));
|
||||
eth_hw_addr_set(netdev, addr);
|
||||
KUNIT_EXPECT_EQ(test, 0, memcmp(netdev->dev_addr, addr, sizeof(addr)));
|
||||
KUNIT_EXPECT_MEMEQ(test, netdev->dev_addr, addr, sizeof(addr));
|
||||
|
||||
memset(addr, 3, sizeof(addr));
|
||||
dev_addr_set(netdev, addr);
|
||||
KUNIT_EXPECT_EQ(test, 0, memcmp(netdev->dev_addr, addr, sizeof(addr)));
|
||||
KUNIT_EXPECT_MEMEQ(test, netdev->dev_addr, addr, sizeof(addr));
|
||||
}
|
||||
|
||||
static void dev_addr_test_sync_one(struct kunit *test)
|
||||
|
||||
+17
-5
@@ -19,6 +19,12 @@ obj-$(CONFIG_RUST) += alloc.o bindings.o kernel.o
|
||||
always-$(CONFIG_RUST) += exports_alloc_generated.h exports_bindings_generated.h \
|
||||
exports_kernel_generated.h
|
||||
|
||||
ifdef CONFIG_RUST_BUILD_ASSERT_ALLOW
|
||||
obj-$(CONFIG_RUST) += build_error.o
|
||||
else
|
||||
always-$(CONFIG_RUST) += build_error.o
|
||||
endif
|
||||
|
||||
obj-$(CONFIG_RUST) += exports.o
|
||||
|
||||
# Avoids running `$(RUSTC)` for the sysroot when it may not be available.
|
||||
@@ -108,7 +114,7 @@ rustdoc-alloc: $(src)/alloc/lib.rs rustdoc-core rustdoc-compiler_builtins FORCE
|
||||
$(call if_changed,rustdoc)
|
||||
|
||||
rustdoc-kernel: private rustc_target_flags = --extern alloc \
|
||||
--extern macros=$(objtree)/$(obj)/libmacros.so \
|
||||
--extern build_error --extern macros=$(objtree)/$(obj)/libmacros.so \
|
||||
--extern bindings
|
||||
rustdoc-kernel: $(src)/kernel/lib.rs rustdoc-core rustdoc-macros \
|
||||
rustdoc-compiler_builtins rustdoc-alloc $(obj)/libmacros.so \
|
||||
@@ -126,6 +132,9 @@ quiet_cmd_rustc_test_library = RUSTC TL $<
|
||||
-L$(objtree)/$(obj)/test \
|
||||
--crate-name $(subst rusttest-,,$(subst rusttestlib-,,$@)) $<
|
||||
|
||||
rusttestlib-build_error: $(src)/build_error.rs rusttest-prepare FORCE
|
||||
$(call if_changed,rustc_test_library)
|
||||
|
||||
rusttestlib-macros: private rustc_target_flags = --extern proc_macro
|
||||
rusttestlib-macros: private rustc_test_library_proc = yes
|
||||
rusttestlib-macros: $(src)/macros/lib.rs rusttest-prepare FORCE
|
||||
@@ -216,9 +225,9 @@ rusttest-macros: $(src)/macros/lib.rs rusttest-prepare FORCE
|
||||
$(call if_changed,rustdoc_test)
|
||||
|
||||
rusttest-kernel: private rustc_target_flags = --extern alloc \
|
||||
--extern macros --extern bindings
|
||||
--extern build_error --extern macros --extern bindings
|
||||
rusttest-kernel: $(src)/kernel/lib.rs rusttest-prepare \
|
||||
rusttestlib-macros rusttestlib-bindings FORCE
|
||||
rusttestlib-build_error rusttestlib-macros rusttestlib-bindings FORCE
|
||||
$(call if_changed,rustc_test)
|
||||
$(call if_changed,rustc_test_library)
|
||||
|
||||
@@ -366,6 +375,9 @@ $(obj)/alloc.o: private rustc_target_flags = $(alloc-cfgs)
|
||||
$(obj)/alloc.o: $(src)/alloc/lib.rs $(obj)/compiler_builtins.o FORCE
|
||||
$(call if_changed_dep,rustc_library)
|
||||
|
||||
$(obj)/build_error.o: $(src)/build_error.rs $(obj)/compiler_builtins.o FORCE
|
||||
$(call if_changed_dep,rustc_library)
|
||||
|
||||
$(obj)/bindings.o: $(src)/bindings/lib.rs \
|
||||
$(obj)/compiler_builtins.o \
|
||||
$(obj)/bindings/bindings_generated.rs \
|
||||
@@ -373,8 +385,8 @@ $(obj)/bindings.o: $(src)/bindings/lib.rs \
|
||||
$(call if_changed_dep,rustc_library)
|
||||
|
||||
$(obj)/kernel.o: private rustc_target_flags = --extern alloc \
|
||||
--extern macros --extern bindings
|
||||
$(obj)/kernel.o: $(src)/kernel/lib.rs $(obj)/alloc.o \
|
||||
--extern build_error --extern macros --extern bindings
|
||||
$(obj)/kernel.o: $(src)/kernel/lib.rs $(obj)/alloc.o $(obj)/build_error.o \
|
||||
$(obj)/libmacros.so $(obj)/bindings.o FORCE
|
||||
$(call if_changed_dep,rustc_library)
|
||||
|
||||
|
||||
+32
-1
@@ -20,11 +20,11 @@ use crate::collections::TryReserveErrorKind::*;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
#[cfg(not(no_global_oom_handling))]
|
||||
enum AllocInit {
|
||||
/// The contents of the new memory are uninitialized.
|
||||
Uninitialized,
|
||||
/// The new memory is guaranteed to be zeroed.
|
||||
#[allow(dead_code)]
|
||||
Zeroed,
|
||||
}
|
||||
|
||||
@@ -133,6 +133,13 @@ impl<T, A: Allocator> RawVec<T, A> {
|
||||
Self::allocate_in(capacity, AllocInit::Uninitialized, alloc)
|
||||
}
|
||||
|
||||
/// Like `try_with_capacity`, but parameterized over the choice of
|
||||
/// allocator for the returned `RawVec`.
|
||||
#[inline]
|
||||
pub fn try_with_capacity_in(capacity: usize, alloc: A) -> Result<Self, TryReserveError> {
|
||||
Self::try_allocate_in(capacity, AllocInit::Uninitialized, alloc)
|
||||
}
|
||||
|
||||
/// Like `with_capacity_zeroed`, but parameterized over the choice
|
||||
/// of allocator for the returned `RawVec`.
|
||||
#[cfg(not(no_global_oom_handling))]
|
||||
@@ -203,6 +210,30 @@ impl<T, A: Allocator> RawVec<T, A> {
|
||||
}
|
||||
}
|
||||
|
||||
fn try_allocate_in(capacity: usize, init: AllocInit, alloc: A) -> Result<Self, TryReserveError> {
|
||||
// Don't allocate here because `Drop` will not deallocate when `capacity` is 0.
|
||||
if mem::size_of::<T>() == 0 || capacity == 0 {
|
||||
return Ok(Self::new_in(alloc));
|
||||
}
|
||||
|
||||
let layout = Layout::array::<T>(capacity).map_err(|_| CapacityOverflow)?;
|
||||
alloc_guard(layout.size())?;
|
||||
let result = match init {
|
||||
AllocInit::Uninitialized => alloc.allocate(layout),
|
||||
AllocInit::Zeroed => alloc.allocate_zeroed(layout),
|
||||
};
|
||||
let ptr = result.map_err(|_| AllocError { layout, non_exhaustive: () })?;
|
||||
|
||||
// Allocators currently return a `NonNull<[u8]>` whose length
|
||||
// matches the size requested. If that ever changes, the capacity
|
||||
// here should change to `ptr.len() / mem::size_of::<T>()`.
|
||||
Ok(Self {
|
||||
ptr: unsafe { Unique::new_unchecked(ptr.cast().as_ptr()) },
|
||||
cap: capacity,
|
||||
alloc,
|
||||
})
|
||||
}
|
||||
|
||||
/// Reconstitutes a `RawVec` from a pointer, capacity, and allocator.
|
||||
///
|
||||
/// # Safety
|
||||
|
||||
@@ -472,6 +472,48 @@ impl<T> Vec<T> {
|
||||
Self::with_capacity_in(capacity, Global)
|
||||
}
|
||||
|
||||
/// Tries to construct a new, empty `Vec<T>` with the specified capacity.
|
||||
///
|
||||
/// The vector will be able to hold exactly `capacity` elements without
|
||||
/// reallocating. If `capacity` is 0, the vector will not allocate.
|
||||
///
|
||||
/// It is important to note that although the returned vector has the
|
||||
/// *capacity* specified, the vector will have a zero *length*. For an
|
||||
/// explanation of the difference between length and capacity, see
|
||||
/// *[Capacity and reallocation]*.
|
||||
///
|
||||
/// [Capacity and reallocation]: #capacity-and-reallocation
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// let mut vec = Vec::try_with_capacity(10).unwrap();
|
||||
///
|
||||
/// // The vector contains no items, even though it has capacity for more
|
||||
/// assert_eq!(vec.len(), 0);
|
||||
/// assert_eq!(vec.capacity(), 10);
|
||||
///
|
||||
/// // These are all done without reallocating...
|
||||
/// for i in 0..10 {
|
||||
/// vec.push(i);
|
||||
/// }
|
||||
/// assert_eq!(vec.len(), 10);
|
||||
/// assert_eq!(vec.capacity(), 10);
|
||||
///
|
||||
/// // ...but this may make the vector reallocate
|
||||
/// vec.push(11);
|
||||
/// assert_eq!(vec.len(), 11);
|
||||
/// assert!(vec.capacity() >= 11);
|
||||
///
|
||||
/// let mut result = Vec::try_with_capacity(usize::MAX);
|
||||
/// assert!(result.is_err());
|
||||
/// ```
|
||||
#[inline]
|
||||
#[stable(feature = "kernel", since = "1.0.0")]
|
||||
pub fn try_with_capacity(capacity: usize) -> Result<Self, TryReserveError> {
|
||||
Self::try_with_capacity_in(capacity, Global)
|
||||
}
|
||||
|
||||
/// Creates a `Vec<T>` directly from the raw components of another vector.
|
||||
///
|
||||
/// # Safety
|
||||
@@ -617,6 +659,53 @@ impl<T, A: Allocator> Vec<T, A> {
|
||||
Vec { buf: RawVec::with_capacity_in(capacity, alloc), len: 0 }
|
||||
}
|
||||
|
||||
/// Tries to construct a new, empty `Vec<T, A>` with the specified capacity
|
||||
/// with the provided allocator.
|
||||
///
|
||||
/// The vector will be able to hold exactly `capacity` elements without
|
||||
/// reallocating. If `capacity` is 0, the vector will not allocate.
|
||||
///
|
||||
/// It is important to note that although the returned vector has the
|
||||
/// *capacity* specified, the vector will have a zero *length*. For an
|
||||
/// explanation of the difference between length and capacity, see
|
||||
/// *[Capacity and reallocation]*.
|
||||
///
|
||||
/// [Capacity and reallocation]: #capacity-and-reallocation
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// #![feature(allocator_api)]
|
||||
///
|
||||
/// use std::alloc::System;
|
||||
///
|
||||
/// let mut vec = Vec::try_with_capacity_in(10, System).unwrap();
|
||||
///
|
||||
/// // The vector contains no items, even though it has capacity for more
|
||||
/// assert_eq!(vec.len(), 0);
|
||||
/// assert_eq!(vec.capacity(), 10);
|
||||
///
|
||||
/// // These are all done without reallocating...
|
||||
/// for i in 0..10 {
|
||||
/// vec.push(i);
|
||||
/// }
|
||||
/// assert_eq!(vec.len(), 10);
|
||||
/// assert_eq!(vec.capacity(), 10);
|
||||
///
|
||||
/// // ...but this may make the vector reallocate
|
||||
/// vec.push(11);
|
||||
/// assert_eq!(vec.len(), 11);
|
||||
/// assert!(vec.capacity() >= 11);
|
||||
///
|
||||
/// let mut result = Vec::try_with_capacity_in(usize::MAX, System);
|
||||
/// assert!(result.is_err());
|
||||
/// ```
|
||||
#[inline]
|
||||
#[stable(feature = "kernel", since = "1.0.0")]
|
||||
pub fn try_with_capacity_in(capacity: usize, alloc: A) -> Result<Self, TryReserveError> {
|
||||
Ok(Vec { buf: RawVec::try_with_capacity_in(capacity, alloc)?, len: 0 })
|
||||
}
|
||||
|
||||
/// Creates a `Vec<T, A>` directly from the raw components of another vector.
|
||||
///
|
||||
/// # Safety
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
//! Build-time error.
|
||||
//!
|
||||
//! This crate provides a [const function][const-functions] `build_error`, which will panic in
|
||||
//! compile-time if executed in [const context][const-context], and will cause a build error
|
||||
//! if not executed at compile time and the optimizer does not optimise away the call.
|
||||
//!
|
||||
//! It is used by `build_assert!` in the kernel crate, allowing checking of
|
||||
//! conditions that could be checked statically, but could not be enforced in
|
||||
//! Rust yet (e.g. perform some checks in [const functions][const-functions], but those
|
||||
//! functions could still be called in the runtime).
|
||||
//!
|
||||
//! For details on constant evaluation in Rust, please see the [Reference][const-eval].
|
||||
//!
|
||||
//! [const-eval]: https://doc.rust-lang.org/reference/const_eval.html
|
||||
//! [const-functions]: https://doc.rust-lang.org/reference/const_eval.html#const-functions
|
||||
//! [const-context]: https://doc.rust-lang.org/reference/const_eval.html#const-context
|
||||
|
||||
#![no_std]
|
||||
|
||||
/// Panics if executed in [const context][const-context], or triggers a build error if not.
|
||||
///
|
||||
/// [const-context]: https://doc.rust-lang.org/reference/const_eval.html#const-context
|
||||
#[inline(never)]
|
||||
#[cold]
|
||||
#[export_name = "rust_build_error"]
|
||||
#[track_caller]
|
||||
pub const fn build_error(msg: &'static str) -> ! {
|
||||
panic!("{}", msg);
|
||||
}
|
||||
@@ -19,3 +19,8 @@
|
||||
#include "exports_alloc_generated.h"
|
||||
#include "exports_bindings_generated.h"
|
||||
#include "exports_kernel_generated.h"
|
||||
|
||||
// For modules using `rust/build_error.rs`.
|
||||
#ifdef CONFIG_RUST_BUILD_ASSERT_ALLOW
|
||||
EXPORT_SYMBOL_RUST_GPL(rust_build_error);
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
//! Build-time assert.
|
||||
|
||||
/// Fails the build if the code path calling `build_error!` can possibly be executed.
|
||||
///
|
||||
/// If the macro is executed in const context, `build_error!` will panic.
|
||||
/// If the compiler or optimizer cannot guarantee that `build_error!` can never
|
||||
/// be called, a build error will be triggered.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use kernel::build_error;
|
||||
/// #[inline]
|
||||
/// fn foo(a: usize) -> usize {
|
||||
/// a.checked_add(1).unwrap_or_else(|| build_error!("overflow"))
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(foo(usize::MAX - 1), usize::MAX); // OK.
|
||||
/// // foo(usize::MAX); // Fails to compile.
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! build_error {
|
||||
() => {{
|
||||
$crate::build_error("")
|
||||
}};
|
||||
($msg:expr) => {{
|
||||
$crate::build_error($msg)
|
||||
}};
|
||||
}
|
||||
|
||||
/// Asserts that a boolean expression is `true` at compile time.
|
||||
///
|
||||
/// If the condition is evaluated to `false` in const context, `build_assert!`
|
||||
/// will panic. If the compiler or optimizer cannot guarantee the condition will
|
||||
/// be evaluated to `true`, a build error will be triggered.
|
||||
///
|
||||
/// [`static_assert!`] should be preferred to `build_assert!` whenever possible.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// These examples show that different types of [`assert!`] will trigger errors
|
||||
/// at different stage of compilation. It is preferred to err as early as
|
||||
/// possible, so [`static_assert!`] should be used whenever possible.
|
||||
/// ```ignore
|
||||
/// fn foo() {
|
||||
/// static_assert!(1 > 1); // Compile-time error
|
||||
/// build_assert!(1 > 1); // Build-time error
|
||||
/// assert!(1 > 1); // Run-time error
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// When the condition refers to generic parameters or parameters of an inline function,
|
||||
/// [`static_assert!`] cannot be used. Use `build_assert!` in this scenario.
|
||||
/// ```
|
||||
/// fn foo<const N: usize>() {
|
||||
/// // `static_assert!(N > 1);` is not allowed
|
||||
/// build_assert!(N > 1); // Build-time check
|
||||
/// assert!(N > 1); // Run-time check
|
||||
/// }
|
||||
///
|
||||
/// #[inline]
|
||||
/// fn bar(n: usize) {
|
||||
/// // `static_assert!(n > 1);` is not allowed
|
||||
/// build_assert!(n > 1); // Build-time check
|
||||
/// assert!(n > 1); // Run-time check
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! build_assert {
|
||||
($cond:expr $(,)?) => {{
|
||||
if !$cond {
|
||||
$crate::build_error(concat!("assertion failed: ", stringify!($cond)));
|
||||
}
|
||||
}};
|
||||
($cond:expr, $msg:expr) => {{
|
||||
if !$cond {
|
||||
$crate::build_error($msg);
|
||||
}
|
||||
}};
|
||||
}
|
||||
+87
-3
@@ -4,12 +4,60 @@
|
||||
//!
|
||||
//! C header: [`include/uapi/asm-generic/errno-base.h`](../../../include/uapi/asm-generic/errno-base.h)
|
||||
|
||||
use alloc::collections::TryReserveError;
|
||||
use alloc::{
|
||||
alloc::{AllocError, LayoutError},
|
||||
collections::TryReserveError,
|
||||
};
|
||||
|
||||
use core::convert::From;
|
||||
use core::num::TryFromIntError;
|
||||
use core::str::Utf8Error;
|
||||
|
||||
/// Contains the C-compatible error codes.
|
||||
pub mod code {
|
||||
/// Out of memory.
|
||||
pub const ENOMEM: super::Error = super::Error(-(crate::bindings::ENOMEM as i32));
|
||||
macro_rules! declare_err {
|
||||
($err:tt $(,)? $($doc:expr),+) => {
|
||||
$(
|
||||
#[doc = $doc]
|
||||
)*
|
||||
pub const $err: super::Error = super::Error(-(crate::bindings::$err as i32));
|
||||
};
|
||||
}
|
||||
|
||||
declare_err!(EPERM, "Operation not permitted.");
|
||||
declare_err!(ENOENT, "No such file or directory.");
|
||||
declare_err!(ESRCH, "No such process.");
|
||||
declare_err!(EINTR, "Interrupted system call.");
|
||||
declare_err!(EIO, "I/O error.");
|
||||
declare_err!(ENXIO, "No such device or address.");
|
||||
declare_err!(E2BIG, "Argument list too long.");
|
||||
declare_err!(ENOEXEC, "Exec format error.");
|
||||
declare_err!(EBADF, "Bad file number.");
|
||||
declare_err!(ECHILD, "Exec format error.");
|
||||
declare_err!(EAGAIN, "Try again.");
|
||||
declare_err!(ENOMEM, "Out of memory.");
|
||||
declare_err!(EACCES, "Permission denied.");
|
||||
declare_err!(EFAULT, "Bad address.");
|
||||
declare_err!(ENOTBLK, "Block device required.");
|
||||
declare_err!(EBUSY, "Device or resource busy.");
|
||||
declare_err!(EEXIST, "File exists.");
|
||||
declare_err!(EXDEV, "Cross-device link.");
|
||||
declare_err!(ENODEV, "No such device.");
|
||||
declare_err!(ENOTDIR, "Not a directory.");
|
||||
declare_err!(EISDIR, "Is a directory.");
|
||||
declare_err!(EINVAL, "Invalid argument.");
|
||||
declare_err!(ENFILE, "File table overflow.");
|
||||
declare_err!(EMFILE, "Too many open files.");
|
||||
declare_err!(ENOTTY, "Not a typewriter.");
|
||||
declare_err!(ETXTBSY, "Text file busy.");
|
||||
declare_err!(EFBIG, "File too large.");
|
||||
declare_err!(ENOSPC, "No space left on device.");
|
||||
declare_err!(ESPIPE, "Illegal seek.");
|
||||
declare_err!(EROFS, "Read-only file system.");
|
||||
declare_err!(EMLINK, "Too many links.");
|
||||
declare_err!(EPIPE, "Broken pipe.");
|
||||
declare_err!(EDOM, "Math argument out of domain of func.");
|
||||
declare_err!(ERANGE, "Math result not representable.");
|
||||
}
|
||||
|
||||
/// Generic integer kernel error.
|
||||
@@ -30,12 +78,48 @@ impl Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<AllocError> for Error {
|
||||
fn from(_: AllocError) -> Error {
|
||||
code::ENOMEM
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TryFromIntError> for Error {
|
||||
fn from(_: TryFromIntError) -> Error {
|
||||
code::EINVAL
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Utf8Error> for Error {
|
||||
fn from(_: Utf8Error) -> Error {
|
||||
code::EINVAL
|
||||
}
|
||||
}
|
||||
|
||||
impl From<TryReserveError> for Error {
|
||||
fn from(_: TryReserveError) -> Error {
|
||||
code::ENOMEM
|
||||
}
|
||||
}
|
||||
|
||||
impl From<LayoutError> for Error {
|
||||
fn from(_: LayoutError) -> Error {
|
||||
code::ENOMEM
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::fmt::Error> for Error {
|
||||
fn from(_: core::fmt::Error) -> Error {
|
||||
code::EINVAL
|
||||
}
|
||||
}
|
||||
|
||||
impl From<core::convert::Infallible> for Error {
|
||||
fn from(e: core::convert::Infallible) -> Error {
|
||||
match e {}
|
||||
}
|
||||
}
|
||||
|
||||
/// A [`Result`] with an [`Error`] error type.
|
||||
///
|
||||
/// To be used as the return type for functions that may fail.
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
//! do so first instead of bypassing this crate.
|
||||
|
||||
#![no_std]
|
||||
#![feature(allocator_api)]
|
||||
#![feature(core_ffi_c)]
|
||||
|
||||
// Ensure conditional compilation based on the kernel configuration works;
|
||||
@@ -22,15 +23,23 @@ compile_error!("Missing kernel configuration for conditional compilation");
|
||||
#[cfg(not(test))]
|
||||
#[cfg(not(testlib))]
|
||||
mod allocator;
|
||||
mod build_assert;
|
||||
pub mod error;
|
||||
pub mod prelude;
|
||||
pub mod print;
|
||||
mod static_assert;
|
||||
#[doc(hidden)]
|
||||
pub mod std_vendor;
|
||||
pub mod str;
|
||||
pub mod types;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use bindings;
|
||||
pub use macros;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub use build_error::build_error;
|
||||
|
||||
/// Prefix to appear before log messages printed from within the `kernel` crate.
|
||||
const __LOG_PREFIX: &[u8] = b"rust_kernel\0";
|
||||
|
||||
|
||||
+14
-6
@@ -11,10 +11,18 @@
|
||||
//! use kernel::prelude::*;
|
||||
//! ```
|
||||
|
||||
pub use super::{
|
||||
error::{Error, Result},
|
||||
pr_emerg, pr_info, ThisModule,
|
||||
};
|
||||
pub use alloc::{boxed::Box, vec::Vec};
|
||||
pub use core::pin::Pin;
|
||||
pub use macros::module;
|
||||
|
||||
pub use alloc::{boxed::Box, vec::Vec};
|
||||
|
||||
pub use macros::{module, vtable};
|
||||
|
||||
pub use super::build_assert;
|
||||
|
||||
pub use super::{dbg, pr_alert, pr_crit, pr_debug, pr_emerg, pr_err, pr_info, pr_notice, pr_warn};
|
||||
|
||||
pub use super::static_assert;
|
||||
|
||||
pub use super::error::{code::*, Error, Result};
|
||||
|
||||
pub use super::{str::CStr, ThisModule};
|
||||
|
||||
+211
-3
@@ -74,7 +74,14 @@ pub mod format_strings {
|
||||
// Furthermore, `static` instead of `const` is used to share the strings
|
||||
// for all the kernel.
|
||||
pub static EMERG: [u8; LENGTH] = generate(false, bindings::KERN_EMERG);
|
||||
pub static ALERT: [u8; LENGTH] = generate(false, bindings::KERN_ALERT);
|
||||
pub static CRIT: [u8; LENGTH] = generate(false, bindings::KERN_CRIT);
|
||||
pub static ERR: [u8; LENGTH] = generate(false, bindings::KERN_ERR);
|
||||
pub static WARNING: [u8; LENGTH] = generate(false, bindings::KERN_WARNING);
|
||||
pub static NOTICE: [u8; LENGTH] = generate(false, bindings::KERN_NOTICE);
|
||||
pub static INFO: [u8; LENGTH] = generate(false, bindings::KERN_INFO);
|
||||
pub static DEBUG: [u8; LENGTH] = generate(false, bindings::KERN_DEBUG);
|
||||
pub static CONT: [u8; LENGTH] = generate(true, bindings::KERN_CONT);
|
||||
}
|
||||
|
||||
/// Prints a message via the kernel's [`_printk`].
|
||||
@@ -105,6 +112,26 @@ pub unsafe fn call_printk(
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a message via the kernel's [`_printk`] for the `CONT` level.
|
||||
///
|
||||
/// Public but hidden since it should only be used from public macros.
|
||||
///
|
||||
/// [`_printk`]: ../../../../include/linux/printk.h
|
||||
#[doc(hidden)]
|
||||
#[cfg_attr(not(CONFIG_PRINTK), allow(unused_variables))]
|
||||
pub fn call_printk_cont(args: fmt::Arguments<'_>) {
|
||||
// `_printk` does not seem to fail in any path.
|
||||
//
|
||||
// SAFETY: The format string is fixed.
|
||||
#[cfg(CONFIG_PRINTK)]
|
||||
unsafe {
|
||||
bindings::_printk(
|
||||
format_strings::CONT.as_ptr() as _,
|
||||
&args as *const _ as *const c_void,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs formatting and forwards the string to [`call_printk`].
|
||||
///
|
||||
/// Public but hidden since it should only be used from public macros.
|
||||
@@ -114,7 +141,7 @@ pub unsafe fn call_printk(
|
||||
#[allow(clippy::crate_in_macro_def)]
|
||||
macro_rules! print_macro (
|
||||
// The non-continuation cases (most of them, e.g. `INFO`).
|
||||
($format_string:path, $($arg:tt)+) => (
|
||||
($format_string:path, false, $($arg:tt)+) => (
|
||||
// SAFETY: This hidden macro should only be called by the documented
|
||||
// printing macros which ensure the format string is one of the fixed
|
||||
// ones. All `__LOG_PREFIX`s are null-terminated as they are generated
|
||||
@@ -128,6 +155,13 @@ macro_rules! print_macro (
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
// The `CONT` case.
|
||||
($format_string:path, true, $($arg:tt)+) => (
|
||||
$crate::print::call_printk_cont(
|
||||
format_args!($($arg)+),
|
||||
);
|
||||
);
|
||||
);
|
||||
|
||||
/// Stub for doctests
|
||||
@@ -168,7 +202,127 @@ macro_rules! print_macro (
|
||||
#[macro_export]
|
||||
macro_rules! pr_emerg (
|
||||
($($arg:tt)*) => (
|
||||
$crate::print_macro!($crate::print::format_strings::EMERG, $($arg)*)
|
||||
$crate::print_macro!($crate::print::format_strings::EMERG, false, $($arg)*)
|
||||
)
|
||||
);
|
||||
|
||||
/// Prints an alert-level message (level 1).
|
||||
///
|
||||
/// Use this level if action must be taken immediately.
|
||||
///
|
||||
/// Equivalent to the kernel's [`pr_alert`] macro.
|
||||
///
|
||||
/// Mimics the interface of [`std::print!`]. See [`core::fmt`] and
|
||||
/// `alloc::format!` for information about the formatting syntax.
|
||||
///
|
||||
/// [`pr_alert`]: https://www.kernel.org/doc/html/latest/core-api/printk-basics.html#c.pr_alert
|
||||
/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// pr_alert!("hello {}\n", "there");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! pr_alert (
|
||||
($($arg:tt)*) => (
|
||||
$crate::print_macro!($crate::print::format_strings::ALERT, false, $($arg)*)
|
||||
)
|
||||
);
|
||||
|
||||
/// Prints a critical-level message (level 2).
|
||||
///
|
||||
/// Use this level for critical conditions.
|
||||
///
|
||||
/// Equivalent to the kernel's [`pr_crit`] macro.
|
||||
///
|
||||
/// Mimics the interface of [`std::print!`]. See [`core::fmt`] and
|
||||
/// `alloc::format!` for information about the formatting syntax.
|
||||
///
|
||||
/// [`pr_crit`]: https://www.kernel.org/doc/html/latest/core-api/printk-basics.html#c.pr_crit
|
||||
/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// pr_crit!("hello {}\n", "there");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! pr_crit (
|
||||
($($arg:tt)*) => (
|
||||
$crate::print_macro!($crate::print::format_strings::CRIT, false, $($arg)*)
|
||||
)
|
||||
);
|
||||
|
||||
/// Prints an error-level message (level 3).
|
||||
///
|
||||
/// Use this level for error conditions.
|
||||
///
|
||||
/// Equivalent to the kernel's [`pr_err`] macro.
|
||||
///
|
||||
/// Mimics the interface of [`std::print!`]. See [`core::fmt`] and
|
||||
/// `alloc::format!` for information about the formatting syntax.
|
||||
///
|
||||
/// [`pr_err`]: https://www.kernel.org/doc/html/latest/core-api/printk-basics.html#c.pr_err
|
||||
/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// pr_err!("hello {}\n", "there");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! pr_err (
|
||||
($($arg:tt)*) => (
|
||||
$crate::print_macro!($crate::print::format_strings::ERR, false, $($arg)*)
|
||||
)
|
||||
);
|
||||
|
||||
/// Prints a warning-level message (level 4).
|
||||
///
|
||||
/// Use this level for warning conditions.
|
||||
///
|
||||
/// Equivalent to the kernel's [`pr_warn`] macro.
|
||||
///
|
||||
/// Mimics the interface of [`std::print!`]. See [`core::fmt`] and
|
||||
/// `alloc::format!` for information about the formatting syntax.
|
||||
///
|
||||
/// [`pr_warn`]: https://www.kernel.org/doc/html/latest/core-api/printk-basics.html#c.pr_warn
|
||||
/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// pr_warn!("hello {}\n", "there");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! pr_warn (
|
||||
($($arg:tt)*) => (
|
||||
$crate::print_macro!($crate::print::format_strings::WARNING, false, $($arg)*)
|
||||
)
|
||||
);
|
||||
|
||||
/// Prints a notice-level message (level 5).
|
||||
///
|
||||
/// Use this level for normal but significant conditions.
|
||||
///
|
||||
/// Equivalent to the kernel's [`pr_notice`] macro.
|
||||
///
|
||||
/// Mimics the interface of [`std::print!`]. See [`core::fmt`] and
|
||||
/// `alloc::format!` for information about the formatting syntax.
|
||||
///
|
||||
/// [`pr_notice`]: https://www.kernel.org/doc/html/latest/core-api/printk-basics.html#c.pr_notice
|
||||
/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// pr_notice!("hello {}\n", "there");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! pr_notice (
|
||||
($($arg:tt)*) => (
|
||||
$crate::print_macro!($crate::print::format_strings::NOTICE, false, $($arg)*)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -193,6 +347,60 @@ macro_rules! pr_emerg (
|
||||
#[doc(alias = "print")]
|
||||
macro_rules! pr_info (
|
||||
($($arg:tt)*) => (
|
||||
$crate::print_macro!($crate::print::format_strings::INFO, $($arg)*)
|
||||
$crate::print_macro!($crate::print::format_strings::INFO, false, $($arg)*)
|
||||
)
|
||||
);
|
||||
|
||||
/// Prints a debug-level message (level 7).
|
||||
///
|
||||
/// Use this level for debug messages.
|
||||
///
|
||||
/// Equivalent to the kernel's [`pr_debug`] macro, except that it doesn't support dynamic debug
|
||||
/// yet.
|
||||
///
|
||||
/// Mimics the interface of [`std::print!`]. See [`core::fmt`] and
|
||||
/// `alloc::format!` for information about the formatting syntax.
|
||||
///
|
||||
/// [`pr_debug`]: https://www.kernel.org/doc/html/latest/core-api/printk-basics.html#c.pr_debug
|
||||
/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// pr_debug!("hello {}\n", "there");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
#[doc(alias = "print")]
|
||||
macro_rules! pr_debug (
|
||||
($($arg:tt)*) => (
|
||||
if cfg!(debug_assertions) {
|
||||
$crate::print_macro!($crate::print::format_strings::DEBUG, false, $($arg)*)
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
/// Continues a previous log message in the same line.
|
||||
///
|
||||
/// Use only when continuing a previous `pr_*!` macro (e.g. [`pr_info!`]).
|
||||
///
|
||||
/// Equivalent to the kernel's [`pr_cont`] macro.
|
||||
///
|
||||
/// Mimics the interface of [`std::print!`]. See [`core::fmt`] and
|
||||
/// `alloc::format!` for information about the formatting syntax.
|
||||
///
|
||||
/// [`pr_cont`]: https://www.kernel.org/doc/html/latest/core-api/printk-basics.html#c.pr_cont
|
||||
/// [`std::print!`]: https://doc.rust-lang.org/std/macro.print.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use kernel::pr_cont;
|
||||
/// pr_info!("hello");
|
||||
/// pr_cont!(" {}\n", "there");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! pr_cont (
|
||||
($($arg:tt)*) => (
|
||||
$crate::print_macro!($crate::print::format_strings::CONT, true, $($arg)*)
|
||||
)
|
||||
);
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
//! Static assert.
|
||||
|
||||
/// Static assert (i.e. compile-time assert).
|
||||
///
|
||||
/// Similar to C11 [`_Static_assert`] and C++11 [`static_assert`].
|
||||
///
|
||||
/// The feature may be added to Rust in the future: see [RFC 2790].
|
||||
///
|
||||
/// [`_Static_assert`]: https://en.cppreference.com/w/c/language/_Static_assert
|
||||
/// [`static_assert`]: https://en.cppreference.com/w/cpp/language/static_assert
|
||||
/// [RFC 2790]: https://github.com/rust-lang/rfcs/issues/2790
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// static_assert!(42 > 24);
|
||||
/// static_assert!(core::mem::size_of::<u8>() == 1);
|
||||
///
|
||||
/// const X: &[u8] = b"bar";
|
||||
/// static_assert!(X[1] == b'a');
|
||||
///
|
||||
/// const fn f(x: i32) -> i32 {
|
||||
/// x + 2
|
||||
/// }
|
||||
/// static_assert!(f(40) == 42);
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! static_assert {
|
||||
($condition:expr) => {
|
||||
const _: () = core::assert!($condition);
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
//! The contents of this file come from the Rust standard library, hosted in
|
||||
//! the <https://github.com/rust-lang/rust> repository, licensed under
|
||||
//! "Apache-2.0 OR MIT" and adapted for kernel use. For copyright details,
|
||||
//! see <https://github.com/rust-lang/rust/blob/master/COPYRIGHT>.
|
||||
|
||||
/// [`std::dbg`], but using [`pr_info`] instead of [`eprintln`].
|
||||
///
|
||||
/// Prints and returns the value of a given expression for quick and dirty
|
||||
/// debugging.
|
||||
///
|
||||
/// An example:
|
||||
///
|
||||
/// ```rust
|
||||
/// let a = 2;
|
||||
/// # #[allow(clippy::dbg_macro)]
|
||||
/// let b = dbg!(a * 2) + 1;
|
||||
/// // ^-- prints: [src/main.rs:2] a * 2 = 4
|
||||
/// assert_eq!(b, 5);
|
||||
/// ```
|
||||
///
|
||||
/// The macro works by using the `Debug` implementation of the type of
|
||||
/// the given expression to print the value with [`printk`] along with the
|
||||
/// source location of the macro invocation as well as the source code
|
||||
/// of the expression.
|
||||
///
|
||||
/// Invoking the macro on an expression moves and takes ownership of it
|
||||
/// before returning the evaluated expression unchanged. If the type
|
||||
/// of the expression does not implement `Copy` and you don't want
|
||||
/// to give up ownership, you can instead borrow with `dbg!(&expr)`
|
||||
/// for some expression `expr`.
|
||||
///
|
||||
/// The `dbg!` macro works exactly the same in release builds.
|
||||
/// This is useful when debugging issues that only occur in release
|
||||
/// builds or when debugging in release mode is significantly faster.
|
||||
///
|
||||
/// Note that the macro is intended as a temporary debugging tool to be
|
||||
/// used during development. Therefore, avoid committing `dbg!` macro
|
||||
/// invocations into the kernel tree.
|
||||
///
|
||||
/// For debug output that is intended to be kept in the kernel tree,
|
||||
/// use [`pr_debug`] and similar facilities instead.
|
||||
///
|
||||
/// # Stability
|
||||
///
|
||||
/// The exact output printed by this macro should not be relied upon
|
||||
/// and is subject to future changes.
|
||||
///
|
||||
/// # Further examples
|
||||
///
|
||||
/// With a method call:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[allow(clippy::dbg_macro)]
|
||||
/// fn foo(n: usize) {
|
||||
/// if dbg!(n.checked_sub(4)).is_some() {
|
||||
/// // ...
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// foo(3)
|
||||
/// ```
|
||||
///
|
||||
/// This prints to the kernel log:
|
||||
///
|
||||
/// ```text,ignore
|
||||
/// [src/main.rs:4] n.checked_sub(4) = None
|
||||
/// ```
|
||||
///
|
||||
/// Naive factorial implementation:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[allow(clippy::dbg_macro)]
|
||||
/// # {
|
||||
/// fn factorial(n: u32) -> u32 {
|
||||
/// if dbg!(n <= 1) {
|
||||
/// dbg!(1)
|
||||
/// } else {
|
||||
/// dbg!(n * factorial(n - 1))
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// dbg!(factorial(4));
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// This prints to the kernel log:
|
||||
///
|
||||
/// ```text,ignore
|
||||
/// [src/main.rs:3] n <= 1 = false
|
||||
/// [src/main.rs:3] n <= 1 = false
|
||||
/// [src/main.rs:3] n <= 1 = false
|
||||
/// [src/main.rs:3] n <= 1 = true
|
||||
/// [src/main.rs:4] 1 = 1
|
||||
/// [src/main.rs:5] n * factorial(n - 1) = 2
|
||||
/// [src/main.rs:5] n * factorial(n - 1) = 6
|
||||
/// [src/main.rs:5] n * factorial(n - 1) = 24
|
||||
/// [src/main.rs:11] factorial(4) = 24
|
||||
/// ```
|
||||
///
|
||||
/// The `dbg!(..)` macro moves the input:
|
||||
///
|
||||
/// ```ignore
|
||||
/// /// A wrapper around `usize` which importantly is not Copyable.
|
||||
/// #[derive(Debug)]
|
||||
/// struct NoCopy(usize);
|
||||
///
|
||||
/// let a = NoCopy(42);
|
||||
/// let _ = dbg!(a); // <-- `a` is moved here.
|
||||
/// let _ = dbg!(a); // <-- `a` is moved again; error!
|
||||
/// ```
|
||||
///
|
||||
/// You can also use `dbg!()` without a value to just print the
|
||||
/// file and line whenever it's reached.
|
||||
///
|
||||
/// Finally, if you want to `dbg!(..)` multiple values, it will treat them as
|
||||
/// a tuple (and return it, too):
|
||||
///
|
||||
/// ```
|
||||
/// # #[allow(clippy::dbg_macro)]
|
||||
/// assert_eq!(dbg!(1usize, 2u32), (1, 2));
|
||||
/// ```
|
||||
///
|
||||
/// However, a single argument with a trailing comma will still not be treated
|
||||
/// as a tuple, following the convention of ignoring trailing commas in macro
|
||||
/// invocations. You can use a 1-tuple directly if you need one:
|
||||
///
|
||||
/// ```
|
||||
/// # #[allow(clippy::dbg_macro)]
|
||||
/// # {
|
||||
/// assert_eq!(1, dbg!(1u32,)); // trailing comma ignored
|
||||
/// assert_eq!((1,), dbg!((1u32,))); // 1-tuple
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// [`std::dbg`]: https://doc.rust-lang.org/std/macro.dbg.html
|
||||
/// [`eprintln`]: https://doc.rust-lang.org/std/macro.eprintln.html
|
||||
/// [`printk`]: https://www.kernel.org/doc/html/latest/core-api/printk-basics.html
|
||||
#[macro_export]
|
||||
macro_rules! dbg {
|
||||
// NOTE: We cannot use `concat!` to make a static string as a format argument
|
||||
// of `pr_info!` because `file!` could contain a `{` or
|
||||
// `$val` expression could be a block (`{ .. }`), in which case the `pr_info!`
|
||||
// will be malformed.
|
||||
() => {
|
||||
$crate::pr_info!("[{}:{}]\n", ::core::file!(), ::core::line!())
|
||||
};
|
||||
($val:expr $(,)?) => {
|
||||
// Use of `match` here is intentional because it affects the lifetimes
|
||||
// of temporaries - https://stackoverflow.com/a/48732525/1063961
|
||||
match $val {
|
||||
tmp => {
|
||||
$crate::pr_info!("[{}:{}] {} = {:#?}\n",
|
||||
::core::file!(), ::core::line!(), ::core::stringify!($val), &tmp);
|
||||
tmp
|
||||
}
|
||||
}
|
||||
};
|
||||
($($val:expr),+ $(,)?) => {
|
||||
($($crate::dbg!($val)),+,)
|
||||
};
|
||||
}
|
||||
+521
-2
@@ -2,7 +2,377 @@
|
||||
|
||||
//! String representations.
|
||||
|
||||
use core::fmt;
|
||||
use alloc::vec::Vec;
|
||||
use core::fmt::{self, Write};
|
||||
use core::ops::{self, Deref, Index};
|
||||
|
||||
use crate::{
|
||||
bindings,
|
||||
error::{code::*, Error},
|
||||
};
|
||||
|
||||
/// Byte string without UTF-8 validity guarantee.
|
||||
///
|
||||
/// `BStr` is simply an alias to `[u8]`, but has a more evident semantical meaning.
|
||||
pub type BStr = [u8];
|
||||
|
||||
/// Creates a new [`BStr`] from a string literal.
|
||||
///
|
||||
/// `b_str!` converts the supplied string literal to byte string, so non-ASCII
|
||||
/// characters can be included.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use kernel::b_str;
|
||||
/// # use kernel::str::BStr;
|
||||
/// const MY_BSTR: &BStr = b_str!("My awesome BStr!");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! b_str {
|
||||
($str:literal) => {{
|
||||
const S: &'static str = $str;
|
||||
const C: &'static $crate::str::BStr = S.as_bytes();
|
||||
C
|
||||
}};
|
||||
}
|
||||
|
||||
/// Possible errors when using conversion functions in [`CStr`].
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum CStrConvertError {
|
||||
/// Supplied bytes contain an interior `NUL`.
|
||||
InteriorNul,
|
||||
|
||||
/// Supplied bytes are not terminated by `NUL`.
|
||||
NotNulTerminated,
|
||||
}
|
||||
|
||||
impl From<CStrConvertError> for Error {
|
||||
#[inline]
|
||||
fn from(_: CStrConvertError) -> Error {
|
||||
EINVAL
|
||||
}
|
||||
}
|
||||
|
||||
/// A string that is guaranteed to have exactly one `NUL` byte, which is at the
|
||||
/// end.
|
||||
///
|
||||
/// Used for interoperability with kernel APIs that take C strings.
|
||||
#[repr(transparent)]
|
||||
pub struct CStr([u8]);
|
||||
|
||||
impl CStr {
|
||||
/// Returns the length of this string excluding `NUL`.
|
||||
#[inline]
|
||||
pub const fn len(&self) -> usize {
|
||||
self.len_with_nul() - 1
|
||||
}
|
||||
|
||||
/// Returns the length of this string with `NUL`.
|
||||
#[inline]
|
||||
pub const fn len_with_nul(&self) -> usize {
|
||||
// SAFETY: This is one of the invariant of `CStr`.
|
||||
// We add a `unreachable_unchecked` here to hint the optimizer that
|
||||
// the value returned from this function is non-zero.
|
||||
if self.0.is_empty() {
|
||||
unsafe { core::hint::unreachable_unchecked() };
|
||||
}
|
||||
self.0.len()
|
||||
}
|
||||
|
||||
/// Returns `true` if the string only includes `NUL`.
|
||||
#[inline]
|
||||
pub const fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
/// Wraps a raw C string pointer.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `ptr` must be a valid pointer to a `NUL`-terminated C string, and it must
|
||||
/// last at least `'a`. When `CStr` is alive, the memory pointed by `ptr`
|
||||
/// must not be mutated.
|
||||
#[inline]
|
||||
pub unsafe fn from_char_ptr<'a>(ptr: *const core::ffi::c_char) -> &'a Self {
|
||||
// SAFETY: The safety precondition guarantees `ptr` is a valid pointer
|
||||
// to a `NUL`-terminated C string.
|
||||
let len = unsafe { bindings::strlen(ptr) } + 1;
|
||||
// SAFETY: Lifetime guaranteed by the safety precondition.
|
||||
let bytes = unsafe { core::slice::from_raw_parts(ptr as _, len as _) };
|
||||
// SAFETY: As `len` is returned by `strlen`, `bytes` does not contain interior `NUL`.
|
||||
// As we have added 1 to `len`, the last byte is known to be `NUL`.
|
||||
unsafe { Self::from_bytes_with_nul_unchecked(bytes) }
|
||||
}
|
||||
|
||||
/// Creates a [`CStr`] from a `[u8]`.
|
||||
///
|
||||
/// The provided slice must be `NUL`-terminated, does not contain any
|
||||
/// interior `NUL` bytes.
|
||||
pub const fn from_bytes_with_nul(bytes: &[u8]) -> Result<&Self, CStrConvertError> {
|
||||
if bytes.is_empty() {
|
||||
return Err(CStrConvertError::NotNulTerminated);
|
||||
}
|
||||
if bytes[bytes.len() - 1] != 0 {
|
||||
return Err(CStrConvertError::NotNulTerminated);
|
||||
}
|
||||
let mut i = 0;
|
||||
// `i + 1 < bytes.len()` allows LLVM to optimize away bounds checking,
|
||||
// while it couldn't optimize away bounds checks for `i < bytes.len() - 1`.
|
||||
while i + 1 < bytes.len() {
|
||||
if bytes[i] == 0 {
|
||||
return Err(CStrConvertError::InteriorNul);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
// SAFETY: We just checked that all properties hold.
|
||||
Ok(unsafe { Self::from_bytes_with_nul_unchecked(bytes) })
|
||||
}
|
||||
|
||||
/// Creates a [`CStr`] from a `[u8]` without performing any additional
|
||||
/// checks.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// `bytes` *must* end with a `NUL` byte, and should only have a single
|
||||
/// `NUL` byte (or the string will be truncated).
|
||||
#[inline]
|
||||
pub const unsafe fn from_bytes_with_nul_unchecked(bytes: &[u8]) -> &CStr {
|
||||
// SAFETY: Properties of `bytes` guaranteed by the safety precondition.
|
||||
unsafe { core::mem::transmute(bytes) }
|
||||
}
|
||||
|
||||
/// Returns a C pointer to the string.
|
||||
#[inline]
|
||||
pub const fn as_char_ptr(&self) -> *const core::ffi::c_char {
|
||||
self.0.as_ptr() as _
|
||||
}
|
||||
|
||||
/// Convert the string to a byte slice without the trailing 0 byte.
|
||||
#[inline]
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.0[..self.len()]
|
||||
}
|
||||
|
||||
/// Convert the string to a byte slice containing the trailing 0 byte.
|
||||
#[inline]
|
||||
pub const fn as_bytes_with_nul(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Yields a [`&str`] slice if the [`CStr`] contains valid UTF-8.
|
||||
///
|
||||
/// If the contents of the [`CStr`] are valid UTF-8 data, this
|
||||
/// function will return the corresponding [`&str`] slice. Otherwise,
|
||||
/// it will return an error with details of where UTF-8 validation failed.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use kernel::str::CStr;
|
||||
/// let cstr = CStr::from_bytes_with_nul(b"foo\0").unwrap();
|
||||
/// assert_eq!(cstr.to_str(), Ok("foo"));
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn to_str(&self) -> Result<&str, core::str::Utf8Error> {
|
||||
core::str::from_utf8(self.as_bytes())
|
||||
}
|
||||
|
||||
/// Unsafely convert this [`CStr`] into a [`&str`], without checking for
|
||||
/// valid UTF-8.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The contents must be valid UTF-8.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use kernel::c_str;
|
||||
/// # use kernel::str::CStr;
|
||||
/// // SAFETY: String literals are guaranteed to be valid UTF-8
|
||||
/// // by the Rust compiler.
|
||||
/// let bar = c_str!("ツ");
|
||||
/// assert_eq!(unsafe { bar.as_str_unchecked() }, "ツ");
|
||||
/// ```
|
||||
#[inline]
|
||||
pub unsafe fn as_str_unchecked(&self) -> &str {
|
||||
unsafe { core::str::from_utf8_unchecked(self.as_bytes()) }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for CStr {
|
||||
/// Formats printable ASCII characters, escaping the rest.
|
||||
///
|
||||
/// ```
|
||||
/// # use kernel::c_str;
|
||||
/// # use kernel::str::CStr;
|
||||
/// # use kernel::str::CString;
|
||||
/// let penguin = c_str!("🐧");
|
||||
/// let s = CString::try_from_fmt(fmt!("{}", penguin)).unwrap();
|
||||
/// assert_eq!(s.as_bytes_with_nul(), "\\xf0\\x9f\\x90\\xa7\0".as_bytes());
|
||||
///
|
||||
/// let ascii = c_str!("so \"cool\"");
|
||||
/// let s = CString::try_from_fmt(fmt!("{}", ascii)).unwrap();
|
||||
/// assert_eq!(s.as_bytes_with_nul(), "so \"cool\"\0".as_bytes());
|
||||
/// ```
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
for &c in self.as_bytes() {
|
||||
if (0x20..0x7f).contains(&c) {
|
||||
// Printable character.
|
||||
f.write_char(c as char)?;
|
||||
} else {
|
||||
write!(f, "\\x{:02x}", c)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for CStr {
|
||||
/// Formats printable ASCII characters with a double quote on either end, escaping the rest.
|
||||
///
|
||||
/// ```
|
||||
/// # use kernel::c_str;
|
||||
/// # use kernel::str::CStr;
|
||||
/// # use kernel::str::CString;
|
||||
/// let penguin = c_str!("🐧");
|
||||
/// let s = CString::try_from_fmt(fmt!("{:?}", penguin)).unwrap();
|
||||
/// assert_eq!(s.as_bytes_with_nul(), "\"\\xf0\\x9f\\x90\\xa7\"\0".as_bytes());
|
||||
///
|
||||
/// // Embedded double quotes are escaped.
|
||||
/// let ascii = c_str!("so \"cool\"");
|
||||
/// let s = CString::try_from_fmt(fmt!("{:?}", ascii)).unwrap();
|
||||
/// assert_eq!(s.as_bytes_with_nul(), "\"so \\\"cool\\\"\"\0".as_bytes());
|
||||
/// ```
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("\"")?;
|
||||
for &c in self.as_bytes() {
|
||||
match c {
|
||||
// Printable characters.
|
||||
b'\"' => f.write_str("\\\"")?,
|
||||
0x20..=0x7e => f.write_char(c as char)?,
|
||||
_ => write!(f, "\\x{:02x}", c)?,
|
||||
}
|
||||
}
|
||||
f.write_str("\"")
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<BStr> for CStr {
|
||||
#[inline]
|
||||
fn as_ref(&self) -> &BStr {
|
||||
self.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CStr {
|
||||
type Target = BStr;
|
||||
|
||||
#[inline]
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.as_bytes()
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<ops::RangeFrom<usize>> for CStr {
|
||||
type Output = CStr;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: ops::RangeFrom<usize>) -> &Self::Output {
|
||||
// Delegate bounds checking to slice.
|
||||
// Assign to _ to mute clippy's unnecessary operation warning.
|
||||
let _ = &self.as_bytes()[index.start..];
|
||||
// SAFETY: We just checked the bounds.
|
||||
unsafe { Self::from_bytes_with_nul_unchecked(&self.0[index.start..]) }
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<ops::RangeFull> for CStr {
|
||||
type Output = CStr;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, _index: ops::RangeFull) -> &Self::Output {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
mod private {
|
||||
use core::ops;
|
||||
|
||||
// Marker trait for index types that can be forward to `BStr`.
|
||||
pub trait CStrIndex {}
|
||||
|
||||
impl CStrIndex for usize {}
|
||||
impl CStrIndex for ops::Range<usize> {}
|
||||
impl CStrIndex for ops::RangeInclusive<usize> {}
|
||||
impl CStrIndex for ops::RangeToInclusive<usize> {}
|
||||
}
|
||||
|
||||
impl<Idx> Index<Idx> for CStr
|
||||
where
|
||||
Idx: private::CStrIndex,
|
||||
BStr: Index<Idx>,
|
||||
{
|
||||
type Output = <BStr as Index<Idx>>::Output;
|
||||
|
||||
#[inline]
|
||||
fn index(&self, index: Idx) -> &Self::Output {
|
||||
&self.as_bytes()[index]
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new [`CStr`] from a string literal.
|
||||
///
|
||||
/// The string literal should not contain any `NUL` bytes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// # use kernel::c_str;
|
||||
/// # use kernel::str::CStr;
|
||||
/// const MY_CSTR: &CStr = c_str!("My awesome CStr!");
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! c_str {
|
||||
($str:expr) => {{
|
||||
const S: &str = concat!($str, "\0");
|
||||
const C: &$crate::str::CStr = match $crate::str::CStr::from_bytes_with_nul(S.as_bytes()) {
|
||||
Ok(v) => v,
|
||||
Err(_) => panic!("string contains interior NUL"),
|
||||
};
|
||||
C
|
||||
}};
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_cstr_to_str() {
|
||||
let good_bytes = b"\xf0\x9f\xa6\x80\0";
|
||||
let checked_cstr = CStr::from_bytes_with_nul(good_bytes).unwrap();
|
||||
let checked_str = checked_cstr.to_str().unwrap();
|
||||
assert_eq!(checked_str, "🦀");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn test_cstr_to_str_panic() {
|
||||
let bad_bytes = b"\xc3\x28\0";
|
||||
let checked_cstr = CStr::from_bytes_with_nul(bad_bytes).unwrap();
|
||||
checked_cstr.to_str().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_cstr_as_str_unchecked() {
|
||||
let good_bytes = b"\xf0\x9f\x90\xA7\0";
|
||||
let checked_cstr = CStr::from_bytes_with_nul(good_bytes).unwrap();
|
||||
let unchecked_str = unsafe { checked_cstr.as_str_unchecked() };
|
||||
assert_eq!(unchecked_str, "🐧");
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows formatting of [`fmt::Arguments`] into a raw buffer.
|
||||
///
|
||||
@@ -15,13 +385,22 @@ use core::fmt;
|
||||
/// is less than `end`.
|
||||
pub(crate) struct RawFormatter {
|
||||
// Use `usize` to use `saturating_*` functions.
|
||||
#[allow(dead_code)]
|
||||
beg: usize,
|
||||
pos: usize,
|
||||
end: usize,
|
||||
}
|
||||
|
||||
impl RawFormatter {
|
||||
/// Creates a new instance of [`RawFormatter`] with an empty buffer.
|
||||
fn new() -> Self {
|
||||
// INVARIANT: The buffer is empty, so the region that needs to be writable is empty.
|
||||
Self {
|
||||
beg: 0,
|
||||
pos: 0,
|
||||
end: 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new instance of [`RawFormatter`] with the given buffer pointers.
|
||||
///
|
||||
/// # Safety
|
||||
@@ -37,12 +416,34 @@ impl RawFormatter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a new instance of [`RawFormatter`] with the given buffer.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The memory region starting at `buf` and extending for `len` bytes must be valid for writes
|
||||
/// for the lifetime of the returned [`RawFormatter`].
|
||||
pub(crate) unsafe fn from_buffer(buf: *mut u8, len: usize) -> Self {
|
||||
let pos = buf as usize;
|
||||
// INVARIANT: We ensure that `end` is never less then `buf`, and the safety requirements
|
||||
// guarantees that the memory region is valid for writes.
|
||||
Self {
|
||||
pos,
|
||||
beg: pos,
|
||||
end: pos.saturating_add(len),
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current insert position.
|
||||
///
|
||||
/// N.B. It may point to invalid memory.
|
||||
pub(crate) fn pos(&self) -> *mut u8 {
|
||||
self.pos as _
|
||||
}
|
||||
|
||||
/// Return the number of bytes written to the formatter.
|
||||
pub(crate) fn bytes_written(&self) -> usize {
|
||||
self.pos - self.beg
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Write for RawFormatter {
|
||||
@@ -70,3 +471,121 @@ impl fmt::Write for RawFormatter {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
/// Allows formatting of [`fmt::Arguments`] into a raw buffer.
|
||||
///
|
||||
/// Fails if callers attempt to write more than will fit in the buffer.
|
||||
pub(crate) struct Formatter(RawFormatter);
|
||||
|
||||
impl Formatter {
|
||||
/// Creates a new instance of [`Formatter`] with the given buffer.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// The memory region starting at `buf` and extending for `len` bytes must be valid for writes
|
||||
/// for the lifetime of the returned [`Formatter`].
|
||||
pub(crate) unsafe fn from_buffer(buf: *mut u8, len: usize) -> Self {
|
||||
// SAFETY: The safety requirements of this function satisfy those of the callee.
|
||||
Self(unsafe { RawFormatter::from_buffer(buf, len) })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for Formatter {
|
||||
type Target = RawFormatter;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Write for Formatter {
|
||||
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||
self.0.write_str(s)?;
|
||||
|
||||
// Fail the request if we go past the end of the buffer.
|
||||
if self.0.pos > self.0.end {
|
||||
Err(fmt::Error)
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An owned string that is guaranteed to have exactly one `NUL` byte, which is at the end.
|
||||
///
|
||||
/// Used for interoperability with kernel APIs that take C strings.
|
||||
///
|
||||
/// # Invariants
|
||||
///
|
||||
/// The string is always `NUL`-terminated and contains no other `NUL` bytes.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use kernel::str::CString;
|
||||
///
|
||||
/// let s = CString::try_from_fmt(fmt!("{}{}{}", "abc", 10, 20)).unwrap();
|
||||
/// assert_eq!(s.as_bytes_with_nul(), "abc1020\0".as_bytes());
|
||||
///
|
||||
/// let tmp = "testing";
|
||||
/// let s = CString::try_from_fmt(fmt!("{tmp}{}", 123)).unwrap();
|
||||
/// assert_eq!(s.as_bytes_with_nul(), "testing123\0".as_bytes());
|
||||
///
|
||||
/// // This fails because it has an embedded `NUL` byte.
|
||||
/// let s = CString::try_from_fmt(fmt!("a\0b{}", 123));
|
||||
/// assert_eq!(s.is_ok(), false);
|
||||
/// ```
|
||||
pub struct CString {
|
||||
buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl CString {
|
||||
/// Creates an instance of [`CString`] from the given formatted arguments.
|
||||
pub fn try_from_fmt(args: fmt::Arguments<'_>) -> Result<Self, Error> {
|
||||
// Calculate the size needed (formatted string plus `NUL` terminator).
|
||||
let mut f = RawFormatter::new();
|
||||
f.write_fmt(args)?;
|
||||
f.write_str("\0")?;
|
||||
let size = f.bytes_written();
|
||||
|
||||
// Allocate a vector with the required number of bytes, and write to it.
|
||||
let mut buf = Vec::try_with_capacity(size)?;
|
||||
// SAFETY: The buffer stored in `buf` is at least of size `size` and is valid for writes.
|
||||
let mut f = unsafe { Formatter::from_buffer(buf.as_mut_ptr(), size) };
|
||||
f.write_fmt(args)?;
|
||||
f.write_str("\0")?;
|
||||
|
||||
// SAFETY: The number of bytes that can be written to `f` is bounded by `size`, which is
|
||||
// `buf`'s capacity. The contents of the buffer have been initialised by writes to `f`.
|
||||
unsafe { buf.set_len(f.bytes_written()) };
|
||||
|
||||
// Check that there are no `NUL` bytes before the end.
|
||||
// SAFETY: The buffer is valid for read because `f.bytes_written()` is bounded by `size`
|
||||
// (which the minimum buffer size) and is non-zero (we wrote at least the `NUL` terminator)
|
||||
// so `f.bytes_written() - 1` doesn't underflow.
|
||||
let ptr = unsafe { bindings::memchr(buf.as_ptr().cast(), 0, (f.bytes_written() - 1) as _) };
|
||||
if !ptr.is_null() {
|
||||
return Err(EINVAL);
|
||||
}
|
||||
|
||||
// INVARIANT: We wrote the `NUL` terminator and checked above that no other `NUL` bytes
|
||||
// exist in the buffer.
|
||||
Ok(Self { buf })
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for CString {
|
||||
type Target = CStr;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
// SAFETY: The type invariants guarantee that the string is `NUL`-terminated and that no
|
||||
// other `NUL` bytes exist.
|
||||
unsafe { CStr::from_bytes_with_nul_unchecked(self.buf.as_slice()) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A convenience alias for [`core::format_args`].
|
||||
#[macro_export]
|
||||
macro_rules! fmt {
|
||||
($($f:tt)*) => ( core::format_args!($($f)*) )
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
//! Kernel types.
|
||||
|
||||
use core::{cell::UnsafeCell, mem::MaybeUninit};
|
||||
|
||||
/// Stores an opaque value.
|
||||
///
|
||||
/// This is meant to be used with FFI objects that are never interpreted by Rust code.
|
||||
#[repr(transparent)]
|
||||
pub struct Opaque<T>(MaybeUninit<UnsafeCell<T>>);
|
||||
|
||||
impl<T> Opaque<T> {
|
||||
/// Creates a new opaque value.
|
||||
pub const fn new(value: T) -> Self {
|
||||
Self(MaybeUninit::new(UnsafeCell::new(value)))
|
||||
}
|
||||
|
||||
/// Creates an uninitialised value.
|
||||
pub const fn uninit() -> Self {
|
||||
Self(MaybeUninit::uninit())
|
||||
}
|
||||
|
||||
/// Returns a raw pointer to the opaque data.
|
||||
pub fn get(&self) -> *mut T {
|
||||
UnsafeCell::raw_get(self.0.as_ptr())
|
||||
}
|
||||
}
|
||||
|
||||
/// A sum type that always holds either a value of type `L` or `R`.
|
||||
pub enum Either<L, R> {
|
||||
/// Constructs an instance of [`Either`] containing a value of type `L`.
|
||||
Left(L),
|
||||
|
||||
/// Constructs an instance of [`Either`] containing a value of type `R`.
|
||||
Right(R),
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
use proc_macro::{token_stream, Ident, TokenStream, TokenTree};
|
||||
|
||||
use crate::helpers::expect_punct;
|
||||
|
||||
fn expect_ident(it: &mut token_stream::IntoIter) -> Ident {
|
||||
if let Some(TokenTree::Ident(ident)) = it.next() {
|
||||
ident
|
||||
} else {
|
||||
panic!("Expected Ident")
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn concat_idents(ts: TokenStream) -> TokenStream {
|
||||
let mut it = ts.into_iter();
|
||||
let a = expect_ident(&mut it);
|
||||
assert_eq!(expect_punct(&mut it), ',');
|
||||
let b = expect_ident(&mut it);
|
||||
assert!(it.next().is_none(), "only two idents can be concatenated");
|
||||
let res = Ident::new(&format!("{a}{b}"), b.span());
|
||||
TokenStream::from_iter([TokenTree::Ident(res)])
|
||||
}
|
||||
+18
-6
@@ -18,10 +18,16 @@ pub(crate) fn try_literal(it: &mut token_stream::IntoIter) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn try_byte_string(it: &mut token_stream::IntoIter) -> Option<String> {
|
||||
try_literal(it).and_then(|byte_string| {
|
||||
if byte_string.starts_with("b\"") && byte_string.ends_with('\"') {
|
||||
Some(byte_string[2..byte_string.len() - 1].to_string())
|
||||
pub(crate) fn try_string(it: &mut token_stream::IntoIter) -> Option<String> {
|
||||
try_literal(it).and_then(|string| {
|
||||
if string.starts_with('\"') && string.ends_with('\"') {
|
||||
let content = &string[1..string.len() - 1];
|
||||
if content.contains('\\') {
|
||||
panic!("Escape sequences in string literals not yet handled");
|
||||
}
|
||||
Some(content.to_string())
|
||||
} else if string.starts_with("r\"") {
|
||||
panic!("Raw string literals are not yet handled");
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -40,8 +46,14 @@ pub(crate) fn expect_punct(it: &mut token_stream::IntoIter) -> char {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn expect_byte_string(it: &mut token_stream::IntoIter) -> String {
|
||||
try_byte_string(it).expect("Expected byte string")
|
||||
pub(crate) fn expect_string(it: &mut token_stream::IntoIter) -> String {
|
||||
try_string(it).expect("Expected string")
|
||||
}
|
||||
|
||||
pub(crate) fn expect_string_ascii(it: &mut token_stream::IntoIter) -> String {
|
||||
let string = try_string(it).expect("Expected string");
|
||||
assert!(string.is_ascii(), "Expected ASCII string");
|
||||
string
|
||||
}
|
||||
|
||||
pub(crate) fn expect_end(it: &mut token_stream::IntoIter) {
|
||||
|
||||
+102
-6
@@ -2,8 +2,10 @@
|
||||
|
||||
//! Crate for all kernel procedural macros.
|
||||
|
||||
mod concat_idents;
|
||||
mod helpers;
|
||||
mod module;
|
||||
mod vtable;
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
|
||||
@@ -23,20 +25,20 @@ use proc_macro::TokenStream;
|
||||
///
|
||||
/// module!{
|
||||
/// type: MyModule,
|
||||
/// name: b"my_kernel_module",
|
||||
/// author: b"Rust for Linux Contributors",
|
||||
/// description: b"My very own kernel module!",
|
||||
/// license: b"GPL",
|
||||
/// name: "my_kernel_module",
|
||||
/// author: "Rust for Linux Contributors",
|
||||
/// description: "My very own kernel module!",
|
||||
/// license: "GPL",
|
||||
/// params: {
|
||||
/// my_i32: i32 {
|
||||
/// default: 42,
|
||||
/// permissions: 0o000,
|
||||
/// description: b"Example of i32",
|
||||
/// description: "Example of i32",
|
||||
/// },
|
||||
/// writeable_i32: i32 {
|
||||
/// default: 42,
|
||||
/// permissions: 0o644,
|
||||
/// description: b"Example of i32",
|
||||
/// description: "Example of i32",
|
||||
/// },
|
||||
/// },
|
||||
/// }
|
||||
@@ -70,3 +72,97 @@ use proc_macro::TokenStream;
|
||||
pub fn module(ts: TokenStream) -> TokenStream {
|
||||
module::module(ts)
|
||||
}
|
||||
|
||||
/// Declares or implements a vtable trait.
|
||||
///
|
||||
/// Linux's use of pure vtables is very close to Rust traits, but they differ
|
||||
/// in how unimplemented functions are represented. In Rust, traits can provide
|
||||
/// default implementation for all non-required methods (and the default
|
||||
/// implementation could just return `Error::EINVAL`); Linux typically use C
|
||||
/// `NULL` pointers to represent these functions.
|
||||
///
|
||||
/// This attribute is intended to close the gap. Traits can be declared and
|
||||
/// implemented with the `#[vtable]` attribute, and a `HAS_*` associated constant
|
||||
/// will be generated for each method in the trait, indicating if the implementor
|
||||
/// has overridden a method.
|
||||
///
|
||||
/// This attribute is not needed if all methods are required.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use kernel::prelude::*;
|
||||
///
|
||||
/// // Declares a `#[vtable]` trait
|
||||
/// #[vtable]
|
||||
/// pub trait Operations: Send + Sync + Sized {
|
||||
/// fn foo(&self) -> Result<()> {
|
||||
/// Err(EINVAL)
|
||||
/// }
|
||||
///
|
||||
/// fn bar(&self) -> Result<()> {
|
||||
/// Err(EINVAL)
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// struct Foo;
|
||||
///
|
||||
/// // Implements the `#[vtable]` trait
|
||||
/// #[vtable]
|
||||
/// impl Operations for Foo {
|
||||
/// fn foo(&self) -> Result<()> {
|
||||
/// # Err(EINVAL)
|
||||
/// // ...
|
||||
/// }
|
||||
/// }
|
||||
///
|
||||
/// assert_eq!(<Foo as Operations>::HAS_FOO, true);
|
||||
/// assert_eq!(<Foo as Operations>::HAS_BAR, false);
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn vtable(attr: TokenStream, ts: TokenStream) -> TokenStream {
|
||||
vtable::vtable(attr, ts)
|
||||
}
|
||||
|
||||
/// Concatenate two identifiers.
|
||||
///
|
||||
/// This is useful in macros that need to declare or reference items with names
|
||||
/// starting with a fixed prefix and ending in a user specified name. The resulting
|
||||
/// identifier has the span of the second argument.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use kernel::macro::concat_idents;
|
||||
///
|
||||
/// macro_rules! pub_no_prefix {
|
||||
/// ($prefix:ident, $($newname:ident),+) => {
|
||||
/// $(pub(crate) const $newname: u32 = kernel::macros::concat_idents!($prefix, $newname);)+
|
||||
/// };
|
||||
/// }
|
||||
///
|
||||
/// pub_no_prefix!(
|
||||
/// binder_driver_return_protocol_,
|
||||
/// BR_OK,
|
||||
/// BR_ERROR,
|
||||
/// BR_TRANSACTION,
|
||||
/// BR_REPLY,
|
||||
/// BR_DEAD_REPLY,
|
||||
/// BR_TRANSACTION_COMPLETE,
|
||||
/// BR_INCREFS,
|
||||
/// BR_ACQUIRE,
|
||||
/// BR_RELEASE,
|
||||
/// BR_DECREFS,
|
||||
/// BR_NOOP,
|
||||
/// BR_SPAWN_LOOPER,
|
||||
/// BR_DEAD_BINDER,
|
||||
/// BR_CLEAR_DEATH_NOTIFICATION_DONE,
|
||||
/// BR_FAILED_REPLY
|
||||
/// );
|
||||
///
|
||||
/// assert_eq!(BR_OK, binder_driver_return_protocol_BR_OK);
|
||||
/// ```
|
||||
#[proc_macro]
|
||||
pub fn concat_idents(ts: TokenStream) -> TokenStream {
|
||||
concat_idents::concat_idents(ts)
|
||||
}
|
||||
|
||||
@@ -108,11 +108,11 @@ impl ModuleInfo {
|
||||
|
||||
match key.as_str() {
|
||||
"type" => info.type_ = expect_ident(it),
|
||||
"name" => info.name = expect_byte_string(it),
|
||||
"author" => info.author = Some(expect_byte_string(it)),
|
||||
"description" => info.description = Some(expect_byte_string(it)),
|
||||
"license" => info.license = expect_byte_string(it),
|
||||
"alias" => info.alias = Some(expect_byte_string(it)),
|
||||
"name" => info.name = expect_string_ascii(it),
|
||||
"author" => info.author = Some(expect_string(it)),
|
||||
"description" => info.description = Some(expect_string(it)),
|
||||
"license" => info.license = expect_string_ascii(it),
|
||||
"alias" => info.alias = Some(expect_string_ascii(it)),
|
||||
_ => panic!(
|
||||
"Unknown key \"{}\". Valid keys are: {:?}.",
|
||||
key, EXPECTED_KEYS
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Write;
|
||||
|
||||
pub(crate) fn vtable(_attr: TokenStream, ts: TokenStream) -> TokenStream {
|
||||
let mut tokens: Vec<_> = ts.into_iter().collect();
|
||||
|
||||
// Scan for the `trait` or `impl` keyword.
|
||||
let is_trait = tokens
|
||||
.iter()
|
||||
.find_map(|token| match token {
|
||||
TokenTree::Ident(ident) => match ident.to_string().as_str() {
|
||||
"trait" => Some(true),
|
||||
"impl" => Some(false),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
})
|
||||
.expect("#[vtable] attribute should only be applied to trait or impl block");
|
||||
|
||||
// Retrieve the main body. The main body should be the last token tree.
|
||||
let body = match tokens.pop() {
|
||||
Some(TokenTree::Group(group)) if group.delimiter() == Delimiter::Brace => group,
|
||||
_ => panic!("cannot locate main body of trait or impl block"),
|
||||
};
|
||||
|
||||
let mut body_it = body.stream().into_iter();
|
||||
let mut functions = Vec::new();
|
||||
let mut consts = HashSet::new();
|
||||
while let Some(token) = body_it.next() {
|
||||
match token {
|
||||
TokenTree::Ident(ident) if ident.to_string() == "fn" => {
|
||||
let fn_name = match body_it.next() {
|
||||
Some(TokenTree::Ident(ident)) => ident.to_string(),
|
||||
// Possibly we've encountered a fn pointer type instead.
|
||||
_ => continue,
|
||||
};
|
||||
functions.push(fn_name);
|
||||
}
|
||||
TokenTree::Ident(ident) if ident.to_string() == "const" => {
|
||||
let const_name = match body_it.next() {
|
||||
Some(TokenTree::Ident(ident)) => ident.to_string(),
|
||||
// Possibly we've encountered an inline const block instead.
|
||||
_ => continue,
|
||||
};
|
||||
consts.insert(const_name);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
let mut const_items;
|
||||
if is_trait {
|
||||
const_items = "
|
||||
/// A marker to prevent implementors from forgetting to use [`#[vtable]`](vtable)
|
||||
/// attribute when implementing this trait.
|
||||
const USE_VTABLE_ATTR: ();
|
||||
"
|
||||
.to_owned();
|
||||
|
||||
for f in functions {
|
||||
let gen_const_name = format!("HAS_{}", f.to_uppercase());
|
||||
// Skip if it's declared already -- this allows user override.
|
||||
if consts.contains(&gen_const_name) {
|
||||
continue;
|
||||
}
|
||||
// We don't know on the implementation-site whether a method is required or provided
|
||||
// so we have to generate a const for all methods.
|
||||
write!(
|
||||
const_items,
|
||||
"/// Indicates if the `{f}` method is overridden by the implementor.
|
||||
const {gen_const_name}: bool = false;",
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
} else {
|
||||
const_items = "const USE_VTABLE_ATTR: () = ();".to_owned();
|
||||
|
||||
for f in functions {
|
||||
let gen_const_name = format!("HAS_{}", f.to_uppercase());
|
||||
if consts.contains(&gen_const_name) {
|
||||
continue;
|
||||
}
|
||||
write!(const_items, "const {gen_const_name}: bool = true;").unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
let new_body = vec![const_items.parse().unwrap(), body.stream()]
|
||||
.into_iter()
|
||||
.collect();
|
||||
tokens.push(TokenTree::Group(Group::new(Delimiter::Brace, new_body)));
|
||||
tokens.into_iter().collect()
|
||||
}
|
||||
@@ -20,6 +20,16 @@ config SAMPLE_RUST_MINIMAL
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config SAMPLE_RUST_PRINT
|
||||
tristate "Printing macros"
|
||||
help
|
||||
This option builds the Rust printing macros sample.
|
||||
|
||||
To compile this as a module, choose M here:
|
||||
the module will be called rust_print.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config SAMPLE_RUST_HOSTPROGS
|
||||
bool "Host programs"
|
||||
help
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
obj-$(CONFIG_SAMPLE_RUST_MINIMAL) += rust_minimal.o
|
||||
obj-$(CONFIG_SAMPLE_RUST_PRINT) += rust_print.o
|
||||
|
||||
subdir-$(CONFIG_SAMPLE_RUST_HOSTPROGS) += hostprogs
|
||||
|
||||
@@ -6,10 +6,10 @@ use kernel::prelude::*;
|
||||
|
||||
module! {
|
||||
type: RustMinimal,
|
||||
name: b"rust_minimal",
|
||||
author: b"Rust for Linux Contributors",
|
||||
description: b"Rust minimal sample",
|
||||
license: b"GPL",
|
||||
name: "rust_minimal",
|
||||
author: "Rust for Linux Contributors",
|
||||
description: "Rust minimal sample",
|
||||
license: "GPL",
|
||||
}
|
||||
|
||||
struct RustMinimal {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
//! Rust printing macros sample.
|
||||
|
||||
use kernel::pr_cont;
|
||||
use kernel::prelude::*;
|
||||
|
||||
module! {
|
||||
type: RustPrint,
|
||||
name: "rust_print",
|
||||
author: "Rust for Linux Contributors",
|
||||
description: "Rust printing macros sample",
|
||||
license: "GPL",
|
||||
}
|
||||
|
||||
struct RustPrint;
|
||||
|
||||
impl kernel::Module for RustPrint {
|
||||
fn init(_module: &'static ThisModule) -> Result<Self> {
|
||||
pr_info!("Rust printing macros sample (init)\n");
|
||||
|
||||
pr_emerg!("Emergency message (level 0) without args\n");
|
||||
pr_alert!("Alert message (level 1) without args\n");
|
||||
pr_crit!("Critical message (level 2) without args\n");
|
||||
pr_err!("Error message (level 3) without args\n");
|
||||
pr_warn!("Warning message (level 4) without args\n");
|
||||
pr_notice!("Notice message (level 5) without args\n");
|
||||
pr_info!("Info message (level 6) without args\n");
|
||||
|
||||
pr_info!("A line that");
|
||||
pr_cont!(" is continued");
|
||||
pr_cont!(" without args\n");
|
||||
|
||||
pr_emerg!("{} message (level {}) with args\n", "Emergency", 0);
|
||||
pr_alert!("{} message (level {}) with args\n", "Alert", 1);
|
||||
pr_crit!("{} message (level {}) with args\n", "Critical", 2);
|
||||
pr_err!("{} message (level {}) with args\n", "Error", 3);
|
||||
pr_warn!("{} message (level {}) with args\n", "Warning", 4);
|
||||
pr_notice!("{} message (level {}) with args\n", "Notice", 5);
|
||||
pr_info!("{} message (level {}) with args\n", "Info", 6);
|
||||
|
||||
pr_info!("A {} that", "line");
|
||||
pr_cont!(" is {}", "continued");
|
||||
pr_cont!(" with {}\n", "args");
|
||||
|
||||
Ok(RustPrint)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for RustPrint {
|
||||
fn drop(&mut self) {
|
||||
pr_info!("Rust printing macros sample (exit)\n");
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,12 @@ def generate_crates(srctree, objtree, sysroot_src):
|
||||
)
|
||||
crates[-1]["proc_macro_dylib_path"] = "rust/libmacros.so"
|
||||
|
||||
append_crate(
|
||||
"build_error",
|
||||
srctree / "rust" / "build_error.rs",
|
||||
["core", "compiler_builtins"],
|
||||
)
|
||||
|
||||
append_crate(
|
||||
"bindings",
|
||||
srctree / "rust"/ "bindings" / "lib.rs",
|
||||
@@ -78,7 +84,7 @@ def generate_crates(srctree, objtree, sysroot_src):
|
||||
append_crate(
|
||||
"kernel",
|
||||
srctree / "rust" / "kernel" / "lib.rs",
|
||||
["core", "alloc", "macros", "bindings"],
|
||||
["core", "alloc", "macros", "build_error", "bindings"],
|
||||
cfg=cfg,
|
||||
)
|
||||
crates[-1]["source"] = {
|
||||
|
||||
@@ -106,8 +106,8 @@ config SECURITY_APPARMOR_PARANOID_LOAD
|
||||
Disabling the check will speed up policy loads.
|
||||
|
||||
config SECURITY_APPARMOR_KUNIT_TEST
|
||||
bool "Build KUnit tests for policy_unpack.c" if !KUNIT_ALL_TESTS
|
||||
depends on KUNIT=y && SECURITY_APPARMOR
|
||||
tristate "Build KUnit tests for policy_unpack.c" if !KUNIT_ALL_TESTS
|
||||
depends on KUNIT && SECURITY_APPARMOR
|
||||
default KUNIT_ALL_TESTS
|
||||
help
|
||||
This builds the AppArmor KUnit tests.
|
||||
|
||||
@@ -8,6 +8,9 @@ apparmor-y := apparmorfs.o audit.o capability.o task.o ipc.o lib.o match.o \
|
||||
resource.o secid.o file.o policy_ns.o label.o mount.o net.o
|
||||
apparmor-$(CONFIG_SECURITY_APPARMOR_HASH) += crypto.o
|
||||
|
||||
obj-$(CONFIG_SECURITY_APPARMOR_KUNIT_TEST) += apparmor_policy_unpack_test.o
|
||||
apparmor_policy_unpack_test-objs += policy_unpack_test.o
|
||||
|
||||
clean-files := capability_names.h rlim_names.h net_names.h
|
||||
|
||||
# Build a lower case string table of address family names
|
||||
|
||||
@@ -48,6 +48,43 @@ enum {
|
||||
AAFS_LOADDATA_NDENTS /* count of entries */
|
||||
};
|
||||
|
||||
/*
|
||||
* The AppArmor interface treats data as a type byte followed by the
|
||||
* actual data. The interface has the notion of a named entry
|
||||
* which has a name (AA_NAME typecode followed by name string) followed by
|
||||
* the entries typecode and data. Named types allow for optional
|
||||
* elements and extensions to be added and tested for without breaking
|
||||
* backwards compatibility.
|
||||
*/
|
||||
|
||||
enum aa_code {
|
||||
AA_U8,
|
||||
AA_U16,
|
||||
AA_U32,
|
||||
AA_U64,
|
||||
AA_NAME, /* same as string except it is items name */
|
||||
AA_STRING,
|
||||
AA_BLOB,
|
||||
AA_STRUCT,
|
||||
AA_STRUCTEND,
|
||||
AA_LIST,
|
||||
AA_LISTEND,
|
||||
AA_ARRAY,
|
||||
AA_ARRAYEND,
|
||||
};
|
||||
|
||||
/*
|
||||
* aa_ext is the read of the buffer containing the serialized profile. The
|
||||
* data is copied into a kernel buffer in apparmorfs and then handed off to
|
||||
* the unpack routines.
|
||||
*/
|
||||
struct aa_ext {
|
||||
void *start;
|
||||
void *end;
|
||||
void *pos; /* pointer to current position in the buffer */
|
||||
u32 version;
|
||||
};
|
||||
|
||||
/*
|
||||
* struct aa_loaddata - buffer of policy raw_data set
|
||||
*
|
||||
@@ -126,4 +163,17 @@ static inline void aa_put_loaddata(struct aa_loaddata *data)
|
||||
kref_put(&data->count, aa_loaddata_kref);
|
||||
}
|
||||
|
||||
#if IS_ENABLED(CONFIG_KUNIT)
|
||||
bool aa_inbounds(struct aa_ext *e, size_t size);
|
||||
size_t aa_unpack_u16_chunk(struct aa_ext *e, char **chunk);
|
||||
bool aa_unpack_X(struct aa_ext *e, enum aa_code code);
|
||||
bool aa_unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name);
|
||||
bool aa_unpack_u32(struct aa_ext *e, u32 *data, const char *name);
|
||||
bool aa_unpack_u64(struct aa_ext *e, u64 *data, const char *name);
|
||||
size_t aa_unpack_array(struct aa_ext *e, const char *name);
|
||||
size_t aa_unpack_blob(struct aa_ext *e, char **blob, const char *name);
|
||||
int aa_unpack_str(struct aa_ext *e, const char **string, const char *name);
|
||||
int aa_unpack_strdup(struct aa_ext *e, char **string, const char *name);
|
||||
#endif
|
||||
|
||||
#endif /* __POLICY_INTERFACE_H */
|
||||
|
||||
+104
-134
@@ -14,6 +14,7 @@
|
||||
*/
|
||||
|
||||
#include <asm/unaligned.h>
|
||||
#include <kunit/visibility.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/zlib.h>
|
||||
@@ -37,43 +38,6 @@
|
||||
#define v7 7
|
||||
#define v8 8 /* full network masking */
|
||||
|
||||
/*
|
||||
* The AppArmor interface treats data as a type byte followed by the
|
||||
* actual data. The interface has the notion of a named entry
|
||||
* which has a name (AA_NAME typecode followed by name string) followed by
|
||||
* the entries typecode and data. Named types allow for optional
|
||||
* elements and extensions to be added and tested for without breaking
|
||||
* backwards compatibility.
|
||||
*/
|
||||
|
||||
enum aa_code {
|
||||
AA_U8,
|
||||
AA_U16,
|
||||
AA_U32,
|
||||
AA_U64,
|
||||
AA_NAME, /* same as string except it is items name */
|
||||
AA_STRING,
|
||||
AA_BLOB,
|
||||
AA_STRUCT,
|
||||
AA_STRUCTEND,
|
||||
AA_LIST,
|
||||
AA_LISTEND,
|
||||
AA_ARRAY,
|
||||
AA_ARRAYEND,
|
||||
};
|
||||
|
||||
/*
|
||||
* aa_ext is the read of the buffer containing the serialized profile. The
|
||||
* data is copied into a kernel buffer in apparmorfs and then handed off to
|
||||
* the unpack routines.
|
||||
*/
|
||||
struct aa_ext {
|
||||
void *start;
|
||||
void *end;
|
||||
void *pos; /* pointer to current position in the buffer */
|
||||
u32 version;
|
||||
};
|
||||
|
||||
/* audit callback for unpack fields */
|
||||
static void audit_cb(struct audit_buffer *ab, void *va)
|
||||
{
|
||||
@@ -199,10 +163,11 @@ struct aa_loaddata *aa_loaddata_alloc(size_t size)
|
||||
}
|
||||
|
||||
/* test if read will be in packed data bounds */
|
||||
static bool inbounds(struct aa_ext *e, size_t size)
|
||||
VISIBLE_IF_KUNIT bool aa_inbounds(struct aa_ext *e, size_t size)
|
||||
{
|
||||
return (size <= e->end - e->pos);
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(aa_inbounds);
|
||||
|
||||
static void *kvmemdup(const void *src, size_t len)
|
||||
{
|
||||
@@ -214,22 +179,22 @@ static void *kvmemdup(const void *src, size_t len)
|
||||
}
|
||||
|
||||
/**
|
||||
* unpack_u16_chunk - test and do bounds checking for a u16 size based chunk
|
||||
* aa_unpack_u16_chunk - test and do bounds checking for a u16 size based chunk
|
||||
* @e: serialized data read head (NOT NULL)
|
||||
* @chunk: start address for chunk of data (NOT NULL)
|
||||
*
|
||||
* Returns: the size of chunk found with the read head at the end of the chunk.
|
||||
*/
|
||||
static size_t unpack_u16_chunk(struct aa_ext *e, char **chunk)
|
||||
VISIBLE_IF_KUNIT size_t aa_unpack_u16_chunk(struct aa_ext *e, char **chunk)
|
||||
{
|
||||
size_t size = 0;
|
||||
void *pos = e->pos;
|
||||
|
||||
if (!inbounds(e, sizeof(u16)))
|
||||
if (!aa_inbounds(e, sizeof(u16)))
|
||||
goto fail;
|
||||
size = le16_to_cpu(get_unaligned((__le16 *) e->pos));
|
||||
e->pos += sizeof(__le16);
|
||||
if (!inbounds(e, size))
|
||||
if (!aa_inbounds(e, size))
|
||||
goto fail;
|
||||
*chunk = e->pos;
|
||||
e->pos += size;
|
||||
@@ -239,20 +204,22 @@ fail:
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u16_chunk);
|
||||
|
||||
/* unpack control byte */
|
||||
static bool unpack_X(struct aa_ext *e, enum aa_code code)
|
||||
VISIBLE_IF_KUNIT bool aa_unpack_X(struct aa_ext *e, enum aa_code code)
|
||||
{
|
||||
if (!inbounds(e, 1))
|
||||
if (!aa_inbounds(e, 1))
|
||||
return false;
|
||||
if (*(u8 *) e->pos != code)
|
||||
return false;
|
||||
e->pos++;
|
||||
return true;
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_X);
|
||||
|
||||
/**
|
||||
* unpack_nameX - check is the next element is of type X with a name of @name
|
||||
* aa_unpack_nameX - check is the next element is of type X with a name of @name
|
||||
* @e: serialized data extent information (NOT NULL)
|
||||
* @code: type code
|
||||
* @name: name to match to the serialized element. (MAYBE NULL)
|
||||
@@ -267,7 +234,7 @@ static bool unpack_X(struct aa_ext *e, enum aa_code code)
|
||||
*
|
||||
* Returns: false if either match fails, the read head does not move
|
||||
*/
|
||||
static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
|
||||
VISIBLE_IF_KUNIT bool aa_unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
|
||||
{
|
||||
/*
|
||||
* May need to reset pos if name or type doesn't match
|
||||
@@ -277,9 +244,9 @@ static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
|
||||
* Check for presence of a tagname, and if present name size
|
||||
* AA_NAME tag value is a u16.
|
||||
*/
|
||||
if (unpack_X(e, AA_NAME)) {
|
||||
if (aa_unpack_X(e, AA_NAME)) {
|
||||
char *tag = NULL;
|
||||
size_t size = unpack_u16_chunk(e, &tag);
|
||||
size_t size = aa_unpack_u16_chunk(e, &tag);
|
||||
/* if a name is specified it must match. otherwise skip tag */
|
||||
if (name && (!size || tag[size-1] != '\0' || strcmp(name, tag)))
|
||||
goto fail;
|
||||
@@ -289,20 +256,21 @@ static bool unpack_nameX(struct aa_ext *e, enum aa_code code, const char *name)
|
||||
}
|
||||
|
||||
/* now check if type code matches */
|
||||
if (unpack_X(e, code))
|
||||
if (aa_unpack_X(e, code))
|
||||
return true;
|
||||
|
||||
fail:
|
||||
e->pos = pos;
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_nameX);
|
||||
|
||||
static bool unpack_u8(struct aa_ext *e, u8 *data, const char *name)
|
||||
{
|
||||
void *pos = e->pos;
|
||||
|
||||
if (unpack_nameX(e, AA_U8, name)) {
|
||||
if (!inbounds(e, sizeof(u8)))
|
||||
if (aa_unpack_nameX(e, AA_U8, name)) {
|
||||
if (!aa_inbounds(e, sizeof(u8)))
|
||||
goto fail;
|
||||
if (data)
|
||||
*data = *((u8 *)e->pos);
|
||||
@@ -315,12 +283,12 @@ fail:
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool unpack_u32(struct aa_ext *e, u32 *data, const char *name)
|
||||
VISIBLE_IF_KUNIT bool aa_unpack_u32(struct aa_ext *e, u32 *data, const char *name)
|
||||
{
|
||||
void *pos = e->pos;
|
||||
|
||||
if (unpack_nameX(e, AA_U32, name)) {
|
||||
if (!inbounds(e, sizeof(u32)))
|
||||
if (aa_unpack_nameX(e, AA_U32, name)) {
|
||||
if (!aa_inbounds(e, sizeof(u32)))
|
||||
goto fail;
|
||||
if (data)
|
||||
*data = le32_to_cpu(get_unaligned((__le32 *) e->pos));
|
||||
@@ -332,13 +300,14 @@ fail:
|
||||
e->pos = pos;
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u32);
|
||||
|
||||
static bool unpack_u64(struct aa_ext *e, u64 *data, const char *name)
|
||||
VISIBLE_IF_KUNIT bool aa_unpack_u64(struct aa_ext *e, u64 *data, const char *name)
|
||||
{
|
||||
void *pos = e->pos;
|
||||
|
||||
if (unpack_nameX(e, AA_U64, name)) {
|
||||
if (!inbounds(e, sizeof(u64)))
|
||||
if (aa_unpack_nameX(e, AA_U64, name)) {
|
||||
if (!aa_inbounds(e, sizeof(u64)))
|
||||
goto fail;
|
||||
if (data)
|
||||
*data = le64_to_cpu(get_unaligned((__le64 *) e->pos));
|
||||
@@ -350,14 +319,15 @@ fail:
|
||||
e->pos = pos;
|
||||
return false;
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_u64);
|
||||
|
||||
static size_t unpack_array(struct aa_ext *e, const char *name)
|
||||
VISIBLE_IF_KUNIT size_t aa_unpack_array(struct aa_ext *e, const char *name)
|
||||
{
|
||||
void *pos = e->pos;
|
||||
|
||||
if (unpack_nameX(e, AA_ARRAY, name)) {
|
||||
if (aa_unpack_nameX(e, AA_ARRAY, name)) {
|
||||
int size;
|
||||
if (!inbounds(e, sizeof(u16)))
|
||||
if (!aa_inbounds(e, sizeof(u16)))
|
||||
goto fail;
|
||||
size = (int)le16_to_cpu(get_unaligned((__le16 *) e->pos));
|
||||
e->pos += sizeof(u16);
|
||||
@@ -368,18 +338,19 @@ fail:
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_array);
|
||||
|
||||
static size_t unpack_blob(struct aa_ext *e, char **blob, const char *name)
|
||||
VISIBLE_IF_KUNIT size_t aa_unpack_blob(struct aa_ext *e, char **blob, const char *name)
|
||||
{
|
||||
void *pos = e->pos;
|
||||
|
||||
if (unpack_nameX(e, AA_BLOB, name)) {
|
||||
if (aa_unpack_nameX(e, AA_BLOB, name)) {
|
||||
u32 size;
|
||||
if (!inbounds(e, sizeof(u32)))
|
||||
if (!aa_inbounds(e, sizeof(u32)))
|
||||
goto fail;
|
||||
size = le32_to_cpu(get_unaligned((__le32 *) e->pos));
|
||||
e->pos += sizeof(u32);
|
||||
if (inbounds(e, (size_t) size)) {
|
||||
if (aa_inbounds(e, (size_t) size)) {
|
||||
*blob = e->pos;
|
||||
e->pos += size;
|
||||
return size;
|
||||
@@ -390,15 +361,16 @@ fail:
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_blob);
|
||||
|
||||
static int unpack_str(struct aa_ext *e, const char **string, const char *name)
|
||||
VISIBLE_IF_KUNIT int aa_unpack_str(struct aa_ext *e, const char **string, const char *name)
|
||||
{
|
||||
char *src_str;
|
||||
size_t size = 0;
|
||||
void *pos = e->pos;
|
||||
*string = NULL;
|
||||
if (unpack_nameX(e, AA_STRING, name)) {
|
||||
size = unpack_u16_chunk(e, &src_str);
|
||||
if (aa_unpack_nameX(e, AA_STRING, name)) {
|
||||
size = aa_unpack_u16_chunk(e, &src_str);
|
||||
if (size) {
|
||||
/* strings are null terminated, length is size - 1 */
|
||||
if (src_str[size - 1] != 0)
|
||||
@@ -413,12 +385,13 @@ fail:
|
||||
e->pos = pos;
|
||||
return 0;
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_str);
|
||||
|
||||
static int unpack_strdup(struct aa_ext *e, char **string, const char *name)
|
||||
VISIBLE_IF_KUNIT int aa_unpack_strdup(struct aa_ext *e, char **string, const char *name)
|
||||
{
|
||||
const char *tmp;
|
||||
void *pos = e->pos;
|
||||
int res = unpack_str(e, &tmp, name);
|
||||
int res = aa_unpack_str(e, &tmp, name);
|
||||
*string = NULL;
|
||||
|
||||
if (!res)
|
||||
@@ -432,6 +405,7 @@ static int unpack_strdup(struct aa_ext *e, char **string, const char *name)
|
||||
|
||||
return res;
|
||||
}
|
||||
EXPORT_SYMBOL_IF_KUNIT(aa_unpack_strdup);
|
||||
|
||||
|
||||
/**
|
||||
@@ -446,7 +420,7 @@ static struct aa_dfa *unpack_dfa(struct aa_ext *e)
|
||||
size_t size;
|
||||
struct aa_dfa *dfa = NULL;
|
||||
|
||||
size = unpack_blob(e, &blob, "aadfa");
|
||||
size = aa_unpack_blob(e, &blob, "aadfa");
|
||||
if (size) {
|
||||
/*
|
||||
* The dfa is aligned with in the blob to 8 bytes
|
||||
@@ -482,10 +456,10 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
|
||||
void *saved_pos = e->pos;
|
||||
|
||||
/* exec table is optional */
|
||||
if (unpack_nameX(e, AA_STRUCT, "xtable")) {
|
||||
if (aa_unpack_nameX(e, AA_STRUCT, "xtable")) {
|
||||
int i, size;
|
||||
|
||||
size = unpack_array(e, NULL);
|
||||
size = aa_unpack_array(e, NULL);
|
||||
/* currently 4 exec bits and entries 0-3 are reserved iupcx */
|
||||
if (size > 16 - 4)
|
||||
goto fail;
|
||||
@@ -497,8 +471,8 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
|
||||
profile->file.trans.size = size;
|
||||
for (i = 0; i < size; i++) {
|
||||
char *str;
|
||||
int c, j, pos, size2 = unpack_strdup(e, &str, NULL);
|
||||
/* unpack_strdup verifies that the last character is
|
||||
int c, j, pos, size2 = aa_unpack_strdup(e, &str, NULL);
|
||||
/* aa_unpack_strdup verifies that the last character is
|
||||
* null termination byte.
|
||||
*/
|
||||
if (!size2)
|
||||
@@ -521,7 +495,7 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
|
||||
goto fail;
|
||||
/* beginning with : requires an embedded \0,
|
||||
* verify that exactly 1 internal \0 exists
|
||||
* trailing \0 already verified by unpack_strdup
|
||||
* trailing \0 already verified by aa_unpack_strdup
|
||||
*
|
||||
* convert \0 back to : for label_parse
|
||||
*/
|
||||
@@ -533,9 +507,9 @@ static bool unpack_trans_table(struct aa_ext *e, struct aa_profile *profile)
|
||||
/* fail - all other cases with embedded \0 */
|
||||
goto fail;
|
||||
}
|
||||
if (!unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
goto fail;
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
return true;
|
||||
@@ -550,21 +524,21 @@ static bool unpack_xattrs(struct aa_ext *e, struct aa_profile *profile)
|
||||
{
|
||||
void *pos = e->pos;
|
||||
|
||||
if (unpack_nameX(e, AA_STRUCT, "xattrs")) {
|
||||
if (aa_unpack_nameX(e, AA_STRUCT, "xattrs")) {
|
||||
int i, size;
|
||||
|
||||
size = unpack_array(e, NULL);
|
||||
size = aa_unpack_array(e, NULL);
|
||||
profile->xattr_count = size;
|
||||
profile->xattrs = kcalloc(size, sizeof(char *), GFP_KERNEL);
|
||||
if (!profile->xattrs)
|
||||
goto fail;
|
||||
for (i = 0; i < size; i++) {
|
||||
if (!unpack_strdup(e, &profile->xattrs[i], NULL))
|
||||
if (!aa_unpack_strdup(e, &profile->xattrs[i], NULL))
|
||||
goto fail;
|
||||
}
|
||||
if (!unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
goto fail;
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@@ -580,8 +554,8 @@ static bool unpack_secmark(struct aa_ext *e, struct aa_profile *profile)
|
||||
void *pos = e->pos;
|
||||
int i, size;
|
||||
|
||||
if (unpack_nameX(e, AA_STRUCT, "secmark")) {
|
||||
size = unpack_array(e, NULL);
|
||||
if (aa_unpack_nameX(e, AA_STRUCT, "secmark")) {
|
||||
size = aa_unpack_array(e, NULL);
|
||||
|
||||
profile->secmark = kcalloc(size, sizeof(struct aa_secmark),
|
||||
GFP_KERNEL);
|
||||
@@ -595,12 +569,12 @@ static bool unpack_secmark(struct aa_ext *e, struct aa_profile *profile)
|
||||
goto fail;
|
||||
if (!unpack_u8(e, &profile->secmark[i].deny, NULL))
|
||||
goto fail;
|
||||
if (!unpack_strdup(e, &profile->secmark[i].label, NULL))
|
||||
if (!aa_unpack_strdup(e, &profile->secmark[i].label, NULL))
|
||||
goto fail;
|
||||
}
|
||||
if (!unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
goto fail;
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@@ -624,26 +598,26 @@ static bool unpack_rlimits(struct aa_ext *e, struct aa_profile *profile)
|
||||
void *pos = e->pos;
|
||||
|
||||
/* rlimits are optional */
|
||||
if (unpack_nameX(e, AA_STRUCT, "rlimits")) {
|
||||
if (aa_unpack_nameX(e, AA_STRUCT, "rlimits")) {
|
||||
int i, size;
|
||||
u32 tmp = 0;
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
if (!aa_unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
profile->rlimits.mask = tmp;
|
||||
|
||||
size = unpack_array(e, NULL);
|
||||
size = aa_unpack_array(e, NULL);
|
||||
if (size > RLIM_NLIMITS)
|
||||
goto fail;
|
||||
for (i = 0; i < size; i++) {
|
||||
u64 tmp2 = 0;
|
||||
int a = aa_map_resource(i);
|
||||
if (!unpack_u64(e, &tmp2, NULL))
|
||||
if (!aa_unpack_u64(e, &tmp2, NULL))
|
||||
goto fail;
|
||||
profile->rlimits.limits[a].rlim_max = tmp2;
|
||||
}
|
||||
if (!unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
if (!aa_unpack_nameX(e, AA_ARRAYEND, NULL))
|
||||
goto fail;
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
return true;
|
||||
@@ -691,9 +665,9 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
*ns_name = NULL;
|
||||
|
||||
/* check that we have the right struct being passed */
|
||||
if (!unpack_nameX(e, AA_STRUCT, "profile"))
|
||||
if (!aa_unpack_nameX(e, AA_STRUCT, "profile"))
|
||||
goto fail;
|
||||
if (!unpack_str(e, &name, NULL))
|
||||
if (!aa_unpack_str(e, &name, NULL))
|
||||
goto fail;
|
||||
if (*name == '\0')
|
||||
goto fail;
|
||||
@@ -713,10 +687,10 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
/* profile renaming is optional */
|
||||
(void) unpack_str(e, &profile->rename, "rename");
|
||||
(void) aa_unpack_str(e, &profile->rename, "rename");
|
||||
|
||||
/* attachment string is optional */
|
||||
(void) unpack_str(e, &profile->attach, "attach");
|
||||
(void) aa_unpack_str(e, &profile->attach, "attach");
|
||||
|
||||
/* xmatch is optional and may be NULL */
|
||||
profile->xmatch = unpack_dfa(e);
|
||||
@@ -728,7 +702,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
}
|
||||
/* xmatch_len is not optional if xmatch is set */
|
||||
if (profile->xmatch) {
|
||||
if (!unpack_u32(e, &tmp, NULL)) {
|
||||
if (!aa_unpack_u32(e, &tmp, NULL)) {
|
||||
info = "missing xmatch len";
|
||||
goto fail;
|
||||
}
|
||||
@@ -736,15 +710,15 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
}
|
||||
|
||||
/* disconnected attachment string is optional */
|
||||
(void) unpack_str(e, &profile->disconnected, "disconnected");
|
||||
(void) aa_unpack_str(e, &profile->disconnected, "disconnected");
|
||||
|
||||
/* per profile debug flags (complain, audit) */
|
||||
if (!unpack_nameX(e, AA_STRUCT, "flags")) {
|
||||
if (!aa_unpack_nameX(e, AA_STRUCT, "flags")) {
|
||||
info = "profile missing flags";
|
||||
goto fail;
|
||||
}
|
||||
info = "failed to unpack profile flags";
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
if (!aa_unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
if (tmp & PACKED_FLAG_HAT)
|
||||
profile->label.flags |= FLAG_HAT;
|
||||
@@ -752,7 +726,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
profile->label.flags |= FLAG_DEBUG1;
|
||||
if (tmp & PACKED_FLAG_DEBUG2)
|
||||
profile->label.flags |= FLAG_DEBUG2;
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
if (!aa_unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
if (tmp == PACKED_MODE_COMPLAIN || (e->version & FORCE_COMPLAIN_FLAG)) {
|
||||
profile->mode = APPARMOR_COMPLAIN;
|
||||
@@ -766,16 +740,16 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
} else {
|
||||
goto fail;
|
||||
}
|
||||
if (!unpack_u32(e, &tmp, NULL))
|
||||
if (!aa_unpack_u32(e, &tmp, NULL))
|
||||
goto fail;
|
||||
if (tmp)
|
||||
profile->audit = AUDIT_ALL;
|
||||
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
|
||||
/* path_flags is optional */
|
||||
if (unpack_u32(e, &profile->path_flags, "path_flags"))
|
||||
if (aa_unpack_u32(e, &profile->path_flags, "path_flags"))
|
||||
profile->path_flags |= profile->label.flags &
|
||||
PATH_MEDIATE_DELETED;
|
||||
else
|
||||
@@ -783,38 +757,38 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
profile->path_flags = PATH_MEDIATE_DELETED;
|
||||
|
||||
info = "failed to unpack profile capabilities";
|
||||
if (!unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
|
||||
if (!aa_unpack_u32(e, &(profile->caps.allow.cap[0]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.audit.cap[0]), NULL))
|
||||
if (!aa_unpack_u32(e, &(profile->caps.audit.cap[0]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL))
|
||||
if (!aa_unpack_u32(e, &(profile->caps.quiet.cap[0]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &tmpcap.cap[0], NULL))
|
||||
if (!aa_unpack_u32(e, &tmpcap.cap[0], NULL))
|
||||
goto fail;
|
||||
|
||||
info = "failed to unpack upper profile capabilities";
|
||||
if (unpack_nameX(e, AA_STRUCT, "caps64")) {
|
||||
if (aa_unpack_nameX(e, AA_STRUCT, "caps64")) {
|
||||
/* optional upper half of 64 bit caps */
|
||||
if (!unpack_u32(e, &(profile->caps.allow.cap[1]), NULL))
|
||||
if (!aa_unpack_u32(e, &(profile->caps.allow.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.audit.cap[1]), NULL))
|
||||
if (!aa_unpack_u32(e, &(profile->caps.audit.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL))
|
||||
if (!aa_unpack_u32(e, &(profile->caps.quiet.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(tmpcap.cap[1]), NULL))
|
||||
if (!aa_unpack_u32(e, &(tmpcap.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
info = "failed to unpack extended profile capabilities";
|
||||
if (unpack_nameX(e, AA_STRUCT, "capsx")) {
|
||||
if (aa_unpack_nameX(e, AA_STRUCT, "capsx")) {
|
||||
/* optional extended caps mediation mask */
|
||||
if (!unpack_u32(e, &(profile->caps.extended.cap[0]), NULL))
|
||||
if (!aa_unpack_u32(e, &(profile->caps.extended.cap[0]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_u32(e, &(profile->caps.extended.cap[1]), NULL))
|
||||
if (!aa_unpack_u32(e, &(profile->caps.extended.cap[1]), NULL))
|
||||
goto fail;
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
}
|
||||
|
||||
@@ -833,7 +807,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (unpack_nameX(e, AA_STRUCT, "policydb")) {
|
||||
if (aa_unpack_nameX(e, AA_STRUCT, "policydb")) {
|
||||
/* generic policy dfa - optional and may be NULL */
|
||||
info = "failed to unpack policydb";
|
||||
profile->policy.dfa = unpack_dfa(e);
|
||||
@@ -845,7 +819,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
error = -EPROTO;
|
||||
goto fail;
|
||||
}
|
||||
if (!unpack_u32(e, &profile->policy.start[0], "start"))
|
||||
if (!aa_unpack_u32(e, &profile->policy.start[0], "start"))
|
||||
/* default start state */
|
||||
profile->policy.start[0] = DFA_START;
|
||||
/* setup class index */
|
||||
@@ -855,7 +829,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
profile->policy.start[0],
|
||||
i);
|
||||
}
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL))
|
||||
goto fail;
|
||||
} else
|
||||
profile->policy.dfa = aa_get_dfa(nulldfa);
|
||||
@@ -868,7 +842,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
info = "failed to unpack profile file rules";
|
||||
goto fail;
|
||||
} else if (profile->file.dfa) {
|
||||
if (!unpack_u32(e, &profile->file.start, "dfa_start"))
|
||||
if (!aa_unpack_u32(e, &profile->file.start, "dfa_start"))
|
||||
/* default start state */
|
||||
profile->file.start = DFA_START;
|
||||
} else if (profile->policy.dfa &&
|
||||
@@ -883,7 +857,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
if (unpack_nameX(e, AA_STRUCT, "data")) {
|
||||
if (aa_unpack_nameX(e, AA_STRUCT, "data")) {
|
||||
info = "out of memory";
|
||||
profile->data = kzalloc(sizeof(*profile->data), GFP_KERNEL);
|
||||
if (!profile->data)
|
||||
@@ -901,7 +875,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
goto fail;
|
||||
}
|
||||
|
||||
while (unpack_strdup(e, &key, NULL)) {
|
||||
while (aa_unpack_strdup(e, &key, NULL)) {
|
||||
data = kzalloc(sizeof(*data), GFP_KERNEL);
|
||||
if (!data) {
|
||||
kfree_sensitive(key);
|
||||
@@ -909,7 +883,7 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
}
|
||||
|
||||
data->key = key;
|
||||
data->size = unpack_blob(e, &data->data, NULL);
|
||||
data->size = aa_unpack_blob(e, &data->data, NULL);
|
||||
data->data = kvmemdup(data->data, data->size);
|
||||
if (data->size && !data->data) {
|
||||
kfree_sensitive(data->key);
|
||||
@@ -921,13 +895,13 @@ static struct aa_profile *unpack_profile(struct aa_ext *e, char **ns_name)
|
||||
profile->data->p);
|
||||
}
|
||||
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL)) {
|
||||
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) {
|
||||
info = "failed to unpack end of key, value data table";
|
||||
goto fail;
|
||||
}
|
||||
}
|
||||
|
||||
if (!unpack_nameX(e, AA_STRUCTEND, NULL)) {
|
||||
if (!aa_unpack_nameX(e, AA_STRUCTEND, NULL)) {
|
||||
info = "failed to unpack end of profile";
|
||||
goto fail;
|
||||
}
|
||||
@@ -960,7 +934,7 @@ static int verify_header(struct aa_ext *e, int required, const char **ns)
|
||||
*ns = NULL;
|
||||
|
||||
/* get the interface version */
|
||||
if (!unpack_u32(e, &e->version, "version")) {
|
||||
if (!aa_unpack_u32(e, &e->version, "version")) {
|
||||
if (required) {
|
||||
audit_iface(NULL, NULL, NULL, "invalid profile format",
|
||||
e, error);
|
||||
@@ -979,7 +953,7 @@ static int verify_header(struct aa_ext *e, int required, const char **ns)
|
||||
}
|
||||
|
||||
/* read the namespace if present */
|
||||
if (unpack_str(e, &name, "namespace")) {
|
||||
if (aa_unpack_str(e, &name, "namespace")) {
|
||||
if (*name == '\0') {
|
||||
audit_iface(NULL, NULL, NULL, "invalid namespace name",
|
||||
e, error);
|
||||
@@ -1251,7 +1225,3 @@ fail:
|
||||
|
||||
return error;
|
||||
}
|
||||
|
||||
#ifdef CONFIG_SECURITY_APPARMOR_KUNIT_TEST
|
||||
#include "policy_unpack_test.c"
|
||||
#endif /* CONFIG_SECURITY_APPARMOR_KUNIT_TEST */
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
|
||||
#include <kunit/test.h>
|
||||
#include <kunit/visibility.h>
|
||||
|
||||
#include "include/policy.h"
|
||||
#include "include/policy_unpack.h"
|
||||
@@ -43,6 +44,8 @@
|
||||
#define TEST_ARRAY_BUF_OFFSET \
|
||||
(TEST_NAMED_ARRAY_BUF_OFFSET + 3 + strlen(TEST_ARRAY_NAME) + 1)
|
||||
|
||||
MODULE_IMPORT_NS(EXPORTED_FOR_KUNIT_TESTING);
|
||||
|
||||
struct policy_unpack_fixture {
|
||||
struct aa_ext *e;
|
||||
size_t e_size;
|
||||
@@ -125,16 +128,16 @@ static void policy_unpack_test_inbounds_when_inbounds(struct kunit *test)
|
||||
{
|
||||
struct policy_unpack_fixture *puf = test->priv;
|
||||
|
||||
KUNIT_EXPECT_TRUE(test, inbounds(puf->e, 0));
|
||||
KUNIT_EXPECT_TRUE(test, inbounds(puf->e, puf->e_size / 2));
|
||||
KUNIT_EXPECT_TRUE(test, inbounds(puf->e, puf->e_size));
|
||||
KUNIT_EXPECT_TRUE(test, aa_inbounds(puf->e, 0));
|
||||
KUNIT_EXPECT_TRUE(test, aa_inbounds(puf->e, puf->e_size / 2));
|
||||
KUNIT_EXPECT_TRUE(test, aa_inbounds(puf->e, puf->e_size));
|
||||
}
|
||||
|
||||
static void policy_unpack_test_inbounds_when_out_of_bounds(struct kunit *test)
|
||||
{
|
||||
struct policy_unpack_fixture *puf = test->priv;
|
||||
|
||||
KUNIT_EXPECT_FALSE(test, inbounds(puf->e, puf->e_size + 1));
|
||||
KUNIT_EXPECT_FALSE(test, aa_inbounds(puf->e, puf->e_size + 1));
|
||||
}
|
||||
|
||||
static void policy_unpack_test_unpack_array_with_null_name(struct kunit *test)
|
||||
@@ -144,7 +147,7 @@ static void policy_unpack_test_unpack_array_with_null_name(struct kunit *test)
|
||||
|
||||
puf->e->pos += TEST_ARRAY_BUF_OFFSET;
|
||||
|
||||
array_size = unpack_array(puf->e, NULL);
|
||||
array_size = aa_unpack_array(puf->e, NULL);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, array_size, (u16)TEST_ARRAY_SIZE);
|
||||
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
|
||||
@@ -159,7 +162,7 @@ static void policy_unpack_test_unpack_array_with_name(struct kunit *test)
|
||||
|
||||
puf->e->pos += TEST_NAMED_ARRAY_BUF_OFFSET;
|
||||
|
||||
array_size = unpack_array(puf->e, name);
|
||||
array_size = aa_unpack_array(puf->e, name);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, array_size, (u16)TEST_ARRAY_SIZE);
|
||||
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
|
||||
@@ -175,7 +178,7 @@ static void policy_unpack_test_unpack_array_out_of_bounds(struct kunit *test)
|
||||
puf->e->pos += TEST_NAMED_ARRAY_BUF_OFFSET;
|
||||
puf->e->end = puf->e->start + TEST_ARRAY_BUF_OFFSET + sizeof(u16);
|
||||
|
||||
array_size = unpack_array(puf->e, name);
|
||||
array_size = aa_unpack_array(puf->e, name);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, array_size, 0);
|
||||
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
|
||||
@@ -189,7 +192,7 @@ static void policy_unpack_test_unpack_blob_with_null_name(struct kunit *test)
|
||||
size_t size;
|
||||
|
||||
puf->e->pos += TEST_BLOB_BUF_OFFSET;
|
||||
size = unpack_blob(puf->e, &blob, NULL);
|
||||
size = aa_unpack_blob(puf->e, &blob, NULL);
|
||||
|
||||
KUNIT_ASSERT_EQ(test, size, TEST_BLOB_DATA_SIZE);
|
||||
KUNIT_EXPECT_TRUE(test,
|
||||
@@ -203,7 +206,7 @@ static void policy_unpack_test_unpack_blob_with_name(struct kunit *test)
|
||||
size_t size;
|
||||
|
||||
puf->e->pos += TEST_NAMED_BLOB_BUF_OFFSET;
|
||||
size = unpack_blob(puf->e, &blob, TEST_BLOB_NAME);
|
||||
size = aa_unpack_blob(puf->e, &blob, TEST_BLOB_NAME);
|
||||
|
||||
KUNIT_ASSERT_EQ(test, size, TEST_BLOB_DATA_SIZE);
|
||||
KUNIT_EXPECT_TRUE(test,
|
||||
@@ -222,7 +225,7 @@ static void policy_unpack_test_unpack_blob_out_of_bounds(struct kunit *test)
|
||||
puf->e->end = puf->e->start + TEST_BLOB_BUF_OFFSET
|
||||
+ TEST_BLOB_DATA_SIZE - 1;
|
||||
|
||||
size = unpack_blob(puf->e, &blob, TEST_BLOB_NAME);
|
||||
size = aa_unpack_blob(puf->e, &blob, TEST_BLOB_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, size, 0);
|
||||
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, start);
|
||||
@@ -235,7 +238,7 @@ static void policy_unpack_test_unpack_str_with_null_name(struct kunit *test)
|
||||
size_t size;
|
||||
|
||||
puf->e->pos += TEST_STRING_BUF_OFFSET;
|
||||
size = unpack_str(puf->e, &string, NULL);
|
||||
size = aa_unpack_str(puf->e, &string, NULL);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, size, strlen(TEST_STRING_DATA) + 1);
|
||||
KUNIT_EXPECT_STREQ(test, string, TEST_STRING_DATA);
|
||||
@@ -247,7 +250,7 @@ static void policy_unpack_test_unpack_str_with_name(struct kunit *test)
|
||||
const char *string = NULL;
|
||||
size_t size;
|
||||
|
||||
size = unpack_str(puf->e, &string, TEST_STRING_NAME);
|
||||
size = aa_unpack_str(puf->e, &string, TEST_STRING_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, size, strlen(TEST_STRING_DATA) + 1);
|
||||
KUNIT_EXPECT_STREQ(test, string, TEST_STRING_DATA);
|
||||
@@ -263,7 +266,7 @@ static void policy_unpack_test_unpack_str_out_of_bounds(struct kunit *test)
|
||||
puf->e->end = puf->e->pos + TEST_STRING_BUF_OFFSET
|
||||
+ strlen(TEST_STRING_DATA) - 1;
|
||||
|
||||
size = unpack_str(puf->e, &string, TEST_STRING_NAME);
|
||||
size = aa_unpack_str(puf->e, &string, TEST_STRING_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, size, 0);
|
||||
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos, start);
|
||||
@@ -276,7 +279,7 @@ static void policy_unpack_test_unpack_strdup_with_null_name(struct kunit *test)
|
||||
size_t size;
|
||||
|
||||
puf->e->pos += TEST_STRING_BUF_OFFSET;
|
||||
size = unpack_strdup(puf->e, &string, NULL);
|
||||
size = aa_unpack_strdup(puf->e, &string, NULL);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, size, strlen(TEST_STRING_DATA) + 1);
|
||||
KUNIT_EXPECT_FALSE(test,
|
||||
@@ -291,7 +294,7 @@ static void policy_unpack_test_unpack_strdup_with_name(struct kunit *test)
|
||||
char *string = NULL;
|
||||
size_t size;
|
||||
|
||||
size = unpack_strdup(puf->e, &string, TEST_STRING_NAME);
|
||||
size = aa_unpack_strdup(puf->e, &string, TEST_STRING_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, size, strlen(TEST_STRING_DATA) + 1);
|
||||
KUNIT_EXPECT_FALSE(test,
|
||||
@@ -310,7 +313,7 @@ static void policy_unpack_test_unpack_strdup_out_of_bounds(struct kunit *test)
|
||||
puf->e->end = puf->e->pos + TEST_STRING_BUF_OFFSET
|
||||
+ strlen(TEST_STRING_DATA) - 1;
|
||||
|
||||
size = unpack_strdup(puf->e, &string, TEST_STRING_NAME);
|
||||
size = aa_unpack_strdup(puf->e, &string, TEST_STRING_NAME);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, size, 0);
|
||||
KUNIT_EXPECT_NULL(test, string);
|
||||
@@ -324,7 +327,7 @@ static void policy_unpack_test_unpack_nameX_with_null_name(struct kunit *test)
|
||||
|
||||
puf->e->pos += TEST_U32_BUF_OFFSET;
|
||||
|
||||
success = unpack_nameX(puf->e, AA_U32, NULL);
|
||||
success = aa_unpack_nameX(puf->e, AA_U32, NULL);
|
||||
|
||||
KUNIT_EXPECT_TRUE(test, success);
|
||||
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
|
||||
@@ -338,7 +341,7 @@ static void policy_unpack_test_unpack_nameX_with_wrong_code(struct kunit *test)
|
||||
|
||||
puf->e->pos += TEST_U32_BUF_OFFSET;
|
||||
|
||||
success = unpack_nameX(puf->e, AA_BLOB, NULL);
|
||||
success = aa_unpack_nameX(puf->e, AA_BLOB, NULL);
|
||||
|
||||
KUNIT_EXPECT_FALSE(test, success);
|
||||
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
|
||||
@@ -353,7 +356,7 @@ static void policy_unpack_test_unpack_nameX_with_name(struct kunit *test)
|
||||
|
||||
puf->e->pos += TEST_NAMED_U32_BUF_OFFSET;
|
||||
|
||||
success = unpack_nameX(puf->e, AA_U32, name);
|
||||
success = aa_unpack_nameX(puf->e, AA_U32, name);
|
||||
|
||||
KUNIT_EXPECT_TRUE(test, success);
|
||||
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
|
||||
@@ -368,7 +371,7 @@ static void policy_unpack_test_unpack_nameX_with_wrong_name(struct kunit *test)
|
||||
|
||||
puf->e->pos += TEST_NAMED_U32_BUF_OFFSET;
|
||||
|
||||
success = unpack_nameX(puf->e, AA_U32, name);
|
||||
success = aa_unpack_nameX(puf->e, AA_U32, name);
|
||||
|
||||
KUNIT_EXPECT_FALSE(test, success);
|
||||
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
|
||||
@@ -389,7 +392,7 @@ static void policy_unpack_test_unpack_u16_chunk_basic(struct kunit *test)
|
||||
*/
|
||||
puf->e->end += TEST_U16_DATA;
|
||||
|
||||
size = unpack_u16_chunk(puf->e, &chunk);
|
||||
size = aa_unpack_u16_chunk(puf->e, &chunk);
|
||||
|
||||
KUNIT_EXPECT_PTR_EQ(test, chunk,
|
||||
puf->e->start + TEST_U16_OFFSET + 2);
|
||||
@@ -406,7 +409,7 @@ static void policy_unpack_test_unpack_u16_chunk_out_of_bounds_1(
|
||||
|
||||
puf->e->pos = puf->e->end - 1;
|
||||
|
||||
size = unpack_u16_chunk(puf->e, &chunk);
|
||||
size = aa_unpack_u16_chunk(puf->e, &chunk);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, size, 0);
|
||||
KUNIT_EXPECT_NULL(test, chunk);
|
||||
@@ -428,7 +431,7 @@ static void policy_unpack_test_unpack_u16_chunk_out_of_bounds_2(
|
||||
*/
|
||||
puf->e->end = puf->e->pos + TEST_U16_DATA - 1;
|
||||
|
||||
size = unpack_u16_chunk(puf->e, &chunk);
|
||||
size = aa_unpack_u16_chunk(puf->e, &chunk);
|
||||
|
||||
KUNIT_EXPECT_EQ(test, size, 0);
|
||||
KUNIT_EXPECT_NULL(test, chunk);
|
||||
@@ -443,7 +446,7 @@ static void policy_unpack_test_unpack_u32_with_null_name(struct kunit *test)
|
||||
|
||||
puf->e->pos += TEST_U32_BUF_OFFSET;
|
||||
|
||||
success = unpack_u32(puf->e, &data, NULL);
|
||||
success = aa_unpack_u32(puf->e, &data, NULL);
|
||||
|
||||
KUNIT_EXPECT_TRUE(test, success);
|
||||
KUNIT_EXPECT_EQ(test, data, TEST_U32_DATA);
|
||||
@@ -460,7 +463,7 @@ static void policy_unpack_test_unpack_u32_with_name(struct kunit *test)
|
||||
|
||||
puf->e->pos += TEST_NAMED_U32_BUF_OFFSET;
|
||||
|
||||
success = unpack_u32(puf->e, &data, name);
|
||||
success = aa_unpack_u32(puf->e, &data, name);
|
||||
|
||||
KUNIT_EXPECT_TRUE(test, success);
|
||||
KUNIT_EXPECT_EQ(test, data, TEST_U32_DATA);
|
||||
@@ -478,7 +481,7 @@ static void policy_unpack_test_unpack_u32_out_of_bounds(struct kunit *test)
|
||||
puf->e->pos += TEST_NAMED_U32_BUF_OFFSET;
|
||||
puf->e->end = puf->e->start + TEST_U32_BUF_OFFSET + sizeof(u32);
|
||||
|
||||
success = unpack_u32(puf->e, &data, name);
|
||||
success = aa_unpack_u32(puf->e, &data, name);
|
||||
|
||||
KUNIT_EXPECT_FALSE(test, success);
|
||||
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
|
||||
@@ -493,7 +496,7 @@ static void policy_unpack_test_unpack_u64_with_null_name(struct kunit *test)
|
||||
|
||||
puf->e->pos += TEST_U64_BUF_OFFSET;
|
||||
|
||||
success = unpack_u64(puf->e, &data, NULL);
|
||||
success = aa_unpack_u64(puf->e, &data, NULL);
|
||||
|
||||
KUNIT_EXPECT_TRUE(test, success);
|
||||
KUNIT_EXPECT_EQ(test, data, TEST_U64_DATA);
|
||||
@@ -510,7 +513,7 @@ static void policy_unpack_test_unpack_u64_with_name(struct kunit *test)
|
||||
|
||||
puf->e->pos += TEST_NAMED_U64_BUF_OFFSET;
|
||||
|
||||
success = unpack_u64(puf->e, &data, name);
|
||||
success = aa_unpack_u64(puf->e, &data, name);
|
||||
|
||||
KUNIT_EXPECT_TRUE(test, success);
|
||||
KUNIT_EXPECT_EQ(test, data, TEST_U64_DATA);
|
||||
@@ -528,7 +531,7 @@ static void policy_unpack_test_unpack_u64_out_of_bounds(struct kunit *test)
|
||||
puf->e->pos += TEST_NAMED_U64_BUF_OFFSET;
|
||||
puf->e->end = puf->e->start + TEST_U64_BUF_OFFSET + sizeof(u64);
|
||||
|
||||
success = unpack_u64(puf->e, &data, name);
|
||||
success = aa_unpack_u64(puf->e, &data, name);
|
||||
|
||||
KUNIT_EXPECT_FALSE(test, success);
|
||||
KUNIT_EXPECT_PTR_EQ(test, puf->e->pos,
|
||||
@@ -538,7 +541,7 @@ static void policy_unpack_test_unpack_u64_out_of_bounds(struct kunit *test)
|
||||
static void policy_unpack_test_unpack_X_code_match(struct kunit *test)
|
||||
{
|
||||
struct policy_unpack_fixture *puf = test->priv;
|
||||
bool success = unpack_X(puf->e, AA_NAME);
|
||||
bool success = aa_unpack_X(puf->e, AA_NAME);
|
||||
|
||||
KUNIT_EXPECT_TRUE(test, success);
|
||||
KUNIT_EXPECT_TRUE(test, puf->e->pos == puf->e->start + 1);
|
||||
@@ -547,7 +550,7 @@ static void policy_unpack_test_unpack_X_code_match(struct kunit *test)
|
||||
static void policy_unpack_test_unpack_X_code_mismatch(struct kunit *test)
|
||||
{
|
||||
struct policy_unpack_fixture *puf = test->priv;
|
||||
bool success = unpack_X(puf->e, AA_STRING);
|
||||
bool success = aa_unpack_X(puf->e, AA_STRING);
|
||||
|
||||
KUNIT_EXPECT_FALSE(test, success);
|
||||
KUNIT_EXPECT_TRUE(test, puf->e->pos == puf->e->start);
|
||||
@@ -559,7 +562,7 @@ static void policy_unpack_test_unpack_X_out_of_bounds(struct kunit *test)
|
||||
bool success;
|
||||
|
||||
puf->e->pos = puf->e->end;
|
||||
success = unpack_X(puf->e, AA_NAME);
|
||||
success = aa_unpack_X(puf->e, AA_NAME);
|
||||
|
||||
KUNIT_EXPECT_FALSE(test, success);
|
||||
}
|
||||
@@ -605,3 +608,5 @@ static struct kunit_suite apparmor_policy_unpack_test_module = {
|
||||
};
|
||||
|
||||
kunit_test_suite(apparmor_policy_unpack_test_module);
|
||||
|
||||
MODULE_LICENSE("GPL");
|
||||
|
||||
@@ -802,7 +802,14 @@ sub process_variables {
|
||||
my $end = $3;
|
||||
# append beginning of value to retval
|
||||
$retval = "$retval$begin";
|
||||
if (defined($variable{$var})) {
|
||||
if ($var =~ s/^shell\s+//) {
|
||||
$retval = `$var`;
|
||||
if ($?) {
|
||||
doprint "WARNING: $var returned an error\n";
|
||||
} else {
|
||||
chomp $retval;
|
||||
}
|
||||
} elsif (defined($variable{$var})) {
|
||||
$retval = "$retval$variable{$var}";
|
||||
} elsif (defined($remove_undef) && $remove_undef) {
|
||||
# for if statements, any variable that is not defined,
|
||||
@@ -1963,7 +1970,7 @@ sub run_scp_mod {
|
||||
|
||||
sub _get_grub_index {
|
||||
|
||||
my ($command, $target, $skip) = @_;
|
||||
my ($command, $target, $skip, $submenu) = @_;
|
||||
|
||||
return if (defined($grub_number) && defined($last_grub_menu) &&
|
||||
$last_grub_menu eq $grub_menu && defined($last_machine) &&
|
||||
@@ -1980,11 +1987,16 @@ sub _get_grub_index {
|
||||
|
||||
my $found = 0;
|
||||
|
||||
my $submenu_number = 0;
|
||||
|
||||
while (<IN>) {
|
||||
if (/$target/) {
|
||||
$grub_number++;
|
||||
$found = 1;
|
||||
last;
|
||||
} elsif (defined($submenu) && /$submenu/) {
|
||||
$submenu_number++;
|
||||
$grub_number = -1;
|
||||
} elsif (/$skip/) {
|
||||
$grub_number++;
|
||||
}
|
||||
@@ -1993,6 +2005,9 @@ sub _get_grub_index {
|
||||
|
||||
dodie "Could not find '$grub_menu' through $command on $machine"
|
||||
if (!$found);
|
||||
if ($submenu_number > 0) {
|
||||
$grub_number = "$submenu_number>$grub_number";
|
||||
}
|
||||
doprint "$grub_number\n";
|
||||
$last_grub_menu = $grub_menu;
|
||||
$last_machine = $machine;
|
||||
@@ -2003,6 +2018,7 @@ sub get_grub_index {
|
||||
my $command;
|
||||
my $target;
|
||||
my $skip;
|
||||
my $submenu;
|
||||
my $grub_menu_qt;
|
||||
|
||||
if ($reboot_type !~ /^grub/) {
|
||||
@@ -2017,8 +2033,9 @@ sub get_grub_index {
|
||||
$skip = '^\s*title\s';
|
||||
} elsif ($reboot_type eq "grub2") {
|
||||
$command = "cat $grub_file";
|
||||
$target = '^menuentry.*' . $grub_menu_qt;
|
||||
$skip = '^menuentry\s|^submenu\s';
|
||||
$target = '^\s*menuentry.*' . $grub_menu_qt;
|
||||
$skip = '^\s*menuentry';
|
||||
$submenu = '^\s*submenu\s';
|
||||
} elsif ($reboot_type eq "grub2bls") {
|
||||
$command = $grub_bls_get;
|
||||
$target = '^title=.*' . $grub_menu_qt;
|
||||
@@ -2027,7 +2044,7 @@ sub get_grub_index {
|
||||
return;
|
||||
}
|
||||
|
||||
_get_grub_index($command, $target, $skip);
|
||||
_get_grub_index($command, $target, $skip, $submenu);
|
||||
}
|
||||
|
||||
sub wait_for_input {
|
||||
@@ -2090,7 +2107,7 @@ sub reboot_to {
|
||||
if ($reboot_type eq "grub") {
|
||||
run_ssh "'(echo \"savedefault --default=$grub_number --once\" | grub --batch)'";
|
||||
} elsif (($reboot_type eq "grub2") or ($reboot_type eq "grub2bls")) {
|
||||
run_ssh "$grub_reboot $grub_number";
|
||||
run_ssh "$grub_reboot \"'$grub_number'\"";
|
||||
} elsif ($reboot_type eq "syslinux") {
|
||||
run_ssh "$syslinux --once \\\"$syslinux_label\\\" $syslinux_path";
|
||||
} elsif (defined $reboot_script) {
|
||||
@@ -3768,9 +3785,10 @@ sub test_this_config {
|
||||
# .config to make sure it is missing the config that
|
||||
# we had before
|
||||
my %configs = %min_configs;
|
||||
delete $configs{$config};
|
||||
$configs{$config} = "# $config is not set";
|
||||
make_new_config ((values %configs), (values %keep_configs));
|
||||
make_oldconfig;
|
||||
delete $configs{$config};
|
||||
undef %configs;
|
||||
assign_configs \%configs, $output_config;
|
||||
|
||||
|
||||
@@ -259,6 +259,14 @@
|
||||
# If PATH is not a config variable, then the ${PATH} in
|
||||
# the MAKE_CMD option will be evaluated by the shell when
|
||||
# the MAKE_CMD option is passed into shell processing.
|
||||
#
|
||||
# Shell commands can also be inserted with the ${shell <command>}
|
||||
# expression. Note, this is case sensitive, thus ${SHELL <command>}
|
||||
# will not work.
|
||||
#
|
||||
# HOSTNAME := ${shell hostname}
|
||||
# DEFAULTS IF "${HOSTNAME}" == "frodo"
|
||||
#
|
||||
|
||||
#### Using options in other options ####
|
||||
#
|
||||
|
||||
@@ -192,28 +192,30 @@ def _map_to_overall_status(test_status: kunit_parser.TestStatus) -> KunitStatus:
|
||||
def parse_tests(request: KunitParseRequest, metadata: kunit_json.Metadata, input_data: Iterable[str]) -> Tuple[KunitResult, kunit_parser.Test]:
|
||||
parse_start = time.time()
|
||||
|
||||
test_result = kunit_parser.Test()
|
||||
|
||||
if request.raw_output:
|
||||
# Treat unparsed results as one passing test.
|
||||
test_result.status = kunit_parser.TestStatus.SUCCESS
|
||||
test_result.counts.passed = 1
|
||||
fake_test = kunit_parser.Test()
|
||||
fake_test.status = kunit_parser.TestStatus.SUCCESS
|
||||
fake_test.counts.passed = 1
|
||||
|
||||
output: Iterable[str] = input_data
|
||||
if request.raw_output == 'all':
|
||||
pass
|
||||
elif request.raw_output == 'kunit':
|
||||
output = kunit_parser.extract_tap_lines(output, lstrip=False)
|
||||
output = kunit_parser.extract_tap_lines(output)
|
||||
for line in output:
|
||||
print(line.rstrip())
|
||||
parse_time = time.time() - parse_start
|
||||
return KunitResult(KunitStatus.SUCCESS, parse_time), fake_test
|
||||
|
||||
else:
|
||||
test_result = kunit_parser.parse_run_tests(input_data)
|
||||
parse_end = time.time()
|
||||
|
||||
# Actually parse the test results.
|
||||
test = kunit_parser.parse_run_tests(input_data)
|
||||
parse_time = time.time() - parse_start
|
||||
|
||||
if request.json:
|
||||
json_str = kunit_json.get_json_result(
|
||||
test=test_result,
|
||||
test=test,
|
||||
metadata=metadata)
|
||||
if request.json == 'stdout':
|
||||
print(json_str)
|
||||
@@ -223,10 +225,10 @@ def parse_tests(request: KunitParseRequest, metadata: kunit_json.Metadata, input
|
||||
stdout.print_with_timestamp("Test results stored in %s" %
|
||||
os.path.abspath(request.json))
|
||||
|
||||
if test_result.status != kunit_parser.TestStatus.SUCCESS:
|
||||
return KunitResult(KunitStatus.TEST_FAILURE, parse_end - parse_start), test_result
|
||||
if test.status != kunit_parser.TestStatus.SUCCESS:
|
||||
return KunitResult(KunitStatus.TEST_FAILURE, parse_time), test
|
||||
|
||||
return KunitResult(KunitStatus.SUCCESS, parse_end - parse_start), test_result
|
||||
return KunitResult(KunitStatus.SUCCESS, parse_time), test
|
||||
|
||||
def run_tests(linux: kunit_kernel.LinuxSourceTree,
|
||||
request: KunitRequest) -> KunitResult:
|
||||
@@ -359,14 +361,14 @@ def add_exec_opts(parser) -> None:
|
||||
choices=['suite', 'test'])
|
||||
|
||||
def add_parse_opts(parser) -> None:
|
||||
parser.add_argument('--raw_output', help='If set don\'t format output from kernel. '
|
||||
'If set to --raw_output=kunit, filters to just KUnit output.',
|
||||
parser.add_argument('--raw_output', help='If set don\'t parse output from kernel. '
|
||||
'By default, filters to just KUnit output. Use '
|
||||
'--raw_output=all to show everything',
|
||||
type=str, nargs='?', const='all', default=None, choices=['all', 'kunit'])
|
||||
parser.add_argument('--json',
|
||||
nargs='?',
|
||||
help='Stores test results in a JSON, and either '
|
||||
'prints to stdout or saves to file if a '
|
||||
'filename is specified',
|
||||
help='Prints parsed test results as JSON to stdout or a file if '
|
||||
'a filename is specified. Does nothing if --raw_output is set.',
|
||||
type=str, const='stdout', default=None, metavar='FILE')
|
||||
|
||||
|
||||
|
||||
@@ -10,8 +10,10 @@
|
||||
# Author: Rae Moar <rmoar@google.com>
|
||||
|
||||
from __future__ import annotations
|
||||
from dataclasses import dataclass
|
||||
import re
|
||||
import sys
|
||||
import textwrap
|
||||
|
||||
from enum import Enum, auto
|
||||
from typing import Iterable, Iterator, List, Optional, Tuple
|
||||
@@ -58,6 +60,10 @@ class Test:
|
||||
self.counts.errors += 1
|
||||
stdout.print_with_timestamp(stdout.red('[ERROR]') + f' Test: {self.name}: {error_message}')
|
||||
|
||||
def ok_status(self) -> bool:
|
||||
"""Returns true if the status was ok, i.e. passed or skipped."""
|
||||
return self.status in (TestStatus.SUCCESS, TestStatus.SKIPPED)
|
||||
|
||||
class TestStatus(Enum):
|
||||
"""An enumeration class to represent the status of a test."""
|
||||
SUCCESS = auto()
|
||||
@@ -67,27 +73,17 @@ class TestStatus(Enum):
|
||||
NO_TESTS = auto()
|
||||
FAILURE_TO_PARSE_TESTS = auto()
|
||||
|
||||
@dataclass
|
||||
class TestCounts:
|
||||
"""
|
||||
Tracks the counts of statuses of all test cases and any errors within
|
||||
a Test.
|
||||
|
||||
Attributes:
|
||||
passed : int - the number of tests that have passed
|
||||
failed : int - the number of tests that have failed
|
||||
crashed : int - the number of tests that have crashed
|
||||
skipped : int - the number of tests that have skipped
|
||||
errors : int - the number of errors in the test and subtests
|
||||
"""
|
||||
def __init__(self):
|
||||
"""Creates TestCounts object with counts of all test
|
||||
statuses and test errors set to 0.
|
||||
"""
|
||||
self.passed = 0
|
||||
self.failed = 0
|
||||
self.crashed = 0
|
||||
self.skipped = 0
|
||||
self.errors = 0
|
||||
passed: int = 0
|
||||
failed: int = 0
|
||||
crashed: int = 0
|
||||
skipped: int = 0
|
||||
errors: int = 0
|
||||
|
||||
def __str__(self) -> str:
|
||||
"""Returns the string representation of a TestCounts object."""
|
||||
@@ -213,12 +209,12 @@ class LineStream:
|
||||
|
||||
# Parsing helper methods:
|
||||
|
||||
KTAP_START = re.compile(r'KTAP version ([0-9]+)$')
|
||||
TAP_START = re.compile(r'TAP version ([0-9]+)$')
|
||||
KTAP_END = re.compile('(List of all partitions:|'
|
||||
KTAP_START = re.compile(r'\s*KTAP version ([0-9]+)$')
|
||||
TAP_START = re.compile(r'\s*TAP version ([0-9]+)$')
|
||||
KTAP_END = re.compile(r'\s*(List of all partitions:|'
|
||||
'Kernel panic - not syncing: VFS:|reboot: System halted)')
|
||||
|
||||
def extract_tap_lines(kernel_output: Iterable[str], lstrip=True) -> LineStream:
|
||||
def extract_tap_lines(kernel_output: Iterable[str]) -> LineStream:
|
||||
"""Extracts KTAP lines from the kernel output."""
|
||||
def isolate_ktap_output(kernel_output: Iterable[str]) \
|
||||
-> Iterator[Tuple[int, str]]:
|
||||
@@ -244,11 +240,8 @@ def extract_tap_lines(kernel_output: Iterable[str], lstrip=True) -> LineStream:
|
||||
# stop extracting KTAP lines
|
||||
break
|
||||
elif started:
|
||||
# remove the prefix and optionally any leading
|
||||
# whitespace. Our parsing logic relies on this.
|
||||
# remove the prefix, if any.
|
||||
line = line[prefix_len:]
|
||||
if lstrip:
|
||||
line = line.lstrip()
|
||||
yield line_num, line
|
||||
return LineStream(lines=isolate_ktap_output(kernel_output))
|
||||
|
||||
@@ -300,10 +293,10 @@ def parse_ktap_header(lines: LineStream, test: Test) -> bool:
|
||||
check_version(version_num, TAP_VERSIONS, 'TAP', test)
|
||||
else:
|
||||
return False
|
||||
test.log.append(lines.pop())
|
||||
lines.pop()
|
||||
return True
|
||||
|
||||
TEST_HEADER = re.compile(r'^# Subtest: (.*)$')
|
||||
TEST_HEADER = re.compile(r'^\s*# Subtest: (.*)$')
|
||||
|
||||
def parse_test_header(lines: LineStream, test: Test) -> bool:
|
||||
"""
|
||||
@@ -323,11 +316,11 @@ def parse_test_header(lines: LineStream, test: Test) -> bool:
|
||||
match = TEST_HEADER.match(lines.peek())
|
||||
if not match:
|
||||
return False
|
||||
test.log.append(lines.pop())
|
||||
test.name = match.group(1)
|
||||
lines.pop()
|
||||
return True
|
||||
|
||||
TEST_PLAN = re.compile(r'1\.\.([0-9]+)')
|
||||
TEST_PLAN = re.compile(r'^\s*1\.\.([0-9]+)')
|
||||
|
||||
def parse_test_plan(lines: LineStream, test: Test) -> bool:
|
||||
"""
|
||||
@@ -350,14 +343,14 @@ def parse_test_plan(lines: LineStream, test: Test) -> bool:
|
||||
if not match:
|
||||
test.expected_count = None
|
||||
return False
|
||||
test.log.append(lines.pop())
|
||||
expected_count = int(match.group(1))
|
||||
test.expected_count = expected_count
|
||||
lines.pop()
|
||||
return True
|
||||
|
||||
TEST_RESULT = re.compile(r'^(ok|not ok) ([0-9]+) (- )?([^#]*)( # .*)?$')
|
||||
TEST_RESULT = re.compile(r'^\s*(ok|not ok) ([0-9]+) (- )?([^#]*)( # .*)?$')
|
||||
|
||||
TEST_RESULT_SKIP = re.compile(r'^(ok|not ok) ([0-9]+) (- )?(.*) # SKIP(.*)$')
|
||||
TEST_RESULT_SKIP = re.compile(r'^\s*(ok|not ok) ([0-9]+) (- )?(.*) # SKIP(.*)$')
|
||||
|
||||
def peek_test_name_match(lines: LineStream, test: Test) -> bool:
|
||||
"""
|
||||
@@ -414,7 +407,7 @@ def parse_test_result(lines: LineStream, test: Test,
|
||||
# Check if line matches test result line format
|
||||
if not match:
|
||||
return False
|
||||
test.log.append(lines.pop())
|
||||
lines.pop()
|
||||
|
||||
# Set name of test object
|
||||
if skip_match:
|
||||
@@ -446,6 +439,7 @@ def parse_diagnostic(lines: LineStream) -> List[str]:
|
||||
- '# Subtest: [test name]'
|
||||
- '[ok|not ok] [test number] [-] [test name] [optional skip
|
||||
directive]'
|
||||
- 'KTAP version [version number]'
|
||||
|
||||
Parameters:
|
||||
lines - LineStream of KTAP output to parse
|
||||
@@ -454,8 +448,9 @@ def parse_diagnostic(lines: LineStream) -> List[str]:
|
||||
Log of diagnostic lines
|
||||
"""
|
||||
log = [] # type: List[str]
|
||||
while lines and not TEST_RESULT.match(lines.peek()) and not \
|
||||
TEST_HEADER.match(lines.peek()):
|
||||
non_diagnostic_lines = [TEST_RESULT, TEST_HEADER, KTAP_START]
|
||||
while lines and not any(re.match(lines.peek())
|
||||
for re in non_diagnostic_lines):
|
||||
log.append(lines.pop())
|
||||
return log
|
||||
|
||||
@@ -501,17 +496,22 @@ def print_test_header(test: Test) -> None:
|
||||
test - Test object representing current test being printed
|
||||
"""
|
||||
message = test.name
|
||||
if message != "":
|
||||
# Add a leading space before the subtest counts only if a test name
|
||||
# is provided using a "# Subtest" header line.
|
||||
message += " "
|
||||
if test.expected_count:
|
||||
if test.expected_count == 1:
|
||||
message += ' (1 subtest)'
|
||||
message += '(1 subtest)'
|
||||
else:
|
||||
message += f' ({test.expected_count} subtests)'
|
||||
message += f'({test.expected_count} subtests)'
|
||||
stdout.print_with_timestamp(format_test_divider(message, len(message)))
|
||||
|
||||
def print_log(log: Iterable[str]) -> None:
|
||||
"""Prints all strings in saved log for test in yellow."""
|
||||
for m in log:
|
||||
stdout.print_with_timestamp(stdout.yellow(m))
|
||||
formatted = textwrap.dedent('\n'.join(log))
|
||||
for line in formatted.splitlines():
|
||||
stdout.print_with_timestamp(stdout.yellow(line))
|
||||
|
||||
def format_test_result(test: Test) -> str:
|
||||
"""
|
||||
@@ -565,6 +565,40 @@ def print_test_footer(test: Test) -> None:
|
||||
stdout.print_with_timestamp(format_test_divider(message,
|
||||
len(message) - stdout.color_len()))
|
||||
|
||||
|
||||
|
||||
def _summarize_failed_tests(test: Test) -> str:
|
||||
"""Tries to summarize all the failing subtests in `test`."""
|
||||
|
||||
def failed_names(test: Test, parent_name: str) -> List[str]:
|
||||
# Note: we use 'main' internally for the top-level test.
|
||||
if not parent_name or parent_name == 'main':
|
||||
full_name = test.name
|
||||
else:
|
||||
full_name = parent_name + '.' + test.name
|
||||
|
||||
if not test.subtests: # this is a leaf node
|
||||
return [full_name]
|
||||
|
||||
# If all the children failed, just say this subtest failed.
|
||||
# Don't summarize it down "the top-level test failed", though.
|
||||
failed_subtests = [sub for sub in test.subtests if not sub.ok_status()]
|
||||
if parent_name and len(failed_subtests) == len(test.subtests):
|
||||
return [full_name]
|
||||
|
||||
all_failures = [] # type: List[str]
|
||||
for t in failed_subtests:
|
||||
all_failures.extend(failed_names(t, full_name))
|
||||
return all_failures
|
||||
|
||||
failures = failed_names(test, '')
|
||||
# If there are too many failures, printing them out will just be noisy.
|
||||
if len(failures) > 10: # this is an arbitrary limit
|
||||
return ''
|
||||
|
||||
return 'Failures: ' + ', '.join(failures)
|
||||
|
||||
|
||||
def print_summary_line(test: Test) -> None:
|
||||
"""
|
||||
Prints summary line of test object. Color of line is dependent on
|
||||
@@ -587,6 +621,15 @@ def print_summary_line(test: Test) -> None:
|
||||
color = stdout.red
|
||||
stdout.print_with_timestamp(color(f'Testing complete. {test.counts}'))
|
||||
|
||||
# Summarize failures that might have gone off-screen since we had a lot
|
||||
# of tests (arbitrarily defined as >=100 for now).
|
||||
if test.ok_status() or test.counts.total() < 100:
|
||||
return
|
||||
summarized = _summarize_failed_tests(test)
|
||||
if not summarized:
|
||||
return
|
||||
stdout.print_with_timestamp(color(summarized))
|
||||
|
||||
# Other methods:
|
||||
|
||||
def bubble_up_test_results(test: Test) -> None:
|
||||
@@ -609,7 +652,7 @@ def bubble_up_test_results(test: Test) -> None:
|
||||
elif test.counts.get_status() == TestStatus.TEST_CRASHED:
|
||||
test.status = TestStatus.TEST_CRASHED
|
||||
|
||||
def parse_test(lines: LineStream, expected_num: int, log: List[str]) -> Test:
|
||||
def parse_test(lines: LineStream, expected_num: int, log: List[str], is_subtest: bool) -> Test:
|
||||
"""
|
||||
Finds next test to parse in LineStream, creates new Test object,
|
||||
parses any subtests of the test, populates Test object with all
|
||||
@@ -627,15 +670,32 @@ def parse_test(lines: LineStream, expected_num: int, log: List[str]) -> Test:
|
||||
1..4
|
||||
[subtests]
|
||||
|
||||
- Subtest header line
|
||||
- Subtest header (must include either the KTAP version line or
|
||||
"# Subtest" header line)
|
||||
|
||||
Example:
|
||||
Example (preferred format with both KTAP version line and
|
||||
"# Subtest" line):
|
||||
|
||||
KTAP version 1
|
||||
# Subtest: name
|
||||
1..3
|
||||
[subtests]
|
||||
ok 1 name
|
||||
|
||||
Example (only "# Subtest" line):
|
||||
|
||||
# Subtest: name
|
||||
1..3
|
||||
[subtests]
|
||||
ok 1 name
|
||||
|
||||
Example (only KTAP version line, compliant with KTAP v1 spec):
|
||||
|
||||
KTAP version 1
|
||||
1..3
|
||||
[subtests]
|
||||
ok 1 name
|
||||
|
||||
- Test result line
|
||||
|
||||
Example:
|
||||
@@ -647,28 +707,29 @@ def parse_test(lines: LineStream, expected_num: int, log: List[str]) -> Test:
|
||||
expected_num - expected test number for test to be parsed
|
||||
log - list of strings containing any preceding diagnostic lines
|
||||
corresponding to the current test
|
||||
is_subtest - boolean indicating whether test is a subtest
|
||||
|
||||
Return:
|
||||
Test object populated with characteristics and any subtests
|
||||
"""
|
||||
test = Test()
|
||||
test.log.extend(log)
|
||||
parent_test = False
|
||||
main = parse_ktap_header(lines, test)
|
||||
if main:
|
||||
# If KTAP/TAP header is found, attempt to parse
|
||||
if not is_subtest:
|
||||
# If parsing the main/top-level test, parse KTAP version line and
|
||||
# test plan
|
||||
test.name = "main"
|
||||
ktap_line = parse_ktap_header(lines, test)
|
||||
parse_test_plan(lines, test)
|
||||
parent_test = True
|
||||
else:
|
||||
# If KTAP/TAP header is not found, test must be subtest
|
||||
# header or test result line so parse attempt to parser
|
||||
# subtest header
|
||||
parent_test = parse_test_header(lines, test)
|
||||
# If not the main test, attempt to parse a test header containing
|
||||
# the KTAP version line and/or subtest header line
|
||||
ktap_line = parse_ktap_header(lines, test)
|
||||
subtest_line = parse_test_header(lines, test)
|
||||
parent_test = (ktap_line or subtest_line)
|
||||
if parent_test:
|
||||
# If subtest header is found, attempt to parse
|
||||
# test plan and print header
|
||||
# If KTAP version line and/or subtest header is found, attempt
|
||||
# to parse test plan and print test header
|
||||
parse_test_plan(lines, test)
|
||||
print_test_header(test)
|
||||
expected_count = test.expected_count
|
||||
@@ -683,7 +744,7 @@ def parse_test(lines: LineStream, expected_num: int, log: List[str]) -> Test:
|
||||
sub_log = parse_diagnostic(lines)
|
||||
sub_test = Test()
|
||||
if not lines or (peek_test_name_match(lines, test) and
|
||||
not main):
|
||||
is_subtest):
|
||||
if expected_count and test_num <= expected_count:
|
||||
# If parser reaches end of test before
|
||||
# parsing expected number of subtests, print
|
||||
@@ -697,20 +758,19 @@ def parse_test(lines: LineStream, expected_num: int, log: List[str]) -> Test:
|
||||
test.log.extend(sub_log)
|
||||
break
|
||||
else:
|
||||
sub_test = parse_test(lines, test_num, sub_log)
|
||||
sub_test = parse_test(lines, test_num, sub_log, True)
|
||||
subtests.append(sub_test)
|
||||
test_num += 1
|
||||
test.subtests = subtests
|
||||
if not main:
|
||||
if is_subtest:
|
||||
# If not main test, look for test result line
|
||||
test.log.extend(parse_diagnostic(lines))
|
||||
if (parent_test and peek_test_name_match(lines, test)) or \
|
||||
not parent_test:
|
||||
parse_test_result(lines, test, expected_num)
|
||||
else:
|
||||
if test.name != "" and not peek_test_name_match(lines, test):
|
||||
test.add_error('missing subtest result line!')
|
||||
else:
|
||||
parse_test_result(lines, test, expected_num)
|
||||
|
||||
# Check for there being no tests
|
||||
# Check for there being no subtests within parent test
|
||||
if parent_test and len(subtests) == 0:
|
||||
# Don't override a bad status if this test had one reported.
|
||||
# Assumption: no subtests means CRASHED is from Test.__init__()
|
||||
@@ -720,11 +780,11 @@ def parse_test(lines: LineStream, expected_num: int, log: List[str]) -> Test:
|
||||
|
||||
# Add statuses to TestCounts attribute in Test object
|
||||
bubble_up_test_results(test)
|
||||
if parent_test and not main:
|
||||
if parent_test and is_subtest:
|
||||
# If test has subtests and is not the main test object, print
|
||||
# footer.
|
||||
print_test_footer(test)
|
||||
elif not main:
|
||||
elif is_subtest:
|
||||
print_test_result(test)
|
||||
return test
|
||||
|
||||
@@ -744,10 +804,10 @@ def parse_run_tests(kernel_output: Iterable[str]) -> Test:
|
||||
test = Test()
|
||||
if not lines:
|
||||
test.name = '<missing>'
|
||||
test.add_error('could not find any KTAP output!')
|
||||
test.add_error('Could not find any KTAP output. Did any KUnit tests run?')
|
||||
test.status = TestStatus.FAILURE_TO_PARSE_TESTS
|
||||
else:
|
||||
test = parse_test(lines, 0, [])
|
||||
test = parse_test(lines, 0, [], False)
|
||||
if test.status != TestStatus.NO_TESTS:
|
||||
test.status = test.counts.get_status()
|
||||
stdout.print_with_timestamp(DIVIDER)
|
||||
|
||||
@@ -80,6 +80,13 @@ class KconfigTest(unittest.TestCase):
|
||||
self.assertEqual(actual_kconfig, expected_kconfig)
|
||||
|
||||
class KUnitParserTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.print_mock = mock.patch('kunit_printer.Printer.print').start()
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
|
||||
def noPrintCallContains(self, substr: str):
|
||||
for call in self.print_mock.mock_calls:
|
||||
self.assertNotIn(substr, call.args[0])
|
||||
|
||||
def assertContains(self, needle: str, haystack: kunit_parser.LineStream):
|
||||
# Clone the iterator so we can print the contents on failure.
|
||||
@@ -133,33 +140,29 @@ class KUnitParserTest(unittest.TestCase):
|
||||
all_passed_log = test_data_path('test_is_test_passed-all_passed.log')
|
||||
with open(all_passed_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.SUCCESS,
|
||||
result.status)
|
||||
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
|
||||
self.assertEqual(result.counts.errors, 0)
|
||||
|
||||
def test_parse_successful_nested_tests_log(self):
|
||||
all_passed_log = test_data_path('test_is_test_passed-all_passed_nested.log')
|
||||
with open(all_passed_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.SUCCESS,
|
||||
result.status)
|
||||
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
|
||||
self.assertEqual(result.counts.errors, 0)
|
||||
|
||||
def test_kselftest_nested(self):
|
||||
kselftest_log = test_data_path('test_is_test_passed-kselftest.log')
|
||||
with open(kselftest_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.SUCCESS,
|
||||
result.status)
|
||||
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
|
||||
self.assertEqual(result.counts.errors, 0)
|
||||
|
||||
def test_parse_failed_test_log(self):
|
||||
failed_log = test_data_path('test_is_test_passed-failure.log')
|
||||
with open(failed_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.FAILURE,
|
||||
result.status)
|
||||
self.assertEqual(kunit_parser.TestStatus.FAILURE, result.status)
|
||||
self.assertEqual(result.counts.errors, 0)
|
||||
|
||||
def test_no_header(self):
|
||||
empty_log = test_data_path('test_is_test_passed-no_tests_run_no_header.log')
|
||||
@@ -167,9 +170,8 @@ class KUnitParserTest(unittest.TestCase):
|
||||
result = kunit_parser.parse_run_tests(
|
||||
kunit_parser.extract_tap_lines(file.readlines()))
|
||||
self.assertEqual(0, len(result.subtests))
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.FAILURE_TO_PARSE_TESTS,
|
||||
result.status)
|
||||
self.assertEqual(kunit_parser.TestStatus.FAILURE_TO_PARSE_TESTS, result.status)
|
||||
self.assertEqual(result.counts.errors, 1)
|
||||
|
||||
def test_missing_test_plan(self):
|
||||
missing_plan_log = test_data_path('test_is_test_passed-'
|
||||
@@ -179,12 +181,8 @@ class KUnitParserTest(unittest.TestCase):
|
||||
kunit_parser.extract_tap_lines(
|
||||
file.readlines()))
|
||||
# A missing test plan is not an error.
|
||||
self.assertEqual(0, result.counts.errors)
|
||||
# All tests should be accounted for.
|
||||
self.assertEqual(10, result.counts.total())
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.SUCCESS,
|
||||
result.status)
|
||||
self.assertEqual(result.counts, kunit_parser.TestCounts(passed=10, errors=0))
|
||||
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
|
||||
|
||||
def test_no_tests(self):
|
||||
header_log = test_data_path('test_is_test_passed-no_tests_run_with_header.log')
|
||||
@@ -192,9 +190,8 @@ class KUnitParserTest(unittest.TestCase):
|
||||
result = kunit_parser.parse_run_tests(
|
||||
kunit_parser.extract_tap_lines(file.readlines()))
|
||||
self.assertEqual(0, len(result.subtests))
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.NO_TESTS,
|
||||
result.status)
|
||||
self.assertEqual(kunit_parser.TestStatus.NO_TESTS, result.status)
|
||||
self.assertEqual(result.counts.errors, 1)
|
||||
|
||||
def test_no_tests_no_plan(self):
|
||||
no_plan_log = test_data_path('test_is_test_passed-no_tests_no_plan.log')
|
||||
@@ -205,7 +202,7 @@ class KUnitParserTest(unittest.TestCase):
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.NO_TESTS,
|
||||
result.subtests[0].subtests[0].status)
|
||||
self.assertEqual(1, result.counts.errors)
|
||||
self.assertEqual(result.counts, kunit_parser.TestCounts(passed=1, errors=1))
|
||||
|
||||
|
||||
def test_no_kunit_output(self):
|
||||
@@ -214,9 +211,10 @@ class KUnitParserTest(unittest.TestCase):
|
||||
with open(crash_log) as file:
|
||||
result = kunit_parser.parse_run_tests(
|
||||
kunit_parser.extract_tap_lines(file.readlines()))
|
||||
print_mock.assert_any_call(StrContains('could not find any KTAP output!'))
|
||||
print_mock.assert_any_call(StrContains('Could not find any KTAP output.'))
|
||||
print_mock.stop()
|
||||
self.assertEqual(0, len(result.subtests))
|
||||
self.assertEqual(result.counts.errors, 1)
|
||||
|
||||
def test_skipped_test(self):
|
||||
skipped_log = test_data_path('test_skip_tests.log')
|
||||
@@ -224,18 +222,16 @@ class KUnitParserTest(unittest.TestCase):
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
|
||||
# A skipped test does not fail the whole suite.
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.SUCCESS,
|
||||
result.status)
|
||||
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
|
||||
self.assertEqual(result.counts, kunit_parser.TestCounts(passed=4, skipped=1))
|
||||
|
||||
def test_skipped_all_tests(self):
|
||||
skipped_log = test_data_path('test_skip_all_tests.log')
|
||||
with open(skipped_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.SKIPPED,
|
||||
result.status)
|
||||
self.assertEqual(kunit_parser.TestStatus.SKIPPED, result.status)
|
||||
self.assertEqual(result.counts, kunit_parser.TestCounts(skipped=5))
|
||||
|
||||
def test_ignores_hyphen(self):
|
||||
hyphen_log = test_data_path('test_strip_hyphen.log')
|
||||
@@ -243,71 +239,112 @@ class KUnitParserTest(unittest.TestCase):
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
|
||||
# A skipped test does not fail the whole suite.
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.SUCCESS,
|
||||
result.status)
|
||||
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
|
||||
self.assertEqual(
|
||||
"sysctl_test",
|
||||
result.subtests[0].name)
|
||||
self.assertEqual(
|
||||
"example",
|
||||
result.subtests[1].name)
|
||||
file.close()
|
||||
|
||||
|
||||
def test_ignores_prefix_printk_time(self):
|
||||
prefix_log = test_data_path('test_config_printk_time.log')
|
||||
with open(prefix_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.SUCCESS,
|
||||
result.status)
|
||||
self.assertEqual('kunit-resource-test', result.subtests[0].name)
|
||||
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
|
||||
self.assertEqual('kunit-resource-test', result.subtests[0].name)
|
||||
self.assertEqual(result.counts.errors, 0)
|
||||
|
||||
def test_ignores_multiple_prefixes(self):
|
||||
prefix_log = test_data_path('test_multiple_prefixes.log')
|
||||
with open(prefix_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.SUCCESS,
|
||||
result.status)
|
||||
self.assertEqual('kunit-resource-test', result.subtests[0].name)
|
||||
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
|
||||
self.assertEqual('kunit-resource-test', result.subtests[0].name)
|
||||
self.assertEqual(result.counts.errors, 0)
|
||||
|
||||
def test_prefix_mixed_kernel_output(self):
|
||||
mixed_prefix_log = test_data_path('test_interrupted_tap_output.log')
|
||||
with open(mixed_prefix_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.SUCCESS,
|
||||
result.status)
|
||||
self.assertEqual('kunit-resource-test', result.subtests[0].name)
|
||||
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
|
||||
self.assertEqual('kunit-resource-test', result.subtests[0].name)
|
||||
self.assertEqual(result.counts.errors, 0)
|
||||
|
||||
def test_prefix_poundsign(self):
|
||||
pound_log = test_data_path('test_pound_sign.log')
|
||||
with open(pound_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.SUCCESS,
|
||||
result.status)
|
||||
self.assertEqual('kunit-resource-test', result.subtests[0].name)
|
||||
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
|
||||
self.assertEqual('kunit-resource-test', result.subtests[0].name)
|
||||
self.assertEqual(result.counts.errors, 0)
|
||||
|
||||
def test_kernel_panic_end(self):
|
||||
panic_log = test_data_path('test_kernel_panic_interrupt.log')
|
||||
with open(panic_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.TEST_CRASHED,
|
||||
result.status)
|
||||
self.assertEqual('kunit-resource-test', result.subtests[0].name)
|
||||
self.assertEqual(kunit_parser.TestStatus.TEST_CRASHED, result.status)
|
||||
self.assertEqual('kunit-resource-test', result.subtests[0].name)
|
||||
self.assertGreaterEqual(result.counts.errors, 1)
|
||||
|
||||
def test_pound_no_prefix(self):
|
||||
pound_log = test_data_path('test_pound_no_prefix.log')
|
||||
with open(pound_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
self.assertEqual(
|
||||
kunit_parser.TestStatus.SUCCESS,
|
||||
result.status)
|
||||
self.assertEqual('kunit-resource-test', result.subtests[0].name)
|
||||
self.assertEqual(kunit_parser.TestStatus.SUCCESS, result.status)
|
||||
self.assertEqual('kunit-resource-test', result.subtests[0].name)
|
||||
self.assertEqual(result.counts.errors, 0)
|
||||
|
||||
def test_summarize_failures(self):
|
||||
output = """
|
||||
KTAP version 1
|
||||
1..2
|
||||
# Subtest: all_failed_suite
|
||||
1..2
|
||||
not ok 1 - test1
|
||||
not ok 2 - test2
|
||||
not ok 1 - all_failed_suite
|
||||
# Subtest: some_failed_suite
|
||||
1..2
|
||||
ok 1 - test1
|
||||
not ok 2 - test2
|
||||
not ok 1 - some_failed_suite
|
||||
"""
|
||||
result = kunit_parser.parse_run_tests(output.splitlines())
|
||||
self.assertEqual(kunit_parser.TestStatus.FAILURE, result.status)
|
||||
|
||||
self.assertEqual(kunit_parser._summarize_failed_tests(result),
|
||||
'Failures: all_failed_suite, some_failed_suite.test2')
|
||||
|
||||
def test_ktap_format(self):
|
||||
ktap_log = test_data_path('test_parse_ktap_output.log')
|
||||
with open(ktap_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
self.assertEqual(result.counts, kunit_parser.TestCounts(passed=3))
|
||||
self.assertEqual('suite', result.subtests[0].name)
|
||||
self.assertEqual('case_1', result.subtests[0].subtests[0].name)
|
||||
self.assertEqual('case_2', result.subtests[0].subtests[1].name)
|
||||
|
||||
def test_parse_subtest_header(self):
|
||||
ktap_log = test_data_path('test_parse_subtest_header.log')
|
||||
with open(ktap_log) as file:
|
||||
result = kunit_parser.parse_run_tests(file.readlines())
|
||||
self.print_mock.assert_any_call(StrContains('suite (1 subtest)'))
|
||||
|
||||
def test_show_test_output_on_failure(self):
|
||||
output = """
|
||||
KTAP version 1
|
||||
1..1
|
||||
Test output.
|
||||
Indented more.
|
||||
not ok 1 test1
|
||||
"""
|
||||
result = kunit_parser.parse_run_tests(output.splitlines())
|
||||
self.assertEqual(kunit_parser.TestStatus.FAILURE, result.status)
|
||||
|
||||
self.print_mock.assert_any_call(StrContains('Test output.'))
|
||||
self.print_mock.assert_any_call(StrContains(' Indented more.'))
|
||||
self.noPrintCallContains('not ok 1 test1')
|
||||
|
||||
def line_stream_from_strs(strs: Iterable[str]) -> kunit_parser.LineStream:
|
||||
return kunit_parser.LineStream(enumerate(strs, start=1))
|
||||
@@ -485,6 +522,9 @@ class LinuxSourceTreeTest(unittest.TestCase):
|
||||
|
||||
|
||||
class KUnitJsonTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
self.print_mock = mock.patch('kunit_printer.Printer.print').start()
|
||||
self.addCleanup(mock.patch.stopall)
|
||||
|
||||
def _json_for(self, log_file):
|
||||
with open(test_data_path(log_file)) as file:
|
||||
@@ -581,7 +621,7 @@ class KUnitMainTest(unittest.TestCase):
|
||||
self.assertEqual(e.exception.code, 1)
|
||||
self.assertEqual(self.linux_source_mock.build_reconfig.call_count, 1)
|
||||
self.assertEqual(self.linux_source_mock.run_kernel.call_count, 1)
|
||||
self.print_mock.assert_any_call(StrContains('could not find any KTAP output!'))
|
||||
self.print_mock.assert_any_call(StrContains('Could not find any KTAP output.'))
|
||||
|
||||
def test_exec_no_tests(self):
|
||||
self.linux_source_mock.run_kernel = mock.Mock(return_value=['TAP version 14', '1..0'])
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
KTAP version 1
|
||||
1..1
|
||||
KTAP version 1
|
||||
1..3
|
||||
ok 1 case_1
|
||||
ok 2 case_2
|
||||
ok 3 case_3
|
||||
ok 1 suite
|
||||
@@ -0,0 +1,7 @@
|
||||
KTAP version 1
|
||||
1..1
|
||||
KTAP version 1
|
||||
# Subtest: suite
|
||||
1..1
|
||||
ok 1 test
|
||||
ok 1 suite
|
||||
@@ -4,6 +4,15 @@
|
||||
# No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
|
||||
all:
|
||||
|
||||
TEST_PROGS := amd-pstate-ut.sh
|
||||
uname_M := $(shell uname -m 2>/dev/null || echo not)
|
||||
ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/x86/ -e s/x86_64/x86/)
|
||||
|
||||
ifeq (x86,$(ARCH))
|
||||
TEST_GEN_FILES += ../../../power/x86/amd_pstate_tracer/amd_pstate_trace.py
|
||||
TEST_GEN_FILES += ../../../power/x86/intel_pstate_tracer/intel_pstate_tracer.py
|
||||
endif
|
||||
|
||||
TEST_PROGS := run.sh
|
||||
TEST_FILES := basic.sh tbench.sh gitsource.sh
|
||||
|
||||
include ../lib.mk
|
||||
|
||||
@@ -1,56 +0,0 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
# amd-pstate-ut is a test module for testing the amd-pstate driver.
|
||||
# It can only run on x86 architectures and current cpufreq driver
|
||||
# must be amd-pstate.
|
||||
# (1) It can help all users to verify their processor support
|
||||
# (SBIOS/Firmware or Hardware).
|
||||
# (2) Kernel can have a basic function test to avoid the kernel
|
||||
# regression during the update.
|
||||
# (3) We can introduce more functional or performance tests to align
|
||||
# the result together, it will benefit power and performance scale optimization.
|
||||
|
||||
# Kselftest framework requirement - SKIP code is 4.
|
||||
ksft_skip=4
|
||||
|
||||
# amd-pstate-ut only run on x86/x86_64 AMD systems.
|
||||
ARCH=$(uname -m 2>/dev/null | sed -e 's/i.86/x86/' -e 's/x86_64/x86/')
|
||||
VENDOR=$(cat /proc/cpuinfo | grep -m 1 'vendor_id' | awk '{print $NF}')
|
||||
|
||||
if ! echo "$ARCH" | grep -q x86; then
|
||||
echo "$0 # Skipped: Test can only run on x86 architectures."
|
||||
exit $ksft_skip
|
||||
fi
|
||||
|
||||
if ! echo "$VENDOR" | grep -iq amd; then
|
||||
echo "$0 # Skipped: Test can only run on AMD CPU."
|
||||
echo "$0 # Current cpu vendor is $VENDOR."
|
||||
exit $ksft_skip
|
||||
fi
|
||||
|
||||
scaling_driver=$(cat /sys/devices/system/cpu/cpufreq/policy0/scaling_driver)
|
||||
if [ "$scaling_driver" != "amd-pstate" ]; then
|
||||
echo "$0 # Skipped: Test can only run on amd-pstate driver."
|
||||
echo "$0 # Please set X86_AMD_PSTATE enabled."
|
||||
echo "$0 # Current cpufreq scaling drvier is $scaling_driver."
|
||||
exit $ksft_skip
|
||||
fi
|
||||
|
||||
msg="Skip all tests:"
|
||||
if [ ! -w /dev ]; then
|
||||
echo $msg please run this as root >&2
|
||||
exit $ksft_skip
|
||||
fi
|
||||
|
||||
if ! /sbin/modprobe -q -n amd-pstate-ut; then
|
||||
echo "amd-pstate-ut: module amd-pstate-ut is not found [SKIP]"
|
||||
exit $ksft_skip
|
||||
fi
|
||||
if /sbin/modprobe -q amd-pstate-ut; then
|
||||
/sbin/modprobe -q -r amd-pstate-ut
|
||||
echo "amd-pstate-ut: ok"
|
||||
else
|
||||
echo "amd-pstate-ut: [FAIL]"
|
||||
exit 1
|
||||
fi
|
||||
Executable
+38
@@ -0,0 +1,38 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
# amd-pstate-ut is a test module for testing the amd-pstate driver.
|
||||
# It can only run on x86 architectures and current cpufreq driver
|
||||
# must be amd-pstate.
|
||||
# (1) It can help all users to verify their processor support
|
||||
# (SBIOS/Firmware or Hardware).
|
||||
# (2) Kernel can have a basic function test to avoid the kernel
|
||||
# regression during the update.
|
||||
# (3) We can introduce more functional or performance tests to align
|
||||
# the result together, it will benefit power and performance scale optimization.
|
||||
|
||||
# protect against multiple inclusion
|
||||
if [ $FILE_BASIC ]; then
|
||||
return 0
|
||||
else
|
||||
FILE_BASIC=DONE
|
||||
fi
|
||||
|
||||
amd_pstate_basic()
|
||||
{
|
||||
printf "\n---------------------------------------------\n"
|
||||
printf "*** Running AMD P-state ut ***"
|
||||
printf "\n---------------------------------------------\n"
|
||||
|
||||
if ! /sbin/modprobe -q -n amd-pstate-ut; then
|
||||
echo "amd-pstate-ut: module amd-pstate-ut is not found [SKIP]"
|
||||
exit $ksft_skip
|
||||
fi
|
||||
if /sbin/modprobe -q amd-pstate-ut; then
|
||||
/sbin/modprobe -q -r amd-pstate-ut
|
||||
echo "amd-pstate-basic: ok"
|
||||
else
|
||||
echo "amd-pstate-basic: [FAIL]"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
+354
@@ -0,0 +1,354 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
# Testing and monitor the cpu desire performance, frequency, load,
|
||||
# power consumption and throughput etc. when this script trigger
|
||||
# gitsource test.
|
||||
# 1) Download and tar gitsource codes.
|
||||
# 2) Run gitsource benchmark on specific governors, ondemand or schedutil.
|
||||
# 3) Run tbench benchmark comparative test on acpi-cpufreq kernel driver.
|
||||
# 4) Get desire performance, frequency, load by perf.
|
||||
# 5) Get power consumption and throughput by amd_pstate_trace.py.
|
||||
# 6) Get run time by /usr/bin/time.
|
||||
# 7) Analyse test results and save it in file selftest.gitsource.csv.
|
||||
#8) Plot png images about time, energy and performance per watt for each test.
|
||||
|
||||
# protect against multiple inclusion
|
||||
if [ $FILE_GITSOURCE ]; then
|
||||
return 0
|
||||
else
|
||||
FILE_GITSOURCE=DONE
|
||||
fi
|
||||
|
||||
git_name="git-2.15.1"
|
||||
git_tar="$git_name.tar.gz"
|
||||
gitsource_url="https://github.com/git/git/archive/refs/tags/v2.15.1.tar.gz"
|
||||
gitsource_governors=("ondemand" "schedutil")
|
||||
|
||||
# $1: governor, $2: round, $3: des-perf, $4: freq, $5: load, $6: time $7: energy, $8: PPW
|
||||
store_csv_gitsource()
|
||||
{
|
||||
echo "$1, $2, $3, $4, $5, $6, $7, $8" | tee -a $OUTFILE_GIT.csv > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# clear some special lines
|
||||
clear_csv_gitsource()
|
||||
{
|
||||
if [ -f $OUTFILE_GIT.csv ]; then
|
||||
sed -i '/Comprison(%)/d' $OUTFILE_GIT.csv
|
||||
sed -i "/$(scaling_name)/d" $OUTFILE_GIT.csv
|
||||
fi
|
||||
}
|
||||
|
||||
# find string $1 in file csv and get the number of lines
|
||||
get_lines_csv_gitsource()
|
||||
{
|
||||
if [ -f $OUTFILE_GIT.csv ]; then
|
||||
return `grep -c "$1" $OUTFILE_GIT.csv`
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
pre_clear_gitsource()
|
||||
{
|
||||
post_clear_gitsource
|
||||
rm -rf gitsource_*.png
|
||||
clear_csv_gitsource
|
||||
}
|
||||
|
||||
post_clear_gitsource()
|
||||
{
|
||||
rm -rf results/tracer-gitsource*
|
||||
rm -rf $OUTFILE_GIT*.log
|
||||
rm -rf $OUTFILE_GIT*.result
|
||||
}
|
||||
|
||||
install_gitsource()
|
||||
{
|
||||
if [ ! -d $git_name ]; then
|
||||
printf "Download gitsource, please wait a moment ...\n\n"
|
||||
wget -O $git_tar $gitsource_url > /dev/null 2>&1
|
||||
|
||||
printf "Tar gitsource ...\n\n"
|
||||
tar -xzf $git_tar
|
||||
fi
|
||||
}
|
||||
|
||||
# $1: governor, $2: loop
|
||||
run_gitsource()
|
||||
{
|
||||
echo "Launching amd pstate tracer for $1 #$2 tracer_interval: $TRACER_INTERVAL"
|
||||
./amd_pstate_trace.py -n tracer-gitsource-$1-$2 -i $TRACER_INTERVAL > /dev/null 2>&1 &
|
||||
|
||||
printf "Make and test gitsource for $1 #$2 make_cpus: $MAKE_CPUS\n"
|
||||
cd $git_name
|
||||
perf stat -a --per-socket -I 1000 -e power/energy-pkg/ /usr/bin/time -o ../$OUTFILE_GIT.time-gitsource-$1-$2.log make test -j$MAKE_CPUS > ../$OUTFILE_GIT-perf-$1-$2.log 2>&1
|
||||
cd ..
|
||||
|
||||
for job in `jobs -p`
|
||||
do
|
||||
echo "Waiting for job id $job"
|
||||
wait $job
|
||||
done
|
||||
}
|
||||
|
||||
# $1: governor, $2: loop
|
||||
parse_gitsource()
|
||||
{
|
||||
awk '{print $5}' results/tracer-gitsource-$1-$2/cpu.csv | sed -e '1d' | sed s/,// > $OUTFILE_GIT-des-perf-$1-$2.log
|
||||
avg_des_perf=$(awk 'BEGIN {i=0; sum=0};{i++; sum += $1};END {print sum/i}' $OUTFILE_GIT-des-perf-$1-$2.log)
|
||||
printf "Gitsource-$1-#$2 avg des perf: $avg_des_perf\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
awk '{print $7}' results/tracer-gitsource-$1-$2/cpu.csv | sed -e '1d' | sed s/,// > $OUTFILE_GIT-freq-$1-$2.log
|
||||
avg_freq=$(awk 'BEGIN {i=0; sum=0};{i++; sum += $1};END {print sum/i}' $OUTFILE_GIT-freq-$1-$2.log)
|
||||
printf "Gitsource-$1-#$2 avg freq: $avg_freq\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
awk '{print $11}' results/tracer-gitsource-$1-$2/cpu.csv | sed -e '1d' | sed s/,// > $OUTFILE_GIT-load-$1-$2.log
|
||||
avg_load=$(awk 'BEGIN {i=0; sum=0};{i++; sum += $1};END {print sum/i}' $OUTFILE_GIT-load-$1-$2.log)
|
||||
printf "Gitsource-$1-#$2 avg load: $avg_load\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
grep user $OUTFILE_GIT.time-gitsource-$1-$2.log | awk '{print $1}' | sed -e 's/user//' > $OUTFILE_GIT-time-$1-$2.log
|
||||
time_sum=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum}' $OUTFILE_GIT-time-$1-$2.log)
|
||||
printf "Gitsource-$1-#$2 user time(s): $time_sum\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
grep Joules $OUTFILE_GIT-perf-$1-$2.log | awk '{print $4}' > $OUTFILE_GIT-energy-$1-$2.log
|
||||
en_sum=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum}' $OUTFILE_GIT-energy-$1-$2.log)
|
||||
printf "Gitsource-$1-#$2 power consumption(J): $en_sum\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
# Permance is the number of run gitsource per second, denoted 1/t, where 1 is the number of run gitsource in t
|
||||
# senconds. It is well known that P=E/t, where P is power measured in watts(W), E is energy measured in joules(J),
|
||||
# and t is time measured in seconds(s). This means that performance per watt becomes
|
||||
# 1/t 1/t 1
|
||||
# ----- = ----- = ---
|
||||
# P E/t E
|
||||
# with unit given by 1 per joule.
|
||||
ppw=`echo "scale=9;1/$en_sum" | bc | awk '{printf "%.9f", $0}'`
|
||||
printf "Gitsource-$1-#$2 performance per watt(1/J): $ppw\n" | tee -a $OUTFILE_GIT.result
|
||||
printf "\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
driver_name=`echo $(scaling_name)`
|
||||
store_csv_gitsource "$driver_name-$1" $2 $avg_des_perf $avg_freq $avg_load $time_sum $en_sum $ppw
|
||||
}
|
||||
|
||||
# $1: governor
|
||||
loop_gitsource()
|
||||
{
|
||||
printf "\nGitsource total test times is $LOOP_TIMES for $1\n\n"
|
||||
for i in `seq 1 $LOOP_TIMES`
|
||||
do
|
||||
run_gitsource $1 $i
|
||||
parse_gitsource $1 $i
|
||||
done
|
||||
}
|
||||
|
||||
# $1: governor
|
||||
gather_gitsource()
|
||||
{
|
||||
printf "Gitsource test result for $1 (loops:$LOOP_TIMES)" | tee -a $OUTFILE_GIT.result
|
||||
printf "\n--------------------------------------------------\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
grep "Gitsource-$1-#" $OUTFILE_GIT.result | grep "avg des perf:" | awk '{print $NF}' > $OUTFILE_GIT-des-perf-$1.log
|
||||
avg_des_perf=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum/'$LOOP_TIMES'}' $OUTFILE_GIT-des-perf-$1.log)
|
||||
printf "Gitsource-$1 avg des perf: $avg_des_perf\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
grep "Gitsource-$1-#" $OUTFILE_GIT.result | grep "avg freq:" | awk '{print $NF}' > $OUTFILE_GIT-freq-$1.log
|
||||
avg_freq=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum/'$LOOP_TIMES'}' $OUTFILE_GIT-freq-$1.log)
|
||||
printf "Gitsource-$1 avg freq: $avg_freq\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
grep "Gitsource-$1-#" $OUTFILE_GIT.result | grep "avg load:" | awk '{print $NF}' > $OUTFILE_GIT-load-$1.log
|
||||
avg_load=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum/'$LOOP_TIMES'}' $OUTFILE_GIT-load-$1.log)
|
||||
printf "Gitsource-$1 avg load: $avg_load\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
grep "Gitsource-$1-#" $OUTFILE_GIT.result | grep "user time(s):" | awk '{print $NF}' > $OUTFILE_GIT-time-$1.log
|
||||
time_sum=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum}' $OUTFILE_GIT-time-$1.log)
|
||||
printf "Gitsource-$1 total user time(s): $time_sum\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
avg_time=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum/'$LOOP_TIMES'}' $OUTFILE_GIT-time-$1.log)
|
||||
printf "Gitsource-$1 avg user times(s): $avg_time\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
grep "Gitsource-$1-#" $OUTFILE_GIT.result | grep "power consumption(J):" | awk '{print $NF}' > $OUTFILE_GIT-energy-$1.log
|
||||
en_sum=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum}' $OUTFILE_GIT-energy-$1.log)
|
||||
printf "Gitsource-$1 total power consumption(J): $en_sum\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
avg_en=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum/'$LOOP_TIMES'}' $OUTFILE_GIT-energy-$1.log)
|
||||
printf "Gitsource-$1 avg power consumption(J): $avg_en\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
# Permance is the number of run gitsource per second, denoted 1/t, where 1 is the number of run gitsource in t
|
||||
# senconds. It is well known that P=E/t, where P is power measured in watts(W), E is energy measured in joules(J),
|
||||
# and t is time measured in seconds(s). This means that performance per watt becomes
|
||||
# 1/t 1/t 1
|
||||
# ----- = ----- = ---
|
||||
# P E/t E
|
||||
# with unit given by 1 per joule.
|
||||
ppw=`echo "scale=9;1/$avg_en" | bc | awk '{printf "%.9f", $0}'`
|
||||
printf "Gitsource-$1 performance per watt(1/J): $ppw\n" | tee -a $OUTFILE_GIT.result
|
||||
printf "\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
driver_name=`echo $(scaling_name)`
|
||||
store_csv_gitsource "$driver_name-$1" "Average" $avg_des_perf $avg_freq $avg_load $avg_time $avg_en $ppw
|
||||
}
|
||||
|
||||
# $1: base scaling_driver $2: base governor $3: comparison scaling_driver $4: comparison governor
|
||||
__calc_comp_gitsource()
|
||||
{
|
||||
base=`grep "$1-$2" $OUTFILE_GIT.csv | grep "Average"`
|
||||
comp=`grep "$3-$4" $OUTFILE_GIT.csv | grep "Average"`
|
||||
|
||||
if [ -n "$base" -a -n "$comp" ]; then
|
||||
printf "\n==================================================\n" | tee -a $OUTFILE_GIT.result
|
||||
printf "Gitsource comparison $1-$2 VS $3-$4" | tee -a $OUTFILE_GIT.result
|
||||
printf "\n==================================================\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
# get the base values
|
||||
des_perf_base=`echo "$base" | awk '{print $3}' | sed s/,//`
|
||||
freq_base=`echo "$base" | awk '{print $4}' | sed s/,//`
|
||||
load_base=`echo "$base" | awk '{print $5}' | sed s/,//`
|
||||
time_base=`echo "$base" | awk '{print $6}' | sed s/,//`
|
||||
energy_base=`echo "$base" | awk '{print $7}' | sed s/,//`
|
||||
ppw_base=`echo "$base" | awk '{print $8}' | sed s/,//`
|
||||
|
||||
# get the comparison values
|
||||
des_perf_comp=`echo "$comp" | awk '{print $3}' | sed s/,//`
|
||||
freq_comp=`echo "$comp" | awk '{print $4}' | sed s/,//`
|
||||
load_comp=`echo "$comp" | awk '{print $5}' | sed s/,//`
|
||||
time_comp=`echo "$comp" | awk '{print $6}' | sed s/,//`
|
||||
energy_comp=`echo "$comp" | awk '{print $7}' | sed s/,//`
|
||||
ppw_comp=`echo "$comp" | awk '{print $8}' | sed s/,//`
|
||||
|
||||
# compare the base and comp values
|
||||
des_perf_drop=`echo "scale=4;($des_perf_comp-$des_perf_base)*100/$des_perf_base" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Gitsource-$1 des perf base: $des_perf_base comprison: $des_perf_comp percent: $des_perf_drop\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
freq_drop=`echo "scale=4;($freq_comp-$freq_base)*100/$freq_base" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Gitsource-$1 freq base: $freq_base comprison: $freq_comp percent: $freq_drop\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
load_drop=`echo "scale=4;($load_comp-$load_base)*100/$load_base" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Gitsource-$1 load base: $load_base comprison: $load_comp percent: $load_drop\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
time_drop=`echo "scale=4;($time_comp-$time_base)*100/$time_base" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Gitsource-$1 time base: $time_base comprison: $time_comp percent: $time_drop\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
energy_drop=`echo "scale=4;($energy_comp-$energy_base)*100/$energy_base" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Gitsource-$1 energy base: $energy_base comprison: $energy_comp percent: $energy_drop\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
ppw_drop=`echo "scale=4;($ppw_comp-$ppw_base)*100/$ppw_base" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Gitsource-$1 performance per watt base: $ppw_base comprison: $ppw_comp percent: $ppw_drop\n" | tee -a $OUTFILE_GIT.result
|
||||
printf "\n" | tee -a $OUTFILE_GIT.result
|
||||
|
||||
store_csv_gitsource "$1-$2 VS $3-$4" "Comprison(%)" "$des_perf_drop" "$freq_drop" "$load_drop" "$time_drop" "$energy_drop" "$ppw_drop"
|
||||
fi
|
||||
}
|
||||
|
||||
# calculate the comparison(%)
|
||||
calc_comp_gitsource()
|
||||
{
|
||||
# acpi-cpufreq-ondemand VS acpi-cpufreq-schedutil
|
||||
__calc_comp_gitsource ${all_scaling_names[0]} ${gitsource_governors[0]} ${all_scaling_names[0]} ${gitsource_governors[1]}
|
||||
|
||||
# amd-pstate-ondemand VS amd-pstate-schedutil
|
||||
__calc_comp_gitsource ${all_scaling_names[1]} ${gitsource_governors[0]} ${all_scaling_names[1]} ${gitsource_governors[1]}
|
||||
|
||||
# acpi-cpufreq-ondemand VS amd-pstate-ondemand
|
||||
__calc_comp_gitsource ${all_scaling_names[0]} ${gitsource_governors[0]} ${all_scaling_names[1]} ${gitsource_governors[0]}
|
||||
|
||||
# acpi-cpufreq-schedutil VS amd-pstate-schedutil
|
||||
__calc_comp_gitsource ${all_scaling_names[0]} ${gitsource_governors[1]} ${all_scaling_names[1]} ${gitsource_governors[1]}
|
||||
}
|
||||
|
||||
# $1: file_name, $2: title, $3: ylable, $4: column
|
||||
plot_png_gitsource()
|
||||
{
|
||||
# all_scaling_names[1] all_scaling_names[0] flag
|
||||
# amd-pstate acpi-cpufreq
|
||||
# N N 0
|
||||
# N Y 1
|
||||
# Y N 2
|
||||
# Y Y 3
|
||||
ret=`grep -c "${all_scaling_names[1]}" $OUTFILE_GIT.csv`
|
||||
if [ $ret -eq 0 ]; then
|
||||
ret=`grep -c "${all_scaling_names[0]}" $OUTFILE_GIT.csv`
|
||||
if [ $ret -eq 0 ]; then
|
||||
flag=0
|
||||
else
|
||||
flag=1
|
||||
fi
|
||||
else
|
||||
ret=`grep -c "${all_scaling_names[0]}" $OUTFILE_GIT.csv`
|
||||
if [ $ret -eq 0 ]; then
|
||||
flag=2
|
||||
else
|
||||
flag=3
|
||||
fi
|
||||
fi
|
||||
|
||||
gnuplot << EOF
|
||||
set term png
|
||||
set output "$1"
|
||||
|
||||
set title "$2"
|
||||
set xlabel "Test Cycles (round)"
|
||||
set ylabel "$3"
|
||||
|
||||
set grid
|
||||
set style data histogram
|
||||
set style fill solid 0.5 border
|
||||
set boxwidth 0.8
|
||||
|
||||
if ($flag == 1) {
|
||||
plot \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[0]}-${gitsource_governors[0]}/p' $OUTFILE_GIT.csv)" using $4:xtic(2) title "${all_scaling_names[0]}-${gitsource_governors[0]}", \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[0]}-${gitsource_governors[1]}/p' $OUTFILE_GIT.csv)" using $4:xtic(2) title "${all_scaling_names[0]}-${gitsource_governors[1]}"
|
||||
} else {
|
||||
if ($flag == 2) {
|
||||
plot \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[1]}-${gitsource_governors[0]}/p' $OUTFILE_GIT.csv)" using $4:xtic(2) title "${all_scaling_names[1]}-${gitsource_governors[0]}", \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[1]}-${gitsource_governors[1]}/p' $OUTFILE_GIT.csv)" using $4:xtic(2) title "${all_scaling_names[1]}-${gitsource_governors[1]}"
|
||||
} else {
|
||||
if ($flag == 3 ) {
|
||||
plot \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[0]}-${gitsource_governors[0]}/p' $OUTFILE_GIT.csv)" using $4:xtic(2) title "${all_scaling_names[0]}-${gitsource_governors[0]}", \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[0]}-${gitsource_governors[1]}/p' $OUTFILE_GIT.csv)" using $4:xtic(2) title "${all_scaling_names[0]}-${gitsource_governors[1]}", \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[1]}-${gitsource_governors[0]}/p' $OUTFILE_GIT.csv)" using $4:xtic(2) title "${all_scaling_names[1]}-${gitsource_governors[0]}", \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[1]}-${gitsource_governors[1]}/p' $OUTFILE_GIT.csv)" using $4:xtic(2) title "${all_scaling_names[1]}-${gitsource_governors[1]}"
|
||||
}
|
||||
}
|
||||
}
|
||||
quit
|
||||
EOF
|
||||
}
|
||||
|
||||
amd_pstate_gitsource()
|
||||
{
|
||||
printf "\n---------------------------------------------\n"
|
||||
printf "*** Running gitsource ***"
|
||||
printf "\n---------------------------------------------\n"
|
||||
|
||||
pre_clear_gitsource
|
||||
|
||||
install_gitsource
|
||||
|
||||
get_lines_csv_gitsource "Governor"
|
||||
if [ $? -eq 0 ]; then
|
||||
# add titles and unit for csv file
|
||||
store_csv_gitsource "Governor" "Round" "Des-perf" "Freq" "Load" "Time" "Energy" "Performance Per Watt"
|
||||
store_csv_gitsource "Unit" "" "" "GHz" "" "s" "J" "1/J"
|
||||
fi
|
||||
|
||||
backup_governor
|
||||
for governor in ${gitsource_governors[*]} ; do
|
||||
printf "\nSpecified governor is $governor\n\n"
|
||||
switch_governor $governor
|
||||
loop_gitsource $governor
|
||||
gather_gitsource $governor
|
||||
done
|
||||
restore_governor
|
||||
|
||||
plot_png_gitsource "gitsource_time.png" "Gitsource Benchmark Time" "Time (s)" 6
|
||||
plot_png_gitsource "gitsource_energy.png" "Gitsource Benchmark Energy" "Energy (J)" 7
|
||||
plot_png_gitsource "gitsource_ppw.png" "Gitsource Benchmark Performance Per Watt" "Performance Per Watt (1/J)" 8
|
||||
|
||||
calc_comp_gitsource
|
||||
|
||||
post_clear_gitsource
|
||||
}
|
||||
Executable
+387
@@ -0,0 +1,387 @@
|
||||
#!/bin/bash
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
# protect against multiple inclusion
|
||||
if [ $FILE_MAIN ]; then
|
||||
return 0
|
||||
else
|
||||
FILE_MAIN=DONE
|
||||
fi
|
||||
|
||||
source basic.sh
|
||||
source tbench.sh
|
||||
source gitsource.sh
|
||||
|
||||
# amd-pstate-ut only run on x86/x86_64 AMD systems.
|
||||
ARCH=$(uname -m 2>/dev/null | sed -e 's/i.86/x86/' -e 's/x86_64/x86/')
|
||||
VENDOR=$(cat /proc/cpuinfo | grep -m 1 'vendor_id' | awk '{print $NF}')
|
||||
|
||||
msg="Skip all tests:"
|
||||
FUNC=all
|
||||
OUTFILE=selftest
|
||||
OUTFILE_TBENCH="$OUTFILE.tbench"
|
||||
OUTFILE_GIT="$OUTFILE.gitsource"
|
||||
|
||||
SYSFS=
|
||||
CPUROOT=
|
||||
CPUFREQROOT=
|
||||
MAKE_CPUS=
|
||||
|
||||
TIME_LIMIT=100
|
||||
PROCESS_NUM=128
|
||||
LOOP_TIMES=3
|
||||
TRACER_INTERVAL=10
|
||||
CURRENT_TEST=amd-pstate
|
||||
COMPARATIVE_TEST=
|
||||
|
||||
# Kselftest framework requirement - SKIP code is 4.
|
||||
ksft_skip=4
|
||||
all_scaling_names=("acpi-cpufreq" "amd-pstate")
|
||||
|
||||
# Get current cpufreq scaling driver name
|
||||
scaling_name()
|
||||
{
|
||||
if [ "$COMPARATIVE_TEST" = "" ]; then
|
||||
echo "$CURRENT_TEST"
|
||||
else
|
||||
echo "$COMPARATIVE_TEST"
|
||||
fi
|
||||
}
|
||||
|
||||
# Counts CPUs with cpufreq directories
|
||||
count_cpus()
|
||||
{
|
||||
count=0;
|
||||
|
||||
for cpu in `ls $CPUROOT | grep "cpu[0-9].*"`; do
|
||||
if [ -d $CPUROOT/$cpu/cpufreq ]; then
|
||||
let count=count+1;
|
||||
fi
|
||||
done
|
||||
|
||||
echo $count;
|
||||
}
|
||||
|
||||
# $1: policy
|
||||
find_current_governor()
|
||||
{
|
||||
cat $CPUFREQROOT/$1/scaling_governor
|
||||
}
|
||||
|
||||
backup_governor()
|
||||
{
|
||||
policies=$(ls $CPUFREQROOT| grep "policy[0-9].*")
|
||||
for policy in $policies; do
|
||||
cur_gov=$(find_current_governor $policy)
|
||||
echo "$policy $cur_gov" >> $OUTFILE.backup_governor.log
|
||||
done
|
||||
|
||||
printf "Governor $cur_gov backup done.\n"
|
||||
}
|
||||
|
||||
restore_governor()
|
||||
{
|
||||
i=0;
|
||||
|
||||
policies=$(awk '{print $1}' $OUTFILE.backup_governor.log)
|
||||
for policy in $policies; do
|
||||
let i++;
|
||||
governor=$(sed -n ''$i'p' $OUTFILE.backup_governor.log | awk '{print $2}')
|
||||
|
||||
# switch governor
|
||||
echo $governor > $CPUFREQROOT/$policy/scaling_governor
|
||||
done
|
||||
|
||||
printf "Governor restored to $governor.\n"
|
||||
}
|
||||
|
||||
# $1: governor
|
||||
switch_governor()
|
||||
{
|
||||
policies=$(ls $CPUFREQROOT| grep "policy[0-9].*")
|
||||
for policy in $policies; do
|
||||
filepath=$CPUFREQROOT/$policy/scaling_available_governors
|
||||
|
||||
# Exit if cpu isn't managed by cpufreq core
|
||||
if [ ! -f $filepath ]; then
|
||||
return;
|
||||
fi
|
||||
|
||||
echo $1 > $CPUFREQROOT/$policy/scaling_governor
|
||||
done
|
||||
|
||||
printf "Switched governor to $1.\n"
|
||||
}
|
||||
|
||||
# All amd-pstate tests
|
||||
amd_pstate_all()
|
||||
{
|
||||
printf "\n=============================================\n"
|
||||
printf "***** Running AMD P-state Sanity Tests *****\n"
|
||||
printf "=============================================\n\n"
|
||||
|
||||
count=$(count_cpus)
|
||||
if [ $count = 0 ]; then
|
||||
printf "No cpu is managed by cpufreq core, exiting\n"
|
||||
exit;
|
||||
else
|
||||
printf "AMD P-state manages: $count CPUs\n"
|
||||
fi
|
||||
|
||||
# unit test for amd-pstate kernel driver
|
||||
amd_pstate_basic
|
||||
|
||||
# tbench
|
||||
amd_pstate_tbench
|
||||
|
||||
# gitsource
|
||||
amd_pstate_gitsource
|
||||
}
|
||||
|
||||
help()
|
||||
{
|
||||
printf "Usage: $0 [OPTION...]
|
||||
[-h <help>]
|
||||
[-o <output-file-for-dump>]
|
||||
[-c <all: All testing,
|
||||
basic: Basic testing,
|
||||
tbench: Tbench testing,
|
||||
gitsource: Gitsource testing.>]
|
||||
[-t <tbench time limit>]
|
||||
[-p <tbench process number>]
|
||||
[-l <loop times for tbench>]
|
||||
[-i <amd tracer interval>]
|
||||
[-m <comparative test: acpi-cpufreq>]
|
||||
\n"
|
||||
exit 2
|
||||
}
|
||||
|
||||
parse_arguments()
|
||||
{
|
||||
while getopts ho:c:t:p:l:i:m: arg
|
||||
do
|
||||
case $arg in
|
||||
h) # --help
|
||||
help
|
||||
;;
|
||||
|
||||
c) # --func_type (Function to perform: basic, tbench, gitsource (default: all))
|
||||
FUNC=$OPTARG
|
||||
;;
|
||||
|
||||
o) # --output-file (Output file to store dumps)
|
||||
OUTFILE=$OPTARG
|
||||
;;
|
||||
|
||||
t) # --tbench-time-limit
|
||||
TIME_LIMIT=$OPTARG
|
||||
;;
|
||||
|
||||
p) # --tbench-process-number
|
||||
PROCESS_NUM=$OPTARG
|
||||
;;
|
||||
|
||||
l) # --tbench/gitsource-loop-times
|
||||
LOOP_TIMES=$OPTARG
|
||||
;;
|
||||
|
||||
i) # --amd-tracer-interval
|
||||
TRACER_INTERVAL=$OPTARG
|
||||
;;
|
||||
|
||||
m) # --comparative-test
|
||||
COMPARATIVE_TEST=$OPTARG
|
||||
;;
|
||||
|
||||
*)
|
||||
help
|
||||
;;
|
||||
esac
|
||||
done
|
||||
}
|
||||
|
||||
command_perf()
|
||||
{
|
||||
if ! command -v perf > /dev/null; then
|
||||
echo $msg please install perf. >&2
|
||||
exit $ksft_skip
|
||||
fi
|
||||
}
|
||||
|
||||
command_tbench()
|
||||
{
|
||||
if ! command -v tbench > /dev/null; then
|
||||
if apt policy dbench > /dev/null 2>&1; then
|
||||
echo $msg apt install dbench >&2
|
||||
exit $ksft_skip
|
||||
elif yum list available | grep dbench > /dev/null 2>&1; then
|
||||
echo $msg yum install dbench >&2
|
||||
exit $ksft_skip
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! command -v tbench > /dev/null; then
|
||||
echo $msg please install tbench. >&2
|
||||
exit $ksft_skip
|
||||
fi
|
||||
}
|
||||
|
||||
prerequisite()
|
||||
{
|
||||
if ! echo "$ARCH" | grep -q x86; then
|
||||
echo "$0 # Skipped: Test can only run on x86 architectures."
|
||||
exit $ksft_skip
|
||||
fi
|
||||
|
||||
if ! echo "$VENDOR" | grep -iq amd; then
|
||||
echo "$0 # Skipped: Test can only run on AMD CPU."
|
||||
echo "$0 # Current cpu vendor is $VENDOR."
|
||||
exit $ksft_skip
|
||||
fi
|
||||
|
||||
scaling_driver=$(cat /sys/devices/system/cpu/cpufreq/policy0/scaling_driver)
|
||||
if [ "$COMPARATIVE_TEST" = "" ]; then
|
||||
if [ "$scaling_driver" != "$CURRENT_TEST" ]; then
|
||||
echo "$0 # Skipped: Test can only run on $CURRENT_TEST driver or run comparative test."
|
||||
echo "$0 # Please set X86_AMD_PSTATE enabled or run comparative test."
|
||||
echo "$0 # Current cpufreq scaling drvier is $scaling_driver."
|
||||
exit $ksft_skip
|
||||
fi
|
||||
else
|
||||
case "$FUNC" in
|
||||
"tbench" | "gitsource")
|
||||
if [ "$scaling_driver" != "$COMPARATIVE_TEST" ]; then
|
||||
echo "$0 # Skipped: Comparison test can only run on $COMPARISON_TEST driver."
|
||||
echo "$0 # Current cpufreq scaling drvier is $scaling_driver."
|
||||
exit $ksft_skip
|
||||
fi
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "$0 # Skipped: Comparison test are only for tbench or gitsource."
|
||||
echo "$0 # Current comparative test is for $FUNC."
|
||||
exit $ksft_skip
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
if [ ! -w /dev ]; then
|
||||
echo $msg please run this as root >&2
|
||||
exit $ksft_skip
|
||||
fi
|
||||
|
||||
case "$FUNC" in
|
||||
"all")
|
||||
command_perf
|
||||
command_tbench
|
||||
;;
|
||||
|
||||
"tbench")
|
||||
command_perf
|
||||
command_tbench
|
||||
;;
|
||||
|
||||
"gitsource")
|
||||
command_perf
|
||||
;;
|
||||
esac
|
||||
|
||||
SYSFS=`mount -t sysfs | head -1 | awk '{ print $3 }'`
|
||||
|
||||
if [ ! -d "$SYSFS" ]; then
|
||||
echo $msg sysfs is not mounted >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
CPUROOT=$SYSFS/devices/system/cpu
|
||||
CPUFREQROOT="$CPUROOT/cpufreq"
|
||||
|
||||
if ! ls $CPUROOT/cpu* > /dev/null 2>&1; then
|
||||
echo $msg cpus not available in sysfs >&2
|
||||
exit 2
|
||||
fi
|
||||
|
||||
if ! ls $CPUROOT/cpufreq > /dev/null 2>&1; then
|
||||
echo $msg cpufreq directory not available in sysfs >&2
|
||||
exit 2
|
||||
fi
|
||||
}
|
||||
|
||||
do_test()
|
||||
{
|
||||
# Check if CPUs are managed by cpufreq or not
|
||||
count=$(count_cpus)
|
||||
MAKE_CPUS=$((count*2))
|
||||
|
||||
if [ $count = 0 ]; then
|
||||
echo "No cpu is managed by cpufreq core, exiting"
|
||||
exit 2;
|
||||
fi
|
||||
|
||||
case "$FUNC" in
|
||||
"all")
|
||||
amd_pstate_all
|
||||
;;
|
||||
|
||||
"basic")
|
||||
amd_pstate_basic
|
||||
;;
|
||||
|
||||
"tbench")
|
||||
amd_pstate_tbench
|
||||
;;
|
||||
|
||||
"gitsource")
|
||||
amd_pstate_gitsource
|
||||
;;
|
||||
|
||||
*)
|
||||
echo "Invalid [-f] function type"
|
||||
help
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# clear dumps
|
||||
pre_clear_dumps()
|
||||
{
|
||||
case "$FUNC" in
|
||||
"all")
|
||||
rm -rf $OUTFILE.log
|
||||
rm -rf $OUTFILE.backup_governor.log
|
||||
rm -rf *.png
|
||||
;;
|
||||
|
||||
"tbench")
|
||||
rm -rf $OUTFILE.log
|
||||
rm -rf $OUTFILE.backup_governor.log
|
||||
rm -rf tbench_*.png
|
||||
;;
|
||||
|
||||
"gitsource")
|
||||
rm -rf $OUTFILE.log
|
||||
rm -rf $OUTFILE.backup_governor.log
|
||||
rm -rf gitsource_*.png
|
||||
;;
|
||||
|
||||
*)
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
post_clear_dumps()
|
||||
{
|
||||
rm -rf $OUTFILE.log
|
||||
rm -rf $OUTFILE.backup_governor.log
|
||||
}
|
||||
|
||||
# Parse arguments
|
||||
parse_arguments $@
|
||||
|
||||
# Make sure all requirements are met
|
||||
prerequisite
|
||||
|
||||
# Run requested functions
|
||||
pre_clear_dumps
|
||||
do_test | tee -a $OUTFILE.log
|
||||
post_clear_dumps
|
||||
Executable
+339
@@ -0,0 +1,339 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
# Testing and monitor the cpu desire performance, frequency, load,
|
||||
# power consumption and throughput etc.when this script trigger tbench
|
||||
# test cases.
|
||||
# 1) Run tbench benchmark on specific governors, ondemand or schedutil.
|
||||
# 2) Run tbench benchmark comparative test on acpi-cpufreq kernel driver.
|
||||
# 3) Get desire performance, frequency, load by perf.
|
||||
# 4) Get power consumption and throughput by amd_pstate_trace.py.
|
||||
# 5) Analyse test results and save it in file selftest.tbench.csv.
|
||||
# 6) Plot png images about performance, energy and performance per watt for each test.
|
||||
|
||||
# protect against multiple inclusion
|
||||
if [ $FILE_TBENCH ]; then
|
||||
return 0
|
||||
else
|
||||
FILE_TBENCH=DONE
|
||||
fi
|
||||
|
||||
tbench_governors=("ondemand" "schedutil")
|
||||
|
||||
# $1: governor, $2: round, $3: des-perf, $4: freq, $5: load, $6: performance, $7: energy, $8: performance per watt
|
||||
store_csv_tbench()
|
||||
{
|
||||
echo "$1, $2, $3, $4, $5, $6, $7, $8" | tee -a $OUTFILE_TBENCH.csv > /dev/null 2>&1
|
||||
}
|
||||
|
||||
# clear some special lines
|
||||
clear_csv_tbench()
|
||||
{
|
||||
if [ -f $OUTFILE_TBENCH.csv ]; then
|
||||
sed -i '/Comprison(%)/d' $OUTFILE_TBENCH.csv
|
||||
sed -i "/$(scaling_name)/d" $OUTFILE_TBENCH.csv
|
||||
fi
|
||||
}
|
||||
|
||||
# find string $1 in file csv and get the number of lines
|
||||
get_lines_csv_tbench()
|
||||
{
|
||||
if [ -f $OUTFILE_TBENCH.csv ]; then
|
||||
return `grep -c "$1" $OUTFILE_TBENCH.csv`
|
||||
else
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
pre_clear_tbench()
|
||||
{
|
||||
post_clear_tbench
|
||||
rm -rf tbench_*.png
|
||||
clear_csv_tbench
|
||||
}
|
||||
|
||||
post_clear_tbench()
|
||||
{
|
||||
rm -rf results/tracer-tbench*
|
||||
rm -rf $OUTFILE_TBENCH*.log
|
||||
rm -rf $OUTFILE_TBENCH*.result
|
||||
|
||||
}
|
||||
|
||||
# $1: governor, $2: loop
|
||||
run_tbench()
|
||||
{
|
||||
echo "Launching amd pstate tracer for $1 #$2 tracer_interval: $TRACER_INTERVAL"
|
||||
./amd_pstate_trace.py -n tracer-tbench-$1-$2 -i $TRACER_INTERVAL > /dev/null 2>&1 &
|
||||
|
||||
printf "Test tbench for $1 #$2 time_limit: $TIME_LIMIT procs_num: $PROCESS_NUM\n"
|
||||
tbench_srv > /dev/null 2>&1 &
|
||||
perf stat -a --per-socket -I 1000 -e power/energy-pkg/ tbench -t $TIME_LIMIT $PROCESS_NUM > $OUTFILE_TBENCH-perf-$1-$2.log 2>&1
|
||||
|
||||
pid=`pidof tbench_srv`
|
||||
kill $pid
|
||||
|
||||
for job in `jobs -p`
|
||||
do
|
||||
echo "Waiting for job id $job"
|
||||
wait $job
|
||||
done
|
||||
}
|
||||
|
||||
# $1: governor, $2: loop
|
||||
parse_tbench()
|
||||
{
|
||||
awk '{print $5}' results/tracer-tbench-$1-$2/cpu.csv | sed -e '1d' | sed s/,// > $OUTFILE_TBENCH-des-perf-$1-$2.log
|
||||
avg_des_perf=$(awk 'BEGIN {i=0; sum=0};{i++; sum += $1};END {print sum/i}' $OUTFILE_TBENCH-des-perf-$1-$2.log)
|
||||
printf "Tbench-$1-#$2 avg des perf: $avg_des_perf\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
awk '{print $7}' results/tracer-tbench-$1-$2/cpu.csv | sed -e '1d' | sed s/,// > $OUTFILE_TBENCH-freq-$1-$2.log
|
||||
avg_freq=$(awk 'BEGIN {i=0; sum=0};{i++; sum += $1};END {print sum/i}' $OUTFILE_TBENCH-freq-$1-$2.log)
|
||||
printf "Tbench-$1-#$2 avg freq: $avg_freq\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
awk '{print $11}' results/tracer-tbench-$1-$2/cpu.csv | sed -e '1d' | sed s/,// > $OUTFILE_TBENCH-load-$1-$2.log
|
||||
avg_load=$(awk 'BEGIN {i=0; sum=0};{i++; sum += $1};END {print sum/i}' $OUTFILE_TBENCH-load-$1-$2.log)
|
||||
printf "Tbench-$1-#$2 avg load: $avg_load\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
grep Throughput $OUTFILE_TBENCH-perf-$1-$2.log | awk '{print $2}' > $OUTFILE_TBENCH-throughput-$1-$2.log
|
||||
tp_sum=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum}' $OUTFILE_TBENCH-throughput-$1-$2.log)
|
||||
printf "Tbench-$1-#$2 throughput(MB/s): $tp_sum\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
grep Joules $OUTFILE_TBENCH-perf-$1-$2.log | awk '{print $4}' > $OUTFILE_TBENCH-energy-$1-$2.log
|
||||
en_sum=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum}' $OUTFILE_TBENCH-energy-$1-$2.log)
|
||||
printf "Tbench-$1-#$2 power consumption(J): $en_sum\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
# Permance is throughput per second, denoted T/t, where T is throught rendered in t seconds.
|
||||
# It is well known that P=E/t, where P is power measured in watts(W), E is energy measured in joules(J),
|
||||
# and t is time measured in seconds(s). This means that performance per watt becomes
|
||||
# T/t T/t T
|
||||
# --- = --- = ---
|
||||
# P E/t E
|
||||
# with unit given by MB per joule.
|
||||
ppw=`echo "scale=4;($TIME_LIMIT-1)*$tp_sum/$en_sum" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Tbench-$1-#$2 performance per watt(MB/J): $ppw\n" | tee -a $OUTFILE_TBENCH.result
|
||||
printf "\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
driver_name=`echo $(scaling_name)`
|
||||
store_csv_tbench "$driver_name-$1" $2 $avg_des_perf $avg_freq $avg_load $tp_sum $en_sum $ppw
|
||||
}
|
||||
|
||||
# $1: governor
|
||||
loop_tbench()
|
||||
{
|
||||
printf "\nTbench total test times is $LOOP_TIMES for $1\n\n"
|
||||
for i in `seq 1 $LOOP_TIMES`
|
||||
do
|
||||
run_tbench $1 $i
|
||||
parse_tbench $1 $i
|
||||
done
|
||||
}
|
||||
|
||||
# $1: governor
|
||||
gather_tbench()
|
||||
{
|
||||
printf "Tbench test result for $1 (loops:$LOOP_TIMES)" | tee -a $OUTFILE_TBENCH.result
|
||||
printf "\n--------------------------------------------------\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
grep "Tbench-$1-#" $OUTFILE_TBENCH.result | grep "avg des perf:" | awk '{print $NF}' > $OUTFILE_TBENCH-des-perf-$1.log
|
||||
avg_des_perf=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum/'$LOOP_TIMES'}' $OUTFILE_TBENCH-des-perf-$1.log)
|
||||
printf "Tbench-$1 avg des perf: $avg_des_perf\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
grep "Tbench-$1-#" $OUTFILE_TBENCH.result | grep "avg freq:" | awk '{print $NF}' > $OUTFILE_TBENCH-freq-$1.log
|
||||
avg_freq=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum/'$LOOP_TIMES'}' $OUTFILE_TBENCH-freq-$1.log)
|
||||
printf "Tbench-$1 avg freq: $avg_freq\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
grep "Tbench-$1-#" $OUTFILE_TBENCH.result | grep "avg load:" | awk '{print $NF}' > $OUTFILE_TBENCH-load-$1.log
|
||||
avg_load=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum/'$LOOP_TIMES'}' $OUTFILE_TBENCH-load-$1.log)
|
||||
printf "Tbench-$1 avg load: $avg_load\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
grep "Tbench-$1-#" $OUTFILE_TBENCH.result | grep "throughput(MB/s):" | awk '{print $NF}' > $OUTFILE_TBENCH-throughput-$1.log
|
||||
tp_sum=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum}' $OUTFILE_TBENCH-throughput-$1.log)
|
||||
printf "Tbench-$1 total throughput(MB/s): $tp_sum\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
avg_tp=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum/'$LOOP_TIMES'}' $OUTFILE_TBENCH-throughput-$1.log)
|
||||
printf "Tbench-$1 avg throughput(MB/s): $avg_tp\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
grep "Tbench-$1-#" $OUTFILE_TBENCH.result | grep "power consumption(J):" | awk '{print $NF}' > $OUTFILE_TBENCH-energy-$1.log
|
||||
en_sum=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum}' $OUTFILE_TBENCH-energy-$1.log)
|
||||
printf "Tbench-$1 total power consumption(J): $en_sum\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
avg_en=$(awk 'BEGIN {sum=0};{sum += $1};END {print sum/'$LOOP_TIMES'}' $OUTFILE_TBENCH-energy-$1.log)
|
||||
printf "Tbench-$1 avg power consumption(J): $avg_en\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
# Permance is throughput per second, denoted T/t, where T is throught rendered in t seconds.
|
||||
# It is well known that P=E/t, where P is power measured in watts(W), E is energy measured in joules(J),
|
||||
# and t is time measured in seconds(s). This means that performance per watt becomes
|
||||
# T/t T/t T
|
||||
# --- = --- = ---
|
||||
# P E/t E
|
||||
# with unit given by MB per joule.
|
||||
ppw=`echo "scale=4;($TIME_LIMIT-1)*$avg_tp/$avg_en" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Tbench-$1 performance per watt(MB/J): $ppw\n" | tee -a $OUTFILE_TBENCH.result
|
||||
printf "\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
driver_name=`echo $(scaling_name)`
|
||||
store_csv_tbench "$driver_name-$1" "Average" $avg_des_perf $avg_freq $avg_load $avg_tp $avg_en $ppw
|
||||
}
|
||||
|
||||
# $1: base scaling_driver $2: base governor $3: comparative scaling_driver $4: comparative governor
|
||||
__calc_comp_tbench()
|
||||
{
|
||||
base=`grep "$1-$2" $OUTFILE_TBENCH.csv | grep "Average"`
|
||||
comp=`grep "$3-$4" $OUTFILE_TBENCH.csv | grep "Average"`
|
||||
|
||||
if [ -n "$base" -a -n "$comp" ]; then
|
||||
printf "\n==================================================\n" | tee -a $OUTFILE_TBENCH.result
|
||||
printf "Tbench comparison $1-$2 VS $3-$4" | tee -a $OUTFILE_TBENCH.result
|
||||
printf "\n==================================================\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
# get the base values
|
||||
des_perf_base=`echo "$base" | awk '{print $3}' | sed s/,//`
|
||||
freq_base=`echo "$base" | awk '{print $4}' | sed s/,//`
|
||||
load_base=`echo "$base" | awk '{print $5}' | sed s/,//`
|
||||
perf_base=`echo "$base" | awk '{print $6}' | sed s/,//`
|
||||
energy_base=`echo "$base" | awk '{print $7}' | sed s/,//`
|
||||
ppw_base=`echo "$base" | awk '{print $8}' | sed s/,//`
|
||||
|
||||
# get the comparative values
|
||||
des_perf_comp=`echo "$comp" | awk '{print $3}' | sed s/,//`
|
||||
freq_comp=`echo "$comp" | awk '{print $4}' | sed s/,//`
|
||||
load_comp=`echo "$comp" | awk '{print $5}' | sed s/,//`
|
||||
perf_comp=`echo "$comp" | awk '{print $6}' | sed s/,//`
|
||||
energy_comp=`echo "$comp" | awk '{print $7}' | sed s/,//`
|
||||
ppw_comp=`echo "$comp" | awk '{print $8}' | sed s/,//`
|
||||
|
||||
# compare the base and comp values
|
||||
des_perf_drop=`echo "scale=4;($des_perf_comp-$des_perf_base)*100/$des_perf_base" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Tbench-$1 des perf base: $des_perf_base comprison: $des_perf_comp percent: $des_perf_drop\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
freq_drop=`echo "scale=4;($freq_comp-$freq_base)*100/$freq_base" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Tbench-$1 freq base: $freq_base comprison: $freq_comp percent: $freq_drop\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
load_drop=`echo "scale=4;($load_comp-$load_base)*100/$load_base" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Tbench-$1 load base: $load_base comprison: $load_comp percent: $load_drop\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
perf_drop=`echo "scale=4;($perf_comp-$perf_base)*100/$perf_base" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Tbench-$1 perf base: $perf_base comprison: $perf_comp percent: $perf_drop\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
energy_drop=`echo "scale=4;($energy_comp-$energy_base)*100/$energy_base" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Tbench-$1 energy base: $energy_base comprison: $energy_comp percent: $energy_drop\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
ppw_drop=`echo "scale=4;($ppw_comp-$ppw_base)*100/$ppw_base" | bc | awk '{printf "%.4f", $0}'`
|
||||
printf "Tbench-$1 performance per watt base: $ppw_base comprison: $ppw_comp percent: $ppw_drop\n" | tee -a $OUTFILE_TBENCH.result
|
||||
printf "\n" | tee -a $OUTFILE_TBENCH.result
|
||||
|
||||
store_csv_tbench "$1-$2 VS $3-$4" "Comprison(%)" "$des_perf_drop" "$freq_drop" "$load_drop" "$perf_drop" "$energy_drop" "$ppw_drop"
|
||||
fi
|
||||
}
|
||||
|
||||
# calculate the comparison(%)
|
||||
calc_comp_tbench()
|
||||
{
|
||||
# acpi-cpufreq-ondemand VS acpi-cpufreq-schedutil
|
||||
__calc_comp_tbench ${all_scaling_names[0]} ${tbench_governors[0]} ${all_scaling_names[0]} ${tbench_governors[1]}
|
||||
|
||||
# amd-pstate-ondemand VS amd-pstate-schedutil
|
||||
__calc_comp_tbench ${all_scaling_names[1]} ${tbench_governors[0]} ${all_scaling_names[1]} ${tbench_governors[1]}
|
||||
|
||||
# acpi-cpufreq-ondemand VS amd-pstate-ondemand
|
||||
__calc_comp_tbench ${all_scaling_names[0]} ${tbench_governors[0]} ${all_scaling_names[1]} ${tbench_governors[0]}
|
||||
|
||||
# acpi-cpufreq-schedutil VS amd-pstate-schedutil
|
||||
__calc_comp_tbench ${all_scaling_names[0]} ${tbench_governors[1]} ${all_scaling_names[1]} ${tbench_governors[1]}
|
||||
}
|
||||
|
||||
# $1: file_name, $2: title, $3: ylable, $4: column
|
||||
plot_png_tbench()
|
||||
{
|
||||
# all_scaling_names[1] all_scaling_names[0] flag
|
||||
# amd-pstate acpi-cpufreq
|
||||
# N N 0
|
||||
# N Y 1
|
||||
# Y N 2
|
||||
# Y Y 3
|
||||
ret=`grep -c "${all_scaling_names[1]}" $OUTFILE_TBENCH.csv`
|
||||
if [ $ret -eq 0 ]; then
|
||||
ret=`grep -c "${all_scaling_names[0]}" $OUTFILE_TBENCH.csv`
|
||||
if [ $ret -eq 0 ]; then
|
||||
flag=0
|
||||
else
|
||||
flag=1
|
||||
fi
|
||||
else
|
||||
ret=`grep -c "${all_scaling_names[0]}" $OUTFILE_TBENCH.csv`
|
||||
if [ $ret -eq 0 ]; then
|
||||
flag=2
|
||||
else
|
||||
flag=3
|
||||
fi
|
||||
fi
|
||||
|
||||
gnuplot << EOF
|
||||
set term png
|
||||
set output "$1"
|
||||
|
||||
set title "$2"
|
||||
set xlabel "Test Cycles (round)"
|
||||
set ylabel "$3"
|
||||
|
||||
set grid
|
||||
set style data histogram
|
||||
set style fill solid 0.5 border
|
||||
set boxwidth 0.8
|
||||
|
||||
if ($flag == 1) {
|
||||
plot \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[0]}-${tbench_governors[0]}/p' $OUTFILE_TBENCH.csv)" using $4:xtic(2) title "${all_scaling_names[0]}-${tbench_governors[0]}", \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[0]}-${tbench_governors[1]}/p' $OUTFILE_TBENCH.csv)" using $4:xtic(2) title "${all_scaling_names[0]}-${tbench_governors[1]}"
|
||||
} else {
|
||||
if ($flag == 2) {
|
||||
plot \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[1]}-${tbench_governors[0]}/p' $OUTFILE_TBENCH.csv)" using $4:xtic(2) title "${all_scaling_names[1]}-${tbench_governors[0]}", \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[1]}-${tbench_governors[1]}/p' $OUTFILE_TBENCH.csv)" using $4:xtic(2) title "${all_scaling_names[1]}-${tbench_governors[1]}"
|
||||
} else {
|
||||
if ($flag == 3 ) {
|
||||
plot \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[0]}-${tbench_governors[0]}/p' $OUTFILE_TBENCH.csv)" using $4:xtic(2) title "${all_scaling_names[0]}-${tbench_governors[0]}", \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[0]}-${tbench_governors[1]}/p' $OUTFILE_TBENCH.csv)" using $4:xtic(2) title "${all_scaling_names[0]}-${tbench_governors[1]}", \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[1]}-${tbench_governors[0]}/p' $OUTFILE_TBENCH.csv)" using $4:xtic(2) title "${all_scaling_names[1]}-${tbench_governors[0]}", \
|
||||
"<(sed -n -e 's/,//g' -e '/${all_scaling_names[1]}-${tbench_governors[1]}/p' $OUTFILE_TBENCH.csv)" using $4:xtic(2) title "${all_scaling_names[1]}-${tbench_governors[1]}"
|
||||
}
|
||||
}
|
||||
}
|
||||
quit
|
||||
EOF
|
||||
}
|
||||
|
||||
amd_pstate_tbench()
|
||||
{
|
||||
printf "\n---------------------------------------------\n"
|
||||
printf "*** Running tbench ***"
|
||||
printf "\n---------------------------------------------\n"
|
||||
|
||||
pre_clear_tbench
|
||||
|
||||
get_lines_csv_tbench "Governor"
|
||||
if [ $? -eq 0 ]; then
|
||||
# add titles and unit for csv file
|
||||
store_csv_tbench "Governor" "Round" "Des-perf" "Freq" "Load" "Performance" "Energy" "Performance Per Watt"
|
||||
store_csv_tbench "Unit" "" "" "GHz" "" "MB/s" "J" "MB/J"
|
||||
fi
|
||||
|
||||
backup_governor
|
||||
for governor in ${tbench_governors[*]} ; do
|
||||
printf "\nSpecified governor is $governor\n\n"
|
||||
switch_governor $governor
|
||||
loop_tbench $governor
|
||||
gather_tbench $governor
|
||||
done
|
||||
restore_governor
|
||||
|
||||
plot_png_tbench "tbench_perfromance.png" "Tbench Benchmark Performance" "Performance" 6
|
||||
plot_png_tbench "tbench_energy.png" "Tbench Benchmark Energy" "Energy (J)" 7
|
||||
plot_png_tbench "tbench_ppw.png" "Tbench Benchmark Performance Per Watt" "Performance Per Watt (MB/J)" 8
|
||||
|
||||
calc_comp_tbench
|
||||
|
||||
post_clear_tbench
|
||||
}
|
||||
@@ -87,6 +87,11 @@ test_create_read()
|
||||
{
|
||||
local file=$efivarfs_mount/$FUNCNAME-$test_guid
|
||||
./create-read $file
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "create and read $file failed"
|
||||
file_cleanup $file
|
||||
exit 1
|
||||
fi
|
||||
file_cleanup $file
|
||||
}
|
||||
|
||||
|
||||
@@ -38,11 +38,18 @@ cnt_trace() {
|
||||
|
||||
test_event_enabled() {
|
||||
val=$1
|
||||
check_times=10 # wait for 10 * SLEEP_TIME at most
|
||||
|
||||
e=`cat $EVENT_ENABLE`
|
||||
if [ "$e" != $val ]; then
|
||||
fail "Expected $val but found $e"
|
||||
fi
|
||||
while [ $check_times -ne 0 ]; do
|
||||
e=`cat $EVENT_ENABLE`
|
||||
if [ "$e" == $val ]; then
|
||||
return 0
|
||||
fi
|
||||
sleep $SLEEP_TIME
|
||||
check_times=$((check_times - 1))
|
||||
done
|
||||
|
||||
fail "Expected $val but found $e"
|
||||
}
|
||||
|
||||
run_enable_disable() {
|
||||
|
||||
@@ -142,9 +142,15 @@ finish_ftrace() {
|
||||
|
||||
check_requires() { # Check required files and tracers
|
||||
for i in "$@" ; do
|
||||
p=${i%:program}
|
||||
r=${i%:README}
|
||||
t=${i%:tracer}
|
||||
if [ $t != $i ]; then
|
||||
if [ $p != $i ]; then
|
||||
if ! which $p ; then
|
||||
echo "Required program $p is not found."
|
||||
exit_unresolved
|
||||
fi
|
||||
elif [ $t != $i ]; then
|
||||
if ! grep -wq $t available_tracers ; then
|
||||
echo "Required tracer $t is not configured."
|
||||
exit_unsupported
|
||||
|
||||
@@ -46,10 +46,10 @@ cat trace
|
||||
grep -q "tracer: preemptoff" trace || fail
|
||||
|
||||
# Check the end of the section
|
||||
egrep -q "5.....us : <stack trace>" trace || fail
|
||||
grep -E -q "5.....us : <stack trace>" trace || fail
|
||||
|
||||
# Check for 500ms of latency
|
||||
egrep -q "latency: 5..... us" trace || fail
|
||||
grep -E -q "latency: 5..... us" trace || fail
|
||||
|
||||
reset_tracer
|
||||
|
||||
@@ -69,10 +69,10 @@ cat trace
|
||||
grep -q "tracer: irqsoff" trace || fail
|
||||
|
||||
# Check the end of the section
|
||||
egrep -q "5.....us : <stack trace>" trace || fail
|
||||
grep -E -q "5.....us : <stack trace>" trace || fail
|
||||
|
||||
# Check for 500ms of latency
|
||||
egrep -q "latency: 5..... us" trace || fail
|
||||
grep -E -q "latency: 5..... us" trace || fail
|
||||
|
||||
reset_tracer
|
||||
exit 0
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: Test wakeup tracer
|
||||
# requires: wakeup:tracer
|
||||
|
||||
if ! which chrt ; then
|
||||
echo "chrt is not found. This test requires nice command."
|
||||
exit_unresolved
|
||||
fi
|
||||
# requires: wakeup:tracer chrt:program
|
||||
|
||||
echo wakeup > current_tracer
|
||||
echo 1 > tracing_on
|
||||
|
||||
@@ -1,12 +1,7 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: Test wakeup RT tracer
|
||||
# requires: wakeup_rt:tracer
|
||||
|
||||
if ! which chrt ; then
|
||||
echo "chrt is not found. This test requires chrt command."
|
||||
exit_unresolved
|
||||
fi
|
||||
# requires: wakeup_rt:tracer chrt:program
|
||||
|
||||
echo wakeup_rt > current_tracer
|
||||
echo 1 > tracing_on
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: event trigger - test field variable support
|
||||
# requires: set_event synthetic_events events/sched/sched_process_fork/hist
|
||||
# requires: set_event synthetic_events events/sched/sched_process_fork/hist ping:program
|
||||
|
||||
fail() { #msg
|
||||
echo $1
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: event trigger - test inter-event combined histogram trigger
|
||||
# requires: set_event synthetic_events events/sched/sched_process_fork/hist
|
||||
# requires: set_event synthetic_events events/sched/sched_process_fork/hist ping:program
|
||||
|
||||
fail() { #msg
|
||||
echo $1
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: event trigger - test inter-event histogram trigger onchange action
|
||||
# requires: set_event "onchange(var)":README
|
||||
# requires: set_event "onchange(var)":README ping:program
|
||||
|
||||
fail() { #msg
|
||||
echo $1
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: event trigger - test inter-event histogram trigger onmatch action
|
||||
# requires: set_event synthetic_events events/sched/sched_process_fork/hist
|
||||
# requires: set_event synthetic_events events/sched/sched_process_fork/hist ping:program
|
||||
|
||||
fail() { #msg
|
||||
echo $1
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: event trigger - test inter-event histogram trigger onmatch-onmax action
|
||||
# requires: set_event synthetic_events events/sched/sched_process_fork/hist
|
||||
# requires: set_event synthetic_events events/sched/sched_process_fork/hist ping:program
|
||||
|
||||
fail() { #msg
|
||||
echo $1
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: event trigger - test inter-event histogram trigger onmax action
|
||||
# requires: set_event synthetic_events events/sched/sched_process_fork/hist
|
||||
# requires: set_event synthetic_events events/sched/sched_process_fork/hist ping:program
|
||||
|
||||
fail() { #msg
|
||||
echo $1
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: event trigger - test inter-event histogram trigger snapshot action
|
||||
# requires: set_event snapshot events/sched/sched_process_fork/hist "onchange(var)":README "snapshot()":README
|
||||
# requires: set_event snapshot events/sched/sched_process_fork/hist "onchange(var)":README "snapshot()":README ping:program
|
||||
|
||||
fail() { #msg
|
||||
echo $1
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ FIELD="filename"
|
||||
SYNTH="synth_open"
|
||||
EPROBE="eprobe_open"
|
||||
|
||||
echo "$SYNTH u64 filename; s64 ret;" > synthetic_events
|
||||
echo "$SYNTH unsigned long filename; long ret;" > synthetic_events
|
||||
echo "hist:keys=common_pid:__arg__1=$FIELD" > events/$SYSTEM/$START/trigger
|
||||
echo "hist:keys=common_pid:filename=\$__arg__1,ret=ret:onmatch($SYSTEM.$START).trace($SYNTH,\$filename,\$ret)" > events/$SYSTEM/$END/trigger
|
||||
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: event trigger - test inter-event histogram trigger trace action with dynamic string param
|
||||
# requires: set_event synthetic_events events/sched/sched_process_exec/hist "char name[]' >> synthetic_events":README
|
||||
# requires: set_event synthetic_events events/sched/sched_process_exec/hist "char name[]' >> synthetic_events":README ping:program
|
||||
|
||||
fail() { #msg
|
||||
echo $1
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: event trigger - test inter-event histogram trigger trace action
|
||||
# requires: set_event synthetic_events events/sched/sched_process_fork/hist "trace(<synthetic_event>":README
|
||||
# requires: set_event synthetic_events events/sched/sched_process_fork/hist "trace(<synthetic_event>":README ping:program
|
||||
|
||||
fail() { #msg
|
||||
echo $1
|
||||
|
||||
@@ -27,7 +27,7 @@ remove_chip() {
|
||||
continue
|
||||
fi
|
||||
|
||||
LINES=`ls $CONFIGFS_DIR/$CHIP/$BANK/ | egrep ^line`
|
||||
LINES=`ls $CONFIGFS_DIR/$CHIP/$BANK/ | grep -E ^line`
|
||||
if [ "$?" = 0 ]; then
|
||||
for LINE in $LINES; do
|
||||
if [ -e $CONFIGFS_DIR/$CHIP/$BANK/$LINE/hog ]; then
|
||||
|
||||
@@ -90,7 +90,7 @@ pass_libs=()
|
||||
pass_cnt=0
|
||||
|
||||
# Get all TARGETS from selftests Makefile
|
||||
targets=$(egrep "^TARGETS +|^TARGETS =" Makefile | cut -d "=" -f2)
|
||||
targets=$(grep -E "^TARGETS +|^TARGETS =" Makefile | cut -d "=" -f2)
|
||||
|
||||
# Single test case
|
||||
if [ $# -eq 2 ]
|
||||
|
||||
@@ -123,6 +123,11 @@ endef
|
||||
clean:
|
||||
$(CLEAN)
|
||||
|
||||
# Enables to extend CFLAGS and LDFLAGS from command line, e.g.
|
||||
# make USERCFLAGS=-Werror USERLDFLAGS=-static
|
||||
CFLAGS += $(USERCFLAGS)
|
||||
LDFLAGS += $(USERLDFLAGS)
|
||||
|
||||
# When make O= with kselftest target from main level
|
||||
# the following aren't defined.
|
||||
#
|
||||
|
||||
@@ -31,7 +31,6 @@ FIXTURE(rtc) {
|
||||
|
||||
FIXTURE_SETUP(rtc) {
|
||||
self->fd = open(rtc_file, O_RDONLY);
|
||||
ASSERT_NE(-1, self->fd);
|
||||
}
|
||||
|
||||
FIXTURE_TEARDOWN(rtc) {
|
||||
@@ -42,6 +41,10 @@ TEST_F(rtc, date_read) {
|
||||
int rc;
|
||||
struct rtc_time rtc_tm;
|
||||
|
||||
if (self->fd == -1 && errno == ENOENT)
|
||||
SKIP(return, "Skipping test since %s does not exist", rtc_file);
|
||||
ASSERT_NE(-1, self->fd);
|
||||
|
||||
/* Read the RTC time/date */
|
||||
rc = ioctl(self->fd, RTC_RD_TIME, &rtc_tm);
|
||||
ASSERT_NE(-1, rc);
|
||||
@@ -85,6 +88,10 @@ TEST_F_TIMEOUT(rtc, date_read_loop, READ_LOOP_DURATION_SEC + 2) {
|
||||
struct rtc_time rtc_tm;
|
||||
time_t start_rtc_read, prev_rtc_read;
|
||||
|
||||
if (self->fd == -1 && errno == ENOENT)
|
||||
SKIP(return, "Skipping test since %s does not exist", rtc_file);
|
||||
ASSERT_NE(-1, self->fd);
|
||||
|
||||
TH_LOG("Continuously reading RTC time for %ds (with %dms breaks after every read).",
|
||||
READ_LOOP_DURATION_SEC, READ_LOOP_SLEEP_MS);
|
||||
|
||||
@@ -119,6 +126,10 @@ TEST_F_TIMEOUT(rtc, uie_read, NUM_UIE + 2) {
|
||||
int i, rc, irq = 0;
|
||||
unsigned long data;
|
||||
|
||||
if (self->fd == -1 && errno == ENOENT)
|
||||
SKIP(return, "Skipping test since %s does not exist", rtc_file);
|
||||
ASSERT_NE(-1, self->fd);
|
||||
|
||||
/* Turn on update interrupts */
|
||||
rc = ioctl(self->fd, RTC_UIE_ON, 0);
|
||||
if (rc == -1) {
|
||||
@@ -144,6 +155,10 @@ TEST_F(rtc, uie_select) {
|
||||
int i, rc, irq = 0;
|
||||
unsigned long data;
|
||||
|
||||
if (self->fd == -1 && errno == ENOENT)
|
||||
SKIP(return, "Skipping test since %s does not exist", rtc_file);
|
||||
ASSERT_NE(-1, self->fd);
|
||||
|
||||
/* Turn on update interrupts */
|
||||
rc = ioctl(self->fd, RTC_UIE_ON, 0);
|
||||
if (rc == -1) {
|
||||
@@ -183,6 +198,10 @@ TEST_F(rtc, alarm_alm_set) {
|
||||
time_t secs, new;
|
||||
int rc;
|
||||
|
||||
if (self->fd == -1 && errno == ENOENT)
|
||||
SKIP(return, "Skipping test since %s does not exist", rtc_file);
|
||||
ASSERT_NE(-1, self->fd);
|
||||
|
||||
rc = ioctl(self->fd, RTC_RD_TIME, &tm);
|
||||
ASSERT_NE(-1, rc);
|
||||
|
||||
@@ -237,6 +256,10 @@ TEST_F(rtc, alarm_wkalm_set) {
|
||||
time_t secs, new;
|
||||
int rc;
|
||||
|
||||
if (self->fd == -1 && errno == ENOENT)
|
||||
SKIP(return, "Skipping test since %s does not exist", rtc_file);
|
||||
ASSERT_NE(-1, self->fd);
|
||||
|
||||
rc = ioctl(self->fd, RTC_RD_TIME, &alarm.time);
|
||||
ASSERT_NE(-1, rc);
|
||||
|
||||
@@ -285,6 +308,10 @@ TEST_F_TIMEOUT(rtc, alarm_alm_set_minute, 65) {
|
||||
time_t secs, new;
|
||||
int rc;
|
||||
|
||||
if (self->fd == -1 && errno == ENOENT)
|
||||
SKIP(return, "Skipping test since %s does not exist", rtc_file);
|
||||
ASSERT_NE(-1, self->fd);
|
||||
|
||||
rc = ioctl(self->fd, RTC_RD_TIME, &tm);
|
||||
ASSERT_NE(-1, rc);
|
||||
|
||||
@@ -339,6 +366,10 @@ TEST_F_TIMEOUT(rtc, alarm_wkalm_set_minute, 65) {
|
||||
time_t secs, new;
|
||||
int rc;
|
||||
|
||||
if (self->fd == -1 && errno == ENOENT)
|
||||
SKIP(return, "Skipping test since %s does not exist", rtc_file);
|
||||
ASSERT_NE(-1, self->fd);
|
||||
|
||||
rc = ioctl(self->fd, RTC_RD_TIME, &alarm.time);
|
||||
ASSERT_NE(-1, rc);
|
||||
|
||||
|
||||
@@ -127,7 +127,7 @@ expect_success "proc_handler: special read splice" test_splice /proc/sys/kernel/
|
||||
if ! [ -d /sys/module/test_module/sections ] ; then
|
||||
expect_success "test_module kernel module load" modprobe test_module
|
||||
fi
|
||||
expect_failure "kernfs attr splice" test_splice /sys/module/test_module/coresize
|
||||
expect_failure "kernfs binattr splice" test_splice /sys/module/test_module/sections/.init.text
|
||||
expect_success "kernfs attr splice" test_splice /sys/module/test_module/coresize
|
||||
expect_success "kernfs binattr splice" test_splice /sys/module/test_module/sections/.init.text
|
||||
|
||||
exit $ret
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
|
||||
include ../lib.mk
|
||||
|
||||
TEST_PROGS := test_smoke.sh test_space.sh
|
||||
TEST_PROGS := test_smoke.sh test_space.sh test_async.sh
|
||||
TEST_PROGS_EXTENDED := tpm2.py tpm2_tests.py
|
||||
|
||||
Executable
+10
@@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
# SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
|
||||
|
||||
# Kselftest framework requirement - SKIP code is 4.
|
||||
ksft_skip=4
|
||||
|
||||
[ -e /dev/tpm0 ] || exit $ksft_skip
|
||||
[ -e /dev/tpmrm0 ] || exit $ksft_skip
|
||||
|
||||
python3 -m unittest -v tpm2_tests.AsyncTest
|
||||
@@ -7,4 +7,3 @@ ksft_skip=4
|
||||
[ -e /dev/tpm0 ] || exit $ksft_skip
|
||||
|
||||
python3 -m unittest -v tpm2_tests.SmokeTest
|
||||
python3 -m unittest -v tpm2_tests.AsyncTest
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
#include "../kselftest.h"
|
||||
#include "parse_vdso.h"
|
||||
|
||||
#if defined(__riscv)
|
||||
const char *version = "LINUX_4.15";
|
||||
#else
|
||||
const char *version = "LINUX_2.6";
|
||||
#endif
|
||||
const char *name = "__vdso_getcpu";
|
||||
|
||||
struct getcpu_cache;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user