mirror of https://gitee.com/openkylin/linux.git
Tracing updates:
- Added new "bootconfig". Looks for a file appended to initrd to add boot config options. This has been discussed thoroughly at Linux Plumbers. Very useful for adding kprobes at bootup. Only enabled if "bootconfig" is on the real kernel command line. - Created dynamic event creation. Merges common code between creating synthetic events and kprobe events. - Rename perf "ring_buffer" structure to "perf_buffer" - Rename ftrace "ring_buffer" structure to "trace_buffer" Had to rename existing "trace_buffer" to "array_buffer" - Allow trace_printk() to work withing (some) tracing code. - Sort of tracing configs to be a little better organized - Fixed bug where ftrace_graph hash was not being protected properly - Various other small fixes and clean ups -----BEGIN PGP SIGNATURE----- iIoEABYIADIWIQRRSw7ePDh/lE+zeZMp5XQQmuv6qgUCXjtAURQccm9zdGVkdEBn b29kbWlzLm9yZwAKCRAp5XQQmuv6qshOAQDzopQmvAVrrI6oogghr8JQA30Z2yqT i+Ld7vPWL2MV9wEA1S+zLGDSYrj8f/vsCq6BxRYT1ApO+YtmY6LTXiUejwg= =WNds -----END PGP SIGNATURE----- Merge tag 'trace-v5.6-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-trace Pull tracing updates from Steven Rostedt: - Added new "bootconfig". This looks for a file appended to initrd to add boot config options, and has been discussed thoroughly at Linux Plumbers. Very useful for adding kprobes at bootup. Only enabled if "bootconfig" is on the real kernel command line. - Created dynamic event creation. Merges common code between creating synthetic events and kprobe events. - Rename perf "ring_buffer" structure to "perf_buffer" - Rename ftrace "ring_buffer" structure to "trace_buffer" Had to rename existing "trace_buffer" to "array_buffer" - Allow trace_printk() to work withing (some) tracing code. - Sort of tracing configs to be a little better organized - Fixed bug where ftrace_graph hash was not being protected properly - Various other small fixes and clean ups * tag 'trace-v5.6-2' of git://git.kernel.org/pub/scm/linux/kernel/git/rostedt/linux-trace: (88 commits) bootconfig: Show the number of nodes on boot message tools/bootconfig: Show the number of bootconfig nodes bootconfig: Add more parse error messages bootconfig: Use bootconfig instead of boot config ftrace: Protect ftrace_graph_hash with ftrace_sync ftrace: Add comment to why rcu_dereference_sched() is open coded tracing: Annotate ftrace_graph_notrace_hash pointer with __rcu tracing: Annotate ftrace_graph_hash pointer with __rcu bootconfig: Only load bootconfig if "bootconfig" is on the kernel cmdline tracing: Use seq_buf for building dynevent_cmd string tracing: Remove useless code in dynevent_arg_pair_add() tracing: Remove check_arg() callbacks from dynevent args tracing: Consolidate some synth_event_trace code tracing: Fix now invalid var_ref_vals assumption in trace action tracing: Change trace_boot to use synth_event interface tracing: Move tracing selftests to bottom of menu tracing: Move mmio tracer config up with the other tracers tracing: Move tracing test module configs together tracing: Move all function tracing configs together tracing: Documentation for in-kernel synthetic event API ...
This commit is contained in:
commit
e310396bb8
|
@ -0,0 +1,190 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
.. _bootconfig:
|
||||
|
||||
==================
|
||||
Boot Configuration
|
||||
==================
|
||||
|
||||
:Author: Masami Hiramatsu <mhiramat@kernel.org>
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
The boot configuration expands the current kernel command line to support
|
||||
additional key-value data when booting the kernel in an efficient way.
|
||||
This allows administrators to pass a structured-Key config file.
|
||||
|
||||
Config File Syntax
|
||||
==================
|
||||
|
||||
The boot config syntax is a simple structured key-value. Each key consists
|
||||
of dot-connected-words, and key and value are connected by ``=``. The value
|
||||
has to be terminated by semi-colon (``;``) or newline (``\n``).
|
||||
For array value, array entries are separated by comma (``,``). ::
|
||||
|
||||
KEY[.WORD[...]] = VALUE[, VALUE2[...]][;]
|
||||
|
||||
Unlike the kernel command line syntax, spaces are OK around the comma and ``=``.
|
||||
|
||||
Each key word must contain only alphabets, numbers, dash (``-``) or underscore
|
||||
(``_``). And each value only contains printable characters or spaces except
|
||||
for delimiters such as semi-colon (``;``), new-line (``\n``), comma (``,``),
|
||||
hash (``#``) and closing brace (``}``).
|
||||
|
||||
If you want to use those delimiters in a value, you can use either double-
|
||||
quotes (``"VALUE"``) or single-quotes (``'VALUE'``) to quote it. Note that
|
||||
you can not escape these quotes.
|
||||
|
||||
There can be a key which doesn't have value or has an empty value. Those keys
|
||||
are used for checking if the key exists or not (like a boolean).
|
||||
|
||||
Key-Value Syntax
|
||||
----------------
|
||||
|
||||
The boot config file syntax allows user to merge partially same word keys
|
||||
by brace. For example::
|
||||
|
||||
foo.bar.baz = value1
|
||||
foo.bar.qux.quux = value2
|
||||
|
||||
These can be written also in::
|
||||
|
||||
foo.bar {
|
||||
baz = value1
|
||||
qux.quux = value2
|
||||
}
|
||||
|
||||
Or more shorter, written as following::
|
||||
|
||||
foo.bar { baz = value1; qux.quux = value2 }
|
||||
|
||||
In both styles, same key words are automatically merged when parsing it
|
||||
at boot time. So you can append similar trees or key-values.
|
||||
|
||||
Comments
|
||||
--------
|
||||
|
||||
The config syntax accepts shell-script style comments. The comments starting
|
||||
with hash ("#") until newline ("\n") will be ignored.
|
||||
|
||||
::
|
||||
|
||||
# comment line
|
||||
foo = value # value is set to foo.
|
||||
bar = 1, # 1st element
|
||||
2, # 2nd element
|
||||
3 # 3rd element
|
||||
|
||||
This is parsed as below::
|
||||
|
||||
foo = value
|
||||
bar = 1, 2, 3
|
||||
|
||||
Note that you can not put a comment between value and delimiter(``,`` or
|
||||
``;``). This means following config has a syntax error ::
|
||||
|
||||
key = 1 # comment
|
||||
,2
|
||||
|
||||
|
||||
/proc/bootconfig
|
||||
================
|
||||
|
||||
/proc/bootconfig is a user-space interface of the boot config.
|
||||
Unlike /proc/cmdline, this file shows the key-value style list.
|
||||
Each key-value pair is shown in each line with following style::
|
||||
|
||||
KEY[.WORDS...] = "[VALUE]"[,"VALUE2"...]
|
||||
|
||||
|
||||
Boot Kernel With a Boot Config
|
||||
==============================
|
||||
|
||||
Since the boot configuration file is loaded with initrd, it will be added
|
||||
to the end of the initrd (initramfs) image file. The Linux kernel decodes
|
||||
the last part of the initrd image in memory to get the boot configuration
|
||||
data.
|
||||
Because of this "piggyback" method, there is no need to change or
|
||||
update the boot loader and the kernel image itself.
|
||||
|
||||
To do this operation, Linux kernel provides "bootconfig" command under
|
||||
tools/bootconfig, which allows admin to apply or delete the config file
|
||||
to/from initrd image. You can build it by the following command::
|
||||
|
||||
# make -C tools/bootconfig
|
||||
|
||||
To add your boot config file to initrd image, run bootconfig as below
|
||||
(Old data is removed automatically if exists)::
|
||||
|
||||
# tools/bootconfig/bootconfig -a your-config /boot/initrd.img-X.Y.Z
|
||||
|
||||
To remove the config from the image, you can use -d option as below::
|
||||
|
||||
# tools/bootconfig/bootconfig -d /boot/initrd.img-X.Y.Z
|
||||
|
||||
Then add "bootconfig" on the normal kernel command line to tell the
|
||||
kernel to look for the bootconfig at the end of the initrd file.
|
||||
|
||||
Config File Limitation
|
||||
======================
|
||||
|
||||
Currently the maximum config size size is 32KB and the total key-words (not
|
||||
key-value entries) must be under 1024 nodes.
|
||||
Note: this is not the number of entries but nodes, an entry must consume
|
||||
more than 2 nodes (a key-word and a value). So theoretically, it will be
|
||||
up to 512 key-value pairs. If keys contains 3 words in average, it can
|
||||
contain 256 key-value pairs. In most cases, the number of config items
|
||||
will be under 100 entries and smaller than 8KB, so it would be enough.
|
||||
If the node number exceeds 1024, parser returns an error even if the file
|
||||
size is smaller than 32KB.
|
||||
Anyway, since bootconfig command verifies it when appending a boot config
|
||||
to initrd image, user can notice it before boot.
|
||||
|
||||
|
||||
Bootconfig APIs
|
||||
===============
|
||||
|
||||
User can query or loop on key-value pairs, also it is possible to find
|
||||
a root (prefix) key node and find key-values under that node.
|
||||
|
||||
If you have a key string, you can query the value directly with the key
|
||||
using xbc_find_value(). If you want to know what keys exist in the boot
|
||||
config, you can use xbc_for_each_key_value() to iterate key-value pairs.
|
||||
Note that you need to use xbc_array_for_each_value() for accessing
|
||||
each array's value, e.g.::
|
||||
|
||||
vnode = NULL;
|
||||
xbc_find_value("key.word", &vnode);
|
||||
if (vnode && xbc_node_is_array(vnode))
|
||||
xbc_array_for_each_value(vnode, value) {
|
||||
printk("%s ", value);
|
||||
}
|
||||
|
||||
If you want to focus on keys which have a prefix string, you can use
|
||||
xbc_find_node() to find a node by the prefix string, and iterate
|
||||
keys under the prefix node with xbc_node_for_each_key_value().
|
||||
|
||||
But the most typical usage is to get the named value under prefix
|
||||
or get the named array under prefix as below::
|
||||
|
||||
root = xbc_find_node("key.prefix");
|
||||
value = xbc_node_find_value(root, "option", &vnode);
|
||||
...
|
||||
xbc_node_for_each_array_value(root, "array-option", value, anode) {
|
||||
...
|
||||
}
|
||||
|
||||
This accesses a value of "key.prefix.option" and an array of
|
||||
"key.prefix.array-option".
|
||||
|
||||
Locking is not needed, since after initialization, the config becomes
|
||||
read-only. All data and keys must be copied if you need to modify it.
|
||||
|
||||
|
||||
Functions and structures
|
||||
========================
|
||||
|
||||
.. kernel-doc:: include/linux/bootconfig.h
|
||||
.. kernel-doc:: lib/bootconfig.c
|
||||
|
|
@ -64,6 +64,7 @@ configure specific aspects of kernel behavior to your liking.
|
|||
binderfs
|
||||
binfmt-misc
|
||||
blockdev/index
|
||||
bootconfig
|
||||
braille-console
|
||||
btmrvl
|
||||
cgroup-v1/index
|
||||
|
|
|
@ -437,6 +437,12 @@
|
|||
no delay (0).
|
||||
Format: integer
|
||||
|
||||
bootconfig [KNL]
|
||||
Extended command line options can be added to an initrd
|
||||
and this will cause the kernel to look for it.
|
||||
|
||||
See Documentation/admin-guide/bootconfig.rst
|
||||
|
||||
bert_disable [ACPI]
|
||||
Disable BERT OS support on buggy BIOSes.
|
||||
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
.. SPDX-License-Identifier: GPL-2.0
|
||||
|
||||
=================
|
||||
Boot-time tracing
|
||||
=================
|
||||
|
||||
:Author: Masami Hiramatsu <mhiramat@kernel.org>
|
||||
|
||||
Overview
|
||||
========
|
||||
|
||||
Boot-time tracing allows users to trace boot-time process including
|
||||
device initialization with full features of ftrace including per-event
|
||||
filter and actions, histograms, kprobe-events and synthetic-events,
|
||||
and trace instances.
|
||||
Since kernel command line is not enough to control these complex features,
|
||||
this uses bootconfig file to describe tracing feature programming.
|
||||
|
||||
Options in the Boot Config
|
||||
==========================
|
||||
|
||||
Here is the list of available options list for boot time tracing in
|
||||
boot config file [1]_. All options are under "ftrace." or "kernel."
|
||||
prefix. See kernel parameters for the options which starts
|
||||
with "kernel." prefix [2]_.
|
||||
|
||||
.. [1] See :ref:`Documentation/admin-guide/bootconfig.rst <bootconfig>`
|
||||
.. [2] See :ref:`Documentation/admin-guide/kernel-parameters.rst <kernelparameters>`
|
||||
|
||||
Ftrace Global Options
|
||||
---------------------
|
||||
|
||||
Ftrace global options have "kernel." prefix in boot config, which means
|
||||
these options are passed as a part of kernel legacy command line.
|
||||
|
||||
kernel.tp_printk
|
||||
Output trace-event data on printk buffer too.
|
||||
|
||||
kernel.dump_on_oops [= MODE]
|
||||
Dump ftrace on Oops. If MODE = 1 or omitted, dump trace buffer
|
||||
on all CPUs. If MODE = 2, dump a buffer on a CPU which kicks Oops.
|
||||
|
||||
kernel.traceoff_on_warning
|
||||
Stop tracing if WARN_ON() occurs.
|
||||
|
||||
kernel.fgraph_max_depth = MAX_DEPTH
|
||||
Set MAX_DEPTH to maximum depth of fgraph tracer.
|
||||
|
||||
kernel.fgraph_filters = FILTER[, FILTER2...]
|
||||
Add fgraph tracing function filters.
|
||||
|
||||
kernel.fgraph_notraces = FILTER[, FILTER2...]
|
||||
Add fgraph non-tracing function filters.
|
||||
|
||||
|
||||
Ftrace Per-instance Options
|
||||
---------------------------
|
||||
|
||||
These options can be used for each instance including global ftrace node.
|
||||
|
||||
ftrace.[instance.INSTANCE.]options = OPT1[, OPT2[...]]
|
||||
Enable given ftrace options.
|
||||
|
||||
ftrace.[instance.INSTANCE.]trace_clock = CLOCK
|
||||
Set given CLOCK to ftrace's trace_clock.
|
||||
|
||||
ftrace.[instance.INSTANCE.]buffer_size = SIZE
|
||||
Configure ftrace buffer size to SIZE. You can use "KB" or "MB"
|
||||
for that SIZE.
|
||||
|
||||
ftrace.[instance.INSTANCE.]alloc_snapshot
|
||||
Allocate snapshot buffer.
|
||||
|
||||
ftrace.[instance.INSTANCE.]cpumask = CPUMASK
|
||||
Set CPUMASK as trace cpu-mask.
|
||||
|
||||
ftrace.[instance.INSTANCE.]events = EVENT[, EVENT2[...]]
|
||||
Enable given events on boot. You can use a wild card in EVENT.
|
||||
|
||||
ftrace.[instance.INSTANCE.]tracer = TRACER
|
||||
Set TRACER to current tracer on boot. (e.g. function)
|
||||
|
||||
ftrace.[instance.INSTANCE.]ftrace.filters
|
||||
This will take an array of tracing function filter rules.
|
||||
|
||||
ftrace.[instance.INSTANCE.]ftrace.notraces
|
||||
This will take an array of NON-tracing function filter rules.
|
||||
|
||||
|
||||
Ftrace Per-Event Options
|
||||
------------------------
|
||||
|
||||
These options are setting per-event options.
|
||||
|
||||
ftrace.[instance.INSTANCE.]event.GROUP.EVENT.enable
|
||||
Enable GROUP:EVENT tracing.
|
||||
|
||||
ftrace.[instance.INSTANCE.]event.GROUP.EVENT.filter = FILTER
|
||||
Set FILTER rule to the GROUP:EVENT.
|
||||
|
||||
ftrace.[instance.INSTANCE.]event.GROUP.EVENT.actions = ACTION[, ACTION2[...]]
|
||||
Set ACTIONs to the GROUP:EVENT.
|
||||
|
||||
ftrace.[instance.INSTANCE.]event.kprobes.EVENT.probes = PROBE[, PROBE2[...]]
|
||||
Defines new kprobe event based on PROBEs. It is able to define
|
||||
multiple probes on one event, but those must have same type of
|
||||
arguments. This option is available only for the event which
|
||||
group name is "kprobes".
|
||||
|
||||
ftrace.[instance.INSTANCE.]event.synthetic.EVENT.fields = FIELD[, FIELD2[...]]
|
||||
Defines new synthetic event with FIELDs. Each field should be
|
||||
"type varname".
|
||||
|
||||
Note that kprobe and synthetic event definitions can be written under
|
||||
instance node, but those are also visible from other instances. So please
|
||||
take care for event name conflict.
|
||||
|
||||
|
||||
Examples
|
||||
========
|
||||
|
||||
For example, to add filter and actions for each event, define kprobe
|
||||
events, and synthetic events with histogram, write a boot config like
|
||||
below::
|
||||
|
||||
ftrace.event {
|
||||
task.task_newtask {
|
||||
filter = "pid < 128"
|
||||
enable
|
||||
}
|
||||
kprobes.vfs_read {
|
||||
probes = "vfs_read $arg1 $arg2"
|
||||
filter = "common_pid < 200"
|
||||
enable
|
||||
}
|
||||
synthetic.initcall_latency {
|
||||
fields = "unsigned long func", "u64 lat"
|
||||
actions = "hist:keys=func.sym,lat:vals=lat:sort=lat"
|
||||
}
|
||||
initcall.initcall_start {
|
||||
actions = "hist:keys=func:ts0=common_timestamp.usecs"
|
||||
}
|
||||
initcall.initcall_finish {
|
||||
actions = "hist:keys=func:lat=common_timestamp.usecs-$ts0:onmatch(initcall.initcall_start).initcall_latency(func,$lat)"
|
||||
}
|
||||
}
|
||||
|
||||
Also, boot-time tracing supports "instance" node, which allows us to run
|
||||
several tracers for different purpose at once. For example, one tracer
|
||||
is for tracing functions starting with "user\_", and others tracing
|
||||
"kernel\_" functions, you can write boot config as below::
|
||||
|
||||
ftrace.instance {
|
||||
foo {
|
||||
tracer = "function"
|
||||
ftrace.filters = "user_*"
|
||||
}
|
||||
bar {
|
||||
tracer = "function"
|
||||
ftrace.filters = "kernel_*"
|
||||
}
|
||||
}
|
||||
|
||||
The instance node also accepts event nodes so that each instance
|
||||
can customize its event tracing.
|
||||
|
||||
This boot-time tracing also supports ftrace kernel parameters via boot
|
||||
config.
|
||||
For example, following kernel parameters::
|
||||
|
||||
trace_options=sym-addr trace_event=initcall:* tp_printk trace_buf_size=1M ftrace=function ftrace_filter="vfs*"
|
||||
|
||||
This can be written in boot config like below::
|
||||
|
||||
kernel {
|
||||
trace_options = sym-addr
|
||||
trace_event = "initcall:*"
|
||||
tp_printk
|
||||
trace_buf_size = 1M
|
||||
ftrace = function
|
||||
ftrace_filter = "vfs*"
|
||||
}
|
||||
|
||||
Note that parameters start with "kernel" prefix instead of "ftrace".
|
|
@ -525,3 +525,518 @@ The following commands are supported:
|
|||
event counts (hitcount).
|
||||
|
||||
See Documentation/trace/histogram.rst for details and examples.
|
||||
|
||||
6.3 In-kernel trace event API
|
||||
-----------------------------
|
||||
|
||||
In most cases, the command-line interface to trace events is more than
|
||||
sufficient. Sometimes, however, applications might find the need for
|
||||
more complex relationships than can be expressed through a simple
|
||||
series of linked command-line expressions, or putting together sets of
|
||||
commands may be simply too cumbersome. An example might be an
|
||||
application that needs to 'listen' to the trace stream in order to
|
||||
maintain an in-kernel state machine detecting, for instance, when an
|
||||
illegal kernel state occurs in the scheduler.
|
||||
|
||||
The trace event subsystem provides an in-kernel API allowing modules
|
||||
or other kernel code to generate user-defined 'synthetic' events at
|
||||
will, which can be used to either augment the existing trace stream
|
||||
and/or signal that a particular important state has occurred.
|
||||
|
||||
A similar in-kernel API is also available for creating kprobe and
|
||||
kretprobe events.
|
||||
|
||||
Both the synthetic event and k/ret/probe event APIs are built on top
|
||||
of a lower-level "dynevent_cmd" event command API, which is also
|
||||
available for more specialized applications, or as the basis of other
|
||||
higher-level trace event APIs.
|
||||
|
||||
The API provided for these purposes is describe below and allows the
|
||||
following:
|
||||
|
||||
- dynamically creating synthetic event definitions
|
||||
- dynamically creating kprobe and kretprobe event definitions
|
||||
- tracing synthetic events from in-kernel code
|
||||
- the low-level "dynevent_cmd" API
|
||||
|
||||
6.3.1 Dyamically creating synthetic event definitions
|
||||
-----------------------------------------------------
|
||||
|
||||
There are a couple ways to create a new synthetic event from a kernel
|
||||
module or other kernel code.
|
||||
|
||||
The first creates the event in one step, using synth_event_create().
|
||||
In this method, the name of the event to create and an array defining
|
||||
the fields is supplied to synth_event_create(). If successful, a
|
||||
synthetic event with that name and fields will exist following that
|
||||
call. For example, to create a new "schedtest" synthetic event:
|
||||
|
||||
ret = synth_event_create("schedtest", sched_fields,
|
||||
ARRAY_SIZE(sched_fields), THIS_MODULE);
|
||||
|
||||
The sched_fields param in this example points to an array of struct
|
||||
synth_field_desc, each of which describes an event field by type and
|
||||
name:
|
||||
|
||||
static struct synth_field_desc sched_fields[] = {
|
||||
{ .type = "pid_t", .name = "next_pid_field" },
|
||||
{ .type = "char[16]", .name = "next_comm_field" },
|
||||
{ .type = "u64", .name = "ts_ns" },
|
||||
{ .type = "u64", .name = "ts_ms" },
|
||||
{ .type = "unsigned int", .name = "cpu" },
|
||||
{ .type = "char[64]", .name = "my_string_field" },
|
||||
{ .type = "int", .name = "my_int_field" },
|
||||
};
|
||||
|
||||
See synth_field_size() for available types. If field_name contains [n]
|
||||
the field is considered to be an array.
|
||||
|
||||
If the event is created from within a module, a pointer to the module
|
||||
must be passed to synth_event_create(). This will ensure that the
|
||||
trace buffer won't contain unreadable events when the module is
|
||||
removed.
|
||||
|
||||
At this point, the event object is ready to be used for generating new
|
||||
events.
|
||||
|
||||
In the second method, the event is created in several steps. This
|
||||
allows events to be created dynamically and without the need to create
|
||||
and populate an array of fields beforehand.
|
||||
|
||||
To use this method, an empty or partially empty synthetic event should
|
||||
first be created using synth_event_gen_cmd_start() or
|
||||
synth_event_gen_cmd_array_start(). For synth_event_gen_cmd_start(),
|
||||
the name of the event along with one or more pairs of args each pair
|
||||
representing a 'type field_name;' field specification should be
|
||||
supplied. For synth_event_gen_cmd_array_start(), the name of the
|
||||
event along with an array of struct synth_field_desc should be
|
||||
supplied. Before calling synth_event_gen_cmd_start() or
|
||||
synth_event_gen_cmd_array_start(), the user should create and
|
||||
initialize a dynevent_cmd object using synth_event_cmd_init().
|
||||
|
||||
For example, to create a new "schedtest" synthetic event with two
|
||||
fields:
|
||||
|
||||
struct dynevent_cmd cmd;
|
||||
char *buf;
|
||||
|
||||
/* Create a buffer to hold the generated command */
|
||||
buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
|
||||
|
||||
/* Before generating the command, initialize the cmd object */
|
||||
synth_event_cmd_init(&cmd, buf, MAX_DYNEVENT_CMD_LEN);
|
||||
|
||||
ret = synth_event_gen_cmd_start(&cmd, "schedtest", THIS_MODULE,
|
||||
"pid_t", "next_pid_field",
|
||||
"u64", "ts_ns");
|
||||
|
||||
Alternatively, using an array of struct synth_field_desc fields
|
||||
containing the same information:
|
||||
|
||||
ret = synth_event_gen_cmd_array_start(&cmd, "schedtest", THIS_MODULE,
|
||||
fields, n_fields);
|
||||
|
||||
Once the synthetic event object has been created, it can then be
|
||||
populated with more fields. Fields are added one by one using
|
||||
synth_event_add_field(), supplying the dynevent_cmd object, a field
|
||||
type, and a field name. For example, to add a new int field named
|
||||
"intfield", the following call should be made:
|
||||
|
||||
ret = synth_event_add_field(&cmd, "int", "intfield");
|
||||
|
||||
See synth_field_size() for available types. If field_name contains [n]
|
||||
the field is considered to be an array.
|
||||
|
||||
A group of fields can also be added all at once using an array of
|
||||
synth_field_desc with add_synth_fields(). For example, this would add
|
||||
just the first four sched_fields:
|
||||
|
||||
ret = synth_event_add_fields(&cmd, sched_fields, 4);
|
||||
|
||||
If you already have a string of the form 'type field_name',
|
||||
synth_event_add_field_str() can be used to add it as-is; it will
|
||||
also automatically append a ';' to the string.
|
||||
|
||||
Once all the fields have been added, the event should be finalized and
|
||||
registered by calling the synth_event_gen_cmd_end() function:
|
||||
|
||||
ret = synth_event_gen_cmd_end(&cmd);
|
||||
|
||||
At this point, the event object is ready to be used for tracing new
|
||||
events.
|
||||
|
||||
6.3.3 Tracing synthetic events from in-kernel code
|
||||
--------------------------------------------------
|
||||
|
||||
To trace a synthetic event, there are several options. The first
|
||||
option is to trace the event in one call, using synth_event_trace()
|
||||
with a variable number of values, or synth_event_trace_array() with an
|
||||
array of values to be set. A second option can be used to avoid the
|
||||
need for a pre-formed array of values or list of arguments, via
|
||||
synth_event_trace_start() and synth_event_trace_end() along with
|
||||
synth_event_add_next_val() or synth_event_add_val() to add the values
|
||||
piecewise.
|
||||
|
||||
6.3.3.1 Tracing a synthetic event all at once
|
||||
---------------------------------------------
|
||||
|
||||
To trace a synthetic event all at once, the synth_event_trace() or
|
||||
synth_event_trace_array() functions can be used.
|
||||
|
||||
The synth_event_trace() function is passed the trace_event_file
|
||||
representing the synthetic event (which can be retrieved using
|
||||
trace_get_event_file() using the synthetic event name, "synthetic" as
|
||||
the system name, and the trace instance name (NULL if using the global
|
||||
trace array)), along with an variable number of u64 args, one for each
|
||||
synthetic event field, and the number of values being passed.
|
||||
|
||||
So, to trace an event corresponding to the synthetic event definition
|
||||
above, code like the following could be used:
|
||||
|
||||
ret = synth_event_trace(create_synth_test, 7, /* number of values */
|
||||
444, /* next_pid_field */
|
||||
(u64)"clackers", /* next_comm_field */
|
||||
1000000, /* ts_ns */
|
||||
1000, /* ts_ms */
|
||||
smp_processor_id(),/* cpu */
|
||||
(u64)"Thneed", /* my_string_field */
|
||||
999); /* my_int_field */
|
||||
|
||||
All vals should be cast to u64, and string vals are just pointers to
|
||||
strings, cast to u64. Strings will be copied into space reserved in
|
||||
the event for the string, using these pointers.
|
||||
|
||||
Alternatively, the synth_event_trace_array() function can be used to
|
||||
accomplish the same thing. It is passed the trace_event_file
|
||||
representing the synthetic event (which can be retrieved using
|
||||
trace_get_event_file() using the synthetic event name, "synthetic" as
|
||||
the system name, and the trace instance name (NULL if using the global
|
||||
trace array)), along with an array of u64, one for each synthetic
|
||||
event field.
|
||||
|
||||
To trace an event corresponding to the synthetic event definition
|
||||
above, code like the following could be used:
|
||||
|
||||
u64 vals[7];
|
||||
|
||||
vals[0] = 777; /* next_pid_field */
|
||||
vals[1] = (u64)"tiddlywinks"; /* next_comm_field */
|
||||
vals[2] = 1000000; /* ts_ns */
|
||||
vals[3] = 1000; /* ts_ms */
|
||||
vals[4] = smp_processor_id(); /* cpu */
|
||||
vals[5] = (u64)"thneed"; /* my_string_field */
|
||||
vals[6] = 398; /* my_int_field */
|
||||
|
||||
The 'vals' array is just an array of u64, the number of which must
|
||||
match the number of field in the synthetic event, and which must be in
|
||||
the same order as the synthetic event fields.
|
||||
|
||||
All vals should be cast to u64, and string vals are just pointers to
|
||||
strings, cast to u64. Strings will be copied into space reserved in
|
||||
the event for the string, using these pointers.
|
||||
|
||||
In order to trace a synthetic event, a pointer to the trace event file
|
||||
is needed. The trace_get_event_file() function can be used to get
|
||||
it - it will find the file in the given trace instance (in this case
|
||||
NULL since the top trace array is being used) while at the same time
|
||||
preventing the instance containing it from going away:
|
||||
|
||||
schedtest_event_file = trace_get_event_file(NULL, "synthetic",
|
||||
"schedtest");
|
||||
|
||||
Before tracing the event, it should be enabled in some way, otherwise
|
||||
the synthetic event won't actually show up in the trace buffer.
|
||||
|
||||
To enable a synthetic event from the kernel, trace_array_set_clr_event()
|
||||
can be used (which is not specific to synthetic events, so does need
|
||||
the "synthetic" system name to be specified explicitly).
|
||||
|
||||
To enable the event, pass 'true' to it:
|
||||
|
||||
trace_array_set_clr_event(schedtest_event_file->tr,
|
||||
"synthetic", "schedtest", true);
|
||||
|
||||
To disable it pass false:
|
||||
|
||||
trace_array_set_clr_event(schedtest_event_file->tr,
|
||||
"synthetic", "schedtest", false);
|
||||
|
||||
Finally, synth_event_trace_array() can be used to actually trace the
|
||||
event, which should be visible in the trace buffer afterwards:
|
||||
|
||||
ret = synth_event_trace_array(schedtest_event_file, vals,
|
||||
ARRAY_SIZE(vals));
|
||||
|
||||
To remove the synthetic event, the event should be disabled, and the
|
||||
trace instance should be 'put' back using trace_put_event_file():
|
||||
|
||||
trace_array_set_clr_event(schedtest_event_file->tr,
|
||||
"synthetic", "schedtest", false);
|
||||
trace_put_event_file(schedtest_event_file);
|
||||
|
||||
If those have been successful, synth_event_delete() can be called to
|
||||
remove the event:
|
||||
|
||||
ret = synth_event_delete("schedtest");
|
||||
|
||||
6.3.3.1 Tracing a synthetic event piecewise
|
||||
-------------------------------------------
|
||||
|
||||
To trace a synthetic using the piecewise method described above, the
|
||||
synth_event_trace_start() function is used to 'open' the synthetic
|
||||
event trace:
|
||||
|
||||
struct synth_trace_state trace_state;
|
||||
|
||||
ret = synth_event_trace_start(schedtest_event_file, &trace_state);
|
||||
|
||||
It's passed the trace_event_file representing the synthetic event
|
||||
using the same methods as described above, along with a pointer to a
|
||||
struct synth_trace_state object, which will be zeroed before use and
|
||||
used to maintain state between this and following calls.
|
||||
|
||||
Once the event has been opened, which means space for it has been
|
||||
reserved in the trace buffer, the individual fields can be set. There
|
||||
are two ways to do that, either one after another for each field in
|
||||
the event, which requires no lookups, or by name, which does. The
|
||||
tradeoff is flexibility in doing the assignments vs the cost of a
|
||||
lookup per field.
|
||||
|
||||
To assign the values one after the other without lookups,
|
||||
synth_event_add_next_val() should be used. Each call is passed the
|
||||
same synth_trace_state object used in the synth_event_trace_start(),
|
||||
along with the value to set the next field in the event. After each
|
||||
field is set, the 'cursor' points to the next field, which will be set
|
||||
by the subsequent call, continuing until all the fields have been set
|
||||
in order. The same sequence of calls as in the above examples using
|
||||
this method would be (without error-handling code):
|
||||
|
||||
/* next_pid_field */
|
||||
ret = synth_event_add_next_val(777, &trace_state);
|
||||
|
||||
/* next_comm_field */
|
||||
ret = synth_event_add_next_val((u64)"slinky", &trace_state);
|
||||
|
||||
/* ts_ns */
|
||||
ret = synth_event_add_next_val(1000000, &trace_state);
|
||||
|
||||
/* ts_ms */
|
||||
ret = synth_event_add_next_val(1000, &trace_state);
|
||||
|
||||
/* cpu */
|
||||
ret = synth_event_add_next_val(smp_processor_id(), &trace_state);
|
||||
|
||||
/* my_string_field */
|
||||
ret = synth_event_add_next_val((u64)"thneed_2.01", &trace_state);
|
||||
|
||||
/* my_int_field */
|
||||
ret = synth_event_add_next_val(395, &trace_state);
|
||||
|
||||
To assign the values in any order, synth_event_add_val() should be
|
||||
used. Each call is passed the same synth_trace_state object used in
|
||||
the synth_event_trace_start(), along with the field name of the field
|
||||
to set and the value to set it to. The same sequence of calls as in
|
||||
the above examples using this method would be (without error-handling
|
||||
code):
|
||||
|
||||
ret = synth_event_add_val("next_pid_field", 777, &trace_state);
|
||||
ret = synth_event_add_val("next_comm_field", (u64)"silly putty",
|
||||
&trace_state);
|
||||
ret = synth_event_add_val("ts_ns", 1000000, &trace_state);
|
||||
ret = synth_event_add_val("ts_ms", 1000, &trace_state);
|
||||
ret = synth_event_add_val("cpu", smp_processor_id(), &trace_state);
|
||||
ret = synth_event_add_val("my_string_field", (u64)"thneed_9",
|
||||
&trace_state);
|
||||
ret = synth_event_add_val("my_int_field", 3999, &trace_state);
|
||||
|
||||
Note that synth_event_add_next_val() and synth_event_add_val() are
|
||||
incompatible if used within the same trace of an event - either one
|
||||
can be used but not both at the same time.
|
||||
|
||||
Finally, the event won't be actually traced until it's 'closed',
|
||||
which is done using synth_event_trace_end(), which takes only the
|
||||
struct synth_trace_state object used in the previous calls:
|
||||
|
||||
ret = synth_event_trace_end(&trace_state);
|
||||
|
||||
Note that synth_event_trace_end() must be called at the end regardless
|
||||
of whether any of the add calls failed (say due to a bad field name
|
||||
being passed in).
|
||||
|
||||
6.3.4 Dyamically creating kprobe and kretprobe event definitions
|
||||
----------------------------------------------------------------
|
||||
|
||||
To create a kprobe or kretprobe trace event from kernel code, the
|
||||
kprobe_event_gen_cmd_start() or kretprobe_event_gen_cmd_start()
|
||||
functions can be used.
|
||||
|
||||
To create a kprobe event, an empty or partially empty kprobe event
|
||||
should first be created using kprobe_event_gen_cmd_start(). The name
|
||||
of the event and the probe location should be specfied along with one
|
||||
or args each representing a probe field should be supplied to this
|
||||
function. Before calling kprobe_event_gen_cmd_start(), the user
|
||||
should create and initialize a dynevent_cmd object using
|
||||
kprobe_event_cmd_init().
|
||||
|
||||
For example, to create a new "schedtest" kprobe event with two fields:
|
||||
|
||||
struct dynevent_cmd cmd;
|
||||
char *buf;
|
||||
|
||||
/* Create a buffer to hold the generated command */
|
||||
buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
|
||||
|
||||
/* Before generating the command, initialize the cmd object */
|
||||
kprobe_event_cmd_init(&cmd, buf, MAX_DYNEVENT_CMD_LEN);
|
||||
|
||||
/*
|
||||
* Define the gen_kprobe_test event with the first 2 kprobe
|
||||
* fields.
|
||||
*/
|
||||
ret = kprobe_event_gen_cmd_start(&cmd, "gen_kprobe_test", "do_sys_open",
|
||||
"dfd=%ax", "filename=%dx");
|
||||
|
||||
Once the kprobe event object has been created, it can then be
|
||||
populated with more fields. Fields can be added using
|
||||
kprobe_event_add_fields(), supplying the dynevent_cmd object along
|
||||
with a variable arg list of probe fields. For example, to add a
|
||||
couple additional fields, the following call could be made:
|
||||
|
||||
ret = kprobe_event_add_fields(&cmd, "flags=%cx", "mode=+4($stack)");
|
||||
|
||||
Once all the fields have been added, the event should be finalized and
|
||||
registered by calling the kprobe_event_gen_cmd_end() or
|
||||
kretprobe_event_gen_cmd_end() functions, depending on whether a kprobe
|
||||
or kretprobe command was started:
|
||||
|
||||
ret = kprobe_event_gen_cmd_end(&cmd);
|
||||
|
||||
or
|
||||
|
||||
ret = kretprobe_event_gen_cmd_end(&cmd);
|
||||
|
||||
At this point, the event object is ready to be used for tracing new
|
||||
events.
|
||||
|
||||
Similarly, a kretprobe event can be created using
|
||||
kretprobe_event_gen_cmd_start() with a probe name and location and
|
||||
additional params such as $retval:
|
||||
|
||||
ret = kretprobe_event_gen_cmd_start(&cmd, "gen_kretprobe_test",
|
||||
"do_sys_open", "$retval");
|
||||
|
||||
Similar to the synthetic event case, code like the following can be
|
||||
used to enable the newly created kprobe event:
|
||||
|
||||
gen_kprobe_test = trace_get_event_file(NULL, "kprobes", "gen_kprobe_test");
|
||||
|
||||
ret = trace_array_set_clr_event(gen_kprobe_test->tr,
|
||||
"kprobes", "gen_kprobe_test", true);
|
||||
|
||||
Finally, also similar to synthetic events, the following code can be
|
||||
used to give the kprobe event file back and delete the event:
|
||||
|
||||
trace_put_event_file(gen_kprobe_test);
|
||||
|
||||
ret = kprobe_event_delete("gen_kprobe_test");
|
||||
|
||||
6.3.4 The "dynevent_cmd" low-level API
|
||||
--------------------------------------
|
||||
|
||||
Both the in-kernel synthetic event and kprobe interfaces are built on
|
||||
top of a lower-level "dynevent_cmd" interface. This interface is
|
||||
meant to provide the basis for higher-level interfaces such as the
|
||||
synthetic and kprobe interfaces, which can be used as examples.
|
||||
|
||||
The basic idea is simple and amounts to providing a general-purpose
|
||||
layer that can be used to generate trace event commands. The
|
||||
generated command strings can then be passed to the command-parsing
|
||||
and event creation code that already exists in the trace event
|
||||
subystem for creating the corresponding trace events.
|
||||
|
||||
In a nutshell, the way it works is that the higher-level interface
|
||||
code creates a struct dynevent_cmd object, then uses a couple
|
||||
functions, dynevent_arg_add() and dynevent_arg_pair_add() to build up
|
||||
a command string, which finally causes the command to be executed
|
||||
using the dynevent_create() function. The details of the interface
|
||||
are described below.
|
||||
|
||||
The first step in building a new command string is to create and
|
||||
initialize an instance of a dynevent_cmd. Here, for instance, we
|
||||
create a dynevent_cmd on the stack and initialize it:
|
||||
|
||||
struct dynevent_cmd cmd;
|
||||
char *buf;
|
||||
int ret;
|
||||
|
||||
buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
|
||||
|
||||
dynevent_cmd_init(cmd, buf, maxlen, DYNEVENT_TYPE_FOO,
|
||||
foo_event_run_command);
|
||||
|
||||
The dynevent_cmd initialization needs to be given a user-specified
|
||||
buffer and the length of the buffer (MAX_DYNEVENT_CMD_LEN can be used
|
||||
for this purpose - at 2k it's generally too big to be comfortably put
|
||||
on the stack, so is dynamically allocated), a dynevent type id, which
|
||||
is meant to be used to check that further API calls are for the
|
||||
correct command type, and a pointer to an event-specific run_command()
|
||||
callback that will be called to actually execute the event-specific
|
||||
command function.
|
||||
|
||||
Once that's done, the command string can by built up by successive
|
||||
calls to argument-adding functions.
|
||||
|
||||
To add a single argument, define and initialize a struct dynevent_arg
|
||||
or struct dynevent_arg_pair object. Here's an example of the simplest
|
||||
possible arg addition, which is simply to append the given string as
|
||||
a whitespace-separated argument to the command:
|
||||
|
||||
struct dynevent_arg arg;
|
||||
|
||||
dynevent_arg_init(&arg, NULL, 0);
|
||||
|
||||
arg.str = name;
|
||||
|
||||
ret = dynevent_arg_add(cmd, &arg);
|
||||
|
||||
The arg object is first initialized using dynevent_arg_init() and in
|
||||
this case the parameters are NULL or 0, which means there's no
|
||||
optional sanity-checking function or separator appended to the end of
|
||||
the arg.
|
||||
|
||||
Here's another more complicated example using an 'arg pair', which is
|
||||
used to create an argument that consists of a couple components added
|
||||
together as a unit, for example, a 'type field_name;' arg or a simple
|
||||
expression arg e.g. 'flags=%cx':
|
||||
|
||||
struct dynevent_arg_pair arg_pair;
|
||||
|
||||
dynevent_arg_pair_init(&arg_pair, dynevent_foo_check_arg_fn, 0, ';');
|
||||
|
||||
arg_pair.lhs = type;
|
||||
arg_pair.rhs = name;
|
||||
|
||||
ret = dynevent_arg_pair_add(cmd, &arg_pair);
|
||||
|
||||
Again, the arg_pair is first initialized, in this case with a callback
|
||||
function used to check the sanity of the args (for example, that
|
||||
neither part of the pair is NULL), along with a character to be used
|
||||
to add an operator between the pair (here none) and a separator to be
|
||||
appended onto the end of the arg pair (here ';').
|
||||
|
||||
There's also a dynevent_str_add() function that can be used to simply
|
||||
add a string as-is, with no spaces, delimeters, or arg check.
|
||||
|
||||
Any number of dynevent_*_add() calls can be made to build up the string
|
||||
(until its length surpasses cmd->maxlen). When all the arguments have
|
||||
been added and the command string is complete, the only thing left to
|
||||
do is run the command, which happens by simply calling
|
||||
dynevent_create():
|
||||
|
||||
ret = dynevent_create(&cmd);
|
||||
|
||||
At that point, if the return value is 0, the dynamic event has been
|
||||
created and is ready to use.
|
||||
|
||||
See the dynevent_cmd function definitions themselves for the details
|
||||
of the API.
|
||||
|
|
|
@ -19,6 +19,7 @@ Linux Tracing Technologies
|
|||
events-msr
|
||||
mmiotrace
|
||||
histogram
|
||||
boottime-trace
|
||||
hwlat_detector
|
||||
intel_th
|
||||
stm
|
||||
|
|
|
@ -97,6 +97,7 @@ which shows given pointer in "symbol+offset" style.
|
|||
For $comm, the default type is "string"; any other type is invalid.
|
||||
|
||||
.. _user_mem_access:
|
||||
|
||||
User Memory Access
|
||||
------------------
|
||||
Kprobe events supports user-space memory access. For that purpose, you can use
|
||||
|
|
|
@ -15934,6 +15934,15 @@ S: Supported
|
|||
F: Documentation/networking/device_drivers/stmicro/
|
||||
F: drivers/net/ethernet/stmicro/stmmac/
|
||||
|
||||
EXTRA BOOT CONFIG
|
||||
M: Masami Hiramatsu <mhiramat@kernel.org>
|
||||
S: Maintained
|
||||
F: lib/bootconfig.c
|
||||
F: fs/proc/bootconfig.c
|
||||
F: include/linux/bootconfig.h
|
||||
F: tools/bootconfig/*
|
||||
F: Documentation/admin-guide/bootconfig.rst
|
||||
|
||||
SUN3/3X
|
||||
M: Sam Creasey <sammy@sammy.net>
|
||||
W: http://sammy.net/sun3/
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
|
||||
#define OP_BUFFER_FLAGS 0
|
||||
|
||||
static struct ring_buffer *op_ring_buffer;
|
||||
static struct trace_buffer *op_ring_buffer;
|
||||
DEFINE_PER_CPU(struct oprofile_cpu_buffer, op_cpu_buffer);
|
||||
|
||||
static void wq_sync_buffer(struct work_struct *work);
|
||||
|
|
|
@ -33,3 +33,4 @@ proc-$(CONFIG_PROC_KCORE) += kcore.o
|
|||
proc-$(CONFIG_PROC_VMCORE) += vmcore.o
|
||||
proc-$(CONFIG_PRINTK) += kmsg.o
|
||||
proc-$(CONFIG_PROC_PAGE_MONITOR) += page.o
|
||||
proc-$(CONFIG_BOOT_CONFIG) += bootconfig.o
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* /proc/bootconfig - Extra boot configuration
|
||||
*/
|
||||
#include <linux/fs.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/proc_fs.h>
|
||||
#include <linux/seq_file.h>
|
||||
#include <linux/bootconfig.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
static char *saved_boot_config;
|
||||
|
||||
static int boot_config_proc_show(struct seq_file *m, void *v)
|
||||
{
|
||||
if (saved_boot_config)
|
||||
seq_puts(m, saved_boot_config);
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Rest size of buffer */
|
||||
#define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0)
|
||||
|
||||
/* Return the needed total length if @size is 0 */
|
||||
static int __init copy_xbc_key_value_list(char *dst, size_t size)
|
||||
{
|
||||
struct xbc_node *leaf, *vnode;
|
||||
const char *val;
|
||||
char *key, *end = dst + size;
|
||||
int ret = 0;
|
||||
|
||||
key = kzalloc(XBC_KEYLEN_MAX, GFP_KERNEL);
|
||||
|
||||
xbc_for_each_key_value(leaf, val) {
|
||||
ret = xbc_node_compose_key(leaf, key, XBC_KEYLEN_MAX);
|
||||
if (ret < 0)
|
||||
break;
|
||||
ret = snprintf(dst, rest(dst, end), "%s = ", key);
|
||||
if (ret < 0)
|
||||
break;
|
||||
dst += ret;
|
||||
vnode = xbc_node_get_child(leaf);
|
||||
if (vnode && xbc_node_is_array(vnode)) {
|
||||
xbc_array_for_each_value(vnode, val) {
|
||||
ret = snprintf(dst, rest(dst, end), "\"%s\"%s",
|
||||
val, vnode->next ? ", " : "\n");
|
||||
if (ret < 0)
|
||||
goto out;
|
||||
dst += ret;
|
||||
}
|
||||
} else {
|
||||
ret = snprintf(dst, rest(dst, end), "\"%s\"\n", val);
|
||||
if (ret < 0)
|
||||
break;
|
||||
dst += ret;
|
||||
}
|
||||
}
|
||||
out:
|
||||
kfree(key);
|
||||
|
||||
return ret < 0 ? ret : dst - (end - size);
|
||||
}
|
||||
|
||||
static int __init proc_boot_config_init(void)
|
||||
{
|
||||
int len;
|
||||
|
||||
len = copy_xbc_key_value_list(NULL, 0);
|
||||
if (len < 0)
|
||||
return len;
|
||||
|
||||
if (len > 0) {
|
||||
saved_boot_config = kzalloc(len + 1, GFP_KERNEL);
|
||||
if (!saved_boot_config)
|
||||
return -ENOMEM;
|
||||
|
||||
len = copy_xbc_key_value_list(saved_boot_config, len + 1);
|
||||
if (len < 0) {
|
||||
kfree(saved_boot_config);
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
proc_create_single("bootconfig", 0, NULL, boot_config_proc_show);
|
||||
|
||||
return 0;
|
||||
}
|
||||
fs_initcall(proc_boot_config_init);
|
|
@ -0,0 +1,224 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _LINUX_XBC_H
|
||||
#define _LINUX_XBC_H
|
||||
/*
|
||||
* Extra Boot Config
|
||||
* Copyright (C) 2019 Linaro Ltd.
|
||||
* Author: Masami Hiramatsu <mhiramat@kernel.org>
|
||||
*/
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/types.h>
|
||||
|
||||
/* XBC tree node */
|
||||
struct xbc_node {
|
||||
u16 next;
|
||||
u16 child;
|
||||
u16 parent;
|
||||
u16 data;
|
||||
} __attribute__ ((__packed__));
|
||||
|
||||
#define XBC_KEY 0
|
||||
#define XBC_VALUE (1 << 15)
|
||||
/* Maximum size of boot config is 32KB - 1 */
|
||||
#define XBC_DATA_MAX (XBC_VALUE - 1)
|
||||
|
||||
#define XBC_NODE_MAX 1024
|
||||
#define XBC_KEYLEN_MAX 256
|
||||
#define XBC_DEPTH_MAX 16
|
||||
|
||||
/* Node tree access raw APIs */
|
||||
struct xbc_node * __init xbc_root_node(void);
|
||||
int __init xbc_node_index(struct xbc_node *node);
|
||||
struct xbc_node * __init xbc_node_get_parent(struct xbc_node *node);
|
||||
struct xbc_node * __init xbc_node_get_child(struct xbc_node *node);
|
||||
struct xbc_node * __init xbc_node_get_next(struct xbc_node *node);
|
||||
const char * __init xbc_node_get_data(struct xbc_node *node);
|
||||
|
||||
/**
|
||||
* xbc_node_is_value() - Test the node is a value node
|
||||
* @node: An XBC node.
|
||||
*
|
||||
* Test the @node is a value node and return true if a value node, false if not.
|
||||
*/
|
||||
static inline __init bool xbc_node_is_value(struct xbc_node *node)
|
||||
{
|
||||
return node->data & XBC_VALUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_node_is_key() - Test the node is a key node
|
||||
* @node: An XBC node.
|
||||
*
|
||||
* Test the @node is a key node and return true if a key node, false if not.
|
||||
*/
|
||||
static inline __init bool xbc_node_is_key(struct xbc_node *node)
|
||||
{
|
||||
return !xbc_node_is_value(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_node_is_array() - Test the node is an arraied value node
|
||||
* @node: An XBC node.
|
||||
*
|
||||
* Test the @node is an arraied value node.
|
||||
*/
|
||||
static inline __init bool xbc_node_is_array(struct xbc_node *node)
|
||||
{
|
||||
return xbc_node_is_value(node) && node->next != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_node_is_leaf() - Test the node is a leaf key node
|
||||
* @node: An XBC node.
|
||||
*
|
||||
* Test the @node is a leaf key node which is a key node and has a value node
|
||||
* or no child. Returns true if it is a leaf node, or false if not.
|
||||
*/
|
||||
static inline __init bool xbc_node_is_leaf(struct xbc_node *node)
|
||||
{
|
||||
return xbc_node_is_key(node) &&
|
||||
(!node->child || xbc_node_is_value(xbc_node_get_child(node)));
|
||||
}
|
||||
|
||||
/* Tree-based key-value access APIs */
|
||||
struct xbc_node * __init xbc_node_find_child(struct xbc_node *parent,
|
||||
const char *key);
|
||||
|
||||
const char * __init xbc_node_find_value(struct xbc_node *parent,
|
||||
const char *key,
|
||||
struct xbc_node **vnode);
|
||||
|
||||
struct xbc_node * __init xbc_node_find_next_leaf(struct xbc_node *root,
|
||||
struct xbc_node *leaf);
|
||||
|
||||
const char * __init xbc_node_find_next_key_value(struct xbc_node *root,
|
||||
struct xbc_node **leaf);
|
||||
|
||||
/**
|
||||
* xbc_find_value() - Find a value which matches the key
|
||||
* @key: Search key
|
||||
* @vnode: A container pointer of XBC value node.
|
||||
*
|
||||
* Search a value whose key matches @key from whole of XBC tree and return
|
||||
* the value if found. Found value node is stored in *@vnode.
|
||||
* Note that this can return 0-length string and store NULL in *@vnode for
|
||||
* key-only (non-value) entry.
|
||||
*/
|
||||
static inline const char * __init
|
||||
xbc_find_value(const char *key, struct xbc_node **vnode)
|
||||
{
|
||||
return xbc_node_find_value(NULL, key, vnode);
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_find_node() - Find a node which matches the key
|
||||
* @key: Search key
|
||||
*
|
||||
* Search a (key) node whose key matches @key from whole of XBC tree and
|
||||
* return the node if found. If not found, returns NULL.
|
||||
*/
|
||||
static inline struct xbc_node * __init xbc_find_node(const char *key)
|
||||
{
|
||||
return xbc_node_find_child(NULL, key);
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_array_for_each_value() - Iterate value nodes on an array
|
||||
* @anode: An XBC arraied value node
|
||||
* @value: A value
|
||||
*
|
||||
* Iterate array value nodes and values starts from @anode. This is expected to
|
||||
* be used with xbc_find_value() and xbc_node_find_value(), so that user can
|
||||
* process each array entry node.
|
||||
*/
|
||||
#define xbc_array_for_each_value(anode, value) \
|
||||
for (value = xbc_node_get_data(anode); anode != NULL ; \
|
||||
anode = xbc_node_get_next(anode), \
|
||||
value = anode ? xbc_node_get_data(anode) : NULL)
|
||||
|
||||
/**
|
||||
* xbc_node_for_each_child() - Iterate child nodes
|
||||
* @parent: An XBC node.
|
||||
* @child: Iterated XBC node.
|
||||
*
|
||||
* Iterate child nodes of @parent. Each child nodes are stored to @child.
|
||||
*/
|
||||
#define xbc_node_for_each_child(parent, child) \
|
||||
for (child = xbc_node_get_child(parent); child != NULL ; \
|
||||
child = xbc_node_get_next(child))
|
||||
|
||||
/**
|
||||
* xbc_node_for_each_array_value() - Iterate array entries of geven key
|
||||
* @node: An XBC node.
|
||||
* @key: A key string searched under @node
|
||||
* @anode: Iterated XBC node of array entry.
|
||||
* @value: Iterated value of array entry.
|
||||
*
|
||||
* Iterate array entries of given @key under @node. Each array entry node
|
||||
* is stroed to @anode and @value. If the @node doesn't have @key node,
|
||||
* it does nothing.
|
||||
* Note that even if the found key node has only one value (not array)
|
||||
* this executes block once. Hoever, if the found key node has no value
|
||||
* (key-only node), this does nothing. So don't use this for testing the
|
||||
* key-value pair existence.
|
||||
*/
|
||||
#define xbc_node_for_each_array_value(node, key, anode, value) \
|
||||
for (value = xbc_node_find_value(node, key, &anode); value != NULL; \
|
||||
anode = xbc_node_get_next(anode), \
|
||||
value = anode ? xbc_node_get_data(anode) : NULL)
|
||||
|
||||
/**
|
||||
* xbc_node_for_each_key_value() - Iterate key-value pairs under a node
|
||||
* @node: An XBC node.
|
||||
* @knode: Iterated key node
|
||||
* @value: Iterated value string
|
||||
*
|
||||
* Iterate key-value pairs under @node. Each key node and value string are
|
||||
* stored in @knode and @value respectively.
|
||||
*/
|
||||
#define xbc_node_for_each_key_value(node, knode, value) \
|
||||
for (knode = NULL, value = xbc_node_find_next_key_value(node, &knode);\
|
||||
knode != NULL; value = xbc_node_find_next_key_value(node, &knode))
|
||||
|
||||
/**
|
||||
* xbc_for_each_key_value() - Iterate key-value pairs
|
||||
* @knode: Iterated key node
|
||||
* @value: Iterated value string
|
||||
*
|
||||
* Iterate key-value pairs in whole XBC tree. Each key node and value string
|
||||
* are stored in @knode and @value respectively.
|
||||
*/
|
||||
#define xbc_for_each_key_value(knode, value) \
|
||||
xbc_node_for_each_key_value(NULL, knode, value)
|
||||
|
||||
/* Compose partial key */
|
||||
int __init xbc_node_compose_key_after(struct xbc_node *root,
|
||||
struct xbc_node *node, char *buf, size_t size);
|
||||
|
||||
/**
|
||||
* xbc_node_compose_key() - Compose full key string of the XBC node
|
||||
* @node: An XBC node.
|
||||
* @buf: A buffer to store the key.
|
||||
* @size: The size of the @buf.
|
||||
*
|
||||
* Compose the full-length key of the @node into @buf. Returns the total
|
||||
* length of the key stored in @buf. Or returns -EINVAL if @node is NULL,
|
||||
* and -ERANGE if the key depth is deeper than max depth.
|
||||
*/
|
||||
static inline int __init xbc_node_compose_key(struct xbc_node *node,
|
||||
char *buf, size_t size)
|
||||
{
|
||||
return xbc_node_compose_key_after(NULL, node, buf, size);
|
||||
}
|
||||
|
||||
/* XBC node initializer */
|
||||
int __init xbc_init(char *buf);
|
||||
|
||||
/* XBC cleanup data structures */
|
||||
void __init xbc_destroy_all(void);
|
||||
|
||||
/* Debug dump functions */
|
||||
void __init xbc_debug_dump(void);
|
||||
|
||||
#endif
|
|
@ -582,7 +582,7 @@ struct swevent_hlist {
|
|||
#define PERF_ATTACH_ITRACE 0x10
|
||||
|
||||
struct perf_cgroup;
|
||||
struct ring_buffer;
|
||||
struct perf_buffer;
|
||||
|
||||
struct pmu_event_list {
|
||||
raw_spinlock_t lock;
|
||||
|
@ -694,7 +694,7 @@ struct perf_event {
|
|||
struct mutex mmap_mutex;
|
||||
atomic_t mmap_count;
|
||||
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
struct list_head rb_entry;
|
||||
unsigned long rcu_batches;
|
||||
int rcu_pending;
|
||||
|
@ -854,7 +854,7 @@ struct perf_cpu_context {
|
|||
|
||||
struct perf_output_handle {
|
||||
struct perf_event *event;
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
unsigned long wakeup;
|
||||
unsigned long size;
|
||||
u64 aux_flags;
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#include <linux/seq_file.h>
|
||||
#include <linux/poll.h>
|
||||
|
||||
struct ring_buffer;
|
||||
struct trace_buffer;
|
||||
struct ring_buffer_iter;
|
||||
|
||||
/*
|
||||
|
@ -77,13 +77,13 @@ u64 ring_buffer_event_time_stamp(struct ring_buffer_event *event);
|
|||
* else
|
||||
* ring_buffer_unlock_commit(buffer, event);
|
||||
*/
|
||||
void ring_buffer_discard_commit(struct ring_buffer *buffer,
|
||||
void ring_buffer_discard_commit(struct trace_buffer *buffer,
|
||||
struct ring_buffer_event *event);
|
||||
|
||||
/*
|
||||
* size is in bytes for each per CPU buffer.
|
||||
*/
|
||||
struct ring_buffer *
|
||||
struct trace_buffer *
|
||||
__ring_buffer_alloc(unsigned long size, unsigned flags, struct lock_class_key *key);
|
||||
|
||||
/*
|
||||
|
@ -97,38 +97,38 @@ __ring_buffer_alloc(unsigned long size, unsigned flags, struct lock_class_key *k
|
|||
__ring_buffer_alloc((size), (flags), &__key); \
|
||||
})
|
||||
|
||||
int ring_buffer_wait(struct ring_buffer *buffer, int cpu, int full);
|
||||
__poll_t ring_buffer_poll_wait(struct ring_buffer *buffer, int cpu,
|
||||
int ring_buffer_wait(struct trace_buffer *buffer, int cpu, int full);
|
||||
__poll_t ring_buffer_poll_wait(struct trace_buffer *buffer, int cpu,
|
||||
struct file *filp, poll_table *poll_table);
|
||||
|
||||
|
||||
#define RING_BUFFER_ALL_CPUS -1
|
||||
|
||||
void ring_buffer_free(struct ring_buffer *buffer);
|
||||
void ring_buffer_free(struct trace_buffer *buffer);
|
||||
|
||||
int ring_buffer_resize(struct ring_buffer *buffer, unsigned long size, int cpu);
|
||||
int ring_buffer_resize(struct trace_buffer *buffer, unsigned long size, int cpu);
|
||||
|
||||
void ring_buffer_change_overwrite(struct ring_buffer *buffer, int val);
|
||||
void ring_buffer_change_overwrite(struct trace_buffer *buffer, int val);
|
||||
|
||||
struct ring_buffer_event *ring_buffer_lock_reserve(struct ring_buffer *buffer,
|
||||
struct ring_buffer_event *ring_buffer_lock_reserve(struct trace_buffer *buffer,
|
||||
unsigned long length);
|
||||
int ring_buffer_unlock_commit(struct ring_buffer *buffer,
|
||||
int ring_buffer_unlock_commit(struct trace_buffer *buffer,
|
||||
struct ring_buffer_event *event);
|
||||
int ring_buffer_write(struct ring_buffer *buffer,
|
||||
int ring_buffer_write(struct trace_buffer *buffer,
|
||||
unsigned long length, void *data);
|
||||
|
||||
void ring_buffer_nest_start(struct ring_buffer *buffer);
|
||||
void ring_buffer_nest_end(struct ring_buffer *buffer);
|
||||
void ring_buffer_nest_start(struct trace_buffer *buffer);
|
||||
void ring_buffer_nest_end(struct trace_buffer *buffer);
|
||||
|
||||
struct ring_buffer_event *
|
||||
ring_buffer_peek(struct ring_buffer *buffer, int cpu, u64 *ts,
|
||||
ring_buffer_peek(struct trace_buffer *buffer, int cpu, u64 *ts,
|
||||
unsigned long *lost_events);
|
||||
struct ring_buffer_event *
|
||||
ring_buffer_consume(struct ring_buffer *buffer, int cpu, u64 *ts,
|
||||
ring_buffer_consume(struct trace_buffer *buffer, int cpu, u64 *ts,
|
||||
unsigned long *lost_events);
|
||||
|
||||
struct ring_buffer_iter *
|
||||
ring_buffer_read_prepare(struct ring_buffer *buffer, int cpu, gfp_t flags);
|
||||
ring_buffer_read_prepare(struct trace_buffer *buffer, int cpu, gfp_t flags);
|
||||
void ring_buffer_read_prepare_sync(void);
|
||||
void ring_buffer_read_start(struct ring_buffer_iter *iter);
|
||||
void ring_buffer_read_finish(struct ring_buffer_iter *iter);
|
||||
|
@ -140,59 +140,59 @@ ring_buffer_read(struct ring_buffer_iter *iter, u64 *ts);
|
|||
void ring_buffer_iter_reset(struct ring_buffer_iter *iter);
|
||||
int ring_buffer_iter_empty(struct ring_buffer_iter *iter);
|
||||
|
||||
unsigned long ring_buffer_size(struct ring_buffer *buffer, int cpu);
|
||||
unsigned long ring_buffer_size(struct trace_buffer *buffer, int cpu);
|
||||
|
||||
void ring_buffer_reset_cpu(struct ring_buffer *buffer, int cpu);
|
||||
void ring_buffer_reset(struct ring_buffer *buffer);
|
||||
void ring_buffer_reset_cpu(struct trace_buffer *buffer, int cpu);
|
||||
void ring_buffer_reset(struct trace_buffer *buffer);
|
||||
|
||||
#ifdef CONFIG_RING_BUFFER_ALLOW_SWAP
|
||||
int ring_buffer_swap_cpu(struct ring_buffer *buffer_a,
|
||||
struct ring_buffer *buffer_b, int cpu);
|
||||
int ring_buffer_swap_cpu(struct trace_buffer *buffer_a,
|
||||
struct trace_buffer *buffer_b, int cpu);
|
||||
#else
|
||||
static inline int
|
||||
ring_buffer_swap_cpu(struct ring_buffer *buffer_a,
|
||||
struct ring_buffer *buffer_b, int cpu)
|
||||
ring_buffer_swap_cpu(struct trace_buffer *buffer_a,
|
||||
struct trace_buffer *buffer_b, int cpu)
|
||||
{
|
||||
return -ENODEV;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool ring_buffer_empty(struct ring_buffer *buffer);
|
||||
bool ring_buffer_empty_cpu(struct ring_buffer *buffer, int cpu);
|
||||
bool ring_buffer_empty(struct trace_buffer *buffer);
|
||||
bool ring_buffer_empty_cpu(struct trace_buffer *buffer, int cpu);
|
||||
|
||||
void ring_buffer_record_disable(struct ring_buffer *buffer);
|
||||
void ring_buffer_record_enable(struct ring_buffer *buffer);
|
||||
void ring_buffer_record_off(struct ring_buffer *buffer);
|
||||
void ring_buffer_record_on(struct ring_buffer *buffer);
|
||||
bool ring_buffer_record_is_on(struct ring_buffer *buffer);
|
||||
bool ring_buffer_record_is_set_on(struct ring_buffer *buffer);
|
||||
void ring_buffer_record_disable_cpu(struct ring_buffer *buffer, int cpu);
|
||||
void ring_buffer_record_enable_cpu(struct ring_buffer *buffer, int cpu);
|
||||
void ring_buffer_record_disable(struct trace_buffer *buffer);
|
||||
void ring_buffer_record_enable(struct trace_buffer *buffer);
|
||||
void ring_buffer_record_off(struct trace_buffer *buffer);
|
||||
void ring_buffer_record_on(struct trace_buffer *buffer);
|
||||
bool ring_buffer_record_is_on(struct trace_buffer *buffer);
|
||||
bool ring_buffer_record_is_set_on(struct trace_buffer *buffer);
|
||||
void ring_buffer_record_disable_cpu(struct trace_buffer *buffer, int cpu);
|
||||
void ring_buffer_record_enable_cpu(struct trace_buffer *buffer, int cpu);
|
||||
|
||||
u64 ring_buffer_oldest_event_ts(struct ring_buffer *buffer, int cpu);
|
||||
unsigned long ring_buffer_bytes_cpu(struct ring_buffer *buffer, int cpu);
|
||||
unsigned long ring_buffer_entries(struct ring_buffer *buffer);
|
||||
unsigned long ring_buffer_overruns(struct ring_buffer *buffer);
|
||||
unsigned long ring_buffer_entries_cpu(struct ring_buffer *buffer, int cpu);
|
||||
unsigned long ring_buffer_overrun_cpu(struct ring_buffer *buffer, int cpu);
|
||||
unsigned long ring_buffer_commit_overrun_cpu(struct ring_buffer *buffer, int cpu);
|
||||
unsigned long ring_buffer_dropped_events_cpu(struct ring_buffer *buffer, int cpu);
|
||||
unsigned long ring_buffer_read_events_cpu(struct ring_buffer *buffer, int cpu);
|
||||
u64 ring_buffer_oldest_event_ts(struct trace_buffer *buffer, int cpu);
|
||||
unsigned long ring_buffer_bytes_cpu(struct trace_buffer *buffer, int cpu);
|
||||
unsigned long ring_buffer_entries(struct trace_buffer *buffer);
|
||||
unsigned long ring_buffer_overruns(struct trace_buffer *buffer);
|
||||
unsigned long ring_buffer_entries_cpu(struct trace_buffer *buffer, int cpu);
|
||||
unsigned long ring_buffer_overrun_cpu(struct trace_buffer *buffer, int cpu);
|
||||
unsigned long ring_buffer_commit_overrun_cpu(struct trace_buffer *buffer, int cpu);
|
||||
unsigned long ring_buffer_dropped_events_cpu(struct trace_buffer *buffer, int cpu);
|
||||
unsigned long ring_buffer_read_events_cpu(struct trace_buffer *buffer, int cpu);
|
||||
|
||||
u64 ring_buffer_time_stamp(struct ring_buffer *buffer, int cpu);
|
||||
void ring_buffer_normalize_time_stamp(struct ring_buffer *buffer,
|
||||
u64 ring_buffer_time_stamp(struct trace_buffer *buffer, int cpu);
|
||||
void ring_buffer_normalize_time_stamp(struct trace_buffer *buffer,
|
||||
int cpu, u64 *ts);
|
||||
void ring_buffer_set_clock(struct ring_buffer *buffer,
|
||||
void ring_buffer_set_clock(struct trace_buffer *buffer,
|
||||
u64 (*clock)(void));
|
||||
void ring_buffer_set_time_stamp_abs(struct ring_buffer *buffer, bool abs);
|
||||
bool ring_buffer_time_stamp_abs(struct ring_buffer *buffer);
|
||||
void ring_buffer_set_time_stamp_abs(struct trace_buffer *buffer, bool abs);
|
||||
bool ring_buffer_time_stamp_abs(struct trace_buffer *buffer);
|
||||
|
||||
size_t ring_buffer_nr_pages(struct ring_buffer *buffer, int cpu);
|
||||
size_t ring_buffer_nr_dirty_pages(struct ring_buffer *buffer, int cpu);
|
||||
size_t ring_buffer_nr_pages(struct trace_buffer *buffer, int cpu);
|
||||
size_t ring_buffer_nr_dirty_pages(struct trace_buffer *buffer, int cpu);
|
||||
|
||||
void *ring_buffer_alloc_read_page(struct ring_buffer *buffer, int cpu);
|
||||
void ring_buffer_free_read_page(struct ring_buffer *buffer, int cpu, void *data);
|
||||
int ring_buffer_read_page(struct ring_buffer *buffer, void **data_page,
|
||||
void *ring_buffer_alloc_read_page(struct trace_buffer *buffer, int cpu);
|
||||
void ring_buffer_free_read_page(struct trace_buffer *buffer, int cpu, void *data);
|
||||
int ring_buffer_read_page(struct trace_buffer *buffer, void **data_page,
|
||||
size_t len, int cpu, int full);
|
||||
|
||||
struct trace_seq;
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
#include <linux/tracepoint.h>
|
||||
|
||||
struct trace_array;
|
||||
struct trace_buffer;
|
||||
struct array_buffer;
|
||||
struct tracer;
|
||||
struct dentry;
|
||||
struct bpf_prog;
|
||||
|
@ -79,7 +79,7 @@ struct trace_entry {
|
|||
struct trace_iterator {
|
||||
struct trace_array *tr;
|
||||
struct tracer *trace;
|
||||
struct trace_buffer *trace_buffer;
|
||||
struct array_buffer *array_buffer;
|
||||
void *private;
|
||||
int cpu_file;
|
||||
struct mutex mutex;
|
||||
|
@ -153,7 +153,7 @@ void tracing_generic_entry_update(struct trace_entry *entry,
|
|||
struct trace_event_file;
|
||||
|
||||
struct ring_buffer_event *
|
||||
trace_event_buffer_lock_reserve(struct ring_buffer **current_buffer,
|
||||
trace_event_buffer_lock_reserve(struct trace_buffer **current_buffer,
|
||||
struct trace_event_file *trace_file,
|
||||
int type, unsigned long len,
|
||||
unsigned long flags, int pc);
|
||||
|
@ -226,12 +226,13 @@ extern int trace_event_reg(struct trace_event_call *event,
|
|||
enum trace_reg type, void *data);
|
||||
|
||||
struct trace_event_buffer {
|
||||
struct ring_buffer *buffer;
|
||||
struct trace_buffer *buffer;
|
||||
struct ring_buffer_event *event;
|
||||
struct trace_event_file *trace_file;
|
||||
void *entry;
|
||||
unsigned long flags;
|
||||
int pc;
|
||||
struct pt_regs *regs;
|
||||
};
|
||||
|
||||
void *trace_event_buffer_reserve(struct trace_event_buffer *fbuffer,
|
||||
|
@ -364,6 +365,128 @@ enum {
|
|||
EVENT_FILE_FL_WAS_ENABLED_BIT,
|
||||
};
|
||||
|
||||
extern struct trace_event_file *trace_get_event_file(const char *instance,
|
||||
const char *system,
|
||||
const char *event);
|
||||
extern void trace_put_event_file(struct trace_event_file *file);
|
||||
|
||||
#define MAX_DYNEVENT_CMD_LEN (2048)
|
||||
|
||||
enum dynevent_type {
|
||||
DYNEVENT_TYPE_SYNTH = 1,
|
||||
DYNEVENT_TYPE_KPROBE,
|
||||
DYNEVENT_TYPE_NONE,
|
||||
};
|
||||
|
||||
struct dynevent_cmd;
|
||||
|
||||
typedef int (*dynevent_create_fn_t)(struct dynevent_cmd *cmd);
|
||||
|
||||
struct dynevent_cmd {
|
||||
struct seq_buf seq;
|
||||
const char *event_name;
|
||||
unsigned int n_fields;
|
||||
enum dynevent_type type;
|
||||
dynevent_create_fn_t run_command;
|
||||
void *private_data;
|
||||
};
|
||||
|
||||
extern int dynevent_create(struct dynevent_cmd *cmd);
|
||||
|
||||
extern int synth_event_delete(const char *name);
|
||||
|
||||
extern void synth_event_cmd_init(struct dynevent_cmd *cmd,
|
||||
char *buf, int maxlen);
|
||||
|
||||
extern int __synth_event_gen_cmd_start(struct dynevent_cmd *cmd,
|
||||
const char *name,
|
||||
struct module *mod, ...);
|
||||
|
||||
#define synth_event_gen_cmd_start(cmd, name, mod, ...) \
|
||||
__synth_event_gen_cmd_start(cmd, name, mod, ## __VA_ARGS__, NULL)
|
||||
|
||||
struct synth_field_desc {
|
||||
const char *type;
|
||||
const char *name;
|
||||
};
|
||||
|
||||
extern int synth_event_gen_cmd_array_start(struct dynevent_cmd *cmd,
|
||||
const char *name,
|
||||
struct module *mod,
|
||||
struct synth_field_desc *fields,
|
||||
unsigned int n_fields);
|
||||
extern int synth_event_create(const char *name,
|
||||
struct synth_field_desc *fields,
|
||||
unsigned int n_fields, struct module *mod);
|
||||
|
||||
extern int synth_event_add_field(struct dynevent_cmd *cmd,
|
||||
const char *type,
|
||||
const char *name);
|
||||
extern int synth_event_add_field_str(struct dynevent_cmd *cmd,
|
||||
const char *type_name);
|
||||
extern int synth_event_add_fields(struct dynevent_cmd *cmd,
|
||||
struct synth_field_desc *fields,
|
||||
unsigned int n_fields);
|
||||
|
||||
#define synth_event_gen_cmd_end(cmd) \
|
||||
dynevent_create(cmd)
|
||||
|
||||
struct synth_event;
|
||||
|
||||
struct synth_event_trace_state {
|
||||
struct trace_event_buffer fbuffer;
|
||||
struct synth_trace_event *entry;
|
||||
struct trace_buffer *buffer;
|
||||
struct synth_event *event;
|
||||
unsigned int cur_field;
|
||||
unsigned int n_u64;
|
||||
bool enabled;
|
||||
bool add_next;
|
||||
bool add_name;
|
||||
};
|
||||
|
||||
extern int synth_event_trace(struct trace_event_file *file,
|
||||
unsigned int n_vals, ...);
|
||||
extern int synth_event_trace_array(struct trace_event_file *file, u64 *vals,
|
||||
unsigned int n_vals);
|
||||
extern int synth_event_trace_start(struct trace_event_file *file,
|
||||
struct synth_event_trace_state *trace_state);
|
||||
extern int synth_event_add_next_val(u64 val,
|
||||
struct synth_event_trace_state *trace_state);
|
||||
extern int synth_event_add_val(const char *field_name, u64 val,
|
||||
struct synth_event_trace_state *trace_state);
|
||||
extern int synth_event_trace_end(struct synth_event_trace_state *trace_state);
|
||||
|
||||
extern int kprobe_event_delete(const char *name);
|
||||
|
||||
extern void kprobe_event_cmd_init(struct dynevent_cmd *cmd,
|
||||
char *buf, int maxlen);
|
||||
|
||||
#define kprobe_event_gen_cmd_start(cmd, name, loc, ...) \
|
||||
__kprobe_event_gen_cmd_start(cmd, false, name, loc, ## __VA_ARGS__, NULL)
|
||||
|
||||
#define kretprobe_event_gen_cmd_start(cmd, name, loc, ...) \
|
||||
__kprobe_event_gen_cmd_start(cmd, true, name, loc, ## __VA_ARGS__, NULL)
|
||||
|
||||
extern int __kprobe_event_gen_cmd_start(struct dynevent_cmd *cmd,
|
||||
bool kretprobe,
|
||||
const char *name,
|
||||
const char *loc, ...);
|
||||
|
||||
#define kprobe_event_add_fields(cmd, ...) \
|
||||
__kprobe_event_add_fields(cmd, ## __VA_ARGS__, NULL)
|
||||
|
||||
#define kprobe_event_add_field(cmd, field) \
|
||||
__kprobe_event_add_fields(cmd, field, NULL)
|
||||
|
||||
extern int __kprobe_event_add_fields(struct dynevent_cmd *cmd, ...);
|
||||
|
||||
#define kprobe_event_gen_cmd_end(cmd) \
|
||||
dynevent_create(cmd)
|
||||
|
||||
#define kretprobe_event_gen_cmd_end(cmd) \
|
||||
dynevent_create(cmd)
|
||||
|
||||
/*
|
||||
* Event file flags:
|
||||
* ENABLED - The event is enabled
|
||||
|
|
|
@ -2,7 +2,8 @@
|
|||
/*
|
||||
* Stage 1 of the trace events.
|
||||
*
|
||||
* Override the macros in <trace/trace_events.h> to include the following:
|
||||
* Override the macros in the event tracepoint header <trace/events/XXX.h>
|
||||
* to include the following:
|
||||
*
|
||||
* struct trace_event_raw_<call> {
|
||||
* struct trace_entry ent;
|
||||
|
@ -223,7 +224,8 @@ TRACE_MAKE_SYSTEM_STR();
|
|||
/*
|
||||
* Stage 3 of the trace events.
|
||||
*
|
||||
* Override the macros in <trace/trace_events.h> to include the following:
|
||||
* Override the macros in the event tracepoint header <trace/events/XXX.h>
|
||||
* to include the following:
|
||||
*
|
||||
* enum print_line_t
|
||||
* trace_raw_output_<call>(struct trace_iterator *iter, int flags)
|
||||
|
@ -533,7 +535,8 @@ static inline notrace int trace_event_get_offsets_##call( \
|
|||
/*
|
||||
* Stage 4 of the trace events.
|
||||
*
|
||||
* Override the macros in <trace/trace_events.h> to include the following:
|
||||
* Override the macros in the event tracepoint header <trace/events/XXX.h>
|
||||
* to include the following:
|
||||
*
|
||||
* For those macros defined with TRACE_EVENT:
|
||||
*
|
||||
|
@ -548,7 +551,7 @@ static inline notrace int trace_event_get_offsets_##call( \
|
|||
* enum event_trigger_type __tt = ETT_NONE;
|
||||
* struct ring_buffer_event *event;
|
||||
* struct trace_event_raw_<call> *entry; <-- defined in stage 1
|
||||
* struct ring_buffer *buffer;
|
||||
* struct trace_buffer *buffer;
|
||||
* unsigned long irq_flags;
|
||||
* int __data_size;
|
||||
* int pc;
|
||||
|
|
14
init/Kconfig
14
init/Kconfig
|
@ -1224,6 +1224,20 @@ source "usr/Kconfig"
|
|||
|
||||
endif
|
||||
|
||||
config BOOT_CONFIG
|
||||
bool "Boot config support"
|
||||
depends on BLK_DEV_INITRD
|
||||
select LIBXBC
|
||||
default y
|
||||
help
|
||||
Extra boot config allows system admin to pass a config file as
|
||||
complemental extension of kernel cmdline when booting.
|
||||
The boot config file must be attached at the end of initramfs
|
||||
with checksum and size.
|
||||
See <file:Documentation/admin-guide/bootconfig.rst> for details.
|
||||
|
||||
If unsure, say Y.
|
||||
|
||||
choice
|
||||
prompt "Compiler optimization level"
|
||||
default CC_OPTIMIZE_FOR_PERFORMANCE
|
||||
|
|
229
init/main.c
229
init/main.c
|
@ -28,6 +28,7 @@
|
|||
#include <linux/initrd.h>
|
||||
#include <linux/memblock.h>
|
||||
#include <linux/acpi.h>
|
||||
#include <linux/bootconfig.h>
|
||||
#include <linux/console.h>
|
||||
#include <linux/nmi.h>
|
||||
#include <linux/percpu.h>
|
||||
|
@ -136,8 +137,10 @@ char __initdata boot_command_line[COMMAND_LINE_SIZE];
|
|||
char *saved_command_line;
|
||||
/* Command line for parameter parsing */
|
||||
static char *static_command_line;
|
||||
/* Command line for per-initcall parameter parsing */
|
||||
static char *initcall_command_line;
|
||||
/* Untouched extra command line */
|
||||
static char *extra_command_line;
|
||||
/* Extra init arguments */
|
||||
static char *extra_init_args;
|
||||
|
||||
static char *execute_command;
|
||||
static char *ramdisk_execute_command;
|
||||
|
@ -245,6 +248,156 @@ static int __init loglevel(char *str)
|
|||
|
||||
early_param("loglevel", loglevel);
|
||||
|
||||
#ifdef CONFIG_BOOT_CONFIG
|
||||
|
||||
char xbc_namebuf[XBC_KEYLEN_MAX] __initdata;
|
||||
|
||||
#define rest(dst, end) ((end) > (dst) ? (end) - (dst) : 0)
|
||||
|
||||
static int __init xbc_snprint_cmdline(char *buf, size_t size,
|
||||
struct xbc_node *root)
|
||||
{
|
||||
struct xbc_node *knode, *vnode;
|
||||
char *end = buf + size;
|
||||
char c = '\"';
|
||||
const char *val;
|
||||
int ret;
|
||||
|
||||
xbc_node_for_each_key_value(root, knode, val) {
|
||||
ret = xbc_node_compose_key_after(root, knode,
|
||||
xbc_namebuf, XBC_KEYLEN_MAX);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
vnode = xbc_node_get_child(knode);
|
||||
ret = snprintf(buf, rest(buf, end), "%s%c", xbc_namebuf,
|
||||
vnode ? '=' : ' ');
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
buf += ret;
|
||||
if (!vnode)
|
||||
continue;
|
||||
|
||||
c = '\"';
|
||||
xbc_array_for_each_value(vnode, val) {
|
||||
ret = snprintf(buf, rest(buf, end), "%c%s", c, val);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
buf += ret;
|
||||
c = ',';
|
||||
}
|
||||
if (rest(buf, end) > 2)
|
||||
strcpy(buf, "\" ");
|
||||
buf += 2;
|
||||
}
|
||||
|
||||
return buf - (end - size);
|
||||
}
|
||||
#undef rest
|
||||
|
||||
/* Make an extra command line under given key word */
|
||||
static char * __init xbc_make_cmdline(const char *key)
|
||||
{
|
||||
struct xbc_node *root;
|
||||
char *new_cmdline;
|
||||
int ret, len = 0;
|
||||
|
||||
root = xbc_find_node(key);
|
||||
if (!root)
|
||||
return NULL;
|
||||
|
||||
/* Count required buffer size */
|
||||
len = xbc_snprint_cmdline(NULL, 0, root);
|
||||
if (len <= 0)
|
||||
return NULL;
|
||||
|
||||
new_cmdline = memblock_alloc(len + 1, SMP_CACHE_BYTES);
|
||||
if (!new_cmdline) {
|
||||
pr_err("Failed to allocate memory for extra kernel cmdline.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
ret = xbc_snprint_cmdline(new_cmdline, len + 1, root);
|
||||
if (ret < 0 || ret > len) {
|
||||
pr_err("Failed to print extra kernel cmdline.\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return new_cmdline;
|
||||
}
|
||||
|
||||
u32 boot_config_checksum(unsigned char *p, u32 size)
|
||||
{
|
||||
u32 ret = 0;
|
||||
|
||||
while (size--)
|
||||
ret += *p++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __init setup_boot_config(const char *cmdline)
|
||||
{
|
||||
u32 size, csum;
|
||||
char *data, *copy;
|
||||
const char *p;
|
||||
u32 *hdr;
|
||||
int ret;
|
||||
|
||||
p = strstr(cmdline, "bootconfig");
|
||||
if (!p || (p != cmdline && !isspace(*(p-1))) ||
|
||||
(p[10] && !isspace(p[10])))
|
||||
return;
|
||||
|
||||
if (!initrd_end)
|
||||
goto not_found;
|
||||
|
||||
hdr = (u32 *)(initrd_end - 8);
|
||||
size = hdr[0];
|
||||
csum = hdr[1];
|
||||
|
||||
if (size >= XBC_DATA_MAX) {
|
||||
pr_err("bootconfig size %d greater than max size %d\n",
|
||||
size, XBC_DATA_MAX);
|
||||
return;
|
||||
}
|
||||
|
||||
data = ((void *)hdr) - size;
|
||||
if ((unsigned long)data < initrd_start)
|
||||
goto not_found;
|
||||
|
||||
if (boot_config_checksum((unsigned char *)data, size) != csum) {
|
||||
pr_err("bootconfig checksum failed\n");
|
||||
return;
|
||||
}
|
||||
|
||||
copy = memblock_alloc(size + 1, SMP_CACHE_BYTES);
|
||||
if (!copy) {
|
||||
pr_err("Failed to allocate memory for bootconfig\n");
|
||||
return;
|
||||
}
|
||||
|
||||
memcpy(copy, data, size);
|
||||
copy[size] = '\0';
|
||||
|
||||
ret = xbc_init(copy);
|
||||
if (ret < 0)
|
||||
pr_err("Failed to parse bootconfig\n");
|
||||
else {
|
||||
pr_info("Load bootconfig: %d bytes %d nodes\n", size, ret);
|
||||
/* keys starting with "kernel." are passed via cmdline */
|
||||
extra_command_line = xbc_make_cmdline("kernel");
|
||||
/* Also, "init." keys are init arguments */
|
||||
extra_init_args = xbc_make_cmdline("init");
|
||||
}
|
||||
return;
|
||||
not_found:
|
||||
pr_err("'bootconfig' found on command line, but no bootconfig found\n");
|
||||
}
|
||||
#else
|
||||
#define setup_boot_config(cmdline) do { } while (0)
|
||||
#endif
|
||||
|
||||
/* Change NUL term back to "=", to make "param" the whole string. */
|
||||
static void __init repair_env_string(char *param, char *val)
|
||||
{
|
||||
|
@ -373,22 +526,50 @@ static inline void smp_prepare_cpus(unsigned int maxcpus) { }
|
|||
*/
|
||||
static void __init setup_command_line(char *command_line)
|
||||
{
|
||||
size_t len = strlen(boot_command_line) + 1;
|
||||
size_t len, xlen = 0, ilen = 0;
|
||||
|
||||
saved_command_line = memblock_alloc(len, SMP_CACHE_BYTES);
|
||||
if (extra_command_line)
|
||||
xlen = strlen(extra_command_line);
|
||||
if (extra_init_args)
|
||||
ilen = strlen(extra_init_args) + 4; /* for " -- " */
|
||||
|
||||
len = xlen + strlen(boot_command_line) + 1;
|
||||
|
||||
saved_command_line = memblock_alloc(len + ilen, SMP_CACHE_BYTES);
|
||||
if (!saved_command_line)
|
||||
panic("%s: Failed to allocate %zu bytes\n", __func__, len);
|
||||
|
||||
initcall_command_line = memblock_alloc(len, SMP_CACHE_BYTES);
|
||||
if (!initcall_command_line)
|
||||
panic("%s: Failed to allocate %zu bytes\n", __func__, len);
|
||||
panic("%s: Failed to allocate %zu bytes\n", __func__, len + ilen);
|
||||
|
||||
static_command_line = memblock_alloc(len, SMP_CACHE_BYTES);
|
||||
if (!static_command_line)
|
||||
panic("%s: Failed to allocate %zu bytes\n", __func__, len);
|
||||
|
||||
strcpy(saved_command_line, boot_command_line);
|
||||
strcpy(static_command_line, command_line);
|
||||
if (xlen) {
|
||||
/*
|
||||
* We have to put extra_command_line before boot command
|
||||
* lines because there could be dashes (separator of init
|
||||
* command line) in the command lines.
|
||||
*/
|
||||
strcpy(saved_command_line, extra_command_line);
|
||||
strcpy(static_command_line, extra_command_line);
|
||||
}
|
||||
strcpy(saved_command_line + xlen, boot_command_line);
|
||||
strcpy(static_command_line + xlen, command_line);
|
||||
|
||||
if (ilen) {
|
||||
/*
|
||||
* Append supplemental init boot args to saved_command_line
|
||||
* so that user can check what command line options passed
|
||||
* to init.
|
||||
*/
|
||||
len = strlen(saved_command_line);
|
||||
if (!strstr(boot_command_line, " -- ")) {
|
||||
strcpy(saved_command_line + len, " -- ");
|
||||
len += 4;
|
||||
} else
|
||||
saved_command_line[len++] = ' ';
|
||||
|
||||
strcpy(saved_command_line + len, extra_init_args);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -595,6 +776,7 @@ asmlinkage __visible void __init start_kernel(void)
|
|||
pr_notice("%s", linux_banner);
|
||||
early_security_init();
|
||||
setup_arch(&command_line);
|
||||
setup_boot_config(command_line);
|
||||
setup_command_line(command_line);
|
||||
setup_nr_cpu_ids();
|
||||
setup_per_cpu_areas();
|
||||
|
@ -604,7 +786,7 @@ asmlinkage __visible void __init start_kernel(void)
|
|||
build_all_zonelists(NULL);
|
||||
page_alloc_init();
|
||||
|
||||
pr_notice("Kernel command line: %s\n", boot_command_line);
|
||||
pr_notice("Kernel command line: %s\n", saved_command_line);
|
||||
/* parameters may set static keys */
|
||||
jump_label_init();
|
||||
parse_early_param();
|
||||
|
@ -615,6 +797,9 @@ asmlinkage __visible void __init start_kernel(void)
|
|||
if (!IS_ERR_OR_NULL(after_dashes))
|
||||
parse_args("Setting init args", after_dashes, NULL, 0, -1, -1,
|
||||
NULL, set_init_arg);
|
||||
if (extra_init_args)
|
||||
parse_args("Setting extra init args", extra_init_args,
|
||||
NULL, 0, -1, -1, NULL, set_init_arg);
|
||||
|
||||
/*
|
||||
* These use large bootmem allocations and must precede
|
||||
|
@ -996,13 +1181,12 @@ static int __init ignore_unknown_bootoption(char *param, char *val,
|
|||
return 0;
|
||||
}
|
||||
|
||||
static void __init do_initcall_level(int level)
|
||||
static void __init do_initcall_level(int level, char *command_line)
|
||||
{
|
||||
initcall_entry_t *fn;
|
||||
|
||||
strcpy(initcall_command_line, saved_command_line);
|
||||
parse_args(initcall_level_names[level],
|
||||
initcall_command_line, __start___param,
|
||||
command_line, __start___param,
|
||||
__stop___param - __start___param,
|
||||
level, level,
|
||||
NULL, ignore_unknown_bootoption);
|
||||
|
@ -1015,9 +1199,20 @@ static void __init do_initcall_level(int level)
|
|||
static void __init do_initcalls(void)
|
||||
{
|
||||
int level;
|
||||
size_t len = strlen(saved_command_line) + 1;
|
||||
char *command_line;
|
||||
|
||||
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++)
|
||||
do_initcall_level(level);
|
||||
command_line = kzalloc(len, GFP_KERNEL);
|
||||
if (!command_line)
|
||||
panic("%s: Failed to allocate %zu bytes\n", __func__, len);
|
||||
|
||||
for (level = 0; level < ARRAY_SIZE(initcall_levels) - 1; level++) {
|
||||
/* Parser modifies command_line, restore it each time */
|
||||
strcpy(command_line, saved_command_line);
|
||||
do_initcall_level(level, command_line);
|
||||
}
|
||||
|
||||
kfree(command_line);
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -4373,7 +4373,7 @@ static void free_event_rcu(struct rcu_head *head)
|
|||
}
|
||||
|
||||
static void ring_buffer_attach(struct perf_event *event,
|
||||
struct ring_buffer *rb);
|
||||
struct perf_buffer *rb);
|
||||
|
||||
static void detach_sb_event(struct perf_event *event)
|
||||
{
|
||||
|
@ -5054,7 +5054,7 @@ perf_read(struct file *file, char __user *buf, size_t count, loff_t *ppos)
|
|||
static __poll_t perf_poll(struct file *file, poll_table *wait)
|
||||
{
|
||||
struct perf_event *event = file->private_data;
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
__poll_t events = EPOLLHUP;
|
||||
|
||||
poll_wait(file, &event->waitq, wait);
|
||||
|
@ -5296,7 +5296,7 @@ static long _perf_ioctl(struct perf_event *event, unsigned int cmd, unsigned lon
|
|||
return perf_event_set_bpf_prog(event, arg);
|
||||
|
||||
case PERF_EVENT_IOC_PAUSE_OUTPUT: {
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
|
||||
rcu_read_lock();
|
||||
rb = rcu_dereference(event->rb);
|
||||
|
@ -5432,7 +5432,7 @@ static void calc_timer_values(struct perf_event *event,
|
|||
static void perf_event_init_userpage(struct perf_event *event)
|
||||
{
|
||||
struct perf_event_mmap_page *userpg;
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
|
||||
rcu_read_lock();
|
||||
rb = rcu_dereference(event->rb);
|
||||
|
@ -5464,7 +5464,7 @@ void __weak arch_perf_update_userpage(
|
|||
void perf_event_update_userpage(struct perf_event *event)
|
||||
{
|
||||
struct perf_event_mmap_page *userpg;
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
u64 enabled, running, now;
|
||||
|
||||
rcu_read_lock();
|
||||
|
@ -5515,7 +5515,7 @@ EXPORT_SYMBOL_GPL(perf_event_update_userpage);
|
|||
static vm_fault_t perf_mmap_fault(struct vm_fault *vmf)
|
||||
{
|
||||
struct perf_event *event = vmf->vma->vm_file->private_data;
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
vm_fault_t ret = VM_FAULT_SIGBUS;
|
||||
|
||||
if (vmf->flags & FAULT_FLAG_MKWRITE) {
|
||||
|
@ -5548,9 +5548,9 @@ static vm_fault_t perf_mmap_fault(struct vm_fault *vmf)
|
|||
}
|
||||
|
||||
static void ring_buffer_attach(struct perf_event *event,
|
||||
struct ring_buffer *rb)
|
||||
struct perf_buffer *rb)
|
||||
{
|
||||
struct ring_buffer *old_rb = NULL;
|
||||
struct perf_buffer *old_rb = NULL;
|
||||
unsigned long flags;
|
||||
|
||||
if (event->rb) {
|
||||
|
@ -5608,7 +5608,7 @@ static void ring_buffer_attach(struct perf_event *event,
|
|||
|
||||
static void ring_buffer_wakeup(struct perf_event *event)
|
||||
{
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
|
||||
rcu_read_lock();
|
||||
rb = rcu_dereference(event->rb);
|
||||
|
@ -5619,9 +5619,9 @@ static void ring_buffer_wakeup(struct perf_event *event)
|
|||
rcu_read_unlock();
|
||||
}
|
||||
|
||||
struct ring_buffer *ring_buffer_get(struct perf_event *event)
|
||||
struct perf_buffer *ring_buffer_get(struct perf_event *event)
|
||||
{
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
|
||||
rcu_read_lock();
|
||||
rb = rcu_dereference(event->rb);
|
||||
|
@ -5634,7 +5634,7 @@ struct ring_buffer *ring_buffer_get(struct perf_event *event)
|
|||
return rb;
|
||||
}
|
||||
|
||||
void ring_buffer_put(struct ring_buffer *rb)
|
||||
void ring_buffer_put(struct perf_buffer *rb)
|
||||
{
|
||||
if (!refcount_dec_and_test(&rb->refcount))
|
||||
return;
|
||||
|
@ -5672,7 +5672,7 @@ static void perf_mmap_close(struct vm_area_struct *vma)
|
|||
{
|
||||
struct perf_event *event = vma->vm_file->private_data;
|
||||
|
||||
struct ring_buffer *rb = ring_buffer_get(event);
|
||||
struct perf_buffer *rb = ring_buffer_get(event);
|
||||
struct user_struct *mmap_user = rb->mmap_user;
|
||||
int mmap_locked = rb->mmap_locked;
|
||||
unsigned long size = perf_data_size(rb);
|
||||
|
@ -5790,8 +5790,8 @@ static int perf_mmap(struct file *file, struct vm_area_struct *vma)
|
|||
struct perf_event *event = file->private_data;
|
||||
unsigned long user_locked, user_lock_limit;
|
||||
struct user_struct *user = current_user();
|
||||
struct perf_buffer *rb = NULL;
|
||||
unsigned long locked, lock_limit;
|
||||
struct ring_buffer *rb = NULL;
|
||||
unsigned long vma_size;
|
||||
unsigned long nr_pages;
|
||||
long user_extra = 0, extra = 0;
|
||||
|
@ -6266,7 +6266,7 @@ static unsigned long perf_prepare_sample_aux(struct perf_event *event,
|
|||
size_t size)
|
||||
{
|
||||
struct perf_event *sampler = event->aux_event;
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
|
||||
data->aux_size = 0;
|
||||
|
||||
|
@ -6299,7 +6299,7 @@ static unsigned long perf_prepare_sample_aux(struct perf_event *event,
|
|||
return data->aux_size;
|
||||
}
|
||||
|
||||
long perf_pmu_snapshot_aux(struct ring_buffer *rb,
|
||||
long perf_pmu_snapshot_aux(struct perf_buffer *rb,
|
||||
struct perf_event *event,
|
||||
struct perf_output_handle *handle,
|
||||
unsigned long size)
|
||||
|
@ -6338,8 +6338,8 @@ static void perf_aux_sample_output(struct perf_event *event,
|
|||
struct perf_sample_data *data)
|
||||
{
|
||||
struct perf_event *sampler = event->aux_event;
|
||||
struct perf_buffer *rb;
|
||||
unsigned long pad;
|
||||
struct ring_buffer *rb;
|
||||
long size;
|
||||
|
||||
if (WARN_ON_ONCE(!sampler || !data->aux_size))
|
||||
|
@ -6707,7 +6707,7 @@ void perf_output_sample(struct perf_output_handle *handle,
|
|||
int wakeup_events = event->attr.wakeup_events;
|
||||
|
||||
if (wakeup_events) {
|
||||
struct ring_buffer *rb = handle->rb;
|
||||
struct perf_buffer *rb = handle->rb;
|
||||
int events = local_inc_return(&rb->events);
|
||||
|
||||
if (events >= wakeup_events) {
|
||||
|
@ -7150,7 +7150,7 @@ void perf_event_exec(void)
|
|||
}
|
||||
|
||||
struct remote_output {
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
int err;
|
||||
};
|
||||
|
||||
|
@ -7158,7 +7158,7 @@ static void __perf_event_output_stop(struct perf_event *event, void *data)
|
|||
{
|
||||
struct perf_event *parent = event->parent;
|
||||
struct remote_output *ro = data;
|
||||
struct ring_buffer *rb = ro->rb;
|
||||
struct perf_buffer *rb = ro->rb;
|
||||
struct stop_event_data sd = {
|
||||
.event = event,
|
||||
};
|
||||
|
@ -10998,7 +10998,7 @@ static int perf_copy_attr(struct perf_event_attr __user *uattr,
|
|||
static int
|
||||
perf_event_set_output(struct perf_event *event, struct perf_event *output_event)
|
||||
{
|
||||
struct ring_buffer *rb = NULL;
|
||||
struct perf_buffer *rb = NULL;
|
||||
int ret = -EINVAL;
|
||||
|
||||
if (!output_event)
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
#define RING_BUFFER_WRITABLE 0x01
|
||||
|
||||
struct ring_buffer {
|
||||
struct perf_buffer {
|
||||
refcount_t refcount;
|
||||
struct rcu_head rcu_head;
|
||||
#ifdef CONFIG_PERF_USE_VMALLOC
|
||||
|
@ -58,17 +58,17 @@ struct ring_buffer {
|
|||
void *data_pages[0];
|
||||
};
|
||||
|
||||
extern void rb_free(struct ring_buffer *rb);
|
||||
extern void rb_free(struct perf_buffer *rb);
|
||||
|
||||
static inline void rb_free_rcu(struct rcu_head *rcu_head)
|
||||
{
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
|
||||
rb = container_of(rcu_head, struct ring_buffer, rcu_head);
|
||||
rb = container_of(rcu_head, struct perf_buffer, rcu_head);
|
||||
rb_free(rb);
|
||||
}
|
||||
|
||||
static inline void rb_toggle_paused(struct ring_buffer *rb, bool pause)
|
||||
static inline void rb_toggle_paused(struct perf_buffer *rb, bool pause)
|
||||
{
|
||||
if (!pause && rb->nr_pages)
|
||||
rb->paused = 0;
|
||||
|
@ -76,16 +76,16 @@ static inline void rb_toggle_paused(struct ring_buffer *rb, bool pause)
|
|||
rb->paused = 1;
|
||||
}
|
||||
|
||||
extern struct ring_buffer *
|
||||
extern struct perf_buffer *
|
||||
rb_alloc(int nr_pages, long watermark, int cpu, int flags);
|
||||
extern void perf_event_wakeup(struct perf_event *event);
|
||||
extern int rb_alloc_aux(struct ring_buffer *rb, struct perf_event *event,
|
||||
extern int rb_alloc_aux(struct perf_buffer *rb, struct perf_event *event,
|
||||
pgoff_t pgoff, int nr_pages, long watermark, int flags);
|
||||
extern void rb_free_aux(struct ring_buffer *rb);
|
||||
extern struct ring_buffer *ring_buffer_get(struct perf_event *event);
|
||||
extern void ring_buffer_put(struct ring_buffer *rb);
|
||||
extern void rb_free_aux(struct perf_buffer *rb);
|
||||
extern struct perf_buffer *ring_buffer_get(struct perf_event *event);
|
||||
extern void ring_buffer_put(struct perf_buffer *rb);
|
||||
|
||||
static inline bool rb_has_aux(struct ring_buffer *rb)
|
||||
static inline bool rb_has_aux(struct perf_buffer *rb)
|
||||
{
|
||||
return !!rb->aux_nr_pages;
|
||||
}
|
||||
|
@ -94,7 +94,7 @@ void perf_event_aux_event(struct perf_event *event, unsigned long head,
|
|||
unsigned long size, u64 flags);
|
||||
|
||||
extern struct page *
|
||||
perf_mmap_to_page(struct ring_buffer *rb, unsigned long pgoff);
|
||||
perf_mmap_to_page(struct perf_buffer *rb, unsigned long pgoff);
|
||||
|
||||
#ifdef CONFIG_PERF_USE_VMALLOC
|
||||
/*
|
||||
|
@ -103,25 +103,25 @@ perf_mmap_to_page(struct ring_buffer *rb, unsigned long pgoff);
|
|||
* Required for architectures that have d-cache aliasing issues.
|
||||
*/
|
||||
|
||||
static inline int page_order(struct ring_buffer *rb)
|
||||
static inline int page_order(struct perf_buffer *rb)
|
||||
{
|
||||
return rb->page_order;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static inline int page_order(struct ring_buffer *rb)
|
||||
static inline int page_order(struct perf_buffer *rb)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
static inline unsigned long perf_data_size(struct ring_buffer *rb)
|
||||
static inline unsigned long perf_data_size(struct perf_buffer *rb)
|
||||
{
|
||||
return rb->nr_pages << (PAGE_SHIFT + page_order(rb));
|
||||
}
|
||||
|
||||
static inline unsigned long perf_aux_size(struct ring_buffer *rb)
|
||||
static inline unsigned long perf_aux_size(struct perf_buffer *rb)
|
||||
{
|
||||
return rb->aux_nr_pages << PAGE_SHIFT;
|
||||
}
|
||||
|
@ -141,7 +141,7 @@ static inline unsigned long perf_aux_size(struct ring_buffer *rb)
|
|||
buf += written; \
|
||||
handle->size -= written; \
|
||||
if (!handle->size) { \
|
||||
struct ring_buffer *rb = handle->rb; \
|
||||
struct perf_buffer *rb = handle->rb; \
|
||||
\
|
||||
handle->page++; \
|
||||
handle->page &= rb->nr_pages - 1; \
|
||||
|
|
|
@ -35,7 +35,7 @@ static void perf_output_wakeup(struct perf_output_handle *handle)
|
|||
*/
|
||||
static void perf_output_get_handle(struct perf_output_handle *handle)
|
||||
{
|
||||
struct ring_buffer *rb = handle->rb;
|
||||
struct perf_buffer *rb = handle->rb;
|
||||
|
||||
preempt_disable();
|
||||
|
||||
|
@ -49,7 +49,7 @@ static void perf_output_get_handle(struct perf_output_handle *handle)
|
|||
|
||||
static void perf_output_put_handle(struct perf_output_handle *handle)
|
||||
{
|
||||
struct ring_buffer *rb = handle->rb;
|
||||
struct perf_buffer *rb = handle->rb;
|
||||
unsigned long head;
|
||||
unsigned int nest;
|
||||
|
||||
|
@ -150,7 +150,7 @@ __perf_output_begin(struct perf_output_handle *handle,
|
|||
struct perf_event *event, unsigned int size,
|
||||
bool backward)
|
||||
{
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
unsigned long tail, offset, head;
|
||||
int have_lost, page_shift;
|
||||
struct {
|
||||
|
@ -301,7 +301,7 @@ void perf_output_end(struct perf_output_handle *handle)
|
|||
}
|
||||
|
||||
static void
|
||||
ring_buffer_init(struct ring_buffer *rb, long watermark, int flags)
|
||||
ring_buffer_init(struct perf_buffer *rb, long watermark, int flags)
|
||||
{
|
||||
long max_size = perf_data_size(rb);
|
||||
|
||||
|
@ -361,7 +361,7 @@ void *perf_aux_output_begin(struct perf_output_handle *handle,
|
|||
{
|
||||
struct perf_event *output_event = event;
|
||||
unsigned long aux_head, aux_tail;
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
unsigned int nest;
|
||||
|
||||
if (output_event->parent)
|
||||
|
@ -449,7 +449,7 @@ void *perf_aux_output_begin(struct perf_output_handle *handle,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(perf_aux_output_begin);
|
||||
|
||||
static __always_inline bool rb_need_aux_wakeup(struct ring_buffer *rb)
|
||||
static __always_inline bool rb_need_aux_wakeup(struct perf_buffer *rb)
|
||||
{
|
||||
if (rb->aux_overwrite)
|
||||
return false;
|
||||
|
@ -475,7 +475,7 @@ static __always_inline bool rb_need_aux_wakeup(struct ring_buffer *rb)
|
|||
void perf_aux_output_end(struct perf_output_handle *handle, unsigned long size)
|
||||
{
|
||||
bool wakeup = !!(handle->aux_flags & PERF_AUX_FLAG_TRUNCATED);
|
||||
struct ring_buffer *rb = handle->rb;
|
||||
struct perf_buffer *rb = handle->rb;
|
||||
unsigned long aux_head;
|
||||
|
||||
/* in overwrite mode, driver provides aux_head via handle */
|
||||
|
@ -532,7 +532,7 @@ EXPORT_SYMBOL_GPL(perf_aux_output_end);
|
|||
*/
|
||||
int perf_aux_output_skip(struct perf_output_handle *handle, unsigned long size)
|
||||
{
|
||||
struct ring_buffer *rb = handle->rb;
|
||||
struct perf_buffer *rb = handle->rb;
|
||||
|
||||
if (size > handle->size)
|
||||
return -ENOSPC;
|
||||
|
@ -569,8 +569,8 @@ long perf_output_copy_aux(struct perf_output_handle *aux_handle,
|
|||
struct perf_output_handle *handle,
|
||||
unsigned long from, unsigned long to)
|
||||
{
|
||||
struct perf_buffer *rb = aux_handle->rb;
|
||||
unsigned long tocopy, remainder, len = 0;
|
||||
struct ring_buffer *rb = aux_handle->rb;
|
||||
void *addr;
|
||||
|
||||
from &= (rb->aux_nr_pages << PAGE_SHIFT) - 1;
|
||||
|
@ -626,7 +626,7 @@ static struct page *rb_alloc_aux_page(int node, int order)
|
|||
return page;
|
||||
}
|
||||
|
||||
static void rb_free_aux_page(struct ring_buffer *rb, int idx)
|
||||
static void rb_free_aux_page(struct perf_buffer *rb, int idx)
|
||||
{
|
||||
struct page *page = virt_to_page(rb->aux_pages[idx]);
|
||||
|
||||
|
@ -635,7 +635,7 @@ static void rb_free_aux_page(struct ring_buffer *rb, int idx)
|
|||
__free_page(page);
|
||||
}
|
||||
|
||||
static void __rb_free_aux(struct ring_buffer *rb)
|
||||
static void __rb_free_aux(struct perf_buffer *rb)
|
||||
{
|
||||
int pg;
|
||||
|
||||
|
@ -662,7 +662,7 @@ static void __rb_free_aux(struct ring_buffer *rb)
|
|||
}
|
||||
}
|
||||
|
||||
int rb_alloc_aux(struct ring_buffer *rb, struct perf_event *event,
|
||||
int rb_alloc_aux(struct perf_buffer *rb, struct perf_event *event,
|
||||
pgoff_t pgoff, int nr_pages, long watermark, int flags)
|
||||
{
|
||||
bool overwrite = !(flags & RING_BUFFER_WRITABLE);
|
||||
|
@ -753,7 +753,7 @@ int rb_alloc_aux(struct ring_buffer *rb, struct perf_event *event,
|
|||
return ret;
|
||||
}
|
||||
|
||||
void rb_free_aux(struct ring_buffer *rb)
|
||||
void rb_free_aux(struct perf_buffer *rb)
|
||||
{
|
||||
if (refcount_dec_and_test(&rb->aux_refcount))
|
||||
__rb_free_aux(rb);
|
||||
|
@ -766,7 +766,7 @@ void rb_free_aux(struct ring_buffer *rb)
|
|||
*/
|
||||
|
||||
static struct page *
|
||||
__perf_mmap_to_page(struct ring_buffer *rb, unsigned long pgoff)
|
||||
__perf_mmap_to_page(struct perf_buffer *rb, unsigned long pgoff)
|
||||
{
|
||||
if (pgoff > rb->nr_pages)
|
||||
return NULL;
|
||||
|
@ -798,13 +798,13 @@ static void perf_mmap_free_page(void *addr)
|
|||
__free_page(page);
|
||||
}
|
||||
|
||||
struct ring_buffer *rb_alloc(int nr_pages, long watermark, int cpu, int flags)
|
||||
struct perf_buffer *rb_alloc(int nr_pages, long watermark, int cpu, int flags)
|
||||
{
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
unsigned long size;
|
||||
int i;
|
||||
|
||||
size = sizeof(struct ring_buffer);
|
||||
size = sizeof(struct perf_buffer);
|
||||
size += nr_pages * sizeof(void *);
|
||||
|
||||
if (order_base_2(size) >= PAGE_SHIFT+MAX_ORDER)
|
||||
|
@ -843,7 +843,7 @@ struct ring_buffer *rb_alloc(int nr_pages, long watermark, int cpu, int flags)
|
|||
return NULL;
|
||||
}
|
||||
|
||||
void rb_free(struct ring_buffer *rb)
|
||||
void rb_free(struct perf_buffer *rb)
|
||||
{
|
||||
int i;
|
||||
|
||||
|
@ -854,13 +854,13 @@ void rb_free(struct ring_buffer *rb)
|
|||
}
|
||||
|
||||
#else
|
||||
static int data_page_nr(struct ring_buffer *rb)
|
||||
static int data_page_nr(struct perf_buffer *rb)
|
||||
{
|
||||
return rb->nr_pages << page_order(rb);
|
||||
}
|
||||
|
||||
static struct page *
|
||||
__perf_mmap_to_page(struct ring_buffer *rb, unsigned long pgoff)
|
||||
__perf_mmap_to_page(struct perf_buffer *rb, unsigned long pgoff)
|
||||
{
|
||||
/* The '>' counts in the user page. */
|
||||
if (pgoff > data_page_nr(rb))
|
||||
|
@ -878,11 +878,11 @@ static void perf_mmap_unmark_page(void *addr)
|
|||
|
||||
static void rb_free_work(struct work_struct *work)
|
||||
{
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
void *base;
|
||||
int i, nr;
|
||||
|
||||
rb = container_of(work, struct ring_buffer, work);
|
||||
rb = container_of(work, struct perf_buffer, work);
|
||||
nr = data_page_nr(rb);
|
||||
|
||||
base = rb->user_page;
|
||||
|
@ -894,18 +894,18 @@ static void rb_free_work(struct work_struct *work)
|
|||
kfree(rb);
|
||||
}
|
||||
|
||||
void rb_free(struct ring_buffer *rb)
|
||||
void rb_free(struct perf_buffer *rb)
|
||||
{
|
||||
schedule_work(&rb->work);
|
||||
}
|
||||
|
||||
struct ring_buffer *rb_alloc(int nr_pages, long watermark, int cpu, int flags)
|
||||
struct perf_buffer *rb_alloc(int nr_pages, long watermark, int cpu, int flags)
|
||||
{
|
||||
struct ring_buffer *rb;
|
||||
struct perf_buffer *rb;
|
||||
unsigned long size;
|
||||
void *all_buf;
|
||||
|
||||
size = sizeof(struct ring_buffer);
|
||||
size = sizeof(struct perf_buffer);
|
||||
size += sizeof(void *);
|
||||
|
||||
rb = kzalloc(size, GFP_KERNEL);
|
||||
|
@ -939,7 +939,7 @@ struct ring_buffer *rb_alloc(int nr_pages, long watermark, int cpu, int flags)
|
|||
#endif
|
||||
|
||||
struct page *
|
||||
perf_mmap_to_page(struct ring_buffer *rb, unsigned long pgoff)
|
||||
perf_mmap_to_page(struct perf_buffer *rb, unsigned long pgoff)
|
||||
{
|
||||
if (rb->aux_nr_pages) {
|
||||
/* above AUX space */
|
||||
|
|
|
@ -141,6 +141,15 @@ menuconfig FTRACE
|
|||
|
||||
if FTRACE
|
||||
|
||||
config BOOTTIME_TRACING
|
||||
bool "Boot-time Tracing support"
|
||||
depends on BOOT_CONFIG && TRACING
|
||||
default y
|
||||
help
|
||||
Enable developer to setup ftrace subsystem via supplemental
|
||||
kernel cmdline at boot time for debugging (tracing) driver
|
||||
initialization and boot process.
|
||||
|
||||
config FUNCTION_TRACER
|
||||
bool "Kernel Function Tracer"
|
||||
depends on HAVE_FUNCTION_TRACER
|
||||
|
@ -172,6 +181,77 @@ config FUNCTION_GRAPH_TRACER
|
|||
the return value. This is done by setting the current return
|
||||
address on the current task structure into a stack of calls.
|
||||
|
||||
config DYNAMIC_FTRACE
|
||||
bool "enable/disable function tracing dynamically"
|
||||
depends on FUNCTION_TRACER
|
||||
depends on HAVE_DYNAMIC_FTRACE
|
||||
default y
|
||||
help
|
||||
This option will modify all the calls to function tracing
|
||||
dynamically (will patch them out of the binary image and
|
||||
replace them with a No-Op instruction) on boot up. During
|
||||
compile time, a table is made of all the locations that ftrace
|
||||
can function trace, and this table is linked into the kernel
|
||||
image. When this is enabled, functions can be individually
|
||||
enabled, and the functions not enabled will not affect
|
||||
performance of the system.
|
||||
|
||||
See the files in /sys/kernel/debug/tracing:
|
||||
available_filter_functions
|
||||
set_ftrace_filter
|
||||
set_ftrace_notrace
|
||||
|
||||
This way a CONFIG_FUNCTION_TRACER kernel is slightly larger, but
|
||||
otherwise has native performance as long as no tracing is active.
|
||||
|
||||
config DYNAMIC_FTRACE_WITH_REGS
|
||||
def_bool y
|
||||
depends on DYNAMIC_FTRACE
|
||||
depends on HAVE_DYNAMIC_FTRACE_WITH_REGS
|
||||
|
||||
config DYNAMIC_FTRACE_WITH_DIRECT_CALLS
|
||||
def_bool y
|
||||
depends on DYNAMIC_FTRACE
|
||||
depends on HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
|
||||
|
||||
config FUNCTION_PROFILER
|
||||
bool "Kernel function profiler"
|
||||
depends on FUNCTION_TRACER
|
||||
default n
|
||||
help
|
||||
This option enables the kernel function profiler. A file is created
|
||||
in debugfs called function_profile_enabled which defaults to zero.
|
||||
When a 1 is echoed into this file profiling begins, and when a
|
||||
zero is entered, profiling stops. A "functions" file is created in
|
||||
the trace_stat directory; this file shows the list of functions that
|
||||
have been hit and their counters.
|
||||
|
||||
If in doubt, say N.
|
||||
|
||||
config STACK_TRACER
|
||||
bool "Trace max stack"
|
||||
depends on HAVE_FUNCTION_TRACER
|
||||
select FUNCTION_TRACER
|
||||
select STACKTRACE
|
||||
select KALLSYMS
|
||||
help
|
||||
This special tracer records the maximum stack footprint of the
|
||||
kernel and displays it in /sys/kernel/debug/tracing/stack_trace.
|
||||
|
||||
This tracer works by hooking into every function call that the
|
||||
kernel executes, and keeping a maximum stack depth value and
|
||||
stack-trace saved. If this is configured with DYNAMIC_FTRACE
|
||||
then it will not have any overhead while the stack tracer
|
||||
is disabled.
|
||||
|
||||
To enable the stack tracer on bootup, pass in 'stacktrace'
|
||||
on the kernel command line.
|
||||
|
||||
The stack tracer can also be enabled or disabled via the
|
||||
sysctl kernel.stack_tracer_enabled
|
||||
|
||||
Say N if unsure.
|
||||
|
||||
config TRACE_PREEMPT_TOGGLE
|
||||
bool
|
||||
help
|
||||
|
@ -282,6 +362,19 @@ config HWLAT_TRACER
|
|||
file. Every time a latency is greater than tracing_thresh, it will
|
||||
be recorded into the ring buffer.
|
||||
|
||||
config MMIOTRACE
|
||||
bool "Memory mapped IO tracing"
|
||||
depends on HAVE_MMIOTRACE_SUPPORT && PCI
|
||||
select GENERIC_TRACER
|
||||
help
|
||||
Mmiotrace traces Memory Mapped I/O access and is meant for
|
||||
debugging and reverse engineering. It is called from the ioremap
|
||||
implementation and works via page faults. Tracing is disabled by
|
||||
default and can be enabled at run-time.
|
||||
|
||||
See Documentation/trace/mmiotrace.rst.
|
||||
If you are not helping to develop drivers, say N.
|
||||
|
||||
config ENABLE_DEFAULT_TRACERS
|
||||
bool "Trace process context switches and events"
|
||||
depends on !GENERIC_TRACER
|
||||
|
@ -410,30 +503,6 @@ config BRANCH_TRACER
|
|||
|
||||
Say N if unsure.
|
||||
|
||||
config STACK_TRACER
|
||||
bool "Trace max stack"
|
||||
depends on HAVE_FUNCTION_TRACER
|
||||
select FUNCTION_TRACER
|
||||
select STACKTRACE
|
||||
select KALLSYMS
|
||||
help
|
||||
This special tracer records the maximum stack footprint of the
|
||||
kernel and displays it in /sys/kernel/debug/tracing/stack_trace.
|
||||
|
||||
This tracer works by hooking into every function call that the
|
||||
kernel executes, and keeping a maximum stack depth value and
|
||||
stack-trace saved. If this is configured with DYNAMIC_FTRACE
|
||||
then it will not have any overhead while the stack tracer
|
||||
is disabled.
|
||||
|
||||
To enable the stack tracer on bootup, pass in 'stacktrace'
|
||||
on the kernel command line.
|
||||
|
||||
The stack tracer can also be enabled or disabled via the
|
||||
sysctl kernel.stack_tracer_enabled
|
||||
|
||||
Say N if unsure.
|
||||
|
||||
config BLK_DEV_IO_TRACE
|
||||
bool "Support for tracing block IO actions"
|
||||
depends on SYSFS
|
||||
|
@ -531,53 +600,6 @@ config DYNAMIC_EVENTS
|
|||
config PROBE_EVENTS
|
||||
def_bool n
|
||||
|
||||
config DYNAMIC_FTRACE
|
||||
bool "enable/disable function tracing dynamically"
|
||||
depends on FUNCTION_TRACER
|
||||
depends on HAVE_DYNAMIC_FTRACE
|
||||
default y
|
||||
help
|
||||
This option will modify all the calls to function tracing
|
||||
dynamically (will patch them out of the binary image and
|
||||
replace them with a No-Op instruction) on boot up. During
|
||||
compile time, a table is made of all the locations that ftrace
|
||||
can function trace, and this table is linked into the kernel
|
||||
image. When this is enabled, functions can be individually
|
||||
enabled, and the functions not enabled will not affect
|
||||
performance of the system.
|
||||
|
||||
See the files in /sys/kernel/debug/tracing:
|
||||
available_filter_functions
|
||||
set_ftrace_filter
|
||||
set_ftrace_notrace
|
||||
|
||||
This way a CONFIG_FUNCTION_TRACER kernel is slightly larger, but
|
||||
otherwise has native performance as long as no tracing is active.
|
||||
|
||||
config DYNAMIC_FTRACE_WITH_REGS
|
||||
def_bool y
|
||||
depends on DYNAMIC_FTRACE
|
||||
depends on HAVE_DYNAMIC_FTRACE_WITH_REGS
|
||||
|
||||
config DYNAMIC_FTRACE_WITH_DIRECT_CALLS
|
||||
def_bool y
|
||||
depends on DYNAMIC_FTRACE
|
||||
depends on HAVE_DYNAMIC_FTRACE_WITH_DIRECT_CALLS
|
||||
|
||||
config FUNCTION_PROFILER
|
||||
bool "Kernel function profiler"
|
||||
depends on FUNCTION_TRACER
|
||||
default n
|
||||
help
|
||||
This option enables the kernel function profiler. A file is created
|
||||
in debugfs called function_profile_enabled which defaults to zero.
|
||||
When a 1 is echoed into this file profiling begins, and when a
|
||||
zero is entered, profiling stops. A "functions" file is created in
|
||||
the trace_stat directory; this file shows the list of functions that
|
||||
have been hit and their counters.
|
||||
|
||||
If in doubt, say N.
|
||||
|
||||
config BPF_KPROBE_OVERRIDE
|
||||
bool "Enable BPF programs to override a kprobed function"
|
||||
depends on BPF_EVENTS
|
||||
|
@ -592,54 +614,6 @@ config FTRACE_MCOUNT_RECORD
|
|||
depends on DYNAMIC_FTRACE
|
||||
depends on HAVE_FTRACE_MCOUNT_RECORD
|
||||
|
||||
config FTRACE_SELFTEST
|
||||
bool
|
||||
|
||||
config FTRACE_STARTUP_TEST
|
||||
bool "Perform a startup test on ftrace"
|
||||
depends on GENERIC_TRACER
|
||||
select FTRACE_SELFTEST
|
||||
help
|
||||
This option performs a series of startup tests on ftrace. On bootup
|
||||
a series of tests are made to verify that the tracer is
|
||||
functioning properly. It will do tests on all the configured
|
||||
tracers of ftrace.
|
||||
|
||||
config EVENT_TRACE_STARTUP_TEST
|
||||
bool "Run selftest on trace events"
|
||||
depends on FTRACE_STARTUP_TEST
|
||||
default y
|
||||
help
|
||||
This option performs a test on all trace events in the system.
|
||||
It basically just enables each event and runs some code that
|
||||
will trigger events (not necessarily the event it enables)
|
||||
This may take some time run as there are a lot of events.
|
||||
|
||||
config EVENT_TRACE_TEST_SYSCALLS
|
||||
bool "Run selftest on syscall events"
|
||||
depends on EVENT_TRACE_STARTUP_TEST
|
||||
help
|
||||
This option will also enable testing every syscall event.
|
||||
It only enables the event and disables it and runs various loads
|
||||
with the event enabled. This adds a bit more time for kernel boot
|
||||
up since it runs this on every system call defined.
|
||||
|
||||
TBD - enable a way to actually call the syscalls as we test their
|
||||
events
|
||||
|
||||
config MMIOTRACE
|
||||
bool "Memory mapped IO tracing"
|
||||
depends on HAVE_MMIOTRACE_SUPPORT && PCI
|
||||
select GENERIC_TRACER
|
||||
help
|
||||
Mmiotrace traces Memory Mapped I/O access and is meant for
|
||||
debugging and reverse engineering. It is called from the ioremap
|
||||
implementation and works via page faults. Tracing is disabled by
|
||||
default and can be enabled at run-time.
|
||||
|
||||
See Documentation/trace/mmiotrace.rst.
|
||||
If you are not helping to develop drivers, say N.
|
||||
|
||||
config TRACING_MAP
|
||||
bool
|
||||
depends on ARCH_HAVE_NMI_SAFE_CMPXCHG
|
||||
|
@ -680,16 +654,6 @@ config TRACE_EVENT_INJECT
|
|||
|
||||
If unsure, say N.
|
||||
|
||||
config MMIOTRACE_TEST
|
||||
tristate "Test module for mmiotrace"
|
||||
depends on MMIOTRACE && m
|
||||
help
|
||||
This is a dumb module for testing mmiotrace. It is very dangerous
|
||||
as it will write garbage to IO memory starting at a given address.
|
||||
However, it should be safe to use on e.g. unused portion of VRAM.
|
||||
|
||||
Say N, unless you absolutely know what you are doing.
|
||||
|
||||
config TRACEPOINT_BENCHMARK
|
||||
bool "Add tracepoint that benchmarks tracepoints"
|
||||
help
|
||||
|
@ -736,44 +700,6 @@ config RING_BUFFER_BENCHMARK
|
|||
|
||||
If unsure, say N.
|
||||
|
||||
config RING_BUFFER_STARTUP_TEST
|
||||
bool "Ring buffer startup self test"
|
||||
depends on RING_BUFFER
|
||||
help
|
||||
Run a simple self test on the ring buffer on boot up. Late in the
|
||||
kernel boot sequence, the test will start that kicks off
|
||||
a thread per cpu. Each thread will write various size events
|
||||
into the ring buffer. Another thread is created to send IPIs
|
||||
to each of the threads, where the IPI handler will also write
|
||||
to the ring buffer, to test/stress the nesting ability.
|
||||
If any anomalies are discovered, a warning will be displayed
|
||||
and all ring buffers will be disabled.
|
||||
|
||||
The test runs for 10 seconds. This will slow your boot time
|
||||
by at least 10 more seconds.
|
||||
|
||||
At the end of the test, statics and more checks are done.
|
||||
It will output the stats of each per cpu buffer. What
|
||||
was written, the sizes, what was read, what was lost, and
|
||||
other similar details.
|
||||
|
||||
If unsure, say N
|
||||
|
||||
config PREEMPTIRQ_DELAY_TEST
|
||||
tristate "Preempt / IRQ disable delay thread to test latency tracers"
|
||||
depends on m
|
||||
help
|
||||
Select this option to build a test module that can help test latency
|
||||
tracers by executing a preempt or irq disable section with a user
|
||||
configurable delay. The module busy waits for the duration of the
|
||||
critical section.
|
||||
|
||||
For example, the following invocation generates a burst of three
|
||||
irq-disabled critical sections for 500us:
|
||||
modprobe preemptirq_delay_test test_mode=irq delay=500 burst_size=3
|
||||
|
||||
If unsure, say N
|
||||
|
||||
config TRACE_EVAL_MAP_FILE
|
||||
bool "Show eval mappings for trace events"
|
||||
depends on TRACING
|
||||
|
@ -814,6 +740,114 @@ config GCOV_PROFILE_FTRACE
|
|||
Note that on a kernel compiled with this config, ftrace will
|
||||
run significantly slower.
|
||||
|
||||
config FTRACE_SELFTEST
|
||||
bool
|
||||
|
||||
config FTRACE_STARTUP_TEST
|
||||
bool "Perform a startup test on ftrace"
|
||||
depends on GENERIC_TRACER
|
||||
select FTRACE_SELFTEST
|
||||
help
|
||||
This option performs a series of startup tests on ftrace. On bootup
|
||||
a series of tests are made to verify that the tracer is
|
||||
functioning properly. It will do tests on all the configured
|
||||
tracers of ftrace.
|
||||
|
||||
config EVENT_TRACE_STARTUP_TEST
|
||||
bool "Run selftest on trace events"
|
||||
depends on FTRACE_STARTUP_TEST
|
||||
default y
|
||||
help
|
||||
This option performs a test on all trace events in the system.
|
||||
It basically just enables each event and runs some code that
|
||||
will trigger events (not necessarily the event it enables)
|
||||
This may take some time run as there are a lot of events.
|
||||
|
||||
config EVENT_TRACE_TEST_SYSCALLS
|
||||
bool "Run selftest on syscall events"
|
||||
depends on EVENT_TRACE_STARTUP_TEST
|
||||
help
|
||||
This option will also enable testing every syscall event.
|
||||
It only enables the event and disables it and runs various loads
|
||||
with the event enabled. This adds a bit more time for kernel boot
|
||||
up since it runs this on every system call defined.
|
||||
|
||||
TBD - enable a way to actually call the syscalls as we test their
|
||||
events
|
||||
|
||||
config RING_BUFFER_STARTUP_TEST
|
||||
bool "Ring buffer startup self test"
|
||||
depends on RING_BUFFER
|
||||
help
|
||||
Run a simple self test on the ring buffer on boot up. Late in the
|
||||
kernel boot sequence, the test will start that kicks off
|
||||
a thread per cpu. Each thread will write various size events
|
||||
into the ring buffer. Another thread is created to send IPIs
|
||||
to each of the threads, where the IPI handler will also write
|
||||
to the ring buffer, to test/stress the nesting ability.
|
||||
If any anomalies are discovered, a warning will be displayed
|
||||
and all ring buffers will be disabled.
|
||||
|
||||
The test runs for 10 seconds. This will slow your boot time
|
||||
by at least 10 more seconds.
|
||||
|
||||
At the end of the test, statics and more checks are done.
|
||||
It will output the stats of each per cpu buffer. What
|
||||
was written, the sizes, what was read, what was lost, and
|
||||
other similar details.
|
||||
|
||||
If unsure, say N
|
||||
|
||||
config MMIOTRACE_TEST
|
||||
tristate "Test module for mmiotrace"
|
||||
depends on MMIOTRACE && m
|
||||
help
|
||||
This is a dumb module for testing mmiotrace. It is very dangerous
|
||||
as it will write garbage to IO memory starting at a given address.
|
||||
However, it should be safe to use on e.g. unused portion of VRAM.
|
||||
|
||||
Say N, unless you absolutely know what you are doing.
|
||||
|
||||
config PREEMPTIRQ_DELAY_TEST
|
||||
tristate "Test module to create a preempt / IRQ disable delay thread to test latency tracers"
|
||||
depends on m
|
||||
help
|
||||
Select this option to build a test module that can help test latency
|
||||
tracers by executing a preempt or irq disable section with a user
|
||||
configurable delay. The module busy waits for the duration of the
|
||||
critical section.
|
||||
|
||||
For example, the following invocation generates a burst of three
|
||||
irq-disabled critical sections for 500us:
|
||||
modprobe preemptirq_delay_test test_mode=irq delay=500 burst_size=3
|
||||
|
||||
If unsure, say N
|
||||
|
||||
config SYNTH_EVENT_GEN_TEST
|
||||
tristate "Test module for in-kernel synthetic event generation"
|
||||
depends on HIST_TRIGGERS
|
||||
help
|
||||
This option creates a test module to check the base
|
||||
functionality of in-kernel synthetic event definition and
|
||||
generation.
|
||||
|
||||
To test, insert the module, and then check the trace buffer
|
||||
for the generated sample events.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
config KPROBE_EVENT_GEN_TEST
|
||||
tristate "Test module for in-kernel kprobe event generation"
|
||||
depends on KPROBE_EVENTS
|
||||
help
|
||||
This option creates a test module to check the base
|
||||
functionality of in-kernel kprobe event definition.
|
||||
|
||||
To test, insert the module, and then check the trace buffer
|
||||
for the generated kprobe events.
|
||||
|
||||
If unsure, say N.
|
||||
|
||||
endif # FTRACE
|
||||
|
||||
endif # TRACING_SUPPORT
|
||||
|
|
|
@ -44,6 +44,8 @@ obj-$(CONFIG_TRACING) += trace_stat.o
|
|||
obj-$(CONFIG_TRACING) += trace_printk.o
|
||||
obj-$(CONFIG_TRACING_MAP) += tracing_map.o
|
||||
obj-$(CONFIG_PREEMPTIRQ_DELAY_TEST) += preemptirq_delay_test.o
|
||||
obj-$(CONFIG_SYNTH_EVENT_GEN_TEST) += synth_event_gen_test.o
|
||||
obj-$(CONFIG_KPROBE_EVENT_GEN_TEST) += kprobe_event_gen_test.o
|
||||
obj-$(CONFIG_CONTEXT_SWITCH_TRACER) += trace_sched_switch.o
|
||||
obj-$(CONFIG_FUNCTION_TRACER) += trace_functions.o
|
||||
obj-$(CONFIG_PREEMPTIRQ_TRACEPOINTS) += trace_preemptirq.o
|
||||
|
@ -83,6 +85,7 @@ endif
|
|||
obj-$(CONFIG_DYNAMIC_EVENTS) += trace_dynevent.o
|
||||
obj-$(CONFIG_PROBE_EVENTS) += trace_probe.o
|
||||
obj-$(CONFIG_UPROBE_EVENTS) += trace_uprobe.o
|
||||
obj-$(CONFIG_BOOTTIME_TRACING) += trace_boot.o
|
||||
|
||||
obj-$(CONFIG_TRACEPOINT_BENCHMARK) += trace_benchmark.o
|
||||
|
||||
|
|
|
@ -68,14 +68,14 @@ static void trace_note(struct blk_trace *bt, pid_t pid, int action,
|
|||
{
|
||||
struct blk_io_trace *t;
|
||||
struct ring_buffer_event *event = NULL;
|
||||
struct ring_buffer *buffer = NULL;
|
||||
struct trace_buffer *buffer = NULL;
|
||||
int pc = 0;
|
||||
int cpu = smp_processor_id();
|
||||
bool blk_tracer = blk_tracer_enabled;
|
||||
ssize_t cgid_len = cgid ? sizeof(cgid) : 0;
|
||||
|
||||
if (blk_tracer) {
|
||||
buffer = blk_tr->trace_buffer.buffer;
|
||||
buffer = blk_tr->array_buffer.buffer;
|
||||
pc = preempt_count();
|
||||
event = trace_buffer_lock_reserve(buffer, TRACE_BLK,
|
||||
sizeof(*t) + len + cgid_len,
|
||||
|
@ -215,7 +215,7 @@ static void __blk_add_trace(struct blk_trace *bt, sector_t sector, int bytes,
|
|||
{
|
||||
struct task_struct *tsk = current;
|
||||
struct ring_buffer_event *event = NULL;
|
||||
struct ring_buffer *buffer = NULL;
|
||||
struct trace_buffer *buffer = NULL;
|
||||
struct blk_io_trace *t;
|
||||
unsigned long flags = 0;
|
||||
unsigned long *sequence;
|
||||
|
@ -248,7 +248,7 @@ static void __blk_add_trace(struct blk_trace *bt, sector_t sector, int bytes,
|
|||
if (blk_tracer) {
|
||||
tracing_record_cmdline(current);
|
||||
|
||||
buffer = blk_tr->trace_buffer.buffer;
|
||||
buffer = blk_tr->array_buffer.buffer;
|
||||
pc = preempt_count();
|
||||
event = trace_buffer_lock_reserve(buffer, TRACE_BLK,
|
||||
sizeof(*t) + pdu_len + cgid_len,
|
||||
|
|
|
@ -62,8 +62,6 @@
|
|||
})
|
||||
|
||||
/* hash bits for specific function selection */
|
||||
#define FTRACE_HASH_BITS 7
|
||||
#define FTRACE_FUNC_HASHSIZE (1 << FTRACE_HASH_BITS)
|
||||
#define FTRACE_HASH_DEFAULT_BITS 10
|
||||
#define FTRACE_HASH_MAX_BITS 12
|
||||
|
||||
|
@ -146,7 +144,7 @@ static void ftrace_pid_func(unsigned long ip, unsigned long parent_ip,
|
|||
{
|
||||
struct trace_array *tr = op->private;
|
||||
|
||||
if (tr && this_cpu_read(tr->trace_buffer.data->ftrace_ignore_pid))
|
||||
if (tr && this_cpu_read(tr->array_buffer.data->ftrace_ignore_pid))
|
||||
return;
|
||||
|
||||
op->saved_func(ip, parent_ip, op, regs);
|
||||
|
@ -1103,9 +1101,6 @@ struct ftrace_page {
|
|||
#define ENTRY_SIZE sizeof(struct dyn_ftrace)
|
||||
#define ENTRIES_PER_PAGE (PAGE_SIZE / ENTRY_SIZE)
|
||||
|
||||
/* estimate from running different kernels */
|
||||
#define NR_TO_INIT 10000
|
||||
|
||||
static struct ftrace_page *ftrace_pages_start;
|
||||
static struct ftrace_page *ftrace_pages;
|
||||
|
||||
|
@ -5464,7 +5459,7 @@ static void __init set_ftrace_early_graph(char *buf, int enable)
|
|||
struct ftrace_hash *hash;
|
||||
|
||||
hash = alloc_ftrace_hash(FTRACE_HASH_DEFAULT_BITS);
|
||||
if (WARN_ON(!hash))
|
||||
if (MEM_FAIL(!hash, "Failed to allocate hash\n"))
|
||||
return;
|
||||
|
||||
while (buf) {
|
||||
|
@ -5596,8 +5591,8 @@ static const struct file_operations ftrace_notrace_fops = {
|
|||
|
||||
static DEFINE_MUTEX(graph_lock);
|
||||
|
||||
struct ftrace_hash *ftrace_graph_hash = EMPTY_HASH;
|
||||
struct ftrace_hash *ftrace_graph_notrace_hash = EMPTY_HASH;
|
||||
struct ftrace_hash __rcu *ftrace_graph_hash = EMPTY_HASH;
|
||||
struct ftrace_hash __rcu *ftrace_graph_notrace_hash = EMPTY_HASH;
|
||||
|
||||
enum graph_filter_type {
|
||||
GRAPH_FILTER_NOTRACE = 0,
|
||||
|
@ -5872,8 +5867,15 @@ ftrace_graph_release(struct inode *inode, struct file *file)
|
|||
|
||||
mutex_unlock(&graph_lock);
|
||||
|
||||
/* Wait till all users are no longer using the old hash */
|
||||
synchronize_rcu();
|
||||
/*
|
||||
* We need to do a hard force of sched synchronization.
|
||||
* This is because we use preempt_disable() to do RCU, but
|
||||
* the function tracers can be called where RCU is not watching
|
||||
* (like before user_exit()). We can not rely on the RCU
|
||||
* infrastructure to do the synchronization, thus we must do it
|
||||
* ourselves.
|
||||
*/
|
||||
schedule_on_each_cpu(ftrace_sync);
|
||||
|
||||
free_ftrace_hash(old_hash);
|
||||
}
|
||||
|
@ -6596,7 +6598,7 @@ static void add_to_clear_hash_list(struct list_head *clear_list,
|
|||
|
||||
func = kmalloc(sizeof(*func), GFP_KERNEL);
|
||||
if (!func) {
|
||||
WARN_ONCE(1, "alloc failure, ftrace filter could be stale\n");
|
||||
MEM_FAIL(1, "alloc failure, ftrace filter could be stale\n");
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -6922,7 +6924,7 @@ ftrace_filter_pid_sched_switch_probe(void *data, bool preempt,
|
|||
|
||||
pid_list = rcu_dereference_sched(tr->function_pids);
|
||||
|
||||
this_cpu_write(tr->trace_buffer.data->ftrace_ignore_pid,
|
||||
this_cpu_write(tr->array_buffer.data->ftrace_ignore_pid,
|
||||
trace_ignore_this_task(pid_list, next));
|
||||
}
|
||||
|
||||
|
@ -6976,7 +6978,7 @@ static void clear_ftrace_pids(struct trace_array *tr)
|
|||
unregister_trace_sched_switch(ftrace_filter_pid_sched_switch_probe, tr);
|
||||
|
||||
for_each_possible_cpu(cpu)
|
||||
per_cpu_ptr(tr->trace_buffer.data, cpu)->ftrace_ignore_pid = false;
|
||||
per_cpu_ptr(tr->array_buffer.data, cpu)->ftrace_ignore_pid = false;
|
||||
|
||||
rcu_assign_pointer(tr->function_pids, NULL);
|
||||
|
||||
|
@ -7031,9 +7033,10 @@ static void *fpid_next(struct seq_file *m, void *v, loff_t *pos)
|
|||
struct trace_array *tr = m->private;
|
||||
struct trace_pid_list *pid_list = rcu_dereference_sched(tr->function_pids);
|
||||
|
||||
if (v == FTRACE_NO_PIDS)
|
||||
if (v == FTRACE_NO_PIDS) {
|
||||
(*pos)++;
|
||||
return NULL;
|
||||
|
||||
}
|
||||
return trace_pid_next(pid_list, v, pos);
|
||||
}
|
||||
|
||||
|
@ -7100,7 +7103,7 @@ static void ignore_task_cpu(void *data)
|
|||
pid_list = rcu_dereference_protected(tr->function_pids,
|
||||
mutex_is_locked(&ftrace_lock));
|
||||
|
||||
this_cpu_write(tr->trace_buffer.data->ftrace_ignore_pid,
|
||||
this_cpu_write(tr->array_buffer.data->ftrace_ignore_pid,
|
||||
trace_ignore_this_task(pid_list, current));
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,225 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Test module for in-kernel kprobe event creation and generation.
|
||||
*
|
||||
* Copyright (C) 2019 Tom Zanussi <zanussi@kernel.org>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/trace_events.h>
|
||||
|
||||
/*
|
||||
* This module is a simple test of basic functionality for in-kernel
|
||||
* kprobe/kretprobe event creation. The first test uses
|
||||
* kprobe_event_gen_cmd_start(), kprobe_event_add_fields() and
|
||||
* kprobe_event_gen_cmd_end() to create a kprobe event, which is then
|
||||
* enabled in order to generate trace output. The second creates a
|
||||
* kretprobe event using kretprobe_event_gen_cmd_start() and
|
||||
* kretprobe_event_gen_cmd_end(), and is also then enabled.
|
||||
*
|
||||
* To test, select CONFIG_KPROBE_EVENT_GEN_TEST and build the module.
|
||||
* Then:
|
||||
*
|
||||
* # insmod kernel/trace/kprobe_event_gen_test.ko
|
||||
* # cat /sys/kernel/debug/tracing/trace
|
||||
*
|
||||
* You should see many instances of the "gen_kprobe_test" and
|
||||
* "gen_kretprobe_test" events in the trace buffer.
|
||||
*
|
||||
* To remove the events, remove the module:
|
||||
*
|
||||
* # rmmod kprobe_event_gen_test
|
||||
*
|
||||
*/
|
||||
|
||||
static struct trace_event_file *gen_kprobe_test;
|
||||
static struct trace_event_file *gen_kretprobe_test;
|
||||
|
||||
/*
|
||||
* Test to make sure we can create a kprobe event, then add more
|
||||
* fields.
|
||||
*/
|
||||
static int __init test_gen_kprobe_cmd(void)
|
||||
{
|
||||
struct dynevent_cmd cmd;
|
||||
char *buf;
|
||||
int ret;
|
||||
|
||||
/* Create a buffer to hold the generated command */
|
||||
buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Before generating the command, initialize the cmd object */
|
||||
kprobe_event_cmd_init(&cmd, buf, MAX_DYNEVENT_CMD_LEN);
|
||||
|
||||
/*
|
||||
* Define the gen_kprobe_test event with the first 2 kprobe
|
||||
* fields.
|
||||
*/
|
||||
ret = kprobe_event_gen_cmd_start(&cmd, "gen_kprobe_test",
|
||||
"do_sys_open",
|
||||
"dfd=%ax", "filename=%dx");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
/* Use kprobe_event_add_fields to add the rest of the fields */
|
||||
|
||||
ret = kprobe_event_add_fields(&cmd, "flags=%cx", "mode=+4($stack)");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
/*
|
||||
* This actually creates the event.
|
||||
*/
|
||||
ret = kprobe_event_gen_cmd_end(&cmd);
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
/*
|
||||
* Now get the gen_kprobe_test event file. We need to prevent
|
||||
* the instance and event from disappearing from underneath
|
||||
* us, which trace_get_event_file() does (though in this case
|
||||
* we're using the top-level instance which never goes away).
|
||||
*/
|
||||
gen_kprobe_test = trace_get_event_file(NULL, "kprobes",
|
||||
"gen_kprobe_test");
|
||||
if (IS_ERR(gen_kprobe_test)) {
|
||||
ret = PTR_ERR(gen_kprobe_test);
|
||||
goto delete;
|
||||
}
|
||||
|
||||
/* Enable the event or you won't see anything */
|
||||
ret = trace_array_set_clr_event(gen_kprobe_test->tr,
|
||||
"kprobes", "gen_kprobe_test", true);
|
||||
if (ret) {
|
||||
trace_put_event_file(gen_kprobe_test);
|
||||
goto delete;
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
delete:
|
||||
/* We got an error after creating the event, delete it */
|
||||
ret = kprobe_event_delete("gen_kprobe_test");
|
||||
free:
|
||||
kfree(buf);
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test to make sure we can create a kretprobe event.
|
||||
*/
|
||||
static int __init test_gen_kretprobe_cmd(void)
|
||||
{
|
||||
struct dynevent_cmd cmd;
|
||||
char *buf;
|
||||
int ret;
|
||||
|
||||
/* Create a buffer to hold the generated command */
|
||||
buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Before generating the command, initialize the cmd object */
|
||||
kprobe_event_cmd_init(&cmd, buf, MAX_DYNEVENT_CMD_LEN);
|
||||
|
||||
/*
|
||||
* Define the kretprobe event.
|
||||
*/
|
||||
ret = kretprobe_event_gen_cmd_start(&cmd, "gen_kretprobe_test",
|
||||
"do_sys_open",
|
||||
"$retval");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
/*
|
||||
* This actually creates the event.
|
||||
*/
|
||||
ret = kretprobe_event_gen_cmd_end(&cmd);
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
/*
|
||||
* Now get the gen_kretprobe_test event file. We need to
|
||||
* prevent the instance and event from disappearing from
|
||||
* underneath us, which trace_get_event_file() does (though in
|
||||
* this case we're using the top-level instance which never
|
||||
* goes away).
|
||||
*/
|
||||
gen_kretprobe_test = trace_get_event_file(NULL, "kprobes",
|
||||
"gen_kretprobe_test");
|
||||
if (IS_ERR(gen_kretprobe_test)) {
|
||||
ret = PTR_ERR(gen_kretprobe_test);
|
||||
goto delete;
|
||||
}
|
||||
|
||||
/* Enable the event or you won't see anything */
|
||||
ret = trace_array_set_clr_event(gen_kretprobe_test->tr,
|
||||
"kprobes", "gen_kretprobe_test", true);
|
||||
if (ret) {
|
||||
trace_put_event_file(gen_kretprobe_test);
|
||||
goto delete;
|
||||
}
|
||||
out:
|
||||
return ret;
|
||||
delete:
|
||||
/* We got an error after creating the event, delete it */
|
||||
ret = kprobe_event_delete("gen_kretprobe_test");
|
||||
free:
|
||||
kfree(buf);
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
static int __init kprobe_event_gen_test_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = test_gen_kprobe_cmd();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = test_gen_kretprobe_cmd();
|
||||
if (ret) {
|
||||
WARN_ON(trace_array_set_clr_event(gen_kretprobe_test->tr,
|
||||
"kprobes",
|
||||
"gen_kretprobe_test", false));
|
||||
trace_put_event_file(gen_kretprobe_test);
|
||||
WARN_ON(kprobe_event_delete("gen_kretprobe_test"));
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit kprobe_event_gen_test_exit(void)
|
||||
{
|
||||
/* Disable the event or you can't remove it */
|
||||
WARN_ON(trace_array_set_clr_event(gen_kprobe_test->tr,
|
||||
"kprobes",
|
||||
"gen_kprobe_test", false));
|
||||
|
||||
/* Now give the file and instance back */
|
||||
trace_put_event_file(gen_kprobe_test);
|
||||
|
||||
/* Now unregister and free the event */
|
||||
WARN_ON(kprobe_event_delete("gen_kprobe_test"));
|
||||
|
||||
/* Disable the event or you can't remove it */
|
||||
WARN_ON(trace_array_set_clr_event(gen_kprobe_test->tr,
|
||||
"kprobes",
|
||||
"gen_kretprobe_test", false));
|
||||
|
||||
/* Now give the file and instance back */
|
||||
trace_put_event_file(gen_kretprobe_test);
|
||||
|
||||
/* Now unregister and free the event */
|
||||
WARN_ON(kprobe_event_delete("gen_kretprobe_test"));
|
||||
}
|
||||
|
||||
module_init(kprobe_event_gen_test_init)
|
||||
module_exit(kprobe_event_gen_test_exit)
|
||||
|
||||
MODULE_AUTHOR("Tom Zanussi");
|
||||
MODULE_DESCRIPTION("kprobe event generation test");
|
||||
MODULE_LICENSE("GPL v2");
|
|
@ -300,8 +300,6 @@ u64 ring_buffer_event_time_stamp(struct ring_buffer_event *event)
|
|||
/* Missed count stored at end */
|
||||
#define RB_MISSED_STORED (1 << 30)
|
||||
|
||||
#define RB_MISSED_FLAGS (RB_MISSED_EVENTS|RB_MISSED_STORED)
|
||||
|
||||
struct buffer_data_page {
|
||||
u64 time_stamp; /* page time stamp */
|
||||
local_t commit; /* write committed index */
|
||||
|
@ -443,7 +441,7 @@ enum {
|
|||
struct ring_buffer_per_cpu {
|
||||
int cpu;
|
||||
atomic_t record_disabled;
|
||||
struct ring_buffer *buffer;
|
||||
struct trace_buffer *buffer;
|
||||
raw_spinlock_t reader_lock; /* serialize readers */
|
||||
arch_spinlock_t lock;
|
||||
struct lock_class_key lock_key;
|
||||
|
@ -482,7 +480,7 @@ struct ring_buffer_per_cpu {
|
|||
struct rb_irq_work irq_work;
|
||||
};
|
||||
|
||||
struct ring_buffer {
|
||||
struct trace_buffer {
|
||||
unsigned flags;
|
||||
int cpus;
|
||||
atomic_t record_disabled;
|
||||
|
@ -518,7 +516,7 @@ struct ring_buffer_iter {
|
|||
*
|
||||
* Returns the number of pages used by a per_cpu buffer of the ring buffer.
|
||||
*/
|
||||
size_t ring_buffer_nr_pages(struct ring_buffer *buffer, int cpu)
|
||||
size_t ring_buffer_nr_pages(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
return buffer->buffers[cpu]->nr_pages;
|
||||
}
|
||||
|
@ -530,7 +528,7 @@ size_t ring_buffer_nr_pages(struct ring_buffer *buffer, int cpu)
|
|||
*
|
||||
* Returns the number of pages that have content in the ring buffer.
|
||||
*/
|
||||
size_t ring_buffer_nr_dirty_pages(struct ring_buffer *buffer, int cpu)
|
||||
size_t ring_buffer_nr_dirty_pages(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
size_t read;
|
||||
size_t cnt;
|
||||
|
@ -573,7 +571,7 @@ static void rb_wake_up_waiters(struct irq_work *work)
|
|||
* as data is added to any of the @buffer's cpu buffers. Otherwise
|
||||
* it will wait for data to be added to a specific cpu buffer.
|
||||
*/
|
||||
int ring_buffer_wait(struct ring_buffer *buffer, int cpu, int full)
|
||||
int ring_buffer_wait(struct trace_buffer *buffer, int cpu, int full)
|
||||
{
|
||||
struct ring_buffer_per_cpu *uninitialized_var(cpu_buffer);
|
||||
DEFINE_WAIT(wait);
|
||||
|
@ -684,7 +682,7 @@ int ring_buffer_wait(struct ring_buffer *buffer, int cpu, int full)
|
|||
* Returns EPOLLIN | EPOLLRDNORM if data exists in the buffers,
|
||||
* zero otherwise.
|
||||
*/
|
||||
__poll_t ring_buffer_poll_wait(struct ring_buffer *buffer, int cpu,
|
||||
__poll_t ring_buffer_poll_wait(struct trace_buffer *buffer, int cpu,
|
||||
struct file *filp, poll_table *poll_table)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
|
@ -742,13 +740,13 @@ __poll_t ring_buffer_poll_wait(struct ring_buffer *buffer, int cpu,
|
|||
/* Up this if you want to test the TIME_EXTENTS and normalization */
|
||||
#define DEBUG_SHIFT 0
|
||||
|
||||
static inline u64 rb_time_stamp(struct ring_buffer *buffer)
|
||||
static inline u64 rb_time_stamp(struct trace_buffer *buffer)
|
||||
{
|
||||
/* shift to debug/test normalization and TIME_EXTENTS */
|
||||
return buffer->clock() << DEBUG_SHIFT;
|
||||
}
|
||||
|
||||
u64 ring_buffer_time_stamp(struct ring_buffer *buffer, int cpu)
|
||||
u64 ring_buffer_time_stamp(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
u64 time;
|
||||
|
||||
|
@ -760,7 +758,7 @@ u64 ring_buffer_time_stamp(struct ring_buffer *buffer, int cpu)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(ring_buffer_time_stamp);
|
||||
|
||||
void ring_buffer_normalize_time_stamp(struct ring_buffer *buffer,
|
||||
void ring_buffer_normalize_time_stamp(struct trace_buffer *buffer,
|
||||
int cpu, u64 *ts)
|
||||
{
|
||||
/* Just stupid testing the normalize function and deltas */
|
||||
|
@ -1283,7 +1281,7 @@ static int rb_allocate_pages(struct ring_buffer_per_cpu *cpu_buffer,
|
|||
}
|
||||
|
||||
static struct ring_buffer_per_cpu *
|
||||
rb_allocate_cpu_buffer(struct ring_buffer *buffer, long nr_pages, int cpu)
|
||||
rb_allocate_cpu_buffer(struct trace_buffer *buffer, long nr_pages, int cpu)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
struct buffer_page *bpage;
|
||||
|
@ -1368,16 +1366,17 @@ static void rb_free_cpu_buffer(struct ring_buffer_per_cpu *cpu_buffer)
|
|||
* __ring_buffer_alloc - allocate a new ring_buffer
|
||||
* @size: the size in bytes per cpu that is needed.
|
||||
* @flags: attributes to set for the ring buffer.
|
||||
* @key: ring buffer reader_lock_key.
|
||||
*
|
||||
* Currently the only flag that is available is the RB_FL_OVERWRITE
|
||||
* flag. This flag means that the buffer will overwrite old data
|
||||
* when the buffer wraps. If this flag is not set, the buffer will
|
||||
* drop data when the tail hits the head.
|
||||
*/
|
||||
struct ring_buffer *__ring_buffer_alloc(unsigned long size, unsigned flags,
|
||||
struct trace_buffer *__ring_buffer_alloc(unsigned long size, unsigned flags,
|
||||
struct lock_class_key *key)
|
||||
{
|
||||
struct ring_buffer *buffer;
|
||||
struct trace_buffer *buffer;
|
||||
long nr_pages;
|
||||
int bsize;
|
||||
int cpu;
|
||||
|
@ -1447,7 +1446,7 @@ EXPORT_SYMBOL_GPL(__ring_buffer_alloc);
|
|||
* @buffer: the buffer to free.
|
||||
*/
|
||||
void
|
||||
ring_buffer_free(struct ring_buffer *buffer)
|
||||
ring_buffer_free(struct trace_buffer *buffer)
|
||||
{
|
||||
int cpu;
|
||||
|
||||
|
@ -1463,18 +1462,18 @@ ring_buffer_free(struct ring_buffer *buffer)
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(ring_buffer_free);
|
||||
|
||||
void ring_buffer_set_clock(struct ring_buffer *buffer,
|
||||
void ring_buffer_set_clock(struct trace_buffer *buffer,
|
||||
u64 (*clock)(void))
|
||||
{
|
||||
buffer->clock = clock;
|
||||
}
|
||||
|
||||
void ring_buffer_set_time_stamp_abs(struct ring_buffer *buffer, bool abs)
|
||||
void ring_buffer_set_time_stamp_abs(struct trace_buffer *buffer, bool abs)
|
||||
{
|
||||
buffer->time_stamp_abs = abs;
|
||||
}
|
||||
|
||||
bool ring_buffer_time_stamp_abs(struct ring_buffer *buffer)
|
||||
bool ring_buffer_time_stamp_abs(struct trace_buffer *buffer)
|
||||
{
|
||||
return buffer->time_stamp_abs;
|
||||
}
|
||||
|
@ -1712,7 +1711,7 @@ static void update_pages_handler(struct work_struct *work)
|
|||
*
|
||||
* Returns 0 on success and < 0 on failure.
|
||||
*/
|
||||
int ring_buffer_resize(struct ring_buffer *buffer, unsigned long size,
|
||||
int ring_buffer_resize(struct trace_buffer *buffer, unsigned long size,
|
||||
int cpu_id)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
|
@ -1891,7 +1890,7 @@ int ring_buffer_resize(struct ring_buffer *buffer, unsigned long size,
|
|||
}
|
||||
EXPORT_SYMBOL_GPL(ring_buffer_resize);
|
||||
|
||||
void ring_buffer_change_overwrite(struct ring_buffer *buffer, int val)
|
||||
void ring_buffer_change_overwrite(struct trace_buffer *buffer, int val)
|
||||
{
|
||||
mutex_lock(&buffer->mutex);
|
||||
if (val)
|
||||
|
@ -2206,7 +2205,7 @@ rb_move_tail(struct ring_buffer_per_cpu *cpu_buffer,
|
|||
{
|
||||
struct buffer_page *tail_page = info->tail_page;
|
||||
struct buffer_page *commit_page = cpu_buffer->commit_page;
|
||||
struct ring_buffer *buffer = cpu_buffer->buffer;
|
||||
struct trace_buffer *buffer = cpu_buffer->buffer;
|
||||
struct buffer_page *next_page;
|
||||
int ret;
|
||||
|
||||
|
@ -2330,11 +2329,11 @@ static inline bool rb_event_is_commit(struct ring_buffer_per_cpu *cpu_buffer,
|
|||
|
||||
/**
|
||||
* rb_update_event - update event type and data
|
||||
* @cpu_buffer: The per cpu buffer of the @event
|
||||
* @event: the event to update
|
||||
* @type: the type of event
|
||||
* @length: the size of the event field in the ring buffer
|
||||
* @info: The info to update the @event with (contains length and delta)
|
||||
*
|
||||
* Update the type and data fields of the event. The length
|
||||
* Update the type and data fields of the @event. The length
|
||||
* is the actual size that is written to the ring buffer,
|
||||
* and with this, we can determine what to place into the
|
||||
* data field.
|
||||
|
@ -2609,7 +2608,7 @@ static void rb_commit(struct ring_buffer_per_cpu *cpu_buffer,
|
|||
}
|
||||
|
||||
static __always_inline void
|
||||
rb_wakeups(struct ring_buffer *buffer, struct ring_buffer_per_cpu *cpu_buffer)
|
||||
rb_wakeups(struct trace_buffer *buffer, struct ring_buffer_per_cpu *cpu_buffer)
|
||||
{
|
||||
size_t nr_pages;
|
||||
size_t dirty;
|
||||
|
@ -2733,7 +2732,7 @@ trace_recursive_unlock(struct ring_buffer_per_cpu *cpu_buffer)
|
|||
* Call this function before calling another ring_buffer_lock_reserve() and
|
||||
* call ring_buffer_nest_end() after the nested ring_buffer_unlock_commit().
|
||||
*/
|
||||
void ring_buffer_nest_start(struct ring_buffer *buffer)
|
||||
void ring_buffer_nest_start(struct trace_buffer *buffer)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
int cpu;
|
||||
|
@ -2753,7 +2752,7 @@ void ring_buffer_nest_start(struct ring_buffer *buffer)
|
|||
* Must be called after ring_buffer_nest_start() and after the
|
||||
* ring_buffer_unlock_commit().
|
||||
*/
|
||||
void ring_buffer_nest_end(struct ring_buffer *buffer)
|
||||
void ring_buffer_nest_end(struct trace_buffer *buffer)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
int cpu;
|
||||
|
@ -2775,7 +2774,7 @@ void ring_buffer_nest_end(struct ring_buffer *buffer)
|
|||
*
|
||||
* Must be paired with ring_buffer_lock_reserve.
|
||||
*/
|
||||
int ring_buffer_unlock_commit(struct ring_buffer *buffer,
|
||||
int ring_buffer_unlock_commit(struct trace_buffer *buffer,
|
||||
struct ring_buffer_event *event)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
|
@ -2868,7 +2867,7 @@ __rb_reserve_next(struct ring_buffer_per_cpu *cpu_buffer,
|
|||
}
|
||||
|
||||
static __always_inline struct ring_buffer_event *
|
||||
rb_reserve_next_event(struct ring_buffer *buffer,
|
||||
rb_reserve_next_event(struct trace_buffer *buffer,
|
||||
struct ring_buffer_per_cpu *cpu_buffer,
|
||||
unsigned long length)
|
||||
{
|
||||
|
@ -2961,7 +2960,7 @@ rb_reserve_next_event(struct ring_buffer *buffer,
|
|||
* If NULL is returned, then nothing has been allocated or locked.
|
||||
*/
|
||||
struct ring_buffer_event *
|
||||
ring_buffer_lock_reserve(struct ring_buffer *buffer, unsigned long length)
|
||||
ring_buffer_lock_reserve(struct trace_buffer *buffer, unsigned long length)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
struct ring_buffer_event *event;
|
||||
|
@ -3062,7 +3061,7 @@ rb_decrement_entry(struct ring_buffer_per_cpu *cpu_buffer,
|
|||
* If this function is called, do not call ring_buffer_unlock_commit on
|
||||
* the event.
|
||||
*/
|
||||
void ring_buffer_discard_commit(struct ring_buffer *buffer,
|
||||
void ring_buffer_discard_commit(struct trace_buffer *buffer,
|
||||
struct ring_buffer_event *event)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
|
@ -3113,7 +3112,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_discard_commit);
|
|||
* Note, like ring_buffer_lock_reserve, the length is the length of the data
|
||||
* and not the length of the event which would hold the header.
|
||||
*/
|
||||
int ring_buffer_write(struct ring_buffer *buffer,
|
||||
int ring_buffer_write(struct trace_buffer *buffer,
|
||||
unsigned long length,
|
||||
void *data)
|
||||
{
|
||||
|
@ -3193,7 +3192,7 @@ static bool rb_per_cpu_empty(struct ring_buffer_per_cpu *cpu_buffer)
|
|||
*
|
||||
* The caller should call synchronize_rcu() after this.
|
||||
*/
|
||||
void ring_buffer_record_disable(struct ring_buffer *buffer)
|
||||
void ring_buffer_record_disable(struct trace_buffer *buffer)
|
||||
{
|
||||
atomic_inc(&buffer->record_disabled);
|
||||
}
|
||||
|
@ -3206,7 +3205,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_record_disable);
|
|||
* Note, multiple disables will need the same number of enables
|
||||
* to truly enable the writing (much like preempt_disable).
|
||||
*/
|
||||
void ring_buffer_record_enable(struct ring_buffer *buffer)
|
||||
void ring_buffer_record_enable(struct trace_buffer *buffer)
|
||||
{
|
||||
atomic_dec(&buffer->record_disabled);
|
||||
}
|
||||
|
@ -3223,7 +3222,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_record_enable);
|
|||
* it works like an on/off switch, where as the disable() version
|
||||
* must be paired with a enable().
|
||||
*/
|
||||
void ring_buffer_record_off(struct ring_buffer *buffer)
|
||||
void ring_buffer_record_off(struct trace_buffer *buffer)
|
||||
{
|
||||
unsigned int rd;
|
||||
unsigned int new_rd;
|
||||
|
@ -3246,7 +3245,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_record_off);
|
|||
* it works like an on/off switch, where as the enable() version
|
||||
* must be paired with a disable().
|
||||
*/
|
||||
void ring_buffer_record_on(struct ring_buffer *buffer)
|
||||
void ring_buffer_record_on(struct trace_buffer *buffer)
|
||||
{
|
||||
unsigned int rd;
|
||||
unsigned int new_rd;
|
||||
|
@ -3264,7 +3263,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_record_on);
|
|||
*
|
||||
* Returns true if the ring buffer is in a state that it accepts writes.
|
||||
*/
|
||||
bool ring_buffer_record_is_on(struct ring_buffer *buffer)
|
||||
bool ring_buffer_record_is_on(struct trace_buffer *buffer)
|
||||
{
|
||||
return !atomic_read(&buffer->record_disabled);
|
||||
}
|
||||
|
@ -3280,7 +3279,7 @@ bool ring_buffer_record_is_on(struct ring_buffer *buffer)
|
|||
* ring_buffer_record_disable(), as that is a temporary disabling of
|
||||
* the ring buffer.
|
||||
*/
|
||||
bool ring_buffer_record_is_set_on(struct ring_buffer *buffer)
|
||||
bool ring_buffer_record_is_set_on(struct trace_buffer *buffer)
|
||||
{
|
||||
return !(atomic_read(&buffer->record_disabled) & RB_BUFFER_OFF);
|
||||
}
|
||||
|
@ -3295,7 +3294,7 @@ bool ring_buffer_record_is_set_on(struct ring_buffer *buffer)
|
|||
*
|
||||
* The caller should call synchronize_rcu() after this.
|
||||
*/
|
||||
void ring_buffer_record_disable_cpu(struct ring_buffer *buffer, int cpu)
|
||||
void ring_buffer_record_disable_cpu(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
|
||||
|
@ -3315,7 +3314,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_record_disable_cpu);
|
|||
* Note, multiple disables will need the same number of enables
|
||||
* to truly enable the writing (much like preempt_disable).
|
||||
*/
|
||||
void ring_buffer_record_enable_cpu(struct ring_buffer *buffer, int cpu)
|
||||
void ring_buffer_record_enable_cpu(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
|
||||
|
@ -3345,7 +3344,7 @@ rb_num_of_entries(struct ring_buffer_per_cpu *cpu_buffer)
|
|||
* @buffer: The ring buffer
|
||||
* @cpu: The per CPU buffer to read from.
|
||||
*/
|
||||
u64 ring_buffer_oldest_event_ts(struct ring_buffer *buffer, int cpu)
|
||||
u64 ring_buffer_oldest_event_ts(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
unsigned long flags;
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
|
@ -3378,7 +3377,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_oldest_event_ts);
|
|||
* @buffer: The ring buffer
|
||||
* @cpu: The per CPU buffer to read from.
|
||||
*/
|
||||
unsigned long ring_buffer_bytes_cpu(struct ring_buffer *buffer, int cpu)
|
||||
unsigned long ring_buffer_bytes_cpu(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
unsigned long ret;
|
||||
|
@ -3398,7 +3397,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_bytes_cpu);
|
|||
* @buffer: The ring buffer
|
||||
* @cpu: The per CPU buffer to get the entries from.
|
||||
*/
|
||||
unsigned long ring_buffer_entries_cpu(struct ring_buffer *buffer, int cpu)
|
||||
unsigned long ring_buffer_entries_cpu(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
|
||||
|
@ -3417,7 +3416,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_entries_cpu);
|
|||
* @buffer: The ring buffer
|
||||
* @cpu: The per CPU buffer to get the number of overruns from
|
||||
*/
|
||||
unsigned long ring_buffer_overrun_cpu(struct ring_buffer *buffer, int cpu)
|
||||
unsigned long ring_buffer_overrun_cpu(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
unsigned long ret;
|
||||
|
@ -3440,7 +3439,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_overrun_cpu);
|
|||
* @cpu: The per CPU buffer to get the number of overruns from
|
||||
*/
|
||||
unsigned long
|
||||
ring_buffer_commit_overrun_cpu(struct ring_buffer *buffer, int cpu)
|
||||
ring_buffer_commit_overrun_cpu(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
unsigned long ret;
|
||||
|
@ -3462,7 +3461,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_commit_overrun_cpu);
|
|||
* @cpu: The per CPU buffer to get the number of overruns from
|
||||
*/
|
||||
unsigned long
|
||||
ring_buffer_dropped_events_cpu(struct ring_buffer *buffer, int cpu)
|
||||
ring_buffer_dropped_events_cpu(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
unsigned long ret;
|
||||
|
@ -3483,7 +3482,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_dropped_events_cpu);
|
|||
* @cpu: The per CPU buffer to get the number of events read
|
||||
*/
|
||||
unsigned long
|
||||
ring_buffer_read_events_cpu(struct ring_buffer *buffer, int cpu)
|
||||
ring_buffer_read_events_cpu(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
|
||||
|
@ -3502,7 +3501,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_read_events_cpu);
|
|||
* Returns the total number of entries in the ring buffer
|
||||
* (all CPU entries)
|
||||
*/
|
||||
unsigned long ring_buffer_entries(struct ring_buffer *buffer)
|
||||
unsigned long ring_buffer_entries(struct trace_buffer *buffer)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
unsigned long entries = 0;
|
||||
|
@ -3525,7 +3524,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_entries);
|
|||
* Returns the total number of overruns in the ring buffer
|
||||
* (all CPU entries)
|
||||
*/
|
||||
unsigned long ring_buffer_overruns(struct ring_buffer *buffer)
|
||||
unsigned long ring_buffer_overruns(struct trace_buffer *buffer)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
unsigned long overruns = 0;
|
||||
|
@ -3949,7 +3948,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_peek);
|
|||
static struct ring_buffer_event *
|
||||
rb_iter_peek(struct ring_buffer_iter *iter, u64 *ts)
|
||||
{
|
||||
struct ring_buffer *buffer;
|
||||
struct trace_buffer *buffer;
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
struct ring_buffer_event *event;
|
||||
int nr_loops = 0;
|
||||
|
@ -4077,7 +4076,7 @@ rb_reader_unlock(struct ring_buffer_per_cpu *cpu_buffer, bool locked)
|
|||
* not consume the data.
|
||||
*/
|
||||
struct ring_buffer_event *
|
||||
ring_buffer_peek(struct ring_buffer *buffer, int cpu, u64 *ts,
|
||||
ring_buffer_peek(struct trace_buffer *buffer, int cpu, u64 *ts,
|
||||
unsigned long *lost_events)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer = buffer->buffers[cpu];
|
||||
|
@ -4141,7 +4140,7 @@ ring_buffer_iter_peek(struct ring_buffer_iter *iter, u64 *ts)
|
|||
* and eventually empty the ring buffer if the producer is slower.
|
||||
*/
|
||||
struct ring_buffer_event *
|
||||
ring_buffer_consume(struct ring_buffer *buffer, int cpu, u64 *ts,
|
||||
ring_buffer_consume(struct trace_buffer *buffer, int cpu, u64 *ts,
|
||||
unsigned long *lost_events)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
|
@ -4201,7 +4200,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_consume);
|
|||
* This overall must be paired with ring_buffer_read_finish.
|
||||
*/
|
||||
struct ring_buffer_iter *
|
||||
ring_buffer_read_prepare(struct ring_buffer *buffer, int cpu, gfp_t flags)
|
||||
ring_buffer_read_prepare(struct trace_buffer *buffer, int cpu, gfp_t flags)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
struct ring_buffer_iter *iter;
|
||||
|
@ -4331,8 +4330,9 @@ EXPORT_SYMBOL_GPL(ring_buffer_read);
|
|||
/**
|
||||
* ring_buffer_size - return the size of the ring buffer (in bytes)
|
||||
* @buffer: The ring buffer.
|
||||
* @cpu: The CPU to get ring buffer size from.
|
||||
*/
|
||||
unsigned long ring_buffer_size(struct ring_buffer *buffer, int cpu)
|
||||
unsigned long ring_buffer_size(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
/*
|
||||
* Earlier, this method returned
|
||||
|
@ -4398,7 +4398,7 @@ rb_reset_cpu(struct ring_buffer_per_cpu *cpu_buffer)
|
|||
* @buffer: The ring buffer to reset a per cpu buffer of
|
||||
* @cpu: The CPU buffer to be reset
|
||||
*/
|
||||
void ring_buffer_reset_cpu(struct ring_buffer *buffer, int cpu)
|
||||
void ring_buffer_reset_cpu(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer = buffer->buffers[cpu];
|
||||
unsigned long flags;
|
||||
|
@ -4435,7 +4435,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_reset_cpu);
|
|||
* ring_buffer_reset - reset a ring buffer
|
||||
* @buffer: The ring buffer to reset all cpu buffers
|
||||
*/
|
||||
void ring_buffer_reset(struct ring_buffer *buffer)
|
||||
void ring_buffer_reset(struct trace_buffer *buffer)
|
||||
{
|
||||
int cpu;
|
||||
|
||||
|
@ -4448,7 +4448,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_reset);
|
|||
* rind_buffer_empty - is the ring buffer empty?
|
||||
* @buffer: The ring buffer to test
|
||||
*/
|
||||
bool ring_buffer_empty(struct ring_buffer *buffer)
|
||||
bool ring_buffer_empty(struct trace_buffer *buffer)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
unsigned long flags;
|
||||
|
@ -4478,7 +4478,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_empty);
|
|||
* @buffer: The ring buffer
|
||||
* @cpu: The CPU buffer to test
|
||||
*/
|
||||
bool ring_buffer_empty_cpu(struct ring_buffer *buffer, int cpu)
|
||||
bool ring_buffer_empty_cpu(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
unsigned long flags;
|
||||
|
@ -4504,14 +4504,15 @@ EXPORT_SYMBOL_GPL(ring_buffer_empty_cpu);
|
|||
* ring_buffer_swap_cpu - swap a CPU buffer between two ring buffers
|
||||
* @buffer_a: One buffer to swap with
|
||||
* @buffer_b: The other buffer to swap with
|
||||
* @cpu: the CPU of the buffers to swap
|
||||
*
|
||||
* This function is useful for tracers that want to take a "snapshot"
|
||||
* of a CPU buffer and has another back up buffer lying around.
|
||||
* it is expected that the tracer handles the cpu buffer not being
|
||||
* used at the moment.
|
||||
*/
|
||||
int ring_buffer_swap_cpu(struct ring_buffer *buffer_a,
|
||||
struct ring_buffer *buffer_b, int cpu)
|
||||
int ring_buffer_swap_cpu(struct trace_buffer *buffer_a,
|
||||
struct trace_buffer *buffer_b, int cpu)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer_a;
|
||||
struct ring_buffer_per_cpu *cpu_buffer_b;
|
||||
|
@ -4590,7 +4591,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_swap_cpu);
|
|||
* Returns:
|
||||
* The page allocated, or ERR_PTR
|
||||
*/
|
||||
void *ring_buffer_alloc_read_page(struct ring_buffer *buffer, int cpu)
|
||||
void *ring_buffer_alloc_read_page(struct trace_buffer *buffer, int cpu)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer;
|
||||
struct buffer_data_page *bpage = NULL;
|
||||
|
@ -4637,7 +4638,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_alloc_read_page);
|
|||
*
|
||||
* Free a page allocated from ring_buffer_alloc_read_page.
|
||||
*/
|
||||
void ring_buffer_free_read_page(struct ring_buffer *buffer, int cpu, void *data)
|
||||
void ring_buffer_free_read_page(struct trace_buffer *buffer, int cpu, void *data)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer = buffer->buffers[cpu];
|
||||
struct buffer_data_page *bpage = data;
|
||||
|
@ -4697,7 +4698,7 @@ EXPORT_SYMBOL_GPL(ring_buffer_free_read_page);
|
|||
* >=0 if data has been transferred, returns the offset of consumed data.
|
||||
* <0 if no data has been transferred.
|
||||
*/
|
||||
int ring_buffer_read_page(struct ring_buffer *buffer,
|
||||
int ring_buffer_read_page(struct trace_buffer *buffer,
|
||||
void **data_page, size_t len, int cpu, int full)
|
||||
{
|
||||
struct ring_buffer_per_cpu *cpu_buffer = buffer->buffers[cpu];
|
||||
|
@ -4868,12 +4869,12 @@ EXPORT_SYMBOL_GPL(ring_buffer_read_page);
|
|||
*/
|
||||
int trace_rb_cpu_prepare(unsigned int cpu, struct hlist_node *node)
|
||||
{
|
||||
struct ring_buffer *buffer;
|
||||
struct trace_buffer *buffer;
|
||||
long nr_pages_same;
|
||||
int cpu_i;
|
||||
unsigned long nr_pages;
|
||||
|
||||
buffer = container_of(node, struct ring_buffer, node);
|
||||
buffer = container_of(node, struct trace_buffer, node);
|
||||
if (cpumask_test_cpu(cpu, buffer->cpumask))
|
||||
return 0;
|
||||
|
||||
|
@ -4923,7 +4924,7 @@ int trace_rb_cpu_prepare(unsigned int cpu, struct hlist_node *node)
|
|||
static struct task_struct *rb_threads[NR_CPUS] __initdata;
|
||||
|
||||
struct rb_test_data {
|
||||
struct ring_buffer *buffer;
|
||||
struct trace_buffer *buffer;
|
||||
unsigned long events;
|
||||
unsigned long bytes_written;
|
||||
unsigned long bytes_alloc;
|
||||
|
@ -5065,7 +5066,7 @@ static __init int rb_hammer_test(void *arg)
|
|||
static __init int test_ringbuffer(void)
|
||||
{
|
||||
struct task_struct *rb_hammer;
|
||||
struct ring_buffer *buffer;
|
||||
struct trace_buffer *buffer;
|
||||
int cpu;
|
||||
int ret = 0;
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ static int reader_finish;
|
|||
static DECLARE_COMPLETION(read_start);
|
||||
static DECLARE_COMPLETION(read_done);
|
||||
|
||||
static struct ring_buffer *buffer;
|
||||
static struct trace_buffer *buffer;
|
||||
static struct task_struct *producer;
|
||||
static struct task_struct *consumer;
|
||||
static unsigned long read;
|
||||
|
|
|
@ -0,0 +1,523 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Test module for in-kernel sythetic event creation and generation.
|
||||
*
|
||||
* Copyright (C) 2019 Tom Zanussi <zanussi@kernel.org>
|
||||
*/
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/trace_events.h>
|
||||
|
||||
/*
|
||||
* This module is a simple test of basic functionality for in-kernel
|
||||
* synthetic event creation and generation, the first and second tests
|
||||
* using synth_event_gen_cmd_start() and synth_event_add_field(), the
|
||||
* third uses synth_event_create() to do it all at once with a static
|
||||
* field array.
|
||||
*
|
||||
* Following that are a few examples using the created events to test
|
||||
* various ways of tracing a synthetic event.
|
||||
*
|
||||
* To test, select CONFIG_SYNTH_EVENT_GEN_TEST and build the module.
|
||||
* Then:
|
||||
*
|
||||
* # insmod kernel/trace/synth_event_gen_test.ko
|
||||
* # cat /sys/kernel/debug/tracing/trace
|
||||
*
|
||||
* You should see several events in the trace buffer -
|
||||
* "create_synth_test", "empty_synth_test", and several instances of
|
||||
* "gen_synth_test".
|
||||
*
|
||||
* To remove the events, remove the module:
|
||||
*
|
||||
* # rmmod synth_event_gen_test
|
||||
*
|
||||
*/
|
||||
|
||||
static struct trace_event_file *create_synth_test;
|
||||
static struct trace_event_file *empty_synth_test;
|
||||
static struct trace_event_file *gen_synth_test;
|
||||
|
||||
/*
|
||||
* Test to make sure we can create a synthetic event, then add more
|
||||
* fields.
|
||||
*/
|
||||
static int __init test_gen_synth_cmd(void)
|
||||
{
|
||||
struct dynevent_cmd cmd;
|
||||
u64 vals[7];
|
||||
char *buf;
|
||||
int ret;
|
||||
|
||||
/* Create a buffer to hold the generated command */
|
||||
buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Before generating the command, initialize the cmd object */
|
||||
synth_event_cmd_init(&cmd, buf, MAX_DYNEVENT_CMD_LEN);
|
||||
|
||||
/*
|
||||
* Create the empty gen_synth_test synthetic event with the
|
||||
* first 4 fields.
|
||||
*/
|
||||
ret = synth_event_gen_cmd_start(&cmd, "gen_synth_test", THIS_MODULE,
|
||||
"pid_t", "next_pid_field",
|
||||
"char[16]", "next_comm_field",
|
||||
"u64", "ts_ns",
|
||||
"u64", "ts_ms");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
/* Use synth_event_add_field to add the rest of the fields */
|
||||
|
||||
ret = synth_event_add_field(&cmd, "unsigned int", "cpu");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
ret = synth_event_add_field(&cmd, "char[64]", "my_string_field");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
ret = synth_event_add_field(&cmd, "int", "my_int_field");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
ret = synth_event_gen_cmd_end(&cmd);
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
/*
|
||||
* Now get the gen_synth_test event file. We need to prevent
|
||||
* the instance and event from disappearing from underneath
|
||||
* us, which trace_get_event_file() does (though in this case
|
||||
* we're using the top-level instance which never goes away).
|
||||
*/
|
||||
gen_synth_test = trace_get_event_file(NULL, "synthetic",
|
||||
"gen_synth_test");
|
||||
if (IS_ERR(gen_synth_test)) {
|
||||
ret = PTR_ERR(gen_synth_test);
|
||||
goto delete;
|
||||
}
|
||||
|
||||
/* Enable the event or you won't see anything */
|
||||
ret = trace_array_set_clr_event(gen_synth_test->tr,
|
||||
"synthetic", "gen_synth_test", true);
|
||||
if (ret) {
|
||||
trace_put_event_file(gen_synth_test);
|
||||
goto delete;
|
||||
}
|
||||
|
||||
/* Create some bogus values just for testing */
|
||||
|
||||
vals[0] = 777; /* next_pid_field */
|
||||
vals[1] = (u64)"hula hoops"; /* next_comm_field */
|
||||
vals[2] = 1000000; /* ts_ns */
|
||||
vals[3] = 1000; /* ts_ms */
|
||||
vals[4] = smp_processor_id(); /* cpu */
|
||||
vals[5] = (u64)"thneed"; /* my_string_field */
|
||||
vals[6] = 598; /* my_int_field */
|
||||
|
||||
/* Now generate a gen_synth_test event */
|
||||
ret = synth_event_trace_array(gen_synth_test, vals, ARRAY_SIZE(vals));
|
||||
out:
|
||||
return ret;
|
||||
delete:
|
||||
/* We got an error after creating the event, delete it */
|
||||
synth_event_delete("gen_synth_test");
|
||||
free:
|
||||
kfree(buf);
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test to make sure we can create an initially empty synthetic event,
|
||||
* then add all the fields.
|
||||
*/
|
||||
static int __init test_empty_synth_event(void)
|
||||
{
|
||||
struct dynevent_cmd cmd;
|
||||
u64 vals[7];
|
||||
char *buf;
|
||||
int ret;
|
||||
|
||||
/* Create a buffer to hold the generated command */
|
||||
buf = kzalloc(MAX_DYNEVENT_CMD_LEN, GFP_KERNEL);
|
||||
if (!buf)
|
||||
return -ENOMEM;
|
||||
|
||||
/* Before generating the command, initialize the cmd object */
|
||||
synth_event_cmd_init(&cmd, buf, MAX_DYNEVENT_CMD_LEN);
|
||||
|
||||
/*
|
||||
* Create the empty_synth_test synthetic event with no fields.
|
||||
*/
|
||||
ret = synth_event_gen_cmd_start(&cmd, "empty_synth_test", THIS_MODULE);
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
/* Use synth_event_add_field to add all of the fields */
|
||||
|
||||
ret = synth_event_add_field(&cmd, "pid_t", "next_pid_field");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
ret = synth_event_add_field(&cmd, "char[16]", "next_comm_field");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
ret = synth_event_add_field(&cmd, "u64", "ts_ns");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
ret = synth_event_add_field(&cmd, "u64", "ts_ms");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
ret = synth_event_add_field(&cmd, "unsigned int", "cpu");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
ret = synth_event_add_field(&cmd, "char[64]", "my_string_field");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
ret = synth_event_add_field(&cmd, "int", "my_int_field");
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
/* All fields have been added, close and register the synth event */
|
||||
|
||||
ret = synth_event_gen_cmd_end(&cmd);
|
||||
if (ret)
|
||||
goto free;
|
||||
|
||||
/*
|
||||
* Now get the empty_synth_test event file. We need to
|
||||
* prevent the instance and event from disappearing from
|
||||
* underneath us, which trace_get_event_file() does (though in
|
||||
* this case we're using the top-level instance which never
|
||||
* goes away).
|
||||
*/
|
||||
empty_synth_test = trace_get_event_file(NULL, "synthetic",
|
||||
"empty_synth_test");
|
||||
if (IS_ERR(empty_synth_test)) {
|
||||
ret = PTR_ERR(empty_synth_test);
|
||||
goto delete;
|
||||
}
|
||||
|
||||
/* Enable the event or you won't see anything */
|
||||
ret = trace_array_set_clr_event(empty_synth_test->tr,
|
||||
"synthetic", "empty_synth_test", true);
|
||||
if (ret) {
|
||||
trace_put_event_file(empty_synth_test);
|
||||
goto delete;
|
||||
}
|
||||
|
||||
/* Create some bogus values just for testing */
|
||||
|
||||
vals[0] = 777; /* next_pid_field */
|
||||
vals[1] = (u64)"tiddlywinks"; /* next_comm_field */
|
||||
vals[2] = 1000000; /* ts_ns */
|
||||
vals[3] = 1000; /* ts_ms */
|
||||
vals[4] = smp_processor_id(); /* cpu */
|
||||
vals[5] = (u64)"thneed_2.0"; /* my_string_field */
|
||||
vals[6] = 399; /* my_int_field */
|
||||
|
||||
/* Now trace an empty_synth_test event */
|
||||
ret = synth_event_trace_array(empty_synth_test, vals, ARRAY_SIZE(vals));
|
||||
out:
|
||||
return ret;
|
||||
delete:
|
||||
/* We got an error after creating the event, delete it */
|
||||
synth_event_delete("empty_synth_test");
|
||||
free:
|
||||
kfree(buf);
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
static struct synth_field_desc create_synth_test_fields[] = {
|
||||
{ .type = "pid_t", .name = "next_pid_field" },
|
||||
{ .type = "char[16]", .name = "next_comm_field" },
|
||||
{ .type = "u64", .name = "ts_ns" },
|
||||
{ .type = "u64", .name = "ts_ms" },
|
||||
{ .type = "unsigned int", .name = "cpu" },
|
||||
{ .type = "char[64]", .name = "my_string_field" },
|
||||
{ .type = "int", .name = "my_int_field" },
|
||||
};
|
||||
|
||||
/*
|
||||
* Test synthetic event creation all at once from array of field
|
||||
* descriptors.
|
||||
*/
|
||||
static int __init test_create_synth_event(void)
|
||||
{
|
||||
u64 vals[7];
|
||||
int ret;
|
||||
|
||||
/* Create the create_synth_test event with the fields above */
|
||||
ret = synth_event_create("create_synth_test",
|
||||
create_synth_test_fields,
|
||||
ARRAY_SIZE(create_synth_test_fields),
|
||||
THIS_MODULE);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Now get the create_synth_test event file. We need to
|
||||
* prevent the instance and event from disappearing from
|
||||
* underneath us, which trace_get_event_file() does (though in
|
||||
* this case we're using the top-level instance which never
|
||||
* goes away).
|
||||
*/
|
||||
create_synth_test = trace_get_event_file(NULL, "synthetic",
|
||||
"create_synth_test");
|
||||
if (IS_ERR(create_synth_test)) {
|
||||
ret = PTR_ERR(create_synth_test);
|
||||
goto delete;
|
||||
}
|
||||
|
||||
/* Enable the event or you won't see anything */
|
||||
ret = trace_array_set_clr_event(create_synth_test->tr,
|
||||
"synthetic", "create_synth_test", true);
|
||||
if (ret) {
|
||||
trace_put_event_file(create_synth_test);
|
||||
goto delete;
|
||||
}
|
||||
|
||||
/* Create some bogus values just for testing */
|
||||
|
||||
vals[0] = 777; /* next_pid_field */
|
||||
vals[1] = (u64)"tiddlywinks"; /* next_comm_field */
|
||||
vals[2] = 1000000; /* ts_ns */
|
||||
vals[3] = 1000; /* ts_ms */
|
||||
vals[4] = smp_processor_id(); /* cpu */
|
||||
vals[5] = (u64)"thneed"; /* my_string_field */
|
||||
vals[6] = 398; /* my_int_field */
|
||||
|
||||
/* Now generate a create_synth_test event */
|
||||
ret = synth_event_trace_array(create_synth_test, vals, ARRAY_SIZE(vals));
|
||||
out:
|
||||
return ret;
|
||||
delete:
|
||||
/* We got an error after creating the event, delete it */
|
||||
ret = synth_event_delete("create_synth_test");
|
||||
|
||||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test tracing a synthetic event by reserving trace buffer space,
|
||||
* then filling in fields one after another.
|
||||
*/
|
||||
static int __init test_add_next_synth_val(void)
|
||||
{
|
||||
struct synth_event_trace_state trace_state;
|
||||
int ret;
|
||||
|
||||
/* Start by reserving space in the trace buffer */
|
||||
ret = synth_event_trace_start(gen_synth_test, &trace_state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Write some bogus values into the trace buffer, one after another */
|
||||
|
||||
/* next_pid_field */
|
||||
ret = synth_event_add_next_val(777, &trace_state);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* next_comm_field */
|
||||
ret = synth_event_add_next_val((u64)"slinky", &trace_state);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* ts_ns */
|
||||
ret = synth_event_add_next_val(1000000, &trace_state);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* ts_ms */
|
||||
ret = synth_event_add_next_val(1000, &trace_state);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* cpu */
|
||||
ret = synth_event_add_next_val(smp_processor_id(), &trace_state);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* my_string_field */
|
||||
ret = synth_event_add_next_val((u64)"thneed_2.01", &trace_state);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
/* my_int_field */
|
||||
ret = synth_event_add_next_val(395, &trace_state);
|
||||
out:
|
||||
/* Finally, commit the event */
|
||||
ret = synth_event_trace_end(&trace_state);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test tracing a synthetic event by reserving trace buffer space,
|
||||
* then filling in fields using field names, which can be done in any
|
||||
* order.
|
||||
*/
|
||||
static int __init test_add_synth_val(void)
|
||||
{
|
||||
struct synth_event_trace_state trace_state;
|
||||
int ret;
|
||||
|
||||
/* Start by reserving space in the trace buffer */
|
||||
ret = synth_event_trace_start(gen_synth_test, &trace_state);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
/* Write some bogus values into the trace buffer, using field names */
|
||||
|
||||
ret = synth_event_add_val("ts_ns", 1000000, &trace_state);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = synth_event_add_val("ts_ms", 1000, &trace_state);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = synth_event_add_val("cpu", smp_processor_id(), &trace_state);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = synth_event_add_val("next_pid_field", 777, &trace_state);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = synth_event_add_val("next_comm_field", (u64)"silly putty",
|
||||
&trace_state);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = synth_event_add_val("my_string_field", (u64)"thneed_9",
|
||||
&trace_state);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
ret = synth_event_add_val("my_int_field", 3999, &trace_state);
|
||||
out:
|
||||
/* Finally, commit the event */
|
||||
ret = synth_event_trace_end(&trace_state);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/*
|
||||
* Test tracing a synthetic event all at once from array of values.
|
||||
*/
|
||||
static int __init test_trace_synth_event(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
/* Trace some bogus values just for testing */
|
||||
ret = synth_event_trace(create_synth_test, 7, /* number of values */
|
||||
444, /* next_pid_field */
|
||||
(u64)"clackers", /* next_comm_field */
|
||||
1000000, /* ts_ns */
|
||||
1000, /* ts_ms */
|
||||
smp_processor_id(), /* cpu */
|
||||
(u64)"Thneed", /* my_string_field */
|
||||
999); /* my_int_field */
|
||||
return ret;
|
||||
}
|
||||
|
||||
static int __init synth_event_gen_test_init(void)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = test_gen_synth_cmd();
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
ret = test_empty_synth_event();
|
||||
if (ret) {
|
||||
WARN_ON(trace_array_set_clr_event(gen_synth_test->tr,
|
||||
"synthetic",
|
||||
"gen_synth_test", false));
|
||||
trace_put_event_file(gen_synth_test);
|
||||
WARN_ON(synth_event_delete("gen_synth_test"));
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = test_create_synth_event();
|
||||
if (ret) {
|
||||
WARN_ON(trace_array_set_clr_event(gen_synth_test->tr,
|
||||
"synthetic",
|
||||
"gen_synth_test", false));
|
||||
trace_put_event_file(gen_synth_test);
|
||||
WARN_ON(synth_event_delete("gen_synth_test"));
|
||||
|
||||
WARN_ON(trace_array_set_clr_event(empty_synth_test->tr,
|
||||
"synthetic",
|
||||
"empty_synth_test", false));
|
||||
trace_put_event_file(empty_synth_test);
|
||||
WARN_ON(synth_event_delete("empty_synth_test"));
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = test_add_next_synth_val();
|
||||
WARN_ON(ret);
|
||||
|
||||
ret = test_add_synth_val();
|
||||
WARN_ON(ret);
|
||||
|
||||
ret = test_trace_synth_event();
|
||||
WARN_ON(ret);
|
||||
out:
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void __exit synth_event_gen_test_exit(void)
|
||||
{
|
||||
/* Disable the event or you can't remove it */
|
||||
WARN_ON(trace_array_set_clr_event(gen_synth_test->tr,
|
||||
"synthetic",
|
||||
"gen_synth_test", false));
|
||||
|
||||
/* Now give the file and instance back */
|
||||
trace_put_event_file(gen_synth_test);
|
||||
|
||||
/* Now unregister and free the synthetic event */
|
||||
WARN_ON(synth_event_delete("gen_synth_test"));
|
||||
|
||||
/* Disable the event or you can't remove it */
|
||||
WARN_ON(trace_array_set_clr_event(empty_synth_test->tr,
|
||||
"synthetic",
|
||||
"empty_synth_test", false));
|
||||
|
||||
/* Now give the file and instance back */
|
||||
trace_put_event_file(empty_synth_test);
|
||||
|
||||
/* Now unregister and free the synthetic event */
|
||||
WARN_ON(synth_event_delete("empty_synth_test"));
|
||||
|
||||
/* Disable the event or you can't remove it */
|
||||
WARN_ON(trace_array_set_clr_event(create_synth_test->tr,
|
||||
"synthetic",
|
||||
"create_synth_test", false));
|
||||
|
||||
/* Now give the file and instance back */
|
||||
trace_put_event_file(create_synth_test);
|
||||
|
||||
/* Now unregister and free the synthetic event */
|
||||
WARN_ON(synth_event_delete("create_synth_test"));
|
||||
}
|
||||
|
||||
module_init(synth_event_gen_test_init)
|
||||
module_exit(synth_event_gen_test_exit)
|
||||
|
||||
MODULE_AUTHOR("Tom Zanussi");
|
||||
MODULE_DESCRIPTION("synthetic event generation test");
|
||||
MODULE_LICENSE("GPL v2");
|
File diff suppressed because it is too large
Load Diff
|
@ -93,6 +93,18 @@ enum trace_type {
|
|||
|
||||
#include "trace_entries.h"
|
||||
|
||||
/* Use this for memory failure errors */
|
||||
#define MEM_FAIL(condition, fmt, ...) ({ \
|
||||
static bool __section(.data.once) __warned; \
|
||||
int __ret_warn_once = !!(condition); \
|
||||
\
|
||||
if (unlikely(__ret_warn_once && !__warned)) { \
|
||||
__warned = true; \
|
||||
pr_err("ERROR: " fmt, ##__VA_ARGS__); \
|
||||
} \
|
||||
unlikely(__ret_warn_once); \
|
||||
})
|
||||
|
||||
/*
|
||||
* syscalls are special, and need special handling, this is why
|
||||
* they are not included in trace_entries.h
|
||||
|
@ -175,9 +187,9 @@ struct trace_array_cpu {
|
|||
struct tracer;
|
||||
struct trace_option_dentry;
|
||||
|
||||
struct trace_buffer {
|
||||
struct array_buffer {
|
||||
struct trace_array *tr;
|
||||
struct ring_buffer *buffer;
|
||||
struct trace_buffer *buffer;
|
||||
struct trace_array_cpu __percpu *data;
|
||||
u64 time_start;
|
||||
int cpu;
|
||||
|
@ -248,7 +260,7 @@ struct cond_snapshot {
|
|||
struct trace_array {
|
||||
struct list_head list;
|
||||
char *name;
|
||||
struct trace_buffer trace_buffer;
|
||||
struct array_buffer array_buffer;
|
||||
#ifdef CONFIG_TRACER_MAX_TRACE
|
||||
/*
|
||||
* The max_buffer is used to snapshot the trace when a maximum
|
||||
|
@ -256,12 +268,12 @@ struct trace_array {
|
|||
* Some tracers will use this to store a maximum trace while
|
||||
* it continues examining live traces.
|
||||
*
|
||||
* The buffers for the max_buffer are set up the same as the trace_buffer
|
||||
* The buffers for the max_buffer are set up the same as the array_buffer
|
||||
* When a snapshot is taken, the buffer of the max_buffer is swapped
|
||||
* with the buffer of the trace_buffer and the buffers are reset for
|
||||
* the trace_buffer so the tracing can continue.
|
||||
* with the buffer of the array_buffer and the buffers are reset for
|
||||
* the array_buffer so the tracing can continue.
|
||||
*/
|
||||
struct trace_buffer max_buffer;
|
||||
struct array_buffer max_buffer;
|
||||
bool allocated_snapshot;
|
||||
#endif
|
||||
#if defined(CONFIG_TRACER_MAX_TRACE) || defined(CONFIG_HWLAT_TRACER)
|
||||
|
@ -345,6 +357,8 @@ extern struct mutex trace_types_lock;
|
|||
|
||||
extern int trace_array_get(struct trace_array *tr);
|
||||
extern int tracing_check_open_get_tr(struct trace_array *tr);
|
||||
extern struct trace_array *trace_array_find(const char *instance);
|
||||
extern struct trace_array *trace_array_find_get(const char *instance);
|
||||
|
||||
extern int tracing_set_time_stamp_abs(struct trace_array *tr, bool abs);
|
||||
extern int tracing_set_clock(struct trace_array *tr, const char *clockstr);
|
||||
|
@ -684,7 +698,7 @@ trace_buffer_iter(struct trace_iterator *iter, int cpu)
|
|||
|
||||
int tracer_init(struct tracer *t, struct trace_array *tr);
|
||||
int tracing_is_enabled(void);
|
||||
void tracing_reset_online_cpus(struct trace_buffer *buf);
|
||||
void tracing_reset_online_cpus(struct array_buffer *buf);
|
||||
void tracing_reset_current(int cpu);
|
||||
void tracing_reset_all_online_cpus(void);
|
||||
int tracing_open_generic(struct inode *inode, struct file *filp);
|
||||
|
@ -704,7 +718,7 @@ struct dentry *tracing_init_dentry(void);
|
|||
struct ring_buffer_event;
|
||||
|
||||
struct ring_buffer_event *
|
||||
trace_buffer_lock_reserve(struct ring_buffer *buffer,
|
||||
trace_buffer_lock_reserve(struct trace_buffer *buffer,
|
||||
int type,
|
||||
unsigned long len,
|
||||
unsigned long flags,
|
||||
|
@ -716,7 +730,7 @@ struct trace_entry *tracing_get_trace_entry(struct trace_array *tr,
|
|||
struct trace_entry *trace_find_next_entry(struct trace_iterator *iter,
|
||||
int *ent_cpu, u64 *ent_ts);
|
||||
|
||||
void trace_buffer_unlock_commit_nostack(struct ring_buffer *buffer,
|
||||
void trace_buffer_unlock_commit_nostack(struct trace_buffer *buffer,
|
||||
struct ring_buffer_event *event);
|
||||
|
||||
int trace_empty(struct trace_iterator *iter);
|
||||
|
@ -872,7 +886,7 @@ trace_vprintk(unsigned long ip, const char *fmt, va_list args);
|
|||
extern int
|
||||
trace_array_vprintk(struct trace_array *tr,
|
||||
unsigned long ip, const char *fmt, va_list args);
|
||||
int trace_array_printk_buf(struct ring_buffer *buffer,
|
||||
int trace_array_printk_buf(struct trace_buffer *buffer,
|
||||
unsigned long ip, const char *fmt, ...);
|
||||
void trace_printk_seq(struct trace_seq *s);
|
||||
enum print_line_t print_trace_line(struct trace_iterator *iter);
|
||||
|
@ -949,22 +963,31 @@ extern void __trace_graph_return(struct trace_array *tr,
|
|||
unsigned long flags, int pc);
|
||||
|
||||
#ifdef CONFIG_DYNAMIC_FTRACE
|
||||
extern struct ftrace_hash *ftrace_graph_hash;
|
||||
extern struct ftrace_hash *ftrace_graph_notrace_hash;
|
||||
extern struct ftrace_hash __rcu *ftrace_graph_hash;
|
||||
extern struct ftrace_hash __rcu *ftrace_graph_notrace_hash;
|
||||
|
||||
static inline int ftrace_graph_addr(struct ftrace_graph_ent *trace)
|
||||
{
|
||||
unsigned long addr = trace->func;
|
||||
int ret = 0;
|
||||
struct ftrace_hash *hash;
|
||||
|
||||
preempt_disable_notrace();
|
||||
|
||||
if (ftrace_hash_empty(ftrace_graph_hash)) {
|
||||
/*
|
||||
* Have to open code "rcu_dereference_sched()" because the
|
||||
* function graph tracer can be called when RCU is not
|
||||
* "watching".
|
||||
* Protected with schedule_on_each_cpu(ftrace_sync)
|
||||
*/
|
||||
hash = rcu_dereference_protected(ftrace_graph_hash, !preemptible());
|
||||
|
||||
if (ftrace_hash_empty(hash)) {
|
||||
ret = 1;
|
||||
goto out;
|
||||
}
|
||||
|
||||
if (ftrace_lookup_ip(ftrace_graph_hash, addr)) {
|
||||
if (ftrace_lookup_ip(hash, addr)) {
|
||||
|
||||
/*
|
||||
* This needs to be cleared on the return functions
|
||||
|
@ -1000,10 +1023,20 @@ static inline void ftrace_graph_addr_finish(struct ftrace_graph_ret *trace)
|
|||
static inline int ftrace_graph_notrace_addr(unsigned long addr)
|
||||
{
|
||||
int ret = 0;
|
||||
struct ftrace_hash *notrace_hash;
|
||||
|
||||
preempt_disable_notrace();
|
||||
|
||||
if (ftrace_lookup_ip(ftrace_graph_notrace_hash, addr))
|
||||
/*
|
||||
* Have to open code "rcu_dereference_sched()" because the
|
||||
* function graph tracer can be called when RCU is not
|
||||
* "watching".
|
||||
* Protected with schedule_on_each_cpu(ftrace_sync)
|
||||
*/
|
||||
notrace_hash = rcu_dereference_protected(ftrace_graph_notrace_hash,
|
||||
!preemptible());
|
||||
|
||||
if (ftrace_lookup_ip(notrace_hash, addr))
|
||||
ret = 1;
|
||||
|
||||
preempt_enable_notrace();
|
||||
|
@ -1056,7 +1089,7 @@ struct ftrace_func_command {
|
|||
extern bool ftrace_filter_param __initdata;
|
||||
static inline int ftrace_trace_task(struct trace_array *tr)
|
||||
{
|
||||
return !this_cpu_read(tr->trace_buffer.data->ftrace_ignore_pid);
|
||||
return !this_cpu_read(tr->array_buffer.data->ftrace_ignore_pid);
|
||||
}
|
||||
extern int ftrace_is_dead(void);
|
||||
int ftrace_create_function_files(struct trace_array *tr,
|
||||
|
@ -1144,6 +1177,11 @@ int unregister_ftrace_command(struct ftrace_func_command *cmd);
|
|||
void ftrace_create_filter_files(struct ftrace_ops *ops,
|
||||
struct dentry *parent);
|
||||
void ftrace_destroy_filter_files(struct ftrace_ops *ops);
|
||||
|
||||
extern int ftrace_set_filter(struct ftrace_ops *ops, unsigned char *buf,
|
||||
int len, int reset);
|
||||
extern int ftrace_set_notrace(struct ftrace_ops *ops, unsigned char *buf,
|
||||
int len, int reset);
|
||||
#else
|
||||
struct ftrace_func_command;
|
||||
|
||||
|
@ -1366,17 +1404,17 @@ struct trace_subsystem_dir {
|
|||
};
|
||||
|
||||
extern int call_filter_check_discard(struct trace_event_call *call, void *rec,
|
||||
struct ring_buffer *buffer,
|
||||
struct trace_buffer *buffer,
|
||||
struct ring_buffer_event *event);
|
||||
|
||||
void trace_buffer_unlock_commit_regs(struct trace_array *tr,
|
||||
struct ring_buffer *buffer,
|
||||
struct trace_buffer *buffer,
|
||||
struct ring_buffer_event *event,
|
||||
unsigned long flags, int pc,
|
||||
struct pt_regs *regs);
|
||||
|
||||
static inline void trace_buffer_unlock_commit(struct trace_array *tr,
|
||||
struct ring_buffer *buffer,
|
||||
struct trace_buffer *buffer,
|
||||
struct ring_buffer_event *event,
|
||||
unsigned long flags, int pc)
|
||||
{
|
||||
|
@ -1389,7 +1427,7 @@ void trace_buffered_event_disable(void);
|
|||
void trace_buffered_event_enable(void);
|
||||
|
||||
static inline void
|
||||
__trace_event_discard_commit(struct ring_buffer *buffer,
|
||||
__trace_event_discard_commit(struct trace_buffer *buffer,
|
||||
struct ring_buffer_event *event)
|
||||
{
|
||||
if (this_cpu_read(trace_buffered_event) == event) {
|
||||
|
@ -1415,7 +1453,7 @@ __trace_event_discard_commit(struct ring_buffer *buffer,
|
|||
*/
|
||||
static inline bool
|
||||
__event_trigger_test_discard(struct trace_event_file *file,
|
||||
struct ring_buffer *buffer,
|
||||
struct trace_buffer *buffer,
|
||||
struct ring_buffer_event *event,
|
||||
void *entry,
|
||||
enum event_trigger_type *tt)
|
||||
|
@ -1450,7 +1488,7 @@ __event_trigger_test_discard(struct trace_event_file *file,
|
|||
*/
|
||||
static inline void
|
||||
event_trigger_unlock_commit(struct trace_event_file *file,
|
||||
struct ring_buffer *buffer,
|
||||
struct trace_buffer *buffer,
|
||||
struct ring_buffer_event *event,
|
||||
void *entry, unsigned long irq_flags, int pc)
|
||||
{
|
||||
|
@ -1481,7 +1519,7 @@ event_trigger_unlock_commit(struct trace_event_file *file,
|
|||
*/
|
||||
static inline void
|
||||
event_trigger_unlock_commit_regs(struct trace_event_file *file,
|
||||
struct ring_buffer *buffer,
|
||||
struct trace_buffer *buffer,
|
||||
struct ring_buffer_event *event,
|
||||
void *entry, unsigned long irq_flags, int pc,
|
||||
struct pt_regs *regs)
|
||||
|
@ -1892,6 +1930,15 @@ void trace_printk_start_comm(void);
|
|||
int trace_keep_overwrite(struct tracer *tracer, u32 mask, int set);
|
||||
int set_tracer_flag(struct trace_array *tr, unsigned int mask, int enabled);
|
||||
|
||||
/* Used from boot time tracer */
|
||||
extern int trace_set_options(struct trace_array *tr, char *option);
|
||||
extern int tracing_set_tracer(struct trace_array *tr, const char *buf);
|
||||
extern ssize_t tracing_resize_ring_buffer(struct trace_array *tr,
|
||||
unsigned long size, int cpu_id);
|
||||
extern int tracing_set_cpumask(struct trace_array *tr,
|
||||
cpumask_var_t tracing_cpumask_new);
|
||||
|
||||
|
||||
#define MAX_EVENT_NAME_LEN 64
|
||||
|
||||
extern int trace_run_command(const char *buf, int (*createfn)(int, char**));
|
||||
|
@ -1949,6 +1996,9 @@ static inline const char *get_syscall_name(int syscall)
|
|||
#ifdef CONFIG_EVENT_TRACING
|
||||
void trace_event_init(void);
|
||||
void trace_event_eval_update(struct trace_eval_map **map, int len);
|
||||
/* Used from boot time tracer */
|
||||
extern int ftrace_set_clr_event(struct trace_array *tr, char *buf, int set);
|
||||
extern int trigger_process_regex(struct trace_event_file *file, char *buff);
|
||||
#else
|
||||
static inline void __init trace_event_init(void) { }
|
||||
static inline void trace_event_eval_update(struct trace_eval_map **map, int len) { }
|
||||
|
|
|
@ -0,0 +1,334 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* trace_boot.c
|
||||
* Tracing kernel boot-time
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "trace_boot: " fmt
|
||||
|
||||
#include <linux/bootconfig.h>
|
||||
#include <linux/cpumask.h>
|
||||
#include <linux/ftrace.h>
|
||||
#include <linux/init.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/mutex.h>
|
||||
#include <linux/string.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/trace.h>
|
||||
#include <linux/trace_events.h>
|
||||
|
||||
#include "trace.h"
|
||||
|
||||
#define MAX_BUF_LEN 256
|
||||
|
||||
static void __init
|
||||
trace_boot_set_instance_options(struct trace_array *tr, struct xbc_node *node)
|
||||
{
|
||||
struct xbc_node *anode;
|
||||
const char *p;
|
||||
char buf[MAX_BUF_LEN];
|
||||
unsigned long v = 0;
|
||||
|
||||
/* Common ftrace options */
|
||||
xbc_node_for_each_array_value(node, "options", anode, p) {
|
||||
if (strlcpy(buf, p, ARRAY_SIZE(buf)) >= ARRAY_SIZE(buf)) {
|
||||
pr_err("String is too long: %s\n", p);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (trace_set_options(tr, buf) < 0)
|
||||
pr_err("Failed to set option: %s\n", buf);
|
||||
}
|
||||
|
||||
p = xbc_node_find_value(node, "trace_clock", NULL);
|
||||
if (p && *p != '\0') {
|
||||
if (tracing_set_clock(tr, p) < 0)
|
||||
pr_err("Failed to set trace clock: %s\n", p);
|
||||
}
|
||||
|
||||
p = xbc_node_find_value(node, "buffer_size", NULL);
|
||||
if (p && *p != '\0') {
|
||||
v = memparse(p, NULL);
|
||||
if (v < PAGE_SIZE)
|
||||
pr_err("Buffer size is too small: %s\n", p);
|
||||
if (tracing_resize_ring_buffer(tr, v, RING_BUFFER_ALL_CPUS) < 0)
|
||||
pr_err("Failed to resize trace buffer to %s\n", p);
|
||||
}
|
||||
|
||||
p = xbc_node_find_value(node, "cpumask", NULL);
|
||||
if (p && *p != '\0') {
|
||||
cpumask_var_t new_mask;
|
||||
|
||||
if (alloc_cpumask_var(&new_mask, GFP_KERNEL)) {
|
||||
if (cpumask_parse(p, new_mask) < 0 ||
|
||||
tracing_set_cpumask(tr, new_mask) < 0)
|
||||
pr_err("Failed to set new CPU mask %s\n", p);
|
||||
free_cpumask_var(new_mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_EVENT_TRACING
|
||||
static void __init
|
||||
trace_boot_enable_events(struct trace_array *tr, struct xbc_node *node)
|
||||
{
|
||||
struct xbc_node *anode;
|
||||
char buf[MAX_BUF_LEN];
|
||||
const char *p;
|
||||
|
||||
xbc_node_for_each_array_value(node, "events", anode, p) {
|
||||
if (strlcpy(buf, p, ARRAY_SIZE(buf)) >= ARRAY_SIZE(buf)) {
|
||||
pr_err("String is too long: %s\n", p);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (ftrace_set_clr_event(tr, buf, 1) < 0)
|
||||
pr_err("Failed to enable event: %s\n", p);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CONFIG_KPROBE_EVENTS
|
||||
static int __init
|
||||
trace_boot_add_kprobe_event(struct xbc_node *node, const char *event)
|
||||
{
|
||||
struct dynevent_cmd cmd;
|
||||
struct xbc_node *anode;
|
||||
char buf[MAX_BUF_LEN];
|
||||
const char *val;
|
||||
int ret;
|
||||
|
||||
kprobe_event_cmd_init(&cmd, buf, MAX_BUF_LEN);
|
||||
|
||||
ret = kprobe_event_gen_cmd_start(&cmd, event, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
xbc_node_for_each_array_value(node, "probes", anode, val) {
|
||||
ret = kprobe_event_add_field(&cmd, val);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = kprobe_event_gen_cmd_end(&cmd);
|
||||
if (ret)
|
||||
pr_err("Failed to add probe: %s\n", buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
static inline int __init
|
||||
trace_boot_add_kprobe_event(struct xbc_node *node, const char *event)
|
||||
{
|
||||
pr_err("Kprobe event is not supported.\n");
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_HIST_TRIGGERS
|
||||
static int __init
|
||||
trace_boot_add_synth_event(struct xbc_node *node, const char *event)
|
||||
{
|
||||
struct dynevent_cmd cmd;
|
||||
struct xbc_node *anode;
|
||||
char buf[MAX_BUF_LEN];
|
||||
const char *p;
|
||||
int ret;
|
||||
|
||||
synth_event_cmd_init(&cmd, buf, MAX_BUF_LEN);
|
||||
|
||||
ret = synth_event_gen_cmd_start(&cmd, event, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
xbc_node_for_each_array_value(node, "fields", anode, p) {
|
||||
ret = synth_event_add_field_str(&cmd, p);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = synth_event_gen_cmd_end(&cmd);
|
||||
if (ret < 0)
|
||||
pr_err("Failed to add synthetic event: %s\n", buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
#else
|
||||
static inline int __init
|
||||
trace_boot_add_synth_event(struct xbc_node *node, const char *event)
|
||||
{
|
||||
pr_err("Synthetic event is not supported.\n");
|
||||
return -ENOTSUPP;
|
||||
}
|
||||
#endif
|
||||
|
||||
static void __init
|
||||
trace_boot_init_one_event(struct trace_array *tr, struct xbc_node *gnode,
|
||||
struct xbc_node *enode)
|
||||
{
|
||||
struct trace_event_file *file;
|
||||
struct xbc_node *anode;
|
||||
char buf[MAX_BUF_LEN];
|
||||
const char *p, *group, *event;
|
||||
|
||||
group = xbc_node_get_data(gnode);
|
||||
event = xbc_node_get_data(enode);
|
||||
|
||||
if (!strcmp(group, "kprobes"))
|
||||
if (trace_boot_add_kprobe_event(enode, event) < 0)
|
||||
return;
|
||||
if (!strcmp(group, "synthetic"))
|
||||
if (trace_boot_add_synth_event(enode, event) < 0)
|
||||
return;
|
||||
|
||||
mutex_lock(&event_mutex);
|
||||
file = find_event_file(tr, group, event);
|
||||
if (!file) {
|
||||
pr_err("Failed to find event: %s:%s\n", group, event);
|
||||
goto out;
|
||||
}
|
||||
|
||||
p = xbc_node_find_value(enode, "filter", NULL);
|
||||
if (p && *p != '\0') {
|
||||
if (strlcpy(buf, p, ARRAY_SIZE(buf)) >= ARRAY_SIZE(buf))
|
||||
pr_err("filter string is too long: %s\n", p);
|
||||
else if (apply_event_filter(file, buf) < 0)
|
||||
pr_err("Failed to apply filter: %s\n", buf);
|
||||
}
|
||||
|
||||
xbc_node_for_each_array_value(enode, "actions", anode, p) {
|
||||
if (strlcpy(buf, p, ARRAY_SIZE(buf)) >= ARRAY_SIZE(buf))
|
||||
pr_err("action string is too long: %s\n", p);
|
||||
else if (trigger_process_regex(file, buf) < 0)
|
||||
pr_err("Failed to apply an action: %s\n", buf);
|
||||
}
|
||||
|
||||
if (xbc_node_find_value(enode, "enable", NULL)) {
|
||||
if (trace_event_enable_disable(file, 1, 0) < 0)
|
||||
pr_err("Failed to enable event node: %s:%s\n",
|
||||
group, event);
|
||||
}
|
||||
out:
|
||||
mutex_unlock(&event_mutex);
|
||||
}
|
||||
|
||||
static void __init
|
||||
trace_boot_init_events(struct trace_array *tr, struct xbc_node *node)
|
||||
{
|
||||
struct xbc_node *gnode, *enode;
|
||||
|
||||
node = xbc_node_find_child(node, "event");
|
||||
if (!node)
|
||||
return;
|
||||
/* per-event key starts with "event.GROUP.EVENT" */
|
||||
xbc_node_for_each_child(node, gnode)
|
||||
xbc_node_for_each_child(gnode, enode)
|
||||
trace_boot_init_one_event(tr, gnode, enode);
|
||||
}
|
||||
#else
|
||||
#define trace_boot_enable_events(tr, node) do {} while (0)
|
||||
#define trace_boot_init_events(tr, node) do {} while (0)
|
||||
#endif
|
||||
|
||||
#ifdef CONFIG_DYNAMIC_FTRACE
|
||||
static void __init
|
||||
trace_boot_set_ftrace_filter(struct trace_array *tr, struct xbc_node *node)
|
||||
{
|
||||
struct xbc_node *anode;
|
||||
const char *p;
|
||||
char *q;
|
||||
|
||||
xbc_node_for_each_array_value(node, "ftrace.filters", anode, p) {
|
||||
q = kstrdup(p, GFP_KERNEL);
|
||||
if (!q)
|
||||
return;
|
||||
if (ftrace_set_filter(tr->ops, q, strlen(q), 0) < 0)
|
||||
pr_err("Failed to add %s to ftrace filter\n", p);
|
||||
else
|
||||
ftrace_filter_param = true;
|
||||
kfree(q);
|
||||
}
|
||||
xbc_node_for_each_array_value(node, "ftrace.notraces", anode, p) {
|
||||
q = kstrdup(p, GFP_KERNEL);
|
||||
if (!q)
|
||||
return;
|
||||
if (ftrace_set_notrace(tr->ops, q, strlen(q), 0) < 0)
|
||||
pr_err("Failed to add %s to ftrace filter\n", p);
|
||||
else
|
||||
ftrace_filter_param = true;
|
||||
kfree(q);
|
||||
}
|
||||
}
|
||||
#else
|
||||
#define trace_boot_set_ftrace_filter(tr, node) do {} while (0)
|
||||
#endif
|
||||
|
||||
static void __init
|
||||
trace_boot_enable_tracer(struct trace_array *tr, struct xbc_node *node)
|
||||
{
|
||||
const char *p;
|
||||
|
||||
trace_boot_set_ftrace_filter(tr, node);
|
||||
|
||||
p = xbc_node_find_value(node, "tracer", NULL);
|
||||
if (p && *p != '\0') {
|
||||
if (tracing_set_tracer(tr, p) < 0)
|
||||
pr_err("Failed to set given tracer: %s\n", p);
|
||||
}
|
||||
}
|
||||
|
||||
static void __init
|
||||
trace_boot_init_one_instance(struct trace_array *tr, struct xbc_node *node)
|
||||
{
|
||||
trace_boot_set_instance_options(tr, node);
|
||||
trace_boot_init_events(tr, node);
|
||||
trace_boot_enable_events(tr, node);
|
||||
trace_boot_enable_tracer(tr, node);
|
||||
}
|
||||
|
||||
static void __init
|
||||
trace_boot_init_instances(struct xbc_node *node)
|
||||
{
|
||||
struct xbc_node *inode;
|
||||
struct trace_array *tr;
|
||||
const char *p;
|
||||
|
||||
node = xbc_node_find_child(node, "instance");
|
||||
if (!node)
|
||||
return;
|
||||
|
||||
xbc_node_for_each_child(node, inode) {
|
||||
p = xbc_node_get_data(inode);
|
||||
if (!p || *p == '\0')
|
||||
continue;
|
||||
|
||||
tr = trace_array_get_by_name(p);
|
||||
if (!tr) {
|
||||
pr_err("Failed to get trace instance %s\n", p);
|
||||
continue;
|
||||
}
|
||||
trace_boot_init_one_instance(tr, inode);
|
||||
trace_array_put(tr);
|
||||
}
|
||||
}
|
||||
|
||||
static int __init trace_boot_init(void)
|
||||
{
|
||||
struct xbc_node *trace_node;
|
||||
struct trace_array *tr;
|
||||
|
||||
trace_node = xbc_find_node("ftrace");
|
||||
if (!trace_node)
|
||||
return 0;
|
||||
|
||||
tr = top_trace_array();
|
||||
if (!tr)
|
||||
return 0;
|
||||
|
||||
/* Global trace array is also one instance */
|
||||
trace_boot_init_one_instance(tr, trace_node);
|
||||
trace_boot_init_instances(trace_node);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
fs_initcall(trace_boot_init);
|
|
@ -32,10 +32,10 @@ probe_likely_condition(struct ftrace_likely_data *f, int val, int expect)
|
|||
{
|
||||
struct trace_event_call *call = &event_branch;
|
||||
struct trace_array *tr = branch_tracer;
|
||||
struct trace_buffer *buffer;
|
||||
struct trace_array_cpu *data;
|
||||
struct ring_buffer_event *event;
|
||||
struct trace_branch *entry;
|
||||
struct ring_buffer *buffer;
|
||||
unsigned long flags;
|
||||
int pc;
|
||||
const char *p;
|
||||
|
@ -55,12 +55,12 @@ probe_likely_condition(struct ftrace_likely_data *f, int val, int expect)
|
|||
|
||||
raw_local_irq_save(flags);
|
||||
current->trace_recursion |= TRACE_BRANCH_BIT;
|
||||
data = this_cpu_ptr(tr->trace_buffer.data);
|
||||
data = this_cpu_ptr(tr->array_buffer.data);
|
||||
if (atomic_read(&data->disabled))
|
||||
goto out;
|
||||
|
||||
pc = preempt_count();
|
||||
buffer = tr->trace_buffer.buffer;
|
||||
buffer = tr->array_buffer.buffer;
|
||||
event = trace_buffer_lock_reserve(buffer, TRACE_BRANCH,
|
||||
sizeof(*entry), flags, pc);
|
||||
if (!event)
|
||||
|
|
|
@ -223,3 +223,215 @@ static __init int init_dynamic_event(void)
|
|||
return 0;
|
||||
}
|
||||
fs_initcall(init_dynamic_event);
|
||||
|
||||
/**
|
||||
* dynevent_arg_add - Add an arg to a dynevent_cmd
|
||||
* @cmd: A pointer to the dynevent_cmd struct representing the new event cmd
|
||||
* @arg: The argument to append to the current cmd
|
||||
* @check_arg: An (optional) pointer to a function checking arg sanity
|
||||
*
|
||||
* Append an argument to a dynevent_cmd. The argument string will be
|
||||
* appended to the current cmd string, followed by a separator, if
|
||||
* applicable. Before the argument is added, the @check_arg function,
|
||||
* if present, will be used to check the sanity of the current arg
|
||||
* string.
|
||||
*
|
||||
* The cmd string and separator should be set using the
|
||||
* dynevent_arg_init() before any arguments are added using this
|
||||
* function.
|
||||
*
|
||||
* Return: 0 if successful, error otherwise.
|
||||
*/
|
||||
int dynevent_arg_add(struct dynevent_cmd *cmd,
|
||||
struct dynevent_arg *arg,
|
||||
dynevent_check_arg_fn_t check_arg)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (check_arg) {
|
||||
ret = check_arg(arg);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = seq_buf_printf(&cmd->seq, " %s%c", arg->str, arg->separator);
|
||||
if (ret) {
|
||||
pr_err("String is too long: %s%c\n", arg->str, arg->separator);
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* dynevent_arg_pair_add - Add an arg pair to a dynevent_cmd
|
||||
* @cmd: A pointer to the dynevent_cmd struct representing the new event cmd
|
||||
* @arg_pair: The argument pair to append to the current cmd
|
||||
* @check_arg: An (optional) pointer to a function checking arg sanity
|
||||
*
|
||||
* Append an argument pair to a dynevent_cmd. An argument pair
|
||||
* consists of a left-hand-side argument and a right-hand-side
|
||||
* argument separated by an operator, which can be whitespace, all
|
||||
* followed by a separator, if applicable. This can be used to add
|
||||
* arguments of the form 'type variable_name;' or 'x+y'.
|
||||
*
|
||||
* The lhs argument string will be appended to the current cmd string,
|
||||
* followed by an operator, if applicable, followd by the rhs string,
|
||||
* followed finally by a separator, if applicable. Before the
|
||||
* argument is added, the @check_arg function, if present, will be
|
||||
* used to check the sanity of the current arg strings.
|
||||
*
|
||||
* The cmd strings, operator, and separator should be set using the
|
||||
* dynevent_arg_pair_init() before any arguments are added using this
|
||||
* function.
|
||||
*
|
||||
* Return: 0 if successful, error otherwise.
|
||||
*/
|
||||
int dynevent_arg_pair_add(struct dynevent_cmd *cmd,
|
||||
struct dynevent_arg_pair *arg_pair,
|
||||
dynevent_check_arg_fn_t check_arg)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
if (check_arg) {
|
||||
ret = check_arg(arg_pair);
|
||||
if (ret)
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = seq_buf_printf(&cmd->seq, " %s%c%s%c", arg_pair->lhs,
|
||||
arg_pair->operator, arg_pair->rhs,
|
||||
arg_pair->separator);
|
||||
if (ret) {
|
||||
pr_err("field string is too long: %s%c%s%c\n", arg_pair->lhs,
|
||||
arg_pair->operator, arg_pair->rhs,
|
||||
arg_pair->separator);
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* dynevent_str_add - Add a string to a dynevent_cmd
|
||||
* @cmd: A pointer to the dynevent_cmd struct representing the new event cmd
|
||||
* @str: The string to append to the current cmd
|
||||
*
|
||||
* Append a string to a dynevent_cmd. The string will be appended to
|
||||
* the current cmd string as-is, with nothing prepended or appended.
|
||||
*
|
||||
* Return: 0 if successful, error otherwise.
|
||||
*/
|
||||
int dynevent_str_add(struct dynevent_cmd *cmd, const char *str)
|
||||
{
|
||||
int ret = 0;
|
||||
|
||||
ret = seq_buf_puts(&cmd->seq, str);
|
||||
if (ret) {
|
||||
pr_err("String is too long: %s\n", str);
|
||||
return -E2BIG;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* dynevent_cmd_init - Initialize a dynevent_cmd object
|
||||
* @cmd: A pointer to the dynevent_cmd struct representing the cmd
|
||||
* @buf: A pointer to the buffer to generate the command into
|
||||
* @maxlen: The length of the buffer the command will be generated into
|
||||
* @type: The type of the cmd, checked against further operations
|
||||
* @run_command: The type-specific function that will actually run the command
|
||||
*
|
||||
* Initialize a dynevent_cmd. A dynevent_cmd is used to build up and
|
||||
* run dynamic event creation commands, such as commands for creating
|
||||
* synthetic and kprobe events. Before calling any of the functions
|
||||
* used to build the command, a dynevent_cmd object should be
|
||||
* instantiated and initialized using this function.
|
||||
*
|
||||
* The initialization sets things up by saving a pointer to the
|
||||
* user-supplied buffer and its length via the @buf and @maxlen
|
||||
* params, and by saving the cmd-specific @type and @run_command
|
||||
* params which are used to check subsequent dynevent_cmd operations
|
||||
* and actually run the command when complete.
|
||||
*/
|
||||
void dynevent_cmd_init(struct dynevent_cmd *cmd, char *buf, int maxlen,
|
||||
enum dynevent_type type,
|
||||
dynevent_create_fn_t run_command)
|
||||
{
|
||||
memset(cmd, '\0', sizeof(*cmd));
|
||||
|
||||
seq_buf_init(&cmd->seq, buf, maxlen);
|
||||
cmd->type = type;
|
||||
cmd->run_command = run_command;
|
||||
}
|
||||
|
||||
/**
|
||||
* dynevent_arg_init - Initialize a dynevent_arg object
|
||||
* @arg: A pointer to the dynevent_arg struct representing the arg
|
||||
* @separator: An (optional) separator, appended after adding the arg
|
||||
*
|
||||
* Initialize a dynevent_arg object. A dynevent_arg represents an
|
||||
* object used to append single arguments to the current command
|
||||
* string. After the arg string is successfully appended to the
|
||||
* command string, the optional @separator is appended. If no
|
||||
* separator was specified when initializing the arg, a space will be
|
||||
* appended.
|
||||
*/
|
||||
void dynevent_arg_init(struct dynevent_arg *arg,
|
||||
char separator)
|
||||
{
|
||||
memset(arg, '\0', sizeof(*arg));
|
||||
|
||||
if (!separator)
|
||||
separator = ' ';
|
||||
arg->separator = separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* dynevent_arg_pair_init - Initialize a dynevent_arg_pair object
|
||||
* @arg_pair: A pointer to the dynevent_arg_pair struct representing the arg
|
||||
* @operator: An (optional) operator, appended after adding the first arg
|
||||
* @separator: An (optional) separator, appended after adding the second arg
|
||||
*
|
||||
* Initialize a dynevent_arg_pair object. A dynevent_arg_pair
|
||||
* represents an object used to append argument pairs such as 'type
|
||||
* variable_name;' or 'x+y' to the current command string. An
|
||||
* argument pair consists of a left-hand-side argument and a
|
||||
* right-hand-side argument separated by an operator, which can be
|
||||
* whitespace, all followed by a separator, if applicable. After the
|
||||
* first arg string is successfully appended to the command string,
|
||||
* the optional @operator is appended, followed by the second arg and
|
||||
* and optional @separator. If no separator was specified when
|
||||
* initializing the arg, a space will be appended.
|
||||
*/
|
||||
void dynevent_arg_pair_init(struct dynevent_arg_pair *arg_pair,
|
||||
char operator, char separator)
|
||||
{
|
||||
memset(arg_pair, '\0', sizeof(*arg_pair));
|
||||
|
||||
if (!operator)
|
||||
operator = ' ';
|
||||
arg_pair->operator = operator;
|
||||
|
||||
if (!separator)
|
||||
separator = ' ';
|
||||
arg_pair->separator = separator;
|
||||
}
|
||||
|
||||
/**
|
||||
* dynevent_create - Create the dynamic event contained in dynevent_cmd
|
||||
* @cmd: The dynevent_cmd object containing the dynamic event creation command
|
||||
*
|
||||
* Once a dynevent_cmd object has been successfully built up via the
|
||||
* dynevent_cmd_init(), dynevent_arg_add() and dynevent_arg_pair_add()
|
||||
* functions, this function runs the final command to actually create
|
||||
* the event.
|
||||
*
|
||||
* Return: 0 if the event was successfully created, error otherwise.
|
||||
*/
|
||||
int dynevent_create(struct dynevent_cmd *cmd)
|
||||
{
|
||||
return cmd->run_command(cmd);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(dynevent_create);
|
||||
|
|
|
@ -117,4 +117,36 @@ int dyn_event_release(int argc, char **argv, struct dyn_event_operations *type);
|
|||
#define for_each_dyn_event_safe(pos, n) \
|
||||
list_for_each_entry_safe(pos, n, &dyn_event_list, list)
|
||||
|
||||
extern void dynevent_cmd_init(struct dynevent_cmd *cmd, char *buf, int maxlen,
|
||||
enum dynevent_type type,
|
||||
dynevent_create_fn_t run_command);
|
||||
|
||||
typedef int (*dynevent_check_arg_fn_t)(void *data);
|
||||
|
||||
struct dynevent_arg {
|
||||
const char *str;
|
||||
char separator; /* e.g. ';', ',', or nothing */
|
||||
};
|
||||
|
||||
extern void dynevent_arg_init(struct dynevent_arg *arg,
|
||||
char separator);
|
||||
extern int dynevent_arg_add(struct dynevent_cmd *cmd,
|
||||
struct dynevent_arg *arg,
|
||||
dynevent_check_arg_fn_t check_arg);
|
||||
|
||||
struct dynevent_arg_pair {
|
||||
const char *lhs;
|
||||
const char *rhs;
|
||||
char operator; /* e.g. '=' or nothing */
|
||||
char separator; /* e.g. ';', ',', or nothing */
|
||||
};
|
||||
|
||||
extern void dynevent_arg_pair_init(struct dynevent_arg_pair *arg_pair,
|
||||
char operator, char separator);
|
||||
|
||||
extern int dynevent_arg_pair_add(struct dynevent_cmd *cmd,
|
||||
struct dynevent_arg_pair *arg_pair,
|
||||
dynevent_check_arg_fn_t check_arg);
|
||||
extern int dynevent_str_add(struct dynevent_cmd *cmd, const char *str);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -164,7 +164,7 @@ FTRACE_ENTRY(kernel_stack, stack_entry,
|
|||
|
||||
F_STRUCT(
|
||||
__field( int, size )
|
||||
__dynamic_array(unsigned long, caller )
|
||||
__array( unsigned long, caller, FTRACE_STACK_ENTRIES )
|
||||
),
|
||||
|
||||
F_printk("\t=> %ps\n\t=> %ps\n\t=> %ps\n"
|
||||
|
|
|
@ -238,7 +238,7 @@ bool trace_event_ignore_this_pid(struct trace_event_file *trace_file)
|
|||
if (!pid_list)
|
||||
return false;
|
||||
|
||||
data = this_cpu_ptr(tr->trace_buffer.data);
|
||||
data = this_cpu_ptr(tr->array_buffer.data);
|
||||
|
||||
return data->ignore_pid;
|
||||
}
|
||||
|
@ -273,6 +273,7 @@ void *trace_event_buffer_reserve(struct trace_event_buffer *fbuffer,
|
|||
if (!fbuffer->event)
|
||||
return NULL;
|
||||
|
||||
fbuffer->regs = NULL;
|
||||
fbuffer->entry = ring_buffer_event_data(fbuffer->event);
|
||||
return fbuffer->entry;
|
||||
}
|
||||
|
@ -547,7 +548,7 @@ event_filter_pid_sched_switch_probe_pre(void *data, bool preempt,
|
|||
|
||||
pid_list = rcu_dereference_sched(tr->filtered_pids);
|
||||
|
||||
this_cpu_write(tr->trace_buffer.data->ignore_pid,
|
||||
this_cpu_write(tr->array_buffer.data->ignore_pid,
|
||||
trace_ignore_this_task(pid_list, prev) &&
|
||||
trace_ignore_this_task(pid_list, next));
|
||||
}
|
||||
|
@ -561,7 +562,7 @@ event_filter_pid_sched_switch_probe_post(void *data, bool preempt,
|
|||
|
||||
pid_list = rcu_dereference_sched(tr->filtered_pids);
|
||||
|
||||
this_cpu_write(tr->trace_buffer.data->ignore_pid,
|
||||
this_cpu_write(tr->array_buffer.data->ignore_pid,
|
||||
trace_ignore_this_task(pid_list, next));
|
||||
}
|
||||
|
||||
|
@ -572,12 +573,12 @@ event_filter_pid_sched_wakeup_probe_pre(void *data, struct task_struct *task)
|
|||
struct trace_pid_list *pid_list;
|
||||
|
||||
/* Nothing to do if we are already tracing */
|
||||
if (!this_cpu_read(tr->trace_buffer.data->ignore_pid))
|
||||
if (!this_cpu_read(tr->array_buffer.data->ignore_pid))
|
||||
return;
|
||||
|
||||
pid_list = rcu_dereference_sched(tr->filtered_pids);
|
||||
|
||||
this_cpu_write(tr->trace_buffer.data->ignore_pid,
|
||||
this_cpu_write(tr->array_buffer.data->ignore_pid,
|
||||
trace_ignore_this_task(pid_list, task));
|
||||
}
|
||||
|
||||
|
@ -588,13 +589,13 @@ event_filter_pid_sched_wakeup_probe_post(void *data, struct task_struct *task)
|
|||
struct trace_pid_list *pid_list;
|
||||
|
||||
/* Nothing to do if we are not tracing */
|
||||
if (this_cpu_read(tr->trace_buffer.data->ignore_pid))
|
||||
if (this_cpu_read(tr->array_buffer.data->ignore_pid))
|
||||
return;
|
||||
|
||||
pid_list = rcu_dereference_sched(tr->filtered_pids);
|
||||
|
||||
/* Set tracing if current is enabled */
|
||||
this_cpu_write(tr->trace_buffer.data->ignore_pid,
|
||||
this_cpu_write(tr->array_buffer.data->ignore_pid,
|
||||
trace_ignore_this_task(pid_list, current));
|
||||
}
|
||||
|
||||
|
@ -626,7 +627,7 @@ static void __ftrace_clear_event_pids(struct trace_array *tr)
|
|||
}
|
||||
|
||||
for_each_possible_cpu(cpu)
|
||||
per_cpu_ptr(tr->trace_buffer.data, cpu)->ignore_pid = false;
|
||||
per_cpu_ptr(tr->array_buffer.data, cpu)->ignore_pid = false;
|
||||
|
||||
rcu_assign_pointer(tr->filtered_pids, NULL);
|
||||
|
||||
|
@ -1595,7 +1596,7 @@ static void ignore_task_cpu(void *data)
|
|||
pid_list = rcu_dereference_protected(tr->filtered_pids,
|
||||
mutex_is_locked(&event_mutex));
|
||||
|
||||
this_cpu_write(tr->trace_buffer.data->ignore_pid,
|
||||
this_cpu_write(tr->array_buffer.data->ignore_pid,
|
||||
trace_ignore_this_task(pid_list, current));
|
||||
}
|
||||
|
||||
|
@ -2553,6 +2554,91 @@ find_event_file(struct trace_array *tr, const char *system, const char *event)
|
|||
return file;
|
||||
}
|
||||
|
||||
/**
|
||||
* trace_get_event_file - Find and return a trace event file
|
||||
* @instance: The name of the trace instance containing the event
|
||||
* @system: The name of the system containing the event
|
||||
* @event: The name of the event
|
||||
*
|
||||
* Return a trace event file given the trace instance name, trace
|
||||
* system, and trace event name. If the instance name is NULL, it
|
||||
* refers to the top-level trace array.
|
||||
*
|
||||
* This function will look it up and return it if found, after calling
|
||||
* trace_array_get() to prevent the instance from going away, and
|
||||
* increment the event's module refcount to prevent it from being
|
||||
* removed.
|
||||
*
|
||||
* To release the file, call trace_put_event_file(), which will call
|
||||
* trace_array_put() and decrement the event's module refcount.
|
||||
*
|
||||
* Return: The trace event on success, ERR_PTR otherwise.
|
||||
*/
|
||||
struct trace_event_file *trace_get_event_file(const char *instance,
|
||||
const char *system,
|
||||
const char *event)
|
||||
{
|
||||
struct trace_array *tr = top_trace_array();
|
||||
struct trace_event_file *file = NULL;
|
||||
int ret = -EINVAL;
|
||||
|
||||
if (instance) {
|
||||
tr = trace_array_find_get(instance);
|
||||
if (!tr)
|
||||
return ERR_PTR(-ENOENT);
|
||||
} else {
|
||||
ret = trace_array_get(tr);
|
||||
if (ret)
|
||||
return ERR_PTR(ret);
|
||||
}
|
||||
|
||||
mutex_lock(&event_mutex);
|
||||
|
||||
file = find_event_file(tr, system, event);
|
||||
if (!file) {
|
||||
trace_array_put(tr);
|
||||
ret = -EINVAL;
|
||||
goto out;
|
||||
}
|
||||
|
||||
/* Don't let event modules unload while in use */
|
||||
ret = try_module_get(file->event_call->mod);
|
||||
if (!ret) {
|
||||
trace_array_put(tr);
|
||||
ret = -EBUSY;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
out:
|
||||
mutex_unlock(&event_mutex);
|
||||
|
||||
if (ret)
|
||||
file = ERR_PTR(ret);
|
||||
|
||||
return file;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(trace_get_event_file);
|
||||
|
||||
/**
|
||||
* trace_put_event_file - Release a file from trace_get_event_file()
|
||||
* @file: The trace event file
|
||||
*
|
||||
* If a file was retrieved using trace_get_event_file(), this should
|
||||
* be called when it's no longer needed. It will cancel the previous
|
||||
* trace_array_get() called by that function, and decrement the
|
||||
* event's module refcount.
|
||||
*/
|
||||
void trace_put_event_file(struct trace_event_file *file)
|
||||
{
|
||||
mutex_lock(&event_mutex);
|
||||
module_put(file->event_call->mod);
|
||||
mutex_unlock(&event_mutex);
|
||||
|
||||
trace_array_put(file->tr);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(trace_put_event_file);
|
||||
|
||||
#ifdef CONFIG_DYNAMIC_FTRACE
|
||||
|
||||
/* Avoid typos */
|
||||
|
@ -3409,8 +3495,8 @@ static void __init
|
|||
function_test_events_call(unsigned long ip, unsigned long parent_ip,
|
||||
struct ftrace_ops *op, struct pt_regs *pt_regs)
|
||||
{
|
||||
struct trace_buffer *buffer;
|
||||
struct ring_buffer_event *event;
|
||||
struct ring_buffer *buffer;
|
||||
struct ftrace_entry *entry;
|
||||
unsigned long flags;
|
||||
long disabled;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -116,9 +116,10 @@ static void *trigger_next(struct seq_file *m, void *t, loff_t *pos)
|
|||
{
|
||||
struct trace_event_file *event_file = event_file_data(m->private);
|
||||
|
||||
if (t == SHOW_AVAILABLE_TRIGGERS)
|
||||
if (t == SHOW_AVAILABLE_TRIGGERS) {
|
||||
(*pos)++;
|
||||
return NULL;
|
||||
|
||||
}
|
||||
return seq_list_next(t, &event_file->triggers, pos);
|
||||
}
|
||||
|
||||
|
@ -213,7 +214,7 @@ static int event_trigger_regex_open(struct inode *inode, struct file *file)
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int trigger_process_regex(struct trace_event_file *file, char *buff)
|
||||
int trigger_process_regex(struct trace_event_file *file, char *buff)
|
||||
{
|
||||
char *command, *next = buff;
|
||||
struct event_command *p;
|
||||
|
|
|
@ -101,7 +101,7 @@ static int function_trace_init(struct trace_array *tr)
|
|||
|
||||
ftrace_init_array_ops(tr, func);
|
||||
|
||||
tr->trace_buffer.cpu = get_cpu();
|
||||
tr->array_buffer.cpu = get_cpu();
|
||||
put_cpu();
|
||||
|
||||
tracing_start_cmdline_record();
|
||||
|
@ -118,7 +118,7 @@ static void function_trace_reset(struct trace_array *tr)
|
|||
|
||||
static void function_trace_start(struct trace_array *tr)
|
||||
{
|
||||
tracing_reset_online_cpus(&tr->trace_buffer);
|
||||
tracing_reset_online_cpus(&tr->array_buffer);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -143,7 +143,7 @@ function_trace_call(unsigned long ip, unsigned long parent_ip,
|
|||
goto out;
|
||||
|
||||
cpu = smp_processor_id();
|
||||
data = per_cpu_ptr(tr->trace_buffer.data, cpu);
|
||||
data = per_cpu_ptr(tr->array_buffer.data, cpu);
|
||||
if (!atomic_read(&data->disabled)) {
|
||||
local_save_flags(flags);
|
||||
trace_function(tr, ip, parent_ip, flags, pc);
|
||||
|
@ -192,7 +192,7 @@ function_stack_trace_call(unsigned long ip, unsigned long parent_ip,
|
|||
*/
|
||||
local_irq_save(flags);
|
||||
cpu = raw_smp_processor_id();
|
||||
data = per_cpu_ptr(tr->trace_buffer.data, cpu);
|
||||
data = per_cpu_ptr(tr->array_buffer.data, cpu);
|
||||
disabled = atomic_inc_return(&data->disabled);
|
||||
|
||||
if (likely(disabled == 1)) {
|
||||
|
|
|
@ -101,7 +101,7 @@ int __trace_graph_entry(struct trace_array *tr,
|
|||
{
|
||||
struct trace_event_call *call = &event_funcgraph_entry;
|
||||
struct ring_buffer_event *event;
|
||||
struct ring_buffer *buffer = tr->trace_buffer.buffer;
|
||||
struct trace_buffer *buffer = tr->array_buffer.buffer;
|
||||
struct ftrace_graph_ent_entry *entry;
|
||||
|
||||
event = trace_buffer_lock_reserve(buffer, TRACE_GRAPH_ENT,
|
||||
|
@ -171,7 +171,7 @@ int trace_graph_entry(struct ftrace_graph_ent *trace)
|
|||
|
||||
local_irq_save(flags);
|
||||
cpu = raw_smp_processor_id();
|
||||
data = per_cpu_ptr(tr->trace_buffer.data, cpu);
|
||||
data = per_cpu_ptr(tr->array_buffer.data, cpu);
|
||||
disabled = atomic_inc_return(&data->disabled);
|
||||
if (likely(disabled == 1)) {
|
||||
pc = preempt_count();
|
||||
|
@ -221,7 +221,7 @@ void __trace_graph_return(struct trace_array *tr,
|
|||
{
|
||||
struct trace_event_call *call = &event_funcgraph_exit;
|
||||
struct ring_buffer_event *event;
|
||||
struct ring_buffer *buffer = tr->trace_buffer.buffer;
|
||||
struct trace_buffer *buffer = tr->array_buffer.buffer;
|
||||
struct ftrace_graph_ret_entry *entry;
|
||||
|
||||
event = trace_buffer_lock_reserve(buffer, TRACE_GRAPH_RET,
|
||||
|
@ -252,7 +252,7 @@ void trace_graph_return(struct ftrace_graph_ret *trace)
|
|||
|
||||
local_irq_save(flags);
|
||||
cpu = raw_smp_processor_id();
|
||||
data = per_cpu_ptr(tr->trace_buffer.data, cpu);
|
||||
data = per_cpu_ptr(tr->array_buffer.data, cpu);
|
||||
disabled = atomic_inc_return(&data->disabled);
|
||||
if (likely(disabled == 1)) {
|
||||
pc = preempt_count();
|
||||
|
@ -444,9 +444,9 @@ get_return_for_leaf(struct trace_iterator *iter,
|
|||
* We need to consume the current entry to see
|
||||
* the next one.
|
||||
*/
|
||||
ring_buffer_consume(iter->trace_buffer->buffer, iter->cpu,
|
||||
ring_buffer_consume(iter->array_buffer->buffer, iter->cpu,
|
||||
NULL, NULL);
|
||||
event = ring_buffer_peek(iter->trace_buffer->buffer, iter->cpu,
|
||||
event = ring_buffer_peek(iter->array_buffer->buffer, iter->cpu,
|
||||
NULL, NULL);
|
||||
}
|
||||
|
||||
|
@ -503,7 +503,7 @@ print_graph_rel_time(struct trace_iterator *iter, struct trace_seq *s)
|
|||
{
|
||||
unsigned long long usecs;
|
||||
|
||||
usecs = iter->ts - iter->trace_buffer->time_start;
|
||||
usecs = iter->ts - iter->array_buffer->time_start;
|
||||
do_div(usecs, NSEC_PER_USEC);
|
||||
|
||||
trace_seq_printf(s, "%9llu us | ", usecs);
|
||||
|
|
|
@ -104,7 +104,7 @@ static void trace_hwlat_sample(struct hwlat_sample *sample)
|
|||
{
|
||||
struct trace_array *tr = hwlat_trace;
|
||||
struct trace_event_call *call = &event_hwlat;
|
||||
struct ring_buffer *buffer = tr->trace_buffer.buffer;
|
||||
struct trace_buffer *buffer = tr->array_buffer.buffer;
|
||||
struct ring_buffer_event *event;
|
||||
struct hwlat_entry *entry;
|
||||
unsigned long flags;
|
||||
|
|
|
@ -122,7 +122,7 @@ static int func_prolog_dec(struct trace_array *tr,
|
|||
if (!irqs_disabled_flags(*flags) && !preempt_count())
|
||||
return 0;
|
||||
|
||||
*data = per_cpu_ptr(tr->trace_buffer.data, cpu);
|
||||
*data = per_cpu_ptr(tr->array_buffer.data, cpu);
|
||||
disabled = atomic_inc_return(&(*data)->disabled);
|
||||
|
||||
if (likely(disabled == 1))
|
||||
|
@ -167,7 +167,7 @@ static int irqsoff_display_graph(struct trace_array *tr, int set)
|
|||
per_cpu(tracing_cpu, cpu) = 0;
|
||||
|
||||
tr->max_latency = 0;
|
||||
tracing_reset_online_cpus(&irqsoff_trace->trace_buffer);
|
||||
tracing_reset_online_cpus(&irqsoff_trace->array_buffer);
|
||||
|
||||
return start_irqsoff_tracer(irqsoff_trace, set);
|
||||
}
|
||||
|
@ -382,7 +382,7 @@ start_critical_timing(unsigned long ip, unsigned long parent_ip, int pc)
|
|||
if (per_cpu(tracing_cpu, cpu))
|
||||
return;
|
||||
|
||||
data = per_cpu_ptr(tr->trace_buffer.data, cpu);
|
||||
data = per_cpu_ptr(tr->array_buffer.data, cpu);
|
||||
|
||||
if (unlikely(!data) || atomic_read(&data->disabled))
|
||||
return;
|
||||
|
@ -420,7 +420,7 @@ stop_critical_timing(unsigned long ip, unsigned long parent_ip, int pc)
|
|||
if (!tracer_enabled || !tracing_is_enabled())
|
||||
return;
|
||||
|
||||
data = per_cpu_ptr(tr->trace_buffer.data, cpu);
|
||||
data = per_cpu_ptr(tr->array_buffer.data, cpu);
|
||||
|
||||
if (unlikely(!data) ||
|
||||
!data->critical_start || atomic_read(&data->disabled))
|
||||
|
|
|
@ -43,7 +43,7 @@ static void ftrace_dump_buf(int skip_entries, long cpu_file)
|
|||
if (cpu_file == RING_BUFFER_ALL_CPUS) {
|
||||
for_each_tracing_cpu(cpu) {
|
||||
iter.buffer_iter[cpu] =
|
||||
ring_buffer_read_prepare(iter.trace_buffer->buffer,
|
||||
ring_buffer_read_prepare(iter.array_buffer->buffer,
|
||||
cpu, GFP_ATOMIC);
|
||||
ring_buffer_read_start(iter.buffer_iter[cpu]);
|
||||
tracing_iter_reset(&iter, cpu);
|
||||
|
@ -51,7 +51,7 @@ static void ftrace_dump_buf(int skip_entries, long cpu_file)
|
|||
} else {
|
||||
iter.cpu_file = cpu_file;
|
||||
iter.buffer_iter[cpu_file] =
|
||||
ring_buffer_read_prepare(iter.trace_buffer->buffer,
|
||||
ring_buffer_read_prepare(iter.array_buffer->buffer,
|
||||
cpu_file, GFP_ATOMIC);
|
||||
ring_buffer_read_start(iter.buffer_iter[cpu_file]);
|
||||
tracing_iter_reset(&iter, cpu_file);
|
||||
|
@ -124,7 +124,7 @@ static int kdb_ftdump(int argc, const char **argv)
|
|||
iter.buffer_iter = buffer_iter;
|
||||
|
||||
for_each_tracing_cpu(cpu) {
|
||||
atomic_inc(&per_cpu_ptr(iter.trace_buffer->data, cpu)->disabled);
|
||||
atomic_inc(&per_cpu_ptr(iter.array_buffer->data, cpu)->disabled);
|
||||
}
|
||||
|
||||
/* A negative skip_entries means skip all but the last entries */
|
||||
|
@ -139,7 +139,7 @@ static int kdb_ftdump(int argc, const char **argv)
|
|||
ftrace_dump_buf(skip_entries, cpu_file);
|
||||
|
||||
for_each_tracing_cpu(cpu) {
|
||||
atomic_dec(&per_cpu_ptr(iter.trace_buffer->data, cpu)->disabled);
|
||||
atomic_dec(&per_cpu_ptr(iter.array_buffer->data, cpu)->disabled);
|
||||
}
|
||||
|
||||
kdb_trap_printk--;
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
|
||||
#define KPROBE_EVENT_SYSTEM "kprobes"
|
||||
#define KRETPROBE_MAXACTIVE_MAX 4096
|
||||
#define MAX_KPROBE_CMDLINE_SIZE 1024
|
||||
|
||||
/* Kprobe early definition from command line */
|
||||
static char kprobe_boot_events_buf[COMMAND_LINE_SIZE] __initdata;
|
||||
|
@ -902,6 +901,167 @@ static int create_or_delete_trace_kprobe(int argc, char **argv)
|
|||
return ret == -ECANCELED ? -EINVAL : ret;
|
||||
}
|
||||
|
||||
static int trace_kprobe_run_command(struct dynevent_cmd *cmd)
|
||||
{
|
||||
return trace_run_command(cmd->seq.buffer, create_or_delete_trace_kprobe);
|
||||
}
|
||||
|
||||
/**
|
||||
* kprobe_event_cmd_init - Initialize a kprobe event command object
|
||||
* @cmd: A pointer to the dynevent_cmd struct representing the new event
|
||||
* @buf: A pointer to the buffer used to build the command
|
||||
* @maxlen: The length of the buffer passed in @buf
|
||||
*
|
||||
* Initialize a synthetic event command object. Use this before
|
||||
* calling any of the other kprobe_event functions.
|
||||
*/
|
||||
void kprobe_event_cmd_init(struct dynevent_cmd *cmd, char *buf, int maxlen)
|
||||
{
|
||||
dynevent_cmd_init(cmd, buf, maxlen, DYNEVENT_TYPE_KPROBE,
|
||||
trace_kprobe_run_command);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(kprobe_event_cmd_init);
|
||||
|
||||
/**
|
||||
* __kprobe_event_gen_cmd_start - Generate a kprobe event command from arg list
|
||||
* @cmd: A pointer to the dynevent_cmd struct representing the new event
|
||||
* @name: The name of the kprobe event
|
||||
* @loc: The location of the kprobe event
|
||||
* @kretprobe: Is this a return probe?
|
||||
* @args: Variable number of arg (pairs), one pair for each field
|
||||
*
|
||||
* NOTE: Users normally won't want to call this function directly, but
|
||||
* rather use the kprobe_event_gen_cmd_start() wrapper, which automatically
|
||||
* adds a NULL to the end of the arg list. If this function is used
|
||||
* directly, make sure the last arg in the variable arg list is NULL.
|
||||
*
|
||||
* Generate a kprobe event command to be executed by
|
||||
* kprobe_event_gen_cmd_end(). This function can be used to generate the
|
||||
* complete command or only the first part of it; in the latter case,
|
||||
* kprobe_event_add_fields() can be used to add more fields following this.
|
||||
*
|
||||
* Return: 0 if successful, error otherwise.
|
||||
*/
|
||||
int __kprobe_event_gen_cmd_start(struct dynevent_cmd *cmd, bool kretprobe,
|
||||
const char *name, const char *loc, ...)
|
||||
{
|
||||
char buf[MAX_EVENT_NAME_LEN];
|
||||
struct dynevent_arg arg;
|
||||
va_list args;
|
||||
int ret;
|
||||
|
||||
if (cmd->type != DYNEVENT_TYPE_KPROBE)
|
||||
return -EINVAL;
|
||||
|
||||
if (kretprobe)
|
||||
snprintf(buf, MAX_EVENT_NAME_LEN, "r:kprobes/%s", name);
|
||||
else
|
||||
snprintf(buf, MAX_EVENT_NAME_LEN, "p:kprobes/%s", name);
|
||||
|
||||
ret = dynevent_str_add(cmd, buf);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
dynevent_arg_init(&arg, 0);
|
||||
arg.str = loc;
|
||||
ret = dynevent_arg_add(cmd, &arg, NULL);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
va_start(args, loc);
|
||||
for (;;) {
|
||||
const char *field;
|
||||
|
||||
field = va_arg(args, const char *);
|
||||
if (!field)
|
||||
break;
|
||||
|
||||
if (++cmd->n_fields > MAX_TRACE_ARGS) {
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
arg.str = field;
|
||||
ret = dynevent_arg_add(cmd, &arg, NULL);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__kprobe_event_gen_cmd_start);
|
||||
|
||||
/**
|
||||
* __kprobe_event_add_fields - Add probe fields to a kprobe command from arg list
|
||||
* @cmd: A pointer to the dynevent_cmd struct representing the new event
|
||||
* @args: Variable number of arg (pairs), one pair for each field
|
||||
*
|
||||
* NOTE: Users normally won't want to call this function directly, but
|
||||
* rather use the kprobe_event_add_fields() wrapper, which
|
||||
* automatically adds a NULL to the end of the arg list. If this
|
||||
* function is used directly, make sure the last arg in the variable
|
||||
* arg list is NULL.
|
||||
*
|
||||
* Add probe fields to an existing kprobe command using a variable
|
||||
* list of args. Fields are added in the same order they're listed.
|
||||
*
|
||||
* Return: 0 if successful, error otherwise.
|
||||
*/
|
||||
int __kprobe_event_add_fields(struct dynevent_cmd *cmd, ...)
|
||||
{
|
||||
struct dynevent_arg arg;
|
||||
va_list args;
|
||||
int ret;
|
||||
|
||||
if (cmd->type != DYNEVENT_TYPE_KPROBE)
|
||||
return -EINVAL;
|
||||
|
||||
dynevent_arg_init(&arg, 0);
|
||||
|
||||
va_start(args, cmd);
|
||||
for (;;) {
|
||||
const char *field;
|
||||
|
||||
field = va_arg(args, const char *);
|
||||
if (!field)
|
||||
break;
|
||||
|
||||
if (++cmd->n_fields > MAX_TRACE_ARGS) {
|
||||
ret = -EINVAL;
|
||||
break;
|
||||
}
|
||||
|
||||
arg.str = field;
|
||||
ret = dynevent_arg_add(cmd, &arg, NULL);
|
||||
if (ret)
|
||||
break;
|
||||
}
|
||||
va_end(args);
|
||||
|
||||
return ret;
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(__kprobe_event_add_fields);
|
||||
|
||||
/**
|
||||
* kprobe_event_delete - Delete a kprobe event
|
||||
* @name: The name of the kprobe event to delete
|
||||
*
|
||||
* Delete a kprobe event with the give @name from kernel code rather
|
||||
* than directly from the command line.
|
||||
*
|
||||
* Return: 0 if successful, error otherwise.
|
||||
*/
|
||||
int kprobe_event_delete(const char *name)
|
||||
{
|
||||
char buf[MAX_EVENT_NAME_LEN];
|
||||
|
||||
snprintf(buf, MAX_EVENT_NAME_LEN, "-:%s", name);
|
||||
|
||||
return trace_run_command(buf, create_or_delete_trace_kprobe);
|
||||
}
|
||||
EXPORT_SYMBOL_GPL(kprobe_event_delete);
|
||||
|
||||
static int trace_kprobe_release(struct dyn_event *ev)
|
||||
{
|
||||
struct trace_kprobe *tk = to_trace_kprobe(ev);
|
||||
|
@ -1175,35 +1335,35 @@ __kprobe_trace_func(struct trace_kprobe *tk, struct pt_regs *regs,
|
|||
struct trace_event_file *trace_file)
|
||||
{
|
||||
struct kprobe_trace_entry_head *entry;
|
||||
struct ring_buffer_event *event;
|
||||
struct ring_buffer *buffer;
|
||||
int size, dsize, pc;
|
||||
unsigned long irq_flags;
|
||||
struct trace_event_call *call = trace_probe_event_call(&tk->tp);
|
||||
struct trace_event_buffer fbuffer;
|
||||
int dsize;
|
||||
|
||||
WARN_ON(call != trace_file->event_call);
|
||||
|
||||
if (trace_trigger_soft_disabled(trace_file))
|
||||
return;
|
||||
|
||||
local_save_flags(irq_flags);
|
||||
pc = preempt_count();
|
||||
local_save_flags(fbuffer.flags);
|
||||
fbuffer.pc = preempt_count();
|
||||
fbuffer.trace_file = trace_file;
|
||||
|
||||
dsize = __get_data_size(&tk->tp, regs);
|
||||
size = sizeof(*entry) + tk->tp.size + dsize;
|
||||
|
||||
event = trace_event_buffer_lock_reserve(&buffer, trace_file,
|
||||
call->event.type,
|
||||
size, irq_flags, pc);
|
||||
if (!event)
|
||||
fbuffer.event =
|
||||
trace_event_buffer_lock_reserve(&fbuffer.buffer, trace_file,
|
||||
call->event.type,
|
||||
sizeof(*entry) + tk->tp.size + dsize,
|
||||
fbuffer.flags, fbuffer.pc);
|
||||
if (!fbuffer.event)
|
||||
return;
|
||||
|
||||
entry = ring_buffer_event_data(event);
|
||||
fbuffer.regs = regs;
|
||||
entry = fbuffer.entry = ring_buffer_event_data(fbuffer.event);
|
||||
entry->ip = (unsigned long)tk->rp.kp.addr;
|
||||
store_trace_args(&entry[1], &tk->tp, regs, sizeof(*entry), dsize);
|
||||
|
||||
event_trigger_unlock_commit_regs(trace_file, buffer, event,
|
||||
entry, irq_flags, pc, regs);
|
||||
trace_event_buffer_commit(&fbuffer);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -1223,36 +1383,35 @@ __kretprobe_trace_func(struct trace_kprobe *tk, struct kretprobe_instance *ri,
|
|||
struct trace_event_file *trace_file)
|
||||
{
|
||||
struct kretprobe_trace_entry_head *entry;
|
||||
struct ring_buffer_event *event;
|
||||
struct ring_buffer *buffer;
|
||||
int size, pc, dsize;
|
||||
unsigned long irq_flags;
|
||||
struct trace_event_buffer fbuffer;
|
||||
struct trace_event_call *call = trace_probe_event_call(&tk->tp);
|
||||
int dsize;
|
||||
|
||||
WARN_ON(call != trace_file->event_call);
|
||||
|
||||
if (trace_trigger_soft_disabled(trace_file))
|
||||
return;
|
||||
|
||||
local_save_flags(irq_flags);
|
||||
pc = preempt_count();
|
||||
local_save_flags(fbuffer.flags);
|
||||
fbuffer.pc = preempt_count();
|
||||
fbuffer.trace_file = trace_file;
|
||||
|
||||
dsize = __get_data_size(&tk->tp, regs);
|
||||
size = sizeof(*entry) + tk->tp.size + dsize;
|
||||
|
||||
event = trace_event_buffer_lock_reserve(&buffer, trace_file,
|
||||
call->event.type,
|
||||
size, irq_flags, pc);
|
||||
if (!event)
|
||||
fbuffer.event =
|
||||
trace_event_buffer_lock_reserve(&fbuffer.buffer, trace_file,
|
||||
call->event.type,
|
||||
sizeof(*entry) + tk->tp.size + dsize,
|
||||
fbuffer.flags, fbuffer.pc);
|
||||
if (!fbuffer.event)
|
||||
return;
|
||||
|
||||
entry = ring_buffer_event_data(event);
|
||||
fbuffer.regs = regs;
|
||||
entry = fbuffer.entry = ring_buffer_event_data(fbuffer.event);
|
||||
entry->func = (unsigned long)tk->rp.kp.addr;
|
||||
entry->ret_ip = (unsigned long)ri->ret_addr;
|
||||
store_trace_args(&entry[1], &tk->tp, regs, sizeof(*entry), dsize);
|
||||
|
||||
event_trigger_unlock_commit_regs(trace_file, buffer, event,
|
||||
entry, irq_flags, pc, regs);
|
||||
trace_event_buffer_commit(&fbuffer);
|
||||
}
|
||||
|
||||
static void
|
||||
|
@ -1698,11 +1857,12 @@ static __init void setup_boot_kprobe_events(void)
|
|||
enable_boot_kprobe_events();
|
||||
}
|
||||
|
||||
/* Make a tracefs interface for controlling probe points */
|
||||
static __init int init_kprobe_trace(void)
|
||||
/*
|
||||
* Register dynevent at subsys_initcall. This allows kernel to setup kprobe
|
||||
* events in fs_initcall without tracefs.
|
||||
*/
|
||||
static __init int init_kprobe_trace_early(void)
|
||||
{
|
||||
struct dentry *d_tracer;
|
||||
struct dentry *entry;
|
||||
int ret;
|
||||
|
||||
ret = dyn_event_register(&trace_kprobe_ops);
|
||||
|
@ -1712,6 +1872,16 @@ static __init int init_kprobe_trace(void)
|
|||
if (register_module_notifier(&trace_kprobe_module_nb))
|
||||
return -EINVAL;
|
||||
|
||||
return 0;
|
||||
}
|
||||
subsys_initcall(init_kprobe_trace_early);
|
||||
|
||||
/* Make a tracefs interface for controlling probe points */
|
||||
static __init int init_kprobe_trace(void)
|
||||
{
|
||||
struct dentry *d_tracer;
|
||||
struct dentry *entry;
|
||||
|
||||
d_tracer = tracing_init_dentry();
|
||||
if (IS_ERR(d_tracer))
|
||||
return 0;
|
||||
|
|
|
@ -32,7 +32,7 @@ static void mmio_reset_data(struct trace_array *tr)
|
|||
overrun_detected = false;
|
||||
prev_overruns = 0;
|
||||
|
||||
tracing_reset_online_cpus(&tr->trace_buffer);
|
||||
tracing_reset_online_cpus(&tr->array_buffer);
|
||||
}
|
||||
|
||||
static int mmio_trace_init(struct trace_array *tr)
|
||||
|
@ -122,7 +122,7 @@ static void mmio_close(struct trace_iterator *iter)
|
|||
static unsigned long count_overruns(struct trace_iterator *iter)
|
||||
{
|
||||
unsigned long cnt = atomic_xchg(&dropped_count, 0);
|
||||
unsigned long over = ring_buffer_overruns(iter->trace_buffer->buffer);
|
||||
unsigned long over = ring_buffer_overruns(iter->array_buffer->buffer);
|
||||
|
||||
if (over > prev_overruns)
|
||||
cnt += over - prev_overruns;
|
||||
|
@ -297,7 +297,7 @@ static void __trace_mmiotrace_rw(struct trace_array *tr,
|
|||
struct mmiotrace_rw *rw)
|
||||
{
|
||||
struct trace_event_call *call = &event_mmiotrace_rw;
|
||||
struct ring_buffer *buffer = tr->trace_buffer.buffer;
|
||||
struct trace_buffer *buffer = tr->array_buffer.buffer;
|
||||
struct ring_buffer_event *event;
|
||||
struct trace_mmiotrace_rw *entry;
|
||||
int pc = preempt_count();
|
||||
|
@ -318,7 +318,7 @@ static void __trace_mmiotrace_rw(struct trace_array *tr,
|
|||
void mmio_trace_rw(struct mmiotrace_rw *rw)
|
||||
{
|
||||
struct trace_array *tr = mmio_trace_array;
|
||||
struct trace_array_cpu *data = per_cpu_ptr(tr->trace_buffer.data, smp_processor_id());
|
||||
struct trace_array_cpu *data = per_cpu_ptr(tr->array_buffer.data, smp_processor_id());
|
||||
__trace_mmiotrace_rw(tr, data, rw);
|
||||
}
|
||||
|
||||
|
@ -327,7 +327,7 @@ static void __trace_mmiotrace_map(struct trace_array *tr,
|
|||
struct mmiotrace_map *map)
|
||||
{
|
||||
struct trace_event_call *call = &event_mmiotrace_map;
|
||||
struct ring_buffer *buffer = tr->trace_buffer.buffer;
|
||||
struct trace_buffer *buffer = tr->array_buffer.buffer;
|
||||
struct ring_buffer_event *event;
|
||||
struct trace_mmiotrace_map *entry;
|
||||
int pc = preempt_count();
|
||||
|
@ -351,7 +351,7 @@ void mmio_trace_mapping(struct mmiotrace_map *map)
|
|||
struct trace_array_cpu *data;
|
||||
|
||||
preempt_disable();
|
||||
data = per_cpu_ptr(tr->trace_buffer.data, smp_processor_id());
|
||||
data = per_cpu_ptr(tr->array_buffer.data, smp_processor_id());
|
||||
__trace_mmiotrace_map(tr, data, map);
|
||||
preempt_enable();
|
||||
}
|
||||
|
|
|
@ -538,7 +538,7 @@ lat_print_timestamp(struct trace_iterator *iter, u64 next_ts)
|
|||
struct trace_array *tr = iter->tr;
|
||||
unsigned long verbose = tr->trace_flags & TRACE_ITER_VERBOSE;
|
||||
unsigned long in_ns = iter->iter_flags & TRACE_FILE_TIME_IN_NS;
|
||||
unsigned long long abs_ts = iter->ts - iter->trace_buffer->time_start;
|
||||
unsigned long long abs_ts = iter->ts - iter->array_buffer->time_start;
|
||||
unsigned long long rel_ts = next_ts - iter->ts;
|
||||
struct trace_seq *s = &iter->seq;
|
||||
|
||||
|
|
|
@ -89,8 +89,10 @@ static void tracing_sched_unregister(void)
|
|||
|
||||
static void tracing_start_sched_switch(int ops)
|
||||
{
|
||||
bool sched_register = (!sched_cmdline_ref && !sched_tgid_ref);
|
||||
bool sched_register;
|
||||
|
||||
mutex_lock(&sched_register_mutex);
|
||||
sched_register = (!sched_cmdline_ref && !sched_tgid_ref);
|
||||
|
||||
switch (ops) {
|
||||
case RECORD_CMDLINE:
|
||||
|
|
|
@ -82,7 +82,7 @@ func_prolog_preempt_disable(struct trace_array *tr,
|
|||
if (cpu != wakeup_current_cpu)
|
||||
goto out_enable;
|
||||
|
||||
*data = per_cpu_ptr(tr->trace_buffer.data, cpu);
|
||||
*data = per_cpu_ptr(tr->array_buffer.data, cpu);
|
||||
disabled = atomic_inc_return(&(*data)->disabled);
|
||||
if (unlikely(disabled != 1))
|
||||
goto out;
|
||||
|
@ -378,7 +378,7 @@ tracing_sched_switch_trace(struct trace_array *tr,
|
|||
unsigned long flags, int pc)
|
||||
{
|
||||
struct trace_event_call *call = &event_context_switch;
|
||||
struct ring_buffer *buffer = tr->trace_buffer.buffer;
|
||||
struct trace_buffer *buffer = tr->array_buffer.buffer;
|
||||
struct ring_buffer_event *event;
|
||||
struct ctx_switch_entry *entry;
|
||||
|
||||
|
@ -408,7 +408,7 @@ tracing_sched_wakeup_trace(struct trace_array *tr,
|
|||
struct trace_event_call *call = &event_wakeup;
|
||||
struct ring_buffer_event *event;
|
||||
struct ctx_switch_entry *entry;
|
||||
struct ring_buffer *buffer = tr->trace_buffer.buffer;
|
||||
struct trace_buffer *buffer = tr->array_buffer.buffer;
|
||||
|
||||
event = trace_buffer_lock_reserve(buffer, TRACE_WAKE,
|
||||
sizeof(*entry), flags, pc);
|
||||
|
@ -459,7 +459,7 @@ probe_wakeup_sched_switch(void *ignore, bool preempt,
|
|||
|
||||
/* disable local data, not wakeup_cpu data */
|
||||
cpu = raw_smp_processor_id();
|
||||
disabled = atomic_inc_return(&per_cpu_ptr(wakeup_trace->trace_buffer.data, cpu)->disabled);
|
||||
disabled = atomic_inc_return(&per_cpu_ptr(wakeup_trace->array_buffer.data, cpu)->disabled);
|
||||
if (likely(disabled != 1))
|
||||
goto out;
|
||||
|
||||
|
@ -471,7 +471,7 @@ probe_wakeup_sched_switch(void *ignore, bool preempt,
|
|||
goto out_unlock;
|
||||
|
||||
/* The task we are waiting for is waking up */
|
||||
data = per_cpu_ptr(wakeup_trace->trace_buffer.data, wakeup_cpu);
|
||||
data = per_cpu_ptr(wakeup_trace->array_buffer.data, wakeup_cpu);
|
||||
|
||||
__trace_function(wakeup_trace, CALLER_ADDR0, CALLER_ADDR1, flags, pc);
|
||||
tracing_sched_switch_trace(wakeup_trace, prev, next, flags, pc);
|
||||
|
@ -494,7 +494,7 @@ probe_wakeup_sched_switch(void *ignore, bool preempt,
|
|||
arch_spin_unlock(&wakeup_lock);
|
||||
local_irq_restore(flags);
|
||||
out:
|
||||
atomic_dec(&per_cpu_ptr(wakeup_trace->trace_buffer.data, cpu)->disabled);
|
||||
atomic_dec(&per_cpu_ptr(wakeup_trace->array_buffer.data, cpu)->disabled);
|
||||
}
|
||||
|
||||
static void __wakeup_reset(struct trace_array *tr)
|
||||
|
@ -513,7 +513,7 @@ static void wakeup_reset(struct trace_array *tr)
|
|||
{
|
||||
unsigned long flags;
|
||||
|
||||
tracing_reset_online_cpus(&tr->trace_buffer);
|
||||
tracing_reset_online_cpus(&tr->array_buffer);
|
||||
|
||||
local_irq_save(flags);
|
||||
arch_spin_lock(&wakeup_lock);
|
||||
|
@ -551,7 +551,7 @@ probe_wakeup(void *ignore, struct task_struct *p)
|
|||
return;
|
||||
|
||||
pc = preempt_count();
|
||||
disabled = atomic_inc_return(&per_cpu_ptr(wakeup_trace->trace_buffer.data, cpu)->disabled);
|
||||
disabled = atomic_inc_return(&per_cpu_ptr(wakeup_trace->array_buffer.data, cpu)->disabled);
|
||||
if (unlikely(disabled != 1))
|
||||
goto out;
|
||||
|
||||
|
@ -583,7 +583,7 @@ probe_wakeup(void *ignore, struct task_struct *p)
|
|||
|
||||
local_save_flags(flags);
|
||||
|
||||
data = per_cpu_ptr(wakeup_trace->trace_buffer.data, wakeup_cpu);
|
||||
data = per_cpu_ptr(wakeup_trace->array_buffer.data, wakeup_cpu);
|
||||
data->preempt_timestamp = ftrace_now(cpu);
|
||||
tracing_sched_wakeup_trace(wakeup_trace, p, current, flags, pc);
|
||||
__trace_stack(wakeup_trace, flags, 0, pc);
|
||||
|
@ -598,7 +598,7 @@ probe_wakeup(void *ignore, struct task_struct *p)
|
|||
out_locked:
|
||||
arch_spin_unlock(&wakeup_lock);
|
||||
out:
|
||||
atomic_dec(&per_cpu_ptr(wakeup_trace->trace_buffer.data, cpu)->disabled);
|
||||
atomic_dec(&per_cpu_ptr(wakeup_trace->array_buffer.data, cpu)->disabled);
|
||||
}
|
||||
|
||||
static void start_wakeup_tracer(struct trace_array *tr)
|
||||
|
|
|
@ -23,7 +23,7 @@ static inline int trace_valid_entry(struct trace_entry *entry)
|
|||
return 0;
|
||||
}
|
||||
|
||||
static int trace_test_buffer_cpu(struct trace_buffer *buf, int cpu)
|
||||
static int trace_test_buffer_cpu(struct array_buffer *buf, int cpu)
|
||||
{
|
||||
struct ring_buffer_event *event;
|
||||
struct trace_entry *entry;
|
||||
|
@ -60,7 +60,7 @@ static int trace_test_buffer_cpu(struct trace_buffer *buf, int cpu)
|
|||
* Test the trace buffer to see if all the elements
|
||||
* are still sane.
|
||||
*/
|
||||
static int __maybe_unused trace_test_buffer(struct trace_buffer *buf, unsigned long *count)
|
||||
static int __maybe_unused trace_test_buffer(struct array_buffer *buf, unsigned long *count)
|
||||
{
|
||||
unsigned long flags, cnt = 0;
|
||||
int cpu, ret = 0;
|
||||
|
@ -362,7 +362,7 @@ static int trace_selftest_startup_dynamic_tracing(struct tracer *trace,
|
|||
msleep(100);
|
||||
|
||||
/* we should have nothing in the buffer */
|
||||
ret = trace_test_buffer(&tr->trace_buffer, &count);
|
||||
ret = trace_test_buffer(&tr->array_buffer, &count);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
|
@ -383,7 +383,7 @@ static int trace_selftest_startup_dynamic_tracing(struct tracer *trace,
|
|||
ftrace_enabled = 0;
|
||||
|
||||
/* check the trace buffer */
|
||||
ret = trace_test_buffer(&tr->trace_buffer, &count);
|
||||
ret = trace_test_buffer(&tr->array_buffer, &count);
|
||||
|
||||
ftrace_enabled = 1;
|
||||
tracing_start();
|
||||
|
@ -682,7 +682,7 @@ trace_selftest_startup_function(struct tracer *trace, struct trace_array *tr)
|
|||
ftrace_enabled = 0;
|
||||
|
||||
/* check the trace buffer */
|
||||
ret = trace_test_buffer(&tr->trace_buffer, &count);
|
||||
ret = trace_test_buffer(&tr->array_buffer, &count);
|
||||
|
||||
ftrace_enabled = 1;
|
||||
trace->reset(tr);
|
||||
|
@ -768,7 +768,7 @@ trace_selftest_startup_function_graph(struct tracer *trace,
|
|||
* Simulate the init() callback but we attach a watchdog callback
|
||||
* to detect and recover from possible hangs
|
||||
*/
|
||||
tracing_reset_online_cpus(&tr->trace_buffer);
|
||||
tracing_reset_online_cpus(&tr->array_buffer);
|
||||
set_graph_array(tr);
|
||||
ret = register_ftrace_graph(&fgraph_ops);
|
||||
if (ret) {
|
||||
|
@ -790,7 +790,7 @@ trace_selftest_startup_function_graph(struct tracer *trace,
|
|||
tracing_stop();
|
||||
|
||||
/* check the trace buffer */
|
||||
ret = trace_test_buffer(&tr->trace_buffer, &count);
|
||||
ret = trace_test_buffer(&tr->array_buffer, &count);
|
||||
|
||||
/* Need to also simulate the tr->reset to remove this fgraph_ops */
|
||||
tracing_stop_cmdline_record();
|
||||
|
@ -848,7 +848,7 @@ trace_selftest_startup_irqsoff(struct tracer *trace, struct trace_array *tr)
|
|||
/* stop the tracing. */
|
||||
tracing_stop();
|
||||
/* check both trace buffers */
|
||||
ret = trace_test_buffer(&tr->trace_buffer, NULL);
|
||||
ret = trace_test_buffer(&tr->array_buffer, NULL);
|
||||
if (!ret)
|
||||
ret = trace_test_buffer(&tr->max_buffer, &count);
|
||||
trace->reset(tr);
|
||||
|
@ -910,7 +910,7 @@ trace_selftest_startup_preemptoff(struct tracer *trace, struct trace_array *tr)
|
|||
/* stop the tracing. */
|
||||
tracing_stop();
|
||||
/* check both trace buffers */
|
||||
ret = trace_test_buffer(&tr->trace_buffer, NULL);
|
||||
ret = trace_test_buffer(&tr->array_buffer, NULL);
|
||||
if (!ret)
|
||||
ret = trace_test_buffer(&tr->max_buffer, &count);
|
||||
trace->reset(tr);
|
||||
|
@ -976,7 +976,7 @@ trace_selftest_startup_preemptirqsoff(struct tracer *trace, struct trace_array *
|
|||
/* stop the tracing. */
|
||||
tracing_stop();
|
||||
/* check both trace buffers */
|
||||
ret = trace_test_buffer(&tr->trace_buffer, NULL);
|
||||
ret = trace_test_buffer(&tr->array_buffer, NULL);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
|
@ -1006,7 +1006,7 @@ trace_selftest_startup_preemptirqsoff(struct tracer *trace, struct trace_array *
|
|||
/* stop the tracing. */
|
||||
tracing_stop();
|
||||
/* check both trace buffers */
|
||||
ret = trace_test_buffer(&tr->trace_buffer, NULL);
|
||||
ret = trace_test_buffer(&tr->array_buffer, NULL);
|
||||
if (ret)
|
||||
goto out;
|
||||
|
||||
|
@ -1136,7 +1136,7 @@ trace_selftest_startup_wakeup(struct tracer *trace, struct trace_array *tr)
|
|||
/* stop the tracing. */
|
||||
tracing_stop();
|
||||
/* check both trace buffers */
|
||||
ret = trace_test_buffer(&tr->trace_buffer, NULL);
|
||||
ret = trace_test_buffer(&tr->array_buffer, NULL);
|
||||
if (!ret)
|
||||
ret = trace_test_buffer(&tr->max_buffer, &count);
|
||||
|
||||
|
@ -1177,7 +1177,7 @@ trace_selftest_startup_branch(struct tracer *trace, struct trace_array *tr)
|
|||
/* stop the tracing. */
|
||||
tracing_stop();
|
||||
/* check the trace buffer */
|
||||
ret = trace_test_buffer(&tr->trace_buffer, &count);
|
||||
ret = trace_test_buffer(&tr->array_buffer, &count);
|
||||
trace->reset(tr);
|
||||
tracing_start();
|
||||
|
||||
|
|
|
@ -30,9 +30,6 @@
|
|||
/* How much buffer is left on the trace_seq? */
|
||||
#define TRACE_SEQ_BUF_LEFT(s) seq_buf_buffer_left(&(s)->seq)
|
||||
|
||||
/* How much buffer is written? */
|
||||
#define TRACE_SEQ_BUF_USED(s) seq_buf_used(&(s)->seq)
|
||||
|
||||
/*
|
||||
* trace_seq should work with being initialized with 0s.
|
||||
*/
|
||||
|
|
|
@ -280,18 +280,22 @@ static int tracing_stat_init(void)
|
|||
|
||||
d_tracing = tracing_init_dentry();
|
||||
if (IS_ERR(d_tracing))
|
||||
return 0;
|
||||
return -ENODEV;
|
||||
|
||||
stat_dir = tracefs_create_dir("trace_stat", d_tracing);
|
||||
if (!stat_dir)
|
||||
if (!stat_dir) {
|
||||
pr_warn("Could not create tracefs 'trace_stat' entry\n");
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int init_stat_file(struct stat_session *session)
|
||||
{
|
||||
if (!stat_dir && tracing_stat_init())
|
||||
return -ENODEV;
|
||||
int ret;
|
||||
|
||||
if (!stat_dir && (ret = tracing_stat_init()))
|
||||
return ret;
|
||||
|
||||
session->file = tracefs_create_file(session->ts->name, 0644,
|
||||
stat_dir,
|
||||
|
@ -304,7 +308,7 @@ static int init_stat_file(struct stat_session *session)
|
|||
int register_stat_tracer(struct tracer_stat *trace)
|
||||
{
|
||||
struct stat_session *session, *node;
|
||||
int ret;
|
||||
int ret = -EINVAL;
|
||||
|
||||
if (!trace)
|
||||
return -EINVAL;
|
||||
|
@ -315,17 +319,15 @@ int register_stat_tracer(struct tracer_stat *trace)
|
|||
/* Already registered? */
|
||||
mutex_lock(&all_stat_sessions_mutex);
|
||||
list_for_each_entry(node, &all_stat_sessions, session_list) {
|
||||
if (node->ts == trace) {
|
||||
mutex_unlock(&all_stat_sessions_mutex);
|
||||
return -EINVAL;
|
||||
}
|
||||
if (node->ts == trace)
|
||||
goto out;
|
||||
}
|
||||
mutex_unlock(&all_stat_sessions_mutex);
|
||||
|
||||
ret = -ENOMEM;
|
||||
/* Init the session */
|
||||
session = kzalloc(sizeof(*session), GFP_KERNEL);
|
||||
if (!session)
|
||||
return -ENOMEM;
|
||||
goto out;
|
||||
|
||||
session->ts = trace;
|
||||
INIT_LIST_HEAD(&session->session_list);
|
||||
|
@ -334,15 +336,16 @@ int register_stat_tracer(struct tracer_stat *trace)
|
|||
ret = init_stat_file(session);
|
||||
if (ret) {
|
||||
destroy_session(session);
|
||||
return ret;
|
||||
goto out;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
/* Register */
|
||||
mutex_lock(&all_stat_sessions_mutex);
|
||||
list_add_tail(&session->session_list, &all_stat_sessions);
|
||||
out:
|
||||
mutex_unlock(&all_stat_sessions_mutex);
|
||||
|
||||
return 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void unregister_stat_tracer(struct tracer_stat *trace)
|
||||
|
|
|
@ -297,7 +297,7 @@ static void ftrace_syscall_enter(void *data, struct pt_regs *regs, long id)
|
|||
struct syscall_trace_enter *entry;
|
||||
struct syscall_metadata *sys_data;
|
||||
struct ring_buffer_event *event;
|
||||
struct ring_buffer *buffer;
|
||||
struct trace_buffer *buffer;
|
||||
unsigned long irq_flags;
|
||||
unsigned long args[6];
|
||||
int pc;
|
||||
|
@ -325,7 +325,7 @@ static void ftrace_syscall_enter(void *data, struct pt_regs *regs, long id)
|
|||
local_save_flags(irq_flags);
|
||||
pc = preempt_count();
|
||||
|
||||
buffer = tr->trace_buffer.buffer;
|
||||
buffer = tr->array_buffer.buffer;
|
||||
event = trace_buffer_lock_reserve(buffer,
|
||||
sys_data->enter_event->event.type, size, irq_flags, pc);
|
||||
if (!event)
|
||||
|
@ -347,7 +347,7 @@ static void ftrace_syscall_exit(void *data, struct pt_regs *regs, long ret)
|
|||
struct syscall_trace_exit *entry;
|
||||
struct syscall_metadata *sys_data;
|
||||
struct ring_buffer_event *event;
|
||||
struct ring_buffer *buffer;
|
||||
struct trace_buffer *buffer;
|
||||
unsigned long irq_flags;
|
||||
int pc;
|
||||
int syscall_nr;
|
||||
|
@ -371,7 +371,7 @@ static void ftrace_syscall_exit(void *data, struct pt_regs *regs, long ret)
|
|||
local_save_flags(irq_flags);
|
||||
pc = preempt_count();
|
||||
|
||||
buffer = tr->trace_buffer.buffer;
|
||||
buffer = tr->array_buffer.buffer;
|
||||
event = trace_buffer_lock_reserve(buffer,
|
||||
sys_data->exit_event->event.type, sizeof(*entry),
|
||||
irq_flags, pc);
|
||||
|
|
|
@ -931,8 +931,8 @@ static void __uprobe_trace_func(struct trace_uprobe *tu,
|
|||
struct trace_event_file *trace_file)
|
||||
{
|
||||
struct uprobe_trace_entry_head *entry;
|
||||
struct trace_buffer *buffer;
|
||||
struct ring_buffer_event *event;
|
||||
struct ring_buffer *buffer;
|
||||
void *data;
|
||||
int size, esize;
|
||||
struct trace_event_call *call = trace_probe_event_call(&tu->tp);
|
||||
|
|
|
@ -573,6 +573,9 @@ config DIMLIB
|
|||
config LIBFDT
|
||||
bool
|
||||
|
||||
config LIBXBC
|
||||
bool
|
||||
|
||||
config OID_REGISTRY
|
||||
tristate
|
||||
help
|
||||
|
|
|
@ -230,6 +230,8 @@ $(foreach file, $(libfdt_files), \
|
|||
$(eval CFLAGS_$(file) = -I $(srctree)/scripts/dtc/libfdt))
|
||||
lib-$(CONFIG_LIBFDT) += $(libfdt_files)
|
||||
|
||||
lib-$(CONFIG_LIBXBC) += bootconfig.o
|
||||
|
||||
obj-$(CONFIG_RBTREE_TEST) += rbtree_test.o
|
||||
obj-$(CONFIG_INTERVAL_TREE_TEST) += interval_tree_test.o
|
||||
|
||||
|
|
|
@ -0,0 +1,814 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Extra Boot Config
|
||||
* Masami Hiramatsu <mhiramat@kernel.org>
|
||||
*/
|
||||
|
||||
#define pr_fmt(fmt) "bootconfig: " fmt
|
||||
|
||||
#include <linux/bug.h>
|
||||
#include <linux/ctype.h>
|
||||
#include <linux/errno.h>
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/printk.h>
|
||||
#include <linux/bootconfig.h>
|
||||
#include <linux/string.h>
|
||||
|
||||
/*
|
||||
* Extra Boot Config (XBC) is given as tree-structured ascii text of
|
||||
* key-value pairs on memory.
|
||||
* xbc_parse() parses the text to build a simple tree. Each tree node is
|
||||
* simply a key word or a value. A key node may have a next key node or/and
|
||||
* a child node (both key and value). A value node may have a next value
|
||||
* node (for array).
|
||||
*/
|
||||
|
||||
static struct xbc_node xbc_nodes[XBC_NODE_MAX] __initdata;
|
||||
static int xbc_node_num __initdata;
|
||||
static char *xbc_data __initdata;
|
||||
static size_t xbc_data_size __initdata;
|
||||
static struct xbc_node *last_parent __initdata;
|
||||
|
||||
static int __init xbc_parse_error(const char *msg, const char *p)
|
||||
{
|
||||
int pos = p - xbc_data;
|
||||
|
||||
pr_err("Parse error at pos %d: %s\n", pos, msg);
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_root_node() - Get the root node of extended boot config
|
||||
*
|
||||
* Return the address of root node of extended boot config. If the
|
||||
* extended boot config is not initiized, return NULL.
|
||||
*/
|
||||
struct xbc_node * __init xbc_root_node(void)
|
||||
{
|
||||
if (unlikely(!xbc_data))
|
||||
return NULL;
|
||||
|
||||
return xbc_nodes;
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_node_index() - Get the index of XBC node
|
||||
* @node: A target node of getting index.
|
||||
*
|
||||
* Return the index number of @node in XBC node list.
|
||||
*/
|
||||
int __init xbc_node_index(struct xbc_node *node)
|
||||
{
|
||||
return node - &xbc_nodes[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_node_get_parent() - Get the parent XBC node
|
||||
* @node: An XBC node.
|
||||
*
|
||||
* Return the parent node of @node. If the node is top node of the tree,
|
||||
* return NULL.
|
||||
*/
|
||||
struct xbc_node * __init xbc_node_get_parent(struct xbc_node *node)
|
||||
{
|
||||
return node->parent == XBC_NODE_MAX ? NULL : &xbc_nodes[node->parent];
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_node_get_child() - Get the child XBC node
|
||||
* @node: An XBC node.
|
||||
*
|
||||
* Return the first child node of @node. If the node has no child, return
|
||||
* NULL.
|
||||
*/
|
||||
struct xbc_node * __init xbc_node_get_child(struct xbc_node *node)
|
||||
{
|
||||
return node->child ? &xbc_nodes[node->child] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_node_get_next() - Get the next sibling XBC node
|
||||
* @node: An XBC node.
|
||||
*
|
||||
* Return the NEXT sibling node of @node. If the node has no next sibling,
|
||||
* return NULL. Note that even if this returns NULL, it doesn't mean @node
|
||||
* has no siblings. (You also has to check whether the parent's child node
|
||||
* is @node or not.)
|
||||
*/
|
||||
struct xbc_node * __init xbc_node_get_next(struct xbc_node *node)
|
||||
{
|
||||
return node->next ? &xbc_nodes[node->next] : NULL;
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_node_get_data() - Get the data of XBC node
|
||||
* @node: An XBC node.
|
||||
*
|
||||
* Return the data (which is always a null terminated string) of @node.
|
||||
* If the node has invalid data, warn and return NULL.
|
||||
*/
|
||||
const char * __init xbc_node_get_data(struct xbc_node *node)
|
||||
{
|
||||
int offset = node->data & ~XBC_VALUE;
|
||||
|
||||
if (WARN_ON(offset >= xbc_data_size))
|
||||
return NULL;
|
||||
|
||||
return xbc_data + offset;
|
||||
}
|
||||
|
||||
static bool __init
|
||||
xbc_node_match_prefix(struct xbc_node *node, const char **prefix)
|
||||
{
|
||||
const char *p = xbc_node_get_data(node);
|
||||
int len = strlen(p);
|
||||
|
||||
if (strncmp(*prefix, p, len))
|
||||
return false;
|
||||
|
||||
p = *prefix + len;
|
||||
if (*p == '.')
|
||||
p++;
|
||||
else if (*p != '\0')
|
||||
return false;
|
||||
*prefix = p;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_node_find_child() - Find a child node which matches given key
|
||||
* @parent: An XBC node.
|
||||
* @key: A key string.
|
||||
*
|
||||
* Search a node under @parent which matches @key. The @key can contain
|
||||
* several words jointed with '.'. If @parent is NULL, this searches the
|
||||
* node from whole tree. Return NULL if no node is matched.
|
||||
*/
|
||||
struct xbc_node * __init
|
||||
xbc_node_find_child(struct xbc_node *parent, const char *key)
|
||||
{
|
||||
struct xbc_node *node;
|
||||
|
||||
if (parent)
|
||||
node = xbc_node_get_child(parent);
|
||||
else
|
||||
node = xbc_root_node();
|
||||
|
||||
while (node && xbc_node_is_key(node)) {
|
||||
if (!xbc_node_match_prefix(node, &key))
|
||||
node = xbc_node_get_next(node);
|
||||
else if (*key != '\0')
|
||||
node = xbc_node_get_child(node);
|
||||
else
|
||||
break;
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_node_find_value() - Find a value node which matches given key
|
||||
* @parent: An XBC node.
|
||||
* @key: A key string.
|
||||
* @vnode: A container pointer of found XBC node.
|
||||
*
|
||||
* Search a value node under @parent whose (parent) key node matches @key,
|
||||
* store it in *@vnode, and returns the value string.
|
||||
* The @key can contain several words jointed with '.'. If @parent is NULL,
|
||||
* this searches the node from whole tree. Return the value string if a
|
||||
* matched key found, return NULL if no node is matched.
|
||||
* Note that this returns 0-length string and stores NULL in *@vnode if the
|
||||
* key has no value. And also it will return the value of the first entry if
|
||||
* the value is an array.
|
||||
*/
|
||||
const char * __init
|
||||
xbc_node_find_value(struct xbc_node *parent, const char *key,
|
||||
struct xbc_node **vnode)
|
||||
{
|
||||
struct xbc_node *node = xbc_node_find_child(parent, key);
|
||||
|
||||
if (!node || !xbc_node_is_key(node))
|
||||
return NULL;
|
||||
|
||||
node = xbc_node_get_child(node);
|
||||
if (node && !xbc_node_is_value(node))
|
||||
return NULL;
|
||||
|
||||
if (vnode)
|
||||
*vnode = node;
|
||||
|
||||
return node ? xbc_node_get_data(node) : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_node_compose_key_after() - Compose partial key string of the XBC node
|
||||
* @root: Root XBC node
|
||||
* @node: Target XBC node.
|
||||
* @buf: A buffer to store the key.
|
||||
* @size: The size of the @buf.
|
||||
*
|
||||
* Compose the partial key of the @node into @buf, which is starting right
|
||||
* after @root (@root is not included.) If @root is NULL, this returns full
|
||||
* key words of @node.
|
||||
* Returns the total length of the key stored in @buf. Returns -EINVAL
|
||||
* if @node is NULL or @root is not the ancestor of @node or @root is @node,
|
||||
* or returns -ERANGE if the key depth is deeper than max depth.
|
||||
* This is expected to be used with xbc_find_node() to list up all (child)
|
||||
* keys under given key.
|
||||
*/
|
||||
int __init xbc_node_compose_key_after(struct xbc_node *root,
|
||||
struct xbc_node *node,
|
||||
char *buf, size_t size)
|
||||
{
|
||||
u16 keys[XBC_DEPTH_MAX];
|
||||
int depth = 0, ret = 0, total = 0;
|
||||
|
||||
if (!node || node == root)
|
||||
return -EINVAL;
|
||||
|
||||
if (xbc_node_is_value(node))
|
||||
node = xbc_node_get_parent(node);
|
||||
|
||||
while (node && node != root) {
|
||||
keys[depth++] = xbc_node_index(node);
|
||||
if (depth == XBC_DEPTH_MAX)
|
||||
return -ERANGE;
|
||||
node = xbc_node_get_parent(node);
|
||||
}
|
||||
if (!node && root)
|
||||
return -EINVAL;
|
||||
|
||||
while (--depth >= 0) {
|
||||
node = xbc_nodes + keys[depth];
|
||||
ret = snprintf(buf, size, "%s%s", xbc_node_get_data(node),
|
||||
depth ? "." : "");
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
if (ret > size) {
|
||||
size = 0;
|
||||
} else {
|
||||
size -= ret;
|
||||
buf += ret;
|
||||
}
|
||||
total += ret;
|
||||
}
|
||||
|
||||
return total;
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_node_find_next_leaf() - Find the next leaf node under given node
|
||||
* @root: An XBC root node
|
||||
* @node: An XBC node which starts from.
|
||||
*
|
||||
* Search the next leaf node (which means the terminal key node) of @node
|
||||
* under @root node (including @root node itself).
|
||||
* Return the next node or NULL if next leaf node is not found.
|
||||
*/
|
||||
struct xbc_node * __init xbc_node_find_next_leaf(struct xbc_node *root,
|
||||
struct xbc_node *node)
|
||||
{
|
||||
if (unlikely(!xbc_data))
|
||||
return NULL;
|
||||
|
||||
if (!node) { /* First try */
|
||||
node = root;
|
||||
if (!node)
|
||||
node = xbc_nodes;
|
||||
} else {
|
||||
if (node == root) /* @root was a leaf, no child node. */
|
||||
return NULL;
|
||||
|
||||
while (!node->next) {
|
||||
node = xbc_node_get_parent(node);
|
||||
if (node == root)
|
||||
return NULL;
|
||||
/* User passed a node which is not uder parent */
|
||||
if (WARN_ON(!node))
|
||||
return NULL;
|
||||
}
|
||||
node = xbc_node_get_next(node);
|
||||
}
|
||||
|
||||
while (node && !xbc_node_is_leaf(node))
|
||||
node = xbc_node_get_child(node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_node_find_next_key_value() - Find the next key-value pair nodes
|
||||
* @root: An XBC root node
|
||||
* @leaf: A container pointer of XBC node which starts from.
|
||||
*
|
||||
* Search the next leaf node (which means the terminal key node) of *@leaf
|
||||
* under @root node. Returns the value and update *@leaf if next leaf node
|
||||
* is found, or NULL if no next leaf node is found.
|
||||
* Note that this returns 0-length string if the key has no value, or
|
||||
* the value of the first entry if the value is an array.
|
||||
*/
|
||||
const char * __init xbc_node_find_next_key_value(struct xbc_node *root,
|
||||
struct xbc_node **leaf)
|
||||
{
|
||||
/* tip must be passed */
|
||||
if (WARN_ON(!leaf))
|
||||
return NULL;
|
||||
|
||||
*leaf = xbc_node_find_next_leaf(root, *leaf);
|
||||
if (!*leaf)
|
||||
return NULL;
|
||||
if ((*leaf)->child)
|
||||
return xbc_node_get_data(xbc_node_get_child(*leaf));
|
||||
else
|
||||
return ""; /* No value key */
|
||||
}
|
||||
|
||||
/* XBC parse and tree build */
|
||||
|
||||
static struct xbc_node * __init xbc_add_node(char *data, u32 flag)
|
||||
{
|
||||
struct xbc_node *node;
|
||||
unsigned long offset;
|
||||
|
||||
if (xbc_node_num == XBC_NODE_MAX)
|
||||
return NULL;
|
||||
|
||||
node = &xbc_nodes[xbc_node_num++];
|
||||
offset = data - xbc_data;
|
||||
node->data = (u16)offset;
|
||||
if (WARN_ON(offset >= XBC_DATA_MAX))
|
||||
return NULL;
|
||||
node->data |= flag;
|
||||
node->child = 0;
|
||||
node->next = 0;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static inline __init struct xbc_node *xbc_last_sibling(struct xbc_node *node)
|
||||
{
|
||||
while (node->next)
|
||||
node = xbc_node_get_next(node);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static struct xbc_node * __init xbc_add_sibling(char *data, u32 flag)
|
||||
{
|
||||
struct xbc_node *sib, *node = xbc_add_node(data, flag);
|
||||
|
||||
if (node) {
|
||||
if (!last_parent) {
|
||||
node->parent = XBC_NODE_MAX;
|
||||
sib = xbc_last_sibling(xbc_nodes);
|
||||
sib->next = xbc_node_index(node);
|
||||
} else {
|
||||
node->parent = xbc_node_index(last_parent);
|
||||
if (!last_parent->child) {
|
||||
last_parent->child = xbc_node_index(node);
|
||||
} else {
|
||||
sib = xbc_node_get_child(last_parent);
|
||||
sib = xbc_last_sibling(sib);
|
||||
sib->next = xbc_node_index(node);
|
||||
}
|
||||
}
|
||||
} else
|
||||
xbc_parse_error("Too many nodes", data);
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static inline __init struct xbc_node *xbc_add_child(char *data, u32 flag)
|
||||
{
|
||||
struct xbc_node *node = xbc_add_sibling(data, flag);
|
||||
|
||||
if (node)
|
||||
last_parent = node;
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
static inline __init bool xbc_valid_keyword(char *key)
|
||||
{
|
||||
if (key[0] == '\0')
|
||||
return false;
|
||||
|
||||
while (isalnum(*key) || *key == '-' || *key == '_')
|
||||
key++;
|
||||
|
||||
return *key == '\0';
|
||||
}
|
||||
|
||||
static char *skip_comment(char *p)
|
||||
{
|
||||
char *ret;
|
||||
|
||||
ret = strchr(p, '\n');
|
||||
if (!ret)
|
||||
ret = p + strlen(p);
|
||||
else
|
||||
ret++;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static char *skip_spaces_until_newline(char *p)
|
||||
{
|
||||
while (isspace(*p) && *p != '\n')
|
||||
p++;
|
||||
return p;
|
||||
}
|
||||
|
||||
static int __init __xbc_open_brace(void)
|
||||
{
|
||||
/* Mark the last key as open brace */
|
||||
last_parent->next = XBC_NODE_MAX;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init __xbc_close_brace(char *p)
|
||||
{
|
||||
struct xbc_node *node;
|
||||
|
||||
if (!last_parent || last_parent->next != XBC_NODE_MAX)
|
||||
return xbc_parse_error("Unexpected closing brace", p);
|
||||
|
||||
node = last_parent;
|
||||
node->next = 0;
|
||||
do {
|
||||
node = xbc_node_get_parent(node);
|
||||
} while (node && node->next != XBC_NODE_MAX);
|
||||
last_parent = node;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Return delimiter or error, no node added. As same as lib/cmdline.c,
|
||||
* you can use " around spaces, but can't escape " for value.
|
||||
*/
|
||||
static int __init __xbc_parse_value(char **__v, char **__n)
|
||||
{
|
||||
char *p, *v = *__v;
|
||||
int c, quotes = 0;
|
||||
|
||||
v = skip_spaces(v);
|
||||
while (*v == '#') {
|
||||
v = skip_comment(v);
|
||||
v = skip_spaces(v);
|
||||
}
|
||||
if (*v == '"' || *v == '\'') {
|
||||
quotes = *v;
|
||||
v++;
|
||||
}
|
||||
p = v - 1;
|
||||
while ((c = *++p)) {
|
||||
if (!isprint(c) && !isspace(c))
|
||||
return xbc_parse_error("Non printable value", p);
|
||||
if (quotes) {
|
||||
if (c != quotes)
|
||||
continue;
|
||||
quotes = 0;
|
||||
*p++ = '\0';
|
||||
p = skip_spaces_until_newline(p);
|
||||
c = *p;
|
||||
if (c && !strchr(",;\n#}", c))
|
||||
return xbc_parse_error("No value delimiter", p);
|
||||
if (*p)
|
||||
p++;
|
||||
break;
|
||||
}
|
||||
if (strchr(",;\n#}", c)) {
|
||||
v = strim(v);
|
||||
*p++ = '\0';
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (quotes)
|
||||
return xbc_parse_error("No closing quotes", p);
|
||||
if (c == '#') {
|
||||
p = skip_comment(p);
|
||||
c = '\n'; /* A comment must be treated as a newline */
|
||||
}
|
||||
*__n = p;
|
||||
*__v = v;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static int __init xbc_parse_array(char **__v)
|
||||
{
|
||||
struct xbc_node *node;
|
||||
char *next;
|
||||
int c = 0;
|
||||
|
||||
do {
|
||||
c = __xbc_parse_value(__v, &next);
|
||||
if (c < 0)
|
||||
return c;
|
||||
|
||||
node = xbc_add_sibling(*__v, XBC_VALUE);
|
||||
if (!node)
|
||||
return -ENOMEM;
|
||||
*__v = next;
|
||||
} while (c == ',');
|
||||
node->next = 0;
|
||||
|
||||
return c;
|
||||
}
|
||||
|
||||
static inline __init
|
||||
struct xbc_node *find_match_node(struct xbc_node *node, char *k)
|
||||
{
|
||||
while (node) {
|
||||
if (!strcmp(xbc_node_get_data(node), k))
|
||||
break;
|
||||
node = xbc_node_get_next(node);
|
||||
}
|
||||
return node;
|
||||
}
|
||||
|
||||
static int __init __xbc_add_key(char *k)
|
||||
{
|
||||
struct xbc_node *node;
|
||||
|
||||
if (!xbc_valid_keyword(k))
|
||||
return xbc_parse_error("Invalid keyword", k);
|
||||
|
||||
if (unlikely(xbc_node_num == 0))
|
||||
goto add_node;
|
||||
|
||||
if (!last_parent) /* the first level */
|
||||
node = find_match_node(xbc_nodes, k);
|
||||
else
|
||||
node = find_match_node(xbc_node_get_child(last_parent), k);
|
||||
|
||||
if (node)
|
||||
last_parent = node;
|
||||
else {
|
||||
add_node:
|
||||
node = xbc_add_child(k, XBC_KEY);
|
||||
if (!node)
|
||||
return -ENOMEM;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init __xbc_parse_keys(char *k)
|
||||
{
|
||||
char *p;
|
||||
int ret;
|
||||
|
||||
k = strim(k);
|
||||
while ((p = strchr(k, '.'))) {
|
||||
*p++ = '\0';
|
||||
ret = __xbc_add_key(k);
|
||||
if (ret)
|
||||
return ret;
|
||||
k = p;
|
||||
}
|
||||
|
||||
return __xbc_add_key(k);
|
||||
}
|
||||
|
||||
static int __init xbc_parse_kv(char **k, char *v)
|
||||
{
|
||||
struct xbc_node *prev_parent = last_parent;
|
||||
struct xbc_node *node;
|
||||
char *next;
|
||||
int c, ret;
|
||||
|
||||
ret = __xbc_parse_keys(*k);
|
||||
if (ret)
|
||||
return ret;
|
||||
|
||||
c = __xbc_parse_value(&v, &next);
|
||||
if (c < 0)
|
||||
return c;
|
||||
|
||||
node = xbc_add_sibling(v, XBC_VALUE);
|
||||
if (!node)
|
||||
return -ENOMEM;
|
||||
|
||||
if (c == ',') { /* Array */
|
||||
c = xbc_parse_array(&next);
|
||||
if (c < 0)
|
||||
return c;
|
||||
}
|
||||
|
||||
last_parent = prev_parent;
|
||||
|
||||
if (c == '}') {
|
||||
ret = __xbc_close_brace(next - 1);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
}
|
||||
|
||||
*k = next;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init xbc_parse_key(char **k, char *n)
|
||||
{
|
||||
struct xbc_node *prev_parent = last_parent;
|
||||
int ret;
|
||||
|
||||
*k = strim(*k);
|
||||
if (**k != '\0') {
|
||||
ret = __xbc_parse_keys(*k);
|
||||
if (ret)
|
||||
return ret;
|
||||
last_parent = prev_parent;
|
||||
}
|
||||
*k = n;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __init xbc_open_brace(char **k, char *n)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = __xbc_parse_keys(*k);
|
||||
if (ret)
|
||||
return ret;
|
||||
*k = n;
|
||||
|
||||
return __xbc_open_brace();
|
||||
}
|
||||
|
||||
static int __init xbc_close_brace(char **k, char *n)
|
||||
{
|
||||
int ret;
|
||||
|
||||
ret = xbc_parse_key(k, n);
|
||||
if (ret)
|
||||
return ret;
|
||||
/* k is updated in xbc_parse_key() */
|
||||
|
||||
return __xbc_close_brace(n - 1);
|
||||
}
|
||||
|
||||
static int __init xbc_verify_tree(void)
|
||||
{
|
||||
int i, depth, len, wlen;
|
||||
struct xbc_node *n, *m;
|
||||
|
||||
/* Empty tree */
|
||||
if (xbc_node_num == 0) {
|
||||
xbc_parse_error("Empty config", xbc_data);
|
||||
return -ENOENT;
|
||||
}
|
||||
|
||||
for (i = 0; i < xbc_node_num; i++) {
|
||||
if (xbc_nodes[i].next > xbc_node_num) {
|
||||
return xbc_parse_error("No closing brace",
|
||||
xbc_node_get_data(xbc_nodes + i));
|
||||
}
|
||||
}
|
||||
|
||||
/* Key tree limitation check */
|
||||
n = &xbc_nodes[0];
|
||||
depth = 1;
|
||||
len = 0;
|
||||
|
||||
while (n) {
|
||||
wlen = strlen(xbc_node_get_data(n)) + 1;
|
||||
len += wlen;
|
||||
if (len > XBC_KEYLEN_MAX)
|
||||
return xbc_parse_error("Too long key length",
|
||||
xbc_node_get_data(n));
|
||||
|
||||
m = xbc_node_get_child(n);
|
||||
if (m && xbc_node_is_key(m)) {
|
||||
n = m;
|
||||
depth++;
|
||||
if (depth > XBC_DEPTH_MAX)
|
||||
return xbc_parse_error("Too many key words",
|
||||
xbc_node_get_data(n));
|
||||
continue;
|
||||
}
|
||||
len -= wlen;
|
||||
m = xbc_node_get_next(n);
|
||||
while (!m) {
|
||||
n = xbc_node_get_parent(n);
|
||||
if (!n)
|
||||
break;
|
||||
len -= strlen(xbc_node_get_data(n)) + 1;
|
||||
depth--;
|
||||
m = xbc_node_get_next(n);
|
||||
}
|
||||
n = m;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_destroy_all() - Clean up all parsed bootconfig
|
||||
*
|
||||
* This clears all data structures of parsed bootconfig on memory.
|
||||
* If you need to reuse xbc_init() with new boot config, you can
|
||||
* use this.
|
||||
*/
|
||||
void __init xbc_destroy_all(void)
|
||||
{
|
||||
xbc_data = NULL;
|
||||
xbc_data_size = 0;
|
||||
xbc_node_num = 0;
|
||||
memset(xbc_nodes, 0, sizeof(xbc_nodes));
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_init() - Parse given XBC file and build XBC internal tree
|
||||
* @buf: boot config text
|
||||
*
|
||||
* This parses the boot config text in @buf. @buf must be a
|
||||
* null terminated string and smaller than XBC_DATA_MAX.
|
||||
* Return the number of stored nodes (>0) if succeeded, or -errno
|
||||
* if there is any error.
|
||||
*/
|
||||
int __init xbc_init(char *buf)
|
||||
{
|
||||
char *p, *q;
|
||||
int ret, c;
|
||||
|
||||
if (xbc_data) {
|
||||
pr_err("Error: bootconfig is already initialized.\n");
|
||||
return -EBUSY;
|
||||
}
|
||||
|
||||
ret = strlen(buf);
|
||||
if (ret > XBC_DATA_MAX - 1 || ret == 0) {
|
||||
pr_err("Error: Config data is %s.\n",
|
||||
ret ? "too big" : "empty");
|
||||
return -ERANGE;
|
||||
}
|
||||
|
||||
xbc_data = buf;
|
||||
xbc_data_size = ret + 1;
|
||||
last_parent = NULL;
|
||||
|
||||
p = buf;
|
||||
do {
|
||||
q = strpbrk(p, "{}=;\n#");
|
||||
if (!q) {
|
||||
p = skip_spaces(p);
|
||||
if (*p != '\0')
|
||||
ret = xbc_parse_error("No delimiter", p);
|
||||
break;
|
||||
}
|
||||
|
||||
c = *q;
|
||||
*q++ = '\0';
|
||||
switch (c) {
|
||||
case '=':
|
||||
ret = xbc_parse_kv(&p, q);
|
||||
break;
|
||||
case '{':
|
||||
ret = xbc_open_brace(&p, q);
|
||||
break;
|
||||
case '#':
|
||||
q = skip_comment(q);
|
||||
/* fall through */
|
||||
case ';':
|
||||
case '\n':
|
||||
ret = xbc_parse_key(&p, q);
|
||||
break;
|
||||
case '}':
|
||||
ret = xbc_close_brace(&p, q);
|
||||
break;
|
||||
}
|
||||
} while (!ret);
|
||||
|
||||
if (!ret)
|
||||
ret = xbc_verify_tree();
|
||||
|
||||
if (ret < 0)
|
||||
xbc_destroy_all();
|
||||
else
|
||||
ret = xbc_node_num;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* xbc_debug_dump() - Dump current XBC node list
|
||||
*
|
||||
* Dump the current XBC node list on printk buffer for debug.
|
||||
*/
|
||||
void __init xbc_debug_dump(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < xbc_node_num; i++) {
|
||||
pr_debug("[%d] %s (%s) .next=%d, .child=%d .parent=%d\n", i,
|
||||
xbc_node_get_data(xbc_nodes + i),
|
||||
xbc_node_is_value(xbc_nodes + i) ? "value" : "key",
|
||||
xbc_nodes[i].next, xbc_nodes[i].child,
|
||||
xbc_nodes[i].parent);
|
||||
}
|
||||
}
|
|
@ -28,6 +28,7 @@ help:
|
|||
@echo ' pci - PCI tools'
|
||||
@echo ' perf - Linux performance measurement and analysis tool'
|
||||
@echo ' selftests - various kernel selftests'
|
||||
@echo ' bootconfig - boot config tool'
|
||||
@echo ' spi - spi tools'
|
||||
@echo ' tmon - thermal monitoring and tuning tool'
|
||||
@echo ' turbostat - Intel CPU idle stats and freq reporting tool'
|
||||
|
@ -63,7 +64,7 @@ acpi: FORCE
|
|||
cpupower: FORCE
|
||||
$(call descend,power/$@)
|
||||
|
||||
cgroup firewire hv guest spi usb virtio vm bpf iio gpio objtool leds wmi pci firmware debugging: FORCE
|
||||
cgroup firewire hv guest bootconfig spi usb virtio vm bpf iio gpio objtool leds wmi pci firmware debugging: FORCE
|
||||
$(call descend,$@)
|
||||
|
||||
liblockdep: FORCE
|
||||
|
@ -96,7 +97,7 @@ kvm_stat: FORCE
|
|||
$(call descend,kvm/$@)
|
||||
|
||||
all: acpi cgroup cpupower gpio hv firewire liblockdep \
|
||||
perf selftests spi turbostat usb \
|
||||
perf selftests bootconfig spi turbostat usb \
|
||||
virtio vm bpf x86_energy_perf_policy \
|
||||
tmon freefall iio objtool kvm_stat wmi \
|
||||
pci debugging
|
||||
|
@ -107,7 +108,7 @@ acpi_install:
|
|||
cpupower_install:
|
||||
$(call descend,power/$(@:_install=),install)
|
||||
|
||||
cgroup_install firewire_install gpio_install hv_install iio_install perf_install spi_install usb_install virtio_install vm_install bpf_install objtool_install wmi_install pci_install debugging_install:
|
||||
cgroup_install firewire_install gpio_install hv_install iio_install perf_install bootconfig_install spi_install usb_install virtio_install vm_install bpf_install objtool_install wmi_install pci_install debugging_install:
|
||||
$(call descend,$(@:_install=),install)
|
||||
|
||||
liblockdep_install:
|
||||
|
@ -141,7 +142,7 @@ acpi_clean:
|
|||
cpupower_clean:
|
||||
$(call descend,power/cpupower,clean)
|
||||
|
||||
cgroup_clean hv_clean firewire_clean spi_clean usb_clean virtio_clean vm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean pci_clean firmware_clean debugging_clean:
|
||||
cgroup_clean hv_clean firewire_clean bootconfig_clean spi_clean usb_clean virtio_clean vm_clean wmi_clean bpf_clean iio_clean gpio_clean objtool_clean leds_clean pci_clean firmware_clean debugging_clean:
|
||||
$(call descend,$(@:_clean=),clean)
|
||||
|
||||
liblockdep_clean:
|
||||
|
@ -176,7 +177,7 @@ build_clean:
|
|||
$(call descend,build,clean)
|
||||
|
||||
clean: acpi_clean cgroup_clean cpupower_clean hv_clean firewire_clean \
|
||||
perf_clean selftests_clean turbostat_clean spi_clean usb_clean virtio_clean \
|
||||
perf_clean selftests_clean turbostat_clean bootconfig_clean spi_clean usb_clean virtio_clean \
|
||||
vm_clean bpf_clean iio_clean x86_energy_perf_policy_clean tmon_clean \
|
||||
freefall_clean build_clean libbpf_clean libsubcmd_clean liblockdep_clean \
|
||||
gpio_clean objtool_clean leds_clean wmi_clean pci_clean firmware_clean debugging_clean \
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
bootconfig
|
|
@ -0,0 +1,23 @@
|
|||
# SPDX-License-Identifier: GPL-2.0
|
||||
# Makefile for bootconfig command
|
||||
|
||||
bindir ?= /usr/bin
|
||||
|
||||
HEADER = include/linux/bootconfig.h
|
||||
CFLAGS = -Wall -g -I./include
|
||||
|
||||
PROGS = bootconfig
|
||||
|
||||
all: $(PROGS)
|
||||
|
||||
bootconfig: ../../lib/bootconfig.c main.c $(HEADER)
|
||||
$(CC) $(filter %.c,$^) $(CFLAGS) -o $@
|
||||
|
||||
install: $(PROGS)
|
||||
install bootconfig $(DESTDIR)$(bindir)
|
||||
|
||||
test: bootconfig
|
||||
./test-bootconfig.sh
|
||||
|
||||
clean:
|
||||
$(RM) -f *.o bootconfig
|
|
@ -0,0 +1,7 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _BOOTCONFIG_LINUX_BOOTCONFIG_H
|
||||
#define _BOOTCONFIG_LINUX_BOOTCONFIG_H
|
||||
|
||||
#include "../../../../include/linux/bootconfig.h"
|
||||
|
||||
#endif
|
|
@ -0,0 +1,12 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _SKC_LINUX_BUG_H
|
||||
#define _SKC_LINUX_BUG_H
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define WARN_ON(cond) \
|
||||
((cond) ? printf("Internal warning(%s:%d, %s): %s\n", \
|
||||
__FILE__, __LINE__, __func__, #cond) : 0)
|
||||
|
||||
#endif
|
|
@ -0,0 +1,7 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _SKC_LINUX_CTYPE_H
|
||||
#define _SKC_LINUX_CTYPE_H
|
||||
|
||||
#include <ctype.h>
|
||||
|
||||
#endif
|
|
@ -0,0 +1,7 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _SKC_LINUX_ERRNO_H
|
||||
#define _SKC_LINUX_ERRNO_H
|
||||
|
||||
#include <asm/errno.h>
|
||||
|
||||
#endif
|
|
@ -0,0 +1,18 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _SKC_LINUX_KERNEL_H
|
||||
#define _SKC_LINUX_KERNEL_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
#include <linux/printk.h>
|
||||
|
||||
typedef unsigned short u16;
|
||||
typedef unsigned int u32;
|
||||
|
||||
#define unlikely(cond) (cond)
|
||||
|
||||
#define __init
|
||||
#define __initdata
|
||||
|
||||
#endif
|
|
@ -0,0 +1,17 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _SKC_LINUX_PRINTK_H
|
||||
#define _SKC_LINUX_PRINTK_H
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
/* controllable printf */
|
||||
extern int pr_output;
|
||||
#define printk(fmt, ...) \
|
||||
(pr_output ? printf(fmt, __VA_ARGS__) : 0)
|
||||
|
||||
#define pr_err printk
|
||||
#define pr_warn printk
|
||||
#define pr_info printk
|
||||
#define pr_debug printk
|
||||
|
||||
#endif
|
|
@ -0,0 +1,32 @@
|
|||
/* SPDX-License-Identifier: GPL-2.0 */
|
||||
#ifndef _SKC_LINUX_STRING_H
|
||||
#define _SKC_LINUX_STRING_H
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* Copied from lib/string.c */
|
||||
static inline char *skip_spaces(const char *str)
|
||||
{
|
||||
while (isspace(*str))
|
||||
++str;
|
||||
return (char *)str;
|
||||
}
|
||||
|
||||
static inline char *strim(char *s)
|
||||
{
|
||||
size_t size;
|
||||
char *end;
|
||||
|
||||
size = strlen(s);
|
||||
if (!size)
|
||||
return s;
|
||||
|
||||
end = s + size - 1;
|
||||
while (end >= s && isspace(*end))
|
||||
end--;
|
||||
*(end + 1) = '\0';
|
||||
|
||||
return skip_spaces(s);
|
||||
}
|
||||
|
||||
#endif
|
|
@ -0,0 +1,354 @@
|
|||
// SPDX-License-Identifier: GPL-2.0
|
||||
/*
|
||||
* Boot config tool for initrd image
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
|
||||
#include <linux/kernel.h>
|
||||
#include <linux/bootconfig.h>
|
||||
|
||||
int pr_output = 1;
|
||||
|
||||
static int xbc_show_array(struct xbc_node *node)
|
||||
{
|
||||
const char *val;
|
||||
int i = 0;
|
||||
|
||||
xbc_array_for_each_value(node, val) {
|
||||
printf("\"%s\"%s", val, node->next ? ", " : ";\n");
|
||||
i++;
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
static void xbc_show_compact_tree(void)
|
||||
{
|
||||
struct xbc_node *node, *cnode;
|
||||
int depth = 0, i;
|
||||
|
||||
node = xbc_root_node();
|
||||
while (node && xbc_node_is_key(node)) {
|
||||
for (i = 0; i < depth; i++)
|
||||
printf("\t");
|
||||
cnode = xbc_node_get_child(node);
|
||||
while (cnode && xbc_node_is_key(cnode) && !cnode->next) {
|
||||
printf("%s.", xbc_node_get_data(node));
|
||||
node = cnode;
|
||||
cnode = xbc_node_get_child(node);
|
||||
}
|
||||
if (cnode && xbc_node_is_key(cnode)) {
|
||||
printf("%s {\n", xbc_node_get_data(node));
|
||||
depth++;
|
||||
node = cnode;
|
||||
continue;
|
||||
} else if (cnode && xbc_node_is_value(cnode)) {
|
||||
printf("%s = ", xbc_node_get_data(node));
|
||||
if (cnode->next)
|
||||
xbc_show_array(cnode);
|
||||
else
|
||||
printf("\"%s\";\n", xbc_node_get_data(cnode));
|
||||
} else {
|
||||
printf("%s;\n", xbc_node_get_data(node));
|
||||
}
|
||||
|
||||
if (node->next) {
|
||||
node = xbc_node_get_next(node);
|
||||
continue;
|
||||
}
|
||||
while (!node->next) {
|
||||
node = xbc_node_get_parent(node);
|
||||
if (!node)
|
||||
return;
|
||||
if (!xbc_node_get_child(node)->next)
|
||||
continue;
|
||||
depth--;
|
||||
for (i = 0; i < depth; i++)
|
||||
printf("\t");
|
||||
printf("}\n");
|
||||
}
|
||||
node = xbc_node_get_next(node);
|
||||
}
|
||||
}
|
||||
|
||||
/* Simple real checksum */
|
||||
int checksum(unsigned char *buf, int len)
|
||||
{
|
||||
int i, sum = 0;
|
||||
|
||||
for (i = 0; i < len; i++)
|
||||
sum += buf[i];
|
||||
|
||||
return sum;
|
||||
}
|
||||
|
||||
#define PAGE_SIZE 4096
|
||||
|
||||
int load_xbc_fd(int fd, char **buf, int size)
|
||||
{
|
||||
int ret;
|
||||
|
||||
*buf = malloc(size + 1);
|
||||
if (!*buf)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = read(fd, *buf, size);
|
||||
if (ret < 0)
|
||||
return -errno;
|
||||
(*buf)[size] = '\0';
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Return the read size or -errno */
|
||||
int load_xbc_file(const char *path, char **buf)
|
||||
{
|
||||
struct stat stat;
|
||||
int fd, ret;
|
||||
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0)
|
||||
return -errno;
|
||||
ret = fstat(fd, &stat);
|
||||
if (ret < 0)
|
||||
return -errno;
|
||||
|
||||
ret = load_xbc_fd(fd, buf, stat.st_size);
|
||||
|
||||
close(fd);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int load_xbc_from_initrd(int fd, char **buf)
|
||||
{
|
||||
struct stat stat;
|
||||
int ret;
|
||||
u32 size = 0, csum = 0, rcsum;
|
||||
|
||||
ret = fstat(fd, &stat);
|
||||
if (ret < 0)
|
||||
return -errno;
|
||||
|
||||
if (stat.st_size < 8)
|
||||
return 0;
|
||||
|
||||
if (lseek(fd, -8, SEEK_END) < 0) {
|
||||
printf("Failed to lseek: %d\n", -errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
if (read(fd, &size, sizeof(u32)) < 0)
|
||||
return -errno;
|
||||
|
||||
if (read(fd, &csum, sizeof(u32)) < 0)
|
||||
return -errno;
|
||||
|
||||
/* Wrong size, maybe no boot config here */
|
||||
if (stat.st_size < size + 8)
|
||||
return 0;
|
||||
|
||||
if (lseek(fd, stat.st_size - 8 - size, SEEK_SET) < 0) {
|
||||
printf("Failed to lseek: %d\n", -errno);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
ret = load_xbc_fd(fd, buf, size);
|
||||
if (ret < 0)
|
||||
return ret;
|
||||
|
||||
/* Wrong Checksum, maybe no boot config here */
|
||||
rcsum = checksum((unsigned char *)*buf, size);
|
||||
if (csum != rcsum) {
|
||||
printf("checksum error: %d != %d\n", csum, rcsum);
|
||||
return 0;
|
||||
}
|
||||
|
||||
ret = xbc_init(*buf);
|
||||
/* Wrong data, maybe no boot config here */
|
||||
if (ret < 0)
|
||||
return 0;
|
||||
|
||||
return size;
|
||||
}
|
||||
|
||||
int show_xbc(const char *path)
|
||||
{
|
||||
int ret, fd;
|
||||
char *buf = NULL;
|
||||
|
||||
fd = open(path, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
printf("Failed to open initrd %s: %d\n", path, fd);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
ret = load_xbc_from_initrd(fd, &buf);
|
||||
if (ret < 0)
|
||||
printf("Failed to load a boot config from initrd: %d\n", ret);
|
||||
else
|
||||
xbc_show_compact_tree();
|
||||
|
||||
close(fd);
|
||||
free(buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int delete_xbc(const char *path)
|
||||
{
|
||||
struct stat stat;
|
||||
int ret = 0, fd, size;
|
||||
char *buf = NULL;
|
||||
|
||||
fd = open(path, O_RDWR);
|
||||
if (fd < 0) {
|
||||
printf("Failed to open initrd %s: %d\n", path, fd);
|
||||
return -errno;
|
||||
}
|
||||
|
||||
/*
|
||||
* Suppress error messages in xbc_init() because it can be just a
|
||||
* data which concidentally matches the size and checksum footer.
|
||||
*/
|
||||
pr_output = 0;
|
||||
size = load_xbc_from_initrd(fd, &buf);
|
||||
pr_output = 1;
|
||||
if (size < 0) {
|
||||
ret = size;
|
||||
printf("Failed to load a boot config from initrd: %d\n", ret);
|
||||
} else if (size > 0) {
|
||||
ret = fstat(fd, &stat);
|
||||
if (!ret)
|
||||
ret = ftruncate(fd, stat.st_size - size - 8);
|
||||
if (ret)
|
||||
ret = -errno;
|
||||
} /* Ignore if there is no boot config in initrd */
|
||||
|
||||
close(fd);
|
||||
free(buf);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
int apply_xbc(const char *path, const char *xbc_path)
|
||||
{
|
||||
u32 size, csum;
|
||||
char *buf, *data;
|
||||
int ret, fd;
|
||||
|
||||
ret = load_xbc_file(xbc_path, &buf);
|
||||
if (ret < 0) {
|
||||
printf("Failed to load %s : %d\n", xbc_path, ret);
|
||||
return ret;
|
||||
}
|
||||
size = strlen(buf) + 1;
|
||||
csum = checksum((unsigned char *)buf, size);
|
||||
|
||||
/* Prepare xbc_path data */
|
||||
data = malloc(size + 8);
|
||||
if (!data)
|
||||
return -ENOMEM;
|
||||
strcpy(data, buf);
|
||||
*(u32 *)(data + size) = size;
|
||||
*(u32 *)(data + size + 4) = csum;
|
||||
|
||||
/* Check the data format */
|
||||
ret = xbc_init(buf);
|
||||
if (ret < 0) {
|
||||
printf("Failed to parse %s: %d\n", xbc_path, ret);
|
||||
free(data);
|
||||
free(buf);
|
||||
return ret;
|
||||
}
|
||||
printf("Apply %s to %s\n", xbc_path, path);
|
||||
printf("\tNumber of nodes: %d\n", ret);
|
||||
printf("\tSize: %u bytes\n", (unsigned int)size);
|
||||
printf("\tChecksum: %d\n", (unsigned int)csum);
|
||||
|
||||
/* TODO: Check the options by schema */
|
||||
xbc_destroy_all();
|
||||
free(buf);
|
||||
|
||||
/* Remove old boot config if exists */
|
||||
ret = delete_xbc(path);
|
||||
if (ret < 0) {
|
||||
printf("Failed to delete previous boot config: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
/* Apply new one */
|
||||
fd = open(path, O_RDWR | O_APPEND);
|
||||
if (fd < 0) {
|
||||
printf("Failed to open %s: %d\n", path, fd);
|
||||
return fd;
|
||||
}
|
||||
/* TODO: Ensure the @path is initramfs/initrd image */
|
||||
ret = write(fd, data, size + 8);
|
||||
if (ret < 0) {
|
||||
printf("Failed to apply a boot config: %d\n", ret);
|
||||
return ret;
|
||||
}
|
||||
close(fd);
|
||||
free(data);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int usage(void)
|
||||
{
|
||||
printf("Usage: bootconfig [OPTIONS] <INITRD>\n"
|
||||
" Apply, delete or show boot config to initrd.\n"
|
||||
" Options:\n"
|
||||
" -a <config>: Apply boot config to initrd\n"
|
||||
" -d : Delete boot config file from initrd\n\n"
|
||||
" If no option is given, show current applied boot config.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
char *path = NULL;
|
||||
char *apply = NULL;
|
||||
bool delete = false;
|
||||
int opt;
|
||||
|
||||
while ((opt = getopt(argc, argv, "hda:")) != -1) {
|
||||
switch (opt) {
|
||||
case 'd':
|
||||
delete = true;
|
||||
break;
|
||||
case 'a':
|
||||
apply = optarg;
|
||||
break;
|
||||
case 'h':
|
||||
default:
|
||||
return usage();
|
||||
}
|
||||
}
|
||||
|
||||
if (apply && delete) {
|
||||
printf("Error: You can not specify both -a and -d at once.\n");
|
||||
return usage();
|
||||
}
|
||||
|
||||
if (optind >= argc) {
|
||||
printf("Error: No initrd is specified.\n");
|
||||
return usage();
|
||||
}
|
||||
|
||||
path = argv[optind];
|
||||
|
||||
if (apply)
|
||||
return apply_xbc(path, apply);
|
||||
else if (delete)
|
||||
return delete_xbc(path);
|
||||
|
||||
return show_xbc(path);
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
key = # comment
|
||||
"value1", # comment1
|
||||
"value2" # comment2
|
||||
,
|
||||
"value3"
|
|
@ -0,0 +1,2 @@
|
|||
# Array must be comma separated.
|
||||
key = "value1" "value2"
|
|
@ -0,0 +1,4 @@
|
|||
# do not start keyword with .
|
||||
key {
|
||||
.word = 1
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
# Wrong boot config: comment only
|
|
@ -0,0 +1,2 @@
|
|||
# key word can not contain ","
|
||||
key,word
|
|
@ -0,0 +1 @@
|
|||
key_word_is_too_long01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345
|
|
@ -0,0 +1 @@
|
|||
key1.is2.too3.long4.5.6.7.8.9.10.11.12.13.14.15.16.17
|
|
@ -0,0 +1,2 @@
|
|||
# No keyword
|
||||
{}
|
|
@ -0,0 +1,2 @@
|
|||
# Non printable
|
||||
key = ""
|
|
@ -0,0 +1,2 @@
|
|||
# No space between words
|
||||
key . word
|
|
@ -0,0 +1,5 @@
|
|||
# brace is not closing
|
||||
tree {
|
||||
node {
|
||||
value = 1
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
# Quotes error
|
||||
value = "data
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
key1 = "A\B\C"
|
||||
key2 = '\'\''
|
||||
key3 = "\\"
|
|
@ -0,0 +1,4 @@
|
|||
key = # comment
|
||||
"value1", # comment1
|
||||
"value2" , # comment2
|
||||
"value3"
|
|
@ -0,0 +1 @@
|
|||
key = "value" # comment
|
|
@ -0,0 +1,2 @@
|
|||
key = "
|
||||
!#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
|
|
@ -0,0 +1,11 @@
|
|||
# A good simple bootconfig
|
||||
|
||||
key.word1 = 1
|
||||
key.word2=2
|
||||
key.word3 = 3;
|
||||
|
||||
key {
|
||||
word4 = 4 }
|
||||
|
||||
key { word5 = 5; word6 = 6 }
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
# single key style
|
||||
key = 1
|
||||
key2 = 2
|
||||
key3 = "alpha", "beta"
|
|
@ -0,0 +1 @@
|
|||
key = "value"
|
|
@ -0,0 +1,12 @@
|
|||
key {
|
||||
word {
|
||||
tree {
|
||||
value = "0"}
|
||||
}
|
||||
word2 {
|
||||
tree {
|
||||
value = 1,2 }
|
||||
}
|
||||
}
|
||||
other.tree {
|
||||
value = 2; value2 = 3;}
|
|
@ -0,0 +1,105 @@
|
|||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0-only
|
||||
|
||||
echo "Boot config test script"
|
||||
|
||||
BOOTCONF=./bootconfig
|
||||
INITRD=`mktemp initrd-XXXX`
|
||||
TEMPCONF=`mktemp temp-XXXX.bconf`
|
||||
NG=0
|
||||
|
||||
cleanup() {
|
||||
rm -f $INITRD $TEMPCONF
|
||||
exit $NG
|
||||
}
|
||||
|
||||
trap cleanup EXIT TERM
|
||||
|
||||
NO=1
|
||||
|
||||
xpass() { # pass test command
|
||||
echo "test case $NO ($3)... "
|
||||
if ! ($@ && echo "\t\t[OK]"); then
|
||||
echo "\t\t[NG]"; NG=$((NG + 1))
|
||||
fi
|
||||
NO=$((NO + 1))
|
||||
}
|
||||
|
||||
xfail() { # fail test command
|
||||
echo "test case $NO ($3)... "
|
||||
if ! (! $@ && echo "\t\t[OK]"); then
|
||||
echo "\t\t[NG]"; NG=$((NG + 1))
|
||||
fi
|
||||
NO=$((NO + 1))
|
||||
}
|
||||
|
||||
echo "Basic command test"
|
||||
xpass $BOOTCONF $INITRD
|
||||
|
||||
echo "Delete command should success without bootconfig"
|
||||
xpass $BOOTCONF -d $INITRD
|
||||
|
||||
dd if=/dev/zero of=$INITRD bs=4096 count=1
|
||||
echo "key = value;" > $TEMPCONF
|
||||
bconf_size=$(stat -c %s $TEMPCONF)
|
||||
initrd_size=$(stat -c %s $INITRD)
|
||||
|
||||
echo "Apply command test"
|
||||
xpass $BOOTCONF -a $TEMPCONF $INITRD
|
||||
new_size=$(stat -c %s $INITRD)
|
||||
|
||||
echo "File size check"
|
||||
xpass test $new_size -eq $(expr $bconf_size + $initrd_size + 9)
|
||||
|
||||
echo "Apply command repeat test"
|
||||
xpass $BOOTCONF -a $TEMPCONF $INITRD
|
||||
|
||||
echo "File size check"
|
||||
xpass test $new_size -eq $(stat -c %s $INITRD)
|
||||
|
||||
echo "Delete command check"
|
||||
xpass $BOOTCONF -d $INITRD
|
||||
|
||||
echo "File size check"
|
||||
new_size=$(stat -c %s $INITRD)
|
||||
xpass test $new_size -eq $initrd_size
|
||||
|
||||
echo "Max node number check"
|
||||
|
||||
echo -n > $TEMPCONF
|
||||
for i in `seq 1 1024` ; do
|
||||
echo "node$i" >> $TEMPCONF
|
||||
done
|
||||
xpass $BOOTCONF -a $TEMPCONF $INITRD
|
||||
|
||||
echo "badnode" >> $TEMPCONF
|
||||
xfail $BOOTCONF -a $TEMPCONF $INITRD
|
||||
|
||||
echo "Max filesize check"
|
||||
|
||||
# Max size is 32767 (including terminal byte)
|
||||
echo -n "data = \"" > $TEMPCONF
|
||||
dd if=/dev/urandom bs=768 count=32 | base64 -w0 >> $TEMPCONF
|
||||
echo "\"" >> $TEMPCONF
|
||||
xfail $BOOTCONF -a $TEMPCONF $INITRD
|
||||
|
||||
truncate -s 32764 $TEMPCONF
|
||||
echo "\"" >> $TEMPCONF # add 2 bytes + terminal ('\"\n\0')
|
||||
xpass $BOOTCONF -a $TEMPCONF $INITRD
|
||||
|
||||
echo "=== expected failure cases ==="
|
||||
for i in samples/bad-* ; do
|
||||
xfail $BOOTCONF -a $i $INITRD
|
||||
done
|
||||
|
||||
echo "=== expected success cases ==="
|
||||
for i in samples/good-* ; do
|
||||
xpass $BOOTCONF -a $i $INITRD
|
||||
done
|
||||
|
||||
echo
|
||||
if [ $NG -eq 0 ]; then
|
||||
echo "All tests passed"
|
||||
else
|
||||
echo "$NG tests failed"
|
||||
fi
|
|
@ -0,0 +1,32 @@
|
|||
#!/bin/sh
|
||||
# SPDX-License-Identifier: GPL-2.0
|
||||
# description: event trigger - test histogram parser errors
|
||||
|
||||
if [ ! -f set_event -o ! -d events/kmem ]; then
|
||||
echo "event tracing is not supported"
|
||||
exit_unsupported
|
||||
fi
|
||||
|
||||
if [ ! -f events/kmem/kmalloc/trigger ]; then
|
||||
echo "event trigger is not supported"
|
||||
exit_unsupported
|
||||
fi
|
||||
|
||||
if [ ! -f events/kmem/kmalloc/hist ]; then
|
||||
echo "hist trigger is not supported"
|
||||
exit_unsupported
|
||||
fi
|
||||
|
||||
[ -f error_log ] || exit_unsupported
|
||||
|
||||
check_error() { # command-with-error-pos-by-^
|
||||
ftrace_errlog_check 'hist:kmem:kmalloc' "$1" 'events/kmem/kmalloc/trigger'
|
||||
}
|
||||
|
||||
check_error 'hist:keys=common_pid:vals=bytes_req:sort=common_pid,^junk' # INVALID_SORT_FIELD
|
||||
check_error 'hist:keys=common_pid:vals=bytes_req:^sort=' # EMPTY_ASSIGNMENT
|
||||
check_error 'hist:keys=common_pid:vals=bytes_req:^sort=common_pid,' # EMPTY_SORT_FIELD
|
||||
check_error 'hist:keys=common_pid:vals=bytes_req:sort=common_pid.^junk' # INVALID_SORT_MODIFIER
|
||||
check_error 'hist:keys=common_pid:vals=bytes_req,bytes_alloc:^sort=common_pid,bytes_req,bytes_alloc' # TOO_MANY_SORT_FIELDS
|
||||
|
||||
exit 0
|
Loading…
Reference in New Issue