aboutsummaryrefslogtreecommitdiff
path: root/tools
diff options
context:
space:
mode:
Diffstat (limited to 'tools')
-rw-r--r--tools/Makefile119
-rw-r--r--tools/cgroup/.gitignore1
-rw-r--r--tools/cgroup/Makefile11
-rw-r--r--tools/cgroup/cgroup_event_listener.c82
-rw-r--r--tools/firewire/Makefile19
-rw-r--r--tools/firewire/decode-fcp.c213
-rw-r--r--tools/firewire/list.h62
-rw-r--r--tools/firewire/nosy-dump.c1035
-rw-r--r--tools/firewire/nosy-dump.h173
-rw-r--r--tools/hv/Makefile13
-rw-r--r--tools/hv/hv_fcopy_daemon.c197
-rwxr-xr-xtools/hv/hv_get_dhcp_info.sh28
-rwxr-xr-xtools/hv/hv_get_dns_info.sh13
-rw-r--r--tools/hv/hv_kvp_daemon.c1742
-rwxr-xr-xtools/hv/hv_set_ifconfig.sh64
-rw-r--r--tools/hv/hv_vss_daemon.c267
-rw-r--r--tools/include/asm/bug.h25
-rw-r--r--tools/include/linux/compiler.h40
-rw-r--r--tools/include/linux/export.h10
-rw-r--r--tools/include/linux/hash.h5
-rw-r--r--tools/include/linux/types.h75
-rw-r--r--tools/include/tools/be_byteshift.h70
-rw-r--r--tools/include/tools/le_byteshift.h70
-rw-r--r--tools/lguest/.gitignore1
-rw-r--r--tools/lguest/Makefile7
-rw-r--r--tools/lguest/extract58
-rw-r--r--tools/lguest/lguest.c2072
-rw-r--r--tools/lguest/lguest.txt125
-rw-r--r--tools/lib/api/Makefile44
-rw-r--r--tools/lib/api/fs/debugfs.c100
-rw-r--r--tools/lib/api/fs/debugfs.h29
-rw-r--r--tools/lib/api/fs/fs.c165
-rw-r--r--tools/lib/api/fs/fs.h14
-rw-r--r--tools/lib/lockdep/Makefile243
-rw-r--r--tools/lib/lockdep/common.c33
-rw-r--r--tools/lib/lockdep/include/liblockdep/common.h50
-rw-r--r--tools/lib/lockdep/include/liblockdep/mutex.h70
-rw-r--r--tools/lib/lockdep/include/liblockdep/rwlock.h86
-rwxr-xr-xtools/lib/lockdep/lockdep3
-rw-r--r--tools/lib/lockdep/lockdep.c2
-rw-r--r--tools/lib/lockdep/lockdep_internals.h1
-rw-r--r--tools/lib/lockdep/lockdep_states.h1
-rw-r--r--tools/lib/lockdep/preload.c445
-rw-r--r--tools/lib/lockdep/rbtree.c1
-rwxr-xr-xtools/lib/lockdep/run_tests.sh27
-rw-r--r--tools/lib/lockdep/tests/AA.c13
-rw-r--r--tools/lib/lockdep/tests/ABBA.c13
-rw-r--r--tools/lib/lockdep/tests/ABBCCA.c15
-rw-r--r--tools/lib/lockdep/tests/ABBCCDDA.c17
-rw-r--r--tools/lib/lockdep/tests/ABCABC.c15
-rw-r--r--tools/lib/lockdep/tests/ABCDBCDA.c17
-rw-r--r--tools/lib/lockdep/tests/ABCDBDDA.c17
-rw-r--r--tools/lib/lockdep/tests/WW.c13
-rw-r--r--tools/lib/lockdep/tests/common.h12
-rw-r--r--tools/lib/lockdep/tests/unlock_balance.c12
-rw-r--r--tools/lib/lockdep/uinclude/asm/hash.h6
-rw-r--r--tools/lib/lockdep/uinclude/asm/hweight.h3
-rw-r--r--tools/lib/lockdep/uinclude/asm/sections.h3
-rw-r--r--tools/lib/lockdep/uinclude/linux/bitops.h3
-rw-r--r--tools/lib/lockdep/uinclude/linux/compiler.h7
-rw-r--r--tools/lib/lockdep/uinclude/linux/debug_locks.h12
-rw-r--r--tools/lib/lockdep/uinclude/linux/delay.h3
-rw-r--r--tools/lib/lockdep/uinclude/linux/ftrace.h3
-rw-r--r--tools/lib/lockdep/uinclude/linux/gfp.h3
-rw-r--r--tools/lib/lockdep/uinclude/linux/hardirq.h11
-rw-r--r--tools/lib/lockdep/uinclude/linux/hash.h1
-rw-r--r--tools/lib/lockdep/uinclude/linux/interrupt.h3
-rw-r--r--tools/lib/lockdep/uinclude/linux/irqflags.h38
-rw-r--r--tools/lib/lockdep/uinclude/linux/kallsyms.h32
-rw-r--r--tools/lib/lockdep/uinclude/linux/kern_levels.h25
-rw-r--r--tools/lib/lockdep/uinclude/linux/kernel.h44
-rw-r--r--tools/lib/lockdep/uinclude/linux/kmemcheck.h8
-rw-r--r--tools/lib/lockdep/uinclude/linux/linkage.h3
-rw-r--r--tools/lib/lockdep/uinclude/linux/list.h1
-rw-r--r--tools/lib/lockdep/uinclude/linux/lockdep.h58
-rw-r--r--tools/lib/lockdep/uinclude/linux/module.h6
-rw-r--r--tools/lib/lockdep/uinclude/linux/mutex.h3
-rw-r--r--tools/lib/lockdep/uinclude/linux/poison.h1
-rw-r--r--tools/lib/lockdep/uinclude/linux/prefetch.h6
-rw-r--r--tools/lib/lockdep/uinclude/linux/proc_fs.h3
-rw-r--r--tools/lib/lockdep/uinclude/linux/rbtree.h1
-rw-r--r--tools/lib/lockdep/uinclude/linux/rbtree_augmented.h2
-rw-r--r--tools/lib/lockdep/uinclude/linux/rcu.h21
-rw-r--r--tools/lib/lockdep/uinclude/linux/seq_file.h3
-rw-r--r--tools/lib/lockdep/uinclude/linux/spinlock.h25
-rw-r--r--tools/lib/lockdep/uinclude/linux/stacktrace.h32
-rw-r--r--tools/lib/lockdep/uinclude/linux/stringify.h7
-rw-r--r--tools/lib/lockdep/uinclude/trace/events/lock.h3
-rw-r--r--tools/lib/symbol/kallsyms.c58
-rw-r--r--tools/lib/symbol/kallsyms.h24
-rw-r--r--tools/lib/traceevent/.gitignore1
-rw-r--r--tools/lib/traceevent/Makefile340
-rw-r--r--tools/lib/traceevent/event-parse.c6025
-rw-r--r--tools/lib/traceevent/event-parse.h944
-rw-r--r--tools/lib/traceevent/event-plugin.c416
-rw-r--r--tools/lib/traceevent/event-utils.h81
-rw-r--r--tools/lib/traceevent/kbuffer-parse.c732
-rw-r--r--tools/lib/traceevent/kbuffer.h67
-rw-r--r--tools/lib/traceevent/parse-filter.c2432
-rw-r--r--tools/lib/traceevent/parse-utils.c85
-rw-r--r--tools/lib/traceevent/plugin_cfg80211.c30
-rw-r--r--tools/lib/traceevent/plugin_function.c194
-rw-r--r--tools/lib/traceevent/plugin_hrtimer.c88
-rw-r--r--tools/lib/traceevent/plugin_jbd2.c77
-rw-r--r--tools/lib/traceevent/plugin_kmem.c94
-rw-r--r--tools/lib/traceevent/plugin_kvm.c465
-rw-r--r--tools/lib/traceevent/plugin_mac80211.c102
-rw-r--r--tools/lib/traceevent/plugin_sched_switch.c160
-rw-r--r--tools/lib/traceevent/plugin_scsi.c429
-rw-r--r--tools/lib/traceevent/plugin_xen.c136
-rw-r--r--tools/lib/traceevent/trace-seq.c249
-rw-r--r--tools/net/Makefile34
-rw-r--r--tools/net/bpf_asm.c52
-rw-r--r--tools/net/bpf_dbg.c1395
-rw-r--r--tools/net/bpf_exp.l144
-rw-r--r--tools/net/bpf_exp.y771
-rw-r--r--tools/net/bpf_jit_disasm.c197
-rwxr-xr-xtools/nfsd/inject_fault.sh49
-rw-r--r--tools/perf/.gitignore26
-rw-r--r--tools/perf/CREDITS30
-rw-r--r--tools/perf/Documentation/Makefile343
-rw-r--r--tools/perf/Documentation/android.txt78
-rw-r--r--tools/perf/Documentation/asciidoc.conf91
-rw-r--r--tools/perf/Documentation/examples.txt225
-rw-r--r--tools/perf/Documentation/jit-interface.txt15
-rw-r--r--tools/perf/Documentation/manpage-1.72.xsl14
-rw-r--r--tools/perf/Documentation/manpage-base.xsl35
-rw-r--r--tools/perf/Documentation/manpage-bold-literal.xsl17
-rw-r--r--tools/perf/Documentation/manpage-normal.xsl13
-rw-r--r--tools/perf/Documentation/manpage-suppress-sp.xsl21
-rw-r--r--tools/perf/Documentation/perf-annotate.txt101
-rw-r--r--tools/perf/Documentation/perf-archive.txt22
-rw-r--r--tools/perf/Documentation/perf-bench.txt214
-rw-r--r--tools/perf/Documentation/perf-buildid-cache.txt53
-rw-r--r--tools/perf/Documentation/perf-buildid-list.txt43
-rw-r--r--tools/perf/Documentation/perf-diff.txt206
-rw-r--r--tools/perf/Documentation/perf-evlist.txt38
-rw-r--r--tools/perf/Documentation/perf-help.txt38
-rw-r--r--tools/perf/Documentation/perf-inject.txt46
-rw-r--r--tools/perf/Documentation/perf-kmem.txt47
-rw-r--r--tools/perf/Documentation/perf-kvm.txt156
-rw-r--r--tools/perf/Documentation/perf-list.txt122
-rw-r--r--tools/perf/Documentation/perf-lock.txt66
-rw-r--r--tools/perf/Documentation/perf-mem.txt52
-rw-r--r--tools/perf/Documentation/perf-probe.txt207
-rw-r--r--tools/perf/Documentation/perf-record.txt219
-rw-r--r--tools/perf/Documentation/perf-report.txt310
-rw-r--r--tools/perf/Documentation/perf-sched.txt55
-rw-r--r--tools/perf/Documentation/perf-script-perl.txt216
-rw-r--r--tools/perf/Documentation/perf-script-python.txt620
-rw-r--r--tools/perf/Documentation/perf-script.txt221
-rw-r--r--tools/perf/Documentation/perf-stat.txt165
-rw-r--r--tools/perf/Documentation/perf-test.txt32
-rw-r--r--tools/perf/Documentation/perf-timechart.txt92
-rw-r--r--tools/perf/Documentation/perf-top.txt231
-rw-r--r--tools/perf/Documentation/perf-trace.txt112
-rw-r--r--tools/perf/Documentation/perf.txt24
-rw-r--r--tools/perf/Documentation/perfconfig.example29
-rw-r--r--tools/perf/MANIFEST39
-rw-r--r--tools/perf/Makefile90
-rw-r--r--tools/perf/Makefile.perf938
-rw-r--r--tools/perf/arch/arm/Makefile14
-rw-r--r--tools/perf/arch/arm/include/perf_regs.h59
-rw-r--r--tools/perf/arch/arm/tests/dwarf-unwind.c60
-rw-r--r--tools/perf/arch/arm/tests/regs_load.S58
-rw-r--r--tools/perf/arch/arm/util/dwarf-regs.c64
-rw-r--r--tools/perf/arch/arm/util/unwind-libdw.c36
-rw-r--r--tools/perf/arch/arm/util/unwind-libunwind.c48
-rw-r--r--tools/perf/arch/arm64/Makefile7
-rw-r--r--tools/perf/arch/arm64/include/perf_regs.h88
-rw-r--r--tools/perf/arch/arm64/util/dwarf-regs.c80
-rw-r--r--tools/perf/arch/arm64/util/unwind-libunwind.c82
-rw-r--r--tools/perf/arch/common.c211
-rw-r--r--tools/perf/arch/common.h10
-rw-r--r--tools/perf/arch/powerpc/Makefile5
-rw-r--r--tools/perf/arch/powerpc/util/dwarf-regs.c88
-rw-r--r--tools/perf/arch/powerpc/util/header.c36
-rw-r--r--tools/perf/arch/s390/Makefile4
-rw-r--r--tools/perf/arch/s390/util/dwarf-regs.c22
-rw-r--r--tools/perf/arch/sh/Makefile4
-rw-r--r--tools/perf/arch/sh/util/dwarf-regs.c55
-rw-r--r--tools/perf/arch/sparc/Makefile4
-rw-r--r--tools/perf/arch/sparc/util/dwarf-regs.c43
-rw-r--r--tools/perf/arch/x86/Makefile17
-rw-r--r--tools/perf/arch/x86/include/perf_regs.h86
-rw-r--r--tools/perf/arch/x86/tests/dwarf-unwind.c60
-rw-r--r--tools/perf/arch/x86/tests/regs_load.S98
-rw-r--r--tools/perf/arch/x86/util/dwarf-regs.c75
-rw-r--r--tools/perf/arch/x86/util/header.c59
-rw-r--r--tools/perf/arch/x86/util/tsc.c59
-rw-r--r--tools/perf/arch/x86/util/tsc.h20
-rw-r--r--tools/perf/arch/x86/util/unwind-libdw.c51
-rw-r--r--tools/perf/arch/x86/util/unwind-libunwind.c111
-rw-r--r--tools/perf/bench/bench.h47
-rw-r--r--tools/perf/bench/futex-hash.c212
-rw-r--r--tools/perf/bench/futex-requeue.c211
-rw-r--r--tools/perf/bench/futex-wake.c201
-rw-r--r--tools/perf/bench/futex.h71
-rw-r--r--tools/perf/bench/mem-memcpy-arch.h12
-rw-r--r--tools/perf/bench/mem-memcpy-x86-64-asm-def.h12
-rw-r--r--tools/perf/bench/mem-memcpy-x86-64-asm.S12
-rw-r--r--tools/perf/bench/mem-memcpy.c305
-rw-r--r--tools/perf/bench/mem-memset-arch.h12
-rw-r--r--tools/perf/bench/mem-memset-x86-64-asm-def.h12
-rw-r--r--tools/perf/bench/mem-memset-x86-64-asm.S13
-rw-r--r--tools/perf/bench/mem-memset.c297
-rw-r--r--tools/perf/bench/numa.c1744
-rw-r--r--tools/perf/bench/sched-messaging.c336
-rw-r--r--tools/perf/bench/sched-pipe.c184
-rw-r--r--tools/perf/builtin-annotate.c377
-rw-r--r--tools/perf/builtin-bench.c282
-rw-r--r--tools/perf/builtin-buildid-cache.c396
-rw-r--r--tools/perf/builtin-buildid-list.c111
-rw-r--r--tools/perf/builtin-diff.c1164
-rw-r--r--tools/perf/builtin-evlist.c66
-rw-r--r--tools/perf/builtin-help.c479
-rw-r--r--tools/perf/builtin-inject.c463
-rw-r--r--tools/perf/builtin-kmem.c717
-rw-r--r--tools/perf/builtin-kvm.c1737
-rw-r--r--tools/perf/builtin-list.c77
-rw-r--r--tools/perf/builtin-lock.c1015
-rw-r--r--tools/perf/builtin-mem.c246
-rw-r--r--tools/perf/builtin-probe.c530
-rw-r--r--tools/perf/builtin-record.c960
-rw-r--r--tools/perf/builtin-report.c816
-rw-r--r--tools/perf/builtin-sched.c1777
-rw-r--r--tools/perf/builtin-script.c1826
-rw-r--r--tools/perf/builtin-stat.c1834
-rw-r--r--tools/perf/builtin-timechart.c1350
-rw-r--r--tools/perf/builtin-top.c1241
-rw-r--r--tools/perf/builtin-trace.c2397
-rw-r--r--tools/perf/builtin.h42
-rw-r--r--tools/perf/command-list.txt27
-rw-r--r--tools/perf/config/Makefile738
-rw-r--r--tools/perf/config/Makefile.arch23
-rw-r--r--tools/perf/config/feature-checks/.gitignore2
-rw-r--r--tools/perf/config/feature-checks/Makefile149
-rw-r--r--tools/perf/config/feature-checks/test-all.c116
-rw-r--r--tools/perf/config/feature-checks/test-backtrace.c13
-rw-r--r--tools/perf/config/feature-checks/test-bionic.c6
-rw-r--r--tools/perf/config/feature-checks/test-cplus-demangle.c14
-rw-r--r--tools/perf/config/feature-checks/test-dwarf.c10
-rw-r--r--tools/perf/config/feature-checks/test-fortify-source.c6
-rw-r--r--tools/perf/config/feature-checks/test-glibc.c8
-rw-r--r--tools/perf/config/feature-checks/test-gtk2-infobar.c11
-rw-r--r--tools/perf/config/feature-checks/test-gtk2.c10
-rw-r--r--tools/perf/config/feature-checks/test-hello.c6
-rw-r--r--tools/perf/config/feature-checks/test-libaudit.c10
-rw-r--r--tools/perf/config/feature-checks/test-libbfd.c15
-rw-r--r--tools/perf/config/feature-checks/test-libdw-dwarf-unwind.c13
-rw-r--r--tools/perf/config/feature-checks/test-libelf-getphdrnum.c8
-rw-r--r--tools/perf/config/feature-checks/test-libelf-mmap.c8
-rw-r--r--tools/perf/config/feature-checks/test-libelf.c8
-rw-r--r--tools/perf/config/feature-checks/test-libnuma.c9
-rw-r--r--tools/perf/config/feature-checks/test-libperl.c9
-rw-r--r--tools/perf/config/feature-checks/test-libpython-version.c10
-rw-r--r--tools/perf/config/feature-checks/test-libpython.c8
-rw-r--r--tools/perf/config/feature-checks/test-libslang.c6
-rw-r--r--tools/perf/config/feature-checks/test-libunwind-debug-frame.c16
-rw-r--r--tools/perf/config/feature-checks/test-libunwind.c27
-rw-r--r--tools/perf/config/feature-checks/test-stackprotector-all.c6
-rw-r--r--tools/perf/config/feature-checks/test-timerfd.c18
-rw-r--r--tools/perf/config/utilities.mak180
-rw-r--r--tools/perf/design.txt462
-rw-r--r--tools/perf/perf-archive.sh47
-rw-r--r--tools/perf/perf-completion.sh206
-rw-r--r--tools/perf/perf-sys.h190
-rw-r--r--tools/perf/perf.c556
-rw-r--r--tools/perf/perf.h68
-rwxr-xr-xtools/perf/python/twatch.py41
-rw-r--r--tools/perf/scripts/perl/Perf-Trace-Util/Context.c135
-rw-r--r--tools/perf/scripts/perl/Perf-Trace-Util/Context.xs42
-rw-r--r--tools/perf/scripts/perl/Perf-Trace-Util/Makefile.PL17
-rw-r--r--tools/perf/scripts/perl/Perf-Trace-Util/README59
-rw-r--r--tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Context.pm55
-rw-r--r--tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Core.pm192
-rw-r--r--tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Util.pm94
-rw-r--r--tools/perf/scripts/perl/Perf-Trace-Util/typemap1
-rw-r--r--tools/perf/scripts/perl/bin/check-perf-trace-record2
-rw-r--r--tools/perf/scripts/perl/bin/failed-syscalls-record2
-rw-r--r--tools/perf/scripts/perl/bin/failed-syscalls-report10
-rw-r--r--tools/perf/scripts/perl/bin/rw-by-file-record3
-rw-r--r--tools/perf/scripts/perl/bin/rw-by-file-report10
-rw-r--r--tools/perf/scripts/perl/bin/rw-by-pid-record2
-rw-r--r--tools/perf/scripts/perl/bin/rw-by-pid-report3
-rw-r--r--tools/perf/scripts/perl/bin/rwtop-record2
-rw-r--r--tools/perf/scripts/perl/bin/rwtop-report20
-rw-r--r--tools/perf/scripts/perl/bin/wakeup-latency-record6
-rw-r--r--tools/perf/scripts/perl/bin/wakeup-latency-report3
-rw-r--r--tools/perf/scripts/perl/check-perf-trace.pl106
-rw-r--r--tools/perf/scripts/perl/failed-syscalls.pl42
-rw-r--r--tools/perf/scripts/perl/rw-by-file.pl106
-rw-r--r--tools/perf/scripts/perl/rw-by-pid.pl184
-rw-r--r--tools/perf/scripts/perl/rwtop.pl203
-rw-r--r--tools/perf/scripts/perl/wakeup-latency.pl107
-rw-r--r--tools/perf/scripts/python/Perf-Trace-Util/Context.c88
-rw-r--r--tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py121
-rwxr-xr-xtools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/EventClass.py94
-rw-r--r--tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py184
-rw-r--r--tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py86
-rw-r--r--tools/perf/scripts/python/bin/event_analyzing_sample-record8
-rw-r--r--tools/perf/scripts/python/bin/event_analyzing_sample-report3
-rw-r--r--tools/perf/scripts/python/bin/failed-syscalls-by-pid-record2
-rw-r--r--tools/perf/scripts/python/bin/failed-syscalls-by-pid-report10
-rw-r--r--tools/perf/scripts/python/bin/futex-contention-record2
-rw-r--r--tools/perf/scripts/python/bin/futex-contention-report4
-rwxr-xr-xtools/perf/scripts/python/bin/net_dropmonitor-record2
-rwxr-xr-xtools/perf/scripts/python/bin/net_dropmonitor-report4
-rw-r--r--tools/perf/scripts/python/bin/netdev-times-record8
-rw-r--r--tools/perf/scripts/python/bin/netdev-times-report5
-rw-r--r--tools/perf/scripts/python/bin/sched-migration-record2
-rw-r--r--tools/perf/scripts/python/bin/sched-migration-report3
-rw-r--r--tools/perf/scripts/python/bin/sctop-record2
-rw-r--r--tools/perf/scripts/python/bin/sctop-report24
-rw-r--r--tools/perf/scripts/python/bin/syscall-counts-by-pid-record2
-rw-r--r--tools/perf/scripts/python/bin/syscall-counts-by-pid-report10
-rw-r--r--tools/perf/scripts/python/bin/syscall-counts-record2
-rw-r--r--tools/perf/scripts/python/bin/syscall-counts-report10
-rw-r--r--tools/perf/scripts/python/check-perf-trace.py82
-rw-r--r--tools/perf/scripts/python/event_analyzing_sample.py189
-rw-r--r--tools/perf/scripts/python/failed-syscalls-by-pid.py73
-rw-r--r--tools/perf/scripts/python/futex-contention.py50
-rwxr-xr-xtools/perf/scripts/python/net_dropmonitor.py75
-rw-r--r--tools/perf/scripts/python/netdev-times.py464
-rw-r--r--tools/perf/scripts/python/sched-migration.py461
-rw-r--r--tools/perf/scripts/python/sctop.py75
-rw-r--r--tools/perf/scripts/python/syscall-counts-by-pid.py69
-rw-r--r--tools/perf/scripts/python/syscall-counts.py59
-rw-r--r--tools/perf/tests/attr.c176
-rw-r--r--tools/perf/tests/attr.py332
-rw-r--r--tools/perf/tests/attr/README64
-rw-r--r--tools/perf/tests/attr/base-record40
-rw-r--r--tools/perf/tests/attr/base-stat40
-rw-r--r--tools/perf/tests/attr/test-record-C013
-rw-r--r--tools/perf/tests/attr/test-record-basic5
-rw-r--r--tools/perf/tests/attr/test-record-branch-any8
-rw-r--r--tools/perf/tests/attr/test-record-branch-filter-any8
-rw-r--r--tools/perf/tests/attr/test-record-branch-filter-any_call8
-rw-r--r--tools/perf/tests/attr/test-record-branch-filter-any_ret8
-rw-r--r--tools/perf/tests/attr/test-record-branch-filter-hv8
-rw-r--r--tools/perf/tests/attr/test-record-branch-filter-ind_call8
-rw-r--r--tools/perf/tests/attr/test-record-branch-filter-k8
-rw-r--r--tools/perf/tests/attr/test-record-branch-filter-u8
-rw-r--r--tools/perf/tests/attr/test-record-count8
-rw-r--r--tools/perf/tests/attr/test-record-data11
-rw-r--r--tools/perf/tests/attr/test-record-freq6
-rw-r--r--tools/perf/tests/attr/test-record-graph-default6
-rw-r--r--tools/perf/tests/attr/test-record-graph-dwarf10
-rw-r--r--tools/perf/tests/attr/test-record-graph-fp6
-rw-r--r--tools/perf/tests/attr/test-record-group20
-rw-r--r--tools/perf/tests/attr/test-record-group-sampling36
-rw-r--r--tools/perf/tests/attr/test-record-group121
-rw-r--r--tools/perf/tests/attr/test-record-no-delay9
-rw-r--r--tools/perf/tests/attr/test-record-no-inherit7
-rw-r--r--tools/perf/tests/attr/test-record-no-samples6
-rw-r--r--tools/perf/tests/attr/test-record-period7
-rw-r--r--tools/perf/tests/attr/test-record-raw7
-rw-r--r--tools/perf/tests/attr/test-stat-C09
-rw-r--r--tools/perf/tests/attr/test-stat-basic6
-rw-r--r--tools/perf/tests/attr/test-stat-default64
-rw-r--r--tools/perf/tests/attr/test-stat-detailed-1101
-rw-r--r--tools/perf/tests/attr/test-stat-detailed-2155
-rw-r--r--tools/perf/tests/attr/test-stat-detailed-3173
-rw-r--r--tools/perf/tests/attr/test-stat-group15
-rw-r--r--tools/perf/tests/attr/test-stat-group115
-rw-r--r--tools/perf/tests/attr/test-stat-no-inherit7
-rw-r--r--tools/perf/tests/bp_signal.c192
-rw-r--r--tools/perf/tests/bp_signal_overflow.c132
-rw-r--r--tools/perf/tests/builtin-test.c307
-rw-r--r--tools/perf/tests/code-reading.c581
-rw-r--r--tools/perf/tests/dso-data.c358
-rw-r--r--tools/perf/tests/dwarf-unwind.c144
-rw-r--r--tools/perf/tests/evsel-roundtrip-name.c114
-rw-r--r--tools/perf/tests/evsel-tp-sched.c81
-rw-r--r--tools/perf/tests/hists_common.c209
-rw-r--r--tools/perf/tests/hists_common.h75
-rw-r--r--tools/perf/tests/hists_cumulate.c726
-rw-r--r--tools/perf/tests/hists_filter.c289
-rw-r--r--tools/perf/tests/hists_link.c339
-rw-r--r--tools/perf/tests/hists_output.c621
-rw-r--r--tools/perf/tests/keep-tracking.c152
-rw-r--r--tools/perf/tests/make233
-rw-r--r--tools/perf/tests/mmap-basic.c148
-rw-r--r--tools/perf/tests/mmap-thread-lookup.c233
-rw-r--r--tools/perf/tests/open-syscall-all-cpus.c109
-rw-r--r--tools/perf/tests/open-syscall-tp-fields.c117
-rw-r--r--tools/perf/tests/open-syscall.c55
-rw-r--r--tools/perf/tests/parse-events.c1590
-rw-r--r--tools/perf/tests/parse-no-sample-id-all.c108
-rw-r--r--tools/perf/tests/perf-record.c309
-rwxr-xr-xtools/perf/tests/perf-targz-src-pkg21
-rw-r--r--tools/perf/tests/perf-time-to-tsc.c172
-rw-r--r--tools/perf/tests/pmu.c173
-rw-r--r--tools/perf/tests/python-use.c23
-rw-r--r--tools/perf/tests/rdpmc.c173
-rw-r--r--tools/perf/tests/sample-parsing.c320
-rw-r--r--tools/perf/tests/sw-clock.c122
-rw-r--r--tools/perf/tests/task-exit.c118
-rw-r--r--tools/perf/tests/tests.h60
-rw-r--r--tools/perf/tests/thread-mg-share.c90
-rw-r--r--tools/perf/tests/vmlinux-kallsyms.c242
-rw-r--r--tools/perf/ui/browser.c719
-rw-r--r--tools/perf/ui/browser.h74
-rw-r--r--tools/perf/ui/browsers/annotate.c1023
-rw-r--r--tools/perf/ui/browsers/header.c127
-rw-r--r--tools/perf/ui/browsers/hists.c2007
-rw-r--r--tools/perf/ui/browsers/map.c130
-rw-r--r--tools/perf/ui/browsers/map.h6
-rw-r--r--tools/perf/ui/browsers/scripts.c187
-rw-r--r--tools/perf/ui/gtk/annotate.c252
-rw-r--r--tools/perf/ui/gtk/browser.c87
-rw-r--r--tools/perf/ui/gtk/gtk.h67
-rw-r--r--tools/perf/ui/gtk/helpline.c57
-rw-r--r--tools/perf/ui/gtk/hists.c365
-rw-r--r--tools/perf/ui/gtk/progress.c59
-rw-r--r--tools/perf/ui/gtk/setup.c23
-rw-r--r--tools/perf/ui/gtk/util.c112
-rw-r--r--tools/perf/ui/helpline.c73
-rw-r--r--tools/perf/ui/helpline.h29
-rw-r--r--tools/perf/ui/hist.c624
-rw-r--r--tools/perf/ui/keysyms.h28
-rw-r--r--tools/perf/ui/libslang.h29
-rw-r--r--tools/perf/ui/progress.c38
-rw-r--r--tools/perf/ui/progress.h23
-rw-r--r--tools/perf/ui/setup.c107
-rw-r--r--tools/perf/ui/stdio/hist.c514
-rw-r--r--tools/perf/ui/tui/helpline.c58
-rw-r--r--tools/perf/ui/tui/progress.c44
-rw-r--r--tools/perf/ui/tui/setup.c151
-rw-r--r--tools/perf/ui/tui/tui.h6
-rw-r--r--tools/perf/ui/tui/util.c256
-rw-r--r--tools/perf/ui/ui.h29
-rw-r--r--tools/perf/ui/util.c84
-rw-r--r--tools/perf/ui/util.h21
-rwxr-xr-xtools/perf/util/PERF-VERSION-GEN50
-rw-r--r--tools/perf/util/abspath.c37
-rw-r--r--tools/perf/util/alias.c76
-rw-r--r--tools/perf/util/annotate.c1412
-rw-r--r--tools/perf/util/annotate.h177
-rw-r--r--tools/perf/util/bitmap.c31
-rw-r--r--tools/perf/util/build-id.c108
-rw-r--r--tools/perf/util/build-id.h18
-rw-r--r--tools/perf/util/cache.h76
-rw-r--r--tools/perf/util/callchain.c674
-rw-r--r--tools/perf/util/callchain.h179
-rw-r--r--tools/perf/util/cgroup.c177
-rw-r--r--tools/perf/util/cgroup.h17
-rw-r--r--tools/perf/util/color.c337
-rw-r--r--tools/perf/util/color.h47
-rw-r--r--tools/perf/util/comm.c122
-rw-r--r--tools/perf/util/comm.h21
-rw-r--r--tools/perf/util/config.c513
-rw-r--r--tools/perf/util/cpumap.c479
-rw-r--r--tools/perf/util/cpumap.h84
-rw-r--r--tools/perf/util/ctype.c39
-rw-r--r--tools/perf/util/data.c133
-rw-r--r--tools/perf/util/data.h50
-rw-r--r--tools/perf/util/debug.c107
-rw-r--r--tools/perf/util/debug.h22
-rw-r--r--tools/perf/util/dso.c900
-rw-r--r--tools/perf/util/dso.h232
-rw-r--r--tools/perf/util/dwarf-aux.c884
-rw-r--r--tools/perf/util/dwarf-aux.h118
-rw-r--r--tools/perf/util/environment.c9
-rw-r--r--tools/perf/util/event.c868
-rw-r--r--tools/perf/util/event.h316
-rw-r--r--tools/perf/util/evlist.c1245
-rw-r--r--tools/perf/util/evlist.h265
-rw-r--r--tools/perf/util/evsel.c2036
-rw-r--r--tools/perf/util/evsel.h365
-rw-r--r--tools/perf/util/exec_cmd.c148
-rw-r--r--tools/perf/util/exec_cmd.h12
-rwxr-xr-xtools/perf/util/generate-cmdlist.sh39
-rw-r--r--tools/perf/util/header.c3071
-rw-r--r--tools/perf/util/header.h159
-rw-r--r--tools/perf/util/help.c339
-rw-r--r--tools/perf/util/help.h29
-rw-r--r--tools/perf/util/hist.c1414
-rw-r--r--tools/perf/util/hist.h347
-rw-r--r--tools/perf/util/hweight.c31
-rw-r--r--tools/perf/util/include/asm/alternative-asm.h8
-rw-r--r--tools/perf/util/include/asm/asm-offsets.h1
-rw-r--r--tools/perf/util/include/asm/byteorder.h2
-rw-r--r--tools/perf/util/include/asm/cpufeature.h9
-rw-r--r--tools/perf/util/include/asm/dwarf2.h13
-rw-r--r--tools/perf/util/include/asm/hash.h6
-rw-r--r--tools/perf/util/include/asm/hweight.h8
-rw-r--r--tools/perf/util/include/asm/swab.h1
-rw-r--r--tools/perf/util/include/asm/system.h1
-rw-r--r--tools/perf/util/include/asm/uaccess.h14
-rw-r--r--tools/perf/util/include/asm/unistd_32.h1
-rw-r--r--tools/perf/util/include/asm/unistd_64.h1
-rw-r--r--tools/perf/util/include/dwarf-regs.h8
-rw-r--r--tools/perf/util/include/linux/bitmap.h49
-rw-r--r--tools/perf/util/include/linux/bitops.h160
-rw-r--r--tools/perf/util/include/linux/const.h1
-rw-r--r--tools/perf/util/include/linux/ctype.h1
-rw-r--r--tools/perf/util/include/linux/kernel.h128
-rw-r--r--tools/perf/util/include/linux/linkage.h13
-rw-r--r--tools/perf/util/include/linux/list.h29
-rw-r--r--tools/perf/util/include/linux/poison.h1
-rw-r--r--tools/perf/util/include/linux/rbtree.h2
-rw-r--r--tools/perf/util/include/linux/rbtree_augmented.h2
-rw-r--r--tools/perf/util/include/linux/string.h4
-rw-r--r--tools/perf/util/intlist.c146
-rw-r--r--tools/perf/util/intlist.h77
-rw-r--r--tools/perf/util/levenshtein.c84
-rw-r--r--tools/perf/util/levenshtein.h8
-rw-r--r--tools/perf/util/machine.c1422
-rw-r--r--tools/perf/util/machine.h194
-rw-r--r--tools/perf/util/map.c764
-rw-r--r--tools/perf/util/map.h220
-rw-r--r--tools/perf/util/pager.c100
-rw-r--r--tools/perf/util/parse-events.c1354
-rw-r--r--tools/perf/util/parse-events.h112
-rw-r--r--tools/perf/util/parse-events.l218
-rw-r--r--tools/perf/util/parse-events.y454
-rw-r--r--tools/perf/util/parse-options.c681
-rw-r--r--tools/perf/util/parse-options.h209
-rw-r--r--tools/perf/util/path.c161
-rw-r--r--tools/perf/util/perf_regs.c27
-rw-r--r--tools/perf/util/perf_regs.h29
-rw-r--r--tools/perf/util/pmu.c796
-rw-r--r--tools/perf/util/pmu.h49
-rw-r--r--tools/perf/util/pmu.l43
-rw-r--r--tools/perf/util/pmu.y92
-rw-r--r--tools/perf/util/probe-event.c2573
-rw-r--r--tools/perf/util/probe-event.h141
-rw-r--r--tools/perf/util/probe-finder.c1603
-rw-r--r--tools/perf/util/probe-finder.h112
-rw-r--r--tools/perf/util/pstack.c75
-rw-r--r--tools/perf/util/pstack.h14
-rw-r--r--tools/perf/util/python-ext-sources22
-rw-r--r--tools/perf/util/python.c1074
-rw-r--r--tools/perf/util/quote.c54
-rw-r--r--tools/perf/util/quote.h29
-rw-r--r--tools/perf/util/rblist.c128
-rw-r--r--tools/perf/util/rblist.h48
-rw-r--r--tools/perf/util/record.c215
-rw-r--r--tools/perf/util/run-command.c214
-rw-r--r--tools/perf/util/run-command.h58
-rw-r--r--tools/perf/util/scripting-engines/trace-event-perl.c638
-rw-r--r--tools/perf/util/scripting-engines/trace-event-python.c698
-rw-r--r--tools/perf/util/session.c1673
-rw-r--r--tools/perf/util/session.h129
-rw-r--r--tools/perf/util/setup.py48
-rw-r--r--tools/perf/util/sigchain.c52
-rw-r--r--tools/perf/util/sigchain.h10
-rw-r--r--tools/perf/util/sort.c1706
-rw-r--r--tools/perf/util/sort.h221
-rw-r--r--tools/perf/util/srcline.c299
-rw-r--r--tools/perf/util/stat.c63
-rw-r--r--tools/perf/util/stat.h25
-rw-r--r--tools/perf/util/strbuf.c134
-rw-r--r--tools/perf/util/strbuf.h92
-rw-r--r--tools/perf/util/strfilter.c199
-rw-r--r--tools/perf/util/strfilter.h48
-rw-r--r--tools/perf/util/string.c413
-rw-r--r--tools/perf/util/strlist.c173
-rw-r--r--tools/perf/util/strlist.h79
-rw-r--r--tools/perf/util/svghelper.c716
-rw-r--r--tools/perf/util/svghelper.h33
-rw-r--r--tools/perf/util/symbol-elf.c1625
-rw-r--r--tools/perf/util/symbol-minimal.c330
-rw-r--r--tools/perf/util/symbol.c1880
-rw-r--r--tools/perf/util/symbol.h297
-rw-r--r--tools/perf/util/target.c158
-rw-r--r--tools/perf/util/target.h79
-rw-r--r--tools/perf/util/thread.c193
-rw-r--r--tools/perf/util/thread.h81
-rw-r--r--tools/perf/util/thread_map.c294
-rw-r--r--tools/perf/util/thread_map.h29
-rw-r--r--tools/perf/util/tool.h47
-rw-r--r--tools/perf/util/top.c117
-rw-r--r--tools/perf/util/top.h47
-rw-r--r--tools/perf/util/trace-event-info.c600
-rw-r--r--tools/perf/util/trace-event-parse.c263
-rw-r--r--tools/perf/util/trace-event-read.c444
-rw-r--r--tools/perf/util/trace-event-scripting.c171
-rw-r--r--tools/perf/util/trace-event.c82
-rw-r--r--tools/perf/util/trace-event.h90
-rw-r--r--tools/perf/util/unwind-libdw.c210
-rw-r--r--tools/perf/util/unwind-libdw.h21
-rw-r--r--tools/perf/util/unwind-libunwind.c579
-rw-r--r--tools/perf/util/unwind.h37
-rw-r--r--tools/perf/util/usage.c84
-rw-r--r--tools/perf/util/util.c542
-rw-r--r--tools/perf/util/util.h333
-rw-r--r--tools/perf/util/values.c232
-rw-r--r--tools/perf/util/values.h27
-rw-r--r--tools/perf/util/vdso.c111
-rw-r--r--tools/perf/util/vdso.h18
-rw-r--r--tools/perf/util/wrapper.c41
-rw-r--r--tools/perf/util/xyarray.c20
-rw-r--r--tools/perf/util/xyarray.h20
-rw-r--r--tools/power/acpi/Makefile156
-rw-r--r--tools/power/acpi/common/cmfsize.c101
-rw-r--r--tools/power/acpi/common/getopt.c239
-rw-r--r--tools/power/acpi/man/acpidump.8120
-rw-r--r--tools/power/acpi/os_specific/service_layers/oslinuxtbl.c1329
-rw-r--r--tools/power/acpi/os_specific/service_layers/osunixdir.c204
-rw-r--r--tools/power/acpi/os_specific/service_layers/osunixmap.c151
-rw-r--r--tools/power/acpi/tools/acpidump/acpidump.h130
-rw-r--r--tools/power/acpi/tools/acpidump/apdump.c451
-rw-r--r--tools/power/acpi/tools/acpidump/apfiles.c228
-rw-r--r--tools/power/acpi/tools/acpidump/apmain.c351
-rw-r--r--tools/power/acpi/tools/ec/Makefile22
-rw-r--r--tools/power/acpi/tools/ec/ec_access.c238
-rw-r--r--tools/power/cpupower/.gitignore29
-rw-r--r--tools/power/cpupower/Makefile310
-rw-r--r--tools/power/cpupower/README47
-rw-r--r--tools/power/cpupower/ToDo10
-rw-r--r--tools/power/cpupower/bench/Makefile36
-rw-r--r--tools/power/cpupower/bench/README-BENCH124
-rw-r--r--tools/power/cpupower/bench/benchmark.c194
-rw-r--r--tools/power/cpupower/bench/benchmark.h29
-rw-r--r--tools/power/cpupower/bench/config.h36
-rw-r--r--tools/power/cpupower/bench/cpufreq-bench_plot.sh104
-rw-r--r--tools/power/cpupower/bench/cpufreq-bench_script.sh101
-rw-r--r--tools/power/cpupower/bench/example.cfg11
-rw-r--r--tools/power/cpupower/bench/main.c202
-rw-r--r--tools/power/cpupower/bench/parse.c225
-rw-r--r--tools/power/cpupower/bench/parse.h53
-rw-r--r--tools/power/cpupower/bench/system.c191
-rw-r--r--tools/power/cpupower/bench/system.h29
-rw-r--r--tools/power/cpupower/debug/i386/Makefile41
-rw-r--r--tools/power/cpupower/debug/i386/centrino-decode.c113
-rw-r--r--tools/power/cpupower/debug/i386/dump_psb.c196
-rw-r--r--tools/power/cpupower/debug/i386/intel_gsic.c78
-rw-r--r--tools/power/cpupower/debug/i386/powernow-k8-decode.c96
-rw-r--r--tools/power/cpupower/debug/kernel/Makefile23
-rw-r--r--tools/power/cpupower/debug/kernel/cpufreq-test_tsc.c110
-rw-r--r--tools/power/cpupower/debug/x86_64/Makefile30
-rw-r--r--tools/power/cpupower/lib/cpufreq.c208
-rw-r--r--tools/power/cpupower/lib/cpufreq.h223
-rw-r--r--tools/power/cpupower/lib/sysfs.c672
-rw-r--r--tools/power/cpupower/lib/sysfs.h31
-rw-r--r--tools/power/cpupower/man/cpupower-frequency-info.177
-rw-r--r--tools/power/cpupower/man/cpupower-frequency-set.152
-rw-r--r--tools/power/cpupower/man/cpupower-idle-info.191
-rw-r--r--tools/power/cpupower/man/cpupower-idle-set.177
-rw-r--r--tools/power/cpupower/man/cpupower-info.119
-rw-r--r--tools/power/cpupower/man/cpupower-monitor.1198
-rw-r--r--tools/power/cpupower/man/cpupower-set.165
-rw-r--r--tools/power/cpupower/man/cpupower.172
-rw-r--r--tools/power/cpupower/po/cs.po944
-rw-r--r--tools/power/cpupower/po/de.po961
-rw-r--r--tools/power/cpupower/po/fr.po947
-rw-r--r--tools/power/cpupower/po/it.po961
-rw-r--r--tools/power/cpupower/po/pt.po957
-rw-r--r--tools/power/cpupower/utils/builtin.h12
-rw-r--r--tools/power/cpupower/utils/cpufreq-info.c698
-rw-r--r--tools/power/cpupower/utils/cpufreq-set.c331
-rw-r--r--tools/power/cpupower/utils/cpuidle-info.c208
-rw-r--r--tools/power/cpupower/utils/cpuidle-set.c181
-rw-r--r--tools/power/cpupower/utils/cpupower-info.c103
-rw-r--r--tools/power/cpupower/utils/cpupower-set.c95
-rw-r--r--tools/power/cpupower/utils/cpupower.c229
-rw-r--r--tools/power/cpupower/utils/helpers/amd.c135
-rw-r--r--tools/power/cpupower/utils/helpers/bitmask.c292
-rw-r--r--tools/power/cpupower/utils/helpers/bitmask.h33
-rw-r--r--tools/power/cpupower/utils/helpers/cpuid.c178
-rw-r--r--tools/power/cpupower/utils/helpers/helpers.h195
-rw-r--r--tools/power/cpupower/utils/helpers/misc.c27
-rw-r--r--tools/power/cpupower/utils/helpers/msr.c115
-rw-r--r--tools/power/cpupower/utils/helpers/pci.c55
-rw-r--r--tools/power/cpupower/utils/helpers/sysfs.c472
-rw-r--r--tools/power/cpupower/utils/helpers/sysfs.h38
-rw-r--r--tools/power/cpupower/utils/helpers/topology.c116
-rw-r--r--tools/power/cpupower/utils/idle_monitor/amd_fam14h_idle.c335
-rw-r--r--tools/power/cpupower/utils/idle_monitor/cpuidle_sysfs.c196
-rw-r--r--tools/power/cpupower/utils/idle_monitor/cpupower-monitor.c455
-rw-r--r--tools/power/cpupower/utils/idle_monitor/cpupower-monitor.h85
-rw-r--r--tools/power/cpupower/utils/idle_monitor/hsw_ext_idle.c196
-rw-r--r--tools/power/cpupower/utils/idle_monitor/idle_monitors.def8
-rw-r--r--tools/power/cpupower/utils/idle_monitor/idle_monitors.h18
-rw-r--r--tools/power/cpupower/utils/idle_monitor/mperf_monitor.c338
-rw-r--r--tools/power/cpupower/utils/idle_monitor/nhm_idle.c216
-rw-r--r--tools/power/cpupower/utils/idle_monitor/snb_idle.c200
-rwxr-xr-xtools/power/cpupower/utils/version-gen.sh35
-rw-r--r--tools/power/x86/turbostat/.gitignore1
-rw-r--r--tools/power/x86/turbostat/Makefile22
-rw-r--r--tools/power/x86/turbostat/turbostat.8221
-rw-r--r--tools/power/x86/turbostat/turbostat.c2462
-rw-r--r--tools/power/x86/x86_energy_perf_policy/Makefile10
-rw-r--r--tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.8104
-rw-r--r--tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.c324
-rw-r--r--tools/scripts/Makefile.include84
-rw-r--r--tools/testing/fault-injection/failcmd.sh219
-rwxr-xr-xtools/testing/ktest/compare-ktest-sample.pl32
-rw-r--r--tools/testing/ktest/examples/README32
-rw-r--r--tools/testing/ktest/examples/crosstests.conf254
-rw-r--r--tools/testing/ktest/examples/include/bisect.conf90
-rw-r--r--tools/testing/ktest/examples/include/defaults.conf157
-rw-r--r--tools/testing/ktest/examples/include/min-config.conf60
-rw-r--r--tools/testing/ktest/examples/include/patchcheck.conf111
-rw-r--r--tools/testing/ktest/examples/include/tests.conf74
-rw-r--r--tools/testing/ktest/examples/kvm.conf92
-rw-r--r--tools/testing/ktest/examples/snowball.conf53
-rw-r--r--tools/testing/ktest/examples/test.conf62
-rwxr-xr-xtools/testing/ktest/ktest.pl4181
-rw-r--r--tools/testing/ktest/sample.conf1289
-rw-r--r--tools/testing/selftests/Makefile28
-rw-r--r--tools/testing/selftests/README.txt42
-rw-r--r--tools/testing/selftests/breakpoints/Makefile23
-rw-r--r--tools/testing/selftests/breakpoints/breakpoint_test.c394
-rw-r--r--tools/testing/selftests/cpu-hotplug/Makefile6
-rw-r--r--tools/testing/selftests/cpu-hotplug/on-off-test.sh221
-rw-r--r--tools/testing/selftests/efivarfs/Makefile12
-rw-r--r--tools/testing/selftests/efivarfs/create-read.c38
-rw-r--r--tools/testing/selftests/efivarfs/efivarfs.sh198
-rw-r--r--tools/testing/selftests/efivarfs/open-unlink.c63
-rw-r--r--tools/testing/selftests/ipc/Makefile25
-rw-r--r--tools/testing/selftests/ipc/msgque.c252
-rw-r--r--tools/testing/selftests/kcmp/.gitignore2
-rw-r--r--tools/testing/selftests/kcmp/Makefile28
-rw-r--r--tools/testing/selftests/kcmp/kcmp_test.c96
-rw-r--r--tools/testing/selftests/memory-hotplug/Makefile6
-rw-r--r--tools/testing/selftests/memory-hotplug/on-off-test.sh230
-rw-r--r--tools/testing/selftests/mqueue/.gitignore2
-rw-r--r--tools/testing/selftests/mqueue/Makefile10
-rw-r--r--tools/testing/selftests/mqueue/mq_open_tests.c492
-rw-r--r--tools/testing/selftests/mqueue/mq_perf_tests.c741
-rw-r--r--tools/testing/selftests/net/.gitignore3
-rw-r--r--tools/testing/selftests/net/Makefile25
-rw-r--r--tools/testing/selftests/net/psock_fanout.c312
-rw-r--r--tools/testing/selftests/net/psock_lib.h127
-rw-r--r--tools/testing/selftests/net/psock_tpacket.c805
-rw-r--r--tools/testing/selftests/net/run_afpackettests26
-rw-r--r--tools/testing/selftests/net/run_netsocktests12
-rw-r--r--tools/testing/selftests/net/socket.c92
-rw-r--r--tools/testing/selftests/powerpc/Makefile39
-rw-r--r--tools/testing/selftests/powerpc/copyloops/Makefile29
-rw-r--r--tools/testing/selftests/powerpc/copyloops/asm/ppc_asm.h89
-rw-r--r--tools/testing/selftests/powerpc/copyloops/asm/processor.h0
l---------tools/testing/selftests/powerpc/copyloops/copyuser_64.S1
l---------tools/testing/selftests/powerpc/copyloops/copyuser_power7.S1
l---------tools/testing/selftests/powerpc/copyloops/memcpy_64.S1
l---------tools/testing/selftests/powerpc/copyloops/memcpy_power7.S1
-rw-r--r--tools/testing/selftests/powerpc/copyloops/validate.c99
-rw-r--r--tools/testing/selftests/powerpc/harness.c114
-rw-r--r--tools/testing/selftests/powerpc/mm/Makefile18
-rw-r--r--tools/testing/selftests/powerpc/mm/hugetlb_vs_thp_test.c72
-rw-r--r--tools/testing/selftests/powerpc/pmu/Makefile41
-rw-r--r--tools/testing/selftests/powerpc/pmu/count_instructions.c135
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/Makefile32
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/back_to_back_ebbs_test.c106
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/close_clears_pmcc_test.c59
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/cpu_event_pinned_vs_ebb_test.c93
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/cpu_event_vs_ebb_test.c89
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/cycles_test.c58
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/cycles_with_freeze_test.c117
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/ebb.c727
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/ebb.h78
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/ebb_handler.S365
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/ebb_on_child_test.c86
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/ebb_on_willing_child_test.c92
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/ebb_vs_cpu_event_test.c86
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/event_attributes_test.c131
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/fixed_instruction_loop.S43
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/fork_cleanup_test.c79
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/instruction_count_test.c164
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c100
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/multi_counter_test.c91
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/multi_ebb_procs_test.c109
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/no_handler_test.c61
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/pmae_handling_test.c106
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/pmc56_overflow_test.c93
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/reg.h49
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/reg_access_test.c39
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/task_event_pinned_vs_ebb_test.c91
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/task_event_vs_ebb_test.c83
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/trace.c300
-rw-r--r--tools/testing/selftests/powerpc/pmu/ebb/trace.h41
-rw-r--r--tools/testing/selftests/powerpc/pmu/event.c131
-rw-r--r--tools/testing/selftests/powerpc/pmu/event.h43
-rw-r--r--tools/testing/selftests/powerpc/pmu/lib.c252
-rw-r--r--tools/testing/selftests/powerpc/pmu/lib.h41
-rw-r--r--tools/testing/selftests/powerpc/pmu/loop.S43
-rw-r--r--tools/testing/selftests/powerpc/subunit.h52
-rw-r--r--tools/testing/selftests/powerpc/tm/Makefile15
-rw-r--r--tools/testing/selftests/powerpc/tm/tm-resched-dscr.c98
-rw-r--r--tools/testing/selftests/powerpc/utils.h49
-rw-r--r--tools/testing/selftests/ptrace/Makefile10
-rw-r--r--tools/testing/selftests/ptrace/peeksiginfo.c214
-rw-r--r--tools/testing/selftests/rcutorture/.gitignore6
-rw-r--r--tools/testing/selftests/rcutorture/bin/config2frag.sh25
-rwxr-xr-xtools/testing/selftests/rcutorture/bin/configNR_CPUS.sh45
-rwxr-xr-xtools/testing/selftests/rcutorture/bin/configcheck.sh54
-rwxr-xr-xtools/testing/selftests/rcutorture/bin/configinit.sh74
-rwxr-xr-xtools/testing/selftests/rcutorture/bin/cpus2use.sh41
-rw-r--r--tools/testing/selftests/rcutorture/bin/functions.sh223
-rwxr-xr-xtools/testing/selftests/rcutorture/bin/kvm-build.sh71
-rwxr-xr-xtools/testing/selftests/rcutorture/bin/kvm-recheck-lock.sh51
-rwxr-xr-xtools/testing/selftests/rcutorture/bin/kvm-recheck-rcu.sh51
-rwxr-xr-xtools/testing/selftests/rcutorture/bin/kvm-recheck.sh63
-rwxr-xr-xtools/testing/selftests/rcutorture/bin/kvm-test-1-run.sh222
-rw-r--r--tools/testing/selftests/rcutorture/bin/kvm.sh410
-rwxr-xr-xtools/testing/selftests/rcutorture/bin/parse-build.sh57
-rwxr-xr-xtools/testing/selftests/rcutorture/bin/parse-console.sh41
-rwxr-xr-xtools/testing/selftests/rcutorture/bin/parse-torture.sh106
-rw-r--r--tools/testing/selftests/rcutorture/configs/lock/BUSTED6
-rw-r--r--tools/testing/selftests/rcutorture/configs/lock/BUSTED.boot1
-rw-r--r--tools/testing/selftests/rcutorture/configs/lock/CFLIST1
-rw-r--r--tools/testing/selftests/rcutorture/configs/lock/CFcommon2
-rw-r--r--tools/testing/selftests/rcutorture/configs/lock/LOCK016
-rw-r--r--tools/testing/selftests/rcutorture/configs/lock/ver_functions.sh43
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/BUSTED7
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/BUSTED.boot1
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/CFLIST13
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/CFcommon2
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/SRCU-N7
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/SRCU-N.boot1
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/SRCU-P7
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/SRCU-P.boot1
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TINY0112
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TINY0212
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE0122
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE01.boot1
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE0225
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE02-T25
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE0322
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE0424
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE04.boot1
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE0524
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE05.boot1
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE0625
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE0723
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE0825
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE08-T25
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE08.boot1
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/TREE0920
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/CFLIST14
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/N1-S-T-NH-SD-SMP-HP18
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/N2-2-t-nh-sd-SMP-hp20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/N3-3-T-nh-SD-SMP-hp22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/N4-A-t-NH-sd-SMP-HP18
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/N5-U-T-NH-sd-SMP-hp22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/NT1-nh23
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/NT3-NH20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/P1-S-T-NH-SD-SMP-HP19
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/P2-2-t-nh-sd-SMP-hp20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/P3-3-T-nh-SD-SMP-hp20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/P4-A-t-NH-sd-SMP-HP22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/P5-U-T-NH-sd-SMP-hp28
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/PT1-nh23
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/PT2-NH22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v0.0/ver_functions.sh33
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/CFLIST17
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/N1-S-T-NH-SD-SMP-HP19
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/N2-2-t-nh-sd-SMP-hp20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/N3-3-T-nh-SD-SMP-hp22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/N4-A-t-NH-sd-SMP-HP18
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/N5-U-T-NH-sd-SMP-hp22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/N6---t-nh-SD-smp-hp19
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/N7-4-T-NH-SD-SMP-HP26
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/N8-2-T-NH-SD-SMP-HP22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/NT1-nh23
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/NT3-NH20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/P1-S-T-NH-SD-SMP-HP20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/P2-2-t-nh-sd-SMP-hp20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/P3-3-T-nh-SD-SMP-hp20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/P4-A-t-NH-sd-SMP-HP22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/P5-U-T-NH-sd-SMP-hp28
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/P6---t-nh-SD-smp-hp18
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-HP30
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-HP-all30
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-HP-none30
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-hp30
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/PT1-nh23
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.12/PT2-NH22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/CFLIST14
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/N1-S-T-NH-SD-SMP-HP19
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/N2-2-t-nh-sd-SMP-hp20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/N3-3-T-nh-SD-SMP-hp22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/N4-A-t-NH-sd-SMP-HP18
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/N5-U-T-NH-sd-SMP-hp22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/NT1-nh23
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/NT3-NH20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/P1-S-T-NH-SD-SMP-HP20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/P2-2-t-nh-sd-SMP-hp20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/P3-3-T-nh-SD-SMP-hp20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/P4-A-t-NH-sd-SMP-HP22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/P5-U-T-NH-sd-SMP-hp28
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/PT1-nh23
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/PT2-NH22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.3/ver_functions.sh44
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/CFLIST14
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/N1-S-T-NH-SD-SMP-HP19
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/N2-2-t-nh-sd-SMP-hp20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/N3-3-T-nh-SD-SMP-hp22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/N4-A-t-NH-sd-SMP-HP18
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/N5-U-T-NH-sd-SMP-hp22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/NT1-nh23
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/NT3-NH20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/P1-S-T-NH-SD-SMP-HP20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/P2-2-t-nh-sd-SMP-hp20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/P3-3-T-nh-SD-SMP-hp20
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/P4-A-t-NH-sd-SMP-HP22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/P5-U-T-NH-sd-SMP-hp28
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/PT1-nh23
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/PT2-NH22
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/v3.5/ver_functions.sh57
-rw-r--r--tools/testing/selftests/rcutorture/configs/rcu/ver_functions.sh57
-rw-r--r--tools/testing/selftests/rcutorture/doc/TINY_RCU.txt40
-rw-r--r--tools/testing/selftests/rcutorture/doc/TREE_RCU-kconfig.txt95
-rw-r--r--tools/testing/selftests/rcutorture/doc/initrd.txt90
-rw-r--r--tools/testing/selftests/rcutorture/doc/rcu-test-image.txt42
-rw-r--r--tools/testing/selftests/sysctl/Makefile19
-rw-r--r--tools/testing/selftests/sysctl/common_tests109
-rw-r--r--tools/testing/selftests/sysctl/run_numerictests10
-rw-r--r--tools/testing/selftests/sysctl/run_stringtests77
-rw-r--r--tools/testing/selftests/timers/Makefile8
-rw-r--r--tools/testing/selftests/timers/posix_timers.c221
-rw-r--r--tools/testing/selftests/user/Makefile13
-rw-r--r--tools/testing/selftests/vm/.gitignore4
-rw-r--r--tools/testing/selftests/vm/Makefile15
-rw-r--r--tools/testing/selftests/vm/hugepage-mmap.c92
-rw-r--r--tools/testing/selftests/vm/hugepage-shm.c100
-rw-r--r--tools/testing/selftests/vm/hugetlbfstest.c84
-rw-r--r--tools/testing/selftests/vm/map_hugetlb.c79
-rw-r--r--tools/testing/selftests/vm/run_vmtests93
-rw-r--r--tools/testing/selftests/vm/thuge-gen.c254
-rw-r--r--tools/thermal/tmon/Makefile47
-rw-r--r--tools/thermal/tmon/README50
-rw-r--r--tools/thermal/tmon/pid.c131
-rw-r--r--tools/thermal/tmon/sysfs.c596
-rw-r--r--tools/thermal/tmon/tmon.8142
-rw-r--r--tools/thermal/tmon/tmon.c376
-rw-r--r--tools/thermal/tmon/tmon.h204
-rw-r--r--tools/thermal/tmon/tui.c638
-rw-r--r--tools/usb/Makefile14
-rw-r--r--tools/usb/ffs-aio-example/multibuff/device_app/aio_multibuff.c349
-rw-r--r--tools/usb/ffs-aio-example/multibuff/host_app/Makefile13
-rw-r--r--tools/usb/ffs-aio-example/multibuff/host_app/test.c146
-rw-r--r--tools/usb/ffs-aio-example/simple/device_app/aio_simple.c335
-rw-r--r--tools/usb/ffs-aio-example/simple/host_app/Makefile13
-rw-r--r--tools/usb/ffs-aio-example/simple/host_app/test.c148
-rw-r--r--tools/usb/ffs-test.c527
-rw-r--r--tools/usb/hcd-tests.sh275
-rw-r--r--tools/usb/testusb.c537
-rw-r--r--tools/virtio/.gitignore3
-rw-r--r--tools/virtio/Makefile14
-rw-r--r--tools/virtio/asm/barrier.h14
-rw-r--r--tools/virtio/linux/bug.h10
-rw-r--r--tools/virtio/linux/device.h2
-rw-r--r--tools/virtio/linux/err.h26
-rw-r--r--tools/virtio/linux/hrtimer.h0
-rw-r--r--tools/virtio/linux/irqreturn.h1
-rw-r--r--tools/virtio/linux/kernel.h105
-rw-r--r--tools/virtio/linux/kmemleak.h3
-rw-r--r--tools/virtio/linux/module.h6
-rw-r--r--tools/virtio/linux/printk.h4
-rw-r--r--tools/virtio/linux/ratelimit.h4
-rw-r--r--tools/virtio/linux/scatterlist.h189
-rw-r--r--tools/virtio/linux/slab.h2
-rw-r--r--tools/virtio/linux/uaccess.h50
-rw-r--r--tools/virtio/linux/uio.h3
-rw-r--r--tools/virtio/linux/virtio.h87
-rw-r--r--tools/virtio/linux/virtio_config.h6
-rw-r--r--tools/virtio/linux/virtio_ring.h1
-rw-r--r--tools/virtio/linux/vringh.h1
-rw-r--r--tools/virtio/uapi/linux/uio.h1
-rw-r--r--tools/virtio/uapi/linux/virtio_config.h1
-rw-r--r--tools/virtio/uapi/linux/virtio_ring.h4
-rw-r--r--tools/virtio/vhost_test/Makefile2
-rw-r--r--tools/virtio/vhost_test/vhost_test.c1
-rw-r--r--tools/virtio/virtio-trace/Makefile13
-rw-r--r--tools/virtio/virtio-trace/README118
-rw-r--r--tools/virtio/virtio-trace/trace-agent-ctl.c137
-rw-r--r--tools/virtio/virtio-trace/trace-agent-rw.c192
-rw-r--r--tools/virtio/virtio-trace/trace-agent.c270
-rw-r--r--tools/virtio/virtio-trace/trace-agent.h75
-rw-r--r--tools/virtio/virtio_test.c290
-rw-r--r--tools/virtio/vringh_test.c746
-rw-r--r--tools/vm/.gitignore2
-rw-r--r--tools/vm/Makefile22
-rw-r--r--tools/vm/page-types.c1176
-rw-r--r--tools/vm/slabinfo.c1393
979 files changed, 180290 insertions, 0 deletions
diff --git a/tools/Makefile b/tools/Makefile
new file mode 100644
index 00000000000..9a617adc667
--- /dev/null
+++ b/tools/Makefile
@@ -0,0 +1,119 @@
+include scripts/Makefile.include
+
+help:
+ @echo 'Possible targets:'
+ @echo ''
+ @echo ' acpi - ACPI tools'
+ @echo ' cgroup - cgroup tools'
+ @echo ' cpupower - a tool for all things x86 CPU power'
+ @echo ' firewire - the userspace part of nosy, an IEEE-1394 traffic sniffer'
+ @echo ' hv - tools used when in Hyper-V clients'
+ @echo ' lguest - a minimal 32-bit x86 hypervisor'
+ @echo ' perf - Linux performance measurement and analysis tool'
+ @echo ' selftests - various kernel selftests'
+ @echo ' turbostat - Intel CPU idle stats and freq reporting tool'
+ @echo ' usb - USB testing tools'
+ @echo ' virtio - vhost test module'
+ @echo ' net - misc networking tools'
+ @echo ' vm - misc vm tools'
+ @echo ' x86_energy_perf_policy - Intel energy policy tool'
+ @echo ' tmon - thermal monitoring and tuning tool'
+ @echo ''
+ @echo 'You can do:'
+ @echo ' $$ make -C tools/ <tool>_install'
+ @echo ''
+ @echo ' from the kernel command line to build and install one of'
+ @echo ' the tools above'
+ @echo ''
+ @echo ' $$ make tools/install'
+ @echo ''
+ @echo ' installs all tools.'
+ @echo ''
+ @echo 'Cleaning targets:'
+ @echo ''
+ @echo ' all of the above with the "_clean" string appended cleans'
+ @echo ' the respective build directory.'
+ @echo ' clean: a summary clean target to clean _all_ folders'
+
+acpi: FORCE
+ $(call descend,power/$@)
+
+cpupower: FORCE
+ $(call descend,power/$@)
+
+cgroup firewire hv guest usb virtio vm net: FORCE
+ $(call descend,$@)
+
+liblockdep: FORCE
+ $(call descend,lib/lockdep)
+
+libapikfs: FORCE
+ $(call descend,lib/api)
+
+perf: libapikfs FORCE
+ $(call descend,$@)
+
+selftests: FORCE
+ $(call descend,testing/$@)
+
+turbostat x86_energy_perf_policy: FORCE
+ $(call descend,power/x86/$@)
+
+tmon: FORCE
+ $(call descend,thermal/$@)
+
+acpi_install:
+ $(call descend,power/$(@:_install=),install)
+
+cpupower_install:
+ $(call descend,power/$(@:_install=),install)
+
+cgroup_install firewire_install hv_install lguest_install perf_install usb_install virtio_install vm_install net_install:
+ $(call descend,$(@:_install=),install)
+
+selftests_install:
+ $(call descend,testing/$(@:_clean=),install)
+
+turbostat_install x86_energy_perf_policy_install:
+ $(call descend,power/x86/$(@:_install=),install)
+
+tmon_install:
+ $(call descend,thermal/$(@:_install=),install)
+
+install: acpi_install cgroup_install cpupower_install hv_install firewire_install lguest_install \
+ perf_install selftests_install turbostat_install usb_install \
+ virtio_install vm_install net_install x86_energy_perf_policy_install \
+ tmon
+
+acpi_clean:
+ $(call descend,power/acpi,clean)
+
+cpupower_clean:
+ $(call descend,power/cpupower,clean)
+
+cgroup_clean hv_clean firewire_clean lguest_clean usb_clean virtio_clean vm_clean net_clean:
+ $(call descend,$(@:_clean=),clean)
+
+liblockdep_clean:
+ $(call descend,lib/lockdep,clean)
+
+libapikfs_clean:
+ $(call descend,lib/api,clean)
+
+perf_clean: libapikfs_clean
+ $(call descend,$(@:_clean=),clean)
+
+selftests_clean:
+ $(call descend,testing/$(@:_clean=),clean)
+
+turbostat_clean x86_energy_perf_policy_clean:
+ $(call descend,power/x86/$(@:_clean=),clean)
+
+tmon_clean:
+ $(call descend,thermal/tmon,clean)
+
+clean: acpi_clean cgroup_clean cpupower_clean hv_clean firewire_clean lguest_clean \
+ perf_clean selftests_clean turbostat_clean usb_clean virtio_clean \
+ vm_clean net_clean x86_energy_perf_policy_clean tmon_clean
+
+.PHONY: FORCE
diff --git a/tools/cgroup/.gitignore b/tools/cgroup/.gitignore
new file mode 100644
index 00000000000..633cd9b874f
--- /dev/null
+++ b/tools/cgroup/.gitignore
@@ -0,0 +1 @@
+cgroup_event_listener
diff --git a/tools/cgroup/Makefile b/tools/cgroup/Makefile
new file mode 100644
index 00000000000..b4286196b76
--- /dev/null
+++ b/tools/cgroup/Makefile
@@ -0,0 +1,11 @@
+# Makefile for cgroup tools
+
+CC = $(CROSS_COMPILE)gcc
+CFLAGS = -Wall -Wextra
+
+all: cgroup_event_listener
+%: %.c
+ $(CC) $(CFLAGS) -o $@ $^
+
+clean:
+ $(RM) cgroup_event_listener
diff --git a/tools/cgroup/cgroup_event_listener.c b/tools/cgroup/cgroup_event_listener.c
new file mode 100644
index 00000000000..4eb5507205c
--- /dev/null
+++ b/tools/cgroup/cgroup_event_listener.c
@@ -0,0 +1,82 @@
+/*
+ * cgroup_event_listener.c - Simple listener of cgroup events
+ *
+ * Copyright (C) Kirill A. Shutemov <kirill@shutemov.name>
+ */
+
+#include <assert.h>
+#include <err.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <libgen.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <sys/eventfd.h>
+
+#define USAGE_STR "Usage: cgroup_event_listener <path-to-control-file> <args>"
+
+int main(int argc, char **argv)
+{
+ int efd = -1;
+ int cfd = -1;
+ int event_control = -1;
+ char event_control_path[PATH_MAX];
+ char line[LINE_MAX];
+ int ret;
+
+ if (argc != 3)
+ errx(1, "%s", USAGE_STR);
+
+ cfd = open(argv[1], O_RDONLY);
+ if (cfd == -1)
+ err(1, "Cannot open %s", argv[1]);
+
+ ret = snprintf(event_control_path, PATH_MAX, "%s/cgroup.event_control",
+ dirname(argv[1]));
+ if (ret >= PATH_MAX)
+ errx(1, "Path to cgroup.event_control is too long");
+
+ event_control = open(event_control_path, O_WRONLY);
+ if (event_control == -1)
+ err(1, "Cannot open %s", event_control_path);
+
+ efd = eventfd(0, 0);
+ if (efd == -1)
+ err(1, "eventfd() failed");
+
+ ret = snprintf(line, LINE_MAX, "%d %d %s", efd, cfd, argv[2]);
+ if (ret >= LINE_MAX)
+ errx(1, "Arguments string is too long");
+
+ ret = write(event_control, line, strlen(line) + 1);
+ if (ret == -1)
+ err(1, "Cannot write to cgroup.event_control");
+
+ while (1) {
+ uint64_t result;
+
+ ret = read(efd, &result, sizeof(result));
+ if (ret == -1) {
+ if (errno == EINTR)
+ continue;
+ err(1, "Cannot read from eventfd");
+ }
+ assert(ret == sizeof(result));
+
+ ret = access(event_control_path, W_OK);
+ if ((ret == -1) && (errno == ENOENT)) {
+ puts("The cgroup seems to have removed.");
+ break;
+ }
+
+ if (ret == -1)
+ err(1, "cgroup.event_control is not accessible any more");
+
+ printf("%s %s: crossed\n", argv[1], argv[2]);
+ }
+
+ return 0;
+}
diff --git a/tools/firewire/Makefile b/tools/firewire/Makefile
new file mode 100644
index 00000000000..81767adaae7
--- /dev/null
+++ b/tools/firewire/Makefile
@@ -0,0 +1,19 @@
+prefix = /usr
+nosy-dump-version = 0.4
+
+CC = gcc
+
+all : nosy-dump
+
+nosy-dump : CFLAGS = -Wall -O2 -g
+nosy-dump : CPPFLAGS = -DVERSION=\"$(nosy-dump-version)\" -I../../drivers/firewire
+nosy-dump : LDFLAGS = -g
+nosy-dump : LDLIBS = -lpopt
+
+nosy-dump : nosy-dump.o decode-fcp.o
+
+clean :
+ rm -rf *.o nosy-dump
+
+install :
+ install nosy-dump $(prefix)/bin/nosy-dump
diff --git a/tools/firewire/decode-fcp.c b/tools/firewire/decode-fcp.c
new file mode 100644
index 00000000000..e41223b6a4c
--- /dev/null
+++ b/tools/firewire/decode-fcp.c
@@ -0,0 +1,213 @@
+#include <linux/firewire-constants.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "list.h"
+#include "nosy-dump.h"
+
+#define CSR_FCP_COMMAND 0xfffff0000b00ull
+#define CSR_FCP_RESPONSE 0xfffff0000d00ull
+
+static const char * const ctype_names[] = {
+ [0x0] = "control", [0x8] = "not implemented",
+ [0x1] = "status", [0x9] = "accepted",
+ [0x2] = "specific inquiry", [0xa] = "rejected",
+ [0x3] = "notify", [0xb] = "in transition",
+ [0x4] = "general inquiry", [0xc] = "stable",
+ [0x5] = "(reserved 0x05)", [0xd] = "changed",
+ [0x6] = "(reserved 0x06)", [0xe] = "(reserved 0x0e)",
+ [0x7] = "(reserved 0x07)", [0xf] = "interim",
+};
+
+static const char * const subunit_type_names[] = {
+ [0x00] = "monitor", [0x10] = "(reserved 0x10)",
+ [0x01] = "audio", [0x11] = "(reserved 0x11)",
+ [0x02] = "printer", [0x12] = "(reserved 0x12)",
+ [0x03] = "disc", [0x13] = "(reserved 0x13)",
+ [0x04] = "tape recorder/player",[0x14] = "(reserved 0x14)",
+ [0x05] = "tuner", [0x15] = "(reserved 0x15)",
+ [0x06] = "ca", [0x16] = "(reserved 0x16)",
+ [0x07] = "camera", [0x17] = "(reserved 0x17)",
+ [0x08] = "(reserved 0x08)", [0x18] = "(reserved 0x18)",
+ [0x09] = "panel", [0x19] = "(reserved 0x19)",
+ [0x0a] = "bulletin board", [0x1a] = "(reserved 0x1a)",
+ [0x0b] = "camera storage", [0x1b] = "(reserved 0x1b)",
+ [0x0c] = "(reserved 0x0c)", [0x1c] = "vendor unique",
+ [0x0d] = "(reserved 0x0d)", [0x1d] = "all subunit types",
+ [0x0e] = "(reserved 0x0e)", [0x1e] = "subunit_type extended to next byte",
+ [0x0f] = "(reserved 0x0f)", [0x1f] = "unit",
+};
+
+struct avc_enum {
+ int value;
+ const char *name;
+};
+
+struct avc_field {
+ const char *name; /* Short name for field. */
+ int offset; /* Location of field, specified in bits; */
+ /* negative means from end of packet. */
+ int width; /* Width of field, 0 means use data_length. */
+ struct avc_enum *names;
+};
+
+struct avc_opcode_info {
+ const char *name;
+ struct avc_field fields[8];
+};
+
+struct avc_enum power_field_names[] = {
+ { 0x70, "on" },
+ { 0x60, "off" },
+ { }
+};
+
+static const struct avc_opcode_info opcode_info[256] = {
+
+ /* TA Document 1999026 */
+ /* AV/C Digital Interface Command Set General Specification 4.0 */
+ [0xb2] = { "power", {
+ { "state", 0, 8, power_field_names }
+ }
+ },
+ [0x30] = { "unit info", {
+ { "foo", 0, 8 },
+ { "unit_type", 8, 5 },
+ { "unit", 13, 3 },
+ { "company id", 16, 24 },
+ }
+ },
+ [0x31] = { "subunit info" },
+ [0x01] = { "reserve" },
+ [0xb0] = { "version" },
+ [0x00] = { "vendor dependent" },
+ [0x02] = { "plug info" },
+ [0x12] = { "channel usage" },
+ [0x24] = { "connect" },
+ [0x20] = { "connect av" },
+ [0x22] = { "connections" },
+ [0x11] = { "digital input" },
+ [0x10] = { "digital output" },
+ [0x25] = { "disconnect" },
+ [0x21] = { "disconnect av" },
+ [0x19] = { "input plug signal format" },
+ [0x18] = { "output plug signal format" },
+ [0x1f] = { "general bus setup" },
+
+ /* TA Document 1999025 */
+ /* AV/C Descriptor Mechanism Specification Version 1.0 */
+ [0x0c] = { "create descriptor" },
+ [0x08] = { "open descriptor" },
+ [0x09] = { "read descriptor" },
+ [0x0a] = { "write descriptor" },
+ [0x05] = { "open info block" },
+ [0x06] = { "read info block" },
+ [0x07] = { "write info block" },
+ [0x0b] = { "search descriptor" },
+ [0x0d] = { "object number select" },
+
+ /* TA Document 1999015 */
+ /* AV/C Command Set for Rate Control of Isochronous Data Flow 1.0 */
+ [0xb3] = { "rate", {
+ { "subfunction", 0, 8 },
+ { "result", 8, 8 },
+ { "plug_type", 16, 8 },
+ { "plug_id", 16, 8 },
+ }
+ },
+
+ /* TA Document 1999008 */
+ /* AV/C Audio Subunit Specification 1.0 */
+ [0xb8] = { "function block" },
+
+ /* TA Document 2001001 */
+ /* AV/C Panel Subunit Specification 1.1 */
+ [0x7d] = { "gui update" },
+ [0x7e] = { "push gui data" },
+ [0x7f] = { "user action" },
+ [0x7c] = { "pass through" },
+
+ /* */
+ [0x26] = { "asynchronous connection" },
+};
+
+struct avc_frame {
+ uint32_t operand0:8;
+ uint32_t opcode:8;
+ uint32_t subunit_id:3;
+ uint32_t subunit_type:5;
+ uint32_t ctype:4;
+ uint32_t cts:4;
+};
+
+static void
+decode_avc(struct link_transaction *t)
+{
+ struct avc_frame *frame =
+ (struct avc_frame *) t->request->packet.write_block.data;
+ const struct avc_opcode_info *info;
+ const char *name;
+ char buffer[32];
+ int i;
+
+ info = &opcode_info[frame->opcode];
+ if (info->name == NULL) {
+ snprintf(buffer, sizeof(buffer),
+ "(unknown opcode 0x%02x)", frame->opcode);
+ name = buffer;
+ } else {
+ name = info->name;
+ }
+
+ printf("av/c %s, subunit_type=%s, subunit_id=%d, opcode=%s",
+ ctype_names[frame->ctype], subunit_type_names[frame->subunit_type],
+ frame->subunit_id, name);
+
+ for (i = 0; info->fields[i].name != NULL; i++)
+ printf(", %s", info->fields[i].name);
+
+ printf("\n");
+}
+
+int
+decode_fcp(struct link_transaction *t)
+{
+ struct avc_frame *frame =
+ (struct avc_frame *) t->request->packet.write_block.data;
+ unsigned long long offset =
+ ((unsigned long long) t->request->packet.common.offset_high << 32) |
+ t->request->packet.common.offset_low;
+
+ if (t->request->packet.common.tcode != TCODE_WRITE_BLOCK_REQUEST)
+ return 0;
+
+ if (offset == CSR_FCP_COMMAND || offset == CSR_FCP_RESPONSE) {
+ switch (frame->cts) {
+ case 0x00:
+ decode_avc(t);
+ break;
+ case 0x01:
+ printf("cal fcp frame (cts=0x01)\n");
+ break;
+ case 0x02:
+ printf("ehs fcp frame (cts=0x02)\n");
+ break;
+ case 0x03:
+ printf("havi fcp frame (cts=0x03)\n");
+ break;
+ case 0x0e:
+ printf("vendor specific fcp frame (cts=0x0e)\n");
+ break;
+ case 0x0f:
+ printf("extended cts\n");
+ break;
+ default:
+ printf("reserved fcp frame (ctx=0x%02x)\n", frame->cts);
+ break;
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
diff --git a/tools/firewire/list.h b/tools/firewire/list.h
new file mode 100644
index 00000000000..41f4bdadf63
--- /dev/null
+++ b/tools/firewire/list.h
@@ -0,0 +1,62 @@
+struct list {
+ struct list *next, *prev;
+};
+
+static inline void
+list_init(struct list *list)
+{
+ list->next = list;
+ list->prev = list;
+}
+
+static inline int
+list_empty(struct list *list)
+{
+ return list->next == list;
+}
+
+static inline void
+list_insert(struct list *link, struct list *new_link)
+{
+ new_link->prev = link->prev;
+ new_link->next = link;
+ new_link->prev->next = new_link;
+ new_link->next->prev = new_link;
+}
+
+static inline void
+list_append(struct list *list, struct list *new_link)
+{
+ list_insert((struct list *)list, new_link);
+}
+
+static inline void
+list_prepend(struct list *list, struct list *new_link)
+{
+ list_insert(list->next, new_link);
+}
+
+static inline void
+list_remove(struct list *link)
+{
+ link->prev->next = link->next;
+ link->next->prev = link->prev;
+}
+
+#define list_entry(link, type, member) \
+ ((type *)((char *)(link)-(unsigned long)(&((type *)0)->member)))
+
+#define list_head(list, type, member) \
+ list_entry((list)->next, type, member)
+
+#define list_tail(list, type, member) \
+ list_entry((list)->prev, type, member)
+
+#define list_next(elm, member) \
+ list_entry((elm)->member.next, typeof(*elm), member)
+
+#define list_for_each_entry(pos, list, member) \
+ for (pos = list_head(list, typeof(*pos), member); \
+ &pos->member != (list); \
+ pos = list_next(pos, member))
+
diff --git a/tools/firewire/nosy-dump.c b/tools/firewire/nosy-dump.c
new file mode 100644
index 00000000000..3179c711bd6
--- /dev/null
+++ b/tools/firewire/nosy-dump.c
@@ -0,0 +1,1035 @@
+/*
+ * nosy-dump - Interface to snoop mode driver for TI PCILynx 1394 controllers
+ * Copyright (C) 2002-2006 Kristian Høgsberg
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <byteswap.h>
+#include <endian.h>
+#include <fcntl.h>
+#include <linux/firewire-constants.h>
+#include <poll.h>
+#include <popt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <termios.h>
+#include <unistd.h>
+
+#include "list.h"
+#include "nosy-dump.h"
+#include "nosy-user.h"
+
+enum {
+ PACKET_FIELD_DETAIL = 0x01,
+ PACKET_FIELD_DATA_LENGTH = 0x02,
+ /* Marks the fields we print in transaction view. */
+ PACKET_FIELD_TRANSACTION = 0x04,
+};
+
+static void print_packet(uint32_t *data, size_t length);
+static void decode_link_packet(struct link_packet *packet, size_t length,
+ int include_flags, int exclude_flags);
+static int run = 1;
+sig_t sys_sigint_handler;
+
+static char *option_nosy_device = "/dev/nosy";
+static char *option_view = "packet";
+static char *option_output;
+static char *option_input;
+static int option_hex;
+static int option_iso;
+static int option_cycle_start;
+static int option_version;
+static int option_verbose;
+
+enum {
+ VIEW_TRANSACTION,
+ VIEW_PACKET,
+ VIEW_STATS,
+};
+
+static const struct poptOption options[] = {
+ {
+ .longName = "device",
+ .shortName = 'd',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &option_nosy_device,
+ .descrip = "Path to nosy device.",
+ .argDescrip = "DEVICE"
+ },
+ {
+ .longName = "view",
+ .argInfo = POPT_ARG_STRING,
+ .arg = &option_view,
+ .descrip = "Specify view of bus traffic: packet, transaction or stats.",
+ .argDescrip = "VIEW"
+ },
+ {
+ .longName = "hex",
+ .shortName = 'x',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &option_hex,
+ .descrip = "Print each packet in hex.",
+ },
+ {
+ .longName = "iso",
+ .argInfo = POPT_ARG_NONE,
+ .arg = &option_iso,
+ .descrip = "Print iso packets.",
+ },
+ {
+ .longName = "cycle-start",
+ .argInfo = POPT_ARG_NONE,
+ .arg = &option_cycle_start,
+ .descrip = "Print cycle start packets.",
+ },
+ {
+ .longName = "verbose",
+ .shortName = 'v',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &option_verbose,
+ .descrip = "Verbose packet view.",
+ },
+ {
+ .longName = "output",
+ .shortName = 'o',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &option_output,
+ .descrip = "Log to output file.",
+ .argDescrip = "FILENAME"
+ },
+ {
+ .longName = "input",
+ .shortName = 'i',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &option_input,
+ .descrip = "Decode log from file.",
+ .argDescrip = "FILENAME"
+ },
+ {
+ .longName = "version",
+ .argInfo = POPT_ARG_NONE,
+ .arg = &option_version,
+ .descrip = "Specify print version info.",
+ },
+ POPT_AUTOHELP
+ POPT_TABLEEND
+};
+
+/* Allow all ^C except the first to interrupt the program in the usual way. */
+static void
+sigint_handler(int signal_num)
+{
+ if (run == 1) {
+ run = 0;
+ signal(SIGINT, SIG_DFL);
+ }
+}
+
+static struct subaction *
+subaction_create(uint32_t *data, size_t length)
+{
+ struct subaction *sa;
+
+ /* we put the ack in the subaction struct for easy access. */
+ sa = malloc(sizeof *sa - sizeof sa->packet + length);
+ if (!sa)
+ exit(EXIT_FAILURE);
+ sa->ack = data[length / 4 - 1];
+ sa->length = length;
+ memcpy(&sa->packet, data, length);
+
+ return sa;
+}
+
+static void
+subaction_destroy(struct subaction *sa)
+{
+ free(sa);
+}
+
+static struct list pending_transaction_list = {
+ &pending_transaction_list, &pending_transaction_list
+};
+
+static struct link_transaction *
+link_transaction_lookup(int request_node, int response_node, int tlabel)
+{
+ struct link_transaction *t;
+
+ list_for_each_entry(t, &pending_transaction_list, link) {
+ if (t->request_node == request_node &&
+ t->response_node == response_node &&
+ t->tlabel == tlabel)
+ return t;
+ }
+
+ t = malloc(sizeof *t);
+ if (!t)
+ exit(EXIT_FAILURE);
+ t->request_node = request_node;
+ t->response_node = response_node;
+ t->tlabel = tlabel;
+ list_init(&t->request_list);
+ list_init(&t->response_list);
+
+ list_append(&pending_transaction_list, &t->link);
+
+ return t;
+}
+
+static void
+link_transaction_destroy(struct link_transaction *t)
+{
+ struct subaction *sa;
+
+ while (!list_empty(&t->request_list)) {
+ sa = list_head(&t->request_list, struct subaction, link);
+ list_remove(&sa->link);
+ subaction_destroy(sa);
+ }
+ while (!list_empty(&t->response_list)) {
+ sa = list_head(&t->response_list, struct subaction, link);
+ list_remove(&sa->link);
+ subaction_destroy(sa);
+ }
+ free(t);
+}
+
+struct protocol_decoder {
+ const char *name;
+ int (*decode)(struct link_transaction *t);
+};
+
+static const struct protocol_decoder protocol_decoders[] = {
+ { "FCP", decode_fcp }
+};
+
+static void
+handle_transaction(struct link_transaction *t)
+{
+ struct subaction *sa;
+ int i;
+
+ if (!t->request) {
+ printf("BUG in handle_transaction\n");
+ return;
+ }
+
+ for (i = 0; i < array_length(protocol_decoders); i++)
+ if (protocol_decoders[i].decode(t))
+ break;
+
+ /* HACK: decode only fcp right now. */
+ return;
+
+ decode_link_packet(&t->request->packet, t->request->length,
+ PACKET_FIELD_TRANSACTION, 0);
+ if (t->response)
+ decode_link_packet(&t->response->packet, t->request->length,
+ PACKET_FIELD_TRANSACTION, 0);
+ else
+ printf("[no response]");
+
+ if (option_verbose) {
+ list_for_each_entry(sa, &t->request_list, link)
+ print_packet((uint32_t *) &sa->packet, sa->length);
+ list_for_each_entry(sa, &t->response_list, link)
+ print_packet((uint32_t *) &sa->packet, sa->length);
+ }
+ printf("\r\n");
+
+ link_transaction_destroy(t);
+}
+
+static void
+clear_pending_transaction_list(void)
+{
+ struct link_transaction *t;
+
+ while (!list_empty(&pending_transaction_list)) {
+ t = list_head(&pending_transaction_list,
+ struct link_transaction, link);
+ list_remove(&t->link);
+ link_transaction_destroy(t);
+ /* print unfinished transactions */
+ }
+}
+
+static const char * const tcode_names[] = {
+ [0x0] = "write_quadlet_request", [0x6] = "read_quadlet_response",
+ [0x1] = "write_block_request", [0x7] = "read_block_response",
+ [0x2] = "write_response", [0x8] = "cycle_start",
+ [0x3] = "reserved", [0x9] = "lock_request",
+ [0x4] = "read_quadlet_request", [0xa] = "iso_data",
+ [0x5] = "read_block_request", [0xb] = "lock_response",
+};
+
+static const char * const ack_names[] = {
+ [0x0] = "no ack", [0x8] = "reserved (0x08)",
+ [0x1] = "ack_complete", [0x9] = "reserved (0x09)",
+ [0x2] = "ack_pending", [0xa] = "reserved (0x0a)",
+ [0x3] = "reserved (0x03)", [0xb] = "reserved (0x0b)",
+ [0x4] = "ack_busy_x", [0xc] = "reserved (0x0c)",
+ [0x5] = "ack_busy_a", [0xd] = "ack_data_error",
+ [0x6] = "ack_busy_b", [0xe] = "ack_type_error",
+ [0x7] = "reserved (0x07)", [0xf] = "reserved (0x0f)",
+};
+
+static const char * const rcode_names[] = {
+ [0x0] = "complete", [0x4] = "conflict_error",
+ [0x1] = "reserved (0x01)", [0x5] = "data_error",
+ [0x2] = "reserved (0x02)", [0x6] = "type_error",
+ [0x3] = "reserved (0x03)", [0x7] = "address_error",
+};
+
+static const char * const retry_names[] = {
+ [0x0] = "retry_1",
+ [0x1] = "retry_x",
+ [0x2] = "retry_a",
+ [0x3] = "retry_b",
+};
+
+enum {
+ PACKET_RESERVED,
+ PACKET_REQUEST,
+ PACKET_RESPONSE,
+ PACKET_OTHER,
+};
+
+struct packet_info {
+ const char *name;
+ int type;
+ int response_tcode;
+ const struct packet_field *fields;
+ int field_count;
+};
+
+struct packet_field {
+ const char *name; /* Short name for field. */
+ int offset; /* Location of field, specified in bits; */
+ /* negative means from end of packet. */
+ int width; /* Width of field, 0 means use data_length. */
+ int flags; /* Show options. */
+ const char * const *value_names;
+};
+
+#define COMMON_REQUEST_FIELDS \
+ { "dest", 0, 16, PACKET_FIELD_TRANSACTION }, \
+ { "tl", 16, 6 }, \
+ { "rt", 22, 2, PACKET_FIELD_DETAIL, retry_names }, \
+ { "tcode", 24, 4, PACKET_FIELD_TRANSACTION, tcode_names }, \
+ { "pri", 28, 4, PACKET_FIELD_DETAIL }, \
+ { "src", 32, 16, PACKET_FIELD_TRANSACTION }, \
+ { "offs", 48, 48, PACKET_FIELD_TRANSACTION }
+
+#define COMMON_RESPONSE_FIELDS \
+ { "dest", 0, 16 }, \
+ { "tl", 16, 6 }, \
+ { "rt", 22, 2, PACKET_FIELD_DETAIL, retry_names }, \
+ { "tcode", 24, 4, 0, tcode_names }, \
+ { "pri", 28, 4, PACKET_FIELD_DETAIL }, \
+ { "src", 32, 16 }, \
+ { "rcode", 48, 4, PACKET_FIELD_TRANSACTION, rcode_names }
+
+static const struct packet_field read_quadlet_request_fields[] = {
+ COMMON_REQUEST_FIELDS,
+ { "crc", 96, 32, PACKET_FIELD_DETAIL },
+ { "ack", 156, 4, 0, ack_names },
+};
+
+static const struct packet_field read_quadlet_response_fields[] = {
+ COMMON_RESPONSE_FIELDS,
+ { "data", 96, 32, PACKET_FIELD_TRANSACTION },
+ { "crc", 128, 32, PACKET_FIELD_DETAIL },
+ { "ack", 188, 4, 0, ack_names },
+};
+
+static const struct packet_field read_block_request_fields[] = {
+ COMMON_REQUEST_FIELDS,
+ { "data_length", 96, 16, PACKET_FIELD_TRANSACTION },
+ { "extended_tcode", 112, 16 },
+ { "crc", 128, 32, PACKET_FIELD_DETAIL },
+ { "ack", 188, 4, 0, ack_names },
+};
+
+static const struct packet_field block_response_fields[] = {
+ COMMON_RESPONSE_FIELDS,
+ { "data_length", 96, 16, PACKET_FIELD_DATA_LENGTH },
+ { "extended_tcode", 112, 16 },
+ { "crc", 128, 32, PACKET_FIELD_DETAIL },
+ { "data", 160, 0, PACKET_FIELD_TRANSACTION },
+ { "crc", -64, 32, PACKET_FIELD_DETAIL },
+ { "ack", -4, 4, 0, ack_names },
+};
+
+static const struct packet_field write_quadlet_request_fields[] = {
+ COMMON_REQUEST_FIELDS,
+ { "data", 96, 32, PACKET_FIELD_TRANSACTION },
+ { "ack", -4, 4, 0, ack_names },
+};
+
+static const struct packet_field block_request_fields[] = {
+ COMMON_REQUEST_FIELDS,
+ { "data_length", 96, 16, PACKET_FIELD_DATA_LENGTH | PACKET_FIELD_TRANSACTION },
+ { "extended_tcode", 112, 16, PACKET_FIELD_TRANSACTION },
+ { "crc", 128, 32, PACKET_FIELD_DETAIL },
+ { "data", 160, 0, PACKET_FIELD_TRANSACTION },
+ { "crc", -64, 32, PACKET_FIELD_DETAIL },
+ { "ack", -4, 4, 0, ack_names },
+};
+
+static const struct packet_field write_response_fields[] = {
+ COMMON_RESPONSE_FIELDS,
+ { "reserved", 64, 32, PACKET_FIELD_DETAIL },
+ { "ack", -4, 4, 0, ack_names },
+};
+
+static const struct packet_field iso_data_fields[] = {
+ { "data_length", 0, 16, PACKET_FIELD_DATA_LENGTH },
+ { "tag", 16, 2 },
+ { "channel", 18, 6 },
+ { "tcode", 24, 4, 0, tcode_names },
+ { "sy", 28, 4 },
+ { "crc", 32, 32, PACKET_FIELD_DETAIL },
+ { "data", 64, 0 },
+ { "crc", -64, 32, PACKET_FIELD_DETAIL },
+ { "ack", -4, 4, 0, ack_names },
+};
+
+static const struct packet_info packet_info[] = {
+ {
+ .name = "write_quadlet_request",
+ .type = PACKET_REQUEST,
+ .response_tcode = TCODE_WRITE_RESPONSE,
+ .fields = write_quadlet_request_fields,
+ .field_count = array_length(write_quadlet_request_fields)
+ },
+ {
+ .name = "write_block_request",
+ .type = PACKET_REQUEST,
+ .response_tcode = TCODE_WRITE_RESPONSE,
+ .fields = block_request_fields,
+ .field_count = array_length(block_request_fields)
+ },
+ {
+ .name = "write_response",
+ .type = PACKET_RESPONSE,
+ .fields = write_response_fields,
+ .field_count = array_length(write_response_fields)
+ },
+ {
+ .name = "reserved",
+ .type = PACKET_RESERVED,
+ },
+ {
+ .name = "read_quadlet_request",
+ .type = PACKET_REQUEST,
+ .response_tcode = TCODE_READ_QUADLET_RESPONSE,
+ .fields = read_quadlet_request_fields,
+ .field_count = array_length(read_quadlet_request_fields)
+ },
+ {
+ .name = "read_block_request",
+ .type = PACKET_REQUEST,
+ .response_tcode = TCODE_READ_BLOCK_RESPONSE,
+ .fields = read_block_request_fields,
+ .field_count = array_length(read_block_request_fields)
+ },
+ {
+ .name = "read_quadlet_response",
+ .type = PACKET_RESPONSE,
+ .fields = read_quadlet_response_fields,
+ .field_count = array_length(read_quadlet_response_fields)
+ },
+ {
+ .name = "read_block_response",
+ .type = PACKET_RESPONSE,
+ .fields = block_response_fields,
+ .field_count = array_length(block_response_fields)
+ },
+ {
+ .name = "cycle_start",
+ .type = PACKET_OTHER,
+ .fields = write_quadlet_request_fields,
+ .field_count = array_length(write_quadlet_request_fields)
+ },
+ {
+ .name = "lock_request",
+ .type = PACKET_REQUEST,
+ .fields = block_request_fields,
+ .field_count = array_length(block_request_fields)
+ },
+ {
+ .name = "iso_data",
+ .type = PACKET_OTHER,
+ .fields = iso_data_fields,
+ .field_count = array_length(iso_data_fields)
+ },
+ {
+ .name = "lock_response",
+ .type = PACKET_RESPONSE,
+ .fields = block_response_fields,
+ .field_count = array_length(block_response_fields)
+ },
+};
+
+static int
+handle_request_packet(uint32_t *data, size_t length)
+{
+ struct link_packet *p = (struct link_packet *) data;
+ struct subaction *sa, *prev;
+ struct link_transaction *t;
+
+ t = link_transaction_lookup(p->common.source, p->common.destination,
+ p->common.tlabel);
+ sa = subaction_create(data, length);
+ t->request = sa;
+
+ if (!list_empty(&t->request_list)) {
+ prev = list_tail(&t->request_list,
+ struct subaction, link);
+
+ if (!ACK_BUSY(prev->ack)) {
+ /*
+ * error, we should only see ack_busy_* before the
+ * ack_pending/ack_complete -- this is an ack_pending
+ * instead (ack_complete would have finished the
+ * transaction).
+ */
+ }
+
+ if (prev->packet.common.tcode != sa->packet.common.tcode ||
+ prev->packet.common.tlabel != sa->packet.common.tlabel) {
+ /* memcmp() ? */
+ /* error, these should match for retries. */
+ }
+ }
+
+ list_append(&t->request_list, &sa->link);
+
+ switch (sa->ack) {
+ case ACK_COMPLETE:
+ if (p->common.tcode != TCODE_WRITE_QUADLET_REQUEST &&
+ p->common.tcode != TCODE_WRITE_BLOCK_REQUEST)
+ /* error, unified transactions only allowed for write */;
+ list_remove(&t->link);
+ handle_transaction(t);
+ break;
+
+ case ACK_NO_ACK:
+ case ACK_DATA_ERROR:
+ case ACK_TYPE_ERROR:
+ list_remove(&t->link);
+ handle_transaction(t);
+ break;
+
+ case ACK_PENDING:
+ /* request subaction phase over, wait for response. */
+ break;
+
+ case ACK_BUSY_X:
+ case ACK_BUSY_A:
+ case ACK_BUSY_B:
+ /* ok, wait for retry. */
+ /* check that retry protocol is respected. */
+ break;
+ }
+
+ return 1;
+}
+
+static int
+handle_response_packet(uint32_t *data, size_t length)
+{
+ struct link_packet *p = (struct link_packet *) data;
+ struct subaction *sa, *prev;
+ struct link_transaction *t;
+
+ t = link_transaction_lookup(p->common.destination, p->common.source,
+ p->common.tlabel);
+ if (list_empty(&t->request_list)) {
+ /* unsolicited response */
+ }
+
+ sa = subaction_create(data, length);
+ t->response = sa;
+
+ if (!list_empty(&t->response_list)) {
+ prev = list_tail(&t->response_list, struct subaction, link);
+
+ if (!ACK_BUSY(prev->ack)) {
+ /*
+ * error, we should only see ack_busy_* before the
+ * ack_pending/ack_complete
+ */
+ }
+
+ if (prev->packet.common.tcode != sa->packet.common.tcode ||
+ prev->packet.common.tlabel != sa->packet.common.tlabel) {
+ /* use memcmp() instead? */
+ /* error, these should match for retries. */
+ }
+ } else {
+ prev = list_tail(&t->request_list, struct subaction, link);
+ if (prev->ack != ACK_PENDING) {
+ /*
+ * error, should not get response unless last request got
+ * ack_pending.
+ */
+ }
+
+ if (packet_info[prev->packet.common.tcode].response_tcode !=
+ sa->packet.common.tcode) {
+ /* error, tcode mismatch */
+ }
+ }
+
+ list_append(&t->response_list, &sa->link);
+
+ switch (sa->ack) {
+ case ACK_COMPLETE:
+ case ACK_NO_ACK:
+ case ACK_DATA_ERROR:
+ case ACK_TYPE_ERROR:
+ list_remove(&t->link);
+ handle_transaction(t);
+ /* transaction complete, remove t from pending list. */
+ break;
+
+ case ACK_PENDING:
+ /* error for responses. */
+ break;
+
+ case ACK_BUSY_X:
+ case ACK_BUSY_A:
+ case ACK_BUSY_B:
+ /* no problem, wait for next retry */
+ break;
+ }
+
+ return 1;
+}
+
+static int
+handle_packet(uint32_t *data, size_t length)
+{
+ if (length == 0) {
+ printf("bus reset\r\n");
+ clear_pending_transaction_list();
+ } else if (length > sizeof(struct phy_packet)) {
+ struct link_packet *p = (struct link_packet *) data;
+
+ switch (packet_info[p->common.tcode].type) {
+ case PACKET_REQUEST:
+ return handle_request_packet(data, length);
+
+ case PACKET_RESPONSE:
+ return handle_response_packet(data, length);
+
+ case PACKET_OTHER:
+ case PACKET_RESERVED:
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static unsigned int
+get_bits(struct link_packet *packet, int offset, int width)
+{
+ uint32_t *data = (uint32_t *) packet;
+ uint32_t index, shift, mask;
+
+ index = offset / 32 + 1;
+ shift = 32 - (offset & 31) - width;
+ mask = width == 32 ? ~0 : (1 << width) - 1;
+
+ return (data[index] >> shift) & mask;
+}
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define byte_index(i) ((i) ^ 3)
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define byte_index(i) (i)
+#else
+#error unsupported byte order.
+#endif
+
+static void
+dump_data(unsigned char *data, int length)
+{
+ int i, print_length;
+
+ if (length > 128)
+ print_length = 128;
+ else
+ print_length = length;
+
+ for (i = 0; i < print_length; i++)
+ printf("%s%02hhx",
+ (i % 4 == 0 && i != 0) ? " " : "",
+ data[byte_index(i)]);
+
+ if (print_length < length)
+ printf(" (%d more bytes)", length - print_length);
+}
+
+static void
+decode_link_packet(struct link_packet *packet, size_t length,
+ int include_flags, int exclude_flags)
+{
+ const struct packet_info *pi;
+ int data_length = 0;
+ int i;
+
+ pi = &packet_info[packet->common.tcode];
+
+ for (i = 0; i < pi->field_count; i++) {
+ const struct packet_field *f = &pi->fields[i];
+ int offset;
+
+ if (f->flags & exclude_flags)
+ continue;
+ if (include_flags && !(f->flags & include_flags))
+ continue;
+
+ if (f->offset < 0)
+ offset = length * 8 + f->offset - 32;
+ else
+ offset = f->offset;
+
+ if (f->value_names != NULL) {
+ uint32_t bits;
+
+ bits = get_bits(packet, offset, f->width);
+ printf("%s", f->value_names[bits]);
+ } else if (f->width == 0) {
+ printf("%s=[", f->name);
+ dump_data((unsigned char *) packet + (offset / 8 + 4), data_length);
+ printf("]");
+ } else {
+ unsigned long long bits;
+ int high_width, low_width;
+
+ if ((offset & ~31) != ((offset + f->width - 1) & ~31)) {
+ /* Bit field spans quadlet boundary. */
+ high_width = ((offset + 31) & ~31) - offset;
+ low_width = f->width - high_width;
+
+ bits = get_bits(packet, offset, high_width);
+ bits = (bits << low_width) |
+ get_bits(packet, offset + high_width, low_width);
+ } else {
+ bits = get_bits(packet, offset, f->width);
+ }
+
+ printf("%s=0x%0*llx", f->name, (f->width + 3) / 4, bits);
+
+ if (f->flags & PACKET_FIELD_DATA_LENGTH)
+ data_length = bits;
+ }
+
+ if (i < pi->field_count - 1)
+ printf(", ");
+ }
+}
+
+static void
+print_packet(uint32_t *data, size_t length)
+{
+ int i;
+
+ printf("%6u ", data[0]);
+
+ if (length == 4) {
+ printf("bus reset");
+ } else if (length < sizeof(struct phy_packet)) {
+ printf("short packet: ");
+ for (i = 1; i < length / 4; i++)
+ printf("%s%08x", i == 0 ? "[" : " ", data[i]);
+ printf("]");
+
+ } else if (length == sizeof(struct phy_packet) && data[1] == ~data[2]) {
+ struct phy_packet *pp = (struct phy_packet *) data;
+
+ /* phy packet are 3 quadlets: the 1 quadlet payload,
+ * the bitwise inverse of the payload and the snoop
+ * mode ack */
+
+ switch (pp->common.identifier) {
+ case PHY_PACKET_CONFIGURATION:
+ if (!pp->phy_config.set_root && !pp->phy_config.set_gap_count) {
+ printf("ext phy config: phy_id=%02x", pp->phy_config.root_id);
+ } else {
+ printf("phy config:");
+ if (pp->phy_config.set_root)
+ printf(" set_root_id=%02x", pp->phy_config.root_id);
+ if (pp->phy_config.set_gap_count)
+ printf(" set_gap_count=%d", pp->phy_config.gap_count);
+ }
+ break;
+
+ case PHY_PACKET_LINK_ON:
+ printf("link-on packet, phy_id=%02x", pp->link_on.phy_id);
+ break;
+
+ case PHY_PACKET_SELF_ID:
+ if (pp->self_id.extended) {
+ printf("extended self id: phy_id=%02x, seq=%d",
+ pp->ext_self_id.phy_id, pp->ext_self_id.sequence);
+ } else {
+ static const char * const speed_names[] = {
+ "S100", "S200", "S400", "BETA"
+ };
+ printf("self id: phy_id=%02x, link %s, gap_count=%d, speed=%s%s%s",
+ pp->self_id.phy_id,
+ (pp->self_id.link_active ? "active" : "not active"),
+ pp->self_id.gap_count,
+ speed_names[pp->self_id.phy_speed],
+ (pp->self_id.contender ? ", irm contender" : ""),
+ (pp->self_id.initiated_reset ? ", initiator" : ""));
+ }
+ break;
+ default:
+ printf("unknown phy packet: ");
+ for (i = 1; i < length / 4; i++)
+ printf("%s%08x", i == 0 ? "[" : " ", data[i]);
+ printf("]");
+ break;
+ }
+ } else {
+ struct link_packet *packet = (struct link_packet *) data;
+
+ decode_link_packet(packet, length, 0,
+ option_verbose ? 0 : PACKET_FIELD_DETAIL);
+ }
+
+ if (option_hex) {
+ printf(" [");
+ dump_data((unsigned char *) data + 4, length - 4);
+ printf("]");
+ }
+
+ printf("\r\n");
+}
+
+#define HIDE_CURSOR "\033[?25l"
+#define SHOW_CURSOR "\033[?25h"
+#define CLEAR "\033[H\033[2J"
+
+static void
+print_stats(uint32_t *data, size_t length)
+{
+ static int bus_reset_count, short_packet_count, phy_packet_count;
+ static int tcode_count[16];
+ static struct timeval last_update;
+ struct timeval now;
+ int i;
+
+ if (length == 0)
+ bus_reset_count++;
+ else if (length < sizeof(struct phy_packet))
+ short_packet_count++;
+ else if (length == sizeof(struct phy_packet) && data[1] == ~data[2])
+ phy_packet_count++;
+ else {
+ struct link_packet *packet = (struct link_packet *) data;
+ tcode_count[packet->common.tcode]++;
+ }
+
+ gettimeofday(&now, NULL);
+ if (now.tv_sec <= last_update.tv_sec &&
+ now.tv_usec < last_update.tv_usec + 500000)
+ return;
+
+ last_update = now;
+ printf(CLEAR HIDE_CURSOR
+ " bus resets : %8d\n"
+ " short packets : %8d\n"
+ " phy packets : %8d\n",
+ bus_reset_count, short_packet_count, phy_packet_count);
+
+ for (i = 0; i < array_length(packet_info); i++)
+ if (packet_info[i].type != PACKET_RESERVED)
+ printf(" %-24s: %8d\n", packet_info[i].name, tcode_count[i]);
+ printf(SHOW_CURSOR "\n");
+}
+
+static struct termios saved_attributes;
+
+static void
+reset_input_mode(void)
+{
+ tcsetattr(STDIN_FILENO, TCSANOW, &saved_attributes);
+}
+
+static void
+set_input_mode(void)
+{
+ struct termios tattr;
+
+ /* Make sure stdin is a terminal. */
+ if (!isatty(STDIN_FILENO)) {
+ fprintf(stderr, "Not a terminal.\n");
+ exit(EXIT_FAILURE);
+ }
+
+ /* Save the terminal attributes so we can restore them later. */
+ tcgetattr(STDIN_FILENO, &saved_attributes);
+ atexit(reset_input_mode);
+
+ /* Set the funny terminal modes. */
+ tcgetattr(STDIN_FILENO, &tattr);
+ tattr.c_lflag &= ~(ICANON|ECHO); /* Clear ICANON and ECHO. */
+ tattr.c_cc[VMIN] = 1;
+ tattr.c_cc[VTIME] = 0;
+ tcsetattr(STDIN_FILENO, TCSAFLUSH, &tattr);
+}
+
+int main(int argc, const char *argv[])
+{
+ uint32_t buf[128 * 1024];
+ uint32_t filter;
+ int length, retval, view;
+ int fd = -1;
+ FILE *output = NULL, *input = NULL;
+ poptContext con;
+ char c;
+ struct pollfd pollfds[2];
+
+ sys_sigint_handler = signal(SIGINT, sigint_handler);
+
+ con = poptGetContext(NULL, argc, argv, options, 0);
+ retval = poptGetNextOpt(con);
+ if (retval < -1) {
+ poptPrintUsage(con, stdout, 0);
+ return -1;
+ }
+
+ if (option_version) {
+ printf("dump tool for nosy sniffer, version %s\n", VERSION);
+ return 0;
+ }
+
+ if (__BYTE_ORDER != __LITTLE_ENDIAN)
+ fprintf(stderr, "warning: nosy has only been tested on little "
+ "endian machines\n");
+
+ if (option_input != NULL) {
+ input = fopen(option_input, "r");
+ if (input == NULL) {
+ fprintf(stderr, "Could not open %s, %m\n", option_input);
+ return -1;
+ }
+ } else {
+ fd = open(option_nosy_device, O_RDWR);
+ if (fd < 0) {
+ fprintf(stderr, "Could not open %s, %m\n", option_nosy_device);
+ return -1;
+ }
+ set_input_mode();
+ }
+
+ if (strcmp(option_view, "transaction") == 0)
+ view = VIEW_TRANSACTION;
+ else if (strcmp(option_view, "stats") == 0)
+ view = VIEW_STATS;
+ else
+ view = VIEW_PACKET;
+
+ if (option_output) {
+ output = fopen(option_output, "w");
+ if (output == NULL) {
+ fprintf(stderr, "Could not open %s, %m\n", option_output);
+ return -1;
+ }
+ }
+
+ setvbuf(stdout, NULL, _IOLBF, BUFSIZ);
+
+ filter = ~0;
+ if (!option_iso)
+ filter &= ~(1 << TCODE_STREAM_DATA);
+ if (!option_cycle_start)
+ filter &= ~(1 << TCODE_CYCLE_START);
+ if (view == VIEW_STATS)
+ filter = ~(1 << TCODE_CYCLE_START);
+
+ ioctl(fd, NOSY_IOC_FILTER, filter);
+
+ ioctl(fd, NOSY_IOC_START);
+
+ pollfds[0].fd = fd;
+ pollfds[0].events = POLLIN;
+ pollfds[1].fd = STDIN_FILENO;
+ pollfds[1].events = POLLIN;
+
+ while (run) {
+ if (input != NULL) {
+ if (fread(&length, sizeof length, 1, input) != 1)
+ return 0;
+ fread(buf, 1, length, input);
+ } else {
+ poll(pollfds, 2, -1);
+ if (pollfds[1].revents) {
+ read(STDIN_FILENO, &c, sizeof c);
+ switch (c) {
+ case 'q':
+ if (output != NULL)
+ fclose(output);
+ return 0;
+ }
+ }
+
+ if (pollfds[0].revents)
+ length = read(fd, buf, sizeof buf);
+ else
+ continue;
+ }
+
+ if (output != NULL) {
+ fwrite(&length, sizeof length, 1, output);
+ fwrite(buf, 1, length, output);
+ }
+
+ switch (view) {
+ case VIEW_TRANSACTION:
+ handle_packet(buf, length);
+ break;
+ case VIEW_PACKET:
+ print_packet(buf, length);
+ break;
+ case VIEW_STATS:
+ print_stats(buf, length);
+ break;
+ }
+ }
+
+ if (output != NULL)
+ fclose(output);
+
+ close(fd);
+
+ poptFreeContext(con);
+
+ return 0;
+}
diff --git a/tools/firewire/nosy-dump.h b/tools/firewire/nosy-dump.h
new file mode 100644
index 00000000000..3a4b5b33ba5
--- /dev/null
+++ b/tools/firewire/nosy-dump.h
@@ -0,0 +1,173 @@
+#ifndef __nosy_dump_h__
+#define __nosy_dump_h__
+
+#define array_length(array) (sizeof(array) / sizeof(array[0]))
+
+#define ACK_NO_ACK 0x0
+#define ACK_DONE(a) ((a >> 2) == 0)
+#define ACK_BUSY(a) ((a >> 2) == 1)
+#define ACK_ERROR(a) ((a >> 2) == 3)
+
+#include <stdint.h>
+
+struct phy_packet {
+ uint32_t timestamp;
+ union {
+ struct {
+ uint32_t zero:24;
+ uint32_t phy_id:6;
+ uint32_t identifier:2;
+ } common, link_on;
+
+ struct {
+ uint32_t zero:16;
+ uint32_t gap_count:6;
+ uint32_t set_gap_count:1;
+ uint32_t set_root:1;
+ uint32_t root_id:6;
+ uint32_t identifier:2;
+ } phy_config;
+
+ struct {
+ uint32_t more_packets:1;
+ uint32_t initiated_reset:1;
+ uint32_t port2:2;
+ uint32_t port1:2;
+ uint32_t port0:2;
+ uint32_t power_class:3;
+ uint32_t contender:1;
+ uint32_t phy_delay:2;
+ uint32_t phy_speed:2;
+ uint32_t gap_count:6;
+ uint32_t link_active:1;
+ uint32_t extended:1;
+ uint32_t phy_id:6;
+ uint32_t identifier:2;
+ } self_id;
+
+ struct {
+ uint32_t more_packets:1;
+ uint32_t reserved1:1;
+ uint32_t porth:2;
+ uint32_t portg:2;
+ uint32_t portf:2;
+ uint32_t porte:2;
+ uint32_t portd:2;
+ uint32_t portc:2;
+ uint32_t portb:2;
+ uint32_t porta:2;
+ uint32_t reserved0:2;
+ uint32_t sequence:3;
+ uint32_t extended:1;
+ uint32_t phy_id:6;
+ uint32_t identifier:2;
+ } ext_self_id;
+ };
+ uint32_t inverted;
+ uint32_t ack;
+};
+
+#define TCODE_PHY_PACKET 0x10
+
+#define PHY_PACKET_CONFIGURATION 0x00
+#define PHY_PACKET_LINK_ON 0x01
+#define PHY_PACKET_SELF_ID 0x02
+
+struct link_packet {
+ uint32_t timestamp;
+ union {
+ struct {
+ uint32_t priority:4;
+ uint32_t tcode:4;
+ uint32_t rt:2;
+ uint32_t tlabel:6;
+ uint32_t destination:16;
+
+ uint32_t offset_high:16;
+ uint32_t source:16;
+
+ uint32_t offset_low;
+ } common;
+
+ struct {
+ uint32_t common[3];
+ uint32_t crc;
+ } read_quadlet;
+
+ struct {
+ uint32_t common[3];
+ uint32_t data;
+ uint32_t crc;
+ } read_quadlet_response;
+
+ struct {
+ uint32_t common[3];
+ uint32_t extended_tcode:16;
+ uint32_t data_length:16;
+ uint32_t crc;
+ } read_block;
+
+ struct {
+ uint32_t common[3];
+ uint32_t extended_tcode:16;
+ uint32_t data_length:16;
+ uint32_t crc;
+ uint32_t data[0];
+ /* crc and ack follows. */
+ } read_block_response;
+
+ struct {
+ uint32_t common[3];
+ uint32_t data;
+ uint32_t crc;
+ } write_quadlet;
+
+ struct {
+ uint32_t common[3];
+ uint32_t extended_tcode:16;
+ uint32_t data_length:16;
+ uint32_t crc;
+ uint32_t data[0];
+ /* crc and ack follows. */
+ } write_block;
+
+ struct {
+ uint32_t common[3];
+ uint32_t crc;
+ } write_response;
+
+ struct {
+ uint32_t common[3];
+ uint32_t data;
+ uint32_t crc;
+ } cycle_start;
+
+ struct {
+ uint32_t sy:4;
+ uint32_t tcode:4;
+ uint32_t channel:6;
+ uint32_t tag:2;
+ uint32_t data_length:16;
+
+ uint32_t crc;
+ } iso_data;
+ };
+};
+
+struct subaction {
+ uint32_t ack;
+ size_t length;
+ struct list link;
+ struct link_packet packet;
+};
+
+struct link_transaction {
+ int request_node, response_node, tlabel;
+ struct subaction *request, *response;
+ struct list request_list, response_list;
+ struct list link;
+};
+
+int decode_fcp(struct link_transaction *t);
+
+#endif /* __nosy_dump_h__ */
diff --git a/tools/hv/Makefile b/tools/hv/Makefile
new file mode 100644
index 00000000000..bd22f786a60
--- /dev/null
+++ b/tools/hv/Makefile
@@ -0,0 +1,13 @@
+# Makefile for Hyper-V tools
+
+CC = $(CROSS_COMPILE)gcc
+PTHREAD_LIBS = -lpthread
+WARNINGS = -Wall -Wextra
+CFLAGS = $(WARNINGS) -g $(PTHREAD_LIBS)
+
+all: hv_kvp_daemon hv_vss_daemon
+%: %.c
+ $(CC) $(CFLAGS) -o $@ $^
+
+clean:
+ $(RM) hv_kvp_daemon hv_vss_daemon
diff --git a/tools/hv/hv_fcopy_daemon.c b/tools/hv/hv_fcopy_daemon.c
new file mode 100644
index 00000000000..fba1c75aa48
--- /dev/null
+++ b/tools/hv/hv_fcopy_daemon.c
@@ -0,0 +1,197 @@
+/*
+ * An implementation of host to guest copy functionality for Linux.
+ *
+ * Copyright (C) 2014, Microsoft, Inc.
+ *
+ * Author : K. Y. Srinivasan <kys@microsoft.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT. See the GNU General Public License for more
+ * details.
+ */
+
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <linux/types.h>
+#include <linux/kdev_t.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <linux/hyperv.h>
+#include <syslog.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+
+static int target_fd;
+static char target_fname[W_MAX_PATH];
+
+static int hv_start_fcopy(struct hv_start_fcopy *smsg)
+{
+ int error = HV_E_FAIL;
+ char *q, *p;
+
+ /*
+ * If possile append a path seperator to the path.
+ */
+ if (strlen((char *)smsg->path_name) < (W_MAX_PATH - 2))
+ strcat((char *)smsg->path_name, "/");
+
+ p = (char *)smsg->path_name;
+ snprintf(target_fname, sizeof(target_fname), "%s/%s",
+ (char *)smsg->path_name, smsg->file_name);
+
+ syslog(LOG_INFO, "Target file name: %s", target_fname);
+ /*
+ * Check to see if the path is already in place; if not,
+ * create if required.
+ */
+ while ((q = strchr(p, '/')) != NULL) {
+ if (q == p) {
+ p++;
+ continue;
+ }
+ *q = '\0';
+ if (access((char *)smsg->path_name, F_OK)) {
+ if (smsg->copy_flags & CREATE_PATH) {
+ if (mkdir((char *)smsg->path_name, 0755)) {
+ syslog(LOG_ERR, "Failed to create %s",
+ (char *)smsg->path_name);
+ goto done;
+ }
+ } else {
+ syslog(LOG_ERR, "Invalid path: %s",
+ (char *)smsg->path_name);
+ goto done;
+ }
+ }
+ p = q + 1;
+ *q = '/';
+ }
+
+ if (!access(target_fname, F_OK)) {
+ syslog(LOG_INFO, "File: %s exists", target_fname);
+ if (!(smsg->copy_flags & OVER_WRITE)) {
+ error = HV_ERROR_ALREADY_EXISTS;
+ goto done;
+ }
+ }
+
+ target_fd = open(target_fname, O_RDWR | O_CREAT | O_CLOEXEC, 0744);
+ if (target_fd == -1) {
+ syslog(LOG_INFO, "Open Failed: %s", strerror(errno));
+ goto done;
+ }
+
+ error = 0;
+done:
+ return error;
+}
+
+static int hv_copy_data(struct hv_do_fcopy *cpmsg)
+{
+ ssize_t bytes_written;
+
+ bytes_written = pwrite(target_fd, cpmsg->data, cpmsg->size,
+ cpmsg->offset);
+
+ if (bytes_written != cpmsg->size)
+ return HV_E_FAIL;
+
+ return 0;
+}
+
+static int hv_copy_finished(void)
+{
+ close(target_fd);
+ return 0;
+}
+static int hv_copy_cancel(void)
+{
+ close(target_fd);
+ unlink(target_fname);
+ return 0;
+
+}
+
+int main(void)
+{
+ int fd, fcopy_fd, len;
+ int error;
+ int version = FCOPY_CURRENT_VERSION;
+ char *buffer[4096 * 2];
+ struct hv_fcopy_hdr *in_msg;
+
+ if (daemon(1, 0)) {
+ syslog(LOG_ERR, "daemon() failed; error: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ openlog("HV_FCOPY", 0, LOG_USER);
+ syslog(LOG_INFO, "HV_FCOPY starting; pid is:%d", getpid());
+
+ fcopy_fd = open("/dev/vmbus/hv_fcopy", O_RDWR);
+
+ if (fcopy_fd < 0) {
+ syslog(LOG_ERR, "open /dev/vmbus/hv_fcopy failed; error: %d %s",
+ errno, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * Register with the kernel.
+ */
+ if ((write(fcopy_fd, &version, sizeof(int))) != sizeof(int)) {
+ syslog(LOG_ERR, "Registration failed: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+
+ while (1) {
+ /*
+ * In this loop we process fcopy messages after the
+ * handshake is complete.
+ */
+ len = pread(fcopy_fd, buffer, (4096 * 2), 0);
+ if (len < 0) {
+ syslog(LOG_ERR, "pread failed: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ in_msg = (struct hv_fcopy_hdr *)buffer;
+
+ switch (in_msg->operation) {
+ case START_FILE_COPY:
+ error = hv_start_fcopy((struct hv_start_fcopy *)in_msg);
+ break;
+ case WRITE_TO_FILE:
+ error = hv_copy_data((struct hv_do_fcopy *)in_msg);
+ break;
+ case COMPLETE_FCOPY:
+ error = hv_copy_finished();
+ break;
+ case CANCEL_FCOPY:
+ error = hv_copy_cancel();
+ break;
+
+ default:
+ syslog(LOG_ERR, "Unknown operation: %d",
+ in_msg->operation);
+
+ }
+
+ if (pwrite(fcopy_fd, &error, sizeof(int), 0) != sizeof(int)) {
+ syslog(LOG_ERR, "pwrite failed: %s", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+}
diff --git a/tools/hv/hv_get_dhcp_info.sh b/tools/hv/hv_get_dhcp_info.sh
new file mode 100755
index 00000000000..ccd3e953276
--- /dev/null
+++ b/tools/hv/hv_get_dhcp_info.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+
+# This example script retrieves the DHCP state of a given interface.
+# In the interest of keeping the KVP daemon code free of distro specific
+# information; the kvp daemon code invokes this external script to gather
+# DHCP setting for the specific interface.
+#
+# Input: Name of the interface
+#
+# Output: The script prints the string "Enabled" to stdout to indicate
+# that DHCP is enabled on the interface. If DHCP is not enabled,
+# the script prints the string "Disabled" to stdout.
+#
+# Each Distro is expected to implement this script in a distro specific
+# fashion. For instance on Distros that ship with Network Manager enabled,
+# this script can be based on the Network Manager APIs for retrieving DHCP
+# information.
+
+if_file="/etc/sysconfig/network-scripts/ifcfg-"$1
+
+dhcp=$(grep "dhcp" $if_file 2>/dev/null)
+
+if [ "$dhcp" != "" ];
+then
+echo "Enabled"
+else
+echo "Disabled"
+fi
diff --git a/tools/hv/hv_get_dns_info.sh b/tools/hv/hv_get_dns_info.sh
new file mode 100755
index 00000000000..058c17b46ff
--- /dev/null
+++ b/tools/hv/hv_get_dns_info.sh
@@ -0,0 +1,13 @@
+#!/bin/bash
+
+# This example script parses /etc/resolv.conf to retrive DNS information.
+# In the interest of keeping the KVP daemon code free of distro specific
+# information; the kvp daemon code invokes this external script to gather
+# DNS information.
+# This script is expected to print the nameserver values to stdout.
+# Each Distro is expected to implement this script in a distro specific
+# fashion. For instance on Distros that ship with Network Manager enabled,
+# this script can be based on the Network Manager APIs for retrieving DNS
+# entries.
+
+cat /etc/resolv.conf 2>/dev/null | awk '/^nameserver/ { print $2 }'
diff --git a/tools/hv/hv_kvp_daemon.c b/tools/hv/hv_kvp_daemon.c
new file mode 100644
index 00000000000..4088b816a3e
--- /dev/null
+++ b/tools/hv/hv_kvp_daemon.c
@@ -0,0 +1,1742 @@
+/*
+ * An implementation of key value pair (KVP) functionality for Linux.
+ *
+ *
+ * Copyright (C) 2010, Novell, Inc.
+ * Author : K. Y. Srinivasan <ksrinivasan@novell.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ */
+
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <sys/utsname.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <linux/connector.h>
+#include <linux/hyperv.h>
+#include <linux/netlink.h>
+#include <ifaddrs.h>
+#include <netdb.h>
+#include <syslog.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dirent.h>
+#include <net/if.h>
+
+/*
+ * KVP protocol: The user mode component first registers with the
+ * the kernel component. Subsequently, the kernel component requests, data
+ * for the specified keys. In response to this message the user mode component
+ * fills in the value corresponding to the specified key. We overload the
+ * sequence field in the cn_msg header to define our KVP message types.
+ *
+ * We use this infrastructure for also supporting queries from user mode
+ * application for state that may be maintained in the KVP kernel component.
+ *
+ */
+
+
+enum key_index {
+ FullyQualifiedDomainName = 0,
+ IntegrationServicesVersion, /*This key is serviced in the kernel*/
+ NetworkAddressIPv4,
+ NetworkAddressIPv6,
+ OSBuildNumber,
+ OSName,
+ OSMajorVersion,
+ OSMinorVersion,
+ OSVersion,
+ ProcessorArchitecture
+};
+
+
+enum {
+ IPADDR = 0,
+ NETMASK,
+ GATEWAY,
+ DNS
+};
+
+static struct sockaddr_nl addr;
+static int in_hand_shake = 1;
+
+static char *os_name = "";
+static char *os_major = "";
+static char *os_minor = "";
+static char *processor_arch;
+static char *os_build;
+static char *os_version;
+static char *lic_version = "Unknown version";
+static char full_domain_name[HV_KVP_EXCHANGE_MAX_VALUE_SIZE];
+static struct utsname uts_buf;
+
+/*
+ * The location of the interface configuration file.
+ */
+
+#define KVP_CONFIG_LOC "/var/lib/hyperv"
+
+#define MAX_FILE_NAME 100
+#define ENTRIES_PER_BLOCK 50
+
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+
+struct kvp_record {
+ char key[HV_KVP_EXCHANGE_MAX_KEY_SIZE];
+ char value[HV_KVP_EXCHANGE_MAX_VALUE_SIZE];
+};
+
+struct kvp_file_state {
+ int fd;
+ int num_blocks;
+ struct kvp_record *records;
+ int num_records;
+ char fname[MAX_FILE_NAME];
+};
+
+static struct kvp_file_state kvp_file_info[KVP_POOL_COUNT];
+
+static void kvp_acquire_lock(int pool)
+{
+ struct flock fl = {F_WRLCK, SEEK_SET, 0, 0, 0};
+ fl.l_pid = getpid();
+
+ if (fcntl(kvp_file_info[pool].fd, F_SETLKW, &fl) == -1) {
+ syslog(LOG_ERR, "Failed to acquire the lock pool: %d; error: %d %s", pool,
+ errno, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void kvp_release_lock(int pool)
+{
+ struct flock fl = {F_UNLCK, SEEK_SET, 0, 0, 0};
+ fl.l_pid = getpid();
+
+ if (fcntl(kvp_file_info[pool].fd, F_SETLK, &fl) == -1) {
+ syslog(LOG_ERR, "Failed to release the lock pool: %d; error: %d %s", pool,
+ errno, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+}
+
+static void kvp_update_file(int pool)
+{
+ FILE *filep;
+ size_t bytes_written;
+
+ /*
+ * We are going to write our in-memory registry out to
+ * disk; acquire the lock first.
+ */
+ kvp_acquire_lock(pool);
+
+ filep = fopen(kvp_file_info[pool].fname, "we");
+ if (!filep) {
+ syslog(LOG_ERR, "Failed to open file, pool: %d; error: %d %s", pool,
+ errno, strerror(errno));
+ kvp_release_lock(pool);
+ exit(EXIT_FAILURE);
+ }
+
+ bytes_written = fwrite(kvp_file_info[pool].records,
+ sizeof(struct kvp_record),
+ kvp_file_info[pool].num_records, filep);
+
+ if (ferror(filep) || fclose(filep)) {
+ kvp_release_lock(pool);
+ syslog(LOG_ERR, "Failed to write file, pool: %d", pool);
+ exit(EXIT_FAILURE);
+ }
+
+ kvp_release_lock(pool);
+}
+
+static void kvp_update_mem_state(int pool)
+{
+ FILE *filep;
+ size_t records_read = 0;
+ struct kvp_record *record = kvp_file_info[pool].records;
+ struct kvp_record *readp;
+ int num_blocks = kvp_file_info[pool].num_blocks;
+ int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK;
+
+ kvp_acquire_lock(pool);
+
+ filep = fopen(kvp_file_info[pool].fname, "re");
+ if (!filep) {
+ syslog(LOG_ERR, "Failed to open file, pool: %d; error: %d %s", pool,
+ errno, strerror(errno));
+ kvp_release_lock(pool);
+ exit(EXIT_FAILURE);
+ }
+ for (;;) {
+ readp = &record[records_read];
+ records_read += fread(readp, sizeof(struct kvp_record),
+ ENTRIES_PER_BLOCK * num_blocks,
+ filep);
+
+ if (ferror(filep)) {
+ syslog(LOG_ERR, "Failed to read file, pool: %d", pool);
+ exit(EXIT_FAILURE);
+ }
+
+ if (!feof(filep)) {
+ /*
+ * We have more data to read.
+ */
+ num_blocks++;
+ record = realloc(record, alloc_unit * num_blocks);
+
+ if (record == NULL) {
+ syslog(LOG_ERR, "malloc failed");
+ exit(EXIT_FAILURE);
+ }
+ continue;
+ }
+ break;
+ }
+
+ kvp_file_info[pool].num_blocks = num_blocks;
+ kvp_file_info[pool].records = record;
+ kvp_file_info[pool].num_records = records_read;
+
+ fclose(filep);
+ kvp_release_lock(pool);
+}
+static int kvp_file_init(void)
+{
+ int fd;
+ FILE *filep;
+ size_t records_read;
+ char *fname;
+ struct kvp_record *record;
+ struct kvp_record *readp;
+ int num_blocks;
+ int i;
+ int alloc_unit = sizeof(struct kvp_record) * ENTRIES_PER_BLOCK;
+
+ if (access(KVP_CONFIG_LOC, F_OK)) {
+ if (mkdir(KVP_CONFIG_LOC, 0755 /* rwxr-xr-x */)) {
+ syslog(LOG_ERR, "Failed to create '%s'; error: %d %s", KVP_CONFIG_LOC,
+ errno, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ for (i = 0; i < KVP_POOL_COUNT; i++) {
+ fname = kvp_file_info[i].fname;
+ records_read = 0;
+ num_blocks = 1;
+ sprintf(fname, "%s/.kvp_pool_%d", KVP_CONFIG_LOC, i);
+ fd = open(fname, O_RDWR | O_CREAT | O_CLOEXEC, 0644 /* rw-r--r-- */);
+
+ if (fd == -1)
+ return 1;
+
+
+ filep = fopen(fname, "re");
+ if (!filep) {
+ close(fd);
+ return 1;
+ }
+
+ record = malloc(alloc_unit * num_blocks);
+ if (record == NULL) {
+ fclose(filep);
+ close(fd);
+ return 1;
+ }
+ for (;;) {
+ readp = &record[records_read];
+ records_read += fread(readp, sizeof(struct kvp_record),
+ ENTRIES_PER_BLOCK,
+ filep);
+
+ if (ferror(filep)) {
+ syslog(LOG_ERR, "Failed to read file, pool: %d",
+ i);
+ exit(EXIT_FAILURE);
+ }
+
+ if (!feof(filep)) {
+ /*
+ * We have more data to read.
+ */
+ num_blocks++;
+ record = realloc(record, alloc_unit *
+ num_blocks);
+ if (record == NULL) {
+ fclose(filep);
+ close(fd);
+ return 1;
+ }
+ continue;
+ }
+ break;
+ }
+ kvp_file_info[i].fd = fd;
+ kvp_file_info[i].num_blocks = num_blocks;
+ kvp_file_info[i].records = record;
+ kvp_file_info[i].num_records = records_read;
+ fclose(filep);
+
+ }
+
+ return 0;
+}
+
+static int kvp_key_delete(int pool, const char *key, int key_size)
+{
+ int i;
+ int j, k;
+ int num_records;
+ struct kvp_record *record;
+
+ /*
+ * First update the in-memory state.
+ */
+ kvp_update_mem_state(pool);
+
+ num_records = kvp_file_info[pool].num_records;
+ record = kvp_file_info[pool].records;
+
+ for (i = 0; i < num_records; i++) {
+ if (memcmp(key, record[i].key, key_size))
+ continue;
+ /*
+ * Found a match; just move the remaining
+ * entries up.
+ */
+ if (i == num_records) {
+ kvp_file_info[pool].num_records--;
+ kvp_update_file(pool);
+ return 0;
+ }
+
+ j = i;
+ k = j + 1;
+ for (; k < num_records; k++) {
+ strcpy(record[j].key, record[k].key);
+ strcpy(record[j].value, record[k].value);
+ j++;
+ }
+
+ kvp_file_info[pool].num_records--;
+ kvp_update_file(pool);
+ return 0;
+ }
+ return 1;
+}
+
+static int kvp_key_add_or_modify(int pool, const char *key, int key_size, const char *value,
+ int value_size)
+{
+ int i;
+ int num_records;
+ struct kvp_record *record;
+ int num_blocks;
+
+ if ((key_size > HV_KVP_EXCHANGE_MAX_KEY_SIZE) ||
+ (value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE))
+ return 1;
+
+ /*
+ * First update the in-memory state.
+ */
+ kvp_update_mem_state(pool);
+
+ num_records = kvp_file_info[pool].num_records;
+ record = kvp_file_info[pool].records;
+ num_blocks = kvp_file_info[pool].num_blocks;
+
+ for (i = 0; i < num_records; i++) {
+ if (memcmp(key, record[i].key, key_size))
+ continue;
+ /*
+ * Found a match; just update the value -
+ * this is the modify case.
+ */
+ memcpy(record[i].value, value, value_size);
+ kvp_update_file(pool);
+ return 0;
+ }
+
+ /*
+ * Need to add a new entry;
+ */
+ if (num_records == (ENTRIES_PER_BLOCK * num_blocks)) {
+ /* Need to allocate a larger array for reg entries. */
+ record = realloc(record, sizeof(struct kvp_record) *
+ ENTRIES_PER_BLOCK * (num_blocks + 1));
+
+ if (record == NULL)
+ return 1;
+ kvp_file_info[pool].num_blocks++;
+
+ }
+ memcpy(record[i].value, value, value_size);
+ memcpy(record[i].key, key, key_size);
+ kvp_file_info[pool].records = record;
+ kvp_file_info[pool].num_records++;
+ kvp_update_file(pool);
+ return 0;
+}
+
+static int kvp_get_value(int pool, const char *key, int key_size, char *value,
+ int value_size)
+{
+ int i;
+ int num_records;
+ struct kvp_record *record;
+
+ if ((key_size > HV_KVP_EXCHANGE_MAX_KEY_SIZE) ||
+ (value_size > HV_KVP_EXCHANGE_MAX_VALUE_SIZE))
+ return 1;
+
+ /*
+ * First update the in-memory state.
+ */
+ kvp_update_mem_state(pool);
+
+ num_records = kvp_file_info[pool].num_records;
+ record = kvp_file_info[pool].records;
+
+ for (i = 0; i < num_records; i++) {
+ if (memcmp(key, record[i].key, key_size))
+ continue;
+ /*
+ * Found a match; just copy the value out.
+ */
+ memcpy(value, record[i].value, value_size);
+ return 0;
+ }
+
+ return 1;
+}
+
+static int kvp_pool_enumerate(int pool, int index, char *key, int key_size,
+ char *value, int value_size)
+{
+ struct kvp_record *record;
+
+ /*
+ * First update our in-memory database.
+ */
+ kvp_update_mem_state(pool);
+ record = kvp_file_info[pool].records;
+
+ if (index >= kvp_file_info[pool].num_records) {
+ return 1;
+ }
+
+ memcpy(key, record[index].key, key_size);
+ memcpy(value, record[index].value, value_size);
+ return 0;
+}
+
+
+void kvp_get_os_info(void)
+{
+ FILE *file;
+ char *p, buf[512];
+
+ uname(&uts_buf);
+ os_version = uts_buf.release;
+ os_build = strdup(uts_buf.release);
+
+ os_name = uts_buf.sysname;
+ processor_arch = uts_buf.machine;
+
+ /*
+ * The current windows host (win7) expects the build
+ * string to be of the form: x.y.z
+ * Strip additional information we may have.
+ */
+ p = strchr(os_version, '-');
+ if (p)
+ *p = '\0';
+
+ /*
+ * Parse the /etc/os-release file if present:
+ * http://www.freedesktop.org/software/systemd/man/os-release.html
+ */
+ file = fopen("/etc/os-release", "r");
+ if (file != NULL) {
+ while (fgets(buf, sizeof(buf), file)) {
+ char *value, *q;
+
+ /* Ignore comments */
+ if (buf[0] == '#')
+ continue;
+
+ /* Split into name=value */
+ p = strchr(buf, '=');
+ if (!p)
+ continue;
+ *p++ = 0;
+
+ /* Remove quotes and newline; un-escape */
+ value = p;
+ q = p;
+ while (*p) {
+ if (*p == '\\') {
+ ++p;
+ if (!*p)
+ break;
+ *q++ = *p++;
+ } else if (*p == '\'' || *p == '"' ||
+ *p == '\n') {
+ ++p;
+ } else {
+ *q++ = *p++;
+ }
+ }
+ *q = 0;
+
+ if (!strcmp(buf, "NAME")) {
+ p = strdup(value);
+ if (!p)
+ break;
+ os_name = p;
+ } else if (!strcmp(buf, "VERSION_ID")) {
+ p = strdup(value);
+ if (!p)
+ break;
+ os_major = p;
+ }
+ }
+ fclose(file);
+ return;
+ }
+
+ /* Fallback for older RH/SUSE releases */
+ file = fopen("/etc/SuSE-release", "r");
+ if (file != NULL)
+ goto kvp_osinfo_found;
+ file = fopen("/etc/redhat-release", "r");
+ if (file != NULL)
+ goto kvp_osinfo_found;
+
+ /*
+ * We don't have information about the os.
+ */
+ return;
+
+kvp_osinfo_found:
+ /* up to three lines */
+ p = fgets(buf, sizeof(buf), file);
+ if (p) {
+ p = strchr(buf, '\n');
+ if (p)
+ *p = '\0';
+ p = strdup(buf);
+ if (!p)
+ goto done;
+ os_name = p;
+
+ /* second line */
+ p = fgets(buf, sizeof(buf), file);
+ if (p) {
+ p = strchr(buf, '\n');
+ if (p)
+ *p = '\0';
+ p = strdup(buf);
+ if (!p)
+ goto done;
+ os_major = p;
+
+ /* third line */
+ p = fgets(buf, sizeof(buf), file);
+ if (p) {
+ p = strchr(buf, '\n');
+ if (p)
+ *p = '\0';
+ p = strdup(buf);
+ if (p)
+ os_minor = p;
+ }
+ }
+ }
+
+done:
+ fclose(file);
+ return;
+}
+
+
+
+/*
+ * Retrieve an interface name corresponding to the specified guid.
+ * If there is a match, the function returns a pointer
+ * to the interface name and if not, a NULL is returned.
+ * If a match is found, the caller is responsible for
+ * freeing the memory.
+ */
+
+static char *kvp_get_if_name(char *guid)
+{
+ DIR *dir;
+ struct dirent *entry;
+ FILE *file;
+ char *p, *q, *x;
+ char *if_name = NULL;
+ char buf[256];
+ char *kvp_net_dir = "/sys/class/net/";
+ char dev_id[256];
+
+ dir = opendir(kvp_net_dir);
+ if (dir == NULL)
+ return NULL;
+
+ snprintf(dev_id, sizeof(dev_id), "%s", kvp_net_dir);
+ q = dev_id + strlen(kvp_net_dir);
+
+ while ((entry = readdir(dir)) != NULL) {
+ /*
+ * Set the state for the next pass.
+ */
+ *q = '\0';
+ strcat(dev_id, entry->d_name);
+ strcat(dev_id, "/device/device_id");
+
+ file = fopen(dev_id, "r");
+ if (file == NULL)
+ continue;
+
+ p = fgets(buf, sizeof(buf), file);
+ if (p) {
+ x = strchr(p, '\n');
+ if (x)
+ *x = '\0';
+
+ if (!strcmp(p, guid)) {
+ /*
+ * Found the guid match; return the interface
+ * name. The caller will free the memory.
+ */
+ if_name = strdup(entry->d_name);
+ fclose(file);
+ break;
+ }
+ }
+ fclose(file);
+ }
+
+ closedir(dir);
+ return if_name;
+}
+
+/*
+ * Retrieve the MAC address given the interface name.
+ */
+
+static char *kvp_if_name_to_mac(char *if_name)
+{
+ FILE *file;
+ char *p, *x;
+ char buf[256];
+ char addr_file[256];
+ int i;
+ char *mac_addr = NULL;
+
+ snprintf(addr_file, sizeof(addr_file), "%s%s%s", "/sys/class/net/",
+ if_name, "/address");
+
+ file = fopen(addr_file, "r");
+ if (file == NULL)
+ return NULL;
+
+ p = fgets(buf, sizeof(buf), file);
+ if (p) {
+ x = strchr(p, '\n');
+ if (x)
+ *x = '\0';
+ for (i = 0; i < strlen(p); i++)
+ p[i] = toupper(p[i]);
+ mac_addr = strdup(p);
+ }
+
+ fclose(file);
+ return mac_addr;
+}
+
+
+/*
+ * Retrieve the interface name given tha MAC address.
+ */
+
+static char *kvp_mac_to_if_name(char *mac)
+{
+ DIR *dir;
+ struct dirent *entry;
+ FILE *file;
+ char *p, *q, *x;
+ char *if_name = NULL;
+ char buf[256];
+ char *kvp_net_dir = "/sys/class/net/";
+ char dev_id[256];
+ int i;
+
+ dir = opendir(kvp_net_dir);
+ if (dir == NULL)
+ return NULL;
+
+ snprintf(dev_id, sizeof(dev_id), kvp_net_dir);
+ q = dev_id + strlen(kvp_net_dir);
+
+ while ((entry = readdir(dir)) != NULL) {
+ /*
+ * Set the state for the next pass.
+ */
+ *q = '\0';
+
+ strcat(dev_id, entry->d_name);
+ strcat(dev_id, "/address");
+
+ file = fopen(dev_id, "r");
+ if (file == NULL)
+ continue;
+
+ p = fgets(buf, sizeof(buf), file);
+ if (p) {
+ x = strchr(p, '\n');
+ if (x)
+ *x = '\0';
+
+ for (i = 0; i < strlen(p); i++)
+ p[i] = toupper(p[i]);
+
+ if (!strcmp(p, mac)) {
+ /*
+ * Found the MAC match; return the interface
+ * name. The caller will free the memory.
+ */
+ if_name = strdup(entry->d_name);
+ fclose(file);
+ break;
+ }
+ }
+ fclose(file);
+ }
+
+ closedir(dir);
+ return if_name;
+}
+
+
+static void kvp_process_ipconfig_file(char *cmd,
+ char *config_buf, int len,
+ int element_size, int offset)
+{
+ char buf[256];
+ char *p;
+ char *x;
+ FILE *file;
+
+ /*
+ * First execute the command.
+ */
+ file = popen(cmd, "r");
+ if (file == NULL)
+ return;
+
+ if (offset == 0)
+ memset(config_buf, 0, len);
+ while ((p = fgets(buf, sizeof(buf), file)) != NULL) {
+ if ((len - strlen(config_buf)) < (element_size + 1))
+ break;
+
+ x = strchr(p, '\n');
+ if (x)
+ *x = '\0';
+
+ strcat(config_buf, p);
+ strcat(config_buf, ";");
+ }
+ pclose(file);
+}
+
+static void kvp_get_ipconfig_info(char *if_name,
+ struct hv_kvp_ipaddr_value *buffer)
+{
+ char cmd[512];
+ char dhcp_info[128];
+ char *p;
+ FILE *file;
+
+ /*
+ * Get the address of default gateway (ipv4).
+ */
+ sprintf(cmd, "%s %s", "ip route show dev", if_name);
+ strcat(cmd, " | awk '/default/ {print $3 }'");
+
+ /*
+ * Execute the command to gather gateway info.
+ */
+ kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way,
+ (MAX_GATEWAY_SIZE * 2), INET_ADDRSTRLEN, 0);
+
+ /*
+ * Get the address of default gateway (ipv6).
+ */
+ sprintf(cmd, "%s %s", "ip -f inet6 route show dev", if_name);
+ strcat(cmd, " | awk '/default/ {print $3 }'");
+
+ /*
+ * Execute the command to gather gateway info (ipv6).
+ */
+ kvp_process_ipconfig_file(cmd, (char *)buffer->gate_way,
+ (MAX_GATEWAY_SIZE * 2), INET6_ADDRSTRLEN, 1);
+
+
+ /*
+ * Gather the DNS state.
+ * Since there is no standard way to get this information
+ * across various distributions of interest; we just invoke
+ * an external script that needs to be ported across distros
+ * of interest.
+ *
+ * Following is the expected format of the information from the script:
+ *
+ * ipaddr1 (nameserver1)
+ * ipaddr2 (nameserver2)
+ * .
+ * .
+ */
+
+ sprintf(cmd, "%s", "hv_get_dns_info");
+
+ /*
+ * Execute the command to gather DNS info.
+ */
+ kvp_process_ipconfig_file(cmd, (char *)buffer->dns_addr,
+ (MAX_IP_ADDR_SIZE * 2), INET_ADDRSTRLEN, 0);
+
+ /*
+ * Gather the DHCP state.
+ * We will gather this state by invoking an external script.
+ * The parameter to the script is the interface name.
+ * Here is the expected output:
+ *
+ * Enabled: DHCP enabled.
+ */
+
+ sprintf(cmd, "%s %s", "hv_get_dhcp_info", if_name);
+
+ file = popen(cmd, "r");
+ if (file == NULL)
+ return;
+
+ p = fgets(dhcp_info, sizeof(dhcp_info), file);
+ if (p == NULL) {
+ pclose(file);
+ return;
+ }
+
+ if (!strncmp(p, "Enabled", 7))
+ buffer->dhcp_enabled = 1;
+ else
+ buffer->dhcp_enabled = 0;
+
+ pclose(file);
+}
+
+
+static unsigned int hweight32(unsigned int *w)
+{
+ unsigned int res = *w - ((*w >> 1) & 0x55555555);
+ res = (res & 0x33333333) + ((res >> 2) & 0x33333333);
+ res = (res + (res >> 4)) & 0x0F0F0F0F;
+ res = res + (res >> 8);
+ return (res + (res >> 16)) & 0x000000FF;
+}
+
+static int kvp_process_ip_address(void *addrp,
+ int family, char *buffer,
+ int length, int *offset)
+{
+ struct sockaddr_in *addr;
+ struct sockaddr_in6 *addr6;
+ int addr_length;
+ char tmp[50];
+ const char *str;
+
+ if (family == AF_INET) {
+ addr = (struct sockaddr_in *)addrp;
+ str = inet_ntop(family, &addr->sin_addr, tmp, 50);
+ addr_length = INET_ADDRSTRLEN;
+ } else {
+ addr6 = (struct sockaddr_in6 *)addrp;
+ str = inet_ntop(family, &addr6->sin6_addr.s6_addr, tmp, 50);
+ addr_length = INET6_ADDRSTRLEN;
+ }
+
+ if ((length - *offset) < addr_length + 2)
+ return HV_E_FAIL;
+ if (str == NULL) {
+ strcpy(buffer, "inet_ntop failed\n");
+ return HV_E_FAIL;
+ }
+ if (*offset == 0)
+ strcpy(buffer, tmp);
+ else {
+ strcat(buffer, ";");
+ strcat(buffer, tmp);
+ }
+
+ *offset += strlen(str) + 1;
+
+ return 0;
+}
+
+static int
+kvp_get_ip_info(int family, char *if_name, int op,
+ void *out_buffer, int length)
+{
+ struct ifaddrs *ifap;
+ struct ifaddrs *curp;
+ int offset = 0;
+ int sn_offset = 0;
+ int error = 0;
+ char *buffer;
+ struct hv_kvp_ipaddr_value *ip_buffer;
+ char cidr_mask[5]; /* /xyz */
+ int weight;
+ int i;
+ unsigned int *w;
+ char *sn_str;
+ struct sockaddr_in6 *addr6;
+
+ if (op == KVP_OP_ENUMERATE) {
+ buffer = out_buffer;
+ } else {
+ ip_buffer = out_buffer;
+ buffer = (char *)ip_buffer->ip_addr;
+ ip_buffer->addr_family = 0;
+ }
+ /*
+ * On entry into this function, the buffer is capable of holding the
+ * maximum key value.
+ */
+
+ if (getifaddrs(&ifap)) {
+ strcpy(buffer, "getifaddrs failed\n");
+ return HV_E_FAIL;
+ }
+
+ curp = ifap;
+ while (curp != NULL) {
+ if (curp->ifa_addr == NULL) {
+ curp = curp->ifa_next;
+ continue;
+ }
+
+ if ((if_name != NULL) &&
+ (strncmp(curp->ifa_name, if_name, strlen(if_name)))) {
+ /*
+ * We want info about a specific interface;
+ * just continue.
+ */
+ curp = curp->ifa_next;
+ continue;
+ }
+
+ /*
+ * We only support two address families: AF_INET and AF_INET6.
+ * If a family value of 0 is specified, we collect both
+ * supported address families; if not we gather info on
+ * the specified address family.
+ */
+ if ((((family != 0) &&
+ (curp->ifa_addr->sa_family != family))) ||
+ (curp->ifa_flags & IFF_LOOPBACK)) {
+ curp = curp->ifa_next;
+ continue;
+ }
+ if ((curp->ifa_addr->sa_family != AF_INET) &&
+ (curp->ifa_addr->sa_family != AF_INET6)) {
+ curp = curp->ifa_next;
+ continue;
+ }
+
+ if (op == KVP_OP_GET_IP_INFO) {
+ /*
+ * Gather info other than the IP address.
+ * IP address info will be gathered later.
+ */
+ if (curp->ifa_addr->sa_family == AF_INET) {
+ ip_buffer->addr_family |= ADDR_FAMILY_IPV4;
+ /*
+ * Get subnet info.
+ */
+ error = kvp_process_ip_address(
+ curp->ifa_netmask,
+ AF_INET,
+ (char *)
+ ip_buffer->sub_net,
+ length,
+ &sn_offset);
+ if (error)
+ goto gather_ipaddr;
+ } else {
+ ip_buffer->addr_family |= ADDR_FAMILY_IPV6;
+
+ /*
+ * Get subnet info in CIDR format.
+ */
+ weight = 0;
+ sn_str = (char *)ip_buffer->sub_net;
+ addr6 = (struct sockaddr_in6 *)
+ curp->ifa_netmask;
+ w = addr6->sin6_addr.s6_addr32;
+
+ for (i = 0; i < 4; i++)
+ weight += hweight32(&w[i]);
+
+ sprintf(cidr_mask, "/%d", weight);
+ if ((length - sn_offset) <
+ (strlen(cidr_mask) + 1))
+ goto gather_ipaddr;
+
+ if (sn_offset == 0)
+ strcpy(sn_str, cidr_mask);
+ else {
+ strcat((char *)ip_buffer->sub_net, ";");
+ strcat(sn_str, cidr_mask);
+ }
+ sn_offset += strlen(sn_str) + 1;
+ }
+
+ /*
+ * Collect other ip related configuration info.
+ */
+
+ kvp_get_ipconfig_info(if_name, ip_buffer);
+ }
+
+gather_ipaddr:
+ error = kvp_process_ip_address(curp->ifa_addr,
+ curp->ifa_addr->sa_family,
+ buffer,
+ length, &offset);
+ if (error)
+ goto getaddr_done;
+
+ curp = curp->ifa_next;
+ }
+
+getaddr_done:
+ freeifaddrs(ifap);
+ return error;
+}
+
+
+static int expand_ipv6(char *addr, int type)
+{
+ int ret;
+ struct in6_addr v6_addr;
+
+ ret = inet_pton(AF_INET6, addr, &v6_addr);
+
+ if (ret != 1) {
+ if (type == NETMASK)
+ return 1;
+ return 0;
+ }
+
+ sprintf(addr, "%02x%02x:%02x%02x:%02x%02x:%02x%02x:%02x%02x:"
+ "%02x%02x:%02x%02x:%02x%02x",
+ (int)v6_addr.s6_addr[0], (int)v6_addr.s6_addr[1],
+ (int)v6_addr.s6_addr[2], (int)v6_addr.s6_addr[3],
+ (int)v6_addr.s6_addr[4], (int)v6_addr.s6_addr[5],
+ (int)v6_addr.s6_addr[6], (int)v6_addr.s6_addr[7],
+ (int)v6_addr.s6_addr[8], (int)v6_addr.s6_addr[9],
+ (int)v6_addr.s6_addr[10], (int)v6_addr.s6_addr[11],
+ (int)v6_addr.s6_addr[12], (int)v6_addr.s6_addr[13],
+ (int)v6_addr.s6_addr[14], (int)v6_addr.s6_addr[15]);
+
+ return 1;
+
+}
+
+static int is_ipv4(char *addr)
+{
+ int ret;
+ struct in_addr ipv4_addr;
+
+ ret = inet_pton(AF_INET, addr, &ipv4_addr);
+
+ if (ret == 1)
+ return 1;
+ return 0;
+}
+
+static int parse_ip_val_buffer(char *in_buf, int *offset,
+ char *out_buf, int out_len)
+{
+ char *x;
+ char *start;
+
+ /*
+ * in_buf has sequence of characters that are seperated by
+ * the character ';'. The last sequence does not have the
+ * terminating ";" character.
+ */
+ start = in_buf + *offset;
+
+ x = strchr(start, ';');
+ if (x)
+ *x = 0;
+ else
+ x = start + strlen(start);
+
+ if (strlen(start) != 0) {
+ int i = 0;
+ /*
+ * Get rid of leading spaces.
+ */
+ while (start[i] == ' ')
+ i++;
+
+ if ((x - start) <= out_len) {
+ strcpy(out_buf, (start + i));
+ *offset += (x - start) + 1;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+static int kvp_write_file(FILE *f, char *s1, char *s2, char *s3)
+{
+ int ret;
+
+ ret = fprintf(f, "%s%s%s%s\n", s1, s2, "=", s3);
+
+ if (ret < 0)
+ return HV_E_FAIL;
+
+ return 0;
+}
+
+
+static int process_ip_string(FILE *f, char *ip_string, int type)
+{
+ int error = 0;
+ char addr[INET6_ADDRSTRLEN];
+ int i = 0;
+ int j = 0;
+ char str[256];
+ char sub_str[10];
+ int offset = 0;
+
+ memset(addr, 0, sizeof(addr));
+
+ while (parse_ip_val_buffer(ip_string, &offset, addr,
+ (MAX_IP_ADDR_SIZE * 2))) {
+
+ sub_str[0] = 0;
+ if (is_ipv4(addr)) {
+ switch (type) {
+ case IPADDR:
+ snprintf(str, sizeof(str), "%s", "IPADDR");
+ break;
+ case NETMASK:
+ snprintf(str, sizeof(str), "%s", "NETMASK");
+ break;
+ case GATEWAY:
+ snprintf(str, sizeof(str), "%s", "GATEWAY");
+ break;
+ case DNS:
+ snprintf(str, sizeof(str), "%s", "DNS");
+ break;
+ }
+
+ if (type == DNS) {
+ snprintf(sub_str, sizeof(sub_str), "%d", ++i);
+ } else if (type == GATEWAY && i == 0) {
+ ++i;
+ } else {
+ snprintf(sub_str, sizeof(sub_str), "%d", i++);
+ }
+
+
+ } else if (expand_ipv6(addr, type)) {
+ switch (type) {
+ case IPADDR:
+ snprintf(str, sizeof(str), "%s", "IPV6ADDR");
+ break;
+ case NETMASK:
+ snprintf(str, sizeof(str), "%s", "IPV6NETMASK");
+ break;
+ case GATEWAY:
+ snprintf(str, sizeof(str), "%s",
+ "IPV6_DEFAULTGW");
+ break;
+ case DNS:
+ snprintf(str, sizeof(str), "%s", "DNS");
+ break;
+ }
+
+ if (type == DNS) {
+ snprintf(sub_str, sizeof(sub_str), "%d", ++i);
+ } else if (j == 0) {
+ ++j;
+ } else {
+ snprintf(sub_str, sizeof(sub_str), "_%d", j++);
+ }
+ } else {
+ return HV_INVALIDARG;
+ }
+
+ error = kvp_write_file(f, str, sub_str, addr);
+ if (error)
+ return error;
+ memset(addr, 0, sizeof(addr));
+ }
+
+ return 0;
+}
+
+static int kvp_set_ip_info(char *if_name, struct hv_kvp_ipaddr_value *new_val)
+{
+ int error = 0;
+ char if_file[128];
+ FILE *file;
+ char cmd[512];
+ char *mac_addr;
+
+ /*
+ * Set the configuration for the specified interface with
+ * the information provided. Since there is no standard
+ * way to configure an interface, we will have an external
+ * script that does the job of configuring the interface and
+ * flushing the configuration.
+ *
+ * The parameters passed to this external script are:
+ * 1. A configuration file that has the specified configuration.
+ *
+ * We will embed the name of the interface in the configuration
+ * file: ifcfg-ethx (where ethx is the interface name).
+ *
+ * The information provided here may be more than what is needed
+ * in a given distro to configure the interface and so are free
+ * ignore information that may not be relevant.
+ *
+ * Here is the format of the ip configuration file:
+ *
+ * HWADDR=macaddr
+ * DEVICE=interface name
+ * BOOTPROTO=<protocol> (where <protocol> is "dhcp" if DHCP is configured
+ * or "none" if no boot-time protocol should be used)
+ *
+ * IPADDR0=ipaddr1
+ * IPADDR1=ipaddr2
+ * IPADDRx=ipaddry (where y = x + 1)
+ *
+ * NETMASK0=netmask1
+ * NETMASKx=netmasky (where y = x + 1)
+ *
+ * GATEWAY=ipaddr1
+ * GATEWAYx=ipaddry (where y = x + 1)
+ *
+ * DNSx=ipaddrx (where first DNS address is tagged as DNS1 etc)
+ *
+ * IPV6 addresses will be tagged as IPV6ADDR, IPV6 gateway will be
+ * tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as
+ * IPV6NETMASK.
+ *
+ * The host can specify multiple ipv4 and ipv6 addresses to be
+ * configured for the interface. Furthermore, the configuration
+ * needs to be persistent. A subsequent GET call on the interface
+ * is expected to return the configuration that is set via the SET
+ * call.
+ */
+
+ snprintf(if_file, sizeof(if_file), "%s%s%s", KVP_CONFIG_LOC,
+ "/ifcfg-", if_name);
+
+ file = fopen(if_file, "w");
+
+ if (file == NULL) {
+ syslog(LOG_ERR, "Failed to open config file; error: %d %s",
+ errno, strerror(errno));
+ return HV_E_FAIL;
+ }
+
+ /*
+ * First write out the MAC address.
+ */
+
+ mac_addr = kvp_if_name_to_mac(if_name);
+ if (mac_addr == NULL) {
+ error = HV_E_FAIL;
+ goto setval_error;
+ }
+
+ error = kvp_write_file(file, "HWADDR", "", mac_addr);
+ free(mac_addr);
+ if (error)
+ goto setval_error;
+
+ error = kvp_write_file(file, "DEVICE", "", if_name);
+ if (error)
+ goto setval_error;
+
+ if (new_val->dhcp_enabled) {
+ error = kvp_write_file(file, "BOOTPROTO", "", "dhcp");
+ if (error)
+ goto setval_error;
+
+ /*
+ * We are done!.
+ */
+ goto setval_done;
+
+ } else {
+ error = kvp_write_file(file, "BOOTPROTO", "", "none");
+ if (error)
+ goto setval_error;
+ }
+
+ /*
+ * Write the configuration for ipaddress, netmask, gateway and
+ * name servers.
+ */
+
+ error = process_ip_string(file, (char *)new_val->ip_addr, IPADDR);
+ if (error)
+ goto setval_error;
+
+ error = process_ip_string(file, (char *)new_val->sub_net, NETMASK);
+ if (error)
+ goto setval_error;
+
+ error = process_ip_string(file, (char *)new_val->gate_way, GATEWAY);
+ if (error)
+ goto setval_error;
+
+ error = process_ip_string(file, (char *)new_val->dns_addr, DNS);
+ if (error)
+ goto setval_error;
+
+setval_done:
+ fclose(file);
+
+ /*
+ * Now that we have populated the configuration file,
+ * invoke the external script to do its magic.
+ */
+
+ snprintf(cmd, sizeof(cmd), "%s %s", "hv_set_ifconfig", if_file);
+ if (system(cmd)) {
+ syslog(LOG_ERR, "Failed to execute cmd '%s'; error: %d %s",
+ cmd, errno, strerror(errno));
+ return HV_E_FAIL;
+ }
+ return 0;
+
+setval_error:
+ syslog(LOG_ERR, "Failed to write config file");
+ fclose(file);
+ return error;
+}
+
+
+static void
+kvp_get_domain_name(char *buffer, int length)
+{
+ struct addrinfo hints, *info ;
+ int error = 0;
+
+ gethostname(buffer, length);
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET; /*Get only ipv4 addrinfo. */
+ hints.ai_socktype = SOCK_STREAM;
+ hints.ai_flags = AI_CANONNAME;
+
+ error = getaddrinfo(buffer, NULL, &hints, &info);
+ if (error != 0) {
+ snprintf(buffer, length, "getaddrinfo failed: 0x%x %s",
+ error, gai_strerror(error));
+ return;
+ }
+ snprintf(buffer, length, "%s", info->ai_canonname);
+ freeaddrinfo(info);
+}
+
+static int
+netlink_send(int fd, struct cn_msg *msg)
+{
+ struct nlmsghdr nlh = { .nlmsg_type = NLMSG_DONE };
+ unsigned int size;
+ struct msghdr message;
+ struct iovec iov[2];
+
+ size = sizeof(struct cn_msg) + msg->len;
+
+ nlh.nlmsg_pid = getpid();
+ nlh.nlmsg_len = NLMSG_LENGTH(size);
+
+ iov[0].iov_base = &nlh;
+ iov[0].iov_len = sizeof(nlh);
+
+ iov[1].iov_base = msg;
+ iov[1].iov_len = size;
+
+ memset(&message, 0, sizeof(message));
+ message.msg_name = &addr;
+ message.msg_namelen = sizeof(addr);
+ message.msg_iov = iov;
+ message.msg_iovlen = 2;
+
+ return sendmsg(fd, &message, 0);
+}
+
+int main(void)
+{
+ int fd, len, nl_group;
+ int error;
+ struct cn_msg *message;
+ struct pollfd pfd;
+ struct nlmsghdr *incoming_msg;
+ struct cn_msg *incoming_cn_msg;
+ struct hv_kvp_msg *hv_msg;
+ char *p;
+ char *key_value;
+ char *key_name;
+ int op;
+ int pool;
+ char *if_name;
+ struct hv_kvp_ipaddr_value *kvp_ip_val;
+ char *kvp_recv_buffer;
+ size_t kvp_recv_buffer_len;
+
+ if (daemon(1, 0))
+ return 1;
+ openlog("KVP", 0, LOG_USER);
+ syslog(LOG_INFO, "KVP starting; pid is:%d", getpid());
+
+ kvp_recv_buffer_len = NLMSG_LENGTH(0) + sizeof(struct cn_msg) + sizeof(struct hv_kvp_msg);
+ kvp_recv_buffer = calloc(1, kvp_recv_buffer_len);
+ if (!kvp_recv_buffer) {
+ syslog(LOG_ERR, "Failed to allocate netlink buffer");
+ exit(EXIT_FAILURE);
+ }
+ /*
+ * Retrieve OS release information.
+ */
+ kvp_get_os_info();
+ /*
+ * Cache Fully Qualified Domain Name because getaddrinfo takes an
+ * unpredictable amount of time to finish.
+ */
+ kvp_get_domain_name(full_domain_name, sizeof(full_domain_name));
+
+ if (kvp_file_init()) {
+ syslog(LOG_ERR, "Failed to initialize the pools");
+ exit(EXIT_FAILURE);
+ }
+
+ fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
+ if (fd < 0) {
+ syslog(LOG_ERR, "netlink socket creation failed; error: %d %s", errno,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ addr.nl_family = AF_NETLINK;
+ addr.nl_pad = 0;
+ addr.nl_pid = 0;
+ addr.nl_groups = 0;
+
+
+ error = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (error < 0) {
+ syslog(LOG_ERR, "bind failed; error: %d %s", errno, strerror(errno));
+ close(fd);
+ exit(EXIT_FAILURE);
+ }
+ nl_group = CN_KVP_IDX;
+
+ if (setsockopt(fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &nl_group, sizeof(nl_group)) < 0) {
+ syslog(LOG_ERR, "setsockopt failed; error: %d %s", errno, strerror(errno));
+ close(fd);
+ exit(EXIT_FAILURE);
+ }
+
+ /*
+ * Register ourselves with the kernel.
+ */
+ message = (struct cn_msg *)kvp_recv_buffer;
+ message->id.idx = CN_KVP_IDX;
+ message->id.val = CN_KVP_VAL;
+
+ hv_msg = (struct hv_kvp_msg *)message->data;
+ hv_msg->kvp_hdr.operation = KVP_OP_REGISTER1;
+ message->ack = 0;
+ message->len = sizeof(struct hv_kvp_msg);
+
+ len = netlink_send(fd, message);
+ if (len < 0) {
+ syslog(LOG_ERR, "netlink_send failed; error: %d %s", errno, strerror(errno));
+ close(fd);
+ exit(EXIT_FAILURE);
+ }
+
+ pfd.fd = fd;
+
+ while (1) {
+ struct sockaddr *addr_p = (struct sockaddr *) &addr;
+ socklen_t addr_l = sizeof(addr);
+ pfd.events = POLLIN;
+ pfd.revents = 0;
+
+ if (poll(&pfd, 1, -1) < 0) {
+ syslog(LOG_ERR, "poll failed; error: %d %s", errno, strerror(errno));
+ if (errno == EINVAL) {
+ close(fd);
+ exit(EXIT_FAILURE);
+ }
+ else
+ continue;
+ }
+
+ len = recvfrom(fd, kvp_recv_buffer, kvp_recv_buffer_len, 0,
+ addr_p, &addr_l);
+
+ if (len < 0) {
+ syslog(LOG_ERR, "recvfrom failed; pid:%u error:%d %s",
+ addr.nl_pid, errno, strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ if (addr.nl_pid) {
+ syslog(LOG_WARNING, "Received packet from untrusted pid:%u",
+ addr.nl_pid);
+ continue;
+ }
+
+ incoming_msg = (struct nlmsghdr *)kvp_recv_buffer;
+
+ if (incoming_msg->nlmsg_type != NLMSG_DONE)
+ continue;
+
+ incoming_cn_msg = (struct cn_msg *)NLMSG_DATA(incoming_msg);
+ hv_msg = (struct hv_kvp_msg *)incoming_cn_msg->data;
+
+ /*
+ * We will use the KVP header information to pass back
+ * the error from this daemon. So, first copy the state
+ * and set the error code to success.
+ */
+ op = hv_msg->kvp_hdr.operation;
+ pool = hv_msg->kvp_hdr.pool;
+ hv_msg->error = HV_S_OK;
+
+ if ((in_hand_shake) && (op == KVP_OP_REGISTER1)) {
+ /*
+ * Driver is registering with us; stash away the version
+ * information.
+ */
+ in_hand_shake = 0;
+ p = (char *)hv_msg->body.kvp_register.version;
+ lic_version = malloc(strlen(p) + 1);
+ if (lic_version) {
+ strcpy(lic_version, p);
+ syslog(LOG_INFO, "KVP LIC Version: %s",
+ lic_version);
+ } else {
+ syslog(LOG_ERR, "malloc failed");
+ }
+ continue;
+ }
+
+ switch (op) {
+ case KVP_OP_GET_IP_INFO:
+ kvp_ip_val = &hv_msg->body.kvp_ip_val;
+ if_name =
+ kvp_mac_to_if_name((char *)kvp_ip_val->adapter_id);
+
+ if (if_name == NULL) {
+ /*
+ * We could not map the mac address to an
+ * interface name; return error.
+ */
+ hv_msg->error = HV_E_FAIL;
+ break;
+ }
+ error = kvp_get_ip_info(
+ 0, if_name, KVP_OP_GET_IP_INFO,
+ kvp_ip_val,
+ (MAX_IP_ADDR_SIZE * 2));
+
+ if (error)
+ hv_msg->error = error;
+
+ free(if_name);
+ break;
+
+ case KVP_OP_SET_IP_INFO:
+ kvp_ip_val = &hv_msg->body.kvp_ip_val;
+ if_name = kvp_get_if_name(
+ (char *)kvp_ip_val->adapter_id);
+ if (if_name == NULL) {
+ /*
+ * We could not map the guid to an
+ * interface name; return error.
+ */
+ hv_msg->error = HV_GUID_NOTFOUND;
+ break;
+ }
+ error = kvp_set_ip_info(if_name, kvp_ip_val);
+ if (error)
+ hv_msg->error = error;
+
+ free(if_name);
+ break;
+
+ case KVP_OP_SET:
+ if (kvp_key_add_or_modify(pool,
+ hv_msg->body.kvp_set.data.key,
+ hv_msg->body.kvp_set.data.key_size,
+ hv_msg->body.kvp_set.data.value,
+ hv_msg->body.kvp_set.data.value_size))
+ hv_msg->error = HV_S_CONT;
+ break;
+
+ case KVP_OP_GET:
+ if (kvp_get_value(pool,
+ hv_msg->body.kvp_set.data.key,
+ hv_msg->body.kvp_set.data.key_size,
+ hv_msg->body.kvp_set.data.value,
+ hv_msg->body.kvp_set.data.value_size))
+ hv_msg->error = HV_S_CONT;
+ break;
+
+ case KVP_OP_DELETE:
+ if (kvp_key_delete(pool,
+ hv_msg->body.kvp_delete.key,
+ hv_msg->body.kvp_delete.key_size))
+ hv_msg->error = HV_S_CONT;
+ break;
+
+ default:
+ break;
+ }
+
+ if (op != KVP_OP_ENUMERATE)
+ goto kvp_done;
+
+ /*
+ * If the pool is KVP_POOL_AUTO, dynamically generate
+ * both the key and the value; if not read from the
+ * appropriate pool.
+ */
+ if (pool != KVP_POOL_AUTO) {
+ if (kvp_pool_enumerate(pool,
+ hv_msg->body.kvp_enum_data.index,
+ hv_msg->body.kvp_enum_data.data.key,
+ HV_KVP_EXCHANGE_MAX_KEY_SIZE,
+ hv_msg->body.kvp_enum_data.data.value,
+ HV_KVP_EXCHANGE_MAX_VALUE_SIZE))
+ hv_msg->error = HV_S_CONT;
+ goto kvp_done;
+ }
+
+ hv_msg = (struct hv_kvp_msg *)incoming_cn_msg->data;
+ key_name = (char *)hv_msg->body.kvp_enum_data.data.key;
+ key_value = (char *)hv_msg->body.kvp_enum_data.data.value;
+
+ switch (hv_msg->body.kvp_enum_data.index) {
+ case FullyQualifiedDomainName:
+ strcpy(key_value, full_domain_name);
+ strcpy(key_name, "FullyQualifiedDomainName");
+ break;
+ case IntegrationServicesVersion:
+ strcpy(key_name, "IntegrationServicesVersion");
+ strcpy(key_value, lic_version);
+ break;
+ case NetworkAddressIPv4:
+ kvp_get_ip_info(AF_INET, NULL, KVP_OP_ENUMERATE,
+ key_value, HV_KVP_EXCHANGE_MAX_VALUE_SIZE);
+ strcpy(key_name, "NetworkAddressIPv4");
+ break;
+ case NetworkAddressIPv6:
+ kvp_get_ip_info(AF_INET6, NULL, KVP_OP_ENUMERATE,
+ key_value, HV_KVP_EXCHANGE_MAX_VALUE_SIZE);
+ strcpy(key_name, "NetworkAddressIPv6");
+ break;
+ case OSBuildNumber:
+ strcpy(key_value, os_build);
+ strcpy(key_name, "OSBuildNumber");
+ break;
+ case OSName:
+ strcpy(key_value, os_name);
+ strcpy(key_name, "OSName");
+ break;
+ case OSMajorVersion:
+ strcpy(key_value, os_major);
+ strcpy(key_name, "OSMajorVersion");
+ break;
+ case OSMinorVersion:
+ strcpy(key_value, os_minor);
+ strcpy(key_name, "OSMinorVersion");
+ break;
+ case OSVersion:
+ strcpy(key_value, os_version);
+ strcpy(key_name, "OSVersion");
+ break;
+ case ProcessorArchitecture:
+ strcpy(key_value, processor_arch);
+ strcpy(key_name, "ProcessorArchitecture");
+ break;
+ default:
+ hv_msg->error = HV_S_CONT;
+ break;
+ }
+ /*
+ * Send the value back to the kernel. The response is
+ * already in the receive buffer. Update the cn_msg header to
+ * reflect the key value that has been added to the message
+ */
+kvp_done:
+
+ incoming_cn_msg->id.idx = CN_KVP_IDX;
+ incoming_cn_msg->id.val = CN_KVP_VAL;
+ incoming_cn_msg->ack = 0;
+ incoming_cn_msg->len = sizeof(struct hv_kvp_msg);
+
+ len = netlink_send(fd, incoming_cn_msg);
+ if (len < 0) {
+ syslog(LOG_ERR, "net_link send failed; error: %d %s", errno,
+ strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+}
diff --git a/tools/hv/hv_set_ifconfig.sh b/tools/hv/hv_set_ifconfig.sh
new file mode 100755
index 00000000000..735aafd64a3
--- /dev/null
+++ b/tools/hv/hv_set_ifconfig.sh
@@ -0,0 +1,64 @@
+#!/bin/bash
+
+# This example script activates an interface based on the specified
+# configuration.
+#
+# In the interest of keeping the KVP daemon code free of distro specific
+# information; the kvp daemon code invokes this external script to configure
+# the interface.
+#
+# The only argument to this script is the configuration file that is to
+# be used to configure the interface.
+#
+# Each Distro is expected to implement this script in a distro specific
+# fashion. For instance on Distros that ship with Network Manager enabled,
+# this script can be based on the Network Manager APIs for configuring the
+# interface.
+#
+# This example script is based on a RHEL environment.
+#
+# Here is the format of the ip configuration file:
+#
+# HWADDR=macaddr
+# DEVICE=interface name
+# BOOTPROTO=<protocol> (where <protocol> is "dhcp" if DHCP is configured
+# or "none" if no boot-time protocol should be used)
+#
+# IPADDR0=ipaddr1
+# IPADDR1=ipaddr2
+# IPADDRx=ipaddry (where y = x + 1)
+#
+# NETMASK0=netmask1
+# NETMASKx=netmasky (where y = x + 1)
+#
+# GATEWAY=ipaddr1
+# GATEWAYx=ipaddry (where y = x + 1)
+#
+# DNSx=ipaddrx (where first DNS address is tagged as DNS1 etc)
+#
+# IPV6 addresses will be tagged as IPV6ADDR, IPV6 gateway will be
+# tagged as IPV6_DEFAULTGW and IPV6 NETMASK will be tagged as
+# IPV6NETMASK.
+#
+# The host can specify multiple ipv4 and ipv6 addresses to be
+# configured for the interface. Furthermore, the configuration
+# needs to be persistent. A subsequent GET call on the interface
+# is expected to return the configuration that is set via the SET
+# call.
+#
+
+
+
+echo "IPV6INIT=yes" >> $1
+echo "NM_CONTROLLED=no" >> $1
+echo "PEERDNS=yes" >> $1
+echo "ONBOOT=yes" >> $1
+
+
+cp $1 /etc/sysconfig/network-scripts/
+
+
+interface=$(echo $1 | awk -F - '{ print $2 }')
+
+/sbin/ifdown $interface 2>/dev/null
+/sbin/ifup $interface 2>/dev/null
diff --git a/tools/hv/hv_vss_daemon.c b/tools/hv/hv_vss_daemon.c
new file mode 100644
index 00000000000..6a213b8cd7b
--- /dev/null
+++ b/tools/hv/hv_vss_daemon.c
@@ -0,0 +1,267 @@
+/*
+ * An implementation of the host initiated guest snapshot for Hyper-V.
+ *
+ *
+ * Copyright (C) 2013, Microsoft, Inc.
+ * Author : K. Y. Srinivasan <kys@microsoft.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 as published
+ * by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, GOOD TITLE or
+ * NON INFRINGEMENT. See the GNU General Public License for more
+ * details.
+ *
+ */
+
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/poll.h>
+#include <sys/ioctl.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <mntent.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+#include <arpa/inet.h>
+#include <linux/fs.h>
+#include <linux/connector.h>
+#include <linux/hyperv.h>
+#include <linux/netlink.h>
+#include <syslog.h>
+
+static struct sockaddr_nl addr;
+
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+
+
+static int vss_do_freeze(char *dir, unsigned int cmd, char *fs_op)
+{
+ int ret, fd = open(dir, O_RDONLY);
+
+ if (fd < 0)
+ return 1;
+ ret = ioctl(fd, cmd, 0);
+ syslog(LOG_INFO, "VSS: %s of %s: %s\n", fs_op, dir, strerror(errno));
+ close(fd);
+ return !!ret;
+}
+
+static int vss_operate(int operation)
+{
+ char *fs_op;
+ char match[] = "/dev/";
+ FILE *mounts;
+ struct mntent *ent;
+ unsigned int cmd;
+ int error = 0, root_seen = 0;
+
+ switch (operation) {
+ case VSS_OP_FREEZE:
+ cmd = FIFREEZE;
+ fs_op = "freeze";
+ break;
+ case VSS_OP_THAW:
+ cmd = FITHAW;
+ fs_op = "thaw";
+ break;
+ default:
+ return -1;
+ }
+
+ mounts = setmntent("/proc/mounts", "r");
+ if (mounts == NULL)
+ return -1;
+
+ while ((ent = getmntent(mounts))) {
+ if (strncmp(ent->mnt_fsname, match, strlen(match)))
+ continue;
+ if (strcmp(ent->mnt_type, "iso9660") == 0)
+ continue;
+ if (strcmp(ent->mnt_type, "vfat") == 0)
+ continue;
+ if (strcmp(ent->mnt_dir, "/") == 0) {
+ root_seen = 1;
+ continue;
+ }
+ error |= vss_do_freeze(ent->mnt_dir, cmd, fs_op);
+ }
+ endmntent(mounts);
+
+ if (root_seen) {
+ error |= vss_do_freeze("/", cmd, fs_op);
+ }
+
+ return error;
+}
+
+static int netlink_send(int fd, struct cn_msg *msg)
+{
+ struct nlmsghdr nlh = { .nlmsg_type = NLMSG_DONE };
+ unsigned int size;
+ struct msghdr message;
+ struct iovec iov[2];
+
+ size = sizeof(struct cn_msg) + msg->len;
+
+ nlh.nlmsg_pid = getpid();
+ nlh.nlmsg_len = NLMSG_LENGTH(size);
+
+ iov[0].iov_base = &nlh;
+ iov[0].iov_len = sizeof(nlh);
+
+ iov[1].iov_base = msg;
+ iov[1].iov_len = size;
+
+ memset(&message, 0, sizeof(message));
+ message.msg_name = &addr;
+ message.msg_namelen = sizeof(addr);
+ message.msg_iov = iov;
+ message.msg_iovlen = 2;
+
+ return sendmsg(fd, &message, 0);
+}
+
+int main(void)
+{
+ int fd, len, nl_group;
+ int error;
+ struct cn_msg *message;
+ struct pollfd pfd;
+ struct nlmsghdr *incoming_msg;
+ struct cn_msg *incoming_cn_msg;
+ int op;
+ struct hv_vss_msg *vss_msg;
+ char *vss_recv_buffer;
+ size_t vss_recv_buffer_len;
+
+ if (daemon(1, 0))
+ return 1;
+
+ openlog("Hyper-V VSS", 0, LOG_USER);
+ syslog(LOG_INFO, "VSS starting; pid is:%d", getpid());
+
+ vss_recv_buffer_len = NLMSG_LENGTH(0) + sizeof(struct cn_msg) + sizeof(struct hv_vss_msg);
+ vss_recv_buffer = calloc(1, vss_recv_buffer_len);
+ if (!vss_recv_buffer) {
+ syslog(LOG_ERR, "Failed to allocate netlink buffers");
+ exit(EXIT_FAILURE);
+ }
+
+ fd = socket(AF_NETLINK, SOCK_DGRAM, NETLINK_CONNECTOR);
+ if (fd < 0) {
+ syslog(LOG_ERR, "netlink socket creation failed; error:%d %s",
+ errno, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ addr.nl_family = AF_NETLINK;
+ addr.nl_pad = 0;
+ addr.nl_pid = 0;
+ addr.nl_groups = 0;
+
+
+ error = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
+ if (error < 0) {
+ syslog(LOG_ERR, "bind failed; error:%d %s", errno, strerror(errno));
+ close(fd);
+ exit(EXIT_FAILURE);
+ }
+ nl_group = CN_VSS_IDX;
+ if (setsockopt(fd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP, &nl_group, sizeof(nl_group)) < 0) {
+ syslog(LOG_ERR, "setsockopt failed; error:%d %s", errno, strerror(errno));
+ close(fd);
+ exit(EXIT_FAILURE);
+ }
+ /*
+ * Register ourselves with the kernel.
+ */
+ message = (struct cn_msg *)vss_recv_buffer;
+ message->id.idx = CN_VSS_IDX;
+ message->id.val = CN_VSS_VAL;
+ message->ack = 0;
+ vss_msg = (struct hv_vss_msg *)message->data;
+ vss_msg->vss_hdr.operation = VSS_OP_REGISTER;
+
+ message->len = sizeof(struct hv_vss_msg);
+
+ len = netlink_send(fd, message);
+ if (len < 0) {
+ syslog(LOG_ERR, "netlink_send failed; error:%d %s", errno, strerror(errno));
+ close(fd);
+ exit(EXIT_FAILURE);
+ }
+
+ pfd.fd = fd;
+
+ while (1) {
+ struct sockaddr *addr_p = (struct sockaddr *) &addr;
+ socklen_t addr_l = sizeof(addr);
+ pfd.events = POLLIN;
+ pfd.revents = 0;
+
+ if (poll(&pfd, 1, -1) < 0) {
+ syslog(LOG_ERR, "poll failed; error:%d %s", errno, strerror(errno));
+ if (errno == EINVAL) {
+ close(fd);
+ exit(EXIT_FAILURE);
+ }
+ else
+ continue;
+ }
+
+ len = recvfrom(fd, vss_recv_buffer, vss_recv_buffer_len, 0,
+ addr_p, &addr_l);
+
+ if (len < 0) {
+ syslog(LOG_ERR, "recvfrom failed; pid:%u error:%d %s",
+ addr.nl_pid, errno, strerror(errno));
+ close(fd);
+ return -1;
+ }
+
+ if (addr.nl_pid) {
+ syslog(LOG_WARNING,
+ "Received packet from untrusted pid:%u",
+ addr.nl_pid);
+ continue;
+ }
+
+ incoming_msg = (struct nlmsghdr *)vss_recv_buffer;
+
+ if (incoming_msg->nlmsg_type != NLMSG_DONE)
+ continue;
+
+ incoming_cn_msg = (struct cn_msg *)NLMSG_DATA(incoming_msg);
+ vss_msg = (struct hv_vss_msg *)incoming_cn_msg->data;
+ op = vss_msg->vss_hdr.operation;
+ error = HV_S_OK;
+
+ switch (op) {
+ case VSS_OP_FREEZE:
+ case VSS_OP_THAW:
+ error = vss_operate(op);
+ if (error)
+ error = HV_E_FAIL;
+ break;
+ default:
+ syslog(LOG_ERR, "Illegal op:%d\n", op);
+ }
+ vss_msg->error = error;
+ len = netlink_send(fd, incoming_cn_msg);
+ if (len < 0) {
+ syslog(LOG_ERR, "net_link send failed; error:%d %s",
+ errno, strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+}
diff --git a/tools/include/asm/bug.h b/tools/include/asm/bug.h
new file mode 100644
index 00000000000..9e5f4846967
--- /dev/null
+++ b/tools/include/asm/bug.h
@@ -0,0 +1,25 @@
+#ifndef _TOOLS_ASM_BUG_H
+#define _TOOLS_ASM_BUG_H
+
+#include <linux/compiler.h>
+
+#define __WARN_printf(arg...) do { fprintf(stderr, arg); } while (0)
+
+#define WARN(condition, format...) ({ \
+ int __ret_warn_on = !!(condition); \
+ if (unlikely(__ret_warn_on)) \
+ __WARN_printf(format); \
+ unlikely(__ret_warn_on); \
+})
+
+#define WARN_ONCE(condition, format...) ({ \
+ static int __warned; \
+ int __ret_warn_once = !!(condition); \
+ \
+ if (unlikely(__ret_warn_once)) \
+ if (WARN(!__warned, format)) \
+ __warned = 1; \
+ unlikely(__ret_warn_once); \
+})
+
+#endif /* _TOOLS_ASM_BUG_H */
diff --git a/tools/include/linux/compiler.h b/tools/include/linux/compiler.h
new file mode 100644
index 00000000000..88461f09cc8
--- /dev/null
+++ b/tools/include/linux/compiler.h
@@ -0,0 +1,40 @@
+#ifndef _TOOLS_LINUX_COMPILER_H_
+#define _TOOLS_LINUX_COMPILER_H_
+
+#ifndef __always_inline
+# define __always_inline inline __attribute__((always_inline))
+#endif
+
+#define __user
+
+#ifndef __attribute_const__
+# define __attribute_const__
+#endif
+
+#ifndef __maybe_unused
+# define __maybe_unused __attribute__((unused))
+#endif
+
+#ifndef __packed
+# define __packed __attribute__((__packed__))
+#endif
+
+#ifndef __force
+# define __force
+#endif
+
+#ifndef __weak
+# define __weak __attribute__((weak))
+#endif
+
+#ifndef likely
+# define likely(x) __builtin_expect(!!(x), 1)
+#endif
+
+#ifndef unlikely
+# define unlikely(x) __builtin_expect(!!(x), 0)
+#endif
+
+#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
+
+#endif /* _TOOLS_LINUX_COMPILER_H */
diff --git a/tools/include/linux/export.h b/tools/include/linux/export.h
new file mode 100644
index 00000000000..d07e586b9ba
--- /dev/null
+++ b/tools/include/linux/export.h
@@ -0,0 +1,10 @@
+#ifndef _TOOLS_LINUX_EXPORT_H_
+#define _TOOLS_LINUX_EXPORT_H_
+
+#define EXPORT_SYMBOL(sym)
+#define EXPORT_SYMBOL_GPL(sym)
+#define EXPORT_SYMBOL_GPL_FUTURE(sym)
+#define EXPORT_UNUSED_SYMBOL(sym)
+#define EXPORT_UNUSED_SYMBOL_GPL(sym)
+
+#endif
diff --git a/tools/include/linux/hash.h b/tools/include/linux/hash.h
new file mode 100644
index 00000000000..d026c657301
--- /dev/null
+++ b/tools/include/linux/hash.h
@@ -0,0 +1,5 @@
+#include "../../../include/linux/hash.h"
+
+#ifndef _TOOLS_LINUX_HASH_H
+#define _TOOLS_LINUX_HASH_H
+#endif
diff --git a/tools/include/linux/types.h b/tools/include/linux/types.h
new file mode 100644
index 00000000000..b5cf25e05df
--- /dev/null
+++ b/tools/include/linux/types.h
@@ -0,0 +1,75 @@
+#ifndef _TOOLS_LINUX_TYPES_H_
+#define _TOOLS_LINUX_TYPES_H_
+
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+#define __SANE_USERSPACE_TYPES__ /* For PPC64, to get LL64 types */
+#include <asm/types.h>
+
+struct page;
+struct kmem_cache;
+
+typedef enum {
+ GFP_KERNEL,
+ GFP_ATOMIC,
+ __GFP_HIGHMEM,
+ __GFP_HIGH
+} gfp_t;
+
+/*
+ * We define u64 as uint64_t for every architecture
+ * so that we can print it with "%"PRIx64 without getting warnings.
+ *
+ * typedef __u64 u64;
+ * typedef __s64 s64;
+ */
+typedef uint64_t u64;
+typedef int64_t s64;
+
+typedef __u32 u32;
+typedef __s32 s32;
+
+typedef __u16 u16;
+typedef __s16 s16;
+
+typedef __u8 u8;
+typedef __s8 s8;
+
+#ifdef __CHECKER__
+#define __bitwise__ __attribute__((bitwise))
+#else
+#define __bitwise__
+#endif
+#ifdef __CHECK_ENDIAN__
+#define __bitwise __bitwise__
+#else
+#define __bitwise
+#endif
+
+#define __force
+#define __user
+#define __must_check
+#define __cold
+
+typedef __u16 __bitwise __le16;
+typedef __u16 __bitwise __be16;
+typedef __u32 __bitwise __le32;
+typedef __u32 __bitwise __be32;
+typedef __u64 __bitwise __le64;
+typedef __u64 __bitwise __be64;
+
+struct list_head {
+ struct list_head *next, *prev;
+};
+
+struct hlist_head {
+ struct hlist_node *first;
+};
+
+struct hlist_node {
+ struct hlist_node *next, **pprev;
+};
+
+#endif /* _TOOLS_LINUX_TYPES_H_ */
diff --git a/tools/include/tools/be_byteshift.h b/tools/include/tools/be_byteshift.h
new file mode 100644
index 00000000000..84c17d83657
--- /dev/null
+++ b/tools/include/tools/be_byteshift.h
@@ -0,0 +1,70 @@
+#ifndef _TOOLS_BE_BYTESHIFT_H
+#define _TOOLS_BE_BYTESHIFT_H
+
+#include <stdint.h>
+
+static inline uint16_t __get_unaligned_be16(const uint8_t *p)
+{
+ return p[0] << 8 | p[1];
+}
+
+static inline uint32_t __get_unaligned_be32(const uint8_t *p)
+{
+ return p[0] << 24 | p[1] << 16 | p[2] << 8 | p[3];
+}
+
+static inline uint64_t __get_unaligned_be64(const uint8_t *p)
+{
+ return (uint64_t)__get_unaligned_be32(p) << 32 |
+ __get_unaligned_be32(p + 4);
+}
+
+static inline void __put_unaligned_be16(uint16_t val, uint8_t *p)
+{
+ *p++ = val >> 8;
+ *p++ = val;
+}
+
+static inline void __put_unaligned_be32(uint32_t val, uint8_t *p)
+{
+ __put_unaligned_be16(val >> 16, p);
+ __put_unaligned_be16(val, p + 2);
+}
+
+static inline void __put_unaligned_be64(uint64_t val, uint8_t *p)
+{
+ __put_unaligned_be32(val >> 32, p);
+ __put_unaligned_be32(val, p + 4);
+}
+
+static inline uint16_t get_unaligned_be16(const void *p)
+{
+ return __get_unaligned_be16((const uint8_t *)p);
+}
+
+static inline uint32_t get_unaligned_be32(const void *p)
+{
+ return __get_unaligned_be32((const uint8_t *)p);
+}
+
+static inline uint64_t get_unaligned_be64(const void *p)
+{
+ return __get_unaligned_be64((const uint8_t *)p);
+}
+
+static inline void put_unaligned_be16(uint16_t val, void *p)
+{
+ __put_unaligned_be16(val, p);
+}
+
+static inline void put_unaligned_be32(uint32_t val, void *p)
+{
+ __put_unaligned_be32(val, p);
+}
+
+static inline void put_unaligned_be64(uint64_t val, void *p)
+{
+ __put_unaligned_be64(val, p);
+}
+
+#endif /* _TOOLS_BE_BYTESHIFT_H */
diff --git a/tools/include/tools/le_byteshift.h b/tools/include/tools/le_byteshift.h
new file mode 100644
index 00000000000..8fe9f2488ec
--- /dev/null
+++ b/tools/include/tools/le_byteshift.h
@@ -0,0 +1,70 @@
+#ifndef _TOOLS_LE_BYTESHIFT_H
+#define _TOOLS_LE_BYTESHIFT_H
+
+#include <stdint.h>
+
+static inline uint16_t __get_unaligned_le16(const uint8_t *p)
+{
+ return p[0] | p[1] << 8;
+}
+
+static inline uint32_t __get_unaligned_le32(const uint8_t *p)
+{
+ return p[0] | p[1] << 8 | p[2] << 16 | p[3] << 24;
+}
+
+static inline uint64_t __get_unaligned_le64(const uint8_t *p)
+{
+ return (uint64_t)__get_unaligned_le32(p + 4) << 32 |
+ __get_unaligned_le32(p);
+}
+
+static inline void __put_unaligned_le16(uint16_t val, uint8_t *p)
+{
+ *p++ = val;
+ *p++ = val >> 8;
+}
+
+static inline void __put_unaligned_le32(uint32_t val, uint8_t *p)
+{
+ __put_unaligned_le16(val >> 16, p + 2);
+ __put_unaligned_le16(val, p);
+}
+
+static inline void __put_unaligned_le64(uint64_t val, uint8_t *p)
+{
+ __put_unaligned_le32(val >> 32, p + 4);
+ __put_unaligned_le32(val, p);
+}
+
+static inline uint16_t get_unaligned_le16(const void *p)
+{
+ return __get_unaligned_le16((const uint8_t *)p);
+}
+
+static inline uint32_t get_unaligned_le32(const void *p)
+{
+ return __get_unaligned_le32((const uint8_t *)p);
+}
+
+static inline uint64_t get_unaligned_le64(const void *p)
+{
+ return __get_unaligned_le64((const uint8_t *)p);
+}
+
+static inline void put_unaligned_le16(uint16_t val, void *p)
+{
+ __put_unaligned_le16(val, p);
+}
+
+static inline void put_unaligned_le32(uint32_t val, void *p)
+{
+ __put_unaligned_le32(val, p);
+}
+
+static inline void put_unaligned_le64(uint64_t val, void *p)
+{
+ __put_unaligned_le64(val, p);
+}
+
+#endif /* _TOOLS_LE_BYTESHIFT_H */
diff --git a/tools/lguest/.gitignore b/tools/lguest/.gitignore
new file mode 100644
index 00000000000..115587fd5f6
--- /dev/null
+++ b/tools/lguest/.gitignore
@@ -0,0 +1 @@
+lguest
diff --git a/tools/lguest/Makefile b/tools/lguest/Makefile
new file mode 100644
index 00000000000..97bca4871ea
--- /dev/null
+++ b/tools/lguest/Makefile
@@ -0,0 +1,7 @@
+# This creates the demonstration utility "lguest" which runs a Linux guest.
+CFLAGS:=-m32 -Wall -Wmissing-declarations -Wmissing-prototypes -O3 -U_FORTIFY_SOURCE
+
+all: lguest
+
+clean:
+ rm -f lguest
diff --git a/tools/lguest/extract b/tools/lguest/extract
new file mode 100644
index 00000000000..7730bb6e4b9
--- /dev/null
+++ b/tools/lguest/extract
@@ -0,0 +1,58 @@
+#! /bin/sh
+
+set -e
+
+PREFIX=$1
+shift
+
+trap 'rm -r $TMPDIR' 0
+TMPDIR=`mktemp -d`
+
+exec 3>/dev/null
+for f; do
+ while IFS="
+" read -r LINE; do
+ case "$LINE" in
+ *$PREFIX:[0-9]*:\**)
+ NUM=`echo "$LINE" | sed "s/.*$PREFIX:\([0-9]*\).*/\1/"`
+ if [ -f $TMPDIR/$NUM ]; then
+ echo "$TMPDIR/$NUM already exits prior to $f"
+ exit 1
+ fi
+ exec 3>>$TMPDIR/$NUM
+ echo $f | sed 's,\.\./,,g' > $TMPDIR/.$NUM
+ /bin/echo "$LINE" | sed -e "s/$PREFIX:[0-9]*//" -e "s/:\*/*/" >&3
+ ;;
+ *$PREFIX:[0-9]*)
+ NUM=`echo "$LINE" | sed "s/.*$PREFIX:\([0-9]*\).*/\1/"`
+ if [ -f $TMPDIR/$NUM ]; then
+ echo "$TMPDIR/$NUM already exits prior to $f"
+ exit 1
+ fi
+ exec 3>>$TMPDIR/$NUM
+ echo $f | sed 's,\.\./,,g' > $TMPDIR/.$NUM
+ /bin/echo "$LINE" | sed "s/$PREFIX:[0-9]*//" >&3
+ ;;
+ *:\**)
+ /bin/echo "$LINE" | sed -e "s/:\*/*/" -e "s,/\*\*/,," >&3
+ echo >&3
+ exec 3>/dev/null
+ ;;
+ *)
+ /bin/echo "$LINE" >&3
+ ;;
+ esac
+ done < $f
+ echo >&3
+ exec 3>/dev/null
+done
+
+LASTFILE=""
+for f in $TMPDIR/*; do
+ if [ "$LASTFILE" != $(cat $TMPDIR/.$(basename $f) ) ]; then
+ LASTFILE=$(cat $TMPDIR/.$(basename $f) )
+ echo "[ $LASTFILE ]"
+ fi
+ cat $f
+done
+
diff --git a/tools/lguest/lguest.c b/tools/lguest/lguest.c
new file mode 100644
index 00000000000..32cf2ce15d6
--- /dev/null
+++ b/tools/lguest/lguest.c
@@ -0,0 +1,2072 @@
+/*P:100
+ * This is the Launcher code, a simple program which lays out the "physical"
+ * memory for the new Guest by mapping the kernel image and the virtual
+ * devices, then opens /dev/lguest to tell the kernel about the Guest and
+ * control it.
+:*/
+#define _LARGEFILE64_SOURCE
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <err.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <elf.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/eventfd.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <ctype.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <sys/time.h>
+#include <time.h>
+#include <netinet/in.h>
+#include <net/if.h>
+#include <linux/sockios.h>
+#include <linux/if_tun.h>
+#include <sys/uio.h>
+#include <termios.h>
+#include <getopt.h>
+#include <assert.h>
+#include <sched.h>
+#include <limits.h>
+#include <stddef.h>
+#include <signal.h>
+#include <pwd.h>
+#include <grp.h>
+
+#ifndef VIRTIO_F_ANY_LAYOUT
+#define VIRTIO_F_ANY_LAYOUT 27
+#endif
+
+/*L:110
+ * We can ignore the 43 include files we need for this program, but I do want
+ * to draw attention to the use of kernel-style types.
+ *
+ * As Linus said, "C is a Spartan language, and so should your naming be." I
+ * like these abbreviations, so we define them here. Note that u64 is always
+ * unsigned long long, which works on all Linux systems: this means that we can
+ * use %llu in printf for any u64.
+ */
+typedef unsigned long long u64;
+typedef uint32_t u32;
+typedef uint16_t u16;
+typedef uint8_t u8;
+/*:*/
+
+#include <linux/virtio_config.h>
+#include <linux/virtio_net.h>
+#include <linux/virtio_blk.h>
+#include <linux/virtio_console.h>
+#include <linux/virtio_rng.h>
+#include <linux/virtio_ring.h>
+#include <asm/bootparam.h>
+#include "../../include/linux/lguest_launcher.h"
+
+#define BRIDGE_PFX "bridge:"
+#ifndef SIOCBRADDIF
+#define SIOCBRADDIF 0x89a2 /* add interface to bridge */
+#endif
+/* We can have up to 256 pages for devices. */
+#define DEVICE_PAGES 256
+/* This will occupy 3 pages: it must be a power of 2. */
+#define VIRTQUEUE_NUM 256
+
+/*L:120
+ * verbose is both a global flag and a macro. The C preprocessor allows
+ * this, and although I wouldn't recommend it, it works quite nicely here.
+ */
+static bool verbose;
+#define verbose(args...) \
+ do { if (verbose) printf(args); } while(0)
+/*:*/
+
+/* The pointer to the start of guest memory. */
+static void *guest_base;
+/* The maximum guest physical address allowed, and maximum possible. */
+static unsigned long guest_limit, guest_max;
+/* The /dev/lguest file descriptor. */
+static int lguest_fd;
+
+/* a per-cpu variable indicating whose vcpu is currently running */
+static unsigned int __thread cpu_id;
+
+/* This is our list of devices. */
+struct device_list {
+ /* Counter to assign interrupt numbers. */
+ unsigned int next_irq;
+
+ /* Counter to print out convenient device numbers. */
+ unsigned int device_num;
+
+ /* The descriptor page for the devices. */
+ u8 *descpage;
+
+ /* A single linked list of devices. */
+ struct device *dev;
+ /* And a pointer to the last device for easy append. */
+ struct device *lastdev;
+};
+
+/* The list of Guest devices, based on command line arguments. */
+static struct device_list devices;
+
+/* The device structure describes a single device. */
+struct device {
+ /* The linked-list pointer. */
+ struct device *next;
+
+ /* The device's descriptor, as mapped into the Guest. */
+ struct lguest_device_desc *desc;
+
+ /* We can't trust desc values once Guest has booted: we use these. */
+ unsigned int feature_len;
+ unsigned int num_vq;
+
+ /* The name of this device, for --verbose. */
+ const char *name;
+
+ /* Any queues attached to this device */
+ struct virtqueue *vq;
+
+ /* Is it operational */
+ bool running;
+
+ /* Device-specific data. */
+ void *priv;
+};
+
+/* The virtqueue structure describes a queue attached to a device. */
+struct virtqueue {
+ struct virtqueue *next;
+
+ /* Which device owns me. */
+ struct device *dev;
+
+ /* The configuration for this queue. */
+ struct lguest_vqconfig config;
+
+ /* The actual ring of buffers. */
+ struct vring vring;
+
+ /* Last available index we saw. */
+ u16 last_avail_idx;
+
+ /* How many are used since we sent last irq? */
+ unsigned int pending_used;
+
+ /* Eventfd where Guest notifications arrive. */
+ int eventfd;
+
+ /* Function for the thread which is servicing this virtqueue. */
+ void (*service)(struct virtqueue *vq);
+ pid_t thread;
+};
+
+/* Remember the arguments to the program so we can "reboot" */
+static char **main_args;
+
+/* The original tty settings to restore on exit. */
+static struct termios orig_term;
+
+/*
+ * We have to be careful with barriers: our devices are all run in separate
+ * threads and so we need to make sure that changes visible to the Guest happen
+ * in precise order.
+ */
+#define wmb() __asm__ __volatile__("" : : : "memory")
+#define rmb() __asm__ __volatile__("lock; addl $0,0(%%esp)" : : : "memory")
+#define mb() __asm__ __volatile__("lock; addl $0,0(%%esp)" : : : "memory")
+
+/* Wrapper for the last available index. Makes it easier to change. */
+#define lg_last_avail(vq) ((vq)->last_avail_idx)
+
+/*
+ * The virtio configuration space is defined to be little-endian. x86 is
+ * little-endian too, but it's nice to be explicit so we have these helpers.
+ */
+#define cpu_to_le16(v16) (v16)
+#define cpu_to_le32(v32) (v32)
+#define cpu_to_le64(v64) (v64)
+#define le16_to_cpu(v16) (v16)
+#define le32_to_cpu(v32) (v32)
+#define le64_to_cpu(v64) (v64)
+
+/* Is this iovec empty? */
+static bool iov_empty(const struct iovec iov[], unsigned int num_iov)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_iov; i++)
+ if (iov[i].iov_len)
+ return false;
+ return true;
+}
+
+/* Take len bytes from the front of this iovec. */
+static void iov_consume(struct iovec iov[], unsigned num_iov,
+ void *dest, unsigned len)
+{
+ unsigned int i;
+
+ for (i = 0; i < num_iov; i++) {
+ unsigned int used;
+
+ used = iov[i].iov_len < len ? iov[i].iov_len : len;
+ if (dest) {
+ memcpy(dest, iov[i].iov_base, used);
+ dest += used;
+ }
+ iov[i].iov_base += used;
+ iov[i].iov_len -= used;
+ len -= used;
+ }
+ if (len != 0)
+ errx(1, "iovec too short!");
+}
+
+/* The device virtqueue descriptors are followed by feature bitmasks. */
+static u8 *get_feature_bits(struct device *dev)
+{
+ return (u8 *)(dev->desc + 1)
+ + dev->num_vq * sizeof(struct lguest_vqconfig);
+}
+
+/*L:100
+ * The Launcher code itself takes us out into userspace, that scary place where
+ * pointers run wild and free! Unfortunately, like most userspace programs,
+ * it's quite boring (which is why everyone likes to hack on the kernel!).
+ * Perhaps if you make up an Lguest Drinking Game at this point, it will get
+ * you through this section. Or, maybe not.
+ *
+ * The Launcher sets up a big chunk of memory to be the Guest's "physical"
+ * memory and stores it in "guest_base". In other words, Guest physical ==
+ * Launcher virtual with an offset.
+ *
+ * This can be tough to get your head around, but usually it just means that we
+ * use these trivial conversion functions when the Guest gives us its
+ * "physical" addresses:
+ */
+static void *from_guest_phys(unsigned long addr)
+{
+ return guest_base + addr;
+}
+
+static unsigned long to_guest_phys(const void *addr)
+{
+ return (addr - guest_base);
+}
+
+/*L:130
+ * Loading the Kernel.
+ *
+ * We start with couple of simple helper routines. open_or_die() avoids
+ * error-checking code cluttering the callers:
+ */
+static int open_or_die(const char *name, int flags)
+{
+ int fd = open(name, flags);
+ if (fd < 0)
+ err(1, "Failed to open %s", name);
+ return fd;
+}
+
+/* map_zeroed_pages() takes a number of pages. */
+static void *map_zeroed_pages(unsigned int num)
+{
+ int fd = open_or_die("/dev/zero", O_RDONLY);
+ void *addr;
+
+ /*
+ * We use a private mapping (ie. if we write to the page, it will be
+ * copied). We allocate an extra two pages PROT_NONE to act as guard
+ * pages against read/write attempts that exceed allocated space.
+ */
+ addr = mmap(NULL, getpagesize() * (num+2),
+ PROT_NONE, MAP_PRIVATE, fd, 0);
+
+ if (addr == MAP_FAILED)
+ err(1, "Mmapping %u pages of /dev/zero", num);
+
+ if (mprotect(addr + getpagesize(), getpagesize() * num,
+ PROT_READ|PROT_WRITE) == -1)
+ err(1, "mprotect rw %u pages failed", num);
+
+ /*
+ * One neat mmap feature is that you can close the fd, and it
+ * stays mapped.
+ */
+ close(fd);
+
+ /* Return address after PROT_NONE page */
+ return addr + getpagesize();
+}
+
+/* Get some more pages for a device. */
+static void *get_pages(unsigned int num)
+{
+ void *addr = from_guest_phys(guest_limit);
+
+ guest_limit += num * getpagesize();
+ if (guest_limit > guest_max)
+ errx(1, "Not enough memory for devices");
+ return addr;
+}
+
+/*
+ * This routine is used to load the kernel or initrd. It tries mmap, but if
+ * that fails (Plan 9's kernel file isn't nicely aligned on page boundaries),
+ * it falls back to reading the memory in.
+ */
+static void map_at(int fd, void *addr, unsigned long offset, unsigned long len)
+{
+ ssize_t r;
+
+ /*
+ * We map writable even though for some segments are marked read-only.
+ * The kernel really wants to be writable: it patches its own
+ * instructions.
+ *
+ * MAP_PRIVATE means that the page won't be copied until a write is
+ * done to it. This allows us to share untouched memory between
+ * Guests.
+ */
+ if (mmap(addr, len, PROT_READ|PROT_WRITE,
+ MAP_FIXED|MAP_PRIVATE, fd, offset) != MAP_FAILED)
+ return;
+
+ /* pread does a seek and a read in one shot: saves a few lines. */
+ r = pread(fd, addr, len, offset);
+ if (r != len)
+ err(1, "Reading offset %lu len %lu gave %zi", offset, len, r);
+}
+
+/*
+ * This routine takes an open vmlinux image, which is in ELF, and maps it into
+ * the Guest memory. ELF = Embedded Linking Format, which is the format used
+ * by all modern binaries on Linux including the kernel.
+ *
+ * The ELF headers give *two* addresses: a physical address, and a virtual
+ * address. We use the physical address; the Guest will map itself to the
+ * virtual address.
+ *
+ * We return the starting address.
+ */
+static unsigned long map_elf(int elf_fd, const Elf32_Ehdr *ehdr)
+{
+ Elf32_Phdr phdr[ehdr->e_phnum];
+ unsigned int i;
+
+ /*
+ * Sanity checks on the main ELF header: an x86 executable with a
+ * reasonable number of correctly-sized program headers.
+ */
+ if (ehdr->e_type != ET_EXEC
+ || ehdr->e_machine != EM_386
+ || ehdr->e_phentsize != sizeof(Elf32_Phdr)
+ || ehdr->e_phnum < 1 || ehdr->e_phnum > 65536U/sizeof(Elf32_Phdr))
+ errx(1, "Malformed elf header");
+
+ /*
+ * An ELF executable contains an ELF header and a number of "program"
+ * headers which indicate which parts ("segments") of the program to
+ * load where.
+ */
+
+ /* We read in all the program headers at once: */
+ if (lseek(elf_fd, ehdr->e_phoff, SEEK_SET) < 0)
+ err(1, "Seeking to program headers");
+ if (read(elf_fd, phdr, sizeof(phdr)) != sizeof(phdr))
+ err(1, "Reading program headers");
+
+ /*
+ * Try all the headers: there are usually only three. A read-only one,
+ * a read-write one, and a "note" section which we don't load.
+ */
+ for (i = 0; i < ehdr->e_phnum; i++) {
+ /* If this isn't a loadable segment, we ignore it */
+ if (phdr[i].p_type != PT_LOAD)
+ continue;
+
+ verbose("Section %i: size %i addr %p\n",
+ i, phdr[i].p_memsz, (void *)phdr[i].p_paddr);
+
+ /* We map this section of the file at its physical address. */
+ map_at(elf_fd, from_guest_phys(phdr[i].p_paddr),
+ phdr[i].p_offset, phdr[i].p_filesz);
+ }
+
+ /* The entry point is given in the ELF header. */
+ return ehdr->e_entry;
+}
+
+/*L:150
+ * A bzImage, unlike an ELF file, is not meant to be loaded. You're supposed
+ * to jump into it and it will unpack itself. We used to have to perform some
+ * hairy magic because the unpacking code scared me.
+ *
+ * Fortunately, Jeremy Fitzhardinge convinced me it wasn't that hard and wrote
+ * a small patch to jump over the tricky bits in the Guest, so now we just read
+ * the funky header so we know where in the file to load, and away we go!
+ */
+static unsigned long load_bzimage(int fd)
+{
+ struct boot_params boot;
+ int r;
+ /* Modern bzImages get loaded at 1M. */
+ void *p = from_guest_phys(0x100000);
+
+ /*
+ * Go back to the start of the file and read the header. It should be
+ * a Linux boot header (see Documentation/x86/boot.txt)
+ */
+ lseek(fd, 0, SEEK_SET);
+ read(fd, &boot, sizeof(boot));
+
+ /* Inside the setup_hdr, we expect the magic "HdrS" */
+ if (memcmp(&boot.hdr.header, "HdrS", 4) != 0)
+ errx(1, "This doesn't look like a bzImage to me");
+
+ /* Skip over the extra sectors of the header. */
+ lseek(fd, (boot.hdr.setup_sects+1) * 512, SEEK_SET);
+
+ /* Now read everything into memory. in nice big chunks. */
+ while ((r = read(fd, p, 65536)) > 0)
+ p += r;
+
+ /* Finally, code32_start tells us where to enter the kernel. */
+ return boot.hdr.code32_start;
+}
+
+/*L:140
+ * Loading the kernel is easy when it's a "vmlinux", but most kernels
+ * come wrapped up in the self-decompressing "bzImage" format. With a little
+ * work, we can load those, too.
+ */
+static unsigned long load_kernel(int fd)
+{
+ Elf32_Ehdr hdr;
+
+ /* Read in the first few bytes. */
+ if (read(fd, &hdr, sizeof(hdr)) != sizeof(hdr))
+ err(1, "Reading kernel");
+
+ /* If it's an ELF file, it starts with "\177ELF" */
+ if (memcmp(hdr.e_ident, ELFMAG, SELFMAG) == 0)
+ return map_elf(fd, &hdr);
+
+ /* Otherwise we assume it's a bzImage, and try to load it. */
+ return load_bzimage(fd);
+}
+
+/*
+ * This is a trivial little helper to align pages. Andi Kleen hated it because
+ * it calls getpagesize() twice: "it's dumb code."
+ *
+ * Kernel guys get really het up about optimization, even when it's not
+ * necessary. I leave this code as a reaction against that.
+ */
+static inline unsigned long page_align(unsigned long addr)
+{
+ /* Add upwards and truncate downwards. */
+ return ((addr + getpagesize()-1) & ~(getpagesize()-1));
+}
+
+/*L:180
+ * An "initial ram disk" is a disk image loaded into memory along with the
+ * kernel which the kernel can use to boot from without needing any drivers.
+ * Most distributions now use this as standard: the initrd contains the code to
+ * load the appropriate driver modules for the current machine.
+ *
+ * Importantly, James Morris works for RedHat, and Fedora uses initrds for its
+ * kernels. He sent me this (and tells me when I break it).
+ */
+static unsigned long load_initrd(const char *name, unsigned long mem)
+{
+ int ifd;
+ struct stat st;
+ unsigned long len;
+
+ ifd = open_or_die(name, O_RDONLY);
+ /* fstat() is needed to get the file size. */
+ if (fstat(ifd, &st) < 0)
+ err(1, "fstat() on initrd '%s'", name);
+
+ /*
+ * We map the initrd at the top of memory, but mmap wants it to be
+ * page-aligned, so we round the size up for that.
+ */
+ len = page_align(st.st_size);
+ map_at(ifd, from_guest_phys(mem - len), 0, st.st_size);
+ /*
+ * Once a file is mapped, you can close the file descriptor. It's a
+ * little odd, but quite useful.
+ */
+ close(ifd);
+ verbose("mapped initrd %s size=%lu @ %p\n", name, len, (void*)mem-len);
+
+ /* We return the initrd size. */
+ return len;
+}
+/*:*/
+
+/*
+ * Simple routine to roll all the commandline arguments together with spaces
+ * between them.
+ */
+static void concat(char *dst, char *args[])
+{
+ unsigned int i, len = 0;
+
+ for (i = 0; args[i]; i++) {
+ if (i) {
+ strcat(dst+len, " ");
+ len++;
+ }
+ strcpy(dst+len, args[i]);
+ len += strlen(args[i]);
+ }
+ /* In case it's empty. */
+ dst[len] = '\0';
+}
+
+/*L:185
+ * This is where we actually tell the kernel to initialize the Guest. We
+ * saw the arguments it expects when we looked at initialize() in lguest_user.c:
+ * the base of Guest "physical" memory, the top physical page to allow and the
+ * entry point for the Guest.
+ */
+static void tell_kernel(unsigned long start)
+{
+ unsigned long args[] = { LHREQ_INITIALIZE,
+ (unsigned long)guest_base,
+ guest_limit / getpagesize(), start };
+ verbose("Guest: %p - %p (%#lx)\n",
+ guest_base, guest_base + guest_limit, guest_limit);
+ lguest_fd = open_or_die("/dev/lguest", O_RDWR);
+ if (write(lguest_fd, args, sizeof(args)) < 0)
+ err(1, "Writing to /dev/lguest");
+}
+/*:*/
+
+/*L:200
+ * Device Handling.
+ *
+ * When the Guest gives us a buffer, it sends an array of addresses and sizes.
+ * We need to make sure it's not trying to reach into the Launcher itself, so
+ * we have a convenient routine which checks it and exits with an error message
+ * if something funny is going on:
+ */
+static void *_check_pointer(unsigned long addr, unsigned int size,
+ unsigned int line)
+{
+ /*
+ * Check if the requested address and size exceeds the allocated memory,
+ * or addr + size wraps around.
+ */
+ if ((addr + size) > guest_limit || (addr + size) < addr)
+ errx(1, "%s:%i: Invalid address %#lx", __FILE__, line, addr);
+ /*
+ * We return a pointer for the caller's convenience, now we know it's
+ * safe to use.
+ */
+ return from_guest_phys(addr);
+}
+/* A macro which transparently hands the line number to the real function. */
+#define check_pointer(addr,size) _check_pointer(addr, size, __LINE__)
+
+/*
+ * Each buffer in the virtqueues is actually a chain of descriptors. This
+ * function returns the next descriptor in the chain, or vq->vring.num if we're
+ * at the end.
+ */
+static unsigned next_desc(struct vring_desc *desc,
+ unsigned int i, unsigned int max)
+{
+ unsigned int next;
+
+ /* If this descriptor says it doesn't chain, we're done. */
+ if (!(desc[i].flags & VRING_DESC_F_NEXT))
+ return max;
+
+ /* Check they're not leading us off end of descriptors. */
+ next = desc[i].next;
+ /* Make sure compiler knows to grab that: we don't want it changing! */
+ wmb();
+
+ if (next >= max)
+ errx(1, "Desc next is %u", next);
+
+ return next;
+}
+
+/*
+ * This actually sends the interrupt for this virtqueue, if we've used a
+ * buffer.
+ */
+static void trigger_irq(struct virtqueue *vq)
+{
+ unsigned long buf[] = { LHREQ_IRQ, vq->config.irq };
+
+ /* Don't inform them if nothing used. */
+ if (!vq->pending_used)
+ return;
+ vq->pending_used = 0;
+
+ /* If they don't want an interrupt, don't send one... */
+ if (vq->vring.avail->flags & VRING_AVAIL_F_NO_INTERRUPT) {
+ return;
+ }
+
+ /* Send the Guest an interrupt tell them we used something up. */
+ if (write(lguest_fd, buf, sizeof(buf)) != 0)
+ err(1, "Triggering irq %i", vq->config.irq);
+}
+
+/*
+ * This looks in the virtqueue for the first available buffer, and converts
+ * it to an iovec for convenient access. Since descriptors consist of some
+ * number of output then some number of input descriptors, it's actually two
+ * iovecs, but we pack them into one and note how many of each there were.
+ *
+ * This function waits if necessary, and returns the descriptor number found.
+ */
+static unsigned wait_for_vq_desc(struct virtqueue *vq,
+ struct iovec iov[],
+ unsigned int *out_num, unsigned int *in_num)
+{
+ unsigned int i, head, max;
+ struct vring_desc *desc;
+ u16 last_avail = lg_last_avail(vq);
+
+ /* There's nothing available? */
+ while (last_avail == vq->vring.avail->idx) {
+ u64 event;
+
+ /*
+ * Since we're about to sleep, now is a good time to tell the
+ * Guest about what we've used up to now.
+ */
+ trigger_irq(vq);
+
+ /* OK, now we need to know about added descriptors. */
+ vq->vring.used->flags &= ~VRING_USED_F_NO_NOTIFY;
+
+ /*
+ * They could have slipped one in as we were doing that: make
+ * sure it's written, then check again.
+ */
+ mb();
+ if (last_avail != vq->vring.avail->idx) {
+ vq->vring.used->flags |= VRING_USED_F_NO_NOTIFY;
+ break;
+ }
+
+ /* Nothing new? Wait for eventfd to tell us they refilled. */
+ if (read(vq->eventfd, &event, sizeof(event)) != sizeof(event))
+ errx(1, "Event read failed?");
+
+ /* We don't need to be notified again. */
+ vq->vring.used->flags |= VRING_USED_F_NO_NOTIFY;
+ }
+
+ /* Check it isn't doing very strange things with descriptor numbers. */
+ if ((u16)(vq->vring.avail->idx - last_avail) > vq->vring.num)
+ errx(1, "Guest moved used index from %u to %u",
+ last_avail, vq->vring.avail->idx);
+
+ /*
+ * Make sure we read the descriptor number *after* we read the ring
+ * update; don't let the cpu or compiler change the order.
+ */
+ rmb();
+
+ /*
+ * Grab the next descriptor number they're advertising, and increment
+ * the index we've seen.
+ */
+ head = vq->vring.avail->ring[last_avail % vq->vring.num];
+ lg_last_avail(vq)++;
+
+ /* If their number is silly, that's a fatal mistake. */
+ if (head >= vq->vring.num)
+ errx(1, "Guest says index %u is available", head);
+
+ /* When we start there are none of either input nor output. */
+ *out_num = *in_num = 0;
+
+ max = vq->vring.num;
+ desc = vq->vring.desc;
+ i = head;
+
+ /*
+ * We have to read the descriptor after we read the descriptor number,
+ * but there's a data dependency there so the CPU shouldn't reorder
+ * that: no rmb() required.
+ */
+
+ /*
+ * If this is an indirect entry, then this buffer contains a descriptor
+ * table which we handle as if it's any normal descriptor chain.
+ */
+ if (desc[i].flags & VRING_DESC_F_INDIRECT) {
+ if (desc[i].len % sizeof(struct vring_desc))
+ errx(1, "Invalid size for indirect buffer table");
+
+ max = desc[i].len / sizeof(struct vring_desc);
+ desc = check_pointer(desc[i].addr, desc[i].len);
+ i = 0;
+ }
+
+ do {
+ /* Grab the first descriptor, and check it's OK. */
+ iov[*out_num + *in_num].iov_len = desc[i].len;
+ iov[*out_num + *in_num].iov_base
+ = check_pointer(desc[i].addr, desc[i].len);
+ /* If this is an input descriptor, increment that count. */
+ if (desc[i].flags & VRING_DESC_F_WRITE)
+ (*in_num)++;
+ else {
+ /*
+ * If it's an output descriptor, they're all supposed
+ * to come before any input descriptors.
+ */
+ if (*in_num)
+ errx(1, "Descriptor has out after in");
+ (*out_num)++;
+ }
+
+ /* If we've got too many, that implies a descriptor loop. */
+ if (*out_num + *in_num > max)
+ errx(1, "Looped descriptor");
+ } while ((i = next_desc(desc, i, max)) != max);
+
+ return head;
+}
+
+/*
+ * After we've used one of their buffers, we tell the Guest about it. Sometime
+ * later we'll want to send them an interrupt using trigger_irq(); note that
+ * wait_for_vq_desc() does that for us if it has to wait.
+ */
+static void add_used(struct virtqueue *vq, unsigned int head, int len)
+{
+ struct vring_used_elem *used;
+
+ /*
+ * The virtqueue contains a ring of used buffers. Get a pointer to the
+ * next entry in that used ring.
+ */
+ used = &vq->vring.used->ring[vq->vring.used->idx % vq->vring.num];
+ used->id = head;
+ used->len = len;
+ /* Make sure buffer is written before we update index. */
+ wmb();
+ vq->vring.used->idx++;
+ vq->pending_used++;
+}
+
+/* And here's the combo meal deal. Supersize me! */
+static void add_used_and_trigger(struct virtqueue *vq, unsigned head, int len)
+{
+ add_used(vq, head, len);
+ trigger_irq(vq);
+}
+
+/*
+ * The Console
+ *
+ * We associate some data with the console for our exit hack.
+ */
+struct console_abort {
+ /* How many times have they hit ^C? */
+ int count;
+ /* When did they start? */
+ struct timeval start;
+};
+
+/* This is the routine which handles console input (ie. stdin). */
+static void console_input(struct virtqueue *vq)
+{
+ int len;
+ unsigned int head, in_num, out_num;
+ struct console_abort *abort = vq->dev->priv;
+ struct iovec iov[vq->vring.num];
+
+ /* Make sure there's a descriptor available. */
+ head = wait_for_vq_desc(vq, iov, &out_num, &in_num);
+ if (out_num)
+ errx(1, "Output buffers in console in queue?");
+
+ /* Read into it. This is where we usually wait. */
+ len = readv(STDIN_FILENO, iov, in_num);
+ if (len <= 0) {
+ /* Ran out of input? */
+ warnx("Failed to get console input, ignoring console.");
+ /*
+ * For simplicity, dying threads kill the whole Launcher. So
+ * just nap here.
+ */
+ for (;;)
+ pause();
+ }
+
+ /* Tell the Guest we used a buffer. */
+ add_used_and_trigger(vq, head, len);
+
+ /*
+ * Three ^C within one second? Exit.
+ *
+ * This is such a hack, but works surprisingly well. Each ^C has to
+ * be in a buffer by itself, so they can't be too fast. But we check
+ * that we get three within about a second, so they can't be too
+ * slow.
+ */
+ if (len != 1 || ((char *)iov[0].iov_base)[0] != 3) {
+ abort->count = 0;
+ return;
+ }
+
+ abort->count++;
+ if (abort->count == 1)
+ gettimeofday(&abort->start, NULL);
+ else if (abort->count == 3) {
+ struct timeval now;
+ gettimeofday(&now, NULL);
+ /* Kill all Launcher processes with SIGINT, like normal ^C */
+ if (now.tv_sec <= abort->start.tv_sec+1)
+ kill(0, SIGINT);
+ abort->count = 0;
+ }
+}
+
+/* This is the routine which handles console output (ie. stdout). */
+static void console_output(struct virtqueue *vq)
+{
+ unsigned int head, out, in;
+ struct iovec iov[vq->vring.num];
+
+ /* We usually wait in here, for the Guest to give us something. */
+ head = wait_for_vq_desc(vq, iov, &out, &in);
+ if (in)
+ errx(1, "Input buffers in console output queue?");
+
+ /* writev can return a partial write, so we loop here. */
+ while (!iov_empty(iov, out)) {
+ int len = writev(STDOUT_FILENO, iov, out);
+ if (len <= 0) {
+ warn("Write to stdout gave %i (%d)", len, errno);
+ break;
+ }
+ iov_consume(iov, out, NULL, len);
+ }
+
+ /*
+ * We're finished with that buffer: if we're going to sleep,
+ * wait_for_vq_desc() will prod the Guest with an interrupt.
+ */
+ add_used(vq, head, 0);
+}
+
+/*
+ * The Network
+ *
+ * Handling output for network is also simple: we get all the output buffers
+ * and write them to /dev/net/tun.
+ */
+struct net_info {
+ int tunfd;
+};
+
+static void net_output(struct virtqueue *vq)
+{
+ struct net_info *net_info = vq->dev->priv;
+ unsigned int head, out, in;
+ struct iovec iov[vq->vring.num];
+
+ /* We usually wait in here for the Guest to give us a packet. */
+ head = wait_for_vq_desc(vq, iov, &out, &in);
+ if (in)
+ errx(1, "Input buffers in net output queue?");
+ /*
+ * Send the whole thing through to /dev/net/tun. It expects the exact
+ * same format: what a coincidence!
+ */
+ if (writev(net_info->tunfd, iov, out) < 0)
+ warnx("Write to tun failed (%d)?", errno);
+
+ /*
+ * Done with that one; wait_for_vq_desc() will send the interrupt if
+ * all packets are processed.
+ */
+ add_used(vq, head, 0);
+}
+
+/*
+ * Handling network input is a bit trickier, because I've tried to optimize it.
+ *
+ * First we have a helper routine which tells is if from this file descriptor
+ * (ie. the /dev/net/tun device) will block:
+ */
+static bool will_block(int fd)
+{
+ fd_set fdset;
+ struct timeval zero = { 0, 0 };
+ FD_ZERO(&fdset);
+ FD_SET(fd, &fdset);
+ return select(fd+1, &fdset, NULL, NULL, &zero) != 1;
+}
+
+/*
+ * This handles packets coming in from the tun device to our Guest. Like all
+ * service routines, it gets called again as soon as it returns, so you don't
+ * see a while(1) loop here.
+ */
+static void net_input(struct virtqueue *vq)
+{
+ int len;
+ unsigned int head, out, in;
+ struct iovec iov[vq->vring.num];
+ struct net_info *net_info = vq->dev->priv;
+
+ /*
+ * Get a descriptor to write an incoming packet into. This will also
+ * send an interrupt if they're out of descriptors.
+ */
+ head = wait_for_vq_desc(vq, iov, &out, &in);
+ if (out)
+ errx(1, "Output buffers in net input queue?");
+
+ /*
+ * If it looks like we'll block reading from the tun device, send them
+ * an interrupt.
+ */
+ if (vq->pending_used && will_block(net_info->tunfd))
+ trigger_irq(vq);
+
+ /*
+ * Read in the packet. This is where we normally wait (when there's no
+ * incoming network traffic).
+ */
+ len = readv(net_info->tunfd, iov, in);
+ if (len <= 0)
+ warn("Failed to read from tun (%d).", errno);
+
+ /*
+ * Mark that packet buffer as used, but don't interrupt here. We want
+ * to wait until we've done as much work as we can.
+ */
+ add_used(vq, head, len);
+}
+/*:*/
+
+/* This is the helper to create threads: run the service routine in a loop. */
+static int do_thread(void *_vq)
+{
+ struct virtqueue *vq = _vq;
+
+ for (;;)
+ vq->service(vq);
+ return 0;
+}
+
+/*
+ * When a child dies, we kill our entire process group with SIGTERM. This
+ * also has the side effect that the shell restores the console for us!
+ */
+static void kill_launcher(int signal)
+{
+ kill(0, SIGTERM);
+}
+
+static void reset_device(struct device *dev)
+{
+ struct virtqueue *vq;
+
+ verbose("Resetting device %s\n", dev->name);
+
+ /* Clear any features they've acked. */
+ memset(get_feature_bits(dev) + dev->feature_len, 0, dev->feature_len);
+
+ /* We're going to be explicitly killing threads, so ignore them. */
+ signal(SIGCHLD, SIG_IGN);
+
+ /* Zero out the virtqueues, get rid of their threads */
+ for (vq = dev->vq; vq; vq = vq->next) {
+ if (vq->thread != (pid_t)-1) {
+ kill(vq->thread, SIGTERM);
+ waitpid(vq->thread, NULL, 0);
+ vq->thread = (pid_t)-1;
+ }
+ memset(vq->vring.desc, 0,
+ vring_size(vq->config.num, LGUEST_VRING_ALIGN));
+ lg_last_avail(vq) = 0;
+ }
+ dev->running = false;
+
+ /* Now we care if threads die. */
+ signal(SIGCHLD, (void *)kill_launcher);
+}
+
+/*L:216
+ * This actually creates the thread which services the virtqueue for a device.
+ */
+static void create_thread(struct virtqueue *vq)
+{
+ /*
+ * Create stack for thread. Since the stack grows upwards, we point
+ * the stack pointer to the end of this region.
+ */
+ char *stack = malloc(32768);
+ unsigned long args[] = { LHREQ_EVENTFD,
+ vq->config.pfn*getpagesize(), 0 };
+
+ /* Create a zero-initialized eventfd. */
+ vq->eventfd = eventfd(0, 0);
+ if (vq->eventfd < 0)
+ err(1, "Creating eventfd");
+ args[2] = vq->eventfd;
+
+ /*
+ * Attach an eventfd to this virtqueue: it will go off when the Guest
+ * does an LHCALL_NOTIFY for this vq.
+ */
+ if (write(lguest_fd, &args, sizeof(args)) != 0)
+ err(1, "Attaching eventfd");
+
+ /*
+ * CLONE_VM: because it has to access the Guest memory, and SIGCHLD so
+ * we get a signal if it dies.
+ */
+ vq->thread = clone(do_thread, stack + 32768, CLONE_VM | SIGCHLD, vq);
+ if (vq->thread == (pid_t)-1)
+ err(1, "Creating clone");
+
+ /* We close our local copy now the child has it. */
+ close(vq->eventfd);
+}
+
+static void start_device(struct device *dev)
+{
+ unsigned int i;
+ struct virtqueue *vq;
+
+ verbose("Device %s OK: offered", dev->name);
+ for (i = 0; i < dev->feature_len; i++)
+ verbose(" %02x", get_feature_bits(dev)[i]);
+ verbose(", accepted");
+ for (i = 0; i < dev->feature_len; i++)
+ verbose(" %02x", get_feature_bits(dev)
+ [dev->feature_len+i]);
+
+ for (vq = dev->vq; vq; vq = vq->next) {
+ if (vq->service)
+ create_thread(vq);
+ }
+ dev->running = true;
+}
+
+static void cleanup_devices(void)
+{
+ struct device *dev;
+
+ for (dev = devices.dev; dev; dev = dev->next)
+ reset_device(dev);
+
+ /* If we saved off the original terminal settings, restore them now. */
+ if (orig_term.c_lflag & (ISIG|ICANON|ECHO))
+ tcsetattr(STDIN_FILENO, TCSANOW, &orig_term);
+}
+
+/* When the Guest tells us they updated the status field, we handle it. */
+static void update_device_status(struct device *dev)
+{
+ /* A zero status is a reset, otherwise it's a set of flags. */
+ if (dev->desc->status == 0)
+ reset_device(dev);
+ else if (dev->desc->status & VIRTIO_CONFIG_S_FAILED) {
+ warnx("Device %s configuration FAILED", dev->name);
+ if (dev->running)
+ reset_device(dev);
+ } else {
+ if (dev->running)
+ err(1, "Device %s features finalized twice", dev->name);
+ start_device(dev);
+ }
+}
+
+/*L:215
+ * This is the generic routine we call when the Guest uses LHCALL_NOTIFY. In
+ * particular, it's used to notify us of device status changes during boot.
+ */
+static void handle_output(unsigned long addr)
+{
+ struct device *i;
+
+ /* Check each device. */
+ for (i = devices.dev; i; i = i->next) {
+ struct virtqueue *vq;
+
+ /*
+ * Notifications to device descriptors mean they updated the
+ * device status.
+ */
+ if (from_guest_phys(addr) == i->desc) {
+ update_device_status(i);
+ return;
+ }
+
+ /* Devices should not be used before features are finalized. */
+ for (vq = i->vq; vq; vq = vq->next) {
+ if (addr != vq->config.pfn*getpagesize())
+ continue;
+ errx(1, "Notification on %s before setup!", i->name);
+ }
+ }
+
+ /*
+ * Early console write is done using notify on a nul-terminated string
+ * in Guest memory. It's also great for hacking debugging messages
+ * into a Guest.
+ */
+ if (addr >= guest_limit)
+ errx(1, "Bad NOTIFY %#lx", addr);
+
+ write(STDOUT_FILENO, from_guest_phys(addr),
+ strnlen(from_guest_phys(addr), guest_limit - addr));
+}
+
+/*L:190
+ * Device Setup
+ *
+ * All devices need a descriptor so the Guest knows it exists, and a "struct
+ * device" so the Launcher can keep track of it. We have common helper
+ * routines to allocate and manage them.
+ */
+
+/*
+ * The layout of the device page is a "struct lguest_device_desc" followed by a
+ * number of virtqueue descriptors, then two sets of feature bits, then an
+ * array of configuration bytes. This routine returns the configuration
+ * pointer.
+ */
+static u8 *device_config(const struct device *dev)
+{
+ return (void *)(dev->desc + 1)
+ + dev->num_vq * sizeof(struct lguest_vqconfig)
+ + dev->feature_len * 2;
+}
+
+/*
+ * This routine allocates a new "struct lguest_device_desc" from descriptor
+ * table page just above the Guest's normal memory. It returns a pointer to
+ * that descriptor.
+ */
+static struct lguest_device_desc *new_dev_desc(u16 type)
+{
+ struct lguest_device_desc d = { .type = type };
+ void *p;
+
+ /* Figure out where the next device config is, based on the last one. */
+ if (devices.lastdev)
+ p = device_config(devices.lastdev)
+ + devices.lastdev->desc->config_len;
+ else
+ p = devices.descpage;
+
+ /* We only have one page for all the descriptors. */
+ if (p + sizeof(d) > (void *)devices.descpage + getpagesize())
+ errx(1, "Too many devices");
+
+ /* p might not be aligned, so we memcpy in. */
+ return memcpy(p, &d, sizeof(d));
+}
+
+/*
+ * Each device descriptor is followed by the description of its virtqueues. We
+ * specify how many descriptors the virtqueue is to have.
+ */
+static void add_virtqueue(struct device *dev, unsigned int num_descs,
+ void (*service)(struct virtqueue *))
+{
+ unsigned int pages;
+ struct virtqueue **i, *vq = malloc(sizeof(*vq));
+ void *p;
+
+ /* First we need some memory for this virtqueue. */
+ pages = (vring_size(num_descs, LGUEST_VRING_ALIGN) + getpagesize() - 1)
+ / getpagesize();
+ p = get_pages(pages);
+
+ /* Initialize the virtqueue */
+ vq->next = NULL;
+ vq->last_avail_idx = 0;
+ vq->dev = dev;
+
+ /*
+ * This is the routine the service thread will run, and its Process ID
+ * once it's running.
+ */
+ vq->service = service;
+ vq->thread = (pid_t)-1;
+
+ /* Initialize the configuration. */
+ vq->config.num = num_descs;
+ vq->config.irq = devices.next_irq++;
+ vq->config.pfn = to_guest_phys(p) / getpagesize();
+
+ /* Initialize the vring. */
+ vring_init(&vq->vring, num_descs, p, LGUEST_VRING_ALIGN);
+
+ /*
+ * Append virtqueue to this device's descriptor. We use
+ * device_config() to get the end of the device's current virtqueues;
+ * we check that we haven't added any config or feature information
+ * yet, otherwise we'd be overwriting them.
+ */
+ assert(dev->desc->config_len == 0 && dev->desc->feature_len == 0);
+ memcpy(device_config(dev), &vq->config, sizeof(vq->config));
+ dev->num_vq++;
+ dev->desc->num_vq++;
+
+ verbose("Virtqueue page %#lx\n", to_guest_phys(p));
+
+ /*
+ * Add to tail of list, so dev->vq is first vq, dev->vq->next is
+ * second.
+ */
+ for (i = &dev->vq; *i; i = &(*i)->next);
+ *i = vq;
+}
+
+/*
+ * The first half of the feature bitmask is for us to advertise features. The
+ * second half is for the Guest to accept features.
+ */
+static void add_feature(struct device *dev, unsigned bit)
+{
+ u8 *features = get_feature_bits(dev);
+
+ /* We can't extend the feature bits once we've added config bytes */
+ if (dev->desc->feature_len <= bit / CHAR_BIT) {
+ assert(dev->desc->config_len == 0);
+ dev->feature_len = dev->desc->feature_len = (bit/CHAR_BIT) + 1;
+ }
+
+ features[bit / CHAR_BIT] |= (1 << (bit % CHAR_BIT));
+}
+
+/*
+ * This routine sets the configuration fields for an existing device's
+ * descriptor. It only works for the last device, but that's OK because that's
+ * how we use it.
+ */
+static void set_config(struct device *dev, unsigned len, const void *conf)
+{
+ /* Check we haven't overflowed our single page. */
+ if (device_config(dev) + len > devices.descpage + getpagesize())
+ errx(1, "Too many devices");
+
+ /* Copy in the config information, and store the length. */
+ memcpy(device_config(dev), conf, len);
+ dev->desc->config_len = len;
+
+ /* Size must fit in config_len field (8 bits)! */
+ assert(dev->desc->config_len == len);
+}
+
+/*
+ * This routine does all the creation and setup of a new device, including
+ * calling new_dev_desc() to allocate the descriptor and device memory. We
+ * don't actually start the service threads until later.
+ *
+ * See what I mean about userspace being boring?
+ */
+static struct device *new_device(const char *name, u16 type)
+{
+ struct device *dev = malloc(sizeof(*dev));
+
+ /* Now we populate the fields one at a time. */
+ dev->desc = new_dev_desc(type);
+ dev->name = name;
+ dev->vq = NULL;
+ dev->feature_len = 0;
+ dev->num_vq = 0;
+ dev->running = false;
+ dev->next = NULL;
+
+ /*
+ * Append to device list. Prepending to a single-linked list is
+ * easier, but the user expects the devices to be arranged on the bus
+ * in command-line order. The first network device on the command line
+ * is eth0, the first block device /dev/vda, etc.
+ */
+ if (devices.lastdev)
+ devices.lastdev->next = dev;
+ else
+ devices.dev = dev;
+ devices.lastdev = dev;
+
+ return dev;
+}
+
+/*
+ * Our first setup routine is the console. It's a fairly simple device, but
+ * UNIX tty handling makes it uglier than it could be.
+ */
+static void setup_console(void)
+{
+ struct device *dev;
+
+ /* If we can save the initial standard input settings... */
+ if (tcgetattr(STDIN_FILENO, &orig_term) == 0) {
+ struct termios term = orig_term;
+ /*
+ * Then we turn off echo, line buffering and ^C etc: We want a
+ * raw input stream to the Guest.
+ */
+ term.c_lflag &= ~(ISIG|ICANON|ECHO);
+ tcsetattr(STDIN_FILENO, TCSANOW, &term);
+ }
+
+ dev = new_device("console", VIRTIO_ID_CONSOLE);
+
+ /* We store the console state in dev->priv, and initialize it. */
+ dev->priv = malloc(sizeof(struct console_abort));
+ ((struct console_abort *)dev->priv)->count = 0;
+
+ /*
+ * The console needs two virtqueues: the input then the output. When
+ * they put something the input queue, we make sure we're listening to
+ * stdin. When they put something in the output queue, we write it to
+ * stdout.
+ */
+ add_virtqueue(dev, VIRTQUEUE_NUM, console_input);
+ add_virtqueue(dev, VIRTQUEUE_NUM, console_output);
+
+ verbose("device %u: console\n", ++devices.device_num);
+}
+/*:*/
+
+/*M:010
+ * Inter-guest networking is an interesting area. Simplest is to have a
+ * --sharenet=<name> option which opens or creates a named pipe. This can be
+ * used to send packets to another guest in a 1:1 manner.
+ *
+ * More sophisticated is to use one of the tools developed for project like UML
+ * to do networking.
+ *
+ * Faster is to do virtio bonding in kernel. Doing this 1:1 would be
+ * completely generic ("here's my vring, attach to your vring") and would work
+ * for any traffic. Of course, namespace and permissions issues need to be
+ * dealt with. A more sophisticated "multi-channel" virtio_net.c could hide
+ * multiple inter-guest channels behind one interface, although it would
+ * require some manner of hotplugging new virtio channels.
+ *
+ * Finally, we could use a virtio network switch in the kernel, ie. vhost.
+:*/
+
+static u32 str2ip(const char *ipaddr)
+{
+ unsigned int b[4];
+
+ if (sscanf(ipaddr, "%u.%u.%u.%u", &b[0], &b[1], &b[2], &b[3]) != 4)
+ errx(1, "Failed to parse IP address '%s'", ipaddr);
+ return (b[0] << 24) | (b[1] << 16) | (b[2] << 8) | b[3];
+}
+
+static void str2mac(const char *macaddr, unsigned char mac[6])
+{
+ unsigned int m[6];
+ if (sscanf(macaddr, "%02x:%02x:%02x:%02x:%02x:%02x",
+ &m[0], &m[1], &m[2], &m[3], &m[4], &m[5]) != 6)
+ errx(1, "Failed to parse mac address '%s'", macaddr);
+ mac[0] = m[0];
+ mac[1] = m[1];
+ mac[2] = m[2];
+ mac[3] = m[3];
+ mac[4] = m[4];
+ mac[5] = m[5];
+}
+
+/*
+ * This code is "adapted" from libbridge: it attaches the Host end of the
+ * network device to the bridge device specified by the command line.
+ *
+ * This is yet another James Morris contribution (I'm an IP-level guy, so I
+ * dislike bridging), and I just try not to break it.
+ */
+static void add_to_bridge(int fd, const char *if_name, const char *br_name)
+{
+ int ifidx;
+ struct ifreq ifr;
+
+ if (!*br_name)
+ errx(1, "must specify bridge name");
+
+ ifidx = if_nametoindex(if_name);
+ if (!ifidx)
+ errx(1, "interface %s does not exist!", if_name);
+
+ strncpy(ifr.ifr_name, br_name, IFNAMSIZ);
+ ifr.ifr_name[IFNAMSIZ-1] = '\0';
+ ifr.ifr_ifindex = ifidx;
+ if (ioctl(fd, SIOCBRADDIF, &ifr) < 0)
+ err(1, "can't add %s to bridge %s", if_name, br_name);
+}
+
+/*
+ * This sets up the Host end of the network device with an IP address, brings
+ * it up so packets will flow, the copies the MAC address into the hwaddr
+ * pointer.
+ */
+static void configure_device(int fd, const char *tapif, u32 ipaddr)
+{
+ struct ifreq ifr;
+ struct sockaddr_in sin;
+
+ memset(&ifr, 0, sizeof(ifr));
+ strcpy(ifr.ifr_name, tapif);
+
+ /* Don't read these incantations. Just cut & paste them like I did! */
+ sin.sin_family = AF_INET;
+ sin.sin_addr.s_addr = htonl(ipaddr);
+ memcpy(&ifr.ifr_addr, &sin, sizeof(sin));
+ if (ioctl(fd, SIOCSIFADDR, &ifr) != 0)
+ err(1, "Setting %s interface address", tapif);
+ ifr.ifr_flags = IFF_UP;
+ if (ioctl(fd, SIOCSIFFLAGS, &ifr) != 0)
+ err(1, "Bringing interface %s up", tapif);
+}
+
+static int get_tun_device(char tapif[IFNAMSIZ])
+{
+ struct ifreq ifr;
+ int netfd;
+
+ /* Start with this zeroed. Messy but sure. */
+ memset(&ifr, 0, sizeof(ifr));
+
+ /*
+ * We open the /dev/net/tun device and tell it we want a tap device. A
+ * tap device is like a tun device, only somehow different. To tell
+ * the truth, I completely blundered my way through this code, but it
+ * works now!
+ */
+ netfd = open_or_die("/dev/net/tun", O_RDWR);
+ ifr.ifr_flags = IFF_TAP | IFF_NO_PI | IFF_VNET_HDR;
+ strcpy(ifr.ifr_name, "tap%d");
+ if (ioctl(netfd, TUNSETIFF, &ifr) != 0)
+ err(1, "configuring /dev/net/tun");
+
+ if (ioctl(netfd, TUNSETOFFLOAD,
+ TUN_F_CSUM|TUN_F_TSO4|TUN_F_TSO6|TUN_F_TSO_ECN) != 0)
+ err(1, "Could not set features for tun device");
+
+ /*
+ * We don't need checksums calculated for packets coming in this
+ * device: trust us!
+ */
+ ioctl(netfd, TUNSETNOCSUM, 1);
+
+ memcpy(tapif, ifr.ifr_name, IFNAMSIZ);
+ return netfd;
+}
+
+/*L:195
+ * Our network is a Host<->Guest network. This can either use bridging or
+ * routing, but the principle is the same: it uses the "tun" device to inject
+ * packets into the Host as if they came in from a normal network card. We
+ * just shunt packets between the Guest and the tun device.
+ */
+static void setup_tun_net(char *arg)
+{
+ struct device *dev;
+ struct net_info *net_info = malloc(sizeof(*net_info));
+ int ipfd;
+ u32 ip = INADDR_ANY;
+ bool bridging = false;
+ char tapif[IFNAMSIZ], *p;
+ struct virtio_net_config conf;
+
+ net_info->tunfd = get_tun_device(tapif);
+
+ /* First we create a new network device. */
+ dev = new_device("net", VIRTIO_ID_NET);
+ dev->priv = net_info;
+
+ /* Network devices need a recv and a send queue, just like console. */
+ add_virtqueue(dev, VIRTQUEUE_NUM, net_input);
+ add_virtqueue(dev, VIRTQUEUE_NUM, net_output);
+
+ /*
+ * We need a socket to perform the magic network ioctls to bring up the
+ * tap interface, connect to the bridge etc. Any socket will do!
+ */
+ ipfd = socket(PF_INET, SOCK_DGRAM, IPPROTO_IP);
+ if (ipfd < 0)
+ err(1, "opening IP socket");
+
+ /* If the command line was --tunnet=bridge:<name> do bridging. */
+ if (!strncmp(BRIDGE_PFX, arg, strlen(BRIDGE_PFX))) {
+ arg += strlen(BRIDGE_PFX);
+ bridging = true;
+ }
+
+ /* A mac address may follow the bridge name or IP address */
+ p = strchr(arg, ':');
+ if (p) {
+ str2mac(p+1, conf.mac);
+ add_feature(dev, VIRTIO_NET_F_MAC);
+ *p = '\0';
+ }
+
+ /* arg is now either an IP address or a bridge name */
+ if (bridging)
+ add_to_bridge(ipfd, tapif, arg);
+ else
+ ip = str2ip(arg);
+
+ /* Set up the tun device. */
+ configure_device(ipfd, tapif, ip);
+
+ /* Expect Guest to handle everything except UFO */
+ add_feature(dev, VIRTIO_NET_F_CSUM);
+ add_feature(dev, VIRTIO_NET_F_GUEST_CSUM);
+ add_feature(dev, VIRTIO_NET_F_GUEST_TSO4);
+ add_feature(dev, VIRTIO_NET_F_GUEST_TSO6);
+ add_feature(dev, VIRTIO_NET_F_GUEST_ECN);
+ add_feature(dev, VIRTIO_NET_F_HOST_TSO4);
+ add_feature(dev, VIRTIO_NET_F_HOST_TSO6);
+ add_feature(dev, VIRTIO_NET_F_HOST_ECN);
+ /* We handle indirect ring entries */
+ add_feature(dev, VIRTIO_RING_F_INDIRECT_DESC);
+ /* We're compliant with the damn spec. */
+ add_feature(dev, VIRTIO_F_ANY_LAYOUT);
+ set_config(dev, sizeof(conf), &conf);
+
+ /* We don't need the socket any more; setup is done. */
+ close(ipfd);
+
+ devices.device_num++;
+
+ if (bridging)
+ verbose("device %u: tun %s attached to bridge: %s\n",
+ devices.device_num, tapif, arg);
+ else
+ verbose("device %u: tun %s: %s\n",
+ devices.device_num, tapif, arg);
+}
+/*:*/
+
+/* This hangs off device->priv. */
+struct vblk_info {
+ /* The size of the file. */
+ off64_t len;
+
+ /* The file descriptor for the file. */
+ int fd;
+
+};
+
+/*L:210
+ * The Disk
+ *
+ * The disk only has one virtqueue, so it only has one thread. It is really
+ * simple: the Guest asks for a block number and we read or write that position
+ * in the file.
+ *
+ * Before we serviced each virtqueue in a separate thread, that was unacceptably
+ * slow: the Guest waits until the read is finished before running anything
+ * else, even if it could have been doing useful work.
+ *
+ * We could have used async I/O, except it's reputed to suck so hard that
+ * characters actually go missing from your code when you try to use it.
+ */
+static void blk_request(struct virtqueue *vq)
+{
+ struct vblk_info *vblk = vq->dev->priv;
+ unsigned int head, out_num, in_num, wlen;
+ int ret, i;
+ u8 *in;
+ struct virtio_blk_outhdr out;
+ struct iovec iov[vq->vring.num];
+ off64_t off;
+
+ /*
+ * Get the next request, where we normally wait. It triggers the
+ * interrupt to acknowledge previously serviced requests (if any).
+ */
+ head = wait_for_vq_desc(vq, iov, &out_num, &in_num);
+
+ /* Copy the output header from the front of the iov (adjusts iov) */
+ iov_consume(iov, out_num, &out, sizeof(out));
+
+ /* Find and trim end of iov input array, for our status byte. */
+ in = NULL;
+ for (i = out_num + in_num - 1; i >= out_num; i--) {
+ if (iov[i].iov_len > 0) {
+ in = iov[i].iov_base + iov[i].iov_len - 1;
+ iov[i].iov_len--;
+ break;
+ }
+ }
+ if (!in)
+ errx(1, "Bad virtblk cmd with no room for status");
+
+ /*
+ * For historical reasons, block operations are expressed in 512 byte
+ * "sectors".
+ */
+ off = out.sector * 512;
+
+ /*
+ * In general the virtio block driver is allowed to try SCSI commands.
+ * It'd be nice if we supported eject, for example, but we don't.
+ */
+ if (out.type & VIRTIO_BLK_T_SCSI_CMD) {
+ fprintf(stderr, "Scsi commands unsupported\n");
+ *in = VIRTIO_BLK_S_UNSUPP;
+ wlen = sizeof(*in);
+ } else if (out.type & VIRTIO_BLK_T_OUT) {
+ /*
+ * Write
+ *
+ * Move to the right location in the block file. This can fail
+ * if they try to write past end.
+ */
+ if (lseek64(vblk->fd, off, SEEK_SET) != off)
+ err(1, "Bad seek to sector %llu", out.sector);
+
+ ret = writev(vblk->fd, iov, out_num);
+ verbose("WRITE to sector %llu: %i\n", out.sector, ret);
+
+ /*
+ * Grr... Now we know how long the descriptor they sent was, we
+ * make sure they didn't try to write over the end of the block
+ * file (possibly extending it).
+ */
+ if (ret > 0 && off + ret > vblk->len) {
+ /* Trim it back to the correct length */
+ ftruncate64(vblk->fd, vblk->len);
+ /* Die, bad Guest, die. */
+ errx(1, "Write past end %llu+%u", off, ret);
+ }
+
+ wlen = sizeof(*in);
+ *in = (ret >= 0 ? VIRTIO_BLK_S_OK : VIRTIO_BLK_S_IOERR);
+ } else if (out.type & VIRTIO_BLK_T_FLUSH) {
+ /* Flush */
+ ret = fdatasync(vblk->fd);
+ verbose("FLUSH fdatasync: %i\n", ret);
+ wlen = sizeof(*in);
+ *in = (ret >= 0 ? VIRTIO_BLK_S_OK : VIRTIO_BLK_S_IOERR);
+ } else {
+ /*
+ * Read
+ *
+ * Move to the right location in the block file. This can fail
+ * if they try to read past end.
+ */
+ if (lseek64(vblk->fd, off, SEEK_SET) != off)
+ err(1, "Bad seek to sector %llu", out.sector);
+
+ ret = readv(vblk->fd, iov + out_num, in_num);
+ if (ret >= 0) {
+ wlen = sizeof(*in) + ret;
+ *in = VIRTIO_BLK_S_OK;
+ } else {
+ wlen = sizeof(*in);
+ *in = VIRTIO_BLK_S_IOERR;
+ }
+ }
+
+ /* Finished that request. */
+ add_used(vq, head, wlen);
+}
+
+/*L:198 This actually sets up a virtual block device. */
+static void setup_block_file(const char *filename)
+{
+ struct device *dev;
+ struct vblk_info *vblk;
+ struct virtio_blk_config conf;
+
+ /* Creat the device. */
+ dev = new_device("block", VIRTIO_ID_BLOCK);
+
+ /* The device has one virtqueue, where the Guest places requests. */
+ add_virtqueue(dev, VIRTQUEUE_NUM, blk_request);
+
+ /* Allocate the room for our own bookkeeping */
+ vblk = dev->priv = malloc(sizeof(*vblk));
+
+ /* First we open the file and store the length. */
+ vblk->fd = open_or_die(filename, O_RDWR|O_LARGEFILE);
+ vblk->len = lseek64(vblk->fd, 0, SEEK_END);
+
+ /* We support FLUSH. */
+ add_feature(dev, VIRTIO_BLK_F_FLUSH);
+
+ /* Tell Guest how many sectors this device has. */
+ conf.capacity = cpu_to_le64(vblk->len / 512);
+
+ /*
+ * Tell Guest not to put in too many descriptors at once: two are used
+ * for the in and out elements.
+ */
+ add_feature(dev, VIRTIO_BLK_F_SEG_MAX);
+ conf.seg_max = cpu_to_le32(VIRTQUEUE_NUM - 2);
+
+ /* Don't try to put whole struct: we have 8 bit limit. */
+ set_config(dev, offsetof(struct virtio_blk_config, geometry), &conf);
+
+ verbose("device %u: virtblock %llu sectors\n",
+ ++devices.device_num, le64_to_cpu(conf.capacity));
+}
+
+/*L:211
+ * Our random number generator device reads from /dev/random into the Guest's
+ * input buffers. The usual case is that the Guest doesn't want random numbers
+ * and so has no buffers although /dev/random is still readable, whereas
+ * console is the reverse.
+ *
+ * The same logic applies, however.
+ */
+struct rng_info {
+ int rfd;
+};
+
+static void rng_input(struct virtqueue *vq)
+{
+ int len;
+ unsigned int head, in_num, out_num, totlen = 0;
+ struct rng_info *rng_info = vq->dev->priv;
+ struct iovec iov[vq->vring.num];
+
+ /* First we need a buffer from the Guests's virtqueue. */
+ head = wait_for_vq_desc(vq, iov, &out_num, &in_num);
+ if (out_num)
+ errx(1, "Output buffers in rng?");
+
+ /*
+ * Just like the console write, we loop to cover the whole iovec.
+ * In this case, short reads actually happen quite a bit.
+ */
+ while (!iov_empty(iov, in_num)) {
+ len = readv(rng_info->rfd, iov, in_num);
+ if (len <= 0)
+ err(1, "Read from /dev/random gave %i", len);
+ iov_consume(iov, in_num, NULL, len);
+ totlen += len;
+ }
+
+ /* Tell the Guest about the new input. */
+ add_used(vq, head, totlen);
+}
+
+/*L:199
+ * This creates a "hardware" random number device for the Guest.
+ */
+static void setup_rng(void)
+{
+ struct device *dev;
+ struct rng_info *rng_info = malloc(sizeof(*rng_info));
+
+ /* Our device's privat info simply contains the /dev/random fd. */
+ rng_info->rfd = open_or_die("/dev/random", O_RDONLY);
+
+ /* Create the new device. */
+ dev = new_device("rng", VIRTIO_ID_RNG);
+ dev->priv = rng_info;
+
+ /* The device has one virtqueue, where the Guest places inbufs. */
+ add_virtqueue(dev, VIRTQUEUE_NUM, rng_input);
+
+ verbose("device %u: rng\n", devices.device_num++);
+}
+/* That's the end of device setup. */
+
+/*L:230 Reboot is pretty easy: clean up and exec() the Launcher afresh. */
+static void __attribute__((noreturn)) restart_guest(void)
+{
+ unsigned int i;
+
+ /*
+ * Since we don't track all open fds, we simply close everything beyond
+ * stderr.
+ */
+ for (i = 3; i < FD_SETSIZE; i++)
+ close(i);
+
+ /* Reset all the devices (kills all threads). */
+ cleanup_devices();
+
+ execv(main_args[0], main_args);
+ err(1, "Could not exec %s", main_args[0]);
+}
+
+/*L:220
+ * Finally we reach the core of the Launcher which runs the Guest, serves
+ * its input and output, and finally, lays it to rest.
+ */
+static void __attribute__((noreturn)) run_guest(void)
+{
+ for (;;) {
+ unsigned long notify_addr;
+ int readval;
+
+ /* We read from the /dev/lguest device to run the Guest. */
+ readval = pread(lguest_fd, &notify_addr,
+ sizeof(notify_addr), cpu_id);
+
+ /* One unsigned long means the Guest did HCALL_NOTIFY */
+ if (readval == sizeof(notify_addr)) {
+ verbose("Notify on address %#lx\n", notify_addr);
+ handle_output(notify_addr);
+ /* ENOENT means the Guest died. Reading tells us why. */
+ } else if (errno == ENOENT) {
+ char reason[1024] = { 0 };
+ pread(lguest_fd, reason, sizeof(reason)-1, cpu_id);
+ errx(1, "%s", reason);
+ /* ERESTART means that we need to reboot the guest */
+ } else if (errno == ERESTART) {
+ restart_guest();
+ /* Anything else means a bug or incompatible change. */
+ } else
+ err(1, "Running guest failed");
+ }
+}
+/*L:240
+ * This is the end of the Launcher. The good news: we are over halfway
+ * through! The bad news: the most fiendish part of the code still lies ahead
+ * of us.
+ *
+ * Are you ready? Take a deep breath and join me in the core of the Host, in
+ * "make Host".
+:*/
+
+static struct option opts[] = {
+ { "verbose", 0, NULL, 'v' },
+ { "tunnet", 1, NULL, 't' },
+ { "block", 1, NULL, 'b' },
+ { "rng", 0, NULL, 'r' },
+ { "initrd", 1, NULL, 'i' },
+ { "username", 1, NULL, 'u' },
+ { "chroot", 1, NULL, 'c' },
+ { NULL },
+};
+static void usage(void)
+{
+ errx(1, "Usage: lguest [--verbose] "
+ "[--tunnet=(<ipaddr>:<macaddr>|bridge:<bridgename>:<macaddr>)\n"
+ "|--block=<filename>|--initrd=<filename>]...\n"
+ "<mem-in-mb> vmlinux [args...]");
+}
+
+/*L:105 The main routine is where the real work begins: */
+int main(int argc, char *argv[])
+{
+ /* Memory, code startpoint and size of the (optional) initrd. */
+ unsigned long mem = 0, start, initrd_size = 0;
+ /* Two temporaries. */
+ int i, c;
+ /* The boot information for the Guest. */
+ struct boot_params *boot;
+ /* If they specify an initrd file to load. */
+ const char *initrd_name = NULL;
+
+ /* Password structure for initgroups/setres[gu]id */
+ struct passwd *user_details = NULL;
+
+ /* Directory to chroot to */
+ char *chroot_path = NULL;
+
+ /* Save the args: we "reboot" by execing ourselves again. */
+ main_args = argv;
+
+ /*
+ * First we initialize the device list. We keep a pointer to the last
+ * device, and the next interrupt number to use for devices (1:
+ * remember that 0 is used by the timer).
+ */
+ devices.lastdev = NULL;
+ devices.next_irq = 1;
+
+ /* We're CPU 0. In fact, that's the only CPU possible right now. */
+ cpu_id = 0;
+
+ /*
+ * We need to know how much memory so we can set up the device
+ * descriptor and memory pages for the devices as we parse the command
+ * line. So we quickly look through the arguments to find the amount
+ * of memory now.
+ */
+ for (i = 1; i < argc; i++) {
+ if (argv[i][0] != '-') {
+ mem = atoi(argv[i]) * 1024 * 1024;
+ /*
+ * We start by mapping anonymous pages over all of
+ * guest-physical memory range. This fills it with 0,
+ * and ensures that the Guest won't be killed when it
+ * tries to access it.
+ */
+ guest_base = map_zeroed_pages(mem / getpagesize()
+ + DEVICE_PAGES);
+ guest_limit = mem;
+ guest_max = mem + DEVICE_PAGES*getpagesize();
+ devices.descpage = get_pages(1);
+ break;
+ }
+ }
+
+ /* The options are fairly straight-forward */
+ while ((c = getopt_long(argc, argv, "v", opts, NULL)) != EOF) {
+ switch (c) {
+ case 'v':
+ verbose = true;
+ break;
+ case 't':
+ setup_tun_net(optarg);
+ break;
+ case 'b':
+ setup_block_file(optarg);
+ break;
+ case 'r':
+ setup_rng();
+ break;
+ case 'i':
+ initrd_name = optarg;
+ break;
+ case 'u':
+ user_details = getpwnam(optarg);
+ if (!user_details)
+ err(1, "getpwnam failed, incorrect username?");
+ break;
+ case 'c':
+ chroot_path = optarg;
+ break;
+ default:
+ warnx("Unknown argument %s", argv[optind]);
+ usage();
+ }
+ }
+ /*
+ * After the other arguments we expect memory and kernel image name,
+ * followed by command line arguments for the kernel.
+ */
+ if (optind + 2 > argc)
+ usage();
+
+ verbose("Guest base is at %p\n", guest_base);
+
+ /* We always have a console device */
+ setup_console();
+
+ /* Now we load the kernel */
+ start = load_kernel(open_or_die(argv[optind+1], O_RDONLY));
+
+ /* Boot information is stashed at physical address 0 */
+ boot = from_guest_phys(0);
+
+ /* Map the initrd image if requested (at top of physical memory) */
+ if (initrd_name) {
+ initrd_size = load_initrd(initrd_name, mem);
+ /*
+ * These are the location in the Linux boot header where the
+ * start and size of the initrd are expected to be found.
+ */
+ boot->hdr.ramdisk_image = mem - initrd_size;
+ boot->hdr.ramdisk_size = initrd_size;
+ /* The bootloader type 0xFF means "unknown"; that's OK. */
+ boot->hdr.type_of_loader = 0xFF;
+ }
+
+ /*
+ * The Linux boot header contains an "E820" memory map: ours is a
+ * simple, single region.
+ */
+ boot->e820_entries = 1;
+ boot->e820_map[0] = ((struct e820entry) { 0, mem, E820_RAM });
+ /*
+ * The boot header contains a command line pointer: we put the command
+ * line after the boot header.
+ */
+ boot->hdr.cmd_line_ptr = to_guest_phys(boot + 1);
+ /* We use a simple helper to copy the arguments separated by spaces. */
+ concat((char *)(boot + 1), argv+optind+2);
+
+ /* Set kernel alignment to 16M (CONFIG_PHYSICAL_ALIGN) */
+ boot->hdr.kernel_alignment = 0x1000000;
+
+ /* Boot protocol version: 2.07 supports the fields for lguest. */
+ boot->hdr.version = 0x207;
+
+ /* The hardware_subarch value of "1" tells the Guest it's an lguest. */
+ boot->hdr.hardware_subarch = 1;
+
+ /* Tell the entry path not to try to reload segment registers. */
+ boot->hdr.loadflags |= KEEP_SEGMENTS;
+
+ /* We tell the kernel to initialize the Guest. */
+ tell_kernel(start);
+
+ /* Ensure that we terminate if a device-servicing child dies. */
+ signal(SIGCHLD, kill_launcher);
+
+ /* If we exit via err(), this kills all the threads, restores tty. */
+ atexit(cleanup_devices);
+
+ /* If requested, chroot to a directory */
+ if (chroot_path) {
+ if (chroot(chroot_path) != 0)
+ err(1, "chroot(\"%s\") failed", chroot_path);
+
+ if (chdir("/") != 0)
+ err(1, "chdir(\"/\") failed");
+
+ verbose("chroot done\n");
+ }
+
+ /* If requested, drop privileges */
+ if (user_details) {
+ uid_t u;
+ gid_t g;
+
+ u = user_details->pw_uid;
+ g = user_details->pw_gid;
+
+ if (initgroups(user_details->pw_name, g) != 0)
+ err(1, "initgroups failed");
+
+ if (setresgid(g, g, g) != 0)
+ err(1, "setresgid failed");
+
+ if (setresuid(u, u, u) != 0)
+ err(1, "setresuid failed");
+
+ verbose("Dropping privileges completed\n");
+ }
+
+ /* Finally, run the Guest. This doesn't return. */
+ run_guest();
+}
+/*:*/
+
+/*M:999
+ * Mastery is done: you now know everything I do.
+ *
+ * But surely you have seen code, features and bugs in your wanderings which
+ * you now yearn to attack? That is the real game, and I look forward to you
+ * patching and forking lguest into the Your-Name-Here-visor.
+ *
+ * Farewell, and good coding!
+ * Rusty Russell.
+ */
diff --git a/tools/lguest/lguest.txt b/tools/lguest/lguest.txt
new file mode 100644
index 00000000000..06e1f464951
--- /dev/null
+++ b/tools/lguest/lguest.txt
@@ -0,0 +1,125 @@
+ __
+ (___()'`; Rusty's Remarkably Unreliable Guide to Lguest
+ /, /` - or, A Young Coder's Illustrated Hypervisor
+ \\"--\\ http://lguest.ozlabs.org
+
+Lguest is designed to be a minimal 32-bit x86 hypervisor for the Linux kernel,
+for Linux developers and users to experiment with virtualization with the
+minimum of complexity. Nonetheless, it should have sufficient features to
+make it useful for specific tasks, and, of course, you are encouraged to fork
+and enhance it (see drivers/lguest/README).
+
+Features:
+
+- Kernel module which runs in a normal kernel.
+- Simple I/O model for communication.
+- Simple program to create new guests.
+- Logo contains cute puppies: http://lguest.ozlabs.org
+
+Developer features:
+
+- Fun to hack on.
+- No ABI: being tied to a specific kernel anyway, you can change anything.
+- Many opportunities for improvement or feature implementation.
+
+Running Lguest:
+
+- The easiest way to run lguest is to use same kernel as guest and host.
+ You can configure them differently, but usually it's easiest not to.
+
+ You will need to configure your kernel with the following options:
+
+ "Processor type and features":
+ "Paravirtualized guest support" = Y
+ "Lguest guest support" = Y
+ "High Memory Support" = off/4GB
+ "Alignment value to which kernel should be aligned" = 0x100000
+ (CONFIG_PARAVIRT=y, CONFIG_LGUEST_GUEST=y, CONFIG_HIGHMEM64G=n and
+ CONFIG_PHYSICAL_ALIGN=0x100000)
+
+ "Device Drivers":
+ "Block devices"
+ "Virtio block driver" = M/Y
+ "Network device support"
+ "Universal TUN/TAP device driver support" = M/Y
+ "Virtio network driver" = M/Y
+ (CONFIG_VIRTIO_BLK=m, CONFIG_VIRTIO_NET=m and CONFIG_TUN=m)
+
+ "Virtualization"
+ "Linux hypervisor example code" = M/Y
+ (CONFIG_LGUEST=m)
+
+- A tool called "lguest" is available in this directory: type "make"
+ to build it. If you didn't build your kernel in-tree, use "make
+ O=<builddir>".
+
+- Create or find a root disk image. There are several useful ones
+ around, such as the xm-test tiny root image at
+ http://xm-test.xensource.com/ramdisks/initrd-1.1-i386.img
+
+ For more serious work, I usually use a distribution ISO image and
+ install it under qemu, then make multiple copies:
+
+ dd if=/dev/zero of=rootfile bs=1M count=2048
+ qemu -cdrom image.iso -hda rootfile -net user -net nic -boot d
+
+ Make sure that you install a getty on /dev/hvc0 if you want to log in on the
+ console!
+
+- "modprobe lg" if you built it as a module.
+
+- Run an lguest as root:
+
+ tools/lguest/lguest 64 vmlinux --tunnet=192.168.19.1 \
+ --block=rootfile root=/dev/vda
+
+ Explanation:
+ 64: the amount of memory to use, in MB.
+
+ vmlinux: the kernel image found in the top of your build directory. You
+ can also use a standard bzImage.
+
+ --tunnet=192.168.19.1: configures a "tap" device for networking with this
+ IP address.
+
+ --block=rootfile: a file or block device which becomes /dev/vda
+ inside the guest.
+
+ root=/dev/vda: this (and anything else on the command line) are
+ kernel boot parameters.
+
+- Configuring networking. I usually have the host masquerade, using
+ "iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE" and "echo 1 >
+ /proc/sys/net/ipv4/ip_forward". In this example, I would configure
+ eth0 inside the guest at 192.168.19.2.
+
+ Another method is to bridge the tap device to an external interface
+ using --tunnet=bridge:<bridgename>, and perhaps run dhcp on the guest
+ to obtain an IP address. The bridge needs to be configured first:
+ this option simply adds the tap interface to it.
+
+ A simple example on my system:
+
+ ifconfig eth0 0.0.0.0
+ brctl addbr lg0
+ ifconfig lg0 up
+ brctl addif lg0 eth0
+ dhclient lg0
+
+ Then use --tunnet=bridge:lg0 when launching the guest.
+
+ See:
+
+ http://www.linuxfoundation.org/collaborate/workgroups/networking/bridge
+
+ for general information on how to get bridging to work.
+
+- Random number generation. Using the --rng option will provide a
+ /dev/hwrng in the guest that will read from the host's /dev/random.
+ Use this option in conjunction with rng-tools (see ../hw_random.txt)
+ to provide entropy to the guest kernel's /dev/random.
+
+There is a helpful mailing list at http://ozlabs.org/mailman/listinfo/lguest
+
+Good luck!
+Rusty Russell rusty@rustcorp.com.au.
diff --git a/tools/lib/api/Makefile b/tools/lib/api/Makefile
new file mode 100644
index 00000000000..ce00f7ee645
--- /dev/null
+++ b/tools/lib/api/Makefile
@@ -0,0 +1,44 @@
+include ../../scripts/Makefile.include
+include ../../perf/config/utilities.mak # QUIET_CLEAN
+
+CC = $(CROSS_COMPILE)gcc
+AR = $(CROSS_COMPILE)ar
+
+# guard against environment variables
+LIB_H=
+LIB_OBJS=
+
+LIB_H += fs/debugfs.h
+LIB_H += fs/fs.h
+
+LIB_OBJS += $(OUTPUT)fs/debugfs.o
+LIB_OBJS += $(OUTPUT)fs/fs.o
+
+LIBFILE = libapikfs.a
+
+CFLAGS = -ggdb3 -Wall -Wextra -std=gnu99 -Werror -O6 -D_FORTIFY_SOURCE=2 $(EXTRA_WARNINGS) $(EXTRA_CFLAGS) -fPIC
+EXTLIBS = -lelf -lpthread -lrt -lm
+ALL_CFLAGS = $(CFLAGS) $(BASIC_CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64
+ALL_LDFLAGS = $(LDFLAGS)
+
+RM = rm -f
+
+$(LIBFILE): $(LIB_OBJS)
+ $(QUIET_AR)$(RM) $@ && $(AR) rcs $(OUTPUT)$@ $(LIB_OBJS)
+
+$(LIB_OBJS): $(LIB_H)
+
+libapi_dirs:
+ $(QUIET_MKDIR)mkdir -p $(OUTPUT)fs/
+
+$(OUTPUT)%.o: %.c libapi_dirs
+ $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) $<
+$(OUTPUT)%.s: %.c libapi_dirs
+ $(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $<
+$(OUTPUT)%.o: %.S libapi_dirs
+ $(QUIET_CC)$(CC) -o $@ -c $(ALL_CFLAGS) $<
+
+clean:
+ $(call QUIET_CLEAN, libapi) $(RM) $(LIB_OBJS) $(LIBFILE)
+
+.PHONY: clean
diff --git a/tools/lib/api/fs/debugfs.c b/tools/lib/api/fs/debugfs.c
new file mode 100644
index 00000000000..a74fba6d774
--- /dev/null
+++ b/tools/lib/api/fs/debugfs.c
@@ -0,0 +1,100 @@
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <sys/vfs.h>
+#include <sys/mount.h>
+#include <linux/kernel.h>
+
+#include "debugfs.h"
+
+char debugfs_mountpoint[PATH_MAX + 1] = "/sys/kernel/debug";
+
+static const char * const debugfs_known_mountpoints[] = {
+ "/sys/kernel/debug",
+ "/debug",
+ 0,
+};
+
+static bool debugfs_found;
+
+/* find the path to the mounted debugfs */
+const char *debugfs_find_mountpoint(void)
+{
+ const char * const *ptr;
+ char type[100];
+ FILE *fp;
+
+ if (debugfs_found)
+ return (const char *)debugfs_mountpoint;
+
+ ptr = debugfs_known_mountpoints;
+ while (*ptr) {
+ if (debugfs_valid_mountpoint(*ptr) == 0) {
+ debugfs_found = true;
+ strcpy(debugfs_mountpoint, *ptr);
+ return debugfs_mountpoint;
+ }
+ ptr++;
+ }
+
+ /* give up and parse /proc/mounts */
+ fp = fopen("/proc/mounts", "r");
+ if (fp == NULL)
+ return NULL;
+
+ while (fscanf(fp, "%*s %" STR(PATH_MAX) "s %99s %*s %*d %*d\n",
+ debugfs_mountpoint, type) == 2) {
+ if (strcmp(type, "debugfs") == 0)
+ break;
+ }
+ fclose(fp);
+
+ if (strcmp(type, "debugfs") != 0)
+ return NULL;
+
+ debugfs_found = true;
+
+ return debugfs_mountpoint;
+}
+
+/* verify that a mountpoint is actually a debugfs instance */
+
+int debugfs_valid_mountpoint(const char *debugfs)
+{
+ struct statfs st_fs;
+
+ if (statfs(debugfs, &st_fs) < 0)
+ return -ENOENT;
+ else if (st_fs.f_type != (long) DEBUGFS_MAGIC)
+ return -ENOENT;
+
+ return 0;
+}
+
+/* mount the debugfs somewhere if it's not mounted */
+char *debugfs_mount(const char *mountpoint)
+{
+ /* see if it's already mounted */
+ if (debugfs_find_mountpoint())
+ goto out;
+
+ /* if not mounted and no argument */
+ if (mountpoint == NULL) {
+ /* see if environment variable set */
+ mountpoint = getenv(PERF_DEBUGFS_ENVIRONMENT);
+ /* if no environment variable, use default */
+ if (mountpoint == NULL)
+ mountpoint = "/sys/kernel/debug";
+ }
+
+ if (mount(NULL, mountpoint, "debugfs", 0, NULL) < 0)
+ return NULL;
+
+ /* save the mountpoint */
+ debugfs_found = true;
+ strncpy(debugfs_mountpoint, mountpoint, sizeof(debugfs_mountpoint));
+out:
+ return debugfs_mountpoint;
+}
diff --git a/tools/lib/api/fs/debugfs.h b/tools/lib/api/fs/debugfs.h
new file mode 100644
index 00000000000..f19d3df9609
--- /dev/null
+++ b/tools/lib/api/fs/debugfs.h
@@ -0,0 +1,29 @@
+#ifndef __API_DEBUGFS_H__
+#define __API_DEBUGFS_H__
+
+#define _STR(x) #x
+#define STR(x) _STR(x)
+
+/*
+ * On most systems <limits.h> would have given us this, but not on some systems
+ * (e.g. GNU/Hurd).
+ */
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+#ifndef DEBUGFS_MAGIC
+#define DEBUGFS_MAGIC 0x64626720
+#endif
+
+#ifndef PERF_DEBUGFS_ENVIRONMENT
+#define PERF_DEBUGFS_ENVIRONMENT "PERF_DEBUGFS_DIR"
+#endif
+
+const char *debugfs_find_mountpoint(void);
+int debugfs_valid_mountpoint(const char *debugfs);
+char *debugfs_mount(const char *mountpoint);
+
+extern char debugfs_mountpoint[];
+
+#endif /* __API_DEBUGFS_H__ */
diff --git a/tools/lib/api/fs/fs.c b/tools/lib/api/fs/fs.c
new file mode 100644
index 00000000000..c1b49c36a95
--- /dev/null
+++ b/tools/lib/api/fs/fs.c
@@ -0,0 +1,165 @@
+/* TODO merge/factor in debugfs.c here */
+
+#include <ctype.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/vfs.h>
+
+#include "debugfs.h"
+#include "fs.h"
+
+static const char * const sysfs__fs_known_mountpoints[] = {
+ "/sys",
+ 0,
+};
+
+static const char * const procfs__known_mountpoints[] = {
+ "/proc",
+ 0,
+};
+
+struct fs {
+ const char *name;
+ const char * const *mounts;
+ char path[PATH_MAX + 1];
+ bool found;
+ long magic;
+};
+
+enum {
+ FS__SYSFS = 0,
+ FS__PROCFS = 1,
+};
+
+static struct fs fs__entries[] = {
+ [FS__SYSFS] = {
+ .name = "sysfs",
+ .mounts = sysfs__fs_known_mountpoints,
+ .magic = SYSFS_MAGIC,
+ },
+ [FS__PROCFS] = {
+ .name = "proc",
+ .mounts = procfs__known_mountpoints,
+ .magic = PROC_SUPER_MAGIC,
+ },
+};
+
+static bool fs__read_mounts(struct fs *fs)
+{
+ bool found = false;
+ char type[100];
+ FILE *fp;
+
+ fp = fopen("/proc/mounts", "r");
+ if (fp == NULL)
+ return NULL;
+
+ while (!found &&
+ fscanf(fp, "%*s %" STR(PATH_MAX) "s %99s %*s %*d %*d\n",
+ fs->path, type) == 2) {
+
+ if (strcmp(type, fs->name) == 0)
+ found = true;
+ }
+
+ fclose(fp);
+ return fs->found = found;
+}
+
+static int fs__valid_mount(const char *fs, long magic)
+{
+ struct statfs st_fs;
+
+ if (statfs(fs, &st_fs) < 0)
+ return -ENOENT;
+ else if (st_fs.f_type != magic)
+ return -ENOENT;
+
+ return 0;
+}
+
+static bool fs__check_mounts(struct fs *fs)
+{
+ const char * const *ptr;
+
+ ptr = fs->mounts;
+ while (*ptr) {
+ if (fs__valid_mount(*ptr, fs->magic) == 0) {
+ fs->found = true;
+ strcpy(fs->path, *ptr);
+ return true;
+ }
+ ptr++;
+ }
+
+ return false;
+}
+
+static void mem_toupper(char *f, size_t len)
+{
+ while (len) {
+ *f = toupper(*f);
+ f++;
+ len--;
+ }
+}
+
+/*
+ * Check for "NAME_PATH" environment variable to override fs location (for
+ * testing). This matches the recommendation in Documentation/sysfs-rules.txt
+ * for SYSFS_PATH.
+ */
+static bool fs__env_override(struct fs *fs)
+{
+ char *override_path;
+ size_t name_len = strlen(fs->name);
+ /* name + "_PATH" + '\0' */
+ char upper_name[name_len + 5 + 1];
+ memcpy(upper_name, fs->name, name_len);
+ mem_toupper(upper_name, name_len);
+ strcpy(&upper_name[name_len], "_PATH");
+
+ override_path = getenv(upper_name);
+ if (!override_path)
+ return false;
+
+ fs->found = true;
+ strncpy(fs->path, override_path, sizeof(fs->path));
+ return true;
+}
+
+static const char *fs__get_mountpoint(struct fs *fs)
+{
+ if (fs__env_override(fs))
+ return fs->path;
+
+ if (fs__check_mounts(fs))
+ return fs->path;
+
+ if (fs__read_mounts(fs))
+ return fs->path;
+
+ return NULL;
+}
+
+static const char *fs__mountpoint(int idx)
+{
+ struct fs *fs = &fs__entries[idx];
+
+ if (fs->found)
+ return (const char *)fs->path;
+
+ return fs__get_mountpoint(fs);
+}
+
+#define FS__MOUNTPOINT(name, idx) \
+const char *name##__mountpoint(void) \
+{ \
+ return fs__mountpoint(idx); \
+}
+
+FS__MOUNTPOINT(sysfs, FS__SYSFS);
+FS__MOUNTPOINT(procfs, FS__PROCFS);
diff --git a/tools/lib/api/fs/fs.h b/tools/lib/api/fs/fs.h
new file mode 100644
index 00000000000..cb7049551f3
--- /dev/null
+++ b/tools/lib/api/fs/fs.h
@@ -0,0 +1,14 @@
+#ifndef __API_FS__
+#define __API_FS__
+
+#ifndef SYSFS_MAGIC
+#define SYSFS_MAGIC 0x62656572
+#endif
+
+#ifndef PROC_SUPER_MAGIC
+#define PROC_SUPER_MAGIC 0x9fa0
+#endif
+
+const char *sysfs__mountpoint(void);
+const char *procfs__mountpoint(void);
+#endif /* __API_FS__ */
diff --git a/tools/lib/lockdep/Makefile b/tools/lib/lockdep/Makefile
new file mode 100644
index 00000000000..52f9279c6c1
--- /dev/null
+++ b/tools/lib/lockdep/Makefile
@@ -0,0 +1,243 @@
+# file format version
+FILE_VERSION = 1
+
+LIBLOCKDEP_VERSION=$(shell make --no-print-directory -sC ../../.. kernelversion)
+
+# Makefiles suck: This macro sets a default value of $(2) for the
+# variable named by $(1), unless the variable has been set by
+# environment or command line. This is necessary for CC and AR
+# because make sets default values, so the simpler ?= approach
+# won't work as expected.
+define allow-override
+ $(if $(or $(findstring environment,$(origin $(1))),\
+ $(findstring command line,$(origin $(1)))),,\
+ $(eval $(1) = $(2)))
+endef
+
+# Allow setting CC and AR, or setting CROSS_COMPILE as a prefix.
+$(call allow-override,CC,$(CROSS_COMPILE)gcc)
+$(call allow-override,AR,$(CROSS_COMPILE)ar)
+
+INSTALL = install
+
+# Use DESTDIR for installing into a different root directory.
+# This is useful for building a package. The program will be
+# installed in this directory as if it was the root directory.
+# Then the build tool can move it later.
+DESTDIR ?=
+DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))'
+
+prefix ?= /usr/local
+libdir_relative = lib
+libdir = $(prefix)/$(libdir_relative)
+bindir_relative = bin
+bindir = $(prefix)/$(bindir_relative)
+
+export DESTDIR DESTDIR_SQ INSTALL
+
+# copy a bit from Linux kbuild
+
+ifeq ("$(origin V)", "command line")
+ VERBOSE = $(V)
+endif
+ifndef VERBOSE
+ VERBOSE = 0
+endif
+
+ifeq ("$(origin O)", "command line")
+ BUILD_OUTPUT := $(O)
+endif
+
+ifeq ($(BUILD_SRC),)
+ifneq ($(BUILD_OUTPUT),)
+
+define build_output
+ $(if $(VERBOSE:1=),@)$(MAKE) -C $(BUILD_OUTPUT) \
+ BUILD_SRC=$(CURDIR) -f $(CURDIR)/Makefile $1
+endef
+
+saved-output := $(BUILD_OUTPUT)
+BUILD_OUTPUT := $(shell cd $(BUILD_OUTPUT) && /bin/pwd)
+$(if $(BUILD_OUTPUT),, \
+ $(error output directory "$(saved-output)" does not exist))
+
+all: sub-make
+
+gui: force
+ $(call build_output, all_cmd)
+
+$(filter-out gui,$(MAKECMDGOALS)): sub-make
+
+sub-make: force
+ $(call build_output, $(MAKECMDGOALS))
+
+
+# Leave processing to above invocation of make
+skip-makefile := 1
+
+endif # BUILD_OUTPUT
+endif # BUILD_SRC
+
+# We process the rest of the Makefile if this is the final invocation of make
+ifeq ($(skip-makefile),)
+
+srctree := $(realpath $(if $(BUILD_SRC),$(BUILD_SRC),$(CURDIR)))
+objtree := $(realpath $(CURDIR))
+src := $(srctree)
+obj := $(objtree)
+
+export prefix libdir bindir src obj
+
+# Shell quotes
+libdir_SQ = $(subst ','\'',$(libdir))
+bindir_SQ = $(subst ','\'',$(bindir))
+
+LIB_FILE = liblockdep.a liblockdep.so.$(LIBLOCKDEP_VERSION)
+BIN_FILE = lockdep
+
+CONFIG_INCLUDES =
+CONFIG_LIBS =
+CONFIG_FLAGS =
+
+OBJ = $@
+N =
+
+export Q VERBOSE
+
+INCLUDES = -I. -I/usr/local/include -I./uinclude -I./include -I../../include $(CONFIG_INCLUDES)
+
+# Set compile option CFLAGS if not set elsewhere
+CFLAGS ?= -g -DCONFIG_LOCKDEP -DCONFIG_STACKTRACE -DCONFIG_PROVE_LOCKING -DBITS_PER_LONG=__WORDSIZE -DLIBLOCKDEP_VERSION='"$(LIBLOCKDEP_VERSION)"' -rdynamic -O0 -g
+
+override CFLAGS += $(CONFIG_FLAGS) $(INCLUDES) $(PLUGIN_DIR_SQ)
+
+ifeq ($(VERBOSE),1)
+ Q =
+ print_compile =
+ print_app_build =
+ print_fpic_compile =
+ print_shared_lib_compile =
+ print_install =
+else
+ Q = @
+ print_compile = echo ' CC '$(OBJ);
+ print_app_build = echo ' BUILD '$(OBJ);
+ print_fpic_compile = echo ' CC FPIC '$(OBJ);
+ print_shared_lib_compile = echo ' BUILD SHARED LIB '$(OBJ);
+ print_static_lib_build = echo ' BUILD STATIC LIB '$(OBJ);
+ print_install = echo ' INSTALL '$1' to $(DESTDIR_SQ)$2';
+endif
+
+do_fpic_compile = \
+ ($(print_fpic_compile) \
+ $(CC) -c $(CFLAGS) $(EXT) -fPIC $< -o $@)
+
+do_app_build = \
+ ($(print_app_build) \
+ $(CC) $^ -rdynamic -o $@ $(CONFIG_LIBS) $(LIBS))
+
+do_compile_shared_library = \
+ ($(print_shared_lib_compile) \
+ $(CC) --shared $^ -o $@ -lpthread -ldl -Wl,-soname='"$@"';$(shell ln -s $@ liblockdep.so))
+
+do_build_static_lib = \
+ ($(print_static_lib_build) \
+ $(RM) $@; $(AR) rcs $@ $^)
+
+
+define do_compile
+ $(print_compile) \
+ $(CC) -c $(CFLAGS) $(EXT) $< -o $(obj)/$@;
+endef
+
+$(obj)/%.o: $(src)/%.c
+ $(Q)$(call do_compile)
+
+%.o: $(src)/%.c
+ $(Q)$(call do_compile)
+
+PEVENT_LIB_OBJS = common.o lockdep.o preload.o rbtree.o
+
+ALL_OBJS = $(PEVENT_LIB_OBJS)
+
+CMD_TARGETS = $(LIB_FILE)
+
+TARGETS = $(CMD_TARGETS)
+
+
+all: all_cmd
+
+all_cmd: $(CMD_TARGETS)
+
+liblockdep.so.$(LIBLOCKDEP_VERSION): $(PEVENT_LIB_OBJS)
+ $(Q)$(do_compile_shared_library)
+
+liblockdep.a: $(PEVENT_LIB_OBJS)
+ $(Q)$(do_build_static_lib)
+
+$(PEVENT_LIB_OBJS): %.o: $(src)/%.c
+ $(Q)$(do_fpic_compile)
+
+## make deps
+
+all_objs := $(sort $(ALL_OBJS))
+all_deps := $(all_objs:%.o=.%.d)
+
+# let .d file also depends on the source and header files
+define check_deps
+ @set -e; $(RM) $@; \
+ $(CC) -MM $(CFLAGS) $< > $@.$$$$; \
+ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
+ $(RM) $@.$$$$
+endef
+
+$(all_deps): .%.d: $(src)/%.c
+ $(Q)$(call check_deps)
+
+$(all_objs) : %.o : .%.d
+
+dep_includes := $(wildcard $(all_deps))
+
+ifneq ($(dep_includes),)
+ include $(dep_includes)
+endif
+
+### Detect environment changes
+TRACK_CFLAGS = $(subst ','\'',$(CFLAGS)):$(ARCH):$(CROSS_COMPILE)
+
+tags: force
+ $(RM) tags
+ find . -name '*.[ch]' | xargs ctags --extra=+f --c-kinds=+px \
+ --regex-c++='/_PE\(([^,)]*).*/PEVENT_ERRNO__\1/'
+
+TAGS: force
+ $(RM) TAGS
+ find . -name '*.[ch]' | xargs etags \
+ --regex='/_PE(\([^,)]*\).*/PEVENT_ERRNO__\1/'
+
+define do_install
+ $(print_install) \
+ if [ ! -d '$(DESTDIR_SQ)$2' ]; then \
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$2'; \
+ fi; \
+ $(INSTALL) $1 '$(DESTDIR_SQ)$2'
+endef
+
+install_lib: all_cmd
+ $(Q)$(call do_install,$(LIB_FILE),$(libdir_SQ))
+ $(Q)$(call do_install,$(BIN_FILE),$(bindir_SQ))
+
+install: install_lib
+
+clean:
+ $(RM) *.o *~ $(TARGETS) *.a *liblockdep*.so* $(VERSION_FILES) .*.d
+ $(RM) tags TAGS
+
+endif # skip-makefile
+
+PHONY += force
+force:
+
+# Declare the contents of the .PHONY variable as phony. We keep that
+# information in a variable so we can use it in if_changed and friends.
+.PHONY: $(PHONY)
diff --git a/tools/lib/lockdep/common.c b/tools/lib/lockdep/common.c
new file mode 100644
index 00000000000..8ef602f18a3
--- /dev/null
+++ b/tools/lib/lockdep/common.c
@@ -0,0 +1,33 @@
+#include <stddef.h>
+#include <stdbool.h>
+#include <linux/compiler.h>
+#include <linux/lockdep.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+
+static __thread struct task_struct current_obj;
+
+/* lockdep wants these */
+bool debug_locks = true;
+bool debug_locks_silent;
+
+__attribute__((constructor)) static void liblockdep_init(void)
+{
+ lockdep_init();
+}
+
+__attribute__((destructor)) static void liblockdep_exit(void)
+{
+ debug_check_no_locks_held(&current_obj);
+}
+
+struct task_struct *__curr(void)
+{
+ if (current_obj.pid == 0) {
+ /* Makes lockdep output pretty */
+ prctl(PR_GET_NAME, current_obj.comm);
+ current_obj.pid = syscall(__NR_gettid);
+ }
+
+ return &current_obj;
+}
diff --git a/tools/lib/lockdep/include/liblockdep/common.h b/tools/lib/lockdep/include/liblockdep/common.h
new file mode 100644
index 00000000000..0bda630027c
--- /dev/null
+++ b/tools/lib/lockdep/include/liblockdep/common.h
@@ -0,0 +1,50 @@
+#ifndef _LIBLOCKDEP_COMMON_H
+#define _LIBLOCKDEP_COMMON_H
+
+#include <pthread.h>
+
+#define NR_LOCKDEP_CACHING_CLASSES 2
+#define MAX_LOCKDEP_SUBCLASSES 8UL
+
+#ifndef CALLER_ADDR0
+#define CALLER_ADDR0 ((unsigned long)__builtin_return_address(0))
+#endif
+
+#ifndef _RET_IP_
+#define _RET_IP_ CALLER_ADDR0
+#endif
+
+#ifndef _THIS_IP_
+#define _THIS_IP_ ({ __label__ __here; __here: (unsigned long)&&__here; })
+#endif
+
+struct lockdep_subclass_key {
+ char __one_byte;
+};
+
+struct lock_class_key {
+ struct lockdep_subclass_key subkeys[MAX_LOCKDEP_SUBCLASSES];
+};
+
+struct lockdep_map {
+ struct lock_class_key *key;
+ struct lock_class *class_cache[NR_LOCKDEP_CACHING_CLASSES];
+ const char *name;
+#ifdef CONFIG_LOCK_STAT
+ int cpu;
+ unsigned long ip;
+#endif
+};
+
+void lockdep_init_map(struct lockdep_map *lock, const char *name,
+ struct lock_class_key *key, int subclass);
+void lock_acquire(struct lockdep_map *lock, unsigned int subclass,
+ int trylock, int read, int check,
+ struct lockdep_map *nest_lock, unsigned long ip);
+void lock_release(struct lockdep_map *lock, int nested,
+ unsigned long ip);
+
+#define STATIC_LOCKDEP_MAP_INIT(_name, _key) \
+ { .name = (_name), .key = (void *)(_key), }
+
+#endif
diff --git a/tools/lib/lockdep/include/liblockdep/mutex.h b/tools/lib/lockdep/include/liblockdep/mutex.h
new file mode 100644
index 00000000000..ee53a42818c
--- /dev/null
+++ b/tools/lib/lockdep/include/liblockdep/mutex.h
@@ -0,0 +1,70 @@
+#ifndef _LIBLOCKDEP_MUTEX_H
+#define _LIBLOCKDEP_MUTEX_H
+
+#include <pthread.h>
+#include "common.h"
+
+struct liblockdep_pthread_mutex {
+ pthread_mutex_t mutex;
+ struct lockdep_map dep_map;
+};
+
+typedef struct liblockdep_pthread_mutex liblockdep_pthread_mutex_t;
+
+#define LIBLOCKDEP_PTHREAD_MUTEX_INITIALIZER(mtx) \
+ (const struct liblockdep_pthread_mutex) { \
+ .mutex = PTHREAD_MUTEX_INITIALIZER, \
+ .dep_map = STATIC_LOCKDEP_MAP_INIT(#mtx, &((&(mtx))->dep_map)), \
+}
+
+static inline int __mutex_init(liblockdep_pthread_mutex_t *lock,
+ const char *name,
+ struct lock_class_key *key,
+ const pthread_mutexattr_t *__mutexattr)
+{
+ lockdep_init_map(&lock->dep_map, name, key, 0);
+ return pthread_mutex_init(&lock->mutex, __mutexattr);
+}
+
+#define liblockdep_pthread_mutex_init(mutex, mutexattr) \
+({ \
+ static struct lock_class_key __key; \
+ \
+ __mutex_init((mutex), #mutex, &__key, (mutexattr)); \
+})
+
+static inline int liblockdep_pthread_mutex_lock(liblockdep_pthread_mutex_t *lock)
+{
+ lock_acquire(&lock->dep_map, 0, 0, 0, 1, NULL, (unsigned long)_RET_IP_);
+ return pthread_mutex_lock(&lock->mutex);
+}
+
+static inline int liblockdep_pthread_mutex_unlock(liblockdep_pthread_mutex_t *lock)
+{
+ lock_release(&lock->dep_map, 0, (unsigned long)_RET_IP_);
+ return pthread_mutex_unlock(&lock->mutex);
+}
+
+static inline int liblockdep_pthread_mutex_trylock(liblockdep_pthread_mutex_t *lock)
+{
+ lock_acquire(&lock->dep_map, 0, 1, 0, 1, NULL, (unsigned long)_RET_IP_);
+ return pthread_mutex_trylock(&lock->mutex) == 0 ? 1 : 0;
+}
+
+static inline int liblockdep_pthread_mutex_destroy(liblockdep_pthread_mutex_t *lock)
+{
+ return pthread_mutex_destroy(&lock->mutex);
+}
+
+#ifdef __USE_LIBLOCKDEP
+
+#define pthread_mutex_t liblockdep_pthread_mutex_t
+#define pthread_mutex_init liblockdep_pthread_mutex_init
+#define pthread_mutex_lock liblockdep_pthread_mutex_lock
+#define pthread_mutex_unlock liblockdep_pthread_mutex_unlock
+#define pthread_mutex_trylock liblockdep_pthread_mutex_trylock
+#define pthread_mutex_destroy liblockdep_pthread_mutex_destroy
+
+#endif
+
+#endif
diff --git a/tools/lib/lockdep/include/liblockdep/rwlock.h b/tools/lib/lockdep/include/liblockdep/rwlock.h
new file mode 100644
index 00000000000..4ec03f86155
--- /dev/null
+++ b/tools/lib/lockdep/include/liblockdep/rwlock.h
@@ -0,0 +1,86 @@
+#ifndef _LIBLOCKDEP_RWLOCK_H
+#define _LIBLOCKDEP_RWLOCK_H
+
+#include <pthread.h>
+#include "common.h"
+
+struct liblockdep_pthread_rwlock {
+ pthread_rwlock_t rwlock;
+ struct lockdep_map dep_map;
+};
+
+typedef struct liblockdep_pthread_rwlock liblockdep_pthread_rwlock_t;
+
+#define LIBLOCKDEP_PTHREAD_RWLOCK_INITIALIZER(rwl) \
+ (struct liblockdep_pthread_rwlock) { \
+ .rwlock = PTHREAD_RWLOCK_INITIALIZER, \
+ .dep_map = STATIC_LOCKDEP_MAP_INIT(#rwl, &((&(rwl))->dep_map)), \
+}
+
+static inline int __rwlock_init(liblockdep_pthread_rwlock_t *lock,
+ const char *name,
+ struct lock_class_key *key,
+ const pthread_rwlockattr_t *attr)
+{
+ lockdep_init_map(&lock->dep_map, name, key, 0);
+
+ return pthread_rwlock_init(&lock->rwlock, attr);
+}
+
+#define liblockdep_pthread_rwlock_init(lock, attr) \
+({ \
+ static struct lock_class_key __key; \
+ \
+ __rwlock_init((lock), #lock, &__key, (attr)); \
+})
+
+static inline int liblockdep_pthread_rwlock_rdlock(liblockdep_pthread_rwlock_t *lock)
+{
+ lock_acquire(&lock->dep_map, 0, 0, 2, 1, NULL, (unsigned long)_RET_IP_);
+ return pthread_rwlock_rdlock(&lock->rwlock);
+
+}
+
+static inline int liblockdep_pthread_rwlock_unlock(liblockdep_pthread_rwlock_t *lock)
+{
+ lock_release(&lock->dep_map, 0, (unsigned long)_RET_IP_);
+ return pthread_rwlock_unlock(&lock->rwlock);
+}
+
+static inline int liblockdep_pthread_rwlock_wrlock(liblockdep_pthread_rwlock_t *lock)
+{
+ lock_acquire(&lock->dep_map, 0, 0, 0, 1, NULL, (unsigned long)_RET_IP_);
+ return pthread_rwlock_wrlock(&lock->rwlock);
+}
+
+static inline int liblockdep_pthread_rwlock_tryrdlock(liblockdep_pthread_rwlock_t *lock)
+{
+ lock_acquire(&lock->dep_map, 0, 1, 2, 1, NULL, (unsigned long)_RET_IP_);
+ return pthread_rwlock_tryrdlock(&lock->rwlock) == 0 ? 1 : 0;
+}
+
+static inline int liblockdep_pthread_rwlock_trywlock(liblockdep_pthread_rwlock_t *lock)
+{
+ lock_acquire(&lock->dep_map, 0, 1, 0, 1, NULL, (unsigned long)_RET_IP_);
+ return pthread_rwlock_trywlock(&lock->rwlock) == 0 ? 1 : 0;
+}
+
+static inline int liblockdep_rwlock_destroy(liblockdep_pthread_rwlock_t *lock)
+{
+ return pthread_rwlock_destroy(&lock->rwlock);
+}
+
+#ifdef __USE_LIBLOCKDEP
+
+#define pthread_rwlock_t liblockdep_pthread_rwlock_t
+#define pthread_rwlock_init liblockdep_pthread_rwlock_init
+#define pthread_rwlock_rdlock liblockdep_pthread_rwlock_rdlock
+#define pthread_rwlock_unlock liblockdep_pthread_rwlock_unlock
+#define pthread_rwlock_wrlock liblockdep_pthread_rwlock_wrlock
+#define pthread_rwlock_tryrdlock liblockdep_pthread_rwlock_tryrdlock
+#define pthread_rwlock_trywlock liblockdep_pthread_rwlock_trywlock
+#define pthread_rwlock_destroy liblockdep_rwlock_destroy
+
+#endif
+
+#endif
diff --git a/tools/lib/lockdep/lockdep b/tools/lib/lockdep/lockdep
new file mode 100755
index 00000000000..49af9fe19f5
--- /dev/null
+++ b/tools/lib/lockdep/lockdep
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+LD_PRELOAD="./liblockdep.so $LD_PRELOAD" "$@"
diff --git a/tools/lib/lockdep/lockdep.c b/tools/lib/lockdep/lockdep.c
new file mode 100644
index 00000000000..f42b7e9aa48
--- /dev/null
+++ b/tools/lib/lockdep/lockdep.c
@@ -0,0 +1,2 @@
+#include <linux/lockdep.h>
+#include "../../../kernel/locking/lockdep.c"
diff --git a/tools/lib/lockdep/lockdep_internals.h b/tools/lib/lockdep/lockdep_internals.h
new file mode 100644
index 00000000000..29d0c954cc2
--- /dev/null
+++ b/tools/lib/lockdep/lockdep_internals.h
@@ -0,0 +1 @@
+#include "../../../kernel/locking/lockdep_internals.h"
diff --git a/tools/lib/lockdep/lockdep_states.h b/tools/lib/lockdep/lockdep_states.h
new file mode 100644
index 00000000000..248d235efda
--- /dev/null
+++ b/tools/lib/lockdep/lockdep_states.h
@@ -0,0 +1 @@
+#include "../../../kernel/locking/lockdep_states.h"
diff --git a/tools/lib/lockdep/preload.c b/tools/lib/lockdep/preload.c
new file mode 100644
index 00000000000..6f803609e49
--- /dev/null
+++ b/tools/lib/lockdep/preload.c
@@ -0,0 +1,445 @@
+#define _GNU_SOURCE
+#include <pthread.h>
+#include <stdio.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <sysexits.h>
+#include "include/liblockdep/mutex.h"
+#include "../../../include/linux/rbtree.h"
+
+/**
+ * struct lock_lookup - liblockdep's view of a single unique lock
+ * @orig: pointer to the original pthread lock, used for lookups
+ * @dep_map: lockdep's dep_map structure
+ * @key: lockdep's key structure
+ * @node: rb-tree node used to store the lock in a global tree
+ * @name: a unique name for the lock
+ */
+struct lock_lookup {
+ void *orig; /* Original pthread lock, used for lookups */
+ struct lockdep_map dep_map; /* Since all locks are dynamic, we need
+ * a dep_map and a key for each lock */
+ /*
+ * Wait, there's no support for key classes? Yup :(
+ * Most big projects wrap the pthread api with their own calls to
+ * be compatible with different locking methods. This means that
+ * "classes" will be brokes since the function that creates all
+ * locks will point to a generic locking function instead of the
+ * actual code that wants to do the locking.
+ */
+ struct lock_class_key key;
+ struct rb_node node;
+#define LIBLOCKDEP_MAX_LOCK_NAME 22
+ char name[LIBLOCKDEP_MAX_LOCK_NAME];
+};
+
+/* This is where we store our locks */
+static struct rb_root locks = RB_ROOT;
+static pthread_rwlock_t locks_rwlock = PTHREAD_RWLOCK_INITIALIZER;
+
+/* pthread mutex API */
+
+#ifdef __GLIBC__
+extern int __pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
+extern int __pthread_mutex_lock(pthread_mutex_t *mutex);
+extern int __pthread_mutex_trylock(pthread_mutex_t *mutex);
+extern int __pthread_mutex_unlock(pthread_mutex_t *mutex);
+extern int __pthread_mutex_destroy(pthread_mutex_t *mutex);
+#else
+#define __pthread_mutex_init NULL
+#define __pthread_mutex_lock NULL
+#define __pthread_mutex_trylock NULL
+#define __pthread_mutex_unlock NULL
+#define __pthread_mutex_destroy NULL
+#endif
+static int (*ll_pthread_mutex_init)(pthread_mutex_t *mutex,
+ const pthread_mutexattr_t *attr) = __pthread_mutex_init;
+static int (*ll_pthread_mutex_lock)(pthread_mutex_t *mutex) = __pthread_mutex_lock;
+static int (*ll_pthread_mutex_trylock)(pthread_mutex_t *mutex) = __pthread_mutex_trylock;
+static int (*ll_pthread_mutex_unlock)(pthread_mutex_t *mutex) = __pthread_mutex_unlock;
+static int (*ll_pthread_mutex_destroy)(pthread_mutex_t *mutex) = __pthread_mutex_destroy;
+
+/* pthread rwlock API */
+
+#ifdef __GLIBC__
+extern int __pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
+extern int __pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
+extern int __pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
+extern int __pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
+extern int __pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
+extern int __pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
+extern int __pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
+#else
+#define __pthread_rwlock_init NULL
+#define __pthread_rwlock_destroy NULL
+#define __pthread_rwlock_wrlock NULL
+#define __pthread_rwlock_trywrlock NULL
+#define __pthread_rwlock_rdlock NULL
+#define __pthread_rwlock_tryrdlock NULL
+#define __pthread_rwlock_unlock NULL
+#endif
+
+static int (*ll_pthread_rwlock_init)(pthread_rwlock_t *rwlock,
+ const pthread_rwlockattr_t *attr) = __pthread_rwlock_init;
+static int (*ll_pthread_rwlock_destroy)(pthread_rwlock_t *rwlock) = __pthread_rwlock_destroy;
+static int (*ll_pthread_rwlock_rdlock)(pthread_rwlock_t *rwlock) = __pthread_rwlock_rdlock;
+static int (*ll_pthread_rwlock_tryrdlock)(pthread_rwlock_t *rwlock) = __pthread_rwlock_tryrdlock;
+static int (*ll_pthread_rwlock_trywrlock)(pthread_rwlock_t *rwlock) = __pthread_rwlock_trywrlock;
+static int (*ll_pthread_rwlock_wrlock)(pthread_rwlock_t *rwlock) = __pthread_rwlock_wrlock;
+static int (*ll_pthread_rwlock_unlock)(pthread_rwlock_t *rwlock) = __pthread_rwlock_unlock;
+
+enum { none, prepare, done, } __init_state;
+static void init_preload(void);
+static void try_init_preload(void)
+{
+ if (__init_state != done)
+ init_preload();
+}
+
+static struct rb_node **__get_lock_node(void *lock, struct rb_node **parent)
+{
+ struct rb_node **node = &locks.rb_node;
+ struct lock_lookup *l;
+
+ *parent = NULL;
+
+ while (*node) {
+ l = rb_entry(*node, struct lock_lookup, node);
+
+ *parent = *node;
+ if (lock < l->orig)
+ node = &l->node.rb_left;
+ else if (lock > l->orig)
+ node = &l->node.rb_right;
+ else
+ return node;
+ }
+
+ return node;
+}
+
+#ifndef LIBLOCKDEP_STATIC_ENTRIES
+#define LIBLOCKDEP_STATIC_ENTRIES 1024
+#endif
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+static struct lock_lookup __locks[LIBLOCKDEP_STATIC_ENTRIES];
+static int __locks_nr;
+
+static inline bool is_static_lock(struct lock_lookup *lock)
+{
+ return lock >= __locks && lock < __locks + ARRAY_SIZE(__locks);
+}
+
+static struct lock_lookup *alloc_lock(void)
+{
+ if (__init_state != done) {
+ /*
+ * Some programs attempt to initialize and use locks in their
+ * allocation path. This means that a call to malloc() would
+ * result in locks being initialized and locked.
+ *
+ * Why is it an issue for us? dlsym() below will try allocating
+ * to give us the original function. Since this allocation will
+ * result in a locking operations, we have to let pthread deal
+ * with it, but we can't! we don't have the pointer to the
+ * original API since we're inside dlsym() trying to get it
+ */
+
+ int idx = __locks_nr++;
+ if (idx >= ARRAY_SIZE(__locks)) {
+ fprintf(stderr,
+ "LOCKDEP error: insufficient LIBLOCKDEP_STATIC_ENTRIES\n");
+ exit(EX_UNAVAILABLE);
+ }
+ return __locks + idx;
+ }
+
+ return malloc(sizeof(struct lock_lookup));
+}
+
+static inline void free_lock(struct lock_lookup *lock)
+{
+ if (likely(!is_static_lock(lock)))
+ free(lock);
+}
+
+/**
+ * __get_lock - find or create a lock instance
+ * @lock: pointer to a pthread lock function
+ *
+ * Try to find an existing lock in the rbtree using the provided pointer. If
+ * one wasn't found - create it.
+ */
+static struct lock_lookup *__get_lock(void *lock)
+{
+ struct rb_node **node, *parent;
+ struct lock_lookup *l;
+
+ ll_pthread_rwlock_rdlock(&locks_rwlock);
+ node = __get_lock_node(lock, &parent);
+ ll_pthread_rwlock_unlock(&locks_rwlock);
+ if (*node) {
+ return rb_entry(*node, struct lock_lookup, node);
+ }
+
+ /* We didn't find the lock, let's create it */
+ l = alloc_lock();
+ if (l == NULL)
+ return NULL;
+
+ l->orig = lock;
+ /*
+ * Currently the name of the lock is the ptr value of the pthread lock,
+ * while not optimal, it makes debugging a bit easier.
+ *
+ * TODO: Get the real name of the lock using libdwarf
+ */
+ sprintf(l->name, "%p", lock);
+ lockdep_init_map(&l->dep_map, l->name, &l->key, 0);
+
+ ll_pthread_rwlock_wrlock(&locks_rwlock);
+ /* This might have changed since the last time we fetched it */
+ node = __get_lock_node(lock, &parent);
+ rb_link_node(&l->node, parent, node);
+ rb_insert_color(&l->node, &locks);
+ ll_pthread_rwlock_unlock(&locks_rwlock);
+
+ return l;
+}
+
+static void __del_lock(struct lock_lookup *lock)
+{
+ ll_pthread_rwlock_wrlock(&locks_rwlock);
+ rb_erase(&lock->node, &locks);
+ ll_pthread_rwlock_unlock(&locks_rwlock);
+ free_lock(lock);
+}
+
+int pthread_mutex_init(pthread_mutex_t *mutex,
+ const pthread_mutexattr_t *attr)
+{
+ int r;
+
+ /*
+ * We keep trying to init our preload module because there might be
+ * code in init sections that tries to touch locks before we are
+ * initialized, in that case we'll need to manually call preload
+ * to get us going.
+ *
+ * Funny enough, kernel's lockdep had the same issue, and used
+ * (almost) the same solution. See look_up_lock_class() in
+ * kernel/locking/lockdep.c for details.
+ */
+ try_init_preload();
+
+ r = ll_pthread_mutex_init(mutex, attr);
+ if (r == 0)
+ /*
+ * We do a dummy initialization here so that lockdep could
+ * warn us if something fishy is going on - such as
+ * initializing a held lock.
+ */
+ __get_lock(mutex);
+
+ return r;
+}
+
+int pthread_mutex_lock(pthread_mutex_t *mutex)
+{
+ int r;
+
+ try_init_preload();
+
+ lock_acquire(&__get_lock(mutex)->dep_map, 0, 0, 0, 1, NULL,
+ (unsigned long)_RET_IP_);
+ /*
+ * Here's the thing with pthread mutexes: unlike the kernel variant,
+ * they can fail.
+ *
+ * This means that the behaviour here is a bit different from what's
+ * going on in the kernel: there we just tell lockdep that we took the
+ * lock before actually taking it, but here we must deal with the case
+ * that locking failed.
+ *
+ * To do that we'll "release" the lock if locking failed - this way
+ * we'll get lockdep doing the correct checks when we try to take
+ * the lock, and if that fails - we'll be back to the correct
+ * state by releasing it.
+ */
+ r = ll_pthread_mutex_lock(mutex);
+ if (r)
+ lock_release(&__get_lock(mutex)->dep_map, 0, (unsigned long)_RET_IP_);
+
+ return r;
+}
+
+int pthread_mutex_trylock(pthread_mutex_t *mutex)
+{
+ int r;
+
+ try_init_preload();
+
+ lock_acquire(&__get_lock(mutex)->dep_map, 0, 1, 0, 1, NULL, (unsigned long)_RET_IP_);
+ r = ll_pthread_mutex_trylock(mutex);
+ if (r)
+ lock_release(&__get_lock(mutex)->dep_map, 0, (unsigned long)_RET_IP_);
+
+ return r;
+}
+
+int pthread_mutex_unlock(pthread_mutex_t *mutex)
+{
+ int r;
+
+ try_init_preload();
+
+ lock_release(&__get_lock(mutex)->dep_map, 0, (unsigned long)_RET_IP_);
+ /*
+ * Just like taking a lock, only in reverse!
+ *
+ * If we fail releasing the lock, tell lockdep we're holding it again.
+ */
+ r = ll_pthread_mutex_unlock(mutex);
+ if (r)
+ lock_acquire(&__get_lock(mutex)->dep_map, 0, 0, 0, 1, NULL, (unsigned long)_RET_IP_);
+
+ return r;
+}
+
+int pthread_mutex_destroy(pthread_mutex_t *mutex)
+{
+ try_init_preload();
+
+ /*
+ * Let's see if we're releasing a lock that's held.
+ *
+ * TODO: Hook into free() and add that check there as well.
+ */
+ debug_check_no_locks_freed(mutex, mutex + sizeof(*mutex));
+ __del_lock(__get_lock(mutex));
+ return ll_pthread_mutex_destroy(mutex);
+}
+
+/* This is the rwlock part, very similar to what happened with mutex above */
+int pthread_rwlock_init(pthread_rwlock_t *rwlock,
+ const pthread_rwlockattr_t *attr)
+{
+ int r;
+
+ try_init_preload();
+
+ r = ll_pthread_rwlock_init(rwlock, attr);
+ if (r == 0)
+ __get_lock(rwlock);
+
+ return r;
+}
+
+int pthread_rwlock_destroy(pthread_rwlock_t *rwlock)
+{
+ try_init_preload();
+
+ debug_check_no_locks_freed(rwlock, rwlock + sizeof(*rwlock));
+ __del_lock(__get_lock(rwlock));
+ return ll_pthread_rwlock_destroy(rwlock);
+}
+
+int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock)
+{
+ int r;
+
+ init_preload();
+
+ lock_acquire(&__get_lock(rwlock)->dep_map, 0, 0, 2, 1, NULL, (unsigned long)_RET_IP_);
+ r = ll_pthread_rwlock_rdlock(rwlock);
+ if (r)
+ lock_release(&__get_lock(rwlock)->dep_map, 0, (unsigned long)_RET_IP_);
+
+ return r;
+}
+
+int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock)
+{
+ int r;
+
+ init_preload();
+
+ lock_acquire(&__get_lock(rwlock)->dep_map, 0, 1, 2, 1, NULL, (unsigned long)_RET_IP_);
+ r = ll_pthread_rwlock_tryrdlock(rwlock);
+ if (r)
+ lock_release(&__get_lock(rwlock)->dep_map, 0, (unsigned long)_RET_IP_);
+
+ return r;
+}
+
+int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock)
+{
+ int r;
+
+ init_preload();
+
+ lock_acquire(&__get_lock(rwlock)->dep_map, 0, 1, 0, 1, NULL, (unsigned long)_RET_IP_);
+ r = ll_pthread_rwlock_trywrlock(rwlock);
+ if (r)
+ lock_release(&__get_lock(rwlock)->dep_map, 0, (unsigned long)_RET_IP_);
+
+ return r;
+}
+
+int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock)
+{
+ int r;
+
+ init_preload();
+
+ lock_acquire(&__get_lock(rwlock)->dep_map, 0, 0, 0, 1, NULL, (unsigned long)_RET_IP_);
+ r = ll_pthread_rwlock_wrlock(rwlock);
+ if (r)
+ lock_release(&__get_lock(rwlock)->dep_map, 0, (unsigned long)_RET_IP_);
+
+ return r;
+}
+
+int pthread_rwlock_unlock(pthread_rwlock_t *rwlock)
+{
+ int r;
+
+ init_preload();
+
+ lock_release(&__get_lock(rwlock)->dep_map, 0, (unsigned long)_RET_IP_);
+ r = ll_pthread_rwlock_unlock(rwlock);
+ if (r)
+ lock_acquire(&__get_lock(rwlock)->dep_map, 0, 0, 0, 1, NULL, (unsigned long)_RET_IP_);
+
+ return r;
+}
+
+__attribute__((constructor)) static void init_preload(void)
+{
+ if (__init_state == done)
+ return;
+
+#ifndef __GLIBC__
+ __init_state = prepare;
+
+ ll_pthread_mutex_init = dlsym(RTLD_NEXT, "pthread_mutex_init");
+ ll_pthread_mutex_lock = dlsym(RTLD_NEXT, "pthread_mutex_lock");
+ ll_pthread_mutex_trylock = dlsym(RTLD_NEXT, "pthread_mutex_trylock");
+ ll_pthread_mutex_unlock = dlsym(RTLD_NEXT, "pthread_mutex_unlock");
+ ll_pthread_mutex_destroy = dlsym(RTLD_NEXT, "pthread_mutex_destroy");
+
+ ll_pthread_rwlock_init = dlsym(RTLD_NEXT, "pthread_rwlock_init");
+ ll_pthread_rwlock_destroy = dlsym(RTLD_NEXT, "pthread_rwlock_destroy");
+ ll_pthread_rwlock_rdlock = dlsym(RTLD_NEXT, "pthread_rwlock_rdlock");
+ ll_pthread_rwlock_tryrdlock = dlsym(RTLD_NEXT, "pthread_rwlock_tryrdlock");
+ ll_pthread_rwlock_wrlock = dlsym(RTLD_NEXT, "pthread_rwlock_wrlock");
+ ll_pthread_rwlock_trywrlock = dlsym(RTLD_NEXT, "pthread_rwlock_trywrlock");
+ ll_pthread_rwlock_unlock = dlsym(RTLD_NEXT, "pthread_rwlock_unlock");
+#endif
+
+ lockdep_init();
+
+ __init_state = done;
+}
diff --git a/tools/lib/lockdep/rbtree.c b/tools/lib/lockdep/rbtree.c
new file mode 100644
index 00000000000..f7f43033c8b
--- /dev/null
+++ b/tools/lib/lockdep/rbtree.c
@@ -0,0 +1 @@
+#include "../../../lib/rbtree.c"
diff --git a/tools/lib/lockdep/run_tests.sh b/tools/lib/lockdep/run_tests.sh
new file mode 100755
index 00000000000..5334ad9d39b
--- /dev/null
+++ b/tools/lib/lockdep/run_tests.sh
@@ -0,0 +1,27 @@
+#! /bin/bash
+
+make &> /dev/null
+
+for i in `ls tests/*.c`; do
+ testname=$(basename -s .c "$i")
+ gcc -o tests/$testname -pthread -lpthread $i liblockdep.a -Iinclude -D__USE_LIBLOCKDEP &> /dev/null
+ echo -ne "$testname... "
+ if [ $(timeout 1 ./tests/$testname | wc -l) -gt 0 ]; then
+ echo "PASSED!"
+ else
+ echo "FAILED!"
+ fi
+ rm tests/$testname
+done
+
+for i in `ls tests/*.c`; do
+ testname=$(basename -s .c "$i")
+ gcc -o tests/$testname -pthread -lpthread -Iinclude $i &> /dev/null
+ echo -ne "(PRELOAD) $testname... "
+ if [ $(timeout 1 ./lockdep ./tests/$testname | wc -l) -gt 0 ]; then
+ echo "PASSED!"
+ else
+ echo "FAILED!"
+ fi
+ rm tests/$testname
+done
diff --git a/tools/lib/lockdep/tests/AA.c b/tools/lib/lockdep/tests/AA.c
new file mode 100644
index 00000000000..0f782ff404a
--- /dev/null
+++ b/tools/lib/lockdep/tests/AA.c
@@ -0,0 +1,13 @@
+#include <liblockdep/mutex.h>
+
+void main(void)
+{
+ pthread_mutex_t a, b;
+
+ pthread_mutex_init(&a, NULL);
+ pthread_mutex_init(&b, NULL);
+
+ pthread_mutex_lock(&a);
+ pthread_mutex_lock(&b);
+ pthread_mutex_lock(&a);
+}
diff --git a/tools/lib/lockdep/tests/ABBA.c b/tools/lib/lockdep/tests/ABBA.c
new file mode 100644
index 00000000000..07f0e29d548
--- /dev/null
+++ b/tools/lib/lockdep/tests/ABBA.c
@@ -0,0 +1,13 @@
+#include <liblockdep/mutex.h>
+#include "common.h"
+
+void main(void)
+{
+ pthread_mutex_t a, b;
+
+ pthread_mutex_init(&a, NULL);
+ pthread_mutex_init(&b, NULL);
+
+ LOCK_UNLOCK_2(a, b);
+ LOCK_UNLOCK_2(b, a);
+}
diff --git a/tools/lib/lockdep/tests/ABBCCA.c b/tools/lib/lockdep/tests/ABBCCA.c
new file mode 100644
index 00000000000..843db09ac66
--- /dev/null
+++ b/tools/lib/lockdep/tests/ABBCCA.c
@@ -0,0 +1,15 @@
+#include <liblockdep/mutex.h>
+#include "common.h"
+
+void main(void)
+{
+ pthread_mutex_t a, b, c;
+
+ pthread_mutex_init(&a, NULL);
+ pthread_mutex_init(&b, NULL);
+ pthread_mutex_init(&c, NULL);
+
+ LOCK_UNLOCK_2(a, b);
+ LOCK_UNLOCK_2(b, c);
+ LOCK_UNLOCK_2(c, a);
+}
diff --git a/tools/lib/lockdep/tests/ABBCCDDA.c b/tools/lib/lockdep/tests/ABBCCDDA.c
new file mode 100644
index 00000000000..33620e268f8
--- /dev/null
+++ b/tools/lib/lockdep/tests/ABBCCDDA.c
@@ -0,0 +1,17 @@
+#include <liblockdep/mutex.h>
+#include "common.h"
+
+void main(void)
+{
+ pthread_mutex_t a, b, c, d;
+
+ pthread_mutex_init(&a, NULL);
+ pthread_mutex_init(&b, NULL);
+ pthread_mutex_init(&c, NULL);
+ pthread_mutex_init(&d, NULL);
+
+ LOCK_UNLOCK_2(a, b);
+ LOCK_UNLOCK_2(b, c);
+ LOCK_UNLOCK_2(c, d);
+ LOCK_UNLOCK_2(d, a);
+}
diff --git a/tools/lib/lockdep/tests/ABCABC.c b/tools/lib/lockdep/tests/ABCABC.c
new file mode 100644
index 00000000000..3fee51e3a68
--- /dev/null
+++ b/tools/lib/lockdep/tests/ABCABC.c
@@ -0,0 +1,15 @@
+#include <liblockdep/mutex.h>
+#include "common.h"
+
+void main(void)
+{
+ pthread_mutex_t a, b, c;
+
+ pthread_mutex_init(&a, NULL);
+ pthread_mutex_init(&b, NULL);
+ pthread_mutex_init(&c, NULL);
+
+ LOCK_UNLOCK_2(a, b);
+ LOCK_UNLOCK_2(c, a);
+ LOCK_UNLOCK_2(b, c);
+}
diff --git a/tools/lib/lockdep/tests/ABCDBCDA.c b/tools/lib/lockdep/tests/ABCDBCDA.c
new file mode 100644
index 00000000000..427ba562c75
--- /dev/null
+++ b/tools/lib/lockdep/tests/ABCDBCDA.c
@@ -0,0 +1,17 @@
+#include <liblockdep/mutex.h>
+#include "common.h"
+
+void main(void)
+{
+ pthread_mutex_t a, b, c, d;
+
+ pthread_mutex_init(&a, NULL);
+ pthread_mutex_init(&b, NULL);
+ pthread_mutex_init(&c, NULL);
+ pthread_mutex_init(&d, NULL);
+
+ LOCK_UNLOCK_2(a, b);
+ LOCK_UNLOCK_2(c, d);
+ LOCK_UNLOCK_2(b, c);
+ LOCK_UNLOCK_2(d, a);
+}
diff --git a/tools/lib/lockdep/tests/ABCDBDDA.c b/tools/lib/lockdep/tests/ABCDBDDA.c
new file mode 100644
index 00000000000..680c6cf3e91
--- /dev/null
+++ b/tools/lib/lockdep/tests/ABCDBDDA.c
@@ -0,0 +1,17 @@
+#include <liblockdep/mutex.h>
+#include "common.h"
+
+void main(void)
+{
+ pthread_mutex_t a, b, c, d;
+
+ pthread_mutex_init(&a, NULL);
+ pthread_mutex_init(&b, NULL);
+ pthread_mutex_init(&c, NULL);
+ pthread_mutex_init(&d, NULL);
+
+ LOCK_UNLOCK_2(a, b);
+ LOCK_UNLOCK_2(c, d);
+ LOCK_UNLOCK_2(b, d);
+ LOCK_UNLOCK_2(d, a);
+}
diff --git a/tools/lib/lockdep/tests/WW.c b/tools/lib/lockdep/tests/WW.c
new file mode 100644
index 00000000000..d44f77d7102
--- /dev/null
+++ b/tools/lib/lockdep/tests/WW.c
@@ -0,0 +1,13 @@
+#include <liblockdep/rwlock.h>
+
+void main(void)
+{
+ pthread_rwlock_t a, b;
+
+ pthread_rwlock_init(&a, NULL);
+ pthread_rwlock_init(&b, NULL);
+
+ pthread_rwlock_wrlock(&a);
+ pthread_rwlock_rdlock(&b);
+ pthread_rwlock_wrlock(&a);
+}
diff --git a/tools/lib/lockdep/tests/common.h b/tools/lib/lockdep/tests/common.h
new file mode 100644
index 00000000000..d89e94d47d8
--- /dev/null
+++ b/tools/lib/lockdep/tests/common.h
@@ -0,0 +1,12 @@
+#ifndef _LIBLOCKDEP_TEST_COMMON_H
+#define _LIBLOCKDEP_TEST_COMMON_H
+
+#define LOCK_UNLOCK_2(a, b) \
+ do { \
+ pthread_mutex_lock(&(a)); \
+ pthread_mutex_lock(&(b)); \
+ pthread_mutex_unlock(&(b)); \
+ pthread_mutex_unlock(&(a)); \
+ } while(0)
+
+#endif
diff --git a/tools/lib/lockdep/tests/unlock_balance.c b/tools/lib/lockdep/tests/unlock_balance.c
new file mode 100644
index 00000000000..0bc62de686f
--- /dev/null
+++ b/tools/lib/lockdep/tests/unlock_balance.c
@@ -0,0 +1,12 @@
+#include <liblockdep/mutex.h>
+
+void main(void)
+{
+ pthread_mutex_t a;
+
+ pthread_mutex_init(&a, NULL);
+
+ pthread_mutex_lock(&a);
+ pthread_mutex_unlock(&a);
+ pthread_mutex_unlock(&a);
+}
diff --git a/tools/lib/lockdep/uinclude/asm/hash.h b/tools/lib/lockdep/uinclude/asm/hash.h
new file mode 100644
index 00000000000..d82b170bb21
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/asm/hash.h
@@ -0,0 +1,6 @@
+#ifndef __ASM_GENERIC_HASH_H
+#define __ASM_GENERIC_HASH_H
+
+/* Stub */
+
+#endif /* __ASM_GENERIC_HASH_H */
diff --git a/tools/lib/lockdep/uinclude/asm/hweight.h b/tools/lib/lockdep/uinclude/asm/hweight.h
new file mode 100644
index 00000000000..fab00ff936d
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/asm/hweight.h
@@ -0,0 +1,3 @@
+
+/* empty file */
+
diff --git a/tools/lib/lockdep/uinclude/asm/sections.h b/tools/lib/lockdep/uinclude/asm/sections.h
new file mode 100644
index 00000000000..fab00ff936d
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/asm/sections.h
@@ -0,0 +1,3 @@
+
+/* empty file */
+
diff --git a/tools/lib/lockdep/uinclude/linux/bitops.h b/tools/lib/lockdep/uinclude/linux/bitops.h
new file mode 100644
index 00000000000..fab00ff936d
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/bitops.h
@@ -0,0 +1,3 @@
+
+/* empty file */
+
diff --git a/tools/lib/lockdep/uinclude/linux/compiler.h b/tools/lib/lockdep/uinclude/linux/compiler.h
new file mode 100644
index 00000000000..7ac838a1f19
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/compiler.h
@@ -0,0 +1,7 @@
+#ifndef _LIBLOCKDEP_LINUX_COMPILER_H_
+#define _LIBLOCKDEP_LINUX_COMPILER_H_
+
+#define __used __attribute__((__unused__))
+#define unlikely
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/debug_locks.h b/tools/lib/lockdep/uinclude/linux/debug_locks.h
new file mode 100644
index 00000000000..f38eb64df79
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/debug_locks.h
@@ -0,0 +1,12 @@
+#ifndef _LIBLOCKDEP_DEBUG_LOCKS_H_
+#define _LIBLOCKDEP_DEBUG_LOCKS_H_
+
+#include <stddef.h>
+#include <linux/compiler.h>
+
+#define DEBUG_LOCKS_WARN_ON(x) (x)
+
+extern bool debug_locks;
+extern bool debug_locks_silent;
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/delay.h b/tools/lib/lockdep/uinclude/linux/delay.h
new file mode 100644
index 00000000000..fab00ff936d
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/delay.h
@@ -0,0 +1,3 @@
+
+/* empty file */
+
diff --git a/tools/lib/lockdep/uinclude/linux/ftrace.h b/tools/lib/lockdep/uinclude/linux/ftrace.h
new file mode 100644
index 00000000000..fab00ff936d
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/ftrace.h
@@ -0,0 +1,3 @@
+
+/* empty file */
+
diff --git a/tools/lib/lockdep/uinclude/linux/gfp.h b/tools/lib/lockdep/uinclude/linux/gfp.h
new file mode 100644
index 00000000000..fab00ff936d
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/gfp.h
@@ -0,0 +1,3 @@
+
+/* empty file */
+
diff --git a/tools/lib/lockdep/uinclude/linux/hardirq.h b/tools/lib/lockdep/uinclude/linux/hardirq.h
new file mode 100644
index 00000000000..c8f3f8f5872
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/hardirq.h
@@ -0,0 +1,11 @@
+#ifndef _LIBLOCKDEP_LINUX_HARDIRQ_H_
+#define _LIBLOCKDEP_LINUX_HARDIRQ_H_
+
+#define SOFTIRQ_BITS 0UL
+#define HARDIRQ_BITS 0UL
+#define SOFTIRQ_SHIFT 0UL
+#define HARDIRQ_SHIFT 0UL
+#define hardirq_count() 0UL
+#define softirq_count() 0UL
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/hash.h b/tools/lib/lockdep/uinclude/linux/hash.h
new file mode 100644
index 00000000000..0f8479858dc
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/hash.h
@@ -0,0 +1 @@
+#include "../../../include/linux/hash.h"
diff --git a/tools/lib/lockdep/uinclude/linux/interrupt.h b/tools/lib/lockdep/uinclude/linux/interrupt.h
new file mode 100644
index 00000000000..fab00ff936d
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/interrupt.h
@@ -0,0 +1,3 @@
+
+/* empty file */
+
diff --git a/tools/lib/lockdep/uinclude/linux/irqflags.h b/tools/lib/lockdep/uinclude/linux/irqflags.h
new file mode 100644
index 00000000000..6cc296f0fad
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/irqflags.h
@@ -0,0 +1,38 @@
+#ifndef _LIBLOCKDEP_LINUX_TRACE_IRQFLAGS_H_
+#define _LIBLOCKDEP_LINUX_TRACE_IRQFLAGS_H_
+
+# define trace_hardirq_context(p) 0
+# define trace_softirq_context(p) 0
+# define trace_hardirqs_enabled(p) 0
+# define trace_softirqs_enabled(p) 0
+# define trace_hardirq_enter() do { } while (0)
+# define trace_hardirq_exit() do { } while (0)
+# define lockdep_softirq_enter() do { } while (0)
+# define lockdep_softirq_exit() do { } while (0)
+# define INIT_TRACE_IRQFLAGS
+
+# define stop_critical_timings() do { } while (0)
+# define start_critical_timings() do { } while (0)
+
+#define raw_local_irq_disable() do { } while (0)
+#define raw_local_irq_enable() do { } while (0)
+#define raw_local_irq_save(flags) ((flags) = 0)
+#define raw_local_irq_restore(flags) do { } while (0)
+#define raw_local_save_flags(flags) ((flags) = 0)
+#define raw_irqs_disabled_flags(flags) do { } while (0)
+#define raw_irqs_disabled() 0
+#define raw_safe_halt()
+
+#define local_irq_enable() do { } while (0)
+#define local_irq_disable() do { } while (0)
+#define local_irq_save(flags) ((flags) = 0)
+#define local_irq_restore(flags) do { } while (0)
+#define local_save_flags(flags) ((flags) = 0)
+#define irqs_disabled() (1)
+#define irqs_disabled_flags(flags) (0)
+#define safe_halt() do { } while (0)
+
+#define trace_lock_release(x, y)
+#define trace_lock_acquire(a, b, c, d, e, f, g)
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/kallsyms.h b/tools/lib/lockdep/uinclude/linux/kallsyms.h
new file mode 100644
index 00000000000..b0f2dbdf1a1
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/kallsyms.h
@@ -0,0 +1,32 @@
+#ifndef _LIBLOCKDEP_LINUX_KALLSYMS_H_
+#define _LIBLOCKDEP_LINUX_KALLSYMS_H_
+
+#include <linux/kernel.h>
+#include <stdio.h>
+
+#define KSYM_NAME_LEN 128
+
+struct module;
+
+static inline const char *kallsyms_lookup(unsigned long addr,
+ unsigned long *symbolsize,
+ unsigned long *offset,
+ char **modname, char *namebuf)
+{
+ return NULL;
+}
+
+#include <execinfo.h>
+#include <stdlib.h>
+static inline void print_ip_sym(unsigned long ip)
+{
+ char **name;
+
+ name = backtrace_symbols((void **)&ip, 1);
+
+ printf("%s\n", *name);
+
+ free(name);
+}
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/kern_levels.h b/tools/lib/lockdep/uinclude/linux/kern_levels.h
new file mode 100644
index 00000000000..3b9bade2869
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/kern_levels.h
@@ -0,0 +1,25 @@
+#ifndef __KERN_LEVELS_H__
+#define __KERN_LEVELS_H__
+
+#define KERN_SOH "" /* ASCII Start Of Header */
+#define KERN_SOH_ASCII ''
+
+#define KERN_EMERG KERN_SOH "" /* system is unusable */
+#define KERN_ALERT KERN_SOH "" /* action must be taken immediately */
+#define KERN_CRIT KERN_SOH "" /* critical conditions */
+#define KERN_ERR KERN_SOH "" /* error conditions */
+#define KERN_WARNING KERN_SOH "" /* warning conditions */
+#define KERN_NOTICE KERN_SOH "" /* normal but significant condition */
+#define KERN_INFO KERN_SOH "" /* informational */
+#define KERN_DEBUG KERN_SOH "" /* debug-level messages */
+
+#define KERN_DEFAULT KERN_SOH "" /* the default kernel loglevel */
+
+/*
+ * Annotation for a "continued" line of log printout (only done after a
+ * line that had no enclosing \n). Only to be used by core/arch code
+ * during early bootup (a continued line is not SMP-safe otherwise).
+ */
+#define KERN_CONT ""
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/kernel.h b/tools/lib/lockdep/uinclude/linux/kernel.h
new file mode 100644
index 00000000000..a11e3c357be
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/kernel.h
@@ -0,0 +1,44 @@
+#ifndef _LIBLOCKDEP_LINUX_KERNEL_H_
+#define _LIBLOCKDEP_LINUX_KERNEL_H_
+
+#include <linux/export.h>
+#include <linux/types.h>
+#include <linux/rcu.h>
+#include <linux/hardirq.h>
+#include <linux/kern_levels.h>
+
+#ifndef container_of
+#define container_of(ptr, type, member) ({ \
+ const typeof(((type *)0)->member) * __mptr = (ptr); \
+ (type *)((char *)__mptr - offsetof(type, member)); })
+#endif
+
+#define max(x, y) ({ \
+ typeof(x) _max1 = (x); \
+ typeof(y) _max2 = (y); \
+ (void) (&_max1 == &_max2); \
+ _max1 > _max2 ? _max1 : _max2; })
+
+#define BUILD_BUG_ON(condition) ((void)sizeof(char[1 - 2*!!(condition)]))
+#define WARN_ON(x) (x)
+#define WARN_ON_ONCE(x) (x)
+#define likely(x) (x)
+#define WARN(x, y, z) (x)
+#define uninitialized_var(x) x
+#define __init
+#define noinline
+#define list_add_tail_rcu list_add_tail
+
+#ifndef CALLER_ADDR0
+#define CALLER_ADDR0 ((unsigned long)__builtin_return_address(0))
+#endif
+
+#ifndef _RET_IP_
+#define _RET_IP_ CALLER_ADDR0
+#endif
+
+#ifndef _THIS_IP_
+#define _THIS_IP_ ({ __label__ __here; __here: (unsigned long)&&__here; })
+#endif
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/kmemcheck.h b/tools/lib/lockdep/uinclude/linux/kmemcheck.h
new file mode 100644
index 00000000000..94d598bc6ab
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/kmemcheck.h
@@ -0,0 +1,8 @@
+#ifndef _LIBLOCKDEP_LINUX_KMEMCHECK_H_
+#define _LIBLOCKDEP_LINUX_KMEMCHECK_H_
+
+static inline void kmemcheck_mark_initialized(void *address, unsigned int n)
+{
+}
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/linkage.h b/tools/lib/lockdep/uinclude/linux/linkage.h
new file mode 100644
index 00000000000..fab00ff936d
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/linkage.h
@@ -0,0 +1,3 @@
+
+/* empty file */
+
diff --git a/tools/lib/lockdep/uinclude/linux/list.h b/tools/lib/lockdep/uinclude/linux/list.h
new file mode 100644
index 00000000000..6e9ef31ed82
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/list.h
@@ -0,0 +1 @@
+#include "../../../include/linux/list.h"
diff --git a/tools/lib/lockdep/uinclude/linux/lockdep.h b/tools/lib/lockdep/uinclude/linux/lockdep.h
new file mode 100644
index 00000000000..c1552c28507
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/lockdep.h
@@ -0,0 +1,58 @@
+#ifndef _LIBLOCKDEP_LOCKDEP_H_
+#define _LIBLOCKDEP_LOCKDEP_H_
+
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+#include <string.h>
+#include <limits.h>
+#include <linux/utsname.h>
+
+
+#define MAX_LOCK_DEPTH 2000UL
+
+#define asmlinkage
+#define __visible
+
+#include "../../../include/linux/lockdep.h"
+
+struct task_struct {
+ u64 curr_chain_key;
+ int lockdep_depth;
+ unsigned int lockdep_recursion;
+ struct held_lock held_locks[MAX_LOCK_DEPTH];
+ gfp_t lockdep_reclaim_gfp;
+ int pid;
+ char comm[17];
+};
+
+extern struct task_struct *__curr(void);
+
+#define current (__curr())
+
+#define debug_locks_off() 1
+#define task_pid_nr(tsk) ((tsk)->pid)
+
+#define KSYM_NAME_LEN 128
+#define printk printf
+
+#define list_del_rcu list_del
+
+#define atomic_t unsigned long
+#define atomic_inc(x) ((*(x))++)
+
+static struct new_utsname *init_utsname(void)
+{
+ static struct new_utsname n = (struct new_utsname) {
+ .release = "liblockdep",
+ .version = LIBLOCKDEP_VERSION,
+ };
+
+ return &n;
+}
+
+#define print_tainted() ""
+#define static_obj(x) 1
+
+#define debug_show_all_locks()
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/module.h b/tools/lib/lockdep/uinclude/linux/module.h
new file mode 100644
index 00000000000..09c7a7be8cc
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/module.h
@@ -0,0 +1,6 @@
+#ifndef _LIBLOCKDEP_LINUX_MODULE_H_
+#define _LIBLOCKDEP_LINUX_MODULE_H_
+
+#define module_param(name, type, perm)
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/mutex.h b/tools/lib/lockdep/uinclude/linux/mutex.h
new file mode 100644
index 00000000000..fab00ff936d
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/mutex.h
@@ -0,0 +1,3 @@
+
+/* empty file */
+
diff --git a/tools/lib/lockdep/uinclude/linux/poison.h b/tools/lib/lockdep/uinclude/linux/poison.h
new file mode 100644
index 00000000000..0c27bdf1423
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/poison.h
@@ -0,0 +1 @@
+#include "../../../include/linux/poison.h"
diff --git a/tools/lib/lockdep/uinclude/linux/prefetch.h b/tools/lib/lockdep/uinclude/linux/prefetch.h
new file mode 100644
index 00000000000..d73fe6f850a
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/prefetch.h
@@ -0,0 +1,6 @@
+#ifndef _LIBLOCKDEP_LINUX_PREFETCH_H_
+#define _LIBLOCKDEP_LINUX_PREFETCH_H
+
+static inline void prefetch(void *a __attribute__((unused))) { }
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/proc_fs.h b/tools/lib/lockdep/uinclude/linux/proc_fs.h
new file mode 100644
index 00000000000..fab00ff936d
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/proc_fs.h
@@ -0,0 +1,3 @@
+
+/* empty file */
+
diff --git a/tools/lib/lockdep/uinclude/linux/rbtree.h b/tools/lib/lockdep/uinclude/linux/rbtree.h
new file mode 100644
index 00000000000..965901db486
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/rbtree.h
@@ -0,0 +1 @@
+#include "../../../include/linux/rbtree.h"
diff --git a/tools/lib/lockdep/uinclude/linux/rbtree_augmented.h b/tools/lib/lockdep/uinclude/linux/rbtree_augmented.h
new file mode 100644
index 00000000000..c3759477379
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/rbtree_augmented.h
@@ -0,0 +1,2 @@
+#define __always_inline
+#include "../../../include/linux/rbtree_augmented.h"
diff --git a/tools/lib/lockdep/uinclude/linux/rcu.h b/tools/lib/lockdep/uinclude/linux/rcu.h
new file mode 100644
index 00000000000..042ee8e463c
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/rcu.h
@@ -0,0 +1,21 @@
+#ifndef _LIBLOCKDEP_RCU_H_
+#define _LIBLOCKDEP_RCU_H_
+
+int rcu_scheduler_active;
+
+static inline int rcu_lockdep_current_cpu_online(void)
+{
+ return 1;
+}
+
+static inline int rcu_is_cpu_idle(void)
+{
+ return 1;
+}
+
+static inline bool rcu_is_watching(void)
+{
+ return false;
+}
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/seq_file.h b/tools/lib/lockdep/uinclude/linux/seq_file.h
new file mode 100644
index 00000000000..fab00ff936d
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/seq_file.h
@@ -0,0 +1,3 @@
+
+/* empty file */
+
diff --git a/tools/lib/lockdep/uinclude/linux/spinlock.h b/tools/lib/lockdep/uinclude/linux/spinlock.h
new file mode 100644
index 00000000000..68c1aa2bcba
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/spinlock.h
@@ -0,0 +1,25 @@
+#ifndef _LIBLOCKDEP_SPINLOCK_H_
+#define _LIBLOCKDEP_SPINLOCK_H_
+
+#include <pthread.h>
+#include <stdbool.h>
+
+#define arch_spinlock_t pthread_mutex_t
+#define __ARCH_SPIN_LOCK_UNLOCKED PTHREAD_MUTEX_INITIALIZER
+
+static inline void arch_spin_lock(arch_spinlock_t *mutex)
+{
+ pthread_mutex_lock(mutex);
+}
+
+static inline void arch_spin_unlock(arch_spinlock_t *mutex)
+{
+ pthread_mutex_unlock(mutex);
+}
+
+static inline bool arch_spin_is_locked(arch_spinlock_t *mutex)
+{
+ return true;
+}
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/stacktrace.h b/tools/lib/lockdep/uinclude/linux/stacktrace.h
new file mode 100644
index 00000000000..39aecc6b19d
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/stacktrace.h
@@ -0,0 +1,32 @@
+#ifndef _LIBLOCKDEP_LINUX_STACKTRACE_H_
+#define _LIBLOCKDEP_LINUX_STACKTRACE_H_
+
+#include <execinfo.h>
+
+struct stack_trace {
+ unsigned int nr_entries, max_entries;
+ unsigned long *entries;
+ int skip;
+};
+
+static inline void print_stack_trace(struct stack_trace *trace, int spaces)
+{
+ backtrace_symbols_fd((void **)trace->entries, trace->nr_entries, 1);
+}
+
+#define save_stack_trace(trace) \
+ ((trace)->nr_entries = \
+ backtrace((void **)(trace)->entries, (trace)->max_entries))
+
+static inline int dump_stack(void)
+{
+ void *array[64];
+ size_t size;
+
+ size = backtrace(array, 64);
+ backtrace_symbols_fd(array, size, 1);
+
+ return 0;
+}
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/linux/stringify.h b/tools/lib/lockdep/uinclude/linux/stringify.h
new file mode 100644
index 00000000000..05dfcd1ac11
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/linux/stringify.h
@@ -0,0 +1,7 @@
+#ifndef _LIBLOCKDEP_LINUX_STRINGIFY_H_
+#define _LIBLOCKDEP_LINUX_STRINGIFY_H_
+
+#define __stringify_1(x...) #x
+#define __stringify(x...) __stringify_1(x)
+
+#endif
diff --git a/tools/lib/lockdep/uinclude/trace/events/lock.h b/tools/lib/lockdep/uinclude/trace/events/lock.h
new file mode 100644
index 00000000000..fab00ff936d
--- /dev/null
+++ b/tools/lib/lockdep/uinclude/trace/events/lock.h
@@ -0,0 +1,3 @@
+
+/* empty file */
+
diff --git a/tools/lib/symbol/kallsyms.c b/tools/lib/symbol/kallsyms.c
new file mode 100644
index 00000000000..18bc271a4bb
--- /dev/null
+++ b/tools/lib/symbol/kallsyms.c
@@ -0,0 +1,58 @@
+#include "symbol/kallsyms.h"
+#include <stdio.h>
+#include <stdlib.h>
+
+int kallsyms__parse(const char *filename, void *arg,
+ int (*process_symbol)(void *arg, const char *name,
+ char type, u64 start))
+{
+ char *line = NULL;
+ size_t n;
+ int err = -1;
+ FILE *file = fopen(filename, "r");
+
+ if (file == NULL)
+ goto out_failure;
+
+ err = 0;
+
+ while (!feof(file)) {
+ u64 start;
+ int line_len, len;
+ char symbol_type;
+ char *symbol_name;
+
+ line_len = getline(&line, &n, file);
+ if (line_len < 0 || !line)
+ break;
+
+ line[--line_len] = '\0'; /* \n */
+
+ len = hex2u64(line, &start);
+
+ len++;
+ if (len + 2 >= line_len)
+ continue;
+
+ symbol_type = line[len];
+ len += 2;
+ symbol_name = line + len;
+ len = line_len - len;
+
+ if (len >= KSYM_NAME_LEN) {
+ err = -1;
+ break;
+ }
+
+ err = process_symbol(arg, symbol_name, symbol_type, start);
+ if (err)
+ break;
+ }
+
+ free(line);
+ fclose(file);
+ return err;
+
+out_failure:
+ return -1;
+}
diff --git a/tools/lib/symbol/kallsyms.h b/tools/lib/symbol/kallsyms.h
new file mode 100644
index 00000000000..6084f5e18b3
--- /dev/null
+++ b/tools/lib/symbol/kallsyms.h
@@ -0,0 +1,24 @@
+#ifndef __TOOLS_KALLSYMS_H_
+#define __TOOLS_KALLSYMS_H_ 1
+
+#include <elf.h>
+#include <linux/ctype.h>
+#include <linux/types.h>
+
+#ifndef KSYM_NAME_LEN
+#define KSYM_NAME_LEN 256
+#endif
+
+static inline u8 kallsyms2elf_type(char type)
+{
+ if (type == 'W')
+ return STB_WEAK;
+
+ return isupper(type) ? STB_GLOBAL : STB_LOCAL;
+}
+
+int kallsyms__parse(const char *filename, void *arg,
+ int (*process_symbol)(void *arg, const char *name,
+ char type, u64 start));
+
+#endif /* __TOOLS_KALLSYMS_H_ */
diff --git a/tools/lib/traceevent/.gitignore b/tools/lib/traceevent/.gitignore
new file mode 100644
index 00000000000..35f56be5a4c
--- /dev/null
+++ b/tools/lib/traceevent/.gitignore
@@ -0,0 +1 @@
+TRACEEVENT-CFLAGS
diff --git a/tools/lib/traceevent/Makefile b/tools/lib/traceevent/Makefile
new file mode 100644
index 00000000000..005c9cc0693
--- /dev/null
+++ b/tools/lib/traceevent/Makefile
@@ -0,0 +1,340 @@
+# trace-cmd version
+EP_VERSION = 1
+EP_PATCHLEVEL = 1
+EP_EXTRAVERSION = 0
+
+# file format version
+FILE_VERSION = 6
+
+MAKEFLAGS += --no-print-directory
+
+
+# Makefiles suck: This macro sets a default value of $(2) for the
+# variable named by $(1), unless the variable has been set by
+# environment or command line. This is necessary for CC and AR
+# because make sets default values, so the simpler ?= approach
+# won't work as expected.
+define allow-override
+ $(if $(or $(findstring environment,$(origin $(1))),\
+ $(findstring command line,$(origin $(1)))),,\
+ $(eval $(1) = $(2)))
+endef
+
+# Allow setting CC and AR, or setting CROSS_COMPILE as a prefix.
+$(call allow-override,CC,$(CROSS_COMPILE)gcc)
+$(call allow-override,AR,$(CROSS_COMPILE)ar)
+
+EXT = -std=gnu99
+INSTALL = install
+
+# Use DESTDIR for installing into a different root directory.
+# This is useful for building a package. The program will be
+# installed in this directory as if it was the root directory.
+# Then the build tool can move it later.
+DESTDIR ?=
+DESTDIR_SQ = '$(subst ','\'',$(DESTDIR))'
+
+prefix ?= /usr/local
+bindir_relative = bin
+bindir = $(prefix)/$(bindir_relative)
+man_dir = $(prefix)/share/man
+man_dir_SQ = '$(subst ','\'',$(man_dir))'
+
+export man_dir man_dir_SQ INSTALL
+export DESTDIR DESTDIR_SQ
+
+set_plugin_dir := 1
+
+# Set plugin_dir to preffered global plugin location
+# If we install under $HOME directory we go under
+# $(HOME)/.traceevent/plugins
+#
+# We dont set PLUGIN_DIR in case we install under $HOME
+# directory, because by default the code looks under:
+# $(HOME)/.traceevent/plugins by default.
+#
+ifeq ($(plugin_dir),)
+ifeq ($(prefix),$(HOME))
+override plugin_dir = $(HOME)/.traceevent/plugins
+set_plugin_dir := 0
+else
+override plugin_dir = $(prefix)/lib/traceevent/plugins
+endif
+endif
+
+ifeq ($(set_plugin_dir),1)
+PLUGIN_DIR = -DPLUGIN_DIR="$(plugin_dir)"
+PLUGIN_DIR_SQ = '$(subst ','\'',$(PLUGIN_DIR))'
+endif
+
+include $(if $(BUILD_SRC),$(BUILD_SRC)/)../../scripts/Makefile.include
+
+# copy a bit from Linux kbuild
+
+ifeq ("$(origin V)", "command line")
+ VERBOSE = $(V)
+endif
+ifndef VERBOSE
+ VERBOSE = 0
+endif
+
+ifeq ("$(origin O)", "command line")
+ BUILD_OUTPUT := $(O)
+endif
+
+ifeq ($(BUILD_SRC),)
+ifneq ($(OUTPUT),)
+
+define build_output
+ $(if $(VERBOSE:1=),@)+$(MAKE) -C $(OUTPUT) \
+ BUILD_SRC=$(CURDIR)/ -f $(CURDIR)/Makefile $1
+endef
+
+all: sub-make
+
+$(MAKECMDGOALS): sub-make
+
+sub-make: force
+ $(call build_output, $(MAKECMDGOALS))
+
+
+# Leave processing to above invocation of make
+skip-makefile := 1
+
+endif # OUTPUT
+endif # BUILD_SRC
+
+# We process the rest of the Makefile if this is the final invocation of make
+ifeq ($(skip-makefile),)
+
+srctree := $(if $(BUILD_SRC),$(BUILD_SRC),$(CURDIR))
+objtree := $(CURDIR)
+src := $(srctree)
+obj := $(objtree)
+
+export prefix bindir src obj
+
+# Shell quotes
+bindir_SQ = $(subst ','\'',$(bindir))
+bindir_relative_SQ = $(subst ','\'',$(bindir_relative))
+plugin_dir_SQ = $(subst ','\'',$(plugin_dir))
+
+LIB_FILE = libtraceevent.a libtraceevent.so
+
+CONFIG_INCLUDES =
+CONFIG_LIBS =
+CONFIG_FLAGS =
+
+VERSION = $(EP_VERSION)
+PATCHLEVEL = $(EP_PATCHLEVEL)
+EXTRAVERSION = $(EP_EXTRAVERSION)
+
+OBJ = $@
+N =
+
+export Q VERBOSE
+
+EVENT_PARSE_VERSION = $(EP_VERSION).$(EP_PATCHLEVEL).$(EP_EXTRAVERSION)
+
+INCLUDES = -I. -I $(srctree)/../../include $(CONFIG_INCLUDES)
+
+# Set compile option CFLAGS if not set elsewhere
+CFLAGS ?= -g -Wall
+
+# Append required CFLAGS
+override CFLAGS += $(CONFIG_FLAGS) $(INCLUDES) $(PLUGIN_DIR_SQ)
+override CFLAGS += $(udis86-flags) -D_GNU_SOURCE
+
+ifeq ($(VERBOSE),1)
+ Q =
+else
+ Q = @
+endif
+
+do_compile_shared_library = \
+ ($(print_shared_lib_compile) \
+ $(CC) --shared $^ -o $@)
+
+do_plugin_build = \
+ ($(print_plugin_build) \
+ $(CC) $(CFLAGS) -shared -nostartfiles -o $@ $<)
+
+do_build_static_lib = \
+ ($(print_static_lib_build) \
+ $(RM) $@; $(AR) rcs $@ $^)
+
+
+do_compile = $(QUIET_CC)$(CC) -c $(CFLAGS) $(EXT) $< -o $(obj)/$@;
+
+$(obj)/%.o: $(src)/%.c
+ $(call do_compile)
+
+%.o: $(src)/%.c
+ $(call do_compile)
+
+PEVENT_LIB_OBJS = event-parse.o
+PEVENT_LIB_OBJS += event-plugin.o
+PEVENT_LIB_OBJS += trace-seq.o
+PEVENT_LIB_OBJS += parse-filter.o
+PEVENT_LIB_OBJS += parse-utils.o
+PEVENT_LIB_OBJS += kbuffer-parse.o
+
+PLUGIN_OBJS = plugin_jbd2.o
+PLUGIN_OBJS += plugin_hrtimer.o
+PLUGIN_OBJS += plugin_kmem.o
+PLUGIN_OBJS += plugin_kvm.o
+PLUGIN_OBJS += plugin_mac80211.o
+PLUGIN_OBJS += plugin_sched_switch.o
+PLUGIN_OBJS += plugin_function.o
+PLUGIN_OBJS += plugin_xen.o
+PLUGIN_OBJS += plugin_scsi.o
+PLUGIN_OBJS += plugin_cfg80211.o
+
+PLUGINS := $(PLUGIN_OBJS:.o=.so)
+
+ALL_OBJS = $(PEVENT_LIB_OBJS) $(PLUGIN_OBJS)
+
+CMD_TARGETS = $(LIB_FILE) $(PLUGINS)
+
+TARGETS = $(CMD_TARGETS)
+
+
+all: all_cmd
+
+all_cmd: $(CMD_TARGETS)
+
+libtraceevent.so: $(PEVENT_LIB_OBJS)
+ $(QUIET_LINK)$(CC) --shared $^ -o $@
+
+libtraceevent.a: $(PEVENT_LIB_OBJS)
+ $(QUIET_LINK)$(RM) $@; $(AR) rcs $@ $^
+
+plugins: $(PLUGINS)
+
+$(PEVENT_LIB_OBJS): %.o: $(src)/%.c TRACEEVENT-CFLAGS
+ $(QUIET_CC_FPIC)$(CC) -c $(CFLAGS) $(EXT) -fPIC $< -o $@
+
+$(PLUGIN_OBJS): %.o : $(src)/%.c
+ $(QUIET_CC_FPIC)$(CC) -c $(CFLAGS) -fPIC -o $@ $<
+
+$(PLUGINS): %.so: %.o
+ $(QUIET_LINK)$(CC) $(CFLAGS) -shared -nostartfiles -o $@ $<
+
+define make_version.h
+ (echo '/* This file is automatically generated. Do not modify. */'; \
+ echo \#define VERSION_CODE $(shell \
+ expr $(VERSION) \* 256 + $(PATCHLEVEL)); \
+ echo '#define EXTRAVERSION ' $(EXTRAVERSION); \
+ echo '#define VERSION_STRING "'$(VERSION).$(PATCHLEVEL).$(EXTRAVERSION)'"'; \
+ echo '#define FILE_VERSION '$(FILE_VERSION); \
+ ) > $1
+endef
+
+define update_version.h
+ ($(call make_version.h, $@.tmp); \
+ if [ -r $@ ] && cmp -s $@ $@.tmp; then \
+ rm -f $@.tmp; \
+ else \
+ echo ' UPDATE $@'; \
+ mv -f $@.tmp $@; \
+ fi);
+endef
+
+ep_version.h: force
+ $(Q)$(N)$(call update_version.h)
+
+VERSION_FILES = ep_version.h
+
+define update_dir
+ (echo $1 > $@.tmp; \
+ if [ -r $@ ] && cmp -s $@ $@.tmp; then \
+ rm -f $@.tmp; \
+ else \
+ echo ' UPDATE $@'; \
+ mv -f $@.tmp $@; \
+ fi);
+endef
+
+## make deps
+
+all_objs := $(sort $(ALL_OBJS))
+all_deps := $(all_objs:%.o=.%.d)
+
+# let .d file also depends on the source and header files
+define check_deps
+ @set -e; $(RM) $@; \
+ $(CC) -MM $(CFLAGS) $< > $@.$$$$; \
+ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
+ $(RM) $@.$$$$
+endef
+
+$(all_deps): .%.d: $(src)/%.c
+ $(Q)$(call check_deps)
+
+$(all_objs) : %.o : .%.d
+
+dep_includes := $(wildcard $(all_deps))
+
+ifneq ($(dep_includes),)
+ include $(dep_includes)
+endif
+
+### Detect environment changes
+TRACK_CFLAGS = $(subst ','\'',$(CFLAGS)):$(ARCH):$(CROSS_COMPILE)
+
+TRACEEVENT-CFLAGS: force
+ @FLAGS='$(TRACK_CFLAGS)'; \
+ if test x"$$FLAGS" != x"`cat TRACEEVENT-CFLAGS 2>/dev/null`" ; then \
+ echo 1>&2 " FLAGS: * new build flags or cross compiler"; \
+ echo "$$FLAGS" >TRACEEVENT-CFLAGS; \
+ fi
+
+tags: force
+ $(RM) tags
+ find . -name '*.[ch]' | xargs ctags --extra=+f --c-kinds=+px \
+ --regex-c++='/_PE\(([^,)]*).*/PEVENT_ERRNO__\1/'
+
+TAGS: force
+ $(RM) TAGS
+ find . -name '*.[ch]' | xargs etags \
+ --regex='/_PE(\([^,)]*\).*/PEVENT_ERRNO__\1/'
+
+define do_install
+ if [ ! -d '$(DESTDIR_SQ)$2' ]; then \
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$2'; \
+ fi; \
+ $(INSTALL) $1 '$(DESTDIR_SQ)$2'
+endef
+
+define do_install_plugins
+ for plugin in $1; do \
+ $(call do_install,$$plugin,$(plugin_dir_SQ)); \
+ done
+endef
+
+install_lib: all_cmd install_plugins
+ $(call QUIET_INSTALL, $(LIB_FILE)) \
+ $(call do_install,$(LIB_FILE),$(bindir_SQ))
+
+install_plugins: $(PLUGINS)
+ $(call QUIET_INSTALL, trace_plugins) \
+ $(call do_install_plugins, $(PLUGINS))
+
+install: install_lib
+
+clean:
+ $(call QUIET_CLEAN, libtraceevent) \
+ $(RM) *.o *~ $(TARGETS) *.a *.so $(VERSION_FILES) .*.d \
+ $(RM) TRACEEVENT-CFLAGS tags TAGS
+
+endif # skip-makefile
+
+PHONY += force plugins
+force:
+
+plugins:
+ @echo > /dev/null
+
+# Declare the contents of the .PHONY variable as phony. We keep that
+# information in a variable so we can use it in if_changed and friends.
+.PHONY: $(PHONY)
diff --git a/tools/lib/traceevent/event-parse.c b/tools/lib/traceevent/event-parse.c
new file mode 100644
index 00000000000..93825a17dcc
--- /dev/null
+++ b/tools/lib/traceevent/event-parse.c
@@ -0,0 +1,6025 @@
+/*
+ * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * The parts for function graph printing was taken and modified from the
+ * Linux Kernel that were written by
+ * - Copyright (C) 2009 Frederic Weisbecker,
+ * Frederic Weisbecker gave his permission to relicense the code to
+ * the Lesser General Public License.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdint.h>
+#include <limits.h>
+
+#include "event-parse.h"
+#include "event-utils.h"
+
+static const char *input_buf;
+static unsigned long long input_buf_ptr;
+static unsigned long long input_buf_siz;
+
+static int is_flag_field;
+static int is_symbolic_field;
+
+static int show_warning = 1;
+
+#define do_warning(fmt, ...) \
+ do { \
+ if (show_warning) \
+ warning(fmt, ##__VA_ARGS__); \
+ } while (0)
+
+#define do_warning_event(event, fmt, ...) \
+ do { \
+ if (!show_warning) \
+ continue; \
+ \
+ if (event) \
+ warning("[%s:%s] " fmt, event->system, \
+ event->name, ##__VA_ARGS__); \
+ else \
+ warning(fmt, ##__VA_ARGS__); \
+ } while (0)
+
+static void init_input_buf(const char *buf, unsigned long long size)
+{
+ input_buf = buf;
+ input_buf_siz = size;
+ input_buf_ptr = 0;
+}
+
+const char *pevent_get_input_buf(void)
+{
+ return input_buf;
+}
+
+unsigned long long pevent_get_input_buf_ptr(void)
+{
+ return input_buf_ptr;
+}
+
+struct event_handler {
+ struct event_handler *next;
+ int id;
+ const char *sys_name;
+ const char *event_name;
+ pevent_event_handler_func func;
+ void *context;
+};
+
+struct pevent_func_params {
+ struct pevent_func_params *next;
+ enum pevent_func_arg_type type;
+};
+
+struct pevent_function_handler {
+ struct pevent_function_handler *next;
+ enum pevent_func_arg_type ret_type;
+ char *name;
+ pevent_func_handler func;
+ struct pevent_func_params *params;
+ int nr_args;
+};
+
+static unsigned long long
+process_defined_func(struct trace_seq *s, void *data, int size,
+ struct event_format *event, struct print_arg *arg);
+
+static void free_func_handle(struct pevent_function_handler *func);
+
+/**
+ * pevent_buffer_init - init buffer for parsing
+ * @buf: buffer to parse
+ * @size: the size of the buffer
+ *
+ * For use with pevent_read_token(), this initializes the internal
+ * buffer that pevent_read_token() will parse.
+ */
+void pevent_buffer_init(const char *buf, unsigned long long size)
+{
+ init_input_buf(buf, size);
+}
+
+void breakpoint(void)
+{
+ static int x;
+ x++;
+}
+
+struct print_arg *alloc_arg(void)
+{
+ return calloc(1, sizeof(struct print_arg));
+}
+
+struct cmdline {
+ char *comm;
+ int pid;
+};
+
+static int cmdline_cmp(const void *a, const void *b)
+{
+ const struct cmdline *ca = a;
+ const struct cmdline *cb = b;
+
+ if (ca->pid < cb->pid)
+ return -1;
+ if (ca->pid > cb->pid)
+ return 1;
+
+ return 0;
+}
+
+struct cmdline_list {
+ struct cmdline_list *next;
+ char *comm;
+ int pid;
+};
+
+static int cmdline_init(struct pevent *pevent)
+{
+ struct cmdline_list *cmdlist = pevent->cmdlist;
+ struct cmdline_list *item;
+ struct cmdline *cmdlines;
+ int i;
+
+ cmdlines = malloc(sizeof(*cmdlines) * pevent->cmdline_count);
+ if (!cmdlines)
+ return -1;
+
+ i = 0;
+ while (cmdlist) {
+ cmdlines[i].pid = cmdlist->pid;
+ cmdlines[i].comm = cmdlist->comm;
+ i++;
+ item = cmdlist;
+ cmdlist = cmdlist->next;
+ free(item);
+ }
+
+ qsort(cmdlines, pevent->cmdline_count, sizeof(*cmdlines), cmdline_cmp);
+
+ pevent->cmdlines = cmdlines;
+ pevent->cmdlist = NULL;
+
+ return 0;
+}
+
+static const char *find_cmdline(struct pevent *pevent, int pid)
+{
+ const struct cmdline *comm;
+ struct cmdline key;
+
+ if (!pid)
+ return "<idle>";
+
+ if (!pevent->cmdlines && cmdline_init(pevent))
+ return "<not enough memory for cmdlines!>";
+
+ key.pid = pid;
+
+ comm = bsearch(&key, pevent->cmdlines, pevent->cmdline_count,
+ sizeof(*pevent->cmdlines), cmdline_cmp);
+
+ if (comm)
+ return comm->comm;
+ return "<...>";
+}
+
+/**
+ * pevent_pid_is_registered - return if a pid has a cmdline registered
+ * @pevent: handle for the pevent
+ * @pid: The pid to check if it has a cmdline registered with.
+ *
+ * Returns 1 if the pid has a cmdline mapped to it
+ * 0 otherwise.
+ */
+int pevent_pid_is_registered(struct pevent *pevent, int pid)
+{
+ const struct cmdline *comm;
+ struct cmdline key;
+
+ if (!pid)
+ return 1;
+
+ if (!pevent->cmdlines && cmdline_init(pevent))
+ return 0;
+
+ key.pid = pid;
+
+ comm = bsearch(&key, pevent->cmdlines, pevent->cmdline_count,
+ sizeof(*pevent->cmdlines), cmdline_cmp);
+
+ if (comm)
+ return 1;
+ return 0;
+}
+
+/*
+ * If the command lines have been converted to an array, then
+ * we must add this pid. This is much slower than when cmdlines
+ * are added before the array is initialized.
+ */
+static int add_new_comm(struct pevent *pevent, const char *comm, int pid)
+{
+ struct cmdline *cmdlines = pevent->cmdlines;
+ const struct cmdline *cmdline;
+ struct cmdline key;
+
+ if (!pid)
+ return 0;
+
+ /* avoid duplicates */
+ key.pid = pid;
+
+ cmdline = bsearch(&key, pevent->cmdlines, pevent->cmdline_count,
+ sizeof(*pevent->cmdlines), cmdline_cmp);
+ if (cmdline) {
+ errno = EEXIST;
+ return -1;
+ }
+
+ cmdlines = realloc(cmdlines, sizeof(*cmdlines) * (pevent->cmdline_count + 1));
+ if (!cmdlines) {
+ errno = ENOMEM;
+ return -1;
+ }
+
+ cmdlines[pevent->cmdline_count].comm = strdup(comm);
+ if (!cmdlines[pevent->cmdline_count].comm) {
+ free(cmdlines);
+ errno = ENOMEM;
+ return -1;
+ }
+
+ cmdlines[pevent->cmdline_count].pid = pid;
+
+ if (cmdlines[pevent->cmdline_count].comm)
+ pevent->cmdline_count++;
+
+ qsort(cmdlines, pevent->cmdline_count, sizeof(*cmdlines), cmdline_cmp);
+ pevent->cmdlines = cmdlines;
+
+ return 0;
+}
+
+/**
+ * pevent_register_comm - register a pid / comm mapping
+ * @pevent: handle for the pevent
+ * @comm: the command line to register
+ * @pid: the pid to map the command line to
+ *
+ * This adds a mapping to search for command line names with
+ * a given pid. The comm is duplicated.
+ */
+int pevent_register_comm(struct pevent *pevent, const char *comm, int pid)
+{
+ struct cmdline_list *item;
+
+ if (pevent->cmdlines)
+ return add_new_comm(pevent, comm, pid);
+
+ item = malloc(sizeof(*item));
+ if (!item)
+ return -1;
+
+ item->comm = strdup(comm);
+ if (!item->comm) {
+ free(item);
+ return -1;
+ }
+ item->pid = pid;
+ item->next = pevent->cmdlist;
+
+ pevent->cmdlist = item;
+ pevent->cmdline_count++;
+
+ return 0;
+}
+
+void pevent_register_trace_clock(struct pevent *pevent, char *trace_clock)
+{
+ pevent->trace_clock = trace_clock;
+}
+
+struct func_map {
+ unsigned long long addr;
+ char *func;
+ char *mod;
+};
+
+struct func_list {
+ struct func_list *next;
+ unsigned long long addr;
+ char *func;
+ char *mod;
+};
+
+static int func_cmp(const void *a, const void *b)
+{
+ const struct func_map *fa = a;
+ const struct func_map *fb = b;
+
+ if (fa->addr < fb->addr)
+ return -1;
+ if (fa->addr > fb->addr)
+ return 1;
+
+ return 0;
+}
+
+/*
+ * We are searching for a record in between, not an exact
+ * match.
+ */
+static int func_bcmp(const void *a, const void *b)
+{
+ const struct func_map *fa = a;
+ const struct func_map *fb = b;
+
+ if ((fa->addr == fb->addr) ||
+
+ (fa->addr > fb->addr &&
+ fa->addr < (fb+1)->addr))
+ return 0;
+
+ if (fa->addr < fb->addr)
+ return -1;
+
+ return 1;
+}
+
+static int func_map_init(struct pevent *pevent)
+{
+ struct func_list *funclist;
+ struct func_list *item;
+ struct func_map *func_map;
+ int i;
+
+ func_map = malloc(sizeof(*func_map) * (pevent->func_count + 1));
+ if (!func_map)
+ return -1;
+
+ funclist = pevent->funclist;
+
+ i = 0;
+ while (funclist) {
+ func_map[i].func = funclist->func;
+ func_map[i].addr = funclist->addr;
+ func_map[i].mod = funclist->mod;
+ i++;
+ item = funclist;
+ funclist = funclist->next;
+ free(item);
+ }
+
+ qsort(func_map, pevent->func_count, sizeof(*func_map), func_cmp);
+
+ /*
+ * Add a special record at the end.
+ */
+ func_map[pevent->func_count].func = NULL;
+ func_map[pevent->func_count].addr = 0;
+ func_map[pevent->func_count].mod = NULL;
+
+ pevent->func_map = func_map;
+ pevent->funclist = NULL;
+
+ return 0;
+}
+
+static struct func_map *
+find_func(struct pevent *pevent, unsigned long long addr)
+{
+ struct func_map *func;
+ struct func_map key;
+
+ if (!pevent->func_map)
+ func_map_init(pevent);
+
+ key.addr = addr;
+
+ func = bsearch(&key, pevent->func_map, pevent->func_count,
+ sizeof(*pevent->func_map), func_bcmp);
+
+ return func;
+}
+
+/**
+ * pevent_find_function - find a function by a given address
+ * @pevent: handle for the pevent
+ * @addr: the address to find the function with
+ *
+ * Returns a pointer to the function stored that has the given
+ * address. Note, the address does not have to be exact, it
+ * will select the function that would contain the address.
+ */
+const char *pevent_find_function(struct pevent *pevent, unsigned long long addr)
+{
+ struct func_map *map;
+
+ map = find_func(pevent, addr);
+ if (!map)
+ return NULL;
+
+ return map->func;
+}
+
+/**
+ * pevent_find_function_address - find a function address by a given address
+ * @pevent: handle for the pevent
+ * @addr: the address to find the function with
+ *
+ * Returns the address the function starts at. This can be used in
+ * conjunction with pevent_find_function to print both the function
+ * name and the function offset.
+ */
+unsigned long long
+pevent_find_function_address(struct pevent *pevent, unsigned long long addr)
+{
+ struct func_map *map;
+
+ map = find_func(pevent, addr);
+ if (!map)
+ return 0;
+
+ return map->addr;
+}
+
+/**
+ * pevent_register_function - register a function with a given address
+ * @pevent: handle for the pevent
+ * @function: the function name to register
+ * @addr: the address the function starts at
+ * @mod: the kernel module the function may be in (NULL for none)
+ *
+ * This registers a function name with an address and module.
+ * The @func passed in is duplicated.
+ */
+int pevent_register_function(struct pevent *pevent, char *func,
+ unsigned long long addr, char *mod)
+{
+ struct func_list *item = malloc(sizeof(*item));
+
+ if (!item)
+ return -1;
+
+ item->next = pevent->funclist;
+ item->func = strdup(func);
+ if (!item->func)
+ goto out_free;
+
+ if (mod) {
+ item->mod = strdup(mod);
+ if (!item->mod)
+ goto out_free_func;
+ } else
+ item->mod = NULL;
+ item->addr = addr;
+
+ pevent->funclist = item;
+ pevent->func_count++;
+
+ return 0;
+
+out_free_func:
+ free(item->func);
+ item->func = NULL;
+out_free:
+ free(item);
+ errno = ENOMEM;
+ return -1;
+}
+
+/**
+ * pevent_print_funcs - print out the stored functions
+ * @pevent: handle for the pevent
+ *
+ * This prints out the stored functions.
+ */
+void pevent_print_funcs(struct pevent *pevent)
+{
+ int i;
+
+ if (!pevent->func_map)
+ func_map_init(pevent);
+
+ for (i = 0; i < (int)pevent->func_count; i++) {
+ printf("%016llx %s",
+ pevent->func_map[i].addr,
+ pevent->func_map[i].func);
+ if (pevent->func_map[i].mod)
+ printf(" [%s]\n", pevent->func_map[i].mod);
+ else
+ printf("\n");
+ }
+}
+
+struct printk_map {
+ unsigned long long addr;
+ char *printk;
+};
+
+struct printk_list {
+ struct printk_list *next;
+ unsigned long long addr;
+ char *printk;
+};
+
+static int printk_cmp(const void *a, const void *b)
+{
+ const struct printk_map *pa = a;
+ const struct printk_map *pb = b;
+
+ if (pa->addr < pb->addr)
+ return -1;
+ if (pa->addr > pb->addr)
+ return 1;
+
+ return 0;
+}
+
+static int printk_map_init(struct pevent *pevent)
+{
+ struct printk_list *printklist;
+ struct printk_list *item;
+ struct printk_map *printk_map;
+ int i;
+
+ printk_map = malloc(sizeof(*printk_map) * (pevent->printk_count + 1));
+ if (!printk_map)
+ return -1;
+
+ printklist = pevent->printklist;
+
+ i = 0;
+ while (printklist) {
+ printk_map[i].printk = printklist->printk;
+ printk_map[i].addr = printklist->addr;
+ i++;
+ item = printklist;
+ printklist = printklist->next;
+ free(item);
+ }
+
+ qsort(printk_map, pevent->printk_count, sizeof(*printk_map), printk_cmp);
+
+ pevent->printk_map = printk_map;
+ pevent->printklist = NULL;
+
+ return 0;
+}
+
+static struct printk_map *
+find_printk(struct pevent *pevent, unsigned long long addr)
+{
+ struct printk_map *printk;
+ struct printk_map key;
+
+ if (!pevent->printk_map && printk_map_init(pevent))
+ return NULL;
+
+ key.addr = addr;
+
+ printk = bsearch(&key, pevent->printk_map, pevent->printk_count,
+ sizeof(*pevent->printk_map), printk_cmp);
+
+ return printk;
+}
+
+/**
+ * pevent_register_print_string - register a string by its address
+ * @pevent: handle for the pevent
+ * @fmt: the string format to register
+ * @addr: the address the string was located at
+ *
+ * This registers a string by the address it was stored in the kernel.
+ * The @fmt passed in is duplicated.
+ */
+int pevent_register_print_string(struct pevent *pevent, const char *fmt,
+ unsigned long long addr)
+{
+ struct printk_list *item = malloc(sizeof(*item));
+ char *p;
+
+ if (!item)
+ return -1;
+
+ item->next = pevent->printklist;
+ item->addr = addr;
+
+ /* Strip off quotes and '\n' from the end */
+ if (fmt[0] == '"')
+ fmt++;
+ item->printk = strdup(fmt);
+ if (!item->printk)
+ goto out_free;
+
+ p = item->printk + strlen(item->printk) - 1;
+ if (*p == '"')
+ *p = 0;
+
+ p -= 2;
+ if (strcmp(p, "\\n") == 0)
+ *p = 0;
+
+ pevent->printklist = item;
+ pevent->printk_count++;
+
+ return 0;
+
+out_free:
+ free(item);
+ errno = ENOMEM;
+ return -1;
+}
+
+/**
+ * pevent_print_printk - print out the stored strings
+ * @pevent: handle for the pevent
+ *
+ * This prints the string formats that were stored.
+ */
+void pevent_print_printk(struct pevent *pevent)
+{
+ int i;
+
+ if (!pevent->printk_map)
+ printk_map_init(pevent);
+
+ for (i = 0; i < (int)pevent->printk_count; i++) {
+ printf("%016llx %s\n",
+ pevent->printk_map[i].addr,
+ pevent->printk_map[i].printk);
+ }
+}
+
+static struct event_format *alloc_event(void)
+{
+ return calloc(1, sizeof(struct event_format));
+}
+
+static int add_event(struct pevent *pevent, struct event_format *event)
+{
+ int i;
+ struct event_format **events = realloc(pevent->events, sizeof(event) *
+ (pevent->nr_events + 1));
+ if (!events)
+ return -1;
+
+ pevent->events = events;
+
+ for (i = 0; i < pevent->nr_events; i++) {
+ if (pevent->events[i]->id > event->id)
+ break;
+ }
+ if (i < pevent->nr_events)
+ memmove(&pevent->events[i + 1],
+ &pevent->events[i],
+ sizeof(event) * (pevent->nr_events - i));
+
+ pevent->events[i] = event;
+ pevent->nr_events++;
+
+ event->pevent = pevent;
+
+ return 0;
+}
+
+static int event_item_type(enum event_type type)
+{
+ switch (type) {
+ case EVENT_ITEM ... EVENT_SQUOTE:
+ return 1;
+ case EVENT_ERROR ... EVENT_DELIM:
+ default:
+ return 0;
+ }
+}
+
+static void free_flag_sym(struct print_flag_sym *fsym)
+{
+ struct print_flag_sym *next;
+
+ while (fsym) {
+ next = fsym->next;
+ free(fsym->value);
+ free(fsym->str);
+ free(fsym);
+ fsym = next;
+ }
+}
+
+static void free_arg(struct print_arg *arg)
+{
+ struct print_arg *farg;
+
+ if (!arg)
+ return;
+
+ switch (arg->type) {
+ case PRINT_ATOM:
+ free(arg->atom.atom);
+ break;
+ case PRINT_FIELD:
+ free(arg->field.name);
+ break;
+ case PRINT_FLAGS:
+ free_arg(arg->flags.field);
+ free(arg->flags.delim);
+ free_flag_sym(arg->flags.flags);
+ break;
+ case PRINT_SYMBOL:
+ free_arg(arg->symbol.field);
+ free_flag_sym(arg->symbol.symbols);
+ break;
+ case PRINT_HEX:
+ free_arg(arg->hex.field);
+ free_arg(arg->hex.size);
+ break;
+ case PRINT_TYPE:
+ free(arg->typecast.type);
+ free_arg(arg->typecast.item);
+ break;
+ case PRINT_STRING:
+ case PRINT_BSTRING:
+ free(arg->string.string);
+ break;
+ case PRINT_BITMASK:
+ free(arg->bitmask.bitmask);
+ break;
+ case PRINT_DYNAMIC_ARRAY:
+ free(arg->dynarray.index);
+ break;
+ case PRINT_OP:
+ free(arg->op.op);
+ free_arg(arg->op.left);
+ free_arg(arg->op.right);
+ break;
+ case PRINT_FUNC:
+ while (arg->func.args) {
+ farg = arg->func.args;
+ arg->func.args = farg->next;
+ free_arg(farg);
+ }
+ break;
+
+ case PRINT_NULL:
+ default:
+ break;
+ }
+
+ free(arg);
+}
+
+static enum event_type get_type(int ch)
+{
+ if (ch == '\n')
+ return EVENT_NEWLINE;
+ if (isspace(ch))
+ return EVENT_SPACE;
+ if (isalnum(ch) || ch == '_')
+ return EVENT_ITEM;
+ if (ch == '\'')
+ return EVENT_SQUOTE;
+ if (ch == '"')
+ return EVENT_DQUOTE;
+ if (!isprint(ch))
+ return EVENT_NONE;
+ if (ch == '(' || ch == ')' || ch == ',')
+ return EVENT_DELIM;
+
+ return EVENT_OP;
+}
+
+static int __read_char(void)
+{
+ if (input_buf_ptr >= input_buf_siz)
+ return -1;
+
+ return input_buf[input_buf_ptr++];
+}
+
+static int __peek_char(void)
+{
+ if (input_buf_ptr >= input_buf_siz)
+ return -1;
+
+ return input_buf[input_buf_ptr];
+}
+
+/**
+ * pevent_peek_char - peek at the next character that will be read
+ *
+ * Returns the next character read, or -1 if end of buffer.
+ */
+int pevent_peek_char(void)
+{
+ return __peek_char();
+}
+
+static int extend_token(char **tok, char *buf, int size)
+{
+ char *newtok = realloc(*tok, size);
+
+ if (!newtok) {
+ free(*tok);
+ *tok = NULL;
+ return -1;
+ }
+
+ if (!*tok)
+ strcpy(newtok, buf);
+ else
+ strcat(newtok, buf);
+ *tok = newtok;
+
+ return 0;
+}
+
+static enum event_type force_token(const char *str, char **tok);
+
+static enum event_type __read_token(char **tok)
+{
+ char buf[BUFSIZ];
+ int ch, last_ch, quote_ch, next_ch;
+ int i = 0;
+ int tok_size = 0;
+ enum event_type type;
+
+ *tok = NULL;
+
+
+ ch = __read_char();
+ if (ch < 0)
+ return EVENT_NONE;
+
+ type = get_type(ch);
+ if (type == EVENT_NONE)
+ return type;
+
+ buf[i++] = ch;
+
+ switch (type) {
+ case EVENT_NEWLINE:
+ case EVENT_DELIM:
+ if (asprintf(tok, "%c", ch) < 0)
+ return EVENT_ERROR;
+
+ return type;
+
+ case EVENT_OP:
+ switch (ch) {
+ case '-':
+ next_ch = __peek_char();
+ if (next_ch == '>') {
+ buf[i++] = __read_char();
+ break;
+ }
+ /* fall through */
+ case '+':
+ case '|':
+ case '&':
+ case '>':
+ case '<':
+ last_ch = ch;
+ ch = __peek_char();
+ if (ch != last_ch)
+ goto test_equal;
+ buf[i++] = __read_char();
+ switch (last_ch) {
+ case '>':
+ case '<':
+ goto test_equal;
+ default:
+ break;
+ }
+ break;
+ case '!':
+ case '=':
+ goto test_equal;
+ default: /* what should we do instead? */
+ break;
+ }
+ buf[i] = 0;
+ *tok = strdup(buf);
+ return type;
+
+ test_equal:
+ ch = __peek_char();
+ if (ch == '=')
+ buf[i++] = __read_char();
+ goto out;
+
+ case EVENT_DQUOTE:
+ case EVENT_SQUOTE:
+ /* don't keep quotes */
+ i--;
+ quote_ch = ch;
+ last_ch = 0;
+ concat:
+ do {
+ if (i == (BUFSIZ - 1)) {
+ buf[i] = 0;
+ tok_size += BUFSIZ;
+
+ if (extend_token(tok, buf, tok_size) < 0)
+ return EVENT_NONE;
+ i = 0;
+ }
+ last_ch = ch;
+ ch = __read_char();
+ buf[i++] = ch;
+ /* the '\' '\' will cancel itself */
+ if (ch == '\\' && last_ch == '\\')
+ last_ch = 0;
+ } while (ch != quote_ch || last_ch == '\\');
+ /* remove the last quote */
+ i--;
+
+ /*
+ * For strings (double quotes) check the next token.
+ * If it is another string, concatinate the two.
+ */
+ if (type == EVENT_DQUOTE) {
+ unsigned long long save_input_buf_ptr = input_buf_ptr;
+
+ do {
+ ch = __read_char();
+ } while (isspace(ch));
+ if (ch == '"')
+ goto concat;
+ input_buf_ptr = save_input_buf_ptr;
+ }
+
+ goto out;
+
+ case EVENT_ERROR ... EVENT_SPACE:
+ case EVENT_ITEM:
+ default:
+ break;
+ }
+
+ while (get_type(__peek_char()) == type) {
+ if (i == (BUFSIZ - 1)) {
+ buf[i] = 0;
+ tok_size += BUFSIZ;
+
+ if (extend_token(tok, buf, tok_size) < 0)
+ return EVENT_NONE;
+ i = 0;
+ }
+ ch = __read_char();
+ buf[i++] = ch;
+ }
+
+ out:
+ buf[i] = 0;
+ if (extend_token(tok, buf, tok_size + i + 1) < 0)
+ return EVENT_NONE;
+
+ if (type == EVENT_ITEM) {
+ /*
+ * Older versions of the kernel has a bug that
+ * creates invalid symbols and will break the mac80211
+ * parsing. This is a work around to that bug.
+ *
+ * See Linux kernel commit:
+ * 811cb50baf63461ce0bdb234927046131fc7fa8b
+ */
+ if (strcmp(*tok, "LOCAL_PR_FMT") == 0) {
+ free(*tok);
+ *tok = NULL;
+ return force_token("\"\%s\" ", tok);
+ } else if (strcmp(*tok, "STA_PR_FMT") == 0) {
+ free(*tok);
+ *tok = NULL;
+ return force_token("\" sta:%pM\" ", tok);
+ } else if (strcmp(*tok, "VIF_PR_FMT") == 0) {
+ free(*tok);
+ *tok = NULL;
+ return force_token("\" vif:%p(%d)\" ", tok);
+ }
+ }
+
+ return type;
+}
+
+static enum event_type force_token(const char *str, char **tok)
+{
+ const char *save_input_buf;
+ unsigned long long save_input_buf_ptr;
+ unsigned long long save_input_buf_siz;
+ enum event_type type;
+
+ /* save off the current input pointers */
+ save_input_buf = input_buf;
+ save_input_buf_ptr = input_buf_ptr;
+ save_input_buf_siz = input_buf_siz;
+
+ init_input_buf(str, strlen(str));
+
+ type = __read_token(tok);
+
+ /* reset back to original token */
+ input_buf = save_input_buf;
+ input_buf_ptr = save_input_buf_ptr;
+ input_buf_siz = save_input_buf_siz;
+
+ return type;
+}
+
+static void free_token(char *tok)
+{
+ if (tok)
+ free(tok);
+}
+
+static enum event_type read_token(char **tok)
+{
+ enum event_type type;
+
+ for (;;) {
+ type = __read_token(tok);
+ if (type != EVENT_SPACE)
+ return type;
+
+ free_token(*tok);
+ }
+
+ /* not reached */
+ *tok = NULL;
+ return EVENT_NONE;
+}
+
+/**
+ * pevent_read_token - access to utilites to use the pevent parser
+ * @tok: The token to return
+ *
+ * This will parse tokens from the string given by
+ * pevent_init_data().
+ *
+ * Returns the token type.
+ */
+enum event_type pevent_read_token(char **tok)
+{
+ return read_token(tok);
+}
+
+/**
+ * pevent_free_token - free a token returned by pevent_read_token
+ * @token: the token to free
+ */
+void pevent_free_token(char *token)
+{
+ free_token(token);
+}
+
+/* no newline */
+static enum event_type read_token_item(char **tok)
+{
+ enum event_type type;
+
+ for (;;) {
+ type = __read_token(tok);
+ if (type != EVENT_SPACE && type != EVENT_NEWLINE)
+ return type;
+ free_token(*tok);
+ *tok = NULL;
+ }
+
+ /* not reached */
+ *tok = NULL;
+ return EVENT_NONE;
+}
+
+static int test_type(enum event_type type, enum event_type expect)
+{
+ if (type != expect) {
+ do_warning("Error: expected type %d but read %d",
+ expect, type);
+ return -1;
+ }
+ return 0;
+}
+
+static int test_type_token(enum event_type type, const char *token,
+ enum event_type expect, const char *expect_tok)
+{
+ if (type != expect) {
+ do_warning("Error: expected type %d but read %d",
+ expect, type);
+ return -1;
+ }
+
+ if (strcmp(token, expect_tok) != 0) {
+ do_warning("Error: expected '%s' but read '%s'",
+ expect_tok, token);
+ return -1;
+ }
+ return 0;
+}
+
+static int __read_expect_type(enum event_type expect, char **tok, int newline_ok)
+{
+ enum event_type type;
+
+ if (newline_ok)
+ type = read_token(tok);
+ else
+ type = read_token_item(tok);
+ return test_type(type, expect);
+}
+
+static int read_expect_type(enum event_type expect, char **tok)
+{
+ return __read_expect_type(expect, tok, 1);
+}
+
+static int __read_expected(enum event_type expect, const char *str,
+ int newline_ok)
+{
+ enum event_type type;
+ char *token;
+ int ret;
+
+ if (newline_ok)
+ type = read_token(&token);
+ else
+ type = read_token_item(&token);
+
+ ret = test_type_token(type, token, expect, str);
+
+ free_token(token);
+
+ return ret;
+}
+
+static int read_expected(enum event_type expect, const char *str)
+{
+ return __read_expected(expect, str, 1);
+}
+
+static int read_expected_item(enum event_type expect, const char *str)
+{
+ return __read_expected(expect, str, 0);
+}
+
+static char *event_read_name(void)
+{
+ char *token;
+
+ if (read_expected(EVENT_ITEM, "name") < 0)
+ return NULL;
+
+ if (read_expected(EVENT_OP, ":") < 0)
+ return NULL;
+
+ if (read_expect_type(EVENT_ITEM, &token) < 0)
+ goto fail;
+
+ return token;
+
+ fail:
+ free_token(token);
+ return NULL;
+}
+
+static int event_read_id(void)
+{
+ char *token;
+ int id;
+
+ if (read_expected_item(EVENT_ITEM, "ID") < 0)
+ return -1;
+
+ if (read_expected(EVENT_OP, ":") < 0)
+ return -1;
+
+ if (read_expect_type(EVENT_ITEM, &token) < 0)
+ goto fail;
+
+ id = strtoul(token, NULL, 0);
+ free_token(token);
+ return id;
+
+ fail:
+ free_token(token);
+ return -1;
+}
+
+static int field_is_string(struct format_field *field)
+{
+ if ((field->flags & FIELD_IS_ARRAY) &&
+ (strstr(field->type, "char") || strstr(field->type, "u8") ||
+ strstr(field->type, "s8")))
+ return 1;
+
+ return 0;
+}
+
+static int field_is_dynamic(struct format_field *field)
+{
+ if (strncmp(field->type, "__data_loc", 10) == 0)
+ return 1;
+
+ return 0;
+}
+
+static int field_is_long(struct format_field *field)
+{
+ /* includes long long */
+ if (strstr(field->type, "long"))
+ return 1;
+
+ return 0;
+}
+
+static unsigned int type_size(const char *name)
+{
+ /* This covers all FIELD_IS_STRING types. */
+ static struct {
+ const char *type;
+ unsigned int size;
+ } table[] = {
+ { "u8", 1 },
+ { "u16", 2 },
+ { "u32", 4 },
+ { "u64", 8 },
+ { "s8", 1 },
+ { "s16", 2 },
+ { "s32", 4 },
+ { "s64", 8 },
+ { "char", 1 },
+ { },
+ };
+ int i;
+
+ for (i = 0; table[i].type; i++) {
+ if (!strcmp(table[i].type, name))
+ return table[i].size;
+ }
+
+ return 0;
+}
+
+static int event_read_fields(struct event_format *event, struct format_field **fields)
+{
+ struct format_field *field = NULL;
+ enum event_type type;
+ char *token;
+ char *last_token;
+ int count = 0;
+
+ do {
+ unsigned int size_dynamic = 0;
+
+ type = read_token(&token);
+ if (type == EVENT_NEWLINE) {
+ free_token(token);
+ return count;
+ }
+
+ count++;
+
+ if (test_type_token(type, token, EVENT_ITEM, "field"))
+ goto fail;
+ free_token(token);
+
+ type = read_token(&token);
+ /*
+ * The ftrace fields may still use the "special" name.
+ * Just ignore it.
+ */
+ if (event->flags & EVENT_FL_ISFTRACE &&
+ type == EVENT_ITEM && strcmp(token, "special") == 0) {
+ free_token(token);
+ type = read_token(&token);
+ }
+
+ if (test_type_token(type, token, EVENT_OP, ":") < 0)
+ goto fail;
+
+ free_token(token);
+ if (read_expect_type(EVENT_ITEM, &token) < 0)
+ goto fail;
+
+ last_token = token;
+
+ field = calloc(1, sizeof(*field));
+ if (!field)
+ goto fail;
+
+ field->event = event;
+
+ /* read the rest of the type */
+ for (;;) {
+ type = read_token(&token);
+ if (type == EVENT_ITEM ||
+ (type == EVENT_OP && strcmp(token, "*") == 0) ||
+ /*
+ * Some of the ftrace fields are broken and have
+ * an illegal "." in them.
+ */
+ (event->flags & EVENT_FL_ISFTRACE &&
+ type == EVENT_OP && strcmp(token, ".") == 0)) {
+
+ if (strcmp(token, "*") == 0)
+ field->flags |= FIELD_IS_POINTER;
+
+ if (field->type) {
+ char *new_type;
+ new_type = realloc(field->type,
+ strlen(field->type) +
+ strlen(last_token) + 2);
+ if (!new_type) {
+ free(last_token);
+ goto fail;
+ }
+ field->type = new_type;
+ strcat(field->type, " ");
+ strcat(field->type, last_token);
+ free(last_token);
+ } else
+ field->type = last_token;
+ last_token = token;
+ continue;
+ }
+
+ break;
+ }
+
+ if (!field->type) {
+ do_warning_event(event, "%s: no type found", __func__);
+ goto fail;
+ }
+ field->name = last_token;
+
+ if (test_type(type, EVENT_OP))
+ goto fail;
+
+ if (strcmp(token, "[") == 0) {
+ enum event_type last_type = type;
+ char *brackets = token;
+ char *new_brackets;
+ int len;
+
+ field->flags |= FIELD_IS_ARRAY;
+
+ type = read_token(&token);
+
+ if (type == EVENT_ITEM)
+ field->arraylen = strtoul(token, NULL, 0);
+ else
+ field->arraylen = 0;
+
+ while (strcmp(token, "]") != 0) {
+ if (last_type == EVENT_ITEM &&
+ type == EVENT_ITEM)
+ len = 2;
+ else
+ len = 1;
+ last_type = type;
+
+ new_brackets = realloc(brackets,
+ strlen(brackets) +
+ strlen(token) + len);
+ if (!new_brackets) {
+ free(brackets);
+ goto fail;
+ }
+ brackets = new_brackets;
+ if (len == 2)
+ strcat(brackets, " ");
+ strcat(brackets, token);
+ /* We only care about the last token */
+ field->arraylen = strtoul(token, NULL, 0);
+ free_token(token);
+ type = read_token(&token);
+ if (type == EVENT_NONE) {
+ do_warning_event(event, "failed to find token");
+ goto fail;
+ }
+ }
+
+ free_token(token);
+
+ new_brackets = realloc(brackets, strlen(brackets) + 2);
+ if (!new_brackets) {
+ free(brackets);
+ goto fail;
+ }
+ brackets = new_brackets;
+ strcat(brackets, "]");
+
+ /* add brackets to type */
+
+ type = read_token(&token);
+ /*
+ * If the next token is not an OP, then it is of
+ * the format: type [] item;
+ */
+ if (type == EVENT_ITEM) {
+ char *new_type;
+ new_type = realloc(field->type,
+ strlen(field->type) +
+ strlen(field->name) +
+ strlen(brackets) + 2);
+ if (!new_type) {
+ free(brackets);
+ goto fail;
+ }
+ field->type = new_type;
+ strcat(field->type, " ");
+ strcat(field->type, field->name);
+ size_dynamic = type_size(field->name);
+ free_token(field->name);
+ strcat(field->type, brackets);
+ field->name = token;
+ type = read_token(&token);
+ } else {
+ char *new_type;
+ new_type = realloc(field->type,
+ strlen(field->type) +
+ strlen(brackets) + 1);
+ if (!new_type) {
+ free(brackets);
+ goto fail;
+ }
+ field->type = new_type;
+ strcat(field->type, brackets);
+ }
+ free(brackets);
+ }
+
+ if (field_is_string(field))
+ field->flags |= FIELD_IS_STRING;
+ if (field_is_dynamic(field))
+ field->flags |= FIELD_IS_DYNAMIC;
+ if (field_is_long(field))
+ field->flags |= FIELD_IS_LONG;
+
+ if (test_type_token(type, token, EVENT_OP, ";"))
+ goto fail;
+ free_token(token);
+
+ if (read_expected(EVENT_ITEM, "offset") < 0)
+ goto fail_expect;
+
+ if (read_expected(EVENT_OP, ":") < 0)
+ goto fail_expect;
+
+ if (read_expect_type(EVENT_ITEM, &token))
+ goto fail;
+ field->offset = strtoul(token, NULL, 0);
+ free_token(token);
+
+ if (read_expected(EVENT_OP, ";") < 0)
+ goto fail_expect;
+
+ if (read_expected(EVENT_ITEM, "size") < 0)
+ goto fail_expect;
+
+ if (read_expected(EVENT_OP, ":") < 0)
+ goto fail_expect;
+
+ if (read_expect_type(EVENT_ITEM, &token))
+ goto fail;
+ field->size = strtoul(token, NULL, 0);
+ free_token(token);
+
+ if (read_expected(EVENT_OP, ";") < 0)
+ goto fail_expect;
+
+ type = read_token(&token);
+ if (type != EVENT_NEWLINE) {
+ /* newer versions of the kernel have a "signed" type */
+ if (test_type_token(type, token, EVENT_ITEM, "signed"))
+ goto fail;
+
+ free_token(token);
+
+ if (read_expected(EVENT_OP, ":") < 0)
+ goto fail_expect;
+
+ if (read_expect_type(EVENT_ITEM, &token))
+ goto fail;
+
+ if (strtoul(token, NULL, 0))
+ field->flags |= FIELD_IS_SIGNED;
+
+ free_token(token);
+ if (read_expected(EVENT_OP, ";") < 0)
+ goto fail_expect;
+
+ if (read_expect_type(EVENT_NEWLINE, &token))
+ goto fail;
+ }
+
+ free_token(token);
+
+ if (field->flags & FIELD_IS_ARRAY) {
+ if (field->arraylen)
+ field->elementsize = field->size / field->arraylen;
+ else if (field->flags & FIELD_IS_DYNAMIC)
+ field->elementsize = size_dynamic;
+ else if (field->flags & FIELD_IS_STRING)
+ field->elementsize = 1;
+ else if (field->flags & FIELD_IS_LONG)
+ field->elementsize = event->pevent ?
+ event->pevent->long_size :
+ sizeof(long);
+ } else
+ field->elementsize = field->size;
+
+ *fields = field;
+ fields = &field->next;
+
+ } while (1);
+
+ return 0;
+
+fail:
+ free_token(token);
+fail_expect:
+ if (field) {
+ free(field->type);
+ free(field->name);
+ free(field);
+ }
+ return -1;
+}
+
+static int event_read_format(struct event_format *event)
+{
+ char *token;
+ int ret;
+
+ if (read_expected_item(EVENT_ITEM, "format") < 0)
+ return -1;
+
+ if (read_expected(EVENT_OP, ":") < 0)
+ return -1;
+
+ if (read_expect_type(EVENT_NEWLINE, &token))
+ goto fail;
+ free_token(token);
+
+ ret = event_read_fields(event, &event->format.common_fields);
+ if (ret < 0)
+ return ret;
+ event->format.nr_common = ret;
+
+ ret = event_read_fields(event, &event->format.fields);
+ if (ret < 0)
+ return ret;
+ event->format.nr_fields = ret;
+
+ return 0;
+
+ fail:
+ free_token(token);
+ return -1;
+}
+
+static enum event_type
+process_arg_token(struct event_format *event, struct print_arg *arg,
+ char **tok, enum event_type type);
+
+static enum event_type
+process_arg(struct event_format *event, struct print_arg *arg, char **tok)
+{
+ enum event_type type;
+ char *token;
+
+ type = read_token(&token);
+ *tok = token;
+
+ return process_arg_token(event, arg, tok, type);
+}
+
+static enum event_type
+process_op(struct event_format *event, struct print_arg *arg, char **tok);
+
+/*
+ * For __print_symbolic() and __print_flags, we need to completely
+ * evaluate the first argument, which defines what to print next.
+ */
+static enum event_type
+process_field_arg(struct event_format *event, struct print_arg *arg, char **tok)
+{
+ enum event_type type;
+
+ type = process_arg(event, arg, tok);
+
+ while (type == EVENT_OP) {
+ type = process_op(event, arg, tok);
+ }
+
+ return type;
+}
+
+static enum event_type
+process_cond(struct event_format *event, struct print_arg *top, char **tok)
+{
+ struct print_arg *arg, *left, *right;
+ enum event_type type;
+ char *token = NULL;
+
+ arg = alloc_arg();
+ left = alloc_arg();
+ right = alloc_arg();
+
+ if (!arg || !left || !right) {
+ do_warning_event(event, "%s: not enough memory!", __func__);
+ /* arg will be freed at out_free */
+ free_arg(left);
+ free_arg(right);
+ goto out_free;
+ }
+
+ arg->type = PRINT_OP;
+ arg->op.left = left;
+ arg->op.right = right;
+
+ *tok = NULL;
+ type = process_arg(event, left, &token);
+
+ again:
+ /* Handle other operations in the arguments */
+ if (type == EVENT_OP && strcmp(token, ":") != 0) {
+ type = process_op(event, left, &token);
+ goto again;
+ }
+
+ if (test_type_token(type, token, EVENT_OP, ":"))
+ goto out_free;
+
+ arg->op.op = token;
+
+ type = process_arg(event, right, &token);
+
+ top->op.right = arg;
+
+ *tok = token;
+ return type;
+
+out_free:
+ /* Top may point to itself */
+ top->op.right = NULL;
+ free_token(token);
+ free_arg(arg);
+ return EVENT_ERROR;
+}
+
+static enum event_type
+process_array(struct event_format *event, struct print_arg *top, char **tok)
+{
+ struct print_arg *arg;
+ enum event_type type;
+ char *token = NULL;
+
+ arg = alloc_arg();
+ if (!arg) {
+ do_warning_event(event, "%s: not enough memory!", __func__);
+ /* '*tok' is set to top->op.op. No need to free. */
+ *tok = NULL;
+ return EVENT_ERROR;
+ }
+
+ *tok = NULL;
+ type = process_arg(event, arg, &token);
+ if (test_type_token(type, token, EVENT_OP, "]"))
+ goto out_free;
+
+ top->op.right = arg;
+
+ free_token(token);
+ type = read_token_item(&token);
+ *tok = token;
+
+ return type;
+
+out_free:
+ free_token(token);
+ free_arg(arg);
+ return EVENT_ERROR;
+}
+
+static int get_op_prio(char *op)
+{
+ if (!op[1]) {
+ switch (op[0]) {
+ case '~':
+ case '!':
+ return 4;
+ case '*':
+ case '/':
+ case '%':
+ return 6;
+ case '+':
+ case '-':
+ return 7;
+ /* '>>' and '<<' are 8 */
+ case '<':
+ case '>':
+ return 9;
+ /* '==' and '!=' are 10 */
+ case '&':
+ return 11;
+ case '^':
+ return 12;
+ case '|':
+ return 13;
+ case '?':
+ return 16;
+ default:
+ do_warning("unknown op '%c'", op[0]);
+ return -1;
+ }
+ } else {
+ if (strcmp(op, "++") == 0 ||
+ strcmp(op, "--") == 0) {
+ return 3;
+ } else if (strcmp(op, ">>") == 0 ||
+ strcmp(op, "<<") == 0) {
+ return 8;
+ } else if (strcmp(op, ">=") == 0 ||
+ strcmp(op, "<=") == 0) {
+ return 9;
+ } else if (strcmp(op, "==") == 0 ||
+ strcmp(op, "!=") == 0) {
+ return 10;
+ } else if (strcmp(op, "&&") == 0) {
+ return 14;
+ } else if (strcmp(op, "||") == 0) {
+ return 15;
+ } else {
+ do_warning("unknown op '%s'", op);
+ return -1;
+ }
+ }
+}
+
+static int set_op_prio(struct print_arg *arg)
+{
+
+ /* single ops are the greatest */
+ if (!arg->op.left || arg->op.left->type == PRINT_NULL)
+ arg->op.prio = 0;
+ else
+ arg->op.prio = get_op_prio(arg->op.op);
+
+ return arg->op.prio;
+}
+
+/* Note, *tok does not get freed, but will most likely be saved */
+static enum event_type
+process_op(struct event_format *event, struct print_arg *arg, char **tok)
+{
+ struct print_arg *left, *right = NULL;
+ enum event_type type;
+ char *token;
+
+ /* the op is passed in via tok */
+ token = *tok;
+
+ if (arg->type == PRINT_OP && !arg->op.left) {
+ /* handle single op */
+ if (token[1]) {
+ do_warning_event(event, "bad op token %s", token);
+ goto out_free;
+ }
+ switch (token[0]) {
+ case '~':
+ case '!':
+ case '+':
+ case '-':
+ break;
+ default:
+ do_warning_event(event, "bad op token %s", token);
+ goto out_free;
+
+ }
+
+ /* make an empty left */
+ left = alloc_arg();
+ if (!left)
+ goto out_warn_free;
+
+ left->type = PRINT_NULL;
+ arg->op.left = left;
+
+ right = alloc_arg();
+ if (!right)
+ goto out_warn_free;
+
+ arg->op.right = right;
+
+ /* do not free the token, it belongs to an op */
+ *tok = NULL;
+ type = process_arg(event, right, tok);
+
+ } else if (strcmp(token, "?") == 0) {
+
+ left = alloc_arg();
+ if (!left)
+ goto out_warn_free;
+
+ /* copy the top arg to the left */
+ *left = *arg;
+
+ arg->type = PRINT_OP;
+ arg->op.op = token;
+ arg->op.left = left;
+ arg->op.prio = 0;
+
+ /* it will set arg->op.right */
+ type = process_cond(event, arg, tok);
+
+ } else if (strcmp(token, ">>") == 0 ||
+ strcmp(token, "<<") == 0 ||
+ strcmp(token, "&") == 0 ||
+ strcmp(token, "|") == 0 ||
+ strcmp(token, "&&") == 0 ||
+ strcmp(token, "||") == 0 ||
+ strcmp(token, "-") == 0 ||
+ strcmp(token, "+") == 0 ||
+ strcmp(token, "*") == 0 ||
+ strcmp(token, "^") == 0 ||
+ strcmp(token, "/") == 0 ||
+ strcmp(token, "<") == 0 ||
+ strcmp(token, ">") == 0 ||
+ strcmp(token, "<=") == 0 ||
+ strcmp(token, ">=") == 0 ||
+ strcmp(token, "==") == 0 ||
+ strcmp(token, "!=") == 0) {
+
+ left = alloc_arg();
+ if (!left)
+ goto out_warn_free;
+
+ /* copy the top arg to the left */
+ *left = *arg;
+
+ arg->type = PRINT_OP;
+ arg->op.op = token;
+ arg->op.left = left;
+ arg->op.right = NULL;
+
+ if (set_op_prio(arg) == -1) {
+ event->flags |= EVENT_FL_FAILED;
+ /* arg->op.op (= token) will be freed at out_free */
+ arg->op.op = NULL;
+ goto out_free;
+ }
+
+ type = read_token_item(&token);
+ *tok = token;
+
+ /* could just be a type pointer */
+ if ((strcmp(arg->op.op, "*") == 0) &&
+ type == EVENT_DELIM && (strcmp(token, ")") == 0)) {
+ char *new_atom;
+
+ if (left->type != PRINT_ATOM) {
+ do_warning_event(event, "bad pointer type");
+ goto out_free;
+ }
+ new_atom = realloc(left->atom.atom,
+ strlen(left->atom.atom) + 3);
+ if (!new_atom)
+ goto out_warn_free;
+
+ left->atom.atom = new_atom;
+ strcat(left->atom.atom, " *");
+ free(arg->op.op);
+ *arg = *left;
+ free(left);
+
+ return type;
+ }
+
+ right = alloc_arg();
+ if (!right)
+ goto out_warn_free;
+
+ type = process_arg_token(event, right, tok, type);
+ arg->op.right = right;
+
+ } else if (strcmp(token, "[") == 0) {
+
+ left = alloc_arg();
+ if (!left)
+ goto out_warn_free;
+
+ *left = *arg;
+
+ arg->type = PRINT_OP;
+ arg->op.op = token;
+ arg->op.left = left;
+
+ arg->op.prio = 0;
+
+ /* it will set arg->op.right */
+ type = process_array(event, arg, tok);
+
+ } else {
+ do_warning_event(event, "unknown op '%s'", token);
+ event->flags |= EVENT_FL_FAILED;
+ /* the arg is now the left side */
+ goto out_free;
+ }
+
+ if (type == EVENT_OP && strcmp(*tok, ":") != 0) {
+ int prio;
+
+ /* higher prios need to be closer to the root */
+ prio = get_op_prio(*tok);
+
+ if (prio > arg->op.prio)
+ return process_op(event, arg, tok);
+
+ return process_op(event, right, tok);
+ }
+
+ return type;
+
+out_warn_free:
+ do_warning_event(event, "%s: not enough memory!", __func__);
+out_free:
+ free_token(token);
+ *tok = NULL;
+ return EVENT_ERROR;
+}
+
+static enum event_type
+process_entry(struct event_format *event __maybe_unused, struct print_arg *arg,
+ char **tok)
+{
+ enum event_type type;
+ char *field;
+ char *token;
+
+ if (read_expected(EVENT_OP, "->") < 0)
+ goto out_err;
+
+ if (read_expect_type(EVENT_ITEM, &token) < 0)
+ goto out_free;
+ field = token;
+
+ arg->type = PRINT_FIELD;
+ arg->field.name = field;
+
+ if (is_flag_field) {
+ arg->field.field = pevent_find_any_field(event, arg->field.name);
+ arg->field.field->flags |= FIELD_IS_FLAG;
+ is_flag_field = 0;
+ } else if (is_symbolic_field) {
+ arg->field.field = pevent_find_any_field(event, arg->field.name);
+ arg->field.field->flags |= FIELD_IS_SYMBOLIC;
+ is_symbolic_field = 0;
+ }
+
+ type = read_token(&token);
+ *tok = token;
+
+ return type;
+
+ out_free:
+ free_token(token);
+ out_err:
+ *tok = NULL;
+ return EVENT_ERROR;
+}
+
+static char *arg_eval (struct print_arg *arg);
+
+static unsigned long long
+eval_type_str(unsigned long long val, const char *type, int pointer)
+{
+ int sign = 0;
+ char *ref;
+ int len;
+
+ len = strlen(type);
+
+ if (pointer) {
+
+ if (type[len-1] != '*') {
+ do_warning("pointer expected with non pointer type");
+ return val;
+ }
+
+ ref = malloc(len);
+ if (!ref) {
+ do_warning("%s: not enough memory!", __func__);
+ return val;
+ }
+ memcpy(ref, type, len);
+
+ /* chop off the " *" */
+ ref[len - 2] = 0;
+
+ val = eval_type_str(val, ref, 0);
+ free(ref);
+ return val;
+ }
+
+ /* check if this is a pointer */
+ if (type[len - 1] == '*')
+ return val;
+
+ /* Try to figure out the arg size*/
+ if (strncmp(type, "struct", 6) == 0)
+ /* all bets off */
+ return val;
+
+ if (strcmp(type, "u8") == 0)
+ return val & 0xff;
+
+ if (strcmp(type, "u16") == 0)
+ return val & 0xffff;
+
+ if (strcmp(type, "u32") == 0)
+ return val & 0xffffffff;
+
+ if (strcmp(type, "u64") == 0 ||
+ strcmp(type, "s64"))
+ return val;
+
+ if (strcmp(type, "s8") == 0)
+ return (unsigned long long)(char)val & 0xff;
+
+ if (strcmp(type, "s16") == 0)
+ return (unsigned long long)(short)val & 0xffff;
+
+ if (strcmp(type, "s32") == 0)
+ return (unsigned long long)(int)val & 0xffffffff;
+
+ if (strncmp(type, "unsigned ", 9) == 0) {
+ sign = 0;
+ type += 9;
+ }
+
+ if (strcmp(type, "char") == 0) {
+ if (sign)
+ return (unsigned long long)(char)val & 0xff;
+ else
+ return val & 0xff;
+ }
+
+ if (strcmp(type, "short") == 0) {
+ if (sign)
+ return (unsigned long long)(short)val & 0xffff;
+ else
+ return val & 0xffff;
+ }
+
+ if (strcmp(type, "int") == 0) {
+ if (sign)
+ return (unsigned long long)(int)val & 0xffffffff;
+ else
+ return val & 0xffffffff;
+ }
+
+ return val;
+}
+
+/*
+ * Try to figure out the type.
+ */
+static unsigned long long
+eval_type(unsigned long long val, struct print_arg *arg, int pointer)
+{
+ if (arg->type != PRINT_TYPE) {
+ do_warning("expected type argument");
+ return 0;
+ }
+
+ return eval_type_str(val, arg->typecast.type, pointer);
+}
+
+static int arg_num_eval(struct print_arg *arg, long long *val)
+{
+ long long left, right;
+ int ret = 1;
+
+ switch (arg->type) {
+ case PRINT_ATOM:
+ *val = strtoll(arg->atom.atom, NULL, 0);
+ break;
+ case PRINT_TYPE:
+ ret = arg_num_eval(arg->typecast.item, val);
+ if (!ret)
+ break;
+ *val = eval_type(*val, arg, 0);
+ break;
+ case PRINT_OP:
+ switch (arg->op.op[0]) {
+ case '|':
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+ if (arg->op.op[1])
+ *val = left || right;
+ else
+ *val = left | right;
+ break;
+ case '&':
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+ if (arg->op.op[1])
+ *val = left && right;
+ else
+ *val = left & right;
+ break;
+ case '<':
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+ switch (arg->op.op[1]) {
+ case 0:
+ *val = left < right;
+ break;
+ case '<':
+ *val = left << right;
+ break;
+ case '=':
+ *val = left <= right;
+ break;
+ default:
+ do_warning("unknown op '%s'", arg->op.op);
+ ret = 0;
+ }
+ break;
+ case '>':
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+ switch (arg->op.op[1]) {
+ case 0:
+ *val = left > right;
+ break;
+ case '>':
+ *val = left >> right;
+ break;
+ case '=':
+ *val = left >= right;
+ break;
+ default:
+ do_warning("unknown op '%s'", arg->op.op);
+ ret = 0;
+ }
+ break;
+ case '=':
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+
+ if (arg->op.op[1] != '=') {
+ do_warning("unknown op '%s'", arg->op.op);
+ ret = 0;
+ } else
+ *val = left == right;
+ break;
+ case '!':
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+
+ switch (arg->op.op[1]) {
+ case '=':
+ *val = left != right;
+ break;
+ default:
+ do_warning("unknown op '%s'", arg->op.op);
+ ret = 0;
+ }
+ break;
+ case '-':
+ /* check for negative */
+ if (arg->op.left->type == PRINT_NULL)
+ left = 0;
+ else
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+ *val = left - right;
+ break;
+ case '+':
+ if (arg->op.left->type == PRINT_NULL)
+ left = 0;
+ else
+ ret = arg_num_eval(arg->op.left, &left);
+ if (!ret)
+ break;
+ ret = arg_num_eval(arg->op.right, &right);
+ if (!ret)
+ break;
+ *val = left + right;
+ break;
+ default:
+ do_warning("unknown op '%s'", arg->op.op);
+ ret = 0;
+ }
+ break;
+
+ case PRINT_NULL:
+ case PRINT_FIELD ... PRINT_SYMBOL:
+ case PRINT_STRING:
+ case PRINT_BSTRING:
+ case PRINT_BITMASK:
+ default:
+ do_warning("invalid eval type %d", arg->type);
+ ret = 0;
+
+ }
+ return ret;
+}
+
+static char *arg_eval (struct print_arg *arg)
+{
+ long long val;
+ static char buf[20];
+
+ switch (arg->type) {
+ case PRINT_ATOM:
+ return arg->atom.atom;
+ case PRINT_TYPE:
+ return arg_eval(arg->typecast.item);
+ case PRINT_OP:
+ if (!arg_num_eval(arg, &val))
+ break;
+ sprintf(buf, "%lld", val);
+ return buf;
+
+ case PRINT_NULL:
+ case PRINT_FIELD ... PRINT_SYMBOL:
+ case PRINT_STRING:
+ case PRINT_BSTRING:
+ case PRINT_BITMASK:
+ default:
+ do_warning("invalid eval type %d", arg->type);
+ break;
+ }
+
+ return NULL;
+}
+
+static enum event_type
+process_fields(struct event_format *event, struct print_flag_sym **list, char **tok)
+{
+ enum event_type type;
+ struct print_arg *arg = NULL;
+ struct print_flag_sym *field;
+ char *token = *tok;
+ char *value;
+
+ do {
+ free_token(token);
+ type = read_token_item(&token);
+ if (test_type_token(type, token, EVENT_OP, "{"))
+ break;
+
+ arg = alloc_arg();
+ if (!arg)
+ goto out_free;
+
+ free_token(token);
+ type = process_arg(event, arg, &token);
+
+ if (type == EVENT_OP)
+ type = process_op(event, arg, &token);
+
+ if (type == EVENT_ERROR)
+ goto out_free;
+
+ if (test_type_token(type, token, EVENT_DELIM, ","))
+ goto out_free;
+
+ field = calloc(1, sizeof(*field));
+ if (!field)
+ goto out_free;
+
+ value = arg_eval(arg);
+ if (value == NULL)
+ goto out_free_field;
+ field->value = strdup(value);
+ if (field->value == NULL)
+ goto out_free_field;
+
+ free_arg(arg);
+ arg = alloc_arg();
+ if (!arg)
+ goto out_free;
+
+ free_token(token);
+ type = process_arg(event, arg, &token);
+ if (test_type_token(type, token, EVENT_OP, "}"))
+ goto out_free_field;
+
+ value = arg_eval(arg);
+ if (value == NULL)
+ goto out_free_field;
+ field->str = strdup(value);
+ if (field->str == NULL)
+ goto out_free_field;
+ free_arg(arg);
+ arg = NULL;
+
+ *list = field;
+ list = &field->next;
+
+ free_token(token);
+ type = read_token_item(&token);
+ } while (type == EVENT_DELIM && strcmp(token, ",") == 0);
+
+ *tok = token;
+ return type;
+
+out_free_field:
+ free_flag_sym(field);
+out_free:
+ free_arg(arg);
+ free_token(token);
+ *tok = NULL;
+
+ return EVENT_ERROR;
+}
+
+static enum event_type
+process_flags(struct event_format *event, struct print_arg *arg, char **tok)
+{
+ struct print_arg *field;
+ enum event_type type;
+ char *token;
+
+ memset(arg, 0, sizeof(*arg));
+ arg->type = PRINT_FLAGS;
+
+ field = alloc_arg();
+ if (!field) {
+ do_warning_event(event, "%s: not enough memory!", __func__);
+ goto out_free;
+ }
+
+ type = process_field_arg(event, field, &token);
+
+ /* Handle operations in the first argument */
+ while (type == EVENT_OP)
+ type = process_op(event, field, &token);
+
+ if (test_type_token(type, token, EVENT_DELIM, ","))
+ goto out_free_field;
+ free_token(token);
+
+ arg->flags.field = field;
+
+ type = read_token_item(&token);
+ if (event_item_type(type)) {
+ arg->flags.delim = token;
+ type = read_token_item(&token);
+ }
+
+ if (test_type_token(type, token, EVENT_DELIM, ","))
+ goto out_free;
+
+ type = process_fields(event, &arg->flags.flags, &token);
+ if (test_type_token(type, token, EVENT_DELIM, ")"))
+ goto out_free;
+
+ free_token(token);
+ type = read_token_item(tok);
+ return type;
+
+out_free_field:
+ free_arg(field);
+out_free:
+ free_token(token);
+ *tok = NULL;
+ return EVENT_ERROR;
+}
+
+static enum event_type
+process_symbols(struct event_format *event, struct print_arg *arg, char **tok)
+{
+ struct print_arg *field;
+ enum event_type type;
+ char *token;
+
+ memset(arg, 0, sizeof(*arg));
+ arg->type = PRINT_SYMBOL;
+
+ field = alloc_arg();
+ if (!field) {
+ do_warning_event(event, "%s: not enough memory!", __func__);
+ goto out_free;
+ }
+
+ type = process_field_arg(event, field, &token);
+
+ if (test_type_token(type, token, EVENT_DELIM, ","))
+ goto out_free_field;
+
+ arg->symbol.field = field;
+
+ type = process_fields(event, &arg->symbol.symbols, &token);
+ if (test_type_token(type, token, EVENT_DELIM, ")"))
+ goto out_free;
+
+ free_token(token);
+ type = read_token_item(tok);
+ return type;
+
+out_free_field:
+ free_arg(field);
+out_free:
+ free_token(token);
+ *tok = NULL;
+ return EVENT_ERROR;
+}
+
+static enum event_type
+process_hex(struct event_format *event, struct print_arg *arg, char **tok)
+{
+ struct print_arg *field;
+ enum event_type type;
+ char *token;
+
+ memset(arg, 0, sizeof(*arg));
+ arg->type = PRINT_HEX;
+
+ field = alloc_arg();
+ if (!field) {
+ do_warning_event(event, "%s: not enough memory!", __func__);
+ goto out_free;
+ }
+
+ type = process_arg(event, field, &token);
+
+ if (test_type_token(type, token, EVENT_DELIM, ","))
+ goto out_free;
+
+ arg->hex.field = field;
+
+ free_token(token);
+
+ field = alloc_arg();
+ if (!field) {
+ do_warning_event(event, "%s: not enough memory!", __func__);
+ *tok = NULL;
+ return EVENT_ERROR;
+ }
+
+ type = process_arg(event, field, &token);
+
+ if (test_type_token(type, token, EVENT_DELIM, ")"))
+ goto out_free;
+
+ arg->hex.size = field;
+
+ free_token(token);
+ type = read_token_item(tok);
+ return type;
+
+ out_free:
+ free_arg(field);
+ free_token(token);
+ *tok = NULL;
+ return EVENT_ERROR;
+}
+
+static enum event_type
+process_dynamic_array(struct event_format *event, struct print_arg *arg, char **tok)
+{
+ struct format_field *field;
+ enum event_type type;
+ char *token;
+
+ memset(arg, 0, sizeof(*arg));
+ arg->type = PRINT_DYNAMIC_ARRAY;
+
+ /*
+ * The item within the parenthesis is another field that holds
+ * the index into where the array starts.
+ */
+ type = read_token(&token);
+ *tok = token;
+ if (type != EVENT_ITEM)
+ goto out_free;
+
+ /* Find the field */
+
+ field = pevent_find_field(event, token);
+ if (!field)
+ goto out_free;
+
+ arg->dynarray.field = field;
+ arg->dynarray.index = 0;
+
+ if (read_expected(EVENT_DELIM, ")") < 0)
+ goto out_free;
+
+ free_token(token);
+ type = read_token_item(&token);
+ *tok = token;
+ if (type != EVENT_OP || strcmp(token, "[") != 0)
+ return type;
+
+ free_token(token);
+ arg = alloc_arg();
+ if (!arg) {
+ do_warning_event(event, "%s: not enough memory!", __func__);
+ *tok = NULL;
+ return EVENT_ERROR;
+ }
+
+ type = process_arg(event, arg, &token);
+ if (type == EVENT_ERROR)
+ goto out_free_arg;
+
+ if (!test_type_token(type, token, EVENT_OP, "]"))
+ goto out_free_arg;
+
+ free_token(token);
+ type = read_token_item(tok);
+ return type;
+
+ out_free_arg:
+ free_arg(arg);
+ out_free:
+ free_token(token);
+ *tok = NULL;
+ return EVENT_ERROR;
+}
+
+static enum event_type
+process_paren(struct event_format *event, struct print_arg *arg, char **tok)
+{
+ struct print_arg *item_arg;
+ enum event_type type;
+ char *token;
+
+ type = process_arg(event, arg, &token);
+
+ if (type == EVENT_ERROR)
+ goto out_free;
+
+ if (type == EVENT_OP)
+ type = process_op(event, arg, &token);
+
+ if (type == EVENT_ERROR)
+ goto out_free;
+
+ if (test_type_token(type, token, EVENT_DELIM, ")"))
+ goto out_free;
+
+ free_token(token);
+ type = read_token_item(&token);
+
+ /*
+ * If the next token is an item or another open paren, then
+ * this was a typecast.
+ */
+ if (event_item_type(type) ||
+ (type == EVENT_DELIM && strcmp(token, "(") == 0)) {
+
+ /* make this a typecast and contine */
+
+ /* prevous must be an atom */
+ if (arg->type != PRINT_ATOM) {
+ do_warning_event(event, "previous needed to be PRINT_ATOM");
+ goto out_free;
+ }
+
+ item_arg = alloc_arg();
+ if (!item_arg) {
+ do_warning_event(event, "%s: not enough memory!",
+ __func__);
+ goto out_free;
+ }
+
+ arg->type = PRINT_TYPE;
+ arg->typecast.type = arg->atom.atom;
+ arg->typecast.item = item_arg;
+ type = process_arg_token(event, item_arg, &token, type);
+
+ }
+
+ *tok = token;
+ return type;
+
+ out_free:
+ free_token(token);
+ *tok = NULL;
+ return EVENT_ERROR;
+}
+
+
+static enum event_type
+process_str(struct event_format *event __maybe_unused, struct print_arg *arg,
+ char **tok)
+{
+ enum event_type type;
+ char *token;
+
+ if (read_expect_type(EVENT_ITEM, &token) < 0)
+ goto out_free;
+
+ arg->type = PRINT_STRING;
+ arg->string.string = token;
+ arg->string.offset = -1;
+
+ if (read_expected(EVENT_DELIM, ")") < 0)
+ goto out_err;
+
+ type = read_token(&token);
+ *tok = token;
+
+ return type;
+
+ out_free:
+ free_token(token);
+ out_err:
+ *tok = NULL;
+ return EVENT_ERROR;
+}
+
+static enum event_type
+process_bitmask(struct event_format *event __maybe_unused, struct print_arg *arg,
+ char **tok)
+{
+ enum event_type type;
+ char *token;
+
+ if (read_expect_type(EVENT_ITEM, &token) < 0)
+ goto out_free;
+
+ arg->type = PRINT_BITMASK;
+ arg->bitmask.bitmask = token;
+ arg->bitmask.offset = -1;
+
+ if (read_expected(EVENT_DELIM, ")") < 0)
+ goto out_err;
+
+ type = read_token(&token);
+ *tok = token;
+
+ return type;
+
+ out_free:
+ free_token(token);
+ out_err:
+ *tok = NULL;
+ return EVENT_ERROR;
+}
+
+static struct pevent_function_handler *
+find_func_handler(struct pevent *pevent, char *func_name)
+{
+ struct pevent_function_handler *func;
+
+ if (!pevent)
+ return NULL;
+
+ for (func = pevent->func_handlers; func; func = func->next) {
+ if (strcmp(func->name, func_name) == 0)
+ break;
+ }
+
+ return func;
+}
+
+static void remove_func_handler(struct pevent *pevent, char *func_name)
+{
+ struct pevent_function_handler *func;
+ struct pevent_function_handler **next;
+
+ next = &pevent->func_handlers;
+ while ((func = *next)) {
+ if (strcmp(func->name, func_name) == 0) {
+ *next = func->next;
+ free_func_handle(func);
+ break;
+ }
+ next = &func->next;
+ }
+}
+
+static enum event_type
+process_func_handler(struct event_format *event, struct pevent_function_handler *func,
+ struct print_arg *arg, char **tok)
+{
+ struct print_arg **next_arg;
+ struct print_arg *farg;
+ enum event_type type;
+ char *token;
+ int i;
+
+ arg->type = PRINT_FUNC;
+ arg->func.func = func;
+
+ *tok = NULL;
+
+ next_arg = &(arg->func.args);
+ for (i = 0; i < func->nr_args; i++) {
+ farg = alloc_arg();
+ if (!farg) {
+ do_warning_event(event, "%s: not enough memory!",
+ __func__);
+ return EVENT_ERROR;
+ }
+
+ type = process_arg(event, farg, &token);
+ if (i < (func->nr_args - 1)) {
+ if (type != EVENT_DELIM || strcmp(token, ",") != 0) {
+ do_warning_event(event,
+ "Error: function '%s()' expects %d arguments but event %s only uses %d",
+ func->name, func->nr_args,
+ event->name, i + 1);
+ goto err;
+ }
+ } else {
+ if (type != EVENT_DELIM || strcmp(token, ")") != 0) {
+ do_warning_event(event,
+ "Error: function '%s()' only expects %d arguments but event %s has more",
+ func->name, func->nr_args, event->name);
+ goto err;
+ }
+ }
+
+ *next_arg = farg;
+ next_arg = &(farg->next);
+ free_token(token);
+ }
+
+ type = read_token(&token);
+ *tok = token;
+
+ return type;
+
+err:
+ free_arg(farg);
+ free_token(token);
+ return EVENT_ERROR;
+}
+
+static enum event_type
+process_function(struct event_format *event, struct print_arg *arg,
+ char *token, char **tok)
+{
+ struct pevent_function_handler *func;
+
+ if (strcmp(token, "__print_flags") == 0) {
+ free_token(token);
+ is_flag_field = 1;
+ return process_flags(event, arg, tok);
+ }
+ if (strcmp(token, "__print_symbolic") == 0) {
+ free_token(token);
+ is_symbolic_field = 1;
+ return process_symbols(event, arg, tok);
+ }
+ if (strcmp(token, "__print_hex") == 0) {
+ free_token(token);
+ return process_hex(event, arg, tok);
+ }
+ if (strcmp(token, "__get_str") == 0) {
+ free_token(token);
+ return process_str(event, arg, tok);
+ }
+ if (strcmp(token, "__get_bitmask") == 0) {
+ free_token(token);
+ return process_bitmask(event, arg, tok);
+ }
+ if (strcmp(token, "__get_dynamic_array") == 0) {
+ free_token(token);
+ return process_dynamic_array(event, arg, tok);
+ }
+
+ func = find_func_handler(event->pevent, token);
+ if (func) {
+ free_token(token);
+ return process_func_handler(event, func, arg, tok);
+ }
+
+ do_warning_event(event, "function %s not defined", token);
+ free_token(token);
+ return EVENT_ERROR;
+}
+
+static enum event_type
+process_arg_token(struct event_format *event, struct print_arg *arg,
+ char **tok, enum event_type type)
+{
+ char *token;
+ char *atom;
+
+ token = *tok;
+
+ switch (type) {
+ case EVENT_ITEM:
+ if (strcmp(token, "REC") == 0) {
+ free_token(token);
+ type = process_entry(event, arg, &token);
+ break;
+ }
+ atom = token;
+ /* test the next token */
+ type = read_token_item(&token);
+
+ /*
+ * If the next token is a parenthesis, then this
+ * is a function.
+ */
+ if (type == EVENT_DELIM && strcmp(token, "(") == 0) {
+ free_token(token);
+ token = NULL;
+ /* this will free atom. */
+ type = process_function(event, arg, atom, &token);
+ break;
+ }
+ /* atoms can be more than one token long */
+ while (type == EVENT_ITEM) {
+ char *new_atom;
+ new_atom = realloc(atom,
+ strlen(atom) + strlen(token) + 2);
+ if (!new_atom) {
+ free(atom);
+ *tok = NULL;
+ free_token(token);
+ return EVENT_ERROR;
+ }
+ atom = new_atom;
+ strcat(atom, " ");
+ strcat(atom, token);
+ free_token(token);
+ type = read_token_item(&token);
+ }
+
+ arg->type = PRINT_ATOM;
+ arg->atom.atom = atom;
+ break;
+
+ case EVENT_DQUOTE:
+ case EVENT_SQUOTE:
+ arg->type = PRINT_ATOM;
+ arg->atom.atom = token;
+ type = read_token_item(&token);
+ break;
+ case EVENT_DELIM:
+ if (strcmp(token, "(") == 0) {
+ free_token(token);
+ type = process_paren(event, arg, &token);
+ break;
+ }
+ case EVENT_OP:
+ /* handle single ops */
+ arg->type = PRINT_OP;
+ arg->op.op = token;
+ arg->op.left = NULL;
+ type = process_op(event, arg, &token);
+
+ /* On error, the op is freed */
+ if (type == EVENT_ERROR)
+ arg->op.op = NULL;
+
+ /* return error type if errored */
+ break;
+
+ case EVENT_ERROR ... EVENT_NEWLINE:
+ default:
+ do_warning_event(event, "unexpected type %d", type);
+ return EVENT_ERROR;
+ }
+ *tok = token;
+
+ return type;
+}
+
+static int event_read_print_args(struct event_format *event, struct print_arg **list)
+{
+ enum event_type type = EVENT_ERROR;
+ struct print_arg *arg;
+ char *token;
+ int args = 0;
+
+ do {
+ if (type == EVENT_NEWLINE) {
+ type = read_token_item(&token);
+ continue;
+ }
+
+ arg = alloc_arg();
+ if (!arg) {
+ do_warning_event(event, "%s: not enough memory!",
+ __func__);
+ return -1;
+ }
+
+ type = process_arg(event, arg, &token);
+
+ if (type == EVENT_ERROR) {
+ free_token(token);
+ free_arg(arg);
+ return -1;
+ }
+
+ *list = arg;
+ args++;
+
+ if (type == EVENT_OP) {
+ type = process_op(event, arg, &token);
+ free_token(token);
+ if (type == EVENT_ERROR) {
+ *list = NULL;
+ free_arg(arg);
+ return -1;
+ }
+ list = &arg->next;
+ continue;
+ }
+
+ if (type == EVENT_DELIM && strcmp(token, ",") == 0) {
+ free_token(token);
+ *list = arg;
+ list = &arg->next;
+ continue;
+ }
+ break;
+ } while (type != EVENT_NONE);
+
+ if (type != EVENT_NONE && type != EVENT_ERROR)
+ free_token(token);
+
+ return args;
+}
+
+static int event_read_print(struct event_format *event)
+{
+ enum event_type type;
+ char *token;
+ int ret;
+
+ if (read_expected_item(EVENT_ITEM, "print") < 0)
+ return -1;
+
+ if (read_expected(EVENT_ITEM, "fmt") < 0)
+ return -1;
+
+ if (read_expected(EVENT_OP, ":") < 0)
+ return -1;
+
+ if (read_expect_type(EVENT_DQUOTE, &token) < 0)
+ goto fail;
+
+ concat:
+ event->print_fmt.format = token;
+ event->print_fmt.args = NULL;
+
+ /* ok to have no arg */
+ type = read_token_item(&token);
+
+ if (type == EVENT_NONE)
+ return 0;
+
+ /* Handle concatenation of print lines */
+ if (type == EVENT_DQUOTE) {
+ char *cat;
+
+ if (asprintf(&cat, "%s%s", event->print_fmt.format, token) < 0)
+ goto fail;
+ free_token(token);
+ free_token(event->print_fmt.format);
+ event->print_fmt.format = NULL;
+ token = cat;
+ goto concat;
+ }
+
+ if (test_type_token(type, token, EVENT_DELIM, ","))
+ goto fail;
+
+ free_token(token);
+
+ ret = event_read_print_args(event, &event->print_fmt.args);
+ if (ret < 0)
+ return -1;
+
+ return ret;
+
+ fail:
+ free_token(token);
+ return -1;
+}
+
+/**
+ * pevent_find_common_field - return a common field by event
+ * @event: handle for the event
+ * @name: the name of the common field to return
+ *
+ * Returns a common field from the event by the given @name.
+ * This only searchs the common fields and not all field.
+ */
+struct format_field *
+pevent_find_common_field(struct event_format *event, const char *name)
+{
+ struct format_field *format;
+
+ for (format = event->format.common_fields;
+ format; format = format->next) {
+ if (strcmp(format->name, name) == 0)
+ break;
+ }
+
+ return format;
+}
+
+/**
+ * pevent_find_field - find a non-common field
+ * @event: handle for the event
+ * @name: the name of the non-common field
+ *
+ * Returns a non-common field by the given @name.
+ * This does not search common fields.
+ */
+struct format_field *
+pevent_find_field(struct event_format *event, const char *name)
+{
+ struct format_field *format;
+
+ for (format = event->format.fields;
+ format; format = format->next) {
+ if (strcmp(format->name, name) == 0)
+ break;
+ }
+
+ return format;
+}
+
+/**
+ * pevent_find_any_field - find any field by name
+ * @event: handle for the event
+ * @name: the name of the field
+ *
+ * Returns a field by the given @name.
+ * This searchs the common field names first, then
+ * the non-common ones if a common one was not found.
+ */
+struct format_field *
+pevent_find_any_field(struct event_format *event, const char *name)
+{
+ struct format_field *format;
+
+ format = pevent_find_common_field(event, name);
+ if (format)
+ return format;
+ return pevent_find_field(event, name);
+}
+
+/**
+ * pevent_read_number - read a number from data
+ * @pevent: handle for the pevent
+ * @ptr: the raw data
+ * @size: the size of the data that holds the number
+ *
+ * Returns the number (converted to host) from the
+ * raw data.
+ */
+unsigned long long pevent_read_number(struct pevent *pevent,
+ const void *ptr, int size)
+{
+ switch (size) {
+ case 1:
+ return *(unsigned char *)ptr;
+ case 2:
+ return data2host2(pevent, ptr);
+ case 4:
+ return data2host4(pevent, ptr);
+ case 8:
+ return data2host8(pevent, ptr);
+ default:
+ /* BUG! */
+ return 0;
+ }
+}
+
+/**
+ * pevent_read_number_field - read a number from data
+ * @field: a handle to the field
+ * @data: the raw data to read
+ * @value: the value to place the number in
+ *
+ * Reads raw data according to a field offset and size,
+ * and translates it into @value.
+ *
+ * Returns 0 on success, -1 otherwise.
+ */
+int pevent_read_number_field(struct format_field *field, const void *data,
+ unsigned long long *value)
+{
+ if (!field)
+ return -1;
+ switch (field->size) {
+ case 1:
+ case 2:
+ case 4:
+ case 8:
+ *value = pevent_read_number(field->event->pevent,
+ data + field->offset, field->size);
+ return 0;
+ default:
+ return -1;
+ }
+}
+
+static int get_common_info(struct pevent *pevent,
+ const char *type, int *offset, int *size)
+{
+ struct event_format *event;
+ struct format_field *field;
+
+ /*
+ * All events should have the same common elements.
+ * Pick any event to find where the type is;
+ */
+ if (!pevent->events) {
+ do_warning("no event_list!");
+ return -1;
+ }
+
+ event = pevent->events[0];
+ field = pevent_find_common_field(event, type);
+ if (!field)
+ return -1;
+
+ *offset = field->offset;
+ *size = field->size;
+
+ return 0;
+}
+
+static int __parse_common(struct pevent *pevent, void *data,
+ int *size, int *offset, const char *name)
+{
+ int ret;
+
+ if (!*size) {
+ ret = get_common_info(pevent, name, offset, size);
+ if (ret < 0)
+ return ret;
+ }
+ return pevent_read_number(pevent, data + *offset, *size);
+}
+
+static int trace_parse_common_type(struct pevent *pevent, void *data)
+{
+ return __parse_common(pevent, data,
+ &pevent->type_size, &pevent->type_offset,
+ "common_type");
+}
+
+static int parse_common_pid(struct pevent *pevent, void *data)
+{
+ return __parse_common(pevent, data,
+ &pevent->pid_size, &pevent->pid_offset,
+ "common_pid");
+}
+
+static int parse_common_pc(struct pevent *pevent, void *data)
+{
+ return __parse_common(pevent, data,
+ &pevent->pc_size, &pevent->pc_offset,
+ "common_preempt_count");
+}
+
+static int parse_common_flags(struct pevent *pevent, void *data)
+{
+ return __parse_common(pevent, data,
+ &pevent->flags_size, &pevent->flags_offset,
+ "common_flags");
+}
+
+static int parse_common_lock_depth(struct pevent *pevent, void *data)
+{
+ return __parse_common(pevent, data,
+ &pevent->ld_size, &pevent->ld_offset,
+ "common_lock_depth");
+}
+
+static int parse_common_migrate_disable(struct pevent *pevent, void *data)
+{
+ return __parse_common(pevent, data,
+ &pevent->ld_size, &pevent->ld_offset,
+ "common_migrate_disable");
+}
+
+static int events_id_cmp(const void *a, const void *b);
+
+/**
+ * pevent_find_event - find an event by given id
+ * @pevent: a handle to the pevent
+ * @id: the id of the event
+ *
+ * Returns an event that has a given @id.
+ */
+struct event_format *pevent_find_event(struct pevent *pevent, int id)
+{
+ struct event_format **eventptr;
+ struct event_format key;
+ struct event_format *pkey = &key;
+
+ /* Check cache first */
+ if (pevent->last_event && pevent->last_event->id == id)
+ return pevent->last_event;
+
+ key.id = id;
+
+ eventptr = bsearch(&pkey, pevent->events, pevent->nr_events,
+ sizeof(*pevent->events), events_id_cmp);
+
+ if (eventptr) {
+ pevent->last_event = *eventptr;
+ return *eventptr;
+ }
+
+ return NULL;
+}
+
+/**
+ * pevent_find_event_by_name - find an event by given name
+ * @pevent: a handle to the pevent
+ * @sys: the system name to search for
+ * @name: the name of the event to search for
+ *
+ * This returns an event with a given @name and under the system
+ * @sys. If @sys is NULL the first event with @name is returned.
+ */
+struct event_format *
+pevent_find_event_by_name(struct pevent *pevent,
+ const char *sys, const char *name)
+{
+ struct event_format *event;
+ int i;
+
+ if (pevent->last_event &&
+ strcmp(pevent->last_event->name, name) == 0 &&
+ (!sys || strcmp(pevent->last_event->system, sys) == 0))
+ return pevent->last_event;
+
+ for (i = 0; i < pevent->nr_events; i++) {
+ event = pevent->events[i];
+ if (strcmp(event->name, name) == 0) {
+ if (!sys)
+ break;
+ if (strcmp(event->system, sys) == 0)
+ break;
+ }
+ }
+ if (i == pevent->nr_events)
+ event = NULL;
+
+ pevent->last_event = event;
+ return event;
+}
+
+static unsigned long long
+eval_num_arg(void *data, int size, struct event_format *event, struct print_arg *arg)
+{
+ struct pevent *pevent = event->pevent;
+ unsigned long long val = 0;
+ unsigned long long left, right;
+ struct print_arg *typearg = NULL;
+ struct print_arg *larg;
+ unsigned long offset;
+ unsigned int field_size;
+
+ switch (arg->type) {
+ case PRINT_NULL:
+ /* ?? */
+ return 0;
+ case PRINT_ATOM:
+ return strtoull(arg->atom.atom, NULL, 0);
+ case PRINT_FIELD:
+ if (!arg->field.field) {
+ arg->field.field = pevent_find_any_field(event, arg->field.name);
+ if (!arg->field.field)
+ goto out_warning_field;
+
+ }
+ /* must be a number */
+ val = pevent_read_number(pevent, data + arg->field.field->offset,
+ arg->field.field->size);
+ break;
+ case PRINT_FLAGS:
+ case PRINT_SYMBOL:
+ case PRINT_HEX:
+ break;
+ case PRINT_TYPE:
+ val = eval_num_arg(data, size, event, arg->typecast.item);
+ return eval_type(val, arg, 0);
+ case PRINT_STRING:
+ case PRINT_BSTRING:
+ case PRINT_BITMASK:
+ return 0;
+ case PRINT_FUNC: {
+ struct trace_seq s;
+ trace_seq_init(&s);
+ val = process_defined_func(&s, data, size, event, arg);
+ trace_seq_destroy(&s);
+ return val;
+ }
+ case PRINT_OP:
+ if (strcmp(arg->op.op, "[") == 0) {
+ /*
+ * Arrays are special, since we don't want
+ * to read the arg as is.
+ */
+ right = eval_num_arg(data, size, event, arg->op.right);
+
+ /* handle typecasts */
+ larg = arg->op.left;
+ while (larg->type == PRINT_TYPE) {
+ if (!typearg)
+ typearg = larg;
+ larg = larg->typecast.item;
+ }
+
+ /* Default to long size */
+ field_size = pevent->long_size;
+
+ switch (larg->type) {
+ case PRINT_DYNAMIC_ARRAY:
+ offset = pevent_read_number(pevent,
+ data + larg->dynarray.field->offset,
+ larg->dynarray.field->size);
+ if (larg->dynarray.field->elementsize)
+ field_size = larg->dynarray.field->elementsize;
+ /*
+ * The actual length of the dynamic array is stored
+ * in the top half of the field, and the offset
+ * is in the bottom half of the 32 bit field.
+ */
+ offset &= 0xffff;
+ offset += right;
+ break;
+ case PRINT_FIELD:
+ if (!larg->field.field) {
+ larg->field.field =
+ pevent_find_any_field(event, larg->field.name);
+ if (!larg->field.field) {
+ arg = larg;
+ goto out_warning_field;
+ }
+ }
+ field_size = larg->field.field->elementsize;
+ offset = larg->field.field->offset +
+ right * larg->field.field->elementsize;
+ break;
+ default:
+ goto default_op; /* oops, all bets off */
+ }
+ val = pevent_read_number(pevent,
+ data + offset, field_size);
+ if (typearg)
+ val = eval_type(val, typearg, 1);
+ break;
+ } else if (strcmp(arg->op.op, "?") == 0) {
+ left = eval_num_arg(data, size, event, arg->op.left);
+ arg = arg->op.right;
+ if (left)
+ val = eval_num_arg(data, size, event, arg->op.left);
+ else
+ val = eval_num_arg(data, size, event, arg->op.right);
+ break;
+ }
+ default_op:
+ left = eval_num_arg(data, size, event, arg->op.left);
+ right = eval_num_arg(data, size, event, arg->op.right);
+ switch (arg->op.op[0]) {
+ case '!':
+ switch (arg->op.op[1]) {
+ case 0:
+ val = !right;
+ break;
+ case '=':
+ val = left != right;
+ break;
+ default:
+ goto out_warning_op;
+ }
+ break;
+ case '~':
+ val = ~right;
+ break;
+ case '|':
+ if (arg->op.op[1])
+ val = left || right;
+ else
+ val = left | right;
+ break;
+ case '&':
+ if (arg->op.op[1])
+ val = left && right;
+ else
+ val = left & right;
+ break;
+ case '<':
+ switch (arg->op.op[1]) {
+ case 0:
+ val = left < right;
+ break;
+ case '<':
+ val = left << right;
+ break;
+ case '=':
+ val = left <= right;
+ break;
+ default:
+ goto out_warning_op;
+ }
+ break;
+ case '>':
+ switch (arg->op.op[1]) {
+ case 0:
+ val = left > right;
+ break;
+ case '>':
+ val = left >> right;
+ break;
+ case '=':
+ val = left >= right;
+ break;
+ default:
+ goto out_warning_op;
+ }
+ break;
+ case '=':
+ if (arg->op.op[1] != '=')
+ goto out_warning_op;
+
+ val = left == right;
+ break;
+ case '-':
+ val = left - right;
+ break;
+ case '+':
+ val = left + right;
+ break;
+ case '/':
+ val = left / right;
+ break;
+ case '*':
+ val = left * right;
+ break;
+ default:
+ goto out_warning_op;
+ }
+ break;
+ case PRINT_DYNAMIC_ARRAY:
+ /* Without [], we pass the address to the dynamic data */
+ offset = pevent_read_number(pevent,
+ data + arg->dynarray.field->offset,
+ arg->dynarray.field->size);
+ /*
+ * The actual length of the dynamic array is stored
+ * in the top half of the field, and the offset
+ * is in the bottom half of the 32 bit field.
+ */
+ offset &= 0xffff;
+ val = (unsigned long long)((unsigned long)data + offset);
+ break;
+ default: /* not sure what to do there */
+ return 0;
+ }
+ return val;
+
+out_warning_op:
+ do_warning_event(event, "%s: unknown op '%s'", __func__, arg->op.op);
+ return 0;
+
+out_warning_field:
+ do_warning_event(event, "%s: field %s not found",
+ __func__, arg->field.name);
+ return 0;
+}
+
+struct flag {
+ const char *name;
+ unsigned long long value;
+};
+
+static const struct flag flags[] = {
+ { "HI_SOFTIRQ", 0 },
+ { "TIMER_SOFTIRQ", 1 },
+ { "NET_TX_SOFTIRQ", 2 },
+ { "NET_RX_SOFTIRQ", 3 },
+ { "BLOCK_SOFTIRQ", 4 },
+ { "BLOCK_IOPOLL_SOFTIRQ", 5 },
+ { "TASKLET_SOFTIRQ", 6 },
+ { "SCHED_SOFTIRQ", 7 },
+ { "HRTIMER_SOFTIRQ", 8 },
+ { "RCU_SOFTIRQ", 9 },
+
+ { "HRTIMER_NORESTART", 0 },
+ { "HRTIMER_RESTART", 1 },
+};
+
+static unsigned long long eval_flag(const char *flag)
+{
+ int i;
+
+ /*
+ * Some flags in the format files do not get converted.
+ * If the flag is not numeric, see if it is something that
+ * we already know about.
+ */
+ if (isdigit(flag[0]))
+ return strtoull(flag, NULL, 0);
+
+ for (i = 0; i < (int)(sizeof(flags)/sizeof(flags[0])); i++)
+ if (strcmp(flags[i].name, flag) == 0)
+ return flags[i].value;
+
+ return 0;
+}
+
+static void print_str_to_seq(struct trace_seq *s, const char *format,
+ int len_arg, const char *str)
+{
+ if (len_arg >= 0)
+ trace_seq_printf(s, format, len_arg, str);
+ else
+ trace_seq_printf(s, format, str);
+}
+
+static void print_bitmask_to_seq(struct pevent *pevent,
+ struct trace_seq *s, const char *format,
+ int len_arg, const void *data, int size)
+{
+ int nr_bits = size * 8;
+ int str_size = (nr_bits + 3) / 4;
+ int len = 0;
+ char buf[3];
+ char *str;
+ int index;
+ int i;
+
+ /*
+ * The kernel likes to put in commas every 32 bits, we
+ * can do the same.
+ */
+ str_size += (nr_bits - 1) / 32;
+
+ str = malloc(str_size + 1);
+ if (!str) {
+ do_warning("%s: not enough memory!", __func__);
+ return;
+ }
+ str[str_size] = 0;
+
+ /* Start out with -2 for the two chars per byte */
+ for (i = str_size - 2; i >= 0; i -= 2) {
+ /*
+ * data points to a bit mask of size bytes.
+ * In the kernel, this is an array of long words, thus
+ * endianess is very important.
+ */
+ if (pevent->file_bigendian)
+ index = size - (len + 1);
+ else
+ index = len;
+
+ snprintf(buf, 3, "%02x", *((unsigned char *)data + index));
+ memcpy(str + i, buf, 2);
+ len++;
+ if (!(len & 3) && i > 0) {
+ i--;
+ str[i] = ',';
+ }
+ }
+
+ if (len_arg >= 0)
+ trace_seq_printf(s, format, len_arg, str);
+ else
+ trace_seq_printf(s, format, str);
+
+ free(str);
+}
+
+static void print_str_arg(struct trace_seq *s, void *data, int size,
+ struct event_format *event, const char *format,
+ int len_arg, struct print_arg *arg)
+{
+ struct pevent *pevent = event->pevent;
+ struct print_flag_sym *flag;
+ struct format_field *field;
+ struct printk_map *printk;
+ unsigned long long val, fval;
+ unsigned long addr;
+ char *str;
+ unsigned char *hex;
+ int print;
+ int i, len;
+
+ switch (arg->type) {
+ case PRINT_NULL:
+ /* ?? */
+ return;
+ case PRINT_ATOM:
+ print_str_to_seq(s, format, len_arg, arg->atom.atom);
+ return;
+ case PRINT_FIELD:
+ field = arg->field.field;
+ if (!field) {
+ field = pevent_find_any_field(event, arg->field.name);
+ if (!field) {
+ str = arg->field.name;
+ goto out_warning_field;
+ }
+ arg->field.field = field;
+ }
+ /* Zero sized fields, mean the rest of the data */
+ len = field->size ? : size - field->offset;
+
+ /*
+ * Some events pass in pointers. If this is not an array
+ * and the size is the same as long_size, assume that it
+ * is a pointer.
+ */
+ if (!(field->flags & FIELD_IS_ARRAY) &&
+ field->size == pevent->long_size) {
+ addr = *(unsigned long *)(data + field->offset);
+ /* Check if it matches a print format */
+ printk = find_printk(pevent, addr);
+ if (printk)
+ trace_seq_puts(s, printk->printk);
+ else
+ trace_seq_printf(s, "%lx", addr);
+ break;
+ }
+ str = malloc(len + 1);
+ if (!str) {
+ do_warning_event(event, "%s: not enough memory!",
+ __func__);
+ return;
+ }
+ memcpy(str, data + field->offset, len);
+ str[len] = 0;
+ print_str_to_seq(s, format, len_arg, str);
+ free(str);
+ break;
+ case PRINT_FLAGS:
+ val = eval_num_arg(data, size, event, arg->flags.field);
+ print = 0;
+ for (flag = arg->flags.flags; flag; flag = flag->next) {
+ fval = eval_flag(flag->value);
+ if (!val && !fval) {
+ print_str_to_seq(s, format, len_arg, flag->str);
+ break;
+ }
+ if (fval && (val & fval) == fval) {
+ if (print && arg->flags.delim)
+ trace_seq_puts(s, arg->flags.delim);
+ print_str_to_seq(s, format, len_arg, flag->str);
+ print = 1;
+ val &= ~fval;
+ }
+ }
+ break;
+ case PRINT_SYMBOL:
+ val = eval_num_arg(data, size, event, arg->symbol.field);
+ for (flag = arg->symbol.symbols; flag; flag = flag->next) {
+ fval = eval_flag(flag->value);
+ if (val == fval) {
+ print_str_to_seq(s, format, len_arg, flag->str);
+ break;
+ }
+ }
+ break;
+ case PRINT_HEX:
+ if (arg->hex.field->type == PRINT_DYNAMIC_ARRAY) {
+ unsigned long offset;
+ offset = pevent_read_number(pevent,
+ data + arg->hex.field->dynarray.field->offset,
+ arg->hex.field->dynarray.field->size);
+ hex = data + (offset & 0xffff);
+ } else {
+ field = arg->hex.field->field.field;
+ if (!field) {
+ str = arg->hex.field->field.name;
+ field = pevent_find_any_field(event, str);
+ if (!field)
+ goto out_warning_field;
+ arg->hex.field->field.field = field;
+ }
+ hex = data + field->offset;
+ }
+ len = eval_num_arg(data, size, event, arg->hex.size);
+ for (i = 0; i < len; i++) {
+ if (i)
+ trace_seq_putc(s, ' ');
+ trace_seq_printf(s, "%02x", hex[i]);
+ }
+ break;
+
+ case PRINT_TYPE:
+ break;
+ case PRINT_STRING: {
+ int str_offset;
+
+ if (arg->string.offset == -1) {
+ struct format_field *f;
+
+ f = pevent_find_any_field(event, arg->string.string);
+ arg->string.offset = f->offset;
+ }
+ str_offset = data2host4(pevent, data + arg->string.offset);
+ str_offset &= 0xffff;
+ print_str_to_seq(s, format, len_arg, ((char *)data) + str_offset);
+ break;
+ }
+ case PRINT_BSTRING:
+ print_str_to_seq(s, format, len_arg, arg->string.string);
+ break;
+ case PRINT_BITMASK: {
+ int bitmask_offset;
+ int bitmask_size;
+
+ if (arg->bitmask.offset == -1) {
+ struct format_field *f;
+
+ f = pevent_find_any_field(event, arg->bitmask.bitmask);
+ arg->bitmask.offset = f->offset;
+ }
+ bitmask_offset = data2host4(pevent, data + arg->bitmask.offset);
+ bitmask_size = bitmask_offset >> 16;
+ bitmask_offset &= 0xffff;
+ print_bitmask_to_seq(pevent, s, format, len_arg,
+ data + bitmask_offset, bitmask_size);
+ break;
+ }
+ case PRINT_OP:
+ /*
+ * The only op for string should be ? :
+ */
+ if (arg->op.op[0] != '?')
+ return;
+ val = eval_num_arg(data, size, event, arg->op.left);
+ if (val)
+ print_str_arg(s, data, size, event,
+ format, len_arg, arg->op.right->op.left);
+ else
+ print_str_arg(s, data, size, event,
+ format, len_arg, arg->op.right->op.right);
+ break;
+ case PRINT_FUNC:
+ process_defined_func(s, data, size, event, arg);
+ break;
+ default:
+ /* well... */
+ break;
+ }
+
+ return;
+
+out_warning_field:
+ do_warning_event(event, "%s: field %s not found",
+ __func__, arg->field.name);
+}
+
+static unsigned long long
+process_defined_func(struct trace_seq *s, void *data, int size,
+ struct event_format *event, struct print_arg *arg)
+{
+ struct pevent_function_handler *func_handle = arg->func.func;
+ struct pevent_func_params *param;
+ unsigned long long *args;
+ unsigned long long ret;
+ struct print_arg *farg;
+ struct trace_seq str;
+ struct save_str {
+ struct save_str *next;
+ char *str;
+ } *strings = NULL, *string;
+ int i;
+
+ if (!func_handle->nr_args) {
+ ret = (*func_handle->func)(s, NULL);
+ goto out;
+ }
+
+ farg = arg->func.args;
+ param = func_handle->params;
+
+ ret = ULLONG_MAX;
+ args = malloc(sizeof(*args) * func_handle->nr_args);
+ if (!args)
+ goto out;
+
+ for (i = 0; i < func_handle->nr_args; i++) {
+ switch (param->type) {
+ case PEVENT_FUNC_ARG_INT:
+ case PEVENT_FUNC_ARG_LONG:
+ case PEVENT_FUNC_ARG_PTR:
+ args[i] = eval_num_arg(data, size, event, farg);
+ break;
+ case PEVENT_FUNC_ARG_STRING:
+ trace_seq_init(&str);
+ print_str_arg(&str, data, size, event, "%s", -1, farg);
+ trace_seq_terminate(&str);
+ string = malloc(sizeof(*string));
+ if (!string) {
+ do_warning_event(event, "%s(%d): malloc str",
+ __func__, __LINE__);
+ goto out_free;
+ }
+ string->next = strings;
+ string->str = strdup(str.buffer);
+ if (!string->str) {
+ free(string);
+ do_warning_event(event, "%s(%d): malloc str",
+ __func__, __LINE__);
+ goto out_free;
+ }
+ args[i] = (uintptr_t)string->str;
+ strings = string;
+ trace_seq_destroy(&str);
+ break;
+ default:
+ /*
+ * Something went totally wrong, this is not
+ * an input error, something in this code broke.
+ */
+ do_warning_event(event, "Unexpected end of arguments\n");
+ goto out_free;
+ }
+ farg = farg->next;
+ param = param->next;
+ }
+
+ ret = (*func_handle->func)(s, args);
+out_free:
+ free(args);
+ while (strings) {
+ string = strings;
+ strings = string->next;
+ free(string->str);
+ free(string);
+ }
+
+ out:
+ /* TBD : handle return type here */
+ return ret;
+}
+
+static void free_args(struct print_arg *args)
+{
+ struct print_arg *next;
+
+ while (args) {
+ next = args->next;
+
+ free_arg(args);
+ args = next;
+ }
+}
+
+static struct print_arg *make_bprint_args(char *fmt, void *data, int size, struct event_format *event)
+{
+ struct pevent *pevent = event->pevent;
+ struct format_field *field, *ip_field;
+ struct print_arg *args, *arg, **next;
+ unsigned long long ip, val;
+ char *ptr;
+ void *bptr;
+ int vsize;
+
+ field = pevent->bprint_buf_field;
+ ip_field = pevent->bprint_ip_field;
+
+ if (!field) {
+ field = pevent_find_field(event, "buf");
+ if (!field) {
+ do_warning_event(event, "can't find buffer field for binary printk");
+ return NULL;
+ }
+ ip_field = pevent_find_field(event, "ip");
+ if (!ip_field) {
+ do_warning_event(event, "can't find ip field for binary printk");
+ return NULL;
+ }
+ pevent->bprint_buf_field = field;
+ pevent->bprint_ip_field = ip_field;
+ }
+
+ ip = pevent_read_number(pevent, data + ip_field->offset, ip_field->size);
+
+ /*
+ * The first arg is the IP pointer.
+ */
+ args = alloc_arg();
+ if (!args) {
+ do_warning_event(event, "%s(%d): not enough memory!",
+ __func__, __LINE__);
+ return NULL;
+ }
+ arg = args;
+ arg->next = NULL;
+ next = &arg->next;
+
+ arg->type = PRINT_ATOM;
+
+ if (asprintf(&arg->atom.atom, "%lld", ip) < 0)
+ goto out_free;
+
+ /* skip the first "%pf: " */
+ for (ptr = fmt + 5, bptr = data + field->offset;
+ bptr < data + size && *ptr; ptr++) {
+ int ls = 0;
+
+ if (*ptr == '%') {
+ process_again:
+ ptr++;
+ switch (*ptr) {
+ case '%':
+ break;
+ case 'l':
+ ls++;
+ goto process_again;
+ case 'L':
+ ls = 2;
+ goto process_again;
+ case '0' ... '9':
+ goto process_again;
+ case '.':
+ goto process_again;
+ case 'p':
+ ls = 1;
+ /* fall through */
+ case 'd':
+ case 'u':
+ case 'x':
+ case 'i':
+ switch (ls) {
+ case 0:
+ vsize = 4;
+ break;
+ case 1:
+ vsize = pevent->long_size;
+ break;
+ case 2:
+ vsize = 8;
+ break;
+ default:
+ vsize = ls; /* ? */
+ break;
+ }
+ /* fall through */
+ case '*':
+ if (*ptr == '*')
+ vsize = 4;
+
+ /* the pointers are always 4 bytes aligned */
+ bptr = (void *)(((unsigned long)bptr + 3) &
+ ~3);
+ val = pevent_read_number(pevent, bptr, vsize);
+ bptr += vsize;
+ arg = alloc_arg();
+ if (!arg) {
+ do_warning_event(event, "%s(%d): not enough memory!",
+ __func__, __LINE__);
+ goto out_free;
+ }
+ arg->next = NULL;
+ arg->type = PRINT_ATOM;
+ if (asprintf(&arg->atom.atom, "%lld", val) < 0) {
+ free(arg);
+ goto out_free;
+ }
+ *next = arg;
+ next = &arg->next;
+ /*
+ * The '*' case means that an arg is used as the length.
+ * We need to continue to figure out for what.
+ */
+ if (*ptr == '*')
+ goto process_again;
+
+ break;
+ case 's':
+ arg = alloc_arg();
+ if (!arg) {
+ do_warning_event(event, "%s(%d): not enough memory!",
+ __func__, __LINE__);
+ goto out_free;
+ }
+ arg->next = NULL;
+ arg->type = PRINT_BSTRING;
+ arg->string.string = strdup(bptr);
+ if (!arg->string.string)
+ goto out_free;
+ bptr += strlen(bptr) + 1;
+ *next = arg;
+ next = &arg->next;
+ default:
+ break;
+ }
+ }
+ }
+
+ return args;
+
+out_free:
+ free_args(args);
+ return NULL;
+}
+
+static char *
+get_bprint_format(void *data, int size __maybe_unused,
+ struct event_format *event)
+{
+ struct pevent *pevent = event->pevent;
+ unsigned long long addr;
+ struct format_field *field;
+ struct printk_map *printk;
+ char *format;
+
+ field = pevent->bprint_fmt_field;
+
+ if (!field) {
+ field = pevent_find_field(event, "fmt");
+ if (!field) {
+ do_warning_event(event, "can't find format field for binary printk");
+ return NULL;
+ }
+ pevent->bprint_fmt_field = field;
+ }
+
+ addr = pevent_read_number(pevent, data + field->offset, field->size);
+
+ printk = find_printk(pevent, addr);
+ if (!printk) {
+ if (asprintf(&format, "%%pf: (NO FORMAT FOUND at %llx)\n", addr) < 0)
+ return NULL;
+ return format;
+ }
+
+ if (asprintf(&format, "%s: %s", "%pf", printk->printk) < 0)
+ return NULL;
+
+ return format;
+}
+
+static void print_mac_arg(struct trace_seq *s, int mac, void *data, int size,
+ struct event_format *event, struct print_arg *arg)
+{
+ unsigned char *buf;
+ const char *fmt = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x";
+
+ if (arg->type == PRINT_FUNC) {
+ process_defined_func(s, data, size, event, arg);
+ return;
+ }
+
+ if (arg->type != PRINT_FIELD) {
+ trace_seq_printf(s, "ARG TYPE NOT FIELD BUT %d",
+ arg->type);
+ return;
+ }
+
+ if (mac == 'm')
+ fmt = "%.2x%.2x%.2x%.2x%.2x%.2x";
+ if (!arg->field.field) {
+ arg->field.field =
+ pevent_find_any_field(event, arg->field.name);
+ if (!arg->field.field) {
+ do_warning_event(event, "%s: field %s not found",
+ __func__, arg->field.name);
+ return;
+ }
+ }
+ if (arg->field.field->size != 6) {
+ trace_seq_printf(s, "INVALIDMAC");
+ return;
+ }
+ buf = data + arg->field.field->offset;
+ trace_seq_printf(s, fmt, buf[0], buf[1], buf[2], buf[3], buf[4], buf[5]);
+}
+
+static int is_printable_array(char *p, unsigned int len)
+{
+ unsigned int i;
+
+ for (i = 0; i < len && p[i]; i++)
+ if (!isprint(p[i]) && !isspace(p[i]))
+ return 0;
+ return 1;
+}
+
+static void print_event_fields(struct trace_seq *s, void *data,
+ int size __maybe_unused,
+ struct event_format *event)
+{
+ struct format_field *field;
+ unsigned long long val;
+ unsigned int offset, len, i;
+
+ field = event->format.fields;
+ while (field) {
+ trace_seq_printf(s, " %s=", field->name);
+ if (field->flags & FIELD_IS_ARRAY) {
+ offset = field->offset;
+ len = field->size;
+ if (field->flags & FIELD_IS_DYNAMIC) {
+ val = pevent_read_number(event->pevent, data + offset, len);
+ offset = val;
+ len = offset >> 16;
+ offset &= 0xffff;
+ }
+ if (field->flags & FIELD_IS_STRING &&
+ is_printable_array(data + offset, len)) {
+ trace_seq_printf(s, "%s", (char *)data + offset);
+ } else {
+ trace_seq_puts(s, "ARRAY[");
+ for (i = 0; i < len; i++) {
+ if (i)
+ trace_seq_puts(s, ", ");
+ trace_seq_printf(s, "%02x",
+ *((unsigned char *)data + offset + i));
+ }
+ trace_seq_putc(s, ']');
+ field->flags &= ~FIELD_IS_STRING;
+ }
+ } else {
+ val = pevent_read_number(event->pevent, data + field->offset,
+ field->size);
+ if (field->flags & FIELD_IS_POINTER) {
+ trace_seq_printf(s, "0x%llx", val);
+ } else if (field->flags & FIELD_IS_SIGNED) {
+ switch (field->size) {
+ case 4:
+ /*
+ * If field is long then print it in hex.
+ * A long usually stores pointers.
+ */
+ if (field->flags & FIELD_IS_LONG)
+ trace_seq_printf(s, "0x%x", (int)val);
+ else
+ trace_seq_printf(s, "%d", (int)val);
+ break;
+ case 2:
+ trace_seq_printf(s, "%2d", (short)val);
+ break;
+ case 1:
+ trace_seq_printf(s, "%1d", (char)val);
+ break;
+ default:
+ trace_seq_printf(s, "%lld", val);
+ }
+ } else {
+ if (field->flags & FIELD_IS_LONG)
+ trace_seq_printf(s, "0x%llx", val);
+ else
+ trace_seq_printf(s, "%llu", val);
+ }
+ }
+ field = field->next;
+ }
+}
+
+static void pretty_print(struct trace_seq *s, void *data, int size, struct event_format *event)
+{
+ struct pevent *pevent = event->pevent;
+ struct print_fmt *print_fmt = &event->print_fmt;
+ struct print_arg *arg = print_fmt->args;
+ struct print_arg *args = NULL;
+ const char *ptr = print_fmt->format;
+ unsigned long long val;
+ struct func_map *func;
+ const char *saveptr;
+ struct trace_seq p;
+ char *bprint_fmt = NULL;
+ char format[32];
+ int show_func;
+ int len_as_arg;
+ int len_arg;
+ int len;
+ int ls;
+
+ if (event->flags & EVENT_FL_FAILED) {
+ trace_seq_printf(s, "[FAILED TO PARSE]");
+ print_event_fields(s, data, size, event);
+ return;
+ }
+
+ if (event->flags & EVENT_FL_ISBPRINT) {
+ bprint_fmt = get_bprint_format(data, size, event);
+ args = make_bprint_args(bprint_fmt, data, size, event);
+ arg = args;
+ ptr = bprint_fmt;
+ }
+
+ for (; *ptr; ptr++) {
+ ls = 0;
+ if (*ptr == '\\') {
+ ptr++;
+ switch (*ptr) {
+ case 'n':
+ trace_seq_putc(s, '\n');
+ break;
+ case 't':
+ trace_seq_putc(s, '\t');
+ break;
+ case 'r':
+ trace_seq_putc(s, '\r');
+ break;
+ case '\\':
+ trace_seq_putc(s, '\\');
+ break;
+ default:
+ trace_seq_putc(s, *ptr);
+ break;
+ }
+
+ } else if (*ptr == '%') {
+ saveptr = ptr;
+ show_func = 0;
+ len_as_arg = 0;
+ cont_process:
+ ptr++;
+ switch (*ptr) {
+ case '%':
+ trace_seq_putc(s, '%');
+ break;
+ case '#':
+ /* FIXME: need to handle properly */
+ goto cont_process;
+ case 'h':
+ ls--;
+ goto cont_process;
+ case 'l':
+ ls++;
+ goto cont_process;
+ case 'L':
+ ls = 2;
+ goto cont_process;
+ case '*':
+ /* The argument is the length. */
+ if (!arg) {
+ do_warning_event(event, "no argument match");
+ event->flags |= EVENT_FL_FAILED;
+ goto out_failed;
+ }
+ len_arg = eval_num_arg(data, size, event, arg);
+ len_as_arg = 1;
+ arg = arg->next;
+ goto cont_process;
+ case '.':
+ case 'z':
+ case 'Z':
+ case '0' ... '9':
+ goto cont_process;
+ case 'p':
+ if (pevent->long_size == 4)
+ ls = 1;
+ else
+ ls = 2;
+
+ if (*(ptr+1) == 'F' ||
+ *(ptr+1) == 'f') {
+ ptr++;
+ show_func = *ptr;
+ } else if (*(ptr+1) == 'M' || *(ptr+1) == 'm') {
+ print_mac_arg(s, *(ptr+1), data, size, event, arg);
+ ptr++;
+ arg = arg->next;
+ break;
+ }
+
+ /* fall through */
+ case 'd':
+ case 'i':
+ case 'x':
+ case 'X':
+ case 'u':
+ if (!arg) {
+ do_warning_event(event, "no argument match");
+ event->flags |= EVENT_FL_FAILED;
+ goto out_failed;
+ }
+
+ len = ((unsigned long)ptr + 1) -
+ (unsigned long)saveptr;
+
+ /* should never happen */
+ if (len > 31) {
+ do_warning_event(event, "bad format!");
+ event->flags |= EVENT_FL_FAILED;
+ len = 31;
+ }
+
+ memcpy(format, saveptr, len);
+ format[len] = 0;
+
+ val = eval_num_arg(data, size, event, arg);
+ arg = arg->next;
+
+ if (show_func) {
+ func = find_func(pevent, val);
+ if (func) {
+ trace_seq_puts(s, func->func);
+ if (show_func == 'F')
+ trace_seq_printf(s,
+ "+0x%llx",
+ val - func->addr);
+ break;
+ }
+ }
+ if (pevent->long_size == 8 && ls &&
+ sizeof(long) != 8) {
+ char *p;
+
+ ls = 2;
+ /* make %l into %ll */
+ p = strchr(format, 'l');
+ if (p)
+ memmove(p+1, p, strlen(p)+1);
+ else if (strcmp(format, "%p") == 0)
+ strcpy(format, "0x%llx");
+ }
+ switch (ls) {
+ case -2:
+ if (len_as_arg)
+ trace_seq_printf(s, format, len_arg, (char)val);
+ else
+ trace_seq_printf(s, format, (char)val);
+ break;
+ case -1:
+ if (len_as_arg)
+ trace_seq_printf(s, format, len_arg, (short)val);
+ else
+ trace_seq_printf(s, format, (short)val);
+ break;
+ case 0:
+ if (len_as_arg)
+ trace_seq_printf(s, format, len_arg, (int)val);
+ else
+ trace_seq_printf(s, format, (int)val);
+ break;
+ case 1:
+ if (len_as_arg)
+ trace_seq_printf(s, format, len_arg, (long)val);
+ else
+ trace_seq_printf(s, format, (long)val);
+ break;
+ case 2:
+ if (len_as_arg)
+ trace_seq_printf(s, format, len_arg,
+ (long long)val);
+ else
+ trace_seq_printf(s, format, (long long)val);
+ break;
+ default:
+ do_warning_event(event, "bad count (%d)", ls);
+ event->flags |= EVENT_FL_FAILED;
+ }
+ break;
+ case 's':
+ if (!arg) {
+ do_warning_event(event, "no matching argument");
+ event->flags |= EVENT_FL_FAILED;
+ goto out_failed;
+ }
+
+ len = ((unsigned long)ptr + 1) -
+ (unsigned long)saveptr;
+
+ /* should never happen */
+ if (len > 31) {
+ do_warning_event(event, "bad format!");
+ event->flags |= EVENT_FL_FAILED;
+ len = 31;
+ }
+
+ memcpy(format, saveptr, len);
+ format[len] = 0;
+ if (!len_as_arg)
+ len_arg = -1;
+ /* Use helper trace_seq */
+ trace_seq_init(&p);
+ print_str_arg(&p, data, size, event,
+ format, len_arg, arg);
+ trace_seq_terminate(&p);
+ trace_seq_puts(s, p.buffer);
+ trace_seq_destroy(&p);
+ arg = arg->next;
+ break;
+ default:
+ trace_seq_printf(s, ">%c<", *ptr);
+
+ }
+ } else
+ trace_seq_putc(s, *ptr);
+ }
+
+ if (event->flags & EVENT_FL_FAILED) {
+out_failed:
+ trace_seq_printf(s, "[FAILED TO PARSE]");
+ }
+
+ if (args) {
+ free_args(args);
+ free(bprint_fmt);
+ }
+}
+
+/**
+ * pevent_data_lat_fmt - parse the data for the latency format
+ * @pevent: a handle to the pevent
+ * @s: the trace_seq to write to
+ * @record: the record to read from
+ *
+ * This parses out the Latency format (interrupts disabled,
+ * need rescheduling, in hard/soft interrupt, preempt count
+ * and lock depth) and places it into the trace_seq.
+ */
+void pevent_data_lat_fmt(struct pevent *pevent,
+ struct trace_seq *s, struct pevent_record *record)
+{
+ static int check_lock_depth = 1;
+ static int check_migrate_disable = 1;
+ static int lock_depth_exists;
+ static int migrate_disable_exists;
+ unsigned int lat_flags;
+ unsigned int pc;
+ int lock_depth;
+ int migrate_disable;
+ int hardirq;
+ int softirq;
+ void *data = record->data;
+
+ lat_flags = parse_common_flags(pevent, data);
+ pc = parse_common_pc(pevent, data);
+ /* lock_depth may not always exist */
+ if (lock_depth_exists)
+ lock_depth = parse_common_lock_depth(pevent, data);
+ else if (check_lock_depth) {
+ lock_depth = parse_common_lock_depth(pevent, data);
+ if (lock_depth < 0)
+ check_lock_depth = 0;
+ else
+ lock_depth_exists = 1;
+ }
+
+ /* migrate_disable may not always exist */
+ if (migrate_disable_exists)
+ migrate_disable = parse_common_migrate_disable(pevent, data);
+ else if (check_migrate_disable) {
+ migrate_disable = parse_common_migrate_disable(pevent, data);
+ if (migrate_disable < 0)
+ check_migrate_disable = 0;
+ else
+ migrate_disable_exists = 1;
+ }
+
+ hardirq = lat_flags & TRACE_FLAG_HARDIRQ;
+ softirq = lat_flags & TRACE_FLAG_SOFTIRQ;
+
+ trace_seq_printf(s, "%c%c%c",
+ (lat_flags & TRACE_FLAG_IRQS_OFF) ? 'd' :
+ (lat_flags & TRACE_FLAG_IRQS_NOSUPPORT) ?
+ 'X' : '.',
+ (lat_flags & TRACE_FLAG_NEED_RESCHED) ?
+ 'N' : '.',
+ (hardirq && softirq) ? 'H' :
+ hardirq ? 'h' : softirq ? 's' : '.');
+
+ if (pc)
+ trace_seq_printf(s, "%x", pc);
+ else
+ trace_seq_putc(s, '.');
+
+ if (migrate_disable_exists) {
+ if (migrate_disable < 0)
+ trace_seq_putc(s, '.');
+ else
+ trace_seq_printf(s, "%d", migrate_disable);
+ }
+
+ if (lock_depth_exists) {
+ if (lock_depth < 0)
+ trace_seq_putc(s, '.');
+ else
+ trace_seq_printf(s, "%d", lock_depth);
+ }
+
+ trace_seq_terminate(s);
+}
+
+/**
+ * pevent_data_type - parse out the given event type
+ * @pevent: a handle to the pevent
+ * @rec: the record to read from
+ *
+ * This returns the event id from the @rec.
+ */
+int pevent_data_type(struct pevent *pevent, struct pevent_record *rec)
+{
+ return trace_parse_common_type(pevent, rec->data);
+}
+
+/**
+ * pevent_data_event_from_type - find the event by a given type
+ * @pevent: a handle to the pevent
+ * @type: the type of the event.
+ *
+ * This returns the event form a given @type;
+ */
+struct event_format *pevent_data_event_from_type(struct pevent *pevent, int type)
+{
+ return pevent_find_event(pevent, type);
+}
+
+/**
+ * pevent_data_pid - parse the PID from raw data
+ * @pevent: a handle to the pevent
+ * @rec: the record to parse
+ *
+ * This returns the PID from a raw data.
+ */
+int pevent_data_pid(struct pevent *pevent, struct pevent_record *rec)
+{
+ return parse_common_pid(pevent, rec->data);
+}
+
+/**
+ * pevent_data_comm_from_pid - return the command line from PID
+ * @pevent: a handle to the pevent
+ * @pid: the PID of the task to search for
+ *
+ * This returns a pointer to the command line that has the given
+ * @pid.
+ */
+const char *pevent_data_comm_from_pid(struct pevent *pevent, int pid)
+{
+ const char *comm;
+
+ comm = find_cmdline(pevent, pid);
+ return comm;
+}
+
+/**
+ * pevent_data_comm_from_pid - parse the data into the print format
+ * @s: the trace_seq to write to
+ * @event: the handle to the event
+ * @record: the record to read from
+ *
+ * This parses the raw @data using the given @event information and
+ * writes the print format into the trace_seq.
+ */
+void pevent_event_info(struct trace_seq *s, struct event_format *event,
+ struct pevent_record *record)
+{
+ int print_pretty = 1;
+
+ if (event->pevent->print_raw || (event->flags & EVENT_FL_PRINTRAW))
+ print_event_fields(s, record->data, record->size, event);
+ else {
+
+ if (event->handler && !(event->flags & EVENT_FL_NOHANDLE))
+ print_pretty = event->handler(s, record, event,
+ event->context);
+
+ if (print_pretty)
+ pretty_print(s, record->data, record->size, event);
+ }
+
+ trace_seq_terminate(s);
+}
+
+static bool is_timestamp_in_us(char *trace_clock, bool use_trace_clock)
+{
+ if (!use_trace_clock)
+ return true;
+
+ if (!strcmp(trace_clock, "local") || !strcmp(trace_clock, "global")
+ || !strcmp(trace_clock, "uptime") || !strcmp(trace_clock, "perf"))
+ return true;
+
+ /* trace_clock is setting in tsc or counter mode */
+ return false;
+}
+
+void pevent_print_event(struct pevent *pevent, struct trace_seq *s,
+ struct pevent_record *record, bool use_trace_clock)
+{
+ static const char *spaces = " "; /* 20 spaces */
+ struct event_format *event;
+ unsigned long secs;
+ unsigned long usecs;
+ unsigned long nsecs;
+ const char *comm;
+ void *data = record->data;
+ int type;
+ int pid;
+ int len;
+ int p;
+ bool use_usec_format;
+
+ use_usec_format = is_timestamp_in_us(pevent->trace_clock,
+ use_trace_clock);
+ if (use_usec_format) {
+ secs = record->ts / NSECS_PER_SEC;
+ nsecs = record->ts - secs * NSECS_PER_SEC;
+ }
+
+ if (record->size < 0) {
+ do_warning("ug! negative record size %d", record->size);
+ return;
+ }
+
+ type = trace_parse_common_type(pevent, data);
+
+ event = pevent_find_event(pevent, type);
+ if (!event) {
+ do_warning("ug! no event found for type %d", type);
+ return;
+ }
+
+ pid = parse_common_pid(pevent, data);
+ comm = find_cmdline(pevent, pid);
+
+ if (pevent->latency_format) {
+ trace_seq_printf(s, "%8.8s-%-5d %3d",
+ comm, pid, record->cpu);
+ pevent_data_lat_fmt(pevent, s, record);
+ } else
+ trace_seq_printf(s, "%16s-%-5d [%03d]", comm, pid, record->cpu);
+
+ if (use_usec_format) {
+ if (pevent->flags & PEVENT_NSEC_OUTPUT) {
+ usecs = nsecs;
+ p = 9;
+ } else {
+ usecs = (nsecs + 500) / NSECS_PER_USEC;
+ p = 6;
+ }
+
+ trace_seq_printf(s, " %5lu.%0*lu: %s: ",
+ secs, p, usecs, event->name);
+ } else
+ trace_seq_printf(s, " %12llu: %s: ",
+ record->ts, event->name);
+
+ /* Space out the event names evenly. */
+ len = strlen(event->name);
+ if (len < 20)
+ trace_seq_printf(s, "%.*s", 20 - len, spaces);
+
+ pevent_event_info(s, event, record);
+}
+
+static int events_id_cmp(const void *a, const void *b)
+{
+ struct event_format * const * ea = a;
+ struct event_format * const * eb = b;
+
+ if ((*ea)->id < (*eb)->id)
+ return -1;
+
+ if ((*ea)->id > (*eb)->id)
+ return 1;
+
+ return 0;
+}
+
+static int events_name_cmp(const void *a, const void *b)
+{
+ struct event_format * const * ea = a;
+ struct event_format * const * eb = b;
+ int res;
+
+ res = strcmp((*ea)->name, (*eb)->name);
+ if (res)
+ return res;
+
+ res = strcmp((*ea)->system, (*eb)->system);
+ if (res)
+ return res;
+
+ return events_id_cmp(a, b);
+}
+
+static int events_system_cmp(const void *a, const void *b)
+{
+ struct event_format * const * ea = a;
+ struct event_format * const * eb = b;
+ int res;
+
+ res = strcmp((*ea)->system, (*eb)->system);
+ if (res)
+ return res;
+
+ res = strcmp((*ea)->name, (*eb)->name);
+ if (res)
+ return res;
+
+ return events_id_cmp(a, b);
+}
+
+struct event_format **pevent_list_events(struct pevent *pevent, enum event_sort_type sort_type)
+{
+ struct event_format **events;
+ int (*sort)(const void *a, const void *b);
+
+ events = pevent->sort_events;
+
+ if (events && pevent->last_type == sort_type)
+ return events;
+
+ if (!events) {
+ events = malloc(sizeof(*events) * (pevent->nr_events + 1));
+ if (!events)
+ return NULL;
+
+ memcpy(events, pevent->events, sizeof(*events) * pevent->nr_events);
+ events[pevent->nr_events] = NULL;
+
+ pevent->sort_events = events;
+
+ /* the internal events are sorted by id */
+ if (sort_type == EVENT_SORT_ID) {
+ pevent->last_type = sort_type;
+ return events;
+ }
+ }
+
+ switch (sort_type) {
+ case EVENT_SORT_ID:
+ sort = events_id_cmp;
+ break;
+ case EVENT_SORT_NAME:
+ sort = events_name_cmp;
+ break;
+ case EVENT_SORT_SYSTEM:
+ sort = events_system_cmp;
+ break;
+ default:
+ return events;
+ }
+
+ qsort(events, pevent->nr_events, sizeof(*events), sort);
+ pevent->last_type = sort_type;
+
+ return events;
+}
+
+static struct format_field **
+get_event_fields(const char *type, const char *name,
+ int count, struct format_field *list)
+{
+ struct format_field **fields;
+ struct format_field *field;
+ int i = 0;
+
+ fields = malloc(sizeof(*fields) * (count + 1));
+ if (!fields)
+ return NULL;
+
+ for (field = list; field; field = field->next) {
+ fields[i++] = field;
+ if (i == count + 1) {
+ do_warning("event %s has more %s fields than specified",
+ name, type);
+ i--;
+ break;
+ }
+ }
+
+ if (i != count)
+ do_warning("event %s has less %s fields than specified",
+ name, type);
+
+ fields[i] = NULL;
+
+ return fields;
+}
+
+/**
+ * pevent_event_common_fields - return a list of common fields for an event
+ * @event: the event to return the common fields of.
+ *
+ * Returns an allocated array of fields. The last item in the array is NULL.
+ * The array must be freed with free().
+ */
+struct format_field **pevent_event_common_fields(struct event_format *event)
+{
+ return get_event_fields("common", event->name,
+ event->format.nr_common,
+ event->format.common_fields);
+}
+
+/**
+ * pevent_event_fields - return a list of event specific fields for an event
+ * @event: the event to return the fields of.
+ *
+ * Returns an allocated array of fields. The last item in the array is NULL.
+ * The array must be freed with free().
+ */
+struct format_field **pevent_event_fields(struct event_format *event)
+{
+ return get_event_fields("event", event->name,
+ event->format.nr_fields,
+ event->format.fields);
+}
+
+static void print_fields(struct trace_seq *s, struct print_flag_sym *field)
+{
+ trace_seq_printf(s, "{ %s, %s }", field->value, field->str);
+ if (field->next) {
+ trace_seq_puts(s, ", ");
+ print_fields(s, field->next);
+ }
+}
+
+/* for debugging */
+static void print_args(struct print_arg *args)
+{
+ int print_paren = 1;
+ struct trace_seq s;
+
+ switch (args->type) {
+ case PRINT_NULL:
+ printf("null");
+ break;
+ case PRINT_ATOM:
+ printf("%s", args->atom.atom);
+ break;
+ case PRINT_FIELD:
+ printf("REC->%s", args->field.name);
+ break;
+ case PRINT_FLAGS:
+ printf("__print_flags(");
+ print_args(args->flags.field);
+ printf(", %s, ", args->flags.delim);
+ trace_seq_init(&s);
+ print_fields(&s, args->flags.flags);
+ trace_seq_do_printf(&s);
+ trace_seq_destroy(&s);
+ printf(")");
+ break;
+ case PRINT_SYMBOL:
+ printf("__print_symbolic(");
+ print_args(args->symbol.field);
+ printf(", ");
+ trace_seq_init(&s);
+ print_fields(&s, args->symbol.symbols);
+ trace_seq_do_printf(&s);
+ trace_seq_destroy(&s);
+ printf(")");
+ break;
+ case PRINT_HEX:
+ printf("__print_hex(");
+ print_args(args->hex.field);
+ printf(", ");
+ print_args(args->hex.size);
+ printf(")");
+ break;
+ case PRINT_STRING:
+ case PRINT_BSTRING:
+ printf("__get_str(%s)", args->string.string);
+ break;
+ case PRINT_BITMASK:
+ printf("__get_bitmask(%s)", args->bitmask.bitmask);
+ break;
+ case PRINT_TYPE:
+ printf("(%s)", args->typecast.type);
+ print_args(args->typecast.item);
+ break;
+ case PRINT_OP:
+ if (strcmp(args->op.op, ":") == 0)
+ print_paren = 0;
+ if (print_paren)
+ printf("(");
+ print_args(args->op.left);
+ printf(" %s ", args->op.op);
+ print_args(args->op.right);
+ if (print_paren)
+ printf(")");
+ break;
+ default:
+ /* we should warn... */
+ return;
+ }
+ if (args->next) {
+ printf("\n");
+ print_args(args->next);
+ }
+}
+
+static void parse_header_field(const char *field,
+ int *offset, int *size, int mandatory)
+{
+ unsigned long long save_input_buf_ptr;
+ unsigned long long save_input_buf_siz;
+ char *token;
+ int type;
+
+ save_input_buf_ptr = input_buf_ptr;
+ save_input_buf_siz = input_buf_siz;
+
+ if (read_expected(EVENT_ITEM, "field") < 0)
+ return;
+ if (read_expected(EVENT_OP, ":") < 0)
+ return;
+
+ /* type */
+ if (read_expect_type(EVENT_ITEM, &token) < 0)
+ goto fail;
+ free_token(token);
+
+ /*
+ * If this is not a mandatory field, then test it first.
+ */
+ if (mandatory) {
+ if (read_expected(EVENT_ITEM, field) < 0)
+ return;
+ } else {
+ if (read_expect_type(EVENT_ITEM, &token) < 0)
+ goto fail;
+ if (strcmp(token, field) != 0)
+ goto discard;
+ free_token(token);
+ }
+
+ if (read_expected(EVENT_OP, ";") < 0)
+ return;
+ if (read_expected(EVENT_ITEM, "offset") < 0)
+ return;
+ if (read_expected(EVENT_OP, ":") < 0)
+ return;
+ if (read_expect_type(EVENT_ITEM, &token) < 0)
+ goto fail;
+ *offset = atoi(token);
+ free_token(token);
+ if (read_expected(EVENT_OP, ";") < 0)
+ return;
+ if (read_expected(EVENT_ITEM, "size") < 0)
+ return;
+ if (read_expected(EVENT_OP, ":") < 0)
+ return;
+ if (read_expect_type(EVENT_ITEM, &token) < 0)
+ goto fail;
+ *size = atoi(token);
+ free_token(token);
+ if (read_expected(EVENT_OP, ";") < 0)
+ return;
+ type = read_token(&token);
+ if (type != EVENT_NEWLINE) {
+ /* newer versions of the kernel have a "signed" type */
+ if (type != EVENT_ITEM)
+ goto fail;
+
+ if (strcmp(token, "signed") != 0)
+ goto fail;
+
+ free_token(token);
+
+ if (read_expected(EVENT_OP, ":") < 0)
+ return;
+
+ if (read_expect_type(EVENT_ITEM, &token))
+ goto fail;
+
+ free_token(token);
+ if (read_expected(EVENT_OP, ";") < 0)
+ return;
+
+ if (read_expect_type(EVENT_NEWLINE, &token))
+ goto fail;
+ }
+ fail:
+ free_token(token);
+ return;
+
+ discard:
+ input_buf_ptr = save_input_buf_ptr;
+ input_buf_siz = save_input_buf_siz;
+ *offset = 0;
+ *size = 0;
+ free_token(token);
+}
+
+/**
+ * pevent_parse_header_page - parse the data stored in the header page
+ * @pevent: the handle to the pevent
+ * @buf: the buffer storing the header page format string
+ * @size: the size of @buf
+ * @long_size: the long size to use if there is no header
+ *
+ * This parses the header page format for information on the
+ * ring buffer used. The @buf should be copied from
+ *
+ * /sys/kernel/debug/tracing/events/header_page
+ */
+int pevent_parse_header_page(struct pevent *pevent, char *buf, unsigned long size,
+ int long_size)
+{
+ int ignore;
+
+ if (!size) {
+ /*
+ * Old kernels did not have header page info.
+ * Sorry but we just use what we find here in user space.
+ */
+ pevent->header_page_ts_size = sizeof(long long);
+ pevent->header_page_size_size = long_size;
+ pevent->header_page_data_offset = sizeof(long long) + long_size;
+ pevent->old_format = 1;
+ return -1;
+ }
+ init_input_buf(buf, size);
+
+ parse_header_field("timestamp", &pevent->header_page_ts_offset,
+ &pevent->header_page_ts_size, 1);
+ parse_header_field("commit", &pevent->header_page_size_offset,
+ &pevent->header_page_size_size, 1);
+ parse_header_field("overwrite", &pevent->header_page_overwrite,
+ &ignore, 0);
+ parse_header_field("data", &pevent->header_page_data_offset,
+ &pevent->header_page_data_size, 1);
+
+ return 0;
+}
+
+static int event_matches(struct event_format *event,
+ int id, const char *sys_name,
+ const char *event_name)
+{
+ if (id >= 0 && id != event->id)
+ return 0;
+
+ if (event_name && (strcmp(event_name, event->name) != 0))
+ return 0;
+
+ if (sys_name && (strcmp(sys_name, event->system) != 0))
+ return 0;
+
+ return 1;
+}
+
+static void free_handler(struct event_handler *handle)
+{
+ free((void *)handle->sys_name);
+ free((void *)handle->event_name);
+ free(handle);
+}
+
+static int find_event_handle(struct pevent *pevent, struct event_format *event)
+{
+ struct event_handler *handle, **next;
+
+ for (next = &pevent->handlers; *next;
+ next = &(*next)->next) {
+ handle = *next;
+ if (event_matches(event, handle->id,
+ handle->sys_name,
+ handle->event_name))
+ break;
+ }
+
+ if (!(*next))
+ return 0;
+
+ pr_stat("overriding event (%d) %s:%s with new print handler",
+ event->id, event->system, event->name);
+
+ event->handler = handle->func;
+ event->context = handle->context;
+
+ *next = handle->next;
+ free_handler(handle);
+
+ return 1;
+}
+
+/**
+ * __pevent_parse_format - parse the event format
+ * @buf: the buffer storing the event format string
+ * @size: the size of @buf
+ * @sys: the system the event belongs to
+ *
+ * This parses the event format and creates an event structure
+ * to quickly parse raw data for a given event.
+ *
+ * These files currently come from:
+ *
+ * /sys/kernel/debug/tracing/events/.../.../format
+ */
+enum pevent_errno __pevent_parse_format(struct event_format **eventp,
+ struct pevent *pevent, const char *buf,
+ unsigned long size, const char *sys)
+{
+ struct event_format *event;
+ int ret;
+
+ init_input_buf(buf, size);
+
+ *eventp = event = alloc_event();
+ if (!event)
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+
+ event->name = event_read_name();
+ if (!event->name) {
+ /* Bad event? */
+ ret = PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ goto event_alloc_failed;
+ }
+
+ if (strcmp(sys, "ftrace") == 0) {
+ event->flags |= EVENT_FL_ISFTRACE;
+
+ if (strcmp(event->name, "bprint") == 0)
+ event->flags |= EVENT_FL_ISBPRINT;
+ }
+
+ event->id = event_read_id();
+ if (event->id < 0) {
+ ret = PEVENT_ERRNO__READ_ID_FAILED;
+ /*
+ * This isn't an allocation error actually.
+ * But as the ID is critical, just bail out.
+ */
+ goto event_alloc_failed;
+ }
+
+ event->system = strdup(sys);
+ if (!event->system) {
+ ret = PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ goto event_alloc_failed;
+ }
+
+ /* Add pevent to event so that it can be referenced */
+ event->pevent = pevent;
+
+ ret = event_read_format(event);
+ if (ret < 0) {
+ ret = PEVENT_ERRNO__READ_FORMAT_FAILED;
+ goto event_parse_failed;
+ }
+
+ /*
+ * If the event has an override, don't print warnings if the event
+ * print format fails to parse.
+ */
+ if (pevent && find_event_handle(pevent, event))
+ show_warning = 0;
+
+ ret = event_read_print(event);
+ show_warning = 1;
+
+ if (ret < 0) {
+ ret = PEVENT_ERRNO__READ_PRINT_FAILED;
+ goto event_parse_failed;
+ }
+
+ if (!ret && (event->flags & EVENT_FL_ISFTRACE)) {
+ struct format_field *field;
+ struct print_arg *arg, **list;
+
+ /* old ftrace had no args */
+ list = &event->print_fmt.args;
+ for (field = event->format.fields; field; field = field->next) {
+ arg = alloc_arg();
+ if (!arg) {
+ event->flags |= EVENT_FL_FAILED;
+ return PEVENT_ERRNO__OLD_FTRACE_ARG_FAILED;
+ }
+ arg->type = PRINT_FIELD;
+ arg->field.name = strdup(field->name);
+ if (!arg->field.name) {
+ event->flags |= EVENT_FL_FAILED;
+ free_arg(arg);
+ return PEVENT_ERRNO__OLD_FTRACE_ARG_FAILED;
+ }
+ arg->field.field = field;
+ *list = arg;
+ list = &arg->next;
+ }
+ return 0;
+ }
+
+ return 0;
+
+ event_parse_failed:
+ event->flags |= EVENT_FL_FAILED;
+ return ret;
+
+ event_alloc_failed:
+ free(event->system);
+ free(event->name);
+ free(event);
+ *eventp = NULL;
+ return ret;
+}
+
+static enum pevent_errno
+__pevent_parse_event(struct pevent *pevent,
+ struct event_format **eventp,
+ const char *buf, unsigned long size,
+ const char *sys)
+{
+ int ret = __pevent_parse_format(eventp, pevent, buf, size, sys);
+ struct event_format *event = *eventp;
+
+ if (event == NULL)
+ return ret;
+
+ if (pevent && add_event(pevent, event)) {
+ ret = PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ goto event_add_failed;
+ }
+
+#define PRINT_ARGS 0
+ if (PRINT_ARGS && event->print_fmt.args)
+ print_args(event->print_fmt.args);
+
+ return 0;
+
+event_add_failed:
+ pevent_free_format(event);
+ return ret;
+}
+
+/**
+ * pevent_parse_format - parse the event format
+ * @pevent: the handle to the pevent
+ * @eventp: returned format
+ * @buf: the buffer storing the event format string
+ * @size: the size of @buf
+ * @sys: the system the event belongs to
+ *
+ * This parses the event format and creates an event structure
+ * to quickly parse raw data for a given event.
+ *
+ * These files currently come from:
+ *
+ * /sys/kernel/debug/tracing/events/.../.../format
+ */
+enum pevent_errno pevent_parse_format(struct pevent *pevent,
+ struct event_format **eventp,
+ const char *buf,
+ unsigned long size, const char *sys)
+{
+ return __pevent_parse_event(pevent, eventp, buf, size, sys);
+}
+
+/**
+ * pevent_parse_event - parse the event format
+ * @pevent: the handle to the pevent
+ * @buf: the buffer storing the event format string
+ * @size: the size of @buf
+ * @sys: the system the event belongs to
+ *
+ * This parses the event format and creates an event structure
+ * to quickly parse raw data for a given event.
+ *
+ * These files currently come from:
+ *
+ * /sys/kernel/debug/tracing/events/.../.../format
+ */
+enum pevent_errno pevent_parse_event(struct pevent *pevent, const char *buf,
+ unsigned long size, const char *sys)
+{
+ struct event_format *event = NULL;
+ return __pevent_parse_event(pevent, &event, buf, size, sys);
+}
+
+#undef _PE
+#define _PE(code, str) str
+static const char * const pevent_error_str[] = {
+ PEVENT_ERRORS
+};
+#undef _PE
+
+int pevent_strerror(struct pevent *pevent __maybe_unused,
+ enum pevent_errno errnum, char *buf, size_t buflen)
+{
+ int idx;
+ const char *msg;
+
+ if (errnum >= 0) {
+ msg = strerror_r(errnum, buf, buflen);
+ if (msg != buf) {
+ size_t len = strlen(msg);
+ memcpy(buf, msg, min(buflen - 1, len));
+ *(buf + min(buflen - 1, len)) = '\0';
+ }
+ return 0;
+ }
+
+ if (errnum <= __PEVENT_ERRNO__START ||
+ errnum >= __PEVENT_ERRNO__END)
+ return -1;
+
+ idx = errnum - __PEVENT_ERRNO__START - 1;
+ msg = pevent_error_str[idx];
+ snprintf(buf, buflen, "%s", msg);
+
+ return 0;
+}
+
+int get_field_val(struct trace_seq *s, struct format_field *field,
+ const char *name, struct pevent_record *record,
+ unsigned long long *val, int err)
+{
+ if (!field) {
+ if (err)
+ trace_seq_printf(s, "<CANT FIND FIELD %s>", name);
+ return -1;
+ }
+
+ if (pevent_read_number_field(field, record->data, val)) {
+ if (err)
+ trace_seq_printf(s, " %s=INVALID", name);
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * pevent_get_field_raw - return the raw pointer into the data field
+ * @s: The seq to print to on error
+ * @event: the event that the field is for
+ * @name: The name of the field
+ * @record: The record with the field name.
+ * @len: place to store the field length.
+ * @err: print default error if failed.
+ *
+ * Returns a pointer into record->data of the field and places
+ * the length of the field in @len.
+ *
+ * On failure, it returns NULL.
+ */
+void *pevent_get_field_raw(struct trace_seq *s, struct event_format *event,
+ const char *name, struct pevent_record *record,
+ int *len, int err)
+{
+ struct format_field *field;
+ void *data = record->data;
+ unsigned offset;
+ int dummy;
+
+ if (!event)
+ return NULL;
+
+ field = pevent_find_field(event, name);
+
+ if (!field) {
+ if (err)
+ trace_seq_printf(s, "<CANT FIND FIELD %s>", name);
+ return NULL;
+ }
+
+ /* Allow @len to be NULL */
+ if (!len)
+ len = &dummy;
+
+ offset = field->offset;
+ if (field->flags & FIELD_IS_DYNAMIC) {
+ offset = pevent_read_number(event->pevent,
+ data + offset, field->size);
+ *len = offset >> 16;
+ offset &= 0xffff;
+ } else
+ *len = field->size;
+
+ return data + offset;
+}
+
+/**
+ * pevent_get_field_val - find a field and return its value
+ * @s: The seq to print to on error
+ * @event: the event that the field is for
+ * @name: The name of the field
+ * @record: The record with the field name.
+ * @val: place to store the value of the field.
+ * @err: print default error if failed.
+ *
+ * Returns 0 on success -1 on field not found.
+ */
+int pevent_get_field_val(struct trace_seq *s, struct event_format *event,
+ const char *name, struct pevent_record *record,
+ unsigned long long *val, int err)
+{
+ struct format_field *field;
+
+ if (!event)
+ return -1;
+
+ field = pevent_find_field(event, name);
+
+ return get_field_val(s, field, name, record, val, err);
+}
+
+/**
+ * pevent_get_common_field_val - find a common field and return its value
+ * @s: The seq to print to on error
+ * @event: the event that the field is for
+ * @name: The name of the field
+ * @record: The record with the field name.
+ * @val: place to store the value of the field.
+ * @err: print default error if failed.
+ *
+ * Returns 0 on success -1 on field not found.
+ */
+int pevent_get_common_field_val(struct trace_seq *s, struct event_format *event,
+ const char *name, struct pevent_record *record,
+ unsigned long long *val, int err)
+{
+ struct format_field *field;
+
+ if (!event)
+ return -1;
+
+ field = pevent_find_common_field(event, name);
+
+ return get_field_val(s, field, name, record, val, err);
+}
+
+/**
+ * pevent_get_any_field_val - find a any field and return its value
+ * @s: The seq to print to on error
+ * @event: the event that the field is for
+ * @name: The name of the field
+ * @record: The record with the field name.
+ * @val: place to store the value of the field.
+ * @err: print default error if failed.
+ *
+ * Returns 0 on success -1 on field not found.
+ */
+int pevent_get_any_field_val(struct trace_seq *s, struct event_format *event,
+ const char *name, struct pevent_record *record,
+ unsigned long long *val, int err)
+{
+ struct format_field *field;
+
+ if (!event)
+ return -1;
+
+ field = pevent_find_any_field(event, name);
+
+ return get_field_val(s, field, name, record, val, err);
+}
+
+/**
+ * pevent_print_num_field - print a field and a format
+ * @s: The seq to print to
+ * @fmt: The printf format to print the field with.
+ * @event: the event that the field is for
+ * @name: The name of the field
+ * @record: The record with the field name.
+ * @err: print default error if failed.
+ *
+ * Returns: 0 on success, -1 field not found, or 1 if buffer is full.
+ */
+int pevent_print_num_field(struct trace_seq *s, const char *fmt,
+ struct event_format *event, const char *name,
+ struct pevent_record *record, int err)
+{
+ struct format_field *field = pevent_find_field(event, name);
+ unsigned long long val;
+
+ if (!field)
+ goto failed;
+
+ if (pevent_read_number_field(field, record->data, &val))
+ goto failed;
+
+ return trace_seq_printf(s, fmt, val);
+
+ failed:
+ if (err)
+ trace_seq_printf(s, "CAN'T FIND FIELD \"%s\"", name);
+ return -1;
+}
+
+/**
+ * pevent_print_func_field - print a field and a format for function pointers
+ * @s: The seq to print to
+ * @fmt: The printf format to print the field with.
+ * @event: the event that the field is for
+ * @name: The name of the field
+ * @record: The record with the field name.
+ * @err: print default error if failed.
+ *
+ * Returns: 0 on success, -1 field not found, or 1 if buffer is full.
+ */
+int pevent_print_func_field(struct trace_seq *s, const char *fmt,
+ struct event_format *event, const char *name,
+ struct pevent_record *record, int err)
+{
+ struct format_field *field = pevent_find_field(event, name);
+ struct pevent *pevent = event->pevent;
+ unsigned long long val;
+ struct func_map *func;
+ char tmp[128];
+
+ if (!field)
+ goto failed;
+
+ if (pevent_read_number_field(field, record->data, &val))
+ goto failed;
+
+ func = find_func(pevent, val);
+
+ if (func)
+ snprintf(tmp, 128, "%s/0x%llx", func->func, func->addr - val);
+ else
+ sprintf(tmp, "0x%08llx", val);
+
+ return trace_seq_printf(s, fmt, tmp);
+
+ failed:
+ if (err)
+ trace_seq_printf(s, "CAN'T FIND FIELD \"%s\"", name);
+ return -1;
+}
+
+static void free_func_handle(struct pevent_function_handler *func)
+{
+ struct pevent_func_params *params;
+
+ free(func->name);
+
+ while (func->params) {
+ params = func->params;
+ func->params = params->next;
+ free(params);
+ }
+
+ free(func);
+}
+
+/**
+ * pevent_register_print_function - register a helper function
+ * @pevent: the handle to the pevent
+ * @func: the function to process the helper function
+ * @ret_type: the return type of the helper function
+ * @name: the name of the helper function
+ * @parameters: A list of enum pevent_func_arg_type
+ *
+ * Some events may have helper functions in the print format arguments.
+ * This allows a plugin to dynamically create a way to process one
+ * of these functions.
+ *
+ * The @parameters is a variable list of pevent_func_arg_type enums that
+ * must end with PEVENT_FUNC_ARG_VOID.
+ */
+int pevent_register_print_function(struct pevent *pevent,
+ pevent_func_handler func,
+ enum pevent_func_arg_type ret_type,
+ char *name, ...)
+{
+ struct pevent_function_handler *func_handle;
+ struct pevent_func_params **next_param;
+ struct pevent_func_params *param;
+ enum pevent_func_arg_type type;
+ va_list ap;
+ int ret;
+
+ func_handle = find_func_handler(pevent, name);
+ if (func_handle) {
+ /*
+ * This is most like caused by the users own
+ * plugins updating the function. This overrides the
+ * system defaults.
+ */
+ pr_stat("override of function helper '%s'", name);
+ remove_func_handler(pevent, name);
+ }
+
+ func_handle = calloc(1, sizeof(*func_handle));
+ if (!func_handle) {
+ do_warning("Failed to allocate function handler");
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ }
+
+ func_handle->ret_type = ret_type;
+ func_handle->name = strdup(name);
+ func_handle->func = func;
+ if (!func_handle->name) {
+ do_warning("Failed to allocate function name");
+ free(func_handle);
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ }
+
+ next_param = &(func_handle->params);
+ va_start(ap, name);
+ for (;;) {
+ type = va_arg(ap, enum pevent_func_arg_type);
+ if (type == PEVENT_FUNC_ARG_VOID)
+ break;
+
+ if (type >= PEVENT_FUNC_ARG_MAX_TYPES) {
+ do_warning("Invalid argument type %d", type);
+ ret = PEVENT_ERRNO__INVALID_ARG_TYPE;
+ goto out_free;
+ }
+
+ param = malloc(sizeof(*param));
+ if (!param) {
+ do_warning("Failed to allocate function param");
+ ret = PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ goto out_free;
+ }
+ param->type = type;
+ param->next = NULL;
+
+ *next_param = param;
+ next_param = &(param->next);
+
+ func_handle->nr_args++;
+ }
+ va_end(ap);
+
+ func_handle->next = pevent->func_handlers;
+ pevent->func_handlers = func_handle;
+
+ return 0;
+ out_free:
+ va_end(ap);
+ free_func_handle(func_handle);
+ return ret;
+}
+
+/**
+ * pevent_unregister_print_function - unregister a helper function
+ * @pevent: the handle to the pevent
+ * @func: the function to process the helper function
+ * @name: the name of the helper function
+ *
+ * This function removes existing print handler for function @name.
+ *
+ * Returns 0 if the handler was removed successully, -1 otherwise.
+ */
+int pevent_unregister_print_function(struct pevent *pevent,
+ pevent_func_handler func, char *name)
+{
+ struct pevent_function_handler *func_handle;
+
+ func_handle = find_func_handler(pevent, name);
+ if (func_handle && func_handle->func == func) {
+ remove_func_handler(pevent, name);
+ return 0;
+ }
+ return -1;
+}
+
+static struct event_format *pevent_search_event(struct pevent *pevent, int id,
+ const char *sys_name,
+ const char *event_name)
+{
+ struct event_format *event;
+
+ if (id >= 0) {
+ /* search by id */
+ event = pevent_find_event(pevent, id);
+ if (!event)
+ return NULL;
+ if (event_name && (strcmp(event_name, event->name) != 0))
+ return NULL;
+ if (sys_name && (strcmp(sys_name, event->system) != 0))
+ return NULL;
+ } else {
+ event = pevent_find_event_by_name(pevent, sys_name, event_name);
+ if (!event)
+ return NULL;
+ }
+ return event;
+}
+
+/**
+ * pevent_register_event_handler - register a way to parse an event
+ * @pevent: the handle to the pevent
+ * @id: the id of the event to register
+ * @sys_name: the system name the event belongs to
+ * @event_name: the name of the event
+ * @func: the function to call to parse the event information
+ * @context: the data to be passed to @func
+ *
+ * This function allows a developer to override the parsing of
+ * a given event. If for some reason the default print format
+ * is not sufficient, this function will register a function
+ * for an event to be used to parse the data instead.
+ *
+ * If @id is >= 0, then it is used to find the event.
+ * else @sys_name and @event_name are used.
+ */
+int pevent_register_event_handler(struct pevent *pevent, int id,
+ const char *sys_name, const char *event_name,
+ pevent_event_handler_func func, void *context)
+{
+ struct event_format *event;
+ struct event_handler *handle;
+
+ event = pevent_search_event(pevent, id, sys_name, event_name);
+ if (event == NULL)
+ goto not_found;
+
+ pr_stat("overriding event (%d) %s:%s with new print handler",
+ event->id, event->system, event->name);
+
+ event->handler = func;
+ event->context = context;
+ return 0;
+
+ not_found:
+ /* Save for later use. */
+ handle = calloc(1, sizeof(*handle));
+ if (!handle) {
+ do_warning("Failed to allocate event handler");
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ }
+
+ handle->id = id;
+ if (event_name)
+ handle->event_name = strdup(event_name);
+ if (sys_name)
+ handle->sys_name = strdup(sys_name);
+
+ if ((event_name && !handle->event_name) ||
+ (sys_name && !handle->sys_name)) {
+ do_warning("Failed to allocate event/sys name");
+ free((void *)handle->event_name);
+ free((void *)handle->sys_name);
+ free(handle);
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ }
+
+ handle->func = func;
+ handle->next = pevent->handlers;
+ pevent->handlers = handle;
+ handle->context = context;
+
+ return -1;
+}
+
+static int handle_matches(struct event_handler *handler, int id,
+ const char *sys_name, const char *event_name,
+ pevent_event_handler_func func, void *context)
+{
+ if (id >= 0 && id != handler->id)
+ return 0;
+
+ if (event_name && (strcmp(event_name, handler->event_name) != 0))
+ return 0;
+
+ if (sys_name && (strcmp(sys_name, handler->sys_name) != 0))
+ return 0;
+
+ if (func != handler->func || context != handler->context)
+ return 0;
+
+ return 1;
+}
+
+/**
+ * pevent_unregister_event_handler - unregister an existing event handler
+ * @pevent: the handle to the pevent
+ * @id: the id of the event to unregister
+ * @sys_name: the system name the handler belongs to
+ * @event_name: the name of the event handler
+ * @func: the function to call to parse the event information
+ * @context: the data to be passed to @func
+ *
+ * This function removes existing event handler (parser).
+ *
+ * If @id is >= 0, then it is used to find the event.
+ * else @sys_name and @event_name are used.
+ *
+ * Returns 0 if handler was removed successfully, -1 if event was not found.
+ */
+int pevent_unregister_event_handler(struct pevent *pevent, int id,
+ const char *sys_name, const char *event_name,
+ pevent_event_handler_func func, void *context)
+{
+ struct event_format *event;
+ struct event_handler *handle;
+ struct event_handler **next;
+
+ event = pevent_search_event(pevent, id, sys_name, event_name);
+ if (event == NULL)
+ goto not_found;
+
+ if (event->handler == func && event->context == context) {
+ pr_stat("removing override handler for event (%d) %s:%s. Going back to default handler.",
+ event->id, event->system, event->name);
+
+ event->handler = NULL;
+ event->context = NULL;
+ return 0;
+ }
+
+not_found:
+ for (next = &pevent->handlers; *next; next = &(*next)->next) {
+ handle = *next;
+ if (handle_matches(handle, id, sys_name, event_name,
+ func, context))
+ break;
+ }
+
+ if (!(*next))
+ return -1;
+
+ *next = handle->next;
+ free_handler(handle);
+
+ return 0;
+}
+
+/**
+ * pevent_alloc - create a pevent handle
+ */
+struct pevent *pevent_alloc(void)
+{
+ struct pevent *pevent = calloc(1, sizeof(*pevent));
+
+ if (pevent)
+ pevent->ref_count = 1;
+
+ return pevent;
+}
+
+void pevent_ref(struct pevent *pevent)
+{
+ pevent->ref_count++;
+}
+
+static void free_format_fields(struct format_field *field)
+{
+ struct format_field *next;
+
+ while (field) {
+ next = field->next;
+ free(field->type);
+ free(field->name);
+ free(field);
+ field = next;
+ }
+}
+
+static void free_formats(struct format *format)
+{
+ free_format_fields(format->common_fields);
+ free_format_fields(format->fields);
+}
+
+void pevent_free_format(struct event_format *event)
+{
+ free(event->name);
+ free(event->system);
+
+ free_formats(&event->format);
+
+ free(event->print_fmt.format);
+ free_args(event->print_fmt.args);
+
+ free(event);
+}
+
+/**
+ * pevent_free - free a pevent handle
+ * @pevent: the pevent handle to free
+ */
+void pevent_free(struct pevent *pevent)
+{
+ struct cmdline_list *cmdlist, *cmdnext;
+ struct func_list *funclist, *funcnext;
+ struct printk_list *printklist, *printknext;
+ struct pevent_function_handler *func_handler;
+ struct event_handler *handle;
+ int i;
+
+ if (!pevent)
+ return;
+
+ cmdlist = pevent->cmdlist;
+ funclist = pevent->funclist;
+ printklist = pevent->printklist;
+
+ pevent->ref_count--;
+ if (pevent->ref_count)
+ return;
+
+ if (pevent->cmdlines) {
+ for (i = 0; i < pevent->cmdline_count; i++)
+ free(pevent->cmdlines[i].comm);
+ free(pevent->cmdlines);
+ }
+
+ while (cmdlist) {
+ cmdnext = cmdlist->next;
+ free(cmdlist->comm);
+ free(cmdlist);
+ cmdlist = cmdnext;
+ }
+
+ if (pevent->func_map) {
+ for (i = 0; i < (int)pevent->func_count; i++) {
+ free(pevent->func_map[i].func);
+ free(pevent->func_map[i].mod);
+ }
+ free(pevent->func_map);
+ }
+
+ while (funclist) {
+ funcnext = funclist->next;
+ free(funclist->func);
+ free(funclist->mod);
+ free(funclist);
+ funclist = funcnext;
+ }
+
+ while (pevent->func_handlers) {
+ func_handler = pevent->func_handlers;
+ pevent->func_handlers = func_handler->next;
+ free_func_handle(func_handler);
+ }
+
+ if (pevent->printk_map) {
+ for (i = 0; i < (int)pevent->printk_count; i++)
+ free(pevent->printk_map[i].printk);
+ free(pevent->printk_map);
+ }
+
+ while (printklist) {
+ printknext = printklist->next;
+ free(printklist->printk);
+ free(printklist);
+ printklist = printknext;
+ }
+
+ for (i = 0; i < pevent->nr_events; i++)
+ pevent_free_format(pevent->events[i]);
+
+ while (pevent->handlers) {
+ handle = pevent->handlers;
+ pevent->handlers = handle->next;
+ free_handler(handle);
+ }
+
+ free(pevent->events);
+ free(pevent->sort_events);
+
+ free(pevent);
+}
+
+void pevent_unref(struct pevent *pevent)
+{
+ pevent_free(pevent);
+}
diff --git a/tools/lib/traceevent/event-parse.h b/tools/lib/traceevent/event-parse.h
new file mode 100644
index 00000000000..7a3873ff9a4
--- /dev/null
+++ b/tools/lib/traceevent/event-parse.h
@@ -0,0 +1,944 @@
+/*
+ * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#ifndef _PARSE_EVENTS_H
+#define _PARSE_EVENTS_H
+
+#include <stdbool.h>
+#include <stdarg.h>
+#include <regex.h>
+#include <string.h>
+
+#ifndef __maybe_unused
+#define __maybe_unused __attribute__((unused))
+#endif
+
+/* ----------------------- trace_seq ----------------------- */
+
+
+#ifndef TRACE_SEQ_BUF_SIZE
+#define TRACE_SEQ_BUF_SIZE 4096
+#endif
+
+#ifndef DEBUG_RECORD
+#define DEBUG_RECORD 0
+#endif
+
+struct pevent_record {
+ unsigned long long ts;
+ unsigned long long offset;
+ long long missed_events; /* buffer dropped events before */
+ int record_size; /* size of binary record */
+ int size; /* size of data */
+ void *data;
+ int cpu;
+ int ref_count;
+ int locked; /* Do not free, even if ref_count is zero */
+ void *priv;
+#if DEBUG_RECORD
+ struct pevent_record *prev;
+ struct pevent_record *next;
+ long alloc_addr;
+#endif
+};
+
+enum trace_seq_fail {
+ TRACE_SEQ__GOOD,
+ TRACE_SEQ__BUFFER_POISONED,
+ TRACE_SEQ__MEM_ALLOC_FAILED,
+};
+
+/*
+ * Trace sequences are used to allow a function to call several other functions
+ * to create a string of data to use (up to a max of PAGE_SIZE).
+ */
+
+struct trace_seq {
+ char *buffer;
+ unsigned int buffer_size;
+ unsigned int len;
+ unsigned int readpos;
+ enum trace_seq_fail state;
+};
+
+void trace_seq_init(struct trace_seq *s);
+void trace_seq_reset(struct trace_seq *s);
+void trace_seq_destroy(struct trace_seq *s);
+
+extern int trace_seq_printf(struct trace_seq *s, const char *fmt, ...)
+ __attribute__ ((format (printf, 2, 3)));
+extern int trace_seq_vprintf(struct trace_seq *s, const char *fmt, va_list args)
+ __attribute__ ((format (printf, 2, 0)));
+
+extern int trace_seq_puts(struct trace_seq *s, const char *str);
+extern int trace_seq_putc(struct trace_seq *s, unsigned char c);
+
+extern void trace_seq_terminate(struct trace_seq *s);
+
+extern int trace_seq_do_printf(struct trace_seq *s);
+
+
+/* ----------------------- pevent ----------------------- */
+
+struct pevent;
+struct event_format;
+
+typedef int (*pevent_event_handler_func)(struct trace_seq *s,
+ struct pevent_record *record,
+ struct event_format *event,
+ void *context);
+
+typedef int (*pevent_plugin_load_func)(struct pevent *pevent);
+typedef int (*pevent_plugin_unload_func)(struct pevent *pevent);
+
+struct pevent_plugin_option {
+ struct pevent_plugin_option *next;
+ void *handle;
+ char *file;
+ char *name;
+ char *plugin_alias;
+ char *description;
+ char *value;
+ void *priv;
+ int set;
+};
+
+/*
+ * Plugin hooks that can be called:
+ *
+ * PEVENT_PLUGIN_LOADER: (required)
+ * The function name to initialized the plugin.
+ *
+ * int PEVENT_PLUGIN_LOADER(struct pevent *pevent)
+ *
+ * PEVENT_PLUGIN_UNLOADER: (optional)
+ * The function called just before unloading
+ *
+ * int PEVENT_PLUGIN_UNLOADER(struct pevent *pevent)
+ *
+ * PEVENT_PLUGIN_OPTIONS: (optional)
+ * Plugin options that can be set before loading
+ *
+ * struct pevent_plugin_option PEVENT_PLUGIN_OPTIONS[] = {
+ * {
+ * .name = "option-name",
+ * .plugin_alias = "overide-file-name", (optional)
+ * .description = "description of option to show users",
+ * },
+ * {
+ * .name = NULL,
+ * },
+ * };
+ *
+ * Array must end with .name = NULL;
+ *
+ *
+ * .plugin_alias is used to give a shorter name to access
+ * the vairable. Useful if a plugin handles more than one event.
+ *
+ * PEVENT_PLUGIN_ALIAS: (optional)
+ * The name to use for finding options (uses filename if not defined)
+ */
+#define PEVENT_PLUGIN_LOADER pevent_plugin_loader
+#define PEVENT_PLUGIN_UNLOADER pevent_plugin_unloader
+#define PEVENT_PLUGIN_OPTIONS pevent_plugin_options
+#define PEVENT_PLUGIN_ALIAS pevent_plugin_alias
+#define _MAKE_STR(x) #x
+#define MAKE_STR(x) _MAKE_STR(x)
+#define PEVENT_PLUGIN_LOADER_NAME MAKE_STR(PEVENT_PLUGIN_LOADER)
+#define PEVENT_PLUGIN_UNLOADER_NAME MAKE_STR(PEVENT_PLUGIN_UNLOADER)
+#define PEVENT_PLUGIN_OPTIONS_NAME MAKE_STR(PEVENT_PLUGIN_OPTIONS)
+#define PEVENT_PLUGIN_ALIAS_NAME MAKE_STR(PEVENT_PLUGIN_ALIAS)
+
+#define NSECS_PER_SEC 1000000000ULL
+#define NSECS_PER_USEC 1000ULL
+
+enum format_flags {
+ FIELD_IS_ARRAY = 1,
+ FIELD_IS_POINTER = 2,
+ FIELD_IS_SIGNED = 4,
+ FIELD_IS_STRING = 8,
+ FIELD_IS_DYNAMIC = 16,
+ FIELD_IS_LONG = 32,
+ FIELD_IS_FLAG = 64,
+ FIELD_IS_SYMBOLIC = 128,
+};
+
+struct format_field {
+ struct format_field *next;
+ struct event_format *event;
+ char *type;
+ char *name;
+ int offset;
+ int size;
+ unsigned int arraylen;
+ unsigned int elementsize;
+ unsigned long flags;
+};
+
+struct format {
+ int nr_common;
+ int nr_fields;
+ struct format_field *common_fields;
+ struct format_field *fields;
+};
+
+struct print_arg_atom {
+ char *atom;
+};
+
+struct print_arg_string {
+ char *string;
+ int offset;
+};
+
+struct print_arg_bitmask {
+ char *bitmask;
+ int offset;
+};
+
+struct print_arg_field {
+ char *name;
+ struct format_field *field;
+};
+
+struct print_flag_sym {
+ struct print_flag_sym *next;
+ char *value;
+ char *str;
+};
+
+struct print_arg_typecast {
+ char *type;
+ struct print_arg *item;
+};
+
+struct print_arg_flags {
+ struct print_arg *field;
+ char *delim;
+ struct print_flag_sym *flags;
+};
+
+struct print_arg_symbol {
+ struct print_arg *field;
+ struct print_flag_sym *symbols;
+};
+
+struct print_arg_hex {
+ struct print_arg *field;
+ struct print_arg *size;
+};
+
+struct print_arg_dynarray {
+ struct format_field *field;
+ struct print_arg *index;
+};
+
+struct print_arg;
+
+struct print_arg_op {
+ char *op;
+ int prio;
+ struct print_arg *left;
+ struct print_arg *right;
+};
+
+struct pevent_function_handler;
+
+struct print_arg_func {
+ struct pevent_function_handler *func;
+ struct print_arg *args;
+};
+
+enum print_arg_type {
+ PRINT_NULL,
+ PRINT_ATOM,
+ PRINT_FIELD,
+ PRINT_FLAGS,
+ PRINT_SYMBOL,
+ PRINT_HEX,
+ PRINT_TYPE,
+ PRINT_STRING,
+ PRINT_BSTRING,
+ PRINT_DYNAMIC_ARRAY,
+ PRINT_OP,
+ PRINT_FUNC,
+ PRINT_BITMASK,
+};
+
+struct print_arg {
+ struct print_arg *next;
+ enum print_arg_type type;
+ union {
+ struct print_arg_atom atom;
+ struct print_arg_field field;
+ struct print_arg_typecast typecast;
+ struct print_arg_flags flags;
+ struct print_arg_symbol symbol;
+ struct print_arg_hex hex;
+ struct print_arg_func func;
+ struct print_arg_string string;
+ struct print_arg_bitmask bitmask;
+ struct print_arg_op op;
+ struct print_arg_dynarray dynarray;
+ };
+};
+
+struct print_fmt {
+ char *format;
+ struct print_arg *args;
+};
+
+struct event_format {
+ struct pevent *pevent;
+ char *name;
+ int id;
+ int flags;
+ struct format format;
+ struct print_fmt print_fmt;
+ char *system;
+ pevent_event_handler_func handler;
+ void *context;
+};
+
+enum {
+ EVENT_FL_ISFTRACE = 0x01,
+ EVENT_FL_ISPRINT = 0x02,
+ EVENT_FL_ISBPRINT = 0x04,
+ EVENT_FL_ISFUNCENT = 0x10,
+ EVENT_FL_ISFUNCRET = 0x20,
+ EVENT_FL_NOHANDLE = 0x40,
+ EVENT_FL_PRINTRAW = 0x80,
+
+ EVENT_FL_FAILED = 0x80000000
+};
+
+enum event_sort_type {
+ EVENT_SORT_ID,
+ EVENT_SORT_NAME,
+ EVENT_SORT_SYSTEM,
+};
+
+enum event_type {
+ EVENT_ERROR,
+ EVENT_NONE,
+ EVENT_SPACE,
+ EVENT_NEWLINE,
+ EVENT_OP,
+ EVENT_DELIM,
+ EVENT_ITEM,
+ EVENT_DQUOTE,
+ EVENT_SQUOTE,
+};
+
+typedef unsigned long long (*pevent_func_handler)(struct trace_seq *s,
+ unsigned long long *args);
+
+enum pevent_func_arg_type {
+ PEVENT_FUNC_ARG_VOID,
+ PEVENT_FUNC_ARG_INT,
+ PEVENT_FUNC_ARG_LONG,
+ PEVENT_FUNC_ARG_STRING,
+ PEVENT_FUNC_ARG_PTR,
+ PEVENT_FUNC_ARG_MAX_TYPES
+};
+
+enum pevent_flag {
+ PEVENT_NSEC_OUTPUT = 1, /* output in NSECS */
+ PEVENT_DISABLE_SYS_PLUGINS = 1 << 1,
+ PEVENT_DISABLE_PLUGINS = 1 << 2,
+};
+
+#define PEVENT_ERRORS \
+ _PE(MEM_ALLOC_FAILED, "failed to allocate memory"), \
+ _PE(PARSE_EVENT_FAILED, "failed to parse event"), \
+ _PE(READ_ID_FAILED, "failed to read event id"), \
+ _PE(READ_FORMAT_FAILED, "failed to read event format"), \
+ _PE(READ_PRINT_FAILED, "failed to read event print fmt"), \
+ _PE(OLD_FTRACE_ARG_FAILED,"failed to allocate field name for ftrace"),\
+ _PE(INVALID_ARG_TYPE, "invalid argument type"), \
+ _PE(INVALID_EXP_TYPE, "invalid expression type"), \
+ _PE(INVALID_OP_TYPE, "invalid operator type"), \
+ _PE(INVALID_EVENT_NAME, "invalid event name"), \
+ _PE(EVENT_NOT_FOUND, "no event found"), \
+ _PE(SYNTAX_ERROR, "syntax error"), \
+ _PE(ILLEGAL_RVALUE, "illegal rvalue"), \
+ _PE(ILLEGAL_LVALUE, "illegal lvalue for string comparison"), \
+ _PE(INVALID_REGEX, "regex did not compute"), \
+ _PE(ILLEGAL_STRING_CMP, "illegal comparison for string"), \
+ _PE(ILLEGAL_INTEGER_CMP,"illegal comparison for integer"), \
+ _PE(REPARENT_NOT_OP, "cannot reparent other than OP"), \
+ _PE(REPARENT_FAILED, "failed to reparent filter OP"), \
+ _PE(BAD_FILTER_ARG, "bad arg in filter tree"), \
+ _PE(UNEXPECTED_TYPE, "unexpected type (not a value)"), \
+ _PE(ILLEGAL_TOKEN, "illegal token"), \
+ _PE(INVALID_PAREN, "open parenthesis cannot come here"), \
+ _PE(UNBALANCED_PAREN, "unbalanced number of parenthesis"), \
+ _PE(UNKNOWN_TOKEN, "unknown token"), \
+ _PE(FILTER_NOT_FOUND, "no filter found"), \
+ _PE(NOT_A_NUMBER, "must have number field"), \
+ _PE(NO_FILTER, "no filters exists"), \
+ _PE(FILTER_MISS, "record does not match to filter")
+
+#undef _PE
+#define _PE(__code, __str) PEVENT_ERRNO__ ## __code
+enum pevent_errno {
+ PEVENT_ERRNO__SUCCESS = 0,
+ PEVENT_ERRNO__FILTER_MATCH = PEVENT_ERRNO__SUCCESS,
+
+ /*
+ * Choose an arbitrary negative big number not to clash with standard
+ * errno since SUS requires the errno has distinct positive values.
+ * See 'Issue 6' in the link below.
+ *
+ * http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html
+ */
+ __PEVENT_ERRNO__START = -100000,
+
+ PEVENT_ERRORS,
+
+ __PEVENT_ERRNO__END,
+};
+#undef _PE
+
+struct plugin_list;
+
+#define INVALID_PLUGIN_LIST_OPTION ((char **)((unsigned long)-1))
+
+struct plugin_list *traceevent_load_plugins(struct pevent *pevent);
+void traceevent_unload_plugins(struct plugin_list *plugin_list,
+ struct pevent *pevent);
+char **traceevent_plugin_list_options(void);
+void traceevent_plugin_free_options_list(char **list);
+int traceevent_plugin_add_options(const char *name,
+ struct pevent_plugin_option *options);
+void traceevent_plugin_remove_options(struct pevent_plugin_option *options);
+void traceevent_print_plugins(struct trace_seq *s,
+ const char *prefix, const char *suffix,
+ const struct plugin_list *list);
+
+struct cmdline;
+struct cmdline_list;
+struct func_map;
+struct func_list;
+struct event_handler;
+
+struct pevent {
+ int ref_count;
+
+ int header_page_ts_offset;
+ int header_page_ts_size;
+ int header_page_size_offset;
+ int header_page_size_size;
+ int header_page_data_offset;
+ int header_page_data_size;
+ int header_page_overwrite;
+
+ int file_bigendian;
+ int host_bigendian;
+
+ int latency_format;
+
+ int old_format;
+
+ int cpus;
+ int long_size;
+ int page_size;
+
+ struct cmdline *cmdlines;
+ struct cmdline_list *cmdlist;
+ int cmdline_count;
+
+ struct func_map *func_map;
+ struct func_list *funclist;
+ unsigned int func_count;
+
+ struct printk_map *printk_map;
+ struct printk_list *printklist;
+ unsigned int printk_count;
+
+
+ struct event_format **events;
+ int nr_events;
+ struct event_format **sort_events;
+ enum event_sort_type last_type;
+
+ int type_offset;
+ int type_size;
+
+ int pid_offset;
+ int pid_size;
+
+ int pc_offset;
+ int pc_size;
+
+ int flags_offset;
+ int flags_size;
+
+ int ld_offset;
+ int ld_size;
+
+ int print_raw;
+
+ int test_filters;
+
+ int flags;
+
+ struct format_field *bprint_ip_field;
+ struct format_field *bprint_fmt_field;
+ struct format_field *bprint_buf_field;
+
+ struct event_handler *handlers;
+ struct pevent_function_handler *func_handlers;
+
+ /* cache */
+ struct event_format *last_event;
+
+ char *trace_clock;
+};
+
+static inline void pevent_set_flag(struct pevent *pevent, int flag)
+{
+ pevent->flags |= flag;
+}
+
+static inline unsigned short
+__data2host2(struct pevent *pevent, unsigned short data)
+{
+ unsigned short swap;
+
+ if (pevent->host_bigendian == pevent->file_bigendian)
+ return data;
+
+ swap = ((data & 0xffULL) << 8) |
+ ((data & (0xffULL << 8)) >> 8);
+
+ return swap;
+}
+
+static inline unsigned int
+__data2host4(struct pevent *pevent, unsigned int data)
+{
+ unsigned int swap;
+
+ if (pevent->host_bigendian == pevent->file_bigendian)
+ return data;
+
+ swap = ((data & 0xffULL) << 24) |
+ ((data & (0xffULL << 8)) << 8) |
+ ((data & (0xffULL << 16)) >> 8) |
+ ((data & (0xffULL << 24)) >> 24);
+
+ return swap;
+}
+
+static inline unsigned long long
+__data2host8(struct pevent *pevent, unsigned long long data)
+{
+ unsigned long long swap;
+
+ if (pevent->host_bigendian == pevent->file_bigendian)
+ return data;
+
+ swap = ((data & 0xffULL) << 56) |
+ ((data & (0xffULL << 8)) << 40) |
+ ((data & (0xffULL << 16)) << 24) |
+ ((data & (0xffULL << 24)) << 8) |
+ ((data & (0xffULL << 32)) >> 8) |
+ ((data & (0xffULL << 40)) >> 24) |
+ ((data & (0xffULL << 48)) >> 40) |
+ ((data & (0xffULL << 56)) >> 56);
+
+ return swap;
+}
+
+#define data2host2(pevent, ptr) __data2host2(pevent, *(unsigned short *)(ptr))
+#define data2host4(pevent, ptr) __data2host4(pevent, *(unsigned int *)(ptr))
+#define data2host8(pevent, ptr) \
+({ \
+ unsigned long long __val; \
+ \
+ memcpy(&__val, (ptr), sizeof(unsigned long long)); \
+ __data2host8(pevent, __val); \
+})
+
+static inline int traceevent_host_bigendian(void)
+{
+ unsigned char str[] = { 0x1, 0x2, 0x3, 0x4 };
+ unsigned int val;
+
+ memcpy(&val, str, 4);
+ return val == 0x01020304;
+}
+
+/* taken from kernel/trace/trace.h */
+enum trace_flag_type {
+ TRACE_FLAG_IRQS_OFF = 0x01,
+ TRACE_FLAG_IRQS_NOSUPPORT = 0x02,
+ TRACE_FLAG_NEED_RESCHED = 0x04,
+ TRACE_FLAG_HARDIRQ = 0x08,
+ TRACE_FLAG_SOFTIRQ = 0x10,
+};
+
+int pevent_register_comm(struct pevent *pevent, const char *comm, int pid);
+void pevent_register_trace_clock(struct pevent *pevent, char *trace_clock);
+int pevent_register_function(struct pevent *pevent, char *name,
+ unsigned long long addr, char *mod);
+int pevent_register_print_string(struct pevent *pevent, const char *fmt,
+ unsigned long long addr);
+int pevent_pid_is_registered(struct pevent *pevent, int pid);
+
+void pevent_print_event(struct pevent *pevent, struct trace_seq *s,
+ struct pevent_record *record, bool use_trace_clock);
+
+int pevent_parse_header_page(struct pevent *pevent, char *buf, unsigned long size,
+ int long_size);
+
+enum pevent_errno pevent_parse_event(struct pevent *pevent, const char *buf,
+ unsigned long size, const char *sys);
+enum pevent_errno pevent_parse_format(struct pevent *pevent,
+ struct event_format **eventp,
+ const char *buf,
+ unsigned long size, const char *sys);
+void pevent_free_format(struct event_format *event);
+
+void *pevent_get_field_raw(struct trace_seq *s, struct event_format *event,
+ const char *name, struct pevent_record *record,
+ int *len, int err);
+
+int pevent_get_field_val(struct trace_seq *s, struct event_format *event,
+ const char *name, struct pevent_record *record,
+ unsigned long long *val, int err);
+int pevent_get_common_field_val(struct trace_seq *s, struct event_format *event,
+ const char *name, struct pevent_record *record,
+ unsigned long long *val, int err);
+int pevent_get_any_field_val(struct trace_seq *s, struct event_format *event,
+ const char *name, struct pevent_record *record,
+ unsigned long long *val, int err);
+
+int pevent_print_num_field(struct trace_seq *s, const char *fmt,
+ struct event_format *event, const char *name,
+ struct pevent_record *record, int err);
+
+int pevent_print_func_field(struct trace_seq *s, const char *fmt,
+ struct event_format *event, const char *name,
+ struct pevent_record *record, int err);
+
+int pevent_register_event_handler(struct pevent *pevent, int id,
+ const char *sys_name, const char *event_name,
+ pevent_event_handler_func func, void *context);
+int pevent_unregister_event_handler(struct pevent *pevent, int id,
+ const char *sys_name, const char *event_name,
+ pevent_event_handler_func func, void *context);
+int pevent_register_print_function(struct pevent *pevent,
+ pevent_func_handler func,
+ enum pevent_func_arg_type ret_type,
+ char *name, ...);
+int pevent_unregister_print_function(struct pevent *pevent,
+ pevent_func_handler func, char *name);
+
+struct format_field *pevent_find_common_field(struct event_format *event, const char *name);
+struct format_field *pevent_find_field(struct event_format *event, const char *name);
+struct format_field *pevent_find_any_field(struct event_format *event, const char *name);
+
+const char *pevent_find_function(struct pevent *pevent, unsigned long long addr);
+unsigned long long
+pevent_find_function_address(struct pevent *pevent, unsigned long long addr);
+unsigned long long pevent_read_number(struct pevent *pevent, const void *ptr, int size);
+int pevent_read_number_field(struct format_field *field, const void *data,
+ unsigned long long *value);
+
+struct event_format *pevent_find_event(struct pevent *pevent, int id);
+
+struct event_format *
+pevent_find_event_by_name(struct pevent *pevent, const char *sys, const char *name);
+
+void pevent_data_lat_fmt(struct pevent *pevent,
+ struct trace_seq *s, struct pevent_record *record);
+int pevent_data_type(struct pevent *pevent, struct pevent_record *rec);
+struct event_format *pevent_data_event_from_type(struct pevent *pevent, int type);
+int pevent_data_pid(struct pevent *pevent, struct pevent_record *rec);
+const char *pevent_data_comm_from_pid(struct pevent *pevent, int pid);
+void pevent_event_info(struct trace_seq *s, struct event_format *event,
+ struct pevent_record *record);
+int pevent_strerror(struct pevent *pevent, enum pevent_errno errnum,
+ char *buf, size_t buflen);
+
+struct event_format **pevent_list_events(struct pevent *pevent, enum event_sort_type);
+struct format_field **pevent_event_common_fields(struct event_format *event);
+struct format_field **pevent_event_fields(struct event_format *event);
+
+static inline int pevent_get_cpus(struct pevent *pevent)
+{
+ return pevent->cpus;
+}
+
+static inline void pevent_set_cpus(struct pevent *pevent, int cpus)
+{
+ pevent->cpus = cpus;
+}
+
+static inline int pevent_get_long_size(struct pevent *pevent)
+{
+ return pevent->long_size;
+}
+
+static inline void pevent_set_long_size(struct pevent *pevent, int long_size)
+{
+ pevent->long_size = long_size;
+}
+
+static inline int pevent_get_page_size(struct pevent *pevent)
+{
+ return pevent->page_size;
+}
+
+static inline void pevent_set_page_size(struct pevent *pevent, int _page_size)
+{
+ pevent->page_size = _page_size;
+}
+
+static inline int pevent_is_file_bigendian(struct pevent *pevent)
+{
+ return pevent->file_bigendian;
+}
+
+static inline void pevent_set_file_bigendian(struct pevent *pevent, int endian)
+{
+ pevent->file_bigendian = endian;
+}
+
+static inline int pevent_is_host_bigendian(struct pevent *pevent)
+{
+ return pevent->host_bigendian;
+}
+
+static inline void pevent_set_host_bigendian(struct pevent *pevent, int endian)
+{
+ pevent->host_bigendian = endian;
+}
+
+static inline int pevent_is_latency_format(struct pevent *pevent)
+{
+ return pevent->latency_format;
+}
+
+static inline void pevent_set_latency_format(struct pevent *pevent, int lat)
+{
+ pevent->latency_format = lat;
+}
+
+struct pevent *pevent_alloc(void);
+void pevent_free(struct pevent *pevent);
+void pevent_ref(struct pevent *pevent);
+void pevent_unref(struct pevent *pevent);
+
+/* access to the internal parser */
+void pevent_buffer_init(const char *buf, unsigned long long size);
+enum event_type pevent_read_token(char **tok);
+void pevent_free_token(char *token);
+int pevent_peek_char(void);
+const char *pevent_get_input_buf(void);
+unsigned long long pevent_get_input_buf_ptr(void);
+
+/* for debugging */
+void pevent_print_funcs(struct pevent *pevent);
+void pevent_print_printk(struct pevent *pevent);
+
+/* ----------------------- filtering ----------------------- */
+
+enum filter_boolean_type {
+ FILTER_FALSE,
+ FILTER_TRUE,
+};
+
+enum filter_op_type {
+ FILTER_OP_AND = 1,
+ FILTER_OP_OR,
+ FILTER_OP_NOT,
+};
+
+enum filter_cmp_type {
+ FILTER_CMP_NONE,
+ FILTER_CMP_EQ,
+ FILTER_CMP_NE,
+ FILTER_CMP_GT,
+ FILTER_CMP_LT,
+ FILTER_CMP_GE,
+ FILTER_CMP_LE,
+ FILTER_CMP_MATCH,
+ FILTER_CMP_NOT_MATCH,
+ FILTER_CMP_REGEX,
+ FILTER_CMP_NOT_REGEX,
+};
+
+enum filter_exp_type {
+ FILTER_EXP_NONE,
+ FILTER_EXP_ADD,
+ FILTER_EXP_SUB,
+ FILTER_EXP_MUL,
+ FILTER_EXP_DIV,
+ FILTER_EXP_MOD,
+ FILTER_EXP_RSHIFT,
+ FILTER_EXP_LSHIFT,
+ FILTER_EXP_AND,
+ FILTER_EXP_OR,
+ FILTER_EXP_XOR,
+ FILTER_EXP_NOT,
+};
+
+enum filter_arg_type {
+ FILTER_ARG_NONE,
+ FILTER_ARG_BOOLEAN,
+ FILTER_ARG_VALUE,
+ FILTER_ARG_FIELD,
+ FILTER_ARG_EXP,
+ FILTER_ARG_OP,
+ FILTER_ARG_NUM,
+ FILTER_ARG_STR,
+};
+
+enum filter_value_type {
+ FILTER_NUMBER,
+ FILTER_STRING,
+ FILTER_CHAR
+};
+
+struct fliter_arg;
+
+struct filter_arg_boolean {
+ enum filter_boolean_type value;
+};
+
+struct filter_arg_field {
+ struct format_field *field;
+};
+
+struct filter_arg_value {
+ enum filter_value_type type;
+ union {
+ char *str;
+ unsigned long long val;
+ };
+};
+
+struct filter_arg_op {
+ enum filter_op_type type;
+ struct filter_arg *left;
+ struct filter_arg *right;
+};
+
+struct filter_arg_exp {
+ enum filter_exp_type type;
+ struct filter_arg *left;
+ struct filter_arg *right;
+};
+
+struct filter_arg_num {
+ enum filter_cmp_type type;
+ struct filter_arg *left;
+ struct filter_arg *right;
+};
+
+struct filter_arg_str {
+ enum filter_cmp_type type;
+ struct format_field *field;
+ char *val;
+ char *buffer;
+ regex_t reg;
+};
+
+struct filter_arg {
+ enum filter_arg_type type;
+ union {
+ struct filter_arg_boolean boolean;
+ struct filter_arg_field field;
+ struct filter_arg_value value;
+ struct filter_arg_op op;
+ struct filter_arg_exp exp;
+ struct filter_arg_num num;
+ struct filter_arg_str str;
+ };
+};
+
+struct filter_type {
+ int event_id;
+ struct event_format *event;
+ struct filter_arg *filter;
+};
+
+#define PEVENT_FILTER_ERROR_BUFSZ 1024
+
+struct event_filter {
+ struct pevent *pevent;
+ int filters;
+ struct filter_type *event_filters;
+ char error_buffer[PEVENT_FILTER_ERROR_BUFSZ];
+};
+
+struct event_filter *pevent_filter_alloc(struct pevent *pevent);
+
+/* for backward compatibility */
+#define FILTER_NONE PEVENT_ERRNO__NO_FILTER
+#define FILTER_NOEXIST PEVENT_ERRNO__FILTER_NOT_FOUND
+#define FILTER_MISS PEVENT_ERRNO__FILTER_MISS
+#define FILTER_MATCH PEVENT_ERRNO__FILTER_MATCH
+
+enum filter_trivial_type {
+ FILTER_TRIVIAL_FALSE,
+ FILTER_TRIVIAL_TRUE,
+ FILTER_TRIVIAL_BOTH,
+};
+
+enum pevent_errno pevent_filter_add_filter_str(struct event_filter *filter,
+ const char *filter_str);
+
+enum pevent_errno pevent_filter_match(struct event_filter *filter,
+ struct pevent_record *record);
+
+int pevent_filter_strerror(struct event_filter *filter, enum pevent_errno err,
+ char *buf, size_t buflen);
+
+int pevent_event_filtered(struct event_filter *filter,
+ int event_id);
+
+void pevent_filter_reset(struct event_filter *filter);
+
+int pevent_filter_clear_trivial(struct event_filter *filter,
+ enum filter_trivial_type type);
+
+void pevent_filter_free(struct event_filter *filter);
+
+char *pevent_filter_make_string(struct event_filter *filter, int event_id);
+
+int pevent_filter_remove_event(struct event_filter *filter,
+ int event_id);
+
+int pevent_filter_event_has_trivial(struct event_filter *filter,
+ int event_id,
+ enum filter_trivial_type type);
+
+int pevent_filter_copy(struct event_filter *dest, struct event_filter *source);
+
+int pevent_update_trivial(struct event_filter *dest, struct event_filter *source,
+ enum filter_trivial_type type);
+
+int pevent_filter_compare(struct event_filter *filter1, struct event_filter *filter2);
+
+#endif /* _PARSE_EVENTS_H */
diff --git a/tools/lib/traceevent/event-plugin.c b/tools/lib/traceevent/event-plugin.c
new file mode 100644
index 00000000000..136162c03af
--- /dev/null
+++ b/tools/lib/traceevent/event-plugin.c
@@ -0,0 +1,416 @@
+/*
+ * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <dlfcn.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <dirent.h>
+#include "event-parse.h"
+#include "event-utils.h"
+
+#define LOCAL_PLUGIN_DIR ".traceevent/plugins"
+
+static struct registered_plugin_options {
+ struct registered_plugin_options *next;
+ struct pevent_plugin_option *options;
+} *registered_options;
+
+static struct trace_plugin_options {
+ struct trace_plugin_options *next;
+ char *plugin;
+ char *option;
+ char *value;
+} *trace_plugin_options;
+
+struct plugin_list {
+ struct plugin_list *next;
+ char *name;
+ void *handle;
+};
+
+/**
+ * traceevent_plugin_list_options - get list of plugin options
+ *
+ * Returns an array of char strings that list the currently registered
+ * plugin options in the format of <plugin>:<option>. This list can be
+ * used by toggling the option.
+ *
+ * Returns NULL if there's no options registered. On error it returns
+ * INVALID_PLUGIN_LIST_OPTION
+ *
+ * Must be freed with traceevent_plugin_free_options_list().
+ */
+char **traceevent_plugin_list_options(void)
+{
+ struct registered_plugin_options *reg;
+ struct pevent_plugin_option *op;
+ char **list = NULL;
+ char *name;
+ int count = 0;
+
+ for (reg = registered_options; reg; reg = reg->next) {
+ for (op = reg->options; op->name; op++) {
+ char *alias = op->plugin_alias ? op->plugin_alias : op->file;
+ char **temp = list;
+
+ name = malloc(strlen(op->name) + strlen(alias) + 2);
+ if (!name)
+ goto err;
+
+ sprintf(name, "%s:%s", alias, op->name);
+ list = realloc(list, count + 2);
+ if (!list) {
+ list = temp;
+ free(name);
+ goto err;
+ }
+ list[count++] = name;
+ list[count] = NULL;
+ }
+ }
+ return list;
+
+ err:
+ while (--count >= 0)
+ free(list[count]);
+ free(list);
+
+ return INVALID_PLUGIN_LIST_OPTION;
+}
+
+void traceevent_plugin_free_options_list(char **list)
+{
+ int i;
+
+ if (!list)
+ return;
+
+ if (list == INVALID_PLUGIN_LIST_OPTION)
+ return;
+
+ for (i = 0; list[i]; i++)
+ free(list[i]);
+
+ free(list);
+}
+
+static int
+update_option(const char *file, struct pevent_plugin_option *option)
+{
+ struct trace_plugin_options *op;
+ char *plugin;
+
+ if (option->plugin_alias) {
+ plugin = strdup(option->plugin_alias);
+ if (!plugin)
+ return -1;
+ } else {
+ char *p;
+ plugin = strdup(file);
+ if (!plugin)
+ return -1;
+ p = strstr(plugin, ".");
+ if (p)
+ *p = '\0';
+ }
+
+ /* first look for named options */
+ for (op = trace_plugin_options; op; op = op->next) {
+ if (!op->plugin)
+ continue;
+ if (strcmp(op->plugin, plugin) != 0)
+ continue;
+ if (strcmp(op->option, option->name) != 0)
+ continue;
+
+ option->value = op->value;
+ option->set ^= 1;
+ goto out;
+ }
+
+ /* first look for unnamed options */
+ for (op = trace_plugin_options; op; op = op->next) {
+ if (op->plugin)
+ continue;
+ if (strcmp(op->option, option->name) != 0)
+ continue;
+
+ option->value = op->value;
+ option->set ^= 1;
+ break;
+ }
+
+ out:
+ free(plugin);
+ return 0;
+}
+
+/**
+ * traceevent_plugin_add_options - Add a set of options by a plugin
+ * @name: The name of the plugin adding the options
+ * @options: The set of options being loaded
+ *
+ * Sets the options with the values that have been added by user.
+ */
+int traceevent_plugin_add_options(const char *name,
+ struct pevent_plugin_option *options)
+{
+ struct registered_plugin_options *reg;
+
+ reg = malloc(sizeof(*reg));
+ if (!reg)
+ return -1;
+ reg->next = registered_options;
+ reg->options = options;
+ registered_options = reg;
+
+ while (options->name) {
+ update_option(name, options);
+ options++;
+ }
+ return 0;
+}
+
+/**
+ * traceevent_plugin_remove_options - remove plugin options that were registered
+ * @options: Options to removed that were registered with traceevent_plugin_add_options
+ */
+void traceevent_plugin_remove_options(struct pevent_plugin_option *options)
+{
+ struct registered_plugin_options **last;
+ struct registered_plugin_options *reg;
+
+ for (last = &registered_options; *last; last = &(*last)->next) {
+ if ((*last)->options == options) {
+ reg = *last;
+ *last = reg->next;
+ free(reg);
+ return;
+ }
+ }
+}
+
+/**
+ * traceevent_print_plugins - print out the list of plugins loaded
+ * @s: the trace_seq descripter to write to
+ * @prefix: The prefix string to add before listing the option name
+ * @suffix: The suffix string ot append after the option name
+ * @list: The list of plugins (usually returned by traceevent_load_plugins()
+ *
+ * Writes to the trace_seq @s the list of plugins (files) that is
+ * returned by traceevent_load_plugins(). Use @prefix and @suffix for formating:
+ * @prefix = " ", @suffix = "\n".
+ */
+void traceevent_print_plugins(struct trace_seq *s,
+ const char *prefix, const char *suffix,
+ const struct plugin_list *list)
+{
+ while (list) {
+ trace_seq_printf(s, "%s%s%s", prefix, list->name, suffix);
+ list = list->next;
+ }
+}
+
+static void
+load_plugin(struct pevent *pevent, const char *path,
+ const char *file, void *data)
+{
+ struct plugin_list **plugin_list = data;
+ pevent_plugin_load_func func;
+ struct plugin_list *list;
+ const char *alias;
+ char *plugin;
+ void *handle;
+
+ plugin = malloc(strlen(path) + strlen(file) + 2);
+ if (!plugin) {
+ warning("could not allocate plugin memory\n");
+ return;
+ }
+
+ strcpy(plugin, path);
+ strcat(plugin, "/");
+ strcat(plugin, file);
+
+ handle = dlopen(plugin, RTLD_NOW | RTLD_GLOBAL);
+ if (!handle) {
+ warning("could not load plugin '%s'\n%s\n",
+ plugin, dlerror());
+ goto out_free;
+ }
+
+ alias = dlsym(handle, PEVENT_PLUGIN_ALIAS_NAME);
+ if (!alias)
+ alias = file;
+
+ func = dlsym(handle, PEVENT_PLUGIN_LOADER_NAME);
+ if (!func) {
+ warning("could not find func '%s' in plugin '%s'\n%s\n",
+ PEVENT_PLUGIN_LOADER_NAME, plugin, dlerror());
+ goto out_free;
+ }
+
+ list = malloc(sizeof(*list));
+ if (!list) {
+ warning("could not allocate plugin memory\n");
+ goto out_free;
+ }
+
+ list->next = *plugin_list;
+ list->handle = handle;
+ list->name = plugin;
+ *plugin_list = list;
+
+ pr_stat("registering plugin: %s", plugin);
+ func(pevent);
+ return;
+
+ out_free:
+ free(plugin);
+}
+
+static void
+load_plugins_dir(struct pevent *pevent, const char *suffix,
+ const char *path,
+ void (*load_plugin)(struct pevent *pevent,
+ const char *path,
+ const char *name,
+ void *data),
+ void *data)
+{
+ struct dirent *dent;
+ struct stat st;
+ DIR *dir;
+ int ret;
+
+ ret = stat(path, &st);
+ if (ret < 0)
+ return;
+
+ if (!S_ISDIR(st.st_mode))
+ return;
+
+ dir = opendir(path);
+ if (!dir)
+ return;
+
+ while ((dent = readdir(dir))) {
+ const char *name = dent->d_name;
+
+ if (strcmp(name, ".") == 0 ||
+ strcmp(name, "..") == 0)
+ continue;
+
+ /* Only load plugins that end in suffix */
+ if (strcmp(name + (strlen(name) - strlen(suffix)), suffix) != 0)
+ continue;
+
+ load_plugin(pevent, path, name, data);
+ }
+
+ closedir(dir);
+}
+
+static void
+load_plugins(struct pevent *pevent, const char *suffix,
+ void (*load_plugin)(struct pevent *pevent,
+ const char *path,
+ const char *name,
+ void *data),
+ void *data)
+{
+ char *home;
+ char *path;
+ char *envdir;
+
+ if (pevent->flags & PEVENT_DISABLE_PLUGINS)
+ return;
+
+ /*
+ * If a system plugin directory was defined,
+ * check that first.
+ */
+#ifdef PLUGIN_DIR
+ if (!(pevent->flags & PEVENT_DISABLE_SYS_PLUGINS))
+ load_plugins_dir(pevent, suffix, PLUGIN_DIR,
+ load_plugin, data);
+#endif
+
+ /*
+ * Next let the environment-set plugin directory
+ * override the system defaults.
+ */
+ envdir = getenv("TRACEEVENT_PLUGIN_DIR");
+ if (envdir)
+ load_plugins_dir(pevent, suffix, envdir, load_plugin, data);
+
+ /*
+ * Now let the home directory override the environment
+ * or system defaults.
+ */
+ home = getenv("HOME");
+ if (!home)
+ return;
+
+ path = malloc(strlen(home) + strlen(LOCAL_PLUGIN_DIR) + 2);
+ if (!path) {
+ warning("could not allocate plugin memory\n");
+ return;
+ }
+
+ strcpy(path, home);
+ strcat(path, "/");
+ strcat(path, LOCAL_PLUGIN_DIR);
+
+ load_plugins_dir(pevent, suffix, path, load_plugin, data);
+
+ free(path);
+}
+
+struct plugin_list*
+traceevent_load_plugins(struct pevent *pevent)
+{
+ struct plugin_list *list = NULL;
+
+ load_plugins(pevent, ".so", load_plugin, &list);
+ return list;
+}
+
+void
+traceevent_unload_plugins(struct plugin_list *plugin_list, struct pevent *pevent)
+{
+ pevent_plugin_unload_func func;
+ struct plugin_list *list;
+
+ while (plugin_list) {
+ list = plugin_list;
+ plugin_list = list->next;
+ func = dlsym(list->handle, PEVENT_PLUGIN_UNLOADER_NAME);
+ if (func)
+ func(pevent);
+ dlclose(list->handle);
+ free(list->name);
+ free(list);
+ }
+}
diff --git a/tools/lib/traceevent/event-utils.h b/tools/lib/traceevent/event-utils.h
new file mode 100644
index 00000000000..d1dc2170e40
--- /dev/null
+++ b/tools/lib/traceevent/event-utils.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#ifndef __UTIL_H
+#define __UTIL_H
+
+#include <ctype.h>
+
+/* Can be overridden */
+void warning(const char *fmt, ...);
+void pr_stat(const char *fmt, ...);
+void vpr_stat(const char *fmt, va_list ap);
+
+/* Always available */
+void __warning(const char *fmt, ...);
+void __pr_stat(const char *fmt, ...);
+
+void __vwarning(const char *fmt, ...);
+void __vpr_stat(const char *fmt, ...);
+
+#define min(x, y) ({ \
+ typeof(x) _min1 = (x); \
+ typeof(y) _min2 = (y); \
+ (void) (&_min1 == &_min2); \
+ _min1 < _min2 ? _min1 : _min2; })
+
+static inline char *strim(char *string)
+{
+ char *ret;
+
+ if (!string)
+ return NULL;
+ while (*string) {
+ if (!isspace(*string))
+ break;
+ string++;
+ }
+ ret = string;
+
+ string = ret + strlen(ret) - 1;
+ while (string > ret) {
+ if (!isspace(*string))
+ break;
+ string--;
+ }
+ string[1] = 0;
+
+ return ret;
+}
+
+static inline int has_text(const char *text)
+{
+ if (!text)
+ return 0;
+
+ while (*text) {
+ if (!isspace(*text))
+ return 1;
+ text++;
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/tools/lib/traceevent/kbuffer-parse.c b/tools/lib/traceevent/kbuffer-parse.c
new file mode 100644
index 00000000000..dcc665228c7
--- /dev/null
+++ b/tools/lib/traceevent/kbuffer-parse.c
@@ -0,0 +1,732 @@
+/*
+ * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "kbuffer.h"
+
+#define MISSING_EVENTS (1 << 31)
+#define MISSING_STORED (1 << 30)
+
+#define COMMIT_MASK ((1 << 27) - 1)
+
+enum {
+ KBUFFER_FL_HOST_BIG_ENDIAN = (1<<0),
+ KBUFFER_FL_BIG_ENDIAN = (1<<1),
+ KBUFFER_FL_LONG_8 = (1<<2),
+ KBUFFER_FL_OLD_FORMAT = (1<<3),
+};
+
+#define ENDIAN_MASK (KBUFFER_FL_HOST_BIG_ENDIAN | KBUFFER_FL_BIG_ENDIAN)
+
+/** kbuffer
+ * @timestamp - timestamp of current event
+ * @lost_events - # of lost events between this subbuffer and previous
+ * @flags - special flags of the kbuffer
+ * @subbuffer - pointer to the sub-buffer page
+ * @data - pointer to the start of data on the sub-buffer page
+ * @index - index from @data to the @curr event data
+ * @curr - offset from @data to the start of current event
+ * (includes metadata)
+ * @next - offset from @data to the start of next event
+ * @size - The size of data on @data
+ * @start - The offset from @subbuffer where @data lives
+ *
+ * @read_4 - Function to read 4 raw bytes (may swap)
+ * @read_8 - Function to read 8 raw bytes (may swap)
+ * @read_long - Function to read a long word (4 or 8 bytes with needed swap)
+ */
+struct kbuffer {
+ unsigned long long timestamp;
+ long long lost_events;
+ unsigned long flags;
+ void *subbuffer;
+ void *data;
+ unsigned int index;
+ unsigned int curr;
+ unsigned int next;
+ unsigned int size;
+ unsigned int start;
+
+ unsigned int (*read_4)(void *ptr);
+ unsigned long long (*read_8)(void *ptr);
+ unsigned long long (*read_long)(struct kbuffer *kbuf, void *ptr);
+ int (*next_event)(struct kbuffer *kbuf);
+};
+
+static void *zmalloc(size_t size)
+{
+ return calloc(1, size);
+}
+
+static int host_is_bigendian(void)
+{
+ unsigned char str[] = { 0x1, 0x2, 0x3, 0x4 };
+ unsigned int *ptr;
+
+ ptr = (unsigned int *)str;
+ return *ptr == 0x01020304;
+}
+
+static int do_swap(struct kbuffer *kbuf)
+{
+ return ((kbuf->flags & KBUFFER_FL_HOST_BIG_ENDIAN) + kbuf->flags) &
+ ENDIAN_MASK;
+}
+
+static unsigned long long __read_8(void *ptr)
+{
+ unsigned long long data = *(unsigned long long *)ptr;
+
+ return data;
+}
+
+static unsigned long long __read_8_sw(void *ptr)
+{
+ unsigned long long data = *(unsigned long long *)ptr;
+ unsigned long long swap;
+
+ swap = ((data & 0xffULL) << 56) |
+ ((data & (0xffULL << 8)) << 40) |
+ ((data & (0xffULL << 16)) << 24) |
+ ((data & (0xffULL << 24)) << 8) |
+ ((data & (0xffULL << 32)) >> 8) |
+ ((data & (0xffULL << 40)) >> 24) |
+ ((data & (0xffULL << 48)) >> 40) |
+ ((data & (0xffULL << 56)) >> 56);
+
+ return swap;
+}
+
+static unsigned int __read_4(void *ptr)
+{
+ unsigned int data = *(unsigned int *)ptr;
+
+ return data;
+}
+
+static unsigned int __read_4_sw(void *ptr)
+{
+ unsigned int data = *(unsigned int *)ptr;
+ unsigned int swap;
+
+ swap = ((data & 0xffULL) << 24) |
+ ((data & (0xffULL << 8)) << 8) |
+ ((data & (0xffULL << 16)) >> 8) |
+ ((data & (0xffULL << 24)) >> 24);
+
+ return swap;
+}
+
+static unsigned long long read_8(struct kbuffer *kbuf, void *ptr)
+{
+ return kbuf->read_8(ptr);
+}
+
+static unsigned int read_4(struct kbuffer *kbuf, void *ptr)
+{
+ return kbuf->read_4(ptr);
+}
+
+static unsigned long long __read_long_8(struct kbuffer *kbuf, void *ptr)
+{
+ return kbuf->read_8(ptr);
+}
+
+static unsigned long long __read_long_4(struct kbuffer *kbuf, void *ptr)
+{
+ return kbuf->read_4(ptr);
+}
+
+static unsigned long long read_long(struct kbuffer *kbuf, void *ptr)
+{
+ return kbuf->read_long(kbuf, ptr);
+}
+
+static int calc_index(struct kbuffer *kbuf, void *ptr)
+{
+ return (unsigned long)ptr - (unsigned long)kbuf->data;
+}
+
+static int __next_event(struct kbuffer *kbuf);
+
+/**
+ * kbuffer_alloc - allocat a new kbuffer
+ * @size; enum to denote size of word
+ * @endian: enum to denote endianness
+ *
+ * Allocates and returns a new kbuffer.
+ */
+struct kbuffer *
+kbuffer_alloc(enum kbuffer_long_size size, enum kbuffer_endian endian)
+{
+ struct kbuffer *kbuf;
+ int flags = 0;
+
+ switch (size) {
+ case KBUFFER_LSIZE_4:
+ break;
+ case KBUFFER_LSIZE_8:
+ flags |= KBUFFER_FL_LONG_8;
+ break;
+ default:
+ return NULL;
+ }
+
+ switch (endian) {
+ case KBUFFER_ENDIAN_LITTLE:
+ break;
+ case KBUFFER_ENDIAN_BIG:
+ flags |= KBUFFER_FL_BIG_ENDIAN;
+ break;
+ default:
+ return NULL;
+ }
+
+ kbuf = zmalloc(sizeof(*kbuf));
+ if (!kbuf)
+ return NULL;
+
+ kbuf->flags = flags;
+
+ if (host_is_bigendian())
+ kbuf->flags |= KBUFFER_FL_HOST_BIG_ENDIAN;
+
+ if (do_swap(kbuf)) {
+ kbuf->read_8 = __read_8_sw;
+ kbuf->read_4 = __read_4_sw;
+ } else {
+ kbuf->read_8 = __read_8;
+ kbuf->read_4 = __read_4;
+ }
+
+ if (kbuf->flags & KBUFFER_FL_LONG_8)
+ kbuf->read_long = __read_long_8;
+ else
+ kbuf->read_long = __read_long_4;
+
+ /* May be changed by kbuffer_set_old_format() */
+ kbuf->next_event = __next_event;
+
+ return kbuf;
+}
+
+/** kbuffer_free - free an allocated kbuffer
+ * @kbuf: The kbuffer to free
+ *
+ * Can take NULL as a parameter.
+ */
+void kbuffer_free(struct kbuffer *kbuf)
+{
+ free(kbuf);
+}
+
+static unsigned int type4host(struct kbuffer *kbuf,
+ unsigned int type_len_ts)
+{
+ if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN)
+ return (type_len_ts >> 29) & 3;
+ else
+ return type_len_ts & 3;
+}
+
+static unsigned int len4host(struct kbuffer *kbuf,
+ unsigned int type_len_ts)
+{
+ if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN)
+ return (type_len_ts >> 27) & 7;
+ else
+ return (type_len_ts >> 2) & 7;
+}
+
+static unsigned int type_len4host(struct kbuffer *kbuf,
+ unsigned int type_len_ts)
+{
+ if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN)
+ return (type_len_ts >> 27) & ((1 << 5) - 1);
+ else
+ return type_len_ts & ((1 << 5) - 1);
+}
+
+static unsigned int ts4host(struct kbuffer *kbuf,
+ unsigned int type_len_ts)
+{
+ if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN)
+ return type_len_ts & ((1 << 27) - 1);
+ else
+ return type_len_ts >> 5;
+}
+
+/*
+ * Linux 2.6.30 and earlier (not much ealier) had a different
+ * ring buffer format. It should be obsolete, but we handle it anyway.
+ */
+enum old_ring_buffer_type {
+ OLD_RINGBUF_TYPE_PADDING,
+ OLD_RINGBUF_TYPE_TIME_EXTEND,
+ OLD_RINGBUF_TYPE_TIME_STAMP,
+ OLD_RINGBUF_TYPE_DATA,
+};
+
+static unsigned int old_update_pointers(struct kbuffer *kbuf)
+{
+ unsigned long long extend;
+ unsigned int type_len_ts;
+ unsigned int type;
+ unsigned int len;
+ unsigned int delta;
+ unsigned int length;
+ void *ptr = kbuf->data + kbuf->curr;
+
+ type_len_ts = read_4(kbuf, ptr);
+ ptr += 4;
+
+ type = type4host(kbuf, type_len_ts);
+ len = len4host(kbuf, type_len_ts);
+ delta = ts4host(kbuf, type_len_ts);
+
+ switch (type) {
+ case OLD_RINGBUF_TYPE_PADDING:
+ kbuf->next = kbuf->size;
+ return 0;
+
+ case OLD_RINGBUF_TYPE_TIME_EXTEND:
+ extend = read_4(kbuf, ptr);
+ extend <<= TS_SHIFT;
+ extend += delta;
+ delta = extend;
+ ptr += 4;
+ break;
+
+ case OLD_RINGBUF_TYPE_TIME_STAMP:
+ /* should never happen! */
+ kbuf->curr = kbuf->size;
+ kbuf->next = kbuf->size;
+ kbuf->index = kbuf->size;
+ return -1;
+ default:
+ if (len)
+ length = len * 4;
+ else {
+ length = read_4(kbuf, ptr);
+ length -= 4;
+ ptr += 4;
+ }
+ break;
+ }
+
+ kbuf->timestamp += delta;
+ kbuf->index = calc_index(kbuf, ptr);
+ kbuf->next = kbuf->index + length;
+
+ return type;
+}
+
+static int __old_next_event(struct kbuffer *kbuf)
+{
+ int type;
+
+ do {
+ kbuf->curr = kbuf->next;
+ if (kbuf->next >= kbuf->size)
+ return -1;
+ type = old_update_pointers(kbuf);
+ } while (type == OLD_RINGBUF_TYPE_TIME_EXTEND || type == OLD_RINGBUF_TYPE_PADDING);
+
+ return 0;
+}
+
+static unsigned int
+translate_data(struct kbuffer *kbuf, void *data, void **rptr,
+ unsigned long long *delta, int *length)
+{
+ unsigned long long extend;
+ unsigned int type_len_ts;
+ unsigned int type_len;
+
+ type_len_ts = read_4(kbuf, data);
+ data += 4;
+
+ type_len = type_len4host(kbuf, type_len_ts);
+ *delta = ts4host(kbuf, type_len_ts);
+
+ switch (type_len) {
+ case KBUFFER_TYPE_PADDING:
+ *length = read_4(kbuf, data);
+ data += *length;
+ break;
+
+ case KBUFFER_TYPE_TIME_EXTEND:
+ extend = read_4(kbuf, data);
+ data += 4;
+ extend <<= TS_SHIFT;
+ extend += *delta;
+ *delta = extend;
+ *length = 0;
+ break;
+
+ case KBUFFER_TYPE_TIME_STAMP:
+ data += 12;
+ *length = 0;
+ break;
+ case 0:
+ *length = read_4(kbuf, data) - 4;
+ *length = (*length + 3) & ~3;
+ data += 4;
+ break;
+ default:
+ *length = type_len * 4;
+ break;
+ }
+
+ *rptr = data;
+
+ return type_len;
+}
+
+static unsigned int update_pointers(struct kbuffer *kbuf)
+{
+ unsigned long long delta;
+ unsigned int type_len;
+ int length;
+ void *ptr = kbuf->data + kbuf->curr;
+
+ type_len = translate_data(kbuf, ptr, &ptr, &delta, &length);
+
+ kbuf->timestamp += delta;
+ kbuf->index = calc_index(kbuf, ptr);
+ kbuf->next = kbuf->index + length;
+
+ return type_len;
+}
+
+/**
+ * kbuffer_translate_data - read raw data to get a record
+ * @swap: Set to 1 if bytes in words need to be swapped when read
+ * @data: The raw data to read
+ * @size: Address to store the size of the event data.
+ *
+ * Returns a pointer to the event data. To determine the entire
+ * record size (record metadata + data) just add the difference between
+ * @data and the returned value to @size.
+ */
+void *kbuffer_translate_data(int swap, void *data, unsigned int *size)
+{
+ unsigned long long delta;
+ struct kbuffer kbuf;
+ int type_len;
+ int length;
+ void *ptr;
+
+ if (swap) {
+ kbuf.read_8 = __read_8_sw;
+ kbuf.read_4 = __read_4_sw;
+ kbuf.flags = host_is_bigendian() ? 0 : KBUFFER_FL_BIG_ENDIAN;
+ } else {
+ kbuf.read_8 = __read_8;
+ kbuf.read_4 = __read_4;
+ kbuf.flags = host_is_bigendian() ? KBUFFER_FL_BIG_ENDIAN: 0;
+ }
+
+ type_len = translate_data(&kbuf, data, &ptr, &delta, &length);
+ switch (type_len) {
+ case KBUFFER_TYPE_PADDING:
+ case KBUFFER_TYPE_TIME_EXTEND:
+ case KBUFFER_TYPE_TIME_STAMP:
+ return NULL;
+ };
+
+ *size = length;
+
+ return ptr;
+}
+
+static int __next_event(struct kbuffer *kbuf)
+{
+ int type;
+
+ do {
+ kbuf->curr = kbuf->next;
+ if (kbuf->next >= kbuf->size)
+ return -1;
+ type = update_pointers(kbuf);
+ } while (type == KBUFFER_TYPE_TIME_EXTEND || type == KBUFFER_TYPE_PADDING);
+
+ return 0;
+}
+
+static int next_event(struct kbuffer *kbuf)
+{
+ return kbuf->next_event(kbuf);
+}
+
+/**
+ * kbuffer_next_event - increment the current pointer
+ * @kbuf: The kbuffer to read
+ * @ts: Address to store the next record's timestamp (may be NULL to ignore)
+ *
+ * Increments the pointers into the subbuffer of the kbuffer to point to the
+ * next event so that the next kbuffer_read_event() will return a
+ * new event.
+ *
+ * Returns the data of the next event if a new event exists on the subbuffer,
+ * NULL otherwise.
+ */
+void *kbuffer_next_event(struct kbuffer *kbuf, unsigned long long *ts)
+{
+ int ret;
+
+ if (!kbuf || !kbuf->subbuffer)
+ return NULL;
+
+ ret = next_event(kbuf);
+ if (ret < 0)
+ return NULL;
+
+ if (ts)
+ *ts = kbuf->timestamp;
+
+ return kbuf->data + kbuf->index;
+}
+
+/**
+ * kbuffer_load_subbuffer - load a new subbuffer into the kbuffer
+ * @kbuf: The kbuffer to load
+ * @subbuffer: The subbuffer to load into @kbuf.
+ *
+ * Load a new subbuffer (page) into @kbuf. This will reset all
+ * the pointers and update the @kbuf timestamp. The next read will
+ * return the first event on @subbuffer.
+ *
+ * Returns 0 on succes, -1 otherwise.
+ */
+int kbuffer_load_subbuffer(struct kbuffer *kbuf, void *subbuffer)
+{
+ unsigned long long flags;
+ void *ptr = subbuffer;
+
+ if (!kbuf || !subbuffer)
+ return -1;
+
+ kbuf->subbuffer = subbuffer;
+
+ kbuf->timestamp = read_8(kbuf, ptr);
+ ptr += 8;
+
+ kbuf->curr = 0;
+
+ if (kbuf->flags & KBUFFER_FL_LONG_8)
+ kbuf->start = 16;
+ else
+ kbuf->start = 12;
+
+ kbuf->data = subbuffer + kbuf->start;
+
+ flags = read_long(kbuf, ptr);
+ kbuf->size = (unsigned int)flags & COMMIT_MASK;
+
+ if (flags & MISSING_EVENTS) {
+ if (flags & MISSING_STORED) {
+ ptr = kbuf->data + kbuf->size;
+ kbuf->lost_events = read_long(kbuf, ptr);
+ } else
+ kbuf->lost_events = -1;
+ } else
+ kbuf->lost_events = 0;
+
+ kbuf->index = 0;
+ kbuf->next = 0;
+
+ next_event(kbuf);
+
+ return 0;
+}
+
+/**
+ * kbuffer_read_event - read the next event in the kbuffer subbuffer
+ * @kbuf: The kbuffer to read from
+ * @ts: The address to store the timestamp of the event (may be NULL to ignore)
+ *
+ * Returns a pointer to the data part of the current event.
+ * NULL if no event is left on the subbuffer.
+ */
+void *kbuffer_read_event(struct kbuffer *kbuf, unsigned long long *ts)
+{
+ if (!kbuf || !kbuf->subbuffer)
+ return NULL;
+
+ if (kbuf->curr >= kbuf->size)
+ return NULL;
+
+ if (ts)
+ *ts = kbuf->timestamp;
+ return kbuf->data + kbuf->index;
+}
+
+/**
+ * kbuffer_timestamp - Return the timestamp of the current event
+ * @kbuf: The kbuffer to read from
+ *
+ * Returns the timestamp of the current (next) event.
+ */
+unsigned long long kbuffer_timestamp(struct kbuffer *kbuf)
+{
+ return kbuf->timestamp;
+}
+
+/**
+ * kbuffer_read_at_offset - read the event that is at offset
+ * @kbuf: The kbuffer to read from
+ * @offset: The offset into the subbuffer
+ * @ts: The address to store the timestamp of the event (may be NULL to ignore)
+ *
+ * The @offset must be an index from the @kbuf subbuffer beginning.
+ * If @offset is bigger than the stored subbuffer, NULL will be returned.
+ *
+ * Returns the data of the record that is at @offset. Note, @offset does
+ * not need to be the start of the record, the offset just needs to be
+ * in the record (or beginning of it).
+ *
+ * Note, the kbuf timestamp and pointers are updated to the
+ * returned record. That is, kbuffer_read_event() will return the same
+ * data and timestamp, and kbuffer_next_event() will increment from
+ * this record.
+ */
+void *kbuffer_read_at_offset(struct kbuffer *kbuf, int offset,
+ unsigned long long *ts)
+{
+ void *data;
+
+ if (offset < kbuf->start)
+ offset = 0;
+ else
+ offset -= kbuf->start;
+
+ /* Reset the buffer */
+ kbuffer_load_subbuffer(kbuf, kbuf->subbuffer);
+
+ while (kbuf->curr < offset) {
+ data = kbuffer_next_event(kbuf, ts);
+ if (!data)
+ break;
+ }
+
+ return data;
+}
+
+/**
+ * kbuffer_subbuffer_size - the size of the loaded subbuffer
+ * @kbuf: The kbuffer to read from
+ *
+ * Returns the size of the subbuffer. Note, this size is
+ * where the last event resides. The stored subbuffer may actually be
+ * bigger due to padding and such.
+ */
+int kbuffer_subbuffer_size(struct kbuffer *kbuf)
+{
+ return kbuf->size;
+}
+
+/**
+ * kbuffer_curr_index - Return the index of the record
+ * @kbuf: The kbuffer to read from
+ *
+ * Returns the index from the start of the data part of
+ * the subbuffer to the current location. Note this is not
+ * from the start of the subbuffer. An index of zero will
+ * point to the first record. Use kbuffer_curr_offset() for
+ * the actually offset (that can be used by kbuffer_read_at_offset())
+ */
+int kbuffer_curr_index(struct kbuffer *kbuf)
+{
+ return kbuf->curr;
+}
+
+/**
+ * kbuffer_curr_offset - Return the offset of the record
+ * @kbuf: The kbuffer to read from
+ *
+ * Returns the offset from the start of the subbuffer to the
+ * current location.
+ */
+int kbuffer_curr_offset(struct kbuffer *kbuf)
+{
+ return kbuf->curr + kbuf->start;
+}
+
+/**
+ * kbuffer_event_size - return the size of the event data
+ * @kbuf: The kbuffer to read
+ *
+ * Returns the size of the event data (the payload not counting
+ * the meta data of the record) of the current event.
+ */
+int kbuffer_event_size(struct kbuffer *kbuf)
+{
+ return kbuf->next - kbuf->index;
+}
+
+/**
+ * kbuffer_curr_size - return the size of the entire record
+ * @kbuf: The kbuffer to read
+ *
+ * Returns the size of the entire record (meta data and payload)
+ * of the current event.
+ */
+int kbuffer_curr_size(struct kbuffer *kbuf)
+{
+ return kbuf->next - kbuf->curr;
+}
+
+/**
+ * kbuffer_missed_events - return the # of missed events from last event.
+ * @kbuf: The kbuffer to read from
+ *
+ * Returns the # of missed events (if recorded) before the current
+ * event. Note, only events on the beginning of a subbuffer can
+ * have missed events, all other events within the buffer will be
+ * zero.
+ */
+int kbuffer_missed_events(struct kbuffer *kbuf)
+{
+ /* Only the first event can have missed events */
+ if (kbuf->curr)
+ return 0;
+
+ return kbuf->lost_events;
+}
+
+/**
+ * kbuffer_set_old_forma - set the kbuffer to use the old format parsing
+ * @kbuf: The kbuffer to set
+ *
+ * This is obsolete (or should be). The first kernels to use the
+ * new ring buffer had a slightly different ring buffer format
+ * (2.6.30 and earlier). It is still somewhat supported by kbuffer,
+ * but should not be counted on in the future.
+ */
+void kbuffer_set_old_format(struct kbuffer *kbuf)
+{
+ kbuf->flags |= KBUFFER_FL_OLD_FORMAT;
+
+ kbuf->next_event = __old_next_event;
+}
diff --git a/tools/lib/traceevent/kbuffer.h b/tools/lib/traceevent/kbuffer.h
new file mode 100644
index 00000000000..c831f64b17a
--- /dev/null
+++ b/tools/lib/traceevent/kbuffer.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2012 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#ifndef _KBUFFER_H
+#define _KBUFFER_H
+
+#ifndef TS_SHIFT
+#define TS_SHIFT 27
+#endif
+
+enum kbuffer_endian {
+ KBUFFER_ENDIAN_BIG,
+ KBUFFER_ENDIAN_LITTLE,
+};
+
+enum kbuffer_long_size {
+ KBUFFER_LSIZE_4,
+ KBUFFER_LSIZE_8,
+};
+
+enum {
+ KBUFFER_TYPE_PADDING = 29,
+ KBUFFER_TYPE_TIME_EXTEND = 30,
+ KBUFFER_TYPE_TIME_STAMP = 31,
+};
+
+struct kbuffer;
+
+struct kbuffer *kbuffer_alloc(enum kbuffer_long_size size, enum kbuffer_endian endian);
+void kbuffer_free(struct kbuffer *kbuf);
+int kbuffer_load_subbuffer(struct kbuffer *kbuf, void *subbuffer);
+void *kbuffer_read_event(struct kbuffer *kbuf, unsigned long long *ts);
+void *kbuffer_next_event(struct kbuffer *kbuf, unsigned long long *ts);
+unsigned long long kbuffer_timestamp(struct kbuffer *kbuf);
+
+void *kbuffer_translate_data(int swap, void *data, unsigned int *size);
+
+void *kbuffer_read_at_offset(struct kbuffer *kbuf, int offset, unsigned long long *ts);
+
+int kbuffer_curr_index(struct kbuffer *kbuf);
+
+int kbuffer_curr_offset(struct kbuffer *kbuf);
+int kbuffer_curr_size(struct kbuffer *kbuf);
+int kbuffer_event_size(struct kbuffer *kbuf);
+int kbuffer_missed_events(struct kbuffer *kbuf);
+int kbuffer_subbuffer_size(struct kbuffer *kbuf);
+
+void kbuffer_set_old_format(struct kbuffer *kbuf);
+
+#endif /* _K_BUFFER_H */
diff --git a/tools/lib/traceevent/parse-filter.c b/tools/lib/traceevent/parse-filter.c
new file mode 100644
index 00000000000..b50234402fc
--- /dev/null
+++ b/tools/lib/traceevent/parse-filter.c
@@ -0,0 +1,2432 @@
+/*
+ * Copyright (C) 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+#include <sys/types.h>
+
+#include "event-parse.h"
+#include "event-utils.h"
+
+#define COMM "COMM"
+
+static struct format_field comm = {
+ .name = "COMM",
+};
+
+struct event_list {
+ struct event_list *next;
+ struct event_format *event;
+};
+
+static void show_error(char *error_buf, const char *fmt, ...)
+{
+ unsigned long long index;
+ const char *input;
+ va_list ap;
+ int len;
+ int i;
+
+ input = pevent_get_input_buf();
+ index = pevent_get_input_buf_ptr();
+ len = input ? strlen(input) : 0;
+
+ if (len) {
+ strcpy(error_buf, input);
+ error_buf[len] = '\n';
+ for (i = 1; i < len && i < index; i++)
+ error_buf[len+i] = ' ';
+ error_buf[len + i] = '^';
+ error_buf[len + i + 1] = '\n';
+ len += i+2;
+ }
+
+ va_start(ap, fmt);
+ vsnprintf(error_buf + len, PEVENT_FILTER_ERROR_BUFSZ - len, fmt, ap);
+ va_end(ap);
+}
+
+static void free_token(char *token)
+{
+ pevent_free_token(token);
+}
+
+static enum event_type read_token(char **tok)
+{
+ enum event_type type;
+ char *token = NULL;
+
+ do {
+ free_token(token);
+ type = pevent_read_token(&token);
+ } while (type == EVENT_NEWLINE || type == EVENT_SPACE);
+
+ /* If token is = or ! check to see if the next char is ~ */
+ if (token &&
+ (strcmp(token, "=") == 0 || strcmp(token, "!") == 0) &&
+ pevent_peek_char() == '~') {
+ /* append it */
+ *tok = malloc(3);
+ if (*tok == NULL) {
+ free_token(token);
+ return EVENT_ERROR;
+ }
+ sprintf(*tok, "%c%c", *token, '~');
+ free_token(token);
+ /* Now remove the '~' from the buffer */
+ pevent_read_token(&token);
+ free_token(token);
+ } else
+ *tok = token;
+
+ return type;
+}
+
+static int filter_cmp(const void *a, const void *b)
+{
+ const struct filter_type *ea = a;
+ const struct filter_type *eb = b;
+
+ if (ea->event_id < eb->event_id)
+ return -1;
+
+ if (ea->event_id > eb->event_id)
+ return 1;
+
+ return 0;
+}
+
+static struct filter_type *
+find_filter_type(struct event_filter *filter, int id)
+{
+ struct filter_type *filter_type;
+ struct filter_type key;
+
+ key.event_id = id;
+
+ filter_type = bsearch(&key, filter->event_filters,
+ filter->filters,
+ sizeof(*filter->event_filters),
+ filter_cmp);
+
+ return filter_type;
+}
+
+static struct filter_type *
+add_filter_type(struct event_filter *filter, int id)
+{
+ struct filter_type *filter_type;
+ int i;
+
+ filter_type = find_filter_type(filter, id);
+ if (filter_type)
+ return filter_type;
+
+ filter_type = realloc(filter->event_filters,
+ sizeof(*filter->event_filters) *
+ (filter->filters + 1));
+ if (!filter_type)
+ return NULL;
+
+ filter->event_filters = filter_type;
+
+ for (i = 0; i < filter->filters; i++) {
+ if (filter->event_filters[i].event_id > id)
+ break;
+ }
+
+ if (i < filter->filters)
+ memmove(&filter->event_filters[i+1],
+ &filter->event_filters[i],
+ sizeof(*filter->event_filters) *
+ (filter->filters - i));
+
+ filter_type = &filter->event_filters[i];
+ filter_type->event_id = id;
+ filter_type->event = pevent_find_event(filter->pevent, id);
+ filter_type->filter = NULL;
+
+ filter->filters++;
+
+ return filter_type;
+}
+
+/**
+ * pevent_filter_alloc - create a new event filter
+ * @pevent: The pevent that this filter is associated with
+ */
+struct event_filter *pevent_filter_alloc(struct pevent *pevent)
+{
+ struct event_filter *filter;
+
+ filter = malloc(sizeof(*filter));
+ if (filter == NULL)
+ return NULL;
+
+ memset(filter, 0, sizeof(*filter));
+ filter->pevent = pevent;
+ pevent_ref(pevent);
+
+ return filter;
+}
+
+static struct filter_arg *allocate_arg(void)
+{
+ return calloc(1, sizeof(struct filter_arg));
+}
+
+static void free_arg(struct filter_arg *arg)
+{
+ if (!arg)
+ return;
+
+ switch (arg->type) {
+ case FILTER_ARG_NONE:
+ case FILTER_ARG_BOOLEAN:
+ break;
+
+ case FILTER_ARG_NUM:
+ free_arg(arg->num.left);
+ free_arg(arg->num.right);
+ break;
+
+ case FILTER_ARG_EXP:
+ free_arg(arg->exp.left);
+ free_arg(arg->exp.right);
+ break;
+
+ case FILTER_ARG_STR:
+ free(arg->str.val);
+ regfree(&arg->str.reg);
+ free(arg->str.buffer);
+ break;
+
+ case FILTER_ARG_VALUE:
+ if (arg->value.type == FILTER_STRING ||
+ arg->value.type == FILTER_CHAR)
+ free(arg->value.str);
+ break;
+
+ case FILTER_ARG_OP:
+ free_arg(arg->op.left);
+ free_arg(arg->op.right);
+ default:
+ break;
+ }
+
+ free(arg);
+}
+
+static int add_event(struct event_list **events,
+ struct event_format *event)
+{
+ struct event_list *list;
+
+ list = malloc(sizeof(*list));
+ if (list == NULL)
+ return -1;
+
+ list->next = *events;
+ *events = list;
+ list->event = event;
+ return 0;
+}
+
+static int event_match(struct event_format *event,
+ regex_t *sreg, regex_t *ereg)
+{
+ if (sreg) {
+ return !regexec(sreg, event->system, 0, NULL, 0) &&
+ !regexec(ereg, event->name, 0, NULL, 0);
+ }
+
+ return !regexec(ereg, event->system, 0, NULL, 0) ||
+ !regexec(ereg, event->name, 0, NULL, 0);
+}
+
+static enum pevent_errno
+find_event(struct pevent *pevent, struct event_list **events,
+ char *sys_name, char *event_name)
+{
+ struct event_format *event;
+ regex_t ereg;
+ regex_t sreg;
+ int match = 0;
+ int fail = 0;
+ char *reg;
+ int ret;
+ int i;
+
+ if (!event_name) {
+ /* if no name is given, then swap sys and name */
+ event_name = sys_name;
+ sys_name = NULL;
+ }
+
+ reg = malloc(strlen(event_name) + 3);
+ if (reg == NULL)
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+
+ sprintf(reg, "^%s$", event_name);
+
+ ret = regcomp(&ereg, reg, REG_ICASE|REG_NOSUB);
+ free(reg);
+
+ if (ret)
+ return PEVENT_ERRNO__INVALID_EVENT_NAME;
+
+ if (sys_name) {
+ reg = malloc(strlen(sys_name) + 3);
+ if (reg == NULL) {
+ regfree(&ereg);
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ }
+
+ sprintf(reg, "^%s$", sys_name);
+ ret = regcomp(&sreg, reg, REG_ICASE|REG_NOSUB);
+ free(reg);
+ if (ret) {
+ regfree(&ereg);
+ return PEVENT_ERRNO__INVALID_EVENT_NAME;
+ }
+ }
+
+ for (i = 0; i < pevent->nr_events; i++) {
+ event = pevent->events[i];
+ if (event_match(event, sys_name ? &sreg : NULL, &ereg)) {
+ match = 1;
+ if (add_event(events, event) < 0) {
+ fail = 1;
+ break;
+ }
+ }
+ }
+
+ regfree(&ereg);
+ if (sys_name)
+ regfree(&sreg);
+
+ if (!match)
+ return PEVENT_ERRNO__EVENT_NOT_FOUND;
+ if (fail)
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+
+ return 0;
+}
+
+static void free_events(struct event_list *events)
+{
+ struct event_list *event;
+
+ while (events) {
+ event = events;
+ events = events->next;
+ free(event);
+ }
+}
+
+static enum pevent_errno
+create_arg_item(struct event_format *event, const char *token,
+ enum event_type type, struct filter_arg **parg, char *error_str)
+{
+ struct format_field *field;
+ struct filter_arg *arg;
+
+ arg = allocate_arg();
+ if (arg == NULL) {
+ show_error(error_str, "failed to allocate filter arg");
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ }
+
+ switch (type) {
+
+ case EVENT_SQUOTE:
+ case EVENT_DQUOTE:
+ arg->type = FILTER_ARG_VALUE;
+ arg->value.type =
+ type == EVENT_DQUOTE ? FILTER_STRING : FILTER_CHAR;
+ arg->value.str = strdup(token);
+ if (!arg->value.str) {
+ free_arg(arg);
+ show_error(error_str, "failed to allocate string filter arg");
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ }
+ break;
+ case EVENT_ITEM:
+ /* if it is a number, then convert it */
+ if (isdigit(token[0])) {
+ arg->type = FILTER_ARG_VALUE;
+ arg->value.type = FILTER_NUMBER;
+ arg->value.val = strtoull(token, NULL, 0);
+ break;
+ }
+ /* Consider this a field */
+ field = pevent_find_any_field(event, token);
+ if (!field) {
+ if (strcmp(token, COMM) != 0) {
+ /* not a field, Make it false */
+ arg->type = FILTER_ARG_BOOLEAN;
+ arg->boolean.value = FILTER_FALSE;
+ break;
+ }
+ /* If token is 'COMM' then it is special */
+ field = &comm;
+ }
+ arg->type = FILTER_ARG_FIELD;
+ arg->field.field = field;
+ break;
+ default:
+ free_arg(arg);
+ show_error(error_str, "expected a value but found %s", token);
+ return PEVENT_ERRNO__UNEXPECTED_TYPE;
+ }
+ *parg = arg;
+ return 0;
+}
+
+static struct filter_arg *
+create_arg_op(enum filter_op_type btype)
+{
+ struct filter_arg *arg;
+
+ arg = allocate_arg();
+ if (!arg)
+ return NULL;
+
+ arg->type = FILTER_ARG_OP;
+ arg->op.type = btype;
+
+ return arg;
+}
+
+static struct filter_arg *
+create_arg_exp(enum filter_exp_type etype)
+{
+ struct filter_arg *arg;
+
+ arg = allocate_arg();
+ if (!arg)
+ return NULL;
+
+ arg->type = FILTER_ARG_EXP;
+ arg->op.type = etype;
+
+ return arg;
+}
+
+static struct filter_arg *
+create_arg_cmp(enum filter_exp_type etype)
+{
+ struct filter_arg *arg;
+
+ arg = allocate_arg();
+ if (!arg)
+ return NULL;
+
+ /* Use NUM and change if necessary */
+ arg->type = FILTER_ARG_NUM;
+ arg->op.type = etype;
+
+ return arg;
+}
+
+static enum pevent_errno
+add_right(struct filter_arg *op, struct filter_arg *arg, char *error_str)
+{
+ struct filter_arg *left;
+ char *str;
+ int op_type;
+ int ret;
+
+ switch (op->type) {
+ case FILTER_ARG_EXP:
+ if (op->exp.right)
+ goto out_fail;
+ op->exp.right = arg;
+ break;
+
+ case FILTER_ARG_OP:
+ if (op->op.right)
+ goto out_fail;
+ op->op.right = arg;
+ break;
+
+ case FILTER_ARG_NUM:
+ if (op->op.right)
+ goto out_fail;
+ /*
+ * The arg must be num, str, or field
+ */
+ switch (arg->type) {
+ case FILTER_ARG_VALUE:
+ case FILTER_ARG_FIELD:
+ break;
+ default:
+ show_error(error_str, "Illegal rvalue");
+ return PEVENT_ERRNO__ILLEGAL_RVALUE;
+ }
+
+ /*
+ * Depending on the type, we may need to
+ * convert this to a string or regex.
+ */
+ switch (arg->value.type) {
+ case FILTER_CHAR:
+ /*
+ * A char should be converted to number if
+ * the string is 1 byte, and the compare
+ * is not a REGEX.
+ */
+ if (strlen(arg->value.str) == 1 &&
+ op->num.type != FILTER_CMP_REGEX &&
+ op->num.type != FILTER_CMP_NOT_REGEX) {
+ arg->value.type = FILTER_NUMBER;
+ goto do_int;
+ }
+ /* fall through */
+ case FILTER_STRING:
+
+ /* convert op to a string arg */
+ op_type = op->num.type;
+ left = op->num.left;
+ str = arg->value.str;
+
+ /* reset the op for the new field */
+ memset(op, 0, sizeof(*op));
+
+ /*
+ * If left arg was a field not found then
+ * NULL the entire op.
+ */
+ if (left->type == FILTER_ARG_BOOLEAN) {
+ free_arg(left);
+ free_arg(arg);
+ op->type = FILTER_ARG_BOOLEAN;
+ op->boolean.value = FILTER_FALSE;
+ break;
+ }
+
+ /* Left arg must be a field */
+ if (left->type != FILTER_ARG_FIELD) {
+ show_error(error_str,
+ "Illegal lvalue for string comparison");
+ return PEVENT_ERRNO__ILLEGAL_LVALUE;
+ }
+
+ /* Make sure this is a valid string compare */
+ switch (op_type) {
+ case FILTER_CMP_EQ:
+ op_type = FILTER_CMP_MATCH;
+ break;
+ case FILTER_CMP_NE:
+ op_type = FILTER_CMP_NOT_MATCH;
+ break;
+
+ case FILTER_CMP_REGEX:
+ case FILTER_CMP_NOT_REGEX:
+ ret = regcomp(&op->str.reg, str, REG_ICASE|REG_NOSUB);
+ if (ret) {
+ show_error(error_str,
+ "RegEx '%s' did not compute",
+ str);
+ return PEVENT_ERRNO__INVALID_REGEX;
+ }
+ break;
+ default:
+ show_error(error_str,
+ "Illegal comparison for string");
+ return PEVENT_ERRNO__ILLEGAL_STRING_CMP;
+ }
+
+ op->type = FILTER_ARG_STR;
+ op->str.type = op_type;
+ op->str.field = left->field.field;
+ op->str.val = strdup(str);
+ if (!op->str.val) {
+ show_error(error_str, "Failed to allocate string filter");
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ }
+ /*
+ * Need a buffer to copy data for tests
+ */
+ op->str.buffer = malloc(op->str.field->size + 1);
+ if (!op->str.buffer) {
+ show_error(error_str, "Failed to allocate string filter");
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ }
+ /* Null terminate this buffer */
+ op->str.buffer[op->str.field->size] = 0;
+
+ /* We no longer have left or right args */
+ free_arg(arg);
+ free_arg(left);
+
+ break;
+
+ case FILTER_NUMBER:
+
+ do_int:
+ switch (op->num.type) {
+ case FILTER_CMP_REGEX:
+ case FILTER_CMP_NOT_REGEX:
+ show_error(error_str,
+ "Op not allowed with integers");
+ return PEVENT_ERRNO__ILLEGAL_INTEGER_CMP;
+
+ default:
+ break;
+ }
+
+ /* numeric compare */
+ op->num.right = arg;
+ break;
+ default:
+ goto out_fail;
+ }
+ break;
+ default:
+ goto out_fail;
+ }
+
+ return 0;
+
+ out_fail:
+ show_error(error_str, "Syntax error");
+ return PEVENT_ERRNO__SYNTAX_ERROR;
+}
+
+static struct filter_arg *
+rotate_op_right(struct filter_arg *a, struct filter_arg *b)
+{
+ struct filter_arg *arg;
+
+ arg = a->op.right;
+ a->op.right = b;
+ return arg;
+}
+
+static enum pevent_errno add_left(struct filter_arg *op, struct filter_arg *arg)
+{
+ switch (op->type) {
+ case FILTER_ARG_EXP:
+ if (arg->type == FILTER_ARG_OP)
+ arg = rotate_op_right(arg, op);
+ op->exp.left = arg;
+ break;
+
+ case FILTER_ARG_OP:
+ op->op.left = arg;
+ break;
+ case FILTER_ARG_NUM:
+ if (arg->type == FILTER_ARG_OP)
+ arg = rotate_op_right(arg, op);
+
+ /* left arg of compares must be a field */
+ if (arg->type != FILTER_ARG_FIELD &&
+ arg->type != FILTER_ARG_BOOLEAN)
+ return PEVENT_ERRNO__INVALID_ARG_TYPE;
+ op->num.left = arg;
+ break;
+ default:
+ return PEVENT_ERRNO__INVALID_ARG_TYPE;
+ }
+ return 0;
+}
+
+enum op_type {
+ OP_NONE,
+ OP_BOOL,
+ OP_NOT,
+ OP_EXP,
+ OP_CMP,
+};
+
+static enum op_type process_op(const char *token,
+ enum filter_op_type *btype,
+ enum filter_cmp_type *ctype,
+ enum filter_exp_type *etype)
+{
+ *btype = FILTER_OP_NOT;
+ *etype = FILTER_EXP_NONE;
+ *ctype = FILTER_CMP_NONE;
+
+ if (strcmp(token, "&&") == 0)
+ *btype = FILTER_OP_AND;
+ else if (strcmp(token, "||") == 0)
+ *btype = FILTER_OP_OR;
+ else if (strcmp(token, "!") == 0)
+ return OP_NOT;
+
+ if (*btype != FILTER_OP_NOT)
+ return OP_BOOL;
+
+ /* Check for value expressions */
+ if (strcmp(token, "+") == 0) {
+ *etype = FILTER_EXP_ADD;
+ } else if (strcmp(token, "-") == 0) {
+ *etype = FILTER_EXP_SUB;
+ } else if (strcmp(token, "*") == 0) {
+ *etype = FILTER_EXP_MUL;
+ } else if (strcmp(token, "/") == 0) {
+ *etype = FILTER_EXP_DIV;
+ } else if (strcmp(token, "%") == 0) {
+ *etype = FILTER_EXP_MOD;
+ } else if (strcmp(token, ">>") == 0) {
+ *etype = FILTER_EXP_RSHIFT;
+ } else if (strcmp(token, "<<") == 0) {
+ *etype = FILTER_EXP_LSHIFT;
+ } else if (strcmp(token, "&") == 0) {
+ *etype = FILTER_EXP_AND;
+ } else if (strcmp(token, "|") == 0) {
+ *etype = FILTER_EXP_OR;
+ } else if (strcmp(token, "^") == 0) {
+ *etype = FILTER_EXP_XOR;
+ } else if (strcmp(token, "~") == 0)
+ *etype = FILTER_EXP_NOT;
+
+ if (*etype != FILTER_EXP_NONE)
+ return OP_EXP;
+
+ /* Check for compares */
+ if (strcmp(token, "==") == 0)
+ *ctype = FILTER_CMP_EQ;
+ else if (strcmp(token, "!=") == 0)
+ *ctype = FILTER_CMP_NE;
+ else if (strcmp(token, "<") == 0)
+ *ctype = FILTER_CMP_LT;
+ else if (strcmp(token, ">") == 0)
+ *ctype = FILTER_CMP_GT;
+ else if (strcmp(token, "<=") == 0)
+ *ctype = FILTER_CMP_LE;
+ else if (strcmp(token, ">=") == 0)
+ *ctype = FILTER_CMP_GE;
+ else if (strcmp(token, "=~") == 0)
+ *ctype = FILTER_CMP_REGEX;
+ else if (strcmp(token, "!~") == 0)
+ *ctype = FILTER_CMP_NOT_REGEX;
+ else
+ return OP_NONE;
+
+ return OP_CMP;
+}
+
+static int check_op_done(struct filter_arg *arg)
+{
+ switch (arg->type) {
+ case FILTER_ARG_EXP:
+ return arg->exp.right != NULL;
+
+ case FILTER_ARG_OP:
+ return arg->op.right != NULL;
+
+ case FILTER_ARG_NUM:
+ return arg->num.right != NULL;
+
+ case FILTER_ARG_STR:
+ /* A string conversion is always done */
+ return 1;
+
+ case FILTER_ARG_BOOLEAN:
+ /* field not found, is ok */
+ return 1;
+
+ default:
+ return 0;
+ }
+}
+
+enum filter_vals {
+ FILTER_VAL_NORM,
+ FILTER_VAL_FALSE,
+ FILTER_VAL_TRUE,
+};
+
+static enum pevent_errno
+reparent_op_arg(struct filter_arg *parent, struct filter_arg *old_child,
+ struct filter_arg *arg, char *error_str)
+{
+ struct filter_arg *other_child;
+ struct filter_arg **ptr;
+
+ if (parent->type != FILTER_ARG_OP &&
+ arg->type != FILTER_ARG_OP) {
+ show_error(error_str, "can not reparent other than OP");
+ return PEVENT_ERRNO__REPARENT_NOT_OP;
+ }
+
+ /* Get the sibling */
+ if (old_child->op.right == arg) {
+ ptr = &old_child->op.right;
+ other_child = old_child->op.left;
+ } else if (old_child->op.left == arg) {
+ ptr = &old_child->op.left;
+ other_child = old_child->op.right;
+ } else {
+ show_error(error_str, "Error in reparent op, find other child");
+ return PEVENT_ERRNO__REPARENT_FAILED;
+ }
+
+ /* Detach arg from old_child */
+ *ptr = NULL;
+
+ /* Check for root */
+ if (parent == old_child) {
+ free_arg(other_child);
+ *parent = *arg;
+ /* Free arg without recussion */
+ free(arg);
+ return 0;
+ }
+
+ if (parent->op.right == old_child)
+ ptr = &parent->op.right;
+ else if (parent->op.left == old_child)
+ ptr = &parent->op.left;
+ else {
+ show_error(error_str, "Error in reparent op");
+ return PEVENT_ERRNO__REPARENT_FAILED;
+ }
+
+ *ptr = arg;
+
+ free_arg(old_child);
+ return 0;
+}
+
+/* Returns either filter_vals (success) or pevent_errno (failfure) */
+static int test_arg(struct filter_arg *parent, struct filter_arg *arg,
+ char *error_str)
+{
+ int lval, rval;
+
+ switch (arg->type) {
+
+ /* bad case */
+ case FILTER_ARG_BOOLEAN:
+ return FILTER_VAL_FALSE + arg->boolean.value;
+
+ /* good cases: */
+ case FILTER_ARG_STR:
+ case FILTER_ARG_VALUE:
+ case FILTER_ARG_FIELD:
+ return FILTER_VAL_NORM;
+
+ case FILTER_ARG_EXP:
+ lval = test_arg(arg, arg->exp.left, error_str);
+ if (lval != FILTER_VAL_NORM)
+ return lval;
+ rval = test_arg(arg, arg->exp.right, error_str);
+ if (rval != FILTER_VAL_NORM)
+ return rval;
+ return FILTER_VAL_NORM;
+
+ case FILTER_ARG_NUM:
+ lval = test_arg(arg, arg->num.left, error_str);
+ if (lval != FILTER_VAL_NORM)
+ return lval;
+ rval = test_arg(arg, arg->num.right, error_str);
+ if (rval != FILTER_VAL_NORM)
+ return rval;
+ return FILTER_VAL_NORM;
+
+ case FILTER_ARG_OP:
+ if (arg->op.type != FILTER_OP_NOT) {
+ lval = test_arg(arg, arg->op.left, error_str);
+ switch (lval) {
+ case FILTER_VAL_NORM:
+ break;
+ case FILTER_VAL_TRUE:
+ if (arg->op.type == FILTER_OP_OR)
+ return FILTER_VAL_TRUE;
+ rval = test_arg(arg, arg->op.right, error_str);
+ if (rval != FILTER_VAL_NORM)
+ return rval;
+
+ return reparent_op_arg(parent, arg, arg->op.right,
+ error_str);
+
+ case FILTER_VAL_FALSE:
+ if (arg->op.type == FILTER_OP_AND)
+ return FILTER_VAL_FALSE;
+ rval = test_arg(arg, arg->op.right, error_str);
+ if (rval != FILTER_VAL_NORM)
+ return rval;
+
+ return reparent_op_arg(parent, arg, arg->op.right,
+ error_str);
+
+ default:
+ return lval;
+ }
+ }
+
+ rval = test_arg(arg, arg->op.right, error_str);
+ switch (rval) {
+ case FILTER_VAL_NORM:
+ default:
+ break;
+
+ case FILTER_VAL_TRUE:
+ if (arg->op.type == FILTER_OP_OR)
+ return FILTER_VAL_TRUE;
+ if (arg->op.type == FILTER_OP_NOT)
+ return FILTER_VAL_FALSE;
+
+ return reparent_op_arg(parent, arg, arg->op.left,
+ error_str);
+
+ case FILTER_VAL_FALSE:
+ if (arg->op.type == FILTER_OP_AND)
+ return FILTER_VAL_FALSE;
+ if (arg->op.type == FILTER_OP_NOT)
+ return FILTER_VAL_TRUE;
+
+ return reparent_op_arg(parent, arg, arg->op.left,
+ error_str);
+ }
+
+ return rval;
+ default:
+ show_error(error_str, "bad arg in filter tree");
+ return PEVENT_ERRNO__BAD_FILTER_ARG;
+ }
+ return FILTER_VAL_NORM;
+}
+
+/* Remove any unknown event fields */
+static int collapse_tree(struct filter_arg *arg,
+ struct filter_arg **arg_collapsed, char *error_str)
+{
+ int ret;
+
+ ret = test_arg(arg, arg, error_str);
+ switch (ret) {
+ case FILTER_VAL_NORM:
+ break;
+
+ case FILTER_VAL_TRUE:
+ case FILTER_VAL_FALSE:
+ free_arg(arg);
+ arg = allocate_arg();
+ if (arg) {
+ arg->type = FILTER_ARG_BOOLEAN;
+ arg->boolean.value = ret == FILTER_VAL_TRUE;
+ } else {
+ show_error(error_str, "Failed to allocate filter arg");
+ ret = PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ }
+ break;
+
+ default:
+ /* test_arg() already set the error_str */
+ free_arg(arg);
+ arg = NULL;
+ break;
+ }
+
+ *arg_collapsed = arg;
+ return ret;
+}
+
+static enum pevent_errno
+process_filter(struct event_format *event, struct filter_arg **parg,
+ char *error_str, int not)
+{
+ enum event_type type;
+ char *token = NULL;
+ struct filter_arg *current_op = NULL;
+ struct filter_arg *current_exp = NULL;
+ struct filter_arg *left_item = NULL;
+ struct filter_arg *arg = NULL;
+ enum op_type op_type;
+ enum filter_op_type btype;
+ enum filter_exp_type etype;
+ enum filter_cmp_type ctype;
+ enum pevent_errno ret;
+
+ *parg = NULL;
+
+ do {
+ free(token);
+ type = read_token(&token);
+ switch (type) {
+ case EVENT_SQUOTE:
+ case EVENT_DQUOTE:
+ case EVENT_ITEM:
+ ret = create_arg_item(event, token, type, &arg, error_str);
+ if (ret < 0)
+ goto fail;
+ if (!left_item)
+ left_item = arg;
+ else if (current_exp) {
+ ret = add_right(current_exp, arg, error_str);
+ if (ret < 0)
+ goto fail;
+ left_item = NULL;
+ /* Not's only one one expression */
+ if (not) {
+ arg = NULL;
+ if (current_op)
+ goto fail_syntax;
+ free(token);
+ *parg = current_exp;
+ return 0;
+ }
+ } else
+ goto fail_syntax;
+ arg = NULL;
+ break;
+
+ case EVENT_DELIM:
+ if (*token == ',') {
+ show_error(error_str, "Illegal token ','");
+ ret = PEVENT_ERRNO__ILLEGAL_TOKEN;
+ goto fail;
+ }
+
+ if (*token == '(') {
+ if (left_item) {
+ show_error(error_str,
+ "Open paren can not come after item");
+ ret = PEVENT_ERRNO__INVALID_PAREN;
+ goto fail;
+ }
+ if (current_exp) {
+ show_error(error_str,
+ "Open paren can not come after expression");
+ ret = PEVENT_ERRNO__INVALID_PAREN;
+ goto fail;
+ }
+
+ ret = process_filter(event, &arg, error_str, 0);
+ if (ret != PEVENT_ERRNO__UNBALANCED_PAREN) {
+ if (ret == 0) {
+ show_error(error_str,
+ "Unbalanced number of '('");
+ ret = PEVENT_ERRNO__UNBALANCED_PAREN;
+ }
+ goto fail;
+ }
+ ret = 0;
+
+ /* A not wants just one expression */
+ if (not) {
+ if (current_op)
+ goto fail_syntax;
+ *parg = arg;
+ return 0;
+ }
+
+ if (current_op)
+ ret = add_right(current_op, arg, error_str);
+ else
+ current_exp = arg;
+
+ if (ret < 0)
+ goto fail;
+
+ } else { /* ')' */
+ if (!current_op && !current_exp)
+ goto fail_syntax;
+
+ /* Make sure everything is finished at this level */
+ if (current_exp && !check_op_done(current_exp))
+ goto fail_syntax;
+ if (current_op && !check_op_done(current_op))
+ goto fail_syntax;
+
+ if (current_op)
+ *parg = current_op;
+ else
+ *parg = current_exp;
+ return PEVENT_ERRNO__UNBALANCED_PAREN;
+ }
+ break;
+
+ case EVENT_OP:
+ op_type = process_op(token, &btype, &ctype, &etype);
+
+ /* All expect a left arg except for NOT */
+ switch (op_type) {
+ case OP_BOOL:
+ /* Logic ops need a left expression */
+ if (!current_exp && !current_op)
+ goto fail_syntax;
+ /* fall through */
+ case OP_NOT:
+ /* logic only processes ops and exp */
+ if (left_item)
+ goto fail_syntax;
+ break;
+ case OP_EXP:
+ case OP_CMP:
+ if (!left_item)
+ goto fail_syntax;
+ break;
+ case OP_NONE:
+ show_error(error_str,
+ "Unknown op token %s", token);
+ ret = PEVENT_ERRNO__UNKNOWN_TOKEN;
+ goto fail;
+ }
+
+ ret = 0;
+ switch (op_type) {
+ case OP_BOOL:
+ arg = create_arg_op(btype);
+ if (arg == NULL)
+ goto fail_alloc;
+ if (current_op)
+ ret = add_left(arg, current_op);
+ else
+ ret = add_left(arg, current_exp);
+ current_op = arg;
+ current_exp = NULL;
+ break;
+
+ case OP_NOT:
+ arg = create_arg_op(btype);
+ if (arg == NULL)
+ goto fail_alloc;
+ if (current_op)
+ ret = add_right(current_op, arg, error_str);
+ if (ret < 0)
+ goto fail;
+ current_exp = arg;
+ ret = process_filter(event, &arg, error_str, 1);
+ if (ret < 0)
+ goto fail;
+ ret = add_right(current_exp, arg, error_str);
+ if (ret < 0)
+ goto fail;
+ break;
+
+ case OP_EXP:
+ case OP_CMP:
+ if (op_type == OP_EXP)
+ arg = create_arg_exp(etype);
+ else
+ arg = create_arg_cmp(ctype);
+ if (arg == NULL)
+ goto fail_alloc;
+
+ if (current_op)
+ ret = add_right(current_op, arg, error_str);
+ if (ret < 0)
+ goto fail;
+ ret = add_left(arg, left_item);
+ if (ret < 0) {
+ arg = NULL;
+ goto fail_syntax;
+ }
+ current_exp = arg;
+ break;
+ default:
+ break;
+ }
+ arg = NULL;
+ if (ret < 0)
+ goto fail_syntax;
+ break;
+ case EVENT_NONE:
+ break;
+ case EVENT_ERROR:
+ goto fail_alloc;
+ default:
+ goto fail_syntax;
+ }
+ } while (type != EVENT_NONE);
+
+ if (!current_op && !current_exp)
+ goto fail_syntax;
+
+ if (!current_op)
+ current_op = current_exp;
+
+ ret = collapse_tree(current_op, parg, error_str);
+ if (ret < 0)
+ goto fail;
+
+ *parg = current_op;
+
+ return 0;
+
+ fail_alloc:
+ show_error(error_str, "failed to allocate filter arg");
+ ret = PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ goto fail;
+ fail_syntax:
+ show_error(error_str, "Syntax error");
+ ret = PEVENT_ERRNO__SYNTAX_ERROR;
+ fail:
+ free_arg(current_op);
+ free_arg(current_exp);
+ free_arg(arg);
+ free(token);
+ return ret;
+}
+
+static enum pevent_errno
+process_event(struct event_format *event, const char *filter_str,
+ struct filter_arg **parg, char *error_str)
+{
+ int ret;
+
+ pevent_buffer_init(filter_str, strlen(filter_str));
+
+ ret = process_filter(event, parg, error_str, 0);
+ if (ret < 0)
+ return ret;
+
+ /* If parg is NULL, then make it into FALSE */
+ if (!*parg) {
+ *parg = allocate_arg();
+ if (*parg == NULL)
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+
+ (*parg)->type = FILTER_ARG_BOOLEAN;
+ (*parg)->boolean.value = FILTER_FALSE;
+ }
+
+ return 0;
+}
+
+static enum pevent_errno
+filter_event(struct event_filter *filter, struct event_format *event,
+ const char *filter_str, char *error_str)
+{
+ struct filter_type *filter_type;
+ struct filter_arg *arg;
+ enum pevent_errno ret;
+
+ if (filter_str) {
+ ret = process_event(event, filter_str, &arg, error_str);
+ if (ret < 0)
+ return ret;
+
+ } else {
+ /* just add a TRUE arg */
+ arg = allocate_arg();
+ if (arg == NULL)
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+
+ arg->type = FILTER_ARG_BOOLEAN;
+ arg->boolean.value = FILTER_TRUE;
+ }
+
+ filter_type = add_filter_type(filter, event->id);
+ if (filter_type == NULL)
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+
+ if (filter_type->filter)
+ free_arg(filter_type->filter);
+ filter_type->filter = arg;
+
+ return 0;
+}
+
+static void filter_init_error_buf(struct event_filter *filter)
+{
+ /* clear buffer to reset show error */
+ pevent_buffer_init("", 0);
+ filter->error_buffer[0] = '\0';
+}
+
+/**
+ * pevent_filter_add_filter_str - add a new filter
+ * @filter: the event filter to add to
+ * @filter_str: the filter string that contains the filter
+ *
+ * Returns 0 if the filter was successfully added or a
+ * negative error code. Use pevent_filter_strerror() to see
+ * actual error message in case of error.
+ */
+enum pevent_errno pevent_filter_add_filter_str(struct event_filter *filter,
+ const char *filter_str)
+{
+ struct pevent *pevent = filter->pevent;
+ struct event_list *event;
+ struct event_list *events = NULL;
+ const char *filter_start;
+ const char *next_event;
+ char *this_event;
+ char *event_name = NULL;
+ char *sys_name = NULL;
+ char *sp;
+ enum pevent_errno rtn = 0; /* PEVENT_ERRNO__SUCCESS */
+ int len;
+ int ret;
+
+ filter_init_error_buf(filter);
+
+ filter_start = strchr(filter_str, ':');
+ if (filter_start)
+ len = filter_start - filter_str;
+ else
+ len = strlen(filter_str);
+
+ do {
+ next_event = strchr(filter_str, ',');
+ if (next_event &&
+ (!filter_start || next_event < filter_start))
+ len = next_event - filter_str;
+ else if (filter_start)
+ len = filter_start - filter_str;
+ else
+ len = strlen(filter_str);
+
+ this_event = malloc(len + 1);
+ if (this_event == NULL) {
+ /* This can only happen when events is NULL, but still */
+ free_events(events);
+ return PEVENT_ERRNO__MEM_ALLOC_FAILED;
+ }
+ memcpy(this_event, filter_str, len);
+ this_event[len] = 0;
+
+ if (next_event)
+ next_event++;
+
+ filter_str = next_event;
+
+ sys_name = strtok_r(this_event, "/", &sp);
+ event_name = strtok_r(NULL, "/", &sp);
+
+ if (!sys_name) {
+ /* This can only happen when events is NULL, but still */
+ free_events(events);
+ free(this_event);
+ return PEVENT_ERRNO__FILTER_NOT_FOUND;
+ }
+
+ /* Find this event */
+ ret = find_event(pevent, &events, strim(sys_name), strim(event_name));
+ if (ret < 0) {
+ free_events(events);
+ free(this_event);
+ return ret;
+ }
+ free(this_event);
+ } while (filter_str);
+
+ /* Skip the ':' */
+ if (filter_start)
+ filter_start++;
+
+ /* filter starts here */
+ for (event = events; event; event = event->next) {
+ ret = filter_event(filter, event->event, filter_start,
+ filter->error_buffer);
+ /* Failures are returned if a parse error happened */
+ if (ret < 0)
+ rtn = ret;
+
+ if (ret >= 0 && pevent->test_filters) {
+ char *test;
+ test = pevent_filter_make_string(filter, event->event->id);
+ if (test) {
+ printf(" '%s: %s'\n", event->event->name, test);
+ free(test);
+ }
+ }
+ }
+
+ free_events(events);
+
+ if (rtn >= 0 && pevent->test_filters)
+ exit(0);
+
+ return rtn;
+}
+
+static void free_filter_type(struct filter_type *filter_type)
+{
+ free_arg(filter_type->filter);
+}
+
+/**
+ * pevent_filter_strerror - fill error message in a buffer
+ * @filter: the event filter contains error
+ * @err: the error code
+ * @buf: the buffer to be filled in
+ * @buflen: the size of the buffer
+ *
+ * Returns 0 if message was filled successfully, -1 if error
+ */
+int pevent_filter_strerror(struct event_filter *filter, enum pevent_errno err,
+ char *buf, size_t buflen)
+{
+ if (err <= __PEVENT_ERRNO__START || err >= __PEVENT_ERRNO__END)
+ return -1;
+
+ if (strlen(filter->error_buffer) > 0) {
+ size_t len = snprintf(buf, buflen, "%s", filter->error_buffer);
+
+ if (len > buflen)
+ return -1;
+ return 0;
+ }
+
+ return pevent_strerror(filter->pevent, err, buf, buflen);
+}
+
+/**
+ * pevent_filter_remove_event - remove a filter for an event
+ * @filter: the event filter to remove from
+ * @event_id: the event to remove a filter for
+ *
+ * Removes the filter saved for an event defined by @event_id
+ * from the @filter.
+ *
+ * Returns 1: if an event was removed
+ * 0: if the event was not found
+ */
+int pevent_filter_remove_event(struct event_filter *filter,
+ int event_id)
+{
+ struct filter_type *filter_type;
+ unsigned long len;
+
+ if (!filter->filters)
+ return 0;
+
+ filter_type = find_filter_type(filter, event_id);
+
+ if (!filter_type)
+ return 0;
+
+ free_filter_type(filter_type);
+
+ /* The filter_type points into the event_filters array */
+ len = (unsigned long)(filter->event_filters + filter->filters) -
+ (unsigned long)(filter_type + 1);
+
+ memmove(filter_type, filter_type + 1, len);
+ filter->filters--;
+
+ memset(&filter->event_filters[filter->filters], 0,
+ sizeof(*filter_type));
+
+ return 1;
+}
+
+/**
+ * pevent_filter_reset - clear all filters in a filter
+ * @filter: the event filter to reset
+ *
+ * Removes all filters from a filter and resets it.
+ */
+void pevent_filter_reset(struct event_filter *filter)
+{
+ int i;
+
+ for (i = 0; i < filter->filters; i++)
+ free_filter_type(&filter->event_filters[i]);
+
+ free(filter->event_filters);
+ filter->filters = 0;
+ filter->event_filters = NULL;
+}
+
+void pevent_filter_free(struct event_filter *filter)
+{
+ pevent_unref(filter->pevent);
+
+ pevent_filter_reset(filter);
+
+ free(filter);
+}
+
+static char *arg_to_str(struct event_filter *filter, struct filter_arg *arg);
+
+static int copy_filter_type(struct event_filter *filter,
+ struct event_filter *source,
+ struct filter_type *filter_type)
+{
+ struct filter_arg *arg;
+ struct event_format *event;
+ const char *sys;
+ const char *name;
+ char *str;
+
+ /* Can't assume that the pevent's are the same */
+ sys = filter_type->event->system;
+ name = filter_type->event->name;
+ event = pevent_find_event_by_name(filter->pevent, sys, name);
+ if (!event)
+ return -1;
+
+ str = arg_to_str(source, filter_type->filter);
+ if (!str)
+ return -1;
+
+ if (strcmp(str, "TRUE") == 0 || strcmp(str, "FALSE") == 0) {
+ /* Add trivial event */
+ arg = allocate_arg();
+ if (arg == NULL)
+ return -1;
+
+ arg->type = FILTER_ARG_BOOLEAN;
+ if (strcmp(str, "TRUE") == 0)
+ arg->boolean.value = 1;
+ else
+ arg->boolean.value = 0;
+
+ filter_type = add_filter_type(filter, event->id);
+ if (filter_type == NULL)
+ return -1;
+
+ filter_type->filter = arg;
+
+ free(str);
+ return 0;
+ }
+
+ filter_event(filter, event, str, NULL);
+ free(str);
+
+ return 0;
+}
+
+/**
+ * pevent_filter_copy - copy a filter using another filter
+ * @dest - the filter to copy to
+ * @source - the filter to copy from
+ *
+ * Returns 0 on success and -1 if not all filters were copied
+ */
+int pevent_filter_copy(struct event_filter *dest, struct event_filter *source)
+{
+ int ret = 0;
+ int i;
+
+ pevent_filter_reset(dest);
+
+ for (i = 0; i < source->filters; i++) {
+ if (copy_filter_type(dest, source, &source->event_filters[i]))
+ ret = -1;
+ }
+ return ret;
+}
+
+
+/**
+ * pevent_update_trivial - update the trivial filters with the given filter
+ * @dest - the filter to update
+ * @source - the filter as the source of the update
+ * @type - the type of trivial filter to update.
+ *
+ * Scan dest for trivial events matching @type to replace with the source.
+ *
+ * Returns 0 on success and -1 if there was a problem updating, but
+ * events may have still been updated on error.
+ */
+int pevent_update_trivial(struct event_filter *dest, struct event_filter *source,
+ enum filter_trivial_type type)
+{
+ struct pevent *src_pevent;
+ struct pevent *dest_pevent;
+ struct event_format *event;
+ struct filter_type *filter_type;
+ struct filter_arg *arg;
+ char *str;
+ int i;
+
+ src_pevent = source->pevent;
+ dest_pevent = dest->pevent;
+
+ /* Do nothing if either of the filters has nothing to filter */
+ if (!dest->filters || !source->filters)
+ return 0;
+
+ for (i = 0; i < dest->filters; i++) {
+ filter_type = &dest->event_filters[i];
+ arg = filter_type->filter;
+ if (arg->type != FILTER_ARG_BOOLEAN)
+ continue;
+ if ((arg->boolean.value && type == FILTER_TRIVIAL_FALSE) ||
+ (!arg->boolean.value && type == FILTER_TRIVIAL_TRUE))
+ continue;
+
+ event = filter_type->event;
+
+ if (src_pevent != dest_pevent) {
+ /* do a look up */
+ event = pevent_find_event_by_name(src_pevent,
+ event->system,
+ event->name);
+ if (!event)
+ return -1;
+ }
+
+ str = pevent_filter_make_string(source, event->id);
+ if (!str)
+ continue;
+
+ /* Don't bother if the filter is trivial too */
+ if (strcmp(str, "TRUE") != 0 && strcmp(str, "FALSE") != 0)
+ filter_event(dest, event, str, NULL);
+ free(str);
+ }
+ return 0;
+}
+
+/**
+ * pevent_filter_clear_trivial - clear TRUE and FALSE filters
+ * @filter: the filter to remove trivial filters from
+ * @type: remove only true, false, or both
+ *
+ * Removes filters that only contain a TRUE or FALES boolean arg.
+ *
+ * Returns 0 on success and -1 if there was a problem.
+ */
+int pevent_filter_clear_trivial(struct event_filter *filter,
+ enum filter_trivial_type type)
+{
+ struct filter_type *filter_type;
+ int count = 0;
+ int *ids = NULL;
+ int i;
+
+ if (!filter->filters)
+ return 0;
+
+ /*
+ * Two steps, first get all ids with trivial filters.
+ * then remove those ids.
+ */
+ for (i = 0; i < filter->filters; i++) {
+ int *new_ids;
+
+ filter_type = &filter->event_filters[i];
+ if (filter_type->filter->type != FILTER_ARG_BOOLEAN)
+ continue;
+ switch (type) {
+ case FILTER_TRIVIAL_FALSE:
+ if (filter_type->filter->boolean.value)
+ continue;
+ case FILTER_TRIVIAL_TRUE:
+ if (!filter_type->filter->boolean.value)
+ continue;
+ default:
+ break;
+ }
+
+ new_ids = realloc(ids, sizeof(*ids) * (count + 1));
+ if (!new_ids) {
+ free(ids);
+ return -1;
+ }
+
+ ids = new_ids;
+ ids[count++] = filter_type->event_id;
+ }
+
+ if (!count)
+ return 0;
+
+ for (i = 0; i < count; i++)
+ pevent_filter_remove_event(filter, ids[i]);
+
+ free(ids);
+ return 0;
+}
+
+/**
+ * pevent_filter_event_has_trivial - return true event contains trivial filter
+ * @filter: the filter with the information
+ * @event_id: the id of the event to test
+ * @type: trivial type to test for (TRUE, FALSE, EITHER)
+ *
+ * Returns 1 if the event contains a matching trivial type
+ * otherwise 0.
+ */
+int pevent_filter_event_has_trivial(struct event_filter *filter,
+ int event_id,
+ enum filter_trivial_type type)
+{
+ struct filter_type *filter_type;
+
+ if (!filter->filters)
+ return 0;
+
+ filter_type = find_filter_type(filter, event_id);
+
+ if (!filter_type)
+ return 0;
+
+ if (filter_type->filter->type != FILTER_ARG_BOOLEAN)
+ return 0;
+
+ switch (type) {
+ case FILTER_TRIVIAL_FALSE:
+ return !filter_type->filter->boolean.value;
+
+ case FILTER_TRIVIAL_TRUE:
+ return filter_type->filter->boolean.value;
+ default:
+ return 1;
+ }
+}
+
+static int test_filter(struct event_format *event, struct filter_arg *arg,
+ struct pevent_record *record, enum pevent_errno *err);
+
+static const char *
+get_comm(struct event_format *event, struct pevent_record *record)
+{
+ const char *comm;
+ int pid;
+
+ pid = pevent_data_pid(event->pevent, record);
+ comm = pevent_data_comm_from_pid(event->pevent, pid);
+ return comm;
+}
+
+static unsigned long long
+get_value(struct event_format *event,
+ struct format_field *field, struct pevent_record *record)
+{
+ unsigned long long val;
+
+ /* Handle our dummy "comm" field */
+ if (field == &comm) {
+ const char *name;
+
+ name = get_comm(event, record);
+ return (unsigned long)name;
+ }
+
+ pevent_read_number_field(field, record->data, &val);
+
+ if (!(field->flags & FIELD_IS_SIGNED))
+ return val;
+
+ switch (field->size) {
+ case 1:
+ return (char)val;
+ case 2:
+ return (short)val;
+ case 4:
+ return (int)val;
+ case 8:
+ return (long long)val;
+ }
+ return val;
+}
+
+static unsigned long long
+get_arg_value(struct event_format *event, struct filter_arg *arg,
+ struct pevent_record *record, enum pevent_errno *err);
+
+static unsigned long long
+get_exp_value(struct event_format *event, struct filter_arg *arg,
+ struct pevent_record *record, enum pevent_errno *err)
+{
+ unsigned long long lval, rval;
+
+ lval = get_arg_value(event, arg->exp.left, record, err);
+ rval = get_arg_value(event, arg->exp.right, record, err);
+
+ if (*err) {
+ /*
+ * There was an error, no need to process anymore.
+ */
+ return 0;
+ }
+
+ switch (arg->exp.type) {
+ case FILTER_EXP_ADD:
+ return lval + rval;
+
+ case FILTER_EXP_SUB:
+ return lval - rval;
+
+ case FILTER_EXP_MUL:
+ return lval * rval;
+
+ case FILTER_EXP_DIV:
+ return lval / rval;
+
+ case FILTER_EXP_MOD:
+ return lval % rval;
+
+ case FILTER_EXP_RSHIFT:
+ return lval >> rval;
+
+ case FILTER_EXP_LSHIFT:
+ return lval << rval;
+
+ case FILTER_EXP_AND:
+ return lval & rval;
+
+ case FILTER_EXP_OR:
+ return lval | rval;
+
+ case FILTER_EXP_XOR:
+ return lval ^ rval;
+
+ case FILTER_EXP_NOT:
+ default:
+ if (!*err)
+ *err = PEVENT_ERRNO__INVALID_EXP_TYPE;
+ }
+ return 0;
+}
+
+static unsigned long long
+get_arg_value(struct event_format *event, struct filter_arg *arg,
+ struct pevent_record *record, enum pevent_errno *err)
+{
+ switch (arg->type) {
+ case FILTER_ARG_FIELD:
+ return get_value(event, arg->field.field, record);
+
+ case FILTER_ARG_VALUE:
+ if (arg->value.type != FILTER_NUMBER) {
+ if (!*err)
+ *err = PEVENT_ERRNO__NOT_A_NUMBER;
+ }
+ return arg->value.val;
+
+ case FILTER_ARG_EXP:
+ return get_exp_value(event, arg, record, err);
+
+ default:
+ if (!*err)
+ *err = PEVENT_ERRNO__INVALID_ARG_TYPE;
+ }
+ return 0;
+}
+
+static int test_num(struct event_format *event, struct filter_arg *arg,
+ struct pevent_record *record, enum pevent_errno *err)
+{
+ unsigned long long lval, rval;
+
+ lval = get_arg_value(event, arg->num.left, record, err);
+ rval = get_arg_value(event, arg->num.right, record, err);
+
+ if (*err) {
+ /*
+ * There was an error, no need to process anymore.
+ */
+ return 0;
+ }
+
+ switch (arg->num.type) {
+ case FILTER_CMP_EQ:
+ return lval == rval;
+
+ case FILTER_CMP_NE:
+ return lval != rval;
+
+ case FILTER_CMP_GT:
+ return lval > rval;
+
+ case FILTER_CMP_LT:
+ return lval < rval;
+
+ case FILTER_CMP_GE:
+ return lval >= rval;
+
+ case FILTER_CMP_LE:
+ return lval <= rval;
+
+ default:
+ if (!*err)
+ *err = PEVENT_ERRNO__ILLEGAL_INTEGER_CMP;
+ return 0;
+ }
+}
+
+static const char *get_field_str(struct filter_arg *arg, struct pevent_record *record)
+{
+ struct event_format *event;
+ struct pevent *pevent;
+ unsigned long long addr;
+ const char *val = NULL;
+ char hex[64];
+
+ /* If the field is not a string convert it */
+ if (arg->str.field->flags & FIELD_IS_STRING) {
+ val = record->data + arg->str.field->offset;
+
+ /*
+ * We need to copy the data since we can't be sure the field
+ * is null terminated.
+ */
+ if (*(val + arg->str.field->size - 1)) {
+ /* copy it */
+ memcpy(arg->str.buffer, val, arg->str.field->size);
+ /* the buffer is already NULL terminated */
+ val = arg->str.buffer;
+ }
+
+ } else {
+ event = arg->str.field->event;
+ pevent = event->pevent;
+ addr = get_value(event, arg->str.field, record);
+
+ if (arg->str.field->flags & (FIELD_IS_POINTER | FIELD_IS_LONG))
+ /* convert to a kernel symbol */
+ val = pevent_find_function(pevent, addr);
+
+ if (val == NULL) {
+ /* just use the hex of the string name */
+ snprintf(hex, 64, "0x%llx", addr);
+ val = hex;
+ }
+ }
+
+ return val;
+}
+
+static int test_str(struct event_format *event, struct filter_arg *arg,
+ struct pevent_record *record, enum pevent_errno *err)
+{
+ const char *val;
+
+ if (arg->str.field == &comm)
+ val = get_comm(event, record);
+ else
+ val = get_field_str(arg, record);
+
+ switch (arg->str.type) {
+ case FILTER_CMP_MATCH:
+ return strcmp(val, arg->str.val) == 0;
+
+ case FILTER_CMP_NOT_MATCH:
+ return strcmp(val, arg->str.val) != 0;
+
+ case FILTER_CMP_REGEX:
+ /* Returns zero on match */
+ return !regexec(&arg->str.reg, val, 0, NULL, 0);
+
+ case FILTER_CMP_NOT_REGEX:
+ return regexec(&arg->str.reg, val, 0, NULL, 0);
+
+ default:
+ if (!*err)
+ *err = PEVENT_ERRNO__ILLEGAL_STRING_CMP;
+ return 0;
+ }
+}
+
+static int test_op(struct event_format *event, struct filter_arg *arg,
+ struct pevent_record *record, enum pevent_errno *err)
+{
+ switch (arg->op.type) {
+ case FILTER_OP_AND:
+ return test_filter(event, arg->op.left, record, err) &&
+ test_filter(event, arg->op.right, record, err);
+
+ case FILTER_OP_OR:
+ return test_filter(event, arg->op.left, record, err) ||
+ test_filter(event, arg->op.right, record, err);
+
+ case FILTER_OP_NOT:
+ return !test_filter(event, arg->op.right, record, err);
+
+ default:
+ if (!*err)
+ *err = PEVENT_ERRNO__INVALID_OP_TYPE;
+ return 0;
+ }
+}
+
+static int test_filter(struct event_format *event, struct filter_arg *arg,
+ struct pevent_record *record, enum pevent_errno *err)
+{
+ if (*err) {
+ /*
+ * There was an error, no need to process anymore.
+ */
+ return 0;
+ }
+
+ switch (arg->type) {
+ case FILTER_ARG_BOOLEAN:
+ /* easy case */
+ return arg->boolean.value;
+
+ case FILTER_ARG_OP:
+ return test_op(event, arg, record, err);
+
+ case FILTER_ARG_NUM:
+ return test_num(event, arg, record, err);
+
+ case FILTER_ARG_STR:
+ return test_str(event, arg, record, err);
+
+ case FILTER_ARG_EXP:
+ case FILTER_ARG_VALUE:
+ case FILTER_ARG_FIELD:
+ /*
+ * Expressions, fields and values evaluate
+ * to true if they return non zero
+ */
+ return !!get_arg_value(event, arg, record, err);
+
+ default:
+ if (!*err)
+ *err = PEVENT_ERRNO__INVALID_ARG_TYPE;
+ return 0;
+ }
+}
+
+/**
+ * pevent_event_filtered - return true if event has filter
+ * @filter: filter struct with filter information
+ * @event_id: event id to test if filter exists
+ *
+ * Returns 1 if filter found for @event_id
+ * otherwise 0;
+ */
+int pevent_event_filtered(struct event_filter *filter, int event_id)
+{
+ struct filter_type *filter_type;
+
+ if (!filter->filters)
+ return 0;
+
+ filter_type = find_filter_type(filter, event_id);
+
+ return filter_type ? 1 : 0;
+}
+
+/**
+ * pevent_filter_match - test if a record matches a filter
+ * @filter: filter struct with filter information
+ * @record: the record to test against the filter
+ *
+ * Returns: match result or error code (prefixed with PEVENT_ERRNO__)
+ * FILTER_MATCH - filter found for event and @record matches
+ * FILTER_MISS - filter found for event and @record does not match
+ * FILTER_NOT_FOUND - no filter found for @record's event
+ * NO_FILTER - if no filters exist
+ * otherwise - error occurred during test
+ */
+enum pevent_errno pevent_filter_match(struct event_filter *filter,
+ struct pevent_record *record)
+{
+ struct pevent *pevent = filter->pevent;
+ struct filter_type *filter_type;
+ int event_id;
+ int ret;
+ enum pevent_errno err = 0;
+
+ filter_init_error_buf(filter);
+
+ if (!filter->filters)
+ return PEVENT_ERRNO__NO_FILTER;
+
+ event_id = pevent_data_type(pevent, record);
+
+ filter_type = find_filter_type(filter, event_id);
+ if (!filter_type)
+ return PEVENT_ERRNO__FILTER_NOT_FOUND;
+
+ ret = test_filter(filter_type->event, filter_type->filter, record, &err);
+ if (err)
+ return err;
+
+ return ret ? PEVENT_ERRNO__FILTER_MATCH : PEVENT_ERRNO__FILTER_MISS;
+}
+
+static char *op_to_str(struct event_filter *filter, struct filter_arg *arg)
+{
+ char *str = NULL;
+ char *left = NULL;
+ char *right = NULL;
+ char *op = NULL;
+ int left_val = -1;
+ int right_val = -1;
+ int val;
+
+ switch (arg->op.type) {
+ case FILTER_OP_AND:
+ op = "&&";
+ /* fall through */
+ case FILTER_OP_OR:
+ if (!op)
+ op = "||";
+
+ left = arg_to_str(filter, arg->op.left);
+ right = arg_to_str(filter, arg->op.right);
+ if (!left || !right)
+ break;
+
+ /* Try to consolidate boolean values */
+ if (strcmp(left, "TRUE") == 0)
+ left_val = 1;
+ else if (strcmp(left, "FALSE") == 0)
+ left_val = 0;
+
+ if (strcmp(right, "TRUE") == 0)
+ right_val = 1;
+ else if (strcmp(right, "FALSE") == 0)
+ right_val = 0;
+
+ if (left_val >= 0) {
+ if ((arg->op.type == FILTER_OP_AND && !left_val) ||
+ (arg->op.type == FILTER_OP_OR && left_val)) {
+ /* Just return left value */
+ str = left;
+ left = NULL;
+ break;
+ }
+ if (right_val >= 0) {
+ /* just evaluate this. */
+ val = 0;
+ switch (arg->op.type) {
+ case FILTER_OP_AND:
+ val = left_val && right_val;
+ break;
+ case FILTER_OP_OR:
+ val = left_val || right_val;
+ break;
+ default:
+ break;
+ }
+ asprintf(&str, val ? "TRUE" : "FALSE");
+ break;
+ }
+ }
+ if (right_val >= 0) {
+ if ((arg->op.type == FILTER_OP_AND && !right_val) ||
+ (arg->op.type == FILTER_OP_OR && right_val)) {
+ /* Just return right value */
+ str = right;
+ right = NULL;
+ break;
+ }
+ /* The right value is meaningless */
+ str = left;
+ left = NULL;
+ break;
+ }
+
+ asprintf(&str, "(%s) %s (%s)", left, op, right);
+ break;
+
+ case FILTER_OP_NOT:
+ op = "!";
+ right = arg_to_str(filter, arg->op.right);
+ if (!right)
+ break;
+
+ /* See if we can consolidate */
+ if (strcmp(right, "TRUE") == 0)
+ right_val = 1;
+ else if (strcmp(right, "FALSE") == 0)
+ right_val = 0;
+ if (right_val >= 0) {
+ /* just return the opposite */
+ asprintf(&str, right_val ? "FALSE" : "TRUE");
+ break;
+ }
+ asprintf(&str, "%s(%s)", op, right);
+ break;
+
+ default:
+ /* ?? */
+ break;
+ }
+ free(left);
+ free(right);
+ return str;
+}
+
+static char *val_to_str(struct event_filter *filter, struct filter_arg *arg)
+{
+ char *str = NULL;
+
+ asprintf(&str, "%lld", arg->value.val);
+
+ return str;
+}
+
+static char *field_to_str(struct event_filter *filter, struct filter_arg *arg)
+{
+ return strdup(arg->field.field->name);
+}
+
+static char *exp_to_str(struct event_filter *filter, struct filter_arg *arg)
+{
+ char *lstr;
+ char *rstr;
+ char *op;
+ char *str = NULL;
+
+ lstr = arg_to_str(filter, arg->exp.left);
+ rstr = arg_to_str(filter, arg->exp.right);
+ if (!lstr || !rstr)
+ goto out;
+
+ switch (arg->exp.type) {
+ case FILTER_EXP_ADD:
+ op = "+";
+ break;
+ case FILTER_EXP_SUB:
+ op = "-";
+ break;
+ case FILTER_EXP_MUL:
+ op = "*";
+ break;
+ case FILTER_EXP_DIV:
+ op = "/";
+ break;
+ case FILTER_EXP_MOD:
+ op = "%";
+ break;
+ case FILTER_EXP_RSHIFT:
+ op = ">>";
+ break;
+ case FILTER_EXP_LSHIFT:
+ op = "<<";
+ break;
+ case FILTER_EXP_AND:
+ op = "&";
+ break;
+ case FILTER_EXP_OR:
+ op = "|";
+ break;
+ case FILTER_EXP_XOR:
+ op = "^";
+ break;
+ default:
+ op = "[ERROR IN EXPRESSION TYPE]";
+ break;
+ }
+
+ asprintf(&str, "%s %s %s", lstr, op, rstr);
+out:
+ free(lstr);
+ free(rstr);
+
+ return str;
+}
+
+static char *num_to_str(struct event_filter *filter, struct filter_arg *arg)
+{
+ char *lstr;
+ char *rstr;
+ char *str = NULL;
+ char *op = NULL;
+
+ lstr = arg_to_str(filter, arg->num.left);
+ rstr = arg_to_str(filter, arg->num.right);
+ if (!lstr || !rstr)
+ goto out;
+
+ switch (arg->num.type) {
+ case FILTER_CMP_EQ:
+ op = "==";
+ /* fall through */
+ case FILTER_CMP_NE:
+ if (!op)
+ op = "!=";
+ /* fall through */
+ case FILTER_CMP_GT:
+ if (!op)
+ op = ">";
+ /* fall through */
+ case FILTER_CMP_LT:
+ if (!op)
+ op = "<";
+ /* fall through */
+ case FILTER_CMP_GE:
+ if (!op)
+ op = ">=";
+ /* fall through */
+ case FILTER_CMP_LE:
+ if (!op)
+ op = "<=";
+
+ asprintf(&str, "%s %s %s", lstr, op, rstr);
+ break;
+
+ default:
+ /* ?? */
+ break;
+ }
+
+out:
+ free(lstr);
+ free(rstr);
+ return str;
+}
+
+static char *str_to_str(struct event_filter *filter, struct filter_arg *arg)
+{
+ char *str = NULL;
+ char *op = NULL;
+
+ switch (arg->str.type) {
+ case FILTER_CMP_MATCH:
+ op = "==";
+ /* fall through */
+ case FILTER_CMP_NOT_MATCH:
+ if (!op)
+ op = "!=";
+ /* fall through */
+ case FILTER_CMP_REGEX:
+ if (!op)
+ op = "=~";
+ /* fall through */
+ case FILTER_CMP_NOT_REGEX:
+ if (!op)
+ op = "!~";
+
+ asprintf(&str, "%s %s \"%s\"",
+ arg->str.field->name, op, arg->str.val);
+ break;
+
+ default:
+ /* ?? */
+ break;
+ }
+ return str;
+}
+
+static char *arg_to_str(struct event_filter *filter, struct filter_arg *arg)
+{
+ char *str = NULL;
+
+ switch (arg->type) {
+ case FILTER_ARG_BOOLEAN:
+ asprintf(&str, arg->boolean.value ? "TRUE" : "FALSE");
+ return str;
+
+ case FILTER_ARG_OP:
+ return op_to_str(filter, arg);
+
+ case FILTER_ARG_NUM:
+ return num_to_str(filter, arg);
+
+ case FILTER_ARG_STR:
+ return str_to_str(filter, arg);
+
+ case FILTER_ARG_VALUE:
+ return val_to_str(filter, arg);
+
+ case FILTER_ARG_FIELD:
+ return field_to_str(filter, arg);
+
+ case FILTER_ARG_EXP:
+ return exp_to_str(filter, arg);
+
+ default:
+ /* ?? */
+ return NULL;
+ }
+
+}
+
+/**
+ * pevent_filter_make_string - return a string showing the filter
+ * @filter: filter struct with filter information
+ * @event_id: the event id to return the filter string with
+ *
+ * Returns a string that displays the filter contents.
+ * This string must be freed with free(str).
+ * NULL is returned if no filter is found or allocation failed.
+ */
+char *
+pevent_filter_make_string(struct event_filter *filter, int event_id)
+{
+ struct filter_type *filter_type;
+
+ if (!filter->filters)
+ return NULL;
+
+ filter_type = find_filter_type(filter, event_id);
+
+ if (!filter_type)
+ return NULL;
+
+ return arg_to_str(filter, filter_type->filter);
+}
+
+/**
+ * pevent_filter_compare - compare two filters and return if they are the same
+ * @filter1: Filter to compare with @filter2
+ * @filter2: Filter to compare with @filter1
+ *
+ * Returns:
+ * 1 if the two filters hold the same content.
+ * 0 if they do not.
+ */
+int pevent_filter_compare(struct event_filter *filter1, struct event_filter *filter2)
+{
+ struct filter_type *filter_type1;
+ struct filter_type *filter_type2;
+ char *str1, *str2;
+ int result;
+ int i;
+
+ /* Do the easy checks first */
+ if (filter1->filters != filter2->filters)
+ return 0;
+ if (!filter1->filters && !filter2->filters)
+ return 1;
+
+ /*
+ * Now take a look at each of the events to see if they have the same
+ * filters to them.
+ */
+ for (i = 0; i < filter1->filters; i++) {
+ filter_type1 = &filter1->event_filters[i];
+ filter_type2 = find_filter_type(filter2, filter_type1->event_id);
+ if (!filter_type2)
+ break;
+ if (filter_type1->filter->type != filter_type2->filter->type)
+ break;
+ switch (filter_type1->filter->type) {
+ case FILTER_TRIVIAL_FALSE:
+ case FILTER_TRIVIAL_TRUE:
+ /* trivial types just need the type compared */
+ continue;
+ default:
+ break;
+ }
+ /* The best way to compare complex filters is with strings */
+ str1 = arg_to_str(filter1, filter_type1->filter);
+ str2 = arg_to_str(filter2, filter_type2->filter);
+ if (str1 && str2)
+ result = strcmp(str1, str2) != 0;
+ else
+ /* bail out if allocation fails */
+ result = 1;
+
+ free(str1);
+ free(str2);
+ if (result)
+ break;
+ }
+
+ if (i < filter1->filters)
+ return 0;
+ return 1;
+}
+
diff --git a/tools/lib/traceevent/parse-utils.c b/tools/lib/traceevent/parse-utils.c
new file mode 100644
index 00000000000..eda07fa31dc
--- /dev/null
+++ b/tools/lib/traceevent/parse-utils.c
@@ -0,0 +1,85 @@
+/*
+ * Copyright (C) 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <errno.h>
+
+#define __weak __attribute__((weak))
+
+void __vwarning(const char *fmt, va_list ap)
+{
+ if (errno)
+ perror("trace-cmd");
+ errno = 0;
+
+ fprintf(stderr, " ");
+ vfprintf(stderr, fmt, ap);
+
+ fprintf(stderr, "\n");
+}
+
+void __warning(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ __vwarning(fmt, ap);
+ va_end(ap);
+}
+
+void __weak warning(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ __vwarning(fmt, ap);
+ va_end(ap);
+}
+
+void __vpr_stat(const char *fmt, va_list ap)
+{
+ vprintf(fmt, ap);
+ printf("\n");
+}
+
+void __pr_stat(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ __vpr_stat(fmt, ap);
+ va_end(ap);
+}
+
+void __weak vpr_stat(const char *fmt, va_list ap)
+{
+ __vpr_stat(fmt, ap);
+}
+
+void __weak pr_stat(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ __vpr_stat(fmt, ap);
+ va_end(ap);
+}
diff --git a/tools/lib/traceevent/plugin_cfg80211.c b/tools/lib/traceevent/plugin_cfg80211.c
new file mode 100644
index 00000000000..c066b25905f
--- /dev/null
+++ b/tools/lib/traceevent/plugin_cfg80211.c
@@ -0,0 +1,30 @@
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include <endian.h>
+#include "event-parse.h"
+
+static unsigned long long
+process___le16_to_cpup(struct trace_seq *s,
+ unsigned long long *args)
+{
+ uint16_t *val = (uint16_t *) (unsigned long) args[0];
+ return val ? (long long) le16toh(*val) : 0;
+}
+
+int PEVENT_PLUGIN_LOADER(struct pevent *pevent)
+{
+ pevent_register_print_function(pevent,
+ process___le16_to_cpup,
+ PEVENT_FUNC_ARG_INT,
+ "__le16_to_cpup",
+ PEVENT_FUNC_ARG_PTR,
+ PEVENT_FUNC_ARG_VOID);
+ return 0;
+}
+
+void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent)
+{
+ pevent_unregister_print_function(pevent, process___le16_to_cpup,
+ "__le16_to_cpup");
+}
diff --git a/tools/lib/traceevent/plugin_function.c b/tools/lib/traceevent/plugin_function.c
new file mode 100644
index 00000000000..a00ec190821
--- /dev/null
+++ b/tools/lib/traceevent/plugin_function.c
@@ -0,0 +1,194 @@
+/*
+ * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "event-parse.h"
+#include "event-utils.h"
+
+static struct func_stack {
+ int size;
+ char **stack;
+} *fstack;
+
+static int cpus = -1;
+
+#define STK_BLK 10
+
+struct pevent_plugin_option plugin_options[] =
+{
+ {
+ .name = "parent",
+ .plugin_alias = "ftrace",
+ .description =
+ "Print parent of functions for function events",
+ },
+ {
+ .name = "indent",
+ .plugin_alias = "ftrace",
+ .description =
+ "Try to show function call indents, based on parents",
+ .set = 1,
+ },
+ {
+ .name = NULL,
+ }
+};
+
+static struct pevent_plugin_option *ftrace_parent = &plugin_options[0];
+static struct pevent_plugin_option *ftrace_indent = &plugin_options[1];
+
+static void add_child(struct func_stack *stack, const char *child, int pos)
+{
+ int i;
+
+ if (!child)
+ return;
+
+ if (pos < stack->size)
+ free(stack->stack[pos]);
+ else {
+ char **ptr;
+
+ ptr = realloc(stack->stack, sizeof(char *) *
+ (stack->size + STK_BLK));
+ if (!ptr) {
+ warning("could not allocate plugin memory\n");
+ return;
+ }
+
+ stack->stack = ptr;
+
+ for (i = stack->size; i < stack->size + STK_BLK; i++)
+ stack->stack[i] = NULL;
+ stack->size += STK_BLK;
+ }
+
+ stack->stack[pos] = strdup(child);
+}
+
+static int add_and_get_index(const char *parent, const char *child, int cpu)
+{
+ int i;
+
+ if (cpu < 0)
+ return 0;
+
+ if (cpu > cpus) {
+ struct func_stack *ptr;
+
+ ptr = realloc(fstack, sizeof(*fstack) * (cpu + 1));
+ if (!ptr) {
+ warning("could not allocate plugin memory\n");
+ return 0;
+ }
+
+ fstack = ptr;
+
+ /* Account for holes in the cpu count */
+ for (i = cpus + 1; i <= cpu; i++)
+ memset(&fstack[i], 0, sizeof(fstack[i]));
+ cpus = cpu;
+ }
+
+ for (i = 0; i < fstack[cpu].size && fstack[cpu].stack[i]; i++) {
+ if (strcmp(parent, fstack[cpu].stack[i]) == 0) {
+ add_child(&fstack[cpu], child, i+1);
+ return i;
+ }
+ }
+
+ /* Not found */
+ add_child(&fstack[cpu], parent, 0);
+ add_child(&fstack[cpu], child, 1);
+ return 0;
+}
+
+static int function_handler(struct trace_seq *s, struct pevent_record *record,
+ struct event_format *event, void *context)
+{
+ struct pevent *pevent = event->pevent;
+ unsigned long long function;
+ unsigned long long pfunction;
+ const char *func;
+ const char *parent;
+ int index;
+
+ if (pevent_get_field_val(s, event, "ip", record, &function, 1))
+ return trace_seq_putc(s, '!');
+
+ func = pevent_find_function(pevent, function);
+
+ if (pevent_get_field_val(s, event, "parent_ip", record, &pfunction, 1))
+ return trace_seq_putc(s, '!');
+
+ parent = pevent_find_function(pevent, pfunction);
+
+ if (parent && ftrace_indent->set)
+ index = add_and_get_index(parent, func, record->cpu);
+
+ trace_seq_printf(s, "%*s", index*3, "");
+
+ if (func)
+ trace_seq_printf(s, "%s", func);
+ else
+ trace_seq_printf(s, "0x%llx", function);
+
+ if (ftrace_parent->set) {
+ trace_seq_printf(s, " <-- ");
+ if (parent)
+ trace_seq_printf(s, "%s", parent);
+ else
+ trace_seq_printf(s, "0x%llx", pfunction);
+ }
+
+ return 0;
+}
+
+int PEVENT_PLUGIN_LOADER(struct pevent *pevent)
+{
+ pevent_register_event_handler(pevent, -1, "ftrace", "function",
+ function_handler, NULL);
+
+ traceevent_plugin_add_options("ftrace", plugin_options);
+
+ return 0;
+}
+
+void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent)
+{
+ int i, x;
+
+ pevent_unregister_event_handler(pevent, -1, "ftrace", "function",
+ function_handler, NULL);
+
+ for (i = 0; i <= cpus; i++) {
+ for (x = 0; x < fstack[i].size && fstack[i].stack[x]; x++)
+ free(fstack[i].stack[x]);
+ free(fstack[i].stack);
+ }
+
+ traceevent_plugin_remove_options(plugin_options);
+
+ free(fstack);
+ fstack = NULL;
+ cpus = -1;
+}
diff --git a/tools/lib/traceevent/plugin_hrtimer.c b/tools/lib/traceevent/plugin_hrtimer.c
new file mode 100644
index 00000000000..12bf14cc115
--- /dev/null
+++ b/tools/lib/traceevent/plugin_hrtimer.c
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2009 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ * Copyright (C) 2009 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "event-parse.h"
+
+static int timer_expire_handler(struct trace_seq *s,
+ struct pevent_record *record,
+ struct event_format *event, void *context)
+{
+ trace_seq_printf(s, "hrtimer=");
+
+ if (pevent_print_num_field(s, "0x%llx", event, "timer",
+ record, 0) == -1)
+ pevent_print_num_field(s, "0x%llx", event, "hrtimer",
+ record, 1);
+
+ trace_seq_printf(s, " now=");
+
+ pevent_print_num_field(s, "%llu", event, "now", record, 1);
+
+ pevent_print_func_field(s, " function=%s", event, "function",
+ record, 0);
+ return 0;
+}
+
+static int timer_start_handler(struct trace_seq *s,
+ struct pevent_record *record,
+ struct event_format *event, void *context)
+{
+ trace_seq_printf(s, "hrtimer=");
+
+ if (pevent_print_num_field(s, "0x%llx", event, "timer",
+ record, 0) == -1)
+ pevent_print_num_field(s, "0x%llx", event, "hrtimer",
+ record, 1);
+
+ pevent_print_func_field(s, " function=%s", event, "function",
+ record, 0);
+
+ trace_seq_printf(s, " expires=");
+ pevent_print_num_field(s, "%llu", event, "expires", record, 1);
+
+ trace_seq_printf(s, " softexpires=");
+ pevent_print_num_field(s, "%llu", event, "softexpires", record, 1);
+ return 0;
+}
+
+int PEVENT_PLUGIN_LOADER(struct pevent *pevent)
+{
+ pevent_register_event_handler(pevent, -1,
+ "timer", "hrtimer_expire_entry",
+ timer_expire_handler, NULL);
+
+ pevent_register_event_handler(pevent, -1, "timer", "hrtimer_start",
+ timer_start_handler, NULL);
+ return 0;
+}
+
+void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent)
+{
+ pevent_unregister_event_handler(pevent, -1,
+ "timer", "hrtimer_expire_entry",
+ timer_expire_handler, NULL);
+
+ pevent_unregister_event_handler(pevent, -1, "timer", "hrtimer_start",
+ timer_start_handler, NULL);
+}
diff --git a/tools/lib/traceevent/plugin_jbd2.c b/tools/lib/traceevent/plugin_jbd2.c
new file mode 100644
index 00000000000..0db714c721b
--- /dev/null
+++ b/tools/lib/traceevent/plugin_jbd2.c
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "event-parse.h"
+
+#define MINORBITS 20
+#define MINORMASK ((1U << MINORBITS) - 1)
+
+#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
+#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
+
+static unsigned long long
+process_jbd2_dev_to_name(struct trace_seq *s,
+ unsigned long long *args)
+{
+ unsigned int dev = args[0];
+
+ trace_seq_printf(s, "%d:%d", MAJOR(dev), MINOR(dev));
+ return 0;
+}
+
+static unsigned long long
+process_jiffies_to_msecs(struct trace_seq *s,
+ unsigned long long *args)
+{
+ unsigned long long jiffies = args[0];
+
+ trace_seq_printf(s, "%lld", jiffies);
+ return jiffies;
+}
+
+int PEVENT_PLUGIN_LOADER(struct pevent *pevent)
+{
+ pevent_register_print_function(pevent,
+ process_jbd2_dev_to_name,
+ PEVENT_FUNC_ARG_STRING,
+ "jbd2_dev_to_name",
+ PEVENT_FUNC_ARG_INT,
+ PEVENT_FUNC_ARG_VOID);
+
+ pevent_register_print_function(pevent,
+ process_jiffies_to_msecs,
+ PEVENT_FUNC_ARG_LONG,
+ "jiffies_to_msecs",
+ PEVENT_FUNC_ARG_LONG,
+ PEVENT_FUNC_ARG_VOID);
+ return 0;
+}
+
+void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent)
+{
+ pevent_unregister_print_function(pevent, process_jbd2_dev_to_name,
+ "jbd2_dev_to_name");
+
+ pevent_unregister_print_function(pevent, process_jiffies_to_msecs,
+ "jiffies_to_msecs");
+}
diff --git a/tools/lib/traceevent/plugin_kmem.c b/tools/lib/traceevent/plugin_kmem.c
new file mode 100644
index 00000000000..70650ff48d7
--- /dev/null
+++ b/tools/lib/traceevent/plugin_kmem.c
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2009 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "event-parse.h"
+
+static int call_site_handler(struct trace_seq *s, struct pevent_record *record,
+ struct event_format *event, void *context)
+{
+ struct format_field *field;
+ unsigned long long val, addr;
+ void *data = record->data;
+ const char *func;
+
+ field = pevent_find_field(event, "call_site");
+ if (!field)
+ return 1;
+
+ if (pevent_read_number_field(field, data, &val))
+ return 1;
+
+ func = pevent_find_function(event->pevent, val);
+ if (!func)
+ return 1;
+
+ addr = pevent_find_function_address(event->pevent, val);
+
+ trace_seq_printf(s, "(%s+0x%x) ", func, (int)(val - addr));
+ return 1;
+}
+
+int PEVENT_PLUGIN_LOADER(struct pevent *pevent)
+{
+ pevent_register_event_handler(pevent, -1, "kmem", "kfree",
+ call_site_handler, NULL);
+
+ pevent_register_event_handler(pevent, -1, "kmem", "kmalloc",
+ call_site_handler, NULL);
+
+ pevent_register_event_handler(pevent, -1, "kmem", "kmalloc_node",
+ call_site_handler, NULL);
+
+ pevent_register_event_handler(pevent, -1, "kmem", "kmem_cache_alloc",
+ call_site_handler, NULL);
+
+ pevent_register_event_handler(pevent, -1, "kmem",
+ "kmem_cache_alloc_node",
+ call_site_handler, NULL);
+
+ pevent_register_event_handler(pevent, -1, "kmem", "kmem_cache_free",
+ call_site_handler, NULL);
+ return 0;
+}
+
+void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent)
+{
+ pevent_unregister_event_handler(pevent, -1, "kmem", "kfree",
+ call_site_handler, NULL);
+
+ pevent_unregister_event_handler(pevent, -1, "kmem", "kmalloc",
+ call_site_handler, NULL);
+
+ pevent_unregister_event_handler(pevent, -1, "kmem", "kmalloc_node",
+ call_site_handler, NULL);
+
+ pevent_unregister_event_handler(pevent, -1, "kmem", "kmem_cache_alloc",
+ call_site_handler, NULL);
+
+ pevent_unregister_event_handler(pevent, -1, "kmem",
+ "kmem_cache_alloc_node",
+ call_site_handler, NULL);
+
+ pevent_unregister_event_handler(pevent, -1, "kmem", "kmem_cache_free",
+ call_site_handler, NULL);
+}
diff --git a/tools/lib/traceevent/plugin_kvm.c b/tools/lib/traceevent/plugin_kvm.c
new file mode 100644
index 00000000000..9e0e8c61b43
--- /dev/null
+++ b/tools/lib/traceevent/plugin_kvm.c
@@ -0,0 +1,465 @@
+/*
+ * Copyright (C) 2009 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+
+#include "event-parse.h"
+
+#ifdef HAVE_UDIS86
+
+#include <udis86.h>
+
+static ud_t ud;
+
+static void init_disassembler(void)
+{
+ ud_init(&ud);
+ ud_set_syntax(&ud, UD_SYN_ATT);
+}
+
+static const char *disassemble(unsigned char *insn, int len, uint64_t rip,
+ int cr0_pe, int eflags_vm,
+ int cs_d, int cs_l)
+{
+ int mode;
+
+ if (!cr0_pe)
+ mode = 16;
+ else if (eflags_vm)
+ mode = 16;
+ else if (cs_l)
+ mode = 64;
+ else if (cs_d)
+ mode = 32;
+ else
+ mode = 16;
+
+ ud_set_pc(&ud, rip);
+ ud_set_mode(&ud, mode);
+ ud_set_input_buffer(&ud, insn, len);
+ ud_disassemble(&ud);
+ return ud_insn_asm(&ud);
+}
+
+#else
+
+static void init_disassembler(void)
+{
+}
+
+static const char *disassemble(unsigned char *insn, int len, uint64_t rip,
+ int cr0_pe, int eflags_vm,
+ int cs_d, int cs_l)
+{
+ static char out[15*3+1];
+ int i;
+
+ for (i = 0; i < len; ++i)
+ sprintf(out + i * 3, "%02x ", insn[i]);
+ out[len*3-1] = '\0';
+ return out;
+}
+
+#endif
+
+
+#define VMX_EXIT_REASONS \
+ _ER(EXCEPTION_NMI, 0) \
+ _ER(EXTERNAL_INTERRUPT, 1) \
+ _ER(TRIPLE_FAULT, 2) \
+ _ER(PENDING_INTERRUPT, 7) \
+ _ER(NMI_WINDOW, 8) \
+ _ER(TASK_SWITCH, 9) \
+ _ER(CPUID, 10) \
+ _ER(HLT, 12) \
+ _ER(INVD, 13) \
+ _ER(INVLPG, 14) \
+ _ER(RDPMC, 15) \
+ _ER(RDTSC, 16) \
+ _ER(VMCALL, 18) \
+ _ER(VMCLEAR, 19) \
+ _ER(VMLAUNCH, 20) \
+ _ER(VMPTRLD, 21) \
+ _ER(VMPTRST, 22) \
+ _ER(VMREAD, 23) \
+ _ER(VMRESUME, 24) \
+ _ER(VMWRITE, 25) \
+ _ER(VMOFF, 26) \
+ _ER(VMON, 27) \
+ _ER(CR_ACCESS, 28) \
+ _ER(DR_ACCESS, 29) \
+ _ER(IO_INSTRUCTION, 30) \
+ _ER(MSR_READ, 31) \
+ _ER(MSR_WRITE, 32) \
+ _ER(MWAIT_INSTRUCTION, 36) \
+ _ER(MONITOR_INSTRUCTION, 39) \
+ _ER(PAUSE_INSTRUCTION, 40) \
+ _ER(MCE_DURING_VMENTRY, 41) \
+ _ER(TPR_BELOW_THRESHOLD, 43) \
+ _ER(APIC_ACCESS, 44) \
+ _ER(EOI_INDUCED, 45) \
+ _ER(EPT_VIOLATION, 48) \
+ _ER(EPT_MISCONFIG, 49) \
+ _ER(INVEPT, 50) \
+ _ER(PREEMPTION_TIMER, 52) \
+ _ER(WBINVD, 54) \
+ _ER(XSETBV, 55) \
+ _ER(APIC_WRITE, 56) \
+ _ER(INVPCID, 58)
+
+#define SVM_EXIT_REASONS \
+ _ER(EXIT_READ_CR0, 0x000) \
+ _ER(EXIT_READ_CR3, 0x003) \
+ _ER(EXIT_READ_CR4, 0x004) \
+ _ER(EXIT_READ_CR8, 0x008) \
+ _ER(EXIT_WRITE_CR0, 0x010) \
+ _ER(EXIT_WRITE_CR3, 0x013) \
+ _ER(EXIT_WRITE_CR4, 0x014) \
+ _ER(EXIT_WRITE_CR8, 0x018) \
+ _ER(EXIT_READ_DR0, 0x020) \
+ _ER(EXIT_READ_DR1, 0x021) \
+ _ER(EXIT_READ_DR2, 0x022) \
+ _ER(EXIT_READ_DR3, 0x023) \
+ _ER(EXIT_READ_DR4, 0x024) \
+ _ER(EXIT_READ_DR5, 0x025) \
+ _ER(EXIT_READ_DR6, 0x026) \
+ _ER(EXIT_READ_DR7, 0x027) \
+ _ER(EXIT_WRITE_DR0, 0x030) \
+ _ER(EXIT_WRITE_DR1, 0x031) \
+ _ER(EXIT_WRITE_DR2, 0x032) \
+ _ER(EXIT_WRITE_DR3, 0x033) \
+ _ER(EXIT_WRITE_DR4, 0x034) \
+ _ER(EXIT_WRITE_DR5, 0x035) \
+ _ER(EXIT_WRITE_DR6, 0x036) \
+ _ER(EXIT_WRITE_DR7, 0x037) \
+ _ER(EXIT_EXCP_BASE, 0x040) \
+ _ER(EXIT_INTR, 0x060) \
+ _ER(EXIT_NMI, 0x061) \
+ _ER(EXIT_SMI, 0x062) \
+ _ER(EXIT_INIT, 0x063) \
+ _ER(EXIT_VINTR, 0x064) \
+ _ER(EXIT_CR0_SEL_WRITE, 0x065) \
+ _ER(EXIT_IDTR_READ, 0x066) \
+ _ER(EXIT_GDTR_READ, 0x067) \
+ _ER(EXIT_LDTR_READ, 0x068) \
+ _ER(EXIT_TR_READ, 0x069) \
+ _ER(EXIT_IDTR_WRITE, 0x06a) \
+ _ER(EXIT_GDTR_WRITE, 0x06b) \
+ _ER(EXIT_LDTR_WRITE, 0x06c) \
+ _ER(EXIT_TR_WRITE, 0x06d) \
+ _ER(EXIT_RDTSC, 0x06e) \
+ _ER(EXIT_RDPMC, 0x06f) \
+ _ER(EXIT_PUSHF, 0x070) \
+ _ER(EXIT_POPF, 0x071) \
+ _ER(EXIT_CPUID, 0x072) \
+ _ER(EXIT_RSM, 0x073) \
+ _ER(EXIT_IRET, 0x074) \
+ _ER(EXIT_SWINT, 0x075) \
+ _ER(EXIT_INVD, 0x076) \
+ _ER(EXIT_PAUSE, 0x077) \
+ _ER(EXIT_HLT, 0x078) \
+ _ER(EXIT_INVLPG, 0x079) \
+ _ER(EXIT_INVLPGA, 0x07a) \
+ _ER(EXIT_IOIO, 0x07b) \
+ _ER(EXIT_MSR, 0x07c) \
+ _ER(EXIT_TASK_SWITCH, 0x07d) \
+ _ER(EXIT_FERR_FREEZE, 0x07e) \
+ _ER(EXIT_SHUTDOWN, 0x07f) \
+ _ER(EXIT_VMRUN, 0x080) \
+ _ER(EXIT_VMMCALL, 0x081) \
+ _ER(EXIT_VMLOAD, 0x082) \
+ _ER(EXIT_VMSAVE, 0x083) \
+ _ER(EXIT_STGI, 0x084) \
+ _ER(EXIT_CLGI, 0x085) \
+ _ER(EXIT_SKINIT, 0x086) \
+ _ER(EXIT_RDTSCP, 0x087) \
+ _ER(EXIT_ICEBP, 0x088) \
+ _ER(EXIT_WBINVD, 0x089) \
+ _ER(EXIT_MONITOR, 0x08a) \
+ _ER(EXIT_MWAIT, 0x08b) \
+ _ER(EXIT_MWAIT_COND, 0x08c) \
+ _ER(EXIT_NPF, 0x400) \
+ _ER(EXIT_ERR, -1)
+
+#define _ER(reason, val) { #reason, val },
+struct str_values {
+ const char *str;
+ int val;
+};
+
+static struct str_values vmx_exit_reasons[] = {
+ VMX_EXIT_REASONS
+ { NULL, -1}
+};
+
+static struct str_values svm_exit_reasons[] = {
+ SVM_EXIT_REASONS
+ { NULL, -1}
+};
+
+static struct isa_exit_reasons {
+ unsigned isa;
+ struct str_values *strings;
+} isa_exit_reasons[] = {
+ { .isa = 1, .strings = vmx_exit_reasons },
+ { .isa = 2, .strings = svm_exit_reasons },
+ { }
+};
+
+static const char *find_exit_reason(unsigned isa, int val)
+{
+ struct str_values *strings = NULL;
+ int i;
+
+ for (i = 0; isa_exit_reasons[i].strings; ++i)
+ if (isa_exit_reasons[i].isa == isa) {
+ strings = isa_exit_reasons[i].strings;
+ break;
+ }
+ if (!strings)
+ return "UNKNOWN-ISA";
+ for (i = 0; strings[i].val >= 0; i++)
+ if (strings[i].val == val)
+ break;
+ if (strings[i].str)
+ return strings[i].str;
+ return "UNKNOWN";
+}
+
+static int kvm_exit_handler(struct trace_seq *s, struct pevent_record *record,
+ struct event_format *event, void *context)
+{
+ unsigned long long isa;
+ unsigned long long val;
+ unsigned long long info1 = 0, info2 = 0;
+
+ if (pevent_get_field_val(s, event, "exit_reason", record, &val, 1) < 0)
+ return -1;
+
+ if (pevent_get_field_val(s, event, "isa", record, &isa, 0) < 0)
+ isa = 1;
+
+ trace_seq_printf(s, "reason %s", find_exit_reason(isa, val));
+
+ pevent_print_num_field(s, " rip 0x%lx", event, "guest_rip", record, 1);
+
+ if (pevent_get_field_val(s, event, "info1", record, &info1, 0) >= 0
+ && pevent_get_field_val(s, event, "info2", record, &info2, 0) >= 0)
+ trace_seq_printf(s, " info %llx %llx", info1, info2);
+
+ return 0;
+}
+
+#define KVM_EMUL_INSN_F_CR0_PE (1 << 0)
+#define KVM_EMUL_INSN_F_EFL_VM (1 << 1)
+#define KVM_EMUL_INSN_F_CS_D (1 << 2)
+#define KVM_EMUL_INSN_F_CS_L (1 << 3)
+
+static int kvm_emulate_insn_handler(struct trace_seq *s,
+ struct pevent_record *record,
+ struct event_format *event, void *context)
+{
+ unsigned long long rip, csbase, len, flags, failed;
+ int llen;
+ uint8_t *insn;
+ const char *disasm;
+
+ if (pevent_get_field_val(s, event, "rip", record, &rip, 1) < 0)
+ return -1;
+
+ if (pevent_get_field_val(s, event, "csbase", record, &csbase, 1) < 0)
+ return -1;
+
+ if (pevent_get_field_val(s, event, "len", record, &len, 1) < 0)
+ return -1;
+
+ if (pevent_get_field_val(s, event, "flags", record, &flags, 1) < 0)
+ return -1;
+
+ if (pevent_get_field_val(s, event, "failed", record, &failed, 1) < 0)
+ return -1;
+
+ insn = pevent_get_field_raw(s, event, "insn", record, &llen, 1);
+ if (!insn)
+ return -1;
+
+ disasm = disassemble(insn, len, rip,
+ flags & KVM_EMUL_INSN_F_CR0_PE,
+ flags & KVM_EMUL_INSN_F_EFL_VM,
+ flags & KVM_EMUL_INSN_F_CS_D,
+ flags & KVM_EMUL_INSN_F_CS_L);
+
+ trace_seq_printf(s, "%llx:%llx: %s%s", csbase, rip, disasm,
+ failed ? " FAIL" : "");
+ return 0;
+}
+
+union kvm_mmu_page_role {
+ unsigned word;
+ struct {
+ unsigned glevels:4;
+ unsigned level:4;
+ unsigned quadrant:2;
+ unsigned pad_for_nice_hex_output:6;
+ unsigned direct:1;
+ unsigned access:3;
+ unsigned invalid:1;
+ unsigned cr4_pge:1;
+ unsigned nxe:1;
+ };
+};
+
+static int kvm_mmu_print_role(struct trace_seq *s, struct pevent_record *record,
+ struct event_format *event, void *context)
+{
+ unsigned long long val;
+ static const char *access_str[] = {
+ "---", "--x", "w--", "w-x", "-u-", "-ux", "wu-", "wux"
+ };
+ union kvm_mmu_page_role role;
+
+ if (pevent_get_field_val(s, event, "role", record, &val, 1) < 0)
+ return -1;
+
+ role.word = (int)val;
+
+ /*
+ * We can only use the structure if file is of the same
+ * endianess.
+ */
+ if (pevent_is_file_bigendian(event->pevent) ==
+ pevent_is_host_bigendian(event->pevent)) {
+
+ trace_seq_printf(s, "%u/%u q%u%s %s%s %spge %snxe",
+ role.level,
+ role.glevels,
+ role.quadrant,
+ role.direct ? " direct" : "",
+ access_str[role.access],
+ role.invalid ? " invalid" : "",
+ role.cr4_pge ? "" : "!",
+ role.nxe ? "" : "!");
+ } else
+ trace_seq_printf(s, "WORD: %08x", role.word);
+
+ pevent_print_num_field(s, " root %u ", event,
+ "root_count", record, 1);
+
+ if (pevent_get_field_val(s, event, "unsync", record, &val, 1) < 0)
+ return -1;
+
+ trace_seq_printf(s, "%s%c", val ? "unsync" : "sync", 0);
+ return 0;
+}
+
+static int kvm_mmu_get_page_handler(struct trace_seq *s,
+ struct pevent_record *record,
+ struct event_format *event, void *context)
+{
+ unsigned long long val;
+
+ if (pevent_get_field_val(s, event, "created", record, &val, 1) < 0)
+ return -1;
+
+ trace_seq_printf(s, "%s ", val ? "new" : "existing");
+
+ if (pevent_get_field_val(s, event, "gfn", record, &val, 1) < 0)
+ return -1;
+
+ trace_seq_printf(s, "sp gfn %llx ", val);
+ return kvm_mmu_print_role(s, record, event, context);
+}
+
+#define PT_WRITABLE_SHIFT 1
+#define PT_WRITABLE_MASK (1ULL << PT_WRITABLE_SHIFT)
+
+static unsigned long long
+process_is_writable_pte(struct trace_seq *s, unsigned long long *args)
+{
+ unsigned long pte = args[0];
+ return pte & PT_WRITABLE_MASK;
+}
+
+int PEVENT_PLUGIN_LOADER(struct pevent *pevent)
+{
+ init_disassembler();
+
+ pevent_register_event_handler(pevent, -1, "kvm", "kvm_exit",
+ kvm_exit_handler, NULL);
+
+ pevent_register_event_handler(pevent, -1, "kvm", "kvm_emulate_insn",
+ kvm_emulate_insn_handler, NULL);
+
+ pevent_register_event_handler(pevent, -1, "kvmmmu", "kvm_mmu_get_page",
+ kvm_mmu_get_page_handler, NULL);
+
+ pevent_register_event_handler(pevent, -1, "kvmmmu", "kvm_mmu_sync_page",
+ kvm_mmu_print_role, NULL);
+
+ pevent_register_event_handler(pevent, -1,
+ "kvmmmu", "kvm_mmu_unsync_page",
+ kvm_mmu_print_role, NULL);
+
+ pevent_register_event_handler(pevent, -1, "kvmmmu", "kvm_mmu_zap_page",
+ kvm_mmu_print_role, NULL);
+
+ pevent_register_event_handler(pevent, -1, "kvmmmu",
+ "kvm_mmu_prepare_zap_page", kvm_mmu_print_role,
+ NULL);
+
+ pevent_register_print_function(pevent,
+ process_is_writable_pte,
+ PEVENT_FUNC_ARG_INT,
+ "is_writable_pte",
+ PEVENT_FUNC_ARG_LONG,
+ PEVENT_FUNC_ARG_VOID);
+ return 0;
+}
+
+void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent)
+{
+ pevent_unregister_event_handler(pevent, -1, "kvm", "kvm_exit",
+ kvm_exit_handler, NULL);
+
+ pevent_unregister_event_handler(pevent, -1, "kvm", "kvm_emulate_insn",
+ kvm_emulate_insn_handler, NULL);
+
+ pevent_unregister_event_handler(pevent, -1, "kvmmmu", "kvm_mmu_get_page",
+ kvm_mmu_get_page_handler, NULL);
+
+ pevent_unregister_event_handler(pevent, -1, "kvmmmu", "kvm_mmu_sync_page",
+ kvm_mmu_print_role, NULL);
+
+ pevent_unregister_event_handler(pevent, -1,
+ "kvmmmu", "kvm_mmu_unsync_page",
+ kvm_mmu_print_role, NULL);
+
+ pevent_unregister_event_handler(pevent, -1, "kvmmmu", "kvm_mmu_zap_page",
+ kvm_mmu_print_role, NULL);
+
+ pevent_unregister_event_handler(pevent, -1, "kvmmmu",
+ "kvm_mmu_prepare_zap_page", kvm_mmu_print_role,
+ NULL);
+
+ pevent_unregister_print_function(pevent, process_is_writable_pte,
+ "is_writable_pte");
+}
diff --git a/tools/lib/traceevent/plugin_mac80211.c b/tools/lib/traceevent/plugin_mac80211.c
new file mode 100644
index 00000000000..7e15a0f1c2f
--- /dev/null
+++ b/tools/lib/traceevent/plugin_mac80211.c
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2009 Johannes Berg <johannes@sipsolutions.net>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "event-parse.h"
+
+#define INDENT 65
+
+static void print_string(struct trace_seq *s, struct event_format *event,
+ const char *name, const void *data)
+{
+ struct format_field *f = pevent_find_field(event, name);
+ int offset;
+ int length;
+
+ if (!f) {
+ trace_seq_printf(s, "NOTFOUND:%s", name);
+ return;
+ }
+
+ offset = f->offset;
+ length = f->size;
+
+ if (!strncmp(f->type, "__data_loc", 10)) {
+ unsigned long long v;
+ if (pevent_read_number_field(f, data, &v)) {
+ trace_seq_printf(s, "invalid_data_loc");
+ return;
+ }
+ offset = v & 0xffff;
+ length = v >> 16;
+ }
+
+ trace_seq_printf(s, "%.*s", length, (char *)data + offset);
+}
+
+#define SF(fn) pevent_print_num_field(s, fn ":%d", event, fn, record, 0)
+#define SFX(fn) pevent_print_num_field(s, fn ":%#x", event, fn, record, 0)
+#define SP() trace_seq_putc(s, ' ')
+
+static int drv_bss_info_changed(struct trace_seq *s,
+ struct pevent_record *record,
+ struct event_format *event, void *context)
+{
+ void *data = record->data;
+
+ print_string(s, event, "wiphy_name", data);
+ trace_seq_printf(s, " vif:");
+ print_string(s, event, "vif_name", data);
+ pevent_print_num_field(s, "(%d)", event, "vif_type", record, 1);
+
+ trace_seq_printf(s, "\n%*s", INDENT, "");
+ SF("assoc"); SP();
+ SF("aid"); SP();
+ SF("cts"); SP();
+ SF("shortpre"); SP();
+ SF("shortslot"); SP();
+ SF("dtimper"); SP();
+ trace_seq_printf(s, "\n%*s", INDENT, "");
+ SF("bcnint"); SP();
+ SFX("assoc_cap"); SP();
+ SFX("basic_rates"); SP();
+ SF("enable_beacon");
+ trace_seq_printf(s, "\n%*s", INDENT, "");
+ SF("ht_operation_mode");
+
+ return 0;
+}
+
+int PEVENT_PLUGIN_LOADER(struct pevent *pevent)
+{
+ pevent_register_event_handler(pevent, -1, "mac80211",
+ "drv_bss_info_changed",
+ drv_bss_info_changed, NULL);
+ return 0;
+}
+
+void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent)
+{
+ pevent_unregister_event_handler(pevent, -1, "mac80211",
+ "drv_bss_info_changed",
+ drv_bss_info_changed, NULL);
+}
diff --git a/tools/lib/traceevent/plugin_sched_switch.c b/tools/lib/traceevent/plugin_sched_switch.c
new file mode 100644
index 00000000000..f1ce6006525
--- /dev/null
+++ b/tools/lib/traceevent/plugin_sched_switch.c
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "event-parse.h"
+
+static void write_state(struct trace_seq *s, int val)
+{
+ const char states[] = "SDTtZXxW";
+ int found = 0;
+ int i;
+
+ for (i = 0; i < (sizeof(states) - 1); i++) {
+ if (!(val & (1 << i)))
+ continue;
+
+ if (found)
+ trace_seq_putc(s, '|');
+
+ found = 1;
+ trace_seq_putc(s, states[i]);
+ }
+
+ if (!found)
+ trace_seq_putc(s, 'R');
+}
+
+static void write_and_save_comm(struct format_field *field,
+ struct pevent_record *record,
+ struct trace_seq *s, int pid)
+{
+ const char *comm;
+ int len;
+
+ comm = (char *)(record->data + field->offset);
+ len = s->len;
+ trace_seq_printf(s, "%.*s",
+ field->size, comm);
+
+ /* make sure the comm has a \0 at the end. */
+ trace_seq_terminate(s);
+ comm = &s->buffer[len];
+
+ /* Help out the comm to ids. This will handle dups */
+ pevent_register_comm(field->event->pevent, comm, pid);
+}
+
+static int sched_wakeup_handler(struct trace_seq *s,
+ struct pevent_record *record,
+ struct event_format *event, void *context)
+{
+ struct format_field *field;
+ unsigned long long val;
+
+ if (pevent_get_field_val(s, event, "pid", record, &val, 1))
+ return trace_seq_putc(s, '!');
+
+ field = pevent_find_any_field(event, "comm");
+ if (field) {
+ write_and_save_comm(field, record, s, val);
+ trace_seq_putc(s, ':');
+ }
+ trace_seq_printf(s, "%lld", val);
+
+ if (pevent_get_field_val(s, event, "prio", record, &val, 0) == 0)
+ trace_seq_printf(s, " [%lld]", val);
+
+ if (pevent_get_field_val(s, event, "success", record, &val, 1) == 0)
+ trace_seq_printf(s, " success=%lld", val);
+
+ if (pevent_get_field_val(s, event, "target_cpu", record, &val, 0) == 0)
+ trace_seq_printf(s, " CPU:%03llu", val);
+
+ return 0;
+}
+
+static int sched_switch_handler(struct trace_seq *s,
+ struct pevent_record *record,
+ struct event_format *event, void *context)
+{
+ struct format_field *field;
+ unsigned long long val;
+
+ if (pevent_get_field_val(s, event, "prev_pid", record, &val, 1))
+ return trace_seq_putc(s, '!');
+
+ field = pevent_find_any_field(event, "prev_comm");
+ if (field) {
+ write_and_save_comm(field, record, s, val);
+ trace_seq_putc(s, ':');
+ }
+ trace_seq_printf(s, "%lld ", val);
+
+ if (pevent_get_field_val(s, event, "prev_prio", record, &val, 0) == 0)
+ trace_seq_printf(s, "[%lld] ", val);
+
+ if (pevent_get_field_val(s, event, "prev_state", record, &val, 0) == 0)
+ write_state(s, val);
+
+ trace_seq_puts(s, " ==> ");
+
+ if (pevent_get_field_val(s, event, "next_pid", record, &val, 1))
+ return trace_seq_putc(s, '!');
+
+ field = pevent_find_any_field(event, "next_comm");
+ if (field) {
+ write_and_save_comm(field, record, s, val);
+ trace_seq_putc(s, ':');
+ }
+ trace_seq_printf(s, "%lld", val);
+
+ if (pevent_get_field_val(s, event, "next_prio", record, &val, 0) == 0)
+ trace_seq_printf(s, " [%lld]", val);
+
+ return 0;
+}
+
+int PEVENT_PLUGIN_LOADER(struct pevent *pevent)
+{
+ pevent_register_event_handler(pevent, -1, "sched", "sched_switch",
+ sched_switch_handler, NULL);
+
+ pevent_register_event_handler(pevent, -1, "sched", "sched_wakeup",
+ sched_wakeup_handler, NULL);
+
+ pevent_register_event_handler(pevent, -1, "sched", "sched_wakeup_new",
+ sched_wakeup_handler, NULL);
+ return 0;
+}
+
+void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent)
+{
+ pevent_unregister_event_handler(pevent, -1, "sched", "sched_switch",
+ sched_switch_handler, NULL);
+
+ pevent_unregister_event_handler(pevent, -1, "sched", "sched_wakeup",
+ sched_wakeup_handler, NULL);
+
+ pevent_unregister_event_handler(pevent, -1, "sched", "sched_wakeup_new",
+ sched_wakeup_handler, NULL);
+}
diff --git a/tools/lib/traceevent/plugin_scsi.c b/tools/lib/traceevent/plugin_scsi.c
new file mode 100644
index 00000000000..eda326fc862
--- /dev/null
+++ b/tools/lib/traceevent/plugin_scsi.c
@@ -0,0 +1,429 @@
+#include <stdio.h>
+#include <string.h>
+#include <inttypes.h>
+#include "event-parse.h"
+
+typedef unsigned long sector_t;
+typedef uint64_t u64;
+typedef unsigned int u32;
+
+/*
+ * SCSI opcodes
+ */
+#define TEST_UNIT_READY 0x00
+#define REZERO_UNIT 0x01
+#define REQUEST_SENSE 0x03
+#define FORMAT_UNIT 0x04
+#define READ_BLOCK_LIMITS 0x05
+#define REASSIGN_BLOCKS 0x07
+#define INITIALIZE_ELEMENT_STATUS 0x07
+#define READ_6 0x08
+#define WRITE_6 0x0a
+#define SEEK_6 0x0b
+#define READ_REVERSE 0x0f
+#define WRITE_FILEMARKS 0x10
+#define SPACE 0x11
+#define INQUIRY 0x12
+#define RECOVER_BUFFERED_DATA 0x14
+#define MODE_SELECT 0x15
+#define RESERVE 0x16
+#define RELEASE 0x17
+#define COPY 0x18
+#define ERASE 0x19
+#define MODE_SENSE 0x1a
+#define START_STOP 0x1b
+#define RECEIVE_DIAGNOSTIC 0x1c
+#define SEND_DIAGNOSTIC 0x1d
+#define ALLOW_MEDIUM_REMOVAL 0x1e
+
+#define READ_FORMAT_CAPACITIES 0x23
+#define SET_WINDOW 0x24
+#define READ_CAPACITY 0x25
+#define READ_10 0x28
+#define WRITE_10 0x2a
+#define SEEK_10 0x2b
+#define POSITION_TO_ELEMENT 0x2b
+#define WRITE_VERIFY 0x2e
+#define VERIFY 0x2f
+#define SEARCH_HIGH 0x30
+#define SEARCH_EQUAL 0x31
+#define SEARCH_LOW 0x32
+#define SET_LIMITS 0x33
+#define PRE_FETCH 0x34
+#define READ_POSITION 0x34
+#define SYNCHRONIZE_CACHE 0x35
+#define LOCK_UNLOCK_CACHE 0x36
+#define READ_DEFECT_DATA 0x37
+#define MEDIUM_SCAN 0x38
+#define COMPARE 0x39
+#define COPY_VERIFY 0x3a
+#define WRITE_BUFFER 0x3b
+#define READ_BUFFER 0x3c
+#define UPDATE_BLOCK 0x3d
+#define READ_LONG 0x3e
+#define WRITE_LONG 0x3f
+#define CHANGE_DEFINITION 0x40
+#define WRITE_SAME 0x41
+#define UNMAP 0x42
+#define READ_TOC 0x43
+#define READ_HEADER 0x44
+#define GET_EVENT_STATUS_NOTIFICATION 0x4a
+#define LOG_SELECT 0x4c
+#define LOG_SENSE 0x4d
+#define XDWRITEREAD_10 0x53
+#define MODE_SELECT_10 0x55
+#define RESERVE_10 0x56
+#define RELEASE_10 0x57
+#define MODE_SENSE_10 0x5a
+#define PERSISTENT_RESERVE_IN 0x5e
+#define PERSISTENT_RESERVE_OUT 0x5f
+#define VARIABLE_LENGTH_CMD 0x7f
+#define REPORT_LUNS 0xa0
+#define SECURITY_PROTOCOL_IN 0xa2
+#define MAINTENANCE_IN 0xa3
+#define MAINTENANCE_OUT 0xa4
+#define MOVE_MEDIUM 0xa5
+#define EXCHANGE_MEDIUM 0xa6
+#define READ_12 0xa8
+#define WRITE_12 0xaa
+#define READ_MEDIA_SERIAL_NUMBER 0xab
+#define WRITE_VERIFY_12 0xae
+#define VERIFY_12 0xaf
+#define SEARCH_HIGH_12 0xb0
+#define SEARCH_EQUAL_12 0xb1
+#define SEARCH_LOW_12 0xb2
+#define SECURITY_PROTOCOL_OUT 0xb5
+#define READ_ELEMENT_STATUS 0xb8
+#define SEND_VOLUME_TAG 0xb6
+#define WRITE_LONG_2 0xea
+#define EXTENDED_COPY 0x83
+#define RECEIVE_COPY_RESULTS 0x84
+#define ACCESS_CONTROL_IN 0x86
+#define ACCESS_CONTROL_OUT 0x87
+#define READ_16 0x88
+#define WRITE_16 0x8a
+#define READ_ATTRIBUTE 0x8c
+#define WRITE_ATTRIBUTE 0x8d
+#define VERIFY_16 0x8f
+#define SYNCHRONIZE_CACHE_16 0x91
+#define WRITE_SAME_16 0x93
+#define SERVICE_ACTION_IN 0x9e
+/* values for service action in */
+#define SAI_READ_CAPACITY_16 0x10
+#define SAI_GET_LBA_STATUS 0x12
+/* values for VARIABLE_LENGTH_CMD service action codes
+ * see spc4r17 Section D.3.5, table D.7 and D.8 */
+#define VLC_SA_RECEIVE_CREDENTIAL 0x1800
+/* values for maintenance in */
+#define MI_REPORT_IDENTIFYING_INFORMATION 0x05
+#define MI_REPORT_TARGET_PGS 0x0a
+#define MI_REPORT_ALIASES 0x0b
+#define MI_REPORT_SUPPORTED_OPERATION_CODES 0x0c
+#define MI_REPORT_SUPPORTED_TASK_MANAGEMENT_FUNCTIONS 0x0d
+#define MI_REPORT_PRIORITY 0x0e
+#define MI_REPORT_TIMESTAMP 0x0f
+#define MI_MANAGEMENT_PROTOCOL_IN 0x10
+/* value for MI_REPORT_TARGET_PGS ext header */
+#define MI_EXT_HDR_PARAM_FMT 0x20
+/* values for maintenance out */
+#define MO_SET_IDENTIFYING_INFORMATION 0x06
+#define MO_SET_TARGET_PGS 0x0a
+#define MO_CHANGE_ALIASES 0x0b
+#define MO_SET_PRIORITY 0x0e
+#define MO_SET_TIMESTAMP 0x0f
+#define MO_MANAGEMENT_PROTOCOL_OUT 0x10
+/* values for variable length command */
+#define XDREAD_32 0x03
+#define XDWRITE_32 0x04
+#define XPWRITE_32 0x06
+#define XDWRITEREAD_32 0x07
+#define READ_32 0x09
+#define VERIFY_32 0x0a
+#define WRITE_32 0x0b
+#define WRITE_SAME_32 0x0d
+
+#define SERVICE_ACTION16(cdb) (cdb[1] & 0x1f)
+#define SERVICE_ACTION32(cdb) ((cdb[8] << 8) | cdb[9])
+
+static const char *
+scsi_trace_misc(struct trace_seq *, unsigned char *, int);
+
+static const char *
+scsi_trace_rw6(struct trace_seq *p, unsigned char *cdb, int len)
+{
+ const char *ret = p->buffer + p->len;
+ sector_t lba = 0, txlen = 0;
+
+ lba |= ((cdb[1] & 0x1F) << 16);
+ lba |= (cdb[2] << 8);
+ lba |= cdb[3];
+ txlen = cdb[4];
+
+ trace_seq_printf(p, "lba=%llu txlen=%llu",
+ (unsigned long long)lba, (unsigned long long)txlen);
+ trace_seq_putc(p, 0);
+ return ret;
+}
+
+static const char *
+scsi_trace_rw10(struct trace_seq *p, unsigned char *cdb, int len)
+{
+ const char *ret = p->buffer + p->len;
+ sector_t lba = 0, txlen = 0;
+
+ lba |= (cdb[2] << 24);
+ lba |= (cdb[3] << 16);
+ lba |= (cdb[4] << 8);
+ lba |= cdb[5];
+ txlen |= (cdb[7] << 8);
+ txlen |= cdb[8];
+
+ trace_seq_printf(p, "lba=%llu txlen=%llu protect=%u",
+ (unsigned long long)lba, (unsigned long long)txlen,
+ cdb[1] >> 5);
+
+ if (cdb[0] == WRITE_SAME)
+ trace_seq_printf(p, " unmap=%u", cdb[1] >> 3 & 1);
+
+ trace_seq_putc(p, 0);
+ return ret;
+}
+
+static const char *
+scsi_trace_rw12(struct trace_seq *p, unsigned char *cdb, int len)
+{
+ const char *ret = p->buffer + p->len;
+ sector_t lba = 0, txlen = 0;
+
+ lba |= (cdb[2] << 24);
+ lba |= (cdb[3] << 16);
+ lba |= (cdb[4] << 8);
+ lba |= cdb[5];
+ txlen |= (cdb[6] << 24);
+ txlen |= (cdb[7] << 16);
+ txlen |= (cdb[8] << 8);
+ txlen |= cdb[9];
+
+ trace_seq_printf(p, "lba=%llu txlen=%llu protect=%u",
+ (unsigned long long)lba, (unsigned long long)txlen,
+ cdb[1] >> 5);
+ trace_seq_putc(p, 0);
+ return ret;
+}
+
+static const char *
+scsi_trace_rw16(struct trace_seq *p, unsigned char *cdb, int len)
+{
+ const char *ret = p->buffer + p->len;
+ sector_t lba = 0, txlen = 0;
+
+ lba |= ((u64)cdb[2] << 56);
+ lba |= ((u64)cdb[3] << 48);
+ lba |= ((u64)cdb[4] << 40);
+ lba |= ((u64)cdb[5] << 32);
+ lba |= (cdb[6] << 24);
+ lba |= (cdb[7] << 16);
+ lba |= (cdb[8] << 8);
+ lba |= cdb[9];
+ txlen |= (cdb[10] << 24);
+ txlen |= (cdb[11] << 16);
+ txlen |= (cdb[12] << 8);
+ txlen |= cdb[13];
+
+ trace_seq_printf(p, "lba=%llu txlen=%llu protect=%u",
+ (unsigned long long)lba, (unsigned long long)txlen,
+ cdb[1] >> 5);
+
+ if (cdb[0] == WRITE_SAME_16)
+ trace_seq_printf(p, " unmap=%u", cdb[1] >> 3 & 1);
+
+ trace_seq_putc(p, 0);
+ return ret;
+}
+
+static const char *
+scsi_trace_rw32(struct trace_seq *p, unsigned char *cdb, int len)
+{
+ const char *ret = p->buffer + p->len, *cmd;
+ sector_t lba = 0, txlen = 0;
+ u32 ei_lbrt = 0;
+
+ switch (SERVICE_ACTION32(cdb)) {
+ case READ_32:
+ cmd = "READ";
+ break;
+ case VERIFY_32:
+ cmd = "VERIFY";
+ break;
+ case WRITE_32:
+ cmd = "WRITE";
+ break;
+ case WRITE_SAME_32:
+ cmd = "WRITE_SAME";
+ break;
+ default:
+ trace_seq_printf(p, "UNKNOWN");
+ goto out;
+ }
+
+ lba |= ((u64)cdb[12] << 56);
+ lba |= ((u64)cdb[13] << 48);
+ lba |= ((u64)cdb[14] << 40);
+ lba |= ((u64)cdb[15] << 32);
+ lba |= (cdb[16] << 24);
+ lba |= (cdb[17] << 16);
+ lba |= (cdb[18] << 8);
+ lba |= cdb[19];
+ ei_lbrt |= (cdb[20] << 24);
+ ei_lbrt |= (cdb[21] << 16);
+ ei_lbrt |= (cdb[22] << 8);
+ ei_lbrt |= cdb[23];
+ txlen |= (cdb[28] << 24);
+ txlen |= (cdb[29] << 16);
+ txlen |= (cdb[30] << 8);
+ txlen |= cdb[31];
+
+ trace_seq_printf(p, "%s_32 lba=%llu txlen=%llu protect=%u ei_lbrt=%u",
+ cmd, (unsigned long long)lba,
+ (unsigned long long)txlen, cdb[10] >> 5, ei_lbrt);
+
+ if (SERVICE_ACTION32(cdb) == WRITE_SAME_32)
+ trace_seq_printf(p, " unmap=%u", cdb[10] >> 3 & 1);
+
+out:
+ trace_seq_putc(p, 0);
+ return ret;
+}
+
+static const char *
+scsi_trace_unmap(struct trace_seq *p, unsigned char *cdb, int len)
+{
+ const char *ret = p->buffer + p->len;
+ unsigned int regions = cdb[7] << 8 | cdb[8];
+
+ trace_seq_printf(p, "regions=%u", (regions - 8) / 16);
+ trace_seq_putc(p, 0);
+ return ret;
+}
+
+static const char *
+scsi_trace_service_action_in(struct trace_seq *p, unsigned char *cdb, int len)
+{
+ const char *ret = p->buffer + p->len, *cmd;
+ sector_t lba = 0;
+ u32 alloc_len = 0;
+
+ switch (SERVICE_ACTION16(cdb)) {
+ case SAI_READ_CAPACITY_16:
+ cmd = "READ_CAPACITY_16";
+ break;
+ case SAI_GET_LBA_STATUS:
+ cmd = "GET_LBA_STATUS";
+ break;
+ default:
+ trace_seq_printf(p, "UNKNOWN");
+ goto out;
+ }
+
+ lba |= ((u64)cdb[2] << 56);
+ lba |= ((u64)cdb[3] << 48);
+ lba |= ((u64)cdb[4] << 40);
+ lba |= ((u64)cdb[5] << 32);
+ lba |= (cdb[6] << 24);
+ lba |= (cdb[7] << 16);
+ lba |= (cdb[8] << 8);
+ lba |= cdb[9];
+ alloc_len |= (cdb[10] << 24);
+ alloc_len |= (cdb[11] << 16);
+ alloc_len |= (cdb[12] << 8);
+ alloc_len |= cdb[13];
+
+ trace_seq_printf(p, "%s lba=%llu alloc_len=%u", cmd,
+ (unsigned long long)lba, alloc_len);
+
+out:
+ trace_seq_putc(p, 0);
+ return ret;
+}
+
+static const char *
+scsi_trace_varlen(struct trace_seq *p, unsigned char *cdb, int len)
+{
+ switch (SERVICE_ACTION32(cdb)) {
+ case READ_32:
+ case VERIFY_32:
+ case WRITE_32:
+ case WRITE_SAME_32:
+ return scsi_trace_rw32(p, cdb, len);
+ default:
+ return scsi_trace_misc(p, cdb, len);
+ }
+}
+
+static const char *
+scsi_trace_misc(struct trace_seq *p, unsigned char *cdb, int len)
+{
+ const char *ret = p->buffer + p->len;
+
+ trace_seq_printf(p, "-");
+ trace_seq_putc(p, 0);
+ return ret;
+}
+
+const char *
+scsi_trace_parse_cdb(struct trace_seq *p, unsigned char *cdb, int len)
+{
+ switch (cdb[0]) {
+ case READ_6:
+ case WRITE_6:
+ return scsi_trace_rw6(p, cdb, len);
+ case READ_10:
+ case VERIFY:
+ case WRITE_10:
+ case WRITE_SAME:
+ return scsi_trace_rw10(p, cdb, len);
+ case READ_12:
+ case VERIFY_12:
+ case WRITE_12:
+ return scsi_trace_rw12(p, cdb, len);
+ case READ_16:
+ case VERIFY_16:
+ case WRITE_16:
+ case WRITE_SAME_16:
+ return scsi_trace_rw16(p, cdb, len);
+ case UNMAP:
+ return scsi_trace_unmap(p, cdb, len);
+ case SERVICE_ACTION_IN:
+ return scsi_trace_service_action_in(p, cdb, len);
+ case VARIABLE_LENGTH_CMD:
+ return scsi_trace_varlen(p, cdb, len);
+ default:
+ return scsi_trace_misc(p, cdb, len);
+ }
+}
+
+unsigned long long process_scsi_trace_parse_cdb(struct trace_seq *s,
+ unsigned long long *args)
+{
+ scsi_trace_parse_cdb(s, (unsigned char *) (unsigned long) args[1], args[2]);
+ return 0;
+}
+
+int PEVENT_PLUGIN_LOADER(struct pevent *pevent)
+{
+ pevent_register_print_function(pevent,
+ process_scsi_trace_parse_cdb,
+ PEVENT_FUNC_ARG_STRING,
+ "scsi_trace_parse_cdb",
+ PEVENT_FUNC_ARG_PTR,
+ PEVENT_FUNC_ARG_PTR,
+ PEVENT_FUNC_ARG_INT,
+ PEVENT_FUNC_ARG_VOID);
+ return 0;
+}
+
+void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent)
+{
+ pevent_unregister_print_function(pevent, process_scsi_trace_parse_cdb,
+ "scsi_trace_parse_cdb");
+}
diff --git a/tools/lib/traceevent/plugin_xen.c b/tools/lib/traceevent/plugin_xen.c
new file mode 100644
index 00000000000..3a413eaada6
--- /dev/null
+++ b/tools/lib/traceevent/plugin_xen.c
@@ -0,0 +1,136 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "event-parse.h"
+
+#define __HYPERVISOR_set_trap_table 0
+#define __HYPERVISOR_mmu_update 1
+#define __HYPERVISOR_set_gdt 2
+#define __HYPERVISOR_stack_switch 3
+#define __HYPERVISOR_set_callbacks 4
+#define __HYPERVISOR_fpu_taskswitch 5
+#define __HYPERVISOR_sched_op_compat 6
+#define __HYPERVISOR_dom0_op 7
+#define __HYPERVISOR_set_debugreg 8
+#define __HYPERVISOR_get_debugreg 9
+#define __HYPERVISOR_update_descriptor 10
+#define __HYPERVISOR_memory_op 12
+#define __HYPERVISOR_multicall 13
+#define __HYPERVISOR_update_va_mapping 14
+#define __HYPERVISOR_set_timer_op 15
+#define __HYPERVISOR_event_channel_op_compat 16
+#define __HYPERVISOR_xen_version 17
+#define __HYPERVISOR_console_io 18
+#define __HYPERVISOR_physdev_op_compat 19
+#define __HYPERVISOR_grant_table_op 20
+#define __HYPERVISOR_vm_assist 21
+#define __HYPERVISOR_update_va_mapping_otherdomain 22
+#define __HYPERVISOR_iret 23 /* x86 only */
+#define __HYPERVISOR_vcpu_op 24
+#define __HYPERVISOR_set_segment_base 25 /* x86/64 only */
+#define __HYPERVISOR_mmuext_op 26
+#define __HYPERVISOR_acm_op 27
+#define __HYPERVISOR_nmi_op 28
+#define __HYPERVISOR_sched_op 29
+#define __HYPERVISOR_callback_op 30
+#define __HYPERVISOR_xenoprof_op 31
+#define __HYPERVISOR_event_channel_op 32
+#define __HYPERVISOR_physdev_op 33
+#define __HYPERVISOR_hvm_op 34
+#define __HYPERVISOR_tmem_op 38
+
+/* Architecture-specific hypercall definitions. */
+#define __HYPERVISOR_arch_0 48
+#define __HYPERVISOR_arch_1 49
+#define __HYPERVISOR_arch_2 50
+#define __HYPERVISOR_arch_3 51
+#define __HYPERVISOR_arch_4 52
+#define __HYPERVISOR_arch_5 53
+#define __HYPERVISOR_arch_6 54
+#define __HYPERVISOR_arch_7 55
+
+#define N(x) [__HYPERVISOR_##x] = "("#x")"
+static const char *xen_hypercall_names[] = {
+ N(set_trap_table),
+ N(mmu_update),
+ N(set_gdt),
+ N(stack_switch),
+ N(set_callbacks),
+ N(fpu_taskswitch),
+ N(sched_op_compat),
+ N(dom0_op),
+ N(set_debugreg),
+ N(get_debugreg),
+ N(update_descriptor),
+ N(memory_op),
+ N(multicall),
+ N(update_va_mapping),
+ N(set_timer_op),
+ N(event_channel_op_compat),
+ N(xen_version),
+ N(console_io),
+ N(physdev_op_compat),
+ N(grant_table_op),
+ N(vm_assist),
+ N(update_va_mapping_otherdomain),
+ N(iret),
+ N(vcpu_op),
+ N(set_segment_base),
+ N(mmuext_op),
+ N(acm_op),
+ N(nmi_op),
+ N(sched_op),
+ N(callback_op),
+ N(xenoprof_op),
+ N(event_channel_op),
+ N(physdev_op),
+ N(hvm_op),
+
+/* Architecture-specific hypercall definitions. */
+ N(arch_0),
+ N(arch_1),
+ N(arch_2),
+ N(arch_3),
+ N(arch_4),
+ N(arch_5),
+ N(arch_6),
+ N(arch_7),
+};
+#undef N
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+
+static const char *xen_hypercall_name(unsigned op)
+{
+ if (op < ARRAY_SIZE(xen_hypercall_names) &&
+ xen_hypercall_names[op] != NULL)
+ return xen_hypercall_names[op];
+
+ return "";
+}
+
+unsigned long long process_xen_hypercall_name(struct trace_seq *s,
+ unsigned long long *args)
+{
+ unsigned int op = args[0];
+
+ trace_seq_printf(s, "%s", xen_hypercall_name(op));
+ return 0;
+}
+
+int PEVENT_PLUGIN_LOADER(struct pevent *pevent)
+{
+ pevent_register_print_function(pevent,
+ process_xen_hypercall_name,
+ PEVENT_FUNC_ARG_STRING,
+ "xen_hypercall_name",
+ PEVENT_FUNC_ARG_INT,
+ PEVENT_FUNC_ARG_VOID);
+ return 0;
+}
+
+void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent)
+{
+ pevent_unregister_print_function(pevent, process_xen_hypercall_name,
+ "xen_hypercall_name");
+}
diff --git a/tools/lib/traceevent/trace-seq.c b/tools/lib/traceevent/trace-seq.c
new file mode 100644
index 00000000000..ec3bd16a548
--- /dev/null
+++ b/tools/lib/traceevent/trace-seq.c
@@ -0,0 +1,249 @@
+/*
+ * Copyright (C) 2009 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation;
+ * version 2.1 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, see <http://www.gnu.org/licenses>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include <asm/bug.h>
+#include "event-parse.h"
+#include "event-utils.h"
+
+/*
+ * The TRACE_SEQ_POISON is to catch the use of using
+ * a trace_seq structure after it was destroyed.
+ */
+#define TRACE_SEQ_POISON ((void *)0xdeadbeef)
+#define TRACE_SEQ_CHECK(s) \
+do { \
+ if (WARN_ONCE((s)->buffer == TRACE_SEQ_POISON, \
+ "Usage of trace_seq after it was destroyed")) \
+ (s)->state = TRACE_SEQ__BUFFER_POISONED; \
+} while (0)
+
+#define TRACE_SEQ_CHECK_RET_N(s, n) \
+do { \
+ TRACE_SEQ_CHECK(s); \
+ if ((s)->state != TRACE_SEQ__GOOD) \
+ return n; \
+} while (0)
+
+#define TRACE_SEQ_CHECK_RET(s) TRACE_SEQ_CHECK_RET_N(s, )
+#define TRACE_SEQ_CHECK_RET0(s) TRACE_SEQ_CHECK_RET_N(s, 0)
+
+/**
+ * trace_seq_init - initialize the trace_seq structure
+ * @s: a pointer to the trace_seq structure to initialize
+ */
+void trace_seq_init(struct trace_seq *s)
+{
+ s->len = 0;
+ s->readpos = 0;
+ s->buffer_size = TRACE_SEQ_BUF_SIZE;
+ s->buffer = malloc(s->buffer_size);
+ if (s->buffer != NULL)
+ s->state = TRACE_SEQ__GOOD;
+ else
+ s->state = TRACE_SEQ__MEM_ALLOC_FAILED;
+}
+
+/**
+ * trace_seq_reset - re-initialize the trace_seq structure
+ * @s: a pointer to the trace_seq structure to reset
+ */
+void trace_seq_reset(struct trace_seq *s)
+{
+ if (!s)
+ return;
+ TRACE_SEQ_CHECK(s);
+ s->len = 0;
+ s->readpos = 0;
+}
+
+/**
+ * trace_seq_destroy - free up memory of a trace_seq
+ * @s: a pointer to the trace_seq to free the buffer
+ *
+ * Only frees the buffer, not the trace_seq struct itself.
+ */
+void trace_seq_destroy(struct trace_seq *s)
+{
+ if (!s)
+ return;
+ TRACE_SEQ_CHECK_RET(s);
+ free(s->buffer);
+ s->buffer = TRACE_SEQ_POISON;
+}
+
+static void expand_buffer(struct trace_seq *s)
+{
+ char *buf;
+
+ buf = realloc(s->buffer, s->buffer_size + TRACE_SEQ_BUF_SIZE);
+ if (WARN_ONCE(!buf, "Can't allocate trace_seq buffer memory")) {
+ s->state = TRACE_SEQ__MEM_ALLOC_FAILED;
+ return;
+ }
+
+ s->buffer = buf;
+ s->buffer_size += TRACE_SEQ_BUF_SIZE;
+}
+
+/**
+ * trace_seq_printf - sequence printing of trace information
+ * @s: trace sequence descriptor
+ * @fmt: printf format string
+ *
+ * It returns 0 if the trace oversizes the buffer's free
+ * space, 1 otherwise.
+ *
+ * The tracer may use either sequence operations or its own
+ * copy to user routines. To simplify formating of a trace
+ * trace_seq_printf is used to store strings into a special
+ * buffer (@s). Then the output may be either used by
+ * the sequencer or pulled into another buffer.
+ */
+int
+trace_seq_printf(struct trace_seq *s, const char *fmt, ...)
+{
+ va_list ap;
+ int len;
+ int ret;
+
+ try_again:
+ TRACE_SEQ_CHECK_RET0(s);
+
+ len = (s->buffer_size - 1) - s->len;
+
+ va_start(ap, fmt);
+ ret = vsnprintf(s->buffer + s->len, len, fmt, ap);
+ va_end(ap);
+
+ if (ret >= len) {
+ expand_buffer(s);
+ goto try_again;
+ }
+
+ s->len += ret;
+
+ return 1;
+}
+
+/**
+ * trace_seq_vprintf - sequence printing of trace information
+ * @s: trace sequence descriptor
+ * @fmt: printf format string
+ *
+ * The tracer may use either sequence operations or its own
+ * copy to user routines. To simplify formating of a trace
+ * trace_seq_printf is used to store strings into a special
+ * buffer (@s). Then the output may be either used by
+ * the sequencer or pulled into another buffer.
+ */
+int
+trace_seq_vprintf(struct trace_seq *s, const char *fmt, va_list args)
+{
+ int len;
+ int ret;
+
+ try_again:
+ TRACE_SEQ_CHECK_RET0(s);
+
+ len = (s->buffer_size - 1) - s->len;
+
+ ret = vsnprintf(s->buffer + s->len, len, fmt, args);
+
+ if (ret >= len) {
+ expand_buffer(s);
+ goto try_again;
+ }
+
+ s->len += ret;
+
+ return len;
+}
+
+/**
+ * trace_seq_puts - trace sequence printing of simple string
+ * @s: trace sequence descriptor
+ * @str: simple string to record
+ *
+ * The tracer may use either the sequence operations or its own
+ * copy to user routines. This function records a simple string
+ * into a special buffer (@s) for later retrieval by a sequencer
+ * or other mechanism.
+ */
+int trace_seq_puts(struct trace_seq *s, const char *str)
+{
+ int len;
+
+ TRACE_SEQ_CHECK_RET0(s);
+
+ len = strlen(str);
+
+ while (len > ((s->buffer_size - 1) - s->len))
+ expand_buffer(s);
+
+ TRACE_SEQ_CHECK_RET0(s);
+
+ memcpy(s->buffer + s->len, str, len);
+ s->len += len;
+
+ return len;
+}
+
+int trace_seq_putc(struct trace_seq *s, unsigned char c)
+{
+ TRACE_SEQ_CHECK_RET0(s);
+
+ while (s->len >= (s->buffer_size - 1))
+ expand_buffer(s);
+
+ TRACE_SEQ_CHECK_RET0(s);
+
+ s->buffer[s->len++] = c;
+
+ return 1;
+}
+
+void trace_seq_terminate(struct trace_seq *s)
+{
+ TRACE_SEQ_CHECK_RET(s);
+
+ /* There's always one character left on the buffer */
+ s->buffer[s->len] = 0;
+}
+
+int trace_seq_do_printf(struct trace_seq *s)
+{
+ TRACE_SEQ_CHECK(s);
+
+ switch (s->state) {
+ case TRACE_SEQ__GOOD:
+ return printf("%.*s", s->len, s->buffer);
+ case TRACE_SEQ__BUFFER_POISONED:
+ puts("Usage of trace_seq after it was destroyed");
+ break;
+ case TRACE_SEQ__MEM_ALLOC_FAILED:
+ puts("Can't allocate trace_seq buffer memory");
+ break;
+ }
+ return -1;
+}
diff --git a/tools/net/Makefile b/tools/net/Makefile
new file mode 100644
index 00000000000..ee577ea03ba
--- /dev/null
+++ b/tools/net/Makefile
@@ -0,0 +1,34 @@
+prefix = /usr
+
+CC = gcc
+LEX = flex
+YACC = bison
+
+%.yacc.c: %.y
+ $(YACC) -o $@ -d $<
+
+%.lex.c: %.l
+ $(LEX) -o $@ $<
+
+all : bpf_jit_disasm bpf_dbg bpf_asm
+
+bpf_jit_disasm : CFLAGS = -Wall -O2 -DPACKAGE='bpf_jit_disasm'
+bpf_jit_disasm : LDLIBS = -lopcodes -lbfd -ldl
+bpf_jit_disasm : bpf_jit_disasm.o
+
+bpf_dbg : CFLAGS = -Wall -O2
+bpf_dbg : LDLIBS = -lreadline
+bpf_dbg : bpf_dbg.o
+
+bpf_asm : CFLAGS = -Wall -O2 -I.
+bpf_asm : LDLIBS =
+bpf_asm : bpf_asm.o bpf_exp.yacc.o bpf_exp.lex.o
+bpf_exp.lex.o : bpf_exp.yacc.c
+
+clean :
+ rm -rf *.o bpf_jit_disasm bpf_dbg bpf_asm bpf_exp.yacc.* bpf_exp.lex.*
+
+install :
+ install bpf_jit_disasm $(prefix)/bin/bpf_jit_disasm
+ install bpf_dbg $(prefix)/bin/bpf_dbg
+ install bpf_asm $(prefix)/bin/bpf_asm
diff --git a/tools/net/bpf_asm.c b/tools/net/bpf_asm.c
new file mode 100644
index 00000000000..c15aef097b0
--- /dev/null
+++ b/tools/net/bpf_asm.c
@@ -0,0 +1,52 @@
+/*
+ * Minimal BPF assembler
+ *
+ * Instead of libpcap high-level filter expressions, it can be quite
+ * useful to define filters in low-level BPF assembler (that is kept
+ * close to Steven McCanne and Van Jacobson's original BPF paper).
+ * In particular for BPF JIT implementors, JIT security auditors, or
+ * just for defining BPF expressions that contain extensions which are
+ * not supported by compilers.
+ *
+ * How to get into it:
+ *
+ * 1) read Documentation/networking/filter.txt
+ * 2) Run `bpf_asm [-c] <filter-prog file>` to translate into binary
+ * blob that is loadable with xt_bpf, cls_bpf et al. Note: -c will
+ * pretty print a C-like construct.
+ *
+ * Copyright 2013 Daniel Borkmann <borkmann@redhat.com>
+ * Licensed under the GNU General Public License, version 2.0 (GPLv2)
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+extern void bpf_asm_compile(FILE *fp, bool cstyle);
+
+int main(int argc, char **argv)
+{
+ FILE *fp = stdin;
+ bool cstyle = false;
+ int i;
+
+ for (i = 1; i < argc; i++) {
+ if (!strncmp("-c", argv[i], 2)) {
+ cstyle = true;
+ continue;
+ }
+
+ fp = fopen(argv[i], "r");
+ if (!fp) {
+ fp = stdin;
+ continue;
+ }
+
+ break;
+ }
+
+ bpf_asm_compile(fp, cstyle);
+
+ return 0;
+}
diff --git a/tools/net/bpf_dbg.c b/tools/net/bpf_dbg.c
new file mode 100644
index 00000000000..9a287bec695
--- /dev/null
+++ b/tools/net/bpf_dbg.c
@@ -0,0 +1,1395 @@
+/*
+ * Minimal BPF debugger
+ *
+ * Minimal BPF debugger that mimics the kernel's engine (w/o extensions)
+ * and allows for single stepping through selected packets from a pcap
+ * with a provided user filter in order to facilitate verification of a
+ * BPF program. Besides others, this is useful to verify BPF programs
+ * before attaching to a live system, and can be used in socket filters,
+ * cls_bpf, xt_bpf, team driver and e.g. PTP code; in particular when a
+ * single more complex BPF program is being used. Reasons for a more
+ * complex BPF program are likely primarily to optimize execution time
+ * for making a verdict when multiple simple BPF programs are combined
+ * into one in order to prevent parsing same headers multiple times.
+ *
+ * More on how to debug BPF opcodes see Documentation/networking/filter.txt
+ * which is the main document on BPF. Mini howto for getting started:
+ *
+ * 1) `./bpf_dbg` to enter the shell (shell cmds denoted with '>'):
+ * 2) > load bpf 6,40 0 0 12,21 0 3 20... (output from `bpf_asm` or
+ * `tcpdump -iem1 -ddd port 22 | tr '\n' ','` to load as filter)
+ * 3) > load pcap foo.pcap
+ * 4) > run <n>/disassemble/dump/quit (self-explanatory)
+ * 5) > breakpoint 2 (sets bp at loaded BPF insns 2, do `run` then;
+ * multiple bps can be set, of course, a call to `breakpoint`
+ * w/o args shows currently loaded bps, `breakpoint reset` for
+ * resetting all breakpoints)
+ * 6) > select 3 (`run` etc will start from the 3rd packet in the pcap)
+ * 7) > step [-<n>, +<n>] (performs single stepping through the BPF)
+ *
+ * Copyright 2013 Daniel Borkmann <borkmann@redhat.com>
+ * Licensed under the GNU General Public License, version 2.0 (GPLv2)
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdbool.h>
+#include <stdarg.h>
+#include <setjmp.h>
+#include <linux/filter.h>
+#include <linux/if_packet.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <signal.h>
+#include <arpa/inet.h>
+#include <net/ethernet.h>
+
+#define TCPDUMP_MAGIC 0xa1b2c3d4
+
+#define BPF_LDX_B (BPF_LDX | BPF_B)
+#define BPF_LDX_W (BPF_LDX | BPF_W)
+#define BPF_JMP_JA (BPF_JMP | BPF_JA)
+#define BPF_JMP_JEQ (BPF_JMP | BPF_JEQ)
+#define BPF_JMP_JGT (BPF_JMP | BPF_JGT)
+#define BPF_JMP_JGE (BPF_JMP | BPF_JGE)
+#define BPF_JMP_JSET (BPF_JMP | BPF_JSET)
+#define BPF_ALU_ADD (BPF_ALU | BPF_ADD)
+#define BPF_ALU_SUB (BPF_ALU | BPF_SUB)
+#define BPF_ALU_MUL (BPF_ALU | BPF_MUL)
+#define BPF_ALU_DIV (BPF_ALU | BPF_DIV)
+#define BPF_ALU_MOD (BPF_ALU | BPF_MOD)
+#define BPF_ALU_NEG (BPF_ALU | BPF_NEG)
+#define BPF_ALU_AND (BPF_ALU | BPF_AND)
+#define BPF_ALU_OR (BPF_ALU | BPF_OR)
+#define BPF_ALU_XOR (BPF_ALU | BPF_XOR)
+#define BPF_ALU_LSH (BPF_ALU | BPF_LSH)
+#define BPF_ALU_RSH (BPF_ALU | BPF_RSH)
+#define BPF_MISC_TAX (BPF_MISC | BPF_TAX)
+#define BPF_MISC_TXA (BPF_MISC | BPF_TXA)
+#define BPF_LD_B (BPF_LD | BPF_B)
+#define BPF_LD_H (BPF_LD | BPF_H)
+#define BPF_LD_W (BPF_LD | BPF_W)
+
+#ifndef array_size
+# define array_size(x) (sizeof(x) / sizeof((x)[0]))
+#endif
+
+#ifndef __check_format_printf
+# define __check_format_printf(pos_fmtstr, pos_fmtargs) \
+ __attribute__ ((format (printf, (pos_fmtstr), (pos_fmtargs))))
+#endif
+
+enum {
+ CMD_OK,
+ CMD_ERR,
+ CMD_EX,
+};
+
+struct shell_cmd {
+ const char *name;
+ int (*func)(char *args);
+};
+
+struct pcap_filehdr {
+ uint32_t magic;
+ uint16_t version_major;
+ uint16_t version_minor;
+ int32_t thiszone;
+ uint32_t sigfigs;
+ uint32_t snaplen;
+ uint32_t linktype;
+};
+
+struct pcap_timeval {
+ int32_t tv_sec;
+ int32_t tv_usec;
+};
+
+struct pcap_pkthdr {
+ struct pcap_timeval ts;
+ uint32_t caplen;
+ uint32_t len;
+};
+
+struct bpf_regs {
+ uint32_t A;
+ uint32_t X;
+ uint32_t M[BPF_MEMWORDS];
+ uint32_t R;
+ bool Rs;
+ uint16_t Pc;
+};
+
+static struct sock_filter bpf_image[BPF_MAXINSNS + 1];
+static unsigned int bpf_prog_len = 0;
+
+static int bpf_breakpoints[64];
+static struct bpf_regs bpf_regs[BPF_MAXINSNS + 1];
+static struct bpf_regs bpf_curr;
+static unsigned int bpf_regs_len = 0;
+
+static int pcap_fd = -1;
+static unsigned int pcap_packet = 0;
+static size_t pcap_map_size = 0;
+static char *pcap_ptr_va_start, *pcap_ptr_va_curr;
+
+static const char * const op_table[] = {
+ [BPF_ST] = "st",
+ [BPF_STX] = "stx",
+ [BPF_LD_B] = "ldb",
+ [BPF_LD_H] = "ldh",
+ [BPF_LD_W] = "ld",
+ [BPF_LDX] = "ldx",
+ [BPF_LDX_B] = "ldxb",
+ [BPF_JMP_JA] = "ja",
+ [BPF_JMP_JEQ] = "jeq",
+ [BPF_JMP_JGT] = "jgt",
+ [BPF_JMP_JGE] = "jge",
+ [BPF_JMP_JSET] = "jset",
+ [BPF_ALU_ADD] = "add",
+ [BPF_ALU_SUB] = "sub",
+ [BPF_ALU_MUL] = "mul",
+ [BPF_ALU_DIV] = "div",
+ [BPF_ALU_MOD] = "mod",
+ [BPF_ALU_NEG] = "neg",
+ [BPF_ALU_AND] = "and",
+ [BPF_ALU_OR] = "or",
+ [BPF_ALU_XOR] = "xor",
+ [BPF_ALU_LSH] = "lsh",
+ [BPF_ALU_RSH] = "rsh",
+ [BPF_MISC_TAX] = "tax",
+ [BPF_MISC_TXA] = "txa",
+ [BPF_RET] = "ret",
+};
+
+static __check_format_printf(1, 2) int rl_printf(const char *fmt, ...)
+{
+ int ret;
+ va_list vl;
+
+ va_start(vl, fmt);
+ ret = vfprintf(rl_outstream, fmt, vl);
+ va_end(vl);
+
+ return ret;
+}
+
+static int matches(const char *cmd, const char *pattern)
+{
+ int len = strlen(cmd);
+
+ if (len > strlen(pattern))
+ return -1;
+
+ return memcmp(pattern, cmd, len);
+}
+
+static void hex_dump(const uint8_t *buf, size_t len)
+{
+ int i;
+
+ rl_printf("%3u: ", 0);
+ for (i = 0; i < len; i++) {
+ if (i && !(i % 16))
+ rl_printf("\n%3u: ", i);
+ rl_printf("%02x ", buf[i]);
+ }
+ rl_printf("\n");
+}
+
+static bool bpf_prog_loaded(void)
+{
+ if (bpf_prog_len == 0)
+ rl_printf("no bpf program loaded!\n");
+
+ return bpf_prog_len > 0;
+}
+
+static void bpf_disasm(const struct sock_filter f, unsigned int i)
+{
+ const char *op, *fmt;
+ int val = f.k;
+ char buf[256];
+
+ switch (f.code) {
+ case BPF_RET | BPF_K:
+ op = op_table[BPF_RET];
+ fmt = "#%#x";
+ break;
+ case BPF_RET | BPF_A:
+ op = op_table[BPF_RET];
+ fmt = "a";
+ break;
+ case BPF_RET | BPF_X:
+ op = op_table[BPF_RET];
+ fmt = "x";
+ break;
+ case BPF_MISC_TAX:
+ op = op_table[BPF_MISC_TAX];
+ fmt = "";
+ break;
+ case BPF_MISC_TXA:
+ op = op_table[BPF_MISC_TXA];
+ fmt = "";
+ break;
+ case BPF_ST:
+ op = op_table[BPF_ST];
+ fmt = "M[%d]";
+ break;
+ case BPF_STX:
+ op = op_table[BPF_STX];
+ fmt = "M[%d]";
+ break;
+ case BPF_LD_W | BPF_ABS:
+ op = op_table[BPF_LD_W];
+ fmt = "[%d]";
+ break;
+ case BPF_LD_H | BPF_ABS:
+ op = op_table[BPF_LD_H];
+ fmt = "[%d]";
+ break;
+ case BPF_LD_B | BPF_ABS:
+ op = op_table[BPF_LD_B];
+ fmt = "[%d]";
+ break;
+ case BPF_LD_W | BPF_LEN:
+ op = op_table[BPF_LD_W];
+ fmt = "#len";
+ break;
+ case BPF_LD_W | BPF_IND:
+ op = op_table[BPF_LD_W];
+ fmt = "[x+%d]";
+ break;
+ case BPF_LD_H | BPF_IND:
+ op = op_table[BPF_LD_H];
+ fmt = "[x+%d]";
+ break;
+ case BPF_LD_B | BPF_IND:
+ op = op_table[BPF_LD_B];
+ fmt = "[x+%d]";
+ break;
+ case BPF_LD | BPF_IMM:
+ op = op_table[BPF_LD_W];
+ fmt = "#%#x";
+ break;
+ case BPF_LDX | BPF_IMM:
+ op = op_table[BPF_LDX];
+ fmt = "#%#x";
+ break;
+ case BPF_LDX_B | BPF_MSH:
+ op = op_table[BPF_LDX_B];
+ fmt = "4*([%d]&0xf)";
+ break;
+ case BPF_LD | BPF_MEM:
+ op = op_table[BPF_LD_W];
+ fmt = "M[%d]";
+ break;
+ case BPF_LDX | BPF_MEM:
+ op = op_table[BPF_LDX];
+ fmt = "M[%d]";
+ break;
+ case BPF_JMP_JA:
+ op = op_table[BPF_JMP_JA];
+ fmt = "%d";
+ val = i + 1 + f.k;
+ break;
+ case BPF_JMP_JGT | BPF_X:
+ op = op_table[BPF_JMP_JGT];
+ fmt = "x";
+ break;
+ case BPF_JMP_JGT | BPF_K:
+ op = op_table[BPF_JMP_JGT];
+ fmt = "#%#x";
+ break;
+ case BPF_JMP_JGE | BPF_X:
+ op = op_table[BPF_JMP_JGE];
+ fmt = "x";
+ break;
+ case BPF_JMP_JGE | BPF_K:
+ op = op_table[BPF_JMP_JGE];
+ fmt = "#%#x";
+ break;
+ case BPF_JMP_JEQ | BPF_X:
+ op = op_table[BPF_JMP_JEQ];
+ fmt = "x";
+ break;
+ case BPF_JMP_JEQ | BPF_K:
+ op = op_table[BPF_JMP_JEQ];
+ fmt = "#%#x";
+ break;
+ case BPF_JMP_JSET | BPF_X:
+ op = op_table[BPF_JMP_JSET];
+ fmt = "x";
+ break;
+ case BPF_JMP_JSET | BPF_K:
+ op = op_table[BPF_JMP_JSET];
+ fmt = "#%#x";
+ break;
+ case BPF_ALU_NEG:
+ op = op_table[BPF_ALU_NEG];
+ fmt = "";
+ break;
+ case BPF_ALU_LSH | BPF_X:
+ op = op_table[BPF_ALU_LSH];
+ fmt = "x";
+ break;
+ case BPF_ALU_LSH | BPF_K:
+ op = op_table[BPF_ALU_LSH];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_RSH | BPF_X:
+ op = op_table[BPF_ALU_RSH];
+ fmt = "x";
+ break;
+ case BPF_ALU_RSH | BPF_K:
+ op = op_table[BPF_ALU_RSH];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_ADD | BPF_X:
+ op = op_table[BPF_ALU_ADD];
+ fmt = "x";
+ break;
+ case BPF_ALU_ADD | BPF_K:
+ op = op_table[BPF_ALU_ADD];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_SUB | BPF_X:
+ op = op_table[BPF_ALU_SUB];
+ fmt = "x";
+ break;
+ case BPF_ALU_SUB | BPF_K:
+ op = op_table[BPF_ALU_SUB];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_MUL | BPF_X:
+ op = op_table[BPF_ALU_MUL];
+ fmt = "x";
+ break;
+ case BPF_ALU_MUL | BPF_K:
+ op = op_table[BPF_ALU_MUL];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_DIV | BPF_X:
+ op = op_table[BPF_ALU_DIV];
+ fmt = "x";
+ break;
+ case BPF_ALU_DIV | BPF_K:
+ op = op_table[BPF_ALU_DIV];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_MOD | BPF_X:
+ op = op_table[BPF_ALU_MOD];
+ fmt = "x";
+ break;
+ case BPF_ALU_MOD | BPF_K:
+ op = op_table[BPF_ALU_MOD];
+ fmt = "#%d";
+ break;
+ case BPF_ALU_AND | BPF_X:
+ op = op_table[BPF_ALU_AND];
+ fmt = "x";
+ break;
+ case BPF_ALU_AND | BPF_K:
+ op = op_table[BPF_ALU_AND];
+ fmt = "#%#x";
+ break;
+ case BPF_ALU_OR | BPF_X:
+ op = op_table[BPF_ALU_OR];
+ fmt = "x";
+ break;
+ case BPF_ALU_OR | BPF_K:
+ op = op_table[BPF_ALU_OR];
+ fmt = "#%#x";
+ break;
+ case BPF_ALU_XOR | BPF_X:
+ op = op_table[BPF_ALU_XOR];
+ fmt = "x";
+ break;
+ case BPF_ALU_XOR | BPF_K:
+ op = op_table[BPF_ALU_XOR];
+ fmt = "#%#x";
+ break;
+ default:
+ op = "nosup";
+ fmt = "%#x";
+ val = f.code;
+ break;
+ }
+
+ memset(buf, 0, sizeof(buf));
+ snprintf(buf, sizeof(buf), fmt, val);
+ buf[sizeof(buf) - 1] = 0;
+
+ if ((BPF_CLASS(f.code) == BPF_JMP && BPF_OP(f.code) != BPF_JA))
+ rl_printf("l%d:\t%s %s, l%d, l%d\n", i, op, buf,
+ i + 1 + f.jt, i + 1 + f.jf);
+ else
+ rl_printf("l%d:\t%s %s\n", i, op, buf);
+}
+
+static void bpf_dump_curr(struct bpf_regs *r, struct sock_filter *f)
+{
+ int i, m = 0;
+
+ rl_printf("pc: [%u]\n", r->Pc);
+ rl_printf("code: [%u] jt[%u] jf[%u] k[%u]\n",
+ f->code, f->jt, f->jf, f->k);
+ rl_printf("curr: ");
+ bpf_disasm(*f, r->Pc);
+
+ if (f->jt || f->jf) {
+ rl_printf("jt: ");
+ bpf_disasm(*(f + f->jt + 1), r->Pc + f->jt + 1);
+ rl_printf("jf: ");
+ bpf_disasm(*(f + f->jf + 1), r->Pc + f->jf + 1);
+ }
+
+ rl_printf("A: [%#08x][%u]\n", r->A, r->A);
+ rl_printf("X: [%#08x][%u]\n", r->X, r->X);
+ if (r->Rs)
+ rl_printf("ret: [%#08x][%u]!\n", r->R, r->R);
+
+ for (i = 0; i < BPF_MEMWORDS; i++) {
+ if (r->M[i]) {
+ m++;
+ rl_printf("M[%d]: [%#08x][%u]\n", i, r->M[i], r->M[i]);
+ }
+ }
+ if (m == 0)
+ rl_printf("M[0,%d]: [%#08x][%u]\n", BPF_MEMWORDS - 1, 0, 0);
+}
+
+static void bpf_dump_pkt(uint8_t *pkt, uint32_t pkt_caplen, uint32_t pkt_len)
+{
+ if (pkt_caplen != pkt_len)
+ rl_printf("cap: %u, len: %u\n", pkt_caplen, pkt_len);
+ else
+ rl_printf("len: %u\n", pkt_len);
+
+ hex_dump(pkt, pkt_caplen);
+}
+
+static void bpf_disasm_all(const struct sock_filter *f, unsigned int len)
+{
+ unsigned int i;
+
+ for (i = 0; i < len; i++)
+ bpf_disasm(f[i], i);
+}
+
+static void bpf_dump_all(const struct sock_filter *f, unsigned int len)
+{
+ unsigned int i;
+
+ rl_printf("/* { op, jt, jf, k }, */\n");
+ for (i = 0; i < len; i++)
+ rl_printf("{ %#04x, %2u, %2u, %#010x },\n",
+ f[i].code, f[i].jt, f[i].jf, f[i].k);
+}
+
+static bool bpf_runnable(struct sock_filter *f, unsigned int len)
+{
+ int sock, ret, i;
+ struct sock_fprog bpf = {
+ .filter = f,
+ .len = len,
+ };
+
+ sock = socket(AF_INET, SOCK_DGRAM, 0);
+ if (sock < 0) {
+ rl_printf("cannot open socket!\n");
+ return false;
+ }
+ ret = setsockopt(sock, SOL_SOCKET, SO_ATTACH_FILTER, &bpf, sizeof(bpf));
+ close(sock);
+ if (ret < 0) {
+ rl_printf("program not allowed to run by kernel!\n");
+ return false;
+ }
+ for (i = 0; i < len; i++) {
+ if (BPF_CLASS(f[i].code) == BPF_LD &&
+ f[i].k > SKF_AD_OFF) {
+ rl_printf("extensions currently not supported!\n");
+ return false;
+ }
+ }
+
+ return true;
+}
+
+static void bpf_reset_breakpoints(void)
+{
+ int i;
+
+ for (i = 0; i < array_size(bpf_breakpoints); i++)
+ bpf_breakpoints[i] = -1;
+}
+
+static void bpf_set_breakpoints(unsigned int where)
+{
+ int i;
+ bool set = false;
+
+ for (i = 0; i < array_size(bpf_breakpoints); i++) {
+ if (bpf_breakpoints[i] == (int) where) {
+ rl_printf("breakpoint already set!\n");
+ set = true;
+ break;
+ }
+
+ if (bpf_breakpoints[i] == -1 && set == false) {
+ bpf_breakpoints[i] = where;
+ set = true;
+ }
+ }
+
+ if (!set)
+ rl_printf("too many breakpoints set, reset first!\n");
+}
+
+static void bpf_dump_breakpoints(void)
+{
+ int i;
+
+ rl_printf("breakpoints: ");
+
+ for (i = 0; i < array_size(bpf_breakpoints); i++) {
+ if (bpf_breakpoints[i] < 0)
+ continue;
+ rl_printf("%d ", bpf_breakpoints[i]);
+ }
+
+ rl_printf("\n");
+}
+
+static void bpf_reset(void)
+{
+ bpf_regs_len = 0;
+
+ memset(bpf_regs, 0, sizeof(bpf_regs));
+ memset(&bpf_curr, 0, sizeof(bpf_curr));
+}
+
+static void bpf_safe_regs(void)
+{
+ memcpy(&bpf_regs[bpf_regs_len++], &bpf_curr, sizeof(bpf_curr));
+}
+
+static bool bpf_restore_regs(int off)
+{
+ unsigned int index = bpf_regs_len - 1 + off;
+
+ if (index == 0) {
+ bpf_reset();
+ return true;
+ } else if (index < bpf_regs_len) {
+ memcpy(&bpf_curr, &bpf_regs[index], sizeof(bpf_curr));
+ bpf_regs_len = index;
+ return true;
+ } else {
+ rl_printf("reached bottom of register history stack!\n");
+ return false;
+ }
+}
+
+static uint32_t extract_u32(uint8_t *pkt, uint32_t off)
+{
+ uint32_t r;
+
+ memcpy(&r, &pkt[off], sizeof(r));
+
+ return ntohl(r);
+}
+
+static uint16_t extract_u16(uint8_t *pkt, uint32_t off)
+{
+ uint16_t r;
+
+ memcpy(&r, &pkt[off], sizeof(r));
+
+ return ntohs(r);
+}
+
+static uint8_t extract_u8(uint8_t *pkt, uint32_t off)
+{
+ return pkt[off];
+}
+
+static void set_return(struct bpf_regs *r)
+{
+ r->R = 0;
+ r->Rs = true;
+}
+
+static void bpf_single_step(struct bpf_regs *r, struct sock_filter *f,
+ uint8_t *pkt, uint32_t pkt_caplen,
+ uint32_t pkt_len)
+{
+ uint32_t K = f->k;
+ int d;
+
+ switch (f->code) {
+ case BPF_RET | BPF_K:
+ r->R = K;
+ r->Rs = true;
+ break;
+ case BPF_RET | BPF_A:
+ r->R = r->A;
+ r->Rs = true;
+ break;
+ case BPF_RET | BPF_X:
+ r->R = r->X;
+ r->Rs = true;
+ break;
+ case BPF_MISC_TAX:
+ r->X = r->A;
+ break;
+ case BPF_MISC_TXA:
+ r->A = r->X;
+ break;
+ case BPF_ST:
+ r->M[K] = r->A;
+ break;
+ case BPF_STX:
+ r->M[K] = r->X;
+ break;
+ case BPF_LD_W | BPF_ABS:
+ d = pkt_caplen - K;
+ if (d >= sizeof(uint32_t))
+ r->A = extract_u32(pkt, K);
+ else
+ set_return(r);
+ break;
+ case BPF_LD_H | BPF_ABS:
+ d = pkt_caplen - K;
+ if (d >= sizeof(uint16_t))
+ r->A = extract_u16(pkt, K);
+ else
+ set_return(r);
+ break;
+ case BPF_LD_B | BPF_ABS:
+ d = pkt_caplen - K;
+ if (d >= sizeof(uint8_t))
+ r->A = extract_u8(pkt, K);
+ else
+ set_return(r);
+ break;
+ case BPF_LD_W | BPF_IND:
+ d = pkt_caplen - (r->X + K);
+ if (d >= sizeof(uint32_t))
+ r->A = extract_u32(pkt, r->X + K);
+ break;
+ case BPF_LD_H | BPF_IND:
+ d = pkt_caplen - (r->X + K);
+ if (d >= sizeof(uint16_t))
+ r->A = extract_u16(pkt, r->X + K);
+ else
+ set_return(r);
+ break;
+ case BPF_LD_B | BPF_IND:
+ d = pkt_caplen - (r->X + K);
+ if (d >= sizeof(uint8_t))
+ r->A = extract_u8(pkt, r->X + K);
+ else
+ set_return(r);
+ break;
+ case BPF_LDX_B | BPF_MSH:
+ d = pkt_caplen - K;
+ if (d >= sizeof(uint8_t)) {
+ r->X = extract_u8(pkt, K);
+ r->X = (r->X & 0xf) << 2;
+ } else
+ set_return(r);
+ break;
+ case BPF_LD_W | BPF_LEN:
+ r->A = pkt_len;
+ break;
+ case BPF_LDX_W | BPF_LEN:
+ r->A = pkt_len;
+ break;
+ case BPF_LD | BPF_IMM:
+ r->A = K;
+ break;
+ case BPF_LDX | BPF_IMM:
+ r->X = K;
+ break;
+ case BPF_LD | BPF_MEM:
+ r->A = r->M[K];
+ break;
+ case BPF_LDX | BPF_MEM:
+ r->X = r->M[K];
+ break;
+ case BPF_JMP_JA:
+ r->Pc += K;
+ break;
+ case BPF_JMP_JGT | BPF_X:
+ r->Pc += r->A > r->X ? f->jt : f->jf;
+ break;
+ case BPF_JMP_JGT | BPF_K:
+ r->Pc += r->A > K ? f->jt : f->jf;
+ break;
+ case BPF_JMP_JGE | BPF_X:
+ r->Pc += r->A >= r->X ? f->jt : f->jf;
+ break;
+ case BPF_JMP_JGE | BPF_K:
+ r->Pc += r->A >= K ? f->jt : f->jf;
+ break;
+ case BPF_JMP_JEQ | BPF_X:
+ r->Pc += r->A == r->X ? f->jt : f->jf;
+ break;
+ case BPF_JMP_JEQ | BPF_K:
+ r->Pc += r->A == K ? f->jt : f->jf;
+ break;
+ case BPF_JMP_JSET | BPF_X:
+ r->Pc += r->A & r->X ? f->jt : f->jf;
+ break;
+ case BPF_JMP_JSET | BPF_K:
+ r->Pc += r->A & K ? f->jt : f->jf;
+ break;
+ case BPF_ALU_NEG:
+ r->A = -r->A;
+ break;
+ case BPF_ALU_LSH | BPF_X:
+ r->A <<= r->X;
+ break;
+ case BPF_ALU_LSH | BPF_K:
+ r->A <<= K;
+ break;
+ case BPF_ALU_RSH | BPF_X:
+ r->A >>= r->X;
+ break;
+ case BPF_ALU_RSH | BPF_K:
+ r->A >>= K;
+ break;
+ case BPF_ALU_ADD | BPF_X:
+ r->A += r->X;
+ break;
+ case BPF_ALU_ADD | BPF_K:
+ r->A += K;
+ break;
+ case BPF_ALU_SUB | BPF_X:
+ r->A -= r->X;
+ break;
+ case BPF_ALU_SUB | BPF_K:
+ r->A -= K;
+ break;
+ case BPF_ALU_MUL | BPF_X:
+ r->A *= r->X;
+ break;
+ case BPF_ALU_MUL | BPF_K:
+ r->A *= K;
+ break;
+ case BPF_ALU_DIV | BPF_X:
+ case BPF_ALU_MOD | BPF_X:
+ if (r->X == 0) {
+ set_return(r);
+ break;
+ }
+ goto do_div;
+ case BPF_ALU_DIV | BPF_K:
+ case BPF_ALU_MOD | BPF_K:
+ if (K == 0) {
+ set_return(r);
+ break;
+ }
+do_div:
+ switch (f->code) {
+ case BPF_ALU_DIV | BPF_X:
+ r->A /= r->X;
+ break;
+ case BPF_ALU_DIV | BPF_K:
+ r->A /= K;
+ break;
+ case BPF_ALU_MOD | BPF_X:
+ r->A %= r->X;
+ break;
+ case BPF_ALU_MOD | BPF_K:
+ r->A %= K;
+ break;
+ }
+ break;
+ case BPF_ALU_AND | BPF_X:
+ r->A &= r->X;
+ break;
+ case BPF_ALU_AND | BPF_K:
+ r->A &= K;
+ break;
+ case BPF_ALU_OR | BPF_X:
+ r->A |= r->X;
+ break;
+ case BPF_ALU_OR | BPF_K:
+ r->A |= K;
+ break;
+ case BPF_ALU_XOR | BPF_X:
+ r->A ^= r->X;
+ break;
+ case BPF_ALU_XOR | BPF_K:
+ r->A ^= K;
+ break;
+ }
+}
+
+static bool bpf_pc_has_breakpoint(uint16_t pc)
+{
+ int i;
+
+ for (i = 0; i < array_size(bpf_breakpoints); i++) {
+ if (bpf_breakpoints[i] < 0)
+ continue;
+ if (bpf_breakpoints[i] == pc)
+ return true;
+ }
+
+ return false;
+}
+
+static bool bpf_handle_breakpoint(struct bpf_regs *r, struct sock_filter *f,
+ uint8_t *pkt, uint32_t pkt_caplen,
+ uint32_t pkt_len)
+{
+ rl_printf("-- register dump --\n");
+ bpf_dump_curr(r, &f[r->Pc]);
+ rl_printf("-- packet dump --\n");
+ bpf_dump_pkt(pkt, pkt_caplen, pkt_len);
+ rl_printf("(breakpoint)\n");
+ return true;
+}
+
+static int bpf_run_all(struct sock_filter *f, uint16_t bpf_len, uint8_t *pkt,
+ uint32_t pkt_caplen, uint32_t pkt_len)
+{
+ bool stop = false;
+
+ while (bpf_curr.Rs == false && stop == false) {
+ bpf_safe_regs();
+
+ if (bpf_pc_has_breakpoint(bpf_curr.Pc))
+ stop = bpf_handle_breakpoint(&bpf_curr, f, pkt,
+ pkt_caplen, pkt_len);
+
+ bpf_single_step(&bpf_curr, &f[bpf_curr.Pc], pkt, pkt_caplen,
+ pkt_len);
+ bpf_curr.Pc++;
+ }
+
+ return stop ? -1 : bpf_curr.R;
+}
+
+static int bpf_run_stepping(struct sock_filter *f, uint16_t bpf_len,
+ uint8_t *pkt, uint32_t pkt_caplen,
+ uint32_t pkt_len, int next)
+{
+ bool stop = false;
+ int i = 1;
+
+ while (bpf_curr.Rs == false && stop == false) {
+ bpf_safe_regs();
+
+ if (i++ == next)
+ stop = bpf_handle_breakpoint(&bpf_curr, f, pkt,
+ pkt_caplen, pkt_len);
+
+ bpf_single_step(&bpf_curr, &f[bpf_curr.Pc], pkt, pkt_caplen,
+ pkt_len);
+ bpf_curr.Pc++;
+ }
+
+ return stop ? -1 : bpf_curr.R;
+}
+
+static bool pcap_loaded(void)
+{
+ if (pcap_fd < 0)
+ rl_printf("no pcap file loaded!\n");
+
+ return pcap_fd >= 0;
+}
+
+static struct pcap_pkthdr *pcap_curr_pkt(void)
+{
+ return (void *) pcap_ptr_va_curr;
+}
+
+static bool pcap_next_pkt(void)
+{
+ struct pcap_pkthdr *hdr = pcap_curr_pkt();
+
+ if (pcap_ptr_va_curr + sizeof(*hdr) -
+ pcap_ptr_va_start >= pcap_map_size)
+ return false;
+ if (hdr->caplen == 0 || hdr->len == 0 || hdr->caplen > hdr->len)
+ return false;
+ if (pcap_ptr_va_curr + sizeof(*hdr) + hdr->caplen -
+ pcap_ptr_va_start >= pcap_map_size)
+ return false;
+
+ pcap_ptr_va_curr += (sizeof(*hdr) + hdr->caplen);
+ return true;
+}
+
+static void pcap_reset_pkt(void)
+{
+ pcap_ptr_va_curr = pcap_ptr_va_start + sizeof(struct pcap_filehdr);
+}
+
+static int try_load_pcap(const char *file)
+{
+ struct pcap_filehdr *hdr;
+ struct stat sb;
+ int ret;
+
+ pcap_fd = open(file, O_RDONLY);
+ if (pcap_fd < 0) {
+ rl_printf("cannot open pcap [%s]!\n", strerror(errno));
+ return CMD_ERR;
+ }
+
+ ret = fstat(pcap_fd, &sb);
+ if (ret < 0) {
+ rl_printf("cannot fstat pcap file!\n");
+ return CMD_ERR;
+ }
+
+ if (!S_ISREG(sb.st_mode)) {
+ rl_printf("not a regular pcap file, duh!\n");
+ return CMD_ERR;
+ }
+
+ pcap_map_size = sb.st_size;
+ if (pcap_map_size <= sizeof(struct pcap_filehdr)) {
+ rl_printf("pcap file too small!\n");
+ return CMD_ERR;
+ }
+
+ pcap_ptr_va_start = mmap(NULL, pcap_map_size, PROT_READ,
+ MAP_SHARED | MAP_LOCKED, pcap_fd, 0);
+ if (pcap_ptr_va_start == MAP_FAILED) {
+ rl_printf("mmap of file failed!");
+ return CMD_ERR;
+ }
+
+ hdr = (void *) pcap_ptr_va_start;
+ if (hdr->magic != TCPDUMP_MAGIC) {
+ rl_printf("wrong pcap magic!\n");
+ return CMD_ERR;
+ }
+
+ pcap_reset_pkt();
+
+ return CMD_OK;
+
+}
+
+static void try_close_pcap(void)
+{
+ if (pcap_fd >= 0) {
+ munmap(pcap_ptr_va_start, pcap_map_size);
+ close(pcap_fd);
+
+ pcap_ptr_va_start = pcap_ptr_va_curr = NULL;
+ pcap_map_size = 0;
+ pcap_packet = 0;
+ pcap_fd = -1;
+ }
+}
+
+static int cmd_load_bpf(char *bpf_string)
+{
+ char sp, *token, separator = ',';
+ unsigned short bpf_len, i = 0;
+ struct sock_filter tmp;
+
+ bpf_prog_len = 0;
+ memset(bpf_image, 0, sizeof(bpf_image));
+
+ if (sscanf(bpf_string, "%hu%c", &bpf_len, &sp) != 2 ||
+ sp != separator || bpf_len > BPF_MAXINSNS || bpf_len == 0) {
+ rl_printf("syntax error in head length encoding!\n");
+ return CMD_ERR;
+ }
+
+ token = bpf_string;
+ while ((token = strchr(token, separator)) && (++token)[0]) {
+ if (i >= bpf_len) {
+ rl_printf("program exceeds encoded length!\n");
+ return CMD_ERR;
+ }
+
+ if (sscanf(token, "%hu %hhu %hhu %u,",
+ &tmp.code, &tmp.jt, &tmp.jf, &tmp.k) != 4) {
+ rl_printf("syntax error at instruction %d!\n", i);
+ return CMD_ERR;
+ }
+
+ bpf_image[i].code = tmp.code;
+ bpf_image[i].jt = tmp.jt;
+ bpf_image[i].jf = tmp.jf;
+ bpf_image[i].k = tmp.k;
+
+ i++;
+ }
+
+ if (i != bpf_len) {
+ rl_printf("syntax error exceeding encoded length!\n");
+ return CMD_ERR;
+ } else
+ bpf_prog_len = bpf_len;
+ if (!bpf_runnable(bpf_image, bpf_prog_len))
+ bpf_prog_len = 0;
+
+ return CMD_OK;
+}
+
+static int cmd_load_pcap(char *file)
+{
+ char *file_trim, *tmp;
+
+ file_trim = strtok_r(file, " ", &tmp);
+ if (file_trim == NULL)
+ return CMD_ERR;
+
+ try_close_pcap();
+
+ return try_load_pcap(file_trim);
+}
+
+static int cmd_load(char *arg)
+{
+ char *subcmd, *cont, *tmp = strdup(arg);
+ int ret = CMD_OK;
+
+ subcmd = strtok_r(tmp, " ", &cont);
+ if (subcmd == NULL)
+ goto out;
+ if (matches(subcmd, "bpf") == 0) {
+ bpf_reset();
+ bpf_reset_breakpoints();
+
+ ret = cmd_load_bpf(cont);
+ } else if (matches(subcmd, "pcap") == 0) {
+ ret = cmd_load_pcap(cont);
+ } else {
+out:
+ rl_printf("bpf <code>: load bpf code\n");
+ rl_printf("pcap <file>: load pcap file\n");
+ ret = CMD_ERR;
+ }
+
+ free(tmp);
+ return ret;
+}
+
+static int cmd_step(char *num)
+{
+ struct pcap_pkthdr *hdr;
+ int steps, ret;
+
+ if (!bpf_prog_loaded() || !pcap_loaded())
+ return CMD_ERR;
+
+ steps = strtol(num, NULL, 10);
+ if (steps == 0 || strlen(num) == 0)
+ steps = 1;
+ if (steps < 0) {
+ if (!bpf_restore_regs(steps))
+ return CMD_ERR;
+ steps = 1;
+ }
+
+ hdr = pcap_curr_pkt();
+ ret = bpf_run_stepping(bpf_image, bpf_prog_len,
+ (uint8_t *) hdr + sizeof(*hdr),
+ hdr->caplen, hdr->len, steps);
+ if (ret >= 0 || bpf_curr.Rs) {
+ bpf_reset();
+ if (!pcap_next_pkt()) {
+ rl_printf("(going back to first packet)\n");
+ pcap_reset_pkt();
+ } else {
+ rl_printf("(next packet)\n");
+ }
+ }
+
+ return CMD_OK;
+}
+
+static int cmd_select(char *num)
+{
+ unsigned int which, i;
+ bool have_next = true;
+
+ if (!pcap_loaded() || strlen(num) == 0)
+ return CMD_ERR;
+
+ which = strtoul(num, NULL, 10);
+ if (which == 0) {
+ rl_printf("packet count starts with 1, clamping!\n");
+ which = 1;
+ }
+
+ pcap_reset_pkt();
+ bpf_reset();
+
+ for (i = 0; i < which && (have_next = pcap_next_pkt()); i++)
+ /* noop */;
+ if (!have_next || pcap_curr_pkt() == NULL) {
+ rl_printf("no packet #%u available!\n", which);
+ pcap_reset_pkt();
+ return CMD_ERR;
+ }
+
+ return CMD_OK;
+}
+
+static int cmd_breakpoint(char *subcmd)
+{
+ if (!bpf_prog_loaded())
+ return CMD_ERR;
+ if (strlen(subcmd) == 0)
+ bpf_dump_breakpoints();
+ else if (matches(subcmd, "reset") == 0)
+ bpf_reset_breakpoints();
+ else {
+ unsigned int where = strtoul(subcmd, NULL, 10);
+
+ if (where < bpf_prog_len) {
+ bpf_set_breakpoints(where);
+ rl_printf("breakpoint at: ");
+ bpf_disasm(bpf_image[where], where);
+ }
+ }
+
+ return CMD_OK;
+}
+
+static int cmd_run(char *num)
+{
+ static uint32_t pass = 0, fail = 0;
+ bool has_limit = true;
+ int pkts = 0, i = 0;
+
+ if (!bpf_prog_loaded() || !pcap_loaded())
+ return CMD_ERR;
+
+ pkts = strtol(num, NULL, 10);
+ if (pkts == 0 || strlen(num) == 0)
+ has_limit = false;
+
+ do {
+ struct pcap_pkthdr *hdr = pcap_curr_pkt();
+ int ret = bpf_run_all(bpf_image, bpf_prog_len,
+ (uint8_t *) hdr + sizeof(*hdr),
+ hdr->caplen, hdr->len);
+ if (ret > 0)
+ pass++;
+ else if (ret == 0)
+ fail++;
+ else
+ return CMD_OK;
+ bpf_reset();
+ } while (pcap_next_pkt() && (!has_limit || (has_limit && ++i < pkts)));
+
+ rl_printf("bpf passes:%u fails:%u\n", pass, fail);
+
+ pcap_reset_pkt();
+ bpf_reset();
+
+ pass = fail = 0;
+ return CMD_OK;
+}
+
+static int cmd_disassemble(char *line_string)
+{
+ bool single_line = false;
+ unsigned long line;
+
+ if (!bpf_prog_loaded())
+ return CMD_ERR;
+ if (strlen(line_string) > 0 &&
+ (line = strtoul(line_string, NULL, 10)) < bpf_prog_len)
+ single_line = true;
+ if (single_line)
+ bpf_disasm(bpf_image[line], line);
+ else
+ bpf_disasm_all(bpf_image, bpf_prog_len);
+
+ return CMD_OK;
+}
+
+static int cmd_dump(char *dontcare)
+{
+ if (!bpf_prog_loaded())
+ return CMD_ERR;
+
+ bpf_dump_all(bpf_image, bpf_prog_len);
+
+ return CMD_OK;
+}
+
+static int cmd_quit(char *dontcare)
+{
+ return CMD_EX;
+}
+
+static const struct shell_cmd cmds[] = {
+ { .name = "load", .func = cmd_load },
+ { .name = "select", .func = cmd_select },
+ { .name = "step", .func = cmd_step },
+ { .name = "run", .func = cmd_run },
+ { .name = "breakpoint", .func = cmd_breakpoint },
+ { .name = "disassemble", .func = cmd_disassemble },
+ { .name = "dump", .func = cmd_dump },
+ { .name = "quit", .func = cmd_quit },
+};
+
+static int execf(char *arg)
+{
+ char *cmd, *cont, *tmp = strdup(arg);
+ int i, ret = 0, len;
+
+ cmd = strtok_r(tmp, " ", &cont);
+ if (cmd == NULL)
+ goto out;
+ len = strlen(cmd);
+ for (i = 0; i < array_size(cmds); i++) {
+ if (len != strlen(cmds[i].name))
+ continue;
+ if (strncmp(cmds[i].name, cmd, len) == 0) {
+ ret = cmds[i].func(cont);
+ break;
+ }
+ }
+out:
+ free(tmp);
+ return ret;
+}
+
+static char *shell_comp_gen(const char *buf, int state)
+{
+ static int list_index, len;
+
+ if (!state) {
+ list_index = 0;
+ len = strlen(buf);
+ }
+
+ for (; list_index < array_size(cmds); ) {
+ const char *name = cmds[list_index].name;
+
+ list_index++;
+ if (strncmp(name, buf, len) == 0)
+ return strdup(name);
+ }
+
+ return NULL;
+}
+
+static char **shell_completion(const char *buf, int start, int end)
+{
+ char **matches = NULL;
+
+ if (start == 0)
+ matches = rl_completion_matches(buf, shell_comp_gen);
+
+ return matches;
+}
+
+static void intr_shell(int sig)
+{
+ if (rl_end)
+ rl_kill_line(-1, 0);
+
+ rl_crlf();
+ rl_refresh_line(0, 0);
+ rl_free_line_state();
+}
+
+static void init_shell(FILE *fin, FILE *fout)
+{
+ char file[128];
+
+ snprintf(file, sizeof(file), "%s/.bpf_dbg_history", getenv("HOME"));
+ read_history(file);
+
+ rl_instream = fin;
+ rl_outstream = fout;
+
+ rl_readline_name = "bpf_dbg";
+ rl_terminal_name = getenv("TERM");
+
+ rl_catch_signals = 0;
+ rl_catch_sigwinch = 1;
+
+ rl_attempted_completion_function = shell_completion;
+
+ rl_bind_key('\t', rl_complete);
+
+ rl_bind_key_in_map('\t', rl_complete, emacs_meta_keymap);
+ rl_bind_key_in_map('\033', rl_complete, emacs_meta_keymap);
+
+ snprintf(file, sizeof(file), "%s/.bpf_dbg_init", getenv("HOME"));
+ rl_read_init_file(file);
+
+ rl_prep_terminal(0);
+ rl_set_signals();
+
+ signal(SIGINT, intr_shell);
+}
+
+static void exit_shell(FILE *fin, FILE *fout)
+{
+ char file[128];
+
+ snprintf(file, sizeof(file), "%s/.bpf_dbg_history", getenv("HOME"));
+ write_history(file);
+
+ clear_history();
+ rl_deprep_terminal();
+
+ try_close_pcap();
+
+ if (fin != stdin)
+ fclose(fin);
+ if (fout != stdout)
+ fclose(fout);
+}
+
+static int run_shell_loop(FILE *fin, FILE *fout)
+{
+ char *buf;
+
+ init_shell(fin, fout);
+
+ while ((buf = readline("> ")) != NULL) {
+ int ret = execf(buf);
+ if (ret == CMD_EX)
+ break;
+ if (ret == CMD_OK && strlen(buf) > 0)
+ add_history(buf);
+
+ free(buf);
+ }
+
+ exit_shell(fin, fout);
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ FILE *fin = NULL, *fout = NULL;
+
+ if (argc >= 2)
+ fin = fopen(argv[1], "r");
+ if (argc >= 3)
+ fout = fopen(argv[2], "w");
+
+ return run_shell_loop(fin ? : stdin, fout ? : stdout);
+}
diff --git a/tools/net/bpf_exp.l b/tools/net/bpf_exp.l
new file mode 100644
index 00000000000..833a96611da
--- /dev/null
+++ b/tools/net/bpf_exp.l
@@ -0,0 +1,144 @@
+/*
+ * BPF asm code lexer
+ *
+ * This program is free software; you can distribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License,
+ * or (at your option) any later version.
+ *
+ * Syntax kept close to:
+ *
+ * Steven McCanne and Van Jacobson. 1993. The BSD packet filter: a new
+ * architecture for user-level packet capture. In Proceedings of the
+ * USENIX Winter 1993 Conference Proceedings on USENIX Winter 1993
+ * Conference Proceedings (USENIX'93). USENIX Association, Berkeley,
+ * CA, USA, 2-2.
+ *
+ * Copyright 2013 Daniel Borkmann <borkmann@redhat.com>
+ * Licensed under the GNU General Public License, version 2.0 (GPLv2)
+ */
+
+%{
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "bpf_exp.yacc.h"
+
+extern void yyerror(const char *str);
+
+%}
+
+%option align
+%option ecs
+
+%option nounput
+%option noreject
+%option noinput
+%option noyywrap
+
+%option 8bit
+%option caseless
+%option yylineno
+
+%%
+
+"ldb" { return OP_LDB; }
+"ldh" { return OP_LDH; }
+"ld" { return OP_LD; }
+"ldi" { return OP_LDI; }
+"ldx" { return OP_LDX; }
+"ldxi" { return OP_LDXI; }
+"ldxb" { return OP_LDXB; }
+"st" { return OP_ST; }
+"stx" { return OP_STX; }
+"jmp" { return OP_JMP; }
+"ja" { return OP_JMP; }
+"jeq" { return OP_JEQ; }
+"jneq" { return OP_JNEQ; }
+"jne" { return OP_JNEQ; }
+"jlt" { return OP_JLT; }
+"jle" { return OP_JLE; }
+"jgt" { return OP_JGT; }
+"jge" { return OP_JGE; }
+"jset" { return OP_JSET; }
+"add" { return OP_ADD; }
+"sub" { return OP_SUB; }
+"mul" { return OP_MUL; }
+"div" { return OP_DIV; }
+"mod" { return OP_MOD; }
+"neg" { return OP_NEG; }
+"and" { return OP_AND; }
+"xor" { return OP_XOR; }
+"or" { return OP_OR; }
+"lsh" { return OP_LSH; }
+"rsh" { return OP_RSH; }
+"ret" { return OP_RET; }
+"tax" { return OP_TAX; }
+"txa" { return OP_TXA; }
+
+"#"?("len") { return K_PKT_LEN; }
+"#"?("proto") { return K_PROTO; }
+"#"?("type") { return K_TYPE; }
+"#"?("poff") { return K_POFF; }
+"#"?("ifidx") { return K_IFIDX; }
+"#"?("nla") { return K_NLATTR; }
+"#"?("nlan") { return K_NLATTR_NEST; }
+"#"?("mark") { return K_MARK; }
+"#"?("queue") { return K_QUEUE; }
+"#"?("hatype") { return K_HATYPE; }
+"#"?("rxhash") { return K_RXHASH; }
+"#"?("cpu") { return K_CPU; }
+"#"?("vlan_tci") { return K_VLANT; }
+"#"?("vlan_pr") { return K_VLANP; }
+"#"?("rand") { return K_RAND; }
+
+":" { return ':'; }
+"," { return ','; }
+"#" { return '#'; }
+"%" { return '%'; }
+"[" { return '['; }
+"]" { return ']'; }
+"(" { return '('; }
+")" { return ')'; }
+"x" { return 'x'; }
+"a" { return 'a'; }
+"+" { return '+'; }
+"M" { return 'M'; }
+"*" { return '*'; }
+"&" { return '&'; }
+
+([0][x][a-fA-F0-9]+) {
+ yylval.number = strtoul(yytext, NULL, 16);
+ return number;
+ }
+([0][b][0-1]+) {
+ yylval.number = strtol(yytext + 2, NULL, 2);
+ return number;
+ }
+(([0])|([-+]?[1-9][0-9]*)) {
+ yylval.number = strtol(yytext, NULL, 10);
+ return number;
+ }
+([0][0-9]+) {
+ yylval.number = strtol(yytext + 1, NULL, 8);
+ return number;
+ }
+[a-zA-Z_][a-zA-Z0-9_]+ {
+ yylval.label = strdup(yytext);
+ return label;
+ }
+
+"/*"([^\*]|\*[^/])*"*/" { /* NOP */ }
+";"[^\n]* { /* NOP */ }
+^#.* { /* NOP */ }
+[ \t]+ { /* NOP */ }
+[ \n]+ { /* NOP */ }
+
+. {
+ printf("unknown character \'%s\'", yytext);
+ yyerror("lex unknown character");
+ }
+
+%%
diff --git a/tools/net/bpf_exp.y b/tools/net/bpf_exp.y
new file mode 100644
index 00000000000..e6306c51c26
--- /dev/null
+++ b/tools/net/bpf_exp.y
@@ -0,0 +1,771 @@
+/*
+ * BPF asm code parser
+ *
+ * This program is free software; you can distribute it and/or modify
+ * it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation; either version 2 of the License,
+ * or (at your option) any later version.
+ *
+ * Syntax kept close to:
+ *
+ * Steven McCanne and Van Jacobson. 1993. The BSD packet filter: a new
+ * architecture for user-level packet capture. In Proceedings of the
+ * USENIX Winter 1993 Conference Proceedings on USENIX Winter 1993
+ * Conference Proceedings (USENIX'93). USENIX Association, Berkeley,
+ * CA, USA, 2-2.
+ *
+ * Copyright 2013 Daniel Borkmann <borkmann@redhat.com>
+ * Licensed under the GNU General Public License, version 2.0 (GPLv2)
+ */
+
+%{
+
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <unistd.h>
+#include <errno.h>
+#include <assert.h>
+#include <linux/filter.h>
+
+#include "bpf_exp.yacc.h"
+
+enum jmp_type { JTL, JFL, JKL };
+
+extern FILE *yyin;
+extern int yylex(void);
+extern void yyerror(const char *str);
+
+extern void bpf_asm_compile(FILE *fp, bool cstyle);
+static void bpf_set_curr_instr(uint16_t op, uint8_t jt, uint8_t jf, uint32_t k);
+static void bpf_set_curr_label(char *label);
+static void bpf_set_jmp_label(char *label, enum jmp_type type);
+
+%}
+
+%union {
+ char *label;
+ uint32_t number;
+}
+
+%token OP_LDB OP_LDH OP_LD OP_LDX OP_ST OP_STX OP_JMP OP_JEQ OP_JGT OP_JGE
+%token OP_JSET OP_ADD OP_SUB OP_MUL OP_DIV OP_AND OP_OR OP_XOR OP_LSH OP_RSH
+%token OP_RET OP_TAX OP_TXA OP_LDXB OP_MOD OP_NEG OP_JNEQ OP_JLT OP_JLE OP_LDI
+%token OP_LDXI
+
+%token K_PKT_LEN K_PROTO K_TYPE K_NLATTR K_NLATTR_NEST K_MARK K_QUEUE K_HATYPE
+%token K_RXHASH K_CPU K_IFIDX K_VLANT K_VLANP K_POFF K_RAND
+
+%token ':' ',' '[' ']' '(' ')' 'x' 'a' '+' 'M' '*' '&' '#' '%'
+
+%token number label
+
+%type <label> label
+%type <number> number
+
+%%
+
+prog
+ : line
+ | prog line
+ ;
+
+line
+ : instr
+ | labelled_instr
+ ;
+
+labelled_instr
+ : labelled instr
+ ;
+
+instr
+ : ldb
+ | ldh
+ | ld
+ | ldi
+ | ldx
+ | ldxi
+ | st
+ | stx
+ | jmp
+ | jeq
+ | jneq
+ | jlt
+ | jle
+ | jgt
+ | jge
+ | jset
+ | add
+ | sub
+ | mul
+ | div
+ | mod
+ | neg
+ | and
+ | or
+ | xor
+ | lsh
+ | rsh
+ | ret
+ | tax
+ | txa
+ ;
+
+labelled
+ : label ':' { bpf_set_curr_label($1); }
+ ;
+
+ldb
+ : OP_LDB '[' 'x' '+' number ']' {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_IND, 0, 0, $5); }
+ | OP_LDB '[' '%' 'x' '+' number ']' {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_IND, 0, 0, $6); }
+ | OP_LDB '[' number ']' {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0, $3); }
+ | OP_LDB K_PROTO {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_PROTOCOL); }
+ | OP_LDB K_TYPE {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_PKTTYPE); }
+ | OP_LDB K_IFIDX {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_IFINDEX); }
+ | OP_LDB K_NLATTR {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_NLATTR); }
+ | OP_LDB K_NLATTR_NEST {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_NLATTR_NEST); }
+ | OP_LDB K_MARK {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_MARK); }
+ | OP_LDB K_QUEUE {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_QUEUE); }
+ | OP_LDB K_HATYPE {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_HATYPE); }
+ | OP_LDB K_RXHASH {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_RXHASH); }
+ | OP_LDB K_CPU {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_CPU); }
+ | OP_LDB K_VLANT {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_VLAN_TAG); }
+ | OP_LDB K_VLANP {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_VLAN_TAG_PRESENT); }
+ | OP_LDB K_POFF {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_PAY_OFFSET); }
+ | OP_LDB K_RAND {
+ bpf_set_curr_instr(BPF_LD | BPF_B | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_RANDOM); }
+ ;
+
+ldh
+ : OP_LDH '[' 'x' '+' number ']' {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_IND, 0, 0, $5); }
+ | OP_LDH '[' '%' 'x' '+' number ']' {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_IND, 0, 0, $6); }
+ | OP_LDH '[' number ']' {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0, $3); }
+ | OP_LDH K_PROTO {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_PROTOCOL); }
+ | OP_LDH K_TYPE {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_PKTTYPE); }
+ | OP_LDH K_IFIDX {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_IFINDEX); }
+ | OP_LDH K_NLATTR {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_NLATTR); }
+ | OP_LDH K_NLATTR_NEST {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_NLATTR_NEST); }
+ | OP_LDH K_MARK {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_MARK); }
+ | OP_LDH K_QUEUE {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_QUEUE); }
+ | OP_LDH K_HATYPE {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_HATYPE); }
+ | OP_LDH K_RXHASH {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_RXHASH); }
+ | OP_LDH K_CPU {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_CPU); }
+ | OP_LDH K_VLANT {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_VLAN_TAG); }
+ | OP_LDH K_VLANP {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_VLAN_TAG_PRESENT); }
+ | OP_LDH K_POFF {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_PAY_OFFSET); }
+ | OP_LDH K_RAND {
+ bpf_set_curr_instr(BPF_LD | BPF_H | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_RANDOM); }
+ ;
+
+ldi
+ : OP_LDI '#' number {
+ bpf_set_curr_instr(BPF_LD | BPF_IMM, 0, 0, $3); }
+ | OP_LDI number {
+ bpf_set_curr_instr(BPF_LD | BPF_IMM, 0, 0, $2); }
+ ;
+
+ld
+ : OP_LD '#' number {
+ bpf_set_curr_instr(BPF_LD | BPF_IMM, 0, 0, $3); }
+ | OP_LD K_PKT_LEN {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_LEN, 0, 0, 0); }
+ | OP_LD K_PROTO {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_PROTOCOL); }
+ | OP_LD K_TYPE {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_PKTTYPE); }
+ | OP_LD K_IFIDX {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_IFINDEX); }
+ | OP_LD K_NLATTR {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_NLATTR); }
+ | OP_LD K_NLATTR_NEST {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_NLATTR_NEST); }
+ | OP_LD K_MARK {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_MARK); }
+ | OP_LD K_QUEUE {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_QUEUE); }
+ | OP_LD K_HATYPE {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_HATYPE); }
+ | OP_LD K_RXHASH {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_RXHASH); }
+ | OP_LD K_CPU {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_CPU); }
+ | OP_LD K_VLANT {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_VLAN_TAG); }
+ | OP_LD K_VLANP {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_VLAN_TAG_PRESENT); }
+ | OP_LD K_POFF {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_PAY_OFFSET); }
+ | OP_LD K_RAND {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0,
+ SKF_AD_OFF + SKF_AD_RANDOM); }
+ | OP_LD 'M' '[' number ']' {
+ bpf_set_curr_instr(BPF_LD | BPF_MEM, 0, 0, $4); }
+ | OP_LD '[' 'x' '+' number ']' {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_IND, 0, 0, $5); }
+ | OP_LD '[' '%' 'x' '+' number ']' {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_IND, 0, 0, $6); }
+ | OP_LD '[' number ']' {
+ bpf_set_curr_instr(BPF_LD | BPF_W | BPF_ABS, 0, 0, $3); }
+ ;
+
+ldxi
+ : OP_LDXI '#' number {
+ bpf_set_curr_instr(BPF_LDX | BPF_IMM, 0, 0, $3); }
+ | OP_LDXI number {
+ bpf_set_curr_instr(BPF_LDX | BPF_IMM, 0, 0, $2); }
+ ;
+
+ldx
+ : OP_LDX '#' number {
+ bpf_set_curr_instr(BPF_LDX | BPF_IMM, 0, 0, $3); }
+ | OP_LDX K_PKT_LEN {
+ bpf_set_curr_instr(BPF_LDX | BPF_W | BPF_LEN, 0, 0, 0); }
+ | OP_LDX 'M' '[' number ']' {
+ bpf_set_curr_instr(BPF_LDX | BPF_MEM, 0, 0, $4); }
+ | OP_LDXB number '*' '(' '[' number ']' '&' number ')' {
+ if ($2 != 4 || $9 != 0xf) {
+ fprintf(stderr, "ldxb offset not supported!\n");
+ exit(0);
+ } else {
+ bpf_set_curr_instr(BPF_LDX | BPF_MSH | BPF_B, 0, 0, $6); } }
+ | OP_LDX number '*' '(' '[' number ']' '&' number ')' {
+ if ($2 != 4 || $9 != 0xf) {
+ fprintf(stderr, "ldxb offset not supported!\n");
+ exit(0);
+ } else {
+ bpf_set_curr_instr(BPF_LDX | BPF_MSH | BPF_B, 0, 0, $6); } }
+ ;
+
+st
+ : OP_ST 'M' '[' number ']' {
+ bpf_set_curr_instr(BPF_ST, 0, 0, $4); }
+ ;
+
+stx
+ : OP_STX 'M' '[' number ']' {
+ bpf_set_curr_instr(BPF_STX, 0, 0, $4); }
+ ;
+
+jmp
+ : OP_JMP label {
+ bpf_set_jmp_label($2, JKL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JA, 0, 0, 0); }
+ ;
+
+jeq
+ : OP_JEQ '#' number ',' label ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_jmp_label($7, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, $3); }
+ | OP_JEQ 'x' ',' label ',' label {
+ bpf_set_jmp_label($4, JTL);
+ bpf_set_jmp_label($6, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JEQ | BPF_X, 0, 0, 0); }
+ | OP_JEQ '%' 'x' ',' label ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_jmp_label($7, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JEQ | BPF_X, 0, 0, 0); }
+ | OP_JEQ '#' number ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, $3); }
+ | OP_JEQ 'x' ',' label {
+ bpf_set_jmp_label($4, JTL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JEQ | BPF_X, 0, 0, 0); }
+ | OP_JEQ '%' 'x' ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JEQ | BPF_X, 0, 0, 0); }
+ ;
+
+jneq
+ : OP_JNEQ '#' number ',' label {
+ bpf_set_jmp_label($5, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JEQ | BPF_K, 0, 0, $3); }
+ | OP_JNEQ 'x' ',' label {
+ bpf_set_jmp_label($4, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JEQ | BPF_X, 0, 0, 0); }
+ | OP_JNEQ '%' 'x' ',' label {
+ bpf_set_jmp_label($5, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JEQ | BPF_X, 0, 0, 0); }
+ ;
+
+jlt
+ : OP_JLT '#' number ',' label {
+ bpf_set_jmp_label($5, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGE | BPF_K, 0, 0, $3); }
+ | OP_JLT 'x' ',' label {
+ bpf_set_jmp_label($4, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGE | BPF_X, 0, 0, 0); }
+ | OP_JLT '%' 'x' ',' label {
+ bpf_set_jmp_label($5, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGE | BPF_X, 0, 0, 0); }
+ ;
+
+jle
+ : OP_JLE '#' number ',' label {
+ bpf_set_jmp_label($5, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGT | BPF_K, 0, 0, $3); }
+ | OP_JLE 'x' ',' label {
+ bpf_set_jmp_label($4, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGT | BPF_X, 0, 0, 0); }
+ | OP_JLE '%' 'x' ',' label {
+ bpf_set_jmp_label($5, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGT | BPF_X, 0, 0, 0); }
+ ;
+
+jgt
+ : OP_JGT '#' number ',' label ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_jmp_label($7, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGT | BPF_K, 0, 0, $3); }
+ | OP_JGT 'x' ',' label ',' label {
+ bpf_set_jmp_label($4, JTL);
+ bpf_set_jmp_label($6, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGT | BPF_X, 0, 0, 0); }
+ | OP_JGT '%' 'x' ',' label ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_jmp_label($7, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGT | BPF_X, 0, 0, 0); }
+ | OP_JGT '#' number ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGT | BPF_K, 0, 0, $3); }
+ | OP_JGT 'x' ',' label {
+ bpf_set_jmp_label($4, JTL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGT | BPF_X, 0, 0, 0); }
+ | OP_JGT '%' 'x' ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGT | BPF_X, 0, 0, 0); }
+ ;
+
+jge
+ : OP_JGE '#' number ',' label ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_jmp_label($7, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGE | BPF_K, 0, 0, $3); }
+ | OP_JGE 'x' ',' label ',' label {
+ bpf_set_jmp_label($4, JTL);
+ bpf_set_jmp_label($6, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGE | BPF_X, 0, 0, 0); }
+ | OP_JGE '%' 'x' ',' label ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_jmp_label($7, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGE | BPF_X, 0, 0, 0); }
+ | OP_JGE '#' number ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGE | BPF_K, 0, 0, $3); }
+ | OP_JGE 'x' ',' label {
+ bpf_set_jmp_label($4, JTL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGE | BPF_X, 0, 0, 0); }
+ | OP_JGE '%' 'x' ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JGE | BPF_X, 0, 0, 0); }
+ ;
+
+jset
+ : OP_JSET '#' number ',' label ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_jmp_label($7, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JSET | BPF_K, 0, 0, $3); }
+ | OP_JSET 'x' ',' label ',' label {
+ bpf_set_jmp_label($4, JTL);
+ bpf_set_jmp_label($6, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JSET | BPF_X, 0, 0, 0); }
+ | OP_JSET '%' 'x' ',' label ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_jmp_label($7, JFL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JSET | BPF_X, 0, 0, 0); }
+ | OP_JSET '#' number ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JSET | BPF_K, 0, 0, $3); }
+ | OP_JSET 'x' ',' label {
+ bpf_set_jmp_label($4, JTL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JSET | BPF_X, 0, 0, 0); }
+ | OP_JSET '%' 'x' ',' label {
+ bpf_set_jmp_label($5, JTL);
+ bpf_set_curr_instr(BPF_JMP | BPF_JSET | BPF_X, 0, 0, 0); }
+ ;
+
+add
+ : OP_ADD '#' number {
+ bpf_set_curr_instr(BPF_ALU | BPF_ADD | BPF_K, 0, 0, $3); }
+ | OP_ADD 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_ADD | BPF_X, 0, 0, 0); }
+ | OP_ADD '%' 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_ADD | BPF_X, 0, 0, 0); }
+ ;
+
+sub
+ : OP_SUB '#' number {
+ bpf_set_curr_instr(BPF_ALU | BPF_SUB | BPF_K, 0, 0, $3); }
+ | OP_SUB 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_SUB | BPF_X, 0, 0, 0); }
+ | OP_SUB '%' 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_SUB | BPF_X, 0, 0, 0); }
+ ;
+
+mul
+ : OP_MUL '#' number {
+ bpf_set_curr_instr(BPF_ALU | BPF_MUL | BPF_K, 0, 0, $3); }
+ | OP_MUL 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_MUL | BPF_X, 0, 0, 0); }
+ | OP_MUL '%' 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_MUL | BPF_X, 0, 0, 0); }
+ ;
+
+div
+ : OP_DIV '#' number {
+ bpf_set_curr_instr(BPF_ALU | BPF_DIV | BPF_K, 0, 0, $3); }
+ | OP_DIV 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_DIV | BPF_X, 0, 0, 0); }
+ | OP_DIV '%' 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_DIV | BPF_X, 0, 0, 0); }
+ ;
+
+mod
+ : OP_MOD '#' number {
+ bpf_set_curr_instr(BPF_ALU | BPF_MOD | BPF_K, 0, 0, $3); }
+ | OP_MOD 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_MOD | BPF_X, 0, 0, 0); }
+ | OP_MOD '%' 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_MOD | BPF_X, 0, 0, 0); }
+ ;
+
+neg
+ : OP_NEG {
+ bpf_set_curr_instr(BPF_ALU | BPF_NEG, 0, 0, 0); }
+ ;
+
+and
+ : OP_AND '#' number {
+ bpf_set_curr_instr(BPF_ALU | BPF_AND | BPF_K, 0, 0, $3); }
+ | OP_AND 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_AND | BPF_X, 0, 0, 0); }
+ | OP_AND '%' 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_AND | BPF_X, 0, 0, 0); }
+ ;
+
+or
+ : OP_OR '#' number {
+ bpf_set_curr_instr(BPF_ALU | BPF_OR | BPF_K, 0, 0, $3); }
+ | OP_OR 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_OR | BPF_X, 0, 0, 0); }
+ | OP_OR '%' 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_OR | BPF_X, 0, 0, 0); }
+ ;
+
+xor
+ : OP_XOR '#' number {
+ bpf_set_curr_instr(BPF_ALU | BPF_XOR | BPF_K, 0, 0, $3); }
+ | OP_XOR 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_XOR | BPF_X, 0, 0, 0); }
+ | OP_XOR '%' 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_XOR | BPF_X, 0, 0, 0); }
+ ;
+
+lsh
+ : OP_LSH '#' number {
+ bpf_set_curr_instr(BPF_ALU | BPF_LSH | BPF_K, 0, 0, $3); }
+ | OP_LSH 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_LSH | BPF_X, 0, 0, 0); }
+ | OP_LSH '%' 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_LSH | BPF_X, 0, 0, 0); }
+ ;
+
+rsh
+ : OP_RSH '#' number {
+ bpf_set_curr_instr(BPF_ALU | BPF_RSH | BPF_K, 0, 0, $3); }
+ | OP_RSH 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_RSH | BPF_X, 0, 0, 0); }
+ | OP_RSH '%' 'x' {
+ bpf_set_curr_instr(BPF_ALU | BPF_RSH | BPF_X, 0, 0, 0); }
+ ;
+
+ret
+ : OP_RET 'a' {
+ bpf_set_curr_instr(BPF_RET | BPF_A, 0, 0, 0); }
+ | OP_RET '%' 'a' {
+ bpf_set_curr_instr(BPF_RET | BPF_A, 0, 0, 0); }
+ | OP_RET 'x' {
+ bpf_set_curr_instr(BPF_RET | BPF_X, 0, 0, 0); }
+ | OP_RET '%' 'x' {
+ bpf_set_curr_instr(BPF_RET | BPF_X, 0, 0, 0); }
+ | OP_RET '#' number {
+ bpf_set_curr_instr(BPF_RET | BPF_K, 0, 0, $3); }
+ ;
+
+tax
+ : OP_TAX {
+ bpf_set_curr_instr(BPF_MISC | BPF_TAX, 0, 0, 0); }
+ ;
+
+txa
+ : OP_TXA {
+ bpf_set_curr_instr(BPF_MISC | BPF_TXA, 0, 0, 0); }
+ ;
+
+%%
+
+static int curr_instr = 0;
+static struct sock_filter out[BPF_MAXINSNS];
+static char **labels, **labels_jt, **labels_jf, **labels_k;
+
+static void bpf_assert_max(void)
+{
+ if (curr_instr >= BPF_MAXINSNS) {
+ fprintf(stderr, "only max %u insns allowed!\n", BPF_MAXINSNS);
+ exit(0);
+ }
+}
+
+static void bpf_set_curr_instr(uint16_t code, uint8_t jt, uint8_t jf,
+ uint32_t k)
+{
+ bpf_assert_max();
+ out[curr_instr].code = code;
+ out[curr_instr].jt = jt;
+ out[curr_instr].jf = jf;
+ out[curr_instr].k = k;
+ curr_instr++;
+}
+
+static void bpf_set_curr_label(char *label)
+{
+ bpf_assert_max();
+ labels[curr_instr] = label;
+}
+
+static void bpf_set_jmp_label(char *label, enum jmp_type type)
+{
+ bpf_assert_max();
+ switch (type) {
+ case JTL:
+ labels_jt[curr_instr] = label;
+ break;
+ case JFL:
+ labels_jf[curr_instr] = label;
+ break;
+ case JKL:
+ labels_k[curr_instr] = label;
+ break;
+ }
+}
+
+static int bpf_find_insns_offset(const char *label)
+{
+ int i, max = curr_instr, ret = -ENOENT;
+
+ for (i = 0; i < max; i++) {
+ if (labels[i] && !strcmp(label, labels[i])) {
+ ret = i;
+ break;
+ }
+ }
+
+ if (ret == -ENOENT) {
+ fprintf(stderr, "no such label \'%s\'!\n", label);
+ exit(0);
+ }
+
+ return ret;
+}
+
+static void bpf_stage_1_insert_insns(void)
+{
+ yyparse();
+}
+
+static void bpf_reduce_k_jumps(void)
+{
+ int i;
+
+ for (i = 0; i < curr_instr; i++) {
+ if (labels_k[i]) {
+ int off = bpf_find_insns_offset(labels_k[i]);
+ out[i].k = (uint32_t) (off - i - 1);
+ }
+ }
+}
+
+static void bpf_reduce_jt_jumps(void)
+{
+ int i;
+
+ for (i = 0; i < curr_instr; i++) {
+ if (labels_jt[i]) {
+ int off = bpf_find_insns_offset(labels_jt[i]);
+ out[i].jt = (uint8_t) (off - i -1);
+ }
+ }
+}
+
+static void bpf_reduce_jf_jumps(void)
+{
+ int i;
+
+ for (i = 0; i < curr_instr; i++) {
+ if (labels_jf[i]) {
+ int off = bpf_find_insns_offset(labels_jf[i]);
+ out[i].jf = (uint8_t) (off - i - 1);
+ }
+ }
+}
+
+static void bpf_stage_2_reduce_labels(void)
+{
+ bpf_reduce_k_jumps();
+ bpf_reduce_jt_jumps();
+ bpf_reduce_jf_jumps();
+}
+
+static void bpf_pretty_print_c(void)
+{
+ int i;
+
+ for (i = 0; i < curr_instr; i++)
+ printf("{ %#04x, %2u, %2u, %#010x },\n", out[i].code,
+ out[i].jt, out[i].jf, out[i].k);
+}
+
+static void bpf_pretty_print(void)
+{
+ int i;
+
+ printf("%u,", curr_instr);
+ for (i = 0; i < curr_instr; i++)
+ printf("%u %u %u %u,", out[i].code,
+ out[i].jt, out[i].jf, out[i].k);
+ printf("\n");
+}
+
+static void bpf_init(void)
+{
+ memset(out, 0, sizeof(out));
+
+ labels = calloc(BPF_MAXINSNS, sizeof(*labels));
+ assert(labels);
+ labels_jt = calloc(BPF_MAXINSNS, sizeof(*labels_jt));
+ assert(labels_jt);
+ labels_jf = calloc(BPF_MAXINSNS, sizeof(*labels_jf));
+ assert(labels_jf);
+ labels_k = calloc(BPF_MAXINSNS, sizeof(*labels_k));
+ assert(labels_k);
+}
+
+static void bpf_destroy_labels(void)
+{
+ int i;
+
+ for (i = 0; i < curr_instr; i++) {
+ free(labels_jf[i]);
+ free(labels_jt[i]);
+ free(labels_k[i]);
+ free(labels[i]);
+ }
+}
+
+static void bpf_destroy(void)
+{
+ bpf_destroy_labels();
+ free(labels_jt);
+ free(labels_jf);
+ free(labels_k);
+ free(labels);
+}
+
+void bpf_asm_compile(FILE *fp, bool cstyle)
+{
+ yyin = fp;
+
+ bpf_init();
+ bpf_stage_1_insert_insns();
+ bpf_stage_2_reduce_labels();
+ bpf_destroy();
+
+ if (cstyle)
+ bpf_pretty_print_c();
+ else
+ bpf_pretty_print();
+
+ if (fp != stdin)
+ fclose(yyin);
+}
+
+void yyerror(const char *str)
+{
+ exit(1);
+}
diff --git a/tools/net/bpf_jit_disasm.c b/tools/net/bpf_jit_disasm.c
new file mode 100644
index 00000000000..c5baf9c591b
--- /dev/null
+++ b/tools/net/bpf_jit_disasm.c
@@ -0,0 +1,197 @@
+/*
+ * Minimal BPF JIT image disassembler
+ *
+ * Disassembles BPF JIT compiler emitted opcodes back to asm insn's for
+ * debugging or verification purposes.
+ *
+ * To get the disassembly of the JIT code, do the following:
+ *
+ * 1) `echo 2 > /proc/sys/net/core/bpf_jit_enable`
+ * 2) Load a BPF filter (e.g. `tcpdump -p -n -s 0 -i eth1 host 192.168.20.0/24`)
+ * 3) Run e.g. `bpf_jit_disasm -o` to read out the last JIT code
+ *
+ * Copyright 2013 Daniel Borkmann <borkmann@redhat.com>
+ * Licensed under the GNU General Public License, version 2.0 (GPLv2)
+ */
+
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#include <string.h>
+#include <bfd.h>
+#include <dis-asm.h>
+#include <sys/klog.h>
+#include <sys/types.h>
+#include <regex.h>
+
+static void get_exec_path(char *tpath, size_t size)
+{
+ char *path;
+ ssize_t len;
+
+ snprintf(tpath, size, "/proc/%d/exe", (int) getpid());
+ tpath[size - 1] = 0;
+
+ path = strdup(tpath);
+ assert(path);
+
+ len = readlink(path, tpath, size);
+ tpath[len] = 0;
+
+ free(path);
+}
+
+static void get_asm_insns(uint8_t *image, size_t len, int opcodes)
+{
+ int count, i, pc = 0;
+ char tpath[256];
+ struct disassemble_info info;
+ disassembler_ftype disassemble;
+ bfd *bfdf;
+
+ memset(tpath, 0, sizeof(tpath));
+ get_exec_path(tpath, sizeof(tpath));
+
+ bfdf = bfd_openr(tpath, NULL);
+ assert(bfdf);
+ assert(bfd_check_format(bfdf, bfd_object));
+
+ init_disassemble_info(&info, stdout, (fprintf_ftype) fprintf);
+ info.arch = bfd_get_arch(bfdf);
+ info.mach = bfd_get_mach(bfdf);
+ info.buffer = image;
+ info.buffer_length = len;
+
+ disassemble_init_for_target(&info);
+
+ disassemble = disassembler(bfdf);
+ assert(disassemble);
+
+ do {
+ printf("%4x:\t", pc);
+
+ count = disassemble(pc, &info);
+
+ if (opcodes) {
+ printf("\n\t");
+ for (i = 0; i < count; ++i)
+ printf("%02x ", (uint8_t) image[pc + i]);
+ }
+ printf("\n");
+
+ pc += count;
+ } while(count > 0 && pc < len);
+
+ bfd_close(bfdf);
+}
+
+static char *get_klog_buff(int *klen)
+{
+ int ret, len = klogctl(10, NULL, 0);
+ char *buff = malloc(len);
+
+ assert(buff && klen);
+ ret = klogctl(3, buff, len);
+ assert(ret >= 0);
+ *klen = ret;
+
+ return buff;
+}
+
+static void put_klog_buff(char *buff)
+{
+ free(buff);
+}
+
+static int get_last_jit_image(char *haystack, size_t hlen,
+ uint8_t *image, size_t ilen)
+{
+ char *ptr, *pptr, *tmp;
+ off_t off = 0;
+ int ret, flen, proglen, pass, ulen = 0;
+ regmatch_t pmatch[1];
+ unsigned long base;
+ regex_t regex;
+
+ if (hlen == 0)
+ return 0;
+
+ ret = regcomp(&regex, "flen=[[:alnum:]]+ proglen=[[:digit:]]+ "
+ "pass=[[:digit:]]+ image=[[:xdigit:]]+", REG_EXTENDED);
+ assert(ret == 0);
+
+ ptr = haystack;
+ while (1) {
+ ret = regexec(&regex, ptr, 1, pmatch, 0);
+ if (ret == 0) {
+ ptr += pmatch[0].rm_eo;
+ off += pmatch[0].rm_eo;
+ assert(off < hlen);
+ } else
+ break;
+ }
+
+ ptr = haystack + off - (pmatch[0].rm_eo - pmatch[0].rm_so);
+ ret = sscanf(ptr, "flen=%d proglen=%d pass=%d image=%lx",
+ &flen, &proglen, &pass, &base);
+ if (ret != 4)
+ return 0;
+
+ tmp = ptr = haystack + off;
+ while ((ptr = strtok(tmp, "\n")) != NULL && ulen < ilen) {
+ tmp = NULL;
+ if (!strstr(ptr, "JIT code"))
+ continue;
+ pptr = ptr;
+ while ((ptr = strstr(pptr, ":")))
+ pptr = ptr + 1;
+ ptr = pptr;
+ do {
+ image[ulen++] = (uint8_t) strtoul(pptr, &pptr, 16);
+ if (ptr == pptr || ulen >= ilen) {
+ ulen--;
+ break;
+ }
+ ptr = pptr;
+ } while (1);
+ }
+
+ assert(ulen == proglen);
+ printf("%d bytes emitted from JIT compiler (pass:%d, flen:%d)\n",
+ proglen, pass, flen);
+ printf("%lx + <x>:\n", base);
+
+ regfree(&regex);
+ return ulen;
+}
+
+int main(int argc, char **argv)
+{
+ int len, klen, opcodes = 0;
+ char *kbuff;
+ static uint8_t image[32768];
+
+ if (argc > 1) {
+ if (!strncmp("-o", argv[argc - 1], 2)) {
+ opcodes = 1;
+ } else {
+ printf("usage: bpf_jit_disasm [-o: show opcodes]\n");
+ exit(0);
+ }
+ }
+
+ bfd_init();
+ memset(image, 0, sizeof(image));
+
+ kbuff = get_klog_buff(&klen);
+
+ len = get_last_jit_image(kbuff, klen, image, sizeof(image));
+ if (len > 0)
+ get_asm_insns(image, len, opcodes);
+
+ put_klog_buff(kbuff);
+
+ return 0;
+}
diff --git a/tools/nfsd/inject_fault.sh b/tools/nfsd/inject_fault.sh
new file mode 100755
index 00000000000..06a399ac8b2
--- /dev/null
+++ b/tools/nfsd/inject_fault.sh
@@ -0,0 +1,49 @@
+#!/bin/bash
+#
+# Copyright (c) 2011 Bryan Schumaker <bjschuma@netapp.com>
+#
+# Script for easier NFSD fault injection
+
+# Check that debugfs has been mounted
+DEBUGFS=`cat /proc/mounts | grep debugfs`
+if [ "$DEBUGFS" == "" ]; then
+ echo "debugfs does not appear to be mounted!"
+ echo "Please mount debugfs and try again"
+ exit 1
+fi
+
+# Check that the fault injection directory exists
+DEBUGDIR=`echo $DEBUGFS | awk '{print $2}'`/nfsd
+if [ ! -d "$DEBUGDIR" ]; then
+ echo "$DEBUGDIR does not exist"
+ echo "Check that your .config selects CONFIG_NFSD_FAULT_INJECTION"
+ exit 1
+fi
+
+function help()
+{
+ echo "Usage $0 injection_type [count]"
+ echo ""
+ echo "Injection types are:"
+ ls $DEBUGDIR
+ exit 1
+}
+
+if [ $# == 0 ]; then
+ help
+elif [ ! -f $DEBUGDIR/$1 ]; then
+ help
+elif [ $# != 2 ]; then
+ COUNT=0
+else
+ COUNT=$2
+fi
+
+BEFORE=`mktemp`
+AFTER=`mktemp`
+dmesg > $BEFORE
+echo $COUNT > $DEBUGDIR/$1
+dmesg > $AFTER
+# Capture lines that only exist in the $AFTER file
+diff $BEFORE $AFTER | grep ">"
+rm -f $BEFORE $AFTER
diff --git a/tools/perf/.gitignore b/tools/perf/.gitignore
new file mode 100644
index 00000000000..782d86e961b
--- /dev/null
+++ b/tools/perf/.gitignore
@@ -0,0 +1,26 @@
+PERF-CFLAGS
+PERF-GUI-VARS
+PERF-VERSION-FILE
+perf
+perf-help
+perf-record
+perf-report
+perf-stat
+perf-top
+perf*.1
+perf*.xml
+perf*.html
+common-cmds.h
+perf.data
+perf.data.old
+output.svg
+perf-archive
+tags
+TAGS
+cscope*
+config.mak
+config.mak.autogen
+*-bison.*
+*-flex.*
+*.pyc
+*.pyo
diff --git a/tools/perf/CREDITS b/tools/perf/CREDITS
new file mode 100644
index 00000000000..c2ddcb3acbd
--- /dev/null
+++ b/tools/perf/CREDITS
@@ -0,0 +1,30 @@
+Most of the infrastructure that 'perf' uses here has been reused
+from the Git project, as of version:
+
+ 66996ec: Sync with 1.6.2.4
+
+Here is an (incomplete!) list of main contributors to those files
+in util/* and elsewhere:
+
+ Alex Riesen
+ Christian Couder
+ Dmitry Potapov
+ Jeff King
+ Johannes Schindelin
+ Johannes Sixt
+ Junio C Hamano
+ Linus Torvalds
+ Matthias Kestenholz
+ Michal Ostrowski
+ Miklos Vajna
+ Petr Baudis
+ Pierre Habouzit
+ René Scharfe
+ Samuel Tardieu
+ Shawn O. Pearce
+ Steffen Prohaska
+ Steve Haslam
+
+Thanks guys!
+
+The full history of the files can be found in the upstream Git commits.
diff --git a/tools/perf/Documentation/Makefile b/tools/perf/Documentation/Makefile
new file mode 100644
index 00000000000..3ba1c0b0990
--- /dev/null
+++ b/tools/perf/Documentation/Makefile
@@ -0,0 +1,343 @@
+include ../../scripts/Makefile.include
+include ../config/utilities.mak
+
+MAN1_TXT= \
+ $(filter-out $(addsuffix .txt, $(ARTICLES) $(SP_ARTICLES)), \
+ $(wildcard perf-*.txt)) \
+ perf.txt
+MAN5_TXT=
+MAN7_TXT=
+
+MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
+_MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
+_MAN_HTML=$(patsubst %.txt,%.html,$(MAN_TXT))
+
+MAN_XML=$(addprefix $(OUTPUT),$(_MAN_XML))
+MAN_HTML=$(addprefix $(OUTPUT),$(_MAN_HTML))
+
+ARTICLES =
+# with their own formatting rules.
+SP_ARTICLES =
+API_DOCS = $(patsubst %.txt,%,$(filter-out technical/api-index-skel.txt technical/api-index.txt, $(wildcard technical/api-*.txt)))
+SP_ARTICLES += $(API_DOCS)
+SP_ARTICLES += technical/api-index
+
+_DOC_HTML = $(_MAN_HTML)
+_DOC_HTML+=$(patsubst %,%.html,$(ARTICLES) $(SP_ARTICLES))
+DOC_HTML=$(addprefix $(OUTPUT),$(_DOC_HTML))
+
+_DOC_MAN1=$(patsubst %.txt,%.1,$(MAN1_TXT))
+_DOC_MAN5=$(patsubst %.txt,%.5,$(MAN5_TXT))
+_DOC_MAN7=$(patsubst %.txt,%.7,$(MAN7_TXT))
+
+DOC_MAN1=$(addprefix $(OUTPUT),$(_DOC_MAN1))
+DOC_MAN5=$(addprefix $(OUTPUT),$(_DOC_MAN5))
+DOC_MAN7=$(addprefix $(OUTPUT),$(_DOC_MAN7))
+
+# Make the path relative to DESTDIR, not prefix
+ifndef DESTDIR
+prefix?=$(HOME)
+endif
+bindir?=$(prefix)/bin
+htmldir?=$(prefix)/share/doc/perf-doc
+pdfdir?=$(prefix)/share/doc/perf-doc
+mandir?=$(prefix)/share/man
+man1dir=$(mandir)/man1
+man5dir=$(mandir)/man5
+man7dir=$(mandir)/man7
+
+ASCIIDOC=asciidoc
+ASCIIDOC_EXTRA = --unsafe
+MANPAGE_XSL = manpage-normal.xsl
+XMLTO_EXTRA =
+INSTALL?=install
+RM ?= rm -f
+DOC_REF = origin/man
+HTML_REF = origin/html
+
+infodir?=$(prefix)/share/info
+MAKEINFO=makeinfo
+INSTALL_INFO=install-info
+DOCBOOK2X_TEXI=docbook2x-texi
+DBLATEX=dblatex
+XMLTO=xmlto
+ifndef PERL_PATH
+ PERL_PATH = /usr/bin/perl
+endif
+
+-include ../config.mak.autogen
+-include ../config.mak
+
+_tmp_tool_path := $(call get-executable,$(ASCIIDOC))
+ifeq ($(_tmp_tool_path),)
+ missing_tools = $(ASCIIDOC)
+endif
+
+_tmp_tool_path := $(call get-executable,$(XMLTO))
+ifeq ($(_tmp_tool_path),)
+ missing_tools += $(XMLTO)
+endif
+
+#
+# For asciidoc ...
+# -7.1.2, no extra settings are needed.
+# 8.0-, set ASCIIDOC8.
+#
+
+#
+# For docbook-xsl ...
+# -1.68.1, set ASCIIDOC_NO_ROFF? (based on changelog from 1.73.0)
+# 1.69.0, no extra settings are needed?
+# 1.69.1-1.71.0, set DOCBOOK_SUPPRESS_SP?
+# 1.71.1, no extra settings are needed?
+# 1.72.0, set DOCBOOK_XSL_172.
+# 1.73.0-, set ASCIIDOC_NO_ROFF
+#
+
+#
+# If you had been using DOCBOOK_XSL_172 in an attempt to get rid
+# of 'the ".ft C" problem' in your generated manpages, and you
+# instead ended up with weird characters around callouts, try
+# using ASCIIDOC_NO_ROFF instead (it works fine with ASCIIDOC8).
+#
+
+ifdef ASCIIDOC8
+ASCIIDOC_EXTRA += -a asciidoc7compatible
+endif
+ifdef DOCBOOK_XSL_172
+ASCIIDOC_EXTRA += -a perf-asciidoc-no-roff
+MANPAGE_XSL = manpage-1.72.xsl
+else
+ ifdef ASCIIDOC_NO_ROFF
+ # docbook-xsl after 1.72 needs the regular XSL, but will not
+ # pass-thru raw roff codes from asciidoc.conf, so turn them off.
+ ASCIIDOC_EXTRA += -a perf-asciidoc-no-roff
+ endif
+endif
+ifdef MAN_BOLD_LITERAL
+XMLTO_EXTRA += -m manpage-bold-literal.xsl
+endif
+ifdef DOCBOOK_SUPPRESS_SP
+XMLTO_EXTRA += -m manpage-suppress-sp.xsl
+endif
+
+SHELL_PATH ?= $(SHELL)
+# Shell quote;
+SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH))
+
+#
+# Please note that there is a minor bug in asciidoc.
+# The version after 6.0.3 _will_ include the patch found here:
+# http://marc.theaimsgroup.com/?l=perf&m=111558757202243&w=2
+#
+# Until that version is released you may have to apply the patch
+# yourself - yes, all 6 characters of it!
+#
+
+QUIET_SUBDIR0 = +$(MAKE) -C # space to separate -C and subdir
+QUIET_SUBDIR1 =
+
+ifneq ($(findstring $(MAKEFLAGS),w),w)
+PRINT_DIR = --no-print-directory
+else # "make -w"
+NO_SUBDIR = :
+endif
+
+ifneq ($(findstring $(MAKEFLAGS),s),s)
+ifneq ($(V),1)
+ QUIET_ASCIIDOC = @echo ' ASCIIDOC '$@;
+ QUIET_XMLTO = @echo ' XMLTO '$@;
+ QUIET_DB2TEXI = @echo ' DB2TEXI '$@;
+ QUIET_MAKEINFO = @echo ' MAKEINFO '$@;
+ QUIET_DBLATEX = @echo ' DBLATEX '$@;
+ QUIET_XSLTPROC = @echo ' XSLTPROC '$@;
+ QUIET_GEN = @echo ' GEN '$@;
+ QUIET_STDERR = 2> /dev/null
+ QUIET_SUBDIR0 = +@subdir=
+ QUIET_SUBDIR1 = ;$(NO_SUBDIR) \
+ echo ' SUBDIR ' $$subdir; \
+ $(MAKE) $(PRINT_DIR) -C $$subdir
+ export V
+endif
+endif
+
+all: html man
+
+html: $(DOC_HTML)
+
+$(DOC_HTML) $(DOC_MAN1) $(DOC_MAN5) $(DOC_MAN7): asciidoc.conf
+
+man: man1 man5 man7
+man1: $(DOC_MAN1)
+man5: $(DOC_MAN5)
+man7: $(DOC_MAN7)
+
+info: $(OUTPUT)perf.info $(OUTPUT)perfman.info
+
+pdf: $(OUTPUT)user-manual.pdf
+
+install: install-man
+
+check-man-tools:
+ifdef missing_tools
+ $(error "You need to install $(missing_tools) for man pages")
+endif
+
+do-install-man: man
+ $(call QUIET_INSTALL, Documentation-man) \
+ $(INSTALL) -d -m 755 $(DESTDIR)$(man1dir); \
+# $(INSTALL) -d -m 755 $(DESTDIR)$(man5dir); \
+# $(INSTALL) -d -m 755 $(DESTDIR)$(man7dir); \
+ $(INSTALL) -m 644 $(DOC_MAN1) $(DESTDIR)$(man1dir); \
+# $(INSTALL) -m 644 $(DOC_MAN5) $(DESTDIR)$(man5dir); \
+# $(INSTALL) -m 644 $(DOC_MAN7) $(DESTDIR)$(man7dir)
+
+install-man: check-man-tools man
+
+ifdef missing_tools
+ DO_INSTALL_MAN = $(warning Please install $(missing_tools) to have the man pages installed)
+else
+ DO_INSTALL_MAN = do-install-man
+endif
+
+try-install-man: $(DO_INSTALL_MAN)
+
+install-info: info
+ $(call QUIET_INSTALL, Documentation-info) \
+ $(INSTALL) -d -m 755 $(DESTDIR)$(infodir); \
+ $(INSTALL) -m 644 $(OUTPUT)perf.info $(OUTPUT)perfman.info $(DESTDIR)$(infodir); \
+ if test -r $(DESTDIR)$(infodir)/dir; then \
+ $(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) perf.info ;\
+ $(INSTALL_INFO) --info-dir=$(DESTDIR)$(infodir) perfman.info ;\
+ else \
+ echo "No directory found in $(DESTDIR)$(infodir)" >&2 ; \
+ fi
+
+install-pdf: pdf
+ $(call QUIET_INSTALL, Documentation-pdf) \
+ $(INSTALL) -d -m 755 $(DESTDIR)$(pdfdir); \
+ $(INSTALL) -m 644 $(OUTPUT)user-manual.pdf $(DESTDIR)$(pdfdir)
+
+#install-html: html
+# '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(DESTDIR)$(htmldir)
+
+
+#
+# Determine "include::" file references in asciidoc files.
+#
+$(OUTPUT)doc.dep : $(wildcard *.txt) build-docdep.perl
+ $(QUIET_GEN)$(RM) $@+ $@ && \
+ $(PERL_PATH) ./build-docdep.perl >$@+ $(QUIET_STDERR) && \
+ mv $@+ $@
+
+-include $(OUPTUT)doc.dep
+
+_cmds_txt = cmds-ancillaryinterrogators.txt \
+ cmds-ancillarymanipulators.txt \
+ cmds-mainporcelain.txt \
+ cmds-plumbinginterrogators.txt \
+ cmds-plumbingmanipulators.txt \
+ cmds-synchingrepositories.txt \
+ cmds-synchelpers.txt \
+ cmds-purehelpers.txt \
+ cmds-foreignscminterface.txt
+cmds_txt=$(addprefix $(OUTPUT),$(_cmds_txt))
+
+$(cmds_txt): $(OUTPUT)cmd-list.made
+
+$(OUTPUT)cmd-list.made: cmd-list.perl ../command-list.txt $(MAN1_TXT)
+ $(QUIET_GEN)$(RM) $@ && \
+ $(PERL_PATH) ./cmd-list.perl ../command-list.txt $(QUIET_STDERR) && \
+ date >$@
+
+CLEAN_FILES = \
+ $(MAN_XML) $(addsuffix +,$(MAN_XML)) \
+ $(MAN_HTML) $(addsuffix +,$(MAN_HTML)) \
+ $(DOC_HTML) $(DOC_MAN1) $(DOC_MAN5) $(DOC_MAN7) \
+ $(OUTPUT)*.texi $(OUTPUT)*.texi+ $(OUTPUT)*.texi++ \
+ $(OUTPUT)perf.info $(OUTPUT)perfman.info \
+ $(OUTPUT)howto-index.txt $(OUTPUT)howto/*.html $(OUTPUT)doc.dep \
+ $(OUTPUT)technical/api-*.html $(OUTPUT)technical/api-index.txt \
+ $(cmds_txt) $(OUTPUT)*.made
+clean:
+ $(call QUIET_CLEAN, Documentation) $(RM) $(CLEAN_FILES)
+
+$(MAN_HTML): $(OUTPUT)%.html : %.txt
+ $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
+ $(ASCIIDOC) -b xhtml11 -d manpage -f asciidoc.conf \
+ $(ASCIIDOC_EXTRA) -aperf_version=$(PERF_VERSION) -o $@+ $< && \
+ mv $@+ $@
+
+$(OUTPUT)%.1 $(OUTPUT)%.5 $(OUTPUT)%.7 : $(OUTPUT)%.xml
+ $(QUIET_XMLTO)$(RM) $@ && \
+ $(XMLTO) -o $(OUTPUT). -m $(MANPAGE_XSL) $(XMLTO_EXTRA) man $<
+
+$(OUTPUT)%.xml : %.txt
+ $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
+ $(ASCIIDOC) -b docbook -d manpage -f asciidoc.conf \
+ $(ASCIIDOC_EXTRA) -aperf_version=$(PERF_VERSION) -o $@+ $< && \
+ mv $@+ $@
+
+XSLT = docbook.xsl
+XSLTOPTS = --xinclude --stringparam html.stylesheet docbook-xsl.css
+
+$(OUTPUT)user-manual.html: $(OUTPUT)user-manual.xml
+ $(QUIET_XSLTPROC)xsltproc $(XSLTOPTS) -o $@ $(XSLT) $<
+
+$(OUTPUT)perf.info: $(OUTPUT)user-manual.texi
+ $(QUIET_MAKEINFO)$(MAKEINFO) --no-split -o $@ $(OUTPUT)user-manual.texi
+
+$(OUTPUT)user-manual.texi: $(OUTPUT)user-manual.xml
+ $(QUIET_DB2TEXI)$(RM) $@+ $@ && \
+ $(DOCBOOK2X_TEXI) $(OUTPUT)user-manual.xml --encoding=UTF-8 --to-stdout >$@++ && \
+ $(PERL_PATH) fix-texi.perl <$@++ >$@+ && \
+ rm $@++ && \
+ mv $@+ $@
+
+$(OUTPUT)user-manual.pdf: $(OUTPUT)user-manual.xml
+ $(QUIET_DBLATEX)$(RM) $@+ $@ && \
+ $(DBLATEX) -o $@+ -p /etc/asciidoc/dblatex/asciidoc-dblatex.xsl -s /etc/asciidoc/dblatex/asciidoc-dblatex.sty $< && \
+ mv $@+ $@
+
+$(OUTPUT)perfman.texi: $(MAN_XML) cat-texi.perl
+ $(QUIET_DB2TEXI)$(RM) $@+ $@ && \
+ ($(foreach xml,$(MAN_XML),$(DOCBOOK2X_TEXI) --encoding=UTF-8 \
+ --to-stdout $(xml) &&) true) > $@++ && \
+ $(PERL_PATH) cat-texi.perl $@ <$@++ >$@+ && \
+ rm $@++ && \
+ mv $@+ $@
+
+$(OUTPUT)perfman.info: $(OUTPUT)perfman.texi
+ $(QUIET_MAKEINFO)$(MAKEINFO) --no-split --no-validate $*.texi
+
+$(patsubst %.txt,%.texi,$(MAN_TXT)): %.texi : %.xml
+ $(QUIET_DB2TEXI)$(RM) $@+ $@ && \
+ $(DOCBOOK2X_TEXI) --to-stdout $*.xml >$@+ && \
+ mv $@+ $@
+
+howto-index.txt: howto-index.sh $(wildcard howto/*.txt)
+ $(QUIET_GEN)$(RM) $@+ $@ && \
+ '$(SHELL_PATH_SQ)' ./howto-index.sh $(wildcard howto/*.txt) >$@+ && \
+ mv $@+ $@
+
+$(patsubst %,%.html,$(ARTICLES)) : %.html : %.txt
+ $(QUIET_ASCIIDOC)$(ASCIIDOC) -b xhtml11 $*.txt
+
+WEBDOC_DEST = /pub/software/tools/perf/docs
+
+$(patsubst %.txt,%.html,$(wildcard howto/*.txt)): %.html : %.txt
+ $(QUIET_ASCIIDOC)$(RM) $@+ $@ && \
+ sed -e '1,/^$$/d' $< | $(ASCIIDOC) -b xhtml11 - >$@+ && \
+ mv $@+ $@
+
+# UNIMPLEMENTED
+#install-webdoc : html
+# '$(SHELL_PATH_SQ)' ./install-webdoc.sh $(WEBDOC_DEST)
+
+# quick-install: quick-install-man
+
+# quick-install-man:
+# '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(DOC_REF) $(DESTDIR)$(mandir)
+
+#quick-install-html:
+# '$(SHELL_PATH_SQ)' ./install-doc-quick.sh $(HTML_REF) $(DESTDIR)$(htmldir)
diff --git a/tools/perf/Documentation/android.txt b/tools/perf/Documentation/android.txt
new file mode 100644
index 00000000000..8484c3a04a6
--- /dev/null
+++ b/tools/perf/Documentation/android.txt
@@ -0,0 +1,78 @@
+How to compile perf for Android
+=========================================
+
+I. Set the Android NDK environment
+------------------------------------------------
+
+(a). Use the Android NDK
+------------------------------------------------
+1. You need to download and install the Android Native Development Kit (NDK).
+Set the NDK variable to point to the path where you installed the NDK:
+ export NDK=/path/to/android-ndk
+
+2. Set cross-compiling environment variables for NDK toolchain and sysroot.
+For arm:
+ export NDK_TOOLCHAIN=${NDK}/toolchains/arm-linux-androideabi-4.6/prebuilt/linux-x86/bin/arm-linux-androideabi-
+ export NDK_SYSROOT=${NDK}/platforms/android-9/arch-arm
+For x86:
+ export NDK_TOOLCHAIN=${NDK}/toolchains/x86-4.6/prebuilt/linux-x86/bin/i686-linux-android-
+ export NDK_SYSROOT=${NDK}/platforms/android-9/arch-x86
+
+This method is not working for Android NDK versions up to Revision 8b.
+perf uses some bionic enhancements that are not included in these NDK versions.
+You can use method (b) described below instead.
+
+(b). Use the Android source tree
+-----------------------------------------------
+1. Download the master branch of the Android source tree.
+Set the environment for the target you want using:
+ source build/envsetup.sh
+ lunch
+
+2. Build your own NDK sysroot to contain latest bionic changes and set the
+NDK sysroot environment variable.
+ cd ${ANDROID_BUILD_TOP}/ndk
+For arm:
+ ./build/tools/build-ndk-sysroot.sh --abi=arm
+ export NDK_SYSROOT=${ANDROID_BUILD_TOP}/ndk/build/platforms/android-3/arch-arm
+For x86:
+ ./build/tools/build-ndk-sysroot.sh --abi=x86
+ export NDK_SYSROOT=${ANDROID_BUILD_TOP}/ndk/build/platforms/android-3/arch-x86
+
+3. Set the NDK toolchain environment variable.
+For arm:
+ export NDK_TOOLCHAIN=${ANDROID_TOOLCHAIN}/arm-linux-androideabi-
+For x86:
+ export NDK_TOOLCHAIN=${ANDROID_TOOLCHAIN}/i686-linux-android-
+
+II. Compile perf for Android
+------------------------------------------------
+You need to run make with the NDK toolchain and sysroot defined above:
+For arm:
+ make ARCH=arm CROSS_COMPILE=${NDK_TOOLCHAIN} CFLAGS="--sysroot=${NDK_SYSROOT}"
+For x86:
+ make ARCH=x86 CROSS_COMPILE=${NDK_TOOLCHAIN} CFLAGS="--sysroot=${NDK_SYSROOT}"
+
+III. Install perf
+-----------------------------------------------
+You need to connect to your Android device/emulator using adb.
+Install perf using:
+ adb push perf /data/perf
+
+If you also want to use perf-archive you need busybox tools for Android.
+For installing perf-archive, you first need to replace #!/bin/bash with #!/system/bin/sh:
+ sed 's/#!\/bin\/bash/#!\/system\/bin\/sh/g' perf-archive >> /tmp/perf-archive
+ chmod +x /tmp/perf-archive
+ adb push /tmp/perf-archive /data/perf-archive
+
+IV. Environment settings for running perf
+------------------------------------------------
+Some perf features need environment variables to run properly.
+You need to set these before running perf on the target:
+ adb shell
+ # PERF_PAGER=cat
+
+IV. Run perf
+------------------------------------------------
+Run perf on your device/emulator to which you previously connected using adb:
+ # ./data/perf
diff --git a/tools/perf/Documentation/asciidoc.conf b/tools/perf/Documentation/asciidoc.conf
new file mode 100644
index 00000000000..356b23a4033
--- /dev/null
+++ b/tools/perf/Documentation/asciidoc.conf
@@ -0,0 +1,91 @@
+## linkperf: macro
+#
+# Usage: linkperf:command[manpage-section]
+#
+# Note, {0} is the manpage section, while {target} is the command.
+#
+# Show PERF link as: <command>(<section>); if section is defined, else just show
+# the command.
+
+[macros]
+(?su)[\\]?(?P<name>linkperf):(?P<target>\S*?)\[(?P<attrlist>.*?)\]=
+
+[attributes]
+asterisk=&#42;
+plus=&#43;
+caret=&#94;
+startsb=&#91;
+endsb=&#93;
+tilde=&#126;
+
+ifdef::backend-docbook[]
+[linkperf-inlinemacro]
+{0%{target}}
+{0#<citerefentry>}
+{0#<refentrytitle>{target}</refentrytitle><manvolnum>{0}</manvolnum>}
+{0#</citerefentry>}
+endif::backend-docbook[]
+
+ifdef::backend-docbook[]
+ifndef::perf-asciidoc-no-roff[]
+# "unbreak" docbook-xsl v1.68 for manpages. v1.69 works with or without this.
+# v1.72 breaks with this because it replaces dots not in roff requests.
+[listingblock]
+<example><title>{title}</title>
+<literallayout>
+ifdef::doctype-manpage[]
+&#10;.ft C&#10;
+endif::doctype-manpage[]
+|
+ifdef::doctype-manpage[]
+&#10;.ft&#10;
+endif::doctype-manpage[]
+</literallayout>
+{title#}</example>
+endif::perf-asciidoc-no-roff[]
+
+ifdef::perf-asciidoc-no-roff[]
+ifdef::doctype-manpage[]
+# The following two small workarounds insert a simple paragraph after screen
+[listingblock]
+<example><title>{title}</title>
+<literallayout>
+|
+</literallayout><simpara></simpara>
+{title#}</example>
+
+[verseblock]
+<formalpara{id? id="{id}"}><title>{title}</title><para>
+{title%}<literallayout{id? id="{id}"}>
+{title#}<literallayout>
+|
+</literallayout>
+{title#}</para></formalpara>
+{title%}<simpara></simpara>
+endif::doctype-manpage[]
+endif::perf-asciidoc-no-roff[]
+endif::backend-docbook[]
+
+ifdef::doctype-manpage[]
+ifdef::backend-docbook[]
+[header]
+template::[header-declarations]
+<refentry>
+<refmeta>
+<refentrytitle>{mantitle}</refentrytitle>
+<manvolnum>{manvolnum}</manvolnum>
+<refmiscinfo class="source">perf</refmiscinfo>
+<refmiscinfo class="version">{perf_version}</refmiscinfo>
+<refmiscinfo class="manual">perf Manual</refmiscinfo>
+</refmeta>
+<refnamediv>
+ <refname>{manname}</refname>
+ <refpurpose>{manpurpose}</refpurpose>
+</refnamediv>
+endif::backend-docbook[]
+endif::doctype-manpage[]
+
+ifdef::backend-xhtml11[]
+[linkperf-inlinemacro]
+<a href="{target}.html">{target}{0?({0})}</a>
+endif::backend-xhtml11[]
diff --git a/tools/perf/Documentation/examples.txt b/tools/perf/Documentation/examples.txt
new file mode 100644
index 00000000000..a4e39215648
--- /dev/null
+++ b/tools/perf/Documentation/examples.txt
@@ -0,0 +1,225 @@
+
+ ------------------------------
+ ****** perf by examples ******
+ ------------------------------
+
+[ From an e-mail by Ingo Molnar, http://lkml.org/lkml/2009/8/4/346 ]
+
+
+First, discovery/enumeration of available counters can be done via
+'perf list':
+
+titan:~> perf list
+ [...]
+ kmem:kmalloc [Tracepoint event]
+ kmem:kmem_cache_alloc [Tracepoint event]
+ kmem:kmalloc_node [Tracepoint event]
+ kmem:kmem_cache_alloc_node [Tracepoint event]
+ kmem:kfree [Tracepoint event]
+ kmem:kmem_cache_free [Tracepoint event]
+ kmem:mm_page_free [Tracepoint event]
+ kmem:mm_page_free_batched [Tracepoint event]
+ kmem:mm_page_alloc [Tracepoint event]
+ kmem:mm_page_alloc_zone_locked [Tracepoint event]
+ kmem:mm_page_pcpu_drain [Tracepoint event]
+ kmem:mm_page_alloc_extfrag [Tracepoint event]
+
+Then any (or all) of the above event sources can be activated and
+measured. For example the page alloc/free properties of a 'hackbench
+run' are:
+
+ titan:~> perf stat -e kmem:mm_page_pcpu_drain -e kmem:mm_page_alloc
+ -e kmem:mm_page_free_batched -e kmem:mm_page_free ./hackbench 10
+ Time: 0.575
+
+ Performance counter stats for './hackbench 10':
+
+ 13857 kmem:mm_page_pcpu_drain
+ 27576 kmem:mm_page_alloc
+ 6025 kmem:mm_page_free_batched
+ 20934 kmem:mm_page_free
+
+ 0.613972165 seconds time elapsed
+
+You can observe the statistical properties as well, by using the
+'repeat the workload N times' feature of perf stat:
+
+ titan:~> perf stat --repeat 5 -e kmem:mm_page_pcpu_drain -e
+ kmem:mm_page_alloc -e kmem:mm_page_free_batched -e
+ kmem:mm_page_free ./hackbench 10
+ Time: 0.627
+ Time: 0.644
+ Time: 0.564
+ Time: 0.559
+ Time: 0.626
+
+ Performance counter stats for './hackbench 10' (5 runs):
+
+ 12920 kmem:mm_page_pcpu_drain ( +- 3.359% )
+ 25035 kmem:mm_page_alloc ( +- 3.783% )
+ 6104 kmem:mm_page_free_batched ( +- 0.934% )
+ 18376 kmem:mm_page_free ( +- 4.941% )
+
+ 0.643954516 seconds time elapsed ( +- 2.363% )
+
+Furthermore, these tracepoints can be used to sample the workload as
+well. For example the page allocations done by a 'git gc' can be
+captured the following way:
+
+ titan:~/git> perf record -e kmem:mm_page_alloc -c 1 ./git gc
+ Counting objects: 1148, done.
+ Delta compression using up to 2 threads.
+ Compressing objects: 100% (450/450), done.
+ Writing objects: 100% (1148/1148), done.
+ Total 1148 (delta 690), reused 1148 (delta 690)
+ [ perf record: Captured and wrote 0.267 MB perf.data (~11679 samples) ]
+
+To check which functions generated page allocations:
+
+ titan:~/git> perf report
+ # Samples: 10646
+ #
+ # Overhead Command Shared Object
+ # ........ ............... ..........................
+ #
+ 23.57% git-repack /lib64/libc-2.5.so
+ 21.81% git /lib64/libc-2.5.so
+ 14.59% git ./git
+ 11.79% git-repack ./git
+ 7.12% git /lib64/ld-2.5.so
+ 3.16% git-repack /lib64/libpthread-2.5.so
+ 2.09% git-repack /bin/bash
+ 1.97% rm /lib64/libc-2.5.so
+ 1.39% mv /lib64/ld-2.5.so
+ 1.37% mv /lib64/libc-2.5.so
+ 1.12% git-repack /lib64/ld-2.5.so
+ 0.95% rm /lib64/ld-2.5.so
+ 0.90% git-update-serv /lib64/libc-2.5.so
+ 0.73% git-update-serv /lib64/ld-2.5.so
+ 0.68% perf /lib64/libpthread-2.5.so
+ 0.64% git-repack /usr/lib64/libz.so.1.2.3
+
+Or to see it on a more finegrained level:
+
+titan:~/git> perf report --sort comm,dso,symbol
+# Samples: 10646
+#
+# Overhead Command Shared Object Symbol
+# ........ ............... .......................... ......
+#
+ 9.35% git-repack ./git [.] insert_obj_hash
+ 9.12% git ./git [.] insert_obj_hash
+ 7.31% git /lib64/libc-2.5.so [.] memcpy
+ 6.34% git-repack /lib64/libc-2.5.so [.] _int_malloc
+ 6.24% git-repack /lib64/libc-2.5.so [.] memcpy
+ 5.82% git-repack /lib64/libc-2.5.so [.] __GI___fork
+ 5.47% git /lib64/libc-2.5.so [.] _int_malloc
+ 2.99% git /lib64/libc-2.5.so [.] memset
+
+Furthermore, call-graph sampling can be done too, of page
+allocations - to see precisely what kind of page allocations there
+are:
+
+ titan:~/git> perf record -g -e kmem:mm_page_alloc -c 1 ./git gc
+ Counting objects: 1148, done.
+ Delta compression using up to 2 threads.
+ Compressing objects: 100% (450/450), done.
+ Writing objects: 100% (1148/1148), done.
+ Total 1148 (delta 690), reused 1148 (delta 690)
+ [ perf record: Captured and wrote 0.963 MB perf.data (~42069 samples) ]
+
+ titan:~/git> perf report -g
+ # Samples: 10686
+ #
+ # Overhead Command Shared Object
+ # ........ ............... ..........................
+ #
+ 23.25% git-repack /lib64/libc-2.5.so
+ |
+ |--50.00%-- _int_free
+ |
+ |--37.50%-- __GI___fork
+ | make_child
+ |
+ |--12.50%-- ptmalloc_unlock_all2
+ | make_child
+ |
+ --6.25%-- __GI_strcpy
+ 21.61% git /lib64/libc-2.5.so
+ |
+ |--30.00%-- __GI_read
+ | |
+ | --83.33%-- git_config_from_file
+ | git_config
+ | |
+ [...]
+
+Or you can observe the whole system's page allocations for 10
+seconds:
+
+titan:~/git> perf stat -a -e kmem:mm_page_pcpu_drain -e
+kmem:mm_page_alloc -e kmem:mm_page_free_batched -e
+kmem:mm_page_free sleep 10
+
+ Performance counter stats for 'sleep 10':
+
+ 171585 kmem:mm_page_pcpu_drain
+ 322114 kmem:mm_page_alloc
+ 73623 kmem:mm_page_free_batched
+ 254115 kmem:mm_page_free
+
+ 10.000591410 seconds time elapsed
+
+Or observe how fluctuating the page allocations are, via statistical
+analysis done over ten 1-second intervals:
+
+ titan:~/git> perf stat --repeat 10 -a -e kmem:mm_page_pcpu_drain -e
+ kmem:mm_page_alloc -e kmem:mm_page_free_batched -e
+ kmem:mm_page_free sleep 1
+
+ Performance counter stats for 'sleep 1' (10 runs):
+
+ 17254 kmem:mm_page_pcpu_drain ( +- 3.709% )
+ 34394 kmem:mm_page_alloc ( +- 4.617% )
+ 7509 kmem:mm_page_free_batched ( +- 4.820% )
+ 25653 kmem:mm_page_free ( +- 3.672% )
+
+ 1.058135029 seconds time elapsed ( +- 3.089% )
+
+Or you can annotate the recorded 'git gc' run on a per symbol basis
+and check which instructions/source-code generated page allocations:
+
+ titan:~/git> perf annotate __GI___fork
+ ------------------------------------------------
+ Percent | Source code & Disassembly of libc-2.5.so
+ ------------------------------------------------
+ :
+ :
+ : Disassembly of section .plt:
+ : Disassembly of section .text:
+ :
+ : 00000031a2e95560 <__fork>:
+ [...]
+ 0.00 : 31a2e95602: b8 38 00 00 00 mov $0x38,%eax
+ 0.00 : 31a2e95607: 0f 05 syscall
+ 83.42 : 31a2e95609: 48 3d 00 f0 ff ff cmp $0xfffffffffffff000,%rax
+ 0.00 : 31a2e9560f: 0f 87 4d 01 00 00 ja 31a2e95762 <__fork+0x202>
+ 0.00 : 31a2e95615: 85 c0 test %eax,%eax
+
+( this shows that 83.42% of __GI___fork's page allocations come from
+ the 0x38 system call it performs. )
+
+etc. etc. - a lot more is possible. I could list a dozen of
+other different usecases straight away - neither of which is
+possible via /proc/vmstat.
+
+/proc/vmstat is not in the same league really, in terms of
+expressive power of system analysis and performance
+analysis.
+
+All that the above results needed were those new tracepoints
+in include/tracing/events/kmem.h.
+
+ Ingo
+
+
diff --git a/tools/perf/Documentation/jit-interface.txt b/tools/perf/Documentation/jit-interface.txt
new file mode 100644
index 00000000000..a8656f56491
--- /dev/null
+++ b/tools/perf/Documentation/jit-interface.txt
@@ -0,0 +1,15 @@
+perf supports a simple JIT interface to resolve symbols for dynamic code generated
+by a JIT.
+
+The JIT has to write a /tmp/perf-%d.map (%d = pid of process) file
+
+This is a text file.
+
+Each line has the following format, fields separated with spaces:
+
+START SIZE symbolname
+
+START and SIZE are hex numbers without 0x.
+symbolname is the rest of the line, so it could contain special characters.
+
+The ownership of the file has to match the process.
diff --git a/tools/perf/Documentation/manpage-1.72.xsl b/tools/perf/Documentation/manpage-1.72.xsl
new file mode 100644
index 00000000000..b4d315cb8c4
--- /dev/null
+++ b/tools/perf/Documentation/manpage-1.72.xsl
@@ -0,0 +1,14 @@
+<!-- manpage-1.72.xsl:
+ special settings for manpages rendered from asciidoc+docbook
+ handles peculiarities in docbook-xsl 1.72.0 -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="1.0">
+
+<xsl:import href="manpage-base.xsl"/>
+
+<!-- these are the special values for the roff control characters
+ needed for docbook-xsl 1.72.0 -->
+<xsl:param name="git.docbook.backslash">&#x2593;</xsl:param>
+<xsl:param name="git.docbook.dot" >&#x2302;</xsl:param>
+
+</xsl:stylesheet>
diff --git a/tools/perf/Documentation/manpage-base.xsl b/tools/perf/Documentation/manpage-base.xsl
new file mode 100644
index 00000000000..a264fa61609
--- /dev/null
+++ b/tools/perf/Documentation/manpage-base.xsl
@@ -0,0 +1,35 @@
+<!-- manpage-base.xsl:
+ special formatting for manpages rendered from asciidoc+docbook -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="1.0">
+
+<!-- these params silence some output from xmlto -->
+<xsl:param name="man.output.quietly" select="1"/>
+<xsl:param name="refentry.meta.get.quietly" select="1"/>
+
+<!-- convert asciidoc callouts to man page format;
+ git.docbook.backslash and git.docbook.dot params
+ must be supplied by another XSL file or other means -->
+<xsl:template match="co">
+ <xsl:value-of select="concat(
+ $git.docbook.backslash,'fB(',
+ substring-after(@id,'-'),')',
+ $git.docbook.backslash,'fR')"/>
+</xsl:template>
+<xsl:template match="calloutlist">
+ <xsl:value-of select="$git.docbook.dot"/>
+ <xsl:text>sp&#10;</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:text>&#10;</xsl:text>
+</xsl:template>
+<xsl:template match="callout">
+ <xsl:value-of select="concat(
+ $git.docbook.backslash,'fB',
+ substring-after(@arearefs,'-'),
+ '. ',$git.docbook.backslash,'fR')"/>
+ <xsl:apply-templates/>
+ <xsl:value-of select="$git.docbook.dot"/>
+ <xsl:text>br&#10;</xsl:text>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/tools/perf/Documentation/manpage-bold-literal.xsl b/tools/perf/Documentation/manpage-bold-literal.xsl
new file mode 100644
index 00000000000..608eb5df628
--- /dev/null
+++ b/tools/perf/Documentation/manpage-bold-literal.xsl
@@ -0,0 +1,17 @@
+<!-- manpage-bold-literal.xsl:
+ special formatting for manpages rendered from asciidoc+docbook -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="1.0">
+
+<!-- render literal text as bold (instead of plain or monospace);
+ this makes literal text easier to distinguish in manpages
+ viewed on a tty -->
+<xsl:template match="literal">
+ <xsl:value-of select="$git.docbook.backslash"/>
+ <xsl:text>fB</xsl:text>
+ <xsl:apply-templates/>
+ <xsl:value-of select="$git.docbook.backslash"/>
+ <xsl:text>fR</xsl:text>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/tools/perf/Documentation/manpage-normal.xsl b/tools/perf/Documentation/manpage-normal.xsl
new file mode 100644
index 00000000000..a48f5b11f3d
--- /dev/null
+++ b/tools/perf/Documentation/manpage-normal.xsl
@@ -0,0 +1,13 @@
+<!-- manpage-normal.xsl:
+ special settings for manpages rendered from asciidoc+docbook
+ handles anything we want to keep away from docbook-xsl 1.72.0 -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="1.0">
+
+<xsl:import href="manpage-base.xsl"/>
+
+<!-- these are the normal values for the roff control characters -->
+<xsl:param name="git.docbook.backslash">\</xsl:param>
+<xsl:param name="git.docbook.dot" >.</xsl:param>
+
+</xsl:stylesheet>
diff --git a/tools/perf/Documentation/manpage-suppress-sp.xsl b/tools/perf/Documentation/manpage-suppress-sp.xsl
new file mode 100644
index 00000000000..a63c7632a87
--- /dev/null
+++ b/tools/perf/Documentation/manpage-suppress-sp.xsl
@@ -0,0 +1,21 @@
+<!-- manpage-suppress-sp.xsl:
+ special settings for manpages rendered from asciidoc+docbook
+ handles erroneous, inline .sp in manpage output of some
+ versions of docbook-xsl -->
+<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
+ version="1.0">
+
+<!-- attempt to work around spurious .sp at the tail of the line
+ that some versions of docbook stylesheets seem to add -->
+<xsl:template match="simpara">
+ <xsl:variable name="content">
+ <xsl:apply-templates/>
+ </xsl:variable>
+ <xsl:value-of select="normalize-space($content)"/>
+ <xsl:if test="not(ancestor::authorblurb) and
+ not(ancestor::personblurb)">
+ <xsl:text>&#10;&#10;</xsl:text>
+ </xsl:if>
+</xsl:template>
+
+</xsl:stylesheet>
diff --git a/tools/perf/Documentation/perf-annotate.txt b/tools/perf/Documentation/perf-annotate.txt
new file mode 100644
index 00000000000..e9cd39a92dc
--- /dev/null
+++ b/tools/perf/Documentation/perf-annotate.txt
@@ -0,0 +1,101 @@
+perf-annotate(1)
+================
+
+NAME
+----
+perf-annotate - Read perf.data (created by perf record) and display annotated code
+
+SYNOPSIS
+--------
+[verse]
+'perf annotate' [-i <file> | --input=file] [symbol_name]
+
+DESCRIPTION
+-----------
+This command reads the input file and displays an annotated version of the
+code. If the object file has debug symbols then the source code will be
+displayed alongside assembly code.
+
+If there is no debug info in the object, then annotated assembly is displayed.
+
+OPTIONS
+-------
+-i::
+--input=::
+ Input file name. (default: perf.data unless stdin is a fifo)
+
+-d::
+--dsos=<dso[,dso...]>::
+ Only consider symbols in these dsos.
+-s::
+--symbol=<symbol>::
+ Symbol to annotate.
+
+-f::
+--force::
+ Don't complain, do it.
+
+-v::
+--verbose::
+ Be more verbose. (Show symbol address, etc)
+
+-D::
+--dump-raw-trace::
+ Dump raw trace in ASCII.
+
+-k::
+--vmlinux=<file>::
+ vmlinux pathname.
+
+-m::
+--modules::
+ Load module symbols. WARNING: use only with -k and LIVE kernel.
+
+-l::
+--print-line::
+ Print matching source lines (may be slow).
+
+-P::
+--full-paths::
+ Don't shorten the displayed pathnames.
+
+--stdio:: Use the stdio interface.
+
+--tui:: Use the TUI interface. Use of --tui requires a tty, if one is not
+ present, as when piping to other commands, the stdio interface is
+ used. This interfaces starts by centering on the line with more
+ samples, TAB/UNTAB cycles through the lines with more samples.
+
+--gtk:: Use the GTK interface.
+
+-C::
+--cpu:: Only report samples for the list of CPUs provided. Multiple CPUs can
+ be provided as a comma-separated list with no space: 0,1. Ranges of
+ CPUs are specified with -: 0-2. Default is to report samples on all
+ CPUs.
+
+--asm-raw::
+ Show raw instruction encoding of assembly instructions.
+
+--source::
+ Interleave source code with assembly code. Enabled by default,
+ disable with --no-source.
+
+--symfs=<directory>::
+ Look for files with symbols relative to this directory.
+
+-M::
+--disassembler-style=:: Set disassembler style for objdump.
+
+--objdump=<path>::
+ Path to objdump binary.
+
+--skip-missing::
+ Skip symbols that cannot be annotated.
+
+--group::
+ Show event group information together
+
+SEE ALSO
+--------
+linkperf:perf-record[1], linkperf:perf-report[1]
diff --git a/tools/perf/Documentation/perf-archive.txt b/tools/perf/Documentation/perf-archive.txt
new file mode 100644
index 00000000000..ac6ecbb3e66
--- /dev/null
+++ b/tools/perf/Documentation/perf-archive.txt
@@ -0,0 +1,22 @@
+perf-archive(1)
+===============
+
+NAME
+----
+perf-archive - Create archive with object files with build-ids found in perf.data file
+
+SYNOPSIS
+--------
+[verse]
+'perf archive' [file]
+
+DESCRIPTION
+-----------
+This command runs perf-buildid-list --with-hits, and collects the files with the
+buildids found so that analysis of perf.data contents can be possible on another
+machine.
+
+
+SEE ALSO
+--------
+linkperf:perf-record[1], linkperf:perf-buildid-list[1], linkperf:perf-report[1]
diff --git a/tools/perf/Documentation/perf-bench.txt b/tools/perf/Documentation/perf-bench.txt
new file mode 100644
index 00000000000..4464ad770d5
--- /dev/null
+++ b/tools/perf/Documentation/perf-bench.txt
@@ -0,0 +1,214 @@
+perf-bench(1)
+=============
+
+NAME
+----
+perf-bench - General framework for benchmark suites
+
+SYNOPSIS
+--------
+[verse]
+'perf bench' [<common options>] <subsystem> <suite> [<options>]
+
+DESCRIPTION
+-----------
+This 'perf bench' command is a general framework for benchmark suites.
+
+COMMON OPTIONS
+--------------
+-f::
+--format=::
+Specify format style.
+Current available format styles are:
+
+'default'::
+Default style. This is mainly for human reading.
+---------------------
+% perf bench sched pipe # with no style specified
+(executing 1000000 pipe operations between two tasks)
+ Total time:5.855 sec
+ 5.855061 usecs/op
+ 170792 ops/sec
+---------------------
+
+'simple'::
+This simple style is friendly for automated
+processing by scripts.
+---------------------
+% perf bench --format=simple sched pipe # specified simple
+5.988
+---------------------
+
+SUBSYSTEM
+---------
+
+'sched'::
+ Scheduler and IPC mechanisms.
+
+'mem'::
+ Memory access performance.
+
+'numa'::
+ NUMA scheduling and MM benchmarks.
+
+'futex'::
+ Futex stressing benchmarks.
+
+'all'::
+ All benchmark subsystems.
+
+SUITES FOR 'sched'
+~~~~~~~~~~~~~~~~~~
+*messaging*::
+Suite for evaluating performance of scheduler and IPC mechanisms.
+Based on hackbench by Rusty Russell.
+
+Options of *messaging*
+^^^^^^^^^^^^^^^^^^^^^^
+-p::
+--pipe::
+Use pipe() instead of socketpair()
+
+-t::
+--thread::
+Be multi thread instead of multi process
+
+-g::
+--group=::
+Specify number of groups
+
+-l::
+--loop=::
+Specify number of loops
+
+Example of *messaging*
+^^^^^^^^^^^^^^^^^^^^^^
+
+---------------------
+% perf bench sched messaging # run with default
+options (20 sender and receiver processes per group)
+(10 groups == 400 processes run)
+
+ Total time:0.308 sec
+
+% perf bench sched messaging -t -g 20 # be multi-thread, with 20 groups
+(20 sender and receiver threads per group)
+(20 groups == 800 threads run)
+
+ Total time:0.582 sec
+---------------------
+
+*pipe*::
+Suite for pipe() system call.
+Based on pipe-test-1m.c by Ingo Molnar.
+
+Options of *pipe*
+^^^^^^^^^^^^^^^^^
+-l::
+--loop=::
+Specify number of loops.
+
+Example of *pipe*
+^^^^^^^^^^^^^^^^^
+
+---------------------
+% perf bench sched pipe
+(executing 1000000 pipe operations between two tasks)
+
+ Total time:8.091 sec
+ 8.091833 usecs/op
+ 123581 ops/sec
+
+% perf bench sched pipe -l 1000 # loop 1000
+(executing 1000 pipe operations between two tasks)
+
+ Total time:0.016 sec
+ 16.948000 usecs/op
+ 59004 ops/sec
+---------------------
+
+SUITES FOR 'mem'
+~~~~~~~~~~~~~~~~
+*memcpy*::
+Suite for evaluating performance of simple memory copy in various ways.
+
+Options of *memcpy*
+^^^^^^^^^^^^^^^^^^^
+-l::
+--length::
+Specify length of memory to copy (default: 1MB).
+Available units are B, KB, MB, GB and TB (case insensitive).
+
+-r::
+--routine::
+Specify routine to copy (default: default).
+Available routines are depend on the architecture.
+On x86-64, x86-64-unrolled, x86-64-movsq and x86-64-movsb are supported.
+
+-i::
+--iterations::
+Repeat memcpy invocation this number of times.
+
+-c::
+--cycle::
+Use perf's cpu-cycles event instead of gettimeofday syscall.
+
+-o::
+--only-prefault::
+Show only the result with page faults before memcpy.
+
+-n::
+--no-prefault::
+Show only the result without page faults before memcpy.
+
+*memset*::
+Suite for evaluating performance of simple memory set in various ways.
+
+Options of *memset*
+^^^^^^^^^^^^^^^^^^^
+-l::
+--length::
+Specify length of memory to set (default: 1MB).
+Available units are B, KB, MB, GB and TB (case insensitive).
+
+-r::
+--routine::
+Specify routine to set (default: default).
+Available routines are depend on the architecture.
+On x86-64, x86-64-unrolled, x86-64-stosq and x86-64-stosb are supported.
+
+-i::
+--iterations::
+Repeat memset invocation this number of times.
+
+-c::
+--cycle::
+Use perf's cpu-cycles event instead of gettimeofday syscall.
+
+-o::
+--only-prefault::
+Show only the result with page faults before memset.
+
+-n::
+--no-prefault::
+Show only the result without page faults before memset.
+
+SUITES FOR 'numa'
+~~~~~~~~~~~~~~~~~
+*mem*::
+Suite for evaluating NUMA workloads.
+
+SUITES FOR 'futex'
+~~~~~~~~~~~~~~~~~~
+*hash*::
+Suite for evaluating hash tables.
+
+*wake*::
+Suite for evaluating wake calls.
+
+*requeue*::
+Suite for evaluating requeue calls.
+
+SEE ALSO
+--------
+linkperf:perf[1]
diff --git a/tools/perf/Documentation/perf-buildid-cache.txt b/tools/perf/Documentation/perf-buildid-cache.txt
new file mode 100644
index 00000000000..fd77d81ea74
--- /dev/null
+++ b/tools/perf/Documentation/perf-buildid-cache.txt
@@ -0,0 +1,53 @@
+perf-buildid-cache(1)
+=====================
+
+NAME
+----
+perf-buildid-cache - Manage build-id cache.
+
+SYNOPSIS
+--------
+[verse]
+'perf buildid-cache <options>'
+
+DESCRIPTION
+-----------
+This command manages the build-id cache. It can add and remove files to/from
+the cache. In the future it should as well purge older entries, set upper
+limits for the space used by the cache, etc.
+
+OPTIONS
+-------
+-a::
+--add=::
+ Add specified file to the cache.
+-k::
+--kcore::
+ Add specified kcore file to the cache. For the current host that is
+ /proc/kcore which requires root permissions to read. Be aware that
+ running 'perf buildid-cache' as root may update root's build-id cache
+ not the user's. Use the -v option to see where the file is created.
+ Note that the copied file contains only code sections not the whole core
+ image. Note also that files "kallsyms" and "modules" must also be in the
+ same directory and are also copied. All 3 files are created with read
+ permissions for root only. kcore will not be added if there is already a
+ kcore in the cache (with the same build-id) that has the same modules at
+ the same addresses. Use the -v option to see if a copy of kcore is
+ actually made.
+-r::
+--remove=::
+ Remove specified file from the cache.
+-M::
+--missing=::
+ List missing build ids in the cache for the specified file.
+-u::
+--update::
+ Update specified file of the cache. It can be used to update kallsyms
+ kernel dso to vmlinux in order to support annotation.
+-v::
+--verbose::
+ Be more verbose.
+
+SEE ALSO
+--------
+linkperf:perf-record[1], linkperf:perf-report[1], linkperf:perf-buildid-list[1]
diff --git a/tools/perf/Documentation/perf-buildid-list.txt b/tools/perf/Documentation/perf-buildid-list.txt
new file mode 100644
index 00000000000..25c52efcc7f
--- /dev/null
+++ b/tools/perf/Documentation/perf-buildid-list.txt
@@ -0,0 +1,43 @@
+perf-buildid-list(1)
+====================
+
+NAME
+----
+perf-buildid-list - List the buildids in a perf.data file
+
+SYNOPSIS
+--------
+[verse]
+'perf buildid-list <options>'
+
+DESCRIPTION
+-----------
+This command displays the buildids found in a perf.data file, so that other
+tools can be used to fetch packages with matching symbol tables for use by
+perf report.
+
+It can also be used to show the build id of the running kernel or in an ELF
+file using -i/--input.
+
+OPTIONS
+-------
+-H::
+--with-hits::
+ Show only DSOs with hits.
+-i::
+--input=::
+ Input file name. (default: perf.data unless stdin is a fifo)
+-f::
+--force::
+ Don't do ownership validation.
+-k::
+--kernel::
+ Show running kernel build id.
+-v::
+--verbose::
+ Be more verbose.
+
+SEE ALSO
+--------
+linkperf:perf-record[1], linkperf:perf-top[1],
+linkperf:perf-report[1]
diff --git a/tools/perf/Documentation/perf-diff.txt b/tools/perf/Documentation/perf-diff.txt
new file mode 100644
index 00000000000..b3b8abae62b
--- /dev/null
+++ b/tools/perf/Documentation/perf-diff.txt
@@ -0,0 +1,206 @@
+perf-diff(1)
+============
+
+NAME
+----
+perf-diff - Read perf.data files and display the differential profile
+
+SYNOPSIS
+--------
+[verse]
+'perf diff' [baseline file] [data file1] [[data file2] ... ]
+
+DESCRIPTION
+-----------
+This command displays the performance difference amongst two or more perf.data
+files captured via perf record.
+
+If no parameters are passed it will assume perf.data.old and perf.data.
+
+The differential profile is displayed only for events matching both
+specified perf.data files.
+
+OPTIONS
+-------
+-D::
+--dump-raw-trace::
+ Dump raw trace in ASCII.
+
+-m::
+--modules::
+ Load module symbols. WARNING: use only with -k and LIVE kernel
+
+-d::
+--dsos=::
+ Only consider symbols in these dsos. CSV that understands
+ file://filename entries. This option will affect the percentage
+ of the Baseline/Delta column. See --percentage for more info.
+
+-C::
+--comms=::
+ Only consider symbols in these comms. CSV that understands
+ file://filename entries. This option will affect the percentage
+ of the Baseline/Delta column. See --percentage for more info.
+
+-S::
+--symbols=::
+ Only consider these symbols. CSV that understands
+ file://filename entries. This option will affect the percentage
+ of the Baseline/Delta column. See --percentage for more info.
+
+-s::
+--sort=::
+ Sort by key(s): pid, comm, dso, symbol, cpu, parent, srcline.
+ Please see description of --sort in the perf-report man page.
+
+-t::
+--field-separator=::
+
+ Use a special separator character and don't pad with spaces, replacing
+ all occurrences of this separator in symbol names (and other output)
+ with a '.' character, that thus it's the only non valid separator.
+
+-v::
+--verbose::
+ Be verbose, for instance, show the raw counts in addition to the
+ diff.
+
+-f::
+--force::
+ Don't complain, do it.
+
+--symfs=<directory>::
+ Look for files with symbols relative to this directory.
+
+-b::
+--baseline-only::
+ Show only items with match in baseline.
+
+-c::
+--compute::
+ Differential computation selection - delta,ratio,wdiff (default is delta).
+ See COMPARISON METHODS section for more info.
+
+-p::
+--period::
+ Show period values for both compared hist entries.
+
+-F::
+--formula::
+ Show formula for given computation.
+
+-o::
+--order::
+ Specify compute sorting column number.
+
+--percentage::
+ Determine how to display the overhead percentage of filtered entries.
+ Filters can be applied by --comms, --dsos and/or --symbols options.
+
+ "relative" means it's relative to filtered entries only so that the
+ sum of shown entries will be always 100%. "absolute" means it retains
+ the original value before and after the filter is applied.
+
+COMPARISON
+----------
+The comparison is governed by the baseline file. The baseline perf.data
+file is iterated for samples. All other perf.data files specified on
+the command line are searched for the baseline sample pair. If the pair
+is found, specified computation is made and result is displayed.
+
+All samples from non-baseline perf.data files, that do not match any
+baseline entry, are displayed with empty space within baseline column
+and possible computation results (delta) in their related column.
+
+Example files samples:
+- file A with samples f1, f2, f3, f4, f6
+- file B with samples f2, f4, f5
+- file C with samples f1, f2, f5
+
+Example output:
+ x - computation takes place for pair
+ b - baseline sample percentage
+
+- perf diff A B C
+
+ baseline/A compute/B compute/C samples
+ ---------------------------------------
+ b x f1
+ b x x f2
+ b f3
+ b x f4
+ b f6
+ x x f5
+
+- perf diff B A C
+
+ baseline/B compute/A compute/C samples
+ ---------------------------------------
+ b x x f2
+ b x f4
+ b x f5
+ x x f1
+ x f3
+ x f6
+
+- perf diff C B A
+
+ baseline/C compute/B compute/A samples
+ ---------------------------------------
+ b x f1
+ b x x f2
+ b x f5
+ x f3
+ x x f4
+ x f6
+
+COMPARISON METHODS
+------------------
+delta
+~~~~~
+If specified the 'Delta' column is displayed with value 'd' computed as:
+
+ d = A->period_percent - B->period_percent
+
+with:
+ - A/B being matching hist entry from data/baseline file specified
+ (or perf.data/perf.data.old) respectively.
+
+ - period_percent being the % of the hist entry period value within
+ single data file
+
+ - with filtering by -C, -d and/or -S, period_percent might be changed
+ relative to how entries are filtered. Use --percentage=absolute to
+ prevent such fluctuation.
+
+ratio
+~~~~~
+If specified the 'Ratio' column is displayed with value 'r' computed as:
+
+ r = A->period / B->period
+
+with:
+ - A/B being matching hist entry from data/baseline file specified
+ (or perf.data/perf.data.old) respectively.
+
+ - period being the hist entry period value
+
+wdiff:WEIGHT-B,WEIGHT-A
+~~~~~~~~~~~~~~~~~~~~~~~
+If specified the 'Weighted diff' column is displayed with value 'd' computed as:
+
+ d = B->period * WEIGHT-A - A->period * WEIGHT-B
+
+ - A/B being matching hist entry from data/baseline file specified
+ (or perf.data/perf.data.old) respectively.
+
+ - period being the hist entry period value
+
+ - WEIGHT-A/WEIGHT-B being user suplied weights in the the '-c' option
+ behind ':' separator like '-c wdiff:1,2'.
+ - WIEGHT-A being the weight of the data file
+ - WIEGHT-B being the weight of the baseline data file
+
+SEE ALSO
+--------
+linkperf:perf-record[1], linkperf:perf-report[1]
diff --git a/tools/perf/Documentation/perf-evlist.txt b/tools/perf/Documentation/perf-evlist.txt
new file mode 100644
index 00000000000..1ceb3700ffb
--- /dev/null
+++ b/tools/perf/Documentation/perf-evlist.txt
@@ -0,0 +1,38 @@
+perf-evlist(1)
+==============
+
+NAME
+----
+perf-evlist - List the event names in a perf.data file
+
+SYNOPSIS
+--------
+[verse]
+'perf evlist <options>'
+
+DESCRIPTION
+-----------
+This command displays the names of events sampled in a perf.data file.
+
+OPTIONS
+-------
+-i::
+--input=::
+ Input file name. (default: perf.data unless stdin is a fifo)
+
+-F::
+--freq=::
+ Show just the sample frequency used for each event.
+
+-v::
+--verbose=::
+ Show all fields.
+
+-g::
+--group::
+ Show event group information.
+
+SEE ALSO
+--------
+linkperf:perf-record[1], linkperf:perf-list[1],
+linkperf:perf-report[1]
diff --git a/tools/perf/Documentation/perf-help.txt b/tools/perf/Documentation/perf-help.txt
new file mode 100644
index 00000000000..514391818d1
--- /dev/null
+++ b/tools/perf/Documentation/perf-help.txt
@@ -0,0 +1,38 @@
+perf-help(1)
+============
+
+NAME
+----
+perf-help - display help information about perf
+
+SYNOPSIS
+--------
+'perf help' [-a|--all] [COMMAND]
+
+DESCRIPTION
+-----------
+
+With no options and no COMMAND given, the synopsis of the 'perf'
+command and a list of the most commonly used perf commands are printed
+on the standard output.
+
+If the option '--all' or '-a' is given, then all available commands are
+printed on the standard output.
+
+If a perf command is named, a manual page for that command is brought
+up. The 'man' program is used by default for this purpose, but this
+can be overridden by other options or configuration variables.
+
+Note that `perf --help ...` is identical to `perf help ...` because the
+former is internally converted into the latter.
+
+OPTIONS
+-------
+-a::
+--all::
+ Prints all the available commands on the standard output. This
+ option supersedes any other option.
+
+PERF
+----
+Part of the linkperf:perf[1] suite
diff --git a/tools/perf/Documentation/perf-inject.txt b/tools/perf/Documentation/perf-inject.txt
new file mode 100644
index 00000000000..a00a34276c5
--- /dev/null
+++ b/tools/perf/Documentation/perf-inject.txt
@@ -0,0 +1,46 @@
+perf-inject(1)
+==============
+
+NAME
+----
+perf-inject - Filter to augment the events stream with additional information
+
+SYNOPSIS
+--------
+[verse]
+'perf inject <options>'
+
+DESCRIPTION
+-----------
+perf-inject reads a perf-record event stream and repipes it to stdout. At any
+point the processing code can inject other events into the event stream - in
+this case build-ids (-b option) are read and injected as needed into the event
+stream.
+
+Build-ids are just the first user of perf-inject - potentially anything that
+needs userspace processing to augment the events stream with additional
+information could make use of this facility.
+
+OPTIONS
+-------
+-b::
+--build-ids=::
+ Inject build-ids into the output stream
+-v::
+--verbose::
+ Be more verbose.
+-i::
+--input=::
+ Input file name. (default: stdin)
+-o::
+--output=::
+ Output file name. (default: stdout)
+-s::
+--sched-stat::
+ Merge sched_stat and sched_switch for getting events where and how long
+ tasks slept. sched_switch contains a callchain where a task slept and
+ sched_stat contains a timeslice how long a task slept.
+
+SEE ALSO
+--------
+linkperf:perf-record[1], linkperf:perf-report[1], linkperf:perf-archive[1]
diff --git a/tools/perf/Documentation/perf-kmem.txt b/tools/perf/Documentation/perf-kmem.txt
new file mode 100644
index 00000000000..7c8fbbf3f61
--- /dev/null
+++ b/tools/perf/Documentation/perf-kmem.txt
@@ -0,0 +1,47 @@
+perf-kmem(1)
+============
+
+NAME
+----
+perf-kmem - Tool to trace/measure kernel memory(slab) properties
+
+SYNOPSIS
+--------
+[verse]
+'perf kmem' {record|stat} [<options>]
+
+DESCRIPTION
+-----------
+There are two variants of perf kmem:
+
+ 'perf kmem record <command>' to record the kmem events
+ of an arbitrary workload.
+
+ 'perf kmem stat' to report kernel memory statistics.
+
+OPTIONS
+-------
+-i <file>::
+--input=<file>::
+ Select the input file (default: perf.data unless stdin is a fifo)
+
+--caller::
+ Show per-callsite statistics
+
+--alloc::
+ Show per-allocation statistics
+
+-s <key[,key2...]>::
+--sort=<key[,key2...]>::
+ Sort the output (default: frag,hit,bytes)
+
+-l <num>::
+--line=<num>::
+ Print n lines only
+
+--raw-ip::
+ Print raw ip instead of symbol
+
+SEE ALSO
+--------
+linkperf:perf-record[1]
diff --git a/tools/perf/Documentation/perf-kvm.txt b/tools/perf/Documentation/perf-kvm.txt
new file mode 100644
index 00000000000..52276a6d2b7
--- /dev/null
+++ b/tools/perf/Documentation/perf-kvm.txt
@@ -0,0 +1,156 @@
+perf-kvm(1)
+===========
+
+NAME
+----
+perf-kvm - Tool to trace/measure kvm guest os
+
+SYNOPSIS
+--------
+[verse]
+'perf kvm' [--host] [--guest] [--guestmount=<path>
+ [--guestkallsyms=<path> --guestmodules=<path> | --guestvmlinux=<path>]]
+ {top|record|report|diff|buildid-list} [<options>]
+'perf kvm' [--host] [--guest] [--guestkallsyms=<path> --guestmodules=<path>
+ | --guestvmlinux=<path>] {top|record|report|diff|buildid-list|stat} [<options>]
+'perf kvm stat [record|report|live] [<options>]
+
+DESCRIPTION
+-----------
+There are a couple of variants of perf kvm:
+
+ 'perf kvm [options] top <command>' to generates and displays
+ a performance counter profile of guest os in realtime
+ of an arbitrary workload.
+
+ 'perf kvm record <command>' to record the performance counter profile
+ of an arbitrary workload and save it into a perf data file. We set the
+ default behavior of perf kvm as --guest, so if neither --host nor --guest
+ is input, the perf data file name is perf.data.guest. If --host is input,
+ the perf data file name is perf.data.kvm. If you want to record data into
+ perf.data.host, please input --host --no-guest. The behaviors are shown as
+ following:
+ Default('') -> perf.data.guest
+ --host -> perf.data.kvm
+ --guest -> perf.data.guest
+ --host --guest -> perf.data.kvm
+ --host --no-guest -> perf.data.host
+
+ 'perf kvm report' to display the performance counter profile information
+ recorded via perf kvm record.
+
+ 'perf kvm diff' to displays the performance difference amongst two perf.data
+ files captured via perf record.
+
+ 'perf kvm buildid-list' to display the buildids found in a perf data file,
+ so that other tools can be used to fetch packages with matching symbol tables
+ for use by perf report. As buildid is read from /sys/kernel/notes in os, then
+ if you want to list the buildid for guest, please make sure your perf data file
+ was captured with --guestmount in perf kvm record.
+
+ 'perf kvm stat <command>' to run a command and gather performance counter
+ statistics.
+ Especially, perf 'kvm stat record/report' generates a statistical analysis
+ of KVM events. Currently, vmexit, mmio and ioport events are supported.
+ 'perf kvm stat record <command>' records kvm events and the events between
+ start and end <command>.
+ And this command produces a file which contains tracing results of kvm
+ events.
+
+ 'perf kvm stat report' reports statistical data which includes events
+ handled time, samples, and so on.
+
+ 'perf kvm stat live' reports statistical data in a live mode (similar to
+ record + report but with statistical data updated live at a given display
+ rate).
+
+OPTIONS
+-------
+-i::
+--input=<path>::
+ Input file name.
+-o::
+--output=<path>::
+ Output file name.
+--host::
+ Collect host side performance profile.
+--guest::
+ Collect guest side performance profile.
+--guestmount=<path>::
+ Guest os root file system mount directory. Users mounts guest os
+ root directories under <path> by a specific filesystem access method,
+ typically, sshfs. For example, start 2 guest os. The one's pid is 8888
+ and the other's is 9999.
+ #mkdir ~/guestmount; cd ~/guestmount
+ #sshfs -o allow_other,direct_io -p 5551 localhost:/ 8888/
+ #sshfs -o allow_other,direct_io -p 5552 localhost:/ 9999/
+ #perf kvm --host --guest --guestmount=~/guestmount top
+--guestkallsyms=<path>::
+ Guest os /proc/kallsyms file copy. 'perf' kvm' reads it to get guest
+ kernel symbols. Users copy it out from guest os.
+--guestmodules=<path>::
+ Guest os /proc/modules file copy. 'perf' kvm' reads it to get guest
+ kernel module information. Users copy it out from guest os.
+--guestvmlinux=<path>::
+ Guest os kernel vmlinux.
+-v::
+--verbose::
+ Be more verbose (show counter open errors, etc).
+
+STAT REPORT OPTIONS
+-------------------
+--vcpu=<value>::
+ analyze events which occures on this vcpu. (default: all vcpus)
+
+--event=<value>::
+ event to be analyzed. Possible values: vmexit, mmio, ioport.
+ (default: vmexit)
+-k::
+--key=<value>::
+ Sorting key. Possible values: sample (default, sort by samples
+ number), time (sort by average time).
+-p::
+--pid=::
+ Analyze events only for given process ID(s) (comma separated list).
+
+STAT LIVE OPTIONS
+-----------------
+-d::
+--display::
+ Time in seconds between display updates
+
+-m::
+--mmap-pages=::
+ Number of mmap data pages (must be a power of two) or size
+ specification with appended unit character - B/K/M/G. The
+ size is rounded up to have nearest pages power of two value.
+
+-a::
+--all-cpus::
+ System-wide collection from all CPUs.
+
+-p::
+--pid=::
+ Analyze events only for given process ID(s) (comma separated list).
+
+--vcpu=<value>::
+ analyze events which occures on this vcpu. (default: all vcpus)
+
+
+--event=<value>::
+ event to be analyzed. Possible values: vmexit, mmio, ioport.
+ (default: vmexit)
+
+-k::
+--key=<value>::
+ Sorting key. Possible values: sample (default, sort by samples
+ number), time (sort by average time).
+
+--duration=<value>::
+ Show events other than HLT that take longer than duration usecs.
+
+SEE ALSO
+--------
+linkperf:perf-top[1], linkperf:perf-record[1], linkperf:perf-report[1],
+linkperf:perf-diff[1], linkperf:perf-buildid-list[1],
+linkperf:perf-stat[1]
diff --git a/tools/perf/Documentation/perf-list.txt b/tools/perf/Documentation/perf-list.txt
new file mode 100644
index 00000000000..6fce6a62220
--- /dev/null
+++ b/tools/perf/Documentation/perf-list.txt
@@ -0,0 +1,122 @@
+perf-list(1)
+============
+
+NAME
+----
+perf-list - List all symbolic event types
+
+SYNOPSIS
+--------
+[verse]
+'perf list' [hw|sw|cache|tracepoint|pmu|event_glob]
+
+DESCRIPTION
+-----------
+This command displays the symbolic event types which can be selected in the
+various perf commands with the -e option.
+
+[[EVENT_MODIFIERS]]
+EVENT MODIFIERS
+---------------
+
+Events can optionally have a modifer by appending a colon and one or
+more modifiers. Modifiers allow the user to restrict the events to be
+counted. The following modifiers exist:
+
+ u - user-space counting
+ k - kernel counting
+ h - hypervisor counting
+ G - guest counting (in KVM guests)
+ H - host counting (not in KVM guests)
+ p - precise level
+ S - read sample value (PERF_SAMPLE_READ)
+ D - pin the event to the PMU
+
+The 'p' modifier can be used for specifying how precise the instruction
+address should be. The 'p' modifier can be specified multiple times:
+
+ 0 - SAMPLE_IP can have arbitrary skid
+ 1 - SAMPLE_IP must have constant skid
+ 2 - SAMPLE_IP requested to have 0 skid
+ 3 - SAMPLE_IP must have 0 skid
+
+For Intel systems precise event sampling is implemented with PEBS
+which supports up to precise-level 2.
+
+On AMD systems it is implemented using IBS (up to precise-level 2).
+The precise modifier works with event types 0x76 (cpu-cycles, CPU
+clocks not halted) and 0xC1 (micro-ops retired). Both events map to
+IBS execution sampling (IBS op) with the IBS Op Counter Control bit
+(IbsOpCntCtl) set respectively (see AMD64 Architecture Programmer’s
+Manual Volume 2: System Programming, 13.3 Instruction-Based
+Sampling). Examples to use IBS:
+
+ perf record -a -e cpu-cycles:p ... # use ibs op counting cycles
+ perf record -a -e r076:p ... # same as -e cpu-cycles:p
+ perf record -a -e r0C1:p ... # use ibs op counting micro-ops
+
+RAW HARDWARE EVENT DESCRIPTOR
+-----------------------------
+Even when an event is not available in a symbolic form within perf right now,
+it can be encoded in a per processor specific way.
+
+For instance For x86 CPUs NNN represents the raw register encoding with the
+layout of IA32_PERFEVTSELx MSRs (see [Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 3B: System Programming Guide] Figure 30-1 Layout
+of IA32_PERFEVTSELx MSRs) or AMD's PerfEvtSeln (see [AMD64 Architecture Programmer’s Manual Volume 2: System Programming], Page 344,
+Figure 13-7 Performance Event-Select Register (PerfEvtSeln)).
+
+Note: Only the following bit fields can be set in x86 counter
+registers: event, umask, edge, inv, cmask. Esp. guest/host only and
+OS/user mode flags must be setup using <<EVENT_MODIFIERS, EVENT
+MODIFIERS>>.
+
+Example:
+
+If the Intel docs for a QM720 Core i7 describe an event as:
+
+ Event Umask Event Mask
+ Num. Value Mnemonic Description Comment
+
+ A8H 01H LSD.UOPS Counts the number of micro-ops Use cmask=1 and
+ delivered by loop stream detector invert to count
+ cycles
+
+raw encoding of 0x1A8 can be used:
+
+ perf stat -e r1a8 -a sleep 1
+ perf record -e r1a8 ...
+
+You should refer to the processor specific documentation for getting these
+details. Some of them are referenced in the SEE ALSO section below.
+
+OPTIONS
+-------
+
+Without options all known events will be listed.
+
+To limit the list use:
+
+. 'hw' or 'hardware' to list hardware events such as cache-misses, etc.
+
+. 'sw' or 'software' to list software events such as context switches, etc.
+
+. 'cache' or 'hwcache' to list hardware cache events such as L1-dcache-loads, etc.
+
+. 'tracepoint' to list all tracepoint events, alternatively use
+ 'subsys_glob:event_glob' to filter by tracepoint subsystems such as sched,
+ block, etc.
+
+. 'pmu' to print the kernel supplied PMU events.
+
+. If none of the above is matched, it will apply the supplied glob to all
+ events, printing the ones that match.
+
+One or more types can be used at the same time, listing the events for the
+types specified.
+
+SEE ALSO
+--------
+linkperf:perf-stat[1], linkperf:perf-top[1],
+linkperf:perf-record[1],
+http://www.intel.com/Assets/PDF/manual/253669.pdf[Intel® 64 and IA-32 Architectures Software Developer's Manual Volume 3B: System Programming Guide],
+http://support.amd.com/us/Processor_TechDocs/24593_APM_v2.pdf[AMD64 Architecture Programmer’s Manual Volume 2: System Programming]
diff --git a/tools/perf/Documentation/perf-lock.txt b/tools/perf/Documentation/perf-lock.txt
new file mode 100644
index 00000000000..ab25be28c9d
--- /dev/null
+++ b/tools/perf/Documentation/perf-lock.txt
@@ -0,0 +1,66 @@
+perf-lock(1)
+============
+
+NAME
+----
+perf-lock - Analyze lock events
+
+SYNOPSIS
+--------
+[verse]
+'perf lock' {record|report|script|info}
+
+DESCRIPTION
+-----------
+You can analyze various lock behaviours
+and statistics with this 'perf lock' command.
+
+ 'perf lock record <command>' records lock events
+ between start and end <command>. And this command
+ produces the file "perf.data" which contains tracing
+ results of lock events.
+
+ 'perf lock report' reports statistical data.
+
+ 'perf lock script' shows raw lock events.
+
+ 'perf lock info' shows metadata like threads or addresses
+ of lock instances.
+
+COMMON OPTIONS
+--------------
+
+-i::
+--input=<file>::
+ Input file name. (default: perf.data unless stdin is a fifo)
+
+-v::
+--verbose::
+ Be more verbose (show symbol address, etc).
+
+-D::
+--dump-raw-trace::
+ Dump raw trace in ASCII.
+
+REPORT OPTIONS
+--------------
+
+-k::
+--key=<value>::
+ Sorting key. Possible values: acquired (default), contended,
+ avg_wait, wait_total, wait_max, wait_min.
+
+INFO OPTIONS
+------------
+
+-t::
+--threads::
+ dump thread list in perf.data
+
+-m::
+--map::
+ dump map of lock instances (address:name table)
+
+SEE ALSO
+--------
+linkperf:perf[1]
diff --git a/tools/perf/Documentation/perf-mem.txt b/tools/perf/Documentation/perf-mem.txt
new file mode 100644
index 00000000000..1d78a4064da
--- /dev/null
+++ b/tools/perf/Documentation/perf-mem.txt
@@ -0,0 +1,52 @@
+perf-mem(1)
+===========
+
+NAME
+----
+perf-mem - Profile memory accesses
+
+SYNOPSIS
+--------
+[verse]
+'perf mem' [<options>] (record [<command>] | report)
+
+DESCRIPTION
+-----------
+"perf mem -t <TYPE> record" runs a command and gathers memory operation data
+from it, into perf.data. Perf record options are accepted and are passed through.
+
+"perf mem -t <TYPE> report" displays the result. It invokes perf report with the
+right set of options to display a memory access profile.
+
+Note that on Intel systems the memory latency reported is the use-latency,
+not the pure load (or store latency). Use latency includes any pipeline
+queueing delays in addition to the memory subsystem latency.
+
+OPTIONS
+-------
+<command>...::
+ Any command you can specify in a shell.
+
+-t::
+--type=::
+ Select the memory operation type: load or store (default: load)
+
+-D::
+--dump-raw-samples=::
+ Dump the raw decoded samples on the screen in a format that is easy to parse with
+ one sample per line.
+
+-x::
+--field-separator::
+ Specify the field separator used when dump raw samples (-D option). By default,
+ The separator is the space character.
+
+-C::
+--cpu-list::
+ Restrict dump of raw samples to those provided via this option. Note that the same
+ option can be passed in record mode. It will be interpreted the same way as perf
+ record.
+
+SEE ALSO
+--------
+linkperf:perf-record[1], linkperf:perf-report[1]
diff --git a/tools/perf/Documentation/perf-probe.txt b/tools/perf/Documentation/perf-probe.txt
new file mode 100644
index 00000000000..1513935c399
--- /dev/null
+++ b/tools/perf/Documentation/perf-probe.txt
@@ -0,0 +1,207 @@
+perf-probe(1)
+=============
+
+NAME
+----
+perf-probe - Define new dynamic tracepoints
+
+SYNOPSIS
+--------
+[verse]
+'perf probe' [options] --add='PROBE' [...]
+or
+'perf probe' [options] PROBE
+or
+'perf probe' [options] --del='[GROUP:]EVENT' [...]
+or
+'perf probe' --list
+or
+'perf probe' [options] --line='LINE'
+or
+'perf probe' [options] --vars='PROBEPOINT'
+
+DESCRIPTION
+-----------
+This command defines dynamic tracepoint events, by symbol and registers
+without debuginfo, or by C expressions (C line numbers, C function names,
+and C local variables) with debuginfo.
+
+
+OPTIONS
+-------
+-k::
+--vmlinux=PATH::
+ Specify vmlinux path which has debuginfo (Dwarf binary).
+
+-m::
+--module=MODNAME|PATH::
+ Specify module name in which perf-probe searches probe points
+ or lines. If a path of module file is passed, perf-probe
+ treat it as an offline module (this means you can add a probe on
+ a module which has not been loaded yet).
+
+-s::
+--source=PATH::
+ Specify path to kernel source.
+
+-v::
+--verbose::
+ Be more verbose (show parsed arguments, etc).
+
+-a::
+--add=::
+ Define a probe event (see PROBE SYNTAX for detail).
+
+-d::
+--del=::
+ Delete probe events. This accepts glob wildcards('*', '?') and character
+ classes(e.g. [a-z], [!A-Z]).
+
+-l::
+--list::
+ List up current probe events.
+
+-L::
+--line=::
+ Show source code lines which can be probed. This needs an argument
+ which specifies a range of the source code. (see LINE SYNTAX for detail)
+
+-V::
+--vars=::
+ Show available local variables at given probe point. The argument
+ syntax is same as PROBE SYNTAX, but NO ARGs.
+
+--externs::
+ (Only for --vars) Show external defined variables in addition to local
+ variables.
+
+-F::
+--funcs::
+ Show available functions in given module or kernel. With -x/--exec,
+ can also list functions in a user space executable / shared library.
+
+--filter=FILTER::
+ (Only for --vars and --funcs) Set filter. FILTER is a combination of glob
+ pattern, see FILTER PATTERN for detail.
+ Default FILTER is "!__k???tab_* & !__crc_*" for --vars, and "!_*"
+ for --funcs.
+ If several filters are specified, only the last filter is used.
+
+-f::
+--force::
+ Forcibly add events with existing name.
+
+-n::
+--dry-run::
+ Dry run. With this option, --add and --del doesn't execute actual
+ adding and removal operations.
+
+--max-probes::
+ Set the maximum number of probe points for an event. Default is 128.
+
+-x::
+--exec=PATH::
+ Specify path to the executable or shared library file for user
+ space tracing. Can also be used with --funcs option.
+
+In absence of -m/-x options, perf probe checks if the first argument after
+the options is an absolute path name. If its an absolute path, perf probe
+uses it as a target module/target user space binary to probe.
+
+PROBE SYNTAX
+------------
+Probe points are defined by following syntax.
+
+ 1) Define event based on function name
+ [EVENT=]FUNC[@SRC][:RLN|+OFFS|%return|;PTN] [ARG ...]
+
+ 2) Define event based on source file with line number
+ [EVENT=]SRC:ALN [ARG ...]
+
+ 3) Define event based on source file with lazy pattern
+ [EVENT=]SRC;PTN [ARG ...]
+
+
+'EVENT' specifies the name of new event, if omitted, it will be set the name of the probed function. Currently, event group name is set as 'probe'.
+'FUNC' specifies a probed function name, and it may have one of the following options; '+OFFS' is the offset from function entry address in bytes, ':RLN' is the relative-line number from function entry line, and '%return' means that it probes function return. And ';PTN' means lazy matching pattern (see LAZY MATCHING). Note that ';PTN' must be the end of the probe point definition. In addition, '@SRC' specifies a source file which has that function.
+It is also possible to specify a probe point by the source line number or lazy matching by using 'SRC:ALN' or 'SRC;PTN' syntax, where 'SRC' is the source file path, ':ALN' is the line number and ';PTN' is the lazy matching pattern.
+'ARG' specifies the arguments of this probe point, (see PROBE ARGUMENT).
+
+PROBE ARGUMENT
+--------------
+Each probe argument follows below syntax.
+
+ [NAME=]LOCALVAR|$retval|%REG|@SYMBOL[:TYPE]
+
+'NAME' specifies the name of this argument (optional). You can use the name of local variable, local data structure member (e.g. var->field, var.field2), local array with fixed index (e.g. array[1], var->array[0], var->pointer[2]), or kprobe-tracer argument format (e.g. $retval, %ax, etc). Note that the name of this argument will be set as the last member name if you specify a local data structure member (e.g. field2 for 'var->field1.field2'.)
+'TYPE' casts the type of this argument (optional). If omitted, perf probe automatically set the type based on debuginfo. You can specify 'string' type only for the local variable or structure member which is an array of or a pointer to 'char' or 'unsigned char' type.
+
+On x86 systems %REG is always the short form of the register: for example %AX. %RAX or %EAX is not valid.
+
+LINE SYNTAX
+-----------
+Line range is described by following syntax.
+
+ "FUNC[@SRC][:RLN[+NUM|-RLN2]]|SRC[:ALN[+NUM|-ALN2]]"
+
+FUNC specifies the function name of showing lines. 'RLN' is the start line
+number from function entry line, and 'RLN2' is the end line number. As same as
+probe syntax, 'SRC' means the source file path, 'ALN' is start line number,
+and 'ALN2' is end line number in the file. It is also possible to specify how
+many lines to show by using 'NUM'. Moreover, 'FUNC@SRC' combination is good
+for searching a specific function when several functions share same name.
+So, "source.c:100-120" shows lines between 100th to l20th in source.c file. And "func:10+20" shows 20 lines from 10th line of func function.
+
+LAZY MATCHING
+-------------
+ The lazy line matching is similar to glob matching but ignoring spaces in both of pattern and target. So this accepts wildcards('*', '?') and character classes(e.g. [a-z], [!A-Z]).
+
+e.g.
+ 'a=*' can matches 'a=b', 'a = b', 'a == b' and so on.
+
+This provides some sort of flexibility and robustness to probe point definitions against minor code changes. For example, actual 10th line of schedule() can be moved easily by modifying schedule(), but the same line matching 'rq=cpu_rq*' may still exist in the function.)
+
+FILTER PATTERN
+--------------
+ The filter pattern is a glob matching pattern(s) to filter variables.
+ In addition, you can use "!" for specifying filter-out rule. You also can give several rules combined with "&" or "|", and fold those rules as one rule by using "(" ")".
+
+e.g.
+ With --filter "foo* | bar*", perf probe -V shows variables which start with "foo" or "bar".
+ With --filter "!foo* & *bar", perf probe -V shows variables which don't start with "foo" and end with "bar", like "fizzbar". But "foobar" is filtered out.
+
+EXAMPLES
+--------
+Display which lines in schedule() can be probed:
+
+ ./perf probe --line schedule
+
+Add a probe on schedule() function 12th line with recording cpu local variable:
+
+ ./perf probe schedule:12 cpu
+ or
+ ./perf probe --add='schedule:12 cpu'
+
+ this will add one or more probes which has the name start with "schedule".
+
+ Add probes on lines in schedule() function which calls update_rq_clock().
+
+ ./perf probe 'schedule;update_rq_clock*'
+ or
+ ./perf probe --add='schedule;update_rq_clock*'
+
+Delete all probes on schedule().
+
+ ./perf probe --del='schedule*'
+
+Add probes at zfree() function on /bin/zsh
+
+ ./perf probe -x /bin/zsh zfree or ./perf probe /bin/zsh zfree
+
+Add probes at malloc() function on libc
+
+ ./perf probe -x /lib/libc.so.6 malloc or ./perf probe /lib/libc.so.6 malloc
+
+SEE ALSO
+--------
+linkperf:perf-trace[1], linkperf:perf-record[1]
diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt
new file mode 100644
index 00000000000..d460049cae8
--- /dev/null
+++ b/tools/perf/Documentation/perf-record.txt
@@ -0,0 +1,219 @@
+perf-record(1)
+==============
+
+NAME
+----
+perf-record - Run a command and record its profile into perf.data
+
+SYNOPSIS
+--------
+[verse]
+'perf record' [-e <EVENT> | --event=EVENT] [-l] [-a] <command>
+'perf record' [-e <EVENT> | --event=EVENT] [-l] [-a] -- <command> [<options>]
+
+DESCRIPTION
+-----------
+This command runs a command and gathers a performance counter profile
+from it, into perf.data - without displaying anything.
+
+This file can then be inspected later on, using 'perf report'.
+
+
+OPTIONS
+-------
+<command>...::
+ Any command you can specify in a shell.
+
+-e::
+--event=::
+ Select the PMU event. Selection can be:
+
+ - a symbolic event name (use 'perf list' to list all events)
+
+ - a raw PMU event (eventsel+umask) in the form of rNNN where NNN is a
+ hexadecimal event descriptor.
+
+ - a hardware breakpoint event in the form of '\mem:addr[:access]'
+ where addr is the address in memory you want to break in.
+ Access is the memory access type (read, write, execute) it can
+ be passed as follows: '\mem:addr[:[r][w][x]]'.
+ If you want to profile read-write accesses in 0x1000, just set
+ 'mem:0x1000:rw'.
+
+--filter=<filter>::
+ Event filter.
+
+-a::
+--all-cpus::
+ System-wide collection from all CPUs.
+
+-l::
+ Scale counter values.
+
+-p::
+--pid=::
+ Record events on existing process ID (comma separated list).
+
+-t::
+--tid=::
+ Record events on existing thread ID (comma separated list).
+ This option also disables inheritance by default. Enable it by adding
+ --inherit.
+
+-u::
+--uid=::
+ Record events in threads owned by uid. Name or number.
+
+-r::
+--realtime=::
+ Collect data with this RT SCHED_FIFO priority.
+
+--no-buffering::
+ Collect data without buffering.
+
+-c::
+--count=::
+ Event period to sample.
+
+-o::
+--output=::
+ Output file name.
+
+-i::
+--no-inherit::
+ Child tasks do not inherit counters.
+-F::
+--freq=::
+ Profile at this frequency.
+
+-m::
+--mmap-pages=::
+ Number of mmap data pages (must be a power of two) or size
+ specification with appended unit character - B/K/M/G. The
+ size is rounded up to have nearest pages power of two value.
+
+-g::
+ Enables call-graph (stack chain/backtrace) recording.
+
+--call-graph::
+ Setup and enable call-graph (stack chain/backtrace) recording,
+ implies -g.
+
+ Allows specifying "fp" (frame pointer) or "dwarf"
+ (DWARF's CFI - Call Frame Information) as the method to collect
+ the information used to show the call graphs.
+
+ In some systems, where binaries are build with gcc
+ --fomit-frame-pointer, using the "fp" method will produce bogus
+ call graphs, using "dwarf", if available (perf tools linked to
+ the libunwind library) should be used instead.
+
+-q::
+--quiet::
+ Don't print any message, useful for scripting.
+
+-v::
+--verbose::
+ Be more verbose (show counter open errors, etc).
+
+-s::
+--stat::
+ Per thread counts.
+
+-d::
+--data::
+ Sample addresses.
+
+-T::
+--timestamp::
+ Sample timestamps. Use it with 'perf report -D' to see the timestamps,
+ for instance.
+
+-n::
+--no-samples::
+ Don't sample.
+
+-R::
+--raw-samples::
+Collect raw sample records from all opened counters (default for tracepoint counters).
+
+-C::
+--cpu::
+Collect samples only on the list of CPUs provided. Multiple CPUs can be provided as a
+comma-separated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2.
+In per-thread mode with inheritance mode on (default), samples are captured only when
+the thread executes on the designated CPUs. Default is to monitor all CPUs.
+
+-N::
+--no-buildid-cache::
+Do not update the builid cache. This saves some overhead in situations
+where the information in the perf.data file (which includes buildids)
+is sufficient.
+
+-G name,...::
+--cgroup name,...::
+monitor only in the container (cgroup) called "name". This option is available only
+in per-cpu mode. The cgroup filesystem must be mounted. All threads belonging to
+container "name" are monitored when they run on the monitored CPUs. Multiple cgroups
+can be provided. Each cgroup is applied to the corresponding event, i.e., first cgroup
+to first event, second cgroup to second event and so on. It is possible to provide
+an empty cgroup (monitor all the time) using, e.g., -G foo,,bar. Cgroups must have
+corresponding events, i.e., they always refer to events defined earlier on the command
+line.
+
+-b::
+--branch-any::
+Enable taken branch stack sampling. Any type of taken branch may be sampled.
+This is a shortcut for --branch-filter any. See --branch-filter for more infos.
+
+-j::
+--branch-filter::
+Enable taken branch stack sampling. Each sample captures a series of consecutive
+taken branches. The number of branches captured with each sample depends on the
+underlying hardware, the type of branches of interest, and the executed code.
+It is possible to select the types of branches captured by enabling filters. The
+following filters are defined:
+
+ - any: any type of branches
+ - any_call: any function call or system call
+ - any_ret: any function return or system call return
+ - ind_call: any indirect branch
+ - u: only when the branch target is at the user level
+ - k: only when the branch target is in the kernel
+ - hv: only when the target is at the hypervisor level
+ - in_tx: only when the target is in a hardware transaction
+ - no_tx: only when the target is not in a hardware transaction
+ - abort_tx: only when the target is a hardware transaction abort
+ - cond: conditional branches
+
++
+The option requires at least one branch type among any, any_call, any_ret, ind_call, cond.
+The privilege levels may be omitted, in which case, the privilege levels of the associated
+event are applied to the branch filter. Both kernel (k) and hypervisor (hv) privilege
+levels are subject to permissions. When sampling on multiple events, branch stack sampling
+is enabled for all the sampling events. The sampled branch type is the same for all events.
+The various filters must be specified as a comma separated list: --branch-filter any_ret,u,k
+Note that this feature may not be available on all processors.
+
+--weight::
+Enable weightened sampling. An additional weight is recorded per sample and can be
+displayed with the weight and local_weight sort keys. This currently works for TSX
+abort events and some memory events in precise mode on modern Intel CPUs.
+
+--transaction::
+Record transaction flags for transaction related events.
+
+--per-thread::
+Use per-thread mmaps. By default per-cpu mmaps are created. This option
+overrides that and uses per-thread mmaps. A side-effect of that is that
+inheritance is automatically disabled. --per-thread is ignored with a warning
+if combined with -a or -C options.
+
+-D::
+--delay=::
+After starting the program, wait msecs before measuring. This is useful to
+filter out the startup phase of the program, which is often very different.
+
+SEE ALSO
+--------
+linkperf:perf-stat[1], linkperf:perf-list[1]
diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt
new file mode 100644
index 00000000000..d2b59af62bc
--- /dev/null
+++ b/tools/perf/Documentation/perf-report.txt
@@ -0,0 +1,310 @@
+perf-report(1)
+==============
+
+NAME
+----
+perf-report - Read perf.data (created by perf record) and display the profile
+
+SYNOPSIS
+--------
+[verse]
+'perf report' [-i <file> | --input=file]
+
+DESCRIPTION
+-----------
+This command displays the performance counter profile information recorded
+via perf record.
+
+OPTIONS
+-------
+-i::
+--input=::
+ Input file name. (default: perf.data unless stdin is a fifo)
+
+-v::
+--verbose::
+ Be more verbose. (show symbol address, etc)
+
+-n::
+--show-nr-samples::
+ Show the number of samples for each symbol
+
+--showcpuutilization::
+ Show sample percentage for different cpu modes.
+
+-T::
+--threads::
+ Show per-thread event counters
+-c::
+--comms=::
+ Only consider symbols in these comms. CSV that understands
+ file://filename entries. This option will affect the percentage of
+ the overhead column. See --percentage for more info.
+-d::
+--dsos=::
+ Only consider symbols in these dsos. CSV that understands
+ file://filename entries. This option will affect the percentage of
+ the overhead column. See --percentage for more info.
+-S::
+--symbols=::
+ Only consider these symbols. CSV that understands
+ file://filename entries. This option will affect the percentage of
+ the overhead column. See --percentage for more info.
+
+--symbol-filter=::
+ Only show symbols that match (partially) with this filter.
+
+-U::
+--hide-unresolved::
+ Only display entries resolved to a symbol.
+
+-s::
+--sort=::
+ Sort histogram entries by given key(s) - multiple keys can be specified
+ in CSV format. Following sort keys are available:
+ pid, comm, dso, symbol, parent, cpu, srcline, weight, local_weight.
+
+ Each key has following meaning:
+
+ - comm: command (name) of the task which can be read via /proc/<pid>/comm
+ - pid: command and tid of the task
+ - dso: name of library or module executed at the time of sample
+ - symbol: name of function executed at the time of sample
+ - parent: name of function matched to the parent regex filter. Unmatched
+ entries are displayed as "[other]".
+ - cpu: cpu number the task ran at the time of sample
+ - srcline: filename and line number executed at the time of sample. The
+ DWARF debugging info must be provided.
+ - weight: Event specific weight, e.g. memory latency or transaction
+ abort cost. This is the global weight.
+ - local_weight: Local weight version of the weight above.
+ - transaction: Transaction abort flags.
+ - overhead: Overhead percentage of sample
+ - overhead_sys: Overhead percentage of sample running in system mode
+ - overhead_us: Overhead percentage of sample running in user mode
+ - overhead_guest_sys: Overhead percentage of sample running in system mode
+ on guest machine
+ - overhead_guest_us: Overhead percentage of sample running in user mode on
+ guest machine
+ - sample: Number of sample
+ - period: Raw number of event count of sample
+
+ By default, comm, dso and symbol keys are used.
+ (i.e. --sort comm,dso,symbol)
+
+ If --branch-stack option is used, following sort keys are also
+ available:
+ dso_from, dso_to, symbol_from, symbol_to, mispredict.
+
+ - dso_from: name of library or module branched from
+ - dso_to: name of library or module branched to
+ - symbol_from: name of function branched from
+ - symbol_to: name of function branched to
+ - mispredict: "N" for predicted branch, "Y" for mispredicted branch
+ - in_tx: branch in TSX transaction
+ - abort: TSX transaction abort.
+
+ And default sort keys are changed to comm, dso_from, symbol_from, dso_to
+ and symbol_to, see '--branch-stack'.
+
+-F::
+--fields=::
+ Specify output field - multiple keys can be specified in CSV format.
+ Following fields are available:
+ overhead, overhead_sys, overhead_us, overhead_children, sample and period.
+ Also it can contain any sort key(s).
+
+ By default, every sort keys not specified in -F will be appended
+ automatically.
+
+ If --mem-mode option is used, following sort keys are also available
+ (incompatible with --branch-stack):
+ symbol_daddr, dso_daddr, locked, tlb, mem, snoop, dcacheline.
+
+ - symbol_daddr: name of data symbol being executed on at the time of sample
+ - dso_daddr: name of library or module containing the data being executed
+ on at the time of sample
+ - locked: whether the bus was locked at the time of sample
+ - tlb: type of tlb access for the data at the time of sample
+ - mem: type of memory access for the data at the time of sample
+ - snoop: type of snoop (if any) for the data at the time of sample
+ - dcacheline: the cacheline the data address is on at the time of sample
+
+ And default sort keys are changed to local_weight, mem, sym, dso,
+ symbol_daddr, dso_daddr, snoop, tlb, locked, see '--mem-mode'.
+
+-p::
+--parent=<regex>::
+ A regex filter to identify parent. The parent is a caller of this
+ function and searched through the callchain, thus it requires callchain
+ information recorded. The pattern is in the exteneded regex format and
+ defaults to "\^sys_|^do_page_fault", see '--sort parent'.
+
+-x::
+--exclude-other::
+ Only display entries with parent-match.
+
+-w::
+--column-widths=<width[,width...]>::
+ Force each column width to the provided list, for large terminal
+ readability.
+
+-t::
+--field-separator=::
+ Use a special separator character and don't pad with spaces, replacing
+ all occurrences of this separator in symbol names (and other output)
+ with a '.' character, that thus it's the only non valid separator.
+
+-D::
+--dump-raw-trace::
+ Dump raw trace in ASCII.
+
+-g [type,min[,limit],order[,key]]::
+--call-graph::
+ Display call chains using type, min percent threshold, optional print
+ limit and order.
+ type can be either:
+ - flat: single column, linear exposure of call chains.
+ - graph: use a graph tree, displaying absolute overhead rates.
+ - fractal: like graph, but displays relative rates. Each branch of
+ the tree is considered as a new profiled object. +
+
+ order can be either:
+ - callee: callee based call graph.
+ - caller: inverted caller based call graph.
+
+ key can be:
+ - function: compare on functions
+ - address: compare on individual code addresses
+
+ Default: fractal,0.5,callee,function.
+
+--children::
+ Accumulate callchain of children to parent entry so that then can
+ show up in the output. The output will have a new "Children" column
+ and will be sorted on the data. It requires callchains are recorded.
+
+--max-stack::
+ Set the stack depth limit when parsing the callchain, anything
+ beyond the specified depth will be ignored. This is a trade-off
+ between information loss and faster processing especially for
+ workloads that can have a very long callchain stack.
+
+ Default: 127
+
+-G::
+--inverted::
+ alias for inverted caller based call graph.
+
+--ignore-callees=<regex>::
+ Ignore callees of the function(s) matching the given regex.
+ This has the effect of collecting the callers of each such
+ function into one place in the call-graph tree.
+
+--pretty=<key>::
+ Pretty printing style. key: normal, raw
+
+--stdio:: Use the stdio interface.
+
+--tui:: Use the TUI interface, that is integrated with annotate and allows
+ zooming into DSOs or threads, among other features. Use of --tui
+ requires a tty, if one is not present, as when piping to other
+ commands, the stdio interface is used.
+
+--gtk:: Use the GTK2 interface.
+
+-k::
+--vmlinux=<file>::
+ vmlinux pathname
+
+--kallsyms=<file>::
+ kallsyms pathname
+
+-m::
+--modules::
+ Load module symbols. WARNING: This should only be used with -k and
+ a LIVE kernel.
+
+-f::
+--force::
+ Don't complain, do it.
+
+--symfs=<directory>::
+ Look for files with symbols relative to this directory.
+
+-C::
+--cpu:: Only report samples for the list of CPUs provided. Multiple CPUs can
+ be provided as a comma-separated list with no space: 0,1. Ranges of
+ CPUs are specified with -: 0-2. Default is to report samples on all
+ CPUs.
+
+-M::
+--disassembler-style=:: Set disassembler style for objdump.
+
+--source::
+ Interleave source code with assembly code. Enabled by default,
+ disable with --no-source.
+
+--asm-raw::
+ Show raw instruction encoding of assembly instructions.
+
+--show-total-period:: Show a column with the sum of periods.
+
+-I::
+--show-info::
+ Display extended information about the perf.data file. This adds
+ information which may be very large and thus may clutter the display.
+ It currently includes: cpu and numa topology of the host system.
+
+-b::
+--branch-stack::
+ Use the addresses of sampled taken branches instead of the instruction
+ address to build the histograms. To generate meaningful output, the
+ perf.data file must have been obtained using perf record -b or
+ perf record --branch-filter xxx where xxx is a branch filter option.
+ perf report is able to auto-detect whether a perf.data file contains
+ branch stacks and it will automatically switch to the branch view mode,
+ unless --no-branch-stack is used.
+
+--objdump=<path>::
+ Path to objdump binary.
+
+--group::
+ Show event group information together.
+
+--demangle::
+ Demangle symbol names to human readable form. It's enabled by default,
+ disable with --no-demangle.
+
+--mem-mode::
+ Use the data addresses of samples in addition to instruction addresses
+ to build the histograms. To generate meaningful output, the perf.data
+ file must have been obtained using perf record -d -W and using a
+ special event -e cpu/mem-loads/ or -e cpu/mem-stores/. See
+ 'perf mem' for simpler access.
+
+--percent-limit::
+ Do not show entries which have an overhead under that percent.
+ (Default: 0).
+
+--percentage::
+ Determine how to display the overhead percentage of filtered entries.
+ Filters can be applied by --comms, --dsos and/or --symbols options and
+ Zoom operations on the TUI (thread, dso, etc).
+
+ "relative" means it's relative to filtered entries only so that the
+ sum of shown entries will be always 100%. "absolute" means it retains
+ the original value before and after the filter is applied.
+
+--header::
+ Show header information in the perf.data file. This includes
+ various information like hostname, OS and perf version, cpu/mem
+ info, perf command line, event list and so on. Currently only
+ --stdio output supports this feature.
+
+--header-only::
+ Show only perf.data header (forces --stdio).
+
+SEE ALSO
+--------
+linkperf:perf-stat[1], linkperf:perf-annotate[1]
diff --git a/tools/perf/Documentation/perf-sched.txt b/tools/perf/Documentation/perf-sched.txt
new file mode 100644
index 00000000000..8ff4df95695
--- /dev/null
+++ b/tools/perf/Documentation/perf-sched.txt
@@ -0,0 +1,55 @@
+perf-sched(1)
+==============
+
+NAME
+----
+perf-sched - Tool to trace/measure scheduler properties (latencies)
+
+SYNOPSIS
+--------
+[verse]
+'perf sched' {record|latency|map|replay|script}
+
+DESCRIPTION
+-----------
+There are five variants of perf sched:
+
+ 'perf sched record <command>' to record the scheduling events
+ of an arbitrary workload.
+
+ 'perf sched latency' to report the per task scheduling latencies
+ and other scheduling properties of the workload.
+
+ 'perf sched script' to see a detailed trace of the workload that
+ was recorded (aliased to 'perf script' for now).
+
+ 'perf sched replay' to simulate the workload that was recorded
+ via perf sched record. (this is done by starting up mockup threads
+ that mimic the workload based on the events in the trace. These
+ threads can then replay the timings (CPU runtime and sleep patterns)
+ of the workload as it occurred when it was recorded - and can repeat
+ it a number of times, measuring its performance.)
+
+ 'perf sched map' to print a textual context-switching outline of
+ workload captured via perf sched record. Columns stand for
+ individual CPUs, and the two-letter shortcuts stand for tasks that
+ are running on a CPU. A '*' denotes the CPU that had the event, and
+ a dot signals an idle CPU.
+
+OPTIONS
+-------
+-i::
+--input=<file>::
+ Input file name. (default: perf.data unless stdin is a fifo)
+
+-v::
+--verbose::
+ Be more verbose. (show symbol address, etc)
+
+-D::
+--dump-raw-trace=::
+ Display verbose dump of the sched data.
+
+SEE ALSO
+--------
+linkperf:perf-record[1]
diff --git a/tools/perf/Documentation/perf-script-perl.txt b/tools/perf/Documentation/perf-script-perl.txt
new file mode 100644
index 00000000000..d00bef23134
--- /dev/null
+++ b/tools/perf/Documentation/perf-script-perl.txt
@@ -0,0 +1,216 @@
+perf-script-perl(1)
+==================
+
+NAME
+----
+perf-script-perl - Process trace data with a Perl script
+
+SYNOPSIS
+--------
+[verse]
+'perf script' [-s [Perl]:script[.pl] ]
+
+DESCRIPTION
+-----------
+
+This perf script option is used to process perf script data using perf's
+built-in Perl interpreter. It reads and processes the input file and
+displays the results of the trace analysis implemented in the given
+Perl script, if any.
+
+STARTER SCRIPTS
+---------------
+
+You can avoid reading the rest of this document by running 'perf script
+-g perl' in the same directory as an existing perf.data trace file.
+That will generate a starter script containing a handler for each of
+the event types in the trace file; it simply prints every available
+field for each event in the trace file.
+
+You can also look at the existing scripts in
+~/libexec/perf-core/scripts/perl for typical examples showing how to
+do basic things like aggregate event data, print results, etc. Also,
+the check-perf-script.pl script, while not interesting for its results,
+attempts to exercise all of the main scripting features.
+
+EVENT HANDLERS
+--------------
+
+When perf script is invoked using a trace script, a user-defined
+'handler function' is called for each event in the trace. If there's
+no handler function defined for a given event type, the event is
+ignored (or passed to a 'trace_handled' function, see below) and the
+next event is processed.
+
+Most of the event's field values are passed as arguments to the
+handler function; some of the less common ones aren't - those are
+available as calls back into the perf executable (see below).
+
+As an example, the following perf record command can be used to record
+all sched_wakeup events in the system:
+
+ # perf record -a -e sched:sched_wakeup
+
+Traces meant to be processed using a script should be recorded with
+the above option: -a to enable system-wide collection.
+
+The format file for the sched_wakep event defines the following fields
+(see /sys/kernel/debug/tracing/events/sched/sched_wakeup/format):
+
+----
+ format:
+ field:unsigned short common_type;
+ field:unsigned char common_flags;
+ field:unsigned char common_preempt_count;
+ field:int common_pid;
+
+ field:char comm[TASK_COMM_LEN];
+ field:pid_t pid;
+ field:int prio;
+ field:int success;
+ field:int target_cpu;
+----
+
+The handler function for this event would be defined as:
+
+----
+sub sched::sched_wakeup
+{
+ my ($event_name, $context, $common_cpu, $common_secs,
+ $common_nsecs, $common_pid, $common_comm,
+ $comm, $pid, $prio, $success, $target_cpu) = @_;
+}
+----
+
+The handler function takes the form subsystem::event_name.
+
+The $common_* arguments in the handler's argument list are the set of
+arguments passed to all event handlers; some of the fields correspond
+to the common_* fields in the format file, but some are synthesized,
+and some of the common_* fields aren't common enough to to be passed
+to every event as arguments but are available as library functions.
+
+Here's a brief description of each of the invariant event args:
+
+ $event_name the name of the event as text
+ $context an opaque 'cookie' used in calls back into perf
+ $common_cpu the cpu the event occurred on
+ $common_secs the secs portion of the event timestamp
+ $common_nsecs the nsecs portion of the event timestamp
+ $common_pid the pid of the current task
+ $common_comm the name of the current process
+
+All of the remaining fields in the event's format file have
+counterparts as handler function arguments of the same name, as can be
+seen in the example above.
+
+The above provides the basics needed to directly access every field of
+every event in a trace, which covers 90% of what you need to know to
+write a useful trace script. The sections below cover the rest.
+
+SCRIPT LAYOUT
+-------------
+
+Every perf script Perl script should start by setting up a Perl module
+search path and 'use'ing a few support modules (see module
+descriptions below):
+
+----
+ use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib";
+ use lib "./Perf-Trace-Util/lib";
+ use Perf::Trace::Core;
+ use Perf::Trace::Context;
+ use Perf::Trace::Util;
+----
+
+The rest of the script can contain handler functions and support
+functions in any order.
+
+Aside from the event handler functions discussed above, every script
+can implement a set of optional functions:
+
+*trace_begin*, if defined, is called before any event is processed and
+gives scripts a chance to do setup tasks:
+
+----
+ sub trace_begin
+ {
+ }
+----
+
+*trace_end*, if defined, is called after all events have been
+ processed and gives scripts a chance to do end-of-script tasks, such
+ as display results:
+
+----
+sub trace_end
+{
+}
+----
+
+*trace_unhandled*, if defined, is called after for any event that
+ doesn't have a handler explicitly defined for it. The standard set
+ of common arguments are passed into it:
+
+----
+sub trace_unhandled
+{
+ my ($event_name, $context, $common_cpu, $common_secs,
+ $common_nsecs, $common_pid, $common_comm) = @_;
+}
+----
+
+The remaining sections provide descriptions of each of the available
+built-in perf script Perl modules and their associated functions.
+
+AVAILABLE MODULES AND FUNCTIONS
+-------------------------------
+
+The following sections describe the functions and variables available
+via the various Perf::Trace::* Perl modules. To use the functions and
+variables from the given module, add the corresponding 'use
+Perf::Trace::XXX' line to your perf script script.
+
+Perf::Trace::Core Module
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+These functions provide some essential functions to user scripts.
+
+The *flag_str* and *symbol_str* functions provide human-readable
+strings for flag and symbolic fields. These correspond to the strings
+and values parsed from the 'print fmt' fields of the event format
+files:
+
+ flag_str($event_name, $field_name, $field_value) - returns the string represention corresponding to $field_value for the flag field $field_name of event $event_name
+ symbol_str($event_name, $field_name, $field_value) - returns the string represention corresponding to $field_value for the symbolic field $field_name of event $event_name
+
+Perf::Trace::Context Module
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Some of the 'common' fields in the event format file aren't all that
+common, but need to be made accessible to user scripts nonetheless.
+
+Perf::Trace::Context defines a set of functions that can be used to
+access this data in the context of the current event. Each of these
+functions expects a $context variable, which is the same as the
+$context variable passed into every event handler as the second
+argument.
+
+ common_pc($context) - returns common_preempt count for the current event
+ common_flags($context) - returns common_flags for the current event
+ common_lock_depth($context) - returns common_lock_depth for the current event
+
+Perf::Trace::Util Module
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+Various utility functions for use with perf script:
+
+ nsecs($secs, $nsecs) - returns total nsecs given secs/nsecs pair
+ nsecs_secs($nsecs) - returns whole secs portion given nsecs
+ nsecs_nsecs($nsecs) - returns nsecs remainder given nsecs
+ nsecs_str($nsecs) - returns printable string in the form secs.nsecs
+ avg($total, $n) - returns average given a sum and a total number of values
+
+SEE ALSO
+--------
+linkperf:perf-script[1]
diff --git a/tools/perf/Documentation/perf-script-python.txt b/tools/perf/Documentation/perf-script-python.txt
new file mode 100644
index 00000000000..9f1f054b843
--- /dev/null
+++ b/tools/perf/Documentation/perf-script-python.txt
@@ -0,0 +1,620 @@
+perf-script-python(1)
+====================
+
+NAME
+----
+perf-script-python - Process trace data with a Python script
+
+SYNOPSIS
+--------
+[verse]
+'perf script' [-s [Python]:script[.py] ]
+
+DESCRIPTION
+-----------
+
+This perf script option is used to process perf script data using perf's
+built-in Python interpreter. It reads and processes the input file and
+displays the results of the trace analysis implemented in the given
+Python script, if any.
+
+A QUICK EXAMPLE
+---------------
+
+This section shows the process, start to finish, of creating a working
+Python script that aggregates and extracts useful information from a
+raw perf script stream. You can avoid reading the rest of this
+document if an example is enough for you; the rest of the document
+provides more details on each step and lists the library functions
+available to script writers.
+
+This example actually details the steps that were used to create the
+'syscall-counts' script you see when you list the available perf script
+scripts via 'perf script -l'. As such, this script also shows how to
+integrate your script into the list of general-purpose 'perf script'
+scripts listed by that command.
+
+The syscall-counts script is a simple script, but demonstrates all the
+basic ideas necessary to create a useful script. Here's an example
+of its output (syscall names are not yet supported, they will appear
+as numbers):
+
+----
+syscall events:
+
+event count
+---------------------------------------- -----------
+sys_write 455067
+sys_getdents 4072
+sys_close 3037
+sys_swapoff 1769
+sys_read 923
+sys_sched_setparam 826
+sys_open 331
+sys_newfstat 326
+sys_mmap 217
+sys_munmap 216
+sys_futex 141
+sys_select 102
+sys_poll 84
+sys_setitimer 12
+sys_writev 8
+15 8
+sys_lseek 7
+sys_rt_sigprocmask 6
+sys_wait4 3
+sys_ioctl 3
+sys_set_robust_list 1
+sys_exit 1
+56 1
+sys_access 1
+----
+
+Basically our task is to keep a per-syscall tally that gets updated
+every time a system call occurs in the system. Our script will do
+that, but first we need to record the data that will be processed by
+that script. Theoretically, there are a couple of ways we could do
+that:
+
+- we could enable every event under the tracing/events/syscalls
+ directory, but this is over 600 syscalls, well beyond the number
+ allowable by perf. These individual syscall events will however be
+ useful if we want to later use the guidance we get from the
+ general-purpose scripts to drill down and get more detail about
+ individual syscalls of interest.
+
+- we can enable the sys_enter and/or sys_exit syscalls found under
+ tracing/events/raw_syscalls. These are called for all syscalls; the
+ 'id' field can be used to distinguish between individual syscall
+ numbers.
+
+For this script, we only need to know that a syscall was entered; we
+don't care how it exited, so we'll use 'perf record' to record only
+the sys_enter events:
+
+----
+# perf record -a -e raw_syscalls:sys_enter
+
+^C[ perf record: Woken up 1 times to write data ]
+[ perf record: Captured and wrote 56.545 MB perf.data (~2470503 samples) ]
+----
+
+The options basically say to collect data for every syscall event
+system-wide and multiplex the per-cpu output into a single stream.
+That single stream will be recorded in a file in the current directory
+called perf.data.
+
+Once we have a perf.data file containing our data, we can use the -g
+'perf script' option to generate a Python script that will contain a
+callback handler for each event type found in the perf.data trace
+stream (for more details, see the STARTER SCRIPTS section).
+
+----
+# perf script -g python
+generated Python script: perf-script.py
+
+The output file created also in the current directory is named
+perf-script.py. Here's the file in its entirety:
+
+# perf script event handlers, generated by perf script -g python
+# Licensed under the terms of the GNU GPL License version 2
+
+# The common_* event handler fields are the most useful fields common to
+# all events. They don't necessarily correspond to the 'common_*' fields
+# in the format files. Those fields not available as handler params can
+# be retrieved using Python functions of the form common_*(context).
+# See the perf-script-python Documentation for the list of available functions.
+
+import os
+import sys
+
+sys.path.append(os.environ['PERF_EXEC_PATH'] + \
+ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+
+from perf_trace_context import *
+from Core import *
+
+def trace_begin():
+ print "in trace_begin"
+
+def trace_end():
+ print "in trace_end"
+
+def raw_syscalls__sys_enter(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ id, args):
+ print_header(event_name, common_cpu, common_secs, common_nsecs,
+ common_pid, common_comm)
+
+ print "id=%d, args=%s\n" % \
+ (id, args),
+
+def trace_unhandled(event_name, context, common_cpu, common_secs, common_nsecs,
+ common_pid, common_comm):
+ print_header(event_name, common_cpu, common_secs, common_nsecs,
+ common_pid, common_comm)
+
+def print_header(event_name, cpu, secs, nsecs, pid, comm):
+ print "%-20s %5u %05u.%09u %8u %-20s " % \
+ (event_name, cpu, secs, nsecs, pid, comm),
+----
+
+At the top is a comment block followed by some import statements and a
+path append which every perf script script should include.
+
+Following that are a couple generated functions, trace_begin() and
+trace_end(), which are called at the beginning and the end of the
+script respectively (for more details, see the SCRIPT_LAYOUT section
+below).
+
+Following those are the 'event handler' functions generated one for
+every event in the 'perf record' output. The handler functions take
+the form subsystem__event_name, and contain named parameters, one for
+each field in the event; in this case, there's only one event,
+raw_syscalls__sys_enter(). (see the EVENT HANDLERS section below for
+more info on event handlers).
+
+The final couple of functions are, like the begin and end functions,
+generated for every script. The first, trace_unhandled(), is called
+every time the script finds an event in the perf.data file that
+doesn't correspond to any event handler in the script. This could
+mean either that the record step recorded event types that it wasn't
+really interested in, or the script was run against a trace file that
+doesn't correspond to the script.
+
+The script generated by -g option simply prints a line for each
+event found in the trace stream i.e. it basically just dumps the event
+and its parameter values to stdout. The print_header() function is
+simply a utility function used for that purpose. Let's rename the
+script and run it to see the default output:
+
+----
+# mv perf-script.py syscall-counts.py
+# perf script -s syscall-counts.py
+
+raw_syscalls__sys_enter 1 00840.847582083 7506 perf id=1, args=
+raw_syscalls__sys_enter 1 00840.847595764 7506 perf id=1, args=
+raw_syscalls__sys_enter 1 00840.847620860 7506 perf id=1, args=
+raw_syscalls__sys_enter 1 00840.847710478 6533 npviewer.bin id=78, args=
+raw_syscalls__sys_enter 1 00840.847719204 6533 npviewer.bin id=142, args=
+raw_syscalls__sys_enter 1 00840.847755445 6533 npviewer.bin id=3, args=
+raw_syscalls__sys_enter 1 00840.847775601 6533 npviewer.bin id=3, args=
+raw_syscalls__sys_enter 1 00840.847781820 6533 npviewer.bin id=3, args=
+.
+.
+.
+----
+
+Of course, for this script, we're not interested in printing every
+trace event, but rather aggregating it in a useful way. So we'll get
+rid of everything to do with printing as well as the trace_begin() and
+trace_unhandled() functions, which we won't be using. That leaves us
+with this minimalistic skeleton:
+
+----
+import os
+import sys
+
+sys.path.append(os.environ['PERF_EXEC_PATH'] + \
+ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+
+from perf_trace_context import *
+from Core import *
+
+def trace_end():
+ print "in trace_end"
+
+def raw_syscalls__sys_enter(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ id, args):
+----
+
+In trace_end(), we'll simply print the results, but first we need to
+generate some results to print. To do that we need to have our
+sys_enter() handler do the necessary tallying until all events have
+been counted. A hash table indexed by syscall id is a good way to
+store that information; every time the sys_enter() handler is called,
+we simply increment a count associated with that hash entry indexed by
+that syscall id:
+
+----
+ syscalls = autodict()
+
+ try:
+ syscalls[id] += 1
+ except TypeError:
+ syscalls[id] = 1
+----
+
+The syscalls 'autodict' object is a special kind of Python dictionary
+(implemented in Core.py) that implements Perl's 'autovivifying' hashes
+in Python i.e. with autovivifying hashes, you can assign nested hash
+values without having to go to the trouble of creating intermediate
+levels if they don't exist e.g syscalls[comm][pid][id] = 1 will create
+the intermediate hash levels and finally assign the value 1 to the
+hash entry for 'id' (because the value being assigned isn't a hash
+object itself, the initial value is assigned in the TypeError
+exception. Well, there may be a better way to do this in Python but
+that's what works for now).
+
+Putting that code into the raw_syscalls__sys_enter() handler, we
+effectively end up with a single-level dictionary keyed on syscall id
+and having the counts we've tallied as values.
+
+The print_syscall_totals() function iterates over the entries in the
+dictionary and displays a line for each entry containing the syscall
+name (the dictonary keys contain the syscall ids, which are passed to
+the Util function syscall_name(), which translates the raw syscall
+numbers to the corresponding syscall name strings). The output is
+displayed after all the events in the trace have been processed, by
+calling the print_syscall_totals() function from the trace_end()
+handler called at the end of script processing.
+
+The final script producing the output shown above is shown in its
+entirety below (syscall_name() helper is not yet available, you can
+only deal with id's for now):
+
+----
+import os
+import sys
+
+sys.path.append(os.environ['PERF_EXEC_PATH'] + \
+ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+
+from perf_trace_context import *
+from Core import *
+from Util import *
+
+syscalls = autodict()
+
+def trace_end():
+ print_syscall_totals()
+
+def raw_syscalls__sys_enter(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ id, args):
+ try:
+ syscalls[id] += 1
+ except TypeError:
+ syscalls[id] = 1
+
+def print_syscall_totals():
+ if for_comm is not None:
+ print "\nsyscall events for %s:\n\n" % (for_comm),
+ else:
+ print "\nsyscall events:\n\n",
+
+ print "%-40s %10s\n" % ("event", "count"),
+ print "%-40s %10s\n" % ("----------------------------------------", \
+ "-----------"),
+
+ for id, val in sorted(syscalls.iteritems(), key = lambda(k, v): (v, k), \
+ reverse = True):
+ print "%-40s %10d\n" % (syscall_name(id), val),
+----
+
+The script can be run just as before:
+
+ # perf script -s syscall-counts.py
+
+So those are the essential steps in writing and running a script. The
+process can be generalized to any tracepoint or set of tracepoints
+you're interested in - basically find the tracepoint(s) you're
+interested in by looking at the list of available events shown by
+'perf list' and/or look in /sys/kernel/debug/tracing events for
+detailed event and field info, record the corresponding trace data
+using 'perf record', passing it the list of interesting events,
+generate a skeleton script using 'perf script -g python' and modify the
+code to aggregate and display it for your particular needs.
+
+After you've done that you may end up with a general-purpose script
+that you want to keep around and have available for future use. By
+writing a couple of very simple shell scripts and putting them in the
+right place, you can have your script listed alongside the other
+scripts listed by the 'perf script -l' command e.g.:
+
+----
+root@tropicana:~# perf script -l
+List of available trace scripts:
+ wakeup-latency system-wide min/max/avg wakeup latency
+ rw-by-file <comm> r/w activity for a program, by file
+ rw-by-pid system-wide r/w activity
+----
+
+A nice side effect of doing this is that you also then capture the
+probably lengthy 'perf record' command needed to record the events for
+the script.
+
+To have the script appear as a 'built-in' script, you write two simple
+scripts, one for recording and one for 'reporting'.
+
+The 'record' script is a shell script with the same base name as your
+script, but with -record appended. The shell script should be put
+into the perf/scripts/python/bin directory in the kernel source tree.
+In that script, you write the 'perf record' command-line needed for
+your script:
+
+----
+# cat kernel-source/tools/perf/scripts/python/bin/syscall-counts-record
+
+#!/bin/bash
+perf record -a -e raw_syscalls:sys_enter
+----
+
+The 'report' script is also a shell script with the same base name as
+your script, but with -report appended. It should also be located in
+the perf/scripts/python/bin directory. In that script, you write the
+'perf script -s' command-line needed for running your script:
+
+----
+# cat kernel-source/tools/perf/scripts/python/bin/syscall-counts-report
+
+#!/bin/bash
+# description: system-wide syscall counts
+perf script -s ~/libexec/perf-core/scripts/python/syscall-counts.py
+----
+
+Note that the location of the Python script given in the shell script
+is in the libexec/perf-core/scripts/python directory - this is where
+the script will be copied by 'make install' when you install perf.
+For the installation to install your script there, your script needs
+to be located in the perf/scripts/python directory in the kernel
+source tree:
+
+----
+# ls -al kernel-source/tools/perf/scripts/python
+
+root@tropicana:/home/trz/src/tip# ls -al tools/perf/scripts/python
+total 32
+drwxr-xr-x 4 trz trz 4096 2010-01-26 22:30 .
+drwxr-xr-x 4 trz trz 4096 2010-01-26 22:29 ..
+drwxr-xr-x 2 trz trz 4096 2010-01-26 22:29 bin
+-rw-r--r-- 1 trz trz 2548 2010-01-26 22:29 check-perf-script.py
+drwxr-xr-x 3 trz trz 4096 2010-01-26 22:49 Perf-Trace-Util
+-rw-r--r-- 1 trz trz 1462 2010-01-26 22:30 syscall-counts.py
+----
+
+Once you've done that (don't forget to do a new 'make install',
+otherwise your script won't show up at run-time), 'perf script -l'
+should show a new entry for your script:
+
+----
+root@tropicana:~# perf script -l
+List of available trace scripts:
+ wakeup-latency system-wide min/max/avg wakeup latency
+ rw-by-file <comm> r/w activity for a program, by file
+ rw-by-pid system-wide r/w activity
+ syscall-counts system-wide syscall counts
+----
+
+You can now perform the record step via 'perf script record':
+
+ # perf script record syscall-counts
+
+and display the output using 'perf script report':
+
+ # perf script report syscall-counts
+
+STARTER SCRIPTS
+---------------
+
+You can quickly get started writing a script for a particular set of
+trace data by generating a skeleton script using 'perf script -g
+python' in the same directory as an existing perf.data trace file.
+That will generate a starter script containing a handler for each of
+the event types in the trace file; it simply prints every available
+field for each event in the trace file.
+
+You can also look at the existing scripts in
+~/libexec/perf-core/scripts/python for typical examples showing how to
+do basic things like aggregate event data, print results, etc. Also,
+the check-perf-script.py script, while not interesting for its results,
+attempts to exercise all of the main scripting features.
+
+EVENT HANDLERS
+--------------
+
+When perf script is invoked using a trace script, a user-defined
+'handler function' is called for each event in the trace. If there's
+no handler function defined for a given event type, the event is
+ignored (or passed to a 'trace_handled' function, see below) and the
+next event is processed.
+
+Most of the event's field values are passed as arguments to the
+handler function; some of the less common ones aren't - those are
+available as calls back into the perf executable (see below).
+
+As an example, the following perf record command can be used to record
+all sched_wakeup events in the system:
+
+ # perf record -a -e sched:sched_wakeup
+
+Traces meant to be processed using a script should be recorded with
+the above option: -a to enable system-wide collection.
+
+The format file for the sched_wakep event defines the following fields
+(see /sys/kernel/debug/tracing/events/sched/sched_wakeup/format):
+
+----
+ format:
+ field:unsigned short common_type;
+ field:unsigned char common_flags;
+ field:unsigned char common_preempt_count;
+ field:int common_pid;
+
+ field:char comm[TASK_COMM_LEN];
+ field:pid_t pid;
+ field:int prio;
+ field:int success;
+ field:int target_cpu;
+----
+
+The handler function for this event would be defined as:
+
+----
+def sched__sched_wakeup(event_name, context, common_cpu, common_secs,
+ common_nsecs, common_pid, common_comm,
+ comm, pid, prio, success, target_cpu):
+ pass
+----
+
+The handler function takes the form subsystem__event_name.
+
+The common_* arguments in the handler's argument list are the set of
+arguments passed to all event handlers; some of the fields correspond
+to the common_* fields in the format file, but some are synthesized,
+and some of the common_* fields aren't common enough to to be passed
+to every event as arguments but are available as library functions.
+
+Here's a brief description of each of the invariant event args:
+
+ event_name the name of the event as text
+ context an opaque 'cookie' used in calls back into perf
+ common_cpu the cpu the event occurred on
+ common_secs the secs portion of the event timestamp
+ common_nsecs the nsecs portion of the event timestamp
+ common_pid the pid of the current task
+ common_comm the name of the current process
+
+All of the remaining fields in the event's format file have
+counterparts as handler function arguments of the same name, as can be
+seen in the example above.
+
+The above provides the basics needed to directly access every field of
+every event in a trace, which covers 90% of what you need to know to
+write a useful trace script. The sections below cover the rest.
+
+SCRIPT LAYOUT
+-------------
+
+Every perf script Python script should start by setting up a Python
+module search path and 'import'ing a few support modules (see module
+descriptions below):
+
+----
+ import os
+ import sys
+
+ sys.path.append(os.environ['PERF_EXEC_PATH'] + \
+ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+
+ from perf_trace_context import *
+ from Core import *
+----
+
+The rest of the script can contain handler functions and support
+functions in any order.
+
+Aside from the event handler functions discussed above, every script
+can implement a set of optional functions:
+
+*trace_begin*, if defined, is called before any event is processed and
+gives scripts a chance to do setup tasks:
+
+----
+def trace_begin:
+ pass
+----
+
+*trace_end*, if defined, is called after all events have been
+ processed and gives scripts a chance to do end-of-script tasks, such
+ as display results:
+
+----
+def trace_end:
+ pass
+----
+
+*trace_unhandled*, if defined, is called after for any event that
+ doesn't have a handler explicitly defined for it. The standard set
+ of common arguments are passed into it:
+
+----
+def trace_unhandled(event_name, context, common_cpu, common_secs,
+ common_nsecs, common_pid, common_comm):
+ pass
+----
+
+The remaining sections provide descriptions of each of the available
+built-in perf script Python modules and their associated functions.
+
+AVAILABLE MODULES AND FUNCTIONS
+-------------------------------
+
+The following sections describe the functions and variables available
+via the various perf script Python modules. To use the functions and
+variables from the given module, add the corresponding 'from XXXX
+import' line to your perf script script.
+
+Core.py Module
+~~~~~~~~~~~~~~
+
+These functions provide some essential functions to user scripts.
+
+The *flag_str* and *symbol_str* functions provide human-readable
+strings for flag and symbolic fields. These correspond to the strings
+and values parsed from the 'print fmt' fields of the event format
+files:
+
+ flag_str(event_name, field_name, field_value) - returns the string represention corresponding to field_value for the flag field field_name of event event_name
+ symbol_str(event_name, field_name, field_value) - returns the string represention corresponding to field_value for the symbolic field field_name of event event_name
+
+The *autodict* function returns a special kind of Python
+dictionary that implements Perl's 'autovivifying' hashes in Python
+i.e. with autovivifying hashes, you can assign nested hash values
+without having to go to the trouble of creating intermediate levels if
+they don't exist.
+
+ autodict() - returns an autovivifying dictionary instance
+
+
+perf_trace_context Module
+~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Some of the 'common' fields in the event format file aren't all that
+common, but need to be made accessible to user scripts nonetheless.
+
+perf_trace_context defines a set of functions that can be used to
+access this data in the context of the current event. Each of these
+functions expects a context variable, which is the same as the
+context variable passed into every event handler as the second
+argument.
+
+ common_pc(context) - returns common_preempt count for the current event
+ common_flags(context) - returns common_flags for the current event
+ common_lock_depth(context) - returns common_lock_depth for the current event
+
+Util.py Module
+~~~~~~~~~~~~~~
+
+Various utility functions for use with perf script:
+
+ nsecs(secs, nsecs) - returns total nsecs given secs/nsecs pair
+ nsecs_secs(nsecs) - returns whole secs portion given nsecs
+ nsecs_nsecs(nsecs) - returns nsecs remainder given nsecs
+ nsecs_str(nsecs) - returns printable string in the form secs.nsecs
+ avg(total, n) - returns average given a sum and a total number of values
+
+SEE ALSO
+--------
+linkperf:perf-script[1]
diff --git a/tools/perf/Documentation/perf-script.txt b/tools/perf/Documentation/perf-script.txt
new file mode 100644
index 00000000000..05f9a0a6784
--- /dev/null
+++ b/tools/perf/Documentation/perf-script.txt
@@ -0,0 +1,221 @@
+perf-script(1)
+=============
+
+NAME
+----
+perf-script - Read perf.data (created by perf record) and display trace output
+
+SYNOPSIS
+--------
+[verse]
+'perf script' [<options>]
+'perf script' [<options>] record <script> [<record-options>] <command>
+'perf script' [<options>] report <script> [script-args]
+'perf script' [<options>] <script> <required-script-args> [<record-options>] <command>
+'perf script' [<options>] <top-script> [script-args]
+
+DESCRIPTION
+-----------
+This command reads the input file and displays the trace recorded.
+
+There are several variants of perf script:
+
+ 'perf script' to see a detailed trace of the workload that was
+ recorded.
+
+ You can also run a set of pre-canned scripts that aggregate and
+ summarize the raw trace data in various ways (the list of scripts is
+ available via 'perf script -l'). The following variants allow you to
+ record and run those scripts:
+
+ 'perf script record <script> <command>' to record the events required
+ for 'perf script report'. <script> is the name displayed in the
+ output of 'perf script --list' i.e. the actual script name minus any
+ language extension. If <command> is not specified, the events are
+ recorded using the -a (system-wide) 'perf record' option.
+
+ 'perf script report <script> [args]' to run and display the results
+ of <script>. <script> is the name displayed in the output of 'perf
+ trace --list' i.e. the actual script name minus any language
+ extension. The perf.data output from a previous run of 'perf script
+ record <script>' is used and should be present for this command to
+ succeed. [args] refers to the (mainly optional) args expected by
+ the script.
+
+ 'perf script <script> <required-script-args> <command>' to both
+ record the events required for <script> and to run the <script>
+ using 'live-mode' i.e. without writing anything to disk. <script>
+ is the name displayed in the output of 'perf script --list' i.e. the
+ actual script name minus any language extension. If <command> is
+ not specified, the events are recorded using the -a (system-wide)
+ 'perf record' option. If <script> has any required args, they
+ should be specified before <command>. This mode doesn't allow for
+ optional script args to be specified; if optional script args are
+ desired, they can be specified using separate 'perf script record'
+ and 'perf script report' commands, with the stdout of the record step
+ piped to the stdin of the report script, using the '-o -' and '-i -'
+ options of the corresponding commands.
+
+ 'perf script <top-script>' to both record the events required for
+ <top-script> and to run the <top-script> using 'live-mode'
+ i.e. without writing anything to disk. <top-script> is the name
+ displayed in the output of 'perf script --list' i.e. the actual
+ script name minus any language extension; a <top-script> is defined
+ as any script name ending with the string 'top'.
+
+ [<record-options>] can be passed to the record steps of 'perf script
+ record' and 'live-mode' variants; this isn't possible however for
+ <top-script> 'live-mode' or 'perf script report' variants.
+
+ See the 'SEE ALSO' section for links to language-specific
+ information on how to write and run your own trace scripts.
+
+OPTIONS
+-------
+<command>...::
+ Any command you can specify in a shell.
+
+-D::
+--dump-raw-script=::
+ Display verbose dump of the trace data.
+
+-L::
+--Latency=::
+ Show latency attributes (irqs/preemption disabled, etc).
+
+-l::
+--list=::
+ Display a list of available trace scripts.
+
+-s ['lang']::
+--script=::
+ Process trace data with the given script ([lang]:script[.ext]).
+ If the string 'lang' is specified in place of a script name, a
+ list of supported languages will be displayed instead.
+
+-g::
+--gen-script=::
+ Generate perf-script.[ext] starter script for given language,
+ using current perf.data.
+
+-a::
+ Force system-wide collection. Scripts run without a <command>
+ normally use -a by default, while scripts run with a <command>
+ normally don't - this option allows the latter to be run in
+ system-wide mode.
+
+-i::
+--input=::
+ Input file name. (default: perf.data unless stdin is a fifo)
+
+-d::
+--debug-mode::
+ Do various checks like samples ordering and lost events.
+
+-f::
+--fields::
+ Comma separated list of fields to print. Options are:
+ comm, tid, pid, time, cpu, event, trace, ip, sym, dso, addr, symoff, srcline.
+ Field list can be prepended with the type, trace, sw or hw,
+ to indicate to which event type the field list applies.
+ e.g., -f sw:comm,tid,time,ip,sym and -f trace:time,cpu,trace
+
+ perf script -f <fields>
+
+ is equivalent to:
+
+ perf script -f trace:<fields> -f sw:<fields> -f hw:<fields>
+
+ i.e., the specified fields apply to all event types if the type string
+ is not given.
+
+ The arguments are processed in the order received. A later usage can
+ reset a prior request. e.g.:
+
+ -f trace: -f comm,tid,time,ip,sym
+
+ The first -f suppresses trace events (field list is ""), but then the
+ second invocation sets the fields to comm,tid,time,ip,sym. In this case a
+ warning is given to the user:
+
+ "Overriding previous field request for all events."
+
+ Alternativey, consider the order:
+
+ -f comm,tid,time,ip,sym -f trace:
+
+ The first -f sets the fields for all events and the second -f
+ suppresses trace events. The user is given a warning message about
+ the override, and the result of the above is that only S/W and H/W
+ events are displayed with the given fields.
+
+ For the 'wildcard' option if a user selected field is invalid for an
+ event type, a message is displayed to the user that the option is
+ ignored for that type. For example:
+
+ $ perf script -f comm,tid,trace
+ 'trace' not valid for hardware events. Ignoring.
+ 'trace' not valid for software events. Ignoring.
+
+ Alternatively, if the type is given an invalid field is specified it
+ is an error. For example:
+
+ perf script -v -f sw:comm,tid,trace
+ 'trace' not valid for software events.
+
+ At this point usage is displayed, and perf-script exits.
+
+ Finally, a user may not set fields to none for all event types.
+ i.e., -f "" is not allowed.
+
+-k::
+--vmlinux=<file>::
+ vmlinux pathname
+
+--kallsyms=<file>::
+ kallsyms pathname
+
+--symfs=<directory>::
+ Look for files with symbols relative to this directory.
+
+-G::
+--hide-call-graph::
+ When printing symbols do not display call chain.
+
+-C::
+--cpu:: Only report samples for the list of CPUs provided. Multiple CPUs can
+ be provided as a comma-separated list with no space: 0,1. Ranges of
+ CPUs are specified with -: 0-2. Default is to report samples on all
+ CPUs.
+
+-c::
+--comms=::
+ Only display events for these comms. CSV that understands
+ file://filename entries.
+
+-I::
+--show-info::
+ Display extended information about the perf.data file. This adds
+ information which may be very large and thus may clutter the display.
+ It currently includes: cpu and numa topology of the host system.
+ It can only be used with the perf script report mode.
+
+--show-kernel-path::
+ Try to resolve the path of [kernel.kallsyms]
+
+--show-task-events
+ Display task related events (e.g. FORK, COMM, EXIT).
+
+--show-mmap-events
+ Display mmap related events (e.g. MMAP, MMAP2).
+
+--header
+ Show perf.data header.
+
+--header-only
+ Show only perf.data header.
+
+SEE ALSO
+--------
+linkperf:perf-record[1], linkperf:perf-script-perl[1],
+linkperf:perf-script-python[1]
diff --git a/tools/perf/Documentation/perf-stat.txt b/tools/perf/Documentation/perf-stat.txt
new file mode 100644
index 00000000000..29ee857c09c
--- /dev/null
+++ b/tools/perf/Documentation/perf-stat.txt
@@ -0,0 +1,165 @@
+perf-stat(1)
+============
+
+NAME
+----
+perf-stat - Run a command and gather performance counter statistics
+
+SYNOPSIS
+--------
+[verse]
+'perf stat' [-e <EVENT> | --event=EVENT] [-a] <command>
+'perf stat' [-e <EVENT> | --event=EVENT] [-a] -- <command> [<options>]
+
+DESCRIPTION
+-----------
+This command runs a command and gathers performance counter statistics
+from it.
+
+
+OPTIONS
+-------
+<command>...::
+ Any command you can specify in a shell.
+
+
+-e::
+--event=::
+ Select the PMU event. Selection can be a symbolic event name
+ (use 'perf list' to list all events) or a raw PMU
+ event (eventsel+umask) in the form of rNNN where NNN is a
+ hexadecimal event descriptor.
+
+-i::
+--no-inherit::
+ child tasks do not inherit counters
+-p::
+--pid=<pid>::
+ stat events on existing process id (comma separated list)
+
+-t::
+--tid=<tid>::
+ stat events on existing thread id (comma separated list)
+
+
+-a::
+--all-cpus::
+ system-wide collection from all CPUs
+
+-c::
+--scale::
+ scale/normalize counter values
+
+-r::
+--repeat=<n>::
+ repeat command and print average + stddev (max: 100). 0 means forever.
+
+-B::
+--big-num::
+ print large numbers with thousands' separators according to locale
+
+-C::
+--cpu=::
+Count only on the list of CPUs provided. Multiple CPUs can be provided as a
+comma-separated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2.
+In per-thread mode, this option is ignored. The -a option is still necessary
+to activate system-wide monitoring. Default is to count on all CPUs.
+
+-A::
+--no-aggr::
+Do not aggregate counts across all monitored CPUs in system-wide mode (-a).
+This option is only valid in system-wide mode.
+
+-n::
+--null::
+ null run - don't start any counters
+
+-v::
+--verbose::
+ be more verbose (show counter open errors, etc)
+
+-x SEP::
+--field-separator SEP::
+print counts using a CSV-style output to make it easy to import directly into
+spreadsheets. Columns are separated by the string specified in SEP.
+
+-G name::
+--cgroup name::
+monitor only in the container (cgroup) called "name". This option is available only
+in per-cpu mode. The cgroup filesystem must be mounted. All threads belonging to
+container "name" are monitored when they run on the monitored CPUs. Multiple cgroups
+can be provided. Each cgroup is applied to the corresponding event, i.e., first cgroup
+to first event, second cgroup to second event and so on. It is possible to provide
+an empty cgroup (monitor all the time) using, e.g., -G foo,,bar. Cgroups must have
+corresponding events, i.e., they always refer to events defined earlier on the command
+line.
+
+-o file::
+--output file::
+Print the output into the designated file.
+
+--append::
+Append to the output file designated with the -o option. Ignored if -o is not specified.
+
+--log-fd::
+
+Log output to fd, instead of stderr. Complementary to --output, and mutually exclusive
+with it. --append may be used here. Examples:
+ 3>results perf stat --log-fd 3 -- $cmd
+ 3>>results perf stat --log-fd 3 --append -- $cmd
+
+--pre::
+--post::
+ Pre and post measurement hooks, e.g.:
+
+perf stat --repeat 10 --null --sync --pre 'make -s O=defconfig-build/clean' -- make -s -j64 O=defconfig-build/ bzImage
+
+-I msecs::
+--interval-print msecs::
+ Print count deltas every N milliseconds (minimum: 100ms)
+ example: perf stat -I 1000 -e cycles -a sleep 5
+
+--per-socket::
+Aggregate counts per processor socket for system-wide mode measurements. This
+is a useful mode to detect imbalance between sockets. To enable this mode,
+use --per-socket in addition to -a. (system-wide). The output includes the
+socket number and the number of online processors on that socket. This is
+useful to gauge the amount of aggregation.
+
+--per-core::
+Aggregate counts per physical processor for system-wide mode measurements. This
+is a useful mode to detect imbalance between physical cores. To enable this mode,
+use --per-core in addition to -a. (system-wide). The output includes the
+core number and the number of online logical processors on that physical processor.
+
+-D msecs::
+--delay msecs::
+After starting the program, wait msecs before measuring. This is useful to
+filter out the startup phase of the program, which is often very different.
+
+-T::
+--transaction::
+
+Print statistics of transactional execution if supported.
+
+EXAMPLES
+--------
+
+$ perf stat -- make -j
+
+ Performance counter stats for 'make -j':
+
+ 8117.370256 task clock ticks # 11.281 CPU utilization factor
+ 678 context switches # 0.000 M/sec
+ 133 CPU migrations # 0.000 M/sec
+ 235724 pagefaults # 0.029 M/sec
+ 24821162526 CPU cycles # 3057.784 M/sec
+ 18687303457 instructions # 2302.138 M/sec
+ 172158895 cache references # 21.209 M/sec
+ 27075259 cache misses # 3.335 M/sec
+
+ Wall-clock time elapsed: 719.554352 msecs
+
+SEE ALSO
+--------
+linkperf:perf-top[1], linkperf:perf-list[1]
diff --git a/tools/perf/Documentation/perf-test.txt b/tools/perf/Documentation/perf-test.txt
new file mode 100644
index 00000000000..d1d3e5121f8
--- /dev/null
+++ b/tools/perf/Documentation/perf-test.txt
@@ -0,0 +1,32 @@
+perf-test(1)
+============
+
+NAME
+----
+perf-test - Runs sanity tests.
+
+SYNOPSIS
+--------
+[verse]
+'perf test [<options>] [{list <test-name-fragment>|[<test-name-fragments>|<test-numbers>]}]'
+
+DESCRIPTION
+-----------
+This command does assorted sanity tests, initially through linked routines but
+also will look for a directory with more tests in the form of scripts.
+
+To get a list of available tests use 'perf test list', specifying a test name
+fragment will show all tests that have it.
+
+To run just specific tests, inform test name fragments or the numbers obtained
+from 'perf test list'.
+
+OPTIONS
+-------
+-s::
+--skip::
+ Tests to skip (comma separater numeric list).
+
+-v::
+--verbose::
+ Be more verbose.
diff --git a/tools/perf/Documentation/perf-timechart.txt b/tools/perf/Documentation/perf-timechart.txt
new file mode 100644
index 00000000000..5e0f986dff3
--- /dev/null
+++ b/tools/perf/Documentation/perf-timechart.txt
@@ -0,0 +1,92 @@
+perf-timechart(1)
+=================
+
+NAME
+----
+perf-timechart - Tool to visualize total system behavior during a workload
+
+SYNOPSIS
+--------
+[verse]
+'perf timechart' [<timechart options>] {record} [<record options>]
+
+DESCRIPTION
+-----------
+There are two variants of perf timechart:
+
+ 'perf timechart record <command>' to record the system level events
+ of an arbitrary workload.
+
+ 'perf timechart' to turn a trace into a Scalable Vector Graphics file,
+ that can be viewed with popular SVG viewers such as 'Inkscape'.
+
+TIMECHART OPTIONS
+-----------------
+-o::
+--output=::
+ Select the output file (default: output.svg)
+-i::
+--input=::
+ Select the input file (default: perf.data unless stdin is a fifo)
+-w::
+--width=::
+ Select the width of the SVG file (default: 1000)
+-P::
+--power-only::
+ Only output the CPU power section of the diagram
+-T::
+--tasks-only::
+ Don't output processor state transitions
+-p::
+--process::
+ Select the processes to display, by name or PID
+
+--symfs=<directory>::
+ Look for files with symbols relative to this directory.
+-n::
+--proc-num::
+ Print task info for at least given number of tasks.
+-t::
+--topology::
+ Sort CPUs according to topology.
+--highlight=<duration_nsecs|task_name>::
+ Highlight tasks (using different color) that run more than given
+ duration or tasks with given name. If number is given it's interpreted
+ as number of nanoseconds. If non-numeric string is given it's
+ interpreted as task name.
+
+RECORD OPTIONS
+--------------
+-P::
+--power-only::
+ Record only power-related events
+-T::
+--tasks-only::
+ Record only tasks-related events
+-g::
+--callchain::
+ Do call-graph (stack chain/backtrace) recording
+
+EXAMPLES
+--------
+
+$ perf timechart record git pull
+
+ [ perf record: Woken up 13 times to write data ]
+ [ perf record: Captured and wrote 4.253 MB perf.data (~185801 samples) ]
+
+$ perf timechart
+
+ Written 10.2 seconds of trace to output.svg.
+
+Record system-wide timechart:
+
+ $ perf timechart record
+
+ then generate timechart and highlight 'gcc' tasks:
+
+ $ perf timechart --highlight gcc
+
+SEE ALSO
+--------
+linkperf:perf-record[1]
diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt
new file mode 100644
index 00000000000..180ae02137a
--- /dev/null
+++ b/tools/perf/Documentation/perf-top.txt
@@ -0,0 +1,231 @@
+perf-top(1)
+===========
+
+NAME
+----
+perf-top - System profiling tool.
+
+SYNOPSIS
+--------
+[verse]
+'perf top' [-e <EVENT> | --event=EVENT] [<options>]
+
+DESCRIPTION
+-----------
+This command generates and displays a performance counter profile in real time.
+
+
+OPTIONS
+-------
+-a::
+--all-cpus::
+ System-wide collection. (default)
+
+-c <count>::
+--count=<count>::
+ Event period to sample.
+
+-C <cpu-list>::
+--cpu=<cpu>::
+Monitor only on the list of CPUs provided. Multiple CPUs can be provided as a
+comma-separated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2.
+Default is to monitor all CPUS.
+
+-d <seconds>::
+--delay=<seconds>::
+ Number of seconds to delay between refreshes.
+
+-e <event>::
+--event=<event>::
+ Select the PMU event. Selection can be a symbolic event name
+ (use 'perf list' to list all events) or a raw PMU
+ event (eventsel+umask) in the form of rNNN where NNN is a
+ hexadecimal event descriptor.
+
+-E <entries>::
+--entries=<entries>::
+ Display this many functions.
+
+-f <count>::
+--count-filter=<count>::
+ Only display functions with more events than this.
+
+--group::
+ Put the counters into a counter group.
+
+-F <freq>::
+--freq=<freq>::
+ Profile at this frequency.
+
+-i::
+--inherit::
+ Child tasks do not inherit counters.
+
+-k <path>::
+--vmlinux=<path>::
+ Path to vmlinux. Required for annotation functionality.
+
+-m <pages>::
+--mmap-pages=<pages>::
+ Number of mmap data pages (must be a power of two) or size
+ specification with appended unit character - B/K/M/G. The
+ size is rounded up to have nearest pages power of two value.
+
+-p <pid>::
+--pid=<pid>::
+ Profile events on existing Process ID (comma separated list).
+
+-t <tid>::
+--tid=<tid>::
+ Profile events on existing thread ID (comma separated list).
+
+-u::
+--uid=::
+ Record events in threads owned by uid. Name or number.
+
+-r <priority>::
+--realtime=<priority>::
+ Collect data with this RT SCHED_FIFO priority.
+
+--sym-annotate=<symbol>::
+ Annotate this symbol.
+
+-K::
+--hide_kernel_symbols::
+ Hide kernel symbols.
+
+-U::
+--hide_user_symbols::
+ Hide user symbols.
+
+-D::
+--dump-symtab::
+ Dump the symbol table used for profiling.
+
+-v::
+--verbose::
+ Be more verbose (show counter open errors, etc).
+
+-z::
+--zero::
+ Zero history across display updates.
+
+-s::
+--sort::
+ Sort by key(s): pid, comm, dso, symbol, parent, srcline, weight,
+ local_weight, abort, in_tx, transaction, overhead, sample, period.
+ Please see description of --sort in the perf-report man page.
+
+--fields=::
+ Specify output field - multiple keys can be specified in CSV format.
+ Following fields are available:
+ overhead, overhead_sys, overhead_us, overhead_children, sample and period.
+ Also it can contain any sort key(s).
+
+ By default, every sort keys not specified in --field will be appended
+ automatically.
+
+-n::
+--show-nr-samples::
+ Show a column with the number of samples.
+
+--show-total-period::
+ Show a column with the sum of periods.
+
+--dsos::
+ Only consider symbols in these dsos. This option will affect the
+ percentage of the overhead column. See --percentage for more info.
+
+--comms::
+ Only consider symbols in these comms. This option will affect the
+ percentage of the overhead column. See --percentage for more info.
+
+--symbols::
+ Only consider these symbols. This option will affect the
+ percentage of the overhead column. See --percentage for more info.
+
+-M::
+--disassembler-style=:: Set disassembler style for objdump.
+
+--source::
+ Interleave source code with assembly code. Enabled by default,
+ disable with --no-source.
+
+--asm-raw::
+ Show raw instruction encoding of assembly instructions.
+
+-g::
+ Enables call-graph (stack chain/backtrace) recording.
+
+--call-graph::
+ Setup and enable call-graph (stack chain/backtrace) recording,
+ implies -g.
+
+--children::
+ Accumulate callchain of children to parent entry so that then can
+ show up in the output. The output will have a new "Children" column
+ and will be sorted on the data. It requires -g/--call-graph option
+ enabled.
+
+--max-stack::
+ Set the stack depth limit when parsing the callchain, anything
+ beyond the specified depth will be ignored. This is a trade-off
+ between information loss and faster processing especially for
+ workloads that can have a very long callchain stack.
+
+ Default: 127
+
+--ignore-callees=<regex>::
+ Ignore callees of the function(s) matching the given regex.
+ This has the effect of collecting the callers of each such
+ function into one place in the call-graph tree.
+
+--percent-limit::
+ Do not show entries which have an overhead under that percent.
+ (Default: 0).
+
+--percentage::
+ Determine how to display the overhead percentage of filtered entries.
+ Filters can be applied by --comms, --dsos and/or --symbols options and
+ Zoom operations on the TUI (thread, dso, etc).
+
+ "relative" means it's relative to filtered entries only so that the
+ sum of shown entries will be always 100%. "absolute" means it retains
+ the original value before and after the filter is applied.
+
+INTERACTIVE PROMPTING KEYS
+--------------------------
+
+[d]::
+ Display refresh delay.
+
+[e]::
+ Number of entries to display.
+
+[E]::
+ Event to display when multiple counters are active.
+
+[f]::
+ Profile display filter (>= hit count).
+
+[F]::
+ Annotation display filter (>= % of total).
+
+[s]::
+ Annotate symbol.
+
+[S]::
+ Stop annotation, return to full profile display.
+
+[z]::
+ Toggle event count zeroing across display updates.
+
+[qQ]::
+ Quit.
+
+Pressing any unmapped key displays a menu, and prompts for input.
+
+
+SEE ALSO
+--------
+linkperf:perf-stat[1], linkperf:perf-list[1], linkperf:perf-report[1]
diff --git a/tools/perf/Documentation/perf-trace.txt b/tools/perf/Documentation/perf-trace.txt
new file mode 100644
index 00000000000..fae38d9a44a
--- /dev/null
+++ b/tools/perf/Documentation/perf-trace.txt
@@ -0,0 +1,112 @@
+perf-trace(1)
+=============
+
+NAME
+----
+perf-trace - strace inspired tool
+
+SYNOPSIS
+--------
+[verse]
+'perf trace'
+'perf trace record'
+
+DESCRIPTION
+-----------
+This command will show the events associated with the target, initially
+syscalls, but other system events like pagefaults, task lifetime events,
+scheduling events, etc.
+
+This is a live mode tool in addition to working with perf.data files like
+the other perf tools. Files can be generated using the 'perf record' command
+but the session needs to include the raw_syscalls events (-e 'raw_syscalls:*').
+Alernatively, the 'perf trace record' can be used as a shortcut to
+automatically include the raw_syscalls events when writing events to a file.
+
+The following options apply to perf trace; options to perf trace record are
+found in the perf record man page.
+
+OPTIONS
+-------
+
+-a::
+--all-cpus::
+ System-wide collection from all CPUs.
+
+-e::
+--expr::
+ List of events to show, currently only syscall names.
+ Prefixing with ! shows all syscalls but the ones specified. You may
+ need to escape it.
+
+-o::
+--output=::
+ Output file name.
+
+-p::
+--pid=::
+ Record events on existing process ID (comma separated list).
+
+-t::
+--tid=::
+ Record events on existing thread ID (comma separated list).
+
+-u::
+--uid=::
+ Record events in threads owned by uid. Name or number.
+
+-v::
+--verbose=::
+ Verbosity level.
+
+-i::
+--no-inherit::
+ Child tasks do not inherit counters.
+
+-m::
+--mmap-pages=::
+ Number of mmap data pages (must be a power of two) or size
+ specification with appended unit character - B/K/M/G. The
+ size is rounded up to have nearest pages power of two value.
+
+-C::
+--cpu::
+Collect samples only on the list of CPUs provided. Multiple CPUs can be provided as a
+comma-separated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2.
+In per-thread mode with inheritance mode on (default), Events are captured only when
+the thread executes on the designated CPUs. Default is to monitor all CPUs.
+
+--duration:
+ Show only events that had a duration greater than N.M ms.
+
+--sched:
+ Accrue thread runtime and provide a summary at the end of the session.
+
+-i
+--input
+ Process events from a given perf data file.
+
+-T
+--time
+ Print full timestamp rather time relative to first sample.
+
+--comm::
+ Show process COMM right beside its ID, on by default, disable with --no-comm.
+
+-s::
+--summary::
+ Show only a summary of syscalls by thread with min, max, and average times
+ (in msec) and relative stddev.
+
+-S::
+--with-summary::
+ Show all syscalls followed by a summary by thread with min, max, and
+ average times (in msec) and relative stddev.
+
+--tool_stats::
+ Show tool stats such as number of times fd->pathname was discovered thru
+ hooking the open syscall return + vfs_getname or via reading /proc/pid/fd, etc.
+
+SEE ALSO
+--------
+linkperf:perf-record[1], linkperf:perf-script[1]
diff --git a/tools/perf/Documentation/perf.txt b/tools/perf/Documentation/perf.txt
new file mode 100644
index 00000000000..0eeb247dc7d
--- /dev/null
+++ b/tools/perf/Documentation/perf.txt
@@ -0,0 +1,24 @@
+perf(1)
+=======
+
+NAME
+----
+perf - Performance analysis tools for Linux
+
+SYNOPSIS
+--------
+[verse]
+'perf' [--version] [--help] COMMAND [ARGS]
+
+DESCRIPTION
+-----------
+Performance counters for Linux are a new kernel-based subsystem
+that provide a framework for all things performance analysis. It
+covers hardware level (CPU/PMU, Performance Monitoring Unit) features
+and software features (software counters, tracepoints) as well.
+
+SEE ALSO
+--------
+linkperf:perf-stat[1], linkperf:perf-top[1],
+linkperf:perf-record[1], linkperf:perf-report[1],
+linkperf:perf-list[1]
diff --git a/tools/perf/Documentation/perfconfig.example b/tools/perf/Documentation/perfconfig.example
new file mode 100644
index 00000000000..767ea2436e1
--- /dev/null
+++ b/tools/perf/Documentation/perfconfig.example
@@ -0,0 +1,29 @@
+[colors]
+
+ # These were the old defaults
+ top = red, lightgray
+ medium = green, lightgray
+ normal = black, lightgray
+ selected = lightgray, magenta
+ code = blue, lightgray
+ addr = magenta, lightgray
+
+[tui]
+
+ # Defaults if linked with libslang
+ report = on
+ annotate = on
+ top = on
+
+[buildid]
+
+ # Default, disable using /dev/null
+ dir = /root/.debug
+
+[annotate]
+
+ # Defaults
+ hide_src_code = false
+ use_offset = true
+ jump_arrows = true
+ show_nr_jumps = false
diff --git a/tools/perf/MANIFEST b/tools/perf/MANIFEST
new file mode 100644
index 00000000000..45da209b6ed
--- /dev/null
+++ b/tools/perf/MANIFEST
@@ -0,0 +1,39 @@
+tools/perf
+tools/scripts
+tools/lib/traceevent
+tools/lib/api
+tools/lib/symbol/kallsyms.c
+tools/lib/symbol/kallsyms.h
+tools/include/asm/bug.h
+tools/include/linux/compiler.h
+tools/include/linux/hash.h
+tools/include/linux/export.h
+tools/include/linux/types.h
+include/linux/const.h
+include/linux/perf_event.h
+include/linux/rbtree.h
+include/linux/list.h
+include/linux/hash.h
+include/linux/stringify.h
+lib/rbtree.c
+include/linux/swab.h
+arch/*/include/asm/unistd*.h
+arch/*/include/asm/perf_regs.h
+arch/*/include/uapi/asm/unistd*.h
+arch/*/include/uapi/asm/perf_regs.h
+arch/*/lib/memcpy*.S
+arch/*/lib/memset*.S
+include/linux/poison.h
+include/linux/magic.h
+include/linux/hw_breakpoint.h
+include/linux/rbtree_augmented.h
+include/uapi/linux/perf_event.h
+include/uapi/linux/const.h
+include/uapi/linux/swab.h
+include/uapi/linux/hw_breakpoint.h
+arch/x86/include/asm/svm.h
+arch/x86/include/asm/vmx.h
+arch/x86/include/asm/kvm_host.h
+arch/x86/include/uapi/asm/svm.h
+arch/x86/include/uapi/asm/vmx.h
+arch/x86/include/uapi/asm/kvm.h
diff --git a/tools/perf/Makefile b/tools/perf/Makefile
new file mode 100644
index 00000000000..cb2e5868c8e
--- /dev/null
+++ b/tools/perf/Makefile
@@ -0,0 +1,90 @@
+#
+# This is a simple wrapper Makefile that calls the main Makefile.perf
+# with a -j option to do parallel builds
+#
+# If you want to invoke the perf build in some non-standard way then
+# you can use the 'make -f Makefile.perf' method to invoke it.
+#
+
+#
+# Clear out the built-in rules GNU make defines by default (such as .o targets),
+# so that we pass through all targets to Makefile.perf:
+#
+.SUFFIXES:
+
+#
+# We don't want to pass along options like -j:
+#
+unexport MAKEFLAGS
+
+#
+# Do a parallel build with multiple jobs, based on the number of CPUs online
+# in this system: 'make -j8' on a 8-CPU system, etc.
+#
+# (To override it, run 'make JOBS=1' and similar.)
+#
+ifeq ($(JOBS),)
+ JOBS := $(shell grep -c ^processor /proc/cpuinfo 2>/dev/null)
+ ifeq ($(JOBS),)
+ JOBS := 1
+ endif
+endif
+
+#
+# Only pass canonical directory names as the output directory:
+#
+ifneq ($(O),)
+ FULL_O := $(shell readlink -f $(O) || echo $(O))
+endif
+
+#
+# Only accept the 'DEBUG' variable from the command line:
+#
+ifeq ("$(origin DEBUG)", "command line")
+ ifeq ($(DEBUG),)
+ override DEBUG = 0
+ else
+ SET_DEBUG = "DEBUG=$(DEBUG)"
+ endif
+else
+ override DEBUG = 0
+endif
+
+define print_msg
+ @printf ' BUILD: Doing '\''make \033[33m-j'$(JOBS)'\033[m'\'' parallel build\n'
+endef
+
+define make
+ @$(MAKE) -f Makefile.perf --no-print-directory -j$(JOBS) O=$(FULL_O) $(SET_DEBUG) $@
+endef
+
+#
+# Needed if no target specified:
+# (Except for tags and TAGS targets. The reason is that the
+# Makefile does not treat tags/TAGS as targets but as files
+# and thus won't rebuilt them once they are in place.)
+#
+all tags TAGS:
+ $(print_msg)
+ $(make)
+
+#
+# The clean target is not really parallel, don't print the jobs info:
+#
+clean:
+ $(make)
+
+#
+# The build-test target is not really parallel, don't print the jobs info:
+#
+build-test:
+ @$(MAKE) -f tests/make --no-print-directory
+
+#
+# All other targets get passed through:
+#
+%:
+ $(print_msg)
+ $(make)
+
+.PHONY: tags TAGS
diff --git a/tools/perf/Makefile.perf b/tools/perf/Makefile.perf
new file mode 100644
index 00000000000..9670a16fa57
--- /dev/null
+++ b/tools/perf/Makefile.perf
@@ -0,0 +1,938 @@
+include ../scripts/Makefile.include
+
+# The default target of this Makefile is...
+all:
+
+include config/utilities.mak
+
+# Define V to have a more verbose compile.
+#
+# Define VF to have a more verbose feature check output.
+#
+# Define O to save output files in a separate directory.
+#
+# Define ARCH as name of target architecture if you want cross-builds.
+#
+# Define CROSS_COMPILE as prefix name of compiler if you want cross-builds.
+#
+# Define NO_LIBPERL to disable perl script extension.
+#
+# Define NO_LIBPYTHON to disable python script extension.
+#
+# Define PYTHON to point to the python binary if the default
+# `python' is not correct; for example: PYTHON=python2
+#
+# Define PYTHON_CONFIG to point to the python-config binary if
+# the default `$(PYTHON)-config' is not correct.
+#
+# Define ASCIIDOC8 if you want to format documentation with AsciiDoc 8
+#
+# Define DOCBOOK_XSL_172 if you want to format man pages with DocBook XSL v1.72.
+#
+# Define LDFLAGS=-static to build a static binary.
+#
+# Define EXTRA_CFLAGS=-m64 or EXTRA_CFLAGS=-m32 as appropriate for cross-builds.
+#
+# Define NO_DWARF if you do not want debug-info analysis feature at all.
+#
+# Define WERROR=0 to disable treating any warnings as errors.
+#
+# Define NO_NEWT if you do not want TUI support. (deprecated)
+#
+# Define NO_SLANG if you do not want TUI support.
+#
+# Define NO_GTK2 if you do not want GTK+ GUI support.
+#
+# Define NO_DEMANGLE if you do not want C++ symbol demangling.
+#
+# Define NO_LIBELF if you do not want libelf dependency (e.g. cross-builds)
+#
+# Define NO_LIBUNWIND if you do not want libunwind dependency for dwarf
+# backtrace post unwind.
+#
+# Define NO_BACKTRACE if you do not want stack backtrace debug feature
+#
+# Define NO_LIBNUMA if you do not want numa perf benchmark
+#
+# Define NO_LIBAUDIT if you do not want libaudit support
+#
+# Define NO_LIBBIONIC if you do not want bionic support
+#
+# Define NO_LIBDW_DWARF_UNWIND if you do not want libdw support
+# for dwarf backtrace post unwind.
+
+ifeq ($(srctree),)
+srctree := $(patsubst %/,%,$(dir $(shell pwd)))
+srctree := $(patsubst %/,%,$(dir $(srctree)))
+#$(info Determined 'srctree' to be $(srctree))
+endif
+
+ifneq ($(objtree),)
+#$(info Determined 'objtree' to be $(objtree))
+endif
+
+ifneq ($(OUTPUT),)
+#$(info Determined 'OUTPUT' to be $(OUTPUT))
+endif
+
+$(OUTPUT)PERF-VERSION-FILE: ../../.git/HEAD
+ @$(SHELL_PATH) util/PERF-VERSION-GEN $(OUTPUT)
+ @touch $(OUTPUT)PERF-VERSION-FILE
+
+CC = $(CROSS_COMPILE)gcc
+AR = $(CROSS_COMPILE)ar
+PKG_CONFIG = $(CROSS_COMPILE)pkg-config
+
+RM = rm -f
+LN = ln -f
+MKDIR = mkdir
+FIND = find
+INSTALL = install
+FLEX = flex
+BISON = bison
+STRIP = strip
+
+LIB_DIR = $(srctree)/tools/lib/api/
+TRACE_EVENT_DIR = $(srctree)/tools/lib/traceevent/
+
+# include config/Makefile by default and rule out
+# non-config cases
+config := 1
+
+NON_CONFIG_TARGETS := clean TAGS tags cscope help
+
+ifdef MAKECMDGOALS
+ifeq ($(filter-out $(NON_CONFIG_TARGETS),$(MAKECMDGOALS)),)
+ config := 0
+endif
+endif
+
+ifeq ($(config),1)
+include config/Makefile
+endif
+
+export prefix bindir sharedir sysconfdir DESTDIR
+
+# sparse is architecture-neutral, which means that we need to tell it
+# explicitly what architecture to check for. Fix this up for yours..
+SPARSE_FLAGS = -D__BIG_ENDIAN__ -D__powerpc__
+
+# Guard against environment variables
+BUILTIN_OBJS =
+LIB_H =
+LIB_OBJS =
+GTK_OBJS =
+PYRF_OBJS =
+SCRIPT_SH =
+
+SCRIPT_SH += perf-archive.sh
+
+grep-libs = $(filter -l%,$(1))
+strip-libs = $(filter-out -l%,$(1))
+
+ifneq ($(OUTPUT),)
+ TE_PATH=$(OUTPUT)
+ifneq ($(subdir),)
+ LIB_PATH=$(OUTPUT)/../lib/api/
+else
+ LIB_PATH=$(OUTPUT)
+endif
+else
+ TE_PATH=$(TRACE_EVENT_DIR)
+ LIB_PATH=$(LIB_DIR)
+endif
+
+LIBTRACEEVENT = $(TE_PATH)libtraceevent.a
+export LIBTRACEEVENT
+
+LIBAPIKFS = $(LIB_PATH)libapikfs.a
+export LIBAPIKFS
+
+# python extension build directories
+PYTHON_EXTBUILD := $(OUTPUT)python_ext_build/
+PYTHON_EXTBUILD_LIB := $(PYTHON_EXTBUILD)lib/
+PYTHON_EXTBUILD_TMP := $(PYTHON_EXTBUILD)tmp/
+export PYTHON_EXTBUILD_LIB PYTHON_EXTBUILD_TMP
+
+python-clean := $(call QUIET_CLEAN, python) $(RM) -r $(PYTHON_EXTBUILD) $(OUTPUT)python/perf.so
+
+PYTHON_EXT_SRCS := $(shell grep -v ^\# util/python-ext-sources)
+PYTHON_EXT_DEPS := util/python-ext-sources util/setup.py $(LIBTRACEEVENT) $(LIBAPIKFS)
+
+$(OUTPUT)python/perf.so: $(PYTHON_EXT_SRCS) $(PYTHON_EXT_DEPS)
+ $(QUIET_GEN)CFLAGS='$(CFLAGS)' $(PYTHON_WORD) util/setup.py \
+ --quiet build_ext; \
+ mkdir -p $(OUTPUT)python && \
+ cp $(PYTHON_EXTBUILD_LIB)perf.so $(OUTPUT)python/
+#
+# No Perl scripts right now:
+#
+
+SCRIPTS = $(patsubst %.sh,%,$(SCRIPT_SH))
+
+#
+# Single 'perf' binary right now:
+#
+PROGRAMS += $(OUTPUT)perf
+
+# what 'all' will build and 'install' will install, in perfexecdir
+ALL_PROGRAMS = $(PROGRAMS) $(SCRIPTS)
+
+# what 'all' will build but not install in perfexecdir
+OTHER_PROGRAMS = $(OUTPUT)perf
+
+# Set paths to tools early so that they can be used for version tests.
+ifndef SHELL_PATH
+ SHELL_PATH = /bin/sh
+endif
+ifndef PERL_PATH
+ PERL_PATH = /usr/bin/perl
+endif
+
+export PERL_PATH
+
+$(OUTPUT)util/parse-events-flex.c: util/parse-events.l $(OUTPUT)util/parse-events-bison.c
+ $(QUIET_FLEX)$(FLEX) -o $@ --header-file=$(OUTPUT)util/parse-events-flex.h $(PARSER_DEBUG_FLEX) util/parse-events.l
+
+$(OUTPUT)util/parse-events-bison.c: util/parse-events.y
+ $(QUIET_BISON)$(BISON) -v util/parse-events.y -d $(PARSER_DEBUG_BISON) -o $(OUTPUT)util/parse-events-bison.c -p parse_events_
+
+$(OUTPUT)util/pmu-flex.c: util/pmu.l $(OUTPUT)util/pmu-bison.c
+ $(QUIET_FLEX)$(FLEX) -o $@ --header-file=$(OUTPUT)util/pmu-flex.h util/pmu.l
+
+$(OUTPUT)util/pmu-bison.c: util/pmu.y
+ $(QUIET_BISON)$(BISON) -v util/pmu.y -d -o $(OUTPUT)util/pmu-bison.c -p perf_pmu_
+
+$(OUTPUT)util/parse-events.o: $(OUTPUT)util/parse-events-flex.c $(OUTPUT)util/parse-events-bison.c
+$(OUTPUT)util/pmu.o: $(OUTPUT)util/pmu-flex.c $(OUTPUT)util/pmu-bison.c
+
+LIB_FILE=$(OUTPUT)libperf.a
+
+LIB_H += ../lib/symbol/kallsyms.h
+LIB_H += ../../include/uapi/linux/perf_event.h
+LIB_H += ../../include/linux/rbtree.h
+LIB_H += ../../include/linux/list.h
+LIB_H += ../../include/uapi/linux/const.h
+LIB_H += ../include/linux/hash.h
+LIB_H += ../../include/linux/stringify.h
+LIB_H += util/include/linux/bitmap.h
+LIB_H += util/include/linux/bitops.h
+LIB_H += ../include/linux/compiler.h
+LIB_H += util/include/linux/const.h
+LIB_H += util/include/linux/ctype.h
+LIB_H += util/include/linux/kernel.h
+LIB_H += util/include/linux/list.h
+LIB_H += ../include/linux/export.h
+LIB_H += util/include/linux/poison.h
+LIB_H += util/include/linux/rbtree.h
+LIB_H += util/include/linux/rbtree_augmented.h
+LIB_H += util/include/linux/string.h
+LIB_H += ../include/linux/types.h
+LIB_H += util/include/linux/linkage.h
+LIB_H += util/include/asm/asm-offsets.h
+LIB_H += ../include/asm/bug.h
+LIB_H += util/include/asm/byteorder.h
+LIB_H += util/include/asm/hweight.h
+LIB_H += util/include/asm/swab.h
+LIB_H += util/include/asm/system.h
+LIB_H += util/include/asm/uaccess.h
+LIB_H += util/include/dwarf-regs.h
+LIB_H += util/include/asm/dwarf2.h
+LIB_H += util/include/asm/cpufeature.h
+LIB_H += util/include/asm/unistd_32.h
+LIB_H += util/include/asm/unistd_64.h
+LIB_H += perf.h
+LIB_H += util/annotate.h
+LIB_H += util/cache.h
+LIB_H += util/callchain.h
+LIB_H += util/build-id.h
+LIB_H += util/debug.h
+LIB_H += util/pmu.h
+LIB_H += util/event.h
+LIB_H += util/evsel.h
+LIB_H += util/evlist.h
+LIB_H += util/exec_cmd.h
+LIB_H += util/levenshtein.h
+LIB_H += util/machine.h
+LIB_H += util/map.h
+LIB_H += util/parse-options.h
+LIB_H += util/parse-events.h
+LIB_H += util/quote.h
+LIB_H += util/util.h
+LIB_H += util/xyarray.h
+LIB_H += util/header.h
+LIB_H += util/help.h
+LIB_H += util/session.h
+LIB_H += util/strbuf.h
+LIB_H += util/strlist.h
+LIB_H += util/strfilter.h
+LIB_H += util/svghelper.h
+LIB_H += util/tool.h
+LIB_H += util/run-command.h
+LIB_H += util/sigchain.h
+LIB_H += util/dso.h
+LIB_H += util/symbol.h
+LIB_H += util/color.h
+LIB_H += util/values.h
+LIB_H += util/sort.h
+LIB_H += util/hist.h
+LIB_H += util/comm.h
+LIB_H += util/thread.h
+LIB_H += util/thread_map.h
+LIB_H += util/trace-event.h
+LIB_H += util/probe-finder.h
+LIB_H += util/dwarf-aux.h
+LIB_H += util/probe-event.h
+LIB_H += util/pstack.h
+LIB_H += util/cpumap.h
+LIB_H += util/top.h
+LIB_H += $(ARCH_INCLUDE)
+LIB_H += util/cgroup.h
+LIB_H += $(LIB_INCLUDE)traceevent/event-parse.h
+LIB_H += util/target.h
+LIB_H += util/rblist.h
+LIB_H += util/intlist.h
+LIB_H += util/perf_regs.h
+LIB_H += util/unwind.h
+LIB_H += util/vdso.h
+LIB_H += ui/helpline.h
+LIB_H += ui/progress.h
+LIB_H += ui/util.h
+LIB_H += ui/ui.h
+LIB_H += util/data.h
+
+LIB_OBJS += $(OUTPUT)util/abspath.o
+LIB_OBJS += $(OUTPUT)util/alias.o
+LIB_OBJS += $(OUTPUT)util/annotate.o
+LIB_OBJS += $(OUTPUT)util/build-id.o
+LIB_OBJS += $(OUTPUT)util/config.o
+LIB_OBJS += $(OUTPUT)util/ctype.o
+LIB_OBJS += $(OUTPUT)util/pmu.o
+LIB_OBJS += $(OUTPUT)util/environment.o
+LIB_OBJS += $(OUTPUT)util/event.o
+LIB_OBJS += $(OUTPUT)util/evlist.o
+LIB_OBJS += $(OUTPUT)util/evsel.o
+LIB_OBJS += $(OUTPUT)util/exec_cmd.o
+LIB_OBJS += $(OUTPUT)util/help.o
+LIB_OBJS += $(OUTPUT)util/kallsyms.o
+LIB_OBJS += $(OUTPUT)util/levenshtein.o
+LIB_OBJS += $(OUTPUT)util/parse-options.o
+LIB_OBJS += $(OUTPUT)util/parse-events.o
+LIB_OBJS += $(OUTPUT)util/path.o
+LIB_OBJS += $(OUTPUT)util/rbtree.o
+LIB_OBJS += $(OUTPUT)util/bitmap.o
+LIB_OBJS += $(OUTPUT)util/hweight.o
+LIB_OBJS += $(OUTPUT)util/run-command.o
+LIB_OBJS += $(OUTPUT)util/quote.o
+LIB_OBJS += $(OUTPUT)util/strbuf.o
+LIB_OBJS += $(OUTPUT)util/string.o
+LIB_OBJS += $(OUTPUT)util/strlist.o
+LIB_OBJS += $(OUTPUT)util/strfilter.o
+LIB_OBJS += $(OUTPUT)util/top.o
+LIB_OBJS += $(OUTPUT)util/usage.o
+LIB_OBJS += $(OUTPUT)util/wrapper.o
+LIB_OBJS += $(OUTPUT)util/sigchain.o
+LIB_OBJS += $(OUTPUT)util/dso.o
+LIB_OBJS += $(OUTPUT)util/symbol.o
+LIB_OBJS += $(OUTPUT)util/symbol-elf.o
+LIB_OBJS += $(OUTPUT)util/color.o
+LIB_OBJS += $(OUTPUT)util/pager.o
+LIB_OBJS += $(OUTPUT)util/header.o
+LIB_OBJS += $(OUTPUT)util/callchain.o
+LIB_OBJS += $(OUTPUT)util/values.o
+LIB_OBJS += $(OUTPUT)util/debug.o
+LIB_OBJS += $(OUTPUT)util/machine.o
+LIB_OBJS += $(OUTPUT)util/map.o
+LIB_OBJS += $(OUTPUT)util/pstack.o
+LIB_OBJS += $(OUTPUT)util/session.o
+LIB_OBJS += $(OUTPUT)util/comm.o
+LIB_OBJS += $(OUTPUT)util/thread.o
+LIB_OBJS += $(OUTPUT)util/thread_map.o
+LIB_OBJS += $(OUTPUT)util/trace-event-parse.o
+LIB_OBJS += $(OUTPUT)util/parse-events-flex.o
+LIB_OBJS += $(OUTPUT)util/parse-events-bison.o
+LIB_OBJS += $(OUTPUT)util/pmu-flex.o
+LIB_OBJS += $(OUTPUT)util/pmu-bison.o
+LIB_OBJS += $(OUTPUT)util/trace-event-read.o
+LIB_OBJS += $(OUTPUT)util/trace-event-info.o
+LIB_OBJS += $(OUTPUT)util/trace-event-scripting.o
+LIB_OBJS += $(OUTPUT)util/trace-event.o
+LIB_OBJS += $(OUTPUT)util/svghelper.o
+LIB_OBJS += $(OUTPUT)util/sort.o
+LIB_OBJS += $(OUTPUT)util/hist.o
+LIB_OBJS += $(OUTPUT)util/probe-event.o
+LIB_OBJS += $(OUTPUT)util/util.o
+LIB_OBJS += $(OUTPUT)util/xyarray.o
+LIB_OBJS += $(OUTPUT)util/cpumap.o
+LIB_OBJS += $(OUTPUT)util/cgroup.o
+LIB_OBJS += $(OUTPUT)util/target.o
+LIB_OBJS += $(OUTPUT)util/rblist.o
+LIB_OBJS += $(OUTPUT)util/intlist.o
+LIB_OBJS += $(OUTPUT)util/vdso.o
+LIB_OBJS += $(OUTPUT)util/stat.o
+LIB_OBJS += $(OUTPUT)util/record.o
+LIB_OBJS += $(OUTPUT)util/srcline.o
+LIB_OBJS += $(OUTPUT)util/data.o
+
+LIB_OBJS += $(OUTPUT)ui/setup.o
+LIB_OBJS += $(OUTPUT)ui/helpline.o
+LIB_OBJS += $(OUTPUT)ui/progress.o
+LIB_OBJS += $(OUTPUT)ui/util.o
+LIB_OBJS += $(OUTPUT)ui/hist.o
+LIB_OBJS += $(OUTPUT)ui/stdio/hist.o
+
+LIB_OBJS += $(OUTPUT)arch/common.o
+
+LIB_OBJS += $(OUTPUT)tests/parse-events.o
+LIB_OBJS += $(OUTPUT)tests/dso-data.o
+LIB_OBJS += $(OUTPUT)tests/attr.o
+LIB_OBJS += $(OUTPUT)tests/vmlinux-kallsyms.o
+LIB_OBJS += $(OUTPUT)tests/open-syscall.o
+LIB_OBJS += $(OUTPUT)tests/open-syscall-all-cpus.o
+LIB_OBJS += $(OUTPUT)tests/open-syscall-tp-fields.o
+LIB_OBJS += $(OUTPUT)tests/mmap-basic.o
+LIB_OBJS += $(OUTPUT)tests/perf-record.o
+LIB_OBJS += $(OUTPUT)tests/rdpmc.o
+LIB_OBJS += $(OUTPUT)tests/evsel-roundtrip-name.o
+LIB_OBJS += $(OUTPUT)tests/evsel-tp-sched.o
+LIB_OBJS += $(OUTPUT)tests/pmu.o
+LIB_OBJS += $(OUTPUT)tests/hists_common.o
+LIB_OBJS += $(OUTPUT)tests/hists_link.o
+LIB_OBJS += $(OUTPUT)tests/hists_filter.o
+LIB_OBJS += $(OUTPUT)tests/hists_output.o
+LIB_OBJS += $(OUTPUT)tests/hists_cumulate.o
+LIB_OBJS += $(OUTPUT)tests/python-use.o
+LIB_OBJS += $(OUTPUT)tests/bp_signal.o
+LIB_OBJS += $(OUTPUT)tests/bp_signal_overflow.o
+LIB_OBJS += $(OUTPUT)tests/task-exit.o
+LIB_OBJS += $(OUTPUT)tests/sw-clock.o
+ifeq ($(ARCH),x86)
+LIB_OBJS += $(OUTPUT)tests/perf-time-to-tsc.o
+endif
+LIB_OBJS += $(OUTPUT)tests/code-reading.o
+LIB_OBJS += $(OUTPUT)tests/sample-parsing.o
+LIB_OBJS += $(OUTPUT)tests/parse-no-sample-id-all.o
+ifndef NO_DWARF_UNWIND
+ifeq ($(ARCH),$(filter $(ARCH),x86 arm))
+LIB_OBJS += $(OUTPUT)tests/dwarf-unwind.o
+endif
+endif
+LIB_OBJS += $(OUTPUT)tests/mmap-thread-lookup.o
+LIB_OBJS += $(OUTPUT)tests/thread-mg-share.o
+
+BUILTIN_OBJS += $(OUTPUT)builtin-annotate.o
+BUILTIN_OBJS += $(OUTPUT)builtin-bench.o
+# Benchmark modules
+BUILTIN_OBJS += $(OUTPUT)bench/sched-messaging.o
+BUILTIN_OBJS += $(OUTPUT)bench/sched-pipe.o
+ifeq ($(RAW_ARCH),x86_64)
+BUILTIN_OBJS += $(OUTPUT)bench/mem-memcpy-x86-64-asm.o
+BUILTIN_OBJS += $(OUTPUT)bench/mem-memset-x86-64-asm.o
+endif
+BUILTIN_OBJS += $(OUTPUT)bench/mem-memcpy.o
+BUILTIN_OBJS += $(OUTPUT)bench/mem-memset.o
+BUILTIN_OBJS += $(OUTPUT)bench/futex-hash.o
+BUILTIN_OBJS += $(OUTPUT)bench/futex-wake.o
+BUILTIN_OBJS += $(OUTPUT)bench/futex-requeue.o
+
+BUILTIN_OBJS += $(OUTPUT)builtin-diff.o
+BUILTIN_OBJS += $(OUTPUT)builtin-evlist.o
+BUILTIN_OBJS += $(OUTPUT)builtin-help.o
+BUILTIN_OBJS += $(OUTPUT)builtin-sched.o
+BUILTIN_OBJS += $(OUTPUT)builtin-buildid-list.o
+BUILTIN_OBJS += $(OUTPUT)builtin-buildid-cache.o
+BUILTIN_OBJS += $(OUTPUT)builtin-list.o
+BUILTIN_OBJS += $(OUTPUT)builtin-record.o
+BUILTIN_OBJS += $(OUTPUT)builtin-report.o
+BUILTIN_OBJS += $(OUTPUT)builtin-stat.o
+BUILTIN_OBJS += $(OUTPUT)builtin-timechart.o
+BUILTIN_OBJS += $(OUTPUT)builtin-top.o
+BUILTIN_OBJS += $(OUTPUT)builtin-script.o
+BUILTIN_OBJS += $(OUTPUT)builtin-probe.o
+BUILTIN_OBJS += $(OUTPUT)builtin-kmem.o
+BUILTIN_OBJS += $(OUTPUT)builtin-lock.o
+BUILTIN_OBJS += $(OUTPUT)builtin-kvm.o
+BUILTIN_OBJS += $(OUTPUT)builtin-inject.o
+BUILTIN_OBJS += $(OUTPUT)tests/builtin-test.o
+BUILTIN_OBJS += $(OUTPUT)builtin-mem.o
+
+PERFLIBS = $(LIB_FILE) $(LIBAPIKFS) $(LIBTRACEEVENT)
+
+# We choose to avoid "if .. else if .. else .. endif endif"
+# because maintaining the nesting to match is a pain. If
+# we had "elif" things would have been much nicer...
+
+-include arch/$(ARCH)/Makefile
+
+ifneq ($(OUTPUT),)
+ CFLAGS += -I$(OUTPUT)
+endif
+
+ifdef NO_LIBELF
+EXTLIBS := $(filter-out -lelf,$(EXTLIBS))
+
+# Remove ELF/DWARF dependent codes
+LIB_OBJS := $(filter-out $(OUTPUT)util/symbol-elf.o,$(LIB_OBJS))
+LIB_OBJS := $(filter-out $(OUTPUT)util/dwarf-aux.o,$(LIB_OBJS))
+LIB_OBJS := $(filter-out $(OUTPUT)util/probe-event.o,$(LIB_OBJS))
+LIB_OBJS := $(filter-out $(OUTPUT)util/probe-finder.o,$(LIB_OBJS))
+
+BUILTIN_OBJS := $(filter-out $(OUTPUT)builtin-probe.o,$(BUILTIN_OBJS))
+
+# Use minimal symbol handling
+LIB_OBJS += $(OUTPUT)util/symbol-minimal.o
+
+else # NO_LIBELF
+ifndef NO_DWARF
+ LIB_OBJS += $(OUTPUT)util/probe-finder.o
+ LIB_OBJS += $(OUTPUT)util/dwarf-aux.o
+endif # NO_DWARF
+endif # NO_LIBELF
+
+ifndef NO_LIBDW_DWARF_UNWIND
+ LIB_OBJS += $(OUTPUT)util/unwind-libdw.o
+ LIB_H += util/unwind-libdw.h
+endif
+
+ifndef NO_LIBUNWIND
+ LIB_OBJS += $(OUTPUT)util/unwind-libunwind.o
+endif
+LIB_OBJS += $(OUTPUT)tests/keep-tracking.o
+
+ifndef NO_LIBAUDIT
+ BUILTIN_OBJS += $(OUTPUT)builtin-trace.o
+endif
+
+ifndef NO_SLANG
+ LIB_OBJS += $(OUTPUT)ui/browser.o
+ LIB_OBJS += $(OUTPUT)ui/browsers/annotate.o
+ LIB_OBJS += $(OUTPUT)ui/browsers/hists.o
+ LIB_OBJS += $(OUTPUT)ui/browsers/map.o
+ LIB_OBJS += $(OUTPUT)ui/browsers/scripts.o
+ LIB_OBJS += $(OUTPUT)ui/browsers/header.o
+ LIB_OBJS += $(OUTPUT)ui/tui/setup.o
+ LIB_OBJS += $(OUTPUT)ui/tui/util.o
+ LIB_OBJS += $(OUTPUT)ui/tui/helpline.o
+ LIB_OBJS += $(OUTPUT)ui/tui/progress.o
+ LIB_H += ui/tui/tui.h
+ LIB_H += ui/browser.h
+ LIB_H += ui/browsers/map.h
+ LIB_H += ui/keysyms.h
+ LIB_H += ui/libslang.h
+endif
+
+ifndef NO_GTK2
+ ALL_PROGRAMS += $(OUTPUT)libperf-gtk.so
+
+ GTK_OBJS += $(OUTPUT)ui/gtk/browser.o
+ GTK_OBJS += $(OUTPUT)ui/gtk/hists.o
+ GTK_OBJS += $(OUTPUT)ui/gtk/setup.o
+ GTK_OBJS += $(OUTPUT)ui/gtk/util.o
+ GTK_OBJS += $(OUTPUT)ui/gtk/helpline.o
+ GTK_OBJS += $(OUTPUT)ui/gtk/progress.o
+ GTK_OBJS += $(OUTPUT)ui/gtk/annotate.o
+
+install-gtk: $(OUTPUT)libperf-gtk.so
+ $(call QUIET_INSTALL, 'GTK UI') \
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(libdir_SQ)'; \
+ $(INSTALL) $(OUTPUT)libperf-gtk.so '$(DESTDIR_SQ)$(libdir_SQ)'
+endif
+
+ifndef NO_LIBPERL
+ LIB_OBJS += $(OUTPUT)util/scripting-engines/trace-event-perl.o
+ LIB_OBJS += $(OUTPUT)scripts/perl/Perf-Trace-Util/Context.o
+endif
+
+ifndef NO_LIBPYTHON
+ LIB_OBJS += $(OUTPUT)util/scripting-engines/trace-event-python.o
+ LIB_OBJS += $(OUTPUT)scripts/python/Perf-Trace-Util/Context.o
+endif
+
+ifeq ($(NO_PERF_REGS),0)
+ ifeq ($(ARCH),x86)
+ LIB_H += arch/x86/include/perf_regs.h
+ endif
+ LIB_OBJS += $(OUTPUT)util/perf_regs.o
+endif
+
+ifndef NO_LIBNUMA
+ BUILTIN_OBJS += $(OUTPUT)bench/numa.o
+endif
+
+ifdef ASCIIDOC8
+ export ASCIIDOC8
+endif
+
+LIBS = -Wl,--whole-archive $(PERFLIBS) -Wl,--no-whole-archive -Wl,--start-group $(EXTLIBS) -Wl,--end-group
+
+export INSTALL SHELL_PATH
+
+### Build rules
+
+SHELL = $(SHELL_PATH)
+
+all: shell_compatibility_test $(ALL_PROGRAMS) $(LANG_BINDINGS) $(OTHER_PROGRAMS)
+
+please_set_SHELL_PATH_to_a_more_modern_shell:
+ @$$(:)
+
+shell_compatibility_test: please_set_SHELL_PATH_to_a_more_modern_shell
+
+strip: $(PROGRAMS) $(OUTPUT)perf
+ $(STRIP) $(STRIP_OPTS) $(PROGRAMS) $(OUTPUT)perf
+
+$(OUTPUT)perf.o: perf.c $(OUTPUT)common-cmds.h $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -include $(OUTPUT)PERF-VERSION-FILE \
+ '-DPERF_HTML_PATH="$(htmldir_SQ)"' \
+ $(CFLAGS) -c $(filter %.c,$^) -o $@
+
+$(OUTPUT)perf: $(OUTPUT)perf.o $(BUILTIN_OBJS) $(PERFLIBS)
+ $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $(OUTPUT)perf.o \
+ $(BUILTIN_OBJS) $(LIBS) -o $@
+
+$(GTK_OBJS): $(OUTPUT)%.o: %.c $(LIB_H)
+ $(QUIET_CC)$(CC) -o $@ -c -fPIC $(CFLAGS) $(GTK_CFLAGS) $<
+
+$(OUTPUT)libperf-gtk.so: $(GTK_OBJS) $(PERFLIBS)
+ $(QUIET_LINK)$(CC) -o $@ -shared $(LDFLAGS) $(filter %.o,$^) $(GTK_LIBS)
+
+$(OUTPUT)builtin-help.o: builtin-help.c $(OUTPUT)common-cmds.h $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) \
+ '-DPERF_HTML_PATH="$(htmldir_SQ)"' \
+ '-DPERF_MAN_PATH="$(mandir_SQ)"' \
+ '-DPERF_INFO_PATH="$(infodir_SQ)"' $<
+
+$(OUTPUT)builtin-timechart.o: builtin-timechart.c $(OUTPUT)common-cmds.h $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) \
+ '-DPERF_HTML_PATH="$(htmldir_SQ)"' \
+ '-DPERF_MAN_PATH="$(mandir_SQ)"' \
+ '-DPERF_INFO_PATH="$(infodir_SQ)"' $<
+
+$(OUTPUT)common-cmds.h: util/generate-cmdlist.sh command-list.txt
+
+$(OUTPUT)common-cmds.h: $(wildcard Documentation/perf-*.txt)
+ $(QUIET_GEN). util/generate-cmdlist.sh > $@+ && mv $@+ $@
+
+$(SCRIPTS) : % : %.sh
+ $(QUIET_GEN)$(INSTALL) '$@.sh' '$(OUTPUT)$@'
+
+# These can record PERF_VERSION
+$(OUTPUT)perf.o perf.spec \
+ $(SCRIPTS) \
+ : $(OUTPUT)PERF-VERSION-FILE
+
+.SUFFIXES:
+
+#
+# If a target does not match any of the later rules then prefix it by $(OUTPUT)
+# This makes targets like 'make O=/tmp/perf perf.o' work in a natural way.
+#
+ifneq ($(OUTPUT),)
+%.o: $(OUTPUT)%.o
+ @echo " # Redirected target $@ => $(OUTPUT)$@"
+util/%.o: $(OUTPUT)util/%.o
+ @echo " # Redirected target $@ => $(OUTPUT)$@"
+bench/%.o: $(OUTPUT)bench/%.o
+ @echo " # Redirected target $@ => $(OUTPUT)$@"
+tests/%.o: $(OUTPUT)tests/%.o
+ @echo " # Redirected target $@ => $(OUTPUT)$@"
+endif
+
+# These two need to be here so that when O= is not used they take precedence
+# over the general rule for .o
+
+$(OUTPUT)util/%-flex.o: $(OUTPUT)util/%-flex.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c -Iutil/ $(CFLAGS) -w $<
+
+$(OUTPUT)util/%-bison.o: $(OUTPUT)util/%-bison.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c -Iutil/ $(CFLAGS) -DYYENABLE_NLS=0 -DYYLTYPE_IS_TRIVIAL=0 -w $<
+
+$(OUTPUT)%.o: %.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) $<
+$(OUTPUT)%.i: %.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -E $(CFLAGS) $<
+$(OUTPUT)%.s: %.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -S $(CFLAGS) $<
+$(OUTPUT)%.o: %.S
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) $<
+$(OUTPUT)%.s: %.S
+ $(QUIET_CC)$(CC) -o $@ -E $(CFLAGS) $<
+
+$(OUTPUT)util/exec_cmd.o: util/exec_cmd.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) \
+ '-DPERF_EXEC_PATH="$(perfexecdir_SQ)"' \
+ '-DPREFIX="$(prefix_SQ)"' \
+ $<
+
+$(OUTPUT)tests/attr.o: tests/attr.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) \
+ '-DBINDIR="$(bindir_SQ)"' -DPYTHON='"$(PYTHON_WORD)"' \
+ $<
+
+$(OUTPUT)tests/python-use.o: tests/python-use.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) \
+ -DPYTHONPATH='"$(OUTPUT)python"' \
+ -DPYTHON='"$(PYTHON_WORD)"' \
+ $<
+
+$(OUTPUT)tests/dwarf-unwind.o: tests/dwarf-unwind.c
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) -fno-optimize-sibling-calls $<
+
+$(OUTPUT)util/config.o: util/config.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) -DETC_PERFCONFIG='"$(ETC_PERFCONFIG_SQ)"' $<
+
+$(OUTPUT)ui/setup.o: ui/setup.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) -DLIBDIR='"$(libdir_SQ)"' $<
+
+$(OUTPUT)ui/browser.o: ui/browser.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) -DENABLE_SLFUTURE_CONST $<
+
+$(OUTPUT)ui/browsers/annotate.o: ui/browsers/annotate.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) -DENABLE_SLFUTURE_CONST $<
+
+$(OUTPUT)ui/browsers/hists.o: ui/browsers/hists.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) -DENABLE_SLFUTURE_CONST $<
+
+$(OUTPUT)ui/browsers/map.o: ui/browsers/map.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) -DENABLE_SLFUTURE_CONST $<
+
+$(OUTPUT)ui/browsers/scripts.o: ui/browsers/scripts.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) -DENABLE_SLFUTURE_CONST $<
+
+$(OUTPUT)util/kallsyms.o: ../lib/symbol/kallsyms.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) $<
+
+$(OUTPUT)util/rbtree.o: ../../lib/rbtree.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) -Wno-unused-parameter -DETC_PERFCONFIG='"$(ETC_PERFCONFIG_SQ)"' $<
+
+$(OUTPUT)util/parse-events.o: util/parse-events.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) -Wno-redundant-decls $<
+
+$(OUTPUT)util/scripting-engines/trace-event-perl.o: util/scripting-engines/trace-event-perl.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) $(PERL_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-unused-parameter -Wno-shadow -Wno-undef -Wno-switch-default $<
+
+$(OUTPUT)scripts/perl/Perf-Trace-Util/Context.o: scripts/perl/Perf-Trace-Util/Context.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) $(PERL_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-unused-parameter -Wno-nested-externs -Wno-undef -Wno-switch-default $<
+
+$(OUTPUT)util/scripting-engines/trace-event-python.o: util/scripting-engines/trace-event-python.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) $(PYTHON_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-unused-parameter -Wno-shadow $<
+
+$(OUTPUT)scripts/python/Perf-Trace-Util/Context.o: scripts/python/Perf-Trace-Util/Context.c $(OUTPUT)PERF-CFLAGS
+ $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) $(PYTHON_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-unused-parameter -Wno-nested-externs $<
+
+$(OUTPUT)perf-%: %.o $(PERFLIBS)
+ $(QUIET_LINK)$(CC) $(CFLAGS) -o $@ $(LDFLAGS) $(filter %.o,$^) $(LIBS)
+
+$(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)
+$(patsubst perf-%,%.o,$(PROGRAMS)): $(LIB_H) $(wildcard */*.h)
+
+# we compile into subdirectories. if the target directory is not the source directory, they might not exists. So
+# we depend the various files onto their directories.
+DIRECTORY_DEPS = $(LIB_OBJS) $(BUILTIN_OBJS) $(GTK_OBJS)
+DIRECTORY_DEPS += $(OUTPUT)PERF-VERSION-FILE $(OUTPUT)common-cmds.h
+# no need to add flex objects, because they depend on bison ones
+DIRECTORY_DEPS += $(OUTPUT)util/parse-events-bison.c
+DIRECTORY_DEPS += $(OUTPUT)util/pmu-bison.c
+
+OUTPUT_DIRECTORIES := $(sort $(dir $(DIRECTORY_DEPS)))
+
+$(DIRECTORY_DEPS): | $(OUTPUT_DIRECTORIES)
+# In the second step, we make a rule to actually create these directories
+$(OUTPUT_DIRECTORIES):
+ $(QUIET_MKDIR)$(MKDIR) -p $@ 2>/dev/null
+
+$(LIB_FILE): $(LIB_OBJS)
+ $(QUIET_AR)$(RM) $@ && $(AR) rcs $@ $(LIB_OBJS)
+
+# libtraceevent.a
+TE_SOURCES = $(wildcard $(TRACE_EVENT_DIR)*.[ch])
+
+LIBTRACEEVENT_FLAGS = $(QUIET_SUBDIR1) O=$(OUTPUT)
+LIBTRACEEVENT_FLAGS += CFLAGS="-g -Wall $(EXTRA_CFLAGS)"
+LIBTRACEEVENT_FLAGS += plugin_dir=$(plugindir_SQ)
+
+$(LIBTRACEEVENT): $(TE_SOURCES) $(OUTPUT)PERF-CFLAGS
+ $(QUIET_SUBDIR0)$(TRACE_EVENT_DIR) $(LIBTRACEEVENT_FLAGS) libtraceevent.a plugins
+
+$(LIBTRACEEVENT)-clean:
+ $(call QUIET_CLEAN, libtraceevent)
+ @$(MAKE) -C $(TRACE_EVENT_DIR) O=$(OUTPUT) clean >/dev/null
+
+install-traceevent-plugins: $(LIBTRACEEVENT)
+ $(QUIET_SUBDIR0)$(TRACE_EVENT_DIR) $(LIBTRACEEVENT_FLAGS) install_plugins
+
+LIBAPIKFS_SOURCES = $(wildcard $(LIB_PATH)fs/*.[ch])
+
+# if subdir is set, we've been called from above so target has been built
+# already
+$(LIBAPIKFS): $(LIBAPIKFS_SOURCES)
+ifeq ($(subdir),)
+ $(QUIET_SUBDIR0)$(LIB_DIR) $(QUIET_SUBDIR1) O=$(OUTPUT) libapikfs.a
+endif
+
+$(LIBAPIKFS)-clean:
+ifeq ($(subdir),)
+ $(call QUIET_CLEAN, libapikfs)
+ @$(MAKE) -C $(LIB_DIR) O=$(OUTPUT) clean >/dev/null
+endif
+
+help:
+ @echo 'Perf make targets:'
+ @echo ' doc - make *all* documentation (see below)'
+ @echo ' man - make manpage documentation (access with man <foo>)'
+ @echo ' html - make html documentation'
+ @echo ' info - make GNU info documentation (access with info <foo>)'
+ @echo ' pdf - make pdf documentation'
+ @echo ' TAGS - use etags to make tag information for source browsing'
+ @echo ' tags - use ctags to make tag information for source browsing'
+ @echo ' cscope - use cscope to make interactive browsing database'
+ @echo ''
+ @echo 'Perf install targets:'
+ @echo ' NOTE: documentation build requires asciidoc, xmlto packages to be installed'
+ @echo ' HINT: use "prefix" or "DESTDIR" to install to a particular'
+ @echo ' path like "make prefix=/usr/local install install-doc"'
+ @echo ' install - install compiled binaries'
+ @echo ' install-doc - install *all* documentation'
+ @echo ' install-man - install manpage documentation'
+ @echo ' install-html - install html documentation'
+ @echo ' install-info - install GNU info documentation'
+ @echo ' install-pdf - install pdf documentation'
+ @echo ''
+ @echo ' quick-install-doc - alias for quick-install-man'
+ @echo ' quick-install-man - install the documentation quickly'
+ @echo ' quick-install-html - install the html documentation quickly'
+ @echo ''
+ @echo 'Perf maintainer targets:'
+ @echo ' clean - clean all binary objects and build output'
+
+
+DOC_TARGETS := doc man html info pdf
+
+INSTALL_DOC_TARGETS := $(patsubst %,install-%,$(DOC_TARGETS)) try-install-man
+INSTALL_DOC_TARGETS += quick-install-doc quick-install-man quick-install-html
+
+# 'make doc' should call 'make -C Documentation all'
+$(DOC_TARGETS):
+ $(QUIET_SUBDIR0)Documentation $(QUIET_SUBDIR1) $(@:doc=all)
+
+TAG_FOLDERS= . ../lib/traceevent ../lib/api ../lib/symbol
+TAG_FILES= ../../include/uapi/linux/perf_event.h
+
+TAGS:
+ $(QUIET_GEN)$(RM) TAGS; \
+ $(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs etags -a $(TAG_FILES)
+
+tags:
+ $(QUIET_GEN)$(RM) tags; \
+ $(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs ctags -a $(TAG_FILES)
+
+cscope:
+ $(QUIET_GEN)$(RM) cscope*; \
+ $(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs cscope -b $(TAG_FILES)
+
+### Detect prefix changes
+TRACK_CFLAGS = $(subst ','\'',$(CFLAGS)):\
+ $(bindir_SQ):$(perfexecdir_SQ):$(template_dir_SQ):$(prefix_SQ):$(plugindir_SQ)
+
+$(OUTPUT)PERF-CFLAGS: .FORCE-PERF-CFLAGS
+ @FLAGS='$(TRACK_CFLAGS)'; \
+ if test x"$$FLAGS" != x"`cat $(OUTPUT)PERF-CFLAGS 2>/dev/null`" ; then \
+ echo 1>&2 " FLAGS: * new build flags or prefix"; \
+ echo "$$FLAGS" >$(OUTPUT)PERF-CFLAGS; \
+ fi
+
+### Testing rules
+
+# GNU make supports exporting all variables by "export" without parameters.
+# However, the environment gets quite big, and some programs have problems
+# with that.
+
+check: $(OUTPUT)common-cmds.h
+ if sparse; \
+ then \
+ for i in *.c */*.c; \
+ do \
+ sparse $(CFLAGS) $(SPARSE_FLAGS) $$i || exit; \
+ done; \
+ else \
+ exit 1; \
+ fi
+
+### Installation rules
+
+install-gtk:
+
+install-bin: all install-gtk
+ $(call QUIET_INSTALL, binaries) \
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)'; \
+ $(INSTALL) $(OUTPUT)perf '$(DESTDIR_SQ)$(bindir_SQ)'; \
+ $(LN) '$(DESTDIR_SQ)$(bindir_SQ)/perf' '$(DESTDIR_SQ)$(bindir_SQ)/trace'
+ $(call QUIET_INSTALL, libexec) \
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)'
+ $(call QUIET_INSTALL, perf-archive) \
+ $(INSTALL) $(OUTPUT)perf-archive -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)'
+ifndef NO_LIBPERL
+ $(call QUIET_INSTALL, perl-scripts) \
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/Perf-Trace-Util/lib/Perf/Trace'; \
+ $(INSTALL) scripts/perl/Perf-Trace-Util/lib/Perf/Trace/* -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/Perf-Trace-Util/lib/Perf/Trace'; \
+ $(INSTALL) scripts/perl/*.pl -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl'; \
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/bin'; \
+ $(INSTALL) scripts/perl/bin/* -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/bin'
+endif
+ifndef NO_LIBPYTHON
+ $(call QUIET_INSTALL, python-scripts) \
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/python/Perf-Trace-Util/lib/Perf/Trace'; \
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/python/bin'; \
+ $(INSTALL) scripts/python/Perf-Trace-Util/lib/Perf/Trace/* -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/python/Perf-Trace-Util/lib/Perf/Trace'; \
+ $(INSTALL) scripts/python/*.py -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/python'; \
+ $(INSTALL) scripts/python/bin/* -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/python/bin'
+endif
+ $(call QUIET_INSTALL, perf_completion-script) \
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(sysconfdir_SQ)/bash_completion.d'; \
+ $(INSTALL) perf-completion.sh '$(DESTDIR_SQ)$(sysconfdir_SQ)/bash_completion.d/perf'
+ $(call QUIET_INSTALL, tests) \
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/tests'; \
+ $(INSTALL) tests/attr.py '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/tests'; \
+ $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/tests/attr'; \
+ $(INSTALL) tests/attr/* '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/tests/attr'
+
+install: install-bin try-install-man install-traceevent-plugins
+
+install-python_ext:
+ $(PYTHON_WORD) util/setup.py --quiet install --root='/$(DESTDIR_SQ)'
+
+# 'make install-doc' should call 'make -C Documentation install'
+$(INSTALL_DOC_TARGETS):
+ $(QUIET_SUBDIR0)Documentation $(QUIET_SUBDIR1) $(@:-doc=)
+
+### Cleaning rules
+
+#
+# This is here, not in config/Makefile, because config/Makefile does
+# not get included for the clean target:
+#
+config-clean:
+ $(call QUIET_CLEAN, config)
+ @$(MAKE) -C config/feature-checks clean >/dev/null
+
+clean: $(LIBTRACEEVENT)-clean $(LIBAPIKFS)-clean config-clean
+ $(call QUIET_CLEAN, core-objs) $(RM) $(LIB_OBJS) $(BUILTIN_OBJS) $(LIB_FILE) $(OUTPUT)perf-archive $(OUTPUT)perf.o $(LANG_BINDINGS) $(GTK_OBJS)
+ $(call QUIET_CLEAN, core-progs) $(RM) $(ALL_PROGRAMS) perf
+ $(call QUIET_CLEAN, core-gen) $(RM) *.spec *.pyc *.pyo */*.pyc */*.pyo $(OUTPUT)common-cmds.h TAGS tags cscope* $(OUTPUT)PERF-VERSION-FILE $(OUTPUT)PERF-CFLAGS $(OUTPUT)PERF-FEATURES $(OUTPUT)util/*-bison* $(OUTPUT)util/*-flex*
+ $(QUIET_SUBDIR0)Documentation $(QUIET_SUBDIR1) clean
+ $(python-clean)
+
+#
+# Trick: if ../../.git does not exist - we are building out of tree for example,
+# then force version regeneration:
+#
+ifeq ($(wildcard ../../.git/HEAD),)
+ GIT-HEAD-PHONY = ../../.git/HEAD
+else
+ GIT-HEAD-PHONY =
+endif
+
+.PHONY: all install clean config-clean strip install-gtk
+.PHONY: shell_compatibility_test please_set_SHELL_PATH_to_a_more_modern_shell
+.PHONY: $(GIT-HEAD-PHONY) TAGS tags cscope .FORCE-PERF-CFLAGS
+
diff --git a/tools/perf/arch/arm/Makefile b/tools/perf/arch/arm/Makefile
new file mode 100644
index 00000000000..09d62153d38
--- /dev/null
+++ b/tools/perf/arch/arm/Makefile
@@ -0,0 +1,14 @@
+ifndef NO_DWARF
+PERF_HAVE_DWARF_REGS := 1
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o
+endif
+ifndef NO_LIBUNWIND
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/unwind-libunwind.o
+endif
+ifndef NO_LIBDW_DWARF_UNWIND
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/unwind-libdw.o
+endif
+ifndef NO_DWARF_UNWIND
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/tests/regs_load.o
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/tests/dwarf-unwind.o
+endif
diff --git a/tools/perf/arch/arm/include/perf_regs.h b/tools/perf/arch/arm/include/perf_regs.h
new file mode 100644
index 00000000000..f619c9c5a4b
--- /dev/null
+++ b/tools/perf/arch/arm/include/perf_regs.h
@@ -0,0 +1,59 @@
+#ifndef ARCH_PERF_REGS_H
+#define ARCH_PERF_REGS_H
+
+#include <stdlib.h>
+#include <linux/types.h>
+#include <asm/perf_regs.h>
+
+void perf_regs_load(u64 *regs);
+
+#define PERF_REGS_MASK ((1ULL << PERF_REG_ARM_MAX) - 1)
+#define PERF_REGS_MAX PERF_REG_ARM_MAX
+#define PERF_SAMPLE_REGS_ABI PERF_SAMPLE_REGS_ABI_32
+
+#define PERF_REG_IP PERF_REG_ARM_PC
+#define PERF_REG_SP PERF_REG_ARM_SP
+
+static inline const char *perf_reg_name(int id)
+{
+ switch (id) {
+ case PERF_REG_ARM_R0:
+ return "r0";
+ case PERF_REG_ARM_R1:
+ return "r1";
+ case PERF_REG_ARM_R2:
+ return "r2";
+ case PERF_REG_ARM_R3:
+ return "r3";
+ case PERF_REG_ARM_R4:
+ return "r4";
+ case PERF_REG_ARM_R5:
+ return "r5";
+ case PERF_REG_ARM_R6:
+ return "r6";
+ case PERF_REG_ARM_R7:
+ return "r7";
+ case PERF_REG_ARM_R8:
+ return "r8";
+ case PERF_REG_ARM_R9:
+ return "r9";
+ case PERF_REG_ARM_R10:
+ return "r10";
+ case PERF_REG_ARM_FP:
+ return "fp";
+ case PERF_REG_ARM_IP:
+ return "ip";
+ case PERF_REG_ARM_SP:
+ return "sp";
+ case PERF_REG_ARM_LR:
+ return "lr";
+ case PERF_REG_ARM_PC:
+ return "pc";
+ default:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+#endif /* ARCH_PERF_REGS_H */
diff --git a/tools/perf/arch/arm/tests/dwarf-unwind.c b/tools/perf/arch/arm/tests/dwarf-unwind.c
new file mode 100644
index 00000000000..9f870d27cb3
--- /dev/null
+++ b/tools/perf/arch/arm/tests/dwarf-unwind.c
@@ -0,0 +1,60 @@
+#include <string.h>
+#include "perf_regs.h"
+#include "thread.h"
+#include "map.h"
+#include "event.h"
+#include "tests/tests.h"
+
+#define STACK_SIZE 8192
+
+static int sample_ustack(struct perf_sample *sample,
+ struct thread *thread, u64 *regs)
+{
+ struct stack_dump *stack = &sample->user_stack;
+ struct map *map;
+ unsigned long sp;
+ u64 stack_size, *buf;
+
+ buf = malloc(STACK_SIZE);
+ if (!buf) {
+ pr_debug("failed to allocate sample uregs data\n");
+ return -1;
+ }
+
+ sp = (unsigned long) regs[PERF_REG_ARM_SP];
+
+ map = map_groups__find(thread->mg, MAP__VARIABLE, (u64) sp);
+ if (!map) {
+ pr_debug("failed to get stack map\n");
+ free(buf);
+ return -1;
+ }
+
+ stack_size = map->end - sp;
+ stack_size = stack_size > STACK_SIZE ? STACK_SIZE : stack_size;
+
+ memcpy(buf, (void *) sp, stack_size);
+ stack->data = (char *) buf;
+ stack->size = stack_size;
+ return 0;
+}
+
+int test__arch_unwind_sample(struct perf_sample *sample,
+ struct thread *thread)
+{
+ struct regs_dump *regs = &sample->user_regs;
+ u64 *buf;
+
+ buf = calloc(1, sizeof(u64) * PERF_REGS_MAX);
+ if (!buf) {
+ pr_debug("failed to allocate sample uregs data\n");
+ return -1;
+ }
+
+ perf_regs_load(buf);
+ regs->abi = PERF_SAMPLE_REGS_ABI;
+ regs->regs = buf;
+ regs->mask = PERF_REGS_MASK;
+
+ return sample_ustack(sample, thread, buf);
+}
diff --git a/tools/perf/arch/arm/tests/regs_load.S b/tools/perf/arch/arm/tests/regs_load.S
new file mode 100644
index 00000000000..e09e983946f
--- /dev/null
+++ b/tools/perf/arch/arm/tests/regs_load.S
@@ -0,0 +1,58 @@
+#include <linux/linkage.h>
+
+#define R0 0x00
+#define R1 0x08
+#define R2 0x10
+#define R3 0x18
+#define R4 0x20
+#define R5 0x28
+#define R6 0x30
+#define R7 0x38
+#define R8 0x40
+#define R9 0x48
+#define SL 0x50
+#define FP 0x58
+#define IP 0x60
+#define SP 0x68
+#define LR 0x70
+#define PC 0x78
+
+/*
+ * Implementation of void perf_regs_load(u64 *regs);
+ *
+ * This functions fills in the 'regs' buffer from the actual registers values,
+ * in the way the perf built-in unwinding test expects them:
+ * - the PC at the time at the call to this function. Since this function
+ * is called using a bl instruction, the PC value is taken from LR.
+ * The built-in unwinding test then unwinds the call stack from the dwarf
+ * information in unwind__get_entries.
+ *
+ * Notes:
+ * - the 8 bytes stride in the registers offsets comes from the fact
+ * that the registers are stored in an u64 array (u64 *regs),
+ * - the regs buffer needs to be zeroed before the call to this function,
+ * in this case using a calloc in dwarf-unwind.c.
+ */
+
+.text
+.type perf_regs_load,%function
+ENTRY(perf_regs_load)
+ str r0, [r0, #R0]
+ str r1, [r0, #R1]
+ str r2, [r0, #R2]
+ str r3, [r0, #R3]
+ str r4, [r0, #R4]
+ str r5, [r0, #R5]
+ str r6, [r0, #R6]
+ str r7, [r0, #R7]
+ str r8, [r0, #R8]
+ str r9, [r0, #R9]
+ str sl, [r0, #SL]
+ str fp, [r0, #FP]
+ str ip, [r0, #IP]
+ str sp, [r0, #SP]
+ str lr, [r0, #LR]
+ str lr, [r0, #PC] // store pc as lr in order to skip the call
+ // to this function
+ mov pc, lr
+ENDPROC(perf_regs_load)
diff --git a/tools/perf/arch/arm/util/dwarf-regs.c b/tools/perf/arch/arm/util/dwarf-regs.c
new file mode 100644
index 00000000000..33ec5b339da
--- /dev/null
+++ b/tools/perf/arch/arm/util/dwarf-regs.c
@@ -0,0 +1,64 @@
+/*
+ * Mapping of DWARF debug register numbers into register names.
+ *
+ * Copyright (C) 2010 Will Deacon, ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <stddef.h>
+#include <dwarf-regs.h>
+
+struct pt_regs_dwarfnum {
+ const char *name;
+ unsigned int dwarfnum;
+};
+
+#define STR(s) #s
+#define REG_DWARFNUM_NAME(r, num) {.name = r, .dwarfnum = num}
+#define GPR_DWARFNUM_NAME(num) \
+ {.name = STR(%r##num), .dwarfnum = num}
+#define REG_DWARFNUM_END {.name = NULL, .dwarfnum = 0}
+
+/*
+ * Reference:
+ * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0040a/IHI0040A_aadwarf.pdf
+ */
+static const struct pt_regs_dwarfnum regdwarfnum_table[] = {
+ GPR_DWARFNUM_NAME(0),
+ GPR_DWARFNUM_NAME(1),
+ GPR_DWARFNUM_NAME(2),
+ GPR_DWARFNUM_NAME(3),
+ GPR_DWARFNUM_NAME(4),
+ GPR_DWARFNUM_NAME(5),
+ GPR_DWARFNUM_NAME(6),
+ GPR_DWARFNUM_NAME(7),
+ GPR_DWARFNUM_NAME(8),
+ GPR_DWARFNUM_NAME(9),
+ GPR_DWARFNUM_NAME(10),
+ REG_DWARFNUM_NAME("%fp", 11),
+ REG_DWARFNUM_NAME("%ip", 12),
+ REG_DWARFNUM_NAME("%sp", 13),
+ REG_DWARFNUM_NAME("%lr", 14),
+ REG_DWARFNUM_NAME("%pc", 15),
+ REG_DWARFNUM_END,
+};
+
+/**
+ * get_arch_regstr() - lookup register name from it's DWARF register number
+ * @n: the DWARF register number
+ *
+ * get_arch_regstr() returns the name of the register in struct
+ * regdwarfnum_table from it's DWARF register number. If the register is not
+ * found in the table, this returns NULL;
+ */
+const char *get_arch_regstr(unsigned int n)
+{
+ const struct pt_regs_dwarfnum *roff;
+ for (roff = regdwarfnum_table; roff->name != NULL; roff++)
+ if (roff->dwarfnum == n)
+ return roff->name;
+ return NULL;
+}
diff --git a/tools/perf/arch/arm/util/unwind-libdw.c b/tools/perf/arch/arm/util/unwind-libdw.c
new file mode 100644
index 00000000000..b4176c60117
--- /dev/null
+++ b/tools/perf/arch/arm/util/unwind-libdw.c
@@ -0,0 +1,36 @@
+#include <elfutils/libdwfl.h>
+#include "../../util/unwind-libdw.h"
+#include "../../util/perf_regs.h"
+
+bool libdw__arch_set_initial_registers(Dwfl_Thread *thread, void *arg)
+{
+ struct unwind_info *ui = arg;
+ struct regs_dump *user_regs = &ui->sample->user_regs;
+ Dwarf_Word dwarf_regs[PERF_REG_ARM_MAX];
+
+#define REG(r) ({ \
+ Dwarf_Word val = 0; \
+ perf_reg_value(&val, user_regs, PERF_REG_ARM_##r); \
+ val; \
+})
+
+ dwarf_regs[0] = REG(R0);
+ dwarf_regs[1] = REG(R1);
+ dwarf_regs[2] = REG(R2);
+ dwarf_regs[3] = REG(R3);
+ dwarf_regs[4] = REG(R4);
+ dwarf_regs[5] = REG(R5);
+ dwarf_regs[6] = REG(R6);
+ dwarf_regs[7] = REG(R7);
+ dwarf_regs[8] = REG(R8);
+ dwarf_regs[9] = REG(R9);
+ dwarf_regs[10] = REG(R10);
+ dwarf_regs[11] = REG(FP);
+ dwarf_regs[12] = REG(IP);
+ dwarf_regs[13] = REG(SP);
+ dwarf_regs[14] = REG(LR);
+ dwarf_regs[15] = REG(PC);
+
+ return dwfl_thread_state_registers(thread, 0, PERF_REG_ARM_MAX,
+ dwarf_regs);
+}
diff --git a/tools/perf/arch/arm/util/unwind-libunwind.c b/tools/perf/arch/arm/util/unwind-libunwind.c
new file mode 100644
index 00000000000..729ed69a666
--- /dev/null
+++ b/tools/perf/arch/arm/util/unwind-libunwind.c
@@ -0,0 +1,48 @@
+
+#include <errno.h>
+#include <libunwind.h>
+#include "perf_regs.h"
+#include "../../util/unwind.h"
+
+int libunwind__arch_reg_id(int regnum)
+{
+ switch (regnum) {
+ case UNW_ARM_R0:
+ return PERF_REG_ARM_R0;
+ case UNW_ARM_R1:
+ return PERF_REG_ARM_R1;
+ case UNW_ARM_R2:
+ return PERF_REG_ARM_R2;
+ case UNW_ARM_R3:
+ return PERF_REG_ARM_R3;
+ case UNW_ARM_R4:
+ return PERF_REG_ARM_R4;
+ case UNW_ARM_R5:
+ return PERF_REG_ARM_R5;
+ case UNW_ARM_R6:
+ return PERF_REG_ARM_R6;
+ case UNW_ARM_R7:
+ return PERF_REG_ARM_R7;
+ case UNW_ARM_R8:
+ return PERF_REG_ARM_R8;
+ case UNW_ARM_R9:
+ return PERF_REG_ARM_R9;
+ case UNW_ARM_R10:
+ return PERF_REG_ARM_R10;
+ case UNW_ARM_R11:
+ return PERF_REG_ARM_FP;
+ case UNW_ARM_R12:
+ return PERF_REG_ARM_IP;
+ case UNW_ARM_R13:
+ return PERF_REG_ARM_SP;
+ case UNW_ARM_R14:
+ return PERF_REG_ARM_LR;
+ case UNW_ARM_R15:
+ return PERF_REG_ARM_PC;
+ default:
+ pr_err("unwind: invalid reg id %d\n", regnum);
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
diff --git a/tools/perf/arch/arm64/Makefile b/tools/perf/arch/arm64/Makefile
new file mode 100644
index 00000000000..67e9b3d38e8
--- /dev/null
+++ b/tools/perf/arch/arm64/Makefile
@@ -0,0 +1,7 @@
+ifndef NO_DWARF
+PERF_HAVE_DWARF_REGS := 1
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o
+endif
+ifndef NO_LIBUNWIND
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/unwind-libunwind.o
+endif
diff --git a/tools/perf/arch/arm64/include/perf_regs.h b/tools/perf/arch/arm64/include/perf_regs.h
new file mode 100644
index 00000000000..e9441b9e2a3
--- /dev/null
+++ b/tools/perf/arch/arm64/include/perf_regs.h
@@ -0,0 +1,88 @@
+#ifndef ARCH_PERF_REGS_H
+#define ARCH_PERF_REGS_H
+
+#include <stdlib.h>
+#include <linux/types.h>
+#include <asm/perf_regs.h>
+
+#define PERF_REGS_MASK ((1ULL << PERF_REG_ARM64_MAX) - 1)
+#define PERF_REG_IP PERF_REG_ARM64_PC
+#define PERF_REG_SP PERF_REG_ARM64_SP
+
+static inline const char *perf_reg_name(int id)
+{
+ switch (id) {
+ case PERF_REG_ARM64_X0:
+ return "x0";
+ case PERF_REG_ARM64_X1:
+ return "x1";
+ case PERF_REG_ARM64_X2:
+ return "x2";
+ case PERF_REG_ARM64_X3:
+ return "x3";
+ case PERF_REG_ARM64_X4:
+ return "x4";
+ case PERF_REG_ARM64_X5:
+ return "x5";
+ case PERF_REG_ARM64_X6:
+ return "x6";
+ case PERF_REG_ARM64_X7:
+ return "x7";
+ case PERF_REG_ARM64_X8:
+ return "x8";
+ case PERF_REG_ARM64_X9:
+ return "x9";
+ case PERF_REG_ARM64_X10:
+ return "x10";
+ case PERF_REG_ARM64_X11:
+ return "x11";
+ case PERF_REG_ARM64_X12:
+ return "x12";
+ case PERF_REG_ARM64_X13:
+ return "x13";
+ case PERF_REG_ARM64_X14:
+ return "x14";
+ case PERF_REG_ARM64_X15:
+ return "x15";
+ case PERF_REG_ARM64_X16:
+ return "x16";
+ case PERF_REG_ARM64_X17:
+ return "x17";
+ case PERF_REG_ARM64_X18:
+ return "x18";
+ case PERF_REG_ARM64_X19:
+ return "x19";
+ case PERF_REG_ARM64_X20:
+ return "x20";
+ case PERF_REG_ARM64_X21:
+ return "x21";
+ case PERF_REG_ARM64_X22:
+ return "x22";
+ case PERF_REG_ARM64_X23:
+ return "x23";
+ case PERF_REG_ARM64_X24:
+ return "x24";
+ case PERF_REG_ARM64_X25:
+ return "x25";
+ case PERF_REG_ARM64_X26:
+ return "x26";
+ case PERF_REG_ARM64_X27:
+ return "x27";
+ case PERF_REG_ARM64_X28:
+ return "x28";
+ case PERF_REG_ARM64_X29:
+ return "x29";
+ case PERF_REG_ARM64_SP:
+ return "sp";
+ case PERF_REG_ARM64_LR:
+ return "lr";
+ case PERF_REG_ARM64_PC:
+ return "pc";
+ default:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+#endif /* ARCH_PERF_REGS_H */
diff --git a/tools/perf/arch/arm64/util/dwarf-regs.c b/tools/perf/arch/arm64/util/dwarf-regs.c
new file mode 100644
index 00000000000..d49efeb8172
--- /dev/null
+++ b/tools/perf/arch/arm64/util/dwarf-regs.c
@@ -0,0 +1,80 @@
+/*
+ * Mapping of DWARF debug register numbers into register names.
+ *
+ * Copyright (C) 2010 Will Deacon, ARM Ltd.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ */
+
+#include <stddef.h>
+#include <dwarf-regs.h>
+
+struct pt_regs_dwarfnum {
+ const char *name;
+ unsigned int dwarfnum;
+};
+
+#define STR(s) #s
+#define REG_DWARFNUM_NAME(r, num) {.name = r, .dwarfnum = num}
+#define GPR_DWARFNUM_NAME(num) \
+ {.name = STR(%x##num), .dwarfnum = num}
+#define REG_DWARFNUM_END {.name = NULL, .dwarfnum = 0}
+
+/*
+ * Reference:
+ * http://infocenter.arm.com/help/topic/com.arm.doc.ihi0057b/IHI0057B_aadwarf64.pdf
+ */
+static const struct pt_regs_dwarfnum regdwarfnum_table[] = {
+ GPR_DWARFNUM_NAME(0),
+ GPR_DWARFNUM_NAME(1),
+ GPR_DWARFNUM_NAME(2),
+ GPR_DWARFNUM_NAME(3),
+ GPR_DWARFNUM_NAME(4),
+ GPR_DWARFNUM_NAME(5),
+ GPR_DWARFNUM_NAME(6),
+ GPR_DWARFNUM_NAME(7),
+ GPR_DWARFNUM_NAME(8),
+ GPR_DWARFNUM_NAME(9),
+ GPR_DWARFNUM_NAME(10),
+ GPR_DWARFNUM_NAME(11),
+ GPR_DWARFNUM_NAME(12),
+ GPR_DWARFNUM_NAME(13),
+ GPR_DWARFNUM_NAME(14),
+ GPR_DWARFNUM_NAME(15),
+ GPR_DWARFNUM_NAME(16),
+ GPR_DWARFNUM_NAME(17),
+ GPR_DWARFNUM_NAME(18),
+ GPR_DWARFNUM_NAME(19),
+ GPR_DWARFNUM_NAME(20),
+ GPR_DWARFNUM_NAME(21),
+ GPR_DWARFNUM_NAME(22),
+ GPR_DWARFNUM_NAME(23),
+ GPR_DWARFNUM_NAME(24),
+ GPR_DWARFNUM_NAME(25),
+ GPR_DWARFNUM_NAME(26),
+ GPR_DWARFNUM_NAME(27),
+ GPR_DWARFNUM_NAME(28),
+ GPR_DWARFNUM_NAME(29),
+ REG_DWARFNUM_NAME("%lr", 30),
+ REG_DWARFNUM_NAME("%sp", 31),
+ REG_DWARFNUM_END,
+};
+
+/**
+ * get_arch_regstr() - lookup register name from it's DWARF register number
+ * @n: the DWARF register number
+ *
+ * get_arch_regstr() returns the name of the register in struct
+ * regdwarfnum_table from it's DWARF register number. If the register is not
+ * found in the table, this returns NULL;
+ */
+const char *get_arch_regstr(unsigned int n)
+{
+ const struct pt_regs_dwarfnum *roff;
+ for (roff = regdwarfnum_table; roff->name != NULL; roff++)
+ if (roff->dwarfnum == n)
+ return roff->name;
+ return NULL;
+}
diff --git a/tools/perf/arch/arm64/util/unwind-libunwind.c b/tools/perf/arch/arm64/util/unwind-libunwind.c
new file mode 100644
index 00000000000..436ee43859d
--- /dev/null
+++ b/tools/perf/arch/arm64/util/unwind-libunwind.c
@@ -0,0 +1,82 @@
+
+#include <errno.h>
+#include <libunwind.h>
+#include "perf_regs.h"
+#include "../../util/unwind.h"
+
+int libunwind__arch_reg_id(int regnum)
+{
+ switch (regnum) {
+ case UNW_AARCH64_X0:
+ return PERF_REG_ARM64_X0;
+ case UNW_AARCH64_X1:
+ return PERF_REG_ARM64_X1;
+ case UNW_AARCH64_X2:
+ return PERF_REG_ARM64_X2;
+ case UNW_AARCH64_X3:
+ return PERF_REG_ARM64_X3;
+ case UNW_AARCH64_X4:
+ return PERF_REG_ARM64_X4;
+ case UNW_AARCH64_X5:
+ return PERF_REG_ARM64_X5;
+ case UNW_AARCH64_X6:
+ return PERF_REG_ARM64_X6;
+ case UNW_AARCH64_X7:
+ return PERF_REG_ARM64_X7;
+ case UNW_AARCH64_X8:
+ return PERF_REG_ARM64_X8;
+ case UNW_AARCH64_X9:
+ return PERF_REG_ARM64_X9;
+ case UNW_AARCH64_X10:
+ return PERF_REG_ARM64_X10;
+ case UNW_AARCH64_X11:
+ return PERF_REG_ARM64_X11;
+ case UNW_AARCH64_X12:
+ return PERF_REG_ARM64_X12;
+ case UNW_AARCH64_X13:
+ return PERF_REG_ARM64_X13;
+ case UNW_AARCH64_X14:
+ return PERF_REG_ARM64_X14;
+ case UNW_AARCH64_X15:
+ return PERF_REG_ARM64_X15;
+ case UNW_AARCH64_X16:
+ return PERF_REG_ARM64_X16;
+ case UNW_AARCH64_X17:
+ return PERF_REG_ARM64_X17;
+ case UNW_AARCH64_X18:
+ return PERF_REG_ARM64_X18;
+ case UNW_AARCH64_X19:
+ return PERF_REG_ARM64_X19;
+ case UNW_AARCH64_X20:
+ return PERF_REG_ARM64_X20;
+ case UNW_AARCH64_X21:
+ return PERF_REG_ARM64_X21;
+ case UNW_AARCH64_X22:
+ return PERF_REG_ARM64_X22;
+ case UNW_AARCH64_X23:
+ return PERF_REG_ARM64_X23;
+ case UNW_AARCH64_X24:
+ return PERF_REG_ARM64_X24;
+ case UNW_AARCH64_X25:
+ return PERF_REG_ARM64_X25;
+ case UNW_AARCH64_X26:
+ return PERF_REG_ARM64_X26;
+ case UNW_AARCH64_X27:
+ return PERF_REG_ARM64_X27;
+ case UNW_AARCH64_X28:
+ return PERF_REG_ARM64_X28;
+ case UNW_AARCH64_X29:
+ return PERF_REG_ARM64_X29;
+ case UNW_AARCH64_X30:
+ return PERF_REG_ARM64_LR;
+ case UNW_AARCH64_SP:
+ return PERF_REG_ARM64_SP;
+ case UNW_AARCH64_PC:
+ return PERF_REG_ARM64_PC;
+ default:
+ pr_err("unwind: invalid reg id %d\n", regnum);
+ return -EINVAL;
+ }
+
+ return -EINVAL;
+}
diff --git a/tools/perf/arch/common.c b/tools/perf/arch/common.c
new file mode 100644
index 00000000000..42faf369211
--- /dev/null
+++ b/tools/perf/arch/common.c
@@ -0,0 +1,211 @@
+#include <stdio.h>
+#include <sys/utsname.h>
+#include "common.h"
+#include "../util/debug.h"
+
+const char *const arm_triplets[] = {
+ "arm-eabi-",
+ "arm-linux-androideabi-",
+ "arm-unknown-linux-",
+ "arm-unknown-linux-gnu-",
+ "arm-unknown-linux-gnueabi-",
+ NULL
+};
+
+const char *const powerpc_triplets[] = {
+ "powerpc-unknown-linux-gnu-",
+ "powerpc64-unknown-linux-gnu-",
+ NULL
+};
+
+const char *const s390_triplets[] = {
+ "s390-ibm-linux-",
+ NULL
+};
+
+const char *const sh_triplets[] = {
+ "sh-unknown-linux-gnu-",
+ "sh64-unknown-linux-gnu-",
+ NULL
+};
+
+const char *const sparc_triplets[] = {
+ "sparc-unknown-linux-gnu-",
+ "sparc64-unknown-linux-gnu-",
+ NULL
+};
+
+const char *const x86_triplets[] = {
+ "x86_64-pc-linux-gnu-",
+ "x86_64-unknown-linux-gnu-",
+ "i686-pc-linux-gnu-",
+ "i586-pc-linux-gnu-",
+ "i486-pc-linux-gnu-",
+ "i386-pc-linux-gnu-",
+ "i686-linux-android-",
+ "i686-android-linux-",
+ NULL
+};
+
+const char *const mips_triplets[] = {
+ "mips-unknown-linux-gnu-",
+ "mipsel-linux-android-",
+ NULL
+};
+
+static bool lookup_path(char *name)
+{
+ bool found = false;
+ char *path, *tmp;
+ char buf[PATH_MAX];
+ char *env = getenv("PATH");
+
+ if (!env)
+ return false;
+
+ env = strdup(env);
+ if (!env)
+ return false;
+
+ path = strtok_r(env, ":", &tmp);
+ while (path) {
+ scnprintf(buf, sizeof(buf), "%s/%s", path, name);
+ if (access(buf, F_OK) == 0) {
+ found = true;
+ break;
+ }
+ path = strtok_r(NULL, ":", &tmp);
+ }
+ free(env);
+ return found;
+}
+
+static int lookup_triplets(const char *const *triplets, const char *name)
+{
+ int i;
+ char buf[PATH_MAX];
+
+ for (i = 0; triplets[i] != NULL; i++) {
+ scnprintf(buf, sizeof(buf), "%s%s", triplets[i], name);
+ if (lookup_path(buf))
+ return i;
+ }
+ return -1;
+}
+
+/*
+ * Return architecture name in a normalized form.
+ * The conversion logic comes from the Makefile.
+ */
+static const char *normalize_arch(char *arch)
+{
+ if (!strcmp(arch, "x86_64"))
+ return "x86";
+ if (arch[0] == 'i' && arch[2] == '8' && arch[3] == '6')
+ return "x86";
+ if (!strcmp(arch, "sun4u") || !strncmp(arch, "sparc", 5))
+ return "sparc";
+ if (!strncmp(arch, "arm", 3) || !strcmp(arch, "sa110"))
+ return "arm";
+ if (!strncmp(arch, "s390", 4))
+ return "s390";
+ if (!strncmp(arch, "parisc", 6))
+ return "parisc";
+ if (!strncmp(arch, "powerpc", 7) || !strncmp(arch, "ppc", 3))
+ return "powerpc";
+ if (!strncmp(arch, "mips", 4))
+ return "mips";
+ if (!strncmp(arch, "sh", 2) && isdigit(arch[2]))
+ return "sh";
+
+ return arch;
+}
+
+static int perf_session_env__lookup_binutils_path(struct perf_session_env *env,
+ const char *name,
+ const char **path)
+{
+ int idx;
+ const char *arch, *cross_env;
+ struct utsname uts;
+ const char *const *path_list;
+ char *buf = NULL;
+
+ arch = normalize_arch(env->arch);
+
+ if (uname(&uts) < 0)
+ goto out;
+
+ /*
+ * We don't need to try to find objdump path for native system.
+ * Just use default binutils path (e.g.: "objdump").
+ */
+ if (!strcmp(normalize_arch(uts.machine), arch))
+ goto out;
+
+ cross_env = getenv("CROSS_COMPILE");
+ if (cross_env) {
+ if (asprintf(&buf, "%s%s", cross_env, name) < 0)
+ goto out_error;
+ if (buf[0] == '/') {
+ if (access(buf, F_OK) == 0)
+ goto out;
+ goto out_error;
+ }
+ if (lookup_path(buf))
+ goto out;
+ zfree(&buf);
+ }
+
+ if (!strcmp(arch, "arm"))
+ path_list = arm_triplets;
+ else if (!strcmp(arch, "powerpc"))
+ path_list = powerpc_triplets;
+ else if (!strcmp(arch, "sh"))
+ path_list = sh_triplets;
+ else if (!strcmp(arch, "s390"))
+ path_list = s390_triplets;
+ else if (!strcmp(arch, "sparc"))
+ path_list = sparc_triplets;
+ else if (!strcmp(arch, "x86"))
+ path_list = x86_triplets;
+ else if (!strcmp(arch, "mips"))
+ path_list = mips_triplets;
+ else {
+ ui__error("binutils for %s not supported.\n", arch);
+ goto out_error;
+ }
+
+ idx = lookup_triplets(path_list, name);
+ if (idx < 0) {
+ ui__error("Please install %s for %s.\n"
+ "You can add it to PATH, set CROSS_COMPILE or "
+ "override the default using --%s.\n",
+ name, arch, name);
+ goto out_error;
+ }
+
+ if (asprintf(&buf, "%s%s", path_list[idx], name) < 0)
+ goto out_error;
+
+out:
+ *path = buf;
+ return 0;
+out_error:
+ free(buf);
+ *path = NULL;
+ return -1;
+}
+
+int perf_session_env__lookup_objdump(struct perf_session_env *env)
+{
+ /*
+ * For live mode, env->arch will be NULL and we can use
+ * the native objdump tool.
+ */
+ if (env->arch == NULL)
+ return 0;
+
+ return perf_session_env__lookup_binutils_path(env, "objdump",
+ &objdump_path);
+}
diff --git a/tools/perf/arch/common.h b/tools/perf/arch/common.h
new file mode 100644
index 00000000000..ede246eda9b
--- /dev/null
+++ b/tools/perf/arch/common.h
@@ -0,0 +1,10 @@
+#ifndef ARCH_PERF_COMMON_H
+#define ARCH_PERF_COMMON_H
+
+#include "../util/session.h"
+
+extern const char *objdump_path;
+
+int perf_session_env__lookup_objdump(struct perf_session_env *env);
+
+#endif /* ARCH_PERF_COMMON_H */
diff --git a/tools/perf/arch/powerpc/Makefile b/tools/perf/arch/powerpc/Makefile
new file mode 100644
index 00000000000..744e629797b
--- /dev/null
+++ b/tools/perf/arch/powerpc/Makefile
@@ -0,0 +1,5 @@
+ifndef NO_DWARF
+PERF_HAVE_DWARF_REGS := 1
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o
+endif
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/header.o
diff --git a/tools/perf/arch/powerpc/util/dwarf-regs.c b/tools/perf/arch/powerpc/util/dwarf-regs.c
new file mode 100644
index 00000000000..733151cdf46
--- /dev/null
+++ b/tools/perf/arch/powerpc/util/dwarf-regs.c
@@ -0,0 +1,88 @@
+/*
+ * Mapping of DWARF debug register numbers into register names.
+ *
+ * Copyright (C) 2010 Ian Munsie, IBM Corporation.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <stddef.h>
+#include <dwarf-regs.h>
+
+
+struct pt_regs_dwarfnum {
+ const char *name;
+ unsigned int dwarfnum;
+};
+
+#define STR(s) #s
+#define REG_DWARFNUM_NAME(r, num) {.name = r, .dwarfnum = num}
+#define GPR_DWARFNUM_NAME(num) \
+ {.name = STR(%gpr##num), .dwarfnum = num}
+#define REG_DWARFNUM_END {.name = NULL, .dwarfnum = 0}
+
+/*
+ * Reference:
+ * http://refspecs.linuxfoundation.org/ELF/ppc64/PPC-elf64abi-1.9.html
+ */
+static const struct pt_regs_dwarfnum regdwarfnum_table[] = {
+ GPR_DWARFNUM_NAME(0),
+ GPR_DWARFNUM_NAME(1),
+ GPR_DWARFNUM_NAME(2),
+ GPR_DWARFNUM_NAME(3),
+ GPR_DWARFNUM_NAME(4),
+ GPR_DWARFNUM_NAME(5),
+ GPR_DWARFNUM_NAME(6),
+ GPR_DWARFNUM_NAME(7),
+ GPR_DWARFNUM_NAME(8),
+ GPR_DWARFNUM_NAME(9),
+ GPR_DWARFNUM_NAME(10),
+ GPR_DWARFNUM_NAME(11),
+ GPR_DWARFNUM_NAME(12),
+ GPR_DWARFNUM_NAME(13),
+ GPR_DWARFNUM_NAME(14),
+ GPR_DWARFNUM_NAME(15),
+ GPR_DWARFNUM_NAME(16),
+ GPR_DWARFNUM_NAME(17),
+ GPR_DWARFNUM_NAME(18),
+ GPR_DWARFNUM_NAME(19),
+ GPR_DWARFNUM_NAME(20),
+ GPR_DWARFNUM_NAME(21),
+ GPR_DWARFNUM_NAME(22),
+ GPR_DWARFNUM_NAME(23),
+ GPR_DWARFNUM_NAME(24),
+ GPR_DWARFNUM_NAME(25),
+ GPR_DWARFNUM_NAME(26),
+ GPR_DWARFNUM_NAME(27),
+ GPR_DWARFNUM_NAME(28),
+ GPR_DWARFNUM_NAME(29),
+ GPR_DWARFNUM_NAME(30),
+ GPR_DWARFNUM_NAME(31),
+ REG_DWARFNUM_NAME("%msr", 66),
+ REG_DWARFNUM_NAME("%ctr", 109),
+ REG_DWARFNUM_NAME("%link", 108),
+ REG_DWARFNUM_NAME("%xer", 101),
+ REG_DWARFNUM_NAME("%dar", 119),
+ REG_DWARFNUM_NAME("%dsisr", 118),
+ REG_DWARFNUM_END,
+};
+
+/**
+ * get_arch_regstr() - lookup register name from it's DWARF register number
+ * @n: the DWARF register number
+ *
+ * get_arch_regstr() returns the name of the register in struct
+ * regdwarfnum_table from it's DWARF register number. If the register is not
+ * found in the table, this returns NULL;
+ */
+const char *get_arch_regstr(unsigned int n)
+{
+ const struct pt_regs_dwarfnum *roff;
+ for (roff = regdwarfnum_table; roff->name != NULL; roff++)
+ if (roff->dwarfnum == n)
+ return roff->name;
+ return NULL;
+}
diff --git a/tools/perf/arch/powerpc/util/header.c b/tools/perf/arch/powerpc/util/header.c
new file mode 100644
index 00000000000..2f7073d107f
--- /dev/null
+++ b/tools/perf/arch/powerpc/util/header.c
@@ -0,0 +1,36 @@
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../../util/header.h"
+
+#define __stringify_1(x) #x
+#define __stringify(x) __stringify_1(x)
+
+#define mfspr(rn) ({unsigned long rval; \
+ asm volatile("mfspr %0," __stringify(rn) \
+ : "=r" (rval)); rval; })
+
+#define SPRN_PVR 0x11F /* Processor Version Register */
+#define PVR_VER(pvr) (((pvr) >> 16) & 0xFFFF) /* Version field */
+#define PVR_REV(pvr) (((pvr) >> 0) & 0xFFFF) /* Revison field */
+
+int
+get_cpuid(char *buffer, size_t sz)
+{
+ unsigned long pvr;
+ int nb;
+
+ pvr = mfspr(SPRN_PVR);
+
+ nb = scnprintf(buffer, sz, "%lu,%lu$", PVR_VER(pvr), PVR_REV(pvr));
+
+ /* look for end marker to ensure the entire data fit */
+ if (strchr(buffer, '$')) {
+ buffer[nb-1] = '\0';
+ return 0;
+ }
+ return -1;
+}
diff --git a/tools/perf/arch/s390/Makefile b/tools/perf/arch/s390/Makefile
new file mode 100644
index 00000000000..15130b50dfe
--- /dev/null
+++ b/tools/perf/arch/s390/Makefile
@@ -0,0 +1,4 @@
+ifndef NO_DWARF
+PERF_HAVE_DWARF_REGS := 1
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o
+endif
diff --git a/tools/perf/arch/s390/util/dwarf-regs.c b/tools/perf/arch/s390/util/dwarf-regs.c
new file mode 100644
index 00000000000..0469df02ee6
--- /dev/null
+++ b/tools/perf/arch/s390/util/dwarf-regs.c
@@ -0,0 +1,22 @@
+/*
+ * Mapping of DWARF debug register numbers into register names.
+ *
+ * Copyright IBM Corp. 2010
+ * Author(s): Heiko Carstens <heiko.carstens@de.ibm.com>,
+ *
+ */
+
+#include <stddef.h>
+#include <dwarf-regs.h>
+
+#define NUM_GPRS 16
+
+static const char *gpr_names[NUM_GPRS] = {
+ "%r0", "%r1", "%r2", "%r3", "%r4", "%r5", "%r6", "%r7",
+ "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15",
+};
+
+const char *get_arch_regstr(unsigned int n)
+{
+ return (n >= NUM_GPRS) ? NULL : gpr_names[n];
+}
diff --git a/tools/perf/arch/sh/Makefile b/tools/perf/arch/sh/Makefile
new file mode 100644
index 00000000000..15130b50dfe
--- /dev/null
+++ b/tools/perf/arch/sh/Makefile
@@ -0,0 +1,4 @@
+ifndef NO_DWARF
+PERF_HAVE_DWARF_REGS := 1
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o
+endif
diff --git a/tools/perf/arch/sh/util/dwarf-regs.c b/tools/perf/arch/sh/util/dwarf-regs.c
new file mode 100644
index 00000000000..0d0897f57a1
--- /dev/null
+++ b/tools/perf/arch/sh/util/dwarf-regs.c
@@ -0,0 +1,55 @@
+/*
+ * Mapping of DWARF debug register numbers into register names.
+ *
+ * Copyright (C) 2010 Matt Fleming <matt@console-pimps.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <stddef.h>
+#include <dwarf-regs.h>
+
+/*
+ * Generic dwarf analysis helpers
+ */
+
+#define SH_MAX_REGS 18
+const char *sh_regs_table[SH_MAX_REGS] = {
+ "r0",
+ "r1",
+ "r2",
+ "r3",
+ "r4",
+ "r5",
+ "r6",
+ "r7",
+ "r8",
+ "r9",
+ "r10",
+ "r11",
+ "r12",
+ "r13",
+ "r14",
+ "r15",
+ "pc",
+ "pr",
+};
+
+/* Return architecture dependent register string (for kprobe-tracer) */
+const char *get_arch_regstr(unsigned int n)
+{
+ return (n <= SH_MAX_REGS) ? sh_regs_table[n] : NULL;
+}
diff --git a/tools/perf/arch/sparc/Makefile b/tools/perf/arch/sparc/Makefile
new file mode 100644
index 00000000000..15130b50dfe
--- /dev/null
+++ b/tools/perf/arch/sparc/Makefile
@@ -0,0 +1,4 @@
+ifndef NO_DWARF
+PERF_HAVE_DWARF_REGS := 1
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o
+endif
diff --git a/tools/perf/arch/sparc/util/dwarf-regs.c b/tools/perf/arch/sparc/util/dwarf-regs.c
new file mode 100644
index 00000000000..92eda412fed
--- /dev/null
+++ b/tools/perf/arch/sparc/util/dwarf-regs.c
@@ -0,0 +1,43 @@
+/*
+ * Mapping of DWARF debug register numbers into register names.
+ *
+ * Copyright (C) 2010 David S. Miller <davem@davemloft.net>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version
+ * 2 of the License, or (at your option) any later version.
+ */
+
+#include <stddef.h>
+#include <dwarf-regs.h>
+
+#define SPARC_MAX_REGS 96
+
+const char *sparc_regs_table[SPARC_MAX_REGS] = {
+ "%g0", "%g1", "%g2", "%g3", "%g4", "%g5", "%g6", "%g7",
+ "%o0", "%o1", "%o2", "%o3", "%o4", "%o5", "%sp", "%o7",
+ "%l0", "%l1", "%l2", "%l3", "%l4", "%l5", "%l6", "%l7",
+ "%i0", "%i1", "%i2", "%i3", "%i4", "%i5", "%fp", "%i7",
+ "%f0", "%f1", "%f2", "%f3", "%f4", "%f5", "%f6", "%f7",
+ "%f8", "%f9", "%f10", "%f11", "%f12", "%f13", "%f14", "%f15",
+ "%f16", "%f17", "%f18", "%f19", "%f20", "%f21", "%f22", "%f23",
+ "%f24", "%f25", "%f26", "%f27", "%f28", "%f29", "%f30", "%f31",
+ "%f32", "%f33", "%f34", "%f35", "%f36", "%f37", "%f38", "%f39",
+ "%f40", "%f41", "%f42", "%f43", "%f44", "%f45", "%f46", "%f47",
+ "%f48", "%f49", "%f50", "%f51", "%f52", "%f53", "%f54", "%f55",
+ "%f56", "%f57", "%f58", "%f59", "%f60", "%f61", "%f62", "%f63",
+};
+
+/**
+ * get_arch_regstr() - lookup register name from it's DWARF register number
+ * @n: the DWARF register number
+ *
+ * get_arch_regstr() returns the name of the register in struct
+ * regdwarfnum_table from it's DWARF register number. If the register is not
+ * found in the table, this returns NULL;
+ */
+const char *get_arch_regstr(unsigned int n)
+{
+ return (n <= SPARC_MAX_REGS) ? sparc_regs_table[n] : NULL;
+}
diff --git a/tools/perf/arch/x86/Makefile b/tools/perf/arch/x86/Makefile
new file mode 100644
index 00000000000..1641542e363
--- /dev/null
+++ b/tools/perf/arch/x86/Makefile
@@ -0,0 +1,17 @@
+ifndef NO_DWARF
+PERF_HAVE_DWARF_REGS := 1
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o
+endif
+ifndef NO_LIBUNWIND
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/unwind-libunwind.o
+endif
+ifndef NO_LIBDW_DWARF_UNWIND
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/unwind-libdw.o
+endif
+ifndef NO_DWARF_UNWIND
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/tests/regs_load.o
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/tests/dwarf-unwind.o
+endif
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/header.o
+LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/tsc.o
+LIB_H += arch/$(ARCH)/util/tsc.h
diff --git a/tools/perf/arch/x86/include/perf_regs.h b/tools/perf/arch/x86/include/perf_regs.h
new file mode 100644
index 00000000000..7df517acfef
--- /dev/null
+++ b/tools/perf/arch/x86/include/perf_regs.h
@@ -0,0 +1,86 @@
+#ifndef ARCH_PERF_REGS_H
+#define ARCH_PERF_REGS_H
+
+#include <stdlib.h>
+#include <linux/types.h>
+#include <asm/perf_regs.h>
+
+void perf_regs_load(u64 *regs);
+
+#ifndef HAVE_ARCH_X86_64_SUPPORT
+#define PERF_REGS_MASK ((1ULL << PERF_REG_X86_32_MAX) - 1)
+#define PERF_REGS_MAX PERF_REG_X86_32_MAX
+#define PERF_SAMPLE_REGS_ABI PERF_SAMPLE_REGS_ABI_32
+#else
+#define REG_NOSUPPORT ((1ULL << PERF_REG_X86_DS) | \
+ (1ULL << PERF_REG_X86_ES) | \
+ (1ULL << PERF_REG_X86_FS) | \
+ (1ULL << PERF_REG_X86_GS))
+#define PERF_REGS_MASK (((1ULL << PERF_REG_X86_64_MAX) - 1) & ~REG_NOSUPPORT)
+#define PERF_REGS_MAX PERF_REG_X86_64_MAX
+#define PERF_SAMPLE_REGS_ABI PERF_SAMPLE_REGS_ABI_64
+#endif
+#define PERF_REG_IP PERF_REG_X86_IP
+#define PERF_REG_SP PERF_REG_X86_SP
+
+static inline const char *perf_reg_name(int id)
+{
+ switch (id) {
+ case PERF_REG_X86_AX:
+ return "AX";
+ case PERF_REG_X86_BX:
+ return "BX";
+ case PERF_REG_X86_CX:
+ return "CX";
+ case PERF_REG_X86_DX:
+ return "DX";
+ case PERF_REG_X86_SI:
+ return "SI";
+ case PERF_REG_X86_DI:
+ return "DI";
+ case PERF_REG_X86_BP:
+ return "BP";
+ case PERF_REG_X86_SP:
+ return "SP";
+ case PERF_REG_X86_IP:
+ return "IP";
+ case PERF_REG_X86_FLAGS:
+ return "FLAGS";
+ case PERF_REG_X86_CS:
+ return "CS";
+ case PERF_REG_X86_SS:
+ return "SS";
+ case PERF_REG_X86_DS:
+ return "DS";
+ case PERF_REG_X86_ES:
+ return "ES";
+ case PERF_REG_X86_FS:
+ return "FS";
+ case PERF_REG_X86_GS:
+ return "GS";
+#ifdef HAVE_ARCH_X86_64_SUPPORT
+ case PERF_REG_X86_R8:
+ return "R8";
+ case PERF_REG_X86_R9:
+ return "R9";
+ case PERF_REG_X86_R10:
+ return "R10";
+ case PERF_REG_X86_R11:
+ return "R11";
+ case PERF_REG_X86_R12:
+ return "R12";
+ case PERF_REG_X86_R13:
+ return "R13";
+ case PERF_REG_X86_R14:
+ return "R14";
+ case PERF_REG_X86_R15:
+ return "R15";
+#endif /* HAVE_ARCH_X86_64_SUPPORT */
+ default:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+#endif /* ARCH_PERF_REGS_H */
diff --git a/tools/perf/arch/x86/tests/dwarf-unwind.c b/tools/perf/arch/x86/tests/dwarf-unwind.c
new file mode 100644
index 00000000000..9f89f899ccc
--- /dev/null
+++ b/tools/perf/arch/x86/tests/dwarf-unwind.c
@@ -0,0 +1,60 @@
+#include <string.h>
+#include "perf_regs.h"
+#include "thread.h"
+#include "map.h"
+#include "event.h"
+#include "tests/tests.h"
+
+#define STACK_SIZE 8192
+
+static int sample_ustack(struct perf_sample *sample,
+ struct thread *thread, u64 *regs)
+{
+ struct stack_dump *stack = &sample->user_stack;
+ struct map *map;
+ unsigned long sp;
+ u64 stack_size, *buf;
+
+ buf = malloc(STACK_SIZE);
+ if (!buf) {
+ pr_debug("failed to allocate sample uregs data\n");
+ return -1;
+ }
+
+ sp = (unsigned long) regs[PERF_REG_X86_SP];
+
+ map = map_groups__find(thread->mg, MAP__VARIABLE, (u64) sp);
+ if (!map) {
+ pr_debug("failed to get stack map\n");
+ free(buf);
+ return -1;
+ }
+
+ stack_size = map->end - sp;
+ stack_size = stack_size > STACK_SIZE ? STACK_SIZE : stack_size;
+
+ memcpy(buf, (void *) sp, stack_size);
+ stack->data = (char *) buf;
+ stack->size = stack_size;
+ return 0;
+}
+
+int test__arch_unwind_sample(struct perf_sample *sample,
+ struct thread *thread)
+{
+ struct regs_dump *regs = &sample->user_regs;
+ u64 *buf;
+
+ buf = malloc(sizeof(u64) * PERF_REGS_MAX);
+ if (!buf) {
+ pr_debug("failed to allocate sample uregs data\n");
+ return -1;
+ }
+
+ perf_regs_load(buf);
+ regs->abi = PERF_SAMPLE_REGS_ABI;
+ regs->regs = buf;
+ regs->mask = PERF_REGS_MASK;
+
+ return sample_ustack(sample, thread, buf);
+}
diff --git a/tools/perf/arch/x86/tests/regs_load.S b/tools/perf/arch/x86/tests/regs_load.S
new file mode 100644
index 00000000000..60875d5c556
--- /dev/null
+++ b/tools/perf/arch/x86/tests/regs_load.S
@@ -0,0 +1,98 @@
+#include <linux/linkage.h>
+
+#define AX 0
+#define BX 1 * 8
+#define CX 2 * 8
+#define DX 3 * 8
+#define SI 4 * 8
+#define DI 5 * 8
+#define BP 6 * 8
+#define SP 7 * 8
+#define IP 8 * 8
+#define FLAGS 9 * 8
+#define CS 10 * 8
+#define SS 11 * 8
+#define DS 12 * 8
+#define ES 13 * 8
+#define FS 14 * 8
+#define GS 15 * 8
+#define R8 16 * 8
+#define R9 17 * 8
+#define R10 18 * 8
+#define R11 19 * 8
+#define R12 20 * 8
+#define R13 21 * 8
+#define R14 22 * 8
+#define R15 23 * 8
+
+.text
+#ifdef HAVE_ARCH_X86_64_SUPPORT
+ENTRY(perf_regs_load)
+ movq %rax, AX(%rdi)
+ movq %rbx, BX(%rdi)
+ movq %rcx, CX(%rdi)
+ movq %rdx, DX(%rdi)
+ movq %rsi, SI(%rdi)
+ movq %rdi, DI(%rdi)
+ movq %rbp, BP(%rdi)
+
+ leaq 8(%rsp), %rax /* exclude this call. */
+ movq %rax, SP(%rdi)
+
+ movq 0(%rsp), %rax
+ movq %rax, IP(%rdi)
+
+ movq $0, FLAGS(%rdi)
+ movq $0, CS(%rdi)
+ movq $0, SS(%rdi)
+ movq $0, DS(%rdi)
+ movq $0, ES(%rdi)
+ movq $0, FS(%rdi)
+ movq $0, GS(%rdi)
+
+ movq %r8, R8(%rdi)
+ movq %r9, R9(%rdi)
+ movq %r10, R10(%rdi)
+ movq %r11, R11(%rdi)
+ movq %r12, R12(%rdi)
+ movq %r13, R13(%rdi)
+ movq %r14, R14(%rdi)
+ movq %r15, R15(%rdi)
+ ret
+ENDPROC(perf_regs_load)
+#else
+ENTRY(perf_regs_load)
+ push %edi
+ movl 8(%esp), %edi
+ movl %eax, AX(%edi)
+ movl %ebx, BX(%edi)
+ movl %ecx, CX(%edi)
+ movl %edx, DX(%edi)
+ movl %esi, SI(%edi)
+ pop %eax
+ movl %eax, DI(%edi)
+ movl %ebp, BP(%edi)
+
+ leal 4(%esp), %eax /* exclude this call. */
+ movl %eax, SP(%edi)
+
+ movl 0(%esp), %eax
+ movl %eax, IP(%edi)
+
+ movl $0, FLAGS(%edi)
+ movl $0, CS(%edi)
+ movl $0, SS(%edi)
+ movl $0, DS(%edi)
+ movl $0, ES(%edi)
+ movl $0, FS(%edi)
+ movl $0, GS(%edi)
+ ret
+ENDPROC(perf_regs_load)
+#endif
+
+/*
+ * We need to provide note.GNU-stack section, saying that we want
+ * NOT executable stack. Otherwise the final linking will assume that
+ * the ELF stack should not be restricted at all and set it RWX.
+ */
+.section .note.GNU-stack,"",@progbits
diff --git a/tools/perf/arch/x86/util/dwarf-regs.c b/tools/perf/arch/x86/util/dwarf-regs.c
new file mode 100644
index 00000000000..be22dd46323
--- /dev/null
+++ b/tools/perf/arch/x86/util/dwarf-regs.c
@@ -0,0 +1,75 @@
+/*
+ * dwarf-regs.c : Mapping of DWARF debug register numbers into register names.
+ * Extracted from probe-finder.c
+ *
+ * Written by Masami Hiramatsu <mhiramat@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <stddef.h>
+#include <dwarf-regs.h>
+
+/*
+ * Generic dwarf analysis helpers
+ */
+
+#define X86_32_MAX_REGS 8
+const char *x86_32_regs_table[X86_32_MAX_REGS] = {
+ "%ax",
+ "%cx",
+ "%dx",
+ "%bx",
+ "$stack", /* Stack address instead of %sp */
+ "%bp",
+ "%si",
+ "%di",
+};
+
+#define X86_64_MAX_REGS 16
+const char *x86_64_regs_table[X86_64_MAX_REGS] = {
+ "%ax",
+ "%dx",
+ "%cx",
+ "%bx",
+ "%si",
+ "%di",
+ "%bp",
+ "%sp",
+ "%r8",
+ "%r9",
+ "%r10",
+ "%r11",
+ "%r12",
+ "%r13",
+ "%r14",
+ "%r15",
+};
+
+/* TODO: switching by dwarf address size */
+#ifdef __x86_64__
+#define ARCH_MAX_REGS X86_64_MAX_REGS
+#define arch_regs_table x86_64_regs_table
+#else
+#define ARCH_MAX_REGS X86_32_MAX_REGS
+#define arch_regs_table x86_32_regs_table
+#endif
+
+/* Return architecture dependent register string (for kprobe-tracer) */
+const char *get_arch_regstr(unsigned int n)
+{
+ return (n <= ARCH_MAX_REGS) ? arch_regs_table[n] : NULL;
+}
diff --git a/tools/perf/arch/x86/util/header.c b/tools/perf/arch/x86/util/header.c
new file mode 100644
index 00000000000..146d12a1cec
--- /dev/null
+++ b/tools/perf/arch/x86/util/header.c
@@ -0,0 +1,59 @@
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../../util/header.h"
+
+static inline void
+cpuid(unsigned int op, unsigned int *a, unsigned int *b, unsigned int *c,
+ unsigned int *d)
+{
+ __asm__ __volatile__ (".byte 0x53\n\tcpuid\n\t"
+ "movl %%ebx, %%esi\n\t.byte 0x5b"
+ : "=a" (*a),
+ "=S" (*b),
+ "=c" (*c),
+ "=d" (*d)
+ : "a" (op));
+}
+
+int
+get_cpuid(char *buffer, size_t sz)
+{
+ unsigned int a, b, c, d, lvl;
+ int family = -1, model = -1, step = -1;
+ int nb;
+ char vendor[16];
+
+ cpuid(0, &lvl, &b, &c, &d);
+ strncpy(&vendor[0], (char *)(&b), 4);
+ strncpy(&vendor[4], (char *)(&d), 4);
+ strncpy(&vendor[8], (char *)(&c), 4);
+ vendor[12] = '\0';
+
+ if (lvl >= 1) {
+ cpuid(1, &a, &b, &c, &d);
+
+ family = (a >> 8) & 0xf; /* bits 11 - 8 */
+ model = (a >> 4) & 0xf; /* Bits 7 - 4 */
+ step = a & 0xf;
+
+ /* extended family */
+ if (family == 0xf)
+ family += (a >> 20) & 0xff;
+
+ /* extended model */
+ if (family >= 0x6)
+ model += ((a >> 16) & 0xf) << 4;
+ }
+ nb = scnprintf(buffer, sz, "%s,%u,%u,%u$", vendor, family, model, step);
+
+ /* look for end marker to ensure the entire data fit */
+ if (strchr(buffer, '$')) {
+ buffer[nb-1] = '\0';
+ return 0;
+ }
+ return -1;
+}
diff --git a/tools/perf/arch/x86/util/tsc.c b/tools/perf/arch/x86/util/tsc.c
new file mode 100644
index 00000000000..40021fa3129
--- /dev/null
+++ b/tools/perf/arch/x86/util/tsc.c
@@ -0,0 +1,59 @@
+#include <stdbool.h>
+#include <errno.h>
+
+#include <linux/perf_event.h>
+
+#include "../../perf.h"
+#include <linux/types.h>
+#include "../../util/debug.h"
+#include "tsc.h"
+
+u64 perf_time_to_tsc(u64 ns, struct perf_tsc_conversion *tc)
+{
+ u64 t, quot, rem;
+
+ t = ns - tc->time_zero;
+ quot = t / tc->time_mult;
+ rem = t % tc->time_mult;
+ return (quot << tc->time_shift) +
+ (rem << tc->time_shift) / tc->time_mult;
+}
+
+u64 tsc_to_perf_time(u64 cyc, struct perf_tsc_conversion *tc)
+{
+ u64 quot, rem;
+
+ quot = cyc >> tc->time_shift;
+ rem = cyc & ((1 << tc->time_shift) - 1);
+ return tc->time_zero + quot * tc->time_mult +
+ ((rem * tc->time_mult) >> tc->time_shift);
+}
+
+int perf_read_tsc_conversion(const struct perf_event_mmap_page *pc,
+ struct perf_tsc_conversion *tc)
+{
+ bool cap_user_time_zero;
+ u32 seq;
+ int i = 0;
+
+ while (1) {
+ seq = pc->lock;
+ rmb();
+ tc->time_mult = pc->time_mult;
+ tc->time_shift = pc->time_shift;
+ tc->time_zero = pc->time_zero;
+ cap_user_time_zero = pc->cap_user_time_zero;
+ rmb();
+ if (pc->lock == seq && !(seq & 1))
+ break;
+ if (++i > 10000) {
+ pr_debug("failed to get perf_event_mmap_page lock\n");
+ return -EINVAL;
+ }
+ }
+
+ if (!cap_user_time_zero)
+ return -EOPNOTSUPP;
+
+ return 0;
+}
diff --git a/tools/perf/arch/x86/util/tsc.h b/tools/perf/arch/x86/util/tsc.h
new file mode 100644
index 00000000000..2affe0366b5
--- /dev/null
+++ b/tools/perf/arch/x86/util/tsc.h
@@ -0,0 +1,20 @@
+#ifndef TOOLS_PERF_ARCH_X86_UTIL_TSC_H__
+#define TOOLS_PERF_ARCH_X86_UTIL_TSC_H__
+
+#include <linux/types.h>
+
+struct perf_tsc_conversion {
+ u16 time_shift;
+ u32 time_mult;
+ u64 time_zero;
+};
+
+struct perf_event_mmap_page;
+
+int perf_read_tsc_conversion(const struct perf_event_mmap_page *pc,
+ struct perf_tsc_conversion *tc);
+
+u64 perf_time_to_tsc(u64 ns, struct perf_tsc_conversion *tc);
+u64 tsc_to_perf_time(u64 cyc, struct perf_tsc_conversion *tc);
+
+#endif /* TOOLS_PERF_ARCH_X86_UTIL_TSC_H__ */
diff --git a/tools/perf/arch/x86/util/unwind-libdw.c b/tools/perf/arch/x86/util/unwind-libdw.c
new file mode 100644
index 00000000000..c4b72176ca8
--- /dev/null
+++ b/tools/perf/arch/x86/util/unwind-libdw.c
@@ -0,0 +1,51 @@
+#include <elfutils/libdwfl.h>
+#include "../../util/unwind-libdw.h"
+#include "../../util/perf_regs.h"
+
+bool libdw__arch_set_initial_registers(Dwfl_Thread *thread, void *arg)
+{
+ struct unwind_info *ui = arg;
+ struct regs_dump *user_regs = &ui->sample->user_regs;
+ Dwarf_Word dwarf_regs[17];
+ unsigned nregs;
+
+#define REG(r) ({ \
+ Dwarf_Word val = 0; \
+ perf_reg_value(&val, user_regs, PERF_REG_X86_##r); \
+ val; \
+})
+
+ if (user_regs->abi == PERF_SAMPLE_REGS_ABI_32) {
+ dwarf_regs[0] = REG(AX);
+ dwarf_regs[1] = REG(CX);
+ dwarf_regs[2] = REG(DX);
+ dwarf_regs[3] = REG(BX);
+ dwarf_regs[4] = REG(SP);
+ dwarf_regs[5] = REG(BP);
+ dwarf_regs[6] = REG(SI);
+ dwarf_regs[7] = REG(DI);
+ dwarf_regs[8] = REG(IP);
+ nregs = 9;
+ } else {
+ dwarf_regs[0] = REG(AX);
+ dwarf_regs[1] = REG(DX);
+ dwarf_regs[2] = REG(CX);
+ dwarf_regs[3] = REG(BX);
+ dwarf_regs[4] = REG(SI);
+ dwarf_regs[5] = REG(DI);
+ dwarf_regs[6] = REG(BP);
+ dwarf_regs[7] = REG(SP);
+ dwarf_regs[8] = REG(R8);
+ dwarf_regs[9] = REG(R9);
+ dwarf_regs[10] = REG(R10);
+ dwarf_regs[11] = REG(R11);
+ dwarf_regs[12] = REG(R12);
+ dwarf_regs[13] = REG(R13);
+ dwarf_regs[14] = REG(R14);
+ dwarf_regs[15] = REG(R15);
+ dwarf_regs[16] = REG(IP);
+ nregs = 17;
+ }
+
+ return dwfl_thread_state_registers(thread, 0, nregs, dwarf_regs);
+}
diff --git a/tools/perf/arch/x86/util/unwind-libunwind.c b/tools/perf/arch/x86/util/unwind-libunwind.c
new file mode 100644
index 00000000000..3261f68c6a7
--- /dev/null
+++ b/tools/perf/arch/x86/util/unwind-libunwind.c
@@ -0,0 +1,111 @@
+
+#include <errno.h>
+#include <libunwind.h>
+#include "perf_regs.h"
+#include "../../util/unwind.h"
+
+#ifdef HAVE_ARCH_X86_64_SUPPORT
+int libunwind__arch_reg_id(int regnum)
+{
+ int id;
+
+ switch (regnum) {
+ case UNW_X86_64_RAX:
+ id = PERF_REG_X86_AX;
+ break;
+ case UNW_X86_64_RDX:
+ id = PERF_REG_X86_DX;
+ break;
+ case UNW_X86_64_RCX:
+ id = PERF_REG_X86_CX;
+ break;
+ case UNW_X86_64_RBX:
+ id = PERF_REG_X86_BX;
+ break;
+ case UNW_X86_64_RSI:
+ id = PERF_REG_X86_SI;
+ break;
+ case UNW_X86_64_RDI:
+ id = PERF_REG_X86_DI;
+ break;
+ case UNW_X86_64_RBP:
+ id = PERF_REG_X86_BP;
+ break;
+ case UNW_X86_64_RSP:
+ id = PERF_REG_X86_SP;
+ break;
+ case UNW_X86_64_R8:
+ id = PERF_REG_X86_R8;
+ break;
+ case UNW_X86_64_R9:
+ id = PERF_REG_X86_R9;
+ break;
+ case UNW_X86_64_R10:
+ id = PERF_REG_X86_R10;
+ break;
+ case UNW_X86_64_R11:
+ id = PERF_REG_X86_R11;
+ break;
+ case UNW_X86_64_R12:
+ id = PERF_REG_X86_R12;
+ break;
+ case UNW_X86_64_R13:
+ id = PERF_REG_X86_R13;
+ break;
+ case UNW_X86_64_R14:
+ id = PERF_REG_X86_R14;
+ break;
+ case UNW_X86_64_R15:
+ id = PERF_REG_X86_R15;
+ break;
+ case UNW_X86_64_RIP:
+ id = PERF_REG_X86_IP;
+ break;
+ default:
+ pr_err("unwind: invalid reg id %d\n", regnum);
+ return -EINVAL;
+ }
+
+ return id;
+}
+#else
+int libunwind__arch_reg_id(int regnum)
+{
+ int id;
+
+ switch (regnum) {
+ case UNW_X86_EAX:
+ id = PERF_REG_X86_AX;
+ break;
+ case UNW_X86_EDX:
+ id = PERF_REG_X86_DX;
+ break;
+ case UNW_X86_ECX:
+ id = PERF_REG_X86_CX;
+ break;
+ case UNW_X86_EBX:
+ id = PERF_REG_X86_BX;
+ break;
+ case UNW_X86_ESI:
+ id = PERF_REG_X86_SI;
+ break;
+ case UNW_X86_EDI:
+ id = PERF_REG_X86_DI;
+ break;
+ case UNW_X86_EBP:
+ id = PERF_REG_X86_BP;
+ break;
+ case UNW_X86_ESP:
+ id = PERF_REG_X86_SP;
+ break;
+ case UNW_X86_EIP:
+ id = PERF_REG_X86_IP;
+ break;
+ default:
+ pr_err("unwind: invalid reg id %d\n", regnum);
+ return -EINVAL;
+ }
+
+ return id;
+}
+#endif /* HAVE_ARCH_X86_64_SUPPORT */
diff --git a/tools/perf/bench/bench.h b/tools/perf/bench/bench.h
new file mode 100644
index 00000000000..eba46709b27
--- /dev/null
+++ b/tools/perf/bench/bench.h
@@ -0,0 +1,47 @@
+#ifndef BENCH_H
+#define BENCH_H
+
+/*
+ * The madvise transparent hugepage constants were added in glibc
+ * 2.13. For compatibility with older versions of glibc, define these
+ * tokens if they are not already defined.
+ *
+ * PA-RISC uses different madvise values from other architectures and
+ * needs to be special-cased.
+ */
+#ifdef __hppa__
+# ifndef MADV_HUGEPAGE
+# define MADV_HUGEPAGE 67
+# endif
+# ifndef MADV_NOHUGEPAGE
+# define MADV_NOHUGEPAGE 68
+# endif
+#else
+# ifndef MADV_HUGEPAGE
+# define MADV_HUGEPAGE 14
+# endif
+# ifndef MADV_NOHUGEPAGE
+# define MADV_NOHUGEPAGE 15
+# endif
+#endif
+
+extern int bench_numa(int argc, const char **argv, const char *prefix);
+extern int bench_sched_messaging(int argc, const char **argv, const char *prefix);
+extern int bench_sched_pipe(int argc, const char **argv, const char *prefix);
+extern int bench_mem_memcpy(int argc, const char **argv,
+ const char *prefix __maybe_unused);
+extern int bench_mem_memset(int argc, const char **argv, const char *prefix);
+extern int bench_futex_hash(int argc, const char **argv, const char *prefix);
+extern int bench_futex_wake(int argc, const char **argv, const char *prefix);
+extern int bench_futex_requeue(int argc, const char **argv, const char *prefix);
+
+#define BENCH_FORMAT_DEFAULT_STR "default"
+#define BENCH_FORMAT_DEFAULT 0
+#define BENCH_FORMAT_SIMPLE_STR "simple"
+#define BENCH_FORMAT_SIMPLE 1
+
+#define BENCH_FORMAT_UNKNOWN -1
+
+extern int bench_format;
+
+#endif
diff --git a/tools/perf/bench/futex-hash.c b/tools/perf/bench/futex-hash.c
new file mode 100644
index 00000000000..a84206e9c4a
--- /dev/null
+++ b/tools/perf/bench/futex-hash.c
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2013 Davidlohr Bueso <davidlohr@hp.com>
+ *
+ * futex-hash: Stress the hell out of the Linux kernel futex uaddr hashing.
+ *
+ * This program is particularly useful for measuring the kernel's futex hash
+ * table/function implementation. In order for it to make sense, use with as
+ * many threads and futexes as possible.
+ */
+
+#include "../perf.h"
+#include "../util/util.h"
+#include "../util/stat.h"
+#include "../util/parse-options.h"
+#include "../util/header.h"
+#include "bench.h"
+#include "futex.h"
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <pthread.h>
+
+static unsigned int nthreads = 0;
+static unsigned int nsecs = 10;
+/* amount of futexes per thread */
+static unsigned int nfutexes = 1024;
+static bool fshared = false, done = false, silent = false;
+
+struct timeval start, end, runtime;
+static pthread_mutex_t thread_lock;
+static unsigned int threads_starting;
+static struct stats throughput_stats;
+static pthread_cond_t thread_parent, thread_worker;
+
+struct worker {
+ int tid;
+ u_int32_t *futex;
+ pthread_t thread;
+ unsigned long ops;
+};
+
+static const struct option options[] = {
+ OPT_UINTEGER('t', "threads", &nthreads, "Specify amount of threads"),
+ OPT_UINTEGER('r', "runtime", &nsecs, "Specify runtime (in seconds)"),
+ OPT_UINTEGER('f', "futexes", &nfutexes, "Specify amount of futexes per threads"),
+ OPT_BOOLEAN( 's', "silent", &silent, "Silent mode: do not display data/details"),
+ OPT_BOOLEAN( 'S', "shared", &fshared, "Use shared futexes instead of private ones"),
+ OPT_END()
+};
+
+static const char * const bench_futex_hash_usage[] = {
+ "perf bench futex hash <options>",
+ NULL
+};
+
+static void *workerfn(void *arg)
+{
+ int ret;
+ unsigned int i;
+ struct worker *w = (struct worker *) arg;
+
+ pthread_mutex_lock(&thread_lock);
+ threads_starting--;
+ if (!threads_starting)
+ pthread_cond_signal(&thread_parent);
+ pthread_cond_wait(&thread_worker, &thread_lock);
+ pthread_mutex_unlock(&thread_lock);
+
+ do {
+ for (i = 0; i < nfutexes; i++, w->ops++) {
+ /*
+ * We want the futex calls to fail in order to stress
+ * the hashing of uaddr and not measure other steps,
+ * such as internal waitqueue handling, thus enlarging
+ * the critical region protected by hb->lock.
+ */
+ ret = futex_wait(&w->futex[i], 1234, NULL,
+ fshared ? 0 : FUTEX_PRIVATE_FLAG);
+ if (!silent &&
+ (!ret || errno != EAGAIN || errno != EWOULDBLOCK))
+ warn("Non-expected futex return call");
+ }
+ } while (!done);
+
+ return NULL;
+}
+
+static void toggle_done(int sig __maybe_unused,
+ siginfo_t *info __maybe_unused,
+ void *uc __maybe_unused)
+{
+ /* inform all threads that we're done for the day */
+ done = true;
+ gettimeofday(&end, NULL);
+ timersub(&end, &start, &runtime);
+}
+
+static void print_summary(void)
+{
+ unsigned long avg = avg_stats(&throughput_stats);
+ double stddev = stddev_stats(&throughput_stats);
+
+ printf("%sAveraged %ld operations/sec (+- %.2f%%), total secs = %d\n",
+ !silent ? "\n" : "", avg, rel_stddev_stats(stddev, avg),
+ (int) runtime.tv_sec);
+}
+
+int bench_futex_hash(int argc, const char **argv,
+ const char *prefix __maybe_unused)
+{
+ int ret = 0;
+ cpu_set_t cpu;
+ struct sigaction act;
+ unsigned int i, ncpus;
+ pthread_attr_t thread_attr;
+ struct worker *worker = NULL;
+
+ argc = parse_options(argc, argv, options, bench_futex_hash_usage, 0);
+ if (argc) {
+ usage_with_options(bench_futex_hash_usage, options);
+ exit(EXIT_FAILURE);
+ }
+
+ ncpus = sysconf(_SC_NPROCESSORS_ONLN);
+
+ sigfillset(&act.sa_mask);
+ act.sa_sigaction = toggle_done;
+ sigaction(SIGINT, &act, NULL);
+
+ if (!nthreads) /* default to the number of CPUs */
+ nthreads = ncpus;
+
+ worker = calloc(nthreads, sizeof(*worker));
+ if (!worker)
+ goto errmem;
+
+ printf("Run summary [PID %d]: %d threads, each operating on %d [%s] futexes for %d secs.\n\n",
+ getpid(), nthreads, nfutexes, fshared ? "shared":"private", nsecs);
+
+ init_stats(&throughput_stats);
+ pthread_mutex_init(&thread_lock, NULL);
+ pthread_cond_init(&thread_parent, NULL);
+ pthread_cond_init(&thread_worker, NULL);
+
+ threads_starting = nthreads;
+ pthread_attr_init(&thread_attr);
+ gettimeofday(&start, NULL);
+ for (i = 0; i < nthreads; i++) {
+ worker[i].tid = i;
+ worker[i].futex = calloc(nfutexes, sizeof(*worker[i].futex));
+ if (!worker[i].futex)
+ goto errmem;
+
+ CPU_ZERO(&cpu);
+ CPU_SET(i % ncpus, &cpu);
+
+ ret = pthread_attr_setaffinity_np(&thread_attr, sizeof(cpu_set_t), &cpu);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_attr_setaffinity_np");
+
+ ret = pthread_create(&worker[i].thread, &thread_attr, workerfn,
+ (void *)(struct worker *) &worker[i]);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_create");
+
+ }
+ pthread_attr_destroy(&thread_attr);
+
+ pthread_mutex_lock(&thread_lock);
+ while (threads_starting)
+ pthread_cond_wait(&thread_parent, &thread_lock);
+ pthread_cond_broadcast(&thread_worker);
+ pthread_mutex_unlock(&thread_lock);
+
+ sleep(nsecs);
+ toggle_done(0, NULL, NULL);
+
+ for (i = 0; i < nthreads; i++) {
+ ret = pthread_join(worker[i].thread, NULL);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_join");
+ }
+
+ /* cleanup & report results */
+ pthread_cond_destroy(&thread_parent);
+ pthread_cond_destroy(&thread_worker);
+ pthread_mutex_destroy(&thread_lock);
+
+ for (i = 0; i < nthreads; i++) {
+ unsigned long t = worker[i].ops/runtime.tv_sec;
+ update_stats(&throughput_stats, t);
+ if (!silent) {
+ if (nfutexes == 1)
+ printf("[thread %2d] futex: %p [ %ld ops/sec ]\n",
+ worker[i].tid, &worker[i].futex[0], t);
+ else
+ printf("[thread %2d] futexes: %p ... %p [ %ld ops/sec ]\n",
+ worker[i].tid, &worker[i].futex[0],
+ &worker[i].futex[nfutexes-1], t);
+ }
+
+ free(worker[i].futex);
+ }
+
+ print_summary();
+
+ free(worker);
+ return ret;
+errmem:
+ err(EXIT_FAILURE, "calloc");
+}
diff --git a/tools/perf/bench/futex-requeue.c b/tools/perf/bench/futex-requeue.c
new file mode 100644
index 00000000000..a16255876f1
--- /dev/null
+++ b/tools/perf/bench/futex-requeue.c
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2013 Davidlohr Bueso <davidlohr@hp.com>
+ *
+ * futex-requeue: Block a bunch of threads on futex1 and requeue them
+ * on futex2, N at a time.
+ *
+ * This program is particularly useful to measure the latency of nthread
+ * requeues without waking up any tasks -- thus mimicking a regular futex_wait.
+ */
+
+#include "../perf.h"
+#include "../util/util.h"
+#include "../util/stat.h"
+#include "../util/parse-options.h"
+#include "../util/header.h"
+#include "bench.h"
+#include "futex.h"
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <pthread.h>
+
+static u_int32_t futex1 = 0, futex2 = 0;
+
+/*
+ * How many tasks to requeue at a time.
+ * Default to 1 in order to make the kernel work more.
+ */
+static unsigned int nrequeue = 1;
+
+/*
+ * There can be significant variance from run to run,
+ * the more repeats, the more exact the overall avg and
+ * the better idea of the futex latency.
+ */
+static unsigned int repeat = 10;
+
+static pthread_t *worker;
+static bool done = 0, silent = 0;
+static pthread_mutex_t thread_lock;
+static pthread_cond_t thread_parent, thread_worker;
+static struct stats requeuetime_stats, requeued_stats;
+static unsigned int ncpus, threads_starting, nthreads = 0;
+
+static const struct option options[] = {
+ OPT_UINTEGER('t', "threads", &nthreads, "Specify amount of threads"),
+ OPT_UINTEGER('q', "nrequeue", &nrequeue, "Specify amount of threads to requeue at once"),
+ OPT_UINTEGER('r', "repeat", &repeat, "Specify amount of times to repeat the run"),
+ OPT_BOOLEAN( 's', "silent", &silent, "Silent mode: do not display data/details"),
+ OPT_END()
+};
+
+static const char * const bench_futex_requeue_usage[] = {
+ "perf bench futex requeue <options>",
+ NULL
+};
+
+static void print_summary(void)
+{
+ double requeuetime_avg = avg_stats(&requeuetime_stats);
+ double requeuetime_stddev = stddev_stats(&requeuetime_stats);
+ unsigned int requeued_avg = avg_stats(&requeued_stats);
+
+ printf("Requeued %d of %d threads in %.4f ms (+-%.2f%%)\n",
+ requeued_avg,
+ nthreads,
+ requeuetime_avg/1e3,
+ rel_stddev_stats(requeuetime_stddev, requeuetime_avg));
+}
+
+static void *workerfn(void *arg __maybe_unused)
+{
+ pthread_mutex_lock(&thread_lock);
+ threads_starting--;
+ if (!threads_starting)
+ pthread_cond_signal(&thread_parent);
+ pthread_cond_wait(&thread_worker, &thread_lock);
+ pthread_mutex_unlock(&thread_lock);
+
+ futex_wait(&futex1, 0, NULL, FUTEX_PRIVATE_FLAG);
+ return NULL;
+}
+
+static void block_threads(pthread_t *w,
+ pthread_attr_t thread_attr)
+{
+ cpu_set_t cpu;
+ unsigned int i;
+
+ threads_starting = nthreads;
+
+ /* create and block all threads */
+ for (i = 0; i < nthreads; i++) {
+ CPU_ZERO(&cpu);
+ CPU_SET(i % ncpus, &cpu);
+
+ if (pthread_attr_setaffinity_np(&thread_attr, sizeof(cpu_set_t), &cpu))
+ err(EXIT_FAILURE, "pthread_attr_setaffinity_np");
+
+ if (pthread_create(&w[i], &thread_attr, workerfn, NULL))
+ err(EXIT_FAILURE, "pthread_create");
+ }
+}
+
+static void toggle_done(int sig __maybe_unused,
+ siginfo_t *info __maybe_unused,
+ void *uc __maybe_unused)
+{
+ done = true;
+}
+
+int bench_futex_requeue(int argc, const char **argv,
+ const char *prefix __maybe_unused)
+{
+ int ret = 0;
+ unsigned int i, j;
+ struct sigaction act;
+ pthread_attr_t thread_attr;
+
+ argc = parse_options(argc, argv, options, bench_futex_requeue_usage, 0);
+ if (argc)
+ goto err;
+
+ ncpus = sysconf(_SC_NPROCESSORS_ONLN);
+
+ sigfillset(&act.sa_mask);
+ act.sa_sigaction = toggle_done;
+ sigaction(SIGINT, &act, NULL);
+
+ if (!nthreads)
+ nthreads = ncpus;
+
+ worker = calloc(nthreads, sizeof(*worker));
+ if (!worker)
+ err(EXIT_FAILURE, "calloc");
+
+ printf("Run summary [PID %d]: Requeuing %d threads (from %p to %p), "
+ "%d at a time.\n\n",
+ getpid(), nthreads, &futex1, &futex2, nrequeue);
+
+ init_stats(&requeued_stats);
+ init_stats(&requeuetime_stats);
+ pthread_attr_init(&thread_attr);
+ pthread_mutex_init(&thread_lock, NULL);
+ pthread_cond_init(&thread_parent, NULL);
+ pthread_cond_init(&thread_worker, NULL);
+
+ for (j = 0; j < repeat && !done; j++) {
+ unsigned int nrequeued = 0;
+ struct timeval start, end, runtime;
+
+ /* create, launch & block all threads */
+ block_threads(worker, thread_attr);
+
+ /* make sure all threads are already blocked */
+ pthread_mutex_lock(&thread_lock);
+ while (threads_starting)
+ pthread_cond_wait(&thread_parent, &thread_lock);
+ pthread_cond_broadcast(&thread_worker);
+ pthread_mutex_unlock(&thread_lock);
+
+ usleep(100000);
+
+ /* Ok, all threads are patiently blocked, start requeueing */
+ gettimeofday(&start, NULL);
+ for (nrequeued = 0; nrequeued < nthreads; nrequeued += nrequeue)
+ /*
+ * Do not wakeup any tasks blocked on futex1, allowing
+ * us to really measure futex_wait functionality.
+ */
+ futex_cmp_requeue(&futex1, 0, &futex2, 0, nrequeue,
+ FUTEX_PRIVATE_FLAG);
+ gettimeofday(&end, NULL);
+ timersub(&end, &start, &runtime);
+
+ update_stats(&requeued_stats, nrequeued);
+ update_stats(&requeuetime_stats, runtime.tv_usec);
+
+ if (!silent) {
+ printf("[Run %d]: Requeued %d of %d threads in %.4f ms\n",
+ j + 1, nrequeued, nthreads, runtime.tv_usec/1e3);
+ }
+
+ /* everybody should be blocked on futex2, wake'em up */
+ nrequeued = futex_wake(&futex2, nthreads, FUTEX_PRIVATE_FLAG);
+ if (nthreads != nrequeued)
+ warnx("couldn't wakeup all tasks (%d/%d)", nrequeued, nthreads);
+
+ for (i = 0; i < nthreads; i++) {
+ ret = pthread_join(worker[i], NULL);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_join");
+ }
+
+ }
+
+ /* cleanup & report results */
+ pthread_cond_destroy(&thread_parent);
+ pthread_cond_destroy(&thread_worker);
+ pthread_mutex_destroy(&thread_lock);
+ pthread_attr_destroy(&thread_attr);
+
+ print_summary();
+
+ free(worker);
+ return ret;
+err:
+ usage_with_options(bench_futex_requeue_usage, options);
+ exit(EXIT_FAILURE);
+}
diff --git a/tools/perf/bench/futex-wake.c b/tools/perf/bench/futex-wake.c
new file mode 100644
index 00000000000..d096169b161
--- /dev/null
+++ b/tools/perf/bench/futex-wake.c
@@ -0,0 +1,201 @@
+/*
+ * Copyright (C) 2013 Davidlohr Bueso <davidlohr@hp.com>
+ *
+ * futex-wake: Block a bunch of threads on a futex and wake'em up, N at a time.
+ *
+ * This program is particularly useful to measure the latency of nthread wakeups
+ * in non-error situations: all waiters are queued and all wake calls wakeup
+ * one or more tasks, and thus the waitqueue is never empty.
+ */
+
+#include "../perf.h"
+#include "../util/util.h"
+#include "../util/stat.h"
+#include "../util/parse-options.h"
+#include "../util/header.h"
+#include "bench.h"
+#include "futex.h"
+
+#include <err.h>
+#include <stdlib.h>
+#include <sys/time.h>
+#include <pthread.h>
+
+/* all threads will block on the same futex */
+static u_int32_t futex1 = 0;
+
+/*
+ * How many wakeups to do at a time.
+ * Default to 1 in order to make the kernel work more.
+ */
+static unsigned int nwakes = 1;
+
+/*
+ * There can be significant variance from run to run,
+ * the more repeats, the more exact the overall avg and
+ * the better idea of the futex latency.
+ */
+static unsigned int repeat = 10;
+
+pthread_t *worker;
+static bool done = 0, silent = 0;
+static pthread_mutex_t thread_lock;
+static pthread_cond_t thread_parent, thread_worker;
+static struct stats waketime_stats, wakeup_stats;
+static unsigned int ncpus, threads_starting, nthreads = 0;
+
+static const struct option options[] = {
+ OPT_UINTEGER('t', "threads", &nthreads, "Specify amount of threads"),
+ OPT_UINTEGER('w', "nwakes", &nwakes, "Specify amount of threads to wake at once"),
+ OPT_UINTEGER('r', "repeat", &repeat, "Specify amount of times to repeat the run"),
+ OPT_BOOLEAN( 's', "silent", &silent, "Silent mode: do not display data/details"),
+ OPT_END()
+};
+
+static const char * const bench_futex_wake_usage[] = {
+ "perf bench futex wake <options>",
+ NULL
+};
+
+static void *workerfn(void *arg __maybe_unused)
+{
+ pthread_mutex_lock(&thread_lock);
+ threads_starting--;
+ if (!threads_starting)
+ pthread_cond_signal(&thread_parent);
+ pthread_cond_wait(&thread_worker, &thread_lock);
+ pthread_mutex_unlock(&thread_lock);
+
+ futex_wait(&futex1, 0, NULL, FUTEX_PRIVATE_FLAG);
+ return NULL;
+}
+
+static void print_summary(void)
+{
+ double waketime_avg = avg_stats(&waketime_stats);
+ double waketime_stddev = stddev_stats(&waketime_stats);
+ unsigned int wakeup_avg = avg_stats(&wakeup_stats);
+
+ printf("Wokeup %d of %d threads in %.4f ms (+-%.2f%%)\n",
+ wakeup_avg,
+ nthreads,
+ waketime_avg/1e3,
+ rel_stddev_stats(waketime_stddev, waketime_avg));
+}
+
+static void block_threads(pthread_t *w,
+ pthread_attr_t thread_attr)
+{
+ cpu_set_t cpu;
+ unsigned int i;
+
+ threads_starting = nthreads;
+
+ /* create and block all threads */
+ for (i = 0; i < nthreads; i++) {
+ CPU_ZERO(&cpu);
+ CPU_SET(i % ncpus, &cpu);
+
+ if (pthread_attr_setaffinity_np(&thread_attr, sizeof(cpu_set_t), &cpu))
+ err(EXIT_FAILURE, "pthread_attr_setaffinity_np");
+
+ if (pthread_create(&w[i], &thread_attr, workerfn, NULL))
+ err(EXIT_FAILURE, "pthread_create");
+ }
+}
+
+static void toggle_done(int sig __maybe_unused,
+ siginfo_t *info __maybe_unused,
+ void *uc __maybe_unused)
+{
+ done = true;
+}
+
+int bench_futex_wake(int argc, const char **argv,
+ const char *prefix __maybe_unused)
+{
+ int ret = 0;
+ unsigned int i, j;
+ struct sigaction act;
+ pthread_attr_t thread_attr;
+
+ argc = parse_options(argc, argv, options, bench_futex_wake_usage, 0);
+ if (argc) {
+ usage_with_options(bench_futex_wake_usage, options);
+ exit(EXIT_FAILURE);
+ }
+
+ ncpus = sysconf(_SC_NPROCESSORS_ONLN);
+
+ sigfillset(&act.sa_mask);
+ act.sa_sigaction = toggle_done;
+ sigaction(SIGINT, &act, NULL);
+
+ if (!nthreads)
+ nthreads = ncpus;
+
+ worker = calloc(nthreads, sizeof(*worker));
+ if (!worker)
+ err(EXIT_FAILURE, "calloc");
+
+ printf("Run summary [PID %d]: blocking on %d threads (at futex %p), "
+ "waking up %d at a time.\n\n",
+ getpid(), nthreads, &futex1, nwakes);
+
+ init_stats(&wakeup_stats);
+ init_stats(&waketime_stats);
+ pthread_attr_init(&thread_attr);
+ pthread_mutex_init(&thread_lock, NULL);
+ pthread_cond_init(&thread_parent, NULL);
+ pthread_cond_init(&thread_worker, NULL);
+
+ for (j = 0; j < repeat && !done; j++) {
+ unsigned int nwoken = 0;
+ struct timeval start, end, runtime;
+
+ /* create, launch & block all threads */
+ block_threads(worker, thread_attr);
+
+ /* make sure all threads are already blocked */
+ pthread_mutex_lock(&thread_lock);
+ while (threads_starting)
+ pthread_cond_wait(&thread_parent, &thread_lock);
+ pthread_cond_broadcast(&thread_worker);
+ pthread_mutex_unlock(&thread_lock);
+
+ usleep(100000);
+
+ /* Ok, all threads are patiently blocked, start waking folks up */
+ gettimeofday(&start, NULL);
+ while (nwoken != nthreads)
+ nwoken += futex_wake(&futex1, nwakes, FUTEX_PRIVATE_FLAG);
+ gettimeofday(&end, NULL);
+ timersub(&end, &start, &runtime);
+
+ update_stats(&wakeup_stats, nwoken);
+ update_stats(&waketime_stats, runtime.tv_usec);
+
+ if (!silent) {
+ printf("[Run %d]: Wokeup %d of %d threads in %.4f ms\n",
+ j + 1, nwoken, nthreads, runtime.tv_usec/1e3);
+ }
+
+ for (i = 0; i < nthreads; i++) {
+ ret = pthread_join(worker[i], NULL);
+ if (ret)
+ err(EXIT_FAILURE, "pthread_join");
+ }
+
+ }
+
+ /* cleanup & report results */
+ pthread_cond_destroy(&thread_parent);
+ pthread_cond_destroy(&thread_worker);
+ pthread_mutex_destroy(&thread_lock);
+ pthread_attr_destroy(&thread_attr);
+
+ print_summary();
+
+ free(worker);
+ return ret;
+}
diff --git a/tools/perf/bench/futex.h b/tools/perf/bench/futex.h
new file mode 100644
index 00000000000..71f2844cf97
--- /dev/null
+++ b/tools/perf/bench/futex.h
@@ -0,0 +1,71 @@
+/*
+ * Glibc independent futex library for testing kernel functionality.
+ * Shamelessly stolen from Darren Hart <dvhltc@us.ibm.com>
+ * http://git.kernel.org/cgit/linux/kernel/git/dvhart/futextest.git/
+ */
+
+#ifndef _FUTEX_H
+#define _FUTEX_H
+
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <linux/futex.h>
+
+/**
+ * futex() - SYS_futex syscall wrapper
+ * @uaddr: address of first futex
+ * @op: futex op code
+ * @val: typically expected value of uaddr, but varies by op
+ * @timeout: typically an absolute struct timespec (except where noted
+ * otherwise). Overloaded by some ops
+ * @uaddr2: address of second futex for some ops\
+ * @val3: varies by op
+ * @opflags: flags to be bitwise OR'd with op, such as FUTEX_PRIVATE_FLAG
+ *
+ * futex() is used by all the following futex op wrappers. It can also be
+ * used for misuse and abuse testing. Generally, the specific op wrappers
+ * should be used instead. It is a macro instead of an static inline function as
+ * some of the types over overloaded (timeout is used for nr_requeue for
+ * example).
+ *
+ * These argument descriptions are the defaults for all
+ * like-named arguments in the following wrappers except where noted below.
+ */
+#define futex(uaddr, op, val, timeout, uaddr2, val3, opflags) \
+ syscall(SYS_futex, uaddr, op | opflags, val, timeout, uaddr2, val3)
+
+/**
+ * futex_wait() - block on uaddr with optional timeout
+ * @timeout: relative timeout
+ */
+static inline int
+futex_wait(u_int32_t *uaddr, u_int32_t val, struct timespec *timeout, int opflags)
+{
+ return futex(uaddr, FUTEX_WAIT, val, timeout, NULL, 0, opflags);
+}
+
+/**
+ * futex_wake() - wake one or more tasks blocked on uaddr
+ * @nr_wake: wake up to this many tasks
+ */
+static inline int
+futex_wake(u_int32_t *uaddr, int nr_wake, int opflags)
+{
+ return futex(uaddr, FUTEX_WAKE, nr_wake, NULL, NULL, 0, opflags);
+}
+
+/**
+* futex_cmp_requeue() - requeue tasks from uaddr to uaddr2
+* @nr_wake: wake up to this many tasks
+* @nr_requeue: requeue up to this many tasks
+*/
+static inline int
+futex_cmp_requeue(u_int32_t *uaddr, u_int32_t val, u_int32_t *uaddr2, int nr_wake,
+ int nr_requeue, int opflags)
+{
+ return futex(uaddr, FUTEX_CMP_REQUEUE, nr_wake, nr_requeue, uaddr2,
+ val, opflags);
+}
+
+#endif /* _FUTEX_H */
diff --git a/tools/perf/bench/mem-memcpy-arch.h b/tools/perf/bench/mem-memcpy-arch.h
new file mode 100644
index 00000000000..57b4ed87145
--- /dev/null
+++ b/tools/perf/bench/mem-memcpy-arch.h
@@ -0,0 +1,12 @@
+
+#ifdef HAVE_ARCH_X86_64_SUPPORT
+
+#define MEMCPY_FN(fn, name, desc) \
+ extern void *fn(void *, const void *, size_t);
+
+#include "mem-memcpy-x86-64-asm-def.h"
+
+#undef MEMCPY_FN
+
+#endif
+
diff --git a/tools/perf/bench/mem-memcpy-x86-64-asm-def.h b/tools/perf/bench/mem-memcpy-x86-64-asm-def.h
new file mode 100644
index 00000000000..d66ab799b35
--- /dev/null
+++ b/tools/perf/bench/mem-memcpy-x86-64-asm-def.h
@@ -0,0 +1,12 @@
+
+MEMCPY_FN(__memcpy,
+ "x86-64-unrolled",
+ "unrolled memcpy() in arch/x86/lib/memcpy_64.S")
+
+MEMCPY_FN(memcpy_c,
+ "x86-64-movsq",
+ "movsq-based memcpy() in arch/x86/lib/memcpy_64.S")
+
+MEMCPY_FN(memcpy_c_e,
+ "x86-64-movsb",
+ "movsb-based memcpy() in arch/x86/lib/memcpy_64.S")
diff --git a/tools/perf/bench/mem-memcpy-x86-64-asm.S b/tools/perf/bench/mem-memcpy-x86-64-asm.S
new file mode 100644
index 00000000000..fcd9cf00600
--- /dev/null
+++ b/tools/perf/bench/mem-memcpy-x86-64-asm.S
@@ -0,0 +1,12 @@
+#define memcpy MEMCPY /* don't hide glibc's memcpy() */
+#define altinstr_replacement text
+#define globl p2align 4; .globl
+#define Lmemcpy_c globl memcpy_c; memcpy_c
+#define Lmemcpy_c_e globl memcpy_c_e; memcpy_c_e
+#include "../../../arch/x86/lib/memcpy_64.S"
+/*
+ * We need to provide note.GNU-stack section, saying that we want
+ * NOT executable stack. Otherwise the final linking will assume that
+ * the ELF stack should not be restricted at all and set it RWX.
+ */
+.section .note.GNU-stack,"",@progbits
diff --git a/tools/perf/bench/mem-memcpy.c b/tools/perf/bench/mem-memcpy.c
new file mode 100644
index 00000000000..5ce71d3b72c
--- /dev/null
+++ b/tools/perf/bench/mem-memcpy.c
@@ -0,0 +1,305 @@
+/*
+ * mem-memcpy.c
+ *
+ * memcpy: Simple memory copy in various ways
+ *
+ * Written by Hitoshi Mitake <mitake@dcl.info.waseda.ac.jp>
+ */
+
+#include "../perf.h"
+#include "../util/util.h"
+#include "../util/parse-options.h"
+#include "../util/header.h"
+#include "bench.h"
+#include "mem-memcpy-arch.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#define K 1024
+
+static const char *length_str = "1MB";
+static const char *routine = "default";
+static int iterations = 1;
+static bool use_cycle;
+static int cycle_fd;
+static bool only_prefault;
+static bool no_prefault;
+
+static const struct option options[] = {
+ OPT_STRING('l', "length", &length_str, "1MB",
+ "Specify length of memory to copy. "
+ "Available units: B, KB, MB, GB and TB (upper and lower)"),
+ OPT_STRING('r', "routine", &routine, "default",
+ "Specify routine to copy"),
+ OPT_INTEGER('i', "iterations", &iterations,
+ "repeat memcpy() invocation this number of times"),
+ OPT_BOOLEAN('c', "cycle", &use_cycle,
+ "Use cycles event instead of gettimeofday() for measuring"),
+ OPT_BOOLEAN('o', "only-prefault", &only_prefault,
+ "Show only the result with page faults before memcpy()"),
+ OPT_BOOLEAN('n', "no-prefault", &no_prefault,
+ "Show only the result without page faults before memcpy()"),
+ OPT_END()
+};
+
+typedef void *(*memcpy_t)(void *, const void *, size_t);
+
+struct routine {
+ const char *name;
+ const char *desc;
+ memcpy_t fn;
+};
+
+struct routine routines[] = {
+ { "default",
+ "Default memcpy() provided by glibc",
+ memcpy },
+#ifdef HAVE_ARCH_X86_64_SUPPORT
+
+#define MEMCPY_FN(fn, name, desc) { name, desc, fn },
+#include "mem-memcpy-x86-64-asm-def.h"
+#undef MEMCPY_FN
+
+#endif
+
+ { NULL,
+ NULL,
+ NULL }
+};
+
+static const char * const bench_mem_memcpy_usage[] = {
+ "perf bench mem memcpy <options>",
+ NULL
+};
+
+static struct perf_event_attr cycle_attr = {
+ .type = PERF_TYPE_HARDWARE,
+ .config = PERF_COUNT_HW_CPU_CYCLES
+};
+
+static void init_cycle(void)
+{
+ cycle_fd = sys_perf_event_open(&cycle_attr, getpid(), -1, -1, 0);
+
+ if (cycle_fd < 0 && errno == ENOSYS)
+ die("No CONFIG_PERF_EVENTS=y kernel support configured?\n");
+ else
+ BUG_ON(cycle_fd < 0);
+}
+
+static u64 get_cycle(void)
+{
+ int ret;
+ u64 clk;
+
+ ret = read(cycle_fd, &clk, sizeof(u64));
+ BUG_ON(ret != sizeof(u64));
+
+ return clk;
+}
+
+static double timeval2double(struct timeval *ts)
+{
+ return (double)ts->tv_sec +
+ (double)ts->tv_usec / (double)1000000;
+}
+
+static void alloc_mem(void **dst, void **src, size_t length)
+{
+ *dst = zalloc(length);
+ if (!*dst)
+ die("memory allocation failed - maybe length is too large?\n");
+
+ *src = zalloc(length);
+ if (!*src)
+ die("memory allocation failed - maybe length is too large?\n");
+ /* Make sure to always replace the zero pages even if MMAP_THRESH is crossed */
+ memset(*src, 0, length);
+}
+
+static u64 do_memcpy_cycle(memcpy_t fn, size_t len, bool prefault)
+{
+ u64 cycle_start = 0ULL, cycle_end = 0ULL;
+ void *src = NULL, *dst = NULL;
+ int i;
+
+ alloc_mem(&src, &dst, len);
+
+ if (prefault)
+ fn(dst, src, len);
+
+ cycle_start = get_cycle();
+ for (i = 0; i < iterations; ++i)
+ fn(dst, src, len);
+ cycle_end = get_cycle();
+
+ free(src);
+ free(dst);
+ return cycle_end - cycle_start;
+}
+
+static double do_memcpy_gettimeofday(memcpy_t fn, size_t len, bool prefault)
+{
+ struct timeval tv_start, tv_end, tv_diff;
+ void *src = NULL, *dst = NULL;
+ int i;
+
+ alloc_mem(&src, &dst, len);
+
+ if (prefault)
+ fn(dst, src, len);
+
+ BUG_ON(gettimeofday(&tv_start, NULL));
+ for (i = 0; i < iterations; ++i)
+ fn(dst, src, len);
+ BUG_ON(gettimeofday(&tv_end, NULL));
+
+ timersub(&tv_end, &tv_start, &tv_diff);
+
+ free(src);
+ free(dst);
+ return (double)((double)len / timeval2double(&tv_diff));
+}
+
+#define pf (no_prefault ? 0 : 1)
+
+#define print_bps(x) do { \
+ if (x < K) \
+ printf(" %14lf B/Sec", x); \
+ else if (x < K * K) \
+ printf(" %14lfd KB/Sec", x / K); \
+ else if (x < K * K * K) \
+ printf(" %14lf MB/Sec", x / K / K); \
+ else \
+ printf(" %14lf GB/Sec", x / K / K / K); \
+ } while (0)
+
+int bench_mem_memcpy(int argc, const char **argv,
+ const char *prefix __maybe_unused)
+{
+ int i;
+ size_t len;
+ double result_bps[2];
+ u64 result_cycle[2];
+
+ argc = parse_options(argc, argv, options,
+ bench_mem_memcpy_usage, 0);
+
+ if (use_cycle)
+ init_cycle();
+
+ len = (size_t)perf_atoll((char *)length_str);
+
+ result_cycle[0] = result_cycle[1] = 0ULL;
+ result_bps[0] = result_bps[1] = 0.0;
+
+ if ((s64)len <= 0) {
+ fprintf(stderr, "Invalid length:%s\n", length_str);
+ return 1;
+ }
+
+ /* same to without specifying either of prefault and no-prefault */
+ if (only_prefault && no_prefault)
+ only_prefault = no_prefault = false;
+
+ for (i = 0; routines[i].name; i++) {
+ if (!strcmp(routines[i].name, routine))
+ break;
+ }
+ if (!routines[i].name) {
+ printf("Unknown routine:%s\n", routine);
+ printf("Available routines...\n");
+ for (i = 0; routines[i].name; i++) {
+ printf("\t%s ... %s\n",
+ routines[i].name, routines[i].desc);
+ }
+ return 1;
+ }
+
+ if (bench_format == BENCH_FORMAT_DEFAULT)
+ printf("# Copying %s Bytes ...\n\n", length_str);
+
+ if (!only_prefault && !no_prefault) {
+ /* show both of results */
+ if (use_cycle) {
+ result_cycle[0] =
+ do_memcpy_cycle(routines[i].fn, len, false);
+ result_cycle[1] =
+ do_memcpy_cycle(routines[i].fn, len, true);
+ } else {
+ result_bps[0] =
+ do_memcpy_gettimeofday(routines[i].fn,
+ len, false);
+ result_bps[1] =
+ do_memcpy_gettimeofday(routines[i].fn,
+ len, true);
+ }
+ } else {
+ if (use_cycle) {
+ result_cycle[pf] =
+ do_memcpy_cycle(routines[i].fn,
+ len, only_prefault);
+ } else {
+ result_bps[pf] =
+ do_memcpy_gettimeofday(routines[i].fn,
+ len, only_prefault);
+ }
+ }
+
+ switch (bench_format) {
+ case BENCH_FORMAT_DEFAULT:
+ if (!only_prefault && !no_prefault) {
+ if (use_cycle) {
+ printf(" %14lf Cycle/Byte\n",
+ (double)result_cycle[0]
+ / (double)len);
+ printf(" %14lf Cycle/Byte (with prefault)\n",
+ (double)result_cycle[1]
+ / (double)len);
+ } else {
+ print_bps(result_bps[0]);
+ printf("\n");
+ print_bps(result_bps[1]);
+ printf(" (with prefault)\n");
+ }
+ } else {
+ if (use_cycle) {
+ printf(" %14lf Cycle/Byte",
+ (double)result_cycle[pf]
+ / (double)len);
+ } else
+ print_bps(result_bps[pf]);
+
+ printf("%s\n", only_prefault ? " (with prefault)" : "");
+ }
+ break;
+ case BENCH_FORMAT_SIMPLE:
+ if (!only_prefault && !no_prefault) {
+ if (use_cycle) {
+ printf("%lf %lf\n",
+ (double)result_cycle[0] / (double)len,
+ (double)result_cycle[1] / (double)len);
+ } else {
+ printf("%lf %lf\n",
+ result_bps[0], result_bps[1]);
+ }
+ } else {
+ if (use_cycle) {
+ printf("%lf\n", (double)result_cycle[pf]
+ / (double)len);
+ } else
+ printf("%lf\n", result_bps[pf]);
+ }
+ break;
+ default:
+ /* reaching this means there's some disaster: */
+ die("unknown format: %d\n", bench_format);
+ break;
+ }
+
+ return 0;
+}
diff --git a/tools/perf/bench/mem-memset-arch.h b/tools/perf/bench/mem-memset-arch.h
new file mode 100644
index 00000000000..633800cb0dc
--- /dev/null
+++ b/tools/perf/bench/mem-memset-arch.h
@@ -0,0 +1,12 @@
+
+#ifdef HAVE_ARCH_X86_64_SUPPORT
+
+#define MEMSET_FN(fn, name, desc) \
+ extern void *fn(void *, int, size_t);
+
+#include "mem-memset-x86-64-asm-def.h"
+
+#undef MEMSET_FN
+
+#endif
+
diff --git a/tools/perf/bench/mem-memset-x86-64-asm-def.h b/tools/perf/bench/mem-memset-x86-64-asm-def.h
new file mode 100644
index 00000000000..a71dff97c1f
--- /dev/null
+++ b/tools/perf/bench/mem-memset-x86-64-asm-def.h
@@ -0,0 +1,12 @@
+
+MEMSET_FN(__memset,
+ "x86-64-unrolled",
+ "unrolled memset() in arch/x86/lib/memset_64.S")
+
+MEMSET_FN(memset_c,
+ "x86-64-stosq",
+ "movsq-based memset() in arch/x86/lib/memset_64.S")
+
+MEMSET_FN(memset_c_e,
+ "x86-64-stosb",
+ "movsb-based memset() in arch/x86/lib/memset_64.S")
diff --git a/tools/perf/bench/mem-memset-x86-64-asm.S b/tools/perf/bench/mem-memset-x86-64-asm.S
new file mode 100644
index 00000000000..9e5af89ed13
--- /dev/null
+++ b/tools/perf/bench/mem-memset-x86-64-asm.S
@@ -0,0 +1,13 @@
+#define memset MEMSET /* don't hide glibc's memset() */
+#define altinstr_replacement text
+#define globl p2align 4; .globl
+#define Lmemset_c globl memset_c; memset_c
+#define Lmemset_c_e globl memset_c_e; memset_c_e
+#include "../../../arch/x86/lib/memset_64.S"
+
+/*
+ * We need to provide note.GNU-stack section, saying that we want
+ * NOT executable stack. Otherwise the final linking will assume that
+ * the ELF stack should not be restricted at all and set it RWX.
+ */
+.section .note.GNU-stack,"",@progbits
diff --git a/tools/perf/bench/mem-memset.c b/tools/perf/bench/mem-memset.c
new file mode 100644
index 00000000000..9af79d2b18e
--- /dev/null
+++ b/tools/perf/bench/mem-memset.c
@@ -0,0 +1,297 @@
+/*
+ * mem-memset.c
+ *
+ * memset: Simple memory set in various ways
+ *
+ * Trivial clone of mem-memcpy.c.
+ */
+
+#include "../perf.h"
+#include "../util/util.h"
+#include "../util/parse-options.h"
+#include "../util/header.h"
+#include "bench.h"
+#include "mem-memset-arch.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#define K 1024
+
+static const char *length_str = "1MB";
+static const char *routine = "default";
+static int iterations = 1;
+static bool use_cycle;
+static int cycle_fd;
+static bool only_prefault;
+static bool no_prefault;
+
+static const struct option options[] = {
+ OPT_STRING('l', "length", &length_str, "1MB",
+ "Specify length of memory to set. "
+ "Available units: B, KB, MB, GB and TB (upper and lower)"),
+ OPT_STRING('r', "routine", &routine, "default",
+ "Specify routine to set"),
+ OPT_INTEGER('i', "iterations", &iterations,
+ "repeat memset() invocation this number of times"),
+ OPT_BOOLEAN('c', "cycle", &use_cycle,
+ "Use cycles event instead of gettimeofday() for measuring"),
+ OPT_BOOLEAN('o', "only-prefault", &only_prefault,
+ "Show only the result with page faults before memset()"),
+ OPT_BOOLEAN('n', "no-prefault", &no_prefault,
+ "Show only the result without page faults before memset()"),
+ OPT_END()
+};
+
+typedef void *(*memset_t)(void *, int, size_t);
+
+struct routine {
+ const char *name;
+ const char *desc;
+ memset_t fn;
+};
+
+static const struct routine routines[] = {
+ { "default",
+ "Default memset() provided by glibc",
+ memset },
+#ifdef HAVE_ARCH_X86_64_SUPPORT
+
+#define MEMSET_FN(fn, name, desc) { name, desc, fn },
+#include "mem-memset-x86-64-asm-def.h"
+#undef MEMSET_FN
+
+#endif
+
+ { NULL,
+ NULL,
+ NULL }
+};
+
+static const char * const bench_mem_memset_usage[] = {
+ "perf bench mem memset <options>",
+ NULL
+};
+
+static struct perf_event_attr cycle_attr = {
+ .type = PERF_TYPE_HARDWARE,
+ .config = PERF_COUNT_HW_CPU_CYCLES
+};
+
+static void init_cycle(void)
+{
+ cycle_fd = sys_perf_event_open(&cycle_attr, getpid(), -1, -1, 0);
+
+ if (cycle_fd < 0 && errno == ENOSYS)
+ die("No CONFIG_PERF_EVENTS=y kernel support configured?\n");
+ else
+ BUG_ON(cycle_fd < 0);
+}
+
+static u64 get_cycle(void)
+{
+ int ret;
+ u64 clk;
+
+ ret = read(cycle_fd, &clk, sizeof(u64));
+ BUG_ON(ret != sizeof(u64));
+
+ return clk;
+}
+
+static double timeval2double(struct timeval *ts)
+{
+ return (double)ts->tv_sec +
+ (double)ts->tv_usec / (double)1000000;
+}
+
+static void alloc_mem(void **dst, size_t length)
+{
+ *dst = zalloc(length);
+ if (!*dst)
+ die("memory allocation failed - maybe length is too large?\n");
+}
+
+static u64 do_memset_cycle(memset_t fn, size_t len, bool prefault)
+{
+ u64 cycle_start = 0ULL, cycle_end = 0ULL;
+ void *dst = NULL;
+ int i;
+
+ alloc_mem(&dst, len);
+
+ if (prefault)
+ fn(dst, -1, len);
+
+ cycle_start = get_cycle();
+ for (i = 0; i < iterations; ++i)
+ fn(dst, i, len);
+ cycle_end = get_cycle();
+
+ free(dst);
+ return cycle_end - cycle_start;
+}
+
+static double do_memset_gettimeofday(memset_t fn, size_t len, bool prefault)
+{
+ struct timeval tv_start, tv_end, tv_diff;
+ void *dst = NULL;
+ int i;
+
+ alloc_mem(&dst, len);
+
+ if (prefault)
+ fn(dst, -1, len);
+
+ BUG_ON(gettimeofday(&tv_start, NULL));
+ for (i = 0; i < iterations; ++i)
+ fn(dst, i, len);
+ BUG_ON(gettimeofday(&tv_end, NULL));
+
+ timersub(&tv_end, &tv_start, &tv_diff);
+
+ free(dst);
+ return (double)((double)len / timeval2double(&tv_diff));
+}
+
+#define pf (no_prefault ? 0 : 1)
+
+#define print_bps(x) do { \
+ if (x < K) \
+ printf(" %14lf B/Sec", x); \
+ else if (x < K * K) \
+ printf(" %14lfd KB/Sec", x / K); \
+ else if (x < K * K * K) \
+ printf(" %14lf MB/Sec", x / K / K); \
+ else \
+ printf(" %14lf GB/Sec", x / K / K / K); \
+ } while (0)
+
+int bench_mem_memset(int argc, const char **argv,
+ const char *prefix __maybe_unused)
+{
+ int i;
+ size_t len;
+ double result_bps[2];
+ u64 result_cycle[2];
+
+ argc = parse_options(argc, argv, options,
+ bench_mem_memset_usage, 0);
+
+ if (use_cycle)
+ init_cycle();
+
+ len = (size_t)perf_atoll((char *)length_str);
+
+ result_cycle[0] = result_cycle[1] = 0ULL;
+ result_bps[0] = result_bps[1] = 0.0;
+
+ if ((s64)len <= 0) {
+ fprintf(stderr, "Invalid length:%s\n", length_str);
+ return 1;
+ }
+
+ /* same to without specifying either of prefault and no-prefault */
+ if (only_prefault && no_prefault)
+ only_prefault = no_prefault = false;
+
+ for (i = 0; routines[i].name; i++) {
+ if (!strcmp(routines[i].name, routine))
+ break;
+ }
+ if (!routines[i].name) {
+ printf("Unknown routine:%s\n", routine);
+ printf("Available routines...\n");
+ for (i = 0; routines[i].name; i++) {
+ printf("\t%s ... %s\n",
+ routines[i].name, routines[i].desc);
+ }
+ return 1;
+ }
+
+ if (bench_format == BENCH_FORMAT_DEFAULT)
+ printf("# Copying %s Bytes ...\n\n", length_str);
+
+ if (!only_prefault && !no_prefault) {
+ /* show both of results */
+ if (use_cycle) {
+ result_cycle[0] =
+ do_memset_cycle(routines[i].fn, len, false);
+ result_cycle[1] =
+ do_memset_cycle(routines[i].fn, len, true);
+ } else {
+ result_bps[0] =
+ do_memset_gettimeofday(routines[i].fn,
+ len, false);
+ result_bps[1] =
+ do_memset_gettimeofday(routines[i].fn,
+ len, true);
+ }
+ } else {
+ if (use_cycle) {
+ result_cycle[pf] =
+ do_memset_cycle(routines[i].fn,
+ len, only_prefault);
+ } else {
+ result_bps[pf] =
+ do_memset_gettimeofday(routines[i].fn,
+ len, only_prefault);
+ }
+ }
+
+ switch (bench_format) {
+ case BENCH_FORMAT_DEFAULT:
+ if (!only_prefault && !no_prefault) {
+ if (use_cycle) {
+ printf(" %14lf Cycle/Byte\n",
+ (double)result_cycle[0]
+ / (double)len);
+ printf(" %14lf Cycle/Byte (with prefault)\n ",
+ (double)result_cycle[1]
+ / (double)len);
+ } else {
+ print_bps(result_bps[0]);
+ printf("\n");
+ print_bps(result_bps[1]);
+ printf(" (with prefault)\n");
+ }
+ } else {
+ if (use_cycle) {
+ printf(" %14lf Cycle/Byte",
+ (double)result_cycle[pf]
+ / (double)len);
+ } else
+ print_bps(result_bps[pf]);
+
+ printf("%s\n", only_prefault ? " (with prefault)" : "");
+ }
+ break;
+ case BENCH_FORMAT_SIMPLE:
+ if (!only_prefault && !no_prefault) {
+ if (use_cycle) {
+ printf("%lf %lf\n",
+ (double)result_cycle[0] / (double)len,
+ (double)result_cycle[1] / (double)len);
+ } else {
+ printf("%lf %lf\n",
+ result_bps[0], result_bps[1]);
+ }
+ } else {
+ if (use_cycle) {
+ printf("%lf\n", (double)result_cycle[pf]
+ / (double)len);
+ } else
+ printf("%lf\n", result_bps[pf]);
+ }
+ break;
+ default:
+ /* reaching this means there's some disaster: */
+ die("unknown format: %d\n", bench_format);
+ break;
+ }
+
+ return 0;
+}
diff --git a/tools/perf/bench/numa.c b/tools/perf/bench/numa.c
new file mode 100644
index 00000000000..ebfa163b80b
--- /dev/null
+++ b/tools/perf/bench/numa.c
@@ -0,0 +1,1744 @@
+/*
+ * numa.c
+ *
+ * numa: Simulate NUMA-sensitive workload and measure their NUMA performance
+ */
+
+#include "../perf.h"
+#include "../builtin.h"
+#include "../util/util.h"
+#include "../util/parse-options.h"
+
+#include "bench.h"
+
+#include <errno.h>
+#include <sched.h>
+#include <stdio.h>
+#include <assert.h>
+#include <malloc.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <pthread.h>
+#include <sys/mman.h>
+#include <sys/time.h>
+#include <sys/wait.h>
+#include <sys/prctl.h>
+#include <sys/types.h>
+
+#include <numa.h>
+#include <numaif.h>
+
+/*
+ * Regular printout to the terminal, supressed if -q is specified:
+ */
+#define tprintf(x...) do { if (g && g->p.show_details >= 0) printf(x); } while (0)
+
+/*
+ * Debug printf:
+ */
+#define dprintf(x...) do { if (g && g->p.show_details >= 1) printf(x); } while (0)
+
+struct thread_data {
+ int curr_cpu;
+ cpu_set_t bind_cpumask;
+ int bind_node;
+ u8 *process_data;
+ int process_nr;
+ int thread_nr;
+ int task_nr;
+ unsigned int loops_done;
+ u64 val;
+ u64 runtime_ns;
+ pthread_mutex_t *process_lock;
+};
+
+/* Parameters set by options: */
+
+struct params {
+ /* Startup synchronization: */
+ bool serialize_startup;
+
+ /* Task hierarchy: */
+ int nr_proc;
+ int nr_threads;
+
+ /* Working set sizes: */
+ const char *mb_global_str;
+ const char *mb_proc_str;
+ const char *mb_proc_locked_str;
+ const char *mb_thread_str;
+
+ double mb_global;
+ double mb_proc;
+ double mb_proc_locked;
+ double mb_thread;
+
+ /* Access patterns to the working set: */
+ bool data_reads;
+ bool data_writes;
+ bool data_backwards;
+ bool data_zero_memset;
+ bool data_rand_walk;
+ u32 nr_loops;
+ u32 nr_secs;
+ u32 sleep_usecs;
+
+ /* Working set initialization: */
+ bool init_zero;
+ bool init_random;
+ bool init_cpu0;
+
+ /* Misc options: */
+ int show_details;
+ int run_all;
+ int thp;
+
+ long bytes_global;
+ long bytes_process;
+ long bytes_process_locked;
+ long bytes_thread;
+
+ int nr_tasks;
+ bool show_quiet;
+
+ bool show_convergence;
+ bool measure_convergence;
+
+ int perturb_secs;
+ int nr_cpus;
+ int nr_nodes;
+
+ /* Affinity options -C and -N: */
+ char *cpu_list_str;
+ char *node_list_str;
+};
+
+
+/* Global, read-writable area, accessible to all processes and threads: */
+
+struct global_info {
+ u8 *data;
+
+ pthread_mutex_t startup_mutex;
+ int nr_tasks_started;
+
+ pthread_mutex_t startup_done_mutex;
+
+ pthread_mutex_t start_work_mutex;
+ int nr_tasks_working;
+
+ pthread_mutex_t stop_work_mutex;
+ u64 bytes_done;
+
+ struct thread_data *threads;
+
+ /* Convergence latency measurement: */
+ bool all_converged;
+ bool stop_work;
+
+ int print_once;
+
+ struct params p;
+};
+
+static struct global_info *g = NULL;
+
+static int parse_cpus_opt(const struct option *opt, const char *arg, int unset);
+static int parse_nodes_opt(const struct option *opt, const char *arg, int unset);
+
+struct params p0;
+
+static const struct option options[] = {
+ OPT_INTEGER('p', "nr_proc" , &p0.nr_proc, "number of processes"),
+ OPT_INTEGER('t', "nr_threads" , &p0.nr_threads, "number of threads per process"),
+
+ OPT_STRING('G', "mb_global" , &p0.mb_global_str, "MB", "global memory (MBs)"),
+ OPT_STRING('P', "mb_proc" , &p0.mb_proc_str, "MB", "process memory (MBs)"),
+ OPT_STRING('L', "mb_proc_locked", &p0.mb_proc_locked_str,"MB", "process serialized/locked memory access (MBs), <= process_memory"),
+ OPT_STRING('T', "mb_thread" , &p0.mb_thread_str, "MB", "thread memory (MBs)"),
+
+ OPT_UINTEGER('l', "nr_loops" , &p0.nr_loops, "max number of loops to run"),
+ OPT_UINTEGER('s', "nr_secs" , &p0.nr_secs, "max number of seconds to run"),
+ OPT_UINTEGER('u', "usleep" , &p0.sleep_usecs, "usecs to sleep per loop iteration"),
+
+ OPT_BOOLEAN('R', "data_reads" , &p0.data_reads, "access the data via writes (can be mixed with -W)"),
+ OPT_BOOLEAN('W', "data_writes" , &p0.data_writes, "access the data via writes (can be mixed with -R)"),
+ OPT_BOOLEAN('B', "data_backwards", &p0.data_backwards, "access the data backwards as well"),
+ OPT_BOOLEAN('Z', "data_zero_memset", &p0.data_zero_memset,"access the data via glibc bzero only"),
+ OPT_BOOLEAN('r', "data_rand_walk", &p0.data_rand_walk, "access the data with random (32bit LFSR) walk"),
+
+
+ OPT_BOOLEAN('z', "init_zero" , &p0.init_zero, "bzero the initial allocations"),
+ OPT_BOOLEAN('I', "init_random" , &p0.init_random, "randomize the contents of the initial allocations"),
+ OPT_BOOLEAN('0', "init_cpu0" , &p0.init_cpu0, "do the initial allocations on CPU#0"),
+ OPT_INTEGER('x', "perturb_secs", &p0.perturb_secs, "perturb thread 0/0 every X secs, to test convergence stability"),
+
+ OPT_INCR ('d', "show_details" , &p0.show_details, "Show details"),
+ OPT_INCR ('a', "all" , &p0.run_all, "Run all tests in the suite"),
+ OPT_INTEGER('H', "thp" , &p0.thp, "MADV_NOHUGEPAGE < 0 < MADV_HUGEPAGE"),
+ OPT_BOOLEAN('c', "show_convergence", &p0.show_convergence, "show convergence details"),
+ OPT_BOOLEAN('m', "measure_convergence", &p0.measure_convergence, "measure convergence latency"),
+ OPT_BOOLEAN('q', "quiet" , &p0.show_quiet, "bzero the initial allocations"),
+ OPT_BOOLEAN('S', "serialize-startup", &p0.serialize_startup,"serialize thread startup"),
+
+ /* Special option string parsing callbacks: */
+ OPT_CALLBACK('C', "cpus", NULL, "cpu[,cpu2,...cpuN]",
+ "bind the first N tasks to these specific cpus (the rest is unbound)",
+ parse_cpus_opt),
+ OPT_CALLBACK('M', "memnodes", NULL, "node[,node2,...nodeN]",
+ "bind the first N tasks to these specific memory nodes (the rest is unbound)",
+ parse_nodes_opt),
+ OPT_END()
+};
+
+static const char * const bench_numa_usage[] = {
+ "perf bench numa <options>",
+ NULL
+};
+
+static const char * const numa_usage[] = {
+ "perf bench numa mem [<options>]",
+ NULL
+};
+
+static cpu_set_t bind_to_cpu(int target_cpu)
+{
+ cpu_set_t orig_mask, mask;
+ int ret;
+
+ ret = sched_getaffinity(0, sizeof(orig_mask), &orig_mask);
+ BUG_ON(ret);
+
+ CPU_ZERO(&mask);
+
+ if (target_cpu == -1) {
+ int cpu;
+
+ for (cpu = 0; cpu < g->p.nr_cpus; cpu++)
+ CPU_SET(cpu, &mask);
+ } else {
+ BUG_ON(target_cpu < 0 || target_cpu >= g->p.nr_cpus);
+ CPU_SET(target_cpu, &mask);
+ }
+
+ ret = sched_setaffinity(0, sizeof(mask), &mask);
+ BUG_ON(ret);
+
+ return orig_mask;
+}
+
+static cpu_set_t bind_to_node(int target_node)
+{
+ int cpus_per_node = g->p.nr_cpus/g->p.nr_nodes;
+ cpu_set_t orig_mask, mask;
+ int cpu;
+ int ret;
+
+ BUG_ON(cpus_per_node*g->p.nr_nodes != g->p.nr_cpus);
+ BUG_ON(!cpus_per_node);
+
+ ret = sched_getaffinity(0, sizeof(orig_mask), &orig_mask);
+ BUG_ON(ret);
+
+ CPU_ZERO(&mask);
+
+ if (target_node == -1) {
+ for (cpu = 0; cpu < g->p.nr_cpus; cpu++)
+ CPU_SET(cpu, &mask);
+ } else {
+ int cpu_start = (target_node + 0) * cpus_per_node;
+ int cpu_stop = (target_node + 1) * cpus_per_node;
+
+ BUG_ON(cpu_stop > g->p.nr_cpus);
+
+ for (cpu = cpu_start; cpu < cpu_stop; cpu++)
+ CPU_SET(cpu, &mask);
+ }
+
+ ret = sched_setaffinity(0, sizeof(mask), &mask);
+ BUG_ON(ret);
+
+ return orig_mask;
+}
+
+static void bind_to_cpumask(cpu_set_t mask)
+{
+ int ret;
+
+ ret = sched_setaffinity(0, sizeof(mask), &mask);
+ BUG_ON(ret);
+}
+
+static void mempol_restore(void)
+{
+ int ret;
+
+ ret = set_mempolicy(MPOL_DEFAULT, NULL, g->p.nr_nodes-1);
+
+ BUG_ON(ret);
+}
+
+static void bind_to_memnode(int node)
+{
+ unsigned long nodemask;
+ int ret;
+
+ if (node == -1)
+ return;
+
+ BUG_ON(g->p.nr_nodes > (int)sizeof(nodemask));
+ nodemask = 1L << node;
+
+ ret = set_mempolicy(MPOL_BIND, &nodemask, sizeof(nodemask)*8);
+ dprintf("binding to node %d, mask: %016lx => %d\n", node, nodemask, ret);
+
+ BUG_ON(ret);
+}
+
+#define HPSIZE (2*1024*1024)
+
+#define set_taskname(fmt...) \
+do { \
+ char name[20]; \
+ \
+ snprintf(name, 20, fmt); \
+ prctl(PR_SET_NAME, name); \
+} while (0)
+
+static u8 *alloc_data(ssize_t bytes0, int map_flags,
+ int init_zero, int init_cpu0, int thp, int init_random)
+{
+ cpu_set_t orig_mask;
+ ssize_t bytes;
+ u8 *buf;
+ int ret;
+
+ if (!bytes0)
+ return NULL;
+
+ /* Allocate and initialize all memory on CPU#0: */
+ if (init_cpu0) {
+ orig_mask = bind_to_node(0);
+ bind_to_memnode(0);
+ }
+
+ bytes = bytes0 + HPSIZE;
+
+ buf = (void *)mmap(0, bytes, PROT_READ|PROT_WRITE, MAP_ANON|map_flags, -1, 0);
+ BUG_ON(buf == (void *)-1);
+
+ if (map_flags == MAP_PRIVATE) {
+ if (thp > 0) {
+ ret = madvise(buf, bytes, MADV_HUGEPAGE);
+ if (ret && !g->print_once) {
+ g->print_once = 1;
+ printf("WARNING: Could not enable THP - do: 'echo madvise > /sys/kernel/mm/transparent_hugepage/enabled'\n");
+ }
+ }
+ if (thp < 0) {
+ ret = madvise(buf, bytes, MADV_NOHUGEPAGE);
+ if (ret && !g->print_once) {
+ g->print_once = 1;
+ printf("WARNING: Could not disable THP: run a CONFIG_TRANSPARENT_HUGEPAGE kernel?\n");
+ }
+ }
+ }
+
+ if (init_zero) {
+ bzero(buf, bytes);
+ } else {
+ /* Initialize random contents, different in each word: */
+ if (init_random) {
+ u64 *wbuf = (void *)buf;
+ long off = rand();
+ long i;
+
+ for (i = 0; i < bytes/8; i++)
+ wbuf[i] = i + off;
+ }
+ }
+
+ /* Align to 2MB boundary: */
+ buf = (void *)(((unsigned long)buf + HPSIZE-1) & ~(HPSIZE-1));
+
+ /* Restore affinity: */
+ if (init_cpu0) {
+ bind_to_cpumask(orig_mask);
+ mempol_restore();
+ }
+
+ return buf;
+}
+
+static void free_data(void *data, ssize_t bytes)
+{
+ int ret;
+
+ if (!data)
+ return;
+
+ ret = munmap(data, bytes);
+ BUG_ON(ret);
+}
+
+/*
+ * Create a shared memory buffer that can be shared between processes, zeroed:
+ */
+static void * zalloc_shared_data(ssize_t bytes)
+{
+ return alloc_data(bytes, MAP_SHARED, 1, g->p.init_cpu0, g->p.thp, g->p.init_random);
+}
+
+/*
+ * Create a shared memory buffer that can be shared between processes:
+ */
+static void * setup_shared_data(ssize_t bytes)
+{
+ return alloc_data(bytes, MAP_SHARED, 0, g->p.init_cpu0, g->p.thp, g->p.init_random);
+}
+
+/*
+ * Allocate process-local memory - this will either be shared between
+ * threads of this process, or only be accessed by this thread:
+ */
+static void * setup_private_data(ssize_t bytes)
+{
+ return alloc_data(bytes, MAP_PRIVATE, 0, g->p.init_cpu0, g->p.thp, g->p.init_random);
+}
+
+/*
+ * Return a process-shared (global) mutex:
+ */
+static void init_global_mutex(pthread_mutex_t *mutex)
+{
+ pthread_mutexattr_t attr;
+
+ pthread_mutexattr_init(&attr);
+ pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
+ pthread_mutex_init(mutex, &attr);
+}
+
+static int parse_cpu_list(const char *arg)
+{
+ p0.cpu_list_str = strdup(arg);
+
+ dprintf("got CPU list: {%s}\n", p0.cpu_list_str);
+
+ return 0;
+}
+
+static int parse_setup_cpu_list(void)
+{
+ struct thread_data *td;
+ char *str0, *str;
+ int t;
+
+ if (!g->p.cpu_list_str)
+ return 0;
+
+ dprintf("g->p.nr_tasks: %d\n", g->p.nr_tasks);
+
+ str0 = str = strdup(g->p.cpu_list_str);
+ t = 0;
+
+ BUG_ON(!str);
+
+ tprintf("# binding tasks to CPUs:\n");
+ tprintf("# ");
+
+ while (true) {
+ int bind_cpu, bind_cpu_0, bind_cpu_1;
+ char *tok, *tok_end, *tok_step, *tok_len, *tok_mul;
+ int bind_len;
+ int step;
+ int mul;
+
+ tok = strsep(&str, ",");
+ if (!tok)
+ break;
+
+ tok_end = strstr(tok, "-");
+
+ dprintf("\ntoken: {%s}, end: {%s}\n", tok, tok_end);
+ if (!tok_end) {
+ /* Single CPU specified: */
+ bind_cpu_0 = bind_cpu_1 = atol(tok);
+ } else {
+ /* CPU range specified (for example: "5-11"): */
+ bind_cpu_0 = atol(tok);
+ bind_cpu_1 = atol(tok_end + 1);
+ }
+
+ step = 1;
+ tok_step = strstr(tok, "#");
+ if (tok_step) {
+ step = atol(tok_step + 1);
+ BUG_ON(step <= 0 || step >= g->p.nr_cpus);
+ }
+
+ /*
+ * Mask length.
+ * Eg: "--cpus 8_4-16#4" means: '--cpus 8_4,12_4,16_4',
+ * where the _4 means the next 4 CPUs are allowed.
+ */
+ bind_len = 1;
+ tok_len = strstr(tok, "_");
+ if (tok_len) {
+ bind_len = atol(tok_len + 1);
+ BUG_ON(bind_len <= 0 || bind_len > g->p.nr_cpus);
+ }
+
+ /* Multiplicator shortcut, "0x8" is a shortcut for: "0,0,0,0,0,0,0,0" */
+ mul = 1;
+ tok_mul = strstr(tok, "x");
+ if (tok_mul) {
+ mul = atol(tok_mul + 1);
+ BUG_ON(mul <= 0);
+ }
+
+ dprintf("CPUs: %d_%d-%d#%dx%d\n", bind_cpu_0, bind_len, bind_cpu_1, step, mul);
+
+ if (bind_cpu_0 >= g->p.nr_cpus || bind_cpu_1 >= g->p.nr_cpus) {
+ printf("\nTest not applicable, system has only %d CPUs.\n", g->p.nr_cpus);
+ return -1;
+ }
+
+ BUG_ON(bind_cpu_0 < 0 || bind_cpu_1 < 0);
+ BUG_ON(bind_cpu_0 > bind_cpu_1);
+
+ for (bind_cpu = bind_cpu_0; bind_cpu <= bind_cpu_1; bind_cpu += step) {
+ int i;
+
+ for (i = 0; i < mul; i++) {
+ int cpu;
+
+ if (t >= g->p.nr_tasks) {
+ printf("\n# NOTE: ignoring bind CPUs starting at CPU#%d\n #", bind_cpu);
+ goto out;
+ }
+ td = g->threads + t;
+
+ if (t)
+ tprintf(",");
+ if (bind_len > 1) {
+ tprintf("%2d/%d", bind_cpu, bind_len);
+ } else {
+ tprintf("%2d", bind_cpu);
+ }
+
+ CPU_ZERO(&td->bind_cpumask);
+ for (cpu = bind_cpu; cpu < bind_cpu+bind_len; cpu++) {
+ BUG_ON(cpu < 0 || cpu >= g->p.nr_cpus);
+ CPU_SET(cpu, &td->bind_cpumask);
+ }
+ t++;
+ }
+ }
+ }
+out:
+
+ tprintf("\n");
+
+ if (t < g->p.nr_tasks)
+ printf("# NOTE: %d tasks bound, %d tasks unbound\n", t, g->p.nr_tasks - t);
+
+ free(str0);
+ return 0;
+}
+
+static int parse_cpus_opt(const struct option *opt __maybe_unused,
+ const char *arg, int unset __maybe_unused)
+{
+ if (!arg)
+ return -1;
+
+ return parse_cpu_list(arg);
+}
+
+static int parse_node_list(const char *arg)
+{
+ p0.node_list_str = strdup(arg);
+
+ dprintf("got NODE list: {%s}\n", p0.node_list_str);
+
+ return 0;
+}
+
+static int parse_setup_node_list(void)
+{
+ struct thread_data *td;
+ char *str0, *str;
+ int t;
+
+ if (!g->p.node_list_str)
+ return 0;
+
+ dprintf("g->p.nr_tasks: %d\n", g->p.nr_tasks);
+
+ str0 = str = strdup(g->p.node_list_str);
+ t = 0;
+
+ BUG_ON(!str);
+
+ tprintf("# binding tasks to NODEs:\n");
+ tprintf("# ");
+
+ while (true) {
+ int bind_node, bind_node_0, bind_node_1;
+ char *tok, *tok_end, *tok_step, *tok_mul;
+ int step;
+ int mul;
+
+ tok = strsep(&str, ",");
+ if (!tok)
+ break;
+
+ tok_end = strstr(tok, "-");
+
+ dprintf("\ntoken: {%s}, end: {%s}\n", tok, tok_end);
+ if (!tok_end) {
+ /* Single NODE specified: */
+ bind_node_0 = bind_node_1 = atol(tok);
+ } else {
+ /* NODE range specified (for example: "5-11"): */
+ bind_node_0 = atol(tok);
+ bind_node_1 = atol(tok_end + 1);
+ }
+
+ step = 1;
+ tok_step = strstr(tok, "#");
+ if (tok_step) {
+ step = atol(tok_step + 1);
+ BUG_ON(step <= 0 || step >= g->p.nr_nodes);
+ }
+
+ /* Multiplicator shortcut, "0x8" is a shortcut for: "0,0,0,0,0,0,0,0" */
+ mul = 1;
+ tok_mul = strstr(tok, "x");
+ if (tok_mul) {
+ mul = atol(tok_mul + 1);
+ BUG_ON(mul <= 0);
+ }
+
+ dprintf("NODEs: %d-%d #%d\n", bind_node_0, bind_node_1, step);
+
+ if (bind_node_0 >= g->p.nr_nodes || bind_node_1 >= g->p.nr_nodes) {
+ printf("\nTest not applicable, system has only %d nodes.\n", g->p.nr_nodes);
+ return -1;
+ }
+
+ BUG_ON(bind_node_0 < 0 || bind_node_1 < 0);
+ BUG_ON(bind_node_0 > bind_node_1);
+
+ for (bind_node = bind_node_0; bind_node <= bind_node_1; bind_node += step) {
+ int i;
+
+ for (i = 0; i < mul; i++) {
+ if (t >= g->p.nr_tasks) {
+ printf("\n# NOTE: ignoring bind NODEs starting at NODE#%d\n", bind_node);
+ goto out;
+ }
+ td = g->threads + t;
+
+ if (!t)
+ tprintf(" %2d", bind_node);
+ else
+ tprintf(",%2d", bind_node);
+
+ td->bind_node = bind_node;
+ t++;
+ }
+ }
+ }
+out:
+
+ tprintf("\n");
+
+ if (t < g->p.nr_tasks)
+ printf("# NOTE: %d tasks mem-bound, %d tasks unbound\n", t, g->p.nr_tasks - t);
+
+ free(str0);
+ return 0;
+}
+
+static int parse_nodes_opt(const struct option *opt __maybe_unused,
+ const char *arg, int unset __maybe_unused)
+{
+ if (!arg)
+ return -1;
+
+ return parse_node_list(arg);
+
+ return 0;
+}
+
+#define BIT(x) (1ul << x)
+
+static inline uint32_t lfsr_32(uint32_t lfsr)
+{
+ const uint32_t taps = BIT(1) | BIT(5) | BIT(6) | BIT(31);
+ return (lfsr>>1) ^ ((0x0u - (lfsr & 0x1u)) & taps);
+}
+
+/*
+ * Make sure there's real data dependency to RAM (when read
+ * accesses are enabled), so the compiler, the CPU and the
+ * kernel (KSM, zero page, etc.) cannot optimize away RAM
+ * accesses:
+ */
+static inline u64 access_data(u64 *data __attribute__((unused)), u64 val)
+{
+ if (g->p.data_reads)
+ val += *data;
+ if (g->p.data_writes)
+ *data = val + 1;
+ return val;
+}
+
+/*
+ * The worker process does two types of work, a forwards going
+ * loop and a backwards going loop.
+ *
+ * We do this so that on multiprocessor systems we do not create
+ * a 'train' of processing, with highly synchronized processes,
+ * skewing the whole benchmark.
+ */
+static u64 do_work(u8 *__data, long bytes, int nr, int nr_max, int loop, u64 val)
+{
+ long words = bytes/sizeof(u64);
+ u64 *data = (void *)__data;
+ long chunk_0, chunk_1;
+ u64 *d0, *d, *d1;
+ long off;
+ long i;
+
+ BUG_ON(!data && words);
+ BUG_ON(data && !words);
+
+ if (!data)
+ return val;
+
+ /* Very simple memset() work variant: */
+ if (g->p.data_zero_memset && !g->p.data_rand_walk) {
+ bzero(data, bytes);
+ return val;
+ }
+
+ /* Spread out by PID/TID nr and by loop nr: */
+ chunk_0 = words/nr_max;
+ chunk_1 = words/g->p.nr_loops;
+ off = nr*chunk_0 + loop*chunk_1;
+
+ while (off >= words)
+ off -= words;
+
+ if (g->p.data_rand_walk) {
+ u32 lfsr = nr + loop + val;
+ int j;
+
+ for (i = 0; i < words/1024; i++) {
+ long start, end;
+
+ lfsr = lfsr_32(lfsr);
+
+ start = lfsr % words;
+ end = min(start + 1024, words-1);
+
+ if (g->p.data_zero_memset) {
+ bzero(data + start, (end-start) * sizeof(u64));
+ } else {
+ for (j = start; j < end; j++)
+ val = access_data(data + j, val);
+ }
+ }
+ } else if (!g->p.data_backwards || (nr + loop) & 1) {
+
+ d0 = data + off;
+ d = data + off + 1;
+ d1 = data + words;
+
+ /* Process data forwards: */
+ for (;;) {
+ if (unlikely(d >= d1))
+ d = data;
+ if (unlikely(d == d0))
+ break;
+
+ val = access_data(d, val);
+
+ d++;
+ }
+ } else {
+ /* Process data backwards: */
+
+ d0 = data + off;
+ d = data + off - 1;
+ d1 = data + words;
+
+ /* Process data forwards: */
+ for (;;) {
+ if (unlikely(d < data))
+ d = data + words-1;
+ if (unlikely(d == d0))
+ break;
+
+ val = access_data(d, val);
+
+ d--;
+ }
+ }
+
+ return val;
+}
+
+static void update_curr_cpu(int task_nr, unsigned long bytes_worked)
+{
+ unsigned int cpu;
+
+ cpu = sched_getcpu();
+
+ g->threads[task_nr].curr_cpu = cpu;
+ prctl(0, bytes_worked);
+}
+
+#define MAX_NR_NODES 64
+
+/*
+ * Count the number of nodes a process's threads
+ * are spread out on.
+ *
+ * A count of 1 means that the process is compressed
+ * to a single node. A count of g->p.nr_nodes means it's
+ * spread out on the whole system.
+ */
+static int count_process_nodes(int process_nr)
+{
+ char node_present[MAX_NR_NODES] = { 0, };
+ int nodes;
+ int n, t;
+
+ for (t = 0; t < g->p.nr_threads; t++) {
+ struct thread_data *td;
+ int task_nr;
+ int node;
+
+ task_nr = process_nr*g->p.nr_threads + t;
+ td = g->threads + task_nr;
+
+ node = numa_node_of_cpu(td->curr_cpu);
+ node_present[node] = 1;
+ }
+
+ nodes = 0;
+
+ for (n = 0; n < MAX_NR_NODES; n++)
+ nodes += node_present[n];
+
+ return nodes;
+}
+
+/*
+ * Count the number of distinct process-threads a node contains.
+ *
+ * A count of 1 means that the node contains only a single
+ * process. If all nodes on the system contain at most one
+ * process then we are well-converged.
+ */
+static int count_node_processes(int node)
+{
+ int processes = 0;
+ int t, p;
+
+ for (p = 0; p < g->p.nr_proc; p++) {
+ for (t = 0; t < g->p.nr_threads; t++) {
+ struct thread_data *td;
+ int task_nr;
+ int n;
+
+ task_nr = p*g->p.nr_threads + t;
+ td = g->threads + task_nr;
+
+ n = numa_node_of_cpu(td->curr_cpu);
+ if (n == node) {
+ processes++;
+ break;
+ }
+ }
+ }
+
+ return processes;
+}
+
+static void calc_convergence_compression(int *strong)
+{
+ unsigned int nodes_min, nodes_max;
+ int p;
+
+ nodes_min = -1;
+ nodes_max = 0;
+
+ for (p = 0; p < g->p.nr_proc; p++) {
+ unsigned int nodes = count_process_nodes(p);
+
+ nodes_min = min(nodes, nodes_min);
+ nodes_max = max(nodes, nodes_max);
+ }
+
+ /* Strong convergence: all threads compress on a single node: */
+ if (nodes_min == 1 && nodes_max == 1) {
+ *strong = 1;
+ } else {
+ *strong = 0;
+ tprintf(" {%d-%d}", nodes_min, nodes_max);
+ }
+}
+
+static void calc_convergence(double runtime_ns_max, double *convergence)
+{
+ unsigned int loops_done_min, loops_done_max;
+ int process_groups;
+ int nodes[MAX_NR_NODES];
+ int distance;
+ int nr_min;
+ int nr_max;
+ int strong;
+ int sum;
+ int nr;
+ int node;
+ int cpu;
+ int t;
+
+ if (!g->p.show_convergence && !g->p.measure_convergence)
+ return;
+
+ for (node = 0; node < g->p.nr_nodes; node++)
+ nodes[node] = 0;
+
+ loops_done_min = -1;
+ loops_done_max = 0;
+
+ for (t = 0; t < g->p.nr_tasks; t++) {
+ struct thread_data *td = g->threads + t;
+ unsigned int loops_done;
+
+ cpu = td->curr_cpu;
+
+ /* Not all threads have written it yet: */
+ if (cpu < 0)
+ continue;
+
+ node = numa_node_of_cpu(cpu);
+
+ nodes[node]++;
+
+ loops_done = td->loops_done;
+ loops_done_min = min(loops_done, loops_done_min);
+ loops_done_max = max(loops_done, loops_done_max);
+ }
+
+ nr_max = 0;
+ nr_min = g->p.nr_tasks;
+ sum = 0;
+
+ for (node = 0; node < g->p.nr_nodes; node++) {
+ nr = nodes[node];
+ nr_min = min(nr, nr_min);
+ nr_max = max(nr, nr_max);
+ sum += nr;
+ }
+ BUG_ON(nr_min > nr_max);
+
+ BUG_ON(sum > g->p.nr_tasks);
+
+ if (0 && (sum < g->p.nr_tasks))
+ return;
+
+ /*
+ * Count the number of distinct process groups present
+ * on nodes - when we are converged this will decrease
+ * to g->p.nr_proc:
+ */
+ process_groups = 0;
+
+ for (node = 0; node < g->p.nr_nodes; node++) {
+ int processes = count_node_processes(node);
+
+ nr = nodes[node];
+ tprintf(" %2d/%-2d", nr, processes);
+
+ process_groups += processes;
+ }
+
+ distance = nr_max - nr_min;
+
+ tprintf(" [%2d/%-2d]", distance, process_groups);
+
+ tprintf(" l:%3d-%-3d (%3d)",
+ loops_done_min, loops_done_max, loops_done_max-loops_done_min);
+
+ if (loops_done_min && loops_done_max) {
+ double skew = 1.0 - (double)loops_done_min/loops_done_max;
+
+ tprintf(" [%4.1f%%]", skew * 100.0);
+ }
+
+ calc_convergence_compression(&strong);
+
+ if (strong && process_groups == g->p.nr_proc) {
+ if (!*convergence) {
+ *convergence = runtime_ns_max;
+ tprintf(" (%6.1fs converged)\n", *convergence/1e9);
+ if (g->p.measure_convergence) {
+ g->all_converged = true;
+ g->stop_work = true;
+ }
+ }
+ } else {
+ if (*convergence) {
+ tprintf(" (%6.1fs de-converged)", runtime_ns_max/1e9);
+ *convergence = 0;
+ }
+ tprintf("\n");
+ }
+}
+
+static void show_summary(double runtime_ns_max, int l, double *convergence)
+{
+ tprintf("\r # %5.1f%% [%.1f mins]",
+ (double)(l+1)/g->p.nr_loops*100.0, runtime_ns_max/1e9 / 60.0);
+
+ calc_convergence(runtime_ns_max, convergence);
+
+ if (g->p.show_details >= 0)
+ fflush(stdout);
+}
+
+static void *worker_thread(void *__tdata)
+{
+ struct thread_data *td = __tdata;
+ struct timeval start0, start, stop, diff;
+ int process_nr = td->process_nr;
+ int thread_nr = td->thread_nr;
+ unsigned long last_perturbance;
+ int task_nr = td->task_nr;
+ int details = g->p.show_details;
+ int first_task, last_task;
+ double convergence = 0;
+ u64 val = td->val;
+ double runtime_ns_max;
+ u8 *global_data;
+ u8 *process_data;
+ u8 *thread_data;
+ u64 bytes_done;
+ long work_done;
+ u32 l;
+
+ bind_to_cpumask(td->bind_cpumask);
+ bind_to_memnode(td->bind_node);
+
+ set_taskname("thread %d/%d", process_nr, thread_nr);
+
+ global_data = g->data;
+ process_data = td->process_data;
+ thread_data = setup_private_data(g->p.bytes_thread);
+
+ bytes_done = 0;
+
+ last_task = 0;
+ if (process_nr == g->p.nr_proc-1 && thread_nr == g->p.nr_threads-1)
+ last_task = 1;
+
+ first_task = 0;
+ if (process_nr == 0 && thread_nr == 0)
+ first_task = 1;
+
+ if (details >= 2) {
+ printf("# thread %2d / %2d global mem: %p, process mem: %p, thread mem: %p\n",
+ process_nr, thread_nr, global_data, process_data, thread_data);
+ }
+
+ if (g->p.serialize_startup) {
+ pthread_mutex_lock(&g->startup_mutex);
+ g->nr_tasks_started++;
+ pthread_mutex_unlock(&g->startup_mutex);
+
+ /* Here we will wait for the main process to start us all at once: */
+ pthread_mutex_lock(&g->start_work_mutex);
+ g->nr_tasks_working++;
+
+ /* Last one wake the main process: */
+ if (g->nr_tasks_working == g->p.nr_tasks)
+ pthread_mutex_unlock(&g->startup_done_mutex);
+
+ pthread_mutex_unlock(&g->start_work_mutex);
+ }
+
+ gettimeofday(&start0, NULL);
+
+ start = stop = start0;
+ last_perturbance = start.tv_sec;
+
+ for (l = 0; l < g->p.nr_loops; l++) {
+ start = stop;
+
+ if (g->stop_work)
+ break;
+
+ val += do_work(global_data, g->p.bytes_global, process_nr, g->p.nr_proc, l, val);
+ val += do_work(process_data, g->p.bytes_process, thread_nr, g->p.nr_threads, l, val);
+ val += do_work(thread_data, g->p.bytes_thread, 0, 1, l, val);
+
+ if (g->p.sleep_usecs) {
+ pthread_mutex_lock(td->process_lock);
+ usleep(g->p.sleep_usecs);
+ pthread_mutex_unlock(td->process_lock);
+ }
+ /*
+ * Amount of work to be done under a process-global lock:
+ */
+ if (g->p.bytes_process_locked) {
+ pthread_mutex_lock(td->process_lock);
+ val += do_work(process_data, g->p.bytes_process_locked, thread_nr, g->p.nr_threads, l, val);
+ pthread_mutex_unlock(td->process_lock);
+ }
+
+ work_done = g->p.bytes_global + g->p.bytes_process +
+ g->p.bytes_process_locked + g->p.bytes_thread;
+
+ update_curr_cpu(task_nr, work_done);
+ bytes_done += work_done;
+
+ if (details < 0 && !g->p.perturb_secs && !g->p.measure_convergence && !g->p.nr_secs)
+ continue;
+
+ td->loops_done = l;
+
+ gettimeofday(&stop, NULL);
+
+ /* Check whether our max runtime timed out: */
+ if (g->p.nr_secs) {
+ timersub(&stop, &start0, &diff);
+ if ((u32)diff.tv_sec >= g->p.nr_secs) {
+ g->stop_work = true;
+ break;
+ }
+ }
+
+ /* Update the summary at most once per second: */
+ if (start.tv_sec == stop.tv_sec)
+ continue;
+
+ /*
+ * Perturb the first task's equilibrium every g->p.perturb_secs seconds,
+ * by migrating to CPU#0:
+ */
+ if (first_task && g->p.perturb_secs && (int)(stop.tv_sec - last_perturbance) >= g->p.perturb_secs) {
+ cpu_set_t orig_mask;
+ int target_cpu;
+ int this_cpu;
+
+ last_perturbance = stop.tv_sec;
+
+ /*
+ * Depending on where we are running, move into
+ * the other half of the system, to create some
+ * real disturbance:
+ */
+ this_cpu = g->threads[task_nr].curr_cpu;
+ if (this_cpu < g->p.nr_cpus/2)
+ target_cpu = g->p.nr_cpus-1;
+ else
+ target_cpu = 0;
+
+ orig_mask = bind_to_cpu(target_cpu);
+
+ /* Here we are running on the target CPU already */
+ if (details >= 1)
+ printf(" (injecting perturbalance, moved to CPU#%d)\n", target_cpu);
+
+ bind_to_cpumask(orig_mask);
+ }
+
+ if (details >= 3) {
+ timersub(&stop, &start, &diff);
+ runtime_ns_max = diff.tv_sec * 1000000000;
+ runtime_ns_max += diff.tv_usec * 1000;
+
+ if (details >= 0) {
+ printf(" #%2d / %2d: %14.2lf nsecs/op [val: %016"PRIx64"]\n",
+ process_nr, thread_nr, runtime_ns_max / bytes_done, val);
+ }
+ fflush(stdout);
+ }
+ if (!last_task)
+ continue;
+
+ timersub(&stop, &start0, &diff);
+ runtime_ns_max = diff.tv_sec * 1000000000ULL;
+ runtime_ns_max += diff.tv_usec * 1000ULL;
+
+ show_summary(runtime_ns_max, l, &convergence);
+ }
+
+ gettimeofday(&stop, NULL);
+ timersub(&stop, &start0, &diff);
+ td->runtime_ns = diff.tv_sec * 1000000000ULL;
+ td->runtime_ns += diff.tv_usec * 1000ULL;
+
+ free_data(thread_data, g->p.bytes_thread);
+
+ pthread_mutex_lock(&g->stop_work_mutex);
+ g->bytes_done += bytes_done;
+ pthread_mutex_unlock(&g->stop_work_mutex);
+
+ return NULL;
+}
+
+/*
+ * A worker process starts a couple of threads:
+ */
+static void worker_process(int process_nr)
+{
+ pthread_mutex_t process_lock;
+ struct thread_data *td;
+ pthread_t *pthreads;
+ u8 *process_data;
+ int task_nr;
+ int ret;
+ int t;
+
+ pthread_mutex_init(&process_lock, NULL);
+ set_taskname("process %d", process_nr);
+
+ /*
+ * Pick up the memory policy and the CPU binding of our first thread,
+ * so that we initialize memory accordingly:
+ */
+ task_nr = process_nr*g->p.nr_threads;
+ td = g->threads + task_nr;
+
+ bind_to_memnode(td->bind_node);
+ bind_to_cpumask(td->bind_cpumask);
+
+ pthreads = zalloc(g->p.nr_threads * sizeof(pthread_t));
+ process_data = setup_private_data(g->p.bytes_process);
+
+ if (g->p.show_details >= 3) {
+ printf(" # process %2d global mem: %p, process mem: %p\n",
+ process_nr, g->data, process_data);
+ }
+
+ for (t = 0; t < g->p.nr_threads; t++) {
+ task_nr = process_nr*g->p.nr_threads + t;
+ td = g->threads + task_nr;
+
+ td->process_data = process_data;
+ td->process_nr = process_nr;
+ td->thread_nr = t;
+ td->task_nr = task_nr;
+ td->val = rand();
+ td->curr_cpu = -1;
+ td->process_lock = &process_lock;
+
+ ret = pthread_create(pthreads + t, NULL, worker_thread, td);
+ BUG_ON(ret);
+ }
+
+ for (t = 0; t < g->p.nr_threads; t++) {
+ ret = pthread_join(pthreads[t], NULL);
+ BUG_ON(ret);
+ }
+
+ free_data(process_data, g->p.bytes_process);
+ free(pthreads);
+}
+
+static void print_summary(void)
+{
+ if (g->p.show_details < 0)
+ return;
+
+ printf("\n ###\n");
+ printf(" # %d %s will execute (on %d nodes, %d CPUs):\n",
+ g->p.nr_tasks, g->p.nr_tasks == 1 ? "task" : "tasks", g->p.nr_nodes, g->p.nr_cpus);
+ printf(" # %5dx %5ldMB global shared mem operations\n",
+ g->p.nr_loops, g->p.bytes_global/1024/1024);
+ printf(" # %5dx %5ldMB process shared mem operations\n",
+ g->p.nr_loops, g->p.bytes_process/1024/1024);
+ printf(" # %5dx %5ldMB thread local mem operations\n",
+ g->p.nr_loops, g->p.bytes_thread/1024/1024);
+
+ printf(" ###\n");
+
+ printf("\n ###\n"); fflush(stdout);
+}
+
+static void init_thread_data(void)
+{
+ ssize_t size = sizeof(*g->threads)*g->p.nr_tasks;
+ int t;
+
+ g->threads = zalloc_shared_data(size);
+
+ for (t = 0; t < g->p.nr_tasks; t++) {
+ struct thread_data *td = g->threads + t;
+ int cpu;
+
+ /* Allow all nodes by default: */
+ td->bind_node = -1;
+
+ /* Allow all CPUs by default: */
+ CPU_ZERO(&td->bind_cpumask);
+ for (cpu = 0; cpu < g->p.nr_cpus; cpu++)
+ CPU_SET(cpu, &td->bind_cpumask);
+ }
+}
+
+static void deinit_thread_data(void)
+{
+ ssize_t size = sizeof(*g->threads)*g->p.nr_tasks;
+
+ free_data(g->threads, size);
+}
+
+static int init(void)
+{
+ g = (void *)alloc_data(sizeof(*g), MAP_SHARED, 1, 0, 0 /* THP */, 0);
+
+ /* Copy over options: */
+ g->p = p0;
+
+ g->p.nr_cpus = numa_num_configured_cpus();
+
+ g->p.nr_nodes = numa_max_node() + 1;
+
+ /* char array in count_process_nodes(): */
+ BUG_ON(g->p.nr_nodes > MAX_NR_NODES || g->p.nr_nodes < 0);
+
+ if (g->p.show_quiet && !g->p.show_details)
+ g->p.show_details = -1;
+
+ /* Some memory should be specified: */
+ if (!g->p.mb_global_str && !g->p.mb_proc_str && !g->p.mb_thread_str)
+ return -1;
+
+ if (g->p.mb_global_str) {
+ g->p.mb_global = atof(g->p.mb_global_str);
+ BUG_ON(g->p.mb_global < 0);
+ }
+
+ if (g->p.mb_proc_str) {
+ g->p.mb_proc = atof(g->p.mb_proc_str);
+ BUG_ON(g->p.mb_proc < 0);
+ }
+
+ if (g->p.mb_proc_locked_str) {
+ g->p.mb_proc_locked = atof(g->p.mb_proc_locked_str);
+ BUG_ON(g->p.mb_proc_locked < 0);
+ BUG_ON(g->p.mb_proc_locked > g->p.mb_proc);
+ }
+
+ if (g->p.mb_thread_str) {
+ g->p.mb_thread = atof(g->p.mb_thread_str);
+ BUG_ON(g->p.mb_thread < 0);
+ }
+
+ BUG_ON(g->p.nr_threads <= 0);
+ BUG_ON(g->p.nr_proc <= 0);
+
+ g->p.nr_tasks = g->p.nr_proc*g->p.nr_threads;
+
+ g->p.bytes_global = g->p.mb_global *1024L*1024L;
+ g->p.bytes_process = g->p.mb_proc *1024L*1024L;
+ g->p.bytes_process_locked = g->p.mb_proc_locked *1024L*1024L;
+ g->p.bytes_thread = g->p.mb_thread *1024L*1024L;
+
+ g->data = setup_shared_data(g->p.bytes_global);
+
+ /* Startup serialization: */
+ init_global_mutex(&g->start_work_mutex);
+ init_global_mutex(&g->startup_mutex);
+ init_global_mutex(&g->startup_done_mutex);
+ init_global_mutex(&g->stop_work_mutex);
+
+ init_thread_data();
+
+ tprintf("#\n");
+ if (parse_setup_cpu_list() || parse_setup_node_list())
+ return -1;
+ tprintf("#\n");
+
+ print_summary();
+
+ return 0;
+}
+
+static void deinit(void)
+{
+ free_data(g->data, g->p.bytes_global);
+ g->data = NULL;
+
+ deinit_thread_data();
+
+ free_data(g, sizeof(*g));
+ g = NULL;
+}
+
+/*
+ * Print a short or long result, depending on the verbosity setting:
+ */
+static void print_res(const char *name, double val,
+ const char *txt_unit, const char *txt_short, const char *txt_long)
+{
+ if (!name)
+ name = "main,";
+
+ if (g->p.show_quiet)
+ printf(" %-30s %15.3f, %-15s %s\n", name, val, txt_unit, txt_short);
+ else
+ printf(" %14.3f %s\n", val, txt_long);
+}
+
+static int __bench_numa(const char *name)
+{
+ struct timeval start, stop, diff;
+ u64 runtime_ns_min, runtime_ns_sum;
+ pid_t *pids, pid, wpid;
+ double delta_runtime;
+ double runtime_avg;
+ double runtime_sec_max;
+ double runtime_sec_min;
+ int wait_stat;
+ double bytes;
+ int i, t;
+
+ if (init())
+ return -1;
+
+ pids = zalloc(g->p.nr_proc * sizeof(*pids));
+ pid = -1;
+
+ /* All threads try to acquire it, this way we can wait for them to start up: */
+ pthread_mutex_lock(&g->start_work_mutex);
+
+ if (g->p.serialize_startup) {
+ tprintf(" #\n");
+ tprintf(" # Startup synchronization: ..."); fflush(stdout);
+ }
+
+ gettimeofday(&start, NULL);
+
+ for (i = 0; i < g->p.nr_proc; i++) {
+ pid = fork();
+ dprintf(" # process %2d: PID %d\n", i, pid);
+
+ BUG_ON(pid < 0);
+ if (!pid) {
+ /* Child process: */
+ worker_process(i);
+
+ exit(0);
+ }
+ pids[i] = pid;
+
+ }
+ /* Wait for all the threads to start up: */
+ while (g->nr_tasks_started != g->p.nr_tasks)
+ usleep(1000);
+
+ BUG_ON(g->nr_tasks_started != g->p.nr_tasks);
+
+ if (g->p.serialize_startup) {
+ double startup_sec;
+
+ pthread_mutex_lock(&g->startup_done_mutex);
+
+ /* This will start all threads: */
+ pthread_mutex_unlock(&g->start_work_mutex);
+
+ /* This mutex is locked - the last started thread will wake us: */
+ pthread_mutex_lock(&g->startup_done_mutex);
+
+ gettimeofday(&stop, NULL);
+
+ timersub(&stop, &start, &diff);
+
+ startup_sec = diff.tv_sec * 1000000000.0;
+ startup_sec += diff.tv_usec * 1000.0;
+ startup_sec /= 1e9;
+
+ tprintf(" threads initialized in %.6f seconds.\n", startup_sec);
+ tprintf(" #\n");
+
+ start = stop;
+ pthread_mutex_unlock(&g->startup_done_mutex);
+ } else {
+ gettimeofday(&start, NULL);
+ }
+
+ /* Parent process: */
+
+
+ for (i = 0; i < g->p.nr_proc; i++) {
+ wpid = waitpid(pids[i], &wait_stat, 0);
+ BUG_ON(wpid < 0);
+ BUG_ON(!WIFEXITED(wait_stat));
+
+ }
+
+ runtime_ns_sum = 0;
+ runtime_ns_min = -1LL;
+
+ for (t = 0; t < g->p.nr_tasks; t++) {
+ u64 thread_runtime_ns = g->threads[t].runtime_ns;
+
+ runtime_ns_sum += thread_runtime_ns;
+ runtime_ns_min = min(thread_runtime_ns, runtime_ns_min);
+ }
+
+ gettimeofday(&stop, NULL);
+ timersub(&stop, &start, &diff);
+
+ BUG_ON(bench_format != BENCH_FORMAT_DEFAULT);
+
+ tprintf("\n ###\n");
+ tprintf("\n");
+
+ runtime_sec_max = diff.tv_sec * 1000000000.0;
+ runtime_sec_max += diff.tv_usec * 1000.0;
+ runtime_sec_max /= 1e9;
+
+ runtime_sec_min = runtime_ns_min/1e9;
+
+ bytes = g->bytes_done;
+ runtime_avg = (double)runtime_ns_sum / g->p.nr_tasks / 1e9;
+
+ if (g->p.measure_convergence) {
+ print_res(name, runtime_sec_max,
+ "secs,", "NUMA-convergence-latency", "secs latency to NUMA-converge");
+ }
+
+ print_res(name, runtime_sec_max,
+ "secs,", "runtime-max/thread", "secs slowest (max) thread-runtime");
+
+ print_res(name, runtime_sec_min,
+ "secs,", "runtime-min/thread", "secs fastest (min) thread-runtime");
+
+ print_res(name, runtime_avg,
+ "secs,", "runtime-avg/thread", "secs average thread-runtime");
+
+ delta_runtime = (runtime_sec_max - runtime_sec_min)/2.0;
+ print_res(name, delta_runtime / runtime_sec_max * 100.0,
+ "%,", "spread-runtime/thread", "% difference between max/avg runtime");
+
+ print_res(name, bytes / g->p.nr_tasks / 1e9,
+ "GB,", "data/thread", "GB data processed, per thread");
+
+ print_res(name, bytes / 1e9,
+ "GB,", "data-total", "GB data processed, total");
+
+ print_res(name, runtime_sec_max * 1e9 / (bytes / g->p.nr_tasks),
+ "nsecs,", "runtime/byte/thread","nsecs/byte/thread runtime");
+
+ print_res(name, bytes / g->p.nr_tasks / 1e9 / runtime_sec_max,
+ "GB/sec,", "thread-speed", "GB/sec/thread speed");
+
+ print_res(name, bytes / runtime_sec_max / 1e9,
+ "GB/sec,", "total-speed", "GB/sec total speed");
+
+ free(pids);
+
+ deinit();
+
+ return 0;
+}
+
+#define MAX_ARGS 50
+
+static int command_size(const char **argv)
+{
+ int size = 0;
+
+ while (*argv) {
+ size++;
+ argv++;
+ }
+
+ BUG_ON(size >= MAX_ARGS);
+
+ return size;
+}
+
+static void init_params(struct params *p, const char *name, int argc, const char **argv)
+{
+ int i;
+
+ printf("\n # Running %s \"perf bench numa", name);
+
+ for (i = 0; i < argc; i++)
+ printf(" %s", argv[i]);
+
+ printf("\"\n");
+
+ memset(p, 0, sizeof(*p));
+
+ /* Initialize nonzero defaults: */
+
+ p->serialize_startup = 1;
+ p->data_reads = true;
+ p->data_writes = true;
+ p->data_backwards = true;
+ p->data_rand_walk = true;
+ p->nr_loops = -1;
+ p->init_random = true;
+ p->mb_global_str = "1";
+ p->nr_proc = 1;
+ p->nr_threads = 1;
+ p->nr_secs = 5;
+ p->run_all = argc == 1;
+}
+
+static int run_bench_numa(const char *name, const char **argv)
+{
+ int argc = command_size(argv);
+
+ init_params(&p0, name, argc, argv);
+ argc = parse_options(argc, argv, options, bench_numa_usage, 0);
+ if (argc)
+ goto err;
+
+ if (__bench_numa(name))
+ goto err;
+
+ return 0;
+
+err:
+ return -1;
+}
+
+#define OPT_BW_RAM "-s", "20", "-zZq", "--thp", " 1", "--no-data_rand_walk"
+#define OPT_BW_RAM_NOTHP OPT_BW_RAM, "--thp", "-1"
+
+#define OPT_CONV "-s", "100", "-zZ0qcm", "--thp", " 1"
+#define OPT_CONV_NOTHP OPT_CONV, "--thp", "-1"
+
+#define OPT_BW "-s", "20", "-zZ0q", "--thp", " 1"
+#define OPT_BW_NOTHP OPT_BW, "--thp", "-1"
+
+/*
+ * The built-in test-suite executed by "perf bench numa -a".
+ *
+ * (A minimum of 4 nodes and 16 GB of RAM is recommended.)
+ */
+static const char *tests[][MAX_ARGS] = {
+ /* Basic single-stream NUMA bandwidth measurements: */
+ { "RAM-bw-local,", "mem", "-p", "1", "-t", "1", "-P", "1024",
+ "-C" , "0", "-M", "0", OPT_BW_RAM },
+ { "RAM-bw-local-NOTHP,",
+ "mem", "-p", "1", "-t", "1", "-P", "1024",
+ "-C" , "0", "-M", "0", OPT_BW_RAM_NOTHP },
+ { "RAM-bw-remote,", "mem", "-p", "1", "-t", "1", "-P", "1024",
+ "-C" , "0", "-M", "1", OPT_BW_RAM },
+
+ /* 2-stream NUMA bandwidth measurements: */
+ { "RAM-bw-local-2x,", "mem", "-p", "2", "-t", "1", "-P", "1024",
+ "-C", "0,2", "-M", "0x2", OPT_BW_RAM },
+ { "RAM-bw-remote-2x,", "mem", "-p", "2", "-t", "1", "-P", "1024",
+ "-C", "0,2", "-M", "1x2", OPT_BW_RAM },
+
+ /* Cross-stream NUMA bandwidth measurement: */
+ { "RAM-bw-cross,", "mem", "-p", "2", "-t", "1", "-P", "1024",
+ "-C", "0,8", "-M", "1,0", OPT_BW_RAM },
+
+ /* Convergence latency measurements: */
+ { " 1x3-convergence,", "mem", "-p", "1", "-t", "3", "-P", "512", OPT_CONV },
+ { " 1x4-convergence,", "mem", "-p", "1", "-t", "4", "-P", "512", OPT_CONV },
+ { " 1x6-convergence,", "mem", "-p", "1", "-t", "6", "-P", "1020", OPT_CONV },
+ { " 2x3-convergence,", "mem", "-p", "3", "-t", "3", "-P", "1020", OPT_CONV },
+ { " 3x3-convergence,", "mem", "-p", "3", "-t", "3", "-P", "1020", OPT_CONV },
+ { " 4x4-convergence,", "mem", "-p", "4", "-t", "4", "-P", "512", OPT_CONV },
+ { " 4x4-convergence-NOTHP,",
+ "mem", "-p", "4", "-t", "4", "-P", "512", OPT_CONV_NOTHP },
+ { " 4x6-convergence,", "mem", "-p", "4", "-t", "6", "-P", "1020", OPT_CONV },
+ { " 4x8-convergence,", "mem", "-p", "4", "-t", "8", "-P", "512", OPT_CONV },
+ { " 8x4-convergence,", "mem", "-p", "8", "-t", "4", "-P", "512", OPT_CONV },
+ { " 8x4-convergence-NOTHP,",
+ "mem", "-p", "8", "-t", "4", "-P", "512", OPT_CONV_NOTHP },
+ { " 3x1-convergence,", "mem", "-p", "3", "-t", "1", "-P", "512", OPT_CONV },
+ { " 4x1-convergence,", "mem", "-p", "4", "-t", "1", "-P", "512", OPT_CONV },
+ { " 8x1-convergence,", "mem", "-p", "8", "-t", "1", "-P", "512", OPT_CONV },
+ { "16x1-convergence,", "mem", "-p", "16", "-t", "1", "-P", "256", OPT_CONV },
+ { "32x1-convergence,", "mem", "-p", "32", "-t", "1", "-P", "128", OPT_CONV },
+
+ /* Various NUMA process/thread layout bandwidth measurements: */
+ { " 2x1-bw-process,", "mem", "-p", "2", "-t", "1", "-P", "1024", OPT_BW },
+ { " 3x1-bw-process,", "mem", "-p", "3", "-t", "1", "-P", "1024", OPT_BW },
+ { " 4x1-bw-process,", "mem", "-p", "4", "-t", "1", "-P", "1024", OPT_BW },
+ { " 8x1-bw-process,", "mem", "-p", "8", "-t", "1", "-P", " 512", OPT_BW },
+ { " 8x1-bw-process-NOTHP,",
+ "mem", "-p", "8", "-t", "1", "-P", " 512", OPT_BW_NOTHP },
+ { "16x1-bw-process,", "mem", "-p", "16", "-t", "1", "-P", "256", OPT_BW },
+
+ { " 4x1-bw-thread,", "mem", "-p", "1", "-t", "4", "-T", "256", OPT_BW },
+ { " 8x1-bw-thread,", "mem", "-p", "1", "-t", "8", "-T", "256", OPT_BW },
+ { "16x1-bw-thread,", "mem", "-p", "1", "-t", "16", "-T", "128", OPT_BW },
+ { "32x1-bw-thread,", "mem", "-p", "1", "-t", "32", "-T", "64", OPT_BW },
+
+ { " 2x3-bw-thread,", "mem", "-p", "2", "-t", "3", "-P", "512", OPT_BW },
+ { " 4x4-bw-thread,", "mem", "-p", "4", "-t", "4", "-P", "512", OPT_BW },
+ { " 4x6-bw-thread,", "mem", "-p", "4", "-t", "6", "-P", "512", OPT_BW },
+ { " 4x8-bw-thread,", "mem", "-p", "4", "-t", "8", "-P", "512", OPT_BW },
+ { " 4x8-bw-thread-NOTHP,",
+ "mem", "-p", "4", "-t", "8", "-P", "512", OPT_BW_NOTHP },
+ { " 3x3-bw-thread,", "mem", "-p", "3", "-t", "3", "-P", "512", OPT_BW },
+ { " 5x5-bw-thread,", "mem", "-p", "5", "-t", "5", "-P", "512", OPT_BW },
+
+ { "2x16-bw-thread,", "mem", "-p", "2", "-t", "16", "-P", "512", OPT_BW },
+ { "1x32-bw-thread,", "mem", "-p", "1", "-t", "32", "-P", "2048", OPT_BW },
+
+ { "numa02-bw,", "mem", "-p", "1", "-t", "32", "-T", "32", OPT_BW },
+ { "numa02-bw-NOTHP,", "mem", "-p", "1", "-t", "32", "-T", "32", OPT_BW_NOTHP },
+ { "numa01-bw-thread,", "mem", "-p", "2", "-t", "16", "-T", "192", OPT_BW },
+ { "numa01-bw-thread-NOTHP,",
+ "mem", "-p", "2", "-t", "16", "-T", "192", OPT_BW_NOTHP },
+};
+
+static int bench_all(void)
+{
+ int nr = ARRAY_SIZE(tests);
+ int ret;
+ int i;
+
+ ret = system("echo ' #'; echo ' # Running test on: '$(uname -a); echo ' #'");
+ BUG_ON(ret < 0);
+
+ for (i = 0; i < nr; i++) {
+ run_bench_numa(tests[i][0], tests[i] + 1);
+ }
+
+ printf("\n");
+
+ return 0;
+}
+
+int bench_numa(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ init_params(&p0, "main,", argc, argv);
+ argc = parse_options(argc, argv, options, bench_numa_usage, 0);
+ if (argc)
+ goto err;
+
+ if (p0.run_all)
+ return bench_all();
+
+ if (__bench_numa(NULL))
+ goto err;
+
+ return 0;
+
+err:
+ usage_with_options(numa_usage, options);
+ return -1;
+}
diff --git a/tools/perf/bench/sched-messaging.c b/tools/perf/bench/sched-messaging.c
new file mode 100644
index 00000000000..cc1190a0849
--- /dev/null
+++ b/tools/perf/bench/sched-messaging.c
@@ -0,0 +1,336 @@
+/*
+ *
+ * sched-messaging.c
+ *
+ * messaging: Benchmark for scheduler and IPC mechanisms
+ *
+ * Based on hackbench by Rusty Russell <rusty@rustcorp.com.au>
+ * Ported to perf by Hitoshi Mitake <mitake@dcl.info.waseda.ac.jp>
+ *
+ */
+
+#include "../perf.h"
+#include "../util/util.h"
+#include "../util/parse-options.h"
+#include "../builtin.h"
+#include "bench.h"
+
+/* Test groups of 20 processes spraying to 20 receivers */
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/wait.h>
+#include <sys/time.h>
+#include <sys/poll.h>
+#include <limits.h>
+
+#define DATASIZE 100
+
+static bool use_pipes = false;
+static unsigned int loops = 100;
+static bool thread_mode = false;
+static unsigned int num_groups = 10;
+
+struct sender_context {
+ unsigned int num_fds;
+ int ready_out;
+ int wakefd;
+ int out_fds[0];
+};
+
+struct receiver_context {
+ unsigned int num_packets;
+ int in_fds[2];
+ int ready_out;
+ int wakefd;
+};
+
+static void barf(const char *msg)
+{
+ fprintf(stderr, "%s (error: %s)\n", msg, strerror(errno));
+ exit(1);
+}
+
+static void fdpair(int fds[2])
+{
+ if (use_pipes) {
+ if (pipe(fds) == 0)
+ return;
+ } else {
+ if (socketpair(AF_UNIX, SOCK_STREAM, 0, fds) == 0)
+ return;
+ }
+
+ barf(use_pipes ? "pipe()" : "socketpair()");
+}
+
+/* Block until we're ready to go */
+static void ready(int ready_out, int wakefd)
+{
+ char dummy;
+ struct pollfd pollfd = { .fd = wakefd, .events = POLLIN };
+
+ /* Tell them we're ready. */
+ if (write(ready_out, &dummy, 1) != 1)
+ barf("CLIENT: ready write");
+
+ /* Wait for "GO" signal */
+ if (poll(&pollfd, 1, -1) != 1)
+ barf("poll");
+}
+
+/* Sender sprays loops messages down each file descriptor */
+static void *sender(struct sender_context *ctx)
+{
+ char data[DATASIZE];
+ unsigned int i, j;
+
+ ready(ctx->ready_out, ctx->wakefd);
+
+ /* Now pump to every receiver. */
+ for (i = 0; i < loops; i++) {
+ for (j = 0; j < ctx->num_fds; j++) {
+ int ret, done = 0;
+
+again:
+ ret = write(ctx->out_fds[j], data + done,
+ sizeof(data)-done);
+ if (ret < 0)
+ barf("SENDER: write");
+ done += ret;
+ if (done < DATASIZE)
+ goto again;
+ }
+ }
+
+ return NULL;
+}
+
+
+/* One receiver per fd */
+static void *receiver(struct receiver_context* ctx)
+{
+ unsigned int i;
+
+ if (!thread_mode)
+ close(ctx->in_fds[1]);
+
+ /* Wait for start... */
+ ready(ctx->ready_out, ctx->wakefd);
+
+ /* Receive them all */
+ for (i = 0; i < ctx->num_packets; i++) {
+ char data[DATASIZE];
+ int ret, done = 0;
+
+again:
+ ret = read(ctx->in_fds[0], data + done, DATASIZE - done);
+ if (ret < 0)
+ barf("SERVER: read");
+ done += ret;
+ if (done < DATASIZE)
+ goto again;
+ }
+
+ return NULL;
+}
+
+static pthread_t create_worker(void *ctx, void *(*func)(void *))
+{
+ pthread_attr_t attr;
+ pthread_t childid;
+ int err;
+
+ if (!thread_mode) {
+ /* process mode */
+ /* Fork the receiver. */
+ switch (fork()) {
+ case -1:
+ barf("fork()");
+ break;
+ case 0:
+ (*func) (ctx);
+ exit(0);
+ break;
+ default:
+ break;
+ }
+
+ return (pthread_t)0;
+ }
+
+ if (pthread_attr_init(&attr) != 0)
+ barf("pthread_attr_init:");
+
+#ifndef __ia64__
+ if (pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN) != 0)
+ barf("pthread_attr_setstacksize");
+#endif
+
+ err = pthread_create(&childid, &attr, func, ctx);
+ if (err != 0) {
+ fprintf(stderr, "pthread_create failed: %s (%d)\n",
+ strerror(err), err);
+ exit(-1);
+ }
+ return childid;
+}
+
+static void reap_worker(pthread_t id)
+{
+ int proc_status;
+ void *thread_status;
+
+ if (!thread_mode) {
+ /* process mode */
+ wait(&proc_status);
+ if (!WIFEXITED(proc_status))
+ exit(1);
+ } else {
+ pthread_join(id, &thread_status);
+ }
+}
+
+/* One group of senders and receivers */
+static unsigned int group(pthread_t *pth,
+ unsigned int num_fds,
+ int ready_out,
+ int wakefd)
+{
+ unsigned int i;
+ struct sender_context *snd_ctx = malloc(sizeof(struct sender_context)
+ + num_fds * sizeof(int));
+
+ if (!snd_ctx)
+ barf("malloc()");
+
+ for (i = 0; i < num_fds; i++) {
+ int fds[2];
+ struct receiver_context *ctx = malloc(sizeof(*ctx));
+
+ if (!ctx)
+ barf("malloc()");
+
+
+ /* Create the pipe between client and server */
+ fdpair(fds);
+
+ ctx->num_packets = num_fds * loops;
+ ctx->in_fds[0] = fds[0];
+ ctx->in_fds[1] = fds[1];
+ ctx->ready_out = ready_out;
+ ctx->wakefd = wakefd;
+
+ pth[i] = create_worker(ctx, (void *)receiver);
+
+ snd_ctx->out_fds[i] = fds[1];
+ if (!thread_mode)
+ close(fds[0]);
+ }
+
+ /* Now we have all the fds, fork the senders */
+ for (i = 0; i < num_fds; i++) {
+ snd_ctx->ready_out = ready_out;
+ snd_ctx->wakefd = wakefd;
+ snd_ctx->num_fds = num_fds;
+
+ pth[num_fds+i] = create_worker(snd_ctx, (void *)sender);
+ }
+
+ /* Close the fds we have left */
+ if (!thread_mode)
+ for (i = 0; i < num_fds; i++)
+ close(snd_ctx->out_fds[i]);
+
+ /* Return number of children to reap */
+ return num_fds * 2;
+}
+
+static const struct option options[] = {
+ OPT_BOOLEAN('p', "pipe", &use_pipes,
+ "Use pipe() instead of socketpair()"),
+ OPT_BOOLEAN('t', "thread", &thread_mode,
+ "Be multi thread instead of multi process"),
+ OPT_UINTEGER('g', "group", &num_groups, "Specify number of groups"),
+ OPT_UINTEGER('l', "loop", &loops, "Specify number of loops"),
+ OPT_END()
+};
+
+static const char * const bench_sched_message_usage[] = {
+ "perf bench sched messaging <options>",
+ NULL
+};
+
+int bench_sched_messaging(int argc, const char **argv,
+ const char *prefix __maybe_unused)
+{
+ unsigned int i, total_children;
+ struct timeval start, stop, diff;
+ unsigned int num_fds = 20;
+ int readyfds[2], wakefds[2];
+ char dummy;
+ pthread_t *pth_tab;
+
+ argc = parse_options(argc, argv, options,
+ bench_sched_message_usage, 0);
+
+ pth_tab = malloc(num_fds * 2 * num_groups * sizeof(pthread_t));
+ if (!pth_tab)
+ barf("main:malloc()");
+
+ fdpair(readyfds);
+ fdpair(wakefds);
+
+ total_children = 0;
+ for (i = 0; i < num_groups; i++)
+ total_children += group(pth_tab+total_children, num_fds,
+ readyfds[1], wakefds[0]);
+
+ /* Wait for everyone to be ready */
+ for (i = 0; i < total_children; i++)
+ if (read(readyfds[0], &dummy, 1) != 1)
+ barf("Reading for readyfds");
+
+ gettimeofday(&start, NULL);
+
+ /* Kick them off */
+ if (write(wakefds[1], &dummy, 1) != 1)
+ barf("Writing to start them");
+
+ /* Reap them all */
+ for (i = 0; i < total_children; i++)
+ reap_worker(pth_tab[i]);
+
+ gettimeofday(&stop, NULL);
+
+ timersub(&stop, &start, &diff);
+
+ switch (bench_format) {
+ case BENCH_FORMAT_DEFAULT:
+ printf("# %d sender and receiver %s per group\n",
+ num_fds, thread_mode ? "threads" : "processes");
+ printf("# %d groups == %d %s run\n\n",
+ num_groups, num_groups * 2 * num_fds,
+ thread_mode ? "threads" : "processes");
+ printf(" %14s: %lu.%03lu [sec]\n", "Total time",
+ diff.tv_sec,
+ (unsigned long) (diff.tv_usec/1000));
+ break;
+ case BENCH_FORMAT_SIMPLE:
+ printf("%lu.%03lu\n", diff.tv_sec,
+ (unsigned long) (diff.tv_usec/1000));
+ break;
+ default:
+ /* reaching here is something disaster */
+ fprintf(stderr, "Unknown format:%d\n", bench_format);
+ exit(1);
+ break;
+ }
+
+ return 0;
+}
diff --git a/tools/perf/bench/sched-pipe.c b/tools/perf/bench/sched-pipe.c
new file mode 100644
index 00000000000..07a8d7646a1
--- /dev/null
+++ b/tools/perf/bench/sched-pipe.c
@@ -0,0 +1,184 @@
+/*
+ *
+ * sched-pipe.c
+ *
+ * pipe: Benchmark for pipe()
+ *
+ * Based on pipe-test-1m.c by Ingo Molnar <mingo@redhat.com>
+ * http://people.redhat.com/mingo/cfs-scheduler/tools/pipe-test-1m.c
+ * Ported to perf by Hitoshi Mitake <mitake@dcl.info.waseda.ac.jp>
+ */
+#include "../perf.h"
+#include "../util/util.h"
+#include "../util/parse-options.h"
+#include "../builtin.h"
+#include "bench.h"
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/wait.h>
+#include <linux/unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#include <sys/time.h>
+#include <sys/types.h>
+
+#include <pthread.h>
+
+struct thread_data {
+ int nr;
+ int pipe_read;
+ int pipe_write;
+ pthread_t pthread;
+};
+
+#define LOOPS_DEFAULT 1000000
+static int loops = LOOPS_DEFAULT;
+
+/* Use processes by default: */
+static bool threaded;
+
+static const struct option options[] = {
+ OPT_INTEGER('l', "loop", &loops, "Specify number of loops"),
+ OPT_BOOLEAN('T', "threaded", &threaded, "Specify threads/process based task setup"),
+ OPT_END()
+};
+
+static const char * const bench_sched_pipe_usage[] = {
+ "perf bench sched pipe <options>",
+ NULL
+};
+
+static void *worker_thread(void *__tdata)
+{
+ struct thread_data *td = __tdata;
+ int m = 0, i;
+ int ret;
+
+ for (i = 0; i < loops; i++) {
+ if (!td->nr) {
+ ret = read(td->pipe_read, &m, sizeof(int));
+ BUG_ON(ret != sizeof(int));
+ ret = write(td->pipe_write, &m, sizeof(int));
+ BUG_ON(ret != sizeof(int));
+ } else {
+ ret = write(td->pipe_write, &m, sizeof(int));
+ BUG_ON(ret != sizeof(int));
+ ret = read(td->pipe_read, &m, sizeof(int));
+ BUG_ON(ret != sizeof(int));
+ }
+ }
+
+ return NULL;
+}
+
+int bench_sched_pipe(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ struct thread_data threads[2], *td;
+ int pipe_1[2], pipe_2[2];
+ struct timeval start, stop, diff;
+ unsigned long long result_usec = 0;
+ int nr_threads = 2;
+ int t;
+
+ /*
+ * why does "ret" exist?
+ * discarding returned value of read(), write()
+ * causes error in building environment for perf
+ */
+ int __maybe_unused ret, wait_stat;
+ pid_t pid, retpid __maybe_unused;
+
+ argc = parse_options(argc, argv, options, bench_sched_pipe_usage, 0);
+
+ BUG_ON(pipe(pipe_1));
+ BUG_ON(pipe(pipe_2));
+
+ gettimeofday(&start, NULL);
+
+ for (t = 0; t < nr_threads; t++) {
+ td = threads + t;
+
+ td->nr = t;
+
+ if (t == 0) {
+ td->pipe_read = pipe_1[0];
+ td->pipe_write = pipe_2[1];
+ } else {
+ td->pipe_write = pipe_1[1];
+ td->pipe_read = pipe_2[0];
+ }
+ }
+
+
+ if (threaded) {
+
+ for (t = 0; t < nr_threads; t++) {
+ td = threads + t;
+
+ ret = pthread_create(&td->pthread, NULL, worker_thread, td);
+ BUG_ON(ret);
+ }
+
+ for (t = 0; t < nr_threads; t++) {
+ td = threads + t;
+
+ ret = pthread_join(td->pthread, NULL);
+ BUG_ON(ret);
+ }
+
+ } else {
+ pid = fork();
+ assert(pid >= 0);
+
+ if (!pid) {
+ worker_thread(threads + 0);
+ exit(0);
+ } else {
+ worker_thread(threads + 1);
+ }
+
+ retpid = waitpid(pid, &wait_stat, 0);
+ assert((retpid == pid) && WIFEXITED(wait_stat));
+ }
+
+ gettimeofday(&stop, NULL);
+ timersub(&stop, &start, &diff);
+
+ switch (bench_format) {
+ case BENCH_FORMAT_DEFAULT:
+ printf("# Executed %d pipe operations between two %s\n\n",
+ loops, threaded ? "threads" : "processes");
+
+ result_usec = diff.tv_sec * 1000000;
+ result_usec += diff.tv_usec;
+
+ printf(" %14s: %lu.%03lu [sec]\n\n", "Total time",
+ diff.tv_sec,
+ (unsigned long) (diff.tv_usec/1000));
+
+ printf(" %14lf usecs/op\n",
+ (double)result_usec / (double)loops);
+ printf(" %14d ops/sec\n",
+ (int)((double)loops /
+ ((double)result_usec / (double)1000000)));
+ break;
+
+ case BENCH_FORMAT_SIMPLE:
+ printf("%lu.%03lu\n",
+ diff.tv_sec,
+ (unsigned long) (diff.tv_usec / 1000));
+ break;
+
+ default:
+ /* reaching here is something disaster */
+ fprintf(stderr, "Unknown format:%d\n", bench_format);
+ exit(1);
+ break;
+ }
+
+ return 0;
+}
diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c
new file mode 100644
index 00000000000..1ec429fef2b
--- /dev/null
+++ b/tools/perf/builtin-annotate.c
@@ -0,0 +1,377 @@
+/*
+ * builtin-annotate.c
+ *
+ * Builtin annotate command: Analyze the perf.data input file,
+ * look up and read DSOs and symbol information and display
+ * a histogram of results, along various sorting keys.
+ */
+#include "builtin.h"
+
+#include "util/util.h"
+#include "util/color.h"
+#include <linux/list.h>
+#include "util/cache.h"
+#include <linux/rbtree.h>
+#include "util/symbol.h"
+
+#include "perf.h"
+#include "util/debug.h"
+
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/annotate.h"
+#include "util/event.h"
+#include "util/parse-options.h"
+#include "util/parse-events.h"
+#include "util/thread.h"
+#include "util/sort.h"
+#include "util/hist.h"
+#include "util/session.h"
+#include "util/tool.h"
+#include "util/data.h"
+#include "arch/common.h"
+
+#include <dlfcn.h>
+#include <linux/bitmap.h>
+
+struct perf_annotate {
+ struct perf_tool tool;
+ bool force, use_tui, use_stdio, use_gtk;
+ bool full_paths;
+ bool print_line;
+ bool skip_missing;
+ const char *sym_hist_filter;
+ const char *cpu_list;
+ DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS);
+};
+
+static int perf_evsel__add_sample(struct perf_evsel *evsel,
+ struct perf_sample *sample __maybe_unused,
+ struct addr_location *al,
+ struct perf_annotate *ann)
+{
+ struct hist_entry *he;
+ int ret;
+
+ if (ann->sym_hist_filter != NULL &&
+ (al->sym == NULL ||
+ strcmp(ann->sym_hist_filter, al->sym->name) != 0)) {
+ /* We're only interested in a symbol named sym_hist_filter */
+ if (al->sym != NULL) {
+ rb_erase(&al->sym->rb_node,
+ &al->map->dso->symbols[al->map->type]);
+ symbol__delete(al->sym);
+ }
+ return 0;
+ }
+
+ he = __hists__add_entry(&evsel->hists, al, NULL, NULL, NULL, 1, 1, 0,
+ true);
+ if (he == NULL)
+ return -ENOMEM;
+
+ ret = hist_entry__inc_addr_samples(he, evsel->idx, al->addr);
+ hists__inc_nr_samples(&evsel->hists, true);
+ return ret;
+}
+
+static int process_sample_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ struct perf_annotate *ann = container_of(tool, struct perf_annotate, tool);
+ struct addr_location al;
+
+ if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) {
+ pr_warning("problem processing %d event, skipping it.\n",
+ event->header.type);
+ return -1;
+ }
+
+ if (ann->cpu_list && !test_bit(sample->cpu, ann->cpu_bitmap))
+ return 0;
+
+ if (!al.filtered && perf_evsel__add_sample(evsel, sample, &al, ann)) {
+ pr_warning("problem incrementing symbol count, "
+ "skipping event\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int hist_entry__tty_annotate(struct hist_entry *he,
+ struct perf_evsel *evsel,
+ struct perf_annotate *ann)
+{
+ return symbol__tty_annotate(he->ms.sym, he->ms.map, evsel,
+ ann->print_line, ann->full_paths, 0, 0);
+}
+
+static void hists__find_annotations(struct hists *hists,
+ struct perf_evsel *evsel,
+ struct perf_annotate *ann)
+{
+ struct rb_node *nd = rb_first(&hists->entries), *next;
+ int key = K_RIGHT;
+
+ while (nd) {
+ struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
+ struct annotation *notes;
+
+ if (he->ms.sym == NULL || he->ms.map->dso->annotate_warned)
+ goto find_next;
+
+ notes = symbol__annotation(he->ms.sym);
+ if (notes->src == NULL) {
+find_next:
+ if (key == K_LEFT)
+ nd = rb_prev(nd);
+ else
+ nd = rb_next(nd);
+ continue;
+ }
+
+ if (use_browser == 2) {
+ int ret;
+ int (*annotate)(struct hist_entry *he,
+ struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt);
+
+ annotate = dlsym(perf_gtk_handle,
+ "hist_entry__gtk_annotate");
+ if (annotate == NULL) {
+ ui__error("GTK browser not found!\n");
+ return;
+ }
+
+ ret = annotate(he, evsel, NULL);
+ if (!ret || !ann->skip_missing)
+ return;
+
+ /* skip missing symbols */
+ nd = rb_next(nd);
+ } else if (use_browser == 1) {
+ key = hist_entry__tui_annotate(he, evsel, NULL);
+ switch (key) {
+ case -1:
+ if (!ann->skip_missing)
+ return;
+ /* fall through */
+ case K_RIGHT:
+ next = rb_next(nd);
+ break;
+ case K_LEFT:
+ next = rb_prev(nd);
+ break;
+ default:
+ return;
+ }
+
+ if (next != NULL)
+ nd = next;
+ } else {
+ hist_entry__tty_annotate(he, evsel, ann);
+ nd = rb_next(nd);
+ /*
+ * Since we have a hist_entry per IP for the same
+ * symbol, free he->ms.sym->src to signal we already
+ * processed this symbol.
+ */
+ zfree(&notes->src);
+ }
+ }
+}
+
+static int __cmd_annotate(struct perf_annotate *ann)
+{
+ int ret;
+ struct perf_session *session;
+ struct perf_evsel *pos;
+ u64 total_nr_samples;
+ struct perf_data_file file = {
+ .path = input_name,
+ .mode = PERF_DATA_MODE_READ,
+ .force = ann->force,
+ };
+
+ session = perf_session__new(&file, false, &ann->tool);
+ if (session == NULL)
+ return -ENOMEM;
+
+ machines__set_symbol_filter(&session->machines, symbol__annotate_init);
+
+ if (ann->cpu_list) {
+ ret = perf_session__cpu_bitmap(session, ann->cpu_list,
+ ann->cpu_bitmap);
+ if (ret)
+ goto out_delete;
+ }
+
+ if (!objdump_path) {
+ ret = perf_session_env__lookup_objdump(&session->header.env);
+ if (ret)
+ goto out_delete;
+ }
+
+ ret = perf_session__process_events(session, &ann->tool);
+ if (ret)
+ goto out_delete;
+
+ if (dump_trace) {
+ perf_session__fprintf_nr_events(session, stdout);
+ goto out_delete;
+ }
+
+ if (verbose > 3)
+ perf_session__fprintf(session, stdout);
+
+ if (verbose > 2)
+ perf_session__fprintf_dsos(session, stdout);
+
+ total_nr_samples = 0;
+ evlist__for_each(session->evlist, pos) {
+ struct hists *hists = &pos->hists;
+ u32 nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE];
+
+ if (nr_samples > 0) {
+ total_nr_samples += nr_samples;
+ hists__collapse_resort(hists, NULL);
+ hists__output_resort(hists);
+
+ if (symbol_conf.event_group &&
+ !perf_evsel__is_group_leader(pos))
+ continue;
+
+ hists__find_annotations(hists, pos, ann);
+ }
+ }
+
+ if (total_nr_samples == 0) {
+ ui__error("The %s file has no samples!\n", file.path);
+ goto out_delete;
+ }
+
+ if (use_browser == 2) {
+ void (*show_annotations)(void);
+
+ show_annotations = dlsym(perf_gtk_handle,
+ "perf_gtk__show_annotations");
+ if (show_annotations == NULL) {
+ ui__error("GTK browser not found!\n");
+ goto out_delete;
+ }
+ show_annotations();
+ }
+
+out_delete:
+ /*
+ * Speed up the exit process, for large files this can
+ * take quite a while.
+ *
+ * XXX Enable this when using valgrind or if we ever
+ * librarize this command.
+ *
+ * Also experiment with obstacks to see how much speed
+ * up we'll get here.
+ *
+ * perf_session__delete(session);
+ */
+ return ret;
+}
+
+static const char * const annotate_usage[] = {
+ "perf annotate [<options>]",
+ NULL
+};
+
+int cmd_annotate(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ struct perf_annotate annotate = {
+ .tool = {
+ .sample = process_sample_event,
+ .mmap = perf_event__process_mmap,
+ .mmap2 = perf_event__process_mmap2,
+ .comm = perf_event__process_comm,
+ .exit = perf_event__process_exit,
+ .fork = perf_event__process_fork,
+ .ordered_samples = true,
+ .ordering_requires_timestamps = true,
+ },
+ };
+ const struct option options[] = {
+ OPT_STRING('i', "input", &input_name, "file",
+ "input file name"),
+ OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
+ "only consider symbols in these dsos"),
+ OPT_STRING('s', "symbol", &annotate.sym_hist_filter, "symbol",
+ "symbol to annotate"),
+ OPT_BOOLEAN('f', "force", &annotate.force, "don't complain, do it"),
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show symbol address, etc)"),
+ OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
+ "dump raw trace in ASCII"),
+ OPT_BOOLEAN(0, "gtk", &annotate.use_gtk, "Use the GTK interface"),
+ OPT_BOOLEAN(0, "tui", &annotate.use_tui, "Use the TUI interface"),
+ OPT_BOOLEAN(0, "stdio", &annotate.use_stdio, "Use the stdio interface"),
+ OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name,
+ "file", "vmlinux pathname"),
+ OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules,
+ "load module symbols - WARNING: use only with -k and LIVE kernel"),
+ OPT_BOOLEAN('l', "print-line", &annotate.print_line,
+ "print matching source lines (may be slow)"),
+ OPT_BOOLEAN('P', "full-paths", &annotate.full_paths,
+ "Don't shorten the displayed pathnames"),
+ OPT_BOOLEAN(0, "skip-missing", &annotate.skip_missing,
+ "Skip symbols that cannot be annotated"),
+ OPT_STRING('C', "cpu", &annotate.cpu_list, "cpu", "list of cpus to profile"),
+ OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory",
+ "Look for files with symbols relative to this directory"),
+ OPT_BOOLEAN(0, "source", &symbol_conf.annotate_src,
+ "Interleave source code with assembly code (default)"),
+ OPT_BOOLEAN(0, "asm-raw", &symbol_conf.annotate_asm_raw,
+ "Display raw encoding of assembly instructions (default)"),
+ OPT_STRING('M', "disassembler-style", &disassembler_style, "disassembler style",
+ "Specify disassembler style (e.g. -M intel for intel syntax)"),
+ OPT_STRING(0, "objdump", &objdump_path, "path",
+ "objdump binary to use for disassembly and annotations"),
+ OPT_BOOLEAN(0, "group", &symbol_conf.event_group,
+ "Show event group information together"),
+ OPT_END()
+ };
+
+ argc = parse_options(argc, argv, options, annotate_usage, 0);
+
+ if (annotate.use_stdio)
+ use_browser = 0;
+ else if (annotate.use_tui)
+ use_browser = 1;
+ else if (annotate.use_gtk)
+ use_browser = 2;
+
+ setup_browser(true);
+
+ symbol_conf.priv_size = sizeof(struct annotation);
+ symbol_conf.try_vmlinux_path = true;
+
+ if (symbol__init() < 0)
+ return -1;
+
+ if (setup_sorting() < 0)
+ usage_with_options(annotate_usage, options);
+
+ if (argc) {
+ /*
+ * Special case: if there's an argument left then assume that
+ * it's a symbol filter:
+ */
+ if (argc > 1)
+ usage_with_options(annotate_usage, options);
+
+ annotate.sym_hist_filter = argv[0];
+ }
+
+ return __cmd_annotate(&annotate);
+}
diff --git a/tools/perf/builtin-bench.c b/tools/perf/builtin-bench.c
new file mode 100644
index 00000000000..1e6e7771054
--- /dev/null
+++ b/tools/perf/builtin-bench.c
@@ -0,0 +1,282 @@
+/*
+ * builtin-bench.c
+ *
+ * General benchmarking collections provided by perf
+ *
+ * Copyright (C) 2009, Hitoshi Mitake <mitake@dcl.info.waseda.ac.jp>
+ */
+
+/*
+ * Available benchmark collection list:
+ *
+ * sched ... scheduler and IPC performance
+ * mem ... memory access performance
+ * numa ... NUMA scheduling and MM performance
+ * futex ... Futex performance
+ */
+#include "perf.h"
+#include "util/util.h"
+#include "util/parse-options.h"
+#include "builtin.h"
+#include "bench/bench.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+
+typedef int (*bench_fn_t)(int argc, const char **argv, const char *prefix);
+
+struct bench {
+ const char *name;
+ const char *summary;
+ bench_fn_t fn;
+};
+
+#ifdef HAVE_LIBNUMA_SUPPORT
+static struct bench numa_benchmarks[] = {
+ { "mem", "Benchmark for NUMA workloads", bench_numa },
+ { "all", "Test all NUMA benchmarks", NULL },
+ { NULL, NULL, NULL }
+};
+#endif
+
+static struct bench sched_benchmarks[] = {
+ { "messaging", "Benchmark for scheduling and IPC", bench_sched_messaging },
+ { "pipe", "Benchmark for pipe() between two processes", bench_sched_pipe },
+ { "all", "Test all scheduler benchmarks", NULL },
+ { NULL, NULL, NULL }
+};
+
+static struct bench mem_benchmarks[] = {
+ { "memcpy", "Benchmark for memcpy()", bench_mem_memcpy },
+ { "memset", "Benchmark for memset() tests", bench_mem_memset },
+ { "all", "Test all memory benchmarks", NULL },
+ { NULL, NULL, NULL }
+};
+
+static struct bench futex_benchmarks[] = {
+ { "hash", "Benchmark for futex hash table", bench_futex_hash },
+ { "wake", "Benchmark for futex wake calls", bench_futex_wake },
+ { "requeue", "Benchmark for futex requeue calls", bench_futex_requeue },
+ { "all", "Test all futex benchmarks", NULL },
+ { NULL, NULL, NULL }
+};
+
+struct collection {
+ const char *name;
+ const char *summary;
+ struct bench *benchmarks;
+};
+
+static struct collection collections[] = {
+ { "sched", "Scheduler and IPC benchmarks", sched_benchmarks },
+ { "mem", "Memory access benchmarks", mem_benchmarks },
+#ifdef HAVE_LIBNUMA_SUPPORT
+ { "numa", "NUMA scheduling and MM benchmarks", numa_benchmarks },
+#endif
+ {"futex", "Futex stressing benchmarks", futex_benchmarks },
+ { "all", "All benchmarks", NULL },
+ { NULL, NULL, NULL }
+};
+
+/* Iterate over all benchmark collections: */
+#define for_each_collection(coll) \
+ for (coll = collections; coll->name; coll++)
+
+/* Iterate over all benchmarks within a collection: */
+#define for_each_bench(coll, bench) \
+ for (bench = coll->benchmarks; bench && bench->name; bench++)
+
+static void dump_benchmarks(struct collection *coll)
+{
+ struct bench *bench;
+
+ printf("\n # List of available benchmarks for collection '%s':\n\n", coll->name);
+
+ for_each_bench(coll, bench)
+ printf("%14s: %s\n", bench->name, bench->summary);
+
+ printf("\n");
+}
+
+static const char *bench_format_str;
+
+/* Output/formatting style, exported to benchmark modules: */
+int bench_format = BENCH_FORMAT_DEFAULT;
+
+static const struct option bench_options[] = {
+ OPT_STRING('f', "format", &bench_format_str, "default", "Specify format style"),
+ OPT_END()
+};
+
+static const char * const bench_usage[] = {
+ "perf bench [<common options>] <collection> <benchmark> [<options>]",
+ NULL
+};
+
+static void print_usage(void)
+{
+ struct collection *coll;
+ int i;
+
+ printf("Usage: \n");
+ for (i = 0; bench_usage[i]; i++)
+ printf("\t%s\n", bench_usage[i]);
+ printf("\n");
+
+ printf(" # List of all available benchmark collections:\n\n");
+
+ for_each_collection(coll)
+ printf("%14s: %s\n", coll->name, coll->summary);
+ printf("\n");
+}
+
+static int bench_str2int(const char *str)
+{
+ if (!str)
+ return BENCH_FORMAT_DEFAULT;
+
+ if (!strcmp(str, BENCH_FORMAT_DEFAULT_STR))
+ return BENCH_FORMAT_DEFAULT;
+ else if (!strcmp(str, BENCH_FORMAT_SIMPLE_STR))
+ return BENCH_FORMAT_SIMPLE;
+
+ return BENCH_FORMAT_UNKNOWN;
+}
+
+/*
+ * Run a specific benchmark but first rename the running task's ->comm[]
+ * to something meaningful:
+ */
+static int run_bench(const char *coll_name, const char *bench_name, bench_fn_t fn,
+ int argc, const char **argv, const char *prefix)
+{
+ int size;
+ char *name;
+ int ret;
+
+ size = strlen(coll_name) + 1 + strlen(bench_name) + 1;
+
+ name = zalloc(size);
+ BUG_ON(!name);
+
+ scnprintf(name, size, "%s-%s", coll_name, bench_name);
+
+ prctl(PR_SET_NAME, name);
+ argv[0] = name;
+
+ ret = fn(argc, argv, prefix);
+
+ free(name);
+
+ return ret;
+}
+
+static void run_collection(struct collection *coll)
+{
+ struct bench *bench;
+ const char *argv[2];
+
+ argv[1] = NULL;
+ /*
+ * TODO:
+ *
+ * Preparing preset parameters for
+ * embedded, ordinary PC, HPC, etc...
+ * would be helpful.
+ */
+ for_each_bench(coll, bench) {
+ if (!bench->fn)
+ break;
+ printf("# Running %s/%s benchmark...\n", coll->name, bench->name);
+ fflush(stdout);
+
+ argv[1] = bench->name;
+ run_bench(coll->name, bench->name, bench->fn, 1, argv, NULL);
+ printf("\n");
+ }
+}
+
+static void run_all_collections(void)
+{
+ struct collection *coll;
+
+ for_each_collection(coll)
+ run_collection(coll);
+}
+
+int cmd_bench(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ struct collection *coll;
+ int ret = 0;
+
+ if (argc < 2) {
+ /* No collection specified. */
+ print_usage();
+ goto end;
+ }
+
+ argc = parse_options(argc, argv, bench_options, bench_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ bench_format = bench_str2int(bench_format_str);
+ if (bench_format == BENCH_FORMAT_UNKNOWN) {
+ printf("Unknown format descriptor: '%s'\n", bench_format_str);
+ goto end;
+ }
+
+ if (argc < 1) {
+ print_usage();
+ goto end;
+ }
+
+ if (!strcmp(argv[0], "all")) {
+ run_all_collections();
+ goto end;
+ }
+
+ for_each_collection(coll) {
+ struct bench *bench;
+
+ if (strcmp(coll->name, argv[0]))
+ continue;
+
+ if (argc < 2) {
+ /* No bench specified. */
+ dump_benchmarks(coll);
+ goto end;
+ }
+
+ if (!strcmp(argv[1], "all")) {
+ run_collection(coll);
+ goto end;
+ }
+
+ for_each_bench(coll, bench) {
+ if (strcmp(bench->name, argv[1]))
+ continue;
+
+ if (bench_format == BENCH_FORMAT_DEFAULT)
+ printf("# Running '%s/%s' benchmark:\n", coll->name, bench->name);
+ fflush(stdout);
+ ret = run_bench(coll->name, bench->name, bench->fn, argc-1, argv+1, prefix);
+ goto end;
+ }
+
+ if (!strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) {
+ dump_benchmarks(coll);
+ goto end;
+ }
+
+ printf("Unknown benchmark: '%s' for collection '%s'\n", argv[1], argv[0]);
+ ret = 1;
+ goto end;
+ }
+
+ printf("Unknown collection: '%s'\n", argv[0]);
+ ret = 1;
+
+end:
+ return ret;
+}
diff --git a/tools/perf/builtin-buildid-cache.c b/tools/perf/builtin-buildid-cache.c
new file mode 100644
index 00000000000..b22dbb16f87
--- /dev/null
+++ b/tools/perf/builtin-buildid-cache.c
@@ -0,0 +1,396 @@
+/*
+ * builtin-buildid-cache.c
+ *
+ * Builtin buildid-cache command: Manages build-id cache
+ *
+ * Copyright (C) 2010, Red Hat Inc.
+ * Copyright (C) 2010, Arnaldo Carvalho de Melo <acme@redhat.com>
+ */
+#include <sys/types.h>
+#include <sys/time.h>
+#include <time.h>
+#include <dirent.h>
+#include <unistd.h>
+#include "builtin.h"
+#include "perf.h"
+#include "util/cache.h"
+#include "util/debug.h"
+#include "util/header.h"
+#include "util/parse-options.h"
+#include "util/strlist.h"
+#include "util/build-id.h"
+#include "util/session.h"
+#include "util/symbol.h"
+
+static int build_id_cache__kcore_buildid(const char *proc_dir, char *sbuildid)
+{
+ char root_dir[PATH_MAX];
+ char notes[PATH_MAX];
+ u8 build_id[BUILD_ID_SIZE];
+ char *p;
+
+ strlcpy(root_dir, proc_dir, sizeof(root_dir));
+
+ p = strrchr(root_dir, '/');
+ if (!p)
+ return -1;
+ *p = '\0';
+
+ scnprintf(notes, sizeof(notes), "%s/sys/kernel/notes", root_dir);
+
+ if (sysfs__read_build_id(notes, build_id, sizeof(build_id)))
+ return -1;
+
+ build_id__sprintf(build_id, sizeof(build_id), sbuildid);
+
+ return 0;
+}
+
+static int build_id_cache__kcore_dir(char *dir, size_t sz)
+{
+ struct timeval tv;
+ struct tm tm;
+ char dt[32];
+
+ if (gettimeofday(&tv, NULL) || !localtime_r(&tv.tv_sec, &tm))
+ return -1;
+
+ if (!strftime(dt, sizeof(dt), "%Y%m%d%H%M%S", &tm))
+ return -1;
+
+ scnprintf(dir, sz, "%s%02u", dt, (unsigned)tv.tv_usec / 10000);
+
+ return 0;
+}
+
+static bool same_kallsyms_reloc(const char *from_dir, char *to_dir)
+{
+ char from[PATH_MAX];
+ char to[PATH_MAX];
+ const char *name;
+ u64 addr1 = 0, addr2 = 0;
+ int i;
+
+ scnprintf(from, sizeof(from), "%s/kallsyms", from_dir);
+ scnprintf(to, sizeof(to), "%s/kallsyms", to_dir);
+
+ for (i = 0; (name = ref_reloc_sym_names[i]) != NULL; i++) {
+ addr1 = kallsyms__get_function_start(from, name);
+ if (addr1)
+ break;
+ }
+
+ if (name)
+ addr2 = kallsyms__get_function_start(to, name);
+
+ return addr1 == addr2;
+}
+
+static int build_id_cache__kcore_existing(const char *from_dir, char *to_dir,
+ size_t to_dir_sz)
+{
+ char from[PATH_MAX];
+ char to[PATH_MAX];
+ char to_subdir[PATH_MAX];
+ struct dirent *dent;
+ int ret = -1;
+ DIR *d;
+
+ d = opendir(to_dir);
+ if (!d)
+ return -1;
+
+ scnprintf(from, sizeof(from), "%s/modules", from_dir);
+
+ while (1) {
+ dent = readdir(d);
+ if (!dent)
+ break;
+ if (dent->d_type != DT_DIR)
+ continue;
+ scnprintf(to, sizeof(to), "%s/%s/modules", to_dir,
+ dent->d_name);
+ scnprintf(to_subdir, sizeof(to_subdir), "%s/%s",
+ to_dir, dent->d_name);
+ if (!compare_proc_modules(from, to) &&
+ same_kallsyms_reloc(from_dir, to_subdir)) {
+ strlcpy(to_dir, to_subdir, to_dir_sz);
+ ret = 0;
+ break;
+ }
+ }
+
+ closedir(d);
+
+ return ret;
+}
+
+static int build_id_cache__add_kcore(const char *filename, const char *debugdir)
+{
+ char dir[32], sbuildid[BUILD_ID_SIZE * 2 + 1];
+ char from_dir[PATH_MAX], to_dir[PATH_MAX];
+ char *p;
+
+ strlcpy(from_dir, filename, sizeof(from_dir));
+
+ p = strrchr(from_dir, '/');
+ if (!p || strcmp(p + 1, "kcore"))
+ return -1;
+ *p = '\0';
+
+ if (build_id_cache__kcore_buildid(from_dir, sbuildid))
+ return -1;
+
+ scnprintf(to_dir, sizeof(to_dir), "%s/[kernel.kcore]/%s",
+ debugdir, sbuildid);
+
+ if (!build_id_cache__kcore_existing(from_dir, to_dir, sizeof(to_dir))) {
+ pr_debug("same kcore found in %s\n", to_dir);
+ return 0;
+ }
+
+ if (build_id_cache__kcore_dir(dir, sizeof(dir)))
+ return -1;
+
+ scnprintf(to_dir, sizeof(to_dir), "%s/[kernel.kcore]/%s/%s",
+ debugdir, sbuildid, dir);
+
+ if (mkdir_p(to_dir, 0755))
+ return -1;
+
+ if (kcore_copy(from_dir, to_dir)) {
+ /* Remove YYYYmmddHHMMSShh directory */
+ if (!rmdir(to_dir)) {
+ p = strrchr(to_dir, '/');
+ if (p)
+ *p = '\0';
+ /* Try to remove buildid directory */
+ if (!rmdir(to_dir)) {
+ p = strrchr(to_dir, '/');
+ if (p)
+ *p = '\0';
+ /* Try to remove [kernel.kcore] directory */
+ rmdir(to_dir);
+ }
+ }
+ return -1;
+ }
+
+ pr_debug("kcore added to build-id cache directory %s\n", to_dir);
+
+ return 0;
+}
+
+static int build_id_cache__add_file(const char *filename, const char *debugdir)
+{
+ char sbuild_id[BUILD_ID_SIZE * 2 + 1];
+ u8 build_id[BUILD_ID_SIZE];
+ int err;
+
+ if (filename__read_build_id(filename, &build_id, sizeof(build_id)) < 0) {
+ pr_debug("Couldn't read a build-id in %s\n", filename);
+ return -1;
+ }
+
+ build_id__sprintf(build_id, sizeof(build_id), sbuild_id);
+ err = build_id_cache__add_s(sbuild_id, debugdir, filename,
+ false, false);
+ if (verbose)
+ pr_info("Adding %s %s: %s\n", sbuild_id, filename,
+ err ? "FAIL" : "Ok");
+ return err;
+}
+
+static int build_id_cache__remove_file(const char *filename,
+ const char *debugdir)
+{
+ u8 build_id[BUILD_ID_SIZE];
+ char sbuild_id[BUILD_ID_SIZE * 2 + 1];
+
+ int err;
+
+ if (filename__read_build_id(filename, &build_id, sizeof(build_id)) < 0) {
+ pr_debug("Couldn't read a build-id in %s\n", filename);
+ return -1;
+ }
+
+ build_id__sprintf(build_id, sizeof(build_id), sbuild_id);
+ err = build_id_cache__remove_s(sbuild_id, debugdir);
+ if (verbose)
+ pr_info("Removing %s %s: %s\n", sbuild_id, filename,
+ err ? "FAIL" : "Ok");
+
+ return err;
+}
+
+static bool dso__missing_buildid_cache(struct dso *dso, int parm __maybe_unused)
+{
+ char filename[PATH_MAX];
+ u8 build_id[BUILD_ID_SIZE];
+
+ if (dso__build_id_filename(dso, filename, sizeof(filename)) &&
+ filename__read_build_id(filename, build_id,
+ sizeof(build_id)) != sizeof(build_id)) {
+ if (errno == ENOENT)
+ return false;
+
+ pr_warning("Problems with %s file, consider removing it from the cache\n",
+ filename);
+ } else if (memcmp(dso->build_id, build_id, sizeof(dso->build_id))) {
+ pr_warning("Problems with %s file, consider removing it from the cache\n",
+ filename);
+ }
+
+ return true;
+}
+
+static int build_id_cache__fprintf_missing(const char *filename, bool force, FILE *fp)
+{
+ struct perf_data_file file = {
+ .path = filename,
+ .mode = PERF_DATA_MODE_READ,
+ .force = force,
+ };
+ struct perf_session *session = perf_session__new(&file, false, NULL);
+ if (session == NULL)
+ return -1;
+
+ perf_session__fprintf_dsos_buildid(session, fp, dso__missing_buildid_cache, 0);
+ perf_session__delete(session);
+
+ return 0;
+}
+
+static int build_id_cache__update_file(const char *filename,
+ const char *debugdir)
+{
+ u8 build_id[BUILD_ID_SIZE];
+ char sbuild_id[BUILD_ID_SIZE * 2 + 1];
+
+ int err;
+
+ if (filename__read_build_id(filename, &build_id, sizeof(build_id)) < 0) {
+ pr_debug("Couldn't read a build-id in %s\n", filename);
+ return -1;
+ }
+
+ build_id__sprintf(build_id, sizeof(build_id), sbuild_id);
+ err = build_id_cache__remove_s(sbuild_id, debugdir);
+ if (!err) {
+ err = build_id_cache__add_s(sbuild_id, debugdir, filename,
+ false, false);
+ }
+ if (verbose)
+ pr_info("Updating %s %s: %s\n", sbuild_id, filename,
+ err ? "FAIL" : "Ok");
+
+ return err;
+}
+
+int cmd_buildid_cache(int argc, const char **argv,
+ const char *prefix __maybe_unused)
+{
+ struct strlist *list;
+ struct str_node *pos;
+ int ret = 0;
+ bool force = false;
+ char debugdir[PATH_MAX];
+ char const *add_name_list_str = NULL,
+ *remove_name_list_str = NULL,
+ *missing_filename = NULL,
+ *update_name_list_str = NULL,
+ *kcore_filename;
+
+ const struct option buildid_cache_options[] = {
+ OPT_STRING('a', "add", &add_name_list_str,
+ "file list", "file(s) to add"),
+ OPT_STRING('k', "kcore", &kcore_filename,
+ "file", "kcore file to add"),
+ OPT_STRING('r', "remove", &remove_name_list_str, "file list",
+ "file(s) to remove"),
+ OPT_STRING('M', "missing", &missing_filename, "file",
+ "to find missing build ids in the cache"),
+ OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
+ OPT_STRING('u', "update", &update_name_list_str, "file list",
+ "file(s) to update"),
+ OPT_INCR('v', "verbose", &verbose, "be more verbose"),
+ OPT_END()
+ };
+ const char * const buildid_cache_usage[] = {
+ "perf buildid-cache [<options>]",
+ NULL
+ };
+
+ argc = parse_options(argc, argv, buildid_cache_options,
+ buildid_cache_usage, 0);
+
+ if (symbol__init() < 0)
+ return -1;
+
+ setup_pager();
+
+ snprintf(debugdir, sizeof(debugdir), "%s", buildid_dir);
+
+ if (add_name_list_str) {
+ list = strlist__new(true, add_name_list_str);
+ if (list) {
+ strlist__for_each(pos, list)
+ if (build_id_cache__add_file(pos->s, debugdir)) {
+ if (errno == EEXIST) {
+ pr_debug("%s already in the cache\n",
+ pos->s);
+ continue;
+ }
+ pr_warning("Couldn't add %s: %s\n",
+ pos->s, strerror(errno));
+ }
+
+ strlist__delete(list);
+ }
+ }
+
+ if (remove_name_list_str) {
+ list = strlist__new(true, remove_name_list_str);
+ if (list) {
+ strlist__for_each(pos, list)
+ if (build_id_cache__remove_file(pos->s, debugdir)) {
+ if (errno == ENOENT) {
+ pr_debug("%s wasn't in the cache\n",
+ pos->s);
+ continue;
+ }
+ pr_warning("Couldn't remove %s: %s\n",
+ pos->s, strerror(errno));
+ }
+
+ strlist__delete(list);
+ }
+ }
+
+ if (missing_filename)
+ ret = build_id_cache__fprintf_missing(missing_filename, force, stdout);
+
+ if (update_name_list_str) {
+ list = strlist__new(true, update_name_list_str);
+ if (list) {
+ strlist__for_each(pos, list)
+ if (build_id_cache__update_file(pos->s, debugdir)) {
+ if (errno == ENOENT) {
+ pr_debug("%s wasn't in the cache\n",
+ pos->s);
+ continue;
+ }
+ pr_warning("Couldn't update %s: %s\n",
+ pos->s, strerror(errno));
+ }
+
+ strlist__delete(list);
+ }
+ }
+
+ if (kcore_filename &&
+ build_id_cache__add_kcore(kcore_filename, debugdir))
+ pr_warning("Couldn't add %s\n", kcore_filename);
+
+ return ret;
+}
diff --git a/tools/perf/builtin-buildid-list.c b/tools/perf/builtin-buildid-list.c
new file mode 100644
index 00000000000..ed3873b3e23
--- /dev/null
+++ b/tools/perf/builtin-buildid-list.c
@@ -0,0 +1,111 @@
+/*
+ * builtin-buildid-list.c
+ *
+ * Builtin buildid-list command: list buildids in perf.data, in the running
+ * kernel and in ELF files.
+ *
+ * Copyright (C) 2009, Red Hat Inc.
+ * Copyright (C) 2009, Arnaldo Carvalho de Melo <acme@redhat.com>
+ */
+#include "builtin.h"
+#include "perf.h"
+#include "util/build-id.h"
+#include "util/cache.h"
+#include "util/debug.h"
+#include "util/parse-options.h"
+#include "util/session.h"
+#include "util/symbol.h"
+#include "util/data.h"
+
+static int sysfs__fprintf_build_id(FILE *fp)
+{
+ u8 kallsyms_build_id[BUILD_ID_SIZE];
+ char sbuild_id[BUILD_ID_SIZE * 2 + 1];
+
+ if (sysfs__read_build_id("/sys/kernel/notes", kallsyms_build_id,
+ sizeof(kallsyms_build_id)) != 0)
+ return -1;
+
+ build_id__sprintf(kallsyms_build_id, sizeof(kallsyms_build_id),
+ sbuild_id);
+ fprintf(fp, "%s\n", sbuild_id);
+ return 0;
+}
+
+static int filename__fprintf_build_id(const char *name, FILE *fp)
+{
+ u8 build_id[BUILD_ID_SIZE];
+ char sbuild_id[BUILD_ID_SIZE * 2 + 1];
+
+ if (filename__read_build_id(name, build_id,
+ sizeof(build_id)) != sizeof(build_id))
+ return 0;
+
+ build_id__sprintf(build_id, sizeof(build_id), sbuild_id);
+ return fprintf(fp, "%s\n", sbuild_id);
+}
+
+static bool dso__skip_buildid(struct dso *dso, int with_hits)
+{
+ return with_hits && !dso->hit;
+}
+
+static int perf_session__list_build_ids(bool force, bool with_hits)
+{
+ struct perf_session *session;
+ struct perf_data_file file = {
+ .path = input_name,
+ .mode = PERF_DATA_MODE_READ,
+ .force = force,
+ };
+
+ symbol__elf_init();
+ /*
+ * See if this is an ELF file first:
+ */
+ if (filename__fprintf_build_id(input_name, stdout))
+ goto out;
+
+ session = perf_session__new(&file, false, &build_id__mark_dso_hit_ops);
+ if (session == NULL)
+ return -1;
+ /*
+ * in pipe-mode, the only way to get the buildids is to parse
+ * the record stream. Buildids are stored as RECORD_HEADER_BUILD_ID
+ */
+ if (with_hits || perf_data_file__is_pipe(&file))
+ perf_session__process_events(session, &build_id__mark_dso_hit_ops);
+
+ perf_session__fprintf_dsos_buildid(session, stdout, dso__skip_buildid, with_hits);
+ perf_session__delete(session);
+out:
+ return 0;
+}
+
+int cmd_buildid_list(int argc, const char **argv,
+ const char *prefix __maybe_unused)
+{
+ bool show_kernel = false;
+ bool with_hits = false;
+ bool force = false;
+ const struct option options[] = {
+ OPT_BOOLEAN('H', "with-hits", &with_hits, "Show only DSOs with hits"),
+ OPT_STRING('i', "input", &input_name, "file", "input file name"),
+ OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
+ OPT_BOOLEAN('k', "kernel", &show_kernel, "Show current kernel build id"),
+ OPT_INCR('v', "verbose", &verbose, "be more verbose"),
+ OPT_END()
+ };
+ const char * const buildid_list_usage[] = {
+ "perf buildid-list [<options>]",
+ NULL
+ };
+
+ argc = parse_options(argc, argv, options, buildid_list_usage, 0);
+ setup_pager();
+
+ if (show_kernel)
+ return sysfs__fprintf_build_id(stdout);
+
+ return perf_session__list_build_ids(force, with_hits);
+}
diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c
new file mode 100644
index 00000000000..9a5a035cb42
--- /dev/null
+++ b/tools/perf/builtin-diff.c
@@ -0,0 +1,1164 @@
+/*
+ * builtin-diff.c
+ *
+ * Builtin diff command: Analyze two perf.data input files, look up and read
+ * DSOs and symbol information, sort them and produce a diff.
+ */
+#include "builtin.h"
+
+#include "util/debug.h"
+#include "util/event.h"
+#include "util/hist.h"
+#include "util/evsel.h"
+#include "util/evlist.h"
+#include "util/session.h"
+#include "util/tool.h"
+#include "util/sort.h"
+#include "util/symbol.h"
+#include "util/util.h"
+#include "util/data.h"
+
+#include <stdlib.h>
+#include <math.h>
+
+/* Diff command specific HPP columns. */
+enum {
+ PERF_HPP_DIFF__BASELINE,
+ PERF_HPP_DIFF__PERIOD,
+ PERF_HPP_DIFF__PERIOD_BASELINE,
+ PERF_HPP_DIFF__DELTA,
+ PERF_HPP_DIFF__RATIO,
+ PERF_HPP_DIFF__WEIGHTED_DIFF,
+ PERF_HPP_DIFF__FORMULA,
+
+ PERF_HPP_DIFF__MAX_INDEX
+};
+
+struct diff_hpp_fmt {
+ struct perf_hpp_fmt fmt;
+ int idx;
+ char *header;
+ int header_width;
+};
+
+struct data__file {
+ struct perf_session *session;
+ struct perf_data_file file;
+ int idx;
+ struct hists *hists;
+ struct diff_hpp_fmt fmt[PERF_HPP_DIFF__MAX_INDEX];
+};
+
+static struct data__file *data__files;
+static int data__files_cnt;
+
+#define data__for_each_file_start(i, d, s) \
+ for (i = s, d = &data__files[s]; \
+ i < data__files_cnt; \
+ i++, d = &data__files[i])
+
+#define data__for_each_file(i, d) data__for_each_file_start(i, d, 0)
+#define data__for_each_file_new(i, d) data__for_each_file_start(i, d, 1)
+
+static bool force;
+static bool show_period;
+static bool show_formula;
+static bool show_baseline_only;
+static unsigned int sort_compute;
+
+static s64 compute_wdiff_w1;
+static s64 compute_wdiff_w2;
+
+enum {
+ COMPUTE_DELTA,
+ COMPUTE_RATIO,
+ COMPUTE_WEIGHTED_DIFF,
+ COMPUTE_MAX,
+};
+
+const char *compute_names[COMPUTE_MAX] = {
+ [COMPUTE_DELTA] = "delta",
+ [COMPUTE_RATIO] = "ratio",
+ [COMPUTE_WEIGHTED_DIFF] = "wdiff",
+};
+
+static int compute;
+
+static int compute_2_hpp[COMPUTE_MAX] = {
+ [COMPUTE_DELTA] = PERF_HPP_DIFF__DELTA,
+ [COMPUTE_RATIO] = PERF_HPP_DIFF__RATIO,
+ [COMPUTE_WEIGHTED_DIFF] = PERF_HPP_DIFF__WEIGHTED_DIFF,
+};
+
+#define MAX_COL_WIDTH 70
+
+static struct header_column {
+ const char *name;
+ int width;
+} columns[PERF_HPP_DIFF__MAX_INDEX] = {
+ [PERF_HPP_DIFF__BASELINE] = {
+ .name = "Baseline",
+ },
+ [PERF_HPP_DIFF__PERIOD] = {
+ .name = "Period",
+ .width = 14,
+ },
+ [PERF_HPP_DIFF__PERIOD_BASELINE] = {
+ .name = "Base period",
+ .width = 14,
+ },
+ [PERF_HPP_DIFF__DELTA] = {
+ .name = "Delta",
+ .width = 7,
+ },
+ [PERF_HPP_DIFF__RATIO] = {
+ .name = "Ratio",
+ .width = 14,
+ },
+ [PERF_HPP_DIFF__WEIGHTED_DIFF] = {
+ .name = "Weighted diff",
+ .width = 14,
+ },
+ [PERF_HPP_DIFF__FORMULA] = {
+ .name = "Formula",
+ .width = MAX_COL_WIDTH,
+ }
+};
+
+static int setup_compute_opt_wdiff(char *opt)
+{
+ char *w1_str = opt;
+ char *w2_str;
+
+ int ret = -EINVAL;
+
+ if (!opt)
+ goto out;
+
+ w2_str = strchr(opt, ',');
+ if (!w2_str)
+ goto out;
+
+ *w2_str++ = 0x0;
+ if (!*w2_str)
+ goto out;
+
+ compute_wdiff_w1 = strtol(w1_str, NULL, 10);
+ compute_wdiff_w2 = strtol(w2_str, NULL, 10);
+
+ if (!compute_wdiff_w1 || !compute_wdiff_w2)
+ goto out;
+
+ pr_debug("compute wdiff w1(%" PRId64 ") w2(%" PRId64 ")\n",
+ compute_wdiff_w1, compute_wdiff_w2);
+
+ ret = 0;
+
+ out:
+ if (ret)
+ pr_err("Failed: wrong weight data, use 'wdiff:w1,w2'\n");
+
+ return ret;
+}
+
+static int setup_compute_opt(char *opt)
+{
+ if (compute == COMPUTE_WEIGHTED_DIFF)
+ return setup_compute_opt_wdiff(opt);
+
+ if (opt) {
+ pr_err("Failed: extra option specified '%s'", opt);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int setup_compute(const struct option *opt, const char *str,
+ int unset __maybe_unused)
+{
+ int *cp = (int *) opt->value;
+ char *cstr = (char *) str;
+ char buf[50];
+ unsigned i;
+ char *option;
+
+ if (!str) {
+ *cp = COMPUTE_DELTA;
+ return 0;
+ }
+
+ option = strchr(str, ':');
+ if (option) {
+ unsigned len = option++ - str;
+
+ /*
+ * The str data are not writeable, so we need
+ * to use another buffer.
+ */
+
+ /* No option value is longer. */
+ if (len >= sizeof(buf))
+ return -EINVAL;
+
+ strncpy(buf, str, len);
+ buf[len] = 0x0;
+ cstr = buf;
+ }
+
+ for (i = 0; i < COMPUTE_MAX; i++)
+ if (!strcmp(cstr, compute_names[i])) {
+ *cp = i;
+ return setup_compute_opt(option);
+ }
+
+ pr_err("Failed: '%s' is not computation method "
+ "(use 'delta','ratio' or 'wdiff')\n", str);
+ return -EINVAL;
+}
+
+static double period_percent(struct hist_entry *he, u64 period)
+{
+ u64 total = hists__total_period(he->hists);
+
+ return (period * 100.0) / total;
+}
+
+static double compute_delta(struct hist_entry *he, struct hist_entry *pair)
+{
+ double old_percent = period_percent(he, he->stat.period);
+ double new_percent = period_percent(pair, pair->stat.period);
+
+ pair->diff.period_ratio_delta = new_percent - old_percent;
+ pair->diff.computed = true;
+ return pair->diff.period_ratio_delta;
+}
+
+static double compute_ratio(struct hist_entry *he, struct hist_entry *pair)
+{
+ double old_period = he->stat.period ?: 1;
+ double new_period = pair->stat.period;
+
+ pair->diff.computed = true;
+ pair->diff.period_ratio = new_period / old_period;
+ return pair->diff.period_ratio;
+}
+
+static s64 compute_wdiff(struct hist_entry *he, struct hist_entry *pair)
+{
+ u64 old_period = he->stat.period;
+ u64 new_period = pair->stat.period;
+
+ pair->diff.computed = true;
+ pair->diff.wdiff = new_period * compute_wdiff_w2 -
+ old_period * compute_wdiff_w1;
+
+ return pair->diff.wdiff;
+}
+
+static int formula_delta(struct hist_entry *he, struct hist_entry *pair,
+ char *buf, size_t size)
+{
+ u64 he_total = he->hists->stats.total_period;
+ u64 pair_total = pair->hists->stats.total_period;
+
+ if (symbol_conf.filter_relative) {
+ he_total = he->hists->stats.total_non_filtered_period;
+ pair_total = pair->hists->stats.total_non_filtered_period;
+ }
+ return scnprintf(buf, size,
+ "(%" PRIu64 " * 100 / %" PRIu64 ") - "
+ "(%" PRIu64 " * 100 / %" PRIu64 ")",
+ pair->stat.period, pair_total,
+ he->stat.period, he_total);
+}
+
+static int formula_ratio(struct hist_entry *he, struct hist_entry *pair,
+ char *buf, size_t size)
+{
+ double old_period = he->stat.period;
+ double new_period = pair->stat.period;
+
+ return scnprintf(buf, size, "%.0F / %.0F", new_period, old_period);
+}
+
+static int formula_wdiff(struct hist_entry *he, struct hist_entry *pair,
+ char *buf, size_t size)
+{
+ u64 old_period = he->stat.period;
+ u64 new_period = pair->stat.period;
+
+ return scnprintf(buf, size,
+ "(%" PRIu64 " * " "%" PRId64 ") - (%" PRIu64 " * " "%" PRId64 ")",
+ new_period, compute_wdiff_w2, old_period, compute_wdiff_w1);
+}
+
+static int formula_fprintf(struct hist_entry *he, struct hist_entry *pair,
+ char *buf, size_t size)
+{
+ switch (compute) {
+ case COMPUTE_DELTA:
+ return formula_delta(he, pair, buf, size);
+ case COMPUTE_RATIO:
+ return formula_ratio(he, pair, buf, size);
+ case COMPUTE_WEIGHTED_DIFF:
+ return formula_wdiff(he, pair, buf, size);
+ default:
+ BUG_ON(1);
+ }
+
+ return -1;
+}
+
+static int hists__add_entry(struct hists *hists,
+ struct addr_location *al, u64 period,
+ u64 weight, u64 transaction)
+{
+ if (__hists__add_entry(hists, al, NULL, NULL, NULL, period, weight,
+ transaction, true) != NULL)
+ return 0;
+ return -ENOMEM;
+}
+
+static int diff__process_sample_event(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ struct addr_location al;
+
+ if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) {
+ pr_warning("problem processing %d event, skipping it.\n",
+ event->header.type);
+ return -1;
+ }
+
+ if (hists__add_entry(&evsel->hists, &al, sample->period,
+ sample->weight, sample->transaction)) {
+ pr_warning("problem incrementing symbol period, skipping event\n");
+ return -1;
+ }
+
+ /*
+ * The total_period is updated here before going to the output
+ * tree since normally only the baseline hists will call
+ * hists__output_resort() and precompute needs the total
+ * period in order to sort entries by percentage delta.
+ */
+ evsel->hists.stats.total_period += sample->period;
+ if (!al.filtered)
+ evsel->hists.stats.total_non_filtered_period += sample->period;
+
+ return 0;
+}
+
+static struct perf_tool tool = {
+ .sample = diff__process_sample_event,
+ .mmap = perf_event__process_mmap,
+ .comm = perf_event__process_comm,
+ .exit = perf_event__process_exit,
+ .fork = perf_event__process_fork,
+ .lost = perf_event__process_lost,
+ .ordered_samples = true,
+ .ordering_requires_timestamps = true,
+};
+
+static struct perf_evsel *evsel_match(struct perf_evsel *evsel,
+ struct perf_evlist *evlist)
+{
+ struct perf_evsel *e;
+
+ evlist__for_each(evlist, e) {
+ if (perf_evsel__match2(evsel, e))
+ return e;
+ }
+
+ return NULL;
+}
+
+static void perf_evlist__collapse_resort(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel;
+
+ evlist__for_each(evlist, evsel) {
+ struct hists *hists = &evsel->hists;
+
+ hists__collapse_resort(hists, NULL);
+ }
+}
+
+static struct hist_entry*
+get_pair_data(struct hist_entry *he, struct data__file *d)
+{
+ if (hist_entry__has_pairs(he)) {
+ struct hist_entry *pair;
+
+ list_for_each_entry(pair, &he->pairs.head, pairs.node)
+ if (pair->hists == d->hists)
+ return pair;
+ }
+
+ return NULL;
+}
+
+static struct hist_entry*
+get_pair_fmt(struct hist_entry *he, struct diff_hpp_fmt *dfmt)
+{
+ void *ptr = dfmt - dfmt->idx;
+ struct data__file *d = container_of(ptr, struct data__file, fmt);
+
+ return get_pair_data(he, d);
+}
+
+static void hists__baseline_only(struct hists *hists)
+{
+ struct rb_root *root;
+ struct rb_node *next;
+
+ if (sort__need_collapse)
+ root = &hists->entries_collapsed;
+ else
+ root = hists->entries_in;
+
+ next = rb_first(root);
+ while (next != NULL) {
+ struct hist_entry *he = rb_entry(next, struct hist_entry, rb_node_in);
+
+ next = rb_next(&he->rb_node_in);
+ if (!hist_entry__next_pair(he)) {
+ rb_erase(&he->rb_node_in, root);
+ hist_entry__free(he);
+ }
+ }
+}
+
+static void hists__precompute(struct hists *hists)
+{
+ struct rb_root *root;
+ struct rb_node *next;
+
+ if (sort__need_collapse)
+ root = &hists->entries_collapsed;
+ else
+ root = hists->entries_in;
+
+ next = rb_first(root);
+ while (next != NULL) {
+ struct hist_entry *he, *pair;
+
+ he = rb_entry(next, struct hist_entry, rb_node_in);
+ next = rb_next(&he->rb_node_in);
+
+ pair = get_pair_data(he, &data__files[sort_compute]);
+ if (!pair)
+ continue;
+
+ switch (compute) {
+ case COMPUTE_DELTA:
+ compute_delta(he, pair);
+ break;
+ case COMPUTE_RATIO:
+ compute_ratio(he, pair);
+ break;
+ case COMPUTE_WEIGHTED_DIFF:
+ compute_wdiff(he, pair);
+ break;
+ default:
+ BUG_ON(1);
+ }
+ }
+}
+
+static int64_t cmp_doubles(double l, double r)
+{
+ if (l > r)
+ return -1;
+ else if (l < r)
+ return 1;
+ else
+ return 0;
+}
+
+static int64_t
+__hist_entry__cmp_compute(struct hist_entry *left, struct hist_entry *right,
+ int c)
+{
+ switch (c) {
+ case COMPUTE_DELTA:
+ {
+ double l = left->diff.period_ratio_delta;
+ double r = right->diff.period_ratio_delta;
+
+ return cmp_doubles(l, r);
+ }
+ case COMPUTE_RATIO:
+ {
+ double l = left->diff.period_ratio;
+ double r = right->diff.period_ratio;
+
+ return cmp_doubles(l, r);
+ }
+ case COMPUTE_WEIGHTED_DIFF:
+ {
+ s64 l = left->diff.wdiff;
+ s64 r = right->diff.wdiff;
+
+ return r - l;
+ }
+ default:
+ BUG_ON(1);
+ }
+
+ return 0;
+}
+
+static int64_t
+hist_entry__cmp_compute(struct hist_entry *left, struct hist_entry *right,
+ int c)
+{
+ bool pairs_left = hist_entry__has_pairs(left);
+ bool pairs_right = hist_entry__has_pairs(right);
+ struct hist_entry *p_right, *p_left;
+
+ if (!pairs_left && !pairs_right)
+ return 0;
+
+ if (!pairs_left || !pairs_right)
+ return pairs_left ? -1 : 1;
+
+ p_left = get_pair_data(left, &data__files[sort_compute]);
+ p_right = get_pair_data(right, &data__files[sort_compute]);
+
+ if (!p_left && !p_right)
+ return 0;
+
+ if (!p_left || !p_right)
+ return p_left ? -1 : 1;
+
+ /*
+ * We have 2 entries of same kind, let's
+ * make the data comparison.
+ */
+ return __hist_entry__cmp_compute(p_left, p_right, c);
+}
+
+static void insert_hist_entry_by_compute(struct rb_root *root,
+ struct hist_entry *he,
+ int c)
+{
+ struct rb_node **p = &root->rb_node;
+ struct rb_node *parent = NULL;
+ struct hist_entry *iter;
+
+ while (*p != NULL) {
+ parent = *p;
+ iter = rb_entry(parent, struct hist_entry, rb_node);
+ if (hist_entry__cmp_compute(he, iter, c) < 0)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ rb_link_node(&he->rb_node, parent, p);
+ rb_insert_color(&he->rb_node, root);
+}
+
+static void hists__compute_resort(struct hists *hists)
+{
+ struct rb_root *root;
+ struct rb_node *next;
+
+ if (sort__need_collapse)
+ root = &hists->entries_collapsed;
+ else
+ root = hists->entries_in;
+
+ hists->entries = RB_ROOT;
+ next = rb_first(root);
+
+ hists__reset_stats(hists);
+ hists__reset_col_len(hists);
+
+ while (next != NULL) {
+ struct hist_entry *he;
+
+ he = rb_entry(next, struct hist_entry, rb_node_in);
+ next = rb_next(&he->rb_node_in);
+
+ insert_hist_entry_by_compute(&hists->entries, he, compute);
+ hists__inc_stats(hists, he);
+
+ if (!he->filtered)
+ hists__calc_col_len(hists, he);
+ }
+}
+
+static void hists__process(struct hists *hists)
+{
+ if (show_baseline_only)
+ hists__baseline_only(hists);
+
+ if (sort_compute) {
+ hists__precompute(hists);
+ hists__compute_resort(hists);
+ } else {
+ hists__output_resort(hists);
+ }
+
+ hists__fprintf(hists, true, 0, 0, 0, stdout);
+}
+
+static void data__fprintf(void)
+{
+ struct data__file *d;
+ int i;
+
+ fprintf(stdout, "# Data files:\n");
+
+ data__for_each_file(i, d)
+ fprintf(stdout, "# [%d] %s %s\n",
+ d->idx, d->file.path,
+ !d->idx ? "(Baseline)" : "");
+
+ fprintf(stdout, "#\n");
+}
+
+static void data_process(void)
+{
+ struct perf_evlist *evlist_base = data__files[0].session->evlist;
+ struct perf_evsel *evsel_base;
+ bool first = true;
+
+ evlist__for_each(evlist_base, evsel_base) {
+ struct data__file *d;
+ int i;
+
+ data__for_each_file_new(i, d) {
+ struct perf_evlist *evlist = d->session->evlist;
+ struct perf_evsel *evsel;
+
+ evsel = evsel_match(evsel_base, evlist);
+ if (!evsel)
+ continue;
+
+ d->hists = &evsel->hists;
+
+ hists__match(&evsel_base->hists, &evsel->hists);
+
+ if (!show_baseline_only)
+ hists__link(&evsel_base->hists,
+ &evsel->hists);
+ }
+
+ fprintf(stdout, "%s# Event '%s'\n#\n", first ? "" : "\n",
+ perf_evsel__name(evsel_base));
+
+ first = false;
+
+ if (verbose || data__files_cnt > 2)
+ data__fprintf();
+
+ hists__process(&evsel_base->hists);
+ }
+}
+
+static void data__free(struct data__file *d)
+{
+ int col;
+
+ for (col = 0; col < PERF_HPP_DIFF__MAX_INDEX; col++) {
+ struct diff_hpp_fmt *fmt = &d->fmt[col];
+
+ zfree(&fmt->header);
+ }
+}
+
+static int __cmd_diff(void)
+{
+ struct data__file *d;
+ int ret = -EINVAL, i;
+
+ data__for_each_file(i, d) {
+ d->session = perf_session__new(&d->file, false, &tool);
+ if (!d->session) {
+ pr_err("Failed to open %s\n", d->file.path);
+ ret = -ENOMEM;
+ goto out_delete;
+ }
+
+ ret = perf_session__process_events(d->session, &tool);
+ if (ret) {
+ pr_err("Failed to process %s\n", d->file.path);
+ goto out_delete;
+ }
+
+ perf_evlist__collapse_resort(d->session->evlist);
+ }
+
+ data_process();
+
+ out_delete:
+ data__for_each_file(i, d) {
+ if (d->session)
+ perf_session__delete(d->session);
+
+ data__free(d);
+ }
+
+ free(data__files);
+ return ret;
+}
+
+static const char * const diff_usage[] = {
+ "perf diff [<options>] [old_file] [new_file]",
+ NULL,
+};
+
+static const struct option options[] = {
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show symbol address, etc)"),
+ OPT_BOOLEAN('b', "baseline-only", &show_baseline_only,
+ "Show only items with match in baseline"),
+ OPT_CALLBACK('c', "compute", &compute,
+ "delta,ratio,wdiff:w1,w2 (default delta)",
+ "Entries differential computation selection",
+ setup_compute),
+ OPT_BOOLEAN('p', "period", &show_period,
+ "Show period values."),
+ OPT_BOOLEAN('F', "formula", &show_formula,
+ "Show formula."),
+ OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
+ "dump raw trace in ASCII"),
+ OPT_BOOLEAN('f', "force", &force, "don't complain, do it"),
+ OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules,
+ "load module symbols - WARNING: use only with -k and LIVE kernel"),
+ OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
+ "only consider symbols in these dsos"),
+ OPT_STRING('C', "comms", &symbol_conf.comm_list_str, "comm[,comm...]",
+ "only consider symbols in these comms"),
+ OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]",
+ "only consider these symbols"),
+ OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
+ "sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline, ..."
+ " Please refer the man page for the complete list."),
+ OPT_STRING('t', "field-separator", &symbol_conf.field_sep, "separator",
+ "separator for columns, no spaces will be added between "
+ "columns '.' is reserved."),
+ OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory",
+ "Look for files with symbols relative to this directory"),
+ OPT_UINTEGER('o', "order", &sort_compute, "Specify compute sorting."),
+ OPT_CALLBACK(0, "percentage", NULL, "relative|absolute",
+ "How to display percentage of filtered entries", parse_filter_percentage),
+ OPT_END()
+};
+
+static double baseline_percent(struct hist_entry *he)
+{
+ u64 total = hists__total_period(he->hists);
+
+ return 100.0 * he->stat.period / total;
+}
+
+static int hpp__color_baseline(struct perf_hpp_fmt *fmt,
+ struct perf_hpp *hpp, struct hist_entry *he)
+{
+ struct diff_hpp_fmt *dfmt =
+ container_of(fmt, struct diff_hpp_fmt, fmt);
+ double percent = baseline_percent(he);
+ char pfmt[20] = " ";
+
+ if (!he->dummy) {
+ scnprintf(pfmt, 20, "%%%d.2f%%%%", dfmt->header_width - 1);
+ return percent_color_snprintf(hpp->buf, hpp->size,
+ pfmt, percent);
+ } else
+ return scnprintf(hpp->buf, hpp->size, "%*s",
+ dfmt->header_width, pfmt);
+}
+
+static int hpp__entry_baseline(struct hist_entry *he, char *buf, size_t size)
+{
+ double percent = baseline_percent(he);
+ const char *fmt = symbol_conf.field_sep ? "%.2f" : "%6.2f%%";
+ int ret = 0;
+
+ if (!he->dummy)
+ ret = scnprintf(buf, size, fmt, percent);
+
+ return ret;
+}
+
+static int __hpp__color_compare(struct perf_hpp_fmt *fmt,
+ struct perf_hpp *hpp, struct hist_entry *he,
+ int comparison_method)
+{
+ struct diff_hpp_fmt *dfmt =
+ container_of(fmt, struct diff_hpp_fmt, fmt);
+ struct hist_entry *pair = get_pair_fmt(he, dfmt);
+ double diff;
+ s64 wdiff;
+ char pfmt[20] = " ";
+
+ if (!pair)
+ goto dummy_print;
+
+ switch (comparison_method) {
+ case COMPUTE_DELTA:
+ if (pair->diff.computed)
+ diff = pair->diff.period_ratio_delta;
+ else
+ diff = compute_delta(he, pair);
+
+ if (fabs(diff) < 0.01)
+ goto dummy_print;
+ scnprintf(pfmt, 20, "%%%+d.2f%%%%", dfmt->header_width - 1);
+ return percent_color_snprintf(hpp->buf, hpp->size,
+ pfmt, diff);
+ case COMPUTE_RATIO:
+ if (he->dummy)
+ goto dummy_print;
+ if (pair->diff.computed)
+ diff = pair->diff.period_ratio;
+ else
+ diff = compute_ratio(he, pair);
+
+ scnprintf(pfmt, 20, "%%%d.6f", dfmt->header_width);
+ return value_color_snprintf(hpp->buf, hpp->size,
+ pfmt, diff);
+ case COMPUTE_WEIGHTED_DIFF:
+ if (he->dummy)
+ goto dummy_print;
+ if (pair->diff.computed)
+ wdiff = pair->diff.wdiff;
+ else
+ wdiff = compute_wdiff(he, pair);
+
+ scnprintf(pfmt, 20, "%%14ld", dfmt->header_width);
+ return color_snprintf(hpp->buf, hpp->size,
+ get_percent_color(wdiff),
+ pfmt, wdiff);
+ default:
+ BUG_ON(1);
+ }
+dummy_print:
+ return scnprintf(hpp->buf, hpp->size, "%*s",
+ dfmt->header_width, pfmt);
+}
+
+static int hpp__color_delta(struct perf_hpp_fmt *fmt,
+ struct perf_hpp *hpp, struct hist_entry *he)
+{
+ return __hpp__color_compare(fmt, hpp, he, COMPUTE_DELTA);
+}
+
+static int hpp__color_ratio(struct perf_hpp_fmt *fmt,
+ struct perf_hpp *hpp, struct hist_entry *he)
+{
+ return __hpp__color_compare(fmt, hpp, he, COMPUTE_RATIO);
+}
+
+static int hpp__color_wdiff(struct perf_hpp_fmt *fmt,
+ struct perf_hpp *hpp, struct hist_entry *he)
+{
+ return __hpp__color_compare(fmt, hpp, he, COMPUTE_WEIGHTED_DIFF);
+}
+
+static void
+hpp__entry_unpair(struct hist_entry *he, int idx, char *buf, size_t size)
+{
+ switch (idx) {
+ case PERF_HPP_DIFF__PERIOD_BASELINE:
+ scnprintf(buf, size, "%" PRIu64, he->stat.period);
+ break;
+
+ default:
+ break;
+ }
+}
+
+static void
+hpp__entry_pair(struct hist_entry *he, struct hist_entry *pair,
+ int idx, char *buf, size_t size)
+{
+ double diff;
+ double ratio;
+ s64 wdiff;
+
+ switch (idx) {
+ case PERF_HPP_DIFF__DELTA:
+ if (pair->diff.computed)
+ diff = pair->diff.period_ratio_delta;
+ else
+ diff = compute_delta(he, pair);
+
+ if (fabs(diff) >= 0.01)
+ scnprintf(buf, size, "%+4.2F%%", diff);
+ break;
+
+ case PERF_HPP_DIFF__RATIO:
+ /* No point for ratio number if we are dummy.. */
+ if (he->dummy)
+ break;
+
+ if (pair->diff.computed)
+ ratio = pair->diff.period_ratio;
+ else
+ ratio = compute_ratio(he, pair);
+
+ if (ratio > 0.0)
+ scnprintf(buf, size, "%14.6F", ratio);
+ break;
+
+ case PERF_HPP_DIFF__WEIGHTED_DIFF:
+ /* No point for wdiff number if we are dummy.. */
+ if (he->dummy)
+ break;
+
+ if (pair->diff.computed)
+ wdiff = pair->diff.wdiff;
+ else
+ wdiff = compute_wdiff(he, pair);
+
+ if (wdiff != 0)
+ scnprintf(buf, size, "%14ld", wdiff);
+ break;
+
+ case PERF_HPP_DIFF__FORMULA:
+ formula_fprintf(he, pair, buf, size);
+ break;
+
+ case PERF_HPP_DIFF__PERIOD:
+ scnprintf(buf, size, "%" PRIu64, pair->stat.period);
+ break;
+
+ default:
+ BUG_ON(1);
+ };
+}
+
+static void
+__hpp__entry_global(struct hist_entry *he, struct diff_hpp_fmt *dfmt,
+ char *buf, size_t size)
+{
+ struct hist_entry *pair = get_pair_fmt(he, dfmt);
+ int idx = dfmt->idx;
+
+ /* baseline is special */
+ if (idx == PERF_HPP_DIFF__BASELINE)
+ hpp__entry_baseline(he, buf, size);
+ else {
+ if (pair)
+ hpp__entry_pair(he, pair, idx, buf, size);
+ else
+ hpp__entry_unpair(he, idx, buf, size);
+ }
+}
+
+static int hpp__entry_global(struct perf_hpp_fmt *_fmt, struct perf_hpp *hpp,
+ struct hist_entry *he)
+{
+ struct diff_hpp_fmt *dfmt =
+ container_of(_fmt, struct diff_hpp_fmt, fmt);
+ char buf[MAX_COL_WIDTH] = " ";
+
+ __hpp__entry_global(he, dfmt, buf, MAX_COL_WIDTH);
+
+ if (symbol_conf.field_sep)
+ return scnprintf(hpp->buf, hpp->size, "%s", buf);
+ else
+ return scnprintf(hpp->buf, hpp->size, "%*s",
+ dfmt->header_width, buf);
+}
+
+static int hpp__header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct perf_evsel *evsel __maybe_unused)
+{
+ struct diff_hpp_fmt *dfmt =
+ container_of(fmt, struct diff_hpp_fmt, fmt);
+
+ BUG_ON(!dfmt->header);
+ return scnprintf(hpp->buf, hpp->size, dfmt->header);
+}
+
+static int hpp__width(struct perf_hpp_fmt *fmt,
+ struct perf_hpp *hpp __maybe_unused,
+ struct perf_evsel *evsel __maybe_unused)
+{
+ struct diff_hpp_fmt *dfmt =
+ container_of(fmt, struct diff_hpp_fmt, fmt);
+
+ BUG_ON(dfmt->header_width <= 0);
+ return dfmt->header_width;
+}
+
+static void init_header(struct data__file *d, struct diff_hpp_fmt *dfmt)
+{
+#define MAX_HEADER_NAME 100
+ char buf_indent[MAX_HEADER_NAME];
+ char buf[MAX_HEADER_NAME];
+ const char *header = NULL;
+ int width = 0;
+
+ BUG_ON(dfmt->idx >= PERF_HPP_DIFF__MAX_INDEX);
+ header = columns[dfmt->idx].name;
+ width = columns[dfmt->idx].width;
+
+ /* Only our defined HPP fmts should appear here. */
+ BUG_ON(!header);
+
+ if (data__files_cnt > 2)
+ scnprintf(buf, MAX_HEADER_NAME, "%s/%d", header, d->idx);
+
+#define NAME (data__files_cnt > 2 ? buf : header)
+ dfmt->header_width = width;
+ width = (int) strlen(NAME);
+ if (dfmt->header_width < width)
+ dfmt->header_width = width;
+
+ scnprintf(buf_indent, MAX_HEADER_NAME, "%*s",
+ dfmt->header_width, NAME);
+
+ dfmt->header = strdup(buf_indent);
+#undef MAX_HEADER_NAME
+#undef NAME
+}
+
+static void data__hpp_register(struct data__file *d, int idx)
+{
+ struct diff_hpp_fmt *dfmt = &d->fmt[idx];
+ struct perf_hpp_fmt *fmt = &dfmt->fmt;
+
+ dfmt->idx = idx;
+
+ fmt->header = hpp__header;
+ fmt->width = hpp__width;
+ fmt->entry = hpp__entry_global;
+
+ /* TODO more colors */
+ switch (idx) {
+ case PERF_HPP_DIFF__BASELINE:
+ fmt->color = hpp__color_baseline;
+ break;
+ case PERF_HPP_DIFF__DELTA:
+ fmt->color = hpp__color_delta;
+ break;
+ case PERF_HPP_DIFF__RATIO:
+ fmt->color = hpp__color_ratio;
+ break;
+ case PERF_HPP_DIFF__WEIGHTED_DIFF:
+ fmt->color = hpp__color_wdiff;
+ break;
+ default:
+ break;
+ }
+
+ init_header(d, dfmt);
+ perf_hpp__column_register(fmt);
+}
+
+static void ui_init(void)
+{
+ struct data__file *d;
+ int i;
+
+ data__for_each_file(i, d) {
+
+ /*
+ * Baseline or compute realted columns:
+ *
+ * PERF_HPP_DIFF__BASELINE
+ * PERF_HPP_DIFF__DELTA
+ * PERF_HPP_DIFF__RATIO
+ * PERF_HPP_DIFF__WEIGHTED_DIFF
+ */
+ data__hpp_register(d, i ? compute_2_hpp[compute] :
+ PERF_HPP_DIFF__BASELINE);
+
+ /*
+ * And the rest:
+ *
+ * PERF_HPP_DIFF__FORMULA
+ * PERF_HPP_DIFF__PERIOD
+ * PERF_HPP_DIFF__PERIOD_BASELINE
+ */
+ if (show_formula && i)
+ data__hpp_register(d, PERF_HPP_DIFF__FORMULA);
+
+ if (show_period)
+ data__hpp_register(d, i ? PERF_HPP_DIFF__PERIOD :
+ PERF_HPP_DIFF__PERIOD_BASELINE);
+ }
+}
+
+static int data_init(int argc, const char **argv)
+{
+ struct data__file *d;
+ static const char *defaults[] = {
+ "perf.data.old",
+ "perf.data",
+ };
+ bool use_default = true;
+ int i;
+
+ data__files_cnt = 2;
+
+ if (argc) {
+ if (argc == 1)
+ defaults[1] = argv[0];
+ else {
+ data__files_cnt = argc;
+ use_default = false;
+ }
+ } else if (perf_guest) {
+ defaults[0] = "perf.data.host";
+ defaults[1] = "perf.data.guest";
+ }
+
+ if (sort_compute >= (unsigned int) data__files_cnt) {
+ pr_err("Order option out of limit.\n");
+ return -EINVAL;
+ }
+
+ data__files = zalloc(sizeof(*data__files) * data__files_cnt);
+ if (!data__files)
+ return -ENOMEM;
+
+ data__for_each_file(i, d) {
+ struct perf_data_file *file = &d->file;
+
+ file->path = use_default ? defaults[i] : argv[i];
+ file->mode = PERF_DATA_MODE_READ,
+ file->force = force,
+
+ d->idx = i;
+ }
+
+ return 0;
+}
+
+int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ perf_config(perf_default_config, NULL);
+
+ argc = parse_options(argc, argv, options, diff_usage, 0);
+
+ if (symbol__init() < 0)
+ return -1;
+
+ if (data_init(argc, argv) < 0)
+ return -1;
+
+ ui_init();
+
+ sort__mode = SORT_MODE__DIFF;
+
+ if (setup_sorting() < 0)
+ usage_with_options(diff_usage, options);
+
+ setup_pager();
+
+ sort__setup_elide(NULL);
+
+ return __cmd_diff();
+}
diff --git a/tools/perf/builtin-evlist.c b/tools/perf/builtin-evlist.c
new file mode 100644
index 00000000000..c99e0de7e54
--- /dev/null
+++ b/tools/perf/builtin-evlist.c
@@ -0,0 +1,66 @@
+/*
+ * Builtin evlist command: Show the list of event selectors present
+ * in a perf.data file.
+ */
+#include "builtin.h"
+
+#include "util/util.h"
+
+#include <linux/list.h>
+
+#include "perf.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/parse-events.h"
+#include "util/parse-options.h"
+#include "util/session.h"
+#include "util/data.h"
+
+static int __cmd_evlist(const char *file_name, struct perf_attr_details *details)
+{
+ struct perf_session *session;
+ struct perf_evsel *pos;
+ struct perf_data_file file = {
+ .path = file_name,
+ .mode = PERF_DATA_MODE_READ,
+ };
+
+ session = perf_session__new(&file, 0, NULL);
+ if (session == NULL)
+ return -ENOMEM;
+
+ evlist__for_each(session->evlist, pos)
+ perf_evsel__fprintf(pos, details, stdout);
+
+ perf_session__delete(session);
+ return 0;
+}
+
+int cmd_evlist(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ struct perf_attr_details details = { .verbose = false, };
+ const struct option options[] = {
+ OPT_STRING('i', "input", &input_name, "file", "Input file name"),
+ OPT_BOOLEAN('F', "freq", &details.freq, "Show the sample frequency"),
+ OPT_BOOLEAN('v', "verbose", &details.verbose,
+ "Show all event attr details"),
+ OPT_BOOLEAN('g', "group", &details.event_group,
+ "Show event group information"),
+ OPT_END()
+ };
+ const char * const evlist_usage[] = {
+ "perf evlist [<options>]",
+ NULL
+ };
+
+ argc = parse_options(argc, argv, options, evlist_usage, 0);
+ if (argc)
+ usage_with_options(evlist_usage, options);
+
+ if (details.event_group && (details.verbose || details.freq)) {
+ pr_err("--group option is not compatible with other options\n");
+ usage_with_options(evlist_usage, options);
+ }
+
+ return __cmd_evlist(input_name, &details);
+}
diff --git a/tools/perf/builtin-help.c b/tools/perf/builtin-help.c
new file mode 100644
index 00000000000..178b88ae3d2
--- /dev/null
+++ b/tools/perf/builtin-help.c
@@ -0,0 +1,479 @@
+/*
+ * builtin-help.c
+ *
+ * Builtin help command
+ */
+#include "perf.h"
+#include "util/cache.h"
+#include "builtin.h"
+#include "util/exec_cmd.h"
+#include "common-cmds.h"
+#include "util/parse-options.h"
+#include "util/run-command.h"
+#include "util/help.h"
+
+static struct man_viewer_list {
+ struct man_viewer_list *next;
+ char name[FLEX_ARRAY];
+} *man_viewer_list;
+
+static struct man_viewer_info_list {
+ struct man_viewer_info_list *next;
+ const char *info;
+ char name[FLEX_ARRAY];
+} *man_viewer_info_list;
+
+enum help_format {
+ HELP_FORMAT_NONE,
+ HELP_FORMAT_MAN,
+ HELP_FORMAT_INFO,
+ HELP_FORMAT_WEB,
+};
+
+static enum help_format parse_help_format(const char *format)
+{
+ if (!strcmp(format, "man"))
+ return HELP_FORMAT_MAN;
+ if (!strcmp(format, "info"))
+ return HELP_FORMAT_INFO;
+ if (!strcmp(format, "web") || !strcmp(format, "html"))
+ return HELP_FORMAT_WEB;
+
+ pr_err("unrecognized help format '%s'", format);
+ return HELP_FORMAT_NONE;
+}
+
+static const char *get_man_viewer_info(const char *name)
+{
+ struct man_viewer_info_list *viewer;
+
+ for (viewer = man_viewer_info_list; viewer; viewer = viewer->next) {
+ if (!strcasecmp(name, viewer->name))
+ return viewer->info;
+ }
+ return NULL;
+}
+
+static int check_emacsclient_version(void)
+{
+ struct strbuf buffer = STRBUF_INIT;
+ struct child_process ec_process;
+ const char *argv_ec[] = { "emacsclient", "--version", NULL };
+ int version;
+
+ /* emacsclient prints its version number on stderr */
+ memset(&ec_process, 0, sizeof(ec_process));
+ ec_process.argv = argv_ec;
+ ec_process.err = -1;
+ ec_process.stdout_to_stderr = 1;
+ if (start_command(&ec_process)) {
+ fprintf(stderr, "Failed to start emacsclient.\n");
+ return -1;
+ }
+ strbuf_read(&buffer, ec_process.err, 20);
+ close(ec_process.err);
+
+ /*
+ * Don't bother checking return value, because "emacsclient --version"
+ * seems to always exits with code 1.
+ */
+ finish_command(&ec_process);
+
+ if (prefixcmp(buffer.buf, "emacsclient")) {
+ fprintf(stderr, "Failed to parse emacsclient version.\n");
+ strbuf_release(&buffer);
+ return -1;
+ }
+
+ strbuf_remove(&buffer, 0, strlen("emacsclient"));
+ version = atoi(buffer.buf);
+
+ if (version < 22) {
+ fprintf(stderr,
+ "emacsclient version '%d' too old (< 22).\n",
+ version);
+ strbuf_release(&buffer);
+ return -1;
+ }
+
+ strbuf_release(&buffer);
+ return 0;
+}
+
+static void exec_woman_emacs(const char *path, const char *page)
+{
+ if (!check_emacsclient_version()) {
+ /* This works only with emacsclient version >= 22. */
+ struct strbuf man_page = STRBUF_INIT;
+
+ if (!path)
+ path = "emacsclient";
+ strbuf_addf(&man_page, "(woman \"%s\")", page);
+ execlp(path, "emacsclient", "-e", man_page.buf, NULL);
+ warning("failed to exec '%s': %s", path, strerror(errno));
+ }
+}
+
+static void exec_man_konqueror(const char *path, const char *page)
+{
+ const char *display = getenv("DISPLAY");
+ if (display && *display) {
+ struct strbuf man_page = STRBUF_INIT;
+ const char *filename = "kfmclient";
+
+ /* It's simpler to launch konqueror using kfmclient. */
+ if (path) {
+ const char *file = strrchr(path, '/');
+ if (file && !strcmp(file + 1, "konqueror")) {
+ char *new = strdup(path);
+ char *dest = strrchr(new, '/');
+
+ /* strlen("konqueror") == strlen("kfmclient") */
+ strcpy(dest + 1, "kfmclient");
+ path = new;
+ }
+ if (file)
+ filename = file;
+ } else
+ path = "kfmclient";
+ strbuf_addf(&man_page, "man:%s(1)", page);
+ execlp(path, filename, "newTab", man_page.buf, NULL);
+ warning("failed to exec '%s': %s", path, strerror(errno));
+ }
+}
+
+static void exec_man_man(const char *path, const char *page)
+{
+ if (!path)
+ path = "man";
+ execlp(path, "man", page, NULL);
+ warning("failed to exec '%s': %s", path, strerror(errno));
+}
+
+static void exec_man_cmd(const char *cmd, const char *page)
+{
+ struct strbuf shell_cmd = STRBUF_INIT;
+ strbuf_addf(&shell_cmd, "%s %s", cmd, page);
+ execl("/bin/sh", "sh", "-c", shell_cmd.buf, NULL);
+ warning("failed to exec '%s': %s", cmd, strerror(errno));
+}
+
+static void add_man_viewer(const char *name)
+{
+ struct man_viewer_list **p = &man_viewer_list;
+ size_t len = strlen(name);
+
+ while (*p)
+ p = &((*p)->next);
+ *p = zalloc(sizeof(**p) + len + 1);
+ strncpy((*p)->name, name, len);
+}
+
+static int supported_man_viewer(const char *name, size_t len)
+{
+ return (!strncasecmp("man", name, len) ||
+ !strncasecmp("woman", name, len) ||
+ !strncasecmp("konqueror", name, len));
+}
+
+static void do_add_man_viewer_info(const char *name,
+ size_t len,
+ const char *value)
+{
+ struct man_viewer_info_list *new = zalloc(sizeof(*new) + len + 1);
+
+ strncpy(new->name, name, len);
+ new->info = strdup(value);
+ new->next = man_viewer_info_list;
+ man_viewer_info_list = new;
+}
+
+static int add_man_viewer_path(const char *name,
+ size_t len,
+ const char *value)
+{
+ if (supported_man_viewer(name, len))
+ do_add_man_viewer_info(name, len, value);
+ else
+ warning("'%s': path for unsupported man viewer.\n"
+ "Please consider using 'man.<tool>.cmd' instead.",
+ name);
+
+ return 0;
+}
+
+static int add_man_viewer_cmd(const char *name,
+ size_t len,
+ const char *value)
+{
+ if (supported_man_viewer(name, len))
+ warning("'%s': cmd for supported man viewer.\n"
+ "Please consider using 'man.<tool>.path' instead.",
+ name);
+ else
+ do_add_man_viewer_info(name, len, value);
+
+ return 0;
+}
+
+static int add_man_viewer_info(const char *var, const char *value)
+{
+ const char *name = var + 4;
+ const char *subkey = strrchr(name, '.');
+
+ if (!subkey)
+ return error("Config with no key for man viewer: %s", name);
+
+ if (!strcmp(subkey, ".path")) {
+ if (!value)
+ return config_error_nonbool(var);
+ return add_man_viewer_path(name, subkey - name, value);
+ }
+ if (!strcmp(subkey, ".cmd")) {
+ if (!value)
+ return config_error_nonbool(var);
+ return add_man_viewer_cmd(name, subkey - name, value);
+ }
+
+ warning("'%s': unsupported man viewer sub key.", subkey);
+ return 0;
+}
+
+static int perf_help_config(const char *var, const char *value, void *cb)
+{
+ enum help_format *help_formatp = cb;
+
+ if (!strcmp(var, "help.format")) {
+ if (!value)
+ return config_error_nonbool(var);
+ *help_formatp = parse_help_format(value);
+ if (*help_formatp == HELP_FORMAT_NONE)
+ return -1;
+ return 0;
+ }
+ if (!strcmp(var, "man.viewer")) {
+ if (!value)
+ return config_error_nonbool(var);
+ add_man_viewer(value);
+ return 0;
+ }
+ if (!prefixcmp(var, "man."))
+ return add_man_viewer_info(var, value);
+
+ return perf_default_config(var, value, cb);
+}
+
+static struct cmdnames main_cmds, other_cmds;
+
+void list_common_cmds_help(void)
+{
+ unsigned int i, longest = 0;
+
+ for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+ if (longest < strlen(common_cmds[i].name))
+ longest = strlen(common_cmds[i].name);
+ }
+
+ puts(" The most commonly used perf commands are:");
+ for (i = 0; i < ARRAY_SIZE(common_cmds); i++) {
+ printf(" %-*s ", longest, common_cmds[i].name);
+ puts(common_cmds[i].help);
+ }
+}
+
+static int is_perf_command(const char *s)
+{
+ return is_in_cmdlist(&main_cmds, s) ||
+ is_in_cmdlist(&other_cmds, s);
+}
+
+static const char *prepend(const char *prefix, const char *cmd)
+{
+ size_t pre_len = strlen(prefix);
+ size_t cmd_len = strlen(cmd);
+ char *p = malloc(pre_len + cmd_len + 1);
+ memcpy(p, prefix, pre_len);
+ strcpy(p + pre_len, cmd);
+ return p;
+}
+
+static const char *cmd_to_page(const char *perf_cmd)
+{
+ if (!perf_cmd)
+ return "perf";
+ else if (!prefixcmp(perf_cmd, "perf"))
+ return perf_cmd;
+ else
+ return prepend("perf-", perf_cmd);
+}
+
+static void setup_man_path(void)
+{
+ struct strbuf new_path = STRBUF_INIT;
+ const char *old_path = getenv("MANPATH");
+
+ /* We should always put ':' after our path. If there is no
+ * old_path, the ':' at the end will let 'man' to try
+ * system-wide paths after ours to find the manual page. If
+ * there is old_path, we need ':' as delimiter. */
+ strbuf_addstr(&new_path, system_path(PERF_MAN_PATH));
+ strbuf_addch(&new_path, ':');
+ if (old_path)
+ strbuf_addstr(&new_path, old_path);
+
+ setenv("MANPATH", new_path.buf, 1);
+
+ strbuf_release(&new_path);
+}
+
+static void exec_viewer(const char *name, const char *page)
+{
+ const char *info = get_man_viewer_info(name);
+
+ if (!strcasecmp(name, "man"))
+ exec_man_man(info, page);
+ else if (!strcasecmp(name, "woman"))
+ exec_woman_emacs(info, page);
+ else if (!strcasecmp(name, "konqueror"))
+ exec_man_konqueror(info, page);
+ else if (info)
+ exec_man_cmd(info, page);
+ else
+ warning("'%s': unknown man viewer.", name);
+}
+
+static int show_man_page(const char *perf_cmd)
+{
+ struct man_viewer_list *viewer;
+ const char *page = cmd_to_page(perf_cmd);
+ const char *fallback = getenv("PERF_MAN_VIEWER");
+
+ setup_man_path();
+ for (viewer = man_viewer_list; viewer; viewer = viewer->next)
+ exec_viewer(viewer->name, page); /* will return when unable */
+
+ if (fallback)
+ exec_viewer(fallback, page);
+ exec_viewer("man", page);
+
+ pr_err("no man viewer handled the request");
+ return -1;
+}
+
+static int show_info_page(const char *perf_cmd)
+{
+ const char *page = cmd_to_page(perf_cmd);
+ setenv("INFOPATH", system_path(PERF_INFO_PATH), 1);
+ execlp("info", "info", "perfman", page, NULL);
+ return -1;
+}
+
+static int get_html_page_path(struct strbuf *page_path, const char *page)
+{
+ struct stat st;
+ const char *html_path = system_path(PERF_HTML_PATH);
+
+ /* Check that we have a perf documentation directory. */
+ if (stat(mkpath("%s/perf.html", html_path), &st)
+ || !S_ISREG(st.st_mode)) {
+ pr_err("'%s': not a documentation directory.", html_path);
+ return -1;
+ }
+
+ strbuf_init(page_path, 0);
+ strbuf_addf(page_path, "%s/%s.html", html_path, page);
+
+ return 0;
+}
+
+/*
+ * If open_html is not defined in a platform-specific way (see for
+ * example compat/mingw.h), we use the script web--browse to display
+ * HTML.
+ */
+#ifndef open_html
+static void open_html(const char *path)
+{
+ execl_perf_cmd("web--browse", "-c", "help.browser", path, NULL);
+}
+#endif
+
+static int show_html_page(const char *perf_cmd)
+{
+ const char *page = cmd_to_page(perf_cmd);
+ struct strbuf page_path; /* it leaks but we exec bellow */
+
+ if (get_html_page_path(&page_path, page) != 0)
+ return -1;
+
+ open_html(page_path.buf);
+
+ return 0;
+}
+
+int cmd_help(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ bool show_all = false;
+ enum help_format help_format = HELP_FORMAT_MAN;
+ struct option builtin_help_options[] = {
+ OPT_BOOLEAN('a', "all", &show_all, "print all available commands"),
+ OPT_SET_UINT('m', "man", &help_format, "show man page", HELP_FORMAT_MAN),
+ OPT_SET_UINT('w', "web", &help_format, "show manual in web browser",
+ HELP_FORMAT_WEB),
+ OPT_SET_UINT('i', "info", &help_format, "show info page",
+ HELP_FORMAT_INFO),
+ OPT_END(),
+ };
+ const char * const builtin_help_usage[] = {
+ "perf help [--all] [--man|--web|--info] [command]",
+ NULL
+ };
+ const char *alias;
+ int rc = 0;
+
+ load_command_list("perf-", &main_cmds, &other_cmds);
+
+ perf_config(perf_help_config, &help_format);
+
+ argc = parse_options(argc, argv, builtin_help_options,
+ builtin_help_usage, 0);
+
+ if (show_all) {
+ printf("\n usage: %s\n\n", perf_usage_string);
+ list_commands("perf commands", &main_cmds, &other_cmds);
+ printf(" %s\n\n", perf_more_info_string);
+ return 0;
+ }
+
+ if (!argv[0]) {
+ printf("\n usage: %s\n\n", perf_usage_string);
+ list_common_cmds_help();
+ printf("\n %s\n\n", perf_more_info_string);
+ return 0;
+ }
+
+ alias = alias_lookup(argv[0]);
+ if (alias && !is_perf_command(argv[0])) {
+ printf("`perf %s' is aliased to `%s'\n", argv[0], alias);
+ return 0;
+ }
+
+ switch (help_format) {
+ case HELP_FORMAT_MAN:
+ rc = show_man_page(argv[0]);
+ break;
+ case HELP_FORMAT_INFO:
+ rc = show_info_page(argv[0]);
+ break;
+ case HELP_FORMAT_WEB:
+ rc = show_html_page(argv[0]);
+ break;
+ case HELP_FORMAT_NONE:
+ /* fall-through */
+ default:
+ rc = -1;
+ break;
+ }
+
+ return rc;
+}
diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c
new file mode 100644
index 00000000000..16c7c11ad06
--- /dev/null
+++ b/tools/perf/builtin-inject.c
@@ -0,0 +1,463 @@
+/*
+ * builtin-inject.c
+ *
+ * Builtin inject command: Examine the live mode (stdin) event stream
+ * and repipe it to stdout while optionally injecting additional
+ * events into it.
+ */
+#include "builtin.h"
+
+#include "perf.h"
+#include "util/color.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/session.h"
+#include "util/tool.h"
+#include "util/debug.h"
+#include "util/build-id.h"
+#include "util/data.h"
+
+#include "util/parse-options.h"
+
+#include <linux/list.h>
+
+struct perf_inject {
+ struct perf_tool tool;
+ bool build_ids;
+ bool sched_stat;
+ const char *input_name;
+ struct perf_data_file output;
+ u64 bytes_written;
+ struct list_head samples;
+};
+
+struct event_entry {
+ struct list_head node;
+ u32 tid;
+ union perf_event event[0];
+};
+
+static int perf_event__repipe_synth(struct perf_tool *tool,
+ union perf_event *event)
+{
+ struct perf_inject *inject = container_of(tool, struct perf_inject, tool);
+ ssize_t size;
+
+ size = perf_data_file__write(&inject->output, event,
+ event->header.size);
+ if (size < 0)
+ return -errno;
+
+ inject->bytes_written += size;
+ return 0;
+}
+
+static int perf_event__repipe_op2_synth(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_session *session
+ __maybe_unused)
+{
+ return perf_event__repipe_synth(tool, event);
+}
+
+static int perf_event__repipe_attr(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_evlist **pevlist)
+{
+ struct perf_inject *inject = container_of(tool, struct perf_inject,
+ tool);
+ int ret;
+
+ ret = perf_event__process_attr(tool, event, pevlist);
+ if (ret)
+ return ret;
+
+ if (!inject->output.is_pipe)
+ return 0;
+
+ return perf_event__repipe_synth(tool, event);
+}
+
+static int perf_event__repipe(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ return perf_event__repipe_synth(tool, event);
+}
+
+typedef int (*inject_handler)(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine);
+
+static int perf_event__repipe_sample(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ if (evsel->handler) {
+ inject_handler f = evsel->handler;
+ return f(tool, event, sample, evsel, machine);
+ }
+
+ build_id__mark_dso_hit(tool, event, sample, evsel, machine);
+
+ return perf_event__repipe_synth(tool, event);
+}
+
+static int perf_event__repipe_mmap(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ int err;
+
+ err = perf_event__process_mmap(tool, event, sample, machine);
+ perf_event__repipe(tool, event, sample, machine);
+
+ return err;
+}
+
+static int perf_event__repipe_mmap2(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ int err;
+
+ err = perf_event__process_mmap2(tool, event, sample, machine);
+ perf_event__repipe(tool, event, sample, machine);
+
+ return err;
+}
+
+static int perf_event__repipe_fork(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ int err;
+
+ err = perf_event__process_fork(tool, event, sample, machine);
+ perf_event__repipe(tool, event, sample, machine);
+
+ return err;
+}
+
+static int perf_event__repipe_tracing_data(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_session *session)
+{
+ int err;
+
+ perf_event__repipe_synth(tool, event);
+ err = perf_event__process_tracing_data(tool, event, session);
+
+ return err;
+}
+
+static int dso__read_build_id(struct dso *dso)
+{
+ if (dso->has_build_id)
+ return 0;
+
+ if (filename__read_build_id(dso->long_name, dso->build_id,
+ sizeof(dso->build_id)) > 0) {
+ dso->has_build_id = true;
+ return 0;
+ }
+
+ return -1;
+}
+
+static int dso__inject_build_id(struct dso *dso, struct perf_tool *tool,
+ struct machine *machine)
+{
+ u16 misc = PERF_RECORD_MISC_USER;
+ int err;
+
+ if (dso__read_build_id(dso) < 0) {
+ pr_debug("no build_id found for %s\n", dso->long_name);
+ return -1;
+ }
+
+ if (dso->kernel)
+ misc = PERF_RECORD_MISC_KERNEL;
+
+ err = perf_event__synthesize_build_id(tool, dso, misc, perf_event__repipe,
+ machine);
+ if (err) {
+ pr_err("Can't synthesize build_id event for %s\n", dso->long_name);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int perf_event__inject_buildid(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel __maybe_unused,
+ struct machine *machine)
+{
+ struct addr_location al;
+ struct thread *thread;
+ u8 cpumode;
+
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ thread = machine__findnew_thread(machine, sample->pid, sample->tid);
+ if (thread == NULL) {
+ pr_err("problem processing %d event, skipping it.\n",
+ event->header.type);
+ goto repipe;
+ }
+
+ thread__find_addr_map(thread, machine, cpumode, MAP__FUNCTION,
+ sample->ip, &al);
+
+ if (al.map != NULL) {
+ if (!al.map->dso->hit) {
+ al.map->dso->hit = 1;
+ if (map__load(al.map, NULL) >= 0) {
+ dso__inject_build_id(al.map->dso, tool, machine);
+ /*
+ * If this fails, too bad, let the other side
+ * account this as unresolved.
+ */
+ } else {
+#ifdef HAVE_LIBELF_SUPPORT
+ pr_warning("no symbols found in %s, maybe "
+ "install a debug package?\n",
+ al.map->dso->long_name);
+#endif
+ }
+ }
+ }
+
+repipe:
+ perf_event__repipe(tool, event, sample, machine);
+ return 0;
+}
+
+static int perf_inject__sched_process_exit(struct perf_tool *tool,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ struct perf_inject *inject = container_of(tool, struct perf_inject, tool);
+ struct event_entry *ent;
+
+ list_for_each_entry(ent, &inject->samples, node) {
+ if (sample->tid == ent->tid) {
+ list_del_init(&ent->node);
+ free(ent);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int perf_inject__sched_switch(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ struct perf_inject *inject = container_of(tool, struct perf_inject, tool);
+ struct event_entry *ent;
+
+ perf_inject__sched_process_exit(tool, event, sample, evsel, machine);
+
+ ent = malloc(event->header.size + sizeof(struct event_entry));
+ if (ent == NULL) {
+ color_fprintf(stderr, PERF_COLOR_RED,
+ "Not enough memory to process sched switch event!");
+ return -1;
+ }
+
+ ent->tid = sample->tid;
+ memcpy(&ent->event, event, event->header.size);
+ list_add(&ent->node, &inject->samples);
+ return 0;
+}
+
+static int perf_inject__sched_stat(struct perf_tool *tool,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ struct event_entry *ent;
+ union perf_event *event_sw;
+ struct perf_sample sample_sw;
+ struct perf_inject *inject = container_of(tool, struct perf_inject, tool);
+ u32 pid = perf_evsel__intval(evsel, sample, "pid");
+
+ list_for_each_entry(ent, &inject->samples, node) {
+ if (pid == ent->tid)
+ goto found;
+ }
+
+ return 0;
+found:
+ event_sw = &ent->event[0];
+ perf_evsel__parse_sample(evsel, event_sw, &sample_sw);
+
+ sample_sw.period = sample->period;
+ sample_sw.time = sample->time;
+ perf_event__synthesize_sample(event_sw, evsel->attr.sample_type,
+ evsel->attr.read_format, &sample_sw,
+ false);
+ build_id__mark_dso_hit(tool, event_sw, &sample_sw, evsel, machine);
+ return perf_event__repipe(tool, event_sw, &sample_sw, machine);
+}
+
+static void sig_handler(int sig __maybe_unused)
+{
+ session_done = 1;
+}
+
+static int perf_evsel__check_stype(struct perf_evsel *evsel,
+ u64 sample_type, const char *sample_msg)
+{
+ struct perf_event_attr *attr = &evsel->attr;
+ const char *name = perf_evsel__name(evsel);
+
+ if (!(attr->sample_type & sample_type)) {
+ pr_err("Samples for %s event do not have %s attribute set.",
+ name, sample_msg);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int __cmd_inject(struct perf_inject *inject)
+{
+ struct perf_session *session;
+ int ret = -EINVAL;
+ struct perf_data_file file = {
+ .path = inject->input_name,
+ .mode = PERF_DATA_MODE_READ,
+ };
+ struct perf_data_file *file_out = &inject->output;
+
+ signal(SIGINT, sig_handler);
+
+ if (inject->build_ids || inject->sched_stat) {
+ inject->tool.mmap = perf_event__repipe_mmap;
+ inject->tool.mmap2 = perf_event__repipe_mmap2;
+ inject->tool.fork = perf_event__repipe_fork;
+ inject->tool.tracing_data = perf_event__repipe_tracing_data;
+ }
+
+ session = perf_session__new(&file, true, &inject->tool);
+ if (session == NULL)
+ return -ENOMEM;
+
+ if (inject->build_ids) {
+ inject->tool.sample = perf_event__inject_buildid;
+ } else if (inject->sched_stat) {
+ struct perf_evsel *evsel;
+
+ inject->tool.ordered_samples = true;
+
+ evlist__for_each(session->evlist, evsel) {
+ const char *name = perf_evsel__name(evsel);
+
+ if (!strcmp(name, "sched:sched_switch")) {
+ if (perf_evsel__check_stype(evsel, PERF_SAMPLE_TID, "TID"))
+ return -EINVAL;
+
+ evsel->handler = perf_inject__sched_switch;
+ } else if (!strcmp(name, "sched:sched_process_exit"))
+ evsel->handler = perf_inject__sched_process_exit;
+ else if (!strncmp(name, "sched:sched_stat_", 17))
+ evsel->handler = perf_inject__sched_stat;
+ }
+ }
+
+ if (!file_out->is_pipe)
+ lseek(file_out->fd, session->header.data_offset, SEEK_SET);
+
+ ret = perf_session__process_events(session, &inject->tool);
+
+ if (!file_out->is_pipe) {
+ session->header.data_size = inject->bytes_written;
+ perf_session__write_header(session, session->evlist, file_out->fd, true);
+ }
+
+ perf_session__delete(session);
+
+ return ret;
+}
+
+int cmd_inject(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ struct perf_inject inject = {
+ .tool = {
+ .sample = perf_event__repipe_sample,
+ .mmap = perf_event__repipe,
+ .mmap2 = perf_event__repipe,
+ .comm = perf_event__repipe,
+ .fork = perf_event__repipe,
+ .exit = perf_event__repipe,
+ .lost = perf_event__repipe,
+ .read = perf_event__repipe_sample,
+ .throttle = perf_event__repipe,
+ .unthrottle = perf_event__repipe,
+ .attr = perf_event__repipe_attr,
+ .tracing_data = perf_event__repipe_op2_synth,
+ .finished_round = perf_event__repipe_op2_synth,
+ .build_id = perf_event__repipe_op2_synth,
+ },
+ .input_name = "-",
+ .samples = LIST_HEAD_INIT(inject.samples),
+ .output = {
+ .path = "-",
+ .mode = PERF_DATA_MODE_WRITE,
+ },
+ };
+ const struct option options[] = {
+ OPT_BOOLEAN('b', "build-ids", &inject.build_ids,
+ "Inject build-ids into the output stream"),
+ OPT_STRING('i', "input", &inject.input_name, "file",
+ "input file name"),
+ OPT_STRING('o', "output", &inject.output.path, "file",
+ "output file name"),
+ OPT_BOOLEAN('s', "sched-stat", &inject.sched_stat,
+ "Merge sched-stat and sched-switch for getting events "
+ "where and how long tasks slept"),
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show build ids, etc)"),
+ OPT_END()
+ };
+ const char * const inject_usage[] = {
+ "perf inject [<options>]",
+ NULL
+ };
+
+ argc = parse_options(argc, argv, options, inject_usage, 0);
+
+ /*
+ * Any (unrecognized) arguments left?
+ */
+ if (argc)
+ usage_with_options(inject_usage, options);
+
+ if (perf_data_file__open(&inject.output)) {
+ perror("failed to create output file");
+ return -1;
+ }
+
+ if (symbol__init() < 0)
+ return -1;
+
+ return __cmd_inject(&inject);
+}
diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c
new file mode 100644
index 00000000000..bef3376bfaf
--- /dev/null
+++ b/tools/perf/builtin-kmem.c
@@ -0,0 +1,717 @@
+#include "builtin.h"
+#include "perf.h"
+
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/util.h"
+#include "util/cache.h"
+#include "util/symbol.h"
+#include "util/thread.h"
+#include "util/header.h"
+#include "util/session.h"
+#include "util/tool.h"
+
+#include "util/parse-options.h"
+#include "util/trace-event.h"
+#include "util/data.h"
+#include "util/cpumap.h"
+
+#include "util/debug.h"
+
+#include <linux/rbtree.h>
+#include <linux/string.h>
+
+struct alloc_stat;
+typedef int (*sort_fn_t)(struct alloc_stat *, struct alloc_stat *);
+
+static int alloc_flag;
+static int caller_flag;
+
+static int alloc_lines = -1;
+static int caller_lines = -1;
+
+static bool raw_ip;
+
+struct alloc_stat {
+ u64 call_site;
+ u64 ptr;
+ u64 bytes_req;
+ u64 bytes_alloc;
+ u32 hit;
+ u32 pingpong;
+
+ short alloc_cpu;
+
+ struct rb_node node;
+};
+
+static struct rb_root root_alloc_stat;
+static struct rb_root root_alloc_sorted;
+static struct rb_root root_caller_stat;
+static struct rb_root root_caller_sorted;
+
+static unsigned long total_requested, total_allocated;
+static unsigned long nr_allocs, nr_cross_allocs;
+
+static int insert_alloc_stat(unsigned long call_site, unsigned long ptr,
+ int bytes_req, int bytes_alloc, int cpu)
+{
+ struct rb_node **node = &root_alloc_stat.rb_node;
+ struct rb_node *parent = NULL;
+ struct alloc_stat *data = NULL;
+
+ while (*node) {
+ parent = *node;
+ data = rb_entry(*node, struct alloc_stat, node);
+
+ if (ptr > data->ptr)
+ node = &(*node)->rb_right;
+ else if (ptr < data->ptr)
+ node = &(*node)->rb_left;
+ else
+ break;
+ }
+
+ if (data && data->ptr == ptr) {
+ data->hit++;
+ data->bytes_req += bytes_req;
+ data->bytes_alloc += bytes_alloc;
+ } else {
+ data = malloc(sizeof(*data));
+ if (!data) {
+ pr_err("%s: malloc failed\n", __func__);
+ return -1;
+ }
+ data->ptr = ptr;
+ data->pingpong = 0;
+ data->hit = 1;
+ data->bytes_req = bytes_req;
+ data->bytes_alloc = bytes_alloc;
+
+ rb_link_node(&data->node, parent, node);
+ rb_insert_color(&data->node, &root_alloc_stat);
+ }
+ data->call_site = call_site;
+ data->alloc_cpu = cpu;
+ return 0;
+}
+
+static int insert_caller_stat(unsigned long call_site,
+ int bytes_req, int bytes_alloc)
+{
+ struct rb_node **node = &root_caller_stat.rb_node;
+ struct rb_node *parent = NULL;
+ struct alloc_stat *data = NULL;
+
+ while (*node) {
+ parent = *node;
+ data = rb_entry(*node, struct alloc_stat, node);
+
+ if (call_site > data->call_site)
+ node = &(*node)->rb_right;
+ else if (call_site < data->call_site)
+ node = &(*node)->rb_left;
+ else
+ break;
+ }
+
+ if (data && data->call_site == call_site) {
+ data->hit++;
+ data->bytes_req += bytes_req;
+ data->bytes_alloc += bytes_alloc;
+ } else {
+ data = malloc(sizeof(*data));
+ if (!data) {
+ pr_err("%s: malloc failed\n", __func__);
+ return -1;
+ }
+ data->call_site = call_site;
+ data->pingpong = 0;
+ data->hit = 1;
+ data->bytes_req = bytes_req;
+ data->bytes_alloc = bytes_alloc;
+
+ rb_link_node(&data->node, parent, node);
+ rb_insert_color(&data->node, &root_caller_stat);
+ }
+
+ return 0;
+}
+
+static int perf_evsel__process_alloc_event(struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ unsigned long ptr = perf_evsel__intval(evsel, sample, "ptr"),
+ call_site = perf_evsel__intval(evsel, sample, "call_site");
+ int bytes_req = perf_evsel__intval(evsel, sample, "bytes_req"),
+ bytes_alloc = perf_evsel__intval(evsel, sample, "bytes_alloc");
+
+ if (insert_alloc_stat(call_site, ptr, bytes_req, bytes_alloc, sample->cpu) ||
+ insert_caller_stat(call_site, bytes_req, bytes_alloc))
+ return -1;
+
+ total_requested += bytes_req;
+ total_allocated += bytes_alloc;
+
+ nr_allocs++;
+ return 0;
+}
+
+static int perf_evsel__process_alloc_node_event(struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ int ret = perf_evsel__process_alloc_event(evsel, sample);
+
+ if (!ret) {
+ int node1 = cpu__get_node(sample->cpu),
+ node2 = perf_evsel__intval(evsel, sample, "node");
+
+ if (node1 != node2)
+ nr_cross_allocs++;
+ }
+
+ return ret;
+}
+
+static int ptr_cmp(struct alloc_stat *, struct alloc_stat *);
+static int callsite_cmp(struct alloc_stat *, struct alloc_stat *);
+
+static struct alloc_stat *search_alloc_stat(unsigned long ptr,
+ unsigned long call_site,
+ struct rb_root *root,
+ sort_fn_t sort_fn)
+{
+ struct rb_node *node = root->rb_node;
+ struct alloc_stat key = { .ptr = ptr, .call_site = call_site };
+
+ while (node) {
+ struct alloc_stat *data;
+ int cmp;
+
+ data = rb_entry(node, struct alloc_stat, node);
+
+ cmp = sort_fn(&key, data);
+ if (cmp < 0)
+ node = node->rb_left;
+ else if (cmp > 0)
+ node = node->rb_right;
+ else
+ return data;
+ }
+ return NULL;
+}
+
+static int perf_evsel__process_free_event(struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ unsigned long ptr = perf_evsel__intval(evsel, sample, "ptr");
+ struct alloc_stat *s_alloc, *s_caller;
+
+ s_alloc = search_alloc_stat(ptr, 0, &root_alloc_stat, ptr_cmp);
+ if (!s_alloc)
+ return 0;
+
+ if ((short)sample->cpu != s_alloc->alloc_cpu) {
+ s_alloc->pingpong++;
+
+ s_caller = search_alloc_stat(0, s_alloc->call_site,
+ &root_caller_stat, callsite_cmp);
+ if (!s_caller)
+ return -1;
+ s_caller->pingpong++;
+ }
+ s_alloc->alloc_cpu = -1;
+
+ return 0;
+}
+
+typedef int (*tracepoint_handler)(struct perf_evsel *evsel,
+ struct perf_sample *sample);
+
+static int process_sample_event(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ struct thread *thread = machine__findnew_thread(machine, sample->pid,
+ sample->tid);
+
+ if (thread == NULL) {
+ pr_debug("problem processing %d event, skipping it.\n",
+ event->header.type);
+ return -1;
+ }
+
+ dump_printf(" ... thread: %s:%d\n", thread__comm_str(thread), thread->tid);
+
+ if (evsel->handler != NULL) {
+ tracepoint_handler f = evsel->handler;
+ return f(evsel, sample);
+ }
+
+ return 0;
+}
+
+static struct perf_tool perf_kmem = {
+ .sample = process_sample_event,
+ .comm = perf_event__process_comm,
+ .ordered_samples = true,
+};
+
+static double fragmentation(unsigned long n_req, unsigned long n_alloc)
+{
+ if (n_alloc == 0)
+ return 0.0;
+ else
+ return 100.0 - (100.0 * n_req / n_alloc);
+}
+
+static void __print_result(struct rb_root *root, struct perf_session *session,
+ int n_lines, int is_caller)
+{
+ struct rb_node *next;
+ struct machine *machine = &session->machines.host;
+
+ printf("%.102s\n", graph_dotted_line);
+ printf(" %-34s |", is_caller ? "Callsite": "Alloc Ptr");
+ printf(" Total_alloc/Per | Total_req/Per | Hit | Ping-pong | Frag\n");
+ printf("%.102s\n", graph_dotted_line);
+
+ next = rb_first(root);
+
+ while (next && n_lines--) {
+ struct alloc_stat *data = rb_entry(next, struct alloc_stat,
+ node);
+ struct symbol *sym = NULL;
+ struct map *map;
+ char buf[BUFSIZ];
+ u64 addr;
+
+ if (is_caller) {
+ addr = data->call_site;
+ if (!raw_ip)
+ sym = machine__find_kernel_function(machine, addr, &map, NULL);
+ } else
+ addr = data->ptr;
+
+ if (sym != NULL)
+ snprintf(buf, sizeof(buf), "%s+%" PRIx64 "", sym->name,
+ addr - map->unmap_ip(map, sym->start));
+ else
+ snprintf(buf, sizeof(buf), "%#" PRIx64 "", addr);
+ printf(" %-34s |", buf);
+
+ printf(" %9llu/%-5lu | %9llu/%-5lu | %8lu | %8lu | %6.3f%%\n",
+ (unsigned long long)data->bytes_alloc,
+ (unsigned long)data->bytes_alloc / data->hit,
+ (unsigned long long)data->bytes_req,
+ (unsigned long)data->bytes_req / data->hit,
+ (unsigned long)data->hit,
+ (unsigned long)data->pingpong,
+ fragmentation(data->bytes_req, data->bytes_alloc));
+
+ next = rb_next(next);
+ }
+
+ if (n_lines == -1)
+ printf(" ... | ... | ... | ... | ... | ... \n");
+
+ printf("%.102s\n", graph_dotted_line);
+}
+
+static void print_summary(void)
+{
+ printf("\nSUMMARY\n=======\n");
+ printf("Total bytes requested: %lu\n", total_requested);
+ printf("Total bytes allocated: %lu\n", total_allocated);
+ printf("Total bytes wasted on internal fragmentation: %lu\n",
+ total_allocated - total_requested);
+ printf("Internal fragmentation: %f%%\n",
+ fragmentation(total_requested, total_allocated));
+ printf("Cross CPU allocations: %lu/%lu\n", nr_cross_allocs, nr_allocs);
+}
+
+static void print_result(struct perf_session *session)
+{
+ if (caller_flag)
+ __print_result(&root_caller_sorted, session, caller_lines, 1);
+ if (alloc_flag)
+ __print_result(&root_alloc_sorted, session, alloc_lines, 0);
+ print_summary();
+}
+
+struct sort_dimension {
+ const char name[20];
+ sort_fn_t cmp;
+ struct list_head list;
+};
+
+static LIST_HEAD(caller_sort);
+static LIST_HEAD(alloc_sort);
+
+static void sort_insert(struct rb_root *root, struct alloc_stat *data,
+ struct list_head *sort_list)
+{
+ struct rb_node **new = &(root->rb_node);
+ struct rb_node *parent = NULL;
+ struct sort_dimension *sort;
+
+ while (*new) {
+ struct alloc_stat *this;
+ int cmp = 0;
+
+ this = rb_entry(*new, struct alloc_stat, node);
+ parent = *new;
+
+ list_for_each_entry(sort, sort_list, list) {
+ cmp = sort->cmp(data, this);
+ if (cmp)
+ break;
+ }
+
+ if (cmp > 0)
+ new = &((*new)->rb_left);
+ else
+ new = &((*new)->rb_right);
+ }
+
+ rb_link_node(&data->node, parent, new);
+ rb_insert_color(&data->node, root);
+}
+
+static void __sort_result(struct rb_root *root, struct rb_root *root_sorted,
+ struct list_head *sort_list)
+{
+ struct rb_node *node;
+ struct alloc_stat *data;
+
+ for (;;) {
+ node = rb_first(root);
+ if (!node)
+ break;
+
+ rb_erase(node, root);
+ data = rb_entry(node, struct alloc_stat, node);
+ sort_insert(root_sorted, data, sort_list);
+ }
+}
+
+static void sort_result(void)
+{
+ __sort_result(&root_alloc_stat, &root_alloc_sorted, &alloc_sort);
+ __sort_result(&root_caller_stat, &root_caller_sorted, &caller_sort);
+}
+
+static int __cmd_kmem(void)
+{
+ int err = -EINVAL;
+ struct perf_session *session;
+ const struct perf_evsel_str_handler kmem_tracepoints[] = {
+ { "kmem:kmalloc", perf_evsel__process_alloc_event, },
+ { "kmem:kmem_cache_alloc", perf_evsel__process_alloc_event, },
+ { "kmem:kmalloc_node", perf_evsel__process_alloc_node_event, },
+ { "kmem:kmem_cache_alloc_node", perf_evsel__process_alloc_node_event, },
+ { "kmem:kfree", perf_evsel__process_free_event, },
+ { "kmem:kmem_cache_free", perf_evsel__process_free_event, },
+ };
+ struct perf_data_file file = {
+ .path = input_name,
+ .mode = PERF_DATA_MODE_READ,
+ };
+
+ session = perf_session__new(&file, false, &perf_kmem);
+ if (session == NULL)
+ return -ENOMEM;
+
+ if (perf_session__create_kernel_maps(session) < 0)
+ goto out_delete;
+
+ if (!perf_session__has_traces(session, "kmem record"))
+ goto out_delete;
+
+ if (perf_session__set_tracepoints_handlers(session, kmem_tracepoints)) {
+ pr_err("Initializing perf session tracepoint handlers failed\n");
+ return -1;
+ }
+
+ setup_pager();
+ err = perf_session__process_events(session, &perf_kmem);
+ if (err != 0)
+ goto out_delete;
+ sort_result();
+ print_result(session);
+out_delete:
+ perf_session__delete(session);
+ return err;
+}
+
+static int ptr_cmp(struct alloc_stat *l, struct alloc_stat *r)
+{
+ if (l->ptr < r->ptr)
+ return -1;
+ else if (l->ptr > r->ptr)
+ return 1;
+ return 0;
+}
+
+static struct sort_dimension ptr_sort_dimension = {
+ .name = "ptr",
+ .cmp = ptr_cmp,
+};
+
+static int callsite_cmp(struct alloc_stat *l, struct alloc_stat *r)
+{
+ if (l->call_site < r->call_site)
+ return -1;
+ else if (l->call_site > r->call_site)
+ return 1;
+ return 0;
+}
+
+static struct sort_dimension callsite_sort_dimension = {
+ .name = "callsite",
+ .cmp = callsite_cmp,
+};
+
+static int hit_cmp(struct alloc_stat *l, struct alloc_stat *r)
+{
+ if (l->hit < r->hit)
+ return -1;
+ else if (l->hit > r->hit)
+ return 1;
+ return 0;
+}
+
+static struct sort_dimension hit_sort_dimension = {
+ .name = "hit",
+ .cmp = hit_cmp,
+};
+
+static int bytes_cmp(struct alloc_stat *l, struct alloc_stat *r)
+{
+ if (l->bytes_alloc < r->bytes_alloc)
+ return -1;
+ else if (l->bytes_alloc > r->bytes_alloc)
+ return 1;
+ return 0;
+}
+
+static struct sort_dimension bytes_sort_dimension = {
+ .name = "bytes",
+ .cmp = bytes_cmp,
+};
+
+static int frag_cmp(struct alloc_stat *l, struct alloc_stat *r)
+{
+ double x, y;
+
+ x = fragmentation(l->bytes_req, l->bytes_alloc);
+ y = fragmentation(r->bytes_req, r->bytes_alloc);
+
+ if (x < y)
+ return -1;
+ else if (x > y)
+ return 1;
+ return 0;
+}
+
+static struct sort_dimension frag_sort_dimension = {
+ .name = "frag",
+ .cmp = frag_cmp,
+};
+
+static int pingpong_cmp(struct alloc_stat *l, struct alloc_stat *r)
+{
+ if (l->pingpong < r->pingpong)
+ return -1;
+ else if (l->pingpong > r->pingpong)
+ return 1;
+ return 0;
+}
+
+static struct sort_dimension pingpong_sort_dimension = {
+ .name = "pingpong",
+ .cmp = pingpong_cmp,
+};
+
+static struct sort_dimension *avail_sorts[] = {
+ &ptr_sort_dimension,
+ &callsite_sort_dimension,
+ &hit_sort_dimension,
+ &bytes_sort_dimension,
+ &frag_sort_dimension,
+ &pingpong_sort_dimension,
+};
+
+#define NUM_AVAIL_SORTS ((int)ARRAY_SIZE(avail_sorts))
+
+static int sort_dimension__add(const char *tok, struct list_head *list)
+{
+ struct sort_dimension *sort;
+ int i;
+
+ for (i = 0; i < NUM_AVAIL_SORTS; i++) {
+ if (!strcmp(avail_sorts[i]->name, tok)) {
+ sort = memdup(avail_sorts[i], sizeof(*avail_sorts[i]));
+ if (!sort) {
+ pr_err("%s: memdup failed\n", __func__);
+ return -1;
+ }
+ list_add_tail(&sort->list, list);
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static int setup_sorting(struct list_head *sort_list, const char *arg)
+{
+ char *tok;
+ char *str = strdup(arg);
+
+ if (!str) {
+ pr_err("%s: strdup failed\n", __func__);
+ return -1;
+ }
+
+ while (true) {
+ tok = strsep(&str, ",");
+ if (!tok)
+ break;
+ if (sort_dimension__add(tok, sort_list) < 0) {
+ error("Unknown --sort key: '%s'", tok);
+ free(str);
+ return -1;
+ }
+ }
+
+ free(str);
+ return 0;
+}
+
+static int parse_sort_opt(const struct option *opt __maybe_unused,
+ const char *arg, int unset __maybe_unused)
+{
+ if (!arg)
+ return -1;
+
+ if (caller_flag > alloc_flag)
+ return setup_sorting(&caller_sort, arg);
+ else
+ return setup_sorting(&alloc_sort, arg);
+
+ return 0;
+}
+
+static int parse_caller_opt(const struct option *opt __maybe_unused,
+ const char *arg __maybe_unused,
+ int unset __maybe_unused)
+{
+ caller_flag = (alloc_flag + 1);
+ return 0;
+}
+
+static int parse_alloc_opt(const struct option *opt __maybe_unused,
+ const char *arg __maybe_unused,
+ int unset __maybe_unused)
+{
+ alloc_flag = (caller_flag + 1);
+ return 0;
+}
+
+static int parse_line_opt(const struct option *opt __maybe_unused,
+ const char *arg, int unset __maybe_unused)
+{
+ int lines;
+
+ if (!arg)
+ return -1;
+
+ lines = strtoul(arg, NULL, 10);
+
+ if (caller_flag > alloc_flag)
+ caller_lines = lines;
+ else
+ alloc_lines = lines;
+
+ return 0;
+}
+
+static int __cmd_record(int argc, const char **argv)
+{
+ const char * const record_args[] = {
+ "record", "-a", "-R", "-c", "1",
+ "-e", "kmem:kmalloc",
+ "-e", "kmem:kmalloc_node",
+ "-e", "kmem:kfree",
+ "-e", "kmem:kmem_cache_alloc",
+ "-e", "kmem:kmem_cache_alloc_node",
+ "-e", "kmem:kmem_cache_free",
+ };
+ unsigned int rec_argc, i, j;
+ const char **rec_argv;
+
+ rec_argc = ARRAY_SIZE(record_args) + argc - 1;
+ rec_argv = calloc(rec_argc + 1, sizeof(char *));
+
+ if (rec_argv == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(record_args); i++)
+ rec_argv[i] = strdup(record_args[i]);
+
+ for (j = 1; j < (unsigned int)argc; j++, i++)
+ rec_argv[i] = argv[j];
+
+ return cmd_record(i, rec_argv, NULL);
+}
+
+int cmd_kmem(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ const char * const default_sort_order = "frag,hit,bytes";
+ const struct option kmem_options[] = {
+ OPT_STRING('i', "input", &input_name, "file", "input file name"),
+ OPT_CALLBACK_NOOPT(0, "caller", NULL, NULL,
+ "show per-callsite statistics", parse_caller_opt),
+ OPT_CALLBACK_NOOPT(0, "alloc", NULL, NULL,
+ "show per-allocation statistics", parse_alloc_opt),
+ OPT_CALLBACK('s', "sort", NULL, "key[,key2...]",
+ "sort by keys: ptr, call_site, bytes, hit, pingpong, frag",
+ parse_sort_opt),
+ OPT_CALLBACK('l', "line", NULL, "num", "show n lines", parse_line_opt),
+ OPT_BOOLEAN(0, "raw-ip", &raw_ip, "show raw ip instead of symbol"),
+ OPT_END()
+ };
+ const char *const kmem_subcommands[] = { "record", "stat", NULL };
+ const char *kmem_usage[] = {
+ NULL,
+ NULL
+ };
+ argc = parse_options_subcommand(argc, argv, kmem_options,
+ kmem_subcommands, kmem_usage, 0);
+
+ if (!argc)
+ usage_with_options(kmem_usage, kmem_options);
+
+ symbol__init();
+
+ if (!strncmp(argv[0], "rec", 3)) {
+ return __cmd_record(argc, argv);
+ } else if (!strcmp(argv[0], "stat")) {
+ if (cpu__setup_cpunode_map())
+ return -1;
+
+ if (list_empty(&caller_sort))
+ setup_sorting(&caller_sort, default_sort_order);
+ if (list_empty(&alloc_sort))
+ setup_sorting(&alloc_sort, default_sort_order);
+
+ return __cmd_kmem();
+ } else
+ usage_with_options(kmem_usage, kmem_options);
+
+ return 0;
+}
+
diff --git a/tools/perf/builtin-kvm.c b/tools/perf/builtin-kvm.c
new file mode 100644
index 00000000000..0f1e5a2f6ad
--- /dev/null
+++ b/tools/perf/builtin-kvm.c
@@ -0,0 +1,1737 @@
+#include "builtin.h"
+#include "perf.h"
+
+#include "util/evsel.h"
+#include "util/evlist.h"
+#include "util/util.h"
+#include "util/cache.h"
+#include "util/symbol.h"
+#include "util/thread.h"
+#include "util/header.h"
+#include "util/session.h"
+#include "util/intlist.h"
+#include "util/parse-options.h"
+#include "util/trace-event.h"
+#include "util/debug.h"
+#include <api/fs/debugfs.h>
+#include "util/tool.h"
+#include "util/stat.h"
+#include "util/top.h"
+#include "util/data.h"
+
+#include <sys/prctl.h>
+#ifdef HAVE_TIMERFD_SUPPORT
+#include <sys/timerfd.h>
+#endif
+
+#include <termios.h>
+#include <semaphore.h>
+#include <pthread.h>
+#include <math.h>
+
+#if defined(__i386__) || defined(__x86_64__)
+#include <asm/svm.h>
+#include <asm/vmx.h>
+#include <asm/kvm.h>
+
+struct event_key {
+ #define INVALID_KEY (~0ULL)
+ u64 key;
+ int info;
+};
+
+struct kvm_event_stats {
+ u64 time;
+ struct stats stats;
+};
+
+struct kvm_event {
+ struct list_head hash_entry;
+ struct rb_node rb;
+
+ struct event_key key;
+
+ struct kvm_event_stats total;
+
+ #define DEFAULT_VCPU_NUM 8
+ int max_vcpu;
+ struct kvm_event_stats *vcpu;
+};
+
+typedef int (*key_cmp_fun)(struct kvm_event*, struct kvm_event*, int);
+
+struct kvm_event_key {
+ const char *name;
+ key_cmp_fun key;
+};
+
+
+struct perf_kvm_stat;
+
+struct kvm_events_ops {
+ bool (*is_begin_event)(struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct event_key *key);
+ bool (*is_end_event)(struct perf_evsel *evsel,
+ struct perf_sample *sample, struct event_key *key);
+ void (*decode_key)(struct perf_kvm_stat *kvm, struct event_key *key,
+ char decode[20]);
+ const char *name;
+};
+
+struct exit_reasons_table {
+ unsigned long exit_code;
+ const char *reason;
+};
+
+#define EVENTS_BITS 12
+#define EVENTS_CACHE_SIZE (1UL << EVENTS_BITS)
+
+struct perf_kvm_stat {
+ struct perf_tool tool;
+ struct record_opts opts;
+ struct perf_evlist *evlist;
+ struct perf_session *session;
+
+ const char *file_name;
+ const char *report_event;
+ const char *sort_key;
+ int trace_vcpu;
+
+ struct exit_reasons_table *exit_reasons;
+ int exit_reasons_size;
+ const char *exit_reasons_isa;
+
+ struct kvm_events_ops *events_ops;
+ key_cmp_fun compare;
+ struct list_head kvm_events_cache[EVENTS_CACHE_SIZE];
+
+ u64 total_time;
+ u64 total_count;
+ u64 lost_events;
+ u64 duration;
+
+ const char *pid_str;
+ struct intlist *pid_list;
+
+ struct rb_root result;
+
+ int timerfd;
+ unsigned int display_time;
+ bool live;
+};
+
+
+static void exit_event_get_key(struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct event_key *key)
+{
+ key->info = 0;
+ key->key = perf_evsel__intval(evsel, sample, "exit_reason");
+}
+
+static bool kvm_exit_event(struct perf_evsel *evsel)
+{
+ return !strcmp(evsel->name, "kvm:kvm_exit");
+}
+
+static bool exit_event_begin(struct perf_evsel *evsel,
+ struct perf_sample *sample, struct event_key *key)
+{
+ if (kvm_exit_event(evsel)) {
+ exit_event_get_key(evsel, sample, key);
+ return true;
+ }
+
+ return false;
+}
+
+static bool kvm_entry_event(struct perf_evsel *evsel)
+{
+ return !strcmp(evsel->name, "kvm:kvm_entry");
+}
+
+static bool exit_event_end(struct perf_evsel *evsel,
+ struct perf_sample *sample __maybe_unused,
+ struct event_key *key __maybe_unused)
+{
+ return kvm_entry_event(evsel);
+}
+
+static struct exit_reasons_table vmx_exit_reasons[] = {
+ VMX_EXIT_REASONS
+};
+
+static struct exit_reasons_table svm_exit_reasons[] = {
+ SVM_EXIT_REASONS
+};
+
+static const char *get_exit_reason(struct perf_kvm_stat *kvm, u64 exit_code)
+{
+ int i = kvm->exit_reasons_size;
+ struct exit_reasons_table *tbl = kvm->exit_reasons;
+
+ while (i--) {
+ if (tbl->exit_code == exit_code)
+ return tbl->reason;
+ tbl++;
+ }
+
+ pr_err("unknown kvm exit code:%lld on %s\n",
+ (unsigned long long)exit_code, kvm->exit_reasons_isa);
+ return "UNKNOWN";
+}
+
+static void exit_event_decode_key(struct perf_kvm_stat *kvm,
+ struct event_key *key,
+ char decode[20])
+{
+ const char *exit_reason = get_exit_reason(kvm, key->key);
+
+ scnprintf(decode, 20, "%s", exit_reason);
+}
+
+static struct kvm_events_ops exit_events = {
+ .is_begin_event = exit_event_begin,
+ .is_end_event = exit_event_end,
+ .decode_key = exit_event_decode_key,
+ .name = "VM-EXIT"
+};
+
+/*
+ * For the mmio events, we treat:
+ * the time of MMIO write: kvm_mmio(KVM_TRACE_MMIO_WRITE...) -> kvm_entry
+ * the time of MMIO read: kvm_exit -> kvm_mmio(KVM_TRACE_MMIO_READ...).
+ */
+static void mmio_event_get_key(struct perf_evsel *evsel, struct perf_sample *sample,
+ struct event_key *key)
+{
+ key->key = perf_evsel__intval(evsel, sample, "gpa");
+ key->info = perf_evsel__intval(evsel, sample, "type");
+}
+
+#define KVM_TRACE_MMIO_READ_UNSATISFIED 0
+#define KVM_TRACE_MMIO_READ 1
+#define KVM_TRACE_MMIO_WRITE 2
+
+static bool mmio_event_begin(struct perf_evsel *evsel,
+ struct perf_sample *sample, struct event_key *key)
+{
+ /* MMIO read begin event in kernel. */
+ if (kvm_exit_event(evsel))
+ return true;
+
+ /* MMIO write begin event in kernel. */
+ if (!strcmp(evsel->name, "kvm:kvm_mmio") &&
+ perf_evsel__intval(evsel, sample, "type") == KVM_TRACE_MMIO_WRITE) {
+ mmio_event_get_key(evsel, sample, key);
+ return true;
+ }
+
+ return false;
+}
+
+static bool mmio_event_end(struct perf_evsel *evsel, struct perf_sample *sample,
+ struct event_key *key)
+{
+ /* MMIO write end event in kernel. */
+ if (kvm_entry_event(evsel))
+ return true;
+
+ /* MMIO read end event in kernel.*/
+ if (!strcmp(evsel->name, "kvm:kvm_mmio") &&
+ perf_evsel__intval(evsel, sample, "type") == KVM_TRACE_MMIO_READ) {
+ mmio_event_get_key(evsel, sample, key);
+ return true;
+ }
+
+ return false;
+}
+
+static void mmio_event_decode_key(struct perf_kvm_stat *kvm __maybe_unused,
+ struct event_key *key,
+ char decode[20])
+{
+ scnprintf(decode, 20, "%#lx:%s", (unsigned long)key->key,
+ key->info == KVM_TRACE_MMIO_WRITE ? "W" : "R");
+}
+
+static struct kvm_events_ops mmio_events = {
+ .is_begin_event = mmio_event_begin,
+ .is_end_event = mmio_event_end,
+ .decode_key = mmio_event_decode_key,
+ .name = "MMIO Access"
+};
+
+ /* The time of emulation pio access is from kvm_pio to kvm_entry. */
+static void ioport_event_get_key(struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct event_key *key)
+{
+ key->key = perf_evsel__intval(evsel, sample, "port");
+ key->info = perf_evsel__intval(evsel, sample, "rw");
+}
+
+static bool ioport_event_begin(struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct event_key *key)
+{
+ if (!strcmp(evsel->name, "kvm:kvm_pio")) {
+ ioport_event_get_key(evsel, sample, key);
+ return true;
+ }
+
+ return false;
+}
+
+static bool ioport_event_end(struct perf_evsel *evsel,
+ struct perf_sample *sample __maybe_unused,
+ struct event_key *key __maybe_unused)
+{
+ return kvm_entry_event(evsel);
+}
+
+static void ioport_event_decode_key(struct perf_kvm_stat *kvm __maybe_unused,
+ struct event_key *key,
+ char decode[20])
+{
+ scnprintf(decode, 20, "%#llx:%s", (unsigned long long)key->key,
+ key->info ? "POUT" : "PIN");
+}
+
+static struct kvm_events_ops ioport_events = {
+ .is_begin_event = ioport_event_begin,
+ .is_end_event = ioport_event_end,
+ .decode_key = ioport_event_decode_key,
+ .name = "IO Port Access"
+};
+
+static bool register_kvm_events_ops(struct perf_kvm_stat *kvm)
+{
+ bool ret = true;
+
+ if (!strcmp(kvm->report_event, "vmexit"))
+ kvm->events_ops = &exit_events;
+ else if (!strcmp(kvm->report_event, "mmio"))
+ kvm->events_ops = &mmio_events;
+ else if (!strcmp(kvm->report_event, "ioport"))
+ kvm->events_ops = &ioport_events;
+ else {
+ pr_err("Unknown report event:%s\n", kvm->report_event);
+ ret = false;
+ }
+
+ return ret;
+}
+
+struct vcpu_event_record {
+ int vcpu_id;
+ u64 start_time;
+ struct kvm_event *last_event;
+};
+
+
+static void init_kvm_event_record(struct perf_kvm_stat *kvm)
+{
+ unsigned int i;
+
+ for (i = 0; i < EVENTS_CACHE_SIZE; i++)
+ INIT_LIST_HEAD(&kvm->kvm_events_cache[i]);
+}
+
+#ifdef HAVE_TIMERFD_SUPPORT
+static void clear_events_cache_stats(struct list_head *kvm_events_cache)
+{
+ struct list_head *head;
+ struct kvm_event *event;
+ unsigned int i;
+ int j;
+
+ for (i = 0; i < EVENTS_CACHE_SIZE; i++) {
+ head = &kvm_events_cache[i];
+ list_for_each_entry(event, head, hash_entry) {
+ /* reset stats for event */
+ event->total.time = 0;
+ init_stats(&event->total.stats);
+
+ for (j = 0; j < event->max_vcpu; ++j) {
+ event->vcpu[j].time = 0;
+ init_stats(&event->vcpu[j].stats);
+ }
+ }
+ }
+}
+#endif
+
+static int kvm_events_hash_fn(u64 key)
+{
+ return key & (EVENTS_CACHE_SIZE - 1);
+}
+
+static bool kvm_event_expand(struct kvm_event *event, int vcpu_id)
+{
+ int old_max_vcpu = event->max_vcpu;
+ void *prev;
+
+ if (vcpu_id < event->max_vcpu)
+ return true;
+
+ while (event->max_vcpu <= vcpu_id)
+ event->max_vcpu += DEFAULT_VCPU_NUM;
+
+ prev = event->vcpu;
+ event->vcpu = realloc(event->vcpu,
+ event->max_vcpu * sizeof(*event->vcpu));
+ if (!event->vcpu) {
+ free(prev);
+ pr_err("Not enough memory\n");
+ return false;
+ }
+
+ memset(event->vcpu + old_max_vcpu, 0,
+ (event->max_vcpu - old_max_vcpu) * sizeof(*event->vcpu));
+ return true;
+}
+
+static struct kvm_event *kvm_alloc_init_event(struct event_key *key)
+{
+ struct kvm_event *event;
+
+ event = zalloc(sizeof(*event));
+ if (!event) {
+ pr_err("Not enough memory\n");
+ return NULL;
+ }
+
+ event->key = *key;
+ init_stats(&event->total.stats);
+ return event;
+}
+
+static struct kvm_event *find_create_kvm_event(struct perf_kvm_stat *kvm,
+ struct event_key *key)
+{
+ struct kvm_event *event;
+ struct list_head *head;
+
+ BUG_ON(key->key == INVALID_KEY);
+
+ head = &kvm->kvm_events_cache[kvm_events_hash_fn(key->key)];
+ list_for_each_entry(event, head, hash_entry) {
+ if (event->key.key == key->key && event->key.info == key->info)
+ return event;
+ }
+
+ event = kvm_alloc_init_event(key);
+ if (!event)
+ return NULL;
+
+ list_add(&event->hash_entry, head);
+ return event;
+}
+
+static bool handle_begin_event(struct perf_kvm_stat *kvm,
+ struct vcpu_event_record *vcpu_record,
+ struct event_key *key, u64 timestamp)
+{
+ struct kvm_event *event = NULL;
+
+ if (key->key != INVALID_KEY)
+ event = find_create_kvm_event(kvm, key);
+
+ vcpu_record->last_event = event;
+ vcpu_record->start_time = timestamp;
+ return true;
+}
+
+static void
+kvm_update_event_stats(struct kvm_event_stats *kvm_stats, u64 time_diff)
+{
+ kvm_stats->time += time_diff;
+ update_stats(&kvm_stats->stats, time_diff);
+}
+
+static double kvm_event_rel_stddev(int vcpu_id, struct kvm_event *event)
+{
+ struct kvm_event_stats *kvm_stats = &event->total;
+
+ if (vcpu_id != -1)
+ kvm_stats = &event->vcpu[vcpu_id];
+
+ return rel_stddev_stats(stddev_stats(&kvm_stats->stats),
+ avg_stats(&kvm_stats->stats));
+}
+
+static bool update_kvm_event(struct kvm_event *event, int vcpu_id,
+ u64 time_diff)
+{
+ if (vcpu_id == -1) {
+ kvm_update_event_stats(&event->total, time_diff);
+ return true;
+ }
+
+ if (!kvm_event_expand(event, vcpu_id))
+ return false;
+
+ kvm_update_event_stats(&event->vcpu[vcpu_id], time_diff);
+ return true;
+}
+
+static bool handle_end_event(struct perf_kvm_stat *kvm,
+ struct vcpu_event_record *vcpu_record,
+ struct event_key *key,
+ struct perf_sample *sample)
+{
+ struct kvm_event *event;
+ u64 time_begin, time_diff;
+ int vcpu;
+
+ if (kvm->trace_vcpu == -1)
+ vcpu = -1;
+ else
+ vcpu = vcpu_record->vcpu_id;
+
+ event = vcpu_record->last_event;
+ time_begin = vcpu_record->start_time;
+
+ /* The begin event is not caught. */
+ if (!time_begin)
+ return true;
+
+ /*
+ * In some case, the 'begin event' only records the start timestamp,
+ * the actual event is recognized in the 'end event' (e.g. mmio-event).
+ */
+
+ /* Both begin and end events did not get the key. */
+ if (!event && key->key == INVALID_KEY)
+ return true;
+
+ if (!event)
+ event = find_create_kvm_event(kvm, key);
+
+ if (!event)
+ return false;
+
+ vcpu_record->last_event = NULL;
+ vcpu_record->start_time = 0;
+
+ /* seems to happen once in a while during live mode */
+ if (sample->time < time_begin) {
+ pr_debug("End time before begin time; skipping event.\n");
+ return true;
+ }
+
+ time_diff = sample->time - time_begin;
+
+ if (kvm->duration && time_diff > kvm->duration) {
+ char decode[32];
+
+ kvm->events_ops->decode_key(kvm, &event->key, decode);
+ if (strcmp(decode, "HLT")) {
+ pr_info("%" PRIu64 " VM %d, vcpu %d: %s event took %" PRIu64 "usec\n",
+ sample->time, sample->pid, vcpu_record->vcpu_id,
+ decode, time_diff/1000);
+ }
+ }
+
+ return update_kvm_event(event, vcpu, time_diff);
+}
+
+static
+struct vcpu_event_record *per_vcpu_record(struct thread *thread,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ /* Only kvm_entry records vcpu id. */
+ if (!thread->priv && kvm_entry_event(evsel)) {
+ struct vcpu_event_record *vcpu_record;
+
+ vcpu_record = zalloc(sizeof(*vcpu_record));
+ if (!vcpu_record) {
+ pr_err("%s: Not enough memory\n", __func__);
+ return NULL;
+ }
+
+ vcpu_record->vcpu_id = perf_evsel__intval(evsel, sample, "vcpu_id");
+ thread->priv = vcpu_record;
+ }
+
+ return thread->priv;
+}
+
+static bool handle_kvm_event(struct perf_kvm_stat *kvm,
+ struct thread *thread,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ struct vcpu_event_record *vcpu_record;
+ struct event_key key = {.key = INVALID_KEY};
+
+ vcpu_record = per_vcpu_record(thread, evsel, sample);
+ if (!vcpu_record)
+ return true;
+
+ /* only process events for vcpus user cares about */
+ if ((kvm->trace_vcpu != -1) &&
+ (kvm->trace_vcpu != vcpu_record->vcpu_id))
+ return true;
+
+ if (kvm->events_ops->is_begin_event(evsel, sample, &key))
+ return handle_begin_event(kvm, vcpu_record, &key, sample->time);
+
+ if (kvm->events_ops->is_end_event(evsel, sample, &key))
+ return handle_end_event(kvm, vcpu_record, &key, sample);
+
+ return true;
+}
+
+#define GET_EVENT_KEY(func, field) \
+static u64 get_event_ ##func(struct kvm_event *event, int vcpu) \
+{ \
+ if (vcpu == -1) \
+ return event->total.field; \
+ \
+ if (vcpu >= event->max_vcpu) \
+ return 0; \
+ \
+ return event->vcpu[vcpu].field; \
+}
+
+#define COMPARE_EVENT_KEY(func, field) \
+GET_EVENT_KEY(func, field) \
+static int compare_kvm_event_ ## func(struct kvm_event *one, \
+ struct kvm_event *two, int vcpu)\
+{ \
+ return get_event_ ##func(one, vcpu) > \
+ get_event_ ##func(two, vcpu); \
+}
+
+GET_EVENT_KEY(time, time);
+COMPARE_EVENT_KEY(count, stats.n);
+COMPARE_EVENT_KEY(mean, stats.mean);
+GET_EVENT_KEY(max, stats.max);
+GET_EVENT_KEY(min, stats.min);
+
+#define DEF_SORT_NAME_KEY(name, compare_key) \
+ { #name, compare_kvm_event_ ## compare_key }
+
+static struct kvm_event_key keys[] = {
+ DEF_SORT_NAME_KEY(sample, count),
+ DEF_SORT_NAME_KEY(time, mean),
+ { NULL, NULL }
+};
+
+static bool select_key(struct perf_kvm_stat *kvm)
+{
+ int i;
+
+ for (i = 0; keys[i].name; i++) {
+ if (!strcmp(keys[i].name, kvm->sort_key)) {
+ kvm->compare = keys[i].key;
+ return true;
+ }
+ }
+
+ pr_err("Unknown compare key:%s\n", kvm->sort_key);
+ return false;
+}
+
+static void insert_to_result(struct rb_root *result, struct kvm_event *event,
+ key_cmp_fun bigger, int vcpu)
+{
+ struct rb_node **rb = &result->rb_node;
+ struct rb_node *parent = NULL;
+ struct kvm_event *p;
+
+ while (*rb) {
+ p = container_of(*rb, struct kvm_event, rb);
+ parent = *rb;
+
+ if (bigger(event, p, vcpu))
+ rb = &(*rb)->rb_left;
+ else
+ rb = &(*rb)->rb_right;
+ }
+
+ rb_link_node(&event->rb, parent, rb);
+ rb_insert_color(&event->rb, result);
+}
+
+static void
+update_total_count(struct perf_kvm_stat *kvm, struct kvm_event *event)
+{
+ int vcpu = kvm->trace_vcpu;
+
+ kvm->total_count += get_event_count(event, vcpu);
+ kvm->total_time += get_event_time(event, vcpu);
+}
+
+static bool event_is_valid(struct kvm_event *event, int vcpu)
+{
+ return !!get_event_count(event, vcpu);
+}
+
+static void sort_result(struct perf_kvm_stat *kvm)
+{
+ unsigned int i;
+ int vcpu = kvm->trace_vcpu;
+ struct kvm_event *event;
+
+ for (i = 0; i < EVENTS_CACHE_SIZE; i++) {
+ list_for_each_entry(event, &kvm->kvm_events_cache[i], hash_entry) {
+ if (event_is_valid(event, vcpu)) {
+ update_total_count(kvm, event);
+ insert_to_result(&kvm->result, event,
+ kvm->compare, vcpu);
+ }
+ }
+ }
+}
+
+/* returns left most element of result, and erase it */
+static struct kvm_event *pop_from_result(struct rb_root *result)
+{
+ struct rb_node *node = rb_first(result);
+
+ if (!node)
+ return NULL;
+
+ rb_erase(node, result);
+ return container_of(node, struct kvm_event, rb);
+}
+
+static void print_vcpu_info(struct perf_kvm_stat *kvm)
+{
+ int vcpu = kvm->trace_vcpu;
+
+ pr_info("Analyze events for ");
+
+ if (kvm->live) {
+ if (kvm->opts.target.system_wide)
+ pr_info("all VMs, ");
+ else if (kvm->opts.target.pid)
+ pr_info("pid(s) %s, ", kvm->opts.target.pid);
+ else
+ pr_info("dazed and confused on what is monitored, ");
+ }
+
+ if (vcpu == -1)
+ pr_info("all VCPUs:\n\n");
+ else
+ pr_info("VCPU %d:\n\n", vcpu);
+}
+
+static void show_timeofday(void)
+{
+ char date[64];
+ struct timeval tv;
+ struct tm ltime;
+
+ gettimeofday(&tv, NULL);
+ if (localtime_r(&tv.tv_sec, &ltime)) {
+ strftime(date, sizeof(date), "%H:%M:%S", &ltime);
+ pr_info("%s.%06ld", date, tv.tv_usec);
+ } else
+ pr_info("00:00:00.000000");
+
+ return;
+}
+
+static void print_result(struct perf_kvm_stat *kvm)
+{
+ char decode[20];
+ struct kvm_event *event;
+ int vcpu = kvm->trace_vcpu;
+
+ if (kvm->live) {
+ puts(CONSOLE_CLEAR);
+ show_timeofday();
+ }
+
+ pr_info("\n\n");
+ print_vcpu_info(kvm);
+ pr_info("%20s ", kvm->events_ops->name);
+ pr_info("%10s ", "Samples");
+ pr_info("%9s ", "Samples%");
+
+ pr_info("%9s ", "Time%");
+ pr_info("%10s ", "Min Time");
+ pr_info("%10s ", "Max Time");
+ pr_info("%16s ", "Avg time");
+ pr_info("\n\n");
+
+ while ((event = pop_from_result(&kvm->result))) {
+ u64 ecount, etime, max, min;
+
+ ecount = get_event_count(event, vcpu);
+ etime = get_event_time(event, vcpu);
+ max = get_event_max(event, vcpu);
+ min = get_event_min(event, vcpu);
+
+ kvm->events_ops->decode_key(kvm, &event->key, decode);
+ pr_info("%20s ", decode);
+ pr_info("%10llu ", (unsigned long long)ecount);
+ pr_info("%8.2f%% ", (double)ecount / kvm->total_count * 100);
+ pr_info("%8.2f%% ", (double)etime / kvm->total_time * 100);
+ pr_info("%8" PRIu64 "us ", min / 1000);
+ pr_info("%8" PRIu64 "us ", max / 1000);
+ pr_info("%9.2fus ( +-%7.2f%% )", (double)etime / ecount/1e3,
+ kvm_event_rel_stddev(vcpu, event));
+ pr_info("\n");
+ }
+
+ pr_info("\nTotal Samples:%" PRIu64 ", Total events handled time:%.2fus.\n\n",
+ kvm->total_count, kvm->total_time / 1e3);
+
+ if (kvm->lost_events)
+ pr_info("\nLost events: %" PRIu64 "\n\n", kvm->lost_events);
+}
+
+#ifdef HAVE_TIMERFD_SUPPORT
+static int process_lost_event(struct perf_tool *tool,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ struct perf_kvm_stat *kvm = container_of(tool, struct perf_kvm_stat, tool);
+
+ kvm->lost_events++;
+ return 0;
+}
+#endif
+
+static bool skip_sample(struct perf_kvm_stat *kvm,
+ struct perf_sample *sample)
+{
+ if (kvm->pid_list && intlist__find(kvm->pid_list, sample->pid) == NULL)
+ return true;
+
+ return false;
+}
+
+static int process_sample_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ struct thread *thread;
+ struct perf_kvm_stat *kvm = container_of(tool, struct perf_kvm_stat,
+ tool);
+
+ if (skip_sample(kvm, sample))
+ return 0;
+
+ thread = machine__findnew_thread(machine, sample->pid, sample->tid);
+ if (thread == NULL) {
+ pr_debug("problem processing %d event, skipping it.\n",
+ event->header.type);
+ return -1;
+ }
+
+ if (!handle_kvm_event(kvm, thread, evsel, sample))
+ return -1;
+
+ return 0;
+}
+
+static int cpu_isa_config(struct perf_kvm_stat *kvm)
+{
+ char buf[64], *cpuid;
+ int err, isa;
+
+ if (kvm->live) {
+ err = get_cpuid(buf, sizeof(buf));
+ if (err != 0) {
+ pr_err("Failed to look up CPU type (Intel or AMD)\n");
+ return err;
+ }
+ cpuid = buf;
+ } else
+ cpuid = kvm->session->header.env.cpuid;
+
+ if (strstr(cpuid, "Intel"))
+ isa = 1;
+ else if (strstr(cpuid, "AMD"))
+ isa = 0;
+ else {
+ pr_err("CPU %s is not supported.\n", cpuid);
+ return -ENOTSUP;
+ }
+
+ if (isa == 1) {
+ kvm->exit_reasons = vmx_exit_reasons;
+ kvm->exit_reasons_size = ARRAY_SIZE(vmx_exit_reasons);
+ kvm->exit_reasons_isa = "VMX";
+ }
+
+ return 0;
+}
+
+static bool verify_vcpu(int vcpu)
+{
+ if (vcpu != -1 && vcpu < 0) {
+ pr_err("Invalid vcpu:%d.\n", vcpu);
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef HAVE_TIMERFD_SUPPORT
+/* keeping the max events to a modest level to keep
+ * the processing of samples per mmap smooth.
+ */
+#define PERF_KVM__MAX_EVENTS_PER_MMAP 25
+
+static s64 perf_kvm__mmap_read_idx(struct perf_kvm_stat *kvm, int idx,
+ u64 *mmap_time)
+{
+ union perf_event *event;
+ struct perf_sample sample;
+ s64 n = 0;
+ int err;
+
+ *mmap_time = ULLONG_MAX;
+ while ((event = perf_evlist__mmap_read(kvm->evlist, idx)) != NULL) {
+ err = perf_evlist__parse_sample(kvm->evlist, event, &sample);
+ if (err) {
+ perf_evlist__mmap_consume(kvm->evlist, idx);
+ pr_err("Failed to parse sample\n");
+ return -1;
+ }
+
+ err = perf_session_queue_event(kvm->session, event, &sample, 0);
+ /*
+ * FIXME: Here we can't consume the event, as perf_session_queue_event will
+ * point to it, and it'll get possibly overwritten by the kernel.
+ */
+ perf_evlist__mmap_consume(kvm->evlist, idx);
+
+ if (err) {
+ pr_err("Failed to enqueue sample: %d\n", err);
+ return -1;
+ }
+
+ /* save time stamp of our first sample for this mmap */
+ if (n == 0)
+ *mmap_time = sample.time;
+
+ /* limit events per mmap handled all at once */
+ n++;
+ if (n == PERF_KVM__MAX_EVENTS_PER_MMAP)
+ break;
+ }
+
+ return n;
+}
+
+static int perf_kvm__mmap_read(struct perf_kvm_stat *kvm)
+{
+ int i, err, throttled = 0;
+ s64 n, ntotal = 0;
+ u64 flush_time = ULLONG_MAX, mmap_time;
+
+ for (i = 0; i < kvm->evlist->nr_mmaps; i++) {
+ n = perf_kvm__mmap_read_idx(kvm, i, &mmap_time);
+ if (n < 0)
+ return -1;
+
+ /* flush time is going to be the minimum of all the individual
+ * mmap times. Essentially, we flush all the samples queued up
+ * from the last pass under our minimal start time -- that leaves
+ * a very small race for samples to come in with a lower timestamp.
+ * The ioctl to return the perf_clock timestamp should close the
+ * race entirely.
+ */
+ if (mmap_time < flush_time)
+ flush_time = mmap_time;
+
+ ntotal += n;
+ if (n == PERF_KVM__MAX_EVENTS_PER_MMAP)
+ throttled = 1;
+ }
+
+ /* flush queue after each round in which we processed events */
+ if (ntotal) {
+ kvm->session->ordered_samples.next_flush = flush_time;
+ err = kvm->tool.finished_round(&kvm->tool, NULL, kvm->session);
+ if (err) {
+ if (kvm->lost_events)
+ pr_info("\nLost events: %" PRIu64 "\n\n",
+ kvm->lost_events);
+ return err;
+ }
+ }
+
+ return throttled;
+}
+
+static volatile int done;
+
+static void sig_handler(int sig __maybe_unused)
+{
+ done = 1;
+}
+
+static int perf_kvm__timerfd_create(struct perf_kvm_stat *kvm)
+{
+ struct itimerspec new_value;
+ int rc = -1;
+
+ kvm->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+ if (kvm->timerfd < 0) {
+ pr_err("timerfd_create failed\n");
+ goto out;
+ }
+
+ new_value.it_value.tv_sec = kvm->display_time;
+ new_value.it_value.tv_nsec = 0;
+ new_value.it_interval.tv_sec = kvm->display_time;
+ new_value.it_interval.tv_nsec = 0;
+
+ if (timerfd_settime(kvm->timerfd, 0, &new_value, NULL) != 0) {
+ pr_err("timerfd_settime failed: %d\n", errno);
+ close(kvm->timerfd);
+ goto out;
+ }
+
+ rc = 0;
+out:
+ return rc;
+}
+
+static int perf_kvm__handle_timerfd(struct perf_kvm_stat *kvm)
+{
+ uint64_t c;
+ int rc;
+
+ rc = read(kvm->timerfd, &c, sizeof(uint64_t));
+ if (rc < 0) {
+ if (errno == EAGAIN)
+ return 0;
+
+ pr_err("Failed to read timer fd: %d\n", errno);
+ return -1;
+ }
+
+ if (rc != sizeof(uint64_t)) {
+ pr_err("Error reading timer fd - invalid size returned\n");
+ return -1;
+ }
+
+ if (c != 1)
+ pr_debug("Missed timer beats: %" PRIu64 "\n", c-1);
+
+ /* update display */
+ sort_result(kvm);
+ print_result(kvm);
+
+ /* reset counts */
+ clear_events_cache_stats(kvm->kvm_events_cache);
+ kvm->total_count = 0;
+ kvm->total_time = 0;
+ kvm->lost_events = 0;
+
+ return 0;
+}
+
+static int fd_set_nonblock(int fd)
+{
+ long arg = 0;
+
+ arg = fcntl(fd, F_GETFL);
+ if (arg < 0) {
+ pr_err("Failed to get current flags for fd %d\n", fd);
+ return -1;
+ }
+
+ if (fcntl(fd, F_SETFL, arg | O_NONBLOCK) < 0) {
+ pr_err("Failed to set non-block option on fd %d\n", fd);
+ return -1;
+ }
+
+ return 0;
+}
+
+static
+int perf_kvm__handle_stdin(struct termios *tc_now, struct termios *tc_save)
+{
+ int c;
+
+ tcsetattr(0, TCSANOW, tc_now);
+ c = getc(stdin);
+ tcsetattr(0, TCSAFLUSH, tc_save);
+
+ if (c == 'q')
+ return 1;
+
+ return 0;
+}
+
+static int kvm_events_live_report(struct perf_kvm_stat *kvm)
+{
+ struct pollfd *pollfds = NULL;
+ int nr_fds, nr_stdin, ret, err = -EINVAL;
+ struct termios tc, save;
+
+ /* live flag must be set first */
+ kvm->live = true;
+
+ ret = cpu_isa_config(kvm);
+ if (ret < 0)
+ return ret;
+
+ if (!verify_vcpu(kvm->trace_vcpu) ||
+ !select_key(kvm) ||
+ !register_kvm_events_ops(kvm)) {
+ goto out;
+ }
+
+ init_kvm_event_record(kvm);
+
+ tcgetattr(0, &save);
+ tc = save;
+ tc.c_lflag &= ~(ICANON | ECHO);
+ tc.c_cc[VMIN] = 0;
+ tc.c_cc[VTIME] = 0;
+
+ signal(SIGINT, sig_handler);
+ signal(SIGTERM, sig_handler);
+
+ /* copy pollfds -- need to add timerfd and stdin */
+ nr_fds = kvm->evlist->nr_fds;
+ pollfds = zalloc(sizeof(struct pollfd) * (nr_fds + 2));
+ if (!pollfds) {
+ err = -ENOMEM;
+ goto out;
+ }
+ memcpy(pollfds, kvm->evlist->pollfd,
+ sizeof(struct pollfd) * kvm->evlist->nr_fds);
+
+ /* add timer fd */
+ if (perf_kvm__timerfd_create(kvm) < 0) {
+ err = -1;
+ goto out;
+ }
+
+ pollfds[nr_fds].fd = kvm->timerfd;
+ pollfds[nr_fds].events = POLLIN;
+ nr_fds++;
+
+ pollfds[nr_fds].fd = fileno(stdin);
+ pollfds[nr_fds].events = POLLIN;
+ nr_stdin = nr_fds;
+ nr_fds++;
+ if (fd_set_nonblock(fileno(stdin)) != 0)
+ goto out;
+
+ /* everything is good - enable the events and process */
+ perf_evlist__enable(kvm->evlist);
+
+ while (!done) {
+ int rc;
+
+ rc = perf_kvm__mmap_read(kvm);
+ if (rc < 0)
+ break;
+
+ err = perf_kvm__handle_timerfd(kvm);
+ if (err)
+ goto out;
+
+ if (pollfds[nr_stdin].revents & POLLIN)
+ done = perf_kvm__handle_stdin(&tc, &save);
+
+ if (!rc && !done)
+ err = poll(pollfds, nr_fds, 100);
+ }
+
+ perf_evlist__disable(kvm->evlist);
+
+ if (err == 0) {
+ sort_result(kvm);
+ print_result(kvm);
+ }
+
+out:
+ if (kvm->timerfd >= 0)
+ close(kvm->timerfd);
+
+ free(pollfds);
+ return err;
+}
+
+static int kvm_live_open_events(struct perf_kvm_stat *kvm)
+{
+ int err, rc = -1;
+ struct perf_evsel *pos;
+ struct perf_evlist *evlist = kvm->evlist;
+
+ perf_evlist__config(evlist, &kvm->opts);
+
+ /*
+ * Note: exclude_{guest,host} do not apply here.
+ * This command processes KVM tracepoints from host only
+ */
+ evlist__for_each(evlist, pos) {
+ struct perf_event_attr *attr = &pos->attr;
+
+ /* make sure these *are* set */
+ perf_evsel__set_sample_bit(pos, TID);
+ perf_evsel__set_sample_bit(pos, TIME);
+ perf_evsel__set_sample_bit(pos, CPU);
+ perf_evsel__set_sample_bit(pos, RAW);
+ /* make sure these are *not*; want as small a sample as possible */
+ perf_evsel__reset_sample_bit(pos, PERIOD);
+ perf_evsel__reset_sample_bit(pos, IP);
+ perf_evsel__reset_sample_bit(pos, CALLCHAIN);
+ perf_evsel__reset_sample_bit(pos, ADDR);
+ perf_evsel__reset_sample_bit(pos, READ);
+ attr->mmap = 0;
+ attr->comm = 0;
+ attr->task = 0;
+
+ attr->sample_period = 1;
+
+ attr->watermark = 0;
+ attr->wakeup_events = 1000;
+
+ /* will enable all once we are ready */
+ attr->disabled = 1;
+ }
+
+ err = perf_evlist__open(evlist);
+ if (err < 0) {
+ printf("Couldn't create the events: %s\n", strerror(errno));
+ goto out;
+ }
+
+ if (perf_evlist__mmap(evlist, kvm->opts.mmap_pages, false) < 0) {
+ ui__error("Failed to mmap the events: %s\n", strerror(errno));
+ perf_evlist__close(evlist);
+ goto out;
+ }
+
+ rc = 0;
+
+out:
+ return rc;
+}
+#endif
+
+static int read_events(struct perf_kvm_stat *kvm)
+{
+ int ret;
+
+ struct perf_tool eops = {
+ .sample = process_sample_event,
+ .comm = perf_event__process_comm,
+ .ordered_samples = true,
+ };
+ struct perf_data_file file = {
+ .path = kvm->file_name,
+ .mode = PERF_DATA_MODE_READ,
+ };
+
+ kvm->tool = eops;
+ kvm->session = perf_session__new(&file, false, &kvm->tool);
+ if (!kvm->session) {
+ pr_err("Initializing perf session failed\n");
+ return -EINVAL;
+ }
+
+ if (!perf_session__has_traces(kvm->session, "kvm record"))
+ return -EINVAL;
+
+ /*
+ * Do not use 'isa' recorded in kvm_exit tracepoint since it is not
+ * traced in the old kernel.
+ */
+ ret = cpu_isa_config(kvm);
+ if (ret < 0)
+ return ret;
+
+ return perf_session__process_events(kvm->session, &kvm->tool);
+}
+
+static int parse_target_str(struct perf_kvm_stat *kvm)
+{
+ if (kvm->pid_str) {
+ kvm->pid_list = intlist__new(kvm->pid_str);
+ if (kvm->pid_list == NULL) {
+ pr_err("Error parsing process id string\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int kvm_events_report_vcpu(struct perf_kvm_stat *kvm)
+{
+ int ret = -EINVAL;
+ int vcpu = kvm->trace_vcpu;
+
+ if (parse_target_str(kvm) != 0)
+ goto exit;
+
+ if (!verify_vcpu(vcpu))
+ goto exit;
+
+ if (!select_key(kvm))
+ goto exit;
+
+ if (!register_kvm_events_ops(kvm))
+ goto exit;
+
+ init_kvm_event_record(kvm);
+ setup_pager();
+
+ ret = read_events(kvm);
+ if (ret)
+ goto exit;
+
+ sort_result(kvm);
+ print_result(kvm);
+
+exit:
+ return ret;
+}
+
+static const char * const kvm_events_tp[] = {
+ "kvm:kvm_entry",
+ "kvm:kvm_exit",
+ "kvm:kvm_mmio",
+ "kvm:kvm_pio",
+};
+
+#define STRDUP_FAIL_EXIT(s) \
+ ({ char *_p; \
+ _p = strdup(s); \
+ if (!_p) \
+ return -ENOMEM; \
+ _p; \
+ })
+
+static int
+kvm_events_record(struct perf_kvm_stat *kvm, int argc, const char **argv)
+{
+ unsigned int rec_argc, i, j;
+ const char **rec_argv;
+ const char * const record_args[] = {
+ "record",
+ "-R",
+ "-m", "1024",
+ "-c", "1",
+ };
+
+ rec_argc = ARRAY_SIZE(record_args) + argc + 2 +
+ 2 * ARRAY_SIZE(kvm_events_tp);
+ rec_argv = calloc(rec_argc + 1, sizeof(char *));
+
+ if (rec_argv == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(record_args); i++)
+ rec_argv[i] = STRDUP_FAIL_EXIT(record_args[i]);
+
+ for (j = 0; j < ARRAY_SIZE(kvm_events_tp); j++) {
+ rec_argv[i++] = "-e";
+ rec_argv[i++] = STRDUP_FAIL_EXIT(kvm_events_tp[j]);
+ }
+
+ rec_argv[i++] = STRDUP_FAIL_EXIT("-o");
+ rec_argv[i++] = STRDUP_FAIL_EXIT(kvm->file_name);
+
+ for (j = 1; j < (unsigned int)argc; j++, i++)
+ rec_argv[i] = argv[j];
+
+ return cmd_record(i, rec_argv, NULL);
+}
+
+static int
+kvm_events_report(struct perf_kvm_stat *kvm, int argc, const char **argv)
+{
+ const struct option kvm_events_report_options[] = {
+ OPT_STRING(0, "event", &kvm->report_event, "report event",
+ "event for reporting: vmexit, mmio, ioport"),
+ OPT_INTEGER(0, "vcpu", &kvm->trace_vcpu,
+ "vcpu id to report"),
+ OPT_STRING('k', "key", &kvm->sort_key, "sort-key",
+ "key for sorting: sample(sort by samples number)"
+ " time (sort by avg time)"),
+ OPT_STRING('p', "pid", &kvm->pid_str, "pid",
+ "analyze events only for given process id(s)"),
+ OPT_END()
+ };
+
+ const char * const kvm_events_report_usage[] = {
+ "perf kvm stat report [<options>]",
+ NULL
+ };
+
+ symbol__init();
+
+ if (argc) {
+ argc = parse_options(argc, argv,
+ kvm_events_report_options,
+ kvm_events_report_usage, 0);
+ if (argc)
+ usage_with_options(kvm_events_report_usage,
+ kvm_events_report_options);
+ }
+
+ return kvm_events_report_vcpu(kvm);
+}
+
+#ifdef HAVE_TIMERFD_SUPPORT
+static struct perf_evlist *kvm_live_event_list(void)
+{
+ struct perf_evlist *evlist;
+ char *tp, *name, *sys;
+ unsigned int j;
+ int err = -1;
+
+ evlist = perf_evlist__new();
+ if (evlist == NULL)
+ return NULL;
+
+ for (j = 0; j < ARRAY_SIZE(kvm_events_tp); j++) {
+
+ tp = strdup(kvm_events_tp[j]);
+ if (tp == NULL)
+ goto out;
+
+ /* split tracepoint into subsystem and name */
+ sys = tp;
+ name = strchr(tp, ':');
+ if (name == NULL) {
+ pr_err("Error parsing %s tracepoint: subsystem delimiter not found\n",
+ kvm_events_tp[j]);
+ free(tp);
+ goto out;
+ }
+ *name = '\0';
+ name++;
+
+ if (perf_evlist__add_newtp(evlist, sys, name, NULL)) {
+ pr_err("Failed to add %s tracepoint to the list\n", kvm_events_tp[j]);
+ free(tp);
+ goto out;
+ }
+
+ free(tp);
+ }
+
+ err = 0;
+
+out:
+ if (err) {
+ perf_evlist__delete(evlist);
+ evlist = NULL;
+ }
+
+ return evlist;
+}
+
+static int kvm_events_live(struct perf_kvm_stat *kvm,
+ int argc, const char **argv)
+{
+ char errbuf[BUFSIZ];
+ int err;
+
+ const struct option live_options[] = {
+ OPT_STRING('p', "pid", &kvm->opts.target.pid, "pid",
+ "record events on existing process id"),
+ OPT_CALLBACK('m', "mmap-pages", &kvm->opts.mmap_pages, "pages",
+ "number of mmap data pages",
+ perf_evlist__parse_mmap_pages),
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show counter open errors, etc)"),
+ OPT_BOOLEAN('a', "all-cpus", &kvm->opts.target.system_wide,
+ "system-wide collection from all CPUs"),
+ OPT_UINTEGER('d', "display", &kvm->display_time,
+ "time in seconds between display updates"),
+ OPT_STRING(0, "event", &kvm->report_event, "report event",
+ "event for reporting: vmexit, mmio, ioport"),
+ OPT_INTEGER(0, "vcpu", &kvm->trace_vcpu,
+ "vcpu id to report"),
+ OPT_STRING('k', "key", &kvm->sort_key, "sort-key",
+ "key for sorting: sample(sort by samples number)"
+ " time (sort by avg time)"),
+ OPT_U64(0, "duration", &kvm->duration,
+ "show events other than HALT that take longer than duration usecs"),
+ OPT_END()
+ };
+ const char * const live_usage[] = {
+ "perf kvm stat live [<options>]",
+ NULL
+ };
+ struct perf_data_file file = {
+ .mode = PERF_DATA_MODE_WRITE,
+ };
+
+
+ /* event handling */
+ kvm->tool.sample = process_sample_event;
+ kvm->tool.comm = perf_event__process_comm;
+ kvm->tool.exit = perf_event__process_exit;
+ kvm->tool.fork = perf_event__process_fork;
+ kvm->tool.lost = process_lost_event;
+ kvm->tool.ordered_samples = true;
+ perf_tool__fill_defaults(&kvm->tool);
+
+ /* set defaults */
+ kvm->display_time = 1;
+ kvm->opts.user_interval = 1;
+ kvm->opts.mmap_pages = 512;
+ kvm->opts.target.uses_mmap = false;
+ kvm->opts.target.uid_str = NULL;
+ kvm->opts.target.uid = UINT_MAX;
+
+ symbol__init();
+ disable_buildid_cache();
+
+ use_browser = 0;
+ setup_browser(false);
+
+ if (argc) {
+ argc = parse_options(argc, argv, live_options,
+ live_usage, 0);
+ if (argc)
+ usage_with_options(live_usage, live_options);
+ }
+
+ kvm->duration *= NSEC_PER_USEC; /* convert usec to nsec */
+
+ /*
+ * target related setups
+ */
+ err = target__validate(&kvm->opts.target);
+ if (err) {
+ target__strerror(&kvm->opts.target, err, errbuf, BUFSIZ);
+ ui__warning("%s", errbuf);
+ }
+
+ if (target__none(&kvm->opts.target))
+ kvm->opts.target.system_wide = true;
+
+
+ /*
+ * generate the event list
+ */
+ kvm->evlist = kvm_live_event_list();
+ if (kvm->evlist == NULL) {
+ err = -1;
+ goto out;
+ }
+
+ symbol_conf.nr_events = kvm->evlist->nr_entries;
+
+ if (perf_evlist__create_maps(kvm->evlist, &kvm->opts.target) < 0)
+ usage_with_options(live_usage, live_options);
+
+ /*
+ * perf session
+ */
+ kvm->session = perf_session__new(&file, false, &kvm->tool);
+ if (kvm->session == NULL) {
+ err = -ENOMEM;
+ goto out;
+ }
+ kvm->session->evlist = kvm->evlist;
+ perf_session__set_id_hdr_size(kvm->session);
+ machine__synthesize_threads(&kvm->session->machines.host, &kvm->opts.target,
+ kvm->evlist->threads, false);
+ err = kvm_live_open_events(kvm);
+ if (err)
+ goto out;
+
+ err = kvm_events_live_report(kvm);
+
+out:
+ exit_browser(0);
+
+ if (kvm->session)
+ perf_session__delete(kvm->session);
+ kvm->session = NULL;
+ if (kvm->evlist)
+ perf_evlist__delete(kvm->evlist);
+
+ return err;
+}
+#endif
+
+static void print_kvm_stat_usage(void)
+{
+ printf("Usage: perf kvm stat <command>\n\n");
+
+ printf("# Available commands:\n");
+ printf("\trecord: record kvm events\n");
+ printf("\treport: report statistical data of kvm events\n");
+ printf("\tlive: live reporting of statistical data of kvm events\n");
+
+ printf("\nOtherwise, it is the alias of 'perf stat':\n");
+}
+
+static int kvm_cmd_stat(const char *file_name, int argc, const char **argv)
+{
+ struct perf_kvm_stat kvm = {
+ .file_name = file_name,
+
+ .trace_vcpu = -1,
+ .report_event = "vmexit",
+ .sort_key = "sample",
+
+ .exit_reasons = svm_exit_reasons,
+ .exit_reasons_size = ARRAY_SIZE(svm_exit_reasons),
+ .exit_reasons_isa = "SVM",
+ };
+
+ if (argc == 1) {
+ print_kvm_stat_usage();
+ goto perf_stat;
+ }
+
+ if (!strncmp(argv[1], "rec", 3))
+ return kvm_events_record(&kvm, argc - 1, argv + 1);
+
+ if (!strncmp(argv[1], "rep", 3))
+ return kvm_events_report(&kvm, argc - 1 , argv + 1);
+
+#ifdef HAVE_TIMERFD_SUPPORT
+ if (!strncmp(argv[1], "live", 4))
+ return kvm_events_live(&kvm, argc - 1 , argv + 1);
+#endif
+
+perf_stat:
+ return cmd_stat(argc, argv, NULL);
+}
+#endif
+
+static int __cmd_record(const char *file_name, int argc, const char **argv)
+{
+ int rec_argc, i = 0, j;
+ const char **rec_argv;
+
+ rec_argc = argc + 2;
+ rec_argv = calloc(rec_argc + 1, sizeof(char *));
+ rec_argv[i++] = strdup("record");
+ rec_argv[i++] = strdup("-o");
+ rec_argv[i++] = strdup(file_name);
+ for (j = 1; j < argc; j++, i++)
+ rec_argv[i] = argv[j];
+
+ BUG_ON(i != rec_argc);
+
+ return cmd_record(i, rec_argv, NULL);
+}
+
+static int __cmd_report(const char *file_name, int argc, const char **argv)
+{
+ int rec_argc, i = 0, j;
+ const char **rec_argv;
+
+ rec_argc = argc + 2;
+ rec_argv = calloc(rec_argc + 1, sizeof(char *));
+ rec_argv[i++] = strdup("report");
+ rec_argv[i++] = strdup("-i");
+ rec_argv[i++] = strdup(file_name);
+ for (j = 1; j < argc; j++, i++)
+ rec_argv[i] = argv[j];
+
+ BUG_ON(i != rec_argc);
+
+ return cmd_report(i, rec_argv, NULL);
+}
+
+static int
+__cmd_buildid_list(const char *file_name, int argc, const char **argv)
+{
+ int rec_argc, i = 0, j;
+ const char **rec_argv;
+
+ rec_argc = argc + 2;
+ rec_argv = calloc(rec_argc + 1, sizeof(char *));
+ rec_argv[i++] = strdup("buildid-list");
+ rec_argv[i++] = strdup("-i");
+ rec_argv[i++] = strdup(file_name);
+ for (j = 1; j < argc; j++, i++)
+ rec_argv[i] = argv[j];
+
+ BUG_ON(i != rec_argc);
+
+ return cmd_buildid_list(i, rec_argv, NULL);
+}
+
+int cmd_kvm(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ const char *file_name = NULL;
+ const struct option kvm_options[] = {
+ OPT_STRING('i', "input", &file_name, "file",
+ "Input file name"),
+ OPT_STRING('o', "output", &file_name, "file",
+ "Output file name"),
+ OPT_BOOLEAN(0, "guest", &perf_guest,
+ "Collect guest os data"),
+ OPT_BOOLEAN(0, "host", &perf_host,
+ "Collect host os data"),
+ OPT_STRING(0, "guestmount", &symbol_conf.guestmount, "directory",
+ "guest mount directory under which every guest os"
+ " instance has a subdir"),
+ OPT_STRING(0, "guestvmlinux", &symbol_conf.default_guest_vmlinux_name,
+ "file", "file saving guest os vmlinux"),
+ OPT_STRING(0, "guestkallsyms", &symbol_conf.default_guest_kallsyms,
+ "file", "file saving guest os /proc/kallsyms"),
+ OPT_STRING(0, "guestmodules", &symbol_conf.default_guest_modules,
+ "file", "file saving guest os /proc/modules"),
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show counter open errors, etc)"),
+ OPT_END()
+ };
+
+ const char *const kvm_subcommands[] = { "top", "record", "report", "diff",
+ "buildid-list", "stat", NULL };
+ const char *kvm_usage[] = { NULL, NULL };
+
+ perf_host = 0;
+ perf_guest = 1;
+
+ argc = parse_options_subcommand(argc, argv, kvm_options, kvm_subcommands, kvm_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+ if (!argc)
+ usage_with_options(kvm_usage, kvm_options);
+
+ if (!perf_host)
+ perf_guest = 1;
+
+ if (!file_name) {
+ file_name = get_filename_for_perf_kvm();
+
+ if (!file_name) {
+ pr_err("Failed to allocate memory for filename\n");
+ return -ENOMEM;
+ }
+ }
+
+ if (!strncmp(argv[0], "rec", 3))
+ return __cmd_record(file_name, argc, argv);
+ else if (!strncmp(argv[0], "rep", 3))
+ return __cmd_report(file_name, argc, argv);
+ else if (!strncmp(argv[0], "diff", 4))
+ return cmd_diff(argc, argv, NULL);
+ else if (!strncmp(argv[0], "top", 3))
+ return cmd_top(argc, argv, NULL);
+ else if (!strncmp(argv[0], "buildid-list", 12))
+ return __cmd_buildid_list(file_name, argc, argv);
+#if defined(__i386__) || defined(__x86_64__)
+ else if (!strncmp(argv[0], "stat", 4))
+ return kvm_cmd_stat(file_name, argc, argv);
+#endif
+ else
+ usage_with_options(kvm_usage, kvm_options);
+
+ return 0;
+}
diff --git a/tools/perf/builtin-list.c b/tools/perf/builtin-list.c
new file mode 100644
index 00000000000..011195e38f2
--- /dev/null
+++ b/tools/perf/builtin-list.c
@@ -0,0 +1,77 @@
+/*
+ * builtin-list.c
+ *
+ * Builtin list command: list all event types
+ *
+ * Copyright (C) 2009, Thomas Gleixner <tglx@linutronix.de>
+ * Copyright (C) 2008-2009, Red Hat Inc, Ingo Molnar <mingo@redhat.com>
+ * Copyright (C) 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
+ */
+#include "builtin.h"
+
+#include "perf.h"
+
+#include "util/parse-events.h"
+#include "util/cache.h"
+#include "util/pmu.h"
+#include "util/parse-options.h"
+
+int cmd_list(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ int i;
+ const struct option list_options[] = {
+ OPT_END()
+ };
+ const char * const list_usage[] = {
+ "perf list [hw|sw|cache|tracepoint|pmu|event_glob]",
+ NULL
+ };
+
+ argc = parse_options(argc, argv, list_options, list_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ setup_pager();
+
+ if (argc == 0) {
+ print_events(NULL, false);
+ return 0;
+ }
+
+ for (i = 0; i < argc; ++i) {
+ if (i)
+ putchar('\n');
+ if (strncmp(argv[i], "tracepoint", 10) == 0)
+ print_tracepoint_events(NULL, NULL, false);
+ else if (strcmp(argv[i], "hw") == 0 ||
+ strcmp(argv[i], "hardware") == 0)
+ print_events_type(PERF_TYPE_HARDWARE);
+ else if (strcmp(argv[i], "sw") == 0 ||
+ strcmp(argv[i], "software") == 0)
+ print_events_type(PERF_TYPE_SOFTWARE);
+ else if (strcmp(argv[i], "cache") == 0 ||
+ strcmp(argv[i], "hwcache") == 0)
+ print_hwcache_events(NULL, false);
+ else if (strcmp(argv[i], "pmu") == 0)
+ print_pmu_events(NULL, false);
+ else if (strcmp(argv[i], "--raw-dump") == 0)
+ print_events(NULL, true);
+ else {
+ char *sep = strchr(argv[i], ':'), *s;
+ int sep_idx;
+
+ if (sep == NULL) {
+ print_events(argv[i], false);
+ continue;
+ }
+ sep_idx = sep - argv[i];
+ s = strdup(argv[i]);
+ if (s == NULL)
+ return -1;
+
+ s[sep_idx] = '\0';
+ print_tracepoint_events(s, s + sep_idx + 1, false);
+ free(s);
+ }
+ }
+ return 0;
+}
diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c
new file mode 100644
index 00000000000..6148afc995c
--- /dev/null
+++ b/tools/perf/builtin-lock.c
@@ -0,0 +1,1015 @@
+#include "builtin.h"
+#include "perf.h"
+
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/util.h"
+#include "util/cache.h"
+#include "util/symbol.h"
+#include "util/thread.h"
+#include "util/header.h"
+
+#include "util/parse-options.h"
+#include "util/trace-event.h"
+
+#include "util/debug.h"
+#include "util/session.h"
+#include "util/tool.h"
+#include "util/data.h"
+
+#include <sys/types.h>
+#include <sys/prctl.h>
+#include <semaphore.h>
+#include <pthread.h>
+#include <math.h>
+#include <limits.h>
+
+#include <linux/list.h>
+#include <linux/hash.h>
+
+static struct perf_session *session;
+
+/* based on kernel/lockdep.c */
+#define LOCKHASH_BITS 12
+#define LOCKHASH_SIZE (1UL << LOCKHASH_BITS)
+
+static struct list_head lockhash_table[LOCKHASH_SIZE];
+
+#define __lockhashfn(key) hash_long((unsigned long)key, LOCKHASH_BITS)
+#define lockhashentry(key) (lockhash_table + __lockhashfn((key)))
+
+struct lock_stat {
+ struct list_head hash_entry;
+ struct rb_node rb; /* used for sorting */
+
+ /*
+ * FIXME: perf_evsel__intval() returns u64,
+ * so address of lockdep_map should be dealed as 64bit.
+ * Is there more better solution?
+ */
+ void *addr; /* address of lockdep_map, used as ID */
+ char *name; /* for strcpy(), we cannot use const */
+
+ unsigned int nr_acquire;
+ unsigned int nr_acquired;
+ unsigned int nr_contended;
+ unsigned int nr_release;
+
+ unsigned int nr_readlock;
+ unsigned int nr_trylock;
+
+ /* these times are in nano sec. */
+ u64 avg_wait_time;
+ u64 wait_time_total;
+ u64 wait_time_min;
+ u64 wait_time_max;
+
+ int discard; /* flag of blacklist */
+};
+
+/*
+ * States of lock_seq_stat
+ *
+ * UNINITIALIZED is required for detecting first event of acquire.
+ * As the nature of lock events, there is no guarantee
+ * that the first event for the locks are acquire,
+ * it can be acquired, contended or release.
+ */
+#define SEQ_STATE_UNINITIALIZED 0 /* initial state */
+#define SEQ_STATE_RELEASED 1
+#define SEQ_STATE_ACQUIRING 2
+#define SEQ_STATE_ACQUIRED 3
+#define SEQ_STATE_READ_ACQUIRED 4
+#define SEQ_STATE_CONTENDED 5
+
+/*
+ * MAX_LOCK_DEPTH
+ * Imported from include/linux/sched.h.
+ * Should this be synchronized?
+ */
+#define MAX_LOCK_DEPTH 48
+
+/*
+ * struct lock_seq_stat:
+ * Place to put on state of one lock sequence
+ * 1) acquire -> acquired -> release
+ * 2) acquire -> contended -> acquired -> release
+ * 3) acquire (with read or try) -> release
+ * 4) Are there other patterns?
+ */
+struct lock_seq_stat {
+ struct list_head list;
+ int state;
+ u64 prev_event_time;
+ void *addr;
+
+ int read_count;
+};
+
+struct thread_stat {
+ struct rb_node rb;
+
+ u32 tid;
+ struct list_head seq_list;
+};
+
+static struct rb_root thread_stats;
+
+static struct thread_stat *thread_stat_find(u32 tid)
+{
+ struct rb_node *node;
+ struct thread_stat *st;
+
+ node = thread_stats.rb_node;
+ while (node) {
+ st = container_of(node, struct thread_stat, rb);
+ if (st->tid == tid)
+ return st;
+ else if (tid < st->tid)
+ node = node->rb_left;
+ else
+ node = node->rb_right;
+ }
+
+ return NULL;
+}
+
+static void thread_stat_insert(struct thread_stat *new)
+{
+ struct rb_node **rb = &thread_stats.rb_node;
+ struct rb_node *parent = NULL;
+ struct thread_stat *p;
+
+ while (*rb) {
+ p = container_of(*rb, struct thread_stat, rb);
+ parent = *rb;
+
+ if (new->tid < p->tid)
+ rb = &(*rb)->rb_left;
+ else if (new->tid > p->tid)
+ rb = &(*rb)->rb_right;
+ else
+ BUG_ON("inserting invalid thread_stat\n");
+ }
+
+ rb_link_node(&new->rb, parent, rb);
+ rb_insert_color(&new->rb, &thread_stats);
+}
+
+static struct thread_stat *thread_stat_findnew_after_first(u32 tid)
+{
+ struct thread_stat *st;
+
+ st = thread_stat_find(tid);
+ if (st)
+ return st;
+
+ st = zalloc(sizeof(struct thread_stat));
+ if (!st) {
+ pr_err("memory allocation failed\n");
+ return NULL;
+ }
+
+ st->tid = tid;
+ INIT_LIST_HEAD(&st->seq_list);
+
+ thread_stat_insert(st);
+
+ return st;
+}
+
+static struct thread_stat *thread_stat_findnew_first(u32 tid);
+static struct thread_stat *(*thread_stat_findnew)(u32 tid) =
+ thread_stat_findnew_first;
+
+static struct thread_stat *thread_stat_findnew_first(u32 tid)
+{
+ struct thread_stat *st;
+
+ st = zalloc(sizeof(struct thread_stat));
+ if (!st) {
+ pr_err("memory allocation failed\n");
+ return NULL;
+ }
+ st->tid = tid;
+ INIT_LIST_HEAD(&st->seq_list);
+
+ rb_link_node(&st->rb, NULL, &thread_stats.rb_node);
+ rb_insert_color(&st->rb, &thread_stats);
+
+ thread_stat_findnew = thread_stat_findnew_after_first;
+ return st;
+}
+
+/* build simple key function one is bigger than two */
+#define SINGLE_KEY(member) \
+ static int lock_stat_key_ ## member(struct lock_stat *one, \
+ struct lock_stat *two) \
+ { \
+ return one->member > two->member; \
+ }
+
+SINGLE_KEY(nr_acquired)
+SINGLE_KEY(nr_contended)
+SINGLE_KEY(avg_wait_time)
+SINGLE_KEY(wait_time_total)
+SINGLE_KEY(wait_time_max)
+
+static int lock_stat_key_wait_time_min(struct lock_stat *one,
+ struct lock_stat *two)
+{
+ u64 s1 = one->wait_time_min;
+ u64 s2 = two->wait_time_min;
+ if (s1 == ULLONG_MAX)
+ s1 = 0;
+ if (s2 == ULLONG_MAX)
+ s2 = 0;
+ return s1 > s2;
+}
+
+struct lock_key {
+ /*
+ * name: the value for specify by user
+ * this should be simpler than raw name of member
+ * e.g. nr_acquired -> acquired, wait_time_total -> wait_total
+ */
+ const char *name;
+ int (*key)(struct lock_stat*, struct lock_stat*);
+};
+
+static const char *sort_key = "acquired";
+
+static int (*compare)(struct lock_stat *, struct lock_stat *);
+
+static struct rb_root result; /* place to store sorted data */
+
+#define DEF_KEY_LOCK(name, fn_suffix) \
+ { #name, lock_stat_key_ ## fn_suffix }
+struct lock_key keys[] = {
+ DEF_KEY_LOCK(acquired, nr_acquired),
+ DEF_KEY_LOCK(contended, nr_contended),
+ DEF_KEY_LOCK(avg_wait, avg_wait_time),
+ DEF_KEY_LOCK(wait_total, wait_time_total),
+ DEF_KEY_LOCK(wait_min, wait_time_min),
+ DEF_KEY_LOCK(wait_max, wait_time_max),
+
+ /* extra comparisons much complicated should be here */
+
+ { NULL, NULL }
+};
+
+static int select_key(void)
+{
+ int i;
+
+ for (i = 0; keys[i].name; i++) {
+ if (!strcmp(keys[i].name, sort_key)) {
+ compare = keys[i].key;
+ return 0;
+ }
+ }
+
+ pr_err("Unknown compare key: %s\n", sort_key);
+
+ return -1;
+}
+
+static void insert_to_result(struct lock_stat *st,
+ int (*bigger)(struct lock_stat *, struct lock_stat *))
+{
+ struct rb_node **rb = &result.rb_node;
+ struct rb_node *parent = NULL;
+ struct lock_stat *p;
+
+ while (*rb) {
+ p = container_of(*rb, struct lock_stat, rb);
+ parent = *rb;
+
+ if (bigger(st, p))
+ rb = &(*rb)->rb_left;
+ else
+ rb = &(*rb)->rb_right;
+ }
+
+ rb_link_node(&st->rb, parent, rb);
+ rb_insert_color(&st->rb, &result);
+}
+
+/* returns left most element of result, and erase it */
+static struct lock_stat *pop_from_result(void)
+{
+ struct rb_node *node = result.rb_node;
+
+ if (!node)
+ return NULL;
+
+ while (node->rb_left)
+ node = node->rb_left;
+
+ rb_erase(node, &result);
+ return container_of(node, struct lock_stat, rb);
+}
+
+static struct lock_stat *lock_stat_findnew(void *addr, const char *name)
+{
+ struct list_head *entry = lockhashentry(addr);
+ struct lock_stat *ret, *new;
+
+ list_for_each_entry(ret, entry, hash_entry) {
+ if (ret->addr == addr)
+ return ret;
+ }
+
+ new = zalloc(sizeof(struct lock_stat));
+ if (!new)
+ goto alloc_failed;
+
+ new->addr = addr;
+ new->name = zalloc(sizeof(char) * strlen(name) + 1);
+ if (!new->name) {
+ free(new);
+ goto alloc_failed;
+ }
+
+ strcpy(new->name, name);
+ new->wait_time_min = ULLONG_MAX;
+
+ list_add(&new->hash_entry, entry);
+ return new;
+
+alloc_failed:
+ pr_err("memory allocation failed\n");
+ return NULL;
+}
+
+struct trace_lock_handler {
+ int (*acquire_event)(struct perf_evsel *evsel,
+ struct perf_sample *sample);
+
+ int (*acquired_event)(struct perf_evsel *evsel,
+ struct perf_sample *sample);
+
+ int (*contended_event)(struct perf_evsel *evsel,
+ struct perf_sample *sample);
+
+ int (*release_event)(struct perf_evsel *evsel,
+ struct perf_sample *sample);
+};
+
+static struct lock_seq_stat *get_seq(struct thread_stat *ts, void *addr)
+{
+ struct lock_seq_stat *seq;
+
+ list_for_each_entry(seq, &ts->seq_list, list) {
+ if (seq->addr == addr)
+ return seq;
+ }
+
+ seq = zalloc(sizeof(struct lock_seq_stat));
+ if (!seq) {
+ pr_err("memory allocation failed\n");
+ return NULL;
+ }
+ seq->state = SEQ_STATE_UNINITIALIZED;
+ seq->addr = addr;
+
+ list_add(&seq->list, &ts->seq_list);
+ return seq;
+}
+
+enum broken_state {
+ BROKEN_ACQUIRE,
+ BROKEN_ACQUIRED,
+ BROKEN_CONTENDED,
+ BROKEN_RELEASE,
+ BROKEN_MAX,
+};
+
+static int bad_hist[BROKEN_MAX];
+
+enum acquire_flags {
+ TRY_LOCK = 1,
+ READ_LOCK = 2,
+};
+
+static int report_lock_acquire_event(struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ void *addr;
+ struct lock_stat *ls;
+ struct thread_stat *ts;
+ struct lock_seq_stat *seq;
+ const char *name = perf_evsel__strval(evsel, sample, "name");
+ u64 tmp = perf_evsel__intval(evsel, sample, "lockdep_addr");
+ int flag = perf_evsel__intval(evsel, sample, "flag");
+
+ memcpy(&addr, &tmp, sizeof(void *));
+
+ ls = lock_stat_findnew(addr, name);
+ if (!ls)
+ return -ENOMEM;
+ if (ls->discard)
+ return 0;
+
+ ts = thread_stat_findnew(sample->tid);
+ if (!ts)
+ return -ENOMEM;
+
+ seq = get_seq(ts, addr);
+ if (!seq)
+ return -ENOMEM;
+
+ switch (seq->state) {
+ case SEQ_STATE_UNINITIALIZED:
+ case SEQ_STATE_RELEASED:
+ if (!flag) {
+ seq->state = SEQ_STATE_ACQUIRING;
+ } else {
+ if (flag & TRY_LOCK)
+ ls->nr_trylock++;
+ if (flag & READ_LOCK)
+ ls->nr_readlock++;
+ seq->state = SEQ_STATE_READ_ACQUIRED;
+ seq->read_count = 1;
+ ls->nr_acquired++;
+ }
+ break;
+ case SEQ_STATE_READ_ACQUIRED:
+ if (flag & READ_LOCK) {
+ seq->read_count++;
+ ls->nr_acquired++;
+ goto end;
+ } else {
+ goto broken;
+ }
+ break;
+ case SEQ_STATE_ACQUIRED:
+ case SEQ_STATE_ACQUIRING:
+ case SEQ_STATE_CONTENDED:
+broken:
+ /* broken lock sequence, discard it */
+ ls->discard = 1;
+ bad_hist[BROKEN_ACQUIRE]++;
+ list_del(&seq->list);
+ free(seq);
+ goto end;
+ default:
+ BUG_ON("Unknown state of lock sequence found!\n");
+ break;
+ }
+
+ ls->nr_acquire++;
+ seq->prev_event_time = sample->time;
+end:
+ return 0;
+}
+
+static int report_lock_acquired_event(struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ void *addr;
+ struct lock_stat *ls;
+ struct thread_stat *ts;
+ struct lock_seq_stat *seq;
+ u64 contended_term;
+ const char *name = perf_evsel__strval(evsel, sample, "name");
+ u64 tmp = perf_evsel__intval(evsel, sample, "lockdep_addr");
+
+ memcpy(&addr, &tmp, sizeof(void *));
+
+ ls = lock_stat_findnew(addr, name);
+ if (!ls)
+ return -ENOMEM;
+ if (ls->discard)
+ return 0;
+
+ ts = thread_stat_findnew(sample->tid);
+ if (!ts)
+ return -ENOMEM;
+
+ seq = get_seq(ts, addr);
+ if (!seq)
+ return -ENOMEM;
+
+ switch (seq->state) {
+ case SEQ_STATE_UNINITIALIZED:
+ /* orphan event, do nothing */
+ return 0;
+ case SEQ_STATE_ACQUIRING:
+ break;
+ case SEQ_STATE_CONTENDED:
+ contended_term = sample->time - seq->prev_event_time;
+ ls->wait_time_total += contended_term;
+ if (contended_term < ls->wait_time_min)
+ ls->wait_time_min = contended_term;
+ if (ls->wait_time_max < contended_term)
+ ls->wait_time_max = contended_term;
+ break;
+ case SEQ_STATE_RELEASED:
+ case SEQ_STATE_ACQUIRED:
+ case SEQ_STATE_READ_ACQUIRED:
+ /* broken lock sequence, discard it */
+ ls->discard = 1;
+ bad_hist[BROKEN_ACQUIRED]++;
+ list_del(&seq->list);
+ free(seq);
+ goto end;
+ default:
+ BUG_ON("Unknown state of lock sequence found!\n");
+ break;
+ }
+
+ seq->state = SEQ_STATE_ACQUIRED;
+ ls->nr_acquired++;
+ ls->avg_wait_time = ls->nr_contended ? ls->wait_time_total/ls->nr_contended : 0;
+ seq->prev_event_time = sample->time;
+end:
+ return 0;
+}
+
+static int report_lock_contended_event(struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ void *addr;
+ struct lock_stat *ls;
+ struct thread_stat *ts;
+ struct lock_seq_stat *seq;
+ const char *name = perf_evsel__strval(evsel, sample, "name");
+ u64 tmp = perf_evsel__intval(evsel, sample, "lockdep_addr");
+
+ memcpy(&addr, &tmp, sizeof(void *));
+
+ ls = lock_stat_findnew(addr, name);
+ if (!ls)
+ return -ENOMEM;
+ if (ls->discard)
+ return 0;
+
+ ts = thread_stat_findnew(sample->tid);
+ if (!ts)
+ return -ENOMEM;
+
+ seq = get_seq(ts, addr);
+ if (!seq)
+ return -ENOMEM;
+
+ switch (seq->state) {
+ case SEQ_STATE_UNINITIALIZED:
+ /* orphan event, do nothing */
+ return 0;
+ case SEQ_STATE_ACQUIRING:
+ break;
+ case SEQ_STATE_RELEASED:
+ case SEQ_STATE_ACQUIRED:
+ case SEQ_STATE_READ_ACQUIRED:
+ case SEQ_STATE_CONTENDED:
+ /* broken lock sequence, discard it */
+ ls->discard = 1;
+ bad_hist[BROKEN_CONTENDED]++;
+ list_del(&seq->list);
+ free(seq);
+ goto end;
+ default:
+ BUG_ON("Unknown state of lock sequence found!\n");
+ break;
+ }
+
+ seq->state = SEQ_STATE_CONTENDED;
+ ls->nr_contended++;
+ ls->avg_wait_time = ls->wait_time_total/ls->nr_contended;
+ seq->prev_event_time = sample->time;
+end:
+ return 0;
+}
+
+static int report_lock_release_event(struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ void *addr;
+ struct lock_stat *ls;
+ struct thread_stat *ts;
+ struct lock_seq_stat *seq;
+ const char *name = perf_evsel__strval(evsel, sample, "name");
+ u64 tmp = perf_evsel__intval(evsel, sample, "lockdep_addr");
+
+ memcpy(&addr, &tmp, sizeof(void *));
+
+ ls = lock_stat_findnew(addr, name);
+ if (!ls)
+ return -ENOMEM;
+ if (ls->discard)
+ return 0;
+
+ ts = thread_stat_findnew(sample->tid);
+ if (!ts)
+ return -ENOMEM;
+
+ seq = get_seq(ts, addr);
+ if (!seq)
+ return -ENOMEM;
+
+ switch (seq->state) {
+ case SEQ_STATE_UNINITIALIZED:
+ goto end;
+ case SEQ_STATE_ACQUIRED:
+ break;
+ case SEQ_STATE_READ_ACQUIRED:
+ seq->read_count--;
+ BUG_ON(seq->read_count < 0);
+ if (!seq->read_count) {
+ ls->nr_release++;
+ goto end;
+ }
+ break;
+ case SEQ_STATE_ACQUIRING:
+ case SEQ_STATE_CONTENDED:
+ case SEQ_STATE_RELEASED:
+ /* broken lock sequence, discard it */
+ ls->discard = 1;
+ bad_hist[BROKEN_RELEASE]++;
+ goto free_seq;
+ default:
+ BUG_ON("Unknown state of lock sequence found!\n");
+ break;
+ }
+
+ ls->nr_release++;
+free_seq:
+ list_del(&seq->list);
+ free(seq);
+end:
+ return 0;
+}
+
+/* lock oriented handlers */
+/* TODO: handlers for CPU oriented, thread oriented */
+static struct trace_lock_handler report_lock_ops = {
+ .acquire_event = report_lock_acquire_event,
+ .acquired_event = report_lock_acquired_event,
+ .contended_event = report_lock_contended_event,
+ .release_event = report_lock_release_event,
+};
+
+static struct trace_lock_handler *trace_handler;
+
+static int perf_evsel__process_lock_acquire(struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ if (trace_handler->acquire_event)
+ return trace_handler->acquire_event(evsel, sample);
+ return 0;
+}
+
+static int perf_evsel__process_lock_acquired(struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ if (trace_handler->acquired_event)
+ return trace_handler->acquired_event(evsel, sample);
+ return 0;
+}
+
+static int perf_evsel__process_lock_contended(struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ if (trace_handler->contended_event)
+ return trace_handler->contended_event(evsel, sample);
+ return 0;
+}
+
+static int perf_evsel__process_lock_release(struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ if (trace_handler->release_event)
+ return trace_handler->release_event(evsel, sample);
+ return 0;
+}
+
+static void print_bad_events(int bad, int total)
+{
+ /* Output for debug, this have to be removed */
+ int i;
+ const char *name[4] =
+ { "acquire", "acquired", "contended", "release" };
+
+ pr_info("\n=== output for debug===\n\n");
+ pr_info("bad: %d, total: %d\n", bad, total);
+ pr_info("bad rate: %.2f %%\n", (double)bad / (double)total * 100);
+ pr_info("histogram of events caused bad sequence\n");
+ for (i = 0; i < BROKEN_MAX; i++)
+ pr_info(" %10s: %d\n", name[i], bad_hist[i]);
+}
+
+/* TODO: various way to print, coloring, nano or milli sec */
+static void print_result(void)
+{
+ struct lock_stat *st;
+ char cut_name[20];
+ int bad, total;
+
+ pr_info("%20s ", "Name");
+ pr_info("%10s ", "acquired");
+ pr_info("%10s ", "contended");
+
+ pr_info("%15s ", "avg wait (ns)");
+ pr_info("%15s ", "total wait (ns)");
+ pr_info("%15s ", "max wait (ns)");
+ pr_info("%15s ", "min wait (ns)");
+
+ pr_info("\n\n");
+
+ bad = total = 0;
+ while ((st = pop_from_result())) {
+ total++;
+ if (st->discard) {
+ bad++;
+ continue;
+ }
+ bzero(cut_name, 20);
+
+ if (strlen(st->name) < 16) {
+ /* output raw name */
+ pr_info("%20s ", st->name);
+ } else {
+ strncpy(cut_name, st->name, 16);
+ cut_name[16] = '.';
+ cut_name[17] = '.';
+ cut_name[18] = '.';
+ cut_name[19] = '\0';
+ /* cut off name for saving output style */
+ pr_info("%20s ", cut_name);
+ }
+
+ pr_info("%10u ", st->nr_acquired);
+ pr_info("%10u ", st->nr_contended);
+
+ pr_info("%15" PRIu64 " ", st->avg_wait_time);
+ pr_info("%15" PRIu64 " ", st->wait_time_total);
+ pr_info("%15" PRIu64 " ", st->wait_time_max);
+ pr_info("%15" PRIu64 " ", st->wait_time_min == ULLONG_MAX ?
+ 0 : st->wait_time_min);
+ pr_info("\n");
+ }
+
+ print_bad_events(bad, total);
+}
+
+static bool info_threads, info_map;
+
+static void dump_threads(void)
+{
+ struct thread_stat *st;
+ struct rb_node *node;
+ struct thread *t;
+
+ pr_info("%10s: comm\n", "Thread ID");
+
+ node = rb_first(&thread_stats);
+ while (node) {
+ st = container_of(node, struct thread_stat, rb);
+ t = perf_session__findnew(session, st->tid);
+ pr_info("%10d: %s\n", st->tid, thread__comm_str(t));
+ node = rb_next(node);
+ };
+}
+
+static void dump_map(void)
+{
+ unsigned int i;
+ struct lock_stat *st;
+
+ pr_info("Address of instance: name of class\n");
+ for (i = 0; i < LOCKHASH_SIZE; i++) {
+ list_for_each_entry(st, &lockhash_table[i], hash_entry) {
+ pr_info(" %p: %s\n", st->addr, st->name);
+ }
+ }
+}
+
+static int dump_info(void)
+{
+ int rc = 0;
+
+ if (info_threads)
+ dump_threads();
+ else if (info_map)
+ dump_map();
+ else {
+ rc = -1;
+ pr_err("Unknown type of information\n");
+ }
+
+ return rc;
+}
+
+typedef int (*tracepoint_handler)(struct perf_evsel *evsel,
+ struct perf_sample *sample);
+
+static int process_sample_event(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ struct thread *thread = machine__findnew_thread(machine, sample->pid,
+ sample->tid);
+
+ if (thread == NULL) {
+ pr_debug("problem processing %d event, skipping it.\n",
+ event->header.type);
+ return -1;
+ }
+
+ if (evsel->handler != NULL) {
+ tracepoint_handler f = evsel->handler;
+ return f(evsel, sample);
+ }
+
+ return 0;
+}
+
+static void sort_result(void)
+{
+ unsigned int i;
+ struct lock_stat *st;
+
+ for (i = 0; i < LOCKHASH_SIZE; i++) {
+ list_for_each_entry(st, &lockhash_table[i], hash_entry) {
+ insert_to_result(st, compare);
+ }
+ }
+}
+
+static const struct perf_evsel_str_handler lock_tracepoints[] = {
+ { "lock:lock_acquire", perf_evsel__process_lock_acquire, }, /* CONFIG_LOCKDEP */
+ { "lock:lock_acquired", perf_evsel__process_lock_acquired, }, /* CONFIG_LOCKDEP, CONFIG_LOCK_STAT */
+ { "lock:lock_contended", perf_evsel__process_lock_contended, }, /* CONFIG_LOCKDEP, CONFIG_LOCK_STAT */
+ { "lock:lock_release", perf_evsel__process_lock_release, }, /* CONFIG_LOCKDEP */
+};
+
+static int __cmd_report(bool display_info)
+{
+ int err = -EINVAL;
+ struct perf_tool eops = {
+ .sample = process_sample_event,
+ .comm = perf_event__process_comm,
+ .ordered_samples = true,
+ };
+ struct perf_data_file file = {
+ .path = input_name,
+ .mode = PERF_DATA_MODE_READ,
+ };
+
+ session = perf_session__new(&file, false, &eops);
+ if (!session) {
+ pr_err("Initializing perf session failed\n");
+ return -ENOMEM;
+ }
+
+ if (!perf_session__has_traces(session, "lock record"))
+ goto out_delete;
+
+ if (perf_session__set_tracepoints_handlers(session, lock_tracepoints)) {
+ pr_err("Initializing perf session tracepoint handlers failed\n");
+ goto out_delete;
+ }
+
+ if (select_key())
+ goto out_delete;
+
+ err = perf_session__process_events(session, &eops);
+ if (err)
+ goto out_delete;
+
+ setup_pager();
+ if (display_info) /* used for info subcommand */
+ err = dump_info();
+ else {
+ sort_result();
+ print_result();
+ }
+
+out_delete:
+ perf_session__delete(session);
+ return err;
+}
+
+static int __cmd_record(int argc, const char **argv)
+{
+ const char *record_args[] = {
+ "record", "-R", "-m", "1024", "-c", "1",
+ };
+ unsigned int rec_argc, i, j, ret;
+ const char **rec_argv;
+
+ for (i = 0; i < ARRAY_SIZE(lock_tracepoints); i++) {
+ if (!is_valid_tracepoint(lock_tracepoints[i].name)) {
+ pr_err("tracepoint %s is not enabled. "
+ "Are CONFIG_LOCKDEP and CONFIG_LOCK_STAT enabled?\n",
+ lock_tracepoints[i].name);
+ return 1;
+ }
+ }
+
+ rec_argc = ARRAY_SIZE(record_args) + argc - 1;
+ /* factor of 2 is for -e in front of each tracepoint */
+ rec_argc += 2 * ARRAY_SIZE(lock_tracepoints);
+
+ rec_argv = calloc(rec_argc + 1, sizeof(char *));
+ if (!rec_argv)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(record_args); i++)
+ rec_argv[i] = strdup(record_args[i]);
+
+ for (j = 0; j < ARRAY_SIZE(lock_tracepoints); j++) {
+ rec_argv[i++] = "-e";
+ rec_argv[i++] = strdup(lock_tracepoints[j].name);
+ }
+
+ for (j = 1; j < (unsigned int)argc; j++, i++)
+ rec_argv[i] = argv[j];
+
+ BUG_ON(i != rec_argc);
+
+ ret = cmd_record(i, rec_argv, NULL);
+ free(rec_argv);
+ return ret;
+}
+
+int cmd_lock(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ const struct option info_options[] = {
+ OPT_BOOLEAN('t', "threads", &info_threads,
+ "dump thread list in perf.data"),
+ OPT_BOOLEAN('m', "map", &info_map,
+ "map of lock instances (address:name table)"),
+ OPT_END()
+ };
+ const struct option lock_options[] = {
+ OPT_STRING('i', "input", &input_name, "file", "input file name"),
+ OPT_INCR('v', "verbose", &verbose, "be more verbose (show symbol address, etc)"),
+ OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, "dump raw trace in ASCII"),
+ OPT_END()
+ };
+ const struct option report_options[] = {
+ OPT_STRING('k', "key", &sort_key, "acquired",
+ "key for sorting (acquired / contended / avg_wait / wait_total / wait_max / wait_min)"),
+ /* TODO: type */
+ OPT_END()
+ };
+ const char * const info_usage[] = {
+ "perf lock info [<options>]",
+ NULL
+ };
+ const char *const lock_subcommands[] = { "record", "report", "script",
+ "info", NULL };
+ const char *lock_usage[] = {
+ NULL,
+ NULL
+ };
+ const char * const report_usage[] = {
+ "perf lock report [<options>]",
+ NULL
+ };
+ unsigned int i;
+ int rc = 0;
+
+ symbol__init();
+ for (i = 0; i < LOCKHASH_SIZE; i++)
+ INIT_LIST_HEAD(lockhash_table + i);
+
+ argc = parse_options_subcommand(argc, argv, lock_options, lock_subcommands,
+ lock_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+ if (!argc)
+ usage_with_options(lock_usage, lock_options);
+
+ if (!strncmp(argv[0], "rec", 3)) {
+ return __cmd_record(argc, argv);
+ } else if (!strncmp(argv[0], "report", 6)) {
+ trace_handler = &report_lock_ops;
+ if (argc) {
+ argc = parse_options(argc, argv,
+ report_options, report_usage, 0);
+ if (argc)
+ usage_with_options(report_usage, report_options);
+ }
+ rc = __cmd_report(false);
+ } else if (!strcmp(argv[0], "script")) {
+ /* Aliased to 'perf script' */
+ return cmd_script(argc, argv, prefix);
+ } else if (!strcmp(argv[0], "info")) {
+ if (argc) {
+ argc = parse_options(argc, argv,
+ info_options, info_usage, 0);
+ if (argc)
+ usage_with_options(info_usage, info_options);
+ }
+ /* recycling report_lock_ops */
+ trace_handler = &report_lock_ops;
+ rc = __cmd_report(true);
+ } else {
+ usage_with_options(lock_usage, lock_options);
+ }
+
+ return rc;
+}
diff --git a/tools/perf/builtin-mem.c b/tools/perf/builtin-mem.c
new file mode 100644
index 00000000000..4a1a6c94a5e
--- /dev/null
+++ b/tools/perf/builtin-mem.c
@@ -0,0 +1,246 @@
+#include "builtin.h"
+#include "perf.h"
+
+#include "util/parse-options.h"
+#include "util/trace-event.h"
+#include "util/tool.h"
+#include "util/session.h"
+#include "util/data.h"
+
+#define MEM_OPERATION_LOAD "load"
+#define MEM_OPERATION_STORE "store"
+
+static const char *mem_operation = MEM_OPERATION_LOAD;
+
+struct perf_mem {
+ struct perf_tool tool;
+ char const *input_name;
+ bool hide_unresolved;
+ bool dump_raw;
+ const char *cpu_list;
+ DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS);
+};
+
+static int __cmd_record(int argc, const char **argv)
+{
+ int rec_argc, i = 0, j;
+ const char **rec_argv;
+ char event[64];
+ int ret;
+
+ rec_argc = argc + 4;
+ rec_argv = calloc(rec_argc + 1, sizeof(char *));
+ if (!rec_argv)
+ return -1;
+
+ rec_argv[i++] = strdup("record");
+ if (!strcmp(mem_operation, MEM_OPERATION_LOAD))
+ rec_argv[i++] = strdup("-W");
+ rec_argv[i++] = strdup("-d");
+ rec_argv[i++] = strdup("-e");
+
+ if (strcmp(mem_operation, MEM_OPERATION_LOAD))
+ sprintf(event, "cpu/mem-stores/pp");
+ else
+ sprintf(event, "cpu/mem-loads/pp");
+
+ rec_argv[i++] = strdup(event);
+ for (j = 1; j < argc; j++, i++)
+ rec_argv[i] = argv[j];
+
+ ret = cmd_record(i, rec_argv, NULL);
+ free(rec_argv);
+ return ret;
+}
+
+static int
+dump_raw_samples(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct perf_mem *mem = container_of(tool, struct perf_mem, tool);
+ struct addr_location al;
+ const char *fmt;
+
+ if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) {
+ fprintf(stderr, "problem processing %d event, skipping it.\n",
+ event->header.type);
+ return -1;
+ }
+
+ if (al.filtered || (mem->hide_unresolved && al.sym == NULL))
+ return 0;
+
+ if (al.map != NULL)
+ al.map->dso->hit = 1;
+
+ if (symbol_conf.field_sep) {
+ fmt = "%d%s%d%s0x%"PRIx64"%s0x%"PRIx64"%s%"PRIu64
+ "%s0x%"PRIx64"%s%s:%s\n";
+ } else {
+ fmt = "%5d%s%5d%s0x%016"PRIx64"%s0x016%"PRIx64
+ "%s%5"PRIu64"%s0x%06"PRIx64"%s%s:%s\n";
+ symbol_conf.field_sep = " ";
+ }
+
+ printf(fmt,
+ sample->pid,
+ symbol_conf.field_sep,
+ sample->tid,
+ symbol_conf.field_sep,
+ sample->ip,
+ symbol_conf.field_sep,
+ sample->addr,
+ symbol_conf.field_sep,
+ sample->weight,
+ symbol_conf.field_sep,
+ sample->data_src,
+ symbol_conf.field_sep,
+ al.map ? (al.map->dso ? al.map->dso->long_name : "???") : "???",
+ al.sym ? al.sym->name : "???");
+
+ return 0;
+}
+
+static int process_sample_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel __maybe_unused,
+ struct machine *machine)
+{
+ return dump_raw_samples(tool, event, sample, machine);
+}
+
+static int report_raw_events(struct perf_mem *mem)
+{
+ struct perf_data_file file = {
+ .path = input_name,
+ .mode = PERF_DATA_MODE_READ,
+ };
+ int err = -EINVAL;
+ int ret;
+ struct perf_session *session = perf_session__new(&file, false,
+ &mem->tool);
+
+ if (session == NULL)
+ return -ENOMEM;
+
+ if (mem->cpu_list) {
+ ret = perf_session__cpu_bitmap(session, mem->cpu_list,
+ mem->cpu_bitmap);
+ if (ret)
+ goto out_delete;
+ }
+
+ if (symbol__init() < 0)
+ return -1;
+
+ printf("# PID, TID, IP, ADDR, LOCAL WEIGHT, DSRC, SYMBOL\n");
+
+ err = perf_session__process_events(session, &mem->tool);
+ if (err)
+ return err;
+
+ return 0;
+
+out_delete:
+ perf_session__delete(session);
+ return err;
+}
+
+static int report_events(int argc, const char **argv, struct perf_mem *mem)
+{
+ const char **rep_argv;
+ int ret, i = 0, j, rep_argc;
+
+ if (mem->dump_raw)
+ return report_raw_events(mem);
+
+ rep_argc = argc + 3;
+ rep_argv = calloc(rep_argc + 1, sizeof(char *));
+ if (!rep_argv)
+ return -1;
+
+ rep_argv[i++] = strdup("report");
+ rep_argv[i++] = strdup("--mem-mode");
+ rep_argv[i++] = strdup("-n"); /* display number of samples */
+
+ /*
+ * there is no weight (cost) associated with stores, so don't print
+ * the column
+ */
+ if (strcmp(mem_operation, MEM_OPERATION_LOAD))
+ rep_argv[i++] = strdup("--sort=mem,sym,dso,symbol_daddr,"
+ "dso_daddr,tlb,locked");
+
+ for (j = 1; j < argc; j++, i++)
+ rep_argv[i] = argv[j];
+
+ ret = cmd_report(i, rep_argv, NULL);
+ free(rep_argv);
+ return ret;
+}
+
+int cmd_mem(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ struct stat st;
+ struct perf_mem mem = {
+ .tool = {
+ .sample = process_sample_event,
+ .mmap = perf_event__process_mmap,
+ .mmap2 = perf_event__process_mmap2,
+ .comm = perf_event__process_comm,
+ .lost = perf_event__process_lost,
+ .fork = perf_event__process_fork,
+ .build_id = perf_event__process_build_id,
+ .ordered_samples = true,
+ },
+ .input_name = "perf.data",
+ };
+ const struct option mem_options[] = {
+ OPT_STRING('t', "type", &mem_operation,
+ "type", "memory operations(load/store)"),
+ OPT_BOOLEAN('D', "dump-raw-samples", &mem.dump_raw,
+ "dump raw samples in ASCII"),
+ OPT_BOOLEAN('U', "hide-unresolved", &mem.hide_unresolved,
+ "Only display entries resolved to a symbol"),
+ OPT_STRING('i', "input", &input_name, "file",
+ "input file name"),
+ OPT_STRING('C', "cpu", &mem.cpu_list, "cpu",
+ "list of cpus to profile"),
+ OPT_STRING('x', "field-separator", &symbol_conf.field_sep,
+ "separator",
+ "separator for columns, no spaces will be added"
+ " between columns '.' is reserved."),
+ OPT_END()
+ };
+ const char *const mem_subcommands[] = { "record", "report", NULL };
+ const char *mem_usage[] = {
+ NULL,
+ NULL
+ };
+
+
+ argc = parse_options_subcommand(argc, argv, mem_options, mem_subcommands,
+ mem_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (!argc || !(strncmp(argv[0], "rec", 3) || mem_operation))
+ usage_with_options(mem_usage, mem_options);
+
+ if (!mem.input_name || !strlen(mem.input_name)) {
+ if (!fstat(STDIN_FILENO, &st) && S_ISFIFO(st.st_mode))
+ mem.input_name = "-";
+ else
+ mem.input_name = "perf.data";
+ }
+
+ if (!strncmp(argv[0], "rec", 3))
+ return __cmd_record(argc, argv);
+ else if (!strncmp(argv[0], "rep", 3))
+ return report_events(argc, argv, &mem);
+ else
+ usage_with_options(mem_usage, mem_options);
+
+ return 0;
+}
diff --git a/tools/perf/builtin-probe.c b/tools/perf/builtin-probe.c
new file mode 100644
index 00000000000..c63fa292507
--- /dev/null
+++ b/tools/perf/builtin-probe.c
@@ -0,0 +1,530 @@
+/*
+ * builtin-probe.c
+ *
+ * Builtin probe command: Set up probe events by C expression
+ *
+ * Written by Masami Hiramatsu <mhiramat@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "perf.h"
+#include "builtin.h"
+#include "util/util.h"
+#include "util/strlist.h"
+#include "util/strfilter.h"
+#include "util/symbol.h"
+#include "util/debug.h"
+#include <api/fs/debugfs.h>
+#include "util/parse-options.h"
+#include "util/probe-finder.h"
+#include "util/probe-event.h"
+
+#define DEFAULT_VAR_FILTER "!__k???tab_* & !__crc_*"
+#define DEFAULT_FUNC_FILTER "!_*"
+
+/* Session management structure */
+static struct {
+ bool list_events;
+ bool force_add;
+ bool show_lines;
+ bool show_vars;
+ bool show_ext_vars;
+ bool show_funcs;
+ bool mod_events;
+ bool uprobes;
+ int nevents;
+ struct perf_probe_event events[MAX_PROBES];
+ struct strlist *dellist;
+ struct line_range line_range;
+ char *target;
+ int max_probe_points;
+ struct strfilter *filter;
+} params;
+
+/* Parse an event definition. Note that any error must die. */
+static int parse_probe_event(const char *str)
+{
+ struct perf_probe_event *pev = &params.events[params.nevents];
+ int ret;
+
+ pr_debug("probe-definition(%d): %s\n", params.nevents, str);
+ if (++params.nevents == MAX_PROBES) {
+ pr_err("Too many probes (> %d) were specified.", MAX_PROBES);
+ return -1;
+ }
+
+ pev->uprobes = params.uprobes;
+
+ /* Parse a perf-probe command into event */
+ ret = parse_perf_probe_command(str, pev);
+ pr_debug("%d arguments\n", pev->nargs);
+
+ return ret;
+}
+
+static int set_target(const char *ptr)
+{
+ int found = 0;
+ const char *buf;
+
+ /*
+ * The first argument after options can be an absolute path
+ * to an executable / library or kernel module.
+ *
+ * TODO: Support relative path, and $PATH, $LD_LIBRARY_PATH,
+ * short module name.
+ */
+ if (!params.target && ptr && *ptr == '/') {
+ params.target = strdup(ptr);
+ if (!params.target)
+ return -ENOMEM;
+
+ found = 1;
+ buf = ptr + (strlen(ptr) - 3);
+
+ if (strcmp(buf, ".ko"))
+ params.uprobes = true;
+
+ }
+
+ return found;
+}
+
+static int parse_probe_event_argv(int argc, const char **argv)
+{
+ int i, len, ret, found_target;
+ char *buf;
+
+ found_target = set_target(argv[0]);
+ if (found_target < 0)
+ return found_target;
+
+ if (found_target && argc == 1)
+ return 0;
+
+ /* Bind up rest arguments */
+ len = 0;
+ for (i = 0; i < argc; i++) {
+ if (i == 0 && found_target)
+ continue;
+
+ len += strlen(argv[i]) + 1;
+ }
+ buf = zalloc(len + 1);
+ if (buf == NULL)
+ return -ENOMEM;
+ len = 0;
+ for (i = 0; i < argc; i++) {
+ if (i == 0 && found_target)
+ continue;
+
+ len += sprintf(&buf[len], "%s ", argv[i]);
+ }
+ params.mod_events = true;
+ ret = parse_probe_event(buf);
+ free(buf);
+ return ret;
+}
+
+static int opt_add_probe_event(const struct option *opt __maybe_unused,
+ const char *str, int unset __maybe_unused)
+{
+ if (str) {
+ params.mod_events = true;
+ return parse_probe_event(str);
+ } else
+ return 0;
+}
+
+static int opt_del_probe_event(const struct option *opt __maybe_unused,
+ const char *str, int unset __maybe_unused)
+{
+ if (str) {
+ params.mod_events = true;
+ if (!params.dellist)
+ params.dellist = strlist__new(true, NULL);
+ strlist__add(params.dellist, str);
+ }
+ return 0;
+}
+
+static int opt_set_target(const struct option *opt, const char *str,
+ int unset __maybe_unused)
+{
+ int ret = -ENOENT;
+ char *tmp;
+
+ if (str && !params.target) {
+ if (!strcmp(opt->long_name, "exec"))
+ params.uprobes = true;
+#ifdef HAVE_DWARF_SUPPORT
+ else if (!strcmp(opt->long_name, "module"))
+ params.uprobes = false;
+#endif
+ else
+ return ret;
+
+ /* Expand given path to absolute path, except for modulename */
+ if (params.uprobes || strchr(str, '/')) {
+ tmp = realpath(str, NULL);
+ if (!tmp) {
+ pr_warning("Failed to get the absolute path of %s: %m\n", str);
+ return ret;
+ }
+ } else {
+ tmp = strdup(str);
+ if (!tmp)
+ return -ENOMEM;
+ }
+ params.target = tmp;
+ ret = 0;
+ }
+
+ return ret;
+}
+
+#ifdef HAVE_DWARF_SUPPORT
+static int opt_show_lines(const struct option *opt __maybe_unused,
+ const char *str, int unset __maybe_unused)
+{
+ int ret = 0;
+
+ if (!str)
+ return 0;
+
+ if (params.show_lines) {
+ pr_warning("Warning: more than one --line options are"
+ " detected. Only the first one is valid.\n");
+ return 0;
+ }
+
+ params.show_lines = true;
+ ret = parse_line_range_desc(str, &params.line_range);
+
+ return ret;
+}
+
+static int opt_show_vars(const struct option *opt __maybe_unused,
+ const char *str, int unset __maybe_unused)
+{
+ struct perf_probe_event *pev = &params.events[params.nevents];
+ int ret;
+
+ if (!str)
+ return 0;
+
+ ret = parse_probe_event(str);
+ if (!ret && pev->nargs != 0) {
+ pr_err(" Error: '--vars' doesn't accept arguments.\n");
+ return -EINVAL;
+ }
+ params.show_vars = true;
+
+ return ret;
+}
+#endif
+
+static int opt_set_filter(const struct option *opt __maybe_unused,
+ const char *str, int unset __maybe_unused)
+{
+ const char *err;
+
+ if (str) {
+ pr_debug2("Set filter: %s\n", str);
+ if (params.filter)
+ strfilter__delete(params.filter);
+ params.filter = strfilter__new(str, &err);
+ if (!params.filter) {
+ pr_err("Filter parse error at %td.\n", err - str + 1);
+ pr_err("Source: \"%s\"\n", str);
+ pr_err(" %*c\n", (int)(err - str + 1), '^');
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int init_params(void)
+{
+ return line_range__init(&params.line_range);
+}
+
+static void cleanup_params(void)
+{
+ int i;
+
+ for (i = 0; i < params.nevents; i++)
+ clear_perf_probe_event(params.events + i);
+ if (params.dellist)
+ strlist__delete(params.dellist);
+ line_range__clear(&params.line_range);
+ free(params.target);
+ if (params.filter)
+ strfilter__delete(params.filter);
+ memset(&params, 0, sizeof(params));
+}
+
+static void pr_err_with_code(const char *msg, int err)
+{
+ pr_err("%s", msg);
+ pr_debug(" Reason: %s (Code: %d)", strerror(-err), err);
+ pr_err("\n");
+}
+
+static int
+__cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ const char * const probe_usage[] = {
+ "perf probe [<options>] 'PROBEDEF' ['PROBEDEF' ...]",
+ "perf probe [<options>] --add 'PROBEDEF' [--add 'PROBEDEF' ...]",
+ "perf probe [<options>] --del '[GROUP:]EVENT' ...",
+ "perf probe --list",
+#ifdef HAVE_DWARF_SUPPORT
+ "perf probe [<options>] --line 'LINEDESC'",
+ "perf probe [<options>] --vars 'PROBEPOINT'",
+#endif
+ NULL
+};
+ const struct option options[] = {
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show parsed arguments, etc)"),
+ OPT_BOOLEAN('l', "list", &params.list_events,
+ "list up current probe events"),
+ OPT_CALLBACK('d', "del", NULL, "[GROUP:]EVENT", "delete a probe event.",
+ opt_del_probe_event),
+ OPT_CALLBACK('a', "add", NULL,
+#ifdef HAVE_DWARF_SUPPORT
+ "[EVENT=]FUNC[@SRC][+OFF|%return|:RL|;PT]|SRC:AL|SRC;PT"
+ " [[NAME=]ARG ...]",
+#else
+ "[EVENT=]FUNC[+OFF|%return] [[NAME=]ARG ...]",
+#endif
+ "probe point definition, where\n"
+ "\t\tGROUP:\tGroup name (optional)\n"
+ "\t\tEVENT:\tEvent name\n"
+ "\t\tFUNC:\tFunction name\n"
+ "\t\tOFF:\tOffset from function entry (in byte)\n"
+ "\t\t%return:\tPut the probe at function return\n"
+#ifdef HAVE_DWARF_SUPPORT
+ "\t\tSRC:\tSource code path\n"
+ "\t\tRL:\tRelative line number from function entry.\n"
+ "\t\tAL:\tAbsolute line number in file.\n"
+ "\t\tPT:\tLazy expression of line code.\n"
+ "\t\tARG:\tProbe argument (local variable name or\n"
+ "\t\t\tkprobe-tracer argument format.)\n",
+#else
+ "\t\tARG:\tProbe argument (kprobe-tracer argument format.)\n",
+#endif
+ opt_add_probe_event),
+ OPT_BOOLEAN('f', "force", &params.force_add, "forcibly add events"
+ " with existing name"),
+#ifdef HAVE_DWARF_SUPPORT
+ OPT_CALLBACK('L', "line", NULL,
+ "FUNC[:RLN[+NUM|-RLN2]]|SRC:ALN[+NUM|-ALN2]",
+ "Show source code lines.", opt_show_lines),
+ OPT_CALLBACK('V', "vars", NULL,
+ "FUNC[@SRC][+OFF|%return|:RL|;PT]|SRC:AL|SRC;PT",
+ "Show accessible variables on PROBEDEF", opt_show_vars),
+ OPT_BOOLEAN('\0', "externs", &params.show_ext_vars,
+ "Show external variables too (with --vars only)"),
+ OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name,
+ "file", "vmlinux pathname"),
+ OPT_STRING('s', "source", &symbol_conf.source_prefix,
+ "directory", "path to kernel source"),
+ OPT_CALLBACK('m', "module", NULL, "modname|path",
+ "target module name (for online) or path (for offline)",
+ opt_set_target),
+#endif
+ OPT__DRY_RUN(&probe_event_dry_run),
+ OPT_INTEGER('\0', "max-probes", &params.max_probe_points,
+ "Set how many probe points can be found for a probe."),
+ OPT_BOOLEAN('F', "funcs", &params.show_funcs,
+ "Show potential probe-able functions."),
+ OPT_CALLBACK('\0', "filter", NULL,
+ "[!]FILTER", "Set a filter (with --vars/funcs only)\n"
+ "\t\t\t(default: \"" DEFAULT_VAR_FILTER "\" for --vars,\n"
+ "\t\t\t \"" DEFAULT_FUNC_FILTER "\" for --funcs)",
+ opt_set_filter),
+ OPT_CALLBACK('x', "exec", NULL, "executable|path",
+ "target executable name or path", opt_set_target),
+ OPT_BOOLEAN(0, "demangle", &symbol_conf.demangle,
+ "Disable symbol demangling"),
+ OPT_END()
+ };
+ int ret;
+
+ argc = parse_options(argc, argv, options, probe_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+ if (argc > 0) {
+ if (strcmp(argv[0], "-") == 0) {
+ pr_warning(" Error: '-' is not supported.\n");
+ usage_with_options(probe_usage, options);
+ }
+ ret = parse_probe_event_argv(argc, argv);
+ if (ret < 0) {
+ pr_err_with_code(" Error: Command Parse Error.", ret);
+ return ret;
+ }
+ }
+
+ if (params.max_probe_points == 0)
+ params.max_probe_points = MAX_PROBES;
+
+ if ((!params.nevents && !params.dellist && !params.list_events &&
+ !params.show_lines && !params.show_funcs))
+ usage_with_options(probe_usage, options);
+
+ /*
+ * Only consider the user's kernel image path if given.
+ */
+ symbol_conf.try_vmlinux_path = (symbol_conf.vmlinux_name == NULL);
+
+ if (params.list_events) {
+ if (params.mod_events) {
+ pr_err(" Error: Don't use --list with --add/--del.\n");
+ usage_with_options(probe_usage, options);
+ }
+ if (params.show_lines) {
+ pr_err(" Error: Don't use --list with --line.\n");
+ usage_with_options(probe_usage, options);
+ }
+ if (params.show_vars) {
+ pr_err(" Error: Don't use --list with --vars.\n");
+ usage_with_options(probe_usage, options);
+ }
+ if (params.show_funcs) {
+ pr_err(" Error: Don't use --list with --funcs.\n");
+ usage_with_options(probe_usage, options);
+ }
+ if (params.uprobes) {
+ pr_warning(" Error: Don't use --list with --exec.\n");
+ usage_with_options(probe_usage, options);
+ }
+ ret = show_perf_probe_events();
+ if (ret < 0)
+ pr_err_with_code(" Error: Failed to show event list.", ret);
+ return ret;
+ }
+ if (params.show_funcs) {
+ if (params.nevents != 0 || params.dellist) {
+ pr_err(" Error: Don't use --funcs with"
+ " --add/--del.\n");
+ usage_with_options(probe_usage, options);
+ }
+ if (params.show_lines) {
+ pr_err(" Error: Don't use --funcs with --line.\n");
+ usage_with_options(probe_usage, options);
+ }
+ if (params.show_vars) {
+ pr_err(" Error: Don't use --funcs with --vars.\n");
+ usage_with_options(probe_usage, options);
+ }
+ if (!params.filter)
+ params.filter = strfilter__new(DEFAULT_FUNC_FILTER,
+ NULL);
+ ret = show_available_funcs(params.target, params.filter,
+ params.uprobes);
+ strfilter__delete(params.filter);
+ params.filter = NULL;
+ if (ret < 0)
+ pr_err_with_code(" Error: Failed to show functions.", ret);
+ return ret;
+ }
+
+#ifdef HAVE_DWARF_SUPPORT
+ if (params.show_lines) {
+ if (params.mod_events) {
+ pr_err(" Error: Don't use --line with"
+ " --add/--del.\n");
+ usage_with_options(probe_usage, options);
+ }
+ if (params.show_vars) {
+ pr_err(" Error: Don't use --line with --vars.\n");
+ usage_with_options(probe_usage, options);
+ }
+
+ ret = show_line_range(&params.line_range, params.target);
+ if (ret < 0)
+ pr_err_with_code(" Error: Failed to show lines.", ret);
+ return ret;
+ }
+ if (params.show_vars) {
+ if (params.mod_events) {
+ pr_err(" Error: Don't use --vars with"
+ " --add/--del.\n");
+ usage_with_options(probe_usage, options);
+ }
+ if (!params.filter)
+ params.filter = strfilter__new(DEFAULT_VAR_FILTER,
+ NULL);
+
+ ret = show_available_vars(params.events, params.nevents,
+ params.max_probe_points,
+ params.target,
+ params.filter,
+ params.show_ext_vars);
+ strfilter__delete(params.filter);
+ params.filter = NULL;
+ if (ret < 0)
+ pr_err_with_code(" Error: Failed to show vars.", ret);
+ return ret;
+ }
+#endif
+
+ if (params.dellist) {
+ ret = del_perf_probe_events(params.dellist);
+ if (ret < 0) {
+ pr_err_with_code(" Error: Failed to delete events.", ret);
+ return ret;
+ }
+ }
+
+ if (params.nevents) {
+ ret = add_perf_probe_events(params.events, params.nevents,
+ params.max_probe_points,
+ params.target,
+ params.force_add);
+ if (ret < 0) {
+ pr_err_with_code(" Error: Failed to add events.", ret);
+ return ret;
+ }
+ }
+ return 0;
+}
+
+int cmd_probe(int argc, const char **argv, const char *prefix)
+{
+ int ret;
+
+ ret = init_params();
+ if (!ret) {
+ ret = __cmd_probe(argc, argv, prefix);
+ cleanup_params();
+ }
+
+ return ret;
+}
diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c
new file mode 100644
index 00000000000..378b85b731a
--- /dev/null
+++ b/tools/perf/builtin-record.c
@@ -0,0 +1,960 @@
+/*
+ * builtin-record.c
+ *
+ * Builtin record command: Record the profile of a workload
+ * (or a CPU, or a PID) into the perf.data output file - for
+ * later analysis via perf report.
+ */
+#include "builtin.h"
+
+#include "perf.h"
+
+#include "util/build-id.h"
+#include "util/util.h"
+#include "util/parse-options.h"
+#include "util/parse-events.h"
+
+#include "util/header.h"
+#include "util/event.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/debug.h"
+#include "util/session.h"
+#include "util/tool.h"
+#include "util/symbol.h"
+#include "util/cpumap.h"
+#include "util/thread_map.h"
+#include "util/data.h"
+
+#include <unistd.h>
+#include <sched.h>
+#include <sys/mman.h>
+
+
+struct record {
+ struct perf_tool tool;
+ struct record_opts opts;
+ u64 bytes_written;
+ struct perf_data_file file;
+ struct perf_evlist *evlist;
+ struct perf_session *session;
+ const char *progname;
+ int realtime_prio;
+ bool no_buildid;
+ bool no_buildid_cache;
+ long samples;
+};
+
+static int record__write(struct record *rec, void *bf, size_t size)
+{
+ if (perf_data_file__write(rec->session->file, bf, size) < 0) {
+ pr_err("failed to write perf data, error: %m\n");
+ return -1;
+ }
+
+ rec->bytes_written += size;
+ return 0;
+}
+
+static int process_synthesized_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ struct record *rec = container_of(tool, struct record, tool);
+ return record__write(rec, event, event->header.size);
+}
+
+static int record__mmap_read(struct record *rec, struct perf_mmap *md)
+{
+ unsigned int head = perf_mmap__read_head(md);
+ unsigned int old = md->prev;
+ unsigned char *data = md->base + page_size;
+ unsigned long size;
+ void *buf;
+ int rc = 0;
+
+ if (old == head)
+ return 0;
+
+ rec->samples++;
+
+ size = head - old;
+
+ if ((old & md->mask) + size != (head & md->mask)) {
+ buf = &data[old & md->mask];
+ size = md->mask + 1 - (old & md->mask);
+ old += size;
+
+ if (record__write(rec, buf, size) < 0) {
+ rc = -1;
+ goto out;
+ }
+ }
+
+ buf = &data[old & md->mask];
+ size = head - old;
+ old += size;
+
+ if (record__write(rec, buf, size) < 0) {
+ rc = -1;
+ goto out;
+ }
+
+ md->prev = old;
+ perf_mmap__write_tail(md, old);
+
+out:
+ return rc;
+}
+
+static volatile int done = 0;
+static volatile int signr = -1;
+static volatile int child_finished = 0;
+
+static void sig_handler(int sig)
+{
+ if (sig == SIGCHLD)
+ child_finished = 1;
+ else
+ signr = sig;
+
+ done = 1;
+}
+
+static void record__sig_exit(void)
+{
+ if (signr == -1)
+ return;
+
+ signal(signr, SIG_DFL);
+ raise(signr);
+}
+
+static int record__open(struct record *rec)
+{
+ char msg[512];
+ struct perf_evsel *pos;
+ struct perf_evlist *evlist = rec->evlist;
+ struct perf_session *session = rec->session;
+ struct record_opts *opts = &rec->opts;
+ int rc = 0;
+
+ perf_evlist__config(evlist, opts);
+
+ evlist__for_each(evlist, pos) {
+try_again:
+ if (perf_evsel__open(pos, evlist->cpus, evlist->threads) < 0) {
+ if (perf_evsel__fallback(pos, errno, msg, sizeof(msg))) {
+ if (verbose)
+ ui__warning("%s\n", msg);
+ goto try_again;
+ }
+
+ rc = -errno;
+ perf_evsel__open_strerror(pos, &opts->target,
+ errno, msg, sizeof(msg));
+ ui__error("%s\n", msg);
+ goto out;
+ }
+ }
+
+ if (perf_evlist__apply_filters(evlist)) {
+ error("failed to set filter with %d (%s)\n", errno,
+ strerror(errno));
+ rc = -1;
+ goto out;
+ }
+
+ if (perf_evlist__mmap(evlist, opts->mmap_pages, false) < 0) {
+ if (errno == EPERM) {
+ pr_err("Permission error mapping pages.\n"
+ "Consider increasing "
+ "/proc/sys/kernel/perf_event_mlock_kb,\n"
+ "or try again with a smaller value of -m/--mmap_pages.\n"
+ "(current value: %u)\n", opts->mmap_pages);
+ rc = -errno;
+ } else {
+ pr_err("failed to mmap with %d (%s)\n", errno, strerror(errno));
+ rc = -errno;
+ }
+ goto out;
+ }
+
+ session->evlist = evlist;
+ perf_session__set_id_hdr_size(session);
+out:
+ return rc;
+}
+
+static int process_buildids(struct record *rec)
+{
+ struct perf_data_file *file = &rec->file;
+ struct perf_session *session = rec->session;
+ u64 start = session->header.data_offset;
+
+ u64 size = lseek(file->fd, 0, SEEK_CUR);
+ if (size == 0)
+ return 0;
+
+ return __perf_session__process_events(session, start,
+ size - start,
+ size, &build_id__mark_dso_hit_ops);
+}
+
+static void perf_event__synthesize_guest_os(struct machine *machine, void *data)
+{
+ int err;
+ struct perf_tool *tool = data;
+ /*
+ *As for guest kernel when processing subcommand record&report,
+ *we arrange module mmap prior to guest kernel mmap and trigger
+ *a preload dso because default guest module symbols are loaded
+ *from guest kallsyms instead of /lib/modules/XXX/XXX. This
+ *method is used to avoid symbol missing when the first addr is
+ *in module instead of in guest kernel.
+ */
+ err = perf_event__synthesize_modules(tool, process_synthesized_event,
+ machine);
+ if (err < 0)
+ pr_err("Couldn't record guest kernel [%d]'s reference"
+ " relocation symbol.\n", machine->pid);
+
+ /*
+ * We use _stext for guest kernel because guest kernel's /proc/kallsyms
+ * have no _text sometimes.
+ */
+ err = perf_event__synthesize_kernel_mmap(tool, process_synthesized_event,
+ machine);
+ if (err < 0)
+ pr_err("Couldn't record guest kernel [%d]'s reference"
+ " relocation symbol.\n", machine->pid);
+}
+
+static struct perf_event_header finished_round_event = {
+ .size = sizeof(struct perf_event_header),
+ .type = PERF_RECORD_FINISHED_ROUND,
+};
+
+static int record__mmap_read_all(struct record *rec)
+{
+ int i;
+ int rc = 0;
+
+ for (i = 0; i < rec->evlist->nr_mmaps; i++) {
+ if (rec->evlist->mmap[i].base) {
+ if (record__mmap_read(rec, &rec->evlist->mmap[i]) != 0) {
+ rc = -1;
+ goto out;
+ }
+ }
+ }
+
+ if (perf_header__has_feat(&rec->session->header, HEADER_TRACING_DATA))
+ rc = record__write(rec, &finished_round_event, sizeof(finished_round_event));
+
+out:
+ return rc;
+}
+
+static void record__init_features(struct record *rec)
+{
+ struct perf_session *session = rec->session;
+ int feat;
+
+ for (feat = HEADER_FIRST_FEATURE; feat < HEADER_LAST_FEATURE; feat++)
+ perf_header__set_feat(&session->header, feat);
+
+ if (rec->no_buildid)
+ perf_header__clear_feat(&session->header, HEADER_BUILD_ID);
+
+ if (!have_tracepoints(&rec->evlist->entries))
+ perf_header__clear_feat(&session->header, HEADER_TRACING_DATA);
+
+ if (!rec->opts.branch_stack)
+ perf_header__clear_feat(&session->header, HEADER_BRANCH_STACK);
+}
+
+static volatile int workload_exec_errno;
+
+/*
+ * perf_evlist__prepare_workload will send a SIGUSR1
+ * if the fork fails, since we asked by setting its
+ * want_signal to true.
+ */
+static void workload_exec_failed_signal(int signo __maybe_unused,
+ siginfo_t *info,
+ void *ucontext __maybe_unused)
+{
+ workload_exec_errno = info->si_value.sival_int;
+ done = 1;
+ child_finished = 1;
+}
+
+static int __cmd_record(struct record *rec, int argc, const char **argv)
+{
+ int err;
+ int status = 0;
+ unsigned long waking = 0;
+ const bool forks = argc > 0;
+ struct machine *machine;
+ struct perf_tool *tool = &rec->tool;
+ struct record_opts *opts = &rec->opts;
+ struct perf_data_file *file = &rec->file;
+ struct perf_session *session;
+ bool disabled = false;
+
+ rec->progname = argv[0];
+
+ atexit(record__sig_exit);
+ signal(SIGCHLD, sig_handler);
+ signal(SIGINT, sig_handler);
+ signal(SIGTERM, sig_handler);
+
+ session = perf_session__new(file, false, NULL);
+ if (session == NULL) {
+ pr_err("Perf session creation failed.\n");
+ return -1;
+ }
+
+ rec->session = session;
+
+ record__init_features(rec);
+
+ if (forks) {
+ err = perf_evlist__prepare_workload(rec->evlist, &opts->target,
+ argv, file->is_pipe,
+ workload_exec_failed_signal);
+ if (err < 0) {
+ pr_err("Couldn't run the workload!\n");
+ status = err;
+ goto out_delete_session;
+ }
+ }
+
+ if (record__open(rec) != 0) {
+ err = -1;
+ goto out_child;
+ }
+
+ if (!rec->evlist->nr_groups)
+ perf_header__clear_feat(&session->header, HEADER_GROUP_DESC);
+
+ if (file->is_pipe) {
+ err = perf_header__write_pipe(file->fd);
+ if (err < 0)
+ goto out_child;
+ } else {
+ err = perf_session__write_header(session, rec->evlist,
+ file->fd, false);
+ if (err < 0)
+ goto out_child;
+ }
+
+ if (!rec->no_buildid
+ && !perf_header__has_feat(&session->header, HEADER_BUILD_ID)) {
+ pr_err("Couldn't generate buildids. "
+ "Use --no-buildid to profile anyway.\n");
+ err = -1;
+ goto out_child;
+ }
+
+ machine = &session->machines.host;
+
+ if (file->is_pipe) {
+ err = perf_event__synthesize_attrs(tool, session,
+ process_synthesized_event);
+ if (err < 0) {
+ pr_err("Couldn't synthesize attrs.\n");
+ goto out_child;
+ }
+
+ if (have_tracepoints(&rec->evlist->entries)) {
+ /*
+ * FIXME err <= 0 here actually means that
+ * there were no tracepoints so its not really
+ * an error, just that we don't need to
+ * synthesize anything. We really have to
+ * return this more properly and also
+ * propagate errors that now are calling die()
+ */
+ err = perf_event__synthesize_tracing_data(tool, file->fd, rec->evlist,
+ process_synthesized_event);
+ if (err <= 0) {
+ pr_err("Couldn't record tracing data.\n");
+ goto out_child;
+ }
+ rec->bytes_written += err;
+ }
+ }
+
+ err = perf_event__synthesize_kernel_mmap(tool, process_synthesized_event,
+ machine);
+ if (err < 0)
+ pr_err("Couldn't record kernel reference relocation symbol\n"
+ "Symbol resolution may be skewed if relocation was used (e.g. kexec).\n"
+ "Check /proc/kallsyms permission or run as root.\n");
+
+ err = perf_event__synthesize_modules(tool, process_synthesized_event,
+ machine);
+ if (err < 0)
+ pr_err("Couldn't record kernel module information.\n"
+ "Symbol resolution may be skewed if relocation was used (e.g. kexec).\n"
+ "Check /proc/modules permission or run as root.\n");
+
+ if (perf_guest) {
+ machines__process_guests(&session->machines,
+ perf_event__synthesize_guest_os, tool);
+ }
+
+ err = __machine__synthesize_threads(machine, tool, &opts->target, rec->evlist->threads,
+ process_synthesized_event, opts->sample_address);
+ if (err != 0)
+ goto out_child;
+
+ if (rec->realtime_prio) {
+ struct sched_param param;
+
+ param.sched_priority = rec->realtime_prio;
+ if (sched_setscheduler(0, SCHED_FIFO, &param)) {
+ pr_err("Could not set realtime priority.\n");
+ err = -1;
+ goto out_child;
+ }
+ }
+
+ /*
+ * When perf is starting the traced process, all the events
+ * (apart from group members) have enable_on_exec=1 set,
+ * so don't spoil it by prematurely enabling them.
+ */
+ if (!target__none(&opts->target) && !opts->initial_delay)
+ perf_evlist__enable(rec->evlist);
+
+ /*
+ * Let the child rip
+ */
+ if (forks)
+ perf_evlist__start_workload(rec->evlist);
+
+ if (opts->initial_delay) {
+ usleep(opts->initial_delay * 1000);
+ perf_evlist__enable(rec->evlist);
+ }
+
+ for (;;) {
+ int hits = rec->samples;
+
+ if (record__mmap_read_all(rec) < 0) {
+ err = -1;
+ goto out_child;
+ }
+
+ if (hits == rec->samples) {
+ if (done)
+ break;
+ err = poll(rec->evlist->pollfd, rec->evlist->nr_fds, -1);
+ /*
+ * Propagate error, only if there's any. Ignore positive
+ * number of returned events and interrupt error.
+ */
+ if (err > 0 || (err < 0 && errno == EINTR))
+ err = 0;
+ waking++;
+ }
+
+ /*
+ * When perf is starting the traced process, at the end events
+ * die with the process and we wait for that. Thus no need to
+ * disable events in this case.
+ */
+ if (done && !disabled && !target__none(&opts->target)) {
+ perf_evlist__disable(rec->evlist);
+ disabled = true;
+ }
+ }
+
+ if (forks && workload_exec_errno) {
+ char msg[512];
+ const char *emsg = strerror_r(workload_exec_errno, msg, sizeof(msg));
+ pr_err("Workload failed: %s\n", emsg);
+ err = -1;
+ goto out_child;
+ }
+
+ if (!quiet) {
+ fprintf(stderr, "[ perf record: Woken up %ld times to write data ]\n", waking);
+
+ /*
+ * Approximate RIP event size: 24 bytes.
+ */
+ fprintf(stderr,
+ "[ perf record: Captured and wrote %.3f MB %s (~%" PRIu64 " samples) ]\n",
+ (double)rec->bytes_written / 1024.0 / 1024.0,
+ file->path,
+ rec->bytes_written / 24);
+ }
+
+out_child:
+ if (forks) {
+ int exit_status;
+
+ if (!child_finished)
+ kill(rec->evlist->workload.pid, SIGTERM);
+
+ wait(&exit_status);
+
+ if (err < 0)
+ status = err;
+ else if (WIFEXITED(exit_status))
+ status = WEXITSTATUS(exit_status);
+ else if (WIFSIGNALED(exit_status))
+ signr = WTERMSIG(exit_status);
+ } else
+ status = err;
+
+ if (!err && !file->is_pipe) {
+ rec->session->header.data_size += rec->bytes_written;
+
+ if (!rec->no_buildid)
+ process_buildids(rec);
+ perf_session__write_header(rec->session, rec->evlist,
+ file->fd, true);
+ }
+
+out_delete_session:
+ perf_session__delete(session);
+ return status;
+}
+
+#define BRANCH_OPT(n, m) \
+ { .name = n, .mode = (m) }
+
+#define BRANCH_END { .name = NULL }
+
+struct branch_mode {
+ const char *name;
+ int mode;
+};
+
+static const struct branch_mode branch_modes[] = {
+ BRANCH_OPT("u", PERF_SAMPLE_BRANCH_USER),
+ BRANCH_OPT("k", PERF_SAMPLE_BRANCH_KERNEL),
+ BRANCH_OPT("hv", PERF_SAMPLE_BRANCH_HV),
+ BRANCH_OPT("any", PERF_SAMPLE_BRANCH_ANY),
+ BRANCH_OPT("any_call", PERF_SAMPLE_BRANCH_ANY_CALL),
+ BRANCH_OPT("any_ret", PERF_SAMPLE_BRANCH_ANY_RETURN),
+ BRANCH_OPT("ind_call", PERF_SAMPLE_BRANCH_IND_CALL),
+ BRANCH_OPT("abort_tx", PERF_SAMPLE_BRANCH_ABORT_TX),
+ BRANCH_OPT("in_tx", PERF_SAMPLE_BRANCH_IN_TX),
+ BRANCH_OPT("no_tx", PERF_SAMPLE_BRANCH_NO_TX),
+ BRANCH_OPT("cond", PERF_SAMPLE_BRANCH_COND),
+ BRANCH_END
+};
+
+static int
+parse_branch_stack(const struct option *opt, const char *str, int unset)
+{
+#define ONLY_PLM \
+ (PERF_SAMPLE_BRANCH_USER |\
+ PERF_SAMPLE_BRANCH_KERNEL |\
+ PERF_SAMPLE_BRANCH_HV)
+
+ uint64_t *mode = (uint64_t *)opt->value;
+ const struct branch_mode *br;
+ char *s, *os = NULL, *p;
+ int ret = -1;
+
+ if (unset)
+ return 0;
+
+ /*
+ * cannot set it twice, -b + --branch-filter for instance
+ */
+ if (*mode)
+ return -1;
+
+ /* str may be NULL in case no arg is passed to -b */
+ if (str) {
+ /* because str is read-only */
+ s = os = strdup(str);
+ if (!s)
+ return -1;
+
+ for (;;) {
+ p = strchr(s, ',');
+ if (p)
+ *p = '\0';
+
+ for (br = branch_modes; br->name; br++) {
+ if (!strcasecmp(s, br->name))
+ break;
+ }
+ if (!br->name) {
+ ui__warning("unknown branch filter %s,"
+ " check man page\n", s);
+ goto error;
+ }
+
+ *mode |= br->mode;
+
+ if (!p)
+ break;
+
+ s = p + 1;
+ }
+ }
+ ret = 0;
+
+ /* default to any branch */
+ if ((*mode & ~ONLY_PLM) == 0) {
+ *mode = PERF_SAMPLE_BRANCH_ANY;
+ }
+error:
+ free(os);
+ return ret;
+}
+
+#ifdef HAVE_DWARF_UNWIND_SUPPORT
+static int get_stack_size(char *str, unsigned long *_size)
+{
+ char *endptr;
+ unsigned long size;
+ unsigned long max_size = round_down(USHRT_MAX, sizeof(u64));
+
+ size = strtoul(str, &endptr, 0);
+
+ do {
+ if (*endptr)
+ break;
+
+ size = round_up(size, sizeof(u64));
+ if (!size || size > max_size)
+ break;
+
+ *_size = size;
+ return 0;
+
+ } while (0);
+
+ pr_err("callchain: Incorrect stack dump size (max %ld): %s\n",
+ max_size, str);
+ return -1;
+}
+#endif /* HAVE_DWARF_UNWIND_SUPPORT */
+
+int record_parse_callchain(const char *arg, struct record_opts *opts)
+{
+ char *tok, *name, *saveptr = NULL;
+ char *buf;
+ int ret = -1;
+
+ /* We need buffer that we know we can write to. */
+ buf = malloc(strlen(arg) + 1);
+ if (!buf)
+ return -ENOMEM;
+
+ strcpy(buf, arg);
+
+ tok = strtok_r((char *)buf, ",", &saveptr);
+ name = tok ? : (char *)buf;
+
+ do {
+ /* Framepointer style */
+ if (!strncmp(name, "fp", sizeof("fp"))) {
+ if (!strtok_r(NULL, ",", &saveptr)) {
+ opts->call_graph = CALLCHAIN_FP;
+ ret = 0;
+ } else
+ pr_err("callchain: No more arguments "
+ "needed for -g fp\n");
+ break;
+
+#ifdef HAVE_DWARF_UNWIND_SUPPORT
+ /* Dwarf style */
+ } else if (!strncmp(name, "dwarf", sizeof("dwarf"))) {
+ const unsigned long default_stack_dump_size = 8192;
+
+ ret = 0;
+ opts->call_graph = CALLCHAIN_DWARF;
+ opts->stack_dump_size = default_stack_dump_size;
+
+ tok = strtok_r(NULL, ",", &saveptr);
+ if (tok) {
+ unsigned long size = 0;
+
+ ret = get_stack_size(tok, &size);
+ opts->stack_dump_size = size;
+ }
+#endif /* HAVE_DWARF_UNWIND_SUPPORT */
+ } else {
+ pr_err("callchain: Unknown --call-graph option "
+ "value: %s\n", arg);
+ break;
+ }
+
+ } while (0);
+
+ free(buf);
+ return ret;
+}
+
+static void callchain_debug(struct record_opts *opts)
+{
+ static const char *str[CALLCHAIN_MAX] = { "NONE", "FP", "DWARF" };
+
+ pr_debug("callchain: type %s\n", str[opts->call_graph]);
+
+ if (opts->call_graph == CALLCHAIN_DWARF)
+ pr_debug("callchain: stack dump size %d\n",
+ opts->stack_dump_size);
+}
+
+int record_parse_callchain_opt(const struct option *opt,
+ const char *arg,
+ int unset)
+{
+ struct record_opts *opts = opt->value;
+ int ret;
+
+ opts->call_graph_enabled = !unset;
+
+ /* --no-call-graph */
+ if (unset) {
+ opts->call_graph = CALLCHAIN_NONE;
+ pr_debug("callchain: disabled\n");
+ return 0;
+ }
+
+ ret = record_parse_callchain(arg, opts);
+ if (!ret)
+ callchain_debug(opts);
+
+ return ret;
+}
+
+int record_callchain_opt(const struct option *opt,
+ const char *arg __maybe_unused,
+ int unset __maybe_unused)
+{
+ struct record_opts *opts = opt->value;
+
+ opts->call_graph_enabled = !unset;
+
+ if (opts->call_graph == CALLCHAIN_NONE)
+ opts->call_graph = CALLCHAIN_FP;
+
+ callchain_debug(opts);
+ return 0;
+}
+
+static int perf_record_config(const char *var, const char *value, void *cb)
+{
+ struct record *rec = cb;
+
+ if (!strcmp(var, "record.call-graph"))
+ return record_parse_callchain(value, &rec->opts);
+
+ return perf_default_config(var, value, cb);
+}
+
+static const char * const record_usage[] = {
+ "perf record [<options>] [<command>]",
+ "perf record [<options>] -- <command> [<options>]",
+ NULL
+};
+
+/*
+ * XXX Ideally would be local to cmd_record() and passed to a record__new
+ * because we need to have access to it in record__exit, that is called
+ * after cmd_record() exits, but since record_options need to be accessible to
+ * builtin-script, leave it here.
+ *
+ * At least we don't ouch it in all the other functions here directly.
+ *
+ * Just say no to tons of global variables, sigh.
+ */
+static struct record record = {
+ .opts = {
+ .mmap_pages = UINT_MAX,
+ .user_freq = UINT_MAX,
+ .user_interval = ULLONG_MAX,
+ .freq = 4000,
+ .target = {
+ .uses_mmap = true,
+ .default_per_cpu = true,
+ },
+ },
+};
+
+#define CALLCHAIN_HELP "setup and enables call-graph (stack chain/backtrace) recording: "
+
+#ifdef HAVE_DWARF_UNWIND_SUPPORT
+const char record_callchain_help[] = CALLCHAIN_HELP "fp dwarf";
+#else
+const char record_callchain_help[] = CALLCHAIN_HELP "fp";
+#endif
+
+/*
+ * XXX Will stay a global variable till we fix builtin-script.c to stop messing
+ * with it and switch to use the library functions in perf_evlist that came
+ * from builtin-record.c, i.e. use record_opts,
+ * perf_evlist__prepare_workload, etc instead of fork+exec'in 'perf record',
+ * using pipes, etc.
+ */
+const struct option record_options[] = {
+ OPT_CALLBACK('e', "event", &record.evlist, "event",
+ "event selector. use 'perf list' to list available events",
+ parse_events_option),
+ OPT_CALLBACK(0, "filter", &record.evlist, "filter",
+ "event filter", parse_filter),
+ OPT_STRING('p', "pid", &record.opts.target.pid, "pid",
+ "record events on existing process id"),
+ OPT_STRING('t', "tid", &record.opts.target.tid, "tid",
+ "record events on existing thread id"),
+ OPT_INTEGER('r', "realtime", &record.realtime_prio,
+ "collect data with this RT SCHED_FIFO priority"),
+ OPT_BOOLEAN(0, "no-buffering", &record.opts.no_buffering,
+ "collect data without buffering"),
+ OPT_BOOLEAN('R', "raw-samples", &record.opts.raw_samples,
+ "collect raw sample records from all opened counters"),
+ OPT_BOOLEAN('a', "all-cpus", &record.opts.target.system_wide,
+ "system-wide collection from all CPUs"),
+ OPT_STRING('C', "cpu", &record.opts.target.cpu_list, "cpu",
+ "list of cpus to monitor"),
+ OPT_U64('c', "count", &record.opts.user_interval, "event period to sample"),
+ OPT_STRING('o', "output", &record.file.path, "file",
+ "output file name"),
+ OPT_BOOLEAN_SET('i', "no-inherit", &record.opts.no_inherit,
+ &record.opts.no_inherit_set,
+ "child tasks do not inherit counters"),
+ OPT_UINTEGER('F', "freq", &record.opts.user_freq, "profile at this frequency"),
+ OPT_CALLBACK('m', "mmap-pages", &record.opts.mmap_pages, "pages",
+ "number of mmap data pages",
+ perf_evlist__parse_mmap_pages),
+ OPT_BOOLEAN(0, "group", &record.opts.group,
+ "put the counters into a counter group"),
+ OPT_CALLBACK_NOOPT('g', NULL, &record.opts,
+ NULL, "enables call-graph recording" ,
+ &record_callchain_opt),
+ OPT_CALLBACK(0, "call-graph", &record.opts,
+ "mode[,dump_size]", record_callchain_help,
+ &record_parse_callchain_opt),
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show counter open errors, etc)"),
+ OPT_BOOLEAN('q', "quiet", &quiet, "don't print any message"),
+ OPT_BOOLEAN('s', "stat", &record.opts.inherit_stat,
+ "per thread counts"),
+ OPT_BOOLEAN('d', "data", &record.opts.sample_address,
+ "Sample addresses"),
+ OPT_BOOLEAN('T', "timestamp", &record.opts.sample_time, "Sample timestamps"),
+ OPT_BOOLEAN('P', "period", &record.opts.period, "Sample period"),
+ OPT_BOOLEAN('n', "no-samples", &record.opts.no_samples,
+ "don't sample"),
+ OPT_BOOLEAN('N', "no-buildid-cache", &record.no_buildid_cache,
+ "do not update the buildid cache"),
+ OPT_BOOLEAN('B', "no-buildid", &record.no_buildid,
+ "do not collect buildids in perf.data"),
+ OPT_CALLBACK('G', "cgroup", &record.evlist, "name",
+ "monitor event in cgroup name only",
+ parse_cgroups),
+ OPT_UINTEGER('D', "delay", &record.opts.initial_delay,
+ "ms to wait before starting measurement after program start"),
+ OPT_STRING('u', "uid", &record.opts.target.uid_str, "user",
+ "user to profile"),
+
+ OPT_CALLBACK_NOOPT('b', "branch-any", &record.opts.branch_stack,
+ "branch any", "sample any taken branches",
+ parse_branch_stack),
+
+ OPT_CALLBACK('j', "branch-filter", &record.opts.branch_stack,
+ "branch filter mask", "branch stack filter modes",
+ parse_branch_stack),
+ OPT_BOOLEAN('W', "weight", &record.opts.sample_weight,
+ "sample by weight (on special events only)"),
+ OPT_BOOLEAN(0, "transaction", &record.opts.sample_transaction,
+ "sample transaction flags (special events only)"),
+ OPT_BOOLEAN(0, "per-thread", &record.opts.target.per_thread,
+ "use per-thread mmaps"),
+ OPT_END()
+};
+
+int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ int err = -ENOMEM;
+ struct record *rec = &record;
+ char errbuf[BUFSIZ];
+
+ rec->evlist = perf_evlist__new();
+ if (rec->evlist == NULL)
+ return -ENOMEM;
+
+ perf_config(perf_record_config, rec);
+
+ argc = parse_options(argc, argv, record_options, record_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+ if (!argc && target__none(&rec->opts.target))
+ usage_with_options(record_usage, record_options);
+
+ if (nr_cgroups && !rec->opts.target.system_wide) {
+ ui__error("cgroup monitoring only available in"
+ " system-wide mode\n");
+ usage_with_options(record_usage, record_options);
+ }
+
+ symbol__init();
+
+ if (symbol_conf.kptr_restrict)
+ pr_warning(
+"WARNING: Kernel address maps (/proc/{kallsyms,modules}) are restricted,\n"
+"check /proc/sys/kernel/kptr_restrict.\n\n"
+"Samples in kernel functions may not be resolved if a suitable vmlinux\n"
+"file is not found in the buildid cache or in the vmlinux path.\n\n"
+"Samples in kernel modules won't be resolved at all.\n\n"
+"If some relocation was applied (e.g. kexec) symbols may be misresolved\n"
+"even with a suitable vmlinux or kallsyms file.\n\n");
+
+ if (rec->no_buildid_cache || rec->no_buildid)
+ disable_buildid_cache();
+
+ if (rec->evlist->nr_entries == 0 &&
+ perf_evlist__add_default(rec->evlist) < 0) {
+ pr_err("Not enough memory for event selector list\n");
+ goto out_symbol_exit;
+ }
+
+ if (rec->opts.target.tid && !rec->opts.no_inherit_set)
+ rec->opts.no_inherit = true;
+
+ err = target__validate(&rec->opts.target);
+ if (err) {
+ target__strerror(&rec->opts.target, err, errbuf, BUFSIZ);
+ ui__warning("%s", errbuf);
+ }
+
+ err = target__parse_uid(&rec->opts.target);
+ if (err) {
+ int saved_errno = errno;
+
+ target__strerror(&rec->opts.target, err, errbuf, BUFSIZ);
+ ui__error("%s", errbuf);
+
+ err = -saved_errno;
+ goto out_symbol_exit;
+ }
+
+ err = -ENOMEM;
+ if (perf_evlist__create_maps(rec->evlist, &rec->opts.target) < 0)
+ usage_with_options(record_usage, record_options);
+
+ if (record_opts__config(&rec->opts)) {
+ err = -EINVAL;
+ goto out_symbol_exit;
+ }
+
+ err = __cmd_record(&record, argc, argv);
+out_symbol_exit:
+ perf_evlist__delete(rec->evlist);
+ symbol__exit();
+ return err;
+}
diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c
new file mode 100644
index 00000000000..21d830bafff
--- /dev/null
+++ b/tools/perf/builtin-report.c
@@ -0,0 +1,816 @@
+/*
+ * builtin-report.c
+ *
+ * Builtin report command: Analyze the perf.data input file,
+ * look up and read DSOs and symbol information and display
+ * a histogram of results, along various sorting keys.
+ */
+#include "builtin.h"
+
+#include "util/util.h"
+#include "util/cache.h"
+
+#include "util/annotate.h"
+#include "util/color.h"
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include "util/symbol.h"
+#include "util/callchain.h"
+#include "util/strlist.h"
+#include "util/values.h"
+
+#include "perf.h"
+#include "util/debug.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/header.h"
+#include "util/session.h"
+#include "util/tool.h"
+
+#include "util/parse-options.h"
+#include "util/parse-events.h"
+
+#include "util/thread.h"
+#include "util/sort.h"
+#include "util/hist.h"
+#include "util/data.h"
+#include "arch/common.h"
+
+#include <dlfcn.h>
+#include <linux/bitmap.h>
+
+struct report {
+ struct perf_tool tool;
+ struct perf_session *session;
+ bool force, use_tui, use_gtk, use_stdio;
+ bool hide_unresolved;
+ bool dont_use_callchains;
+ bool show_full_info;
+ bool show_threads;
+ bool inverted_callchain;
+ bool mem_mode;
+ bool header;
+ bool header_only;
+ int max_stack;
+ struct perf_read_values show_threads_values;
+ const char *pretty_printing_style;
+ const char *cpu_list;
+ const char *symbol_filter_str;
+ float min_percent;
+ u64 nr_entries;
+ DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS);
+};
+
+static int report__config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "report.group")) {
+ symbol_conf.event_group = perf_config_bool(var, value);
+ return 0;
+ }
+ if (!strcmp(var, "report.percent-limit")) {
+ struct report *rep = cb;
+ rep->min_percent = strtof(value, NULL);
+ return 0;
+ }
+ if (!strcmp(var, "report.children")) {
+ symbol_conf.cumulate_callchain = perf_config_bool(var, value);
+ return 0;
+ }
+
+ return perf_default_config(var, value, cb);
+}
+
+static void report__inc_stats(struct report *rep, struct hist_entry *he)
+{
+ /*
+ * The @he is either of a newly created one or an existing one
+ * merging current sample. We only want to count a new one so
+ * checking ->nr_events being 1.
+ */
+ if (he->stat.nr_events == 1)
+ rep->nr_entries++;
+}
+
+static int hist_iter__report_callback(struct hist_entry_iter *iter,
+ struct addr_location *al, bool single,
+ void *arg)
+{
+ int err = 0;
+ struct report *rep = arg;
+ struct hist_entry *he = iter->he;
+ struct perf_evsel *evsel = iter->evsel;
+ struct mem_info *mi;
+ struct branch_info *bi;
+
+ report__inc_stats(rep, he);
+
+ if (!ui__has_annotation())
+ return 0;
+
+ if (sort__mode == SORT_MODE__BRANCH) {
+ bi = he->branch_info;
+ err = addr_map_symbol__inc_samples(&bi->from, evsel->idx);
+ if (err)
+ goto out;
+
+ err = addr_map_symbol__inc_samples(&bi->to, evsel->idx);
+
+ } else if (rep->mem_mode) {
+ mi = he->mem_info;
+ err = addr_map_symbol__inc_samples(&mi->daddr, evsel->idx);
+ if (err)
+ goto out;
+
+ err = hist_entry__inc_addr_samples(he, evsel->idx, al->addr);
+
+ } else if (symbol_conf.cumulate_callchain) {
+ if (single)
+ err = hist_entry__inc_addr_samples(he, evsel->idx,
+ al->addr);
+ } else {
+ err = hist_entry__inc_addr_samples(he, evsel->idx, al->addr);
+ }
+
+out:
+ return err;
+}
+
+static int process_sample_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ struct report *rep = container_of(tool, struct report, tool);
+ struct addr_location al;
+ struct hist_entry_iter iter = {
+ .hide_unresolved = rep->hide_unresolved,
+ .add_entry_cb = hist_iter__report_callback,
+ };
+ int ret;
+
+ if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) {
+ pr_debug("problem processing %d event, skipping it.\n",
+ event->header.type);
+ return -1;
+ }
+
+ if (rep->hide_unresolved && al.sym == NULL)
+ return 0;
+
+ if (rep->cpu_list && !test_bit(sample->cpu, rep->cpu_bitmap))
+ return 0;
+
+ if (sort__mode == SORT_MODE__BRANCH)
+ iter.ops = &hist_iter_branch;
+ else if (rep->mem_mode)
+ iter.ops = &hist_iter_mem;
+ else if (symbol_conf.cumulate_callchain)
+ iter.ops = &hist_iter_cumulative;
+ else
+ iter.ops = &hist_iter_normal;
+
+ if (al.map != NULL)
+ al.map->dso->hit = 1;
+
+ ret = hist_entry_iter__add(&iter, &al, evsel, sample, rep->max_stack,
+ rep);
+ if (ret < 0)
+ pr_debug("problem adding hist entry, skipping event\n");
+
+ return ret;
+}
+
+static int process_read_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample __maybe_unused,
+ struct perf_evsel *evsel,
+ struct machine *machine __maybe_unused)
+{
+ struct report *rep = container_of(tool, struct report, tool);
+
+ if (rep->show_threads) {
+ const char *name = evsel ? perf_evsel__name(evsel) : "unknown";
+ perf_read_values_add_value(&rep->show_threads_values,
+ event->read.pid, event->read.tid,
+ event->read.id,
+ name,
+ event->read.value);
+ }
+
+ dump_printf(": %d %d %s %" PRIu64 "\n", event->read.pid, event->read.tid,
+ evsel ? perf_evsel__name(evsel) : "FAIL",
+ event->read.value);
+
+ return 0;
+}
+
+/* For pipe mode, sample_type is not currently set */
+static int report__setup_sample_type(struct report *rep)
+{
+ struct perf_session *session = rep->session;
+ u64 sample_type = perf_evlist__combined_sample_type(session->evlist);
+ bool is_pipe = perf_data_file__is_pipe(session->file);
+
+ if (!is_pipe && !(sample_type & PERF_SAMPLE_CALLCHAIN)) {
+ if (sort__has_parent) {
+ ui__error("Selected --sort parent, but no "
+ "callchain data. Did you call "
+ "'perf record' without -g?\n");
+ return -EINVAL;
+ }
+ if (symbol_conf.use_callchain) {
+ ui__error("Selected -g but no callchain data. Did "
+ "you call 'perf record' without -g?\n");
+ return -1;
+ }
+ } else if (!rep->dont_use_callchains &&
+ callchain_param.mode != CHAIN_NONE &&
+ !symbol_conf.use_callchain) {
+ symbol_conf.use_callchain = true;
+ if (callchain_register_param(&callchain_param) < 0) {
+ ui__error("Can't register callchain params.\n");
+ return -EINVAL;
+ }
+ }
+
+ if (symbol_conf.cumulate_callchain) {
+ /* Silently ignore if callchain is missing */
+ if (!(sample_type & PERF_SAMPLE_CALLCHAIN)) {
+ symbol_conf.cumulate_callchain = false;
+ perf_hpp__cancel_cumulate();
+ }
+ }
+
+ if (sort__mode == SORT_MODE__BRANCH) {
+ if (!is_pipe &&
+ !(sample_type & PERF_SAMPLE_BRANCH_STACK)) {
+ ui__error("Selected -b but no branch data. "
+ "Did you call perf record without -b?\n");
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static void sig_handler(int sig __maybe_unused)
+{
+ session_done = 1;
+}
+
+static size_t hists__fprintf_nr_sample_events(struct hists *hists, struct report *rep,
+ const char *evname, FILE *fp)
+{
+ size_t ret;
+ char unit;
+ unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE];
+ u64 nr_events = hists->stats.total_period;
+ struct perf_evsel *evsel = hists_to_evsel(hists);
+ char buf[512];
+ size_t size = sizeof(buf);
+
+ if (symbol_conf.filter_relative) {
+ nr_samples = hists->stats.nr_non_filtered_samples;
+ nr_events = hists->stats.total_non_filtered_period;
+ }
+
+ if (perf_evsel__is_group_event(evsel)) {
+ struct perf_evsel *pos;
+
+ perf_evsel__group_desc(evsel, buf, size);
+ evname = buf;
+
+ for_each_group_member(pos, evsel) {
+ if (symbol_conf.filter_relative) {
+ nr_samples += pos->hists.stats.nr_non_filtered_samples;
+ nr_events += pos->hists.stats.total_non_filtered_period;
+ } else {
+ nr_samples += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE];
+ nr_events += pos->hists.stats.total_period;
+ }
+ }
+ }
+
+ nr_samples = convert_unit(nr_samples, &unit);
+ ret = fprintf(fp, "# Samples: %lu%c", nr_samples, unit);
+ if (evname != NULL)
+ ret += fprintf(fp, " of event '%s'", evname);
+
+ if (rep->mem_mode) {
+ ret += fprintf(fp, "\n# Total weight : %" PRIu64, nr_events);
+ ret += fprintf(fp, "\n# Sort order : %s", sort_order);
+ } else
+ ret += fprintf(fp, "\n# Event count (approx.): %" PRIu64, nr_events);
+ return ret + fprintf(fp, "\n#\n");
+}
+
+static int perf_evlist__tty_browse_hists(struct perf_evlist *evlist,
+ struct report *rep,
+ const char *help)
+{
+ struct perf_evsel *pos;
+
+ evlist__for_each(evlist, pos) {
+ struct hists *hists = &pos->hists;
+ const char *evname = perf_evsel__name(pos);
+
+ if (symbol_conf.event_group &&
+ !perf_evsel__is_group_leader(pos))
+ continue;
+
+ hists__fprintf_nr_sample_events(hists, rep, evname, stdout);
+ hists__fprintf(hists, true, 0, 0, rep->min_percent, stdout);
+ fprintf(stdout, "\n\n");
+ }
+
+ if (sort_order == default_sort_order &&
+ parent_pattern == default_parent_pattern) {
+ fprintf(stdout, "#\n# (%s)\n#\n", help);
+
+ if (rep->show_threads) {
+ bool style = !strcmp(rep->pretty_printing_style, "raw");
+ perf_read_values_display(stdout, &rep->show_threads_values,
+ style);
+ perf_read_values_destroy(&rep->show_threads_values);
+ }
+ }
+
+ return 0;
+}
+
+static void report__warn_kptr_restrict(const struct report *rep)
+{
+ struct map *kernel_map = rep->session->machines.host.vmlinux_maps[MAP__FUNCTION];
+ struct kmap *kernel_kmap = map__kmap(kernel_map);
+
+ if (kernel_map == NULL ||
+ (kernel_map->dso->hit &&
+ (kernel_kmap->ref_reloc_sym == NULL ||
+ kernel_kmap->ref_reloc_sym->addr == 0))) {
+ const char *desc =
+ "As no suitable kallsyms nor vmlinux was found, kernel samples\n"
+ "can't be resolved.";
+
+ if (kernel_map) {
+ const struct dso *kdso = kernel_map->dso;
+ if (!RB_EMPTY_ROOT(&kdso->symbols[MAP__FUNCTION])) {
+ desc = "If some relocation was applied (e.g. "
+ "kexec) symbols may be misresolved.";
+ }
+ }
+
+ ui__warning(
+"Kernel address maps (/proc/{kallsyms,modules}) were restricted.\n\n"
+"Check /proc/sys/kernel/kptr_restrict before running 'perf record'.\n\n%s\n\n"
+"Samples in kernel modules can't be resolved as well.\n\n",
+ desc);
+ }
+}
+
+static int report__gtk_browse_hists(struct report *rep, const char *help)
+{
+ int (*hist_browser)(struct perf_evlist *evlist, const char *help,
+ struct hist_browser_timer *timer, float min_pcnt);
+
+ hist_browser = dlsym(perf_gtk_handle, "perf_evlist__gtk_browse_hists");
+
+ if (hist_browser == NULL) {
+ ui__error("GTK browser not found!\n");
+ return -1;
+ }
+
+ return hist_browser(rep->session->evlist, help, NULL, rep->min_percent);
+}
+
+static int report__browse_hists(struct report *rep)
+{
+ int ret;
+ struct perf_session *session = rep->session;
+ struct perf_evlist *evlist = session->evlist;
+ const char *help = "For a higher level overview, try: perf report --sort comm,dso";
+
+ switch (use_browser) {
+ case 1:
+ ret = perf_evlist__tui_browse_hists(evlist, help, NULL,
+ rep->min_percent,
+ &session->header.env);
+ /*
+ * Usually "ret" is the last pressed key, and we only
+ * care if the key notifies us to switch data file.
+ */
+ if (ret != K_SWITCH_INPUT_DATA)
+ ret = 0;
+ break;
+ case 2:
+ ret = report__gtk_browse_hists(rep, help);
+ break;
+ default:
+ ret = perf_evlist__tty_browse_hists(evlist, rep, help);
+ break;
+ }
+
+ return ret;
+}
+
+static void report__collapse_hists(struct report *rep)
+{
+ struct ui_progress prog;
+ struct perf_evsel *pos;
+
+ ui_progress__init(&prog, rep->nr_entries, "Merging related events...");
+
+ evlist__for_each(rep->session->evlist, pos) {
+ struct hists *hists = &pos->hists;
+
+ if (pos->idx == 0)
+ hists->symbol_filter_str = rep->symbol_filter_str;
+
+ hists__collapse_resort(hists, &prog);
+
+ /* Non-group events are considered as leader */
+ if (symbol_conf.event_group &&
+ !perf_evsel__is_group_leader(pos)) {
+ struct hists *leader_hists = &pos->leader->hists;
+
+ hists__match(leader_hists, hists);
+ hists__link(leader_hists, hists);
+ }
+ }
+
+ ui_progress__finish();
+}
+
+static int __cmd_report(struct report *rep)
+{
+ int ret;
+ struct perf_session *session = rep->session;
+ struct perf_evsel *pos;
+ struct perf_data_file *file = session->file;
+
+ signal(SIGINT, sig_handler);
+
+ if (rep->cpu_list) {
+ ret = perf_session__cpu_bitmap(session, rep->cpu_list,
+ rep->cpu_bitmap);
+ if (ret)
+ return ret;
+ }
+
+ if (rep->show_threads)
+ perf_read_values_init(&rep->show_threads_values);
+
+ ret = report__setup_sample_type(rep);
+ if (ret)
+ return ret;
+
+ ret = perf_session__process_events(session, &rep->tool);
+ if (ret)
+ return ret;
+
+ report__warn_kptr_restrict(rep);
+
+ if (use_browser == 0) {
+ if (verbose > 3)
+ perf_session__fprintf(session, stdout);
+
+ if (verbose > 2)
+ perf_session__fprintf_dsos(session, stdout);
+
+ if (dump_trace) {
+ perf_session__fprintf_nr_events(session, stdout);
+ return 0;
+ }
+ }
+
+ report__collapse_hists(rep);
+
+ if (session_done())
+ return 0;
+
+ if (rep->nr_entries == 0) {
+ ui__error("The %s file has no samples!\n", file->path);
+ return 0;
+ }
+
+ evlist__for_each(session->evlist, pos)
+ hists__output_resort(&pos->hists);
+
+ return report__browse_hists(rep);
+}
+
+static int
+report_parse_callchain_opt(const struct option *opt, const char *arg, int unset)
+{
+ struct report *rep = (struct report *)opt->value;
+
+ /*
+ * --no-call-graph
+ */
+ if (unset) {
+ rep->dont_use_callchains = true;
+ return 0;
+ }
+
+ return parse_callchain_report_opt(arg);
+}
+
+int
+report_parse_ignore_callees_opt(const struct option *opt __maybe_unused,
+ const char *arg, int unset __maybe_unused)
+{
+ if (arg) {
+ int err = regcomp(&ignore_callees_regex, arg, REG_EXTENDED);
+ if (err) {
+ char buf[BUFSIZ];
+ regerror(err, &ignore_callees_regex, buf, sizeof(buf));
+ pr_err("Invalid --ignore-callees regex: %s\n%s", arg, buf);
+ return -1;
+ }
+ have_ignore_callees = 1;
+ }
+
+ return 0;
+}
+
+static int
+parse_branch_mode(const struct option *opt __maybe_unused,
+ const char *str __maybe_unused, int unset)
+{
+ int *branch_mode = opt->value;
+
+ *branch_mode = !unset;
+ return 0;
+}
+
+static int
+parse_percent_limit(const struct option *opt, const char *str,
+ int unset __maybe_unused)
+{
+ struct report *rep = opt->value;
+
+ rep->min_percent = strtof(str, NULL);
+ return 0;
+}
+
+int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ struct perf_session *session;
+ struct stat st;
+ bool has_br_stack = false;
+ int branch_mode = -1;
+ int ret = -1;
+ char callchain_default_opt[] = "fractal,0.5,callee";
+ const char * const report_usage[] = {
+ "perf report [<options>]",
+ NULL
+ };
+ struct report report = {
+ .tool = {
+ .sample = process_sample_event,
+ .mmap = perf_event__process_mmap,
+ .mmap2 = perf_event__process_mmap2,
+ .comm = perf_event__process_comm,
+ .exit = perf_event__process_exit,
+ .fork = perf_event__process_fork,
+ .lost = perf_event__process_lost,
+ .read = process_read_event,
+ .attr = perf_event__process_attr,
+ .tracing_data = perf_event__process_tracing_data,
+ .build_id = perf_event__process_build_id,
+ .ordered_samples = true,
+ .ordering_requires_timestamps = true,
+ },
+ .max_stack = PERF_MAX_STACK_DEPTH,
+ .pretty_printing_style = "normal",
+ };
+ const struct option options[] = {
+ OPT_STRING('i', "input", &input_name, "file",
+ "input file name"),
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show symbol address, etc)"),
+ OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
+ "dump raw trace in ASCII"),
+ OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name,
+ "file", "vmlinux pathname"),
+ OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name,
+ "file", "kallsyms pathname"),
+ OPT_BOOLEAN('f', "force", &report.force, "don't complain, do it"),
+ OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules,
+ "load module symbols - WARNING: use only with -k and LIVE kernel"),
+ OPT_BOOLEAN('n', "show-nr-samples", &symbol_conf.show_nr_samples,
+ "Show a column with the number of samples"),
+ OPT_BOOLEAN('T', "threads", &report.show_threads,
+ "Show per-thread event counters"),
+ OPT_STRING(0, "pretty", &report.pretty_printing_style, "key",
+ "pretty printing style key: normal raw"),
+ OPT_BOOLEAN(0, "tui", &report.use_tui, "Use the TUI interface"),
+ OPT_BOOLEAN(0, "gtk", &report.use_gtk, "Use the GTK2 interface"),
+ OPT_BOOLEAN(0, "stdio", &report.use_stdio,
+ "Use the stdio interface"),
+ OPT_BOOLEAN(0, "header", &report.header, "Show data header."),
+ OPT_BOOLEAN(0, "header-only", &report.header_only,
+ "Show only data header."),
+ OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
+ "sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline, ..."
+ " Please refer the man page for the complete list."),
+ OPT_STRING('F', "fields", &field_order, "key[,keys...]",
+ "output field(s): overhead, period, sample plus all of sort keys"),
+ OPT_BOOLEAN(0, "showcpuutilization", &symbol_conf.show_cpu_utilization,
+ "Show sample percentage for different cpu modes"),
+ OPT_STRING('p', "parent", &parent_pattern, "regex",
+ "regex filter to identify parent, see: '--sort parent'"),
+ OPT_BOOLEAN('x', "exclude-other", &symbol_conf.exclude_other,
+ "Only display entries with parent-match"),
+ OPT_CALLBACK_DEFAULT('g', "call-graph", &report, "output_type,min_percent[,print_limit],call_order",
+ "Display callchains using output_type (graph, flat, fractal, or none) , min percent threshold, optional print limit, callchain order, key (function or address). "
+ "Default: fractal,0.5,callee,function", &report_parse_callchain_opt, callchain_default_opt),
+ OPT_BOOLEAN(0, "children", &symbol_conf.cumulate_callchain,
+ "Accumulate callchains of children and show total overhead as well"),
+ OPT_INTEGER(0, "max-stack", &report.max_stack,
+ "Set the maximum stack depth when parsing the callchain, "
+ "anything beyond the specified depth will be ignored. "
+ "Default: " __stringify(PERF_MAX_STACK_DEPTH)),
+ OPT_BOOLEAN('G', "inverted", &report.inverted_callchain,
+ "alias for inverted call graph"),
+ OPT_CALLBACK(0, "ignore-callees", NULL, "regex",
+ "ignore callees of these functions in call graphs",
+ report_parse_ignore_callees_opt),
+ OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
+ "only consider symbols in these dsos"),
+ OPT_STRING('c', "comms", &symbol_conf.comm_list_str, "comm[,comm...]",
+ "only consider symbols in these comms"),
+ OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]",
+ "only consider these symbols"),
+ OPT_STRING(0, "symbol-filter", &report.symbol_filter_str, "filter",
+ "only show symbols that (partially) match with this filter"),
+ OPT_STRING('w', "column-widths", &symbol_conf.col_width_list_str,
+ "width[,width...]",
+ "don't try to adjust column width, use these fixed values"),
+ OPT_STRING('t', "field-separator", &symbol_conf.field_sep, "separator",
+ "separator for columns, no spaces will be added between "
+ "columns '.' is reserved."),
+ OPT_BOOLEAN('U', "hide-unresolved", &report.hide_unresolved,
+ "Only display entries resolved to a symbol"),
+ OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory",
+ "Look for files with symbols relative to this directory"),
+ OPT_STRING('C', "cpu", &report.cpu_list, "cpu",
+ "list of cpus to profile"),
+ OPT_BOOLEAN('I', "show-info", &report.show_full_info,
+ "Display extended information about perf.data file"),
+ OPT_BOOLEAN(0, "source", &symbol_conf.annotate_src,
+ "Interleave source code with assembly code (default)"),
+ OPT_BOOLEAN(0, "asm-raw", &symbol_conf.annotate_asm_raw,
+ "Display raw encoding of assembly instructions (default)"),
+ OPT_STRING('M', "disassembler-style", &disassembler_style, "disassembler style",
+ "Specify disassembler style (e.g. -M intel for intel syntax)"),
+ OPT_BOOLEAN(0, "show-total-period", &symbol_conf.show_total_period,
+ "Show a column with the sum of periods"),
+ OPT_BOOLEAN(0, "group", &symbol_conf.event_group,
+ "Show event group information together"),
+ OPT_CALLBACK_NOOPT('b', "branch-stack", &branch_mode, "",
+ "use branch records for histogram filling", parse_branch_mode),
+ OPT_STRING(0, "objdump", &objdump_path, "path",
+ "objdump binary to use for disassembly and annotations"),
+ OPT_BOOLEAN(0, "demangle", &symbol_conf.demangle,
+ "Disable symbol demangling"),
+ OPT_BOOLEAN(0, "mem-mode", &report.mem_mode, "mem access profile"),
+ OPT_CALLBACK(0, "percent-limit", &report, "percent",
+ "Don't show entries under that percent", parse_percent_limit),
+ OPT_CALLBACK(0, "percentage", NULL, "relative|absolute",
+ "how to display percentage of filtered entries", parse_filter_percentage),
+ OPT_END()
+ };
+ struct perf_data_file file = {
+ .mode = PERF_DATA_MODE_READ,
+ };
+
+ perf_config(report__config, &report);
+
+ argc = parse_options(argc, argv, options, report_usage, 0);
+
+ if (report.use_stdio)
+ use_browser = 0;
+ else if (report.use_tui)
+ use_browser = 1;
+ else if (report.use_gtk)
+ use_browser = 2;
+
+ if (report.inverted_callchain)
+ callchain_param.order = ORDER_CALLER;
+
+ if (!input_name || !strlen(input_name)) {
+ if (!fstat(STDIN_FILENO, &st) && S_ISFIFO(st.st_mode))
+ input_name = "-";
+ else
+ input_name = "perf.data";
+ }
+
+ file.path = input_name;
+ file.force = report.force;
+
+repeat:
+ session = perf_session__new(&file, false, &report.tool);
+ if (session == NULL)
+ return -ENOMEM;
+
+ report.session = session;
+
+ has_br_stack = perf_header__has_feat(&session->header,
+ HEADER_BRANCH_STACK);
+
+ if (branch_mode == -1 && has_br_stack) {
+ sort__mode = SORT_MODE__BRANCH;
+ symbol_conf.cumulate_callchain = false;
+ }
+
+ if (report.mem_mode) {
+ if (sort__mode == SORT_MODE__BRANCH) {
+ pr_err("branch and mem mode incompatible\n");
+ goto error;
+ }
+ sort__mode = SORT_MODE__MEMORY;
+ symbol_conf.cumulate_callchain = false;
+ }
+
+ if (setup_sorting() < 0) {
+ if (sort_order)
+ parse_options_usage(report_usage, options, "s", 1);
+ if (field_order)
+ parse_options_usage(sort_order ? NULL : report_usage,
+ options, "F", 1);
+ goto error;
+ }
+
+ /* Force tty output for header output. */
+ if (report.header || report.header_only)
+ use_browser = 0;
+
+ if (strcmp(input_name, "-") != 0)
+ setup_browser(true);
+ else
+ use_browser = 0;
+
+ if (report.header || report.header_only) {
+ perf_session__fprintf_info(session, stdout,
+ report.show_full_info);
+ if (report.header_only)
+ return 0;
+ } else if (use_browser == 0) {
+ fputs("# To display the perf.data header info, please use --header/--header-only options.\n#\n",
+ stdout);
+ }
+
+ /*
+ * Only in the TUI browser we are doing integrated annotation,
+ * so don't allocate extra space that won't be used in the stdio
+ * implementation.
+ */
+ if (ui__has_annotation()) {
+ symbol_conf.priv_size = sizeof(struct annotation);
+ machines__set_symbol_filter(&session->machines,
+ symbol__annotate_init);
+ /*
+ * For searching by name on the "Browse map details".
+ * providing it only in verbose mode not to bloat too
+ * much struct symbol.
+ */
+ if (verbose) {
+ /*
+ * XXX: Need to provide a less kludgy way to ask for
+ * more space per symbol, the u32 is for the index on
+ * the ui browser.
+ * See symbol__browser_index.
+ */
+ symbol_conf.priv_size += sizeof(u32);
+ symbol_conf.sort_by_name = true;
+ }
+ }
+
+ if (symbol__init() < 0)
+ goto error;
+
+ if (argc) {
+ /*
+ * Special case: if there's an argument left then assume that
+ * it's a symbol filter:
+ */
+ if (argc > 1)
+ usage_with_options(report_usage, options);
+
+ report.symbol_filter_str = argv[0];
+ }
+
+ sort__setup_elide(stdout);
+
+ ret = __cmd_report(&report);
+ if (ret == K_SWITCH_INPUT_DATA) {
+ perf_session__delete(session);
+ goto repeat;
+ } else
+ ret = 0;
+
+error:
+ perf_session__delete(session);
+ return ret;
+}
diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c
new file mode 100644
index 00000000000..c38d06c0477
--- /dev/null
+++ b/tools/perf/builtin-sched.c
@@ -0,0 +1,1777 @@
+#include "builtin.h"
+#include "perf.h"
+
+#include "util/util.h"
+#include "util/evlist.h"
+#include "util/cache.h"
+#include "util/evsel.h"
+#include "util/symbol.h"
+#include "util/thread.h"
+#include "util/header.h"
+#include "util/session.h"
+#include "util/tool.h"
+
+#include "util/parse-options.h"
+#include "util/trace-event.h"
+
+#include "util/debug.h"
+
+#include <sys/prctl.h>
+#include <sys/resource.h>
+
+#include <semaphore.h>
+#include <pthread.h>
+#include <math.h>
+
+#define PR_SET_NAME 15 /* Set process name */
+#define MAX_CPUS 4096
+#define COMM_LEN 20
+#define SYM_LEN 129
+#define MAX_PID 65536
+
+struct sched_atom;
+
+struct task_desc {
+ unsigned long nr;
+ unsigned long pid;
+ char comm[COMM_LEN];
+
+ unsigned long nr_events;
+ unsigned long curr_event;
+ struct sched_atom **atoms;
+
+ pthread_t thread;
+ sem_t sleep_sem;
+
+ sem_t ready_for_work;
+ sem_t work_done_sem;
+
+ u64 cpu_usage;
+};
+
+enum sched_event_type {
+ SCHED_EVENT_RUN,
+ SCHED_EVENT_SLEEP,
+ SCHED_EVENT_WAKEUP,
+ SCHED_EVENT_MIGRATION,
+};
+
+struct sched_atom {
+ enum sched_event_type type;
+ int specific_wait;
+ u64 timestamp;
+ u64 duration;
+ unsigned long nr;
+ sem_t *wait_sem;
+ struct task_desc *wakee;
+};
+
+#define TASK_STATE_TO_CHAR_STR "RSDTtZXxKWP"
+
+enum thread_state {
+ THREAD_SLEEPING = 0,
+ THREAD_WAIT_CPU,
+ THREAD_SCHED_IN,
+ THREAD_IGNORE
+};
+
+struct work_atom {
+ struct list_head list;
+ enum thread_state state;
+ u64 sched_out_time;
+ u64 wake_up_time;
+ u64 sched_in_time;
+ u64 runtime;
+};
+
+struct work_atoms {
+ struct list_head work_list;
+ struct thread *thread;
+ struct rb_node node;
+ u64 max_lat;
+ u64 max_lat_at;
+ u64 total_lat;
+ u64 nb_atoms;
+ u64 total_runtime;
+};
+
+typedef int (*sort_fn_t)(struct work_atoms *, struct work_atoms *);
+
+struct perf_sched;
+
+struct trace_sched_handler {
+ int (*switch_event)(struct perf_sched *sched, struct perf_evsel *evsel,
+ struct perf_sample *sample, struct machine *machine);
+
+ int (*runtime_event)(struct perf_sched *sched, struct perf_evsel *evsel,
+ struct perf_sample *sample, struct machine *machine);
+
+ int (*wakeup_event)(struct perf_sched *sched, struct perf_evsel *evsel,
+ struct perf_sample *sample, struct machine *machine);
+
+ /* PERF_RECORD_FORK event, not sched_process_fork tracepoint */
+ int (*fork_event)(struct perf_sched *sched, union perf_event *event,
+ struct machine *machine);
+
+ int (*migrate_task_event)(struct perf_sched *sched,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct machine *machine);
+};
+
+struct perf_sched {
+ struct perf_tool tool;
+ const char *sort_order;
+ unsigned long nr_tasks;
+ struct task_desc *pid_to_task[MAX_PID];
+ struct task_desc **tasks;
+ const struct trace_sched_handler *tp_handler;
+ pthread_mutex_t start_work_mutex;
+ pthread_mutex_t work_done_wait_mutex;
+ int profile_cpu;
+/*
+ * Track the current task - that way we can know whether there's any
+ * weird events, such as a task being switched away that is not current.
+ */
+ int max_cpu;
+ u32 curr_pid[MAX_CPUS];
+ struct thread *curr_thread[MAX_CPUS];
+ char next_shortname1;
+ char next_shortname2;
+ unsigned int replay_repeat;
+ unsigned long nr_run_events;
+ unsigned long nr_sleep_events;
+ unsigned long nr_wakeup_events;
+ unsigned long nr_sleep_corrections;
+ unsigned long nr_run_events_optimized;
+ unsigned long targetless_wakeups;
+ unsigned long multitarget_wakeups;
+ unsigned long nr_runs;
+ unsigned long nr_timestamps;
+ unsigned long nr_unordered_timestamps;
+ unsigned long nr_context_switch_bugs;
+ unsigned long nr_events;
+ unsigned long nr_lost_chunks;
+ unsigned long nr_lost_events;
+ u64 run_measurement_overhead;
+ u64 sleep_measurement_overhead;
+ u64 start_time;
+ u64 cpu_usage;
+ u64 runavg_cpu_usage;
+ u64 parent_cpu_usage;
+ u64 runavg_parent_cpu_usage;
+ u64 sum_runtime;
+ u64 sum_fluct;
+ u64 run_avg;
+ u64 all_runtime;
+ u64 all_count;
+ u64 cpu_last_switched[MAX_CPUS];
+ struct rb_root atom_root, sorted_atom_root;
+ struct list_head sort_list, cmp_pid;
+};
+
+static u64 get_nsecs(void)
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+
+ return ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+}
+
+static void burn_nsecs(struct perf_sched *sched, u64 nsecs)
+{
+ u64 T0 = get_nsecs(), T1;
+
+ do {
+ T1 = get_nsecs();
+ } while (T1 + sched->run_measurement_overhead < T0 + nsecs);
+}
+
+static void sleep_nsecs(u64 nsecs)
+{
+ struct timespec ts;
+
+ ts.tv_nsec = nsecs % 999999999;
+ ts.tv_sec = nsecs / 999999999;
+
+ nanosleep(&ts, NULL);
+}
+
+static void calibrate_run_measurement_overhead(struct perf_sched *sched)
+{
+ u64 T0, T1, delta, min_delta = 1000000000ULL;
+ int i;
+
+ for (i = 0; i < 10; i++) {
+ T0 = get_nsecs();
+ burn_nsecs(sched, 0);
+ T1 = get_nsecs();
+ delta = T1-T0;
+ min_delta = min(min_delta, delta);
+ }
+ sched->run_measurement_overhead = min_delta;
+
+ printf("run measurement overhead: %" PRIu64 " nsecs\n", min_delta);
+}
+
+static void calibrate_sleep_measurement_overhead(struct perf_sched *sched)
+{
+ u64 T0, T1, delta, min_delta = 1000000000ULL;
+ int i;
+
+ for (i = 0; i < 10; i++) {
+ T0 = get_nsecs();
+ sleep_nsecs(10000);
+ T1 = get_nsecs();
+ delta = T1-T0;
+ min_delta = min(min_delta, delta);
+ }
+ min_delta -= 10000;
+ sched->sleep_measurement_overhead = min_delta;
+
+ printf("sleep measurement overhead: %" PRIu64 " nsecs\n", min_delta);
+}
+
+static struct sched_atom *
+get_new_event(struct task_desc *task, u64 timestamp)
+{
+ struct sched_atom *event = zalloc(sizeof(*event));
+ unsigned long idx = task->nr_events;
+ size_t size;
+
+ event->timestamp = timestamp;
+ event->nr = idx;
+
+ task->nr_events++;
+ size = sizeof(struct sched_atom *) * task->nr_events;
+ task->atoms = realloc(task->atoms, size);
+ BUG_ON(!task->atoms);
+
+ task->atoms[idx] = event;
+
+ return event;
+}
+
+static struct sched_atom *last_event(struct task_desc *task)
+{
+ if (!task->nr_events)
+ return NULL;
+
+ return task->atoms[task->nr_events - 1];
+}
+
+static void add_sched_event_run(struct perf_sched *sched, struct task_desc *task,
+ u64 timestamp, u64 duration)
+{
+ struct sched_atom *event, *curr_event = last_event(task);
+
+ /*
+ * optimize an existing RUN event by merging this one
+ * to it:
+ */
+ if (curr_event && curr_event->type == SCHED_EVENT_RUN) {
+ sched->nr_run_events_optimized++;
+ curr_event->duration += duration;
+ return;
+ }
+
+ event = get_new_event(task, timestamp);
+
+ event->type = SCHED_EVENT_RUN;
+ event->duration = duration;
+
+ sched->nr_run_events++;
+}
+
+static void add_sched_event_wakeup(struct perf_sched *sched, struct task_desc *task,
+ u64 timestamp, struct task_desc *wakee)
+{
+ struct sched_atom *event, *wakee_event;
+
+ event = get_new_event(task, timestamp);
+ event->type = SCHED_EVENT_WAKEUP;
+ event->wakee = wakee;
+
+ wakee_event = last_event(wakee);
+ if (!wakee_event || wakee_event->type != SCHED_EVENT_SLEEP) {
+ sched->targetless_wakeups++;
+ return;
+ }
+ if (wakee_event->wait_sem) {
+ sched->multitarget_wakeups++;
+ return;
+ }
+
+ wakee_event->wait_sem = zalloc(sizeof(*wakee_event->wait_sem));
+ sem_init(wakee_event->wait_sem, 0, 0);
+ wakee_event->specific_wait = 1;
+ event->wait_sem = wakee_event->wait_sem;
+
+ sched->nr_wakeup_events++;
+}
+
+static void add_sched_event_sleep(struct perf_sched *sched, struct task_desc *task,
+ u64 timestamp, u64 task_state __maybe_unused)
+{
+ struct sched_atom *event = get_new_event(task, timestamp);
+
+ event->type = SCHED_EVENT_SLEEP;
+
+ sched->nr_sleep_events++;
+}
+
+static struct task_desc *register_pid(struct perf_sched *sched,
+ unsigned long pid, const char *comm)
+{
+ struct task_desc *task;
+
+ BUG_ON(pid >= MAX_PID);
+
+ task = sched->pid_to_task[pid];
+
+ if (task)
+ return task;
+
+ task = zalloc(sizeof(*task));
+ task->pid = pid;
+ task->nr = sched->nr_tasks;
+ strcpy(task->comm, comm);
+ /*
+ * every task starts in sleeping state - this gets ignored
+ * if there's no wakeup pointing to this sleep state:
+ */
+ add_sched_event_sleep(sched, task, 0, 0);
+
+ sched->pid_to_task[pid] = task;
+ sched->nr_tasks++;
+ sched->tasks = realloc(sched->tasks, sched->nr_tasks * sizeof(struct task_task *));
+ BUG_ON(!sched->tasks);
+ sched->tasks[task->nr] = task;
+
+ if (verbose)
+ printf("registered task #%ld, PID %ld (%s)\n", sched->nr_tasks, pid, comm);
+
+ return task;
+}
+
+
+static void print_task_traces(struct perf_sched *sched)
+{
+ struct task_desc *task;
+ unsigned long i;
+
+ for (i = 0; i < sched->nr_tasks; i++) {
+ task = sched->tasks[i];
+ printf("task %6ld (%20s:%10ld), nr_events: %ld\n",
+ task->nr, task->comm, task->pid, task->nr_events);
+ }
+}
+
+static void add_cross_task_wakeups(struct perf_sched *sched)
+{
+ struct task_desc *task1, *task2;
+ unsigned long i, j;
+
+ for (i = 0; i < sched->nr_tasks; i++) {
+ task1 = sched->tasks[i];
+ j = i + 1;
+ if (j == sched->nr_tasks)
+ j = 0;
+ task2 = sched->tasks[j];
+ add_sched_event_wakeup(sched, task1, 0, task2);
+ }
+}
+
+static void perf_sched__process_event(struct perf_sched *sched,
+ struct sched_atom *atom)
+{
+ int ret = 0;
+
+ switch (atom->type) {
+ case SCHED_EVENT_RUN:
+ burn_nsecs(sched, atom->duration);
+ break;
+ case SCHED_EVENT_SLEEP:
+ if (atom->wait_sem)
+ ret = sem_wait(atom->wait_sem);
+ BUG_ON(ret);
+ break;
+ case SCHED_EVENT_WAKEUP:
+ if (atom->wait_sem)
+ ret = sem_post(atom->wait_sem);
+ BUG_ON(ret);
+ break;
+ case SCHED_EVENT_MIGRATION:
+ break;
+ default:
+ BUG_ON(1);
+ }
+}
+
+static u64 get_cpu_usage_nsec_parent(void)
+{
+ struct rusage ru;
+ u64 sum;
+ int err;
+
+ err = getrusage(RUSAGE_SELF, &ru);
+ BUG_ON(err);
+
+ sum = ru.ru_utime.tv_sec*1e9 + ru.ru_utime.tv_usec*1e3;
+ sum += ru.ru_stime.tv_sec*1e9 + ru.ru_stime.tv_usec*1e3;
+
+ return sum;
+}
+
+static int self_open_counters(void)
+{
+ struct perf_event_attr attr;
+ int fd;
+
+ memset(&attr, 0, sizeof(attr));
+
+ attr.type = PERF_TYPE_SOFTWARE;
+ attr.config = PERF_COUNT_SW_TASK_CLOCK;
+
+ fd = sys_perf_event_open(&attr, 0, -1, -1, 0);
+
+ if (fd < 0)
+ pr_err("Error: sys_perf_event_open() syscall returned "
+ "with %d (%s)\n", fd, strerror(errno));
+ return fd;
+}
+
+static u64 get_cpu_usage_nsec_self(int fd)
+{
+ u64 runtime;
+ int ret;
+
+ ret = read(fd, &runtime, sizeof(runtime));
+ BUG_ON(ret != sizeof(runtime));
+
+ return runtime;
+}
+
+struct sched_thread_parms {
+ struct task_desc *task;
+ struct perf_sched *sched;
+};
+
+static void *thread_func(void *ctx)
+{
+ struct sched_thread_parms *parms = ctx;
+ struct task_desc *this_task = parms->task;
+ struct perf_sched *sched = parms->sched;
+ u64 cpu_usage_0, cpu_usage_1;
+ unsigned long i, ret;
+ char comm2[22];
+ int fd;
+
+ zfree(&parms);
+
+ sprintf(comm2, ":%s", this_task->comm);
+ prctl(PR_SET_NAME, comm2);
+ fd = self_open_counters();
+ if (fd < 0)
+ return NULL;
+again:
+ ret = sem_post(&this_task->ready_for_work);
+ BUG_ON(ret);
+ ret = pthread_mutex_lock(&sched->start_work_mutex);
+ BUG_ON(ret);
+ ret = pthread_mutex_unlock(&sched->start_work_mutex);
+ BUG_ON(ret);
+
+ cpu_usage_0 = get_cpu_usage_nsec_self(fd);
+
+ for (i = 0; i < this_task->nr_events; i++) {
+ this_task->curr_event = i;
+ perf_sched__process_event(sched, this_task->atoms[i]);
+ }
+
+ cpu_usage_1 = get_cpu_usage_nsec_self(fd);
+ this_task->cpu_usage = cpu_usage_1 - cpu_usage_0;
+ ret = sem_post(&this_task->work_done_sem);
+ BUG_ON(ret);
+
+ ret = pthread_mutex_lock(&sched->work_done_wait_mutex);
+ BUG_ON(ret);
+ ret = pthread_mutex_unlock(&sched->work_done_wait_mutex);
+ BUG_ON(ret);
+
+ goto again;
+}
+
+static void create_tasks(struct perf_sched *sched)
+{
+ struct task_desc *task;
+ pthread_attr_t attr;
+ unsigned long i;
+ int err;
+
+ err = pthread_attr_init(&attr);
+ BUG_ON(err);
+ err = pthread_attr_setstacksize(&attr,
+ (size_t) max(16 * 1024, PTHREAD_STACK_MIN));
+ BUG_ON(err);
+ err = pthread_mutex_lock(&sched->start_work_mutex);
+ BUG_ON(err);
+ err = pthread_mutex_lock(&sched->work_done_wait_mutex);
+ BUG_ON(err);
+ for (i = 0; i < sched->nr_tasks; i++) {
+ struct sched_thread_parms *parms = malloc(sizeof(*parms));
+ BUG_ON(parms == NULL);
+ parms->task = task = sched->tasks[i];
+ parms->sched = sched;
+ sem_init(&task->sleep_sem, 0, 0);
+ sem_init(&task->ready_for_work, 0, 0);
+ sem_init(&task->work_done_sem, 0, 0);
+ task->curr_event = 0;
+ err = pthread_create(&task->thread, &attr, thread_func, parms);
+ BUG_ON(err);
+ }
+}
+
+static void wait_for_tasks(struct perf_sched *sched)
+{
+ u64 cpu_usage_0, cpu_usage_1;
+ struct task_desc *task;
+ unsigned long i, ret;
+
+ sched->start_time = get_nsecs();
+ sched->cpu_usage = 0;
+ pthread_mutex_unlock(&sched->work_done_wait_mutex);
+
+ for (i = 0; i < sched->nr_tasks; i++) {
+ task = sched->tasks[i];
+ ret = sem_wait(&task->ready_for_work);
+ BUG_ON(ret);
+ sem_init(&task->ready_for_work, 0, 0);
+ }
+ ret = pthread_mutex_lock(&sched->work_done_wait_mutex);
+ BUG_ON(ret);
+
+ cpu_usage_0 = get_cpu_usage_nsec_parent();
+
+ pthread_mutex_unlock(&sched->start_work_mutex);
+
+ for (i = 0; i < sched->nr_tasks; i++) {
+ task = sched->tasks[i];
+ ret = sem_wait(&task->work_done_sem);
+ BUG_ON(ret);
+ sem_init(&task->work_done_sem, 0, 0);
+ sched->cpu_usage += task->cpu_usage;
+ task->cpu_usage = 0;
+ }
+
+ cpu_usage_1 = get_cpu_usage_nsec_parent();
+ if (!sched->runavg_cpu_usage)
+ sched->runavg_cpu_usage = sched->cpu_usage;
+ sched->runavg_cpu_usage = (sched->runavg_cpu_usage * 9 + sched->cpu_usage) / 10;
+
+ sched->parent_cpu_usage = cpu_usage_1 - cpu_usage_0;
+ if (!sched->runavg_parent_cpu_usage)
+ sched->runavg_parent_cpu_usage = sched->parent_cpu_usage;
+ sched->runavg_parent_cpu_usage = (sched->runavg_parent_cpu_usage * 9 +
+ sched->parent_cpu_usage)/10;
+
+ ret = pthread_mutex_lock(&sched->start_work_mutex);
+ BUG_ON(ret);
+
+ for (i = 0; i < sched->nr_tasks; i++) {
+ task = sched->tasks[i];
+ sem_init(&task->sleep_sem, 0, 0);
+ task->curr_event = 0;
+ }
+}
+
+static void run_one_test(struct perf_sched *sched)
+{
+ u64 T0, T1, delta, avg_delta, fluct;
+
+ T0 = get_nsecs();
+ wait_for_tasks(sched);
+ T1 = get_nsecs();
+
+ delta = T1 - T0;
+ sched->sum_runtime += delta;
+ sched->nr_runs++;
+
+ avg_delta = sched->sum_runtime / sched->nr_runs;
+ if (delta < avg_delta)
+ fluct = avg_delta - delta;
+ else
+ fluct = delta - avg_delta;
+ sched->sum_fluct += fluct;
+ if (!sched->run_avg)
+ sched->run_avg = delta;
+ sched->run_avg = (sched->run_avg * 9 + delta) / 10;
+
+ printf("#%-3ld: %0.3f, ", sched->nr_runs, (double)delta / 1000000.0);
+
+ printf("ravg: %0.2f, ", (double)sched->run_avg / 1e6);
+
+ printf("cpu: %0.2f / %0.2f",
+ (double)sched->cpu_usage / 1e6, (double)sched->runavg_cpu_usage / 1e6);
+
+#if 0
+ /*
+ * rusage statistics done by the parent, these are less
+ * accurate than the sched->sum_exec_runtime based statistics:
+ */
+ printf(" [%0.2f / %0.2f]",
+ (double)sched->parent_cpu_usage/1e6,
+ (double)sched->runavg_parent_cpu_usage/1e6);
+#endif
+
+ printf("\n");
+
+ if (sched->nr_sleep_corrections)
+ printf(" (%ld sleep corrections)\n", sched->nr_sleep_corrections);
+ sched->nr_sleep_corrections = 0;
+}
+
+static void test_calibrations(struct perf_sched *sched)
+{
+ u64 T0, T1;
+
+ T0 = get_nsecs();
+ burn_nsecs(sched, 1e6);
+ T1 = get_nsecs();
+
+ printf("the run test took %" PRIu64 " nsecs\n", T1 - T0);
+
+ T0 = get_nsecs();
+ sleep_nsecs(1e6);
+ T1 = get_nsecs();
+
+ printf("the sleep test took %" PRIu64 " nsecs\n", T1 - T0);
+}
+
+static int
+replay_wakeup_event(struct perf_sched *sched,
+ struct perf_evsel *evsel, struct perf_sample *sample,
+ struct machine *machine __maybe_unused)
+{
+ const char *comm = perf_evsel__strval(evsel, sample, "comm");
+ const u32 pid = perf_evsel__intval(evsel, sample, "pid");
+ struct task_desc *waker, *wakee;
+
+ if (verbose) {
+ printf("sched_wakeup event %p\n", evsel);
+
+ printf(" ... pid %d woke up %s/%d\n", sample->tid, comm, pid);
+ }
+
+ waker = register_pid(sched, sample->tid, "<unknown>");
+ wakee = register_pid(sched, pid, comm);
+
+ add_sched_event_wakeup(sched, waker, sample->time, wakee);
+ return 0;
+}
+
+static int replay_switch_event(struct perf_sched *sched,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct machine *machine __maybe_unused)
+{
+ const char *prev_comm = perf_evsel__strval(evsel, sample, "prev_comm"),
+ *next_comm = perf_evsel__strval(evsel, sample, "next_comm");
+ const u32 prev_pid = perf_evsel__intval(evsel, sample, "prev_pid"),
+ next_pid = perf_evsel__intval(evsel, sample, "next_pid");
+ const u64 prev_state = perf_evsel__intval(evsel, sample, "prev_state");
+ struct task_desc *prev, __maybe_unused *next;
+ u64 timestamp0, timestamp = sample->time;
+ int cpu = sample->cpu;
+ s64 delta;
+
+ if (verbose)
+ printf("sched_switch event %p\n", evsel);
+
+ if (cpu >= MAX_CPUS || cpu < 0)
+ return 0;
+
+ timestamp0 = sched->cpu_last_switched[cpu];
+ if (timestamp0)
+ delta = timestamp - timestamp0;
+ else
+ delta = 0;
+
+ if (delta < 0) {
+ pr_err("hm, delta: %" PRIu64 " < 0 ?\n", delta);
+ return -1;
+ }
+
+ pr_debug(" ... switch from %s/%d to %s/%d [ran %" PRIu64 " nsecs]\n",
+ prev_comm, prev_pid, next_comm, next_pid, delta);
+
+ prev = register_pid(sched, prev_pid, prev_comm);
+ next = register_pid(sched, next_pid, next_comm);
+
+ sched->cpu_last_switched[cpu] = timestamp;
+
+ add_sched_event_run(sched, prev, timestamp, delta);
+ add_sched_event_sleep(sched, prev, timestamp, prev_state);
+
+ return 0;
+}
+
+static int replay_fork_event(struct perf_sched *sched,
+ union perf_event *event,
+ struct machine *machine)
+{
+ struct thread *child, *parent;
+
+ child = machine__findnew_thread(machine, event->fork.pid,
+ event->fork.tid);
+ parent = machine__findnew_thread(machine, event->fork.ppid,
+ event->fork.ptid);
+
+ if (child == NULL || parent == NULL) {
+ pr_debug("thread does not exist on fork event: child %p, parent %p\n",
+ child, parent);
+ return 0;
+ }
+
+ if (verbose) {
+ printf("fork event\n");
+ printf("... parent: %s/%d\n", thread__comm_str(parent), parent->tid);
+ printf("... child: %s/%d\n", thread__comm_str(child), child->tid);
+ }
+
+ register_pid(sched, parent->tid, thread__comm_str(parent));
+ register_pid(sched, child->tid, thread__comm_str(child));
+ return 0;
+}
+
+struct sort_dimension {
+ const char *name;
+ sort_fn_t cmp;
+ struct list_head list;
+};
+
+static int
+thread_lat_cmp(struct list_head *list, struct work_atoms *l, struct work_atoms *r)
+{
+ struct sort_dimension *sort;
+ int ret = 0;
+
+ BUG_ON(list_empty(list));
+
+ list_for_each_entry(sort, list, list) {
+ ret = sort->cmp(l, r);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+static struct work_atoms *
+thread_atoms_search(struct rb_root *root, struct thread *thread,
+ struct list_head *sort_list)
+{
+ struct rb_node *node = root->rb_node;
+ struct work_atoms key = { .thread = thread };
+
+ while (node) {
+ struct work_atoms *atoms;
+ int cmp;
+
+ atoms = container_of(node, struct work_atoms, node);
+
+ cmp = thread_lat_cmp(sort_list, &key, atoms);
+ if (cmp > 0)
+ node = node->rb_left;
+ else if (cmp < 0)
+ node = node->rb_right;
+ else {
+ BUG_ON(thread != atoms->thread);
+ return atoms;
+ }
+ }
+ return NULL;
+}
+
+static void
+__thread_latency_insert(struct rb_root *root, struct work_atoms *data,
+ struct list_head *sort_list)
+{
+ struct rb_node **new = &(root->rb_node), *parent = NULL;
+
+ while (*new) {
+ struct work_atoms *this;
+ int cmp;
+
+ this = container_of(*new, struct work_atoms, node);
+ parent = *new;
+
+ cmp = thread_lat_cmp(sort_list, data, this);
+
+ if (cmp > 0)
+ new = &((*new)->rb_left);
+ else
+ new = &((*new)->rb_right);
+ }
+
+ rb_link_node(&data->node, parent, new);
+ rb_insert_color(&data->node, root);
+}
+
+static int thread_atoms_insert(struct perf_sched *sched, struct thread *thread)
+{
+ struct work_atoms *atoms = zalloc(sizeof(*atoms));
+ if (!atoms) {
+ pr_err("No memory at %s\n", __func__);
+ return -1;
+ }
+
+ atoms->thread = thread;
+ INIT_LIST_HEAD(&atoms->work_list);
+ __thread_latency_insert(&sched->atom_root, atoms, &sched->cmp_pid);
+ return 0;
+}
+
+static char sched_out_state(u64 prev_state)
+{
+ const char *str = TASK_STATE_TO_CHAR_STR;
+
+ return str[prev_state];
+}
+
+static int
+add_sched_out_event(struct work_atoms *atoms,
+ char run_state,
+ u64 timestamp)
+{
+ struct work_atom *atom = zalloc(sizeof(*atom));
+ if (!atom) {
+ pr_err("Non memory at %s", __func__);
+ return -1;
+ }
+
+ atom->sched_out_time = timestamp;
+
+ if (run_state == 'R') {
+ atom->state = THREAD_WAIT_CPU;
+ atom->wake_up_time = atom->sched_out_time;
+ }
+
+ list_add_tail(&atom->list, &atoms->work_list);
+ return 0;
+}
+
+static void
+add_runtime_event(struct work_atoms *atoms, u64 delta,
+ u64 timestamp __maybe_unused)
+{
+ struct work_atom *atom;
+
+ BUG_ON(list_empty(&atoms->work_list));
+
+ atom = list_entry(atoms->work_list.prev, struct work_atom, list);
+
+ atom->runtime += delta;
+ atoms->total_runtime += delta;
+}
+
+static void
+add_sched_in_event(struct work_atoms *atoms, u64 timestamp)
+{
+ struct work_atom *atom;
+ u64 delta;
+
+ if (list_empty(&atoms->work_list))
+ return;
+
+ atom = list_entry(atoms->work_list.prev, struct work_atom, list);
+
+ if (atom->state != THREAD_WAIT_CPU)
+ return;
+
+ if (timestamp < atom->wake_up_time) {
+ atom->state = THREAD_IGNORE;
+ return;
+ }
+
+ atom->state = THREAD_SCHED_IN;
+ atom->sched_in_time = timestamp;
+
+ delta = atom->sched_in_time - atom->wake_up_time;
+ atoms->total_lat += delta;
+ if (delta > atoms->max_lat) {
+ atoms->max_lat = delta;
+ atoms->max_lat_at = timestamp;
+ }
+ atoms->nb_atoms++;
+}
+
+static int latency_switch_event(struct perf_sched *sched,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ const u32 prev_pid = perf_evsel__intval(evsel, sample, "prev_pid"),
+ next_pid = perf_evsel__intval(evsel, sample, "next_pid");
+ const u64 prev_state = perf_evsel__intval(evsel, sample, "prev_state");
+ struct work_atoms *out_events, *in_events;
+ struct thread *sched_out, *sched_in;
+ u64 timestamp0, timestamp = sample->time;
+ int cpu = sample->cpu;
+ s64 delta;
+
+ BUG_ON(cpu >= MAX_CPUS || cpu < 0);
+
+ timestamp0 = sched->cpu_last_switched[cpu];
+ sched->cpu_last_switched[cpu] = timestamp;
+ if (timestamp0)
+ delta = timestamp - timestamp0;
+ else
+ delta = 0;
+
+ if (delta < 0) {
+ pr_err("hm, delta: %" PRIu64 " < 0 ?\n", delta);
+ return -1;
+ }
+
+ sched_out = machine__findnew_thread(machine, 0, prev_pid);
+ sched_in = machine__findnew_thread(machine, 0, next_pid);
+
+ out_events = thread_atoms_search(&sched->atom_root, sched_out, &sched->cmp_pid);
+ if (!out_events) {
+ if (thread_atoms_insert(sched, sched_out))
+ return -1;
+ out_events = thread_atoms_search(&sched->atom_root, sched_out, &sched->cmp_pid);
+ if (!out_events) {
+ pr_err("out-event: Internal tree error");
+ return -1;
+ }
+ }
+ if (add_sched_out_event(out_events, sched_out_state(prev_state), timestamp))
+ return -1;
+
+ in_events = thread_atoms_search(&sched->atom_root, sched_in, &sched->cmp_pid);
+ if (!in_events) {
+ if (thread_atoms_insert(sched, sched_in))
+ return -1;
+ in_events = thread_atoms_search(&sched->atom_root, sched_in, &sched->cmp_pid);
+ if (!in_events) {
+ pr_err("in-event: Internal tree error");
+ return -1;
+ }
+ /*
+ * Take came in we have not heard about yet,
+ * add in an initial atom in runnable state:
+ */
+ if (add_sched_out_event(in_events, 'R', timestamp))
+ return -1;
+ }
+ add_sched_in_event(in_events, timestamp);
+
+ return 0;
+}
+
+static int latency_runtime_event(struct perf_sched *sched,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ const u32 pid = perf_evsel__intval(evsel, sample, "pid");
+ const u64 runtime = perf_evsel__intval(evsel, sample, "runtime");
+ struct thread *thread = machine__findnew_thread(machine, 0, pid);
+ struct work_atoms *atoms = thread_atoms_search(&sched->atom_root, thread, &sched->cmp_pid);
+ u64 timestamp = sample->time;
+ int cpu = sample->cpu;
+
+ BUG_ON(cpu >= MAX_CPUS || cpu < 0);
+ if (!atoms) {
+ if (thread_atoms_insert(sched, thread))
+ return -1;
+ atoms = thread_atoms_search(&sched->atom_root, thread, &sched->cmp_pid);
+ if (!atoms) {
+ pr_err("in-event: Internal tree error");
+ return -1;
+ }
+ if (add_sched_out_event(atoms, 'R', timestamp))
+ return -1;
+ }
+
+ add_runtime_event(atoms, runtime, timestamp);
+ return 0;
+}
+
+static int latency_wakeup_event(struct perf_sched *sched,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ const u32 pid = perf_evsel__intval(evsel, sample, "pid");
+ struct work_atoms *atoms;
+ struct work_atom *atom;
+ struct thread *wakee;
+ u64 timestamp = sample->time;
+
+ wakee = machine__findnew_thread(machine, 0, pid);
+ atoms = thread_atoms_search(&sched->atom_root, wakee, &sched->cmp_pid);
+ if (!atoms) {
+ if (thread_atoms_insert(sched, wakee))
+ return -1;
+ atoms = thread_atoms_search(&sched->atom_root, wakee, &sched->cmp_pid);
+ if (!atoms) {
+ pr_err("wakeup-event: Internal tree error");
+ return -1;
+ }
+ if (add_sched_out_event(atoms, 'S', timestamp))
+ return -1;
+ }
+
+ BUG_ON(list_empty(&atoms->work_list));
+
+ atom = list_entry(atoms->work_list.prev, struct work_atom, list);
+
+ /*
+ * As we do not guarantee the wakeup event happens when
+ * task is out of run queue, also may happen when task is
+ * on run queue and wakeup only change ->state to TASK_RUNNING,
+ * then we should not set the ->wake_up_time when wake up a
+ * task which is on run queue.
+ *
+ * You WILL be missing events if you've recorded only
+ * one CPU, or are only looking at only one, so don't
+ * skip in this case.
+ */
+ if (sched->profile_cpu == -1 && atom->state != THREAD_SLEEPING)
+ return 0;
+
+ sched->nr_timestamps++;
+ if (atom->sched_out_time > timestamp) {
+ sched->nr_unordered_timestamps++;
+ return 0;
+ }
+
+ atom->state = THREAD_WAIT_CPU;
+ atom->wake_up_time = timestamp;
+ return 0;
+}
+
+static int latency_migrate_task_event(struct perf_sched *sched,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ const u32 pid = perf_evsel__intval(evsel, sample, "pid");
+ u64 timestamp = sample->time;
+ struct work_atoms *atoms;
+ struct work_atom *atom;
+ struct thread *migrant;
+
+ /*
+ * Only need to worry about migration when profiling one CPU.
+ */
+ if (sched->profile_cpu == -1)
+ return 0;
+
+ migrant = machine__findnew_thread(machine, 0, pid);
+ atoms = thread_atoms_search(&sched->atom_root, migrant, &sched->cmp_pid);
+ if (!atoms) {
+ if (thread_atoms_insert(sched, migrant))
+ return -1;
+ register_pid(sched, migrant->tid, thread__comm_str(migrant));
+ atoms = thread_atoms_search(&sched->atom_root, migrant, &sched->cmp_pid);
+ if (!atoms) {
+ pr_err("migration-event: Internal tree error");
+ return -1;
+ }
+ if (add_sched_out_event(atoms, 'R', timestamp))
+ return -1;
+ }
+
+ BUG_ON(list_empty(&atoms->work_list));
+
+ atom = list_entry(atoms->work_list.prev, struct work_atom, list);
+ atom->sched_in_time = atom->sched_out_time = atom->wake_up_time = timestamp;
+
+ sched->nr_timestamps++;
+
+ if (atom->sched_out_time > timestamp)
+ sched->nr_unordered_timestamps++;
+
+ return 0;
+}
+
+static void output_lat_thread(struct perf_sched *sched, struct work_atoms *work_list)
+{
+ int i;
+ int ret;
+ u64 avg;
+
+ if (!work_list->nb_atoms)
+ return;
+ /*
+ * Ignore idle threads:
+ */
+ if (!strcmp(thread__comm_str(work_list->thread), "swapper"))
+ return;
+
+ sched->all_runtime += work_list->total_runtime;
+ sched->all_count += work_list->nb_atoms;
+
+ ret = printf(" %s:%d ", thread__comm_str(work_list->thread), work_list->thread->tid);
+
+ for (i = 0; i < 24 - ret; i++)
+ printf(" ");
+
+ avg = work_list->total_lat / work_list->nb_atoms;
+
+ printf("|%11.3f ms |%9" PRIu64 " | avg:%9.3f ms | max:%9.3f ms | max at: %13.6f s\n",
+ (double)work_list->total_runtime / 1e6,
+ work_list->nb_atoms, (double)avg / 1e6,
+ (double)work_list->max_lat / 1e6,
+ (double)work_list->max_lat_at / 1e9);
+}
+
+static int pid_cmp(struct work_atoms *l, struct work_atoms *r)
+{
+ if (l->thread->tid < r->thread->tid)
+ return -1;
+ if (l->thread->tid > r->thread->tid)
+ return 1;
+
+ return 0;
+}
+
+static int avg_cmp(struct work_atoms *l, struct work_atoms *r)
+{
+ u64 avgl, avgr;
+
+ if (!l->nb_atoms)
+ return -1;
+
+ if (!r->nb_atoms)
+ return 1;
+
+ avgl = l->total_lat / l->nb_atoms;
+ avgr = r->total_lat / r->nb_atoms;
+
+ if (avgl < avgr)
+ return -1;
+ if (avgl > avgr)
+ return 1;
+
+ return 0;
+}
+
+static int max_cmp(struct work_atoms *l, struct work_atoms *r)
+{
+ if (l->max_lat < r->max_lat)
+ return -1;
+ if (l->max_lat > r->max_lat)
+ return 1;
+
+ return 0;
+}
+
+static int switch_cmp(struct work_atoms *l, struct work_atoms *r)
+{
+ if (l->nb_atoms < r->nb_atoms)
+ return -1;
+ if (l->nb_atoms > r->nb_atoms)
+ return 1;
+
+ return 0;
+}
+
+static int runtime_cmp(struct work_atoms *l, struct work_atoms *r)
+{
+ if (l->total_runtime < r->total_runtime)
+ return -1;
+ if (l->total_runtime > r->total_runtime)
+ return 1;
+
+ return 0;
+}
+
+static int sort_dimension__add(const char *tok, struct list_head *list)
+{
+ size_t i;
+ static struct sort_dimension avg_sort_dimension = {
+ .name = "avg",
+ .cmp = avg_cmp,
+ };
+ static struct sort_dimension max_sort_dimension = {
+ .name = "max",
+ .cmp = max_cmp,
+ };
+ static struct sort_dimension pid_sort_dimension = {
+ .name = "pid",
+ .cmp = pid_cmp,
+ };
+ static struct sort_dimension runtime_sort_dimension = {
+ .name = "runtime",
+ .cmp = runtime_cmp,
+ };
+ static struct sort_dimension switch_sort_dimension = {
+ .name = "switch",
+ .cmp = switch_cmp,
+ };
+ struct sort_dimension *available_sorts[] = {
+ &pid_sort_dimension,
+ &avg_sort_dimension,
+ &max_sort_dimension,
+ &switch_sort_dimension,
+ &runtime_sort_dimension,
+ };
+
+ for (i = 0; i < ARRAY_SIZE(available_sorts); i++) {
+ if (!strcmp(available_sorts[i]->name, tok)) {
+ list_add_tail(&available_sorts[i]->list, list);
+
+ return 0;
+ }
+ }
+
+ return -1;
+}
+
+static void perf_sched__sort_lat(struct perf_sched *sched)
+{
+ struct rb_node *node;
+
+ for (;;) {
+ struct work_atoms *data;
+ node = rb_first(&sched->atom_root);
+ if (!node)
+ break;
+
+ rb_erase(node, &sched->atom_root);
+ data = rb_entry(node, struct work_atoms, node);
+ __thread_latency_insert(&sched->sorted_atom_root, data, &sched->sort_list);
+ }
+}
+
+static int process_sched_wakeup_event(struct perf_tool *tool,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct perf_sched *sched = container_of(tool, struct perf_sched, tool);
+
+ if (sched->tp_handler->wakeup_event)
+ return sched->tp_handler->wakeup_event(sched, evsel, sample, machine);
+
+ return 0;
+}
+
+static int map_switch_event(struct perf_sched *sched, struct perf_evsel *evsel,
+ struct perf_sample *sample, struct machine *machine)
+{
+ const u32 next_pid = perf_evsel__intval(evsel, sample, "next_pid");
+ struct thread *sched_in;
+ int new_shortname;
+ u64 timestamp0, timestamp = sample->time;
+ s64 delta;
+ int cpu, this_cpu = sample->cpu;
+
+ BUG_ON(this_cpu >= MAX_CPUS || this_cpu < 0);
+
+ if (this_cpu > sched->max_cpu)
+ sched->max_cpu = this_cpu;
+
+ timestamp0 = sched->cpu_last_switched[this_cpu];
+ sched->cpu_last_switched[this_cpu] = timestamp;
+ if (timestamp0)
+ delta = timestamp - timestamp0;
+ else
+ delta = 0;
+
+ if (delta < 0) {
+ pr_err("hm, delta: %" PRIu64 " < 0 ?\n", delta);
+ return -1;
+ }
+
+ sched_in = machine__findnew_thread(machine, 0, next_pid);
+
+ sched->curr_thread[this_cpu] = sched_in;
+
+ printf(" ");
+
+ new_shortname = 0;
+ if (!sched_in->shortname[0]) {
+ if (!strcmp(thread__comm_str(sched_in), "swapper")) {
+ /*
+ * Don't allocate a letter-number for swapper:0
+ * as a shortname. Instead, we use '.' for it.
+ */
+ sched_in->shortname[0] = '.';
+ sched_in->shortname[1] = ' ';
+ } else {
+ sched_in->shortname[0] = sched->next_shortname1;
+ sched_in->shortname[1] = sched->next_shortname2;
+
+ if (sched->next_shortname1 < 'Z') {
+ sched->next_shortname1++;
+ } else {
+ sched->next_shortname1 = 'A';
+ if (sched->next_shortname2 < '9')
+ sched->next_shortname2++;
+ else
+ sched->next_shortname2 = '0';
+ }
+ }
+ new_shortname = 1;
+ }
+
+ for (cpu = 0; cpu <= sched->max_cpu; cpu++) {
+ if (cpu != this_cpu)
+ printf(" ");
+ else
+ printf("*");
+
+ if (sched->curr_thread[cpu])
+ printf("%2s ", sched->curr_thread[cpu]->shortname);
+ else
+ printf(" ");
+ }
+
+ printf(" %12.6f secs ", (double)timestamp/1e9);
+ if (new_shortname) {
+ printf("%s => %s:%d\n",
+ sched_in->shortname, thread__comm_str(sched_in), sched_in->tid);
+ } else {
+ printf("\n");
+ }
+
+ return 0;
+}
+
+static int process_sched_switch_event(struct perf_tool *tool,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct perf_sched *sched = container_of(tool, struct perf_sched, tool);
+ int this_cpu = sample->cpu, err = 0;
+ u32 prev_pid = perf_evsel__intval(evsel, sample, "prev_pid"),
+ next_pid = perf_evsel__intval(evsel, sample, "next_pid");
+
+ if (sched->curr_pid[this_cpu] != (u32)-1) {
+ /*
+ * Are we trying to switch away a PID that is
+ * not current?
+ */
+ if (sched->curr_pid[this_cpu] != prev_pid)
+ sched->nr_context_switch_bugs++;
+ }
+
+ if (sched->tp_handler->switch_event)
+ err = sched->tp_handler->switch_event(sched, evsel, sample, machine);
+
+ sched->curr_pid[this_cpu] = next_pid;
+ return err;
+}
+
+static int process_sched_runtime_event(struct perf_tool *tool,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct perf_sched *sched = container_of(tool, struct perf_sched, tool);
+
+ if (sched->tp_handler->runtime_event)
+ return sched->tp_handler->runtime_event(sched, evsel, sample, machine);
+
+ return 0;
+}
+
+static int perf_sched__process_fork_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct perf_sched *sched = container_of(tool, struct perf_sched, tool);
+
+ /* run the fork event through the perf machineruy */
+ perf_event__process_fork(tool, event, sample, machine);
+
+ /* and then run additional processing needed for this command */
+ if (sched->tp_handler->fork_event)
+ return sched->tp_handler->fork_event(sched, event, machine);
+
+ return 0;
+}
+
+static int process_sched_migrate_task_event(struct perf_tool *tool,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct perf_sched *sched = container_of(tool, struct perf_sched, tool);
+
+ if (sched->tp_handler->migrate_task_event)
+ return sched->tp_handler->migrate_task_event(sched, evsel, sample, machine);
+
+ return 0;
+}
+
+typedef int (*tracepoint_handler)(struct perf_tool *tool,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct machine *machine);
+
+static int perf_sched__process_tracepoint_sample(struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ int err = 0;
+
+ evsel->hists.stats.total_period += sample->period;
+ hists__inc_nr_samples(&evsel->hists, true);
+
+ if (evsel->handler != NULL) {
+ tracepoint_handler f = evsel->handler;
+ err = f(tool, evsel, sample, machine);
+ }
+
+ return err;
+}
+
+static int perf_sched__read_events(struct perf_sched *sched,
+ struct perf_session **psession)
+{
+ const struct perf_evsel_str_handler handlers[] = {
+ { "sched:sched_switch", process_sched_switch_event, },
+ { "sched:sched_stat_runtime", process_sched_runtime_event, },
+ { "sched:sched_wakeup", process_sched_wakeup_event, },
+ { "sched:sched_wakeup_new", process_sched_wakeup_event, },
+ { "sched:sched_migrate_task", process_sched_migrate_task_event, },
+ };
+ struct perf_session *session;
+ struct perf_data_file file = {
+ .path = input_name,
+ .mode = PERF_DATA_MODE_READ,
+ };
+
+ session = perf_session__new(&file, false, &sched->tool);
+ if (session == NULL) {
+ pr_debug("No Memory for session\n");
+ return -1;
+ }
+
+ if (perf_session__set_tracepoints_handlers(session, handlers))
+ goto out_delete;
+
+ if (perf_session__has_traces(session, "record -R")) {
+ int err = perf_session__process_events(session, &sched->tool);
+ if (err) {
+ pr_err("Failed to process events, error %d", err);
+ goto out_delete;
+ }
+
+ sched->nr_events = session->stats.nr_events[0];
+ sched->nr_lost_events = session->stats.total_lost;
+ sched->nr_lost_chunks = session->stats.nr_events[PERF_RECORD_LOST];
+ }
+
+ if (psession)
+ *psession = session;
+ else
+ perf_session__delete(session);
+
+ return 0;
+
+out_delete:
+ perf_session__delete(session);
+ return -1;
+}
+
+static void print_bad_events(struct perf_sched *sched)
+{
+ if (sched->nr_unordered_timestamps && sched->nr_timestamps) {
+ printf(" INFO: %.3f%% unordered timestamps (%ld out of %ld)\n",
+ (double)sched->nr_unordered_timestamps/(double)sched->nr_timestamps*100.0,
+ sched->nr_unordered_timestamps, sched->nr_timestamps);
+ }
+ if (sched->nr_lost_events && sched->nr_events) {
+ printf(" INFO: %.3f%% lost events (%ld out of %ld, in %ld chunks)\n",
+ (double)sched->nr_lost_events/(double)sched->nr_events * 100.0,
+ sched->nr_lost_events, sched->nr_events, sched->nr_lost_chunks);
+ }
+ if (sched->nr_context_switch_bugs && sched->nr_timestamps) {
+ printf(" INFO: %.3f%% context switch bugs (%ld out of %ld)",
+ (double)sched->nr_context_switch_bugs/(double)sched->nr_timestamps*100.0,
+ sched->nr_context_switch_bugs, sched->nr_timestamps);
+ if (sched->nr_lost_events)
+ printf(" (due to lost events?)");
+ printf("\n");
+ }
+}
+
+static int perf_sched__lat(struct perf_sched *sched)
+{
+ struct rb_node *next;
+ struct perf_session *session;
+
+ setup_pager();
+
+ /* save session -- references to threads are held in work_list */
+ if (perf_sched__read_events(sched, &session))
+ return -1;
+
+ perf_sched__sort_lat(sched);
+
+ printf("\n -----------------------------------------------------------------------------------------------------------------\n");
+ printf(" Task | Runtime ms | Switches | Average delay ms | Maximum delay ms | Maximum delay at |\n");
+ printf(" -----------------------------------------------------------------------------------------------------------------\n");
+
+ next = rb_first(&sched->sorted_atom_root);
+
+ while (next) {
+ struct work_atoms *work_list;
+
+ work_list = rb_entry(next, struct work_atoms, node);
+ output_lat_thread(sched, work_list);
+ next = rb_next(next);
+ }
+
+ printf(" -----------------------------------------------------------------------------------------------------------------\n");
+ printf(" TOTAL: |%11.3f ms |%9" PRIu64 " |\n",
+ (double)sched->all_runtime / 1e6, sched->all_count);
+
+ printf(" ---------------------------------------------------\n");
+
+ print_bad_events(sched);
+ printf("\n");
+
+ perf_session__delete(session);
+ return 0;
+}
+
+static int perf_sched__map(struct perf_sched *sched)
+{
+ sched->max_cpu = sysconf(_SC_NPROCESSORS_CONF);
+
+ setup_pager();
+ if (perf_sched__read_events(sched, NULL))
+ return -1;
+ print_bad_events(sched);
+ return 0;
+}
+
+static int perf_sched__replay(struct perf_sched *sched)
+{
+ unsigned long i;
+
+ calibrate_run_measurement_overhead(sched);
+ calibrate_sleep_measurement_overhead(sched);
+
+ test_calibrations(sched);
+
+ if (perf_sched__read_events(sched, NULL))
+ return -1;
+
+ printf("nr_run_events: %ld\n", sched->nr_run_events);
+ printf("nr_sleep_events: %ld\n", sched->nr_sleep_events);
+ printf("nr_wakeup_events: %ld\n", sched->nr_wakeup_events);
+
+ if (sched->targetless_wakeups)
+ printf("target-less wakeups: %ld\n", sched->targetless_wakeups);
+ if (sched->multitarget_wakeups)
+ printf("multi-target wakeups: %ld\n", sched->multitarget_wakeups);
+ if (sched->nr_run_events_optimized)
+ printf("run atoms optimized: %ld\n",
+ sched->nr_run_events_optimized);
+
+ print_task_traces(sched);
+ add_cross_task_wakeups(sched);
+
+ create_tasks(sched);
+ printf("------------------------------------------------------------\n");
+ for (i = 0; i < sched->replay_repeat; i++)
+ run_one_test(sched);
+
+ return 0;
+}
+
+static void setup_sorting(struct perf_sched *sched, const struct option *options,
+ const char * const usage_msg[])
+{
+ char *tmp, *tok, *str = strdup(sched->sort_order);
+
+ for (tok = strtok_r(str, ", ", &tmp);
+ tok; tok = strtok_r(NULL, ", ", &tmp)) {
+ if (sort_dimension__add(tok, &sched->sort_list) < 0) {
+ error("Unknown --sort key: `%s'", tok);
+ usage_with_options(usage_msg, options);
+ }
+ }
+
+ free(str);
+
+ sort_dimension__add("pid", &sched->cmp_pid);
+}
+
+static int __cmd_record(int argc, const char **argv)
+{
+ unsigned int rec_argc, i, j;
+ const char **rec_argv;
+ const char * const record_args[] = {
+ "record",
+ "-a",
+ "-R",
+ "-m", "1024",
+ "-c", "1",
+ "-e", "sched:sched_switch",
+ "-e", "sched:sched_stat_wait",
+ "-e", "sched:sched_stat_sleep",
+ "-e", "sched:sched_stat_iowait",
+ "-e", "sched:sched_stat_runtime",
+ "-e", "sched:sched_process_fork",
+ "-e", "sched:sched_wakeup",
+ "-e", "sched:sched_wakeup_new",
+ "-e", "sched:sched_migrate_task",
+ };
+
+ rec_argc = ARRAY_SIZE(record_args) + argc - 1;
+ rec_argv = calloc(rec_argc + 1, sizeof(char *));
+
+ if (rec_argv == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(record_args); i++)
+ rec_argv[i] = strdup(record_args[i]);
+
+ for (j = 1; j < (unsigned int)argc; j++, i++)
+ rec_argv[i] = argv[j];
+
+ BUG_ON(i != rec_argc);
+
+ return cmd_record(i, rec_argv, NULL);
+}
+
+int cmd_sched(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ const char default_sort_order[] = "avg, max, switch, runtime";
+ struct perf_sched sched = {
+ .tool = {
+ .sample = perf_sched__process_tracepoint_sample,
+ .comm = perf_event__process_comm,
+ .lost = perf_event__process_lost,
+ .fork = perf_sched__process_fork_event,
+ .ordered_samples = true,
+ },
+ .cmp_pid = LIST_HEAD_INIT(sched.cmp_pid),
+ .sort_list = LIST_HEAD_INIT(sched.sort_list),
+ .start_work_mutex = PTHREAD_MUTEX_INITIALIZER,
+ .work_done_wait_mutex = PTHREAD_MUTEX_INITIALIZER,
+ .sort_order = default_sort_order,
+ .replay_repeat = 10,
+ .profile_cpu = -1,
+ .next_shortname1 = 'A',
+ .next_shortname2 = '0',
+ };
+ const struct option latency_options[] = {
+ OPT_STRING('s', "sort", &sched.sort_order, "key[,key2...]",
+ "sort by key(s): runtime, switch, avg, max"),
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show symbol address, etc)"),
+ OPT_INTEGER('C', "CPU", &sched.profile_cpu,
+ "CPU to profile on"),
+ OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
+ "dump raw trace in ASCII"),
+ OPT_END()
+ };
+ const struct option replay_options[] = {
+ OPT_UINTEGER('r', "repeat", &sched.replay_repeat,
+ "repeat the workload replay N times (-1: infinite)"),
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show symbol address, etc)"),
+ OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
+ "dump raw trace in ASCII"),
+ OPT_END()
+ };
+ const struct option sched_options[] = {
+ OPT_STRING('i', "input", &input_name, "file",
+ "input file name"),
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show symbol address, etc)"),
+ OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
+ "dump raw trace in ASCII"),
+ OPT_END()
+ };
+ const char * const latency_usage[] = {
+ "perf sched latency [<options>]",
+ NULL
+ };
+ const char * const replay_usage[] = {
+ "perf sched replay [<options>]",
+ NULL
+ };
+ const char *const sched_subcommands[] = { "record", "latency", "map",
+ "replay", "script", NULL };
+ const char *sched_usage[] = {
+ NULL,
+ NULL
+ };
+ struct trace_sched_handler lat_ops = {
+ .wakeup_event = latency_wakeup_event,
+ .switch_event = latency_switch_event,
+ .runtime_event = latency_runtime_event,
+ .migrate_task_event = latency_migrate_task_event,
+ };
+ struct trace_sched_handler map_ops = {
+ .switch_event = map_switch_event,
+ };
+ struct trace_sched_handler replay_ops = {
+ .wakeup_event = replay_wakeup_event,
+ .switch_event = replay_switch_event,
+ .fork_event = replay_fork_event,
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(sched.curr_pid); i++)
+ sched.curr_pid[i] = -1;
+
+ argc = parse_options_subcommand(argc, argv, sched_options, sched_subcommands,
+ sched_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+ if (!argc)
+ usage_with_options(sched_usage, sched_options);
+
+ /*
+ * Aliased to 'perf script' for now:
+ */
+ if (!strcmp(argv[0], "script"))
+ return cmd_script(argc, argv, prefix);
+
+ symbol__init();
+ if (!strncmp(argv[0], "rec", 3)) {
+ return __cmd_record(argc, argv);
+ } else if (!strncmp(argv[0], "lat", 3)) {
+ sched.tp_handler = &lat_ops;
+ if (argc > 1) {
+ argc = parse_options(argc, argv, latency_options, latency_usage, 0);
+ if (argc)
+ usage_with_options(latency_usage, latency_options);
+ }
+ setup_sorting(&sched, latency_options, latency_usage);
+ return perf_sched__lat(&sched);
+ } else if (!strcmp(argv[0], "map")) {
+ sched.tp_handler = &map_ops;
+ setup_sorting(&sched, latency_options, latency_usage);
+ return perf_sched__map(&sched);
+ } else if (!strncmp(argv[0], "rep", 3)) {
+ sched.tp_handler = &replay_ops;
+ if (argc) {
+ argc = parse_options(argc, argv, replay_options, replay_usage, 0);
+ if (argc)
+ usage_with_options(replay_usage, replay_options);
+ }
+ return perf_sched__replay(&sched);
+ } else {
+ usage_with_options(sched_usage, sched_options);
+ }
+
+ return 0;
+}
diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c
new file mode 100644
index 00000000000..9e9c91f5b7f
--- /dev/null
+++ b/tools/perf/builtin-script.c
@@ -0,0 +1,1826 @@
+#include "builtin.h"
+
+#include "perf.h"
+#include "util/cache.h"
+#include "util/debug.h"
+#include "util/exec_cmd.h"
+#include "util/header.h"
+#include "util/parse-options.h"
+#include "util/session.h"
+#include "util/tool.h"
+#include "util/symbol.h"
+#include "util/thread.h"
+#include "util/trace-event.h"
+#include "util/util.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/sort.h"
+#include "util/data.h"
+#include <linux/bitmap.h>
+
+static char const *script_name;
+static char const *generate_script_lang;
+static bool debug_mode;
+static u64 last_timestamp;
+static u64 nr_unordered;
+extern const struct option record_options[];
+static bool no_callchain;
+static bool latency_format;
+static bool system_wide;
+static const char *cpu_list;
+static DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS);
+
+enum perf_output_field {
+ PERF_OUTPUT_COMM = 1U << 0,
+ PERF_OUTPUT_TID = 1U << 1,
+ PERF_OUTPUT_PID = 1U << 2,
+ PERF_OUTPUT_TIME = 1U << 3,
+ PERF_OUTPUT_CPU = 1U << 4,
+ PERF_OUTPUT_EVNAME = 1U << 5,
+ PERF_OUTPUT_TRACE = 1U << 6,
+ PERF_OUTPUT_IP = 1U << 7,
+ PERF_OUTPUT_SYM = 1U << 8,
+ PERF_OUTPUT_DSO = 1U << 9,
+ PERF_OUTPUT_ADDR = 1U << 10,
+ PERF_OUTPUT_SYMOFFSET = 1U << 11,
+ PERF_OUTPUT_SRCLINE = 1U << 12,
+};
+
+struct output_option {
+ const char *str;
+ enum perf_output_field field;
+} all_output_options[] = {
+ {.str = "comm", .field = PERF_OUTPUT_COMM},
+ {.str = "tid", .field = PERF_OUTPUT_TID},
+ {.str = "pid", .field = PERF_OUTPUT_PID},
+ {.str = "time", .field = PERF_OUTPUT_TIME},
+ {.str = "cpu", .field = PERF_OUTPUT_CPU},
+ {.str = "event", .field = PERF_OUTPUT_EVNAME},
+ {.str = "trace", .field = PERF_OUTPUT_TRACE},
+ {.str = "ip", .field = PERF_OUTPUT_IP},
+ {.str = "sym", .field = PERF_OUTPUT_SYM},
+ {.str = "dso", .field = PERF_OUTPUT_DSO},
+ {.str = "addr", .field = PERF_OUTPUT_ADDR},
+ {.str = "symoff", .field = PERF_OUTPUT_SYMOFFSET},
+ {.str = "srcline", .field = PERF_OUTPUT_SRCLINE},
+};
+
+/* default set to maintain compatibility with current format */
+static struct {
+ bool user_set;
+ bool wildcard_set;
+ unsigned int print_ip_opts;
+ u64 fields;
+ u64 invalid_fields;
+} output[PERF_TYPE_MAX] = {
+
+ [PERF_TYPE_HARDWARE] = {
+ .user_set = false,
+
+ .fields = PERF_OUTPUT_COMM | PERF_OUTPUT_TID |
+ PERF_OUTPUT_CPU | PERF_OUTPUT_TIME |
+ PERF_OUTPUT_EVNAME | PERF_OUTPUT_IP |
+ PERF_OUTPUT_SYM | PERF_OUTPUT_DSO,
+
+ .invalid_fields = PERF_OUTPUT_TRACE,
+ },
+
+ [PERF_TYPE_SOFTWARE] = {
+ .user_set = false,
+
+ .fields = PERF_OUTPUT_COMM | PERF_OUTPUT_TID |
+ PERF_OUTPUT_CPU | PERF_OUTPUT_TIME |
+ PERF_OUTPUT_EVNAME | PERF_OUTPUT_IP |
+ PERF_OUTPUT_SYM | PERF_OUTPUT_DSO,
+
+ .invalid_fields = PERF_OUTPUT_TRACE,
+ },
+
+ [PERF_TYPE_TRACEPOINT] = {
+ .user_set = false,
+
+ .fields = PERF_OUTPUT_COMM | PERF_OUTPUT_TID |
+ PERF_OUTPUT_CPU | PERF_OUTPUT_TIME |
+ PERF_OUTPUT_EVNAME | PERF_OUTPUT_TRACE,
+ },
+
+ [PERF_TYPE_RAW] = {
+ .user_set = false,
+
+ .fields = PERF_OUTPUT_COMM | PERF_OUTPUT_TID |
+ PERF_OUTPUT_CPU | PERF_OUTPUT_TIME |
+ PERF_OUTPUT_EVNAME | PERF_OUTPUT_IP |
+ PERF_OUTPUT_SYM | PERF_OUTPUT_DSO,
+
+ .invalid_fields = PERF_OUTPUT_TRACE,
+ },
+};
+
+static bool output_set_by_user(void)
+{
+ int j;
+ for (j = 0; j < PERF_TYPE_MAX; ++j) {
+ if (output[j].user_set)
+ return true;
+ }
+ return false;
+}
+
+static const char *output_field2str(enum perf_output_field field)
+{
+ int i, imax = ARRAY_SIZE(all_output_options);
+ const char *str = "";
+
+ for (i = 0; i < imax; ++i) {
+ if (all_output_options[i].field == field) {
+ str = all_output_options[i].str;
+ break;
+ }
+ }
+ return str;
+}
+
+#define PRINT_FIELD(x) (output[attr->type].fields & PERF_OUTPUT_##x)
+
+static int perf_evsel__check_stype(struct perf_evsel *evsel,
+ u64 sample_type, const char *sample_msg,
+ enum perf_output_field field)
+{
+ struct perf_event_attr *attr = &evsel->attr;
+ int type = attr->type;
+ const char *evname;
+
+ if (attr->sample_type & sample_type)
+ return 0;
+
+ if (output[type].user_set) {
+ evname = perf_evsel__name(evsel);
+ pr_err("Samples for '%s' event do not have %s attribute set. "
+ "Cannot print '%s' field.\n",
+ evname, sample_msg, output_field2str(field));
+ return -1;
+ }
+
+ /* user did not ask for it explicitly so remove from the default list */
+ output[type].fields &= ~field;
+ evname = perf_evsel__name(evsel);
+ pr_debug("Samples for '%s' event do not have %s attribute set. "
+ "Skipping '%s' field.\n",
+ evname, sample_msg, output_field2str(field));
+
+ return 0;
+}
+
+static int perf_evsel__check_attr(struct perf_evsel *evsel,
+ struct perf_session *session)
+{
+ struct perf_event_attr *attr = &evsel->attr;
+
+ if (PRINT_FIELD(TRACE) &&
+ !perf_session__has_traces(session, "record -R"))
+ return -EINVAL;
+
+ if (PRINT_FIELD(IP)) {
+ if (perf_evsel__check_stype(evsel, PERF_SAMPLE_IP, "IP",
+ PERF_OUTPUT_IP))
+ return -EINVAL;
+
+ if (!no_callchain &&
+ !(attr->sample_type & PERF_SAMPLE_CALLCHAIN))
+ symbol_conf.use_callchain = false;
+ }
+
+ if (PRINT_FIELD(ADDR) &&
+ perf_evsel__check_stype(evsel, PERF_SAMPLE_ADDR, "ADDR",
+ PERF_OUTPUT_ADDR))
+ return -EINVAL;
+
+ if (PRINT_FIELD(SYM) && !PRINT_FIELD(IP) && !PRINT_FIELD(ADDR)) {
+ pr_err("Display of symbols requested but neither sample IP nor "
+ "sample address\nis selected. Hence, no addresses to convert "
+ "to symbols.\n");
+ return -EINVAL;
+ }
+ if (PRINT_FIELD(SYMOFFSET) && !PRINT_FIELD(SYM)) {
+ pr_err("Display of offsets requested but symbol is not"
+ "selected.\n");
+ return -EINVAL;
+ }
+ if (PRINT_FIELD(DSO) && !PRINT_FIELD(IP) && !PRINT_FIELD(ADDR)) {
+ pr_err("Display of DSO requested but neither sample IP nor "
+ "sample address\nis selected. Hence, no addresses to convert "
+ "to DSO.\n");
+ return -EINVAL;
+ }
+ if (PRINT_FIELD(SRCLINE) && !PRINT_FIELD(IP)) {
+ pr_err("Display of source line number requested but sample IP is not\n"
+ "selected. Hence, no address to lookup the source line number.\n");
+ return -EINVAL;
+ }
+
+ if ((PRINT_FIELD(PID) || PRINT_FIELD(TID)) &&
+ perf_evsel__check_stype(evsel, PERF_SAMPLE_TID, "TID",
+ PERF_OUTPUT_TID|PERF_OUTPUT_PID))
+ return -EINVAL;
+
+ if (PRINT_FIELD(TIME) &&
+ perf_evsel__check_stype(evsel, PERF_SAMPLE_TIME, "TIME",
+ PERF_OUTPUT_TIME))
+ return -EINVAL;
+
+ if (PRINT_FIELD(CPU) &&
+ perf_evsel__check_stype(evsel, PERF_SAMPLE_CPU, "CPU",
+ PERF_OUTPUT_CPU))
+ return -EINVAL;
+
+ return 0;
+}
+
+static void set_print_ip_opts(struct perf_event_attr *attr)
+{
+ unsigned int type = attr->type;
+
+ output[type].print_ip_opts = 0;
+ if (PRINT_FIELD(IP))
+ output[type].print_ip_opts |= PRINT_IP_OPT_IP;
+
+ if (PRINT_FIELD(SYM))
+ output[type].print_ip_opts |= PRINT_IP_OPT_SYM;
+
+ if (PRINT_FIELD(DSO))
+ output[type].print_ip_opts |= PRINT_IP_OPT_DSO;
+
+ if (PRINT_FIELD(SYMOFFSET))
+ output[type].print_ip_opts |= PRINT_IP_OPT_SYMOFFSET;
+
+ if (PRINT_FIELD(SRCLINE))
+ output[type].print_ip_opts |= PRINT_IP_OPT_SRCLINE;
+}
+
+/*
+ * verify all user requested events exist and the samples
+ * have the expected data
+ */
+static int perf_session__check_output_opt(struct perf_session *session)
+{
+ int j;
+ struct perf_evsel *evsel;
+
+ for (j = 0; j < PERF_TYPE_MAX; ++j) {
+ evsel = perf_session__find_first_evtype(session, j);
+
+ /*
+ * even if fields is set to 0 (ie., show nothing) event must
+ * exist if user explicitly includes it on the command line
+ */
+ if (!evsel && output[j].user_set && !output[j].wildcard_set) {
+ pr_err("%s events do not exist. "
+ "Remove corresponding -f option to proceed.\n",
+ event_type(j));
+ return -1;
+ }
+
+ if (evsel && output[j].fields &&
+ perf_evsel__check_attr(evsel, session))
+ return -1;
+
+ if (evsel == NULL)
+ continue;
+
+ set_print_ip_opts(&evsel->attr);
+ }
+
+ /*
+ * set default for tracepoints to print symbols only
+ * if callchains are present
+ */
+ if (symbol_conf.use_callchain &&
+ !output[PERF_TYPE_TRACEPOINT].user_set) {
+ struct perf_event_attr *attr;
+
+ j = PERF_TYPE_TRACEPOINT;
+ evsel = perf_session__find_first_evtype(session, j);
+ if (evsel == NULL)
+ goto out;
+
+ attr = &evsel->attr;
+
+ if (attr->sample_type & PERF_SAMPLE_CALLCHAIN) {
+ output[j].fields |= PERF_OUTPUT_IP;
+ output[j].fields |= PERF_OUTPUT_SYM;
+ output[j].fields |= PERF_OUTPUT_DSO;
+ set_print_ip_opts(attr);
+ }
+ }
+
+out:
+ return 0;
+}
+
+static void print_sample_start(struct perf_sample *sample,
+ struct thread *thread,
+ struct perf_evsel *evsel)
+{
+ struct perf_event_attr *attr = &evsel->attr;
+ unsigned long secs;
+ unsigned long usecs;
+ unsigned long long nsecs;
+
+ if (PRINT_FIELD(COMM)) {
+ if (latency_format)
+ printf("%8.8s ", thread__comm_str(thread));
+ else if (PRINT_FIELD(IP) && symbol_conf.use_callchain)
+ printf("%s ", thread__comm_str(thread));
+ else
+ printf("%16s ", thread__comm_str(thread));
+ }
+
+ if (PRINT_FIELD(PID) && PRINT_FIELD(TID))
+ printf("%5d/%-5d ", sample->pid, sample->tid);
+ else if (PRINT_FIELD(PID))
+ printf("%5d ", sample->pid);
+ else if (PRINT_FIELD(TID))
+ printf("%5d ", sample->tid);
+
+ if (PRINT_FIELD(CPU)) {
+ if (latency_format)
+ printf("%3d ", sample->cpu);
+ else
+ printf("[%03d] ", sample->cpu);
+ }
+
+ if (PRINT_FIELD(TIME)) {
+ nsecs = sample->time;
+ secs = nsecs / NSECS_PER_SEC;
+ nsecs -= secs * NSECS_PER_SEC;
+ usecs = nsecs / NSECS_PER_USEC;
+ printf("%5lu.%06lu: ", secs, usecs);
+ }
+}
+
+static bool is_bts_event(struct perf_event_attr *attr)
+{
+ return ((attr->type == PERF_TYPE_HARDWARE) &&
+ (attr->config & PERF_COUNT_HW_BRANCH_INSTRUCTIONS) &&
+ (attr->sample_period == 1));
+}
+
+static bool sample_addr_correlates_sym(struct perf_event_attr *attr)
+{
+ if ((attr->type == PERF_TYPE_SOFTWARE) &&
+ ((attr->config == PERF_COUNT_SW_PAGE_FAULTS) ||
+ (attr->config == PERF_COUNT_SW_PAGE_FAULTS_MIN) ||
+ (attr->config == PERF_COUNT_SW_PAGE_FAULTS_MAJ)))
+ return true;
+
+ if (is_bts_event(attr))
+ return true;
+
+ return false;
+}
+
+static void print_sample_addr(union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine,
+ struct thread *thread,
+ struct perf_event_attr *attr)
+{
+ struct addr_location al;
+ u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ printf("%16" PRIx64, sample->addr);
+
+ if (!sample_addr_correlates_sym(attr))
+ return;
+
+ thread__find_addr_map(thread, machine, cpumode, MAP__FUNCTION,
+ sample->addr, &al);
+ if (!al.map)
+ thread__find_addr_map(thread, machine, cpumode, MAP__VARIABLE,
+ sample->addr, &al);
+
+ al.cpu = sample->cpu;
+ al.sym = NULL;
+
+ if (al.map)
+ al.sym = map__find_symbol(al.map, al.addr, NULL);
+
+ if (PRINT_FIELD(SYM)) {
+ printf(" ");
+ if (PRINT_FIELD(SYMOFFSET))
+ symbol__fprintf_symname_offs(al.sym, &al, stdout);
+ else
+ symbol__fprintf_symname(al.sym, stdout);
+ }
+
+ if (PRINT_FIELD(DSO)) {
+ printf(" (");
+ map__fprintf_dsoname(al.map, stdout);
+ printf(")");
+ }
+}
+
+static void print_sample_bts(union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct thread *thread,
+ struct addr_location *al)
+{
+ struct perf_event_attr *attr = &evsel->attr;
+
+ /* print branch_from information */
+ if (PRINT_FIELD(IP)) {
+ if (!symbol_conf.use_callchain)
+ printf(" ");
+ else
+ printf("\n");
+ perf_evsel__print_ip(evsel, sample, al,
+ output[attr->type].print_ip_opts,
+ PERF_MAX_STACK_DEPTH);
+ }
+
+ printf(" => ");
+
+ /* print branch_to information */
+ if (PRINT_FIELD(ADDR) ||
+ ((evsel->attr.sample_type & PERF_SAMPLE_ADDR) &&
+ !output[attr->type].user_set))
+ print_sample_addr(event, sample, al->machine, thread, attr);
+
+ printf("\n");
+}
+
+static void process_event(union perf_event *event, struct perf_sample *sample,
+ struct perf_evsel *evsel, struct thread *thread,
+ struct addr_location *al)
+{
+ struct perf_event_attr *attr = &evsel->attr;
+
+ if (output[attr->type].fields == 0)
+ return;
+
+ print_sample_start(sample, thread, evsel);
+
+ if (PRINT_FIELD(EVNAME)) {
+ const char *evname = perf_evsel__name(evsel);
+ printf("%s: ", evname ? evname : "[unknown]");
+ }
+
+ if (is_bts_event(attr)) {
+ print_sample_bts(event, sample, evsel, thread, al);
+ return;
+ }
+
+ if (PRINT_FIELD(TRACE))
+ event_format__print(evsel->tp_format, sample->cpu,
+ sample->raw_data, sample->raw_size);
+ if (PRINT_FIELD(ADDR))
+ print_sample_addr(event, sample, al->machine, thread, attr);
+
+ if (PRINT_FIELD(IP)) {
+ if (!symbol_conf.use_callchain)
+ printf(" ");
+ else
+ printf("\n");
+
+ perf_evsel__print_ip(evsel, sample, al,
+ output[attr->type].print_ip_opts,
+ PERF_MAX_STACK_DEPTH);
+ }
+
+ printf("\n");
+}
+
+static int default_start_script(const char *script __maybe_unused,
+ int argc __maybe_unused,
+ const char **argv __maybe_unused)
+{
+ return 0;
+}
+
+static int default_stop_script(void)
+{
+ return 0;
+}
+
+static int default_generate_script(struct pevent *pevent __maybe_unused,
+ const char *outfile __maybe_unused)
+{
+ return 0;
+}
+
+static struct scripting_ops default_scripting_ops = {
+ .start_script = default_start_script,
+ .stop_script = default_stop_script,
+ .process_event = process_event,
+ .generate_script = default_generate_script,
+};
+
+static struct scripting_ops *scripting_ops;
+
+static void setup_scripting(void)
+{
+ setup_perl_scripting();
+ setup_python_scripting();
+
+ scripting_ops = &default_scripting_ops;
+}
+
+static int cleanup_scripting(void)
+{
+ pr_debug("\nperf script stopped\n");
+
+ return scripting_ops->stop_script();
+}
+
+static int process_sample_event(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ struct addr_location al;
+ struct thread *thread = machine__findnew_thread(machine, sample->pid,
+ sample->tid);
+
+ if (thread == NULL) {
+ pr_debug("problem processing %d event, skipping it.\n",
+ event->header.type);
+ return -1;
+ }
+
+ if (debug_mode) {
+ if (sample->time < last_timestamp) {
+ pr_err("Samples misordered, previous: %" PRIu64
+ " this: %" PRIu64 "\n", last_timestamp,
+ sample->time);
+ nr_unordered++;
+ }
+ last_timestamp = sample->time;
+ return 0;
+ }
+
+ if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) {
+ pr_err("problem processing %d event, skipping it.\n",
+ event->header.type);
+ return -1;
+ }
+
+ if (al.filtered)
+ return 0;
+
+ if (cpu_list && !test_bit(sample->cpu, cpu_bitmap))
+ return 0;
+
+ scripting_ops->process_event(event, sample, evsel, thread, &al);
+
+ evsel->hists.stats.total_period += sample->period;
+ return 0;
+}
+
+struct perf_script {
+ struct perf_tool tool;
+ struct perf_session *session;
+ bool show_task_events;
+ bool show_mmap_events;
+};
+
+static int process_attr(struct perf_tool *tool, union perf_event *event,
+ struct perf_evlist **pevlist)
+{
+ struct perf_script *scr = container_of(tool, struct perf_script, tool);
+ struct perf_evlist *evlist;
+ struct perf_evsel *evsel, *pos;
+ int err;
+
+ err = perf_event__process_attr(tool, event, pevlist);
+ if (err)
+ return err;
+
+ evlist = *pevlist;
+ evsel = perf_evlist__last(*pevlist);
+
+ if (evsel->attr.type >= PERF_TYPE_MAX)
+ return 0;
+
+ evlist__for_each(evlist, pos) {
+ if (pos->attr.type == evsel->attr.type && pos != evsel)
+ return 0;
+ }
+
+ set_print_ip_opts(&evsel->attr);
+
+ return perf_evsel__check_attr(evsel, scr->session);
+}
+
+static int process_comm_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct thread *thread;
+ struct perf_script *script = container_of(tool, struct perf_script, tool);
+ struct perf_session *session = script->session;
+ struct perf_evsel *evsel = perf_evlist__first(session->evlist);
+ int ret = -1;
+
+ thread = machine__findnew_thread(machine, event->comm.pid, event->comm.tid);
+ if (thread == NULL) {
+ pr_debug("problem processing COMM event, skipping it.\n");
+ return -1;
+ }
+
+ if (perf_event__process_comm(tool, event, sample, machine) < 0)
+ goto out;
+
+ if (!evsel->attr.sample_id_all) {
+ sample->cpu = 0;
+ sample->time = 0;
+ sample->tid = event->comm.tid;
+ sample->pid = event->comm.pid;
+ }
+ print_sample_start(sample, thread, evsel);
+ perf_event__fprintf(event, stdout);
+ ret = 0;
+
+out:
+ return ret;
+}
+
+static int process_fork_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct thread *thread;
+ struct perf_script *script = container_of(tool, struct perf_script, tool);
+ struct perf_session *session = script->session;
+ struct perf_evsel *evsel = perf_evlist__first(session->evlist);
+
+ if (perf_event__process_fork(tool, event, sample, machine) < 0)
+ return -1;
+
+ thread = machine__findnew_thread(machine, event->fork.pid, event->fork.tid);
+ if (thread == NULL) {
+ pr_debug("problem processing FORK event, skipping it.\n");
+ return -1;
+ }
+
+ if (!evsel->attr.sample_id_all) {
+ sample->cpu = 0;
+ sample->time = event->fork.time;
+ sample->tid = event->fork.tid;
+ sample->pid = event->fork.pid;
+ }
+ print_sample_start(sample, thread, evsel);
+ perf_event__fprintf(event, stdout);
+
+ return 0;
+}
+static int process_exit_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct thread *thread;
+ struct perf_script *script = container_of(tool, struct perf_script, tool);
+ struct perf_session *session = script->session;
+ struct perf_evsel *evsel = perf_evlist__first(session->evlist);
+
+ thread = machine__findnew_thread(machine, event->fork.pid, event->fork.tid);
+ if (thread == NULL) {
+ pr_debug("problem processing EXIT event, skipping it.\n");
+ return -1;
+ }
+
+ if (!evsel->attr.sample_id_all) {
+ sample->cpu = 0;
+ sample->time = 0;
+ sample->tid = event->comm.tid;
+ sample->pid = event->comm.pid;
+ }
+ print_sample_start(sample, thread, evsel);
+ perf_event__fprintf(event, stdout);
+
+ if (perf_event__process_exit(tool, event, sample, machine) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int process_mmap_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct thread *thread;
+ struct perf_script *script = container_of(tool, struct perf_script, tool);
+ struct perf_session *session = script->session;
+ struct perf_evsel *evsel = perf_evlist__first(session->evlist);
+
+ if (perf_event__process_mmap(tool, event, sample, machine) < 0)
+ return -1;
+
+ thread = machine__findnew_thread(machine, event->mmap.pid, event->mmap.tid);
+ if (thread == NULL) {
+ pr_debug("problem processing MMAP event, skipping it.\n");
+ return -1;
+ }
+
+ if (!evsel->attr.sample_id_all) {
+ sample->cpu = 0;
+ sample->time = 0;
+ sample->tid = event->mmap.tid;
+ sample->pid = event->mmap.pid;
+ }
+ print_sample_start(sample, thread, evsel);
+ perf_event__fprintf(event, stdout);
+
+ return 0;
+}
+
+static int process_mmap2_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct thread *thread;
+ struct perf_script *script = container_of(tool, struct perf_script, tool);
+ struct perf_session *session = script->session;
+ struct perf_evsel *evsel = perf_evlist__first(session->evlist);
+
+ if (perf_event__process_mmap2(tool, event, sample, machine) < 0)
+ return -1;
+
+ thread = machine__findnew_thread(machine, event->mmap2.pid, event->mmap2.tid);
+ if (thread == NULL) {
+ pr_debug("problem processing MMAP2 event, skipping it.\n");
+ return -1;
+ }
+
+ if (!evsel->attr.sample_id_all) {
+ sample->cpu = 0;
+ sample->time = 0;
+ sample->tid = event->mmap2.tid;
+ sample->pid = event->mmap2.pid;
+ }
+ print_sample_start(sample, thread, evsel);
+ perf_event__fprintf(event, stdout);
+
+ return 0;
+}
+
+static void sig_handler(int sig __maybe_unused)
+{
+ session_done = 1;
+}
+
+static int __cmd_script(struct perf_script *script)
+{
+ int ret;
+
+ signal(SIGINT, sig_handler);
+
+ /* override event processing functions */
+ if (script->show_task_events) {
+ script->tool.comm = process_comm_event;
+ script->tool.fork = process_fork_event;
+ script->tool.exit = process_exit_event;
+ }
+ if (script->show_mmap_events) {
+ script->tool.mmap = process_mmap_event;
+ script->tool.mmap2 = process_mmap2_event;
+ }
+
+ ret = perf_session__process_events(script->session, &script->tool);
+
+ if (debug_mode)
+ pr_err("Misordered timestamps: %" PRIu64 "\n", nr_unordered);
+
+ return ret;
+}
+
+struct script_spec {
+ struct list_head node;
+ struct scripting_ops *ops;
+ char spec[0];
+};
+
+static LIST_HEAD(script_specs);
+
+static struct script_spec *script_spec__new(const char *spec,
+ struct scripting_ops *ops)
+{
+ struct script_spec *s = malloc(sizeof(*s) + strlen(spec) + 1);
+
+ if (s != NULL) {
+ strcpy(s->spec, spec);
+ s->ops = ops;
+ }
+
+ return s;
+}
+
+static void script_spec__add(struct script_spec *s)
+{
+ list_add_tail(&s->node, &script_specs);
+}
+
+static struct script_spec *script_spec__find(const char *spec)
+{
+ struct script_spec *s;
+
+ list_for_each_entry(s, &script_specs, node)
+ if (strcasecmp(s->spec, spec) == 0)
+ return s;
+ return NULL;
+}
+
+static struct script_spec *script_spec__findnew(const char *spec,
+ struct scripting_ops *ops)
+{
+ struct script_spec *s = script_spec__find(spec);
+
+ if (s)
+ return s;
+
+ s = script_spec__new(spec, ops);
+ if (!s)
+ return NULL;
+
+ script_spec__add(s);
+
+ return s;
+}
+
+int script_spec_register(const char *spec, struct scripting_ops *ops)
+{
+ struct script_spec *s;
+
+ s = script_spec__find(spec);
+ if (s)
+ return -1;
+
+ s = script_spec__findnew(spec, ops);
+ if (!s)
+ return -1;
+
+ return 0;
+}
+
+static struct scripting_ops *script_spec__lookup(const char *spec)
+{
+ struct script_spec *s = script_spec__find(spec);
+ if (!s)
+ return NULL;
+
+ return s->ops;
+}
+
+static void list_available_languages(void)
+{
+ struct script_spec *s;
+
+ fprintf(stderr, "\n");
+ fprintf(stderr, "Scripting language extensions (used in "
+ "perf script -s [spec:]script.[spec]):\n\n");
+
+ list_for_each_entry(s, &script_specs, node)
+ fprintf(stderr, " %-42s [%s]\n", s->spec, s->ops->name);
+
+ fprintf(stderr, "\n");
+}
+
+static int parse_scriptname(const struct option *opt __maybe_unused,
+ const char *str, int unset __maybe_unused)
+{
+ char spec[PATH_MAX];
+ const char *script, *ext;
+ int len;
+
+ if (strcmp(str, "lang") == 0) {
+ list_available_languages();
+ exit(0);
+ }
+
+ script = strchr(str, ':');
+ if (script) {
+ len = script - str;
+ if (len >= PATH_MAX) {
+ fprintf(stderr, "invalid language specifier");
+ return -1;
+ }
+ strncpy(spec, str, len);
+ spec[len] = '\0';
+ scripting_ops = script_spec__lookup(spec);
+ if (!scripting_ops) {
+ fprintf(stderr, "invalid language specifier");
+ return -1;
+ }
+ script++;
+ } else {
+ script = str;
+ ext = strrchr(script, '.');
+ if (!ext) {
+ fprintf(stderr, "invalid script extension");
+ return -1;
+ }
+ scripting_ops = script_spec__lookup(++ext);
+ if (!scripting_ops) {
+ fprintf(stderr, "invalid script extension");
+ return -1;
+ }
+ }
+
+ script_name = strdup(script);
+
+ return 0;
+}
+
+static int parse_output_fields(const struct option *opt __maybe_unused,
+ const char *arg, int unset __maybe_unused)
+{
+ char *tok;
+ int i, imax = ARRAY_SIZE(all_output_options);
+ int j;
+ int rc = 0;
+ char *str = strdup(arg);
+ int type = -1;
+
+ if (!str)
+ return -ENOMEM;
+
+ /* first word can state for which event type the user is specifying
+ * the fields. If no type exists, the specified fields apply to all
+ * event types found in the file minus the invalid fields for a type.
+ */
+ tok = strchr(str, ':');
+ if (tok) {
+ *tok = '\0';
+ tok++;
+ if (!strcmp(str, "hw"))
+ type = PERF_TYPE_HARDWARE;
+ else if (!strcmp(str, "sw"))
+ type = PERF_TYPE_SOFTWARE;
+ else if (!strcmp(str, "trace"))
+ type = PERF_TYPE_TRACEPOINT;
+ else if (!strcmp(str, "raw"))
+ type = PERF_TYPE_RAW;
+ else {
+ fprintf(stderr, "Invalid event type in field string.\n");
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if (output[type].user_set)
+ pr_warning("Overriding previous field request for %s events.\n",
+ event_type(type));
+
+ output[type].fields = 0;
+ output[type].user_set = true;
+ output[type].wildcard_set = false;
+
+ } else {
+ tok = str;
+ if (strlen(str) == 0) {
+ fprintf(stderr,
+ "Cannot set fields to 'none' for all event types.\n");
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if (output_set_by_user())
+ pr_warning("Overriding previous field request for all events.\n");
+
+ for (j = 0; j < PERF_TYPE_MAX; ++j) {
+ output[j].fields = 0;
+ output[j].user_set = true;
+ output[j].wildcard_set = true;
+ }
+ }
+
+ tok = strtok(tok, ",");
+ while (tok) {
+ for (i = 0; i < imax; ++i) {
+ if (strcmp(tok, all_output_options[i].str) == 0)
+ break;
+ }
+ if (i == imax) {
+ fprintf(stderr, "Invalid field requested.\n");
+ rc = -EINVAL;
+ goto out;
+ }
+
+ if (type == -1) {
+ /* add user option to all events types for
+ * which it is valid
+ */
+ for (j = 0; j < PERF_TYPE_MAX; ++j) {
+ if (output[j].invalid_fields & all_output_options[i].field) {
+ pr_warning("\'%s\' not valid for %s events. Ignoring.\n",
+ all_output_options[i].str, event_type(j));
+ } else
+ output[j].fields |= all_output_options[i].field;
+ }
+ } else {
+ if (output[type].invalid_fields & all_output_options[i].field) {
+ fprintf(stderr, "\'%s\' not valid for %s events.\n",
+ all_output_options[i].str, event_type(type));
+
+ rc = -EINVAL;
+ goto out;
+ }
+ output[type].fields |= all_output_options[i].field;
+ }
+
+ tok = strtok(NULL, ",");
+ }
+
+ if (type >= 0) {
+ if (output[type].fields == 0) {
+ pr_debug("No fields requested for %s type. "
+ "Events will not be displayed.\n", event_type(type));
+ }
+ }
+
+out:
+ free(str);
+ return rc;
+}
+
+/* Helper function for filesystems that return a dent->d_type DT_UNKNOWN */
+static int is_directory(const char *base_path, const struct dirent *dent)
+{
+ char path[PATH_MAX];
+ struct stat st;
+
+ sprintf(path, "%s/%s", base_path, dent->d_name);
+ if (stat(path, &st))
+ return 0;
+
+ return S_ISDIR(st.st_mode);
+}
+
+#define for_each_lang(scripts_path, scripts_dir, lang_dirent, lang_next)\
+ while (!readdir_r(scripts_dir, &lang_dirent, &lang_next) && \
+ lang_next) \
+ if ((lang_dirent.d_type == DT_DIR || \
+ (lang_dirent.d_type == DT_UNKNOWN && \
+ is_directory(scripts_path, &lang_dirent))) && \
+ (strcmp(lang_dirent.d_name, ".")) && \
+ (strcmp(lang_dirent.d_name, "..")))
+
+#define for_each_script(lang_path, lang_dir, script_dirent, script_next)\
+ while (!readdir_r(lang_dir, &script_dirent, &script_next) && \
+ script_next) \
+ if (script_dirent.d_type != DT_DIR && \
+ (script_dirent.d_type != DT_UNKNOWN || \
+ !is_directory(lang_path, &script_dirent)))
+
+
+#define RECORD_SUFFIX "-record"
+#define REPORT_SUFFIX "-report"
+
+struct script_desc {
+ struct list_head node;
+ char *name;
+ char *half_liner;
+ char *args;
+};
+
+static LIST_HEAD(script_descs);
+
+static struct script_desc *script_desc__new(const char *name)
+{
+ struct script_desc *s = zalloc(sizeof(*s));
+
+ if (s != NULL && name)
+ s->name = strdup(name);
+
+ return s;
+}
+
+static void script_desc__delete(struct script_desc *s)
+{
+ zfree(&s->name);
+ zfree(&s->half_liner);
+ zfree(&s->args);
+ free(s);
+}
+
+static void script_desc__add(struct script_desc *s)
+{
+ list_add_tail(&s->node, &script_descs);
+}
+
+static struct script_desc *script_desc__find(const char *name)
+{
+ struct script_desc *s;
+
+ list_for_each_entry(s, &script_descs, node)
+ if (strcasecmp(s->name, name) == 0)
+ return s;
+ return NULL;
+}
+
+static struct script_desc *script_desc__findnew(const char *name)
+{
+ struct script_desc *s = script_desc__find(name);
+
+ if (s)
+ return s;
+
+ s = script_desc__new(name);
+ if (!s)
+ goto out_delete_desc;
+
+ script_desc__add(s);
+
+ return s;
+
+out_delete_desc:
+ script_desc__delete(s);
+
+ return NULL;
+}
+
+static const char *ends_with(const char *str, const char *suffix)
+{
+ size_t suffix_len = strlen(suffix);
+ const char *p = str;
+
+ if (strlen(str) > suffix_len) {
+ p = str + strlen(str) - suffix_len;
+ if (!strncmp(p, suffix, suffix_len))
+ return p;
+ }
+
+ return NULL;
+}
+
+static int read_script_info(struct script_desc *desc, const char *filename)
+{
+ char line[BUFSIZ], *p;
+ FILE *fp;
+
+ fp = fopen(filename, "r");
+ if (!fp)
+ return -1;
+
+ while (fgets(line, sizeof(line), fp)) {
+ p = ltrim(line);
+ if (strlen(p) == 0)
+ continue;
+ if (*p != '#')
+ continue;
+ p++;
+ if (strlen(p) && *p == '!')
+ continue;
+
+ p = ltrim(p);
+ if (strlen(p) && p[strlen(p) - 1] == '\n')
+ p[strlen(p) - 1] = '\0';
+
+ if (!strncmp(p, "description:", strlen("description:"))) {
+ p += strlen("description:");
+ desc->half_liner = strdup(ltrim(p));
+ continue;
+ }
+
+ if (!strncmp(p, "args:", strlen("args:"))) {
+ p += strlen("args:");
+ desc->args = strdup(ltrim(p));
+ continue;
+ }
+ }
+
+ fclose(fp);
+
+ return 0;
+}
+
+static char *get_script_root(struct dirent *script_dirent, const char *suffix)
+{
+ char *script_root, *str;
+
+ script_root = strdup(script_dirent->d_name);
+ if (!script_root)
+ return NULL;
+
+ str = (char *)ends_with(script_root, suffix);
+ if (!str) {
+ free(script_root);
+ return NULL;
+ }
+
+ *str = '\0';
+ return script_root;
+}
+
+static int list_available_scripts(const struct option *opt __maybe_unused,
+ const char *s __maybe_unused,
+ int unset __maybe_unused)
+{
+ struct dirent *script_next, *lang_next, script_dirent, lang_dirent;
+ char scripts_path[MAXPATHLEN];
+ DIR *scripts_dir, *lang_dir;
+ char script_path[MAXPATHLEN];
+ char lang_path[MAXPATHLEN];
+ struct script_desc *desc;
+ char first_half[BUFSIZ];
+ char *script_root;
+
+ snprintf(scripts_path, MAXPATHLEN, "%s/scripts", perf_exec_path());
+
+ scripts_dir = opendir(scripts_path);
+ if (!scripts_dir)
+ return -1;
+
+ for_each_lang(scripts_path, scripts_dir, lang_dirent, lang_next) {
+ snprintf(lang_path, MAXPATHLEN, "%s/%s/bin", scripts_path,
+ lang_dirent.d_name);
+ lang_dir = opendir(lang_path);
+ if (!lang_dir)
+ continue;
+
+ for_each_script(lang_path, lang_dir, script_dirent, script_next) {
+ script_root = get_script_root(&script_dirent, REPORT_SUFFIX);
+ if (script_root) {
+ desc = script_desc__findnew(script_root);
+ snprintf(script_path, MAXPATHLEN, "%s/%s",
+ lang_path, script_dirent.d_name);
+ read_script_info(desc, script_path);
+ free(script_root);
+ }
+ }
+ }
+
+ fprintf(stdout, "List of available trace scripts:\n");
+ list_for_each_entry(desc, &script_descs, node) {
+ sprintf(first_half, "%s %s", desc->name,
+ desc->args ? desc->args : "");
+ fprintf(stdout, " %-36s %s\n", first_half,
+ desc->half_liner ? desc->half_liner : "");
+ }
+
+ exit(0);
+}
+
+/*
+ * Some scripts specify the required events in their "xxx-record" file,
+ * this function will check if the events in perf.data match those
+ * mentioned in the "xxx-record".
+ *
+ * Fixme: All existing "xxx-record" are all in good formats "-e event ",
+ * which is covered well now. And new parsing code should be added to
+ * cover the future complexing formats like event groups etc.
+ */
+static int check_ev_match(char *dir_name, char *scriptname,
+ struct perf_session *session)
+{
+ char filename[MAXPATHLEN], evname[128];
+ char line[BUFSIZ], *p;
+ struct perf_evsel *pos;
+ int match, len;
+ FILE *fp;
+
+ sprintf(filename, "%s/bin/%s-record", dir_name, scriptname);
+
+ fp = fopen(filename, "r");
+ if (!fp)
+ return -1;
+
+ while (fgets(line, sizeof(line), fp)) {
+ p = ltrim(line);
+ if (*p == '#')
+ continue;
+
+ while (strlen(p)) {
+ p = strstr(p, "-e");
+ if (!p)
+ break;
+
+ p += 2;
+ p = ltrim(p);
+ len = strcspn(p, " \t");
+ if (!len)
+ break;
+
+ snprintf(evname, len + 1, "%s", p);
+
+ match = 0;
+ evlist__for_each(session->evlist, pos) {
+ if (!strcmp(perf_evsel__name(pos), evname)) {
+ match = 1;
+ break;
+ }
+ }
+
+ if (!match) {
+ fclose(fp);
+ return -1;
+ }
+ }
+ }
+
+ fclose(fp);
+ return 0;
+}
+
+/*
+ * Return -1 if none is found, otherwise the actual scripts number.
+ *
+ * Currently the only user of this function is the script browser, which
+ * will list all statically runnable scripts, select one, execute it and
+ * show the output in a perf browser.
+ */
+int find_scripts(char **scripts_array, char **scripts_path_array)
+{
+ struct dirent *script_next, *lang_next, script_dirent, lang_dirent;
+ char scripts_path[MAXPATHLEN], lang_path[MAXPATHLEN];
+ DIR *scripts_dir, *lang_dir;
+ struct perf_session *session;
+ struct perf_data_file file = {
+ .path = input_name,
+ .mode = PERF_DATA_MODE_READ,
+ };
+ char *temp;
+ int i = 0;
+
+ session = perf_session__new(&file, false, NULL);
+ if (!session)
+ return -1;
+
+ snprintf(scripts_path, MAXPATHLEN, "%s/scripts", perf_exec_path());
+
+ scripts_dir = opendir(scripts_path);
+ if (!scripts_dir) {
+ perf_session__delete(session);
+ return -1;
+ }
+
+ for_each_lang(scripts_path, scripts_dir, lang_dirent, lang_next) {
+ snprintf(lang_path, MAXPATHLEN, "%s/%s", scripts_path,
+ lang_dirent.d_name);
+#ifdef NO_LIBPERL
+ if (strstr(lang_path, "perl"))
+ continue;
+#endif
+#ifdef NO_LIBPYTHON
+ if (strstr(lang_path, "python"))
+ continue;
+#endif
+
+ lang_dir = opendir(lang_path);
+ if (!lang_dir)
+ continue;
+
+ for_each_script(lang_path, lang_dir, script_dirent, script_next) {
+ /* Skip those real time scripts: xxxtop.p[yl] */
+ if (strstr(script_dirent.d_name, "top."))
+ continue;
+ sprintf(scripts_path_array[i], "%s/%s", lang_path,
+ script_dirent.d_name);
+ temp = strchr(script_dirent.d_name, '.');
+ snprintf(scripts_array[i],
+ (temp - script_dirent.d_name) + 1,
+ "%s", script_dirent.d_name);
+
+ if (check_ev_match(lang_path,
+ scripts_array[i], session))
+ continue;
+
+ i++;
+ }
+ closedir(lang_dir);
+ }
+
+ closedir(scripts_dir);
+ perf_session__delete(session);
+ return i;
+}
+
+static char *get_script_path(const char *script_root, const char *suffix)
+{
+ struct dirent *script_next, *lang_next, script_dirent, lang_dirent;
+ char scripts_path[MAXPATHLEN];
+ char script_path[MAXPATHLEN];
+ DIR *scripts_dir, *lang_dir;
+ char lang_path[MAXPATHLEN];
+ char *__script_root;
+
+ snprintf(scripts_path, MAXPATHLEN, "%s/scripts", perf_exec_path());
+
+ scripts_dir = opendir(scripts_path);
+ if (!scripts_dir)
+ return NULL;
+
+ for_each_lang(scripts_path, scripts_dir, lang_dirent, lang_next) {
+ snprintf(lang_path, MAXPATHLEN, "%s/%s/bin", scripts_path,
+ lang_dirent.d_name);
+ lang_dir = opendir(lang_path);
+ if (!lang_dir)
+ continue;
+
+ for_each_script(lang_path, lang_dir, script_dirent, script_next) {
+ __script_root = get_script_root(&script_dirent, suffix);
+ if (__script_root && !strcmp(script_root, __script_root)) {
+ free(__script_root);
+ closedir(lang_dir);
+ closedir(scripts_dir);
+ snprintf(script_path, MAXPATHLEN, "%s/%s",
+ lang_path, script_dirent.d_name);
+ return strdup(script_path);
+ }
+ free(__script_root);
+ }
+ closedir(lang_dir);
+ }
+ closedir(scripts_dir);
+
+ return NULL;
+}
+
+static bool is_top_script(const char *script_path)
+{
+ return ends_with(script_path, "top") == NULL ? false : true;
+}
+
+static int has_required_arg(char *script_path)
+{
+ struct script_desc *desc;
+ int n_args = 0;
+ char *p;
+
+ desc = script_desc__new(NULL);
+
+ if (read_script_info(desc, script_path))
+ goto out;
+
+ if (!desc->args)
+ goto out;
+
+ for (p = desc->args; *p; p++)
+ if (*p == '<')
+ n_args++;
+out:
+ script_desc__delete(desc);
+
+ return n_args;
+}
+
+static int have_cmd(int argc, const char **argv)
+{
+ char **__argv = malloc(sizeof(const char *) * argc);
+
+ if (!__argv) {
+ pr_err("malloc failed\n");
+ return -1;
+ }
+
+ memcpy(__argv, argv, sizeof(const char *) * argc);
+ argc = parse_options(argc, (const char **)__argv, record_options,
+ NULL, PARSE_OPT_STOP_AT_NON_OPTION);
+ free(__argv);
+
+ system_wide = (argc == 0);
+
+ return 0;
+}
+
+int cmd_script(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ bool show_full_info = false;
+ bool header = false;
+ bool header_only = false;
+ char *rec_script_path = NULL;
+ char *rep_script_path = NULL;
+ struct perf_session *session;
+ char *script_path = NULL;
+ const char **__argv;
+ int i, j, err;
+ struct perf_script script = {
+ .tool = {
+ .sample = process_sample_event,
+ .mmap = perf_event__process_mmap,
+ .mmap2 = perf_event__process_mmap2,
+ .comm = perf_event__process_comm,
+ .exit = perf_event__process_exit,
+ .fork = perf_event__process_fork,
+ .attr = process_attr,
+ .tracing_data = perf_event__process_tracing_data,
+ .build_id = perf_event__process_build_id,
+ .ordered_samples = true,
+ .ordering_requires_timestamps = true,
+ },
+ };
+ const struct option options[] = {
+ OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace,
+ "dump raw trace in ASCII"),
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show symbol address, etc)"),
+ OPT_BOOLEAN('L', "Latency", &latency_format,
+ "show latency attributes (irqs/preemption disabled, etc)"),
+ OPT_CALLBACK_NOOPT('l', "list", NULL, NULL, "list available scripts",
+ list_available_scripts),
+ OPT_CALLBACK('s', "script", NULL, "name",
+ "script file name (lang:script name, script name, or *)",
+ parse_scriptname),
+ OPT_STRING('g', "gen-script", &generate_script_lang, "lang",
+ "generate perf-script.xx script in specified language"),
+ OPT_STRING('i', "input", &input_name, "file", "input file name"),
+ OPT_BOOLEAN('d', "debug-mode", &debug_mode,
+ "do various checks like samples ordering and lost events"),
+ OPT_BOOLEAN(0, "header", &header, "Show data header."),
+ OPT_BOOLEAN(0, "header-only", &header_only, "Show only data header."),
+ OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name,
+ "file", "vmlinux pathname"),
+ OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name,
+ "file", "kallsyms pathname"),
+ OPT_BOOLEAN('G', "hide-call-graph", &no_callchain,
+ "When printing symbols do not display call chain"),
+ OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory",
+ "Look for files with symbols relative to this directory"),
+ OPT_CALLBACK('f', "fields", NULL, "str",
+ "comma separated output fields prepend with 'type:'. "
+ "Valid types: hw,sw,trace,raw. "
+ "Fields: comm,tid,pid,time,cpu,event,trace,ip,sym,dso,"
+ "addr,symoff", parse_output_fields),
+ OPT_BOOLEAN('a', "all-cpus", &system_wide,
+ "system-wide collection from all CPUs"),
+ OPT_STRING('S', "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]",
+ "only consider these symbols"),
+ OPT_STRING('C', "cpu", &cpu_list, "cpu", "list of cpus to profile"),
+ OPT_STRING('c', "comms", &symbol_conf.comm_list_str, "comm[,comm...]",
+ "only display events for these comms"),
+ OPT_BOOLEAN('I', "show-info", &show_full_info,
+ "display extended information from perf.data file"),
+ OPT_BOOLEAN('\0', "show-kernel-path", &symbol_conf.show_kernel_path,
+ "Show the path of [kernel.kallsyms]"),
+ OPT_BOOLEAN('\0', "show-task-events", &script.show_task_events,
+ "Show the fork/comm/exit events"),
+ OPT_BOOLEAN('\0', "show-mmap-events", &script.show_mmap_events,
+ "Show the mmap events"),
+ OPT_END()
+ };
+ const char * const script_usage[] = {
+ "perf script [<options>]",
+ "perf script [<options>] record <script> [<record-options>] <command>",
+ "perf script [<options>] report <script> [script-args]",
+ "perf script [<options>] <script> [<record-options>] <command>",
+ "perf script [<options>] <top-script> [script-args]",
+ NULL
+ };
+ struct perf_data_file file = {
+ .mode = PERF_DATA_MODE_READ,
+ };
+
+ setup_scripting();
+
+ argc = parse_options(argc, argv, options, script_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ file.path = input_name;
+
+ if (argc > 1 && !strncmp(argv[0], "rec", strlen("rec"))) {
+ rec_script_path = get_script_path(argv[1], RECORD_SUFFIX);
+ if (!rec_script_path)
+ return cmd_record(argc, argv, NULL);
+ }
+
+ if (argc > 1 && !strncmp(argv[0], "rep", strlen("rep"))) {
+ rep_script_path = get_script_path(argv[1], REPORT_SUFFIX);
+ if (!rep_script_path) {
+ fprintf(stderr,
+ "Please specify a valid report script"
+ "(see 'perf script -l' for listing)\n");
+ return -1;
+ }
+ }
+
+ /* make sure PERF_EXEC_PATH is set for scripts */
+ perf_set_argv_exec_path(perf_exec_path());
+
+ if (argc && !script_name && !rec_script_path && !rep_script_path) {
+ int live_pipe[2];
+ int rep_args;
+ pid_t pid;
+
+ rec_script_path = get_script_path(argv[0], RECORD_SUFFIX);
+ rep_script_path = get_script_path(argv[0], REPORT_SUFFIX);
+
+ if (!rec_script_path && !rep_script_path) {
+ fprintf(stderr, " Couldn't find script %s\n\n See perf"
+ " script -l for available scripts.\n", argv[0]);
+ usage_with_options(script_usage, options);
+ }
+
+ if (is_top_script(argv[0])) {
+ rep_args = argc - 1;
+ } else {
+ int rec_args;
+
+ rep_args = has_required_arg(rep_script_path);
+ rec_args = (argc - 1) - rep_args;
+ if (rec_args < 0) {
+ fprintf(stderr, " %s script requires options."
+ "\n\n See perf script -l for available "
+ "scripts and options.\n", argv[0]);
+ usage_with_options(script_usage, options);
+ }
+ }
+
+ if (pipe(live_pipe) < 0) {
+ perror("failed to create pipe");
+ return -1;
+ }
+
+ pid = fork();
+ if (pid < 0) {
+ perror("failed to fork");
+ return -1;
+ }
+
+ if (!pid) {
+ j = 0;
+
+ dup2(live_pipe[1], 1);
+ close(live_pipe[0]);
+
+ if (is_top_script(argv[0])) {
+ system_wide = true;
+ } else if (!system_wide) {
+ if (have_cmd(argc - rep_args, &argv[rep_args]) != 0) {
+ err = -1;
+ goto out;
+ }
+ }
+
+ __argv = malloc((argc + 6) * sizeof(const char *));
+ if (!__argv) {
+ pr_err("malloc failed\n");
+ err = -ENOMEM;
+ goto out;
+ }
+
+ __argv[j++] = "/bin/sh";
+ __argv[j++] = rec_script_path;
+ if (system_wide)
+ __argv[j++] = "-a";
+ __argv[j++] = "-q";
+ __argv[j++] = "-o";
+ __argv[j++] = "-";
+ for (i = rep_args + 1; i < argc; i++)
+ __argv[j++] = argv[i];
+ __argv[j++] = NULL;
+
+ execvp("/bin/sh", (char **)__argv);
+ free(__argv);
+ exit(-1);
+ }
+
+ dup2(live_pipe[0], 0);
+ close(live_pipe[1]);
+
+ __argv = malloc((argc + 4) * sizeof(const char *));
+ if (!__argv) {
+ pr_err("malloc failed\n");
+ err = -ENOMEM;
+ goto out;
+ }
+
+ j = 0;
+ __argv[j++] = "/bin/sh";
+ __argv[j++] = rep_script_path;
+ for (i = 1; i < rep_args + 1; i++)
+ __argv[j++] = argv[i];
+ __argv[j++] = "-i";
+ __argv[j++] = "-";
+ __argv[j++] = NULL;
+
+ execvp("/bin/sh", (char **)__argv);
+ free(__argv);
+ exit(-1);
+ }
+
+ if (rec_script_path)
+ script_path = rec_script_path;
+ if (rep_script_path)
+ script_path = rep_script_path;
+
+ if (script_path) {
+ j = 0;
+
+ if (!rec_script_path)
+ system_wide = false;
+ else if (!system_wide) {
+ if (have_cmd(argc - 1, &argv[1]) != 0) {
+ err = -1;
+ goto out;
+ }
+ }
+
+ __argv = malloc((argc + 2) * sizeof(const char *));
+ if (!__argv) {
+ pr_err("malloc failed\n");
+ err = -ENOMEM;
+ goto out;
+ }
+
+ __argv[j++] = "/bin/sh";
+ __argv[j++] = script_path;
+ if (system_wide)
+ __argv[j++] = "-a";
+ for (i = 2; i < argc; i++)
+ __argv[j++] = argv[i];
+ __argv[j++] = NULL;
+
+ execvp("/bin/sh", (char **)__argv);
+ free(__argv);
+ exit(-1);
+ }
+
+ if (symbol__init() < 0)
+ return -1;
+ if (!script_name)
+ setup_pager();
+
+ session = perf_session__new(&file, false, &script.tool);
+ if (session == NULL)
+ return -ENOMEM;
+
+ if (header || header_only) {
+ perf_session__fprintf_info(session, stdout, show_full_info);
+ if (header_only)
+ return 0;
+ }
+
+ script.session = session;
+
+ if (cpu_list) {
+ if (perf_session__cpu_bitmap(session, cpu_list, cpu_bitmap))
+ return -1;
+ }
+
+ if (!no_callchain)
+ symbol_conf.use_callchain = true;
+ else
+ symbol_conf.use_callchain = false;
+
+ if (generate_script_lang) {
+ struct stat perf_stat;
+ int input;
+
+ if (output_set_by_user()) {
+ fprintf(stderr,
+ "custom fields not supported for generated scripts");
+ return -1;
+ }
+
+ input = open(file.path, O_RDONLY); /* input_name */
+ if (input < 0) {
+ perror("failed to open file");
+ return -1;
+ }
+
+ err = fstat(input, &perf_stat);
+ if (err < 0) {
+ perror("failed to stat file");
+ return -1;
+ }
+
+ if (!perf_stat.st_size) {
+ fprintf(stderr, "zero-sized file, nothing to do!\n");
+ return 0;
+ }
+
+ scripting_ops = script_spec__lookup(generate_script_lang);
+ if (!scripting_ops) {
+ fprintf(stderr, "invalid language specifier");
+ return -1;
+ }
+
+ err = scripting_ops->generate_script(session->tevent.pevent,
+ "perf-script");
+ goto out;
+ }
+
+ if (script_name) {
+ err = scripting_ops->start_script(script_name, argc, argv);
+ if (err)
+ goto out;
+ pr_debug("perf script started with script %s\n\n", script_name);
+ }
+
+
+ err = perf_session__check_output_opt(session);
+ if (err < 0)
+ goto out;
+
+ err = __cmd_script(&script);
+
+ perf_session__delete(session);
+ cleanup_scripting();
+out:
+ return err;
+}
diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c
new file mode 100644
index 00000000000..65a151e3606
--- /dev/null
+++ b/tools/perf/builtin-stat.c
@@ -0,0 +1,1834 @@
+/*
+ * builtin-stat.c
+ *
+ * Builtin stat command: Give a precise performance counters summary
+ * overview about any workload, CPU or specific PID.
+ *
+ * Sample output:
+
+ $ perf stat ./hackbench 10
+
+ Time: 0.118
+
+ Performance counter stats for './hackbench 10':
+
+ 1708.761321 task-clock # 11.037 CPUs utilized
+ 41,190 context-switches # 0.024 M/sec
+ 6,735 CPU-migrations # 0.004 M/sec
+ 17,318 page-faults # 0.010 M/sec
+ 5,205,202,243 cycles # 3.046 GHz
+ 3,856,436,920 stalled-cycles-frontend # 74.09% frontend cycles idle
+ 1,600,790,871 stalled-cycles-backend # 30.75% backend cycles idle
+ 2,603,501,247 instructions # 0.50 insns per cycle
+ # 1.48 stalled cycles per insn
+ 484,357,498 branches # 283.455 M/sec
+ 6,388,934 branch-misses # 1.32% of all branches
+
+ 0.154822978 seconds time elapsed
+
+ *
+ * Copyright (C) 2008-2011, Red Hat Inc, Ingo Molnar <mingo@redhat.com>
+ *
+ * Improvements and fixes by:
+ *
+ * Arjan van de Ven <arjan@linux.intel.com>
+ * Yanmin Zhang <yanmin.zhang@intel.com>
+ * Wu Fengguang <fengguang.wu@intel.com>
+ * Mike Galbraith <efault@gmx.de>
+ * Paul Mackerras <paulus@samba.org>
+ * Jaswinder Singh Rajput <jaswinder@kernel.org>
+ *
+ * Released under the GPL v2. (and only v2, not any later version)
+ */
+
+#include "perf.h"
+#include "builtin.h"
+#include "util/util.h"
+#include "util/parse-options.h"
+#include "util/parse-events.h"
+#include "util/pmu.h"
+#include "util/event.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/debug.h"
+#include "util/color.h"
+#include "util/stat.h"
+#include "util/header.h"
+#include "util/cpumap.h"
+#include "util/thread.h"
+#include "util/thread_map.h"
+
+#include <stdlib.h>
+#include <sys/prctl.h>
+#include <locale.h>
+
+#define DEFAULT_SEPARATOR " "
+#define CNTR_NOT_SUPPORTED "<not supported>"
+#define CNTR_NOT_COUNTED "<not counted>"
+
+static void print_stat(int argc, const char **argv);
+static void print_counter_aggr(struct perf_evsel *counter, char *prefix);
+static void print_counter(struct perf_evsel *counter, char *prefix);
+static void print_aggr(char *prefix);
+
+/* Default events used for perf stat -T */
+static const char * const transaction_attrs[] = {
+ "task-clock",
+ "{"
+ "instructions,"
+ "cycles,"
+ "cpu/cycles-t/,"
+ "cpu/tx-start/,"
+ "cpu/el-start/,"
+ "cpu/cycles-ct/"
+ "}"
+};
+
+/* More limited version when the CPU does not have all events. */
+static const char * const transaction_limited_attrs[] = {
+ "task-clock",
+ "{"
+ "instructions,"
+ "cycles,"
+ "cpu/cycles-t/,"
+ "cpu/tx-start/"
+ "}"
+};
+
+/* must match transaction_attrs and the beginning limited_attrs */
+enum {
+ T_TASK_CLOCK,
+ T_INSTRUCTIONS,
+ T_CYCLES,
+ T_CYCLES_IN_TX,
+ T_TRANSACTION_START,
+ T_ELISION_START,
+ T_CYCLES_IN_TX_CP,
+};
+
+static struct perf_evlist *evsel_list;
+
+static struct target target = {
+ .uid = UINT_MAX,
+};
+
+enum aggr_mode {
+ AGGR_NONE,
+ AGGR_GLOBAL,
+ AGGR_SOCKET,
+ AGGR_CORE,
+};
+
+static int run_count = 1;
+static bool no_inherit = false;
+static bool scale = true;
+static enum aggr_mode aggr_mode = AGGR_GLOBAL;
+static volatile pid_t child_pid = -1;
+static bool null_run = false;
+static int detailed_run = 0;
+static bool transaction_run;
+static bool big_num = true;
+static int big_num_opt = -1;
+static const char *csv_sep = NULL;
+static bool csv_output = false;
+static bool group = false;
+static FILE *output = NULL;
+static const char *pre_cmd = NULL;
+static const char *post_cmd = NULL;
+static bool sync_run = false;
+static unsigned int interval = 0;
+static unsigned int initial_delay = 0;
+static unsigned int unit_width = 4; /* strlen("unit") */
+static bool forever = false;
+static struct timespec ref_time;
+static struct cpu_map *aggr_map;
+static int (*aggr_get_id)(struct cpu_map *m, int cpu);
+
+static volatile int done = 0;
+
+struct perf_stat {
+ struct stats res_stats[3];
+};
+
+static inline void diff_timespec(struct timespec *r, struct timespec *a,
+ struct timespec *b)
+{
+ r->tv_sec = a->tv_sec - b->tv_sec;
+ if (a->tv_nsec < b->tv_nsec) {
+ r->tv_nsec = a->tv_nsec + 1000000000L - b->tv_nsec;
+ r->tv_sec--;
+ } else {
+ r->tv_nsec = a->tv_nsec - b->tv_nsec ;
+ }
+}
+
+static inline struct cpu_map *perf_evsel__cpus(struct perf_evsel *evsel)
+{
+ return (evsel->cpus && !target.cpu_list) ? evsel->cpus : evsel_list->cpus;
+}
+
+static inline int perf_evsel__nr_cpus(struct perf_evsel *evsel)
+{
+ return perf_evsel__cpus(evsel)->nr;
+}
+
+static void perf_evsel__reset_stat_priv(struct perf_evsel *evsel)
+{
+ int i;
+ struct perf_stat *ps = evsel->priv;
+
+ for (i = 0; i < 3; i++)
+ init_stats(&ps->res_stats[i]);
+}
+
+static int perf_evsel__alloc_stat_priv(struct perf_evsel *evsel)
+{
+ evsel->priv = zalloc(sizeof(struct perf_stat));
+ if (evsel == NULL)
+ return -ENOMEM;
+ perf_evsel__reset_stat_priv(evsel);
+ return 0;
+}
+
+static void perf_evsel__free_stat_priv(struct perf_evsel *evsel)
+{
+ zfree(&evsel->priv);
+}
+
+static int perf_evsel__alloc_prev_raw_counts(struct perf_evsel *evsel)
+{
+ void *addr;
+ size_t sz;
+
+ sz = sizeof(*evsel->counts) +
+ (perf_evsel__nr_cpus(evsel) * sizeof(struct perf_counts_values));
+
+ addr = zalloc(sz);
+ if (!addr)
+ return -ENOMEM;
+
+ evsel->prev_raw_counts = addr;
+
+ return 0;
+}
+
+static void perf_evsel__free_prev_raw_counts(struct perf_evsel *evsel)
+{
+ zfree(&evsel->prev_raw_counts);
+}
+
+static void perf_evlist__free_stats(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel;
+
+ evlist__for_each(evlist, evsel) {
+ perf_evsel__free_stat_priv(evsel);
+ perf_evsel__free_counts(evsel);
+ perf_evsel__free_prev_raw_counts(evsel);
+ }
+}
+
+static int perf_evlist__alloc_stats(struct perf_evlist *evlist, bool alloc_raw)
+{
+ struct perf_evsel *evsel;
+
+ evlist__for_each(evlist, evsel) {
+ if (perf_evsel__alloc_stat_priv(evsel) < 0 ||
+ perf_evsel__alloc_counts(evsel, perf_evsel__nr_cpus(evsel)) < 0 ||
+ (alloc_raw && perf_evsel__alloc_prev_raw_counts(evsel) < 0))
+ goto out_free;
+ }
+
+ return 0;
+
+out_free:
+ perf_evlist__free_stats(evlist);
+ return -1;
+}
+
+static struct stats runtime_nsecs_stats[MAX_NR_CPUS];
+static struct stats runtime_cycles_stats[MAX_NR_CPUS];
+static struct stats runtime_stalled_cycles_front_stats[MAX_NR_CPUS];
+static struct stats runtime_stalled_cycles_back_stats[MAX_NR_CPUS];
+static struct stats runtime_branches_stats[MAX_NR_CPUS];
+static struct stats runtime_cacherefs_stats[MAX_NR_CPUS];
+static struct stats runtime_l1_dcache_stats[MAX_NR_CPUS];
+static struct stats runtime_l1_icache_stats[MAX_NR_CPUS];
+static struct stats runtime_ll_cache_stats[MAX_NR_CPUS];
+static struct stats runtime_itlb_cache_stats[MAX_NR_CPUS];
+static struct stats runtime_dtlb_cache_stats[MAX_NR_CPUS];
+static struct stats runtime_cycles_in_tx_stats[MAX_NR_CPUS];
+static struct stats walltime_nsecs_stats;
+static struct stats runtime_transaction_stats[MAX_NR_CPUS];
+static struct stats runtime_elision_stats[MAX_NR_CPUS];
+
+static void perf_stat__reset_stats(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel;
+
+ evlist__for_each(evlist, evsel) {
+ perf_evsel__reset_stat_priv(evsel);
+ perf_evsel__reset_counts(evsel, perf_evsel__nr_cpus(evsel));
+ }
+
+ memset(runtime_nsecs_stats, 0, sizeof(runtime_nsecs_stats));
+ memset(runtime_cycles_stats, 0, sizeof(runtime_cycles_stats));
+ memset(runtime_stalled_cycles_front_stats, 0, sizeof(runtime_stalled_cycles_front_stats));
+ memset(runtime_stalled_cycles_back_stats, 0, sizeof(runtime_stalled_cycles_back_stats));
+ memset(runtime_branches_stats, 0, sizeof(runtime_branches_stats));
+ memset(runtime_cacherefs_stats, 0, sizeof(runtime_cacherefs_stats));
+ memset(runtime_l1_dcache_stats, 0, sizeof(runtime_l1_dcache_stats));
+ memset(runtime_l1_icache_stats, 0, sizeof(runtime_l1_icache_stats));
+ memset(runtime_ll_cache_stats, 0, sizeof(runtime_ll_cache_stats));
+ memset(runtime_itlb_cache_stats, 0, sizeof(runtime_itlb_cache_stats));
+ memset(runtime_dtlb_cache_stats, 0, sizeof(runtime_dtlb_cache_stats));
+ memset(runtime_cycles_in_tx_stats, 0,
+ sizeof(runtime_cycles_in_tx_stats));
+ memset(runtime_transaction_stats, 0,
+ sizeof(runtime_transaction_stats));
+ memset(runtime_elision_stats, 0, sizeof(runtime_elision_stats));
+ memset(&walltime_nsecs_stats, 0, sizeof(walltime_nsecs_stats));
+}
+
+static int create_perf_stat_counter(struct perf_evsel *evsel)
+{
+ struct perf_event_attr *attr = &evsel->attr;
+
+ if (scale)
+ attr->read_format = PERF_FORMAT_TOTAL_TIME_ENABLED |
+ PERF_FORMAT_TOTAL_TIME_RUNNING;
+
+ attr->inherit = !no_inherit;
+
+ if (target__has_cpu(&target))
+ return perf_evsel__open_per_cpu(evsel, perf_evsel__cpus(evsel));
+
+ if (!target__has_task(&target) && perf_evsel__is_group_leader(evsel)) {
+ attr->disabled = 1;
+ if (!initial_delay)
+ attr->enable_on_exec = 1;
+ }
+
+ return perf_evsel__open_per_thread(evsel, evsel_list->threads);
+}
+
+/*
+ * Does the counter have nsecs as a unit?
+ */
+static inline int nsec_counter(struct perf_evsel *evsel)
+{
+ if (perf_evsel__match(evsel, SOFTWARE, SW_CPU_CLOCK) ||
+ perf_evsel__match(evsel, SOFTWARE, SW_TASK_CLOCK))
+ return 1;
+
+ return 0;
+}
+
+static struct perf_evsel *nth_evsel(int n)
+{
+ static struct perf_evsel **array;
+ static int array_len;
+ struct perf_evsel *ev;
+ int j;
+
+ /* Assumes this only called when evsel_list does not change anymore. */
+ if (!array) {
+ evlist__for_each(evsel_list, ev)
+ array_len++;
+ array = malloc(array_len * sizeof(void *));
+ if (!array)
+ exit(ENOMEM);
+ j = 0;
+ evlist__for_each(evsel_list, ev)
+ array[j++] = ev;
+ }
+ if (n < array_len)
+ return array[n];
+ return NULL;
+}
+
+/*
+ * Update various tracking values we maintain to print
+ * more semantic information such as miss/hit ratios,
+ * instruction rates, etc:
+ */
+static void update_shadow_stats(struct perf_evsel *counter, u64 *count)
+{
+ if (perf_evsel__match(counter, SOFTWARE, SW_TASK_CLOCK))
+ update_stats(&runtime_nsecs_stats[0], count[0]);
+ else if (perf_evsel__match(counter, HARDWARE, HW_CPU_CYCLES))
+ update_stats(&runtime_cycles_stats[0], count[0]);
+ else if (transaction_run &&
+ perf_evsel__cmp(counter, nth_evsel(T_CYCLES_IN_TX)))
+ update_stats(&runtime_cycles_in_tx_stats[0], count[0]);
+ else if (transaction_run &&
+ perf_evsel__cmp(counter, nth_evsel(T_TRANSACTION_START)))
+ update_stats(&runtime_transaction_stats[0], count[0]);
+ else if (transaction_run &&
+ perf_evsel__cmp(counter, nth_evsel(T_ELISION_START)))
+ update_stats(&runtime_elision_stats[0], count[0]);
+ else if (perf_evsel__match(counter, HARDWARE, HW_STALLED_CYCLES_FRONTEND))
+ update_stats(&runtime_stalled_cycles_front_stats[0], count[0]);
+ else if (perf_evsel__match(counter, HARDWARE, HW_STALLED_CYCLES_BACKEND))
+ update_stats(&runtime_stalled_cycles_back_stats[0], count[0]);
+ else if (perf_evsel__match(counter, HARDWARE, HW_BRANCH_INSTRUCTIONS))
+ update_stats(&runtime_branches_stats[0], count[0]);
+ else if (perf_evsel__match(counter, HARDWARE, HW_CACHE_REFERENCES))
+ update_stats(&runtime_cacherefs_stats[0], count[0]);
+ else if (perf_evsel__match(counter, HW_CACHE, HW_CACHE_L1D))
+ update_stats(&runtime_l1_dcache_stats[0], count[0]);
+ else if (perf_evsel__match(counter, HW_CACHE, HW_CACHE_L1I))
+ update_stats(&runtime_l1_icache_stats[0], count[0]);
+ else if (perf_evsel__match(counter, HW_CACHE, HW_CACHE_LL))
+ update_stats(&runtime_ll_cache_stats[0], count[0]);
+ else if (perf_evsel__match(counter, HW_CACHE, HW_CACHE_DTLB))
+ update_stats(&runtime_dtlb_cache_stats[0], count[0]);
+ else if (perf_evsel__match(counter, HW_CACHE, HW_CACHE_ITLB))
+ update_stats(&runtime_itlb_cache_stats[0], count[0]);
+}
+
+/*
+ * Read out the results of a single counter:
+ * aggregate counts across CPUs in system-wide mode
+ */
+static int read_counter_aggr(struct perf_evsel *counter)
+{
+ struct perf_stat *ps = counter->priv;
+ u64 *count = counter->counts->aggr.values;
+ int i;
+
+ if (__perf_evsel__read(counter, perf_evsel__nr_cpus(counter),
+ thread_map__nr(evsel_list->threads), scale) < 0)
+ return -1;
+
+ for (i = 0; i < 3; i++)
+ update_stats(&ps->res_stats[i], count[i]);
+
+ if (verbose) {
+ fprintf(output, "%s: %" PRIu64 " %" PRIu64 " %" PRIu64 "\n",
+ perf_evsel__name(counter), count[0], count[1], count[2]);
+ }
+
+ /*
+ * Save the full runtime - to allow normalization during printout:
+ */
+ update_shadow_stats(counter, count);
+
+ return 0;
+}
+
+/*
+ * Read out the results of a single counter:
+ * do not aggregate counts across CPUs in system-wide mode
+ */
+static int read_counter(struct perf_evsel *counter)
+{
+ u64 *count;
+ int cpu;
+
+ for (cpu = 0; cpu < perf_evsel__nr_cpus(counter); cpu++) {
+ if (__perf_evsel__read_on_cpu(counter, cpu, 0, scale) < 0)
+ return -1;
+
+ count = counter->counts->cpu[cpu].values;
+
+ update_shadow_stats(counter, count);
+ }
+
+ return 0;
+}
+
+static void print_interval(void)
+{
+ static int num_print_interval;
+ struct perf_evsel *counter;
+ struct perf_stat *ps;
+ struct timespec ts, rs;
+ char prefix[64];
+
+ if (aggr_mode == AGGR_GLOBAL) {
+ evlist__for_each(evsel_list, counter) {
+ ps = counter->priv;
+ memset(ps->res_stats, 0, sizeof(ps->res_stats));
+ read_counter_aggr(counter);
+ }
+ } else {
+ evlist__for_each(evsel_list, counter) {
+ ps = counter->priv;
+ memset(ps->res_stats, 0, sizeof(ps->res_stats));
+ read_counter(counter);
+ }
+ }
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ diff_timespec(&rs, &ts, &ref_time);
+ sprintf(prefix, "%6lu.%09lu%s", rs.tv_sec, rs.tv_nsec, csv_sep);
+
+ if (num_print_interval == 0 && !csv_output) {
+ switch (aggr_mode) {
+ case AGGR_SOCKET:
+ fprintf(output, "# time socket cpus counts %*s events\n", unit_width, "unit");
+ break;
+ case AGGR_CORE:
+ fprintf(output, "# time core cpus counts %*s events\n", unit_width, "unit");
+ break;
+ case AGGR_NONE:
+ fprintf(output, "# time CPU counts %*s events\n", unit_width, "unit");
+ break;
+ case AGGR_GLOBAL:
+ default:
+ fprintf(output, "# time counts %*s events\n", unit_width, "unit");
+ }
+ }
+
+ if (++num_print_interval == 25)
+ num_print_interval = 0;
+
+ switch (aggr_mode) {
+ case AGGR_CORE:
+ case AGGR_SOCKET:
+ print_aggr(prefix);
+ break;
+ case AGGR_NONE:
+ evlist__for_each(evsel_list, counter)
+ print_counter(counter, prefix);
+ break;
+ case AGGR_GLOBAL:
+ default:
+ evlist__for_each(evsel_list, counter)
+ print_counter_aggr(counter, prefix);
+ }
+
+ fflush(output);
+}
+
+static void handle_initial_delay(void)
+{
+ struct perf_evsel *counter;
+
+ if (initial_delay) {
+ const int ncpus = cpu_map__nr(evsel_list->cpus),
+ nthreads = thread_map__nr(evsel_list->threads);
+
+ usleep(initial_delay * 1000);
+ evlist__for_each(evsel_list, counter)
+ perf_evsel__enable(counter, ncpus, nthreads);
+ }
+}
+
+static volatile int workload_exec_errno;
+
+/*
+ * perf_evlist__prepare_workload will send a SIGUSR1
+ * if the fork fails, since we asked by setting its
+ * want_signal to true.
+ */
+static void workload_exec_failed_signal(int signo __maybe_unused, siginfo_t *info,
+ void *ucontext __maybe_unused)
+{
+ workload_exec_errno = info->si_value.sival_int;
+}
+
+static int __run_perf_stat(int argc, const char **argv)
+{
+ char msg[512];
+ unsigned long long t0, t1;
+ struct perf_evsel *counter;
+ struct timespec ts;
+ size_t l;
+ int status = 0;
+ const bool forks = (argc > 0);
+
+ if (interval) {
+ ts.tv_sec = interval / 1000;
+ ts.tv_nsec = (interval % 1000) * 1000000;
+ } else {
+ ts.tv_sec = 1;
+ ts.tv_nsec = 0;
+ }
+
+ if (forks) {
+ if (perf_evlist__prepare_workload(evsel_list, &target, argv, false,
+ workload_exec_failed_signal) < 0) {
+ perror("failed to prepare workload");
+ return -1;
+ }
+ child_pid = evsel_list->workload.pid;
+ }
+
+ if (group)
+ perf_evlist__set_leader(evsel_list);
+
+ evlist__for_each(evsel_list, counter) {
+ if (create_perf_stat_counter(counter) < 0) {
+ /*
+ * PPC returns ENXIO for HW counters until 2.6.37
+ * (behavior changed with commit b0a873e).
+ */
+ if (errno == EINVAL || errno == ENOSYS ||
+ errno == ENOENT || errno == EOPNOTSUPP ||
+ errno == ENXIO) {
+ if (verbose)
+ ui__warning("%s event is not supported by the kernel.\n",
+ perf_evsel__name(counter));
+ counter->supported = false;
+ continue;
+ }
+
+ perf_evsel__open_strerror(counter, &target,
+ errno, msg, sizeof(msg));
+ ui__error("%s\n", msg);
+
+ if (child_pid != -1)
+ kill(child_pid, SIGTERM);
+
+ return -1;
+ }
+ counter->supported = true;
+
+ l = strlen(counter->unit);
+ if (l > unit_width)
+ unit_width = l;
+ }
+
+ if (perf_evlist__apply_filters(evsel_list)) {
+ error("failed to set filter with %d (%s)\n", errno,
+ strerror(errno));
+ return -1;
+ }
+
+ /*
+ * Enable counters and exec the command:
+ */
+ t0 = rdclock();
+ clock_gettime(CLOCK_MONOTONIC, &ref_time);
+
+ if (forks) {
+ perf_evlist__start_workload(evsel_list);
+ handle_initial_delay();
+
+ if (interval) {
+ while (!waitpid(child_pid, &status, WNOHANG)) {
+ nanosleep(&ts, NULL);
+ print_interval();
+ }
+ }
+ wait(&status);
+
+ if (workload_exec_errno) {
+ const char *emsg = strerror_r(workload_exec_errno, msg, sizeof(msg));
+ pr_err("Workload failed: %s\n", emsg);
+ return -1;
+ }
+
+ if (WIFSIGNALED(status))
+ psignal(WTERMSIG(status), argv[0]);
+ } else {
+ handle_initial_delay();
+ while (!done) {
+ nanosleep(&ts, NULL);
+ if (interval)
+ print_interval();
+ }
+ }
+
+ t1 = rdclock();
+
+ update_stats(&walltime_nsecs_stats, t1 - t0);
+
+ if (aggr_mode == AGGR_GLOBAL) {
+ evlist__for_each(evsel_list, counter) {
+ read_counter_aggr(counter);
+ perf_evsel__close_fd(counter, perf_evsel__nr_cpus(counter),
+ thread_map__nr(evsel_list->threads));
+ }
+ } else {
+ evlist__for_each(evsel_list, counter) {
+ read_counter(counter);
+ perf_evsel__close_fd(counter, perf_evsel__nr_cpus(counter), 1);
+ }
+ }
+
+ return WEXITSTATUS(status);
+}
+
+static int run_perf_stat(int argc, const char **argv)
+{
+ int ret;
+
+ if (pre_cmd) {
+ ret = system(pre_cmd);
+ if (ret)
+ return ret;
+ }
+
+ if (sync_run)
+ sync();
+
+ ret = __run_perf_stat(argc, argv);
+ if (ret)
+ return ret;
+
+ if (post_cmd) {
+ ret = system(post_cmd);
+ if (ret)
+ return ret;
+ }
+
+ return ret;
+}
+
+static void print_noise_pct(double total, double avg)
+{
+ double pct = rel_stddev_stats(total, avg);
+
+ if (csv_output)
+ fprintf(output, "%s%.2f%%", csv_sep, pct);
+ else if (pct)
+ fprintf(output, " ( +-%6.2f%% )", pct);
+}
+
+static void print_noise(struct perf_evsel *evsel, double avg)
+{
+ struct perf_stat *ps;
+
+ if (run_count == 1)
+ return;
+
+ ps = evsel->priv;
+ print_noise_pct(stddev_stats(&ps->res_stats[0]), avg);
+}
+
+static void aggr_printout(struct perf_evsel *evsel, int id, int nr)
+{
+ switch (aggr_mode) {
+ case AGGR_CORE:
+ fprintf(output, "S%d-C%*d%s%*d%s",
+ cpu_map__id_to_socket(id),
+ csv_output ? 0 : -8,
+ cpu_map__id_to_cpu(id),
+ csv_sep,
+ csv_output ? 0 : 4,
+ nr,
+ csv_sep);
+ break;
+ case AGGR_SOCKET:
+ fprintf(output, "S%*d%s%*d%s",
+ csv_output ? 0 : -5,
+ id,
+ csv_sep,
+ csv_output ? 0 : 4,
+ nr,
+ csv_sep);
+ break;
+ case AGGR_NONE:
+ fprintf(output, "CPU%*d%s",
+ csv_output ? 0 : -4,
+ perf_evsel__cpus(evsel)->map[id], csv_sep);
+ break;
+ case AGGR_GLOBAL:
+ default:
+ break;
+ }
+}
+
+static void nsec_printout(int cpu, int nr, struct perf_evsel *evsel, double avg)
+{
+ double msecs = avg / 1e6;
+ const char *fmt_v, *fmt_n;
+ char name[25];
+
+ fmt_v = csv_output ? "%.6f%s" : "%18.6f%s";
+ fmt_n = csv_output ? "%s" : "%-25s";
+
+ aggr_printout(evsel, cpu, nr);
+
+ scnprintf(name, sizeof(name), "%s%s",
+ perf_evsel__name(evsel), csv_output ? "" : " (msec)");
+
+ fprintf(output, fmt_v, msecs, csv_sep);
+
+ if (csv_output)
+ fprintf(output, "%s%s", evsel->unit, csv_sep);
+ else
+ fprintf(output, "%-*s%s", unit_width, evsel->unit, csv_sep);
+
+ fprintf(output, fmt_n, name);
+
+ if (evsel->cgrp)
+ fprintf(output, "%s%s", csv_sep, evsel->cgrp->name);
+
+ if (csv_output || interval)
+ return;
+
+ if (perf_evsel__match(evsel, SOFTWARE, SW_TASK_CLOCK))
+ fprintf(output, " # %8.3f CPUs utilized ",
+ avg / avg_stats(&walltime_nsecs_stats));
+ else
+ fprintf(output, " ");
+}
+
+/* used for get_ratio_color() */
+enum grc_type {
+ GRC_STALLED_CYCLES_FE,
+ GRC_STALLED_CYCLES_BE,
+ GRC_CACHE_MISSES,
+ GRC_MAX_NR
+};
+
+static const char *get_ratio_color(enum grc_type type, double ratio)
+{
+ static const double grc_table[GRC_MAX_NR][3] = {
+ [GRC_STALLED_CYCLES_FE] = { 50.0, 30.0, 10.0 },
+ [GRC_STALLED_CYCLES_BE] = { 75.0, 50.0, 20.0 },
+ [GRC_CACHE_MISSES] = { 20.0, 10.0, 5.0 },
+ };
+ const char *color = PERF_COLOR_NORMAL;
+
+ if (ratio > grc_table[type][0])
+ color = PERF_COLOR_RED;
+ else if (ratio > grc_table[type][1])
+ color = PERF_COLOR_MAGENTA;
+ else if (ratio > grc_table[type][2])
+ color = PERF_COLOR_YELLOW;
+
+ return color;
+}
+
+static void print_stalled_cycles_frontend(int cpu,
+ struct perf_evsel *evsel
+ __maybe_unused, double avg)
+{
+ double total, ratio = 0.0;
+ const char *color;
+
+ total = avg_stats(&runtime_cycles_stats[cpu]);
+
+ if (total)
+ ratio = avg / total * 100.0;
+
+ color = get_ratio_color(GRC_STALLED_CYCLES_FE, ratio);
+
+ fprintf(output, " # ");
+ color_fprintf(output, color, "%6.2f%%", ratio);
+ fprintf(output, " frontend cycles idle ");
+}
+
+static void print_stalled_cycles_backend(int cpu,
+ struct perf_evsel *evsel
+ __maybe_unused, double avg)
+{
+ double total, ratio = 0.0;
+ const char *color;
+
+ total = avg_stats(&runtime_cycles_stats[cpu]);
+
+ if (total)
+ ratio = avg / total * 100.0;
+
+ color = get_ratio_color(GRC_STALLED_CYCLES_BE, ratio);
+
+ fprintf(output, " # ");
+ color_fprintf(output, color, "%6.2f%%", ratio);
+ fprintf(output, " backend cycles idle ");
+}
+
+static void print_branch_misses(int cpu,
+ struct perf_evsel *evsel __maybe_unused,
+ double avg)
+{
+ double total, ratio = 0.0;
+ const char *color;
+
+ total = avg_stats(&runtime_branches_stats[cpu]);
+
+ if (total)
+ ratio = avg / total * 100.0;
+
+ color = get_ratio_color(GRC_CACHE_MISSES, ratio);
+
+ fprintf(output, " # ");
+ color_fprintf(output, color, "%6.2f%%", ratio);
+ fprintf(output, " of all branches ");
+}
+
+static void print_l1_dcache_misses(int cpu,
+ struct perf_evsel *evsel __maybe_unused,
+ double avg)
+{
+ double total, ratio = 0.0;
+ const char *color;
+
+ total = avg_stats(&runtime_l1_dcache_stats[cpu]);
+
+ if (total)
+ ratio = avg / total * 100.0;
+
+ color = get_ratio_color(GRC_CACHE_MISSES, ratio);
+
+ fprintf(output, " # ");
+ color_fprintf(output, color, "%6.2f%%", ratio);
+ fprintf(output, " of all L1-dcache hits ");
+}
+
+static void print_l1_icache_misses(int cpu,
+ struct perf_evsel *evsel __maybe_unused,
+ double avg)
+{
+ double total, ratio = 0.0;
+ const char *color;
+
+ total = avg_stats(&runtime_l1_icache_stats[cpu]);
+
+ if (total)
+ ratio = avg / total * 100.0;
+
+ color = get_ratio_color(GRC_CACHE_MISSES, ratio);
+
+ fprintf(output, " # ");
+ color_fprintf(output, color, "%6.2f%%", ratio);
+ fprintf(output, " of all L1-icache hits ");
+}
+
+static void print_dtlb_cache_misses(int cpu,
+ struct perf_evsel *evsel __maybe_unused,
+ double avg)
+{
+ double total, ratio = 0.0;
+ const char *color;
+
+ total = avg_stats(&runtime_dtlb_cache_stats[cpu]);
+
+ if (total)
+ ratio = avg / total * 100.0;
+
+ color = get_ratio_color(GRC_CACHE_MISSES, ratio);
+
+ fprintf(output, " # ");
+ color_fprintf(output, color, "%6.2f%%", ratio);
+ fprintf(output, " of all dTLB cache hits ");
+}
+
+static void print_itlb_cache_misses(int cpu,
+ struct perf_evsel *evsel __maybe_unused,
+ double avg)
+{
+ double total, ratio = 0.0;
+ const char *color;
+
+ total = avg_stats(&runtime_itlb_cache_stats[cpu]);
+
+ if (total)
+ ratio = avg / total * 100.0;
+
+ color = get_ratio_color(GRC_CACHE_MISSES, ratio);
+
+ fprintf(output, " # ");
+ color_fprintf(output, color, "%6.2f%%", ratio);
+ fprintf(output, " of all iTLB cache hits ");
+}
+
+static void print_ll_cache_misses(int cpu,
+ struct perf_evsel *evsel __maybe_unused,
+ double avg)
+{
+ double total, ratio = 0.0;
+ const char *color;
+
+ total = avg_stats(&runtime_ll_cache_stats[cpu]);
+
+ if (total)
+ ratio = avg / total * 100.0;
+
+ color = get_ratio_color(GRC_CACHE_MISSES, ratio);
+
+ fprintf(output, " # ");
+ color_fprintf(output, color, "%6.2f%%", ratio);
+ fprintf(output, " of all LL-cache hits ");
+}
+
+static void abs_printout(int cpu, int nr, struct perf_evsel *evsel, double avg)
+{
+ double total, ratio = 0.0, total2;
+ double sc = evsel->scale;
+ const char *fmt;
+
+ if (csv_output) {
+ fmt = sc != 1.0 ? "%.2f%s" : "%.0f%s";
+ } else {
+ if (big_num)
+ fmt = sc != 1.0 ? "%'18.2f%s" : "%'18.0f%s";
+ else
+ fmt = sc != 1.0 ? "%18.2f%s" : "%18.0f%s";
+ }
+
+ aggr_printout(evsel, cpu, nr);
+
+ if (aggr_mode == AGGR_GLOBAL)
+ cpu = 0;
+
+ fprintf(output, fmt, avg, csv_sep);
+
+ if (evsel->unit)
+ fprintf(output, "%-*s%s",
+ csv_output ? 0 : unit_width,
+ evsel->unit, csv_sep);
+
+ fprintf(output, "%-*s", csv_output ? 0 : 25, perf_evsel__name(evsel));
+
+ if (evsel->cgrp)
+ fprintf(output, "%s%s", csv_sep, evsel->cgrp->name);
+
+ if (csv_output || interval)
+ return;
+
+ if (perf_evsel__match(evsel, HARDWARE, HW_INSTRUCTIONS)) {
+ total = avg_stats(&runtime_cycles_stats[cpu]);
+ if (total) {
+ ratio = avg / total;
+ fprintf(output, " # %5.2f insns per cycle ", ratio);
+ }
+ total = avg_stats(&runtime_stalled_cycles_front_stats[cpu]);
+ total = max(total, avg_stats(&runtime_stalled_cycles_back_stats[cpu]));
+
+ if (total && avg) {
+ ratio = total / avg;
+ fprintf(output, "\n");
+ if (aggr_mode == AGGR_NONE)
+ fprintf(output, " ");
+ fprintf(output, " # %5.2f stalled cycles per insn", ratio);
+ }
+
+ } else if (perf_evsel__match(evsel, HARDWARE, HW_BRANCH_MISSES) &&
+ runtime_branches_stats[cpu].n != 0) {
+ print_branch_misses(cpu, evsel, avg);
+ } else if (
+ evsel->attr.type == PERF_TYPE_HW_CACHE &&
+ evsel->attr.config == ( PERF_COUNT_HW_CACHE_L1D |
+ ((PERF_COUNT_HW_CACHE_OP_READ) << 8) |
+ ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16)) &&
+ runtime_l1_dcache_stats[cpu].n != 0) {
+ print_l1_dcache_misses(cpu, evsel, avg);
+ } else if (
+ evsel->attr.type == PERF_TYPE_HW_CACHE &&
+ evsel->attr.config == ( PERF_COUNT_HW_CACHE_L1I |
+ ((PERF_COUNT_HW_CACHE_OP_READ) << 8) |
+ ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16)) &&
+ runtime_l1_icache_stats[cpu].n != 0) {
+ print_l1_icache_misses(cpu, evsel, avg);
+ } else if (
+ evsel->attr.type == PERF_TYPE_HW_CACHE &&
+ evsel->attr.config == ( PERF_COUNT_HW_CACHE_DTLB |
+ ((PERF_COUNT_HW_CACHE_OP_READ) << 8) |
+ ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16)) &&
+ runtime_dtlb_cache_stats[cpu].n != 0) {
+ print_dtlb_cache_misses(cpu, evsel, avg);
+ } else if (
+ evsel->attr.type == PERF_TYPE_HW_CACHE &&
+ evsel->attr.config == ( PERF_COUNT_HW_CACHE_ITLB |
+ ((PERF_COUNT_HW_CACHE_OP_READ) << 8) |
+ ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16)) &&
+ runtime_itlb_cache_stats[cpu].n != 0) {
+ print_itlb_cache_misses(cpu, evsel, avg);
+ } else if (
+ evsel->attr.type == PERF_TYPE_HW_CACHE &&
+ evsel->attr.config == ( PERF_COUNT_HW_CACHE_LL |
+ ((PERF_COUNT_HW_CACHE_OP_READ) << 8) |
+ ((PERF_COUNT_HW_CACHE_RESULT_MISS) << 16)) &&
+ runtime_ll_cache_stats[cpu].n != 0) {
+ print_ll_cache_misses(cpu, evsel, avg);
+ } else if (perf_evsel__match(evsel, HARDWARE, HW_CACHE_MISSES) &&
+ runtime_cacherefs_stats[cpu].n != 0) {
+ total = avg_stats(&runtime_cacherefs_stats[cpu]);
+
+ if (total)
+ ratio = avg * 100 / total;
+
+ fprintf(output, " # %8.3f %% of all cache refs ", ratio);
+
+ } else if (perf_evsel__match(evsel, HARDWARE, HW_STALLED_CYCLES_FRONTEND)) {
+ print_stalled_cycles_frontend(cpu, evsel, avg);
+ } else if (perf_evsel__match(evsel, HARDWARE, HW_STALLED_CYCLES_BACKEND)) {
+ print_stalled_cycles_backend(cpu, evsel, avg);
+ } else if (perf_evsel__match(evsel, HARDWARE, HW_CPU_CYCLES)) {
+ total = avg_stats(&runtime_nsecs_stats[cpu]);
+
+ if (total) {
+ ratio = avg / total;
+ fprintf(output, " # %8.3f GHz ", ratio);
+ }
+ } else if (transaction_run &&
+ perf_evsel__cmp(evsel, nth_evsel(T_CYCLES_IN_TX))) {
+ total = avg_stats(&runtime_cycles_stats[cpu]);
+ if (total)
+ fprintf(output,
+ " # %5.2f%% transactional cycles ",
+ 100.0 * (avg / total));
+ } else if (transaction_run &&
+ perf_evsel__cmp(evsel, nth_evsel(T_CYCLES_IN_TX_CP))) {
+ total = avg_stats(&runtime_cycles_stats[cpu]);
+ total2 = avg_stats(&runtime_cycles_in_tx_stats[cpu]);
+ if (total2 < avg)
+ total2 = avg;
+ if (total)
+ fprintf(output,
+ " # %5.2f%% aborted cycles ",
+ 100.0 * ((total2-avg) / total));
+ } else if (transaction_run &&
+ perf_evsel__cmp(evsel, nth_evsel(T_TRANSACTION_START)) &&
+ avg > 0 &&
+ runtime_cycles_in_tx_stats[cpu].n != 0) {
+ total = avg_stats(&runtime_cycles_in_tx_stats[cpu]);
+
+ if (total)
+ ratio = total / avg;
+
+ fprintf(output, " # %8.0f cycles / transaction ", ratio);
+ } else if (transaction_run &&
+ perf_evsel__cmp(evsel, nth_evsel(T_ELISION_START)) &&
+ avg > 0 &&
+ runtime_cycles_in_tx_stats[cpu].n != 0) {
+ total = avg_stats(&runtime_cycles_in_tx_stats[cpu]);
+
+ if (total)
+ ratio = total / avg;
+
+ fprintf(output, " # %8.0f cycles / elision ", ratio);
+ } else if (runtime_nsecs_stats[cpu].n != 0) {
+ char unit = 'M';
+
+ total = avg_stats(&runtime_nsecs_stats[cpu]);
+
+ if (total)
+ ratio = 1000.0 * avg / total;
+ if (ratio < 0.001) {
+ ratio *= 1000;
+ unit = 'K';
+ }
+
+ fprintf(output, " # %8.3f %c/sec ", ratio, unit);
+ } else {
+ fprintf(output, " ");
+ }
+}
+
+static void print_aggr(char *prefix)
+{
+ struct perf_evsel *counter;
+ int cpu, cpu2, s, s2, id, nr;
+ double uval;
+ u64 ena, run, val;
+
+ if (!(aggr_map || aggr_get_id))
+ return;
+
+ for (s = 0; s < aggr_map->nr; s++) {
+ id = aggr_map->map[s];
+ evlist__for_each(evsel_list, counter) {
+ val = ena = run = 0;
+ nr = 0;
+ for (cpu = 0; cpu < perf_evsel__nr_cpus(counter); cpu++) {
+ cpu2 = perf_evsel__cpus(counter)->map[cpu];
+ s2 = aggr_get_id(evsel_list->cpus, cpu2);
+ if (s2 != id)
+ continue;
+ val += counter->counts->cpu[cpu].val;
+ ena += counter->counts->cpu[cpu].ena;
+ run += counter->counts->cpu[cpu].run;
+ nr++;
+ }
+ if (prefix)
+ fprintf(output, "%s", prefix);
+
+ if (run == 0 || ena == 0) {
+ aggr_printout(counter, id, nr);
+
+ fprintf(output, "%*s%s",
+ csv_output ? 0 : 18,
+ counter->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED,
+ csv_sep);
+
+ fprintf(output, "%-*s%s",
+ csv_output ? 0 : unit_width,
+ counter->unit, csv_sep);
+
+ fprintf(output, "%*s",
+ csv_output ? 0 : -25,
+ perf_evsel__name(counter));
+
+ if (counter->cgrp)
+ fprintf(output, "%s%s",
+ csv_sep, counter->cgrp->name);
+
+ fputc('\n', output);
+ continue;
+ }
+ uval = val * counter->scale;
+
+ if (nsec_counter(counter))
+ nsec_printout(id, nr, counter, uval);
+ else
+ abs_printout(id, nr, counter, uval);
+
+ if (!csv_output) {
+ print_noise(counter, 1.0);
+
+ if (run != ena)
+ fprintf(output, " (%.2f%%)",
+ 100.0 * run / ena);
+ }
+ fputc('\n', output);
+ }
+ }
+}
+
+/*
+ * Print out the results of a single counter:
+ * aggregated counts in system-wide mode
+ */
+static void print_counter_aggr(struct perf_evsel *counter, char *prefix)
+{
+ struct perf_stat *ps = counter->priv;
+ double avg = avg_stats(&ps->res_stats[0]);
+ int scaled = counter->counts->scaled;
+ double uval;
+
+ if (prefix)
+ fprintf(output, "%s", prefix);
+
+ if (scaled == -1) {
+ fprintf(output, "%*s%s",
+ csv_output ? 0 : 18,
+ counter->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED,
+ csv_sep);
+ fprintf(output, "%-*s%s",
+ csv_output ? 0 : unit_width,
+ counter->unit, csv_sep);
+ fprintf(output, "%*s",
+ csv_output ? 0 : -25,
+ perf_evsel__name(counter));
+
+ if (counter->cgrp)
+ fprintf(output, "%s%s", csv_sep, counter->cgrp->name);
+
+ fputc('\n', output);
+ return;
+ }
+
+ uval = avg * counter->scale;
+
+ if (nsec_counter(counter))
+ nsec_printout(-1, 0, counter, uval);
+ else
+ abs_printout(-1, 0, counter, uval);
+
+ print_noise(counter, avg);
+
+ if (csv_output) {
+ fputc('\n', output);
+ return;
+ }
+
+ if (scaled) {
+ double avg_enabled, avg_running;
+
+ avg_enabled = avg_stats(&ps->res_stats[1]);
+ avg_running = avg_stats(&ps->res_stats[2]);
+
+ fprintf(output, " [%5.2f%%]", 100 * avg_running / avg_enabled);
+ }
+ fprintf(output, "\n");
+}
+
+/*
+ * Print out the results of a single counter:
+ * does not use aggregated count in system-wide
+ */
+static void print_counter(struct perf_evsel *counter, char *prefix)
+{
+ u64 ena, run, val;
+ double uval;
+ int cpu;
+
+ for (cpu = 0; cpu < perf_evsel__nr_cpus(counter); cpu++) {
+ val = counter->counts->cpu[cpu].val;
+ ena = counter->counts->cpu[cpu].ena;
+ run = counter->counts->cpu[cpu].run;
+
+ if (prefix)
+ fprintf(output, "%s", prefix);
+
+ if (run == 0 || ena == 0) {
+ fprintf(output, "CPU%*d%s%*s%s",
+ csv_output ? 0 : -4,
+ perf_evsel__cpus(counter)->map[cpu], csv_sep,
+ csv_output ? 0 : 18,
+ counter->supported ? CNTR_NOT_COUNTED : CNTR_NOT_SUPPORTED,
+ csv_sep);
+
+ fprintf(output, "%-*s%s",
+ csv_output ? 0 : unit_width,
+ counter->unit, csv_sep);
+
+ fprintf(output, "%*s",
+ csv_output ? 0 : -25,
+ perf_evsel__name(counter));
+
+ if (counter->cgrp)
+ fprintf(output, "%s%s",
+ csv_sep, counter->cgrp->name);
+
+ fputc('\n', output);
+ continue;
+ }
+
+ uval = val * counter->scale;
+
+ if (nsec_counter(counter))
+ nsec_printout(cpu, 0, counter, uval);
+ else
+ abs_printout(cpu, 0, counter, uval);
+
+ if (!csv_output) {
+ print_noise(counter, 1.0);
+
+ if (run != ena)
+ fprintf(output, " (%.2f%%)",
+ 100.0 * run / ena);
+ }
+ fputc('\n', output);
+ }
+}
+
+static void print_stat(int argc, const char **argv)
+{
+ struct perf_evsel *counter;
+ int i;
+
+ fflush(stdout);
+
+ if (!csv_output) {
+ fprintf(output, "\n");
+ fprintf(output, " Performance counter stats for ");
+ if (target.system_wide)
+ fprintf(output, "\'system wide");
+ else if (target.cpu_list)
+ fprintf(output, "\'CPU(s) %s", target.cpu_list);
+ else if (!target__has_task(&target)) {
+ fprintf(output, "\'%s", argv[0]);
+ for (i = 1; i < argc; i++)
+ fprintf(output, " %s", argv[i]);
+ } else if (target.pid)
+ fprintf(output, "process id \'%s", target.pid);
+ else
+ fprintf(output, "thread id \'%s", target.tid);
+
+ fprintf(output, "\'");
+ if (run_count > 1)
+ fprintf(output, " (%d runs)", run_count);
+ fprintf(output, ":\n\n");
+ }
+
+ switch (aggr_mode) {
+ case AGGR_CORE:
+ case AGGR_SOCKET:
+ print_aggr(NULL);
+ break;
+ case AGGR_GLOBAL:
+ evlist__for_each(evsel_list, counter)
+ print_counter_aggr(counter, NULL);
+ break;
+ case AGGR_NONE:
+ evlist__for_each(evsel_list, counter)
+ print_counter(counter, NULL);
+ break;
+ default:
+ break;
+ }
+
+ if (!csv_output) {
+ if (!null_run)
+ fprintf(output, "\n");
+ fprintf(output, " %17.9f seconds time elapsed",
+ avg_stats(&walltime_nsecs_stats)/1e9);
+ if (run_count > 1) {
+ fprintf(output, " ");
+ print_noise_pct(stddev_stats(&walltime_nsecs_stats),
+ avg_stats(&walltime_nsecs_stats));
+ }
+ fprintf(output, "\n\n");
+ }
+}
+
+static volatile int signr = -1;
+
+static void skip_signal(int signo)
+{
+ if ((child_pid == -1) || interval)
+ done = 1;
+
+ signr = signo;
+ /*
+ * render child_pid harmless
+ * won't send SIGTERM to a random
+ * process in case of race condition
+ * and fast PID recycling
+ */
+ child_pid = -1;
+}
+
+static void sig_atexit(void)
+{
+ sigset_t set, oset;
+
+ /*
+ * avoid race condition with SIGCHLD handler
+ * in skip_signal() which is modifying child_pid
+ * goal is to avoid send SIGTERM to a random
+ * process
+ */
+ sigemptyset(&set);
+ sigaddset(&set, SIGCHLD);
+ sigprocmask(SIG_BLOCK, &set, &oset);
+
+ if (child_pid != -1)
+ kill(child_pid, SIGTERM);
+
+ sigprocmask(SIG_SETMASK, &oset, NULL);
+
+ if (signr == -1)
+ return;
+
+ signal(signr, SIG_DFL);
+ kill(getpid(), signr);
+}
+
+static int stat__set_big_num(const struct option *opt __maybe_unused,
+ const char *s __maybe_unused, int unset)
+{
+ big_num_opt = unset ? 0 : 1;
+ return 0;
+}
+
+static int perf_stat_init_aggr_mode(void)
+{
+ switch (aggr_mode) {
+ case AGGR_SOCKET:
+ if (cpu_map__build_socket_map(evsel_list->cpus, &aggr_map)) {
+ perror("cannot build socket map");
+ return -1;
+ }
+ aggr_get_id = cpu_map__get_socket;
+ break;
+ case AGGR_CORE:
+ if (cpu_map__build_core_map(evsel_list->cpus, &aggr_map)) {
+ perror("cannot build core map");
+ return -1;
+ }
+ aggr_get_id = cpu_map__get_core;
+ break;
+ case AGGR_NONE:
+ case AGGR_GLOBAL:
+ default:
+ break;
+ }
+ return 0;
+}
+
+static int setup_events(const char * const *attrs, unsigned len)
+{
+ unsigned i;
+
+ for (i = 0; i < len; i++) {
+ if (parse_events(evsel_list, attrs[i]))
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Add default attributes, if there were no attributes specified or
+ * if -d/--detailed, -d -d or -d -d -d is used:
+ */
+static int add_default_attributes(void)
+{
+ struct perf_event_attr default_attrs[] = {
+
+ { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_TASK_CLOCK },
+ { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_CONTEXT_SWITCHES },
+ { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_CPU_MIGRATIONS },
+ { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_PAGE_FAULTS },
+
+ { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_CPU_CYCLES },
+ { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_STALLED_CYCLES_FRONTEND },
+ { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_STALLED_CYCLES_BACKEND },
+ { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_INSTRUCTIONS },
+ { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_BRANCH_INSTRUCTIONS },
+ { .type = PERF_TYPE_HARDWARE, .config = PERF_COUNT_HW_BRANCH_MISSES },
+
+};
+
+/*
+ * Detailed stats (-d), covering the L1 and last level data caches:
+ */
+ struct perf_event_attr detailed_attrs[] = {
+
+ { .type = PERF_TYPE_HW_CACHE,
+ .config =
+ PERF_COUNT_HW_CACHE_L1D << 0 |
+ (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+ (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) },
+
+ { .type = PERF_TYPE_HW_CACHE,
+ .config =
+ PERF_COUNT_HW_CACHE_L1D << 0 |
+ (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+ (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) },
+
+ { .type = PERF_TYPE_HW_CACHE,
+ .config =
+ PERF_COUNT_HW_CACHE_LL << 0 |
+ (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+ (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) },
+
+ { .type = PERF_TYPE_HW_CACHE,
+ .config =
+ PERF_COUNT_HW_CACHE_LL << 0 |
+ (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+ (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) },
+};
+
+/*
+ * Very detailed stats (-d -d), covering the instruction cache and the TLB caches:
+ */
+ struct perf_event_attr very_detailed_attrs[] = {
+
+ { .type = PERF_TYPE_HW_CACHE,
+ .config =
+ PERF_COUNT_HW_CACHE_L1I << 0 |
+ (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+ (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) },
+
+ { .type = PERF_TYPE_HW_CACHE,
+ .config =
+ PERF_COUNT_HW_CACHE_L1I << 0 |
+ (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+ (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) },
+
+ { .type = PERF_TYPE_HW_CACHE,
+ .config =
+ PERF_COUNT_HW_CACHE_DTLB << 0 |
+ (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+ (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) },
+
+ { .type = PERF_TYPE_HW_CACHE,
+ .config =
+ PERF_COUNT_HW_CACHE_DTLB << 0 |
+ (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+ (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) },
+
+ { .type = PERF_TYPE_HW_CACHE,
+ .config =
+ PERF_COUNT_HW_CACHE_ITLB << 0 |
+ (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+ (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) },
+
+ { .type = PERF_TYPE_HW_CACHE,
+ .config =
+ PERF_COUNT_HW_CACHE_ITLB << 0 |
+ (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+ (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) },
+
+};
+
+/*
+ * Very, very detailed stats (-d -d -d), adding prefetch events:
+ */
+ struct perf_event_attr very_very_detailed_attrs[] = {
+
+ { .type = PERF_TYPE_HW_CACHE,
+ .config =
+ PERF_COUNT_HW_CACHE_L1D << 0 |
+ (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
+ (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16) },
+
+ { .type = PERF_TYPE_HW_CACHE,
+ .config =
+ PERF_COUNT_HW_CACHE_L1D << 0 |
+ (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
+ (PERF_COUNT_HW_CACHE_RESULT_MISS << 16) },
+};
+
+ /* Set attrs if no event is selected and !null_run: */
+ if (null_run)
+ return 0;
+
+ if (transaction_run) {
+ int err;
+ if (pmu_have_event("cpu", "cycles-ct") &&
+ pmu_have_event("cpu", "el-start"))
+ err = setup_events(transaction_attrs,
+ ARRAY_SIZE(transaction_attrs));
+ else
+ err = setup_events(transaction_limited_attrs,
+ ARRAY_SIZE(transaction_limited_attrs));
+ if (err < 0) {
+ fprintf(stderr, "Cannot set up transaction events\n");
+ return -1;
+ }
+ return 0;
+ }
+
+ if (!evsel_list->nr_entries) {
+ if (perf_evlist__add_default_attrs(evsel_list, default_attrs) < 0)
+ return -1;
+ }
+
+ /* Detailed events get appended to the event list: */
+
+ if (detailed_run < 1)
+ return 0;
+
+ /* Append detailed run extra attributes: */
+ if (perf_evlist__add_default_attrs(evsel_list, detailed_attrs) < 0)
+ return -1;
+
+ if (detailed_run < 2)
+ return 0;
+
+ /* Append very detailed run extra attributes: */
+ if (perf_evlist__add_default_attrs(evsel_list, very_detailed_attrs) < 0)
+ return -1;
+
+ if (detailed_run < 3)
+ return 0;
+
+ /* Append very, very detailed run extra attributes: */
+ return perf_evlist__add_default_attrs(evsel_list, very_very_detailed_attrs);
+}
+
+int cmd_stat(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ bool append_file = false;
+ int output_fd = 0;
+ const char *output_name = NULL;
+ const struct option options[] = {
+ OPT_BOOLEAN('T', "transaction", &transaction_run,
+ "hardware transaction statistics"),
+ OPT_CALLBACK('e', "event", &evsel_list, "event",
+ "event selector. use 'perf list' to list available events",
+ parse_events_option),
+ OPT_CALLBACK(0, "filter", &evsel_list, "filter",
+ "event filter", parse_filter),
+ OPT_BOOLEAN('i', "no-inherit", &no_inherit,
+ "child tasks do not inherit counters"),
+ OPT_STRING('p', "pid", &target.pid, "pid",
+ "stat events on existing process id"),
+ OPT_STRING('t', "tid", &target.tid, "tid",
+ "stat events on existing thread id"),
+ OPT_BOOLEAN('a', "all-cpus", &target.system_wide,
+ "system-wide collection from all CPUs"),
+ OPT_BOOLEAN('g', "group", &group,
+ "put the counters into a counter group"),
+ OPT_BOOLEAN('c', "scale", &scale, "scale/normalize counters"),
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show counter open errors, etc)"),
+ OPT_INTEGER('r', "repeat", &run_count,
+ "repeat command and print average + stddev (max: 100, forever: 0)"),
+ OPT_BOOLEAN('n', "null", &null_run,
+ "null run - dont start any counters"),
+ OPT_INCR('d', "detailed", &detailed_run,
+ "detailed run - start a lot of events"),
+ OPT_BOOLEAN('S', "sync", &sync_run,
+ "call sync() before starting a run"),
+ OPT_CALLBACK_NOOPT('B', "big-num", NULL, NULL,
+ "print large numbers with thousands\' separators",
+ stat__set_big_num),
+ OPT_STRING('C', "cpu", &target.cpu_list, "cpu",
+ "list of cpus to monitor in system-wide"),
+ OPT_SET_UINT('A', "no-aggr", &aggr_mode,
+ "disable CPU count aggregation", AGGR_NONE),
+ OPT_STRING('x', "field-separator", &csv_sep, "separator",
+ "print counts with custom separator"),
+ OPT_CALLBACK('G', "cgroup", &evsel_list, "name",
+ "monitor event in cgroup name only", parse_cgroups),
+ OPT_STRING('o', "output", &output_name, "file", "output file name"),
+ OPT_BOOLEAN(0, "append", &append_file, "append to the output file"),
+ OPT_INTEGER(0, "log-fd", &output_fd,
+ "log output to fd, instead of stderr"),
+ OPT_STRING(0, "pre", &pre_cmd, "command",
+ "command to run prior to the measured command"),
+ OPT_STRING(0, "post", &post_cmd, "command",
+ "command to run after to the measured command"),
+ OPT_UINTEGER('I', "interval-print", &interval,
+ "print counts at regular interval in ms (>= 100)"),
+ OPT_SET_UINT(0, "per-socket", &aggr_mode,
+ "aggregate counts per processor socket", AGGR_SOCKET),
+ OPT_SET_UINT(0, "per-core", &aggr_mode,
+ "aggregate counts per physical processor core", AGGR_CORE),
+ OPT_UINTEGER('D', "delay", &initial_delay,
+ "ms to wait before starting measurement after program start"),
+ OPT_END()
+ };
+ const char * const stat_usage[] = {
+ "perf stat [<options>] [<command>]",
+ NULL
+ };
+ int status = -EINVAL, run_idx;
+ const char *mode;
+
+ setlocale(LC_ALL, "");
+
+ evsel_list = perf_evlist__new();
+ if (evsel_list == NULL)
+ return -ENOMEM;
+
+ argc = parse_options(argc, argv, options, stat_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ output = stderr;
+ if (output_name && strcmp(output_name, "-"))
+ output = NULL;
+
+ if (output_name && output_fd) {
+ fprintf(stderr, "cannot use both --output and --log-fd\n");
+ parse_options_usage(stat_usage, options, "o", 1);
+ parse_options_usage(NULL, options, "log-fd", 0);
+ goto out;
+ }
+
+ if (output_fd < 0) {
+ fprintf(stderr, "argument to --log-fd must be a > 0\n");
+ parse_options_usage(stat_usage, options, "log-fd", 0);
+ goto out;
+ }
+
+ if (!output) {
+ struct timespec tm;
+ mode = append_file ? "a" : "w";
+
+ output = fopen(output_name, mode);
+ if (!output) {
+ perror("failed to create output file");
+ return -1;
+ }
+ clock_gettime(CLOCK_REALTIME, &tm);
+ fprintf(output, "# started on %s\n", ctime(&tm.tv_sec));
+ } else if (output_fd > 0) {
+ mode = append_file ? "a" : "w";
+ output = fdopen(output_fd, mode);
+ if (!output) {
+ perror("Failed opening logfd");
+ return -errno;
+ }
+ }
+
+ if (csv_sep) {
+ csv_output = true;
+ if (!strcmp(csv_sep, "\\t"))
+ csv_sep = "\t";
+ } else
+ csv_sep = DEFAULT_SEPARATOR;
+
+ /*
+ * let the spreadsheet do the pretty-printing
+ */
+ if (csv_output) {
+ /* User explicitly passed -B? */
+ if (big_num_opt == 1) {
+ fprintf(stderr, "-B option not supported with -x\n");
+ parse_options_usage(stat_usage, options, "B", 1);
+ parse_options_usage(NULL, options, "x", 1);
+ goto out;
+ } else /* Nope, so disable big number formatting */
+ big_num = false;
+ } else if (big_num_opt == 0) /* User passed --no-big-num */
+ big_num = false;
+
+ if (!argc && target__none(&target))
+ usage_with_options(stat_usage, options);
+
+ if (run_count < 0) {
+ pr_err("Run count must be a positive number\n");
+ parse_options_usage(stat_usage, options, "r", 1);
+ goto out;
+ } else if (run_count == 0) {
+ forever = true;
+ run_count = 1;
+ }
+
+ /* no_aggr, cgroup are for system-wide only */
+ if ((aggr_mode != AGGR_GLOBAL || nr_cgroups) &&
+ !target__has_cpu(&target)) {
+ fprintf(stderr, "both cgroup and no-aggregation "
+ "modes only available in system-wide mode\n");
+
+ parse_options_usage(stat_usage, options, "G", 1);
+ parse_options_usage(NULL, options, "A", 1);
+ parse_options_usage(NULL, options, "a", 1);
+ goto out;
+ }
+
+ if (add_default_attributes())
+ goto out;
+
+ target__validate(&target);
+
+ if (perf_evlist__create_maps(evsel_list, &target) < 0) {
+ if (target__has_task(&target)) {
+ pr_err("Problems finding threads of monitor\n");
+ parse_options_usage(stat_usage, options, "p", 1);
+ parse_options_usage(NULL, options, "t", 1);
+ } else if (target__has_cpu(&target)) {
+ perror("failed to parse CPUs map");
+ parse_options_usage(stat_usage, options, "C", 1);
+ parse_options_usage(NULL, options, "a", 1);
+ }
+ goto out;
+ }
+ if (interval && interval < 100) {
+ pr_err("print interval must be >= 100ms\n");
+ parse_options_usage(stat_usage, options, "I", 1);
+ goto out;
+ }
+
+ if (perf_evlist__alloc_stats(evsel_list, interval))
+ goto out;
+
+ if (perf_stat_init_aggr_mode())
+ goto out;
+
+ /*
+ * We dont want to block the signals - that would cause
+ * child tasks to inherit that and Ctrl-C would not work.
+ * What we want is for Ctrl-C to work in the exec()-ed
+ * task, but being ignored by perf stat itself:
+ */
+ atexit(sig_atexit);
+ if (!forever)
+ signal(SIGINT, skip_signal);
+ signal(SIGCHLD, skip_signal);
+ signal(SIGALRM, skip_signal);
+ signal(SIGABRT, skip_signal);
+
+ status = 0;
+ for (run_idx = 0; forever || run_idx < run_count; run_idx++) {
+ if (run_count != 1 && verbose)
+ fprintf(output, "[ perf stat: executing run #%d ... ]\n",
+ run_idx + 1);
+
+ status = run_perf_stat(argc, argv);
+ if (forever && status != -1) {
+ print_stat(argc, argv);
+ perf_stat__reset_stats(evsel_list);
+ }
+ }
+
+ if (!forever && status != -1 && !interval)
+ print_stat(argc, argv);
+
+ perf_evlist__free_stats(evsel_list);
+out:
+ perf_evlist__delete(evsel_list);
+ return status;
+}
diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c
new file mode 100644
index 00000000000..74db2568b86
--- /dev/null
+++ b/tools/perf/builtin-timechart.c
@@ -0,0 +1,1350 @@
+/*
+ * builtin-timechart.c - make an svg timechart of system activity
+ *
+ * (C) Copyright 2009 Intel Corporation
+ *
+ * Authors:
+ * Arjan van de Ven <arjan@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2
+ * of the License.
+ */
+
+#include <traceevent/event-parse.h>
+
+#include "builtin.h"
+
+#include "util/util.h"
+
+#include "util/color.h"
+#include <linux/list.h>
+#include "util/cache.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include <linux/rbtree.h>
+#include "util/symbol.h"
+#include "util/callchain.h"
+#include "util/strlist.h"
+
+#include "perf.h"
+#include "util/header.h"
+#include "util/parse-options.h"
+#include "util/parse-events.h"
+#include "util/event.h"
+#include "util/session.h"
+#include "util/svghelper.h"
+#include "util/tool.h"
+#include "util/data.h"
+
+#define SUPPORT_OLD_POWER_EVENTS 1
+#define PWR_EVENT_EXIT -1
+
+struct per_pid;
+struct power_event;
+struct wake_event;
+
+struct timechart {
+ struct perf_tool tool;
+ struct per_pid *all_data;
+ struct power_event *power_events;
+ struct wake_event *wake_events;
+ int proc_num;
+ unsigned int numcpus;
+ u64 min_freq, /* Lowest CPU frequency seen */
+ max_freq, /* Highest CPU frequency seen */
+ turbo_frequency,
+ first_time, last_time;
+ bool power_only,
+ tasks_only,
+ with_backtrace,
+ topology;
+};
+
+struct per_pidcomm;
+struct cpu_sample;
+
+/*
+ * Datastructure layout:
+ * We keep an list of "pid"s, matching the kernels notion of a task struct.
+ * Each "pid" entry, has a list of "comm"s.
+ * this is because we want to track different programs different, while
+ * exec will reuse the original pid (by design).
+ * Each comm has a list of samples that will be used to draw
+ * final graph.
+ */
+
+struct per_pid {
+ struct per_pid *next;
+
+ int pid;
+ int ppid;
+
+ u64 start_time;
+ u64 end_time;
+ u64 total_time;
+ int display;
+
+ struct per_pidcomm *all;
+ struct per_pidcomm *current;
+};
+
+
+struct per_pidcomm {
+ struct per_pidcomm *next;
+
+ u64 start_time;
+ u64 end_time;
+ u64 total_time;
+
+ int Y;
+ int display;
+
+ long state;
+ u64 state_since;
+
+ char *comm;
+
+ struct cpu_sample *samples;
+};
+
+struct sample_wrapper {
+ struct sample_wrapper *next;
+
+ u64 timestamp;
+ unsigned char data[0];
+};
+
+#define TYPE_NONE 0
+#define TYPE_RUNNING 1
+#define TYPE_WAITING 2
+#define TYPE_BLOCKED 3
+
+struct cpu_sample {
+ struct cpu_sample *next;
+
+ u64 start_time;
+ u64 end_time;
+ int type;
+ int cpu;
+ const char *backtrace;
+};
+
+#define CSTATE 1
+#define PSTATE 2
+
+struct power_event {
+ struct power_event *next;
+ int type;
+ int state;
+ u64 start_time;
+ u64 end_time;
+ int cpu;
+};
+
+struct wake_event {
+ struct wake_event *next;
+ int waker;
+ int wakee;
+ u64 time;
+ const char *backtrace;
+};
+
+struct process_filter {
+ char *name;
+ int pid;
+ struct process_filter *next;
+};
+
+static struct process_filter *process_filter;
+
+
+static struct per_pid *find_create_pid(struct timechart *tchart, int pid)
+{
+ struct per_pid *cursor = tchart->all_data;
+
+ while (cursor) {
+ if (cursor->pid == pid)
+ return cursor;
+ cursor = cursor->next;
+ }
+ cursor = zalloc(sizeof(*cursor));
+ assert(cursor != NULL);
+ cursor->pid = pid;
+ cursor->next = tchart->all_data;
+ tchart->all_data = cursor;
+ return cursor;
+}
+
+static void pid_set_comm(struct timechart *tchart, int pid, char *comm)
+{
+ struct per_pid *p;
+ struct per_pidcomm *c;
+ p = find_create_pid(tchart, pid);
+ c = p->all;
+ while (c) {
+ if (c->comm && strcmp(c->comm, comm) == 0) {
+ p->current = c;
+ return;
+ }
+ if (!c->comm) {
+ c->comm = strdup(comm);
+ p->current = c;
+ return;
+ }
+ c = c->next;
+ }
+ c = zalloc(sizeof(*c));
+ assert(c != NULL);
+ c->comm = strdup(comm);
+ p->current = c;
+ c->next = p->all;
+ p->all = c;
+}
+
+static void pid_fork(struct timechart *tchart, int pid, int ppid, u64 timestamp)
+{
+ struct per_pid *p, *pp;
+ p = find_create_pid(tchart, pid);
+ pp = find_create_pid(tchart, ppid);
+ p->ppid = ppid;
+ if (pp->current && pp->current->comm && !p->current)
+ pid_set_comm(tchart, pid, pp->current->comm);
+
+ p->start_time = timestamp;
+ if (p->current) {
+ p->current->start_time = timestamp;
+ p->current->state_since = timestamp;
+ }
+}
+
+static void pid_exit(struct timechart *tchart, int pid, u64 timestamp)
+{
+ struct per_pid *p;
+ p = find_create_pid(tchart, pid);
+ p->end_time = timestamp;
+ if (p->current)
+ p->current->end_time = timestamp;
+}
+
+static void pid_put_sample(struct timechart *tchart, int pid, int type,
+ unsigned int cpu, u64 start, u64 end,
+ const char *backtrace)
+{
+ struct per_pid *p;
+ struct per_pidcomm *c;
+ struct cpu_sample *sample;
+
+ p = find_create_pid(tchart, pid);
+ c = p->current;
+ if (!c) {
+ c = zalloc(sizeof(*c));
+ assert(c != NULL);
+ p->current = c;
+ c->next = p->all;
+ p->all = c;
+ }
+
+ sample = zalloc(sizeof(*sample));
+ assert(sample != NULL);
+ sample->start_time = start;
+ sample->end_time = end;
+ sample->type = type;
+ sample->next = c->samples;
+ sample->cpu = cpu;
+ sample->backtrace = backtrace;
+ c->samples = sample;
+
+ if (sample->type == TYPE_RUNNING && end > start && start > 0) {
+ c->total_time += (end-start);
+ p->total_time += (end-start);
+ }
+
+ if (c->start_time == 0 || c->start_time > start)
+ c->start_time = start;
+ if (p->start_time == 0 || p->start_time > start)
+ p->start_time = start;
+}
+
+#define MAX_CPUS 4096
+
+static u64 cpus_cstate_start_times[MAX_CPUS];
+static int cpus_cstate_state[MAX_CPUS];
+static u64 cpus_pstate_start_times[MAX_CPUS];
+static u64 cpus_pstate_state[MAX_CPUS];
+
+static int process_comm_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ struct timechart *tchart = container_of(tool, struct timechart, tool);
+ pid_set_comm(tchart, event->comm.tid, event->comm.comm);
+ return 0;
+}
+
+static int process_fork_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ struct timechart *tchart = container_of(tool, struct timechart, tool);
+ pid_fork(tchart, event->fork.pid, event->fork.ppid, event->fork.time);
+ return 0;
+}
+
+static int process_exit_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ struct timechart *tchart = container_of(tool, struct timechart, tool);
+ pid_exit(tchart, event->fork.pid, event->fork.time);
+ return 0;
+}
+
+#ifdef SUPPORT_OLD_POWER_EVENTS
+static int use_old_power_events;
+#endif
+
+static void c_state_start(int cpu, u64 timestamp, int state)
+{
+ cpus_cstate_start_times[cpu] = timestamp;
+ cpus_cstate_state[cpu] = state;
+}
+
+static void c_state_end(struct timechart *tchart, int cpu, u64 timestamp)
+{
+ struct power_event *pwr = zalloc(sizeof(*pwr));
+
+ if (!pwr)
+ return;
+
+ pwr->state = cpus_cstate_state[cpu];
+ pwr->start_time = cpus_cstate_start_times[cpu];
+ pwr->end_time = timestamp;
+ pwr->cpu = cpu;
+ pwr->type = CSTATE;
+ pwr->next = tchart->power_events;
+
+ tchart->power_events = pwr;
+}
+
+static void p_state_change(struct timechart *tchart, int cpu, u64 timestamp, u64 new_freq)
+{
+ struct power_event *pwr;
+
+ if (new_freq > 8000000) /* detect invalid data */
+ return;
+
+ pwr = zalloc(sizeof(*pwr));
+ if (!pwr)
+ return;
+
+ pwr->state = cpus_pstate_state[cpu];
+ pwr->start_time = cpus_pstate_start_times[cpu];
+ pwr->end_time = timestamp;
+ pwr->cpu = cpu;
+ pwr->type = PSTATE;
+ pwr->next = tchart->power_events;
+
+ if (!pwr->start_time)
+ pwr->start_time = tchart->first_time;
+
+ tchart->power_events = pwr;
+
+ cpus_pstate_state[cpu] = new_freq;
+ cpus_pstate_start_times[cpu] = timestamp;
+
+ if ((u64)new_freq > tchart->max_freq)
+ tchart->max_freq = new_freq;
+
+ if (new_freq < tchart->min_freq || tchart->min_freq == 0)
+ tchart->min_freq = new_freq;
+
+ if (new_freq == tchart->max_freq - 1000)
+ tchart->turbo_frequency = tchart->max_freq;
+}
+
+static void sched_wakeup(struct timechart *tchart, int cpu, u64 timestamp,
+ int waker, int wakee, u8 flags, const char *backtrace)
+{
+ struct per_pid *p;
+ struct wake_event *we = zalloc(sizeof(*we));
+
+ if (!we)
+ return;
+
+ we->time = timestamp;
+ we->waker = waker;
+ we->backtrace = backtrace;
+
+ if ((flags & TRACE_FLAG_HARDIRQ) || (flags & TRACE_FLAG_SOFTIRQ))
+ we->waker = -1;
+
+ we->wakee = wakee;
+ we->next = tchart->wake_events;
+ tchart->wake_events = we;
+ p = find_create_pid(tchart, we->wakee);
+
+ if (p && p->current && p->current->state == TYPE_NONE) {
+ p->current->state_since = timestamp;
+ p->current->state = TYPE_WAITING;
+ }
+ if (p && p->current && p->current->state == TYPE_BLOCKED) {
+ pid_put_sample(tchart, p->pid, p->current->state, cpu,
+ p->current->state_since, timestamp, NULL);
+ p->current->state_since = timestamp;
+ p->current->state = TYPE_WAITING;
+ }
+}
+
+static void sched_switch(struct timechart *tchart, int cpu, u64 timestamp,
+ int prev_pid, int next_pid, u64 prev_state,
+ const char *backtrace)
+{
+ struct per_pid *p = NULL, *prev_p;
+
+ prev_p = find_create_pid(tchart, prev_pid);
+
+ p = find_create_pid(tchart, next_pid);
+
+ if (prev_p->current && prev_p->current->state != TYPE_NONE)
+ pid_put_sample(tchart, prev_pid, TYPE_RUNNING, cpu,
+ prev_p->current->state_since, timestamp,
+ backtrace);
+ if (p && p->current) {
+ if (p->current->state != TYPE_NONE)
+ pid_put_sample(tchart, next_pid, p->current->state, cpu,
+ p->current->state_since, timestamp,
+ backtrace);
+
+ p->current->state_since = timestamp;
+ p->current->state = TYPE_RUNNING;
+ }
+
+ if (prev_p->current) {
+ prev_p->current->state = TYPE_NONE;
+ prev_p->current->state_since = timestamp;
+ if (prev_state & 2)
+ prev_p->current->state = TYPE_BLOCKED;
+ if (prev_state == 0)
+ prev_p->current->state = TYPE_WAITING;
+ }
+}
+
+static const char *cat_backtrace(union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct addr_location al;
+ unsigned int i;
+ char *p = NULL;
+ size_t p_len;
+ u8 cpumode = PERF_RECORD_MISC_USER;
+ struct addr_location tal;
+ struct ip_callchain *chain = sample->callchain;
+ FILE *f = open_memstream(&p, &p_len);
+
+ if (!f) {
+ perror("open_memstream error");
+ return NULL;
+ }
+
+ if (!chain)
+ goto exit;
+
+ if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) {
+ fprintf(stderr, "problem processing %d event, skipping it.\n",
+ event->header.type);
+ goto exit;
+ }
+
+ for (i = 0; i < chain->nr; i++) {
+ u64 ip;
+
+ if (callchain_param.order == ORDER_CALLEE)
+ ip = chain->ips[i];
+ else
+ ip = chain->ips[chain->nr - i - 1];
+
+ if (ip >= PERF_CONTEXT_MAX) {
+ switch (ip) {
+ case PERF_CONTEXT_HV:
+ cpumode = PERF_RECORD_MISC_HYPERVISOR;
+ break;
+ case PERF_CONTEXT_KERNEL:
+ cpumode = PERF_RECORD_MISC_KERNEL;
+ break;
+ case PERF_CONTEXT_USER:
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ default:
+ pr_debug("invalid callchain context: "
+ "%"PRId64"\n", (s64) ip);
+
+ /*
+ * It seems the callchain is corrupted.
+ * Discard all.
+ */
+ zfree(&p);
+ goto exit;
+ }
+ continue;
+ }
+
+ tal.filtered = 0;
+ thread__find_addr_location(al.thread, machine, cpumode,
+ MAP__FUNCTION, ip, &tal);
+
+ if (tal.sym)
+ fprintf(f, "..... %016" PRIx64 " %s\n", ip,
+ tal.sym->name);
+ else
+ fprintf(f, "..... %016" PRIx64 "\n", ip);
+ }
+
+exit:
+ fclose(f);
+
+ return p;
+}
+
+typedef int (*tracepoint_handler)(struct timechart *tchart,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ const char *backtrace);
+
+static int process_sample_event(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ struct timechart *tchart = container_of(tool, struct timechart, tool);
+
+ if (evsel->attr.sample_type & PERF_SAMPLE_TIME) {
+ if (!tchart->first_time || tchart->first_time > sample->time)
+ tchart->first_time = sample->time;
+ if (tchart->last_time < sample->time)
+ tchart->last_time = sample->time;
+ }
+
+ if (evsel->handler != NULL) {
+ tracepoint_handler f = evsel->handler;
+ return f(tchart, evsel, sample,
+ cat_backtrace(event, sample, machine));
+ }
+
+ return 0;
+}
+
+static int
+process_sample_cpu_idle(struct timechart *tchart __maybe_unused,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ const char *backtrace __maybe_unused)
+{
+ u32 state = perf_evsel__intval(evsel, sample, "state");
+ u32 cpu_id = perf_evsel__intval(evsel, sample, "cpu_id");
+
+ if (state == (u32)PWR_EVENT_EXIT)
+ c_state_end(tchart, cpu_id, sample->time);
+ else
+ c_state_start(cpu_id, sample->time, state);
+ return 0;
+}
+
+static int
+process_sample_cpu_frequency(struct timechart *tchart,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ const char *backtrace __maybe_unused)
+{
+ u32 state = perf_evsel__intval(evsel, sample, "state");
+ u32 cpu_id = perf_evsel__intval(evsel, sample, "cpu_id");
+
+ p_state_change(tchart, cpu_id, sample->time, state);
+ return 0;
+}
+
+static int
+process_sample_sched_wakeup(struct timechart *tchart,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ const char *backtrace)
+{
+ u8 flags = perf_evsel__intval(evsel, sample, "common_flags");
+ int waker = perf_evsel__intval(evsel, sample, "common_pid");
+ int wakee = perf_evsel__intval(evsel, sample, "pid");
+
+ sched_wakeup(tchart, sample->cpu, sample->time, waker, wakee, flags, backtrace);
+ return 0;
+}
+
+static int
+process_sample_sched_switch(struct timechart *tchart,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ const char *backtrace)
+{
+ int prev_pid = perf_evsel__intval(evsel, sample, "prev_pid");
+ int next_pid = perf_evsel__intval(evsel, sample, "next_pid");
+ u64 prev_state = perf_evsel__intval(evsel, sample, "prev_state");
+
+ sched_switch(tchart, sample->cpu, sample->time, prev_pid, next_pid,
+ prev_state, backtrace);
+ return 0;
+}
+
+#ifdef SUPPORT_OLD_POWER_EVENTS
+static int
+process_sample_power_start(struct timechart *tchart __maybe_unused,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ const char *backtrace __maybe_unused)
+{
+ u64 cpu_id = perf_evsel__intval(evsel, sample, "cpu_id");
+ u64 value = perf_evsel__intval(evsel, sample, "value");
+
+ c_state_start(cpu_id, sample->time, value);
+ return 0;
+}
+
+static int
+process_sample_power_end(struct timechart *tchart,
+ struct perf_evsel *evsel __maybe_unused,
+ struct perf_sample *sample,
+ const char *backtrace __maybe_unused)
+{
+ c_state_end(tchart, sample->cpu, sample->time);
+ return 0;
+}
+
+static int
+process_sample_power_frequency(struct timechart *tchart,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ const char *backtrace __maybe_unused)
+{
+ u64 cpu_id = perf_evsel__intval(evsel, sample, "cpu_id");
+ u64 value = perf_evsel__intval(evsel, sample, "value");
+
+ p_state_change(tchart, cpu_id, sample->time, value);
+ return 0;
+}
+#endif /* SUPPORT_OLD_POWER_EVENTS */
+
+/*
+ * After the last sample we need to wrap up the current C/P state
+ * and close out each CPU for these.
+ */
+static void end_sample_processing(struct timechart *tchart)
+{
+ u64 cpu;
+ struct power_event *pwr;
+
+ for (cpu = 0; cpu <= tchart->numcpus; cpu++) {
+ /* C state */
+#if 0
+ pwr = zalloc(sizeof(*pwr));
+ if (!pwr)
+ return;
+
+ pwr->state = cpus_cstate_state[cpu];
+ pwr->start_time = cpus_cstate_start_times[cpu];
+ pwr->end_time = tchart->last_time;
+ pwr->cpu = cpu;
+ pwr->type = CSTATE;
+ pwr->next = tchart->power_events;
+
+ tchart->power_events = pwr;
+#endif
+ /* P state */
+
+ pwr = zalloc(sizeof(*pwr));
+ if (!pwr)
+ return;
+
+ pwr->state = cpus_pstate_state[cpu];
+ pwr->start_time = cpus_pstate_start_times[cpu];
+ pwr->end_time = tchart->last_time;
+ pwr->cpu = cpu;
+ pwr->type = PSTATE;
+ pwr->next = tchart->power_events;
+
+ if (!pwr->start_time)
+ pwr->start_time = tchart->first_time;
+ if (!pwr->state)
+ pwr->state = tchart->min_freq;
+ tchart->power_events = pwr;
+ }
+}
+
+/*
+ * Sort the pid datastructure
+ */
+static void sort_pids(struct timechart *tchart)
+{
+ struct per_pid *new_list, *p, *cursor, *prev;
+ /* sort by ppid first, then by pid, lowest to highest */
+
+ new_list = NULL;
+
+ while (tchart->all_data) {
+ p = tchart->all_data;
+ tchart->all_data = p->next;
+ p->next = NULL;
+
+ if (new_list == NULL) {
+ new_list = p;
+ p->next = NULL;
+ continue;
+ }
+ prev = NULL;
+ cursor = new_list;
+ while (cursor) {
+ if (cursor->ppid > p->ppid ||
+ (cursor->ppid == p->ppid && cursor->pid > p->pid)) {
+ /* must insert before */
+ if (prev) {
+ p->next = prev->next;
+ prev->next = p;
+ cursor = NULL;
+ continue;
+ } else {
+ p->next = new_list;
+ new_list = p;
+ cursor = NULL;
+ continue;
+ }
+ }
+
+ prev = cursor;
+ cursor = cursor->next;
+ if (!cursor)
+ prev->next = p;
+ }
+ }
+ tchart->all_data = new_list;
+}
+
+
+static void draw_c_p_states(struct timechart *tchart)
+{
+ struct power_event *pwr;
+ pwr = tchart->power_events;
+
+ /*
+ * two pass drawing so that the P state bars are on top of the C state blocks
+ */
+ while (pwr) {
+ if (pwr->type == CSTATE)
+ svg_cstate(pwr->cpu, pwr->start_time, pwr->end_time, pwr->state);
+ pwr = pwr->next;
+ }
+
+ pwr = tchart->power_events;
+ while (pwr) {
+ if (pwr->type == PSTATE) {
+ if (!pwr->state)
+ pwr->state = tchart->min_freq;
+ svg_pstate(pwr->cpu, pwr->start_time, pwr->end_time, pwr->state);
+ }
+ pwr = pwr->next;
+ }
+}
+
+static void draw_wakeups(struct timechart *tchart)
+{
+ struct wake_event *we;
+ struct per_pid *p;
+ struct per_pidcomm *c;
+
+ we = tchart->wake_events;
+ while (we) {
+ int from = 0, to = 0;
+ char *task_from = NULL, *task_to = NULL;
+
+ /* locate the column of the waker and wakee */
+ p = tchart->all_data;
+ while (p) {
+ if (p->pid == we->waker || p->pid == we->wakee) {
+ c = p->all;
+ while (c) {
+ if (c->Y && c->start_time <= we->time && c->end_time >= we->time) {
+ if (p->pid == we->waker && !from) {
+ from = c->Y;
+ task_from = strdup(c->comm);
+ }
+ if (p->pid == we->wakee && !to) {
+ to = c->Y;
+ task_to = strdup(c->comm);
+ }
+ }
+ c = c->next;
+ }
+ c = p->all;
+ while (c) {
+ if (p->pid == we->waker && !from) {
+ from = c->Y;
+ task_from = strdup(c->comm);
+ }
+ if (p->pid == we->wakee && !to) {
+ to = c->Y;
+ task_to = strdup(c->comm);
+ }
+ c = c->next;
+ }
+ }
+ p = p->next;
+ }
+
+ if (!task_from) {
+ task_from = malloc(40);
+ sprintf(task_from, "[%i]", we->waker);
+ }
+ if (!task_to) {
+ task_to = malloc(40);
+ sprintf(task_to, "[%i]", we->wakee);
+ }
+
+ if (we->waker == -1)
+ svg_interrupt(we->time, to, we->backtrace);
+ else if (from && to && abs(from - to) == 1)
+ svg_wakeline(we->time, from, to, we->backtrace);
+ else
+ svg_partial_wakeline(we->time, from, task_from, to,
+ task_to, we->backtrace);
+ we = we->next;
+
+ free(task_from);
+ free(task_to);
+ }
+}
+
+static void draw_cpu_usage(struct timechart *tchart)
+{
+ struct per_pid *p;
+ struct per_pidcomm *c;
+ struct cpu_sample *sample;
+ p = tchart->all_data;
+ while (p) {
+ c = p->all;
+ while (c) {
+ sample = c->samples;
+ while (sample) {
+ if (sample->type == TYPE_RUNNING) {
+ svg_process(sample->cpu,
+ sample->start_time,
+ sample->end_time,
+ p->pid,
+ c->comm,
+ sample->backtrace);
+ }
+
+ sample = sample->next;
+ }
+ c = c->next;
+ }
+ p = p->next;
+ }
+}
+
+static void draw_process_bars(struct timechart *tchart)
+{
+ struct per_pid *p;
+ struct per_pidcomm *c;
+ struct cpu_sample *sample;
+ int Y = 0;
+
+ Y = 2 * tchart->numcpus + 2;
+
+ p = tchart->all_data;
+ while (p) {
+ c = p->all;
+ while (c) {
+ if (!c->display) {
+ c->Y = 0;
+ c = c->next;
+ continue;
+ }
+
+ svg_box(Y, c->start_time, c->end_time, "process");
+ sample = c->samples;
+ while (sample) {
+ if (sample->type == TYPE_RUNNING)
+ svg_running(Y, sample->cpu,
+ sample->start_time,
+ sample->end_time,
+ sample->backtrace);
+ if (sample->type == TYPE_BLOCKED)
+ svg_blocked(Y, sample->cpu,
+ sample->start_time,
+ sample->end_time,
+ sample->backtrace);
+ if (sample->type == TYPE_WAITING)
+ svg_waiting(Y, sample->cpu,
+ sample->start_time,
+ sample->end_time,
+ sample->backtrace);
+ sample = sample->next;
+ }
+
+ if (c->comm) {
+ char comm[256];
+ if (c->total_time > 5000000000) /* 5 seconds */
+ sprintf(comm, "%s:%i (%2.2fs)", c->comm, p->pid, c->total_time / 1000000000.0);
+ else
+ sprintf(comm, "%s:%i (%3.1fms)", c->comm, p->pid, c->total_time / 1000000.0);
+
+ svg_text(Y, c->start_time, comm);
+ }
+ c->Y = Y;
+ Y++;
+ c = c->next;
+ }
+ p = p->next;
+ }
+}
+
+static void add_process_filter(const char *string)
+{
+ int pid = strtoull(string, NULL, 10);
+ struct process_filter *filt = malloc(sizeof(*filt));
+
+ if (!filt)
+ return;
+
+ filt->name = strdup(string);
+ filt->pid = pid;
+ filt->next = process_filter;
+
+ process_filter = filt;
+}
+
+static int passes_filter(struct per_pid *p, struct per_pidcomm *c)
+{
+ struct process_filter *filt;
+ if (!process_filter)
+ return 1;
+
+ filt = process_filter;
+ while (filt) {
+ if (filt->pid && p->pid == filt->pid)
+ return 1;
+ if (strcmp(filt->name, c->comm) == 0)
+ return 1;
+ filt = filt->next;
+ }
+ return 0;
+}
+
+static int determine_display_tasks_filtered(struct timechart *tchart)
+{
+ struct per_pid *p;
+ struct per_pidcomm *c;
+ int count = 0;
+
+ p = tchart->all_data;
+ while (p) {
+ p->display = 0;
+ if (p->start_time == 1)
+ p->start_time = tchart->first_time;
+
+ /* no exit marker, task kept running to the end */
+ if (p->end_time == 0)
+ p->end_time = tchart->last_time;
+
+ c = p->all;
+
+ while (c) {
+ c->display = 0;
+
+ if (c->start_time == 1)
+ c->start_time = tchart->first_time;
+
+ if (passes_filter(p, c)) {
+ c->display = 1;
+ p->display = 1;
+ count++;
+ }
+
+ if (c->end_time == 0)
+ c->end_time = tchart->last_time;
+
+ c = c->next;
+ }
+ p = p->next;
+ }
+ return count;
+}
+
+static int determine_display_tasks(struct timechart *tchart, u64 threshold)
+{
+ struct per_pid *p;
+ struct per_pidcomm *c;
+ int count = 0;
+
+ if (process_filter)
+ return determine_display_tasks_filtered(tchart);
+
+ p = tchart->all_data;
+ while (p) {
+ p->display = 0;
+ if (p->start_time == 1)
+ p->start_time = tchart->first_time;
+
+ /* no exit marker, task kept running to the end */
+ if (p->end_time == 0)
+ p->end_time = tchart->last_time;
+ if (p->total_time >= threshold)
+ p->display = 1;
+
+ c = p->all;
+
+ while (c) {
+ c->display = 0;
+
+ if (c->start_time == 1)
+ c->start_time = tchart->first_time;
+
+ if (c->total_time >= threshold) {
+ c->display = 1;
+ count++;
+ }
+
+ if (c->end_time == 0)
+ c->end_time = tchart->last_time;
+
+ c = c->next;
+ }
+ p = p->next;
+ }
+ return count;
+}
+
+
+
+#define TIME_THRESH 10000000
+
+static void write_svg_file(struct timechart *tchart, const char *filename)
+{
+ u64 i;
+ int count;
+ int thresh = TIME_THRESH;
+
+ if (tchart->power_only)
+ tchart->proc_num = 0;
+
+ /* We'd like to show at least proc_num tasks;
+ * be less picky if we have fewer */
+ do {
+ count = determine_display_tasks(tchart, thresh);
+ thresh /= 10;
+ } while (!process_filter && thresh && count < tchart->proc_num);
+
+ if (!tchart->proc_num)
+ count = 0;
+
+ open_svg(filename, tchart->numcpus, count, tchart->first_time, tchart->last_time);
+
+ svg_time_grid();
+ svg_legenda();
+
+ for (i = 0; i < tchart->numcpus; i++)
+ svg_cpu_box(i, tchart->max_freq, tchart->turbo_frequency);
+
+ draw_cpu_usage(tchart);
+ if (tchart->proc_num)
+ draw_process_bars(tchart);
+ if (!tchart->tasks_only)
+ draw_c_p_states(tchart);
+ if (tchart->proc_num)
+ draw_wakeups(tchart);
+
+ svg_close();
+}
+
+static int process_header(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph,
+ int feat,
+ int fd __maybe_unused,
+ void *data)
+{
+ struct timechart *tchart = data;
+
+ switch (feat) {
+ case HEADER_NRCPUS:
+ tchart->numcpus = ph->env.nr_cpus_avail;
+ break;
+
+ case HEADER_CPU_TOPOLOGY:
+ if (!tchart->topology)
+ break;
+
+ if (svg_build_topology_map(ph->env.sibling_cores,
+ ph->env.nr_sibling_cores,
+ ph->env.sibling_threads,
+ ph->env.nr_sibling_threads))
+ fprintf(stderr, "problem building topology\n");
+ break;
+
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static int __cmd_timechart(struct timechart *tchart, const char *output_name)
+{
+ const struct perf_evsel_str_handler power_tracepoints[] = {
+ { "power:cpu_idle", process_sample_cpu_idle },
+ { "power:cpu_frequency", process_sample_cpu_frequency },
+ { "sched:sched_wakeup", process_sample_sched_wakeup },
+ { "sched:sched_switch", process_sample_sched_switch },
+#ifdef SUPPORT_OLD_POWER_EVENTS
+ { "power:power_start", process_sample_power_start },
+ { "power:power_end", process_sample_power_end },
+ { "power:power_frequency", process_sample_power_frequency },
+#endif
+ };
+ struct perf_data_file file = {
+ .path = input_name,
+ .mode = PERF_DATA_MODE_READ,
+ };
+
+ struct perf_session *session = perf_session__new(&file, false,
+ &tchart->tool);
+ int ret = -EINVAL;
+
+ if (session == NULL)
+ return -ENOMEM;
+
+ (void)perf_header__process_sections(&session->header,
+ perf_data_file__fd(session->file),
+ tchart,
+ process_header);
+
+ if (!perf_session__has_traces(session, "timechart record"))
+ goto out_delete;
+
+ if (perf_session__set_tracepoints_handlers(session,
+ power_tracepoints)) {
+ pr_err("Initializing session tracepoint handlers failed\n");
+ goto out_delete;
+ }
+
+ ret = perf_session__process_events(session, &tchart->tool);
+ if (ret)
+ goto out_delete;
+
+ end_sample_processing(tchart);
+
+ sort_pids(tchart);
+
+ write_svg_file(tchart, output_name);
+
+ pr_info("Written %2.1f seconds of trace to %s.\n",
+ (tchart->last_time - tchart->first_time) / 1000000000.0, output_name);
+out_delete:
+ perf_session__delete(session);
+ return ret;
+}
+
+static int timechart__record(struct timechart *tchart, int argc, const char **argv)
+{
+ unsigned int rec_argc, i, j;
+ const char **rec_argv;
+ const char **p;
+ unsigned int record_elems;
+
+ const char * const common_args[] = {
+ "record", "-a", "-R", "-c", "1",
+ };
+ unsigned int common_args_nr = ARRAY_SIZE(common_args);
+
+ const char * const backtrace_args[] = {
+ "-g",
+ };
+ unsigned int backtrace_args_no = ARRAY_SIZE(backtrace_args);
+
+ const char * const power_args[] = {
+ "-e", "power:cpu_frequency",
+ "-e", "power:cpu_idle",
+ };
+ unsigned int power_args_nr = ARRAY_SIZE(power_args);
+
+ const char * const old_power_args[] = {
+#ifdef SUPPORT_OLD_POWER_EVENTS
+ "-e", "power:power_start",
+ "-e", "power:power_end",
+ "-e", "power:power_frequency",
+#endif
+ };
+ unsigned int old_power_args_nr = ARRAY_SIZE(old_power_args);
+
+ const char * const tasks_args[] = {
+ "-e", "sched:sched_wakeup",
+ "-e", "sched:sched_switch",
+ };
+ unsigned int tasks_args_nr = ARRAY_SIZE(tasks_args);
+
+#ifdef SUPPORT_OLD_POWER_EVENTS
+ if (!is_valid_tracepoint("power:cpu_idle") &&
+ is_valid_tracepoint("power:power_start")) {
+ use_old_power_events = 1;
+ power_args_nr = 0;
+ } else {
+ old_power_args_nr = 0;
+ }
+#endif
+
+ if (tchart->power_only)
+ tasks_args_nr = 0;
+
+ if (tchart->tasks_only) {
+ power_args_nr = 0;
+ old_power_args_nr = 0;
+ }
+
+ if (!tchart->with_backtrace)
+ backtrace_args_no = 0;
+
+ record_elems = common_args_nr + tasks_args_nr +
+ power_args_nr + old_power_args_nr + backtrace_args_no;
+
+ rec_argc = record_elems + argc;
+ rec_argv = calloc(rec_argc + 1, sizeof(char *));
+
+ if (rec_argv == NULL)
+ return -ENOMEM;
+
+ p = rec_argv;
+ for (i = 0; i < common_args_nr; i++)
+ *p++ = strdup(common_args[i]);
+
+ for (i = 0; i < backtrace_args_no; i++)
+ *p++ = strdup(backtrace_args[i]);
+
+ for (i = 0; i < tasks_args_nr; i++)
+ *p++ = strdup(tasks_args[i]);
+
+ for (i = 0; i < power_args_nr; i++)
+ *p++ = strdup(power_args[i]);
+
+ for (i = 0; i < old_power_args_nr; i++)
+ *p++ = strdup(old_power_args[i]);
+
+ for (j = 0; j < (unsigned int)argc; j++)
+ *p++ = argv[j];
+
+ return cmd_record(rec_argc, rec_argv, NULL);
+}
+
+static int
+parse_process(const struct option *opt __maybe_unused, const char *arg,
+ int __maybe_unused unset)
+{
+ if (arg)
+ add_process_filter(arg);
+ return 0;
+}
+
+static int
+parse_highlight(const struct option *opt __maybe_unused, const char *arg,
+ int __maybe_unused unset)
+{
+ unsigned long duration = strtoul(arg, NULL, 0);
+
+ if (svg_highlight || svg_highlight_name)
+ return -1;
+
+ if (duration)
+ svg_highlight = duration;
+ else
+ svg_highlight_name = strdup(arg);
+
+ return 0;
+}
+
+int cmd_timechart(int argc, const char **argv,
+ const char *prefix __maybe_unused)
+{
+ struct timechart tchart = {
+ .tool = {
+ .comm = process_comm_event,
+ .fork = process_fork_event,
+ .exit = process_exit_event,
+ .sample = process_sample_event,
+ .ordered_samples = true,
+ },
+ .proc_num = 15,
+ };
+ const char *output_name = "output.svg";
+ const struct option timechart_options[] = {
+ OPT_STRING('i', "input", &input_name, "file", "input file name"),
+ OPT_STRING('o', "output", &output_name, "file", "output file name"),
+ OPT_INTEGER('w', "width", &svg_page_width, "page width"),
+ OPT_CALLBACK(0, "highlight", NULL, "duration or task name",
+ "highlight tasks. Pass duration in ns or process name.",
+ parse_highlight),
+ OPT_BOOLEAN('P', "power-only", &tchart.power_only, "output power data only"),
+ OPT_BOOLEAN('T', "tasks-only", &tchart.tasks_only,
+ "output processes data only"),
+ OPT_CALLBACK('p', "process", NULL, "process",
+ "process selector. Pass a pid or process name.",
+ parse_process),
+ OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory",
+ "Look for files with symbols relative to this directory"),
+ OPT_INTEGER('n', "proc-num", &tchart.proc_num,
+ "min. number of tasks to print"),
+ OPT_BOOLEAN('t', "topology", &tchart.topology,
+ "sort CPUs according to topology"),
+ OPT_END()
+ };
+ const char * const timechart_usage[] = {
+ "perf timechart [<options>] {record}",
+ NULL
+ };
+
+ const struct option record_options[] = {
+ OPT_BOOLEAN('P', "power-only", &tchart.power_only, "output power data only"),
+ OPT_BOOLEAN('T', "tasks-only", &tchart.tasks_only,
+ "output processes data only"),
+ OPT_BOOLEAN('g', "callchain", &tchart.with_backtrace, "record callchain"),
+ OPT_END()
+ };
+ const char * const record_usage[] = {
+ "perf timechart record [<options>]",
+ NULL
+ };
+ argc = parse_options(argc, argv, timechart_options, timechart_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (tchart.power_only && tchart.tasks_only) {
+ pr_err("-P and -T options cannot be used at the same time.\n");
+ return -1;
+ }
+
+ symbol__init();
+
+ if (argc && !strncmp(argv[0], "rec", 3)) {
+ argc = parse_options(argc, argv, record_options, record_usage,
+ PARSE_OPT_STOP_AT_NON_OPTION);
+
+ if (tchart.power_only && tchart.tasks_only) {
+ pr_err("-P and -T options cannot be used at the same time.\n");
+ return -1;
+ }
+
+ return timechart__record(&tchart, argc, argv);
+ } else if (argc)
+ usage_with_options(timechart_usage, timechart_options);
+
+ setup_pager();
+
+ return __cmd_timechart(&tchart, output_name);
+}
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c
new file mode 100644
index 00000000000..377971dc89a
--- /dev/null
+++ b/tools/perf/builtin-top.c
@@ -0,0 +1,1241 @@
+/*
+ * builtin-top.c
+ *
+ * Builtin top command: Display a continuously updated profile of
+ * any workload, CPU or specific PID.
+ *
+ * Copyright (C) 2008, Red Hat Inc, Ingo Molnar <mingo@redhat.com>
+ * 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
+ *
+ * Improvements and fixes by:
+ *
+ * Arjan van de Ven <arjan@linux.intel.com>
+ * Yanmin Zhang <yanmin.zhang@intel.com>
+ * Wu Fengguang <fengguang.wu@intel.com>
+ * Mike Galbraith <efault@gmx.de>
+ * Paul Mackerras <paulus@samba.org>
+ *
+ * Released under the GPL v2. (and only v2, not any later version)
+ */
+#include "builtin.h"
+
+#include "perf.h"
+
+#include "util/annotate.h"
+#include "util/cache.h"
+#include "util/color.h"
+#include "util/evlist.h"
+#include "util/evsel.h"
+#include "util/machine.h"
+#include "util/session.h"
+#include "util/symbol.h"
+#include "util/thread.h"
+#include "util/thread_map.h"
+#include "util/top.h"
+#include "util/util.h"
+#include <linux/rbtree.h>
+#include "util/parse-options.h"
+#include "util/parse-events.h"
+#include "util/cpumap.h"
+#include "util/xyarray.h"
+#include "util/sort.h"
+#include "util/intlist.h"
+#include "arch/common.h"
+
+#include "util/debug.h"
+
+#include <assert.h>
+#include <elf.h>
+#include <fcntl.h>
+
+#include <stdio.h>
+#include <termios.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include <errno.h>
+#include <time.h>
+#include <sched.h>
+
+#include <sys/syscall.h>
+#include <sys/ioctl.h>
+#include <sys/poll.h>
+#include <sys/prctl.h>
+#include <sys/wait.h>
+#include <sys/uio.h>
+#include <sys/utsname.h>
+#include <sys/mman.h>
+
+#include <linux/unistd.h>
+#include <linux/types.h>
+
+static volatile int done;
+
+#define HEADER_LINE_NR 5
+
+static void perf_top__update_print_entries(struct perf_top *top)
+{
+ top->print_entries = top->winsize.ws_row - HEADER_LINE_NR;
+}
+
+static void perf_top__sig_winch(int sig __maybe_unused,
+ siginfo_t *info __maybe_unused, void *arg)
+{
+ struct perf_top *top = arg;
+
+ get_term_dimensions(&top->winsize);
+ perf_top__update_print_entries(top);
+}
+
+static int perf_top__parse_source(struct perf_top *top, struct hist_entry *he)
+{
+ struct symbol *sym;
+ struct annotation *notes;
+ struct map *map;
+ int err = -1;
+
+ if (!he || !he->ms.sym)
+ return -1;
+
+ sym = he->ms.sym;
+ map = he->ms.map;
+
+ /*
+ * We can't annotate with just /proc/kallsyms
+ */
+ if (map->dso->symtab_type == DSO_BINARY_TYPE__KALLSYMS &&
+ !dso__is_kcore(map->dso)) {
+ pr_err("Can't annotate %s: No vmlinux file was found in the "
+ "path\n", sym->name);
+ sleep(1);
+ return -1;
+ }
+
+ notes = symbol__annotation(sym);
+ if (notes->src != NULL) {
+ pthread_mutex_lock(&notes->lock);
+ goto out_assign;
+ }
+
+ pthread_mutex_lock(&notes->lock);
+
+ if (symbol__alloc_hist(sym) < 0) {
+ pthread_mutex_unlock(&notes->lock);
+ pr_err("Not enough memory for annotating '%s' symbol!\n",
+ sym->name);
+ sleep(1);
+ return err;
+ }
+
+ err = symbol__annotate(sym, map, 0);
+ if (err == 0) {
+out_assign:
+ top->sym_filter_entry = he;
+ }
+
+ pthread_mutex_unlock(&notes->lock);
+ return err;
+}
+
+static void __zero_source_counters(struct hist_entry *he)
+{
+ struct symbol *sym = he->ms.sym;
+ symbol__annotate_zero_histograms(sym);
+}
+
+static void ui__warn_map_erange(struct map *map, struct symbol *sym, u64 ip)
+{
+ struct utsname uts;
+ int err = uname(&uts);
+
+ ui__warning("Out of bounds address found:\n\n"
+ "Addr: %" PRIx64 "\n"
+ "DSO: %s %c\n"
+ "Map: %" PRIx64 "-%" PRIx64 "\n"
+ "Symbol: %" PRIx64 "-%" PRIx64 " %c %s\n"
+ "Arch: %s\n"
+ "Kernel: %s\n"
+ "Tools: %s\n\n"
+ "Not all samples will be on the annotation output.\n\n"
+ "Please report to linux-kernel@vger.kernel.org\n",
+ ip, map->dso->long_name, dso__symtab_origin(map->dso),
+ map->start, map->end, sym->start, sym->end,
+ sym->binding == STB_GLOBAL ? 'g' :
+ sym->binding == STB_LOCAL ? 'l' : 'w', sym->name,
+ err ? "[unknown]" : uts.machine,
+ err ? "[unknown]" : uts.release, perf_version_string);
+ if (use_browser <= 0)
+ sleep(5);
+
+ map->erange_warned = true;
+}
+
+static void perf_top__record_precise_ip(struct perf_top *top,
+ struct hist_entry *he,
+ int counter, u64 ip)
+{
+ struct annotation *notes;
+ struct symbol *sym;
+ int err = 0;
+
+ if (he == NULL || he->ms.sym == NULL ||
+ ((top->sym_filter_entry == NULL ||
+ top->sym_filter_entry->ms.sym != he->ms.sym) && use_browser != 1))
+ return;
+
+ sym = he->ms.sym;
+ notes = symbol__annotation(sym);
+
+ if (pthread_mutex_trylock(&notes->lock))
+ return;
+
+ ip = he->ms.map->map_ip(he->ms.map, ip);
+
+ if (ui__has_annotation())
+ err = hist_entry__inc_addr_samples(he, counter, ip);
+
+ pthread_mutex_unlock(&notes->lock);
+
+ /*
+ * This function is now called with he->hists->lock held.
+ * Release it before going to sleep.
+ */
+ pthread_mutex_unlock(&he->hists->lock);
+
+ if (err == -ERANGE && !he->ms.map->erange_warned)
+ ui__warn_map_erange(he->ms.map, sym, ip);
+ else if (err == -ENOMEM) {
+ pr_err("Not enough memory for annotating '%s' symbol!\n",
+ sym->name);
+ sleep(1);
+ }
+
+ pthread_mutex_lock(&he->hists->lock);
+}
+
+static void perf_top__show_details(struct perf_top *top)
+{
+ struct hist_entry *he = top->sym_filter_entry;
+ struct annotation *notes;
+ struct symbol *symbol;
+ int more;
+
+ if (!he)
+ return;
+
+ symbol = he->ms.sym;
+ notes = symbol__annotation(symbol);
+
+ pthread_mutex_lock(&notes->lock);
+
+ if (notes->src == NULL)
+ goto out_unlock;
+
+ printf("Showing %s for %s\n", perf_evsel__name(top->sym_evsel), symbol->name);
+ printf(" Events Pcnt (>=%d%%)\n", top->sym_pcnt_filter);
+
+ more = symbol__annotate_printf(symbol, he->ms.map, top->sym_evsel,
+ 0, top->sym_pcnt_filter, top->print_entries, 4);
+ if (top->zero)
+ symbol__annotate_zero_histogram(symbol, top->sym_evsel->idx);
+ else
+ symbol__annotate_decay_histogram(symbol, top->sym_evsel->idx);
+ if (more != 0)
+ printf("%d lines not displayed, maybe increase display entries [e]\n", more);
+out_unlock:
+ pthread_mutex_unlock(&notes->lock);
+}
+
+static void perf_top__print_sym_table(struct perf_top *top)
+{
+ char bf[160];
+ int printed = 0;
+ const int win_width = top->winsize.ws_col - 1;
+
+ puts(CONSOLE_CLEAR);
+
+ perf_top__header_snprintf(top, bf, sizeof(bf));
+ printf("%s\n", bf);
+
+ perf_top__reset_sample_counters(top);
+
+ printf("%-*.*s\n", win_width, win_width, graph_dotted_line);
+
+ if (top->sym_evsel->hists.stats.nr_lost_warned !=
+ top->sym_evsel->hists.stats.nr_events[PERF_RECORD_LOST]) {
+ top->sym_evsel->hists.stats.nr_lost_warned =
+ top->sym_evsel->hists.stats.nr_events[PERF_RECORD_LOST];
+ color_fprintf(stdout, PERF_COLOR_RED,
+ "WARNING: LOST %d chunks, Check IO/CPU overload",
+ top->sym_evsel->hists.stats.nr_lost_warned);
+ ++printed;
+ }
+
+ if (top->sym_filter_entry) {
+ perf_top__show_details(top);
+ return;
+ }
+
+ hists__collapse_resort(&top->sym_evsel->hists, NULL);
+ hists__output_resort(&top->sym_evsel->hists);
+ hists__decay_entries(&top->sym_evsel->hists,
+ top->hide_user_symbols,
+ top->hide_kernel_symbols);
+ hists__output_recalc_col_len(&top->sym_evsel->hists,
+ top->print_entries - printed);
+ putchar('\n');
+ hists__fprintf(&top->sym_evsel->hists, false,
+ top->print_entries - printed, win_width,
+ top->min_percent, stdout);
+}
+
+static void prompt_integer(int *target, const char *msg)
+{
+ char *buf = malloc(0), *p;
+ size_t dummy = 0;
+ int tmp;
+
+ fprintf(stdout, "\n%s: ", msg);
+ if (getline(&buf, &dummy, stdin) < 0)
+ return;
+
+ p = strchr(buf, '\n');
+ if (p)
+ *p = 0;
+
+ p = buf;
+ while(*p) {
+ if (!isdigit(*p))
+ goto out_free;
+ p++;
+ }
+ tmp = strtoul(buf, NULL, 10);
+ *target = tmp;
+out_free:
+ free(buf);
+}
+
+static void prompt_percent(int *target, const char *msg)
+{
+ int tmp = 0;
+
+ prompt_integer(&tmp, msg);
+ if (tmp >= 0 && tmp <= 100)
+ *target = tmp;
+}
+
+static void perf_top__prompt_symbol(struct perf_top *top, const char *msg)
+{
+ char *buf = malloc(0), *p;
+ struct hist_entry *syme = top->sym_filter_entry, *n, *found = NULL;
+ struct rb_node *next;
+ size_t dummy = 0;
+
+ /* zero counters of active symbol */
+ if (syme) {
+ __zero_source_counters(syme);
+ top->sym_filter_entry = NULL;
+ }
+
+ fprintf(stdout, "\n%s: ", msg);
+ if (getline(&buf, &dummy, stdin) < 0)
+ goto out_free;
+
+ p = strchr(buf, '\n');
+ if (p)
+ *p = 0;
+
+ next = rb_first(&top->sym_evsel->hists.entries);
+ while (next) {
+ n = rb_entry(next, struct hist_entry, rb_node);
+ if (n->ms.sym && !strcmp(buf, n->ms.sym->name)) {
+ found = n;
+ break;
+ }
+ next = rb_next(&n->rb_node);
+ }
+
+ if (!found) {
+ fprintf(stderr, "Sorry, %s is not active.\n", buf);
+ sleep(1);
+ } else
+ perf_top__parse_source(top, found);
+
+out_free:
+ free(buf);
+}
+
+static void perf_top__print_mapped_keys(struct perf_top *top)
+{
+ char *name = NULL;
+
+ if (top->sym_filter_entry) {
+ struct symbol *sym = top->sym_filter_entry->ms.sym;
+ name = sym->name;
+ }
+
+ fprintf(stdout, "\nMapped keys:\n");
+ fprintf(stdout, "\t[d] display refresh delay. \t(%d)\n", top->delay_secs);
+ fprintf(stdout, "\t[e] display entries (lines). \t(%d)\n", top->print_entries);
+
+ if (top->evlist->nr_entries > 1)
+ fprintf(stdout, "\t[E] active event counter. \t(%s)\n", perf_evsel__name(top->sym_evsel));
+
+ fprintf(stdout, "\t[f] profile display filter (count). \t(%d)\n", top->count_filter);
+
+ fprintf(stdout, "\t[F] annotate display filter (percent). \t(%d%%)\n", top->sym_pcnt_filter);
+ fprintf(stdout, "\t[s] annotate symbol. \t(%s)\n", name?: "NULL");
+ fprintf(stdout, "\t[S] stop annotation.\n");
+
+ fprintf(stdout,
+ "\t[K] hide kernel_symbols symbols. \t(%s)\n",
+ top->hide_kernel_symbols ? "yes" : "no");
+ fprintf(stdout,
+ "\t[U] hide user symbols. \t(%s)\n",
+ top->hide_user_symbols ? "yes" : "no");
+ fprintf(stdout, "\t[z] toggle sample zeroing. \t(%d)\n", top->zero ? 1 : 0);
+ fprintf(stdout, "\t[qQ] quit.\n");
+}
+
+static int perf_top__key_mapped(struct perf_top *top, int c)
+{
+ switch (c) {
+ case 'd':
+ case 'e':
+ case 'f':
+ case 'z':
+ case 'q':
+ case 'Q':
+ case 'K':
+ case 'U':
+ case 'F':
+ case 's':
+ case 'S':
+ return 1;
+ case 'E':
+ return top->evlist->nr_entries > 1 ? 1 : 0;
+ default:
+ break;
+ }
+
+ return 0;
+}
+
+static bool perf_top__handle_keypress(struct perf_top *top, int c)
+{
+ bool ret = true;
+
+ if (!perf_top__key_mapped(top, c)) {
+ struct pollfd stdin_poll = { .fd = 0, .events = POLLIN };
+ struct termios tc, save;
+
+ perf_top__print_mapped_keys(top);
+ fprintf(stdout, "\nEnter selection, or unmapped key to continue: ");
+ fflush(stdout);
+
+ tcgetattr(0, &save);
+ tc = save;
+ tc.c_lflag &= ~(ICANON | ECHO);
+ tc.c_cc[VMIN] = 0;
+ tc.c_cc[VTIME] = 0;
+ tcsetattr(0, TCSANOW, &tc);
+
+ poll(&stdin_poll, 1, -1);
+ c = getc(stdin);
+
+ tcsetattr(0, TCSAFLUSH, &save);
+ if (!perf_top__key_mapped(top, c))
+ return ret;
+ }
+
+ switch (c) {
+ case 'd':
+ prompt_integer(&top->delay_secs, "Enter display delay");
+ if (top->delay_secs < 1)
+ top->delay_secs = 1;
+ break;
+ case 'e':
+ prompt_integer(&top->print_entries, "Enter display entries (lines)");
+ if (top->print_entries == 0) {
+ struct sigaction act = {
+ .sa_sigaction = perf_top__sig_winch,
+ .sa_flags = SA_SIGINFO,
+ };
+ perf_top__sig_winch(SIGWINCH, NULL, top);
+ sigaction(SIGWINCH, &act, NULL);
+ } else {
+ signal(SIGWINCH, SIG_DFL);
+ }
+ break;
+ case 'E':
+ if (top->evlist->nr_entries > 1) {
+ /* Select 0 as the default event: */
+ int counter = 0;
+
+ fprintf(stderr, "\nAvailable events:");
+
+ evlist__for_each(top->evlist, top->sym_evsel)
+ fprintf(stderr, "\n\t%d %s", top->sym_evsel->idx, perf_evsel__name(top->sym_evsel));
+
+ prompt_integer(&counter, "Enter details event counter");
+
+ if (counter >= top->evlist->nr_entries) {
+ top->sym_evsel = perf_evlist__first(top->evlist);
+ fprintf(stderr, "Sorry, no such event, using %s.\n", perf_evsel__name(top->sym_evsel));
+ sleep(1);
+ break;
+ }
+ evlist__for_each(top->evlist, top->sym_evsel)
+ if (top->sym_evsel->idx == counter)
+ break;
+ } else
+ top->sym_evsel = perf_evlist__first(top->evlist);
+ break;
+ case 'f':
+ prompt_integer(&top->count_filter, "Enter display event count filter");
+ break;
+ case 'F':
+ prompt_percent(&top->sym_pcnt_filter,
+ "Enter details display event filter (percent)");
+ break;
+ case 'K':
+ top->hide_kernel_symbols = !top->hide_kernel_symbols;
+ break;
+ case 'q':
+ case 'Q':
+ printf("exiting.\n");
+ if (top->dump_symtab)
+ perf_session__fprintf_dsos(top->session, stderr);
+ ret = false;
+ break;
+ case 's':
+ perf_top__prompt_symbol(top, "Enter details symbol");
+ break;
+ case 'S':
+ if (!top->sym_filter_entry)
+ break;
+ else {
+ struct hist_entry *syme = top->sym_filter_entry;
+
+ top->sym_filter_entry = NULL;
+ __zero_source_counters(syme);
+ }
+ break;
+ case 'U':
+ top->hide_user_symbols = !top->hide_user_symbols;
+ break;
+ case 'z':
+ top->zero = !top->zero;
+ break;
+ default:
+ break;
+ }
+
+ return ret;
+}
+
+static void perf_top__sort_new_samples(void *arg)
+{
+ struct perf_top *t = arg;
+ perf_top__reset_sample_counters(t);
+
+ if (t->evlist->selected != NULL)
+ t->sym_evsel = t->evlist->selected;
+
+ hists__collapse_resort(&t->sym_evsel->hists, NULL);
+ hists__output_resort(&t->sym_evsel->hists);
+ hists__decay_entries(&t->sym_evsel->hists,
+ t->hide_user_symbols,
+ t->hide_kernel_symbols);
+}
+
+static void *display_thread_tui(void *arg)
+{
+ struct perf_evsel *pos;
+ struct perf_top *top = arg;
+ const char *help = "For a higher level overview, try: perf top --sort comm,dso";
+ struct hist_browser_timer hbt = {
+ .timer = perf_top__sort_new_samples,
+ .arg = top,
+ .refresh = top->delay_secs,
+ };
+
+ perf_top__sort_new_samples(top);
+
+ /*
+ * Initialize the uid_filter_str, in the future the TUI will allow
+ * Zooming in/out UIDs. For now juse use whatever the user passed
+ * via --uid.
+ */
+ evlist__for_each(top->evlist, pos)
+ pos->hists.uid_filter_str = top->record_opts.target.uid_str;
+
+ perf_evlist__tui_browse_hists(top->evlist, help, &hbt, top->min_percent,
+ &top->session->header.env);
+
+ done = 1;
+ return NULL;
+}
+
+static void *display_thread(void *arg)
+{
+ struct pollfd stdin_poll = { .fd = 0, .events = POLLIN };
+ struct termios tc, save;
+ struct perf_top *top = arg;
+ int delay_msecs, c;
+
+ tcgetattr(0, &save);
+ tc = save;
+ tc.c_lflag &= ~(ICANON | ECHO);
+ tc.c_cc[VMIN] = 0;
+ tc.c_cc[VTIME] = 0;
+
+ pthread__unblock_sigwinch();
+repeat:
+ delay_msecs = top->delay_secs * 1000;
+ tcsetattr(0, TCSANOW, &tc);
+ /* trash return*/
+ getc(stdin);
+
+ while (!done) {
+ perf_top__print_sym_table(top);
+ /*
+ * Either timeout expired or we got an EINTR due to SIGWINCH,
+ * refresh screen in both cases.
+ */
+ switch (poll(&stdin_poll, 1, delay_msecs)) {
+ case 0:
+ continue;
+ case -1:
+ if (errno == EINTR)
+ continue;
+ /* Fall trhu */
+ default:
+ c = getc(stdin);
+ tcsetattr(0, TCSAFLUSH, &save);
+
+ if (perf_top__handle_keypress(top, c))
+ goto repeat;
+ done = 1;
+ }
+ }
+
+ return NULL;
+}
+
+static int symbol_filter(struct map *map __maybe_unused, struct symbol *sym)
+{
+ const char *name = sym->name;
+
+ /*
+ * ppc64 uses function descriptors and appends a '.' to the
+ * start of every instruction address. Remove it.
+ */
+ if (name[0] == '.')
+ name++;
+
+ if (!strcmp(name, "_text") ||
+ !strcmp(name, "_etext") ||
+ !strcmp(name, "_sinittext") ||
+ !strncmp("init_module", name, 11) ||
+ !strncmp("cleanup_module", name, 14) ||
+ strstr(name, "_text_start") ||
+ strstr(name, "_text_end"))
+ return 1;
+
+ if (symbol__is_idle(sym))
+ sym->ignore = true;
+
+ return 0;
+}
+
+static int hist_iter__top_callback(struct hist_entry_iter *iter,
+ struct addr_location *al, bool single,
+ void *arg)
+{
+ struct perf_top *top = arg;
+ struct hist_entry *he = iter->he;
+ struct perf_evsel *evsel = iter->evsel;
+
+ if (sort__has_sym && single) {
+ u64 ip = al->addr;
+
+ if (al->map)
+ ip = al->map->unmap_ip(al->map, ip);
+
+ perf_top__record_precise_ip(top, he, evsel->idx, ip);
+ }
+
+ return 0;
+}
+
+static void perf_event__process_sample(struct perf_tool *tool,
+ const union perf_event *event,
+ struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct perf_top *top = container_of(tool, struct perf_top, tool);
+ struct addr_location al;
+ int err;
+
+ if (!machine && perf_guest) {
+ static struct intlist *seen;
+
+ if (!seen)
+ seen = intlist__new(NULL);
+
+ if (!intlist__has_entry(seen, sample->pid)) {
+ pr_err("Can't find guest [%d]'s kernel information\n",
+ sample->pid);
+ intlist__add(seen, sample->pid);
+ }
+ return;
+ }
+
+ if (!machine) {
+ pr_err("%u unprocessable samples recorded.\r",
+ top->session->stats.nr_unprocessable_samples++);
+ return;
+ }
+
+ if (event->header.misc & PERF_RECORD_MISC_EXACT_IP)
+ top->exact_samples++;
+
+ if (perf_event__preprocess_sample(event, machine, &al, sample) < 0)
+ return;
+
+ if (!top->kptr_restrict_warned &&
+ symbol_conf.kptr_restrict &&
+ al.cpumode == PERF_RECORD_MISC_KERNEL) {
+ ui__warning(
+"Kernel address maps (/proc/{kallsyms,modules}) are restricted.\n\n"
+"Check /proc/sys/kernel/kptr_restrict.\n\n"
+"Kernel%s samples will not be resolved.\n",
+ !RB_EMPTY_ROOT(&al.map->dso->symbols[MAP__FUNCTION]) ?
+ " modules" : "");
+ if (use_browser <= 0)
+ sleep(5);
+ top->kptr_restrict_warned = true;
+ }
+
+ if (al.sym == NULL) {
+ const char *msg = "Kernel samples will not be resolved.\n";
+ /*
+ * As we do lazy loading of symtabs we only will know if the
+ * specified vmlinux file is invalid when we actually have a
+ * hit in kernel space and then try to load it. So if we get
+ * here and there are _no_ symbols in the DSO backing the
+ * kernel map, bail out.
+ *
+ * We may never get here, for instance, if we use -K/
+ * --hide-kernel-symbols, even if the user specifies an
+ * invalid --vmlinux ;-)
+ */
+ if (!top->kptr_restrict_warned && !top->vmlinux_warned &&
+ al.map == machine->vmlinux_maps[MAP__FUNCTION] &&
+ RB_EMPTY_ROOT(&al.map->dso->symbols[MAP__FUNCTION])) {
+ if (symbol_conf.vmlinux_name) {
+ ui__warning("The %s file can't be used.\n%s",
+ symbol_conf.vmlinux_name, msg);
+ } else {
+ ui__warning("A vmlinux file was not found.\n%s",
+ msg);
+ }
+
+ if (use_browser <= 0)
+ sleep(5);
+ top->vmlinux_warned = true;
+ }
+ }
+
+ if (al.sym == NULL || !al.sym->ignore) {
+ struct hist_entry_iter iter = {
+ .add_entry_cb = hist_iter__top_callback,
+ };
+
+ if (symbol_conf.cumulate_callchain)
+ iter.ops = &hist_iter_cumulative;
+ else
+ iter.ops = &hist_iter_normal;
+
+ pthread_mutex_lock(&evsel->hists.lock);
+
+ err = hist_entry_iter__add(&iter, &al, evsel, sample,
+ top->max_stack, top);
+ if (err < 0)
+ pr_err("Problem incrementing symbol period, skipping event\n");
+
+ pthread_mutex_unlock(&evsel->hists.lock);
+ }
+
+ return;
+}
+
+static void perf_top__mmap_read_idx(struct perf_top *top, int idx)
+{
+ struct perf_sample sample;
+ struct perf_evsel *evsel;
+ struct perf_session *session = top->session;
+ union perf_event *event;
+ struct machine *machine;
+ u8 origin;
+ int ret;
+
+ while ((event = perf_evlist__mmap_read(top->evlist, idx)) != NULL) {
+ ret = perf_evlist__parse_sample(top->evlist, event, &sample);
+ if (ret) {
+ pr_err("Can't parse sample, err = %d\n", ret);
+ goto next_event;
+ }
+
+ evsel = perf_evlist__id2evsel(session->evlist, sample.id);
+ assert(evsel != NULL);
+
+ origin = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ if (event->header.type == PERF_RECORD_SAMPLE)
+ ++top->samples;
+
+ switch (origin) {
+ case PERF_RECORD_MISC_USER:
+ ++top->us_samples;
+ if (top->hide_user_symbols)
+ goto next_event;
+ machine = &session->machines.host;
+ break;
+ case PERF_RECORD_MISC_KERNEL:
+ ++top->kernel_samples;
+ if (top->hide_kernel_symbols)
+ goto next_event;
+ machine = &session->machines.host;
+ break;
+ case PERF_RECORD_MISC_GUEST_KERNEL:
+ ++top->guest_kernel_samples;
+ machine = perf_session__find_machine(session,
+ sample.pid);
+ break;
+ case PERF_RECORD_MISC_GUEST_USER:
+ ++top->guest_us_samples;
+ /*
+ * TODO: we don't process guest user from host side
+ * except simple counting.
+ */
+ /* Fall thru */
+ default:
+ goto next_event;
+ }
+
+
+ if (event->header.type == PERF_RECORD_SAMPLE) {
+ perf_event__process_sample(&top->tool, event, evsel,
+ &sample, machine);
+ } else if (event->header.type < PERF_RECORD_MAX) {
+ hists__inc_nr_events(&evsel->hists, event->header.type);
+ machine__process_event(machine, event, &sample);
+ } else
+ ++session->stats.nr_unknown_events;
+next_event:
+ perf_evlist__mmap_consume(top->evlist, idx);
+ }
+}
+
+static void perf_top__mmap_read(struct perf_top *top)
+{
+ int i;
+
+ for (i = 0; i < top->evlist->nr_mmaps; i++)
+ perf_top__mmap_read_idx(top, i);
+}
+
+static int perf_top__start_counters(struct perf_top *top)
+{
+ char msg[512];
+ struct perf_evsel *counter;
+ struct perf_evlist *evlist = top->evlist;
+ struct record_opts *opts = &top->record_opts;
+
+ perf_evlist__config(evlist, opts);
+
+ evlist__for_each(evlist, counter) {
+try_again:
+ if (perf_evsel__open(counter, top->evlist->cpus,
+ top->evlist->threads) < 0) {
+ if (perf_evsel__fallback(counter, errno, msg, sizeof(msg))) {
+ if (verbose)
+ ui__warning("%s\n", msg);
+ goto try_again;
+ }
+
+ perf_evsel__open_strerror(counter, &opts->target,
+ errno, msg, sizeof(msg));
+ ui__error("%s\n", msg);
+ goto out_err;
+ }
+ }
+
+ if (perf_evlist__mmap(evlist, opts->mmap_pages, false) < 0) {
+ ui__error("Failed to mmap with %d (%s)\n",
+ errno, strerror(errno));
+ goto out_err;
+ }
+
+ return 0;
+
+out_err:
+ return -1;
+}
+
+static int perf_top__setup_sample_type(struct perf_top *top __maybe_unused)
+{
+ if (!sort__has_sym) {
+ if (symbol_conf.use_callchain) {
+ ui__error("Selected -g but \"sym\" not present in --sort/-s.");
+ return -EINVAL;
+ }
+ } else if (callchain_param.mode != CHAIN_NONE) {
+ if (callchain_register_param(&callchain_param) < 0) {
+ ui__error("Can't register callchain params.\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int __cmd_top(struct perf_top *top)
+{
+ struct record_opts *opts = &top->record_opts;
+ pthread_t thread;
+ int ret;
+
+ top->session = perf_session__new(NULL, false, NULL);
+ if (top->session == NULL)
+ return -ENOMEM;
+
+ machines__set_symbol_filter(&top->session->machines, symbol_filter);
+
+ if (!objdump_path) {
+ ret = perf_session_env__lookup_objdump(&top->session->header.env);
+ if (ret)
+ goto out_delete;
+ }
+
+ ret = perf_top__setup_sample_type(top);
+ if (ret)
+ goto out_delete;
+
+ machine__synthesize_threads(&top->session->machines.host, &opts->target,
+ top->evlist->threads, false);
+ ret = perf_top__start_counters(top);
+ if (ret)
+ goto out_delete;
+
+ top->session->evlist = top->evlist;
+ perf_session__set_id_hdr_size(top->session);
+
+ /*
+ * When perf is starting the traced process, all the events (apart from
+ * group members) have enable_on_exec=1 set, so don't spoil it by
+ * prematurely enabling them.
+ *
+ * XXX 'top' still doesn't start workloads like record, trace, but should,
+ * so leave the check here.
+ */
+ if (!target__none(&opts->target))
+ perf_evlist__enable(top->evlist);
+
+ /* Wait for a minimal set of events before starting the snapshot */
+ poll(top->evlist->pollfd, top->evlist->nr_fds, 100);
+
+ perf_top__mmap_read(top);
+
+ ret = -1;
+ if (pthread_create(&thread, NULL, (use_browser > 0 ? display_thread_tui :
+ display_thread), top)) {
+ ui__error("Could not create display thread.\n");
+ goto out_delete;
+ }
+
+ if (top->realtime_prio) {
+ struct sched_param param;
+
+ param.sched_priority = top->realtime_prio;
+ if (sched_setscheduler(0, SCHED_FIFO, &param)) {
+ ui__error("Could not set realtime priority.\n");
+ goto out_delete;
+ }
+ }
+
+ while (!done) {
+ u64 hits = top->samples;
+
+ perf_top__mmap_read(top);
+
+ if (hits == top->samples)
+ ret = poll(top->evlist->pollfd, top->evlist->nr_fds, 100);
+ }
+
+ ret = 0;
+out_delete:
+ perf_session__delete(top->session);
+ top->session = NULL;
+
+ return ret;
+}
+
+static int
+callchain_opt(const struct option *opt, const char *arg, int unset)
+{
+ symbol_conf.use_callchain = true;
+ return record_callchain_opt(opt, arg, unset);
+}
+
+static int
+parse_callchain_opt(const struct option *opt, const char *arg, int unset)
+{
+ symbol_conf.use_callchain = true;
+ return record_parse_callchain_opt(opt, arg, unset);
+}
+
+static int perf_top_config(const char *var, const char *value, void *cb)
+{
+ struct perf_top *top = cb;
+
+ if (!strcmp(var, "top.call-graph"))
+ return record_parse_callchain(value, &top->record_opts);
+ if (!strcmp(var, "top.children")) {
+ symbol_conf.cumulate_callchain = perf_config_bool(var, value);
+ return 0;
+ }
+
+ return perf_default_config(var, value, cb);
+}
+
+static int
+parse_percent_limit(const struct option *opt, const char *arg,
+ int unset __maybe_unused)
+{
+ struct perf_top *top = opt->value;
+
+ top->min_percent = strtof(arg, NULL);
+ return 0;
+}
+
+int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ int status = -1;
+ char errbuf[BUFSIZ];
+ struct perf_top top = {
+ .count_filter = 5,
+ .delay_secs = 2,
+ .record_opts = {
+ .mmap_pages = UINT_MAX,
+ .user_freq = UINT_MAX,
+ .user_interval = ULLONG_MAX,
+ .freq = 4000, /* 4 KHz */
+ .target = {
+ .uses_mmap = true,
+ },
+ },
+ .max_stack = PERF_MAX_STACK_DEPTH,
+ .sym_pcnt_filter = 5,
+ };
+ struct record_opts *opts = &top.record_opts;
+ struct target *target = &opts->target;
+ const struct option options[] = {
+ OPT_CALLBACK('e', "event", &top.evlist, "event",
+ "event selector. use 'perf list' to list available events",
+ parse_events_option),
+ OPT_U64('c', "count", &opts->user_interval, "event period to sample"),
+ OPT_STRING('p', "pid", &target->pid, "pid",
+ "profile events on existing process id"),
+ OPT_STRING('t', "tid", &target->tid, "tid",
+ "profile events on existing thread id"),
+ OPT_BOOLEAN('a', "all-cpus", &target->system_wide,
+ "system-wide collection from all CPUs"),
+ OPT_STRING('C', "cpu", &target->cpu_list, "cpu",
+ "list of cpus to monitor"),
+ OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name,
+ "file", "vmlinux pathname"),
+ OPT_BOOLEAN(0, "ignore-vmlinux", &symbol_conf.ignore_vmlinux,
+ "don't load vmlinux even if found"),
+ OPT_BOOLEAN('K', "hide_kernel_symbols", &top.hide_kernel_symbols,
+ "hide kernel symbols"),
+ OPT_CALLBACK('m', "mmap-pages", &opts->mmap_pages, "pages",
+ "number of mmap data pages",
+ perf_evlist__parse_mmap_pages),
+ OPT_INTEGER('r', "realtime", &top.realtime_prio,
+ "collect data with this RT SCHED_FIFO priority"),
+ OPT_INTEGER('d', "delay", &top.delay_secs,
+ "number of seconds to delay between refreshes"),
+ OPT_BOOLEAN('D', "dump-symtab", &top.dump_symtab,
+ "dump the symbol table used for profiling"),
+ OPT_INTEGER('f', "count-filter", &top.count_filter,
+ "only display functions with more events than this"),
+ OPT_BOOLEAN(0, "group", &opts->group,
+ "put the counters into a counter group"),
+ OPT_BOOLEAN('i', "no-inherit", &opts->no_inherit,
+ "child tasks do not inherit counters"),
+ OPT_STRING(0, "sym-annotate", &top.sym_filter, "symbol name",
+ "symbol to annotate"),
+ OPT_BOOLEAN('z', "zero", &top.zero, "zero history across updates"),
+ OPT_UINTEGER('F', "freq", &opts->user_freq, "profile at this frequency"),
+ OPT_INTEGER('E', "entries", &top.print_entries,
+ "display this many functions"),
+ OPT_BOOLEAN('U', "hide_user_symbols", &top.hide_user_symbols,
+ "hide user symbols"),
+ OPT_BOOLEAN(0, "tui", &top.use_tui, "Use the TUI interface"),
+ OPT_BOOLEAN(0, "stdio", &top.use_stdio, "Use the stdio interface"),
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show counter open errors, etc)"),
+ OPT_STRING('s', "sort", &sort_order, "key[,key2...]",
+ "sort by key(s): pid, comm, dso, symbol, parent, cpu, srcline, ..."
+ " Please refer the man page for the complete list."),
+ OPT_STRING(0, "fields", &field_order, "key[,keys...]",
+ "output field(s): overhead, period, sample plus all of sort keys"),
+ OPT_BOOLEAN('n', "show-nr-samples", &symbol_conf.show_nr_samples,
+ "Show a column with the number of samples"),
+ OPT_CALLBACK_NOOPT('g', NULL, &top.record_opts,
+ NULL, "enables call-graph recording",
+ &callchain_opt),
+ OPT_CALLBACK(0, "call-graph", &top.record_opts,
+ "mode[,dump_size]", record_callchain_help,
+ &parse_callchain_opt),
+ OPT_BOOLEAN(0, "children", &symbol_conf.cumulate_callchain,
+ "Accumulate callchains of children and show total overhead as well"),
+ OPT_INTEGER(0, "max-stack", &top.max_stack,
+ "Set the maximum stack depth when parsing the callchain. "
+ "Default: " __stringify(PERF_MAX_STACK_DEPTH)),
+ OPT_CALLBACK(0, "ignore-callees", NULL, "regex",
+ "ignore callees of these functions in call graphs",
+ report_parse_ignore_callees_opt),
+ OPT_BOOLEAN(0, "show-total-period", &symbol_conf.show_total_period,
+ "Show a column with the sum of periods"),
+ OPT_STRING(0, "dsos", &symbol_conf.dso_list_str, "dso[,dso...]",
+ "only consider symbols in these dsos"),
+ OPT_STRING(0, "comms", &symbol_conf.comm_list_str, "comm[,comm...]",
+ "only consider symbols in these comms"),
+ OPT_STRING(0, "symbols", &symbol_conf.sym_list_str, "symbol[,symbol...]",
+ "only consider these symbols"),
+ OPT_BOOLEAN(0, "source", &symbol_conf.annotate_src,
+ "Interleave source code with assembly code (default)"),
+ OPT_BOOLEAN(0, "asm-raw", &symbol_conf.annotate_asm_raw,
+ "Display raw encoding of assembly instructions (default)"),
+ OPT_STRING(0, "objdump", &objdump_path, "path",
+ "objdump binary to use for disassembly and annotations"),
+ OPT_STRING('M', "disassembler-style", &disassembler_style, "disassembler style",
+ "Specify disassembler style (e.g. -M intel for intel syntax)"),
+ OPT_STRING('u', "uid", &target->uid_str, "user", "user to profile"),
+ OPT_CALLBACK(0, "percent-limit", &top, "percent",
+ "Don't show entries under that percent", parse_percent_limit),
+ OPT_CALLBACK(0, "percentage", NULL, "relative|absolute",
+ "How to display percentage of filtered entries", parse_filter_percentage),
+ OPT_END()
+ };
+ const char * const top_usage[] = {
+ "perf top [<options>]",
+ NULL
+ };
+
+ top.evlist = perf_evlist__new();
+ if (top.evlist == NULL)
+ return -ENOMEM;
+
+ perf_config(perf_top_config, &top);
+
+ argc = parse_options(argc, argv, options, top_usage, 0);
+ if (argc)
+ usage_with_options(top_usage, options);
+
+ sort__mode = SORT_MODE__TOP;
+ /* display thread wants entries to be collapsed in a different tree */
+ sort__need_collapse = 1;
+
+ if (setup_sorting() < 0) {
+ if (sort_order)
+ parse_options_usage(top_usage, options, "s", 1);
+ if (field_order)
+ parse_options_usage(sort_order ? NULL : top_usage,
+ options, "fields", 0);
+ goto out_delete_evlist;
+ }
+
+ if (top.use_stdio)
+ use_browser = 0;
+ else if (top.use_tui)
+ use_browser = 1;
+
+ setup_browser(false);
+
+ status = target__validate(target);
+ if (status) {
+ target__strerror(target, status, errbuf, BUFSIZ);
+ ui__warning("%s\n", errbuf);
+ }
+
+ status = target__parse_uid(target);
+ if (status) {
+ int saved_errno = errno;
+
+ target__strerror(target, status, errbuf, BUFSIZ);
+ ui__error("%s\n", errbuf);
+
+ status = -saved_errno;
+ goto out_delete_evlist;
+ }
+
+ if (target__none(target))
+ target->system_wide = true;
+
+ if (perf_evlist__create_maps(top.evlist, target) < 0)
+ usage_with_options(top_usage, options);
+
+ if (!top.evlist->nr_entries &&
+ perf_evlist__add_default(top.evlist) < 0) {
+ ui__error("Not enough memory for event selector list\n");
+ goto out_delete_evlist;
+ }
+
+ symbol_conf.nr_events = top.evlist->nr_entries;
+
+ if (top.delay_secs < 1)
+ top.delay_secs = 1;
+
+ if (record_opts__config(opts)) {
+ status = -EINVAL;
+ goto out_delete_evlist;
+ }
+
+ top.sym_evsel = perf_evlist__first(top.evlist);
+
+ if (!symbol_conf.use_callchain) {
+ symbol_conf.cumulate_callchain = false;
+ perf_hpp__cancel_cumulate();
+ }
+
+ symbol_conf.priv_size = sizeof(struct annotation);
+
+ symbol_conf.try_vmlinux_path = (symbol_conf.vmlinux_name == NULL);
+ if (symbol__init() < 0)
+ return -1;
+
+ sort__setup_elide(stdout);
+
+ get_term_dimensions(&top.winsize);
+ if (top.print_entries == 0) {
+ struct sigaction act = {
+ .sa_sigaction = perf_top__sig_winch,
+ .sa_flags = SA_SIGINFO,
+ };
+ perf_top__update_print_entries(&top);
+ sigaction(SIGWINCH, &act, NULL);
+ }
+
+ status = __cmd_top(&top);
+
+out_delete_evlist:
+ perf_evlist__delete(top.evlist);
+
+ return status;
+}
diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c
new file mode 100644
index 00000000000..f954c26de23
--- /dev/null
+++ b/tools/perf/builtin-trace.c
@@ -0,0 +1,2397 @@
+#include <traceevent/event-parse.h>
+#include "builtin.h"
+#include "util/color.h"
+#include "util/debug.h"
+#include "util/evlist.h"
+#include "util/machine.h"
+#include "util/session.h"
+#include "util/thread.h"
+#include "util/parse-options.h"
+#include "util/strlist.h"
+#include "util/intlist.h"
+#include "util/thread_map.h"
+#include "util/stat.h"
+#include "trace-event.h"
+#include "util/parse-events.h"
+
+#include <libaudit.h>
+#include <stdlib.h>
+#include <sys/eventfd.h>
+#include <sys/mman.h>
+#include <linux/futex.h>
+
+/* For older distros: */
+#ifndef MAP_STACK
+# define MAP_STACK 0x20000
+#endif
+
+#ifndef MADV_HWPOISON
+# define MADV_HWPOISON 100
+#endif
+
+#ifndef MADV_MERGEABLE
+# define MADV_MERGEABLE 12
+#endif
+
+#ifndef MADV_UNMERGEABLE
+# define MADV_UNMERGEABLE 13
+#endif
+
+#ifndef EFD_SEMAPHORE
+# define EFD_SEMAPHORE 1
+#endif
+
+struct tp_field {
+ int offset;
+ union {
+ u64 (*integer)(struct tp_field *field, struct perf_sample *sample);
+ void *(*pointer)(struct tp_field *field, struct perf_sample *sample);
+ };
+};
+
+#define TP_UINT_FIELD(bits) \
+static u64 tp_field__u##bits(struct tp_field *field, struct perf_sample *sample) \
+{ \
+ return *(u##bits *)(sample->raw_data + field->offset); \
+}
+
+TP_UINT_FIELD(8);
+TP_UINT_FIELD(16);
+TP_UINT_FIELD(32);
+TP_UINT_FIELD(64);
+
+#define TP_UINT_FIELD__SWAPPED(bits) \
+static u64 tp_field__swapped_u##bits(struct tp_field *field, struct perf_sample *sample) \
+{ \
+ u##bits value = *(u##bits *)(sample->raw_data + field->offset); \
+ return bswap_##bits(value);\
+}
+
+TP_UINT_FIELD__SWAPPED(16);
+TP_UINT_FIELD__SWAPPED(32);
+TP_UINT_FIELD__SWAPPED(64);
+
+static int tp_field__init_uint(struct tp_field *field,
+ struct format_field *format_field,
+ bool needs_swap)
+{
+ field->offset = format_field->offset;
+
+ switch (format_field->size) {
+ case 1:
+ field->integer = tp_field__u8;
+ break;
+ case 2:
+ field->integer = needs_swap ? tp_field__swapped_u16 : tp_field__u16;
+ break;
+ case 4:
+ field->integer = needs_swap ? tp_field__swapped_u32 : tp_field__u32;
+ break;
+ case 8:
+ field->integer = needs_swap ? tp_field__swapped_u64 : tp_field__u64;
+ break;
+ default:
+ return -1;
+ }
+
+ return 0;
+}
+
+static void *tp_field__ptr(struct tp_field *field, struct perf_sample *sample)
+{
+ return sample->raw_data + field->offset;
+}
+
+static int tp_field__init_ptr(struct tp_field *field, struct format_field *format_field)
+{
+ field->offset = format_field->offset;
+ field->pointer = tp_field__ptr;
+ return 0;
+}
+
+struct syscall_tp {
+ struct tp_field id;
+ union {
+ struct tp_field args, ret;
+ };
+};
+
+static int perf_evsel__init_tp_uint_field(struct perf_evsel *evsel,
+ struct tp_field *field,
+ const char *name)
+{
+ struct format_field *format_field = perf_evsel__field(evsel, name);
+
+ if (format_field == NULL)
+ return -1;
+
+ return tp_field__init_uint(field, format_field, evsel->needs_swap);
+}
+
+#define perf_evsel__init_sc_tp_uint_field(evsel, name) \
+ ({ struct syscall_tp *sc = evsel->priv;\
+ perf_evsel__init_tp_uint_field(evsel, &sc->name, #name); })
+
+static int perf_evsel__init_tp_ptr_field(struct perf_evsel *evsel,
+ struct tp_field *field,
+ const char *name)
+{
+ struct format_field *format_field = perf_evsel__field(evsel, name);
+
+ if (format_field == NULL)
+ return -1;
+
+ return tp_field__init_ptr(field, format_field);
+}
+
+#define perf_evsel__init_sc_tp_ptr_field(evsel, name) \
+ ({ struct syscall_tp *sc = evsel->priv;\
+ perf_evsel__init_tp_ptr_field(evsel, &sc->name, #name); })
+
+static void perf_evsel__delete_priv(struct perf_evsel *evsel)
+{
+ zfree(&evsel->priv);
+ perf_evsel__delete(evsel);
+}
+
+static int perf_evsel__init_syscall_tp(struct perf_evsel *evsel, void *handler)
+{
+ evsel->priv = malloc(sizeof(struct syscall_tp));
+ if (evsel->priv != NULL) {
+ if (perf_evsel__init_sc_tp_uint_field(evsel, id))
+ goto out_delete;
+
+ evsel->handler = handler;
+ return 0;
+ }
+
+ return -ENOMEM;
+
+out_delete:
+ zfree(&evsel->priv);
+ return -ENOENT;
+}
+
+static struct perf_evsel *perf_evsel__syscall_newtp(const char *direction, void *handler)
+{
+ struct perf_evsel *evsel = perf_evsel__newtp("raw_syscalls", direction);
+
+ /* older kernel (e.g., RHEL6) use syscalls:{enter,exit} */
+ if (evsel == NULL)
+ evsel = perf_evsel__newtp("syscalls", direction);
+
+ if (evsel) {
+ if (perf_evsel__init_syscall_tp(evsel, handler))
+ goto out_delete;
+ }
+
+ return evsel;
+
+out_delete:
+ perf_evsel__delete_priv(evsel);
+ return NULL;
+}
+
+#define perf_evsel__sc_tp_uint(evsel, name, sample) \
+ ({ struct syscall_tp *fields = evsel->priv; \
+ fields->name.integer(&fields->name, sample); })
+
+#define perf_evsel__sc_tp_ptr(evsel, name, sample) \
+ ({ struct syscall_tp *fields = evsel->priv; \
+ fields->name.pointer(&fields->name, sample); })
+
+static int perf_evlist__add_syscall_newtp(struct perf_evlist *evlist,
+ void *sys_enter_handler,
+ void *sys_exit_handler)
+{
+ int ret = -1;
+ struct perf_evsel *sys_enter, *sys_exit;
+
+ sys_enter = perf_evsel__syscall_newtp("sys_enter", sys_enter_handler);
+ if (sys_enter == NULL)
+ goto out;
+
+ if (perf_evsel__init_sc_tp_ptr_field(sys_enter, args))
+ goto out_delete_sys_enter;
+
+ sys_exit = perf_evsel__syscall_newtp("sys_exit", sys_exit_handler);
+ if (sys_exit == NULL)
+ goto out_delete_sys_enter;
+
+ if (perf_evsel__init_sc_tp_uint_field(sys_exit, ret))
+ goto out_delete_sys_exit;
+
+ perf_evlist__add(evlist, sys_enter);
+ perf_evlist__add(evlist, sys_exit);
+
+ ret = 0;
+out:
+ return ret;
+
+out_delete_sys_exit:
+ perf_evsel__delete_priv(sys_exit);
+out_delete_sys_enter:
+ perf_evsel__delete_priv(sys_enter);
+ goto out;
+}
+
+
+struct syscall_arg {
+ unsigned long val;
+ struct thread *thread;
+ struct trace *trace;
+ void *parm;
+ u8 idx;
+ u8 mask;
+};
+
+struct strarray {
+ int offset;
+ int nr_entries;
+ const char **entries;
+};
+
+#define DEFINE_STRARRAY(array) struct strarray strarray__##array = { \
+ .nr_entries = ARRAY_SIZE(array), \
+ .entries = array, \
+}
+
+#define DEFINE_STRARRAY_OFFSET(array, off) struct strarray strarray__##array = { \
+ .offset = off, \
+ .nr_entries = ARRAY_SIZE(array), \
+ .entries = array, \
+}
+
+static size_t __syscall_arg__scnprintf_strarray(char *bf, size_t size,
+ const char *intfmt,
+ struct syscall_arg *arg)
+{
+ struct strarray *sa = arg->parm;
+ int idx = arg->val - sa->offset;
+
+ if (idx < 0 || idx >= sa->nr_entries)
+ return scnprintf(bf, size, intfmt, arg->val);
+
+ return scnprintf(bf, size, "%s", sa->entries[idx]);
+}
+
+static size_t syscall_arg__scnprintf_strarray(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ return __syscall_arg__scnprintf_strarray(bf, size, "%d", arg);
+}
+
+#define SCA_STRARRAY syscall_arg__scnprintf_strarray
+
+#if defined(__i386__) || defined(__x86_64__)
+/*
+ * FIXME: Make this available to all arches as soon as the ioctl beautifier
+ * gets rewritten to support all arches.
+ */
+static size_t syscall_arg__scnprintf_strhexarray(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ return __syscall_arg__scnprintf_strarray(bf, size, "%#x", arg);
+}
+
+#define SCA_STRHEXARRAY syscall_arg__scnprintf_strhexarray
+#endif /* defined(__i386__) || defined(__x86_64__) */
+
+static size_t syscall_arg__scnprintf_fd(char *bf, size_t size,
+ struct syscall_arg *arg);
+
+#define SCA_FD syscall_arg__scnprintf_fd
+
+static size_t syscall_arg__scnprintf_fd_at(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ int fd = arg->val;
+
+ if (fd == AT_FDCWD)
+ return scnprintf(bf, size, "CWD");
+
+ return syscall_arg__scnprintf_fd(bf, size, arg);
+}
+
+#define SCA_FDAT syscall_arg__scnprintf_fd_at
+
+static size_t syscall_arg__scnprintf_close_fd(char *bf, size_t size,
+ struct syscall_arg *arg);
+
+#define SCA_CLOSE_FD syscall_arg__scnprintf_close_fd
+
+static size_t syscall_arg__scnprintf_hex(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ return scnprintf(bf, size, "%#lx", arg->val);
+}
+
+#define SCA_HEX syscall_arg__scnprintf_hex
+
+static size_t syscall_arg__scnprintf_mmap_prot(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ int printed = 0, prot = arg->val;
+
+ if (prot == PROT_NONE)
+ return scnprintf(bf, size, "NONE");
+#define P_MMAP_PROT(n) \
+ if (prot & PROT_##n) { \
+ printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", #n); \
+ prot &= ~PROT_##n; \
+ }
+
+ P_MMAP_PROT(EXEC);
+ P_MMAP_PROT(READ);
+ P_MMAP_PROT(WRITE);
+#ifdef PROT_SEM
+ P_MMAP_PROT(SEM);
+#endif
+ P_MMAP_PROT(GROWSDOWN);
+ P_MMAP_PROT(GROWSUP);
+#undef P_MMAP_PROT
+
+ if (prot)
+ printed += scnprintf(bf + printed, size - printed, "%s%#x", printed ? "|" : "", prot);
+
+ return printed;
+}
+
+#define SCA_MMAP_PROT syscall_arg__scnprintf_mmap_prot
+
+static size_t syscall_arg__scnprintf_mmap_flags(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ int printed = 0, flags = arg->val;
+
+#define P_MMAP_FLAG(n) \
+ if (flags & MAP_##n) { \
+ printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", #n); \
+ flags &= ~MAP_##n; \
+ }
+
+ P_MMAP_FLAG(SHARED);
+ P_MMAP_FLAG(PRIVATE);
+#ifdef MAP_32BIT
+ P_MMAP_FLAG(32BIT);
+#endif
+ P_MMAP_FLAG(ANONYMOUS);
+ P_MMAP_FLAG(DENYWRITE);
+ P_MMAP_FLAG(EXECUTABLE);
+ P_MMAP_FLAG(FILE);
+ P_MMAP_FLAG(FIXED);
+ P_MMAP_FLAG(GROWSDOWN);
+#ifdef MAP_HUGETLB
+ P_MMAP_FLAG(HUGETLB);
+#endif
+ P_MMAP_FLAG(LOCKED);
+ P_MMAP_FLAG(NONBLOCK);
+ P_MMAP_FLAG(NORESERVE);
+ P_MMAP_FLAG(POPULATE);
+ P_MMAP_FLAG(STACK);
+#ifdef MAP_UNINITIALIZED
+ P_MMAP_FLAG(UNINITIALIZED);
+#endif
+#undef P_MMAP_FLAG
+
+ if (flags)
+ printed += scnprintf(bf + printed, size - printed, "%s%#x", printed ? "|" : "", flags);
+
+ return printed;
+}
+
+#define SCA_MMAP_FLAGS syscall_arg__scnprintf_mmap_flags
+
+static size_t syscall_arg__scnprintf_madvise_behavior(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ int behavior = arg->val;
+
+ switch (behavior) {
+#define P_MADV_BHV(n) case MADV_##n: return scnprintf(bf, size, #n)
+ P_MADV_BHV(NORMAL);
+ P_MADV_BHV(RANDOM);
+ P_MADV_BHV(SEQUENTIAL);
+ P_MADV_BHV(WILLNEED);
+ P_MADV_BHV(DONTNEED);
+ P_MADV_BHV(REMOVE);
+ P_MADV_BHV(DONTFORK);
+ P_MADV_BHV(DOFORK);
+ P_MADV_BHV(HWPOISON);
+#ifdef MADV_SOFT_OFFLINE
+ P_MADV_BHV(SOFT_OFFLINE);
+#endif
+ P_MADV_BHV(MERGEABLE);
+ P_MADV_BHV(UNMERGEABLE);
+#ifdef MADV_HUGEPAGE
+ P_MADV_BHV(HUGEPAGE);
+#endif
+#ifdef MADV_NOHUGEPAGE
+ P_MADV_BHV(NOHUGEPAGE);
+#endif
+#ifdef MADV_DONTDUMP
+ P_MADV_BHV(DONTDUMP);
+#endif
+#ifdef MADV_DODUMP
+ P_MADV_BHV(DODUMP);
+#endif
+#undef P_MADV_PHV
+ default: break;
+ }
+
+ return scnprintf(bf, size, "%#x", behavior);
+}
+
+#define SCA_MADV_BHV syscall_arg__scnprintf_madvise_behavior
+
+static size_t syscall_arg__scnprintf_flock(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ int printed = 0, op = arg->val;
+
+ if (op == 0)
+ return scnprintf(bf, size, "NONE");
+#define P_CMD(cmd) \
+ if ((op & LOCK_##cmd) == LOCK_##cmd) { \
+ printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", #cmd); \
+ op &= ~LOCK_##cmd; \
+ }
+
+ P_CMD(SH);
+ P_CMD(EX);
+ P_CMD(NB);
+ P_CMD(UN);
+ P_CMD(MAND);
+ P_CMD(RW);
+ P_CMD(READ);
+ P_CMD(WRITE);
+#undef P_OP
+
+ if (op)
+ printed += scnprintf(bf + printed, size - printed, "%s%#x", printed ? "|" : "", op);
+
+ return printed;
+}
+
+#define SCA_FLOCK syscall_arg__scnprintf_flock
+
+static size_t syscall_arg__scnprintf_futex_op(char *bf, size_t size, struct syscall_arg *arg)
+{
+ enum syscall_futex_args {
+ SCF_UADDR = (1 << 0),
+ SCF_OP = (1 << 1),
+ SCF_VAL = (1 << 2),
+ SCF_TIMEOUT = (1 << 3),
+ SCF_UADDR2 = (1 << 4),
+ SCF_VAL3 = (1 << 5),
+ };
+ int op = arg->val;
+ int cmd = op & FUTEX_CMD_MASK;
+ size_t printed = 0;
+
+ switch (cmd) {
+#define P_FUTEX_OP(n) case FUTEX_##n: printed = scnprintf(bf, size, #n);
+ P_FUTEX_OP(WAIT); arg->mask |= SCF_VAL3|SCF_UADDR2; break;
+ P_FUTEX_OP(WAKE); arg->mask |= SCF_VAL3|SCF_UADDR2|SCF_TIMEOUT; break;
+ P_FUTEX_OP(FD); arg->mask |= SCF_VAL3|SCF_UADDR2|SCF_TIMEOUT; break;
+ P_FUTEX_OP(REQUEUE); arg->mask |= SCF_VAL3|SCF_TIMEOUT; break;
+ P_FUTEX_OP(CMP_REQUEUE); arg->mask |= SCF_TIMEOUT; break;
+ P_FUTEX_OP(CMP_REQUEUE_PI); arg->mask |= SCF_TIMEOUT; break;
+ P_FUTEX_OP(WAKE_OP); break;
+ P_FUTEX_OP(LOCK_PI); arg->mask |= SCF_VAL3|SCF_UADDR2|SCF_TIMEOUT; break;
+ P_FUTEX_OP(UNLOCK_PI); arg->mask |= SCF_VAL3|SCF_UADDR2|SCF_TIMEOUT; break;
+ P_FUTEX_OP(TRYLOCK_PI); arg->mask |= SCF_VAL3|SCF_UADDR2; break;
+ P_FUTEX_OP(WAIT_BITSET); arg->mask |= SCF_UADDR2; break;
+ P_FUTEX_OP(WAKE_BITSET); arg->mask |= SCF_UADDR2; break;
+ P_FUTEX_OP(WAIT_REQUEUE_PI); break;
+ default: printed = scnprintf(bf, size, "%#x", cmd); break;
+ }
+
+ if (op & FUTEX_PRIVATE_FLAG)
+ printed += scnprintf(bf + printed, size - printed, "|PRIV");
+
+ if (op & FUTEX_CLOCK_REALTIME)
+ printed += scnprintf(bf + printed, size - printed, "|CLKRT");
+
+ return printed;
+}
+
+#define SCA_FUTEX_OP syscall_arg__scnprintf_futex_op
+
+static const char *epoll_ctl_ops[] = { "ADD", "DEL", "MOD", };
+static DEFINE_STRARRAY_OFFSET(epoll_ctl_ops, 1);
+
+static const char *itimers[] = { "REAL", "VIRTUAL", "PROF", };
+static DEFINE_STRARRAY(itimers);
+
+static const char *whences[] = { "SET", "CUR", "END",
+#ifdef SEEK_DATA
+"DATA",
+#endif
+#ifdef SEEK_HOLE
+"HOLE",
+#endif
+};
+static DEFINE_STRARRAY(whences);
+
+static const char *fcntl_cmds[] = {
+ "DUPFD", "GETFD", "SETFD", "GETFL", "SETFL", "GETLK", "SETLK",
+ "SETLKW", "SETOWN", "GETOWN", "SETSIG", "GETSIG", "F_GETLK64",
+ "F_SETLK64", "F_SETLKW64", "F_SETOWN_EX", "F_GETOWN_EX",
+ "F_GETOWNER_UIDS",
+};
+static DEFINE_STRARRAY(fcntl_cmds);
+
+static const char *rlimit_resources[] = {
+ "CPU", "FSIZE", "DATA", "STACK", "CORE", "RSS", "NPROC", "NOFILE",
+ "MEMLOCK", "AS", "LOCKS", "SIGPENDING", "MSGQUEUE", "NICE", "RTPRIO",
+ "RTTIME",
+};
+static DEFINE_STRARRAY(rlimit_resources);
+
+static const char *sighow[] = { "BLOCK", "UNBLOCK", "SETMASK", };
+static DEFINE_STRARRAY(sighow);
+
+static const char *clockid[] = {
+ "REALTIME", "MONOTONIC", "PROCESS_CPUTIME_ID", "THREAD_CPUTIME_ID",
+ "MONOTONIC_RAW", "REALTIME_COARSE", "MONOTONIC_COARSE",
+};
+static DEFINE_STRARRAY(clockid);
+
+static const char *socket_families[] = {
+ "UNSPEC", "LOCAL", "INET", "AX25", "IPX", "APPLETALK", "NETROM",
+ "BRIDGE", "ATMPVC", "X25", "INET6", "ROSE", "DECnet", "NETBEUI",
+ "SECURITY", "KEY", "NETLINK", "PACKET", "ASH", "ECONET", "ATMSVC",
+ "RDS", "SNA", "IRDA", "PPPOX", "WANPIPE", "LLC", "IB", "CAN", "TIPC",
+ "BLUETOOTH", "IUCV", "RXRPC", "ISDN", "PHONET", "IEEE802154", "CAIF",
+ "ALG", "NFC", "VSOCK",
+};
+static DEFINE_STRARRAY(socket_families);
+
+#ifndef SOCK_TYPE_MASK
+#define SOCK_TYPE_MASK 0xf
+#endif
+
+static size_t syscall_arg__scnprintf_socket_type(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ size_t printed;
+ int type = arg->val,
+ flags = type & ~SOCK_TYPE_MASK;
+
+ type &= SOCK_TYPE_MASK;
+ /*
+ * Can't use a strarray, MIPS may override for ABI reasons.
+ */
+ switch (type) {
+#define P_SK_TYPE(n) case SOCK_##n: printed = scnprintf(bf, size, #n); break;
+ P_SK_TYPE(STREAM);
+ P_SK_TYPE(DGRAM);
+ P_SK_TYPE(RAW);
+ P_SK_TYPE(RDM);
+ P_SK_TYPE(SEQPACKET);
+ P_SK_TYPE(DCCP);
+ P_SK_TYPE(PACKET);
+#undef P_SK_TYPE
+ default:
+ printed = scnprintf(bf, size, "%#x", type);
+ }
+
+#define P_SK_FLAG(n) \
+ if (flags & SOCK_##n) { \
+ printed += scnprintf(bf + printed, size - printed, "|%s", #n); \
+ flags &= ~SOCK_##n; \
+ }
+
+ P_SK_FLAG(CLOEXEC);
+ P_SK_FLAG(NONBLOCK);
+#undef P_SK_FLAG
+
+ if (flags)
+ printed += scnprintf(bf + printed, size - printed, "|%#x", flags);
+
+ return printed;
+}
+
+#define SCA_SK_TYPE syscall_arg__scnprintf_socket_type
+
+#ifndef MSG_PROBE
+#define MSG_PROBE 0x10
+#endif
+#ifndef MSG_WAITFORONE
+#define MSG_WAITFORONE 0x10000
+#endif
+#ifndef MSG_SENDPAGE_NOTLAST
+#define MSG_SENDPAGE_NOTLAST 0x20000
+#endif
+#ifndef MSG_FASTOPEN
+#define MSG_FASTOPEN 0x20000000
+#endif
+
+static size_t syscall_arg__scnprintf_msg_flags(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ int printed = 0, flags = arg->val;
+
+ if (flags == 0)
+ return scnprintf(bf, size, "NONE");
+#define P_MSG_FLAG(n) \
+ if (flags & MSG_##n) { \
+ printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", #n); \
+ flags &= ~MSG_##n; \
+ }
+
+ P_MSG_FLAG(OOB);
+ P_MSG_FLAG(PEEK);
+ P_MSG_FLAG(DONTROUTE);
+ P_MSG_FLAG(TRYHARD);
+ P_MSG_FLAG(CTRUNC);
+ P_MSG_FLAG(PROBE);
+ P_MSG_FLAG(TRUNC);
+ P_MSG_FLAG(DONTWAIT);
+ P_MSG_FLAG(EOR);
+ P_MSG_FLAG(WAITALL);
+ P_MSG_FLAG(FIN);
+ P_MSG_FLAG(SYN);
+ P_MSG_FLAG(CONFIRM);
+ P_MSG_FLAG(RST);
+ P_MSG_FLAG(ERRQUEUE);
+ P_MSG_FLAG(NOSIGNAL);
+ P_MSG_FLAG(MORE);
+ P_MSG_FLAG(WAITFORONE);
+ P_MSG_FLAG(SENDPAGE_NOTLAST);
+ P_MSG_FLAG(FASTOPEN);
+ P_MSG_FLAG(CMSG_CLOEXEC);
+#undef P_MSG_FLAG
+
+ if (flags)
+ printed += scnprintf(bf + printed, size - printed, "%s%#x", printed ? "|" : "", flags);
+
+ return printed;
+}
+
+#define SCA_MSG_FLAGS syscall_arg__scnprintf_msg_flags
+
+static size_t syscall_arg__scnprintf_access_mode(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ size_t printed = 0;
+ int mode = arg->val;
+
+ if (mode == F_OK) /* 0 */
+ return scnprintf(bf, size, "F");
+#define P_MODE(n) \
+ if (mode & n##_OK) { \
+ printed += scnprintf(bf + printed, size - printed, "%s", #n); \
+ mode &= ~n##_OK; \
+ }
+
+ P_MODE(R);
+ P_MODE(W);
+ P_MODE(X);
+#undef P_MODE
+
+ if (mode)
+ printed += scnprintf(bf + printed, size - printed, "|%#x", mode);
+
+ return printed;
+}
+
+#define SCA_ACCMODE syscall_arg__scnprintf_access_mode
+
+static size_t syscall_arg__scnprintf_open_flags(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ int printed = 0, flags = arg->val;
+
+ if (!(flags & O_CREAT))
+ arg->mask |= 1 << (arg->idx + 1); /* Mask the mode parm */
+
+ if (flags == 0)
+ return scnprintf(bf, size, "RDONLY");
+#define P_FLAG(n) \
+ if (flags & O_##n) { \
+ printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", #n); \
+ flags &= ~O_##n; \
+ }
+
+ P_FLAG(APPEND);
+ P_FLAG(ASYNC);
+ P_FLAG(CLOEXEC);
+ P_FLAG(CREAT);
+ P_FLAG(DIRECT);
+ P_FLAG(DIRECTORY);
+ P_FLAG(EXCL);
+ P_FLAG(LARGEFILE);
+ P_FLAG(NOATIME);
+ P_FLAG(NOCTTY);
+#ifdef O_NONBLOCK
+ P_FLAG(NONBLOCK);
+#elif O_NDELAY
+ P_FLAG(NDELAY);
+#endif
+#ifdef O_PATH
+ P_FLAG(PATH);
+#endif
+ P_FLAG(RDWR);
+#ifdef O_DSYNC
+ if ((flags & O_SYNC) == O_SYNC)
+ printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", "SYNC");
+ else {
+ P_FLAG(DSYNC);
+ }
+#else
+ P_FLAG(SYNC);
+#endif
+ P_FLAG(TRUNC);
+ P_FLAG(WRONLY);
+#undef P_FLAG
+
+ if (flags)
+ printed += scnprintf(bf + printed, size - printed, "%s%#x", printed ? "|" : "", flags);
+
+ return printed;
+}
+
+#define SCA_OPEN_FLAGS syscall_arg__scnprintf_open_flags
+
+static size_t syscall_arg__scnprintf_eventfd_flags(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ int printed = 0, flags = arg->val;
+
+ if (flags == 0)
+ return scnprintf(bf, size, "NONE");
+#define P_FLAG(n) \
+ if (flags & EFD_##n) { \
+ printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", #n); \
+ flags &= ~EFD_##n; \
+ }
+
+ P_FLAG(SEMAPHORE);
+ P_FLAG(CLOEXEC);
+ P_FLAG(NONBLOCK);
+#undef P_FLAG
+
+ if (flags)
+ printed += scnprintf(bf + printed, size - printed, "%s%#x", printed ? "|" : "", flags);
+
+ return printed;
+}
+
+#define SCA_EFD_FLAGS syscall_arg__scnprintf_eventfd_flags
+
+static size_t syscall_arg__scnprintf_pipe_flags(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ int printed = 0, flags = arg->val;
+
+#define P_FLAG(n) \
+ if (flags & O_##n) { \
+ printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", #n); \
+ flags &= ~O_##n; \
+ }
+
+ P_FLAG(CLOEXEC);
+ P_FLAG(NONBLOCK);
+#undef P_FLAG
+
+ if (flags)
+ printed += scnprintf(bf + printed, size - printed, "%s%#x", printed ? "|" : "", flags);
+
+ return printed;
+}
+
+#define SCA_PIPE_FLAGS syscall_arg__scnprintf_pipe_flags
+
+static size_t syscall_arg__scnprintf_signum(char *bf, size_t size, struct syscall_arg *arg)
+{
+ int sig = arg->val;
+
+ switch (sig) {
+#define P_SIGNUM(n) case SIG##n: return scnprintf(bf, size, #n)
+ P_SIGNUM(HUP);
+ P_SIGNUM(INT);
+ P_SIGNUM(QUIT);
+ P_SIGNUM(ILL);
+ P_SIGNUM(TRAP);
+ P_SIGNUM(ABRT);
+ P_SIGNUM(BUS);
+ P_SIGNUM(FPE);
+ P_SIGNUM(KILL);
+ P_SIGNUM(USR1);
+ P_SIGNUM(SEGV);
+ P_SIGNUM(USR2);
+ P_SIGNUM(PIPE);
+ P_SIGNUM(ALRM);
+ P_SIGNUM(TERM);
+ P_SIGNUM(CHLD);
+ P_SIGNUM(CONT);
+ P_SIGNUM(STOP);
+ P_SIGNUM(TSTP);
+ P_SIGNUM(TTIN);
+ P_SIGNUM(TTOU);
+ P_SIGNUM(URG);
+ P_SIGNUM(XCPU);
+ P_SIGNUM(XFSZ);
+ P_SIGNUM(VTALRM);
+ P_SIGNUM(PROF);
+ P_SIGNUM(WINCH);
+ P_SIGNUM(IO);
+ P_SIGNUM(PWR);
+ P_SIGNUM(SYS);
+#ifdef SIGEMT
+ P_SIGNUM(EMT);
+#endif
+#ifdef SIGSTKFLT
+ P_SIGNUM(STKFLT);
+#endif
+#ifdef SIGSWI
+ P_SIGNUM(SWI);
+#endif
+ default: break;
+ }
+
+ return scnprintf(bf, size, "%#x", sig);
+}
+
+#define SCA_SIGNUM syscall_arg__scnprintf_signum
+
+#if defined(__i386__) || defined(__x86_64__)
+/*
+ * FIXME: Make this available to all arches.
+ */
+#define TCGETS 0x5401
+
+static const char *tioctls[] = {
+ "TCGETS", "TCSETS", "TCSETSW", "TCSETSF", "TCGETA", "TCSETA", "TCSETAW",
+ "TCSETAF", "TCSBRK", "TCXONC", "TCFLSH", "TIOCEXCL", "TIOCNXCL",
+ "TIOCSCTTY", "TIOCGPGRP", "TIOCSPGRP", "TIOCOUTQ", "TIOCSTI",
+ "TIOCGWINSZ", "TIOCSWINSZ", "TIOCMGET", "TIOCMBIS", "TIOCMBIC",
+ "TIOCMSET", "TIOCGSOFTCAR", "TIOCSSOFTCAR", "FIONREAD", "TIOCLINUX",
+ "TIOCCONS", "TIOCGSERIAL", "TIOCSSERIAL", "TIOCPKT", "FIONBIO",
+ "TIOCNOTTY", "TIOCSETD", "TIOCGETD", "TCSBRKP", [0x27] = "TIOCSBRK",
+ "TIOCCBRK", "TIOCGSID", "TCGETS2", "TCSETS2", "TCSETSW2", "TCSETSF2",
+ "TIOCGRS485", "TIOCSRS485", "TIOCGPTN", "TIOCSPTLCK",
+ "TIOCGDEV||TCGETX", "TCSETX", "TCSETXF", "TCSETXW", "TIOCSIG",
+ "TIOCVHANGUP", "TIOCGPKT", "TIOCGPTLCK", "TIOCGEXCL",
+ [0x50] = "FIONCLEX", "FIOCLEX", "FIOASYNC", "TIOCSERCONFIG",
+ "TIOCSERGWILD", "TIOCSERSWILD", "TIOCGLCKTRMIOS", "TIOCSLCKTRMIOS",
+ "TIOCSERGSTRUCT", "TIOCSERGETLSR", "TIOCSERGETMULTI", "TIOCSERSETMULTI",
+ "TIOCMIWAIT", "TIOCGICOUNT", [0x60] = "FIOQSIZE",
+};
+
+static DEFINE_STRARRAY_OFFSET(tioctls, 0x5401);
+#endif /* defined(__i386__) || defined(__x86_64__) */
+
+#define STRARRAY(arg, name, array) \
+ .arg_scnprintf = { [arg] = SCA_STRARRAY, }, \
+ .arg_parm = { [arg] = &strarray__##array, }
+
+static struct syscall_fmt {
+ const char *name;
+ const char *alias;
+ size_t (*arg_scnprintf[6])(char *bf, size_t size, struct syscall_arg *arg);
+ void *arg_parm[6];
+ bool errmsg;
+ bool timeout;
+ bool hexret;
+} syscall_fmts[] = {
+ { .name = "access", .errmsg = true,
+ .arg_scnprintf = { [1] = SCA_ACCMODE, /* mode */ }, },
+ { .name = "arch_prctl", .errmsg = true, .alias = "prctl", },
+ { .name = "brk", .hexret = true,
+ .arg_scnprintf = { [0] = SCA_HEX, /* brk */ }, },
+ { .name = "clock_gettime", .errmsg = true, STRARRAY(0, clk_id, clockid), },
+ { .name = "close", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_CLOSE_FD, /* fd */ }, },
+ { .name = "connect", .errmsg = true, },
+ { .name = "dup", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "dup2", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "dup3", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "epoll_ctl", .errmsg = true, STRARRAY(1, op, epoll_ctl_ops), },
+ { .name = "eventfd2", .errmsg = true,
+ .arg_scnprintf = { [1] = SCA_EFD_FLAGS, /* flags */ }, },
+ { .name = "faccessat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* dfd */ }, },
+ { .name = "fadvise64", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "fallocate", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "fchdir", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "fchmod", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "fchmodat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* fd */ }, },
+ { .name = "fchown", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "fchownat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* fd */ }, },
+ { .name = "fcntl", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */
+ [1] = SCA_STRARRAY, /* cmd */ },
+ .arg_parm = { [1] = &strarray__fcntl_cmds, /* cmd */ }, },
+ { .name = "fdatasync", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "flock", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */
+ [1] = SCA_FLOCK, /* cmd */ }, },
+ { .name = "fsetxattr", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "fstat", .errmsg = true, .alias = "newfstat",
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "fstatat", .errmsg = true, .alias = "newfstatat",
+ .arg_scnprintf = { [0] = SCA_FDAT, /* dfd */ }, },
+ { .name = "fstatfs", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "fsync", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "ftruncate", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "futex", .errmsg = true,
+ .arg_scnprintf = { [1] = SCA_FUTEX_OP, /* op */ }, },
+ { .name = "futimesat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* fd */ }, },
+ { .name = "getdents", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "getdents64", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "getitimer", .errmsg = true, STRARRAY(0, which, itimers), },
+ { .name = "getrlimit", .errmsg = true, STRARRAY(0, resource, rlimit_resources), },
+ { .name = "ioctl", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */
+#if defined(__i386__) || defined(__x86_64__)
+/*
+ * FIXME: Make this available to all arches.
+ */
+ [1] = SCA_STRHEXARRAY, /* cmd */
+ [2] = SCA_HEX, /* arg */ },
+ .arg_parm = { [1] = &strarray__tioctls, /* cmd */ }, },
+#else
+ [2] = SCA_HEX, /* arg */ }, },
+#endif
+ { .name = "kill", .errmsg = true,
+ .arg_scnprintf = { [1] = SCA_SIGNUM, /* sig */ }, },
+ { .name = "linkat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* fd */ }, },
+ { .name = "lseek", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */
+ [2] = SCA_STRARRAY, /* whence */ },
+ .arg_parm = { [2] = &strarray__whences, /* whence */ }, },
+ { .name = "lstat", .errmsg = true, .alias = "newlstat", },
+ { .name = "madvise", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_HEX, /* start */
+ [2] = SCA_MADV_BHV, /* behavior */ }, },
+ { .name = "mkdirat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* fd */ }, },
+ { .name = "mknodat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* fd */ }, },
+ { .name = "mlock", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_HEX, /* addr */ }, },
+ { .name = "mlockall", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_HEX, /* addr */ }, },
+ { .name = "mmap", .hexret = true,
+ .arg_scnprintf = { [0] = SCA_HEX, /* addr */
+ [2] = SCA_MMAP_PROT, /* prot */
+ [3] = SCA_MMAP_FLAGS, /* flags */
+ [4] = SCA_FD, /* fd */ }, },
+ { .name = "mprotect", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_HEX, /* start */
+ [2] = SCA_MMAP_PROT, /* prot */ }, },
+ { .name = "mremap", .hexret = true,
+ .arg_scnprintf = { [0] = SCA_HEX, /* addr */
+ [4] = SCA_HEX, /* new_addr */ }, },
+ { .name = "munlock", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_HEX, /* addr */ }, },
+ { .name = "munmap", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_HEX, /* addr */ }, },
+ { .name = "name_to_handle_at", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* dfd */ }, },
+ { .name = "newfstatat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* dfd */ }, },
+ { .name = "open", .errmsg = true,
+ .arg_scnprintf = { [1] = SCA_OPEN_FLAGS, /* flags */ }, },
+ { .name = "open_by_handle_at", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* dfd */
+ [2] = SCA_OPEN_FLAGS, /* flags */ }, },
+ { .name = "openat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* dfd */
+ [2] = SCA_OPEN_FLAGS, /* flags */ }, },
+ { .name = "pipe2", .errmsg = true,
+ .arg_scnprintf = { [1] = SCA_PIPE_FLAGS, /* flags */ }, },
+ { .name = "poll", .errmsg = true, .timeout = true, },
+ { .name = "ppoll", .errmsg = true, .timeout = true, },
+ { .name = "pread", .errmsg = true, .alias = "pread64",
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "preadv", .errmsg = true, .alias = "pread",
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "prlimit64", .errmsg = true, STRARRAY(1, resource, rlimit_resources), },
+ { .name = "pwrite", .errmsg = true, .alias = "pwrite64",
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "pwritev", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "read", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "readlinkat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* dfd */ }, },
+ { .name = "readv", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "recvfrom", .errmsg = true,
+ .arg_scnprintf = { [3] = SCA_MSG_FLAGS, /* flags */ }, },
+ { .name = "recvmmsg", .errmsg = true,
+ .arg_scnprintf = { [3] = SCA_MSG_FLAGS, /* flags */ }, },
+ { .name = "recvmsg", .errmsg = true,
+ .arg_scnprintf = { [2] = SCA_MSG_FLAGS, /* flags */ }, },
+ { .name = "renameat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* dfd */ }, },
+ { .name = "rt_sigaction", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_SIGNUM, /* sig */ }, },
+ { .name = "rt_sigprocmask", .errmsg = true, STRARRAY(0, how, sighow), },
+ { .name = "rt_sigqueueinfo", .errmsg = true,
+ .arg_scnprintf = { [1] = SCA_SIGNUM, /* sig */ }, },
+ { .name = "rt_tgsigqueueinfo", .errmsg = true,
+ .arg_scnprintf = { [2] = SCA_SIGNUM, /* sig */ }, },
+ { .name = "select", .errmsg = true, .timeout = true, },
+ { .name = "sendmmsg", .errmsg = true,
+ .arg_scnprintf = { [3] = SCA_MSG_FLAGS, /* flags */ }, },
+ { .name = "sendmsg", .errmsg = true,
+ .arg_scnprintf = { [2] = SCA_MSG_FLAGS, /* flags */ }, },
+ { .name = "sendto", .errmsg = true,
+ .arg_scnprintf = { [3] = SCA_MSG_FLAGS, /* flags */ }, },
+ { .name = "setitimer", .errmsg = true, STRARRAY(0, which, itimers), },
+ { .name = "setrlimit", .errmsg = true, STRARRAY(0, resource, rlimit_resources), },
+ { .name = "shutdown", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "socket", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_STRARRAY, /* family */
+ [1] = SCA_SK_TYPE, /* type */ },
+ .arg_parm = { [0] = &strarray__socket_families, /* family */ }, },
+ { .name = "socketpair", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_STRARRAY, /* family */
+ [1] = SCA_SK_TYPE, /* type */ },
+ .arg_parm = { [0] = &strarray__socket_families, /* family */ }, },
+ { .name = "stat", .errmsg = true, .alias = "newstat", },
+ { .name = "symlinkat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* dfd */ }, },
+ { .name = "tgkill", .errmsg = true,
+ .arg_scnprintf = { [2] = SCA_SIGNUM, /* sig */ }, },
+ { .name = "tkill", .errmsg = true,
+ .arg_scnprintf = { [1] = SCA_SIGNUM, /* sig */ }, },
+ { .name = "uname", .errmsg = true, .alias = "newuname", },
+ { .name = "unlinkat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* dfd */ }, },
+ { .name = "utimensat", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FDAT, /* dirfd */ }, },
+ { .name = "write", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+ { .name = "writev", .errmsg = true,
+ .arg_scnprintf = { [0] = SCA_FD, /* fd */ }, },
+};
+
+static int syscall_fmt__cmp(const void *name, const void *fmtp)
+{
+ const struct syscall_fmt *fmt = fmtp;
+ return strcmp(name, fmt->name);
+}
+
+static struct syscall_fmt *syscall_fmt__find(const char *name)
+{
+ const int nmemb = ARRAY_SIZE(syscall_fmts);
+ return bsearch(name, syscall_fmts, nmemb, sizeof(struct syscall_fmt), syscall_fmt__cmp);
+}
+
+struct syscall {
+ struct event_format *tp_format;
+ const char *name;
+ bool filtered;
+ struct syscall_fmt *fmt;
+ size_t (**arg_scnprintf)(char *bf, size_t size, struct syscall_arg *arg);
+ void **arg_parm;
+};
+
+static size_t fprintf_duration(unsigned long t, FILE *fp)
+{
+ double duration = (double)t / NSEC_PER_MSEC;
+ size_t printed = fprintf(fp, "(");
+
+ if (duration >= 1.0)
+ printed += color_fprintf(fp, PERF_COLOR_RED, "%6.3f ms", duration);
+ else if (duration >= 0.01)
+ printed += color_fprintf(fp, PERF_COLOR_YELLOW, "%6.3f ms", duration);
+ else
+ printed += color_fprintf(fp, PERF_COLOR_NORMAL, "%6.3f ms", duration);
+ return printed + fprintf(fp, "): ");
+}
+
+struct thread_trace {
+ u64 entry_time;
+ u64 exit_time;
+ bool entry_pending;
+ unsigned long nr_events;
+ char *entry_str;
+ double runtime_ms;
+ struct {
+ int max;
+ char **table;
+ } paths;
+
+ struct intlist *syscall_stats;
+};
+
+static struct thread_trace *thread_trace__new(void)
+{
+ struct thread_trace *ttrace = zalloc(sizeof(struct thread_trace));
+
+ if (ttrace)
+ ttrace->paths.max = -1;
+
+ ttrace->syscall_stats = intlist__new(NULL);
+
+ return ttrace;
+}
+
+static struct thread_trace *thread__trace(struct thread *thread, FILE *fp)
+{
+ struct thread_trace *ttrace;
+
+ if (thread == NULL)
+ goto fail;
+
+ if (thread->priv == NULL)
+ thread->priv = thread_trace__new();
+
+ if (thread->priv == NULL)
+ goto fail;
+
+ ttrace = thread->priv;
+ ++ttrace->nr_events;
+
+ return ttrace;
+fail:
+ color_fprintf(fp, PERF_COLOR_RED,
+ "WARNING: not enough memory, dropping samples!\n");
+ return NULL;
+}
+
+struct trace {
+ struct perf_tool tool;
+ struct {
+ int machine;
+ int open_id;
+ } audit;
+ struct {
+ int max;
+ struct syscall *table;
+ } syscalls;
+ struct record_opts opts;
+ struct machine *host;
+ u64 base_time;
+ FILE *output;
+ unsigned long nr_events;
+ struct strlist *ev_qualifier;
+ const char *last_vfs_getname;
+ struct intlist *tid_list;
+ struct intlist *pid_list;
+ double duration_filter;
+ double runtime_ms;
+ struct {
+ u64 vfs_getname,
+ proc_getname;
+ } stats;
+ bool not_ev_qualifier;
+ bool live;
+ bool full_time;
+ bool sched;
+ bool multiple_threads;
+ bool summary;
+ bool summary_only;
+ bool show_comm;
+ bool show_tool_stats;
+};
+
+static int trace__set_fd_pathname(struct thread *thread, int fd, const char *pathname)
+{
+ struct thread_trace *ttrace = thread->priv;
+
+ if (fd > ttrace->paths.max) {
+ char **npath = realloc(ttrace->paths.table, (fd + 1) * sizeof(char *));
+
+ if (npath == NULL)
+ return -1;
+
+ if (ttrace->paths.max != -1) {
+ memset(npath + ttrace->paths.max + 1, 0,
+ (fd - ttrace->paths.max) * sizeof(char *));
+ } else {
+ memset(npath, 0, (fd + 1) * sizeof(char *));
+ }
+
+ ttrace->paths.table = npath;
+ ttrace->paths.max = fd;
+ }
+
+ ttrace->paths.table[fd] = strdup(pathname);
+
+ return ttrace->paths.table[fd] != NULL ? 0 : -1;
+}
+
+static int thread__read_fd_path(struct thread *thread, int fd)
+{
+ char linkname[PATH_MAX], pathname[PATH_MAX];
+ struct stat st;
+ int ret;
+
+ if (thread->pid_ == thread->tid) {
+ scnprintf(linkname, sizeof(linkname),
+ "/proc/%d/fd/%d", thread->pid_, fd);
+ } else {
+ scnprintf(linkname, sizeof(linkname),
+ "/proc/%d/task/%d/fd/%d", thread->pid_, thread->tid, fd);
+ }
+
+ if (lstat(linkname, &st) < 0 || st.st_size + 1 > (off_t)sizeof(pathname))
+ return -1;
+
+ ret = readlink(linkname, pathname, sizeof(pathname));
+
+ if (ret < 0 || ret > st.st_size)
+ return -1;
+
+ pathname[ret] = '\0';
+ return trace__set_fd_pathname(thread, fd, pathname);
+}
+
+static const char *thread__fd_path(struct thread *thread, int fd,
+ struct trace *trace)
+{
+ struct thread_trace *ttrace = thread->priv;
+
+ if (ttrace == NULL)
+ return NULL;
+
+ if (fd < 0)
+ return NULL;
+
+ if ((fd > ttrace->paths.max || ttrace->paths.table[fd] == NULL))
+ if (!trace->live)
+ return NULL;
+ ++trace->stats.proc_getname;
+ if (thread__read_fd_path(thread, fd)) {
+ return NULL;
+ }
+
+ return ttrace->paths.table[fd];
+}
+
+static size_t syscall_arg__scnprintf_fd(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ int fd = arg->val;
+ size_t printed = scnprintf(bf, size, "%d", fd);
+ const char *path = thread__fd_path(arg->thread, fd, arg->trace);
+
+ if (path)
+ printed += scnprintf(bf + printed, size - printed, "<%s>", path);
+
+ return printed;
+}
+
+static size_t syscall_arg__scnprintf_close_fd(char *bf, size_t size,
+ struct syscall_arg *arg)
+{
+ int fd = arg->val;
+ size_t printed = syscall_arg__scnprintf_fd(bf, size, arg);
+ struct thread_trace *ttrace = arg->thread->priv;
+
+ if (ttrace && fd >= 0 && fd <= ttrace->paths.max)
+ zfree(&ttrace->paths.table[fd]);
+
+ return printed;
+}
+
+static bool trace__filter_duration(struct trace *trace, double t)
+{
+ return t < (trace->duration_filter * NSEC_PER_MSEC);
+}
+
+static size_t trace__fprintf_tstamp(struct trace *trace, u64 tstamp, FILE *fp)
+{
+ double ts = (double)(tstamp - trace->base_time) / NSEC_PER_MSEC;
+
+ return fprintf(fp, "%10.3f ", ts);
+}
+
+static bool done = false;
+static bool interrupted = false;
+
+static void sig_handler(int sig)
+{
+ done = true;
+ interrupted = sig == SIGINT;
+}
+
+static size_t trace__fprintf_entry_head(struct trace *trace, struct thread *thread,
+ u64 duration, u64 tstamp, FILE *fp)
+{
+ size_t printed = trace__fprintf_tstamp(trace, tstamp, fp);
+ printed += fprintf_duration(duration, fp);
+
+ if (trace->multiple_threads) {
+ if (trace->show_comm)
+ printed += fprintf(fp, "%.14s/", thread__comm_str(thread));
+ printed += fprintf(fp, "%d ", thread->tid);
+ }
+
+ return printed;
+}
+
+static int trace__process_event(struct trace *trace, struct machine *machine,
+ union perf_event *event, struct perf_sample *sample)
+{
+ int ret = 0;
+
+ switch (event->header.type) {
+ case PERF_RECORD_LOST:
+ color_fprintf(trace->output, PERF_COLOR_RED,
+ "LOST %" PRIu64 " events!\n", event->lost.lost);
+ ret = machine__process_lost_event(machine, event, sample);
+ default:
+ ret = machine__process_event(machine, event, sample);
+ break;
+ }
+
+ return ret;
+}
+
+static int trace__tool_process(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ struct trace *trace = container_of(tool, struct trace, tool);
+ return trace__process_event(trace, machine, event, sample);
+}
+
+static int trace__symbols_init(struct trace *trace, struct perf_evlist *evlist)
+{
+ int err = symbol__init();
+
+ if (err)
+ return err;
+
+ trace->host = machine__new_host();
+ if (trace->host == NULL)
+ return -ENOMEM;
+
+ err = __machine__synthesize_threads(trace->host, &trace->tool, &trace->opts.target,
+ evlist->threads, trace__tool_process, false);
+ if (err)
+ symbol__exit();
+
+ return err;
+}
+
+static int syscall__set_arg_fmts(struct syscall *sc)
+{
+ struct format_field *field;
+ int idx = 0;
+
+ sc->arg_scnprintf = calloc(sc->tp_format->format.nr_fields - 1, sizeof(void *));
+ if (sc->arg_scnprintf == NULL)
+ return -1;
+
+ if (sc->fmt)
+ sc->arg_parm = sc->fmt->arg_parm;
+
+ for (field = sc->tp_format->format.fields->next; field; field = field->next) {
+ if (sc->fmt && sc->fmt->arg_scnprintf[idx])
+ sc->arg_scnprintf[idx] = sc->fmt->arg_scnprintf[idx];
+ else if (field->flags & FIELD_IS_POINTER)
+ sc->arg_scnprintf[idx] = syscall_arg__scnprintf_hex;
+ ++idx;
+ }
+
+ return 0;
+}
+
+static int trace__read_syscall_info(struct trace *trace, int id)
+{
+ char tp_name[128];
+ struct syscall *sc;
+ const char *name = audit_syscall_to_name(id, trace->audit.machine);
+
+ if (name == NULL)
+ return -1;
+
+ if (id > trace->syscalls.max) {
+ struct syscall *nsyscalls = realloc(trace->syscalls.table, (id + 1) * sizeof(*sc));
+
+ if (nsyscalls == NULL)
+ return -1;
+
+ if (trace->syscalls.max != -1) {
+ memset(nsyscalls + trace->syscalls.max + 1, 0,
+ (id - trace->syscalls.max) * sizeof(*sc));
+ } else {
+ memset(nsyscalls, 0, (id + 1) * sizeof(*sc));
+ }
+
+ trace->syscalls.table = nsyscalls;
+ trace->syscalls.max = id;
+ }
+
+ sc = trace->syscalls.table + id;
+ sc->name = name;
+
+ if (trace->ev_qualifier) {
+ bool in = strlist__find(trace->ev_qualifier, name) != NULL;
+
+ if (!(in ^ trace->not_ev_qualifier)) {
+ sc->filtered = true;
+ /*
+ * No need to do read tracepoint information since this will be
+ * filtered out.
+ */
+ return 0;
+ }
+ }
+
+ sc->fmt = syscall_fmt__find(sc->name);
+
+ snprintf(tp_name, sizeof(tp_name), "sys_enter_%s", sc->name);
+ sc->tp_format = trace_event__tp_format("syscalls", tp_name);
+
+ if (sc->tp_format == NULL && sc->fmt && sc->fmt->alias) {
+ snprintf(tp_name, sizeof(tp_name), "sys_enter_%s", sc->fmt->alias);
+ sc->tp_format = trace_event__tp_format("syscalls", tp_name);
+ }
+
+ if (sc->tp_format == NULL)
+ return -1;
+
+ return syscall__set_arg_fmts(sc);
+}
+
+static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size,
+ unsigned long *args, struct trace *trace,
+ struct thread *thread)
+{
+ size_t printed = 0;
+
+ if (sc->tp_format != NULL) {
+ struct format_field *field;
+ u8 bit = 1;
+ struct syscall_arg arg = {
+ .idx = 0,
+ .mask = 0,
+ .trace = trace,
+ .thread = thread,
+ };
+
+ for (field = sc->tp_format->format.fields->next; field;
+ field = field->next, ++arg.idx, bit <<= 1) {
+ if (arg.mask & bit)
+ continue;
+ /*
+ * Suppress this argument if its value is zero and
+ * and we don't have a string associated in an
+ * strarray for it.
+ */
+ if (args[arg.idx] == 0 &&
+ !(sc->arg_scnprintf &&
+ sc->arg_scnprintf[arg.idx] == SCA_STRARRAY &&
+ sc->arg_parm[arg.idx]))
+ continue;
+
+ printed += scnprintf(bf + printed, size - printed,
+ "%s%s: ", printed ? ", " : "", field->name);
+ if (sc->arg_scnprintf && sc->arg_scnprintf[arg.idx]) {
+ arg.val = args[arg.idx];
+ if (sc->arg_parm)
+ arg.parm = sc->arg_parm[arg.idx];
+ printed += sc->arg_scnprintf[arg.idx](bf + printed,
+ size - printed, &arg);
+ } else {
+ printed += scnprintf(bf + printed, size - printed,
+ "%ld", args[arg.idx]);
+ }
+ }
+ } else {
+ int i = 0;
+
+ while (i < 6) {
+ printed += scnprintf(bf + printed, size - printed,
+ "%sarg%d: %ld",
+ printed ? ", " : "", i, args[i]);
+ ++i;
+ }
+ }
+
+ return printed;
+}
+
+typedef int (*tracepoint_handler)(struct trace *trace, struct perf_evsel *evsel,
+ struct perf_sample *sample);
+
+static struct syscall *trace__syscall_info(struct trace *trace,
+ struct perf_evsel *evsel, int id)
+{
+
+ if (id < 0) {
+
+ /*
+ * XXX: Noticed on x86_64, reproduced as far back as 3.0.36, haven't tried
+ * before that, leaving at a higher verbosity level till that is
+ * explained. Reproduced with plain ftrace with:
+ *
+ * echo 1 > /t/events/raw_syscalls/sys_exit/enable
+ * grep "NR -1 " /t/trace_pipe
+ *
+ * After generating some load on the machine.
+ */
+ if (verbose > 1) {
+ static u64 n;
+ fprintf(trace->output, "Invalid syscall %d id, skipping (%s, %" PRIu64 ") ...\n",
+ id, perf_evsel__name(evsel), ++n);
+ }
+ return NULL;
+ }
+
+ if ((id > trace->syscalls.max || trace->syscalls.table[id].name == NULL) &&
+ trace__read_syscall_info(trace, id))
+ goto out_cant_read;
+
+ if ((id > trace->syscalls.max || trace->syscalls.table[id].name == NULL))
+ goto out_cant_read;
+
+ return &trace->syscalls.table[id];
+
+out_cant_read:
+ if (verbose) {
+ fprintf(trace->output, "Problems reading syscall %d", id);
+ if (id <= trace->syscalls.max && trace->syscalls.table[id].name != NULL)
+ fprintf(trace->output, "(%s)", trace->syscalls.table[id].name);
+ fputs(" information\n", trace->output);
+ }
+ return NULL;
+}
+
+static void thread__update_stats(struct thread_trace *ttrace,
+ int id, struct perf_sample *sample)
+{
+ struct int_node *inode;
+ struct stats *stats;
+ u64 duration = 0;
+
+ inode = intlist__findnew(ttrace->syscall_stats, id);
+ if (inode == NULL)
+ return;
+
+ stats = inode->priv;
+ if (stats == NULL) {
+ stats = malloc(sizeof(struct stats));
+ if (stats == NULL)
+ return;
+ init_stats(stats);
+ inode->priv = stats;
+ }
+
+ if (ttrace->entry_time && sample->time > ttrace->entry_time)
+ duration = sample->time - ttrace->entry_time;
+
+ update_stats(stats, duration);
+}
+
+static int trace__sys_enter(struct trace *trace, struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ char *msg;
+ void *args;
+ size_t printed = 0;
+ struct thread *thread;
+ int id = perf_evsel__sc_tp_uint(evsel, id, sample);
+ struct syscall *sc = trace__syscall_info(trace, evsel, id);
+ struct thread_trace *ttrace;
+
+ if (sc == NULL)
+ return -1;
+
+ if (sc->filtered)
+ return 0;
+
+ thread = machine__findnew_thread(trace->host, sample->pid, sample->tid);
+ ttrace = thread__trace(thread, trace->output);
+ if (ttrace == NULL)
+ return -1;
+
+ args = perf_evsel__sc_tp_ptr(evsel, args, sample);
+ ttrace = thread->priv;
+
+ if (ttrace->entry_str == NULL) {
+ ttrace->entry_str = malloc(1024);
+ if (!ttrace->entry_str)
+ return -1;
+ }
+
+ ttrace->entry_time = sample->time;
+ msg = ttrace->entry_str;
+ printed += scnprintf(msg + printed, 1024 - printed, "%s(", sc->name);
+
+ printed += syscall__scnprintf_args(sc, msg + printed, 1024 - printed,
+ args, trace, thread);
+
+ if (!strcmp(sc->name, "exit_group") || !strcmp(sc->name, "exit")) {
+ if (!trace->duration_filter && !trace->summary_only) {
+ trace__fprintf_entry_head(trace, thread, 1, sample->time, trace->output);
+ fprintf(trace->output, "%-70s\n", ttrace->entry_str);
+ }
+ } else
+ ttrace->entry_pending = true;
+
+ return 0;
+}
+
+static int trace__sys_exit(struct trace *trace, struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ int ret;
+ u64 duration = 0;
+ struct thread *thread;
+ int id = perf_evsel__sc_tp_uint(evsel, id, sample);
+ struct syscall *sc = trace__syscall_info(trace, evsel, id);
+ struct thread_trace *ttrace;
+
+ if (sc == NULL)
+ return -1;
+
+ if (sc->filtered)
+ return 0;
+
+ thread = machine__findnew_thread(trace->host, sample->pid, sample->tid);
+ ttrace = thread__trace(thread, trace->output);
+ if (ttrace == NULL)
+ return -1;
+
+ if (trace->summary)
+ thread__update_stats(ttrace, id, sample);
+
+ ret = perf_evsel__sc_tp_uint(evsel, ret, sample);
+
+ if (id == trace->audit.open_id && ret >= 0 && trace->last_vfs_getname) {
+ trace__set_fd_pathname(thread, ret, trace->last_vfs_getname);
+ trace->last_vfs_getname = NULL;
+ ++trace->stats.vfs_getname;
+ }
+
+ ttrace = thread->priv;
+
+ ttrace->exit_time = sample->time;
+
+ if (ttrace->entry_time) {
+ duration = sample->time - ttrace->entry_time;
+ if (trace__filter_duration(trace, duration))
+ goto out;
+ } else if (trace->duration_filter)
+ goto out;
+
+ if (trace->summary_only)
+ goto out;
+
+ trace__fprintf_entry_head(trace, thread, duration, sample->time, trace->output);
+
+ if (ttrace->entry_pending) {
+ fprintf(trace->output, "%-70s", ttrace->entry_str);
+ } else {
+ fprintf(trace->output, " ... [");
+ color_fprintf(trace->output, PERF_COLOR_YELLOW, "continued");
+ fprintf(trace->output, "]: %s()", sc->name);
+ }
+
+ if (sc->fmt == NULL) {
+signed_print:
+ fprintf(trace->output, ") = %d", ret);
+ } else if (ret < 0 && sc->fmt->errmsg) {
+ char bf[256];
+ const char *emsg = strerror_r(-ret, bf, sizeof(bf)),
+ *e = audit_errno_to_name(-ret);
+
+ fprintf(trace->output, ") = -1 %s %s", e, emsg);
+ } else if (ret == 0 && sc->fmt->timeout)
+ fprintf(trace->output, ") = 0 Timeout");
+ else if (sc->fmt->hexret)
+ fprintf(trace->output, ") = %#x", ret);
+ else
+ goto signed_print;
+
+ fputc('\n', trace->output);
+out:
+ ttrace->entry_pending = false;
+
+ return 0;
+}
+
+static int trace__vfs_getname(struct trace *trace, struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ trace->last_vfs_getname = perf_evsel__rawptr(evsel, sample, "pathname");
+ return 0;
+}
+
+static int trace__sched_stat_runtime(struct trace *trace, struct perf_evsel *evsel,
+ struct perf_sample *sample)
+{
+ u64 runtime = perf_evsel__intval(evsel, sample, "runtime");
+ double runtime_ms = (double)runtime / NSEC_PER_MSEC;
+ struct thread *thread = machine__findnew_thread(trace->host,
+ sample->pid,
+ sample->tid);
+ struct thread_trace *ttrace = thread__trace(thread, trace->output);
+
+ if (ttrace == NULL)
+ goto out_dump;
+
+ ttrace->runtime_ms += runtime_ms;
+ trace->runtime_ms += runtime_ms;
+ return 0;
+
+out_dump:
+ fprintf(trace->output, "%s: comm=%s,pid=%u,runtime=%" PRIu64 ",vruntime=%" PRIu64 ")\n",
+ evsel->name,
+ perf_evsel__strval(evsel, sample, "comm"),
+ (pid_t)perf_evsel__intval(evsel, sample, "pid"),
+ runtime,
+ perf_evsel__intval(evsel, sample, "vruntime"));
+ return 0;
+}
+
+static bool skip_sample(struct trace *trace, struct perf_sample *sample)
+{
+ if ((trace->pid_list && intlist__find(trace->pid_list, sample->pid)) ||
+ (trace->tid_list && intlist__find(trace->tid_list, sample->tid)))
+ return false;
+
+ if (trace->pid_list || trace->tid_list)
+ return true;
+
+ return false;
+}
+
+static int trace__process_sample(struct perf_tool *tool,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine __maybe_unused)
+{
+ struct trace *trace = container_of(tool, struct trace, tool);
+ int err = 0;
+
+ tracepoint_handler handler = evsel->handler;
+
+ if (skip_sample(trace, sample))
+ return 0;
+
+ if (!trace->full_time && trace->base_time == 0)
+ trace->base_time = sample->time;
+
+ if (handler) {
+ ++trace->nr_events;
+ handler(trace, evsel, sample);
+ }
+
+ return err;
+}
+
+static int parse_target_str(struct trace *trace)
+{
+ if (trace->opts.target.pid) {
+ trace->pid_list = intlist__new(trace->opts.target.pid);
+ if (trace->pid_list == NULL) {
+ pr_err("Error parsing process id string\n");
+ return -EINVAL;
+ }
+ }
+
+ if (trace->opts.target.tid) {
+ trace->tid_list = intlist__new(trace->opts.target.tid);
+ if (trace->tid_list == NULL) {
+ pr_err("Error parsing thread id string\n");
+ return -EINVAL;
+ }
+ }
+
+ return 0;
+}
+
+static int trace__record(int argc, const char **argv)
+{
+ unsigned int rec_argc, i, j;
+ const char **rec_argv;
+ const char * const record_args[] = {
+ "record",
+ "-R",
+ "-m", "1024",
+ "-c", "1",
+ "-e",
+ };
+
+ /* +1 is for the event string below */
+ rec_argc = ARRAY_SIZE(record_args) + 1 + argc;
+ rec_argv = calloc(rec_argc + 1, sizeof(char *));
+
+ if (rec_argv == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < ARRAY_SIZE(record_args); i++)
+ rec_argv[i] = record_args[i];
+
+ /* event string may be different for older kernels - e.g., RHEL6 */
+ if (is_valid_tracepoint("raw_syscalls:sys_enter"))
+ rec_argv[i] = "raw_syscalls:sys_enter,raw_syscalls:sys_exit";
+ else if (is_valid_tracepoint("syscalls:sys_enter"))
+ rec_argv[i] = "syscalls:sys_enter,syscalls:sys_exit";
+ else {
+ pr_err("Neither raw_syscalls nor syscalls events exist.\n");
+ return -1;
+ }
+ i++;
+
+ for (j = 0; j < (unsigned int)argc; j++, i++)
+ rec_argv[i] = argv[j];
+
+ return cmd_record(i, rec_argv, NULL);
+}
+
+static size_t trace__fprintf_thread_summary(struct trace *trace, FILE *fp);
+
+static void perf_evlist__add_vfs_getname(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evsel__newtp("probe", "vfs_getname");
+ if (evsel == NULL)
+ return;
+
+ if (perf_evsel__field(evsel, "pathname") == NULL) {
+ perf_evsel__delete(evsel);
+ return;
+ }
+
+ evsel->handler = trace__vfs_getname;
+ perf_evlist__add(evlist, evsel);
+}
+
+static int trace__run(struct trace *trace, int argc, const char **argv)
+{
+ struct perf_evlist *evlist = perf_evlist__new();
+ struct perf_evsel *evsel;
+ int err = -1, i;
+ unsigned long before;
+ const bool forks = argc > 0;
+
+ trace->live = true;
+
+ if (evlist == NULL) {
+ fprintf(trace->output, "Not enough memory to run!\n");
+ goto out;
+ }
+
+ if (perf_evlist__add_syscall_newtp(evlist, trace__sys_enter, trace__sys_exit))
+ goto out_error_tp;
+
+ perf_evlist__add_vfs_getname(evlist);
+
+ if (trace->sched &&
+ perf_evlist__add_newtp(evlist, "sched", "sched_stat_runtime",
+ trace__sched_stat_runtime))
+ goto out_error_tp;
+
+ err = perf_evlist__create_maps(evlist, &trace->opts.target);
+ if (err < 0) {
+ fprintf(trace->output, "Problems parsing the target to trace, check your options!\n");
+ goto out_delete_evlist;
+ }
+
+ err = trace__symbols_init(trace, evlist);
+ if (err < 0) {
+ fprintf(trace->output, "Problems initializing symbol libraries!\n");
+ goto out_delete_evlist;
+ }
+
+ perf_evlist__config(evlist, &trace->opts);
+
+ signal(SIGCHLD, sig_handler);
+ signal(SIGINT, sig_handler);
+
+ if (forks) {
+ err = perf_evlist__prepare_workload(evlist, &trace->opts.target,
+ argv, false, NULL);
+ if (err < 0) {
+ fprintf(trace->output, "Couldn't run the workload!\n");
+ goto out_delete_evlist;
+ }
+ }
+
+ err = perf_evlist__open(evlist);
+ if (err < 0)
+ goto out_error_open;
+
+ err = perf_evlist__mmap(evlist, trace->opts.mmap_pages, false);
+ if (err < 0) {
+ fprintf(trace->output, "Couldn't mmap the events: %s\n", strerror(errno));
+ goto out_delete_evlist;
+ }
+
+ perf_evlist__enable(evlist);
+
+ if (forks)
+ perf_evlist__start_workload(evlist);
+
+ trace->multiple_threads = evlist->threads->map[0] == -1 || evlist->threads->nr > 1;
+again:
+ before = trace->nr_events;
+
+ for (i = 0; i < evlist->nr_mmaps; i++) {
+ union perf_event *event;
+
+ while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) {
+ const u32 type = event->header.type;
+ tracepoint_handler handler;
+ struct perf_sample sample;
+
+ ++trace->nr_events;
+
+ err = perf_evlist__parse_sample(evlist, event, &sample);
+ if (err) {
+ fprintf(trace->output, "Can't parse sample, err = %d, skipping...\n", err);
+ goto next_event;
+ }
+
+ if (!trace->full_time && trace->base_time == 0)
+ trace->base_time = sample.time;
+
+ if (type != PERF_RECORD_SAMPLE) {
+ trace__process_event(trace, trace->host, event, &sample);
+ continue;
+ }
+
+ evsel = perf_evlist__id2evsel(evlist, sample.id);
+ if (evsel == NULL) {
+ fprintf(trace->output, "Unknown tp ID %" PRIu64 ", skipping...\n", sample.id);
+ goto next_event;
+ }
+
+ if (sample.raw_data == NULL) {
+ fprintf(trace->output, "%s sample with no payload for tid: %d, cpu %d, raw_size=%d, skipping...\n",
+ perf_evsel__name(evsel), sample.tid,
+ sample.cpu, sample.raw_size);
+ goto next_event;
+ }
+
+ handler = evsel->handler;
+ handler(trace, evsel, &sample);
+next_event:
+ perf_evlist__mmap_consume(evlist, i);
+
+ if (interrupted)
+ goto out_disable;
+ }
+ }
+
+ if (trace->nr_events == before) {
+ int timeout = done ? 100 : -1;
+
+ if (poll(evlist->pollfd, evlist->nr_fds, timeout) > 0)
+ goto again;
+ } else {
+ goto again;
+ }
+
+out_disable:
+ perf_evlist__disable(evlist);
+
+ if (!err) {
+ if (trace->summary)
+ trace__fprintf_thread_summary(trace, trace->output);
+
+ if (trace->show_tool_stats) {
+ fprintf(trace->output, "Stats:\n "
+ " vfs_getname : %" PRIu64 "\n"
+ " proc_getname: %" PRIu64 "\n",
+ trace->stats.vfs_getname,
+ trace->stats.proc_getname);
+ }
+ }
+
+out_delete_evlist:
+ perf_evlist__delete(evlist);
+out:
+ trace->live = false;
+ return err;
+{
+ char errbuf[BUFSIZ];
+
+out_error_tp:
+ perf_evlist__strerror_tp(evlist, errno, errbuf, sizeof(errbuf));
+ goto out_error;
+
+out_error_open:
+ perf_evlist__strerror_open(evlist, errno, errbuf, sizeof(errbuf));
+
+out_error:
+ fprintf(trace->output, "%s\n", errbuf);
+ goto out_delete_evlist;
+}
+}
+
+static int trace__replay(struct trace *trace)
+{
+ const struct perf_evsel_str_handler handlers[] = {
+ { "probe:vfs_getname", trace__vfs_getname, },
+ };
+ struct perf_data_file file = {
+ .path = input_name,
+ .mode = PERF_DATA_MODE_READ,
+ };
+ struct perf_session *session;
+ struct perf_evsel *evsel;
+ int err = -1;
+
+ trace->tool.sample = trace__process_sample;
+ trace->tool.mmap = perf_event__process_mmap;
+ trace->tool.mmap2 = perf_event__process_mmap2;
+ trace->tool.comm = perf_event__process_comm;
+ trace->tool.exit = perf_event__process_exit;
+ trace->tool.fork = perf_event__process_fork;
+ trace->tool.attr = perf_event__process_attr;
+ trace->tool.tracing_data = perf_event__process_tracing_data;
+ trace->tool.build_id = perf_event__process_build_id;
+
+ trace->tool.ordered_samples = true;
+ trace->tool.ordering_requires_timestamps = true;
+
+ /* add tid to output */
+ trace->multiple_threads = true;
+
+ if (symbol__init() < 0)
+ return -1;
+
+ session = perf_session__new(&file, false, &trace->tool);
+ if (session == NULL)
+ return -ENOMEM;
+
+ trace->host = &session->machines.host;
+
+ err = perf_session__set_tracepoints_handlers(session, handlers);
+ if (err)
+ goto out;
+
+ evsel = perf_evlist__find_tracepoint_by_name(session->evlist,
+ "raw_syscalls:sys_enter");
+ /* older kernels have syscalls tp versus raw_syscalls */
+ if (evsel == NULL)
+ evsel = perf_evlist__find_tracepoint_by_name(session->evlist,
+ "syscalls:sys_enter");
+ if (evsel == NULL) {
+ pr_err("Data file does not have raw_syscalls:sys_enter event\n");
+ goto out;
+ }
+
+ if (perf_evsel__init_syscall_tp(evsel, trace__sys_enter) < 0 ||
+ perf_evsel__init_sc_tp_ptr_field(evsel, args)) {
+ pr_err("Error during initialize raw_syscalls:sys_enter event\n");
+ goto out;
+ }
+
+ evsel = perf_evlist__find_tracepoint_by_name(session->evlist,
+ "raw_syscalls:sys_exit");
+ if (evsel == NULL)
+ evsel = perf_evlist__find_tracepoint_by_name(session->evlist,
+ "syscalls:sys_exit");
+ if (evsel == NULL) {
+ pr_err("Data file does not have raw_syscalls:sys_exit event\n");
+ goto out;
+ }
+
+ if (perf_evsel__init_syscall_tp(evsel, trace__sys_exit) < 0 ||
+ perf_evsel__init_sc_tp_uint_field(evsel, ret)) {
+ pr_err("Error during initialize raw_syscalls:sys_exit event\n");
+ goto out;
+ }
+
+ err = parse_target_str(trace);
+ if (err != 0)
+ goto out;
+
+ setup_pager();
+
+ err = perf_session__process_events(session, &trace->tool);
+ if (err)
+ pr_err("Failed to process events, error %d", err);
+
+ else if (trace->summary)
+ trace__fprintf_thread_summary(trace, trace->output);
+
+out:
+ perf_session__delete(session);
+
+ return err;
+}
+
+static size_t trace__fprintf_threads_header(FILE *fp)
+{
+ size_t printed;
+
+ printed = fprintf(fp, "\n Summary of events:\n\n");
+
+ return printed;
+}
+
+static size_t thread__dump_stats(struct thread_trace *ttrace,
+ struct trace *trace, FILE *fp)
+{
+ struct stats *stats;
+ size_t printed = 0;
+ struct syscall *sc;
+ struct int_node *inode = intlist__first(ttrace->syscall_stats);
+
+ if (inode == NULL)
+ return 0;
+
+ printed += fprintf(fp, "\n");
+
+ printed += fprintf(fp, " syscall calls min avg max stddev\n");
+ printed += fprintf(fp, " (msec) (msec) (msec) (%%)\n");
+ printed += fprintf(fp, " --------------- -------- --------- --------- --------- ------\n");
+
+ /* each int_node is a syscall */
+ while (inode) {
+ stats = inode->priv;
+ if (stats) {
+ double min = (double)(stats->min) / NSEC_PER_MSEC;
+ double max = (double)(stats->max) / NSEC_PER_MSEC;
+ double avg = avg_stats(stats);
+ double pct;
+ u64 n = (u64) stats->n;
+
+ pct = avg ? 100.0 * stddev_stats(stats)/avg : 0.0;
+ avg /= NSEC_PER_MSEC;
+
+ sc = &trace->syscalls.table[inode->i];
+ printed += fprintf(fp, " %-15s", sc->name);
+ printed += fprintf(fp, " %8" PRIu64 " %9.3f %9.3f",
+ n, min, avg);
+ printed += fprintf(fp, " %9.3f %9.2f%%\n", max, pct);
+ }
+
+ inode = intlist__next(inode);
+ }
+
+ printed += fprintf(fp, "\n\n");
+
+ return printed;
+}
+
+/* struct used to pass data to per-thread function */
+struct summary_data {
+ FILE *fp;
+ struct trace *trace;
+ size_t printed;
+};
+
+static int trace__fprintf_one_thread(struct thread *thread, void *priv)
+{
+ struct summary_data *data = priv;
+ FILE *fp = data->fp;
+ size_t printed = data->printed;
+ struct trace *trace = data->trace;
+ struct thread_trace *ttrace = thread->priv;
+ double ratio;
+
+ if (ttrace == NULL)
+ return 0;
+
+ ratio = (double)ttrace->nr_events / trace->nr_events * 100.0;
+
+ printed += fprintf(fp, " %s (%d), ", thread__comm_str(thread), thread->tid);
+ printed += fprintf(fp, "%lu events, ", ttrace->nr_events);
+ printed += fprintf(fp, "%.1f%%", ratio);
+ printed += fprintf(fp, ", %.3f msec\n", ttrace->runtime_ms);
+ printed += thread__dump_stats(ttrace, trace, fp);
+
+ data->printed += printed;
+
+ return 0;
+}
+
+static size_t trace__fprintf_thread_summary(struct trace *trace, FILE *fp)
+{
+ struct summary_data data = {
+ .fp = fp,
+ .trace = trace
+ };
+ data.printed = trace__fprintf_threads_header(fp);
+
+ machine__for_each_thread(trace->host, trace__fprintf_one_thread, &data);
+
+ return data.printed;
+}
+
+static int trace__set_duration(const struct option *opt, const char *str,
+ int unset __maybe_unused)
+{
+ struct trace *trace = opt->value;
+
+ trace->duration_filter = atof(str);
+ return 0;
+}
+
+static int trace__open_output(struct trace *trace, const char *filename)
+{
+ struct stat st;
+
+ if (!stat(filename, &st) && st.st_size) {
+ char oldname[PATH_MAX];
+
+ scnprintf(oldname, sizeof(oldname), "%s.old", filename);
+ unlink(oldname);
+ rename(filename, oldname);
+ }
+
+ trace->output = fopen(filename, "w");
+
+ return trace->output == NULL ? -errno : 0;
+}
+
+int cmd_trace(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ const char * const trace_usage[] = {
+ "perf trace [<options>] [<command>]",
+ "perf trace [<options>] -- <command> [<options>]",
+ "perf trace record [<options>] [<command>]",
+ "perf trace record [<options>] -- <command> [<options>]",
+ NULL
+ };
+ struct trace trace = {
+ .audit = {
+ .machine = audit_detect_machine(),
+ .open_id = audit_name_to_syscall("open", trace.audit.machine),
+ },
+ .syscalls = {
+ . max = -1,
+ },
+ .opts = {
+ .target = {
+ .uid = UINT_MAX,
+ .uses_mmap = true,
+ },
+ .user_freq = UINT_MAX,
+ .user_interval = ULLONG_MAX,
+ .no_buffering = true,
+ .mmap_pages = 1024,
+ },
+ .output = stdout,
+ .show_comm = true,
+ };
+ const char *output_name = NULL;
+ const char *ev_qualifier_str = NULL;
+ const struct option trace_options[] = {
+ OPT_BOOLEAN(0, "comm", &trace.show_comm,
+ "show the thread COMM next to its id"),
+ OPT_BOOLEAN(0, "tool_stats", &trace.show_tool_stats, "show tool stats"),
+ OPT_STRING('e', "expr", &ev_qualifier_str, "expr",
+ "list of events to trace"),
+ OPT_STRING('o', "output", &output_name, "file", "output file name"),
+ OPT_STRING('i', "input", &input_name, "file", "Analyze events in file"),
+ OPT_STRING('p', "pid", &trace.opts.target.pid, "pid",
+ "trace events on existing process id"),
+ OPT_STRING('t', "tid", &trace.opts.target.tid, "tid",
+ "trace events on existing thread id"),
+ OPT_BOOLEAN('a', "all-cpus", &trace.opts.target.system_wide,
+ "system-wide collection from all CPUs"),
+ OPT_STRING('C', "cpu", &trace.opts.target.cpu_list, "cpu",
+ "list of cpus to monitor"),
+ OPT_BOOLEAN(0, "no-inherit", &trace.opts.no_inherit,
+ "child tasks do not inherit counters"),
+ OPT_CALLBACK('m', "mmap-pages", &trace.opts.mmap_pages, "pages",
+ "number of mmap data pages",
+ perf_evlist__parse_mmap_pages),
+ OPT_STRING('u', "uid", &trace.opts.target.uid_str, "user",
+ "user to profile"),
+ OPT_CALLBACK(0, "duration", &trace, "float",
+ "show only events with duration > N.M ms",
+ trace__set_duration),
+ OPT_BOOLEAN(0, "sched", &trace.sched, "show blocking scheduler events"),
+ OPT_INCR('v', "verbose", &verbose, "be more verbose"),
+ OPT_BOOLEAN('T', "time", &trace.full_time,
+ "Show full timestamp, not time relative to first start"),
+ OPT_BOOLEAN('s', "summary", &trace.summary_only,
+ "Show only syscall summary with statistics"),
+ OPT_BOOLEAN('S', "with-summary", &trace.summary,
+ "Show all syscalls and summary with statistics"),
+ OPT_END()
+ };
+ int err;
+ char bf[BUFSIZ];
+
+ if ((argc > 1) && (strcmp(argv[1], "record") == 0))
+ return trace__record(argc-2, &argv[2]);
+
+ argc = parse_options(argc, argv, trace_options, trace_usage, 0);
+
+ /* summary_only implies summary option, but don't overwrite summary if set */
+ if (trace.summary_only)
+ trace.summary = trace.summary_only;
+
+ if (output_name != NULL) {
+ err = trace__open_output(&trace, output_name);
+ if (err < 0) {
+ perror("failed to create output file");
+ goto out;
+ }
+ }
+
+ if (ev_qualifier_str != NULL) {
+ const char *s = ev_qualifier_str;
+
+ trace.not_ev_qualifier = *s == '!';
+ if (trace.not_ev_qualifier)
+ ++s;
+ trace.ev_qualifier = strlist__new(true, s);
+ if (trace.ev_qualifier == NULL) {
+ fputs("Not enough memory to parse event qualifier",
+ trace.output);
+ err = -ENOMEM;
+ goto out_close;
+ }
+ }
+
+ err = target__validate(&trace.opts.target);
+ if (err) {
+ target__strerror(&trace.opts.target, err, bf, sizeof(bf));
+ fprintf(trace.output, "%s", bf);
+ goto out_close;
+ }
+
+ err = target__parse_uid(&trace.opts.target);
+ if (err) {
+ target__strerror(&trace.opts.target, err, bf, sizeof(bf));
+ fprintf(trace.output, "%s", bf);
+ goto out_close;
+ }
+
+ if (!argc && target__none(&trace.opts.target))
+ trace.opts.target.system_wide = true;
+
+ if (input_name)
+ err = trace__replay(&trace);
+ else
+ err = trace__run(&trace, argc, argv);
+
+out_close:
+ if (output_name != NULL)
+ fclose(trace.output);
+out:
+ return err;
+}
diff --git a/tools/perf/builtin.h b/tools/perf/builtin.h
new file mode 100644
index 00000000000..b210d62907e
--- /dev/null
+++ b/tools/perf/builtin.h
@@ -0,0 +1,42 @@
+#ifndef BUILTIN_H
+#define BUILTIN_H
+
+#include "util/util.h"
+#include "util/strbuf.h"
+
+extern const char perf_usage_string[];
+extern const char perf_more_info_string[];
+
+extern void list_common_cmds_help(void);
+extern const char *help_unknown_cmd(const char *cmd);
+extern void prune_packed_objects(int);
+extern int read_line_with_nul(char *buf, int size, FILE *file);
+extern int check_pager_config(const char *cmd);
+
+extern int cmd_annotate(int argc, const char **argv, const char *prefix);
+extern int cmd_bench(int argc, const char **argv, const char *prefix);
+extern int cmd_buildid_cache(int argc, const char **argv, const char *prefix);
+extern int cmd_buildid_list(int argc, const char **argv, const char *prefix);
+extern int cmd_diff(int argc, const char **argv, const char *prefix);
+extern int cmd_evlist(int argc, const char **argv, const char *prefix);
+extern int cmd_help(int argc, const char **argv, const char *prefix);
+extern int cmd_sched(int argc, const char **argv, const char *prefix);
+extern int cmd_list(int argc, const char **argv, const char *prefix);
+extern int cmd_record(int argc, const char **argv, const char *prefix);
+extern int cmd_report(int argc, const char **argv, const char *prefix);
+extern int cmd_stat(int argc, const char **argv, const char *prefix);
+extern int cmd_timechart(int argc, const char **argv, const char *prefix);
+extern int cmd_top(int argc, const char **argv, const char *prefix);
+extern int cmd_script(int argc, const char **argv, const char *prefix);
+extern int cmd_version(int argc, const char **argv, const char *prefix);
+extern int cmd_probe(int argc, const char **argv, const char *prefix);
+extern int cmd_kmem(int argc, const char **argv, const char *prefix);
+extern int cmd_lock(int argc, const char **argv, const char *prefix);
+extern int cmd_kvm(int argc, const char **argv, const char *prefix);
+extern int cmd_test(int argc, const char **argv, const char *prefix);
+extern int cmd_trace(int argc, const char **argv, const char *prefix);
+extern int cmd_inject(int argc, const char **argv, const char *prefix);
+extern int cmd_mem(int argc, const char **argv, const char *prefix);
+
+extern int find_scripts(char **scripts_array, char **scripts_path_array);
+#endif
diff --git a/tools/perf/command-list.txt b/tools/perf/command-list.txt
new file mode 100644
index 00000000000..0906fc401c5
--- /dev/null
+++ b/tools/perf/command-list.txt
@@ -0,0 +1,27 @@
+#
+# List of known perf commands.
+# command name category [deprecated] [common]
+#
+perf-annotate mainporcelain common
+perf-archive mainporcelain common
+perf-bench mainporcelain common
+perf-buildid-cache mainporcelain common
+perf-buildid-list mainporcelain common
+perf-diff mainporcelain common
+perf-evlist mainporcelain common
+perf-inject mainporcelain common
+perf-kmem mainporcelain common
+perf-kvm mainporcelain common
+perf-list mainporcelain common
+perf-lock mainporcelain common
+perf-mem mainporcelain common
+perf-probe mainporcelain full
+perf-record mainporcelain common
+perf-report mainporcelain common
+perf-sched mainporcelain common
+perf-script mainporcelain common
+perf-stat mainporcelain common
+perf-test mainporcelain common
+perf-timechart mainporcelain common
+perf-top mainporcelain common
+perf-trace mainporcelain common
diff --git a/tools/perf/config/Makefile b/tools/perf/config/Makefile
new file mode 100644
index 00000000000..f30ac5e5d27
--- /dev/null
+++ b/tools/perf/config/Makefile
@@ -0,0 +1,738 @@
+
+ifeq ($(src-perf),)
+src-perf := $(srctree)/tools/perf
+endif
+
+ifeq ($(obj-perf),)
+obj-perf := $(OUTPUT)
+endif
+
+ifneq ($(obj-perf),)
+obj-perf := $(abspath $(obj-perf))/
+endif
+
+LIB_INCLUDE := $(srctree)/tools/lib/
+CFLAGS := $(EXTRA_CFLAGS) $(EXTRA_WARNINGS)
+
+include $(src-perf)/config/Makefile.arch
+
+NO_PERF_REGS := 1
+
+# Additional ARCH settings for x86
+ifeq ($(ARCH),x86)
+ ifeq (${IS_X86_64}, 1)
+ CFLAGS += -DHAVE_ARCH_X86_64_SUPPORT
+ ARCH_INCLUDE = ../../arch/x86/lib/memcpy_64.S ../../arch/x86/lib/memset_64.S
+ LIBUNWIND_LIBS = -lunwind -lunwind-x86_64
+ else
+ LIBUNWIND_LIBS = -lunwind -lunwind-x86
+ endif
+ NO_PERF_REGS := 0
+endif
+
+ifeq ($(ARCH),arm)
+ NO_PERF_REGS := 0
+ LIBUNWIND_LIBS = -lunwind -lunwind-arm
+endif
+
+ifeq ($(ARCH),arm64)
+ NO_PERF_REGS := 0
+ LIBUNWIND_LIBS = -lunwind -lunwind-aarch64
+endif
+
+# So far there's only x86 and arm libdw unwind support merged in perf.
+# Disable it on all other architectures in case libdw unwind
+# support is detected in system. Add supported architectures
+# to the check.
+ifneq ($(ARCH),$(filter $(ARCH),x86 arm))
+ NO_LIBDW_DWARF_UNWIND := 1
+endif
+
+ifeq ($(LIBUNWIND_LIBS),)
+ NO_LIBUNWIND := 1
+else
+ #
+ # For linking with debug library, run like:
+ #
+ # make DEBUG=1 LIBUNWIND_DIR=/opt/libunwind/
+ #
+ ifdef LIBUNWIND_DIR
+ LIBUNWIND_CFLAGS = -I$(LIBUNWIND_DIR)/include
+ LIBUNWIND_LDFLAGS = -L$(LIBUNWIND_DIR)/lib
+ endif
+ LIBUNWIND_LDFLAGS += $(LIBUNWIND_LIBS)
+
+ # Set per-feature check compilation flags
+ FEATURE_CHECK_CFLAGS-libunwind = $(LIBUNWIND_CFLAGS)
+ FEATURE_CHECK_LDFLAGS-libunwind = $(LIBUNWIND_LDFLAGS)
+ FEATURE_CHECK_CFLAGS-libunwind-debug-frame = $(LIBUNWIND_CFLAGS)
+ FEATURE_CHECK_LDFLAGS-libunwind-debug-frame = $(LIBUNWIND_LDFLAGS)
+endif
+
+ifeq ($(NO_PERF_REGS),0)
+ CFLAGS += -DHAVE_PERF_REGS_SUPPORT
+endif
+
+ifndef NO_LIBELF
+ # for linking with debug library, run like:
+ # make DEBUG=1 LIBDW_DIR=/opt/libdw/
+ ifdef LIBDW_DIR
+ LIBDW_CFLAGS := -I$(LIBDW_DIR)/include
+ LIBDW_LDFLAGS := -L$(LIBDW_DIR)/lib
+ endif
+ FEATURE_CHECK_CFLAGS-libdw-dwarf-unwind := $(LIBDW_CFLAGS)
+ FEATURE_CHECK_LDFLAGS-libdw-dwarf-unwind := $(LIBDW_LDFLAGS) -ldw
+endif
+
+# include ARCH specific config
+-include $(src-perf)/arch/$(ARCH)/Makefile
+
+include $(src-perf)/config/utilities.mak
+
+ifeq ($(call get-executable,$(FLEX)),)
+ dummy := $(error Error: $(FLEX) is missing on this system, please install it)
+endif
+
+ifeq ($(call get-executable,$(BISON)),)
+ dummy := $(error Error: $(BISON) is missing on this system, please install it)
+endif
+
+# Treat warnings as errors unless directed not to
+ifneq ($(WERROR),0)
+ CFLAGS += -Werror
+endif
+
+ifndef DEBUG
+ DEBUG := 0
+endif
+
+ifeq ($(DEBUG),0)
+ CFLAGS += -O6
+endif
+
+ifdef PARSER_DEBUG
+ PARSER_DEBUG_BISON := -t
+ PARSER_DEBUG_FLEX := -d
+ CFLAGS += -DPARSER_DEBUG
+endif
+
+CFLAGS += -fno-omit-frame-pointer
+CFLAGS += -ggdb3
+CFLAGS += -funwind-tables
+CFLAGS += -Wall
+CFLAGS += -Wextra
+CFLAGS += -std=gnu99
+
+# Enforce a non-executable stack, as we may regress (again) in the future by
+# adding assembler files missing the .GNU-stack linker note.
+LDFLAGS += -Wl,-z,noexecstack
+
+EXTLIBS = -lelf -lpthread -lrt -lm -ldl
+
+ifneq ($(OUTPUT),)
+ OUTPUT_FEATURES = $(OUTPUT)config/feature-checks/
+ $(shell mkdir -p $(OUTPUT_FEATURES))
+endif
+
+feature_check = $(eval $(feature_check_code))
+define feature_check_code
+ feature-$(1) := $(shell $(MAKE) OUTPUT=$(OUTPUT_FEATURES) CFLAGS="$(EXTRA_CFLAGS) $(FEATURE_CHECK_CFLAGS-$(1))" LDFLAGS="$(LDFLAGS) $(FEATURE_CHECK_LDFLAGS-$(1))" -C config/feature-checks test-$1.bin >/dev/null 2>/dev/null && echo 1 || echo 0)
+endef
+
+feature_set = $(eval $(feature_set_code))
+define feature_set_code
+ feature-$(1) := 1
+endef
+
+#
+# Build the feature check binaries in parallel, ignore errors, ignore return value and suppress output:
+#
+
+#
+# Note that this is not a complete list of all feature tests, just
+# those that are typically built on a fully configured system.
+#
+# [ Feature tests not mentioned here have to be built explicitly in
+# the rule that uses them - an example for that is the 'bionic'
+# feature check. ]
+#
+CORE_FEATURE_TESTS = \
+ backtrace \
+ dwarf \
+ fortify-source \
+ glibc \
+ gtk2 \
+ gtk2-infobar \
+ libaudit \
+ libbfd \
+ libelf \
+ libelf-getphdrnum \
+ libelf-mmap \
+ libnuma \
+ libperl \
+ libpython \
+ libpython-version \
+ libslang \
+ libunwind \
+ stackprotector-all \
+ timerfd \
+ libdw-dwarf-unwind
+
+LIB_FEATURE_TESTS = \
+ dwarf \
+ glibc \
+ gtk2 \
+ libaudit \
+ libbfd \
+ libelf \
+ libnuma \
+ libperl \
+ libpython \
+ libslang \
+ libunwind \
+ libdw-dwarf-unwind
+
+VF_FEATURE_TESTS = \
+ backtrace \
+ fortify-source \
+ gtk2-infobar \
+ libelf-getphdrnum \
+ libelf-mmap \
+ libpython-version \
+ stackprotector-all \
+ timerfd \
+ libunwind-debug-frame \
+ bionic \
+ liberty \
+ liberty-z \
+ cplus-demangle
+
+# Set FEATURE_CHECK_(C|LD)FLAGS-all for all CORE_FEATURE_TESTS features.
+# If in the future we need per-feature checks/flags for features not
+# mentioned in this list we need to refactor this ;-).
+set_test_all_flags = $(eval $(set_test_all_flags_code))
+define set_test_all_flags_code
+ FEATURE_CHECK_CFLAGS-all += $(FEATURE_CHECK_CFLAGS-$(1))
+ FEATURE_CHECK_LDFLAGS-all += $(FEATURE_CHECK_LDFLAGS-$(1))
+endef
+
+$(foreach feat,$(CORE_FEATURE_TESTS),$(call set_test_all_flags,$(feat)))
+
+#
+# Special fast-path for the 'all features are available' case:
+#
+$(call feature_check,all,$(MSG))
+
+#
+# Just in case the build freshly failed, make sure we print the
+# feature matrix:
+#
+ifeq ($(feature-all), 1)
+ #
+ # test-all.c passed - just set all the core feature flags to 1:
+ #
+ $(foreach feat,$(CORE_FEATURE_TESTS),$(call feature_set,$(feat)))
+else
+ $(shell $(MAKE) OUTPUT=$(OUTPUT_FEATURES) CFLAGS="$(EXTRA_CFLAGS)" LDFLAGS=$(LDFLAGS) -i -j -C config/feature-checks $(addsuffix .bin,$(CORE_FEATURE_TESTS)) >/dev/null 2>&1)
+ $(foreach feat,$(CORE_FEATURE_TESTS),$(call feature_check,$(feat)))
+endif
+
+ifeq ($(feature-stackprotector-all), 1)
+ CFLAGS += -fstack-protector-all
+endif
+
+ifeq ($(DEBUG),0)
+ ifeq ($(feature-fortify-source), 1)
+ CFLAGS += -D_FORTIFY_SOURCE=2
+ endif
+endif
+
+CFLAGS += -I$(src-perf)/util/include
+CFLAGS += -I$(src-perf)/arch/$(ARCH)/include
+CFLAGS += -I$(srctree)/tools/include/
+CFLAGS += -I$(srctree)/arch/$(ARCH)/include/uapi
+CFLAGS += -I$(srctree)/arch/$(ARCH)/include
+CFLAGS += -I$(srctree)/include/uapi
+CFLAGS += -I$(srctree)/include
+
+# $(obj-perf) for generated common-cmds.h
+# $(obj-perf)/util for generated bison/flex headers
+ifneq ($(OUTPUT),)
+CFLAGS += -I$(obj-perf)/util
+CFLAGS += -I$(obj-perf)
+endif
+
+CFLAGS += -I$(src-perf)/util
+CFLAGS += -I$(src-perf)
+CFLAGS += -I$(LIB_INCLUDE)
+
+CFLAGS += -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE
+
+ifndef NO_BIONIC
+ $(call feature_check,bionic)
+ ifeq ($(feature-bionic), 1)
+ BIONIC := 1
+ EXTLIBS := $(filter-out -lrt,$(EXTLIBS))
+ EXTLIBS := $(filter-out -lpthread,$(EXTLIBS))
+ endif
+endif
+
+ifdef NO_LIBELF
+ NO_DWARF := 1
+ NO_DEMANGLE := 1
+ NO_LIBUNWIND := 1
+ NO_LIBDW_DWARF_UNWIND := 1
+else
+ ifeq ($(feature-libelf), 0)
+ ifeq ($(feature-glibc), 1)
+ LIBC_SUPPORT := 1
+ endif
+ ifeq ($(BIONIC),1)
+ LIBC_SUPPORT := 1
+ endif
+ ifeq ($(LIBC_SUPPORT),1)
+ msg := $(warning No libelf found, disables 'probe' tool, please install elfutils-libelf-devel/libelf-dev);
+
+ NO_LIBELF := 1
+ NO_DWARF := 1
+ NO_DEMANGLE := 1
+ NO_LIBUNWIND := 1
+ NO_LIBDW_DWARF_UNWIND := 1
+ else
+ ifneq ($(filter s% -static%,$(LDFLAGS),),)
+ msg := $(error No static glibc found, please install glibc-static);
+ else
+ msg := $(error No gnu/libc-version.h found, please install glibc-dev[el]);
+ endif
+ endif
+ else
+ ifndef NO_LIBDW_DWARF_UNWIND
+ ifneq ($(feature-libdw-dwarf-unwind),1)
+ NO_LIBDW_DWARF_UNWIND := 1
+ msg := $(warning No libdw DWARF unwind found, Please install elfutils-devel/libdw-dev >= 0.158 and/or set LIBDW_DIR);
+ endif
+ endif
+ ifneq ($(feature-dwarf), 1)
+ msg := $(warning No libdw.h found or old libdw.h found or elfutils is older than 0.138, disables dwarf support. Please install new elfutils-devel/libdw-dev);
+ NO_DWARF := 1
+ endif # Dwarf support
+ endif # libelf support
+endif # NO_LIBELF
+
+ifndef NO_LIBELF
+ CFLAGS += -DHAVE_LIBELF_SUPPORT
+
+ ifeq ($(feature-libelf-mmap), 1)
+ CFLAGS += -DHAVE_LIBELF_MMAP_SUPPORT
+ endif
+
+ ifeq ($(feature-libelf-getphdrnum), 1)
+ CFLAGS += -DHAVE_ELF_GETPHDRNUM_SUPPORT
+ endif
+
+ # include ARCH specific config
+ -include $(src-perf)/arch/$(ARCH)/Makefile
+
+ ifndef NO_DWARF
+ ifeq ($(origin PERF_HAVE_DWARF_REGS), undefined)
+ msg := $(warning DWARF register mappings have not been defined for architecture $(ARCH), DWARF support disabled);
+ NO_DWARF := 1
+ else
+ CFLAGS += -DHAVE_DWARF_SUPPORT $(LIBDW_CFLAGS)
+ LDFLAGS += $(LIBDW_LDFLAGS)
+ EXTLIBS += -lelf -ldw
+ endif # PERF_HAVE_DWARF_REGS
+ endif # NO_DWARF
+endif # NO_LIBELF
+
+ifndef NO_LIBUNWIND
+ ifneq ($(feature-libunwind), 1)
+ msg := $(warning No libunwind found. Please install libunwind-dev[el] >= 1.1 and/or set LIBUNWIND_DIR);
+ NO_LIBUNWIND := 1
+ endif
+endif
+
+dwarf-post-unwind := 1
+dwarf-post-unwind-text := BUG
+
+# setup DWARF post unwinder
+ifdef NO_LIBUNWIND
+ ifdef NO_LIBDW_DWARF_UNWIND
+ msg := $(warning Disabling post unwind, no support found.);
+ dwarf-post-unwind := 0
+ else
+ dwarf-post-unwind-text := libdw
+ endif
+else
+ dwarf-post-unwind-text := libunwind
+ # Enable libunwind support by default.
+ ifndef NO_LIBDW_DWARF_UNWIND
+ NO_LIBDW_DWARF_UNWIND := 1
+ endif
+endif
+
+ifeq ($(dwarf-post-unwind),1)
+ CFLAGS += -DHAVE_DWARF_UNWIND_SUPPORT
+else
+ NO_DWARF_UNWIND := 1
+endif
+
+ifndef NO_LIBUNWIND
+ ifeq ($(ARCH),$(filter $(ARCH),arm arm64))
+ $(call feature_check,libunwind-debug-frame)
+ ifneq ($(feature-libunwind-debug-frame), 1)
+ msg := $(warning No debug_frame support found in libunwind);
+ CFLAGS += -DNO_LIBUNWIND_DEBUG_FRAME
+ endif
+ else
+ # non-ARM has no dwarf_find_debug_frame() function:
+ CFLAGS += -DNO_LIBUNWIND_DEBUG_FRAME
+ endif
+ CFLAGS += -DHAVE_LIBUNWIND_SUPPORT
+ EXTLIBS += $(LIBUNWIND_LIBS)
+ CFLAGS += $(LIBUNWIND_CFLAGS)
+ LDFLAGS += $(LIBUNWIND_LDFLAGS)
+endif
+
+ifndef NO_LIBAUDIT
+ ifneq ($(feature-libaudit), 1)
+ msg := $(warning No libaudit.h found, disables 'trace' tool, please install audit-libs-devel or libaudit-dev);
+ NO_LIBAUDIT := 1
+ else
+ CFLAGS += -DHAVE_LIBAUDIT_SUPPORT
+ EXTLIBS += -laudit
+ endif
+endif
+
+ifdef NO_NEWT
+ NO_SLANG=1
+endif
+
+ifndef NO_SLANG
+ ifneq ($(feature-libslang), 1)
+ msg := $(warning slang not found, disables TUI support. Please install slang-devel or libslang-dev);
+ NO_SLANG := 1
+ else
+ # Fedora has /usr/include/slang/slang.h, but ubuntu /usr/include/slang.h
+ CFLAGS += -I/usr/include/slang
+ CFLAGS += -DHAVE_SLANG_SUPPORT
+ EXTLIBS += -lslang
+ endif
+endif
+
+ifndef NO_GTK2
+ FLAGS_GTK2=$(CFLAGS) $(LDFLAGS) $(EXTLIBS) $(shell $(PKG_CONFIG) --libs --cflags gtk+-2.0 2>/dev/null)
+ ifneq ($(feature-gtk2), 1)
+ msg := $(warning GTK2 not found, disables GTK2 support. Please install gtk2-devel or libgtk2.0-dev);
+ NO_GTK2 := 1
+ else
+ ifeq ($(feature-gtk2-infobar), 1)
+ GTK_CFLAGS := -DHAVE_GTK_INFO_BAR_SUPPORT
+ endif
+ CFLAGS += -DHAVE_GTK2_SUPPORT
+ GTK_CFLAGS += $(shell $(PKG_CONFIG) --cflags gtk+-2.0 2>/dev/null)
+ GTK_LIBS := $(shell $(PKG_CONFIG) --libs gtk+-2.0 2>/dev/null)
+ EXTLIBS += -ldl
+ endif
+endif
+
+grep-libs = $(filter -l%,$(1))
+strip-libs = $(filter-out -l%,$(1))
+
+ifdef NO_LIBPERL
+ CFLAGS += -DNO_LIBPERL
+else
+ PERL_EMBED_LDOPTS = $(shell perl -MExtUtils::Embed -e ldopts 2>/dev/null)
+ PERL_EMBED_LDFLAGS = $(call strip-libs,$(PERL_EMBED_LDOPTS))
+ PERL_EMBED_LIBADD = $(call grep-libs,$(PERL_EMBED_LDOPTS))
+ PERL_EMBED_CCOPTS = `perl -MExtUtils::Embed -e ccopts 2>/dev/null`
+ FLAGS_PERL_EMBED=$(PERL_EMBED_CCOPTS) $(PERL_EMBED_LDOPTS)
+
+ ifneq ($(feature-libperl), 1)
+ CFLAGS += -DNO_LIBPERL
+ NO_LIBPERL := 1
+ msg := $(warning Missing perl devel files. Disabling perl scripting support, consider installing perl-ExtUtils-Embed);
+ else
+ LDFLAGS += $(PERL_EMBED_LDFLAGS)
+ EXTLIBS += $(PERL_EMBED_LIBADD)
+ endif
+endif
+
+ifeq ($(feature-timerfd), 1)
+ CFLAGS += -DHAVE_TIMERFD_SUPPORT
+else
+ msg := $(warning No timerfd support. Disables 'perf kvm stat live');
+endif
+
+disable-python = $(eval $(disable-python_code))
+define disable-python_code
+ CFLAGS += -DNO_LIBPYTHON
+ $(if $(1),$(warning No $(1) was found))
+ $(warning Python support will not be built)
+ NO_LIBPYTHON := 1
+endef
+
+override PYTHON := \
+ $(call get-executable-or-default,PYTHON,python)
+
+ifndef PYTHON
+ $(call disable-python,python interpreter)
+else
+
+ PYTHON_WORD := $(call shell-wordify,$(PYTHON))
+
+ ifdef NO_LIBPYTHON
+ $(call disable-python)
+ else
+
+ override PYTHON_CONFIG := \
+ $(call get-executable-or-default,PYTHON_CONFIG,$(PYTHON)-config)
+
+ ifndef PYTHON_CONFIG
+ $(call disable-python,python-config tool)
+ else
+
+ PYTHON_CONFIG_SQ := $(call shell-sq,$(PYTHON_CONFIG))
+
+ PYTHON_EMBED_LDOPTS := $(shell $(PYTHON_CONFIG_SQ) --ldflags 2>/dev/null)
+ PYTHON_EMBED_LDFLAGS := $(call strip-libs,$(PYTHON_EMBED_LDOPTS))
+ PYTHON_EMBED_LIBADD := $(call grep-libs,$(PYTHON_EMBED_LDOPTS))
+ PYTHON_EMBED_CCOPTS := $(shell $(PYTHON_CONFIG_SQ) --cflags 2>/dev/null)
+ FLAGS_PYTHON_EMBED := $(PYTHON_EMBED_CCOPTS) $(PYTHON_EMBED_LDOPTS)
+
+ ifneq ($(feature-libpython), 1)
+ $(call disable-python,Python.h (for Python 2.x))
+ else
+
+ ifneq ($(feature-libpython-version), 1)
+ $(warning Python 3 is not yet supported; please set)
+ $(warning PYTHON and/or PYTHON_CONFIG appropriately.)
+ $(warning If you also have Python 2 installed, then)
+ $(warning try something like:)
+ $(warning $(and ,))
+ $(warning $(and ,) make PYTHON=python2)
+ $(warning $(and ,))
+ $(warning Otherwise, disable Python support entirely:)
+ $(warning $(and ,))
+ $(warning $(and ,) make NO_LIBPYTHON=1)
+ $(warning $(and ,))
+ $(error $(and ,))
+ else
+ LDFLAGS += $(PYTHON_EMBED_LDFLAGS)
+ EXTLIBS += $(PYTHON_EMBED_LIBADD)
+ LANG_BINDINGS += $(obj-perf)python/perf.so
+ endif
+ endif
+ endif
+ endif
+endif
+
+ifeq ($(feature-libbfd), 1)
+ EXTLIBS += -lbfd
+
+ # call all detections now so we get correct
+ # status in VF output
+ $(call feature_check,liberty)
+ $(call feature_check,liberty-z)
+ $(call feature_check,cplus-demangle)
+
+ ifeq ($(feature-liberty), 1)
+ EXTLIBS += -liberty
+ else
+ ifeq ($(feature-liberty-z), 1)
+ EXTLIBS += -liberty -lz
+ endif
+ endif
+endif
+
+ifdef NO_DEMANGLE
+ CFLAGS += -DNO_DEMANGLE
+else
+ ifdef HAVE_CPLUS_DEMANGLE_SUPPORT
+ EXTLIBS += -liberty
+ CFLAGS += -DHAVE_CPLUS_DEMANGLE_SUPPORT
+ else
+ ifneq ($(feature-libbfd), 1)
+ ifneq ($(feature-liberty), 1)
+ ifneq ($(feature-liberty-z), 1)
+ # we dont have neither HAVE_CPLUS_DEMANGLE_SUPPORT
+ # or any of 'bfd iberty z' trinity
+ ifeq ($(feature-cplus-demangle), 1)
+ EXTLIBS += -liberty
+ CFLAGS += -DHAVE_CPLUS_DEMANGLE_SUPPORT
+ else
+ msg := $(warning No bfd.h/libbfd found, install binutils-dev[el]/zlib-static to gain symbol demangling)
+ CFLAGS += -DNO_DEMANGLE
+ endif
+ endif
+ endif
+ endif
+ endif
+endif
+
+ifneq ($(filter -lbfd,$(EXTLIBS)),)
+ CFLAGS += -DHAVE_LIBBFD_SUPPORT
+endif
+
+ifndef NO_BACKTRACE
+ ifeq ($(feature-backtrace), 1)
+ CFLAGS += -DHAVE_BACKTRACE_SUPPORT
+ endif
+endif
+
+ifndef NO_LIBNUMA
+ ifeq ($(feature-libnuma), 0)
+ msg := $(warning No numa.h found, disables 'perf bench numa mem' benchmark, please install numactl-devel/libnuma-devel/libnuma-dev);
+ NO_LIBNUMA := 1
+ else
+ CFLAGS += -DHAVE_LIBNUMA_SUPPORT
+ EXTLIBS += -lnuma
+ endif
+endif
+
+# Among the variables below, these:
+# perfexecdir
+# template_dir
+# mandir
+# infodir
+# htmldir
+# ETC_PERFCONFIG (but not sysconfdir)
+# can be specified as a relative path some/where/else;
+# this is interpreted as relative to $(prefix) and "perf" at
+# runtime figures out where they are based on the path to the executable.
+# This can help installing the suite in a relocatable way.
+
+# Make the path relative to DESTDIR, not to prefix
+ifndef DESTDIR
+prefix ?= $(HOME)
+endif
+bindir_relative = bin
+bindir = $(prefix)/$(bindir_relative)
+mandir = share/man
+infodir = share/info
+perfexecdir = libexec/perf-core
+sharedir = $(prefix)/share
+template_dir = share/perf-core/templates
+htmldir = share/doc/perf-doc
+ifeq ($(prefix),/usr)
+sysconfdir = /etc
+ETC_PERFCONFIG = $(sysconfdir)/perfconfig
+else
+sysconfdir = $(prefix)/etc
+ETC_PERFCONFIG = etc/perfconfig
+endif
+ifeq ($(IS_X86_64),1)
+lib = lib64
+else
+lib = lib
+endif
+libdir = $(prefix)/$(lib)
+
+# Shell quote (do not use $(call) to accommodate ancient setups);
+ETC_PERFCONFIG_SQ = $(subst ','\'',$(ETC_PERFCONFIG))
+DESTDIR_SQ = $(subst ','\'',$(DESTDIR))
+bindir_SQ = $(subst ','\'',$(bindir))
+mandir_SQ = $(subst ','\'',$(mandir))
+infodir_SQ = $(subst ','\'',$(infodir))
+perfexecdir_SQ = $(subst ','\'',$(perfexecdir))
+template_dir_SQ = $(subst ','\'',$(template_dir))
+htmldir_SQ = $(subst ','\'',$(htmldir))
+prefix_SQ = $(subst ','\'',$(prefix))
+sysconfdir_SQ = $(subst ','\'',$(sysconfdir))
+libdir_SQ = $(subst ','\'',$(libdir))
+
+ifneq ($(filter /%,$(firstword $(perfexecdir))),)
+perfexec_instdir = $(perfexecdir)
+else
+perfexec_instdir = $(prefix)/$(perfexecdir)
+endif
+perfexec_instdir_SQ = $(subst ','\'',$(perfexec_instdir))
+
+# If we install to $(HOME) we keep the traceevent default:
+# $(HOME)/.traceevent/plugins
+# Otherwise we install plugins into the global $(libdir).
+ifdef DESTDIR
+plugindir=$(libdir)/traceevent/plugins
+plugindir_SQ= $(subst ','\'',$(plugindir))
+endif
+
+#
+# Print the result of the feature test:
+#
+feature_print_status = $(eval $(feature_print_status_code)) $(info $(MSG))
+
+define feature_print_status_code
+ ifeq ($(feature-$(1)), 1)
+ MSG = $(shell printf '...%30s: [ \033[32mon\033[m ]' $(1))
+ else
+ MSG = $(shell printf '...%30s: [ \033[31mOFF\033[m ]' $(1))
+ endif
+endef
+
+feature_print_var = $(eval $(feature_print_var_code)) $(info $(MSG))
+define feature_print_var_code
+ MSG = $(shell printf '...%30s: %s' $(1) $($(1)))
+endef
+
+feature_print_text = $(eval $(feature_print_text_code)) $(info $(MSG))
+define feature_print_text_code
+ MSG = $(shell printf '...%30s: %s' $(1) $(2))
+endef
+
+PERF_FEATURES := $(foreach feat,$(LIB_FEATURE_TESTS),feature-$(feat)($(feature-$(feat))))
+PERF_FEATURES_FILE := $(shell touch $(OUTPUT)PERF-FEATURES; cat $(OUTPUT)PERF-FEATURES)
+
+ifeq ($(dwarf-post-unwind),1)
+ PERF_FEATURES += dwarf-post-unwind($(dwarf-post-unwind-text))
+endif
+
+# The $(display_lib) controls the default detection message
+# output. It's set if:
+# - detected features differes from stored features from
+# last build (in PERF-FEATURES file)
+# - one of the $(LIB_FEATURE_TESTS) is not detected
+# - VF is enabled
+
+ifneq ("$(PERF_FEATURES)","$(PERF_FEATURES_FILE)")
+ $(shell echo "$(PERF_FEATURES)" > $(OUTPUT)PERF-FEATURES)
+ display_lib := 1
+endif
+
+feature_check = $(eval $(feature_check_code))
+define feature_check_code
+ ifneq ($(feature-$(1)), 1)
+ display_lib := 1
+ endif
+endef
+
+$(foreach feat,$(LIB_FEATURE_TESTS),$(call feature_check,$(feat)))
+
+ifeq ($(VF),1)
+ display_lib := 1
+ display_vf := 1
+endif
+
+ifeq ($(display_lib),1)
+ $(info )
+ $(info Auto-detecting system features:)
+ $(foreach feat,$(LIB_FEATURE_TESTS),$(call feature_print_status,$(feat),))
+
+ ifeq ($(dwarf-post-unwind),1)
+ $(call feature_print_text,"DWARF post unwind library", $(dwarf-post-unwind-text))
+ endif
+endif
+
+ifeq ($(display_vf),1)
+ $(foreach feat,$(VF_FEATURE_TESTS),$(call feature_print_status,$(feat),))
+ $(info )
+ $(call feature_print_var,prefix)
+ $(call feature_print_var,bindir)
+ $(call feature_print_var,libdir)
+ $(call feature_print_var,sysconfdir)
+ $(call feature_print_var,LIBUNWIND_DIR)
+ $(call feature_print_var,LIBDW_DIR)
+endif
+
+ifeq ($(display_lib),1)
+ $(info )
+endif
diff --git a/tools/perf/config/Makefile.arch b/tools/perf/config/Makefile.arch
new file mode 100644
index 00000000000..4b06719ee98
--- /dev/null
+++ b/tools/perf/config/Makefile.arch
@@ -0,0 +1,23 @@
+
+uname_M := $(shell uname -m 2>/dev/null || echo not)
+
+ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/ -e s/sun4u/sparc64/ \
+ -e s/arm.*/arm/ -e s/sa110/arm/ \
+ -e s/s390x/s390/ -e s/parisc64/parisc/ \
+ -e s/ppc.*/powerpc/ -e s/mips.*/mips/ \
+ -e s/sh[234].*/sh/ -e s/aarch64.*/arm64/ \
+ -e s/tile.*/tile/ )
+
+# Additional ARCH settings for x86
+ifeq ($(ARCH),i386)
+ override ARCH := x86
+endif
+
+ifeq ($(ARCH),x86_64)
+ override ARCH := x86
+ IS_X86_64 := 0
+ ifeq (, $(findstring m32,$(CFLAGS)))
+ IS_X86_64 := $(shell echo __x86_64__ | ${CC} -E -x c - | tail -n 1)
+ RAW_ARCH := x86_64
+ endif
+endif
diff --git a/tools/perf/config/feature-checks/.gitignore b/tools/perf/config/feature-checks/.gitignore
new file mode 100644
index 00000000000..80f3da0c351
--- /dev/null
+++ b/tools/perf/config/feature-checks/.gitignore
@@ -0,0 +1,2 @@
+*.d
+*.bin
diff --git a/tools/perf/config/feature-checks/Makefile b/tools/perf/config/feature-checks/Makefile
new file mode 100644
index 00000000000..64c84e5f051
--- /dev/null
+++ b/tools/perf/config/feature-checks/Makefile
@@ -0,0 +1,149 @@
+
+FILES= \
+ test-all.bin \
+ test-backtrace.bin \
+ test-bionic.bin \
+ test-dwarf.bin \
+ test-fortify-source.bin \
+ test-glibc.bin \
+ test-gtk2.bin \
+ test-gtk2-infobar.bin \
+ test-hello.bin \
+ test-libaudit.bin \
+ test-libbfd.bin \
+ test-liberty.bin \
+ test-liberty-z.bin \
+ test-cplus-demangle.bin \
+ test-libelf.bin \
+ test-libelf-getphdrnum.bin \
+ test-libelf-mmap.bin \
+ test-libnuma.bin \
+ test-libperl.bin \
+ test-libpython.bin \
+ test-libpython-version.bin \
+ test-libslang.bin \
+ test-libunwind.bin \
+ test-libunwind-debug-frame.bin \
+ test-stackprotector-all.bin \
+ test-timerfd.bin \
+ test-libdw-dwarf-unwind.bin
+
+CC := $(CROSS_COMPILE)gcc -MD
+PKG_CONFIG := $(CROSS_COMPILE)pkg-config
+
+all: $(FILES)
+
+BUILD = $(CC) $(CFLAGS) -o $(OUTPUT)$@ $(patsubst %.bin,%.c,$@) $(LDFLAGS)
+
+###############################
+
+test-all.bin:
+ $(BUILD) -Werror -fstack-protector-all -O2 -Werror -D_FORTIFY_SOURCE=2 -ldw -lelf -lnuma -lelf -laudit -I/usr/include/slang -lslang $(shell $(PKG_CONFIG) --libs --cflags gtk+-2.0 2>/dev/null) $(FLAGS_PERL_EMBED) $(FLAGS_PYTHON_EMBED) -DPACKAGE='"perf"' -lbfd -ldl
+
+test-hello.bin:
+ $(BUILD)
+
+test-stackprotector-all.bin:
+ $(BUILD) -Werror -fstack-protector-all
+
+test-fortify-source.bin:
+ $(BUILD) -O2 -Werror -D_FORTIFY_SOURCE=2
+
+test-bionic.bin:
+ $(BUILD)
+
+test-libelf.bin:
+ $(BUILD) -lelf
+
+test-glibc.bin:
+ $(BUILD)
+
+test-dwarf.bin:
+ $(BUILD) -ldw
+
+test-libelf-mmap.bin:
+ $(BUILD) -lelf
+
+test-libelf-getphdrnum.bin:
+ $(BUILD) -lelf
+
+test-libnuma.bin:
+ $(BUILD) -lnuma
+
+test-libunwind.bin:
+ $(BUILD) -lelf
+
+test-libunwind-debug-frame.bin:
+ $(BUILD) -lelf
+
+test-libaudit.bin:
+ $(BUILD) -laudit
+
+test-libslang.bin:
+ $(BUILD) -I/usr/include/slang -lslang
+
+test-gtk2.bin:
+ $(BUILD) $(shell $(PKG_CONFIG) --libs --cflags gtk+-2.0 2>/dev/null)
+
+test-gtk2-infobar.bin:
+ $(BUILD) $(shell $(PKG_CONFIG) --libs --cflags gtk+-2.0 2>/dev/null)
+
+grep-libs = $(filter -l%,$(1))
+strip-libs = $(filter-out -l%,$(1))
+
+PERL_EMBED_LDOPTS = $(shell perl -MExtUtils::Embed -e ldopts 2>/dev/null)
+PERL_EMBED_LDFLAGS = $(call strip-libs,$(PERL_EMBED_LDOPTS))
+PERL_EMBED_LIBADD = $(call grep-libs,$(PERL_EMBED_LDOPTS))
+PERL_EMBED_CCOPTS = `perl -MExtUtils::Embed -e ccopts 2>/dev/null`
+FLAGS_PERL_EMBED=$(PERL_EMBED_CCOPTS) $(PERL_EMBED_LDOPTS)
+
+test-libperl.bin:
+ $(BUILD) $(FLAGS_PERL_EMBED)
+
+override PYTHON := python
+override PYTHON_CONFIG := python-config
+
+escape-for-shell-sq = $(subst ','\'',$(1))
+shell-sq = '$(escape-for-shell-sq)'
+
+PYTHON_CONFIG_SQ = $(call shell-sq,$(PYTHON_CONFIG))
+
+PYTHON_EMBED_LDOPTS = $(shell $(PYTHON_CONFIG_SQ) --ldflags 2>/dev/null)
+PYTHON_EMBED_LDFLAGS = $(call strip-libs,$(PYTHON_EMBED_LDOPTS))
+PYTHON_EMBED_LIBADD = $(call grep-libs,$(PYTHON_EMBED_LDOPTS))
+PYTHON_EMBED_CCOPTS = $(shell $(PYTHON_CONFIG_SQ) --cflags 2>/dev/null)
+FLAGS_PYTHON_EMBED = $(PYTHON_EMBED_CCOPTS) $(PYTHON_EMBED_LDOPTS)
+
+test-libpython.bin:
+ $(BUILD) $(FLAGS_PYTHON_EMBED)
+
+test-libpython-version.bin:
+ $(BUILD) $(FLAGS_PYTHON_EMBED)
+
+test-libbfd.bin:
+ $(BUILD) -DPACKAGE='"perf"' -lbfd -lz -liberty -ldl
+
+test-liberty.bin:
+ $(CC) -o $(OUTPUT)$@ test-libbfd.c -DPACKAGE='"perf"' -lbfd -ldl -liberty
+
+test-liberty-z.bin:
+ $(CC) -o $(OUTPUT)$@ test-libbfd.c -DPACKAGE='"perf"' -lbfd -ldl -liberty -lz
+
+test-cplus-demangle.bin:
+ $(BUILD) -liberty
+
+test-backtrace.bin:
+ $(BUILD)
+
+test-timerfd.bin:
+ $(BUILD)
+
+test-libdw-dwarf-unwind.bin:
+ $(BUILD)
+
+-include *.d
+
+###############################
+
+clean:
+ rm -f $(FILES) *.d
diff --git a/tools/perf/config/feature-checks/test-all.c b/tools/perf/config/feature-checks/test-all.c
new file mode 100644
index 00000000000..fe5c1e5c952
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-all.c
@@ -0,0 +1,116 @@
+/*
+ * test-all.c: Try to build all the main testcases at once.
+ *
+ * A well-configured system will have all the prereqs installed, so we can speed
+ * up auto-detection on such systems.
+ */
+
+/*
+ * Quirk: Python and Perl headers cannot be in arbitrary places, so keep
+ * these 3 testcases at the top:
+ */
+#define main main_test_libpython
+# include "test-libpython.c"
+#undef main
+
+#define main main_test_libpython_version
+# include "test-libpython-version.c"
+#undef main
+
+#define main main_test_libperl
+# include "test-libperl.c"
+#undef main
+
+#define main main_test_hello
+# include "test-hello.c"
+#undef main
+
+#define main main_test_libelf
+# include "test-libelf.c"
+#undef main
+
+#define main main_test_libelf_mmap
+# include "test-libelf-mmap.c"
+#undef main
+
+#define main main_test_glibc
+# include "test-glibc.c"
+#undef main
+
+#define main main_test_dwarf
+# include "test-dwarf.c"
+#undef main
+
+#define main main_test_libelf_getphdrnum
+# include "test-libelf-getphdrnum.c"
+#undef main
+
+#define main main_test_libunwind
+# include "test-libunwind.c"
+#undef main
+
+#define main main_test_libaudit
+# include "test-libaudit.c"
+#undef main
+
+#define main main_test_libslang
+# include "test-libslang.c"
+#undef main
+
+#define main main_test_gtk2
+# include "test-gtk2.c"
+#undef main
+
+#define main main_test_gtk2_infobar
+# include "test-gtk2-infobar.c"
+#undef main
+
+#define main main_test_libbfd
+# include "test-libbfd.c"
+#undef main
+
+#define main main_test_backtrace
+# include "test-backtrace.c"
+#undef main
+
+#define main main_test_libnuma
+# include "test-libnuma.c"
+#undef main
+
+#define main main_test_timerfd
+# include "test-timerfd.c"
+#undef main
+
+#define main main_test_stackprotector_all
+# include "test-stackprotector-all.c"
+#undef main
+
+#define main main_test_libdw_dwarf_unwind
+# include "test-libdw-dwarf-unwind.c"
+#undef main
+
+int main(int argc, char *argv[])
+{
+ main_test_libpython();
+ main_test_libpython_version();
+ main_test_libperl();
+ main_test_hello();
+ main_test_libelf();
+ main_test_libelf_mmap();
+ main_test_glibc();
+ main_test_dwarf();
+ main_test_libelf_getphdrnum();
+ main_test_libunwind();
+ main_test_libaudit();
+ main_test_libslang();
+ main_test_gtk2(argc, argv);
+ main_test_gtk2_infobar(argc, argv);
+ main_test_libbfd();
+ main_test_backtrace();
+ main_test_libnuma();
+ main_test_timerfd();
+ main_test_stackprotector_all();
+ main_test_libdw_dwarf_unwind();
+
+ return 0;
+}
diff --git a/tools/perf/config/feature-checks/test-backtrace.c b/tools/perf/config/feature-checks/test-backtrace.c
new file mode 100644
index 00000000000..7124aa1dc8f
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-backtrace.c
@@ -0,0 +1,13 @@
+#include <execinfo.h>
+#include <stdio.h>
+
+int main(void)
+{
+ void *backtrace_fns[10];
+ size_t entries;
+
+ entries = backtrace(backtrace_fns, 10);
+ backtrace_symbols_fd(backtrace_fns, entries, 1);
+
+ return 0;
+}
diff --git a/tools/perf/config/feature-checks/test-bionic.c b/tools/perf/config/feature-checks/test-bionic.c
new file mode 100644
index 00000000000..eac24e9513e
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-bionic.c
@@ -0,0 +1,6 @@
+#include <android/api-level.h>
+
+int main(void)
+{
+ return __ANDROID_API__;
+}
diff --git a/tools/perf/config/feature-checks/test-cplus-demangle.c b/tools/perf/config/feature-checks/test-cplus-demangle.c
new file mode 100644
index 00000000000..610c686e000
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-cplus-demangle.c
@@ -0,0 +1,14 @@
+extern int printf(const char *format, ...);
+extern char *cplus_demangle(const char *, int);
+
+int main(void)
+{
+ char symbol[4096] = "FieldName__9ClassNameFd";
+ char *tmp;
+
+ tmp = cplus_demangle(symbol, 0);
+
+ printf("demangled symbol: {%s}\n", tmp);
+
+ return 0;
+}
diff --git a/tools/perf/config/feature-checks/test-dwarf.c b/tools/perf/config/feature-checks/test-dwarf.c
new file mode 100644
index 00000000000..3fc1801ce4a
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-dwarf.c
@@ -0,0 +1,10 @@
+#include <dwarf.h>
+#include <elfutils/libdw.h>
+#include <elfutils/version.h>
+
+int main(void)
+{
+ Dwarf *dbg = dwarf_begin(0, DWARF_C_READ);
+
+ return (long)dbg;
+}
diff --git a/tools/perf/config/feature-checks/test-fortify-source.c b/tools/perf/config/feature-checks/test-fortify-source.c
new file mode 100644
index 00000000000..c9f398d8786
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-fortify-source.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(void)
+{
+ return puts("hi");
+}
diff --git a/tools/perf/config/feature-checks/test-glibc.c b/tools/perf/config/feature-checks/test-glibc.c
new file mode 100644
index 00000000000..b0820345cd9
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-glibc.c
@@ -0,0 +1,8 @@
+#include <gnu/libc-version.h>
+
+int main(void)
+{
+ const char *version = gnu_get_libc_version();
+
+ return (long)version;
+}
diff --git a/tools/perf/config/feature-checks/test-gtk2-infobar.c b/tools/perf/config/feature-checks/test-gtk2-infobar.c
new file mode 100644
index 00000000000..397b4646d06
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-gtk2-infobar.c
@@ -0,0 +1,11 @@
+#pragma GCC diagnostic ignored "-Wstrict-prototypes"
+#include <gtk/gtk.h>
+#pragma GCC diagnostic error "-Wstrict-prototypes"
+
+int main(int argc, char *argv[])
+{
+ gtk_init(&argc, &argv);
+ gtk_info_bar_new();
+
+ return 0;
+}
diff --git a/tools/perf/config/feature-checks/test-gtk2.c b/tools/perf/config/feature-checks/test-gtk2.c
new file mode 100644
index 00000000000..6bd80e50943
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-gtk2.c
@@ -0,0 +1,10 @@
+#pragma GCC diagnostic ignored "-Wstrict-prototypes"
+#include <gtk/gtk.h>
+#pragma GCC diagnostic error "-Wstrict-prototypes"
+
+int main(int argc, char *argv[])
+{
+ gtk_init(&argc, &argv);
+
+ return 0;
+}
diff --git a/tools/perf/config/feature-checks/test-hello.c b/tools/perf/config/feature-checks/test-hello.c
new file mode 100644
index 00000000000..c9f398d8786
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-hello.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(void)
+{
+ return puts("hi");
+}
diff --git a/tools/perf/config/feature-checks/test-libaudit.c b/tools/perf/config/feature-checks/test-libaudit.c
new file mode 100644
index 00000000000..afc019f0864
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-libaudit.c
@@ -0,0 +1,10 @@
+#include <libaudit.h>
+
+extern int printf(const char *format, ...);
+
+int main(void)
+{
+ printf("error message: %s\n", audit_errno_to_name(0));
+
+ return audit_open();
+}
diff --git a/tools/perf/config/feature-checks/test-libbfd.c b/tools/perf/config/feature-checks/test-libbfd.c
new file mode 100644
index 00000000000..24059907e99
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-libbfd.c
@@ -0,0 +1,15 @@
+#include <bfd.h>
+
+extern int printf(const char *format, ...);
+
+int main(void)
+{
+ char symbol[4096] = "FieldName__9ClassNameFd";
+ char *tmp;
+
+ tmp = bfd_demangle(0, symbol, 0);
+
+ printf("demangled symbol: {%s}\n", tmp);
+
+ return 0;
+}
diff --git a/tools/perf/config/feature-checks/test-libdw-dwarf-unwind.c b/tools/perf/config/feature-checks/test-libdw-dwarf-unwind.c
new file mode 100644
index 00000000000..f676a3ff442
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-libdw-dwarf-unwind.c
@@ -0,0 +1,13 @@
+
+#include <elfutils/libdwfl.h>
+
+int main(void)
+{
+ /*
+ * This function is guarded via: __nonnull_attribute__ (1, 2).
+ * Passing '1' as arguments value. This code is never executed,
+ * only compiled.
+ */
+ dwfl_thread_getframes((void *) 1, (void *) 1, NULL);
+ return 0;
+}
diff --git a/tools/perf/config/feature-checks/test-libelf-getphdrnum.c b/tools/perf/config/feature-checks/test-libelf-getphdrnum.c
new file mode 100644
index 00000000000..d710459306c
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-libelf-getphdrnum.c
@@ -0,0 +1,8 @@
+#include <libelf.h>
+
+int main(void)
+{
+ size_t dst;
+
+ return elf_getphdrnum(0, &dst);
+}
diff --git a/tools/perf/config/feature-checks/test-libelf-mmap.c b/tools/perf/config/feature-checks/test-libelf-mmap.c
new file mode 100644
index 00000000000..564427d7ef1
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-libelf-mmap.c
@@ -0,0 +1,8 @@
+#include <libelf.h>
+
+int main(void)
+{
+ Elf *elf = elf_begin(0, ELF_C_READ_MMAP, 0);
+
+ return (long)elf;
+}
diff --git a/tools/perf/config/feature-checks/test-libelf.c b/tools/perf/config/feature-checks/test-libelf.c
new file mode 100644
index 00000000000..08db322d895
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-libelf.c
@@ -0,0 +1,8 @@
+#include <libelf.h>
+
+int main(void)
+{
+ Elf *elf = elf_begin(0, ELF_C_READ, 0);
+
+ return (long)elf;
+}
diff --git a/tools/perf/config/feature-checks/test-libnuma.c b/tools/perf/config/feature-checks/test-libnuma.c
new file mode 100644
index 00000000000..4763d9cd587
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-libnuma.c
@@ -0,0 +1,9 @@
+#include <numa.h>
+#include <numaif.h>
+
+int main(void)
+{
+ numa_available();
+
+ return 0;
+}
diff --git a/tools/perf/config/feature-checks/test-libperl.c b/tools/perf/config/feature-checks/test-libperl.c
new file mode 100644
index 00000000000..8871f6a0fdb
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-libperl.c
@@ -0,0 +1,9 @@
+#include <EXTERN.h>
+#include <perl.h>
+
+int main(void)
+{
+ perl_alloc();
+
+ return 0;
+}
diff --git a/tools/perf/config/feature-checks/test-libpython-version.c b/tools/perf/config/feature-checks/test-libpython-version.c
new file mode 100644
index 00000000000..facea122d81
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-libpython-version.c
@@ -0,0 +1,10 @@
+#include <Python.h>
+
+#if PY_VERSION_HEX >= 0x03000000
+ #error
+#endif
+
+int main(void)
+{
+ return 0;
+}
diff --git a/tools/perf/config/feature-checks/test-libpython.c b/tools/perf/config/feature-checks/test-libpython.c
new file mode 100644
index 00000000000..b24b28ad632
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-libpython.c
@@ -0,0 +1,8 @@
+#include <Python.h>
+
+int main(void)
+{
+ Py_Initialize();
+
+ return 0;
+}
diff --git a/tools/perf/config/feature-checks/test-libslang.c b/tools/perf/config/feature-checks/test-libslang.c
new file mode 100644
index 00000000000..22ff22ed94d
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-libslang.c
@@ -0,0 +1,6 @@
+#include <slang.h>
+
+int main(void)
+{
+ return SLsmg_init_smg();
+}
diff --git a/tools/perf/config/feature-checks/test-libunwind-debug-frame.c b/tools/perf/config/feature-checks/test-libunwind-debug-frame.c
new file mode 100644
index 00000000000..0ef8087a104
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-libunwind-debug-frame.c
@@ -0,0 +1,16 @@
+#include <libunwind.h>
+#include <stdlib.h>
+
+extern int
+UNW_OBJ(dwarf_find_debug_frame) (int found, unw_dyn_info_t *di_debug,
+ unw_word_t ip, unw_word_t segbase,
+ const char *obj_name, unw_word_t start,
+ unw_word_t end);
+
+#define dwarf_find_debug_frame UNW_OBJ(dwarf_find_debug_frame)
+
+int main(void)
+{
+ dwarf_find_debug_frame(0, NULL, 0, 0, NULL, 0, 0);
+ return 0;
+}
diff --git a/tools/perf/config/feature-checks/test-libunwind.c b/tools/perf/config/feature-checks/test-libunwind.c
new file mode 100644
index 00000000000..43b9369bcab
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-libunwind.c
@@ -0,0 +1,27 @@
+#include <libunwind.h>
+#include <stdlib.h>
+
+extern int UNW_OBJ(dwarf_search_unwind_table) (unw_addr_space_t as,
+ unw_word_t ip,
+ unw_dyn_info_t *di,
+ unw_proc_info_t *pi,
+ int need_unwind_info, void *arg);
+
+
+#define dwarf_search_unwind_table UNW_OBJ(dwarf_search_unwind_table)
+
+static unw_accessors_t accessors;
+
+int main(void)
+{
+ unw_addr_space_t addr_space;
+
+ addr_space = unw_create_addr_space(&accessors, 0);
+ if (addr_space)
+ return 0;
+
+ unw_init_remote(NULL, addr_space, NULL);
+ dwarf_search_unwind_table(addr_space, 0, NULL, NULL, 0, NULL);
+
+ return 0;
+}
diff --git a/tools/perf/config/feature-checks/test-stackprotector-all.c b/tools/perf/config/feature-checks/test-stackprotector-all.c
new file mode 100644
index 00000000000..c9f398d8786
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-stackprotector-all.c
@@ -0,0 +1,6 @@
+#include <stdio.h>
+
+int main(void)
+{
+ return puts("hi");
+}
diff --git a/tools/perf/config/feature-checks/test-timerfd.c b/tools/perf/config/feature-checks/test-timerfd.c
new file mode 100644
index 00000000000..8c5c083b4d3
--- /dev/null
+++ b/tools/perf/config/feature-checks/test-timerfd.c
@@ -0,0 +1,18 @@
+/*
+ * test for timerfd functions used by perf-kvm-stat-live
+ */
+#include <sys/timerfd.h>
+
+int main(void)
+{
+ struct itimerspec new_value;
+
+ int fd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
+ if (fd < 0)
+ return 1;
+
+ if (timerfd_settime(fd, 0, &new_value, NULL) != 0)
+ return 1;
+
+ return 0;
+}
diff --git a/tools/perf/config/utilities.mak b/tools/perf/config/utilities.mak
new file mode 100644
index 00000000000..4d985e0f03f
--- /dev/null
+++ b/tools/perf/config/utilities.mak
@@ -0,0 +1,180 @@
+# This allows us to work with the newline character:
+define newline
+
+
+endef
+newline := $(newline)
+
+# nl-escape
+#
+# Usage: escape = $(call nl-escape[,escape])
+#
+# This is used as the common way to specify
+# what should replace a newline when escaping
+# newlines; the default is a bizarre string.
+#
+nl-escape = $(if $(1),$(1),m822df3020w6a44id34bt574ctac44eb9f4n)
+
+# escape-nl
+#
+# Usage: escaped-text = $(call escape-nl,text[,escape])
+#
+# GNU make's $(shell ...) function converts to a
+# single space each newline character in the output
+# produced during the expansion; this may not be
+# desirable.
+#
+# The only solution is to change each newline into
+# something that won't be converted, so that the
+# information can be recovered later with
+# $(call unescape-nl...)
+#
+escape-nl = $(subst $(newline),$(call nl-escape,$(2)),$(1))
+
+# unescape-nl
+#
+# Usage: text = $(call unescape-nl,escaped-text[,escape])
+#
+# See escape-nl.
+#
+unescape-nl = $(subst $(call nl-escape,$(2)),$(newline),$(1))
+
+# shell-escape-nl
+#
+# Usage: $(shell some-command | $(call shell-escape-nl[,escape]))
+#
+# Use this to escape newlines from within a shell call;
+# the default escape is a bizarre string.
+#
+# NOTE: The escape is used directly as a string constant
+# in an `awk' program that is delimited by shell
+# single-quotes, so be wary of the characters
+# that are chosen.
+#
+define shell-escape-nl
+awk 'NR==1 {t=$$0} NR>1 {t=t "$(nl-escape)" $$0} END {printf t}'
+endef
+
+# shell-unescape-nl
+#
+# Usage: $(shell some-command | $(call shell-unescape-nl[,escape]))
+#
+# Use this to unescape newlines from within a shell call;
+# the default escape is a bizarre string.
+#
+# NOTE: The escape is used directly as an extended regular
+# expression constant in an `awk' program that is
+# delimited by shell single-quotes, so be wary
+# of the characters that are chosen.
+#
+# (The bash shell has a bug where `{gsub(...),...}' is
+# misinterpreted as a brace expansion; this can be
+# overcome by putting a space between `{' and `gsub').
+#
+define shell-unescape-nl
+awk 'NR==1 {t=$$0} NR>1 {t=t "\n" $$0} END { gsub(/$(nl-escape)/,"\n",t); printf t }'
+endef
+
+# escape-for-shell-sq
+#
+# Usage: embeddable-text = $(call escape-for-shell-sq,text)
+#
+# This function produces text that is suitable for
+# embedding in a shell string that is delimited by
+# single-quotes.
+#
+escape-for-shell-sq = $(subst ','\'',$(1))
+
+# shell-sq
+#
+# Usage: single-quoted-and-escaped-text = $(call shell-sq,text)
+#
+shell-sq = '$(escape-for-shell-sq)'
+
+# shell-wordify
+#
+# Usage: wordified-text = $(call shell-wordify,text)
+#
+# For instance:
+#
+# |define text
+# |hello
+# |world
+# |endef
+# |
+# |target:
+# | echo $(call shell-wordify,$(text))
+#
+# At least GNU make gets confused by expanding a newline
+# within the context of a command line of a makefile rule
+# (this is in constrast to a `$(shell ...)' function call,
+# which can handle it just fine).
+#
+# This function avoids the problem by producing a string
+# that works as a shell word, regardless of whether or
+# not it contains a newline.
+#
+# If the text to be wordified contains a newline, then
+# an intrictate shell command substitution is constructed
+# to render the text as a single line; when the shell
+# processes the resulting escaped text, it transforms
+# it into the original unescaped text.
+#
+# If the text does not contain a newline, then this function
+# produces the same results as the `$(shell-sq)' function.
+#
+shell-wordify = $(if $(findstring $(newline),$(1)),$(_sw-esc-nl),$(shell-sq))
+define _sw-esc-nl
+"$$(echo $(call escape-nl,$(shell-sq),$(2)) | $(call shell-unescape-nl,$(2)))"
+endef
+
+# is-absolute
+#
+# Usage: bool-value = $(call is-absolute,path)
+#
+is-absolute = $(shell echo $(shell-sq) | grep ^/ -q && echo y)
+
+# lookup
+#
+# Usage: absolute-executable-path-or-empty = $(call lookup,path)
+#
+# (It's necessary to use `sh -c' because GNU make messes up by
+# trying too hard and getting things wrong).
+#
+lookup = $(call unescape-nl,$(shell sh -c $(_l-sh)))
+_l-sh = $(call shell-sq,command -v $(shell-sq) | $(call shell-escape-nl,))
+
+# is-executable
+#
+# Usage: bool-value = $(call is-executable,path)
+#
+# (It's necessary to use `sh -c' because GNU make messes up by
+# trying too hard and getting things wrong).
+#
+is-executable = $(call _is-executable-helper,$(shell-sq))
+_is-executable-helper = $(shell sh -c $(_is-executable-sh))
+_is-executable-sh = $(call shell-sq,test -f $(1) -a -x $(1) && echo y)
+
+# get-executable
+#
+# Usage: absolute-executable-path-or-empty = $(call get-executable,path)
+#
+# The goal is to get an absolute path for an executable;
+# the `command -v' is defined by POSIX, but it's not
+# necessarily very portable, so it's only used if
+# relative path resolution is requested, as determined
+# by the presence of a leading `/'.
+#
+get-executable = $(if $(1),$(if $(is-absolute),$(_ge-abspath),$(lookup)))
+_ge-abspath = $(if $(is-executable),$(1))
+
+# get-supplied-or-default-executable
+#
+# Usage: absolute-executable-path-or-empty = $(call get-executable-or-default,variable,default)
+#
+define get-executable-or-default
+$(if $($(1)),$(call _ge_attempt,$($(1)),$(1)),$(call _ge_attempt,$(2)))
+endef
+_ge_attempt = $(if $(get-executable),$(get-executable),$(_gea_warn)$(call _gea_err,$(2)))
+_gea_warn = $(warning The path '$(1)' is not executable.)
+_gea_err = $(if $(1),$(error Please set '$(1)' appropriately))
diff --git a/tools/perf/design.txt b/tools/perf/design.txt
new file mode 100644
index 00000000000..a28dca2582a
--- /dev/null
+++ b/tools/perf/design.txt
@@ -0,0 +1,462 @@
+
+Performance Counters for Linux
+------------------------------
+
+Performance counters are special hardware registers available on most modern
+CPUs. These registers count the number of certain types of hw events: such
+as instructions executed, cachemisses suffered, or branches mis-predicted -
+without slowing down the kernel or applications. These registers can also
+trigger interrupts when a threshold number of events have passed - and can
+thus be used to profile the code that runs on that CPU.
+
+The Linux Performance Counter subsystem provides an abstraction of these
+hardware capabilities. It provides per task and per CPU counters, counter
+groups, and it provides event capabilities on top of those. It
+provides "virtual" 64-bit counters, regardless of the width of the
+underlying hardware counters.
+
+Performance counters are accessed via special file descriptors.
+There's one file descriptor per virtual counter used.
+
+The special file descriptor is opened via the sys_perf_event_open()
+system call:
+
+ int sys_perf_event_open(struct perf_event_attr *hw_event_uptr,
+ pid_t pid, int cpu, int group_fd,
+ unsigned long flags);
+
+The syscall returns the new fd. The fd can be used via the normal
+VFS system calls: read() can be used to read the counter, fcntl()
+can be used to set the blocking mode, etc.
+
+Multiple counters can be kept open at a time, and the counters
+can be poll()ed.
+
+When creating a new counter fd, 'perf_event_attr' is:
+
+struct perf_event_attr {
+ /*
+ * The MSB of the config word signifies if the rest contains cpu
+ * specific (raw) counter configuration data, if unset, the next
+ * 7 bits are an event type and the rest of the bits are the event
+ * identifier.
+ */
+ __u64 config;
+
+ __u64 irq_period;
+ __u32 record_type;
+ __u32 read_format;
+
+ __u64 disabled : 1, /* off by default */
+ inherit : 1, /* children inherit it */
+ pinned : 1, /* must always be on PMU */
+ exclusive : 1, /* only group on PMU */
+ exclude_user : 1, /* don't count user */
+ exclude_kernel : 1, /* ditto kernel */
+ exclude_hv : 1, /* ditto hypervisor */
+ exclude_idle : 1, /* don't count when idle */
+ mmap : 1, /* include mmap data */
+ munmap : 1, /* include munmap data */
+ comm : 1, /* include comm data */
+
+ __reserved_1 : 52;
+
+ __u32 extra_config_len;
+ __u32 wakeup_events; /* wakeup every n events */
+
+ __u64 __reserved_2;
+ __u64 __reserved_3;
+};
+
+The 'config' field specifies what the counter should count. It
+is divided into 3 bit-fields:
+
+raw_type: 1 bit (most significant bit) 0x8000_0000_0000_0000
+type: 7 bits (next most significant) 0x7f00_0000_0000_0000
+event_id: 56 bits (least significant) 0x00ff_ffff_ffff_ffff
+
+If 'raw_type' is 1, then the counter will count a hardware event
+specified by the remaining 63 bits of event_config. The encoding is
+machine-specific.
+
+If 'raw_type' is 0, then the 'type' field says what kind of counter
+this is, with the following encoding:
+
+enum perf_type_id {
+ PERF_TYPE_HARDWARE = 0,
+ PERF_TYPE_SOFTWARE = 1,
+ PERF_TYPE_TRACEPOINT = 2,
+};
+
+A counter of PERF_TYPE_HARDWARE will count the hardware event
+specified by 'event_id':
+
+/*
+ * Generalized performance counter event types, used by the hw_event.event_id
+ * parameter of the sys_perf_event_open() syscall:
+ */
+enum perf_hw_id {
+ /*
+ * Common hardware events, generalized by the kernel:
+ */
+ PERF_COUNT_HW_CPU_CYCLES = 0,
+ PERF_COUNT_HW_INSTRUCTIONS = 1,
+ PERF_COUNT_HW_CACHE_REFERENCES = 2,
+ PERF_COUNT_HW_CACHE_MISSES = 3,
+ PERF_COUNT_HW_BRANCH_INSTRUCTIONS = 4,
+ PERF_COUNT_HW_BRANCH_MISSES = 5,
+ PERF_COUNT_HW_BUS_CYCLES = 6,
+};
+
+These are standardized types of events that work relatively uniformly
+on all CPUs that implement Performance Counters support under Linux,
+although there may be variations (e.g., different CPUs might count
+cache references and misses at different levels of the cache hierarchy).
+If a CPU is not able to count the selected event, then the system call
+will return -EINVAL.
+
+More hw_event_types are supported as well, but they are CPU-specific
+and accessed as raw events. For example, to count "External bus
+cycles while bus lock signal asserted" events on Intel Core CPUs, pass
+in a 0x4064 event_id value and set hw_event.raw_type to 1.
+
+A counter of type PERF_TYPE_SOFTWARE will count one of the available
+software events, selected by 'event_id':
+
+/*
+ * Special "software" counters provided by the kernel, even if the hardware
+ * does not support performance counters. These counters measure various
+ * physical and sw events of the kernel (and allow the profiling of them as
+ * well):
+ */
+enum perf_sw_ids {
+ PERF_COUNT_SW_CPU_CLOCK = 0,
+ PERF_COUNT_SW_TASK_CLOCK = 1,
+ PERF_COUNT_SW_PAGE_FAULTS = 2,
+ PERF_COUNT_SW_CONTEXT_SWITCHES = 3,
+ PERF_COUNT_SW_CPU_MIGRATIONS = 4,
+ PERF_COUNT_SW_PAGE_FAULTS_MIN = 5,
+ PERF_COUNT_SW_PAGE_FAULTS_MAJ = 6,
+ PERF_COUNT_SW_ALIGNMENT_FAULTS = 7,
+ PERF_COUNT_SW_EMULATION_FAULTS = 8,
+};
+
+Counters of the type PERF_TYPE_TRACEPOINT are available when the ftrace event
+tracer is available, and event_id values can be obtained from
+/debug/tracing/events/*/*/id
+
+
+Counters come in two flavours: counting counters and sampling
+counters. A "counting" counter is one that is used for counting the
+number of events that occur, and is characterised by having
+irq_period = 0.
+
+
+A read() on a counter returns the current value of the counter and possible
+additional values as specified by 'read_format', each value is a u64 (8 bytes)
+in size.
+
+/*
+ * Bits that can be set in hw_event.read_format to request that
+ * reads on the counter should return the indicated quantities,
+ * in increasing order of bit value, after the counter value.
+ */
+enum perf_event_read_format {
+ PERF_FORMAT_TOTAL_TIME_ENABLED = 1,
+ PERF_FORMAT_TOTAL_TIME_RUNNING = 2,
+};
+
+Using these additional values one can establish the overcommit ratio for a
+particular counter allowing one to take the round-robin scheduling effect
+into account.
+
+
+A "sampling" counter is one that is set up to generate an interrupt
+every N events, where N is given by 'irq_period'. A sampling counter
+has irq_period > 0. The record_type controls what data is recorded on each
+interrupt:
+
+/*
+ * Bits that can be set in hw_event.record_type to request information
+ * in the overflow packets.
+ */
+enum perf_event_record_format {
+ PERF_RECORD_IP = 1U << 0,
+ PERF_RECORD_TID = 1U << 1,
+ PERF_RECORD_TIME = 1U << 2,
+ PERF_RECORD_ADDR = 1U << 3,
+ PERF_RECORD_GROUP = 1U << 4,
+ PERF_RECORD_CALLCHAIN = 1U << 5,
+};
+
+Such (and other) events will be recorded in a ring-buffer, which is
+available to user-space using mmap() (see below).
+
+The 'disabled' bit specifies whether the counter starts out disabled
+or enabled. If it is initially disabled, it can be enabled by ioctl
+or prctl (see below).
+
+The 'inherit' bit, if set, specifies that this counter should count
+events on descendant tasks as well as the task specified. This only
+applies to new descendents, not to any existing descendents at the
+time the counter is created (nor to any new descendents of existing
+descendents).
+
+The 'pinned' bit, if set, specifies that the counter should always be
+on the CPU if at all possible. It only applies to hardware counters
+and only to group leaders. If a pinned counter cannot be put onto the
+CPU (e.g. because there are not enough hardware counters or because of
+a conflict with some other event), then the counter goes into an
+'error' state, where reads return end-of-file (i.e. read() returns 0)
+until the counter is subsequently enabled or disabled.
+
+The 'exclusive' bit, if set, specifies that when this counter's group
+is on the CPU, it should be the only group using the CPU's counters.
+In future, this will allow sophisticated monitoring programs to supply
+extra configuration information via 'extra_config_len' to exploit
+advanced features of the CPU's Performance Monitor Unit (PMU) that are
+not otherwise accessible and that might disrupt other hardware
+counters.
+
+The 'exclude_user', 'exclude_kernel' and 'exclude_hv' bits provide a
+way to request that counting of events be restricted to times when the
+CPU is in user, kernel and/or hypervisor mode.
+
+The 'mmap' and 'munmap' bits allow recording of PROT_EXEC mmap/munmap
+operations, these can be used to relate userspace IP addresses to actual
+code, even after the mapping (or even the whole process) is gone,
+these events are recorded in the ring-buffer (see below).
+
+The 'comm' bit allows tracking of process comm data on process creation.
+This too is recorded in the ring-buffer (see below).
+
+The 'pid' parameter to the sys_perf_event_open() system call allows the
+counter to be specific to a task:
+
+ pid == 0: if the pid parameter is zero, the counter is attached to the
+ current task.
+
+ pid > 0: the counter is attached to a specific task (if the current task
+ has sufficient privilege to do so)
+
+ pid < 0: all tasks are counted (per cpu counters)
+
+The 'cpu' parameter allows a counter to be made specific to a CPU:
+
+ cpu >= 0: the counter is restricted to a specific CPU
+ cpu == -1: the counter counts on all CPUs
+
+(Note: the combination of 'pid == -1' and 'cpu == -1' is not valid.)
+
+A 'pid > 0' and 'cpu == -1' counter is a per task counter that counts
+events of that task and 'follows' that task to whatever CPU the task
+gets schedule to. Per task counters can be created by any user, for
+their own tasks.
+
+A 'pid == -1' and 'cpu == x' counter is a per CPU counter that counts
+all events on CPU-x. Per CPU counters need CAP_SYS_ADMIN privilege.
+
+The 'flags' parameter is currently unused and must be zero.
+
+The 'group_fd' parameter allows counter "groups" to be set up. A
+counter group has one counter which is the group "leader". The leader
+is created first, with group_fd = -1 in the sys_perf_event_open call
+that creates it. The rest of the group members are created
+subsequently, with group_fd giving the fd of the group leader.
+(A single counter on its own is created with group_fd = -1 and is
+considered to be a group with only 1 member.)
+
+A counter group is scheduled onto the CPU as a unit, that is, it will
+only be put onto the CPU if all of the counters in the group can be
+put onto the CPU. This means that the values of the member counters
+can be meaningfully compared, added, divided (to get ratios), etc.,
+with each other, since they have counted events for the same set of
+executed instructions.
+
+
+Like stated, asynchronous events, like counter overflow or PROT_EXEC mmap
+tracking are logged into a ring-buffer. This ring-buffer is created and
+accessed through mmap().
+
+The mmap size should be 1+2^n pages, where the first page is a meta-data page
+(struct perf_event_mmap_page) that contains various bits of information such
+as where the ring-buffer head is.
+
+/*
+ * Structure of the page that can be mapped via mmap
+ */
+struct perf_event_mmap_page {
+ __u32 version; /* version number of this structure */
+ __u32 compat_version; /* lowest version this is compat with */
+
+ /*
+ * Bits needed to read the hw counters in user-space.
+ *
+ * u32 seq;
+ * s64 count;
+ *
+ * do {
+ * seq = pc->lock;
+ *
+ * barrier()
+ * if (pc->index) {
+ * count = pmc_read(pc->index - 1);
+ * count += pc->offset;
+ * } else
+ * goto regular_read;
+ *
+ * barrier();
+ * } while (pc->lock != seq);
+ *
+ * NOTE: for obvious reason this only works on self-monitoring
+ * processes.
+ */
+ __u32 lock; /* seqlock for synchronization */
+ __u32 index; /* hardware counter identifier */
+ __s64 offset; /* add to hardware counter value */
+
+ /*
+ * Control data for the mmap() data buffer.
+ *
+ * User-space reading this value should issue an rmb(), on SMP capable
+ * platforms, after reading this value -- see perf_event_wakeup().
+ */
+ __u32 data_head; /* head in the data section */
+};
+
+NOTE: the hw-counter userspace bits are arch specific and are currently only
+ implemented on powerpc.
+
+The following 2^n pages are the ring-buffer which contains events of the form:
+
+#define PERF_RECORD_MISC_KERNEL (1 << 0)
+#define PERF_RECORD_MISC_USER (1 << 1)
+#define PERF_RECORD_MISC_OVERFLOW (1 << 2)
+
+struct perf_event_header {
+ __u32 type;
+ __u16 misc;
+ __u16 size;
+};
+
+enum perf_event_type {
+
+ /*
+ * The MMAP events record the PROT_EXEC mappings so that we can
+ * correlate userspace IPs to code. They have the following structure:
+ *
+ * struct {
+ * struct perf_event_header header;
+ *
+ * u32 pid, tid;
+ * u64 addr;
+ * u64 len;
+ * u64 pgoff;
+ * char filename[];
+ * };
+ */
+ PERF_RECORD_MMAP = 1,
+ PERF_RECORD_MUNMAP = 2,
+
+ /*
+ * struct {
+ * struct perf_event_header header;
+ *
+ * u32 pid, tid;
+ * char comm[];
+ * };
+ */
+ PERF_RECORD_COMM = 3,
+
+ /*
+ * When header.misc & PERF_RECORD_MISC_OVERFLOW the event_type field
+ * will be PERF_RECORD_*
+ *
+ * struct {
+ * struct perf_event_header header;
+ *
+ * { u64 ip; } && PERF_RECORD_IP
+ * { u32 pid, tid; } && PERF_RECORD_TID
+ * { u64 time; } && PERF_RECORD_TIME
+ * { u64 addr; } && PERF_RECORD_ADDR
+ *
+ * { u64 nr;
+ * { u64 event, val; } cnt[nr]; } && PERF_RECORD_GROUP
+ *
+ * { u16 nr,
+ * hv,
+ * kernel,
+ * user;
+ * u64 ips[nr]; } && PERF_RECORD_CALLCHAIN
+ * };
+ */
+};
+
+NOTE: PERF_RECORD_CALLCHAIN is arch specific and currently only implemented
+ on x86.
+
+Notification of new events is possible through poll()/select()/epoll() and
+fcntl() managing signals.
+
+Normally a notification is generated for every page filled, however one can
+additionally set perf_event_attr.wakeup_events to generate one every
+so many counter overflow events.
+
+Future work will include a splice() interface to the ring-buffer.
+
+
+Counters can be enabled and disabled in two ways: via ioctl and via
+prctl. When a counter is disabled, it doesn't count or generate
+events but does continue to exist and maintain its count value.
+
+An individual counter can be enabled with
+
+ ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
+
+or disabled with
+
+ ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
+
+For a counter group, pass PERF_IOC_FLAG_GROUP as the third argument.
+Enabling or disabling the leader of a group enables or disables the
+whole group; that is, while the group leader is disabled, none of the
+counters in the group will count. Enabling or disabling a member of a
+group other than the leader only affects that counter - disabling an
+non-leader stops that counter from counting but doesn't affect any
+other counter.
+
+Additionally, non-inherited overflow counters can use
+
+ ioctl(fd, PERF_EVENT_IOC_REFRESH, nr);
+
+to enable a counter for 'nr' events, after which it gets disabled again.
+
+A process can enable or disable all the counter groups that are
+attached to it, using prctl:
+
+ prctl(PR_TASK_PERF_EVENTS_ENABLE);
+
+ prctl(PR_TASK_PERF_EVENTS_DISABLE);
+
+This applies to all counters on the current process, whether created
+by this process or by another, and doesn't affect any counters that
+this process has created on other processes. It only enables or
+disables the group leaders, not any other members in the groups.
+
+
+Arch requirements
+-----------------
+
+If your architecture does not have hardware performance metrics, you can
+still use the generic software counters based on hrtimers for sampling.
+
+So to start with, in order to add HAVE_PERF_EVENTS to your Kconfig, you
+will need at least this:
+ - asm/perf_event.h - a basic stub will suffice at first
+ - support for atomic64 types (and associated helper functions)
+
+If your architecture does have hardware capabilities, you can override the
+weak stub hw_perf_event_init() to register hardware counters.
+
+Architectures that have d-cache aliassing issues, such as Sparc and ARM,
+should select PERF_USE_VMALLOC in order to avoid these for perf mmap().
diff --git a/tools/perf/perf-archive.sh b/tools/perf/perf-archive.sh
new file mode 100644
index 00000000000..e9193062026
--- /dev/null
+++ b/tools/perf/perf-archive.sh
@@ -0,0 +1,47 @@
+#!/bin/bash
+# perf archive
+# Arnaldo Carvalho de Melo <acme@redhat.com>
+
+PERF_DATA=perf.data
+if [ $# -ne 0 ] ; then
+ PERF_DATA=$1
+fi
+
+#
+# PERF_BUILDID_DIR environment variable set by perf
+# path to buildid directory, default to $HOME/.debug
+#
+if [ -z $PERF_BUILDID_DIR ]; then
+ PERF_BUILDID_DIR=~/.debug/
+else
+ # append / to make substitutions work
+ PERF_BUILDID_DIR=$PERF_BUILDID_DIR/
+fi
+
+BUILDIDS=$(mktemp /tmp/perf-archive-buildids.XXXXXX)
+NOBUILDID=0000000000000000000000000000000000000000
+
+perf buildid-list -i $PERF_DATA --with-hits | grep -v "^$NOBUILDID " > $BUILDIDS
+if [ ! -s $BUILDIDS ] ; then
+ echo "perf archive: no build-ids found"
+ rm $BUILDIDS || true
+ exit 1
+fi
+
+MANIFEST=$(mktemp /tmp/perf-archive-manifest.XXXXXX)
+PERF_BUILDID_LINKDIR=$(readlink -f $PERF_BUILDID_DIR)/
+
+cut -d ' ' -f 1 $BUILDIDS | \
+while read build_id ; do
+ linkname=$PERF_BUILDID_DIR.build-id/${build_id:0:2}/${build_id:2}
+ filename=$(readlink -f $linkname)
+ echo ${linkname#$PERF_BUILDID_DIR} >> $MANIFEST
+ echo ${filename#$PERF_BUILDID_LINKDIR} >> $MANIFEST
+done
+
+tar cjf $PERF_DATA.tar.bz2 -C $PERF_BUILDID_DIR -T $MANIFEST
+rm $MANIFEST $BUILDIDS || true
+echo -e "Now please run:\n"
+echo -e "$ tar xvf $PERF_DATA.tar.bz2 -C ~/.debug\n"
+echo "wherever you need to run 'perf report' on."
+exit 0
diff --git a/tools/perf/perf-completion.sh b/tools/perf/perf-completion.sh
new file mode 100644
index 00000000000..33569847fdc
--- /dev/null
+++ b/tools/perf/perf-completion.sh
@@ -0,0 +1,206 @@
+# perf bash and zsh completion
+
+# Taken from git.git's completion script.
+__my_reassemble_comp_words_by_ref()
+{
+ local exclude i j first
+ # Which word separators to exclude?
+ exclude="${1//[^$COMP_WORDBREAKS]}"
+ cword_=$COMP_CWORD
+ if [ -z "$exclude" ]; then
+ words_=("${COMP_WORDS[@]}")
+ return
+ fi
+ # List of word completion separators has shrunk;
+ # re-assemble words to complete.
+ for ((i=0, j=0; i < ${#COMP_WORDS[@]}; i++, j++)); do
+ # Append each nonempty word consisting of just
+ # word separator characters to the current word.
+ first=t
+ while
+ [ $i -gt 0 ] &&
+ [ -n "${COMP_WORDS[$i]}" ] &&
+ # word consists of excluded word separators
+ [ "${COMP_WORDS[$i]//[^$exclude]}" = "${COMP_WORDS[$i]}" ]
+ do
+ # Attach to the previous token,
+ # unless the previous token is the command name.
+ if [ $j -ge 2 ] && [ -n "$first" ]; then
+ ((j--))
+ fi
+ first=
+ words_[$j]=${words_[j]}${COMP_WORDS[i]}
+ if [ $i = $COMP_CWORD ]; then
+ cword_=$j
+ fi
+ if (($i < ${#COMP_WORDS[@]} - 1)); then
+ ((i++))
+ else
+ # Done.
+ return
+ fi
+ done
+ words_[$j]=${words_[j]}${COMP_WORDS[i]}
+ if [ $i = $COMP_CWORD ]; then
+ cword_=$j
+ fi
+ done
+}
+
+type _get_comp_words_by_ref &>/dev/null ||
+_get_comp_words_by_ref()
+{
+ local exclude cur_ words_ cword_
+ if [ "$1" = "-n" ]; then
+ exclude=$2
+ shift 2
+ fi
+ __my_reassemble_comp_words_by_ref "$exclude"
+ cur_=${words_[cword_]}
+ while [ $# -gt 0 ]; do
+ case "$1" in
+ cur)
+ cur=$cur_
+ ;;
+ prev)
+ prev=${words_[$cword_-1]}
+ ;;
+ words)
+ words=("${words_[@]}")
+ ;;
+ cword)
+ cword=$cword_
+ ;;
+ esac
+ shift
+ done
+}
+
+type __ltrim_colon_completions &>/dev/null ||
+__ltrim_colon_completions()
+{
+ if [[ "$1" == *:* && "$COMP_WORDBREAKS" == *:* ]]; then
+ # Remove colon-word prefix from COMPREPLY items
+ local colon_word=${1%"${1##*:}"}
+ local i=${#COMPREPLY[*]}
+ while [[ $((--i)) -ge 0 ]]; do
+ COMPREPLY[$i]=${COMPREPLY[$i]#"$colon_word"}
+ done
+ fi
+}
+
+__perfcomp ()
+{
+ COMPREPLY=( $( compgen -W "$1" -- "$2" ) )
+}
+
+__perfcomp_colon ()
+{
+ __perfcomp "$1" "$2"
+ __ltrim_colon_completions $cur
+}
+
+__perf_main ()
+{
+ local cmd
+
+ cmd=${words[0]}
+ COMPREPLY=()
+
+ # List perf subcommands or long options
+ if [ $cword -eq 1 ]; then
+ if [[ $cur == --* ]]; then
+ __perfcomp '--help --version \
+ --exec-path --html-path --paginate --no-pager \
+ --perf-dir --work-tree --debugfs-dir' -- "$cur"
+ else
+ cmds=$($cmd --list-cmds)
+ __perfcomp "$cmds" "$cur"
+ fi
+ # List possible events for -e option
+ elif [[ $prev == "-e" && "${words[1]}" == @(record|stat|top) ]]; then
+ evts=$($cmd list --raw-dump)
+ __perfcomp_colon "$evts" "$cur"
+ # List subcommands for perf commands
+ elif [[ $prev == @(kvm|kmem|mem|lock|sched) ]]; then
+ subcmds=$($cmd $prev --list-cmds)
+ __perfcomp_colon "$subcmds" "$cur"
+ # List long option names
+ elif [[ $cur == --* ]]; then
+ subcmd=${words[1]}
+ opts=$($cmd $subcmd --list-opts)
+ __perfcomp "$opts" "$cur"
+ fi
+}
+
+if [[ -n ${ZSH_VERSION-} ]]; then
+ autoload -U +X compinit && compinit
+
+ __perfcomp ()
+ {
+ emulate -L zsh
+
+ local c IFS=$' \t\n'
+ local -a array
+
+ for c in ${=1}; do
+ case $c in
+ --*=*|*.) ;;
+ *) c="$c " ;;
+ esac
+ array[${#array[@]}+1]="$c"
+ done
+
+ compset -P '*[=:]'
+ compadd -Q -S '' -a -- array && _ret=0
+ }
+
+ __perfcomp_colon ()
+ {
+ emulate -L zsh
+
+ local cur_="${2-$cur}"
+ local c IFS=$' \t\n'
+ local -a array
+
+ if [[ "$cur_" == *:* ]]; then
+ local colon_word=${cur_%"${cur_##*:}"}
+ fi
+
+ for c in ${=1}; do
+ case $c in
+ --*=*|*.) ;;
+ *) c="$c " ;;
+ esac
+ array[$#array+1]=${c#"$colon_word"}
+ done
+
+ compset -P '*[=:]'
+ compadd -Q -S '' -a -- array && _ret=0
+ }
+
+ _perf ()
+ {
+ local _ret=1 cur cword prev
+ cur=${words[CURRENT]}
+ prev=${words[CURRENT-1]}
+ let cword=CURRENT-1
+ emulate ksh -c __perf_main
+ let _ret && _default && _ret=0
+ return _ret
+ }
+
+ compdef _perf perf
+ return
+fi
+
+type perf &>/dev/null &&
+_perf()
+{
+ local cur words cword prev
+ _get_comp_words_by_ref -n =: cur words cword prev
+ __perf_main
+} &&
+
+complete -o bashdefault -o default -o nospace -F _perf perf 2>/dev/null \
+ || complete -o default -o nospace -F _perf perf
diff --git a/tools/perf/perf-sys.h b/tools/perf/perf-sys.h
new file mode 100644
index 00000000000..5268a1481d2
--- /dev/null
+++ b/tools/perf/perf-sys.h
@@ -0,0 +1,190 @@
+#ifndef _PERF_SYS_H
+#define _PERF_SYS_H
+
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/syscall.h>
+#include <linux/types.h>
+#include <linux/perf_event.h>
+#include <asm/unistd.h>
+
+#if defined(__i386__)
+#define mb() asm volatile("lock; addl $0,0(%%esp)" ::: "memory")
+#define wmb() asm volatile("lock; addl $0,0(%%esp)" ::: "memory")
+#define rmb() asm volatile("lock; addl $0,0(%%esp)" ::: "memory")
+#define cpu_relax() asm volatile("rep; nop" ::: "memory");
+#define CPUINFO_PROC "model name"
+#ifndef __NR_perf_event_open
+# define __NR_perf_event_open 336
+#endif
+#ifndef __NR_futex
+# define __NR_futex 240
+#endif
+#ifndef __NR_gettid
+# define __NR_gettid 224
+#endif
+#endif
+
+#if defined(__x86_64__)
+#define mb() asm volatile("mfence" ::: "memory")
+#define wmb() asm volatile("sfence" ::: "memory")
+#define rmb() asm volatile("lfence" ::: "memory")
+#define cpu_relax() asm volatile("rep; nop" ::: "memory");
+#define CPUINFO_PROC "model name"
+#ifndef __NR_perf_event_open
+# define __NR_perf_event_open 298
+#endif
+#ifndef __NR_futex
+# define __NR_futex 202
+#endif
+#ifndef __NR_gettid
+# define __NR_gettid 186
+#endif
+#endif
+
+#ifdef __powerpc__
+#include "../../arch/powerpc/include/uapi/asm/unistd.h"
+#define mb() asm volatile ("sync" ::: "memory")
+#define wmb() asm volatile ("sync" ::: "memory")
+#define rmb() asm volatile ("sync" ::: "memory")
+#define CPUINFO_PROC "cpu"
+#endif
+
+#ifdef __s390__
+#define mb() asm volatile("bcr 15,0" ::: "memory")
+#define wmb() asm volatile("bcr 15,0" ::: "memory")
+#define rmb() asm volatile("bcr 15,0" ::: "memory")
+#endif
+
+#ifdef __sh__
+#if defined(__SH4A__) || defined(__SH5__)
+# define mb() asm volatile("synco" ::: "memory")
+# define wmb() asm volatile("synco" ::: "memory")
+# define rmb() asm volatile("synco" ::: "memory")
+#else
+# define mb() asm volatile("" ::: "memory")
+# define wmb() asm volatile("" ::: "memory")
+# define rmb() asm volatile("" ::: "memory")
+#endif
+#define CPUINFO_PROC "cpu type"
+#endif
+
+#ifdef __hppa__
+#define mb() asm volatile("" ::: "memory")
+#define wmb() asm volatile("" ::: "memory")
+#define rmb() asm volatile("" ::: "memory")
+#define CPUINFO_PROC "cpu"
+#endif
+
+#ifdef __sparc__
+#ifdef __LP64__
+#define mb() asm volatile("ba,pt %%xcc, 1f\n" \
+ "membar #StoreLoad\n" \
+ "1:\n":::"memory")
+#else
+#define mb() asm volatile("":::"memory")
+#endif
+#define wmb() asm volatile("":::"memory")
+#define rmb() asm volatile("":::"memory")
+#define CPUINFO_PROC "cpu"
+#endif
+
+#ifdef __alpha__
+#define mb() asm volatile("mb" ::: "memory")
+#define wmb() asm volatile("wmb" ::: "memory")
+#define rmb() asm volatile("mb" ::: "memory")
+#define CPUINFO_PROC "cpu model"
+#endif
+
+#ifdef __ia64__
+#define mb() asm volatile ("mf" ::: "memory")
+#define wmb() asm volatile ("mf" ::: "memory")
+#define rmb() asm volatile ("mf" ::: "memory")
+#define cpu_relax() asm volatile ("hint @pause" ::: "memory")
+#define CPUINFO_PROC "model name"
+#endif
+
+#ifdef __arm__
+/*
+ * Use the __kuser_memory_barrier helper in the CPU helper page. See
+ * arch/arm/kernel/entry-armv.S in the kernel source for details.
+ */
+#define mb() ((void(*)(void))0xffff0fa0)()
+#define wmb() ((void(*)(void))0xffff0fa0)()
+#define rmb() ((void(*)(void))0xffff0fa0)()
+#define CPUINFO_PROC "Processor"
+#endif
+
+#ifdef __aarch64__
+#define mb() asm volatile("dmb ish" ::: "memory")
+#define wmb() asm volatile("dmb ishst" ::: "memory")
+#define rmb() asm volatile("dmb ishld" ::: "memory")
+#define cpu_relax() asm volatile("yield" ::: "memory")
+#endif
+
+#ifdef __mips__
+#define mb() asm volatile( \
+ ".set mips2\n\t" \
+ "sync\n\t" \
+ ".set mips0" \
+ : /* no output */ \
+ : /* no input */ \
+ : "memory")
+#define wmb() mb()
+#define rmb() mb()
+#define CPUINFO_PROC "cpu model"
+#endif
+
+#ifdef __arc__
+#define mb() asm volatile("" ::: "memory")
+#define wmb() asm volatile("" ::: "memory")
+#define rmb() asm volatile("" ::: "memory")
+#define CPUINFO_PROC "Processor"
+#endif
+
+#ifdef __metag__
+#define mb() asm volatile("" ::: "memory")
+#define wmb() asm volatile("" ::: "memory")
+#define rmb() asm volatile("" ::: "memory")
+#define CPUINFO_PROC "CPU"
+#endif
+
+#ifdef __xtensa__
+#define mb() asm volatile("memw" ::: "memory")
+#define wmb() asm volatile("memw" ::: "memory")
+#define rmb() asm volatile("" ::: "memory")
+#define CPUINFO_PROC "core ID"
+#endif
+
+#ifdef __tile__
+#define mb() asm volatile ("mf" ::: "memory")
+#define wmb() asm volatile ("mf" ::: "memory")
+#define rmb() asm volatile ("mf" ::: "memory")
+#define cpu_relax() asm volatile ("mfspr zero, PASS" ::: "memory")
+#define CPUINFO_PROC "model name"
+#endif
+
+#define barrier() asm volatile ("" ::: "memory")
+
+#ifndef cpu_relax
+#define cpu_relax() barrier()
+#endif
+
+static inline int
+sys_perf_event_open(struct perf_event_attr *attr,
+ pid_t pid, int cpu, int group_fd,
+ unsigned long flags)
+{
+ int fd;
+
+ fd = syscall(__NR_perf_event_open, attr, pid, cpu,
+ group_fd, flags);
+
+#ifdef HAVE_ATTR_TEST
+ if (unlikely(test_attr__enabled))
+ test_attr__open(attr, pid, cpu, fd, group_fd, flags);
+#endif
+ return fd;
+}
+
+#endif /* _PERF_SYS_H */
diff --git a/tools/perf/perf.c b/tools/perf/perf.c
new file mode 100644
index 00000000000..95c58fc1528
--- /dev/null
+++ b/tools/perf/perf.c
@@ -0,0 +1,556 @@
+/*
+ * perf.c
+ *
+ * Performance analysis utility.
+ *
+ * This is the main hub from which the sub-commands (perf stat,
+ * perf top, perf record, perf report, etc.) are started.
+ */
+#include "builtin.h"
+
+#include "util/exec_cmd.h"
+#include "util/cache.h"
+#include "util/quote.h"
+#include "util/run-command.h"
+#include "util/parse-events.h"
+#include <api/fs/debugfs.h>
+#include <pthread.h>
+
+const char perf_usage_string[] =
+ "perf [--version] [--help] COMMAND [ARGS]";
+
+const char perf_more_info_string[] =
+ "See 'perf help COMMAND' for more information on a specific command.";
+
+int use_browser = -1;
+static int use_pager = -1;
+const char *input_name;
+
+struct cmd_struct {
+ const char *cmd;
+ int (*fn)(int, const char **, const char *);
+ int option;
+};
+
+static struct cmd_struct commands[] = {
+ { "buildid-cache", cmd_buildid_cache, 0 },
+ { "buildid-list", cmd_buildid_list, 0 },
+ { "diff", cmd_diff, 0 },
+ { "evlist", cmd_evlist, 0 },
+ { "help", cmd_help, 0 },
+ { "list", cmd_list, 0 },
+ { "record", cmd_record, 0 },
+ { "report", cmd_report, 0 },
+ { "bench", cmd_bench, 0 },
+ { "stat", cmd_stat, 0 },
+ { "timechart", cmd_timechart, 0 },
+ { "top", cmd_top, 0 },
+ { "annotate", cmd_annotate, 0 },
+ { "version", cmd_version, 0 },
+ { "script", cmd_script, 0 },
+ { "sched", cmd_sched, 0 },
+#ifdef HAVE_LIBELF_SUPPORT
+ { "probe", cmd_probe, 0 },
+#endif
+ { "kmem", cmd_kmem, 0 },
+ { "lock", cmd_lock, 0 },
+ { "kvm", cmd_kvm, 0 },
+ { "test", cmd_test, 0 },
+#ifdef HAVE_LIBAUDIT_SUPPORT
+ { "trace", cmd_trace, 0 },
+#endif
+ { "inject", cmd_inject, 0 },
+ { "mem", cmd_mem, 0 },
+};
+
+struct pager_config {
+ const char *cmd;
+ int val;
+};
+
+static int pager_command_config(const char *var, const char *value, void *data)
+{
+ struct pager_config *c = data;
+ if (!prefixcmp(var, "pager.") && !strcmp(var + 6, c->cmd))
+ c->val = perf_config_bool(var, value);
+ return 0;
+}
+
+/* returns 0 for "no pager", 1 for "use pager", and -1 for "not specified" */
+int check_pager_config(const char *cmd)
+{
+ struct pager_config c;
+ c.cmd = cmd;
+ c.val = -1;
+ perf_config(pager_command_config, &c);
+ return c.val;
+}
+
+static int browser_command_config(const char *var, const char *value, void *data)
+{
+ struct pager_config *c = data;
+ if (!prefixcmp(var, "tui.") && !strcmp(var + 4, c->cmd))
+ c->val = perf_config_bool(var, value);
+ if (!prefixcmp(var, "gtk.") && !strcmp(var + 4, c->cmd))
+ c->val = perf_config_bool(var, value) ? 2 : 0;
+ return 0;
+}
+
+/*
+ * returns 0 for "no tui", 1 for "use tui", 2 for "use gtk",
+ * and -1 for "not specified"
+ */
+static int check_browser_config(const char *cmd)
+{
+ struct pager_config c;
+ c.cmd = cmd;
+ c.val = -1;
+ perf_config(browser_command_config, &c);
+ return c.val;
+}
+
+static void commit_pager_choice(void)
+{
+ switch (use_pager) {
+ case 0:
+ setenv("PERF_PAGER", "cat", 1);
+ break;
+ case 1:
+ /* setup_pager(); */
+ break;
+ default:
+ break;
+ }
+}
+
+static int handle_options(const char ***argv, int *argc, int *envchanged)
+{
+ int handled = 0;
+
+ while (*argc > 0) {
+ const char *cmd = (*argv)[0];
+ if (cmd[0] != '-')
+ break;
+
+ /*
+ * For legacy reasons, the "version" and "help"
+ * commands can be written with "--" prepended
+ * to make them look like flags.
+ */
+ if (!strcmp(cmd, "--help") || !strcmp(cmd, "--version"))
+ break;
+
+ /*
+ * Check remaining flags.
+ */
+ if (!prefixcmp(cmd, CMD_EXEC_PATH)) {
+ cmd += strlen(CMD_EXEC_PATH);
+ if (*cmd == '=')
+ perf_set_argv_exec_path(cmd + 1);
+ else {
+ puts(perf_exec_path());
+ exit(0);
+ }
+ } else if (!strcmp(cmd, "--html-path")) {
+ puts(system_path(PERF_HTML_PATH));
+ exit(0);
+ } else if (!strcmp(cmd, "-p") || !strcmp(cmd, "--paginate")) {
+ use_pager = 1;
+ } else if (!strcmp(cmd, "--no-pager")) {
+ use_pager = 0;
+ if (envchanged)
+ *envchanged = 1;
+ } else if (!strcmp(cmd, "--perf-dir")) {
+ if (*argc < 2) {
+ fprintf(stderr, "No directory given for --perf-dir.\n");
+ usage(perf_usage_string);
+ }
+ setenv(PERF_DIR_ENVIRONMENT, (*argv)[1], 1);
+ if (envchanged)
+ *envchanged = 1;
+ (*argv)++;
+ (*argc)--;
+ handled++;
+ } else if (!prefixcmp(cmd, CMD_PERF_DIR)) {
+ setenv(PERF_DIR_ENVIRONMENT, cmd + strlen(CMD_PERF_DIR), 1);
+ if (envchanged)
+ *envchanged = 1;
+ } else if (!strcmp(cmd, "--work-tree")) {
+ if (*argc < 2) {
+ fprintf(stderr, "No directory given for --work-tree.\n");
+ usage(perf_usage_string);
+ }
+ setenv(PERF_WORK_TREE_ENVIRONMENT, (*argv)[1], 1);
+ if (envchanged)
+ *envchanged = 1;
+ (*argv)++;
+ (*argc)--;
+ } else if (!prefixcmp(cmd, CMD_WORK_TREE)) {
+ setenv(PERF_WORK_TREE_ENVIRONMENT, cmd + strlen(CMD_WORK_TREE), 1);
+ if (envchanged)
+ *envchanged = 1;
+ } else if (!strcmp(cmd, "--debugfs-dir")) {
+ if (*argc < 2) {
+ fprintf(stderr, "No directory given for --debugfs-dir.\n");
+ usage(perf_usage_string);
+ }
+ perf_debugfs_set_path((*argv)[1]);
+ if (envchanged)
+ *envchanged = 1;
+ (*argv)++;
+ (*argc)--;
+ } else if (!prefixcmp(cmd, CMD_DEBUGFS_DIR)) {
+ perf_debugfs_set_path(cmd + strlen(CMD_DEBUGFS_DIR));
+ fprintf(stderr, "dir: %s\n", debugfs_mountpoint);
+ if (envchanged)
+ *envchanged = 1;
+ } else if (!strcmp(cmd, "--list-cmds")) {
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(commands); i++) {
+ struct cmd_struct *p = commands+i;
+ printf("%s ", p->cmd);
+ }
+ exit(0);
+ } else {
+ fprintf(stderr, "Unknown option: %s\n", cmd);
+ usage(perf_usage_string);
+ }
+
+ (*argv)++;
+ (*argc)--;
+ handled++;
+ }
+ return handled;
+}
+
+static int handle_alias(int *argcp, const char ***argv)
+{
+ int envchanged = 0, ret = 0, saved_errno = errno;
+ int count, option_count;
+ const char **new_argv;
+ const char *alias_command;
+ char *alias_string;
+
+ alias_command = (*argv)[0];
+ alias_string = alias_lookup(alias_command);
+ if (alias_string) {
+ if (alias_string[0] == '!') {
+ if (*argcp > 1) {
+ struct strbuf buf;
+
+ strbuf_init(&buf, PATH_MAX);
+ strbuf_addstr(&buf, alias_string);
+ sq_quote_argv(&buf, (*argv) + 1, PATH_MAX);
+ free(alias_string);
+ alias_string = buf.buf;
+ }
+ ret = system(alias_string + 1);
+ if (ret >= 0 && WIFEXITED(ret) &&
+ WEXITSTATUS(ret) != 127)
+ exit(WEXITSTATUS(ret));
+ die("Failed to run '%s' when expanding alias '%s'",
+ alias_string + 1, alias_command);
+ }
+ count = split_cmdline(alias_string, &new_argv);
+ if (count < 0)
+ die("Bad alias.%s string", alias_command);
+ option_count = handle_options(&new_argv, &count, &envchanged);
+ if (envchanged)
+ die("alias '%s' changes environment variables\n"
+ "You can use '!perf' in the alias to do this.",
+ alias_command);
+ memmove(new_argv - option_count, new_argv,
+ count * sizeof(char *));
+ new_argv -= option_count;
+
+ if (count < 1)
+ die("empty alias for %s", alias_command);
+
+ if (!strcmp(alias_command, new_argv[0]))
+ die("recursive alias: %s", alias_command);
+
+ new_argv = realloc(new_argv, sizeof(char *) *
+ (count + *argcp + 1));
+ /* insert after command name */
+ memcpy(new_argv + count, *argv + 1, sizeof(char *) * *argcp);
+ new_argv[count + *argcp] = NULL;
+
+ *argv = new_argv;
+ *argcp += count - 1;
+
+ ret = 1;
+ }
+
+ errno = saved_errno;
+
+ return ret;
+}
+
+const char perf_version_string[] = PERF_VERSION;
+
+#define RUN_SETUP (1<<0)
+#define USE_PAGER (1<<1)
+/*
+ * require working tree to be present -- anything uses this needs
+ * RUN_SETUP for reading from the configuration file.
+ */
+#define NEED_WORK_TREE (1<<2)
+
+static int run_builtin(struct cmd_struct *p, int argc, const char **argv)
+{
+ int status;
+ struct stat st;
+ const char *prefix;
+
+ prefix = NULL;
+ if (p->option & RUN_SETUP)
+ prefix = NULL; /* setup_perf_directory(); */
+
+ if (use_browser == -1)
+ use_browser = check_browser_config(p->cmd);
+
+ if (use_pager == -1 && p->option & RUN_SETUP)
+ use_pager = check_pager_config(p->cmd);
+ if (use_pager == -1 && p->option & USE_PAGER)
+ use_pager = 1;
+ commit_pager_choice();
+
+ status = p->fn(argc, argv, prefix);
+ exit_browser(status);
+
+ if (status)
+ return status & 0xff;
+
+ /* Somebody closed stdout? */
+ if (fstat(fileno(stdout), &st))
+ return 0;
+ /* Ignore write errors for pipes and sockets.. */
+ if (S_ISFIFO(st.st_mode) || S_ISSOCK(st.st_mode))
+ return 0;
+
+ status = 1;
+ /* Check for ENOSPC and EIO errors.. */
+ if (fflush(stdout)) {
+ fprintf(stderr, "write failure on standard output: %s", strerror(errno));
+ goto out;
+ }
+ if (ferror(stdout)) {
+ fprintf(stderr, "unknown write failure on standard output");
+ goto out;
+ }
+ if (fclose(stdout)) {
+ fprintf(stderr, "close failed on standard output: %s", strerror(errno));
+ goto out;
+ }
+ status = 0;
+out:
+ return status;
+}
+
+static void handle_internal_command(int argc, const char **argv)
+{
+ const char *cmd = argv[0];
+ unsigned int i;
+ static const char ext[] = STRIP_EXTENSION;
+
+ if (sizeof(ext) > 1) {
+ i = strlen(argv[0]) - strlen(ext);
+ if (i > 0 && !strcmp(argv[0] + i, ext)) {
+ char *argv0 = strdup(argv[0]);
+ argv[0] = cmd = argv0;
+ argv0[i] = '\0';
+ }
+ }
+
+ /* Turn "perf cmd --help" into "perf help cmd" */
+ if (argc > 1 && !strcmp(argv[1], "--help")) {
+ argv[1] = argv[0];
+ argv[0] = cmd = "help";
+ }
+
+ for (i = 0; i < ARRAY_SIZE(commands); i++) {
+ struct cmd_struct *p = commands+i;
+ if (strcmp(p->cmd, cmd))
+ continue;
+ exit(run_builtin(p, argc, argv));
+ }
+}
+
+static void execv_dashed_external(const char **argv)
+{
+ struct strbuf cmd = STRBUF_INIT;
+ const char *tmp;
+ int status;
+
+ strbuf_addf(&cmd, "perf-%s", argv[0]);
+
+ /*
+ * argv[0] must be the perf command, but the argv array
+ * belongs to the caller, and may be reused in
+ * subsequent loop iterations. Save argv[0] and
+ * restore it on error.
+ */
+ tmp = argv[0];
+ argv[0] = cmd.buf;
+
+ /*
+ * if we fail because the command is not found, it is
+ * OK to return. Otherwise, we just pass along the status code.
+ */
+ status = run_command_v_opt(argv, 0);
+ if (status != -ERR_RUN_COMMAND_EXEC) {
+ if (IS_RUN_COMMAND_ERR(status))
+ die("unable to run '%s'", argv[0]);
+ exit(-status);
+ }
+ errno = ENOENT; /* as if we called execvp */
+
+ argv[0] = tmp;
+
+ strbuf_release(&cmd);
+}
+
+static int run_argv(int *argcp, const char ***argv)
+{
+ int done_alias = 0;
+
+ while (1) {
+ /* See if it's an internal command */
+ handle_internal_command(*argcp, *argv);
+
+ /* .. then try the external ones */
+ execv_dashed_external(*argv);
+
+ /* It could be an alias -- this works around the insanity
+ * of overriding "perf log" with "perf show" by having
+ * alias.log = show
+ */
+ if (done_alias || !handle_alias(argcp, argv))
+ break;
+ done_alias = 1;
+ }
+
+ return done_alias;
+}
+
+static void pthread__block_sigwinch(void)
+{
+ sigset_t set;
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGWINCH);
+ pthread_sigmask(SIG_BLOCK, &set, NULL);
+}
+
+void pthread__unblock_sigwinch(void)
+{
+ sigset_t set;
+
+ sigemptyset(&set);
+ sigaddset(&set, SIGWINCH);
+ pthread_sigmask(SIG_UNBLOCK, &set, NULL);
+}
+
+int main(int argc, const char **argv)
+{
+ const char *cmd;
+
+ /* The page_size is placed in util object. */
+ page_size = sysconf(_SC_PAGE_SIZE);
+ cacheline_size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE);
+
+ cmd = perf_extract_argv0_path(argv[0]);
+ if (!cmd)
+ cmd = "perf-help";
+ /* get debugfs mount point from /proc/mounts */
+ perf_debugfs_mount(NULL);
+ /*
+ * "perf-xxxx" is the same as "perf xxxx", but we obviously:
+ *
+ * - cannot take flags in between the "perf" and the "xxxx".
+ * - cannot execute it externally (since it would just do
+ * the same thing over again)
+ *
+ * So we just directly call the internal command handler, and
+ * die if that one cannot handle it.
+ */
+ if (!prefixcmp(cmd, "perf-")) {
+ cmd += 5;
+ argv[0] = cmd;
+ handle_internal_command(argc, argv);
+ fprintf(stderr, "cannot handle %s internally", cmd);
+ goto out;
+ }
+ if (!prefixcmp(cmd, "trace")) {
+#ifdef HAVE_LIBAUDIT_SUPPORT
+ set_buildid_dir();
+ setup_path();
+ argv[0] = "trace";
+ return cmd_trace(argc, argv, NULL);
+#else
+ fprintf(stderr,
+ "trace command not available: missing audit-libs devel package at build time.\n");
+ goto out;
+#endif
+ }
+ /* Look for flags.. */
+ argv++;
+ argc--;
+ handle_options(&argv, &argc, NULL);
+ commit_pager_choice();
+ set_buildid_dir();
+
+ if (argc > 0) {
+ if (!prefixcmp(argv[0], "--"))
+ argv[0] += 2;
+ } else {
+ /* The user didn't specify a command; give them help */
+ printf("\n usage: %s\n\n", perf_usage_string);
+ list_common_cmds_help();
+ printf("\n %s\n\n", perf_more_info_string);
+ goto out;
+ }
+ cmd = argv[0];
+
+ test_attr__init();
+
+ /*
+ * We use PATH to find perf commands, but we prepend some higher
+ * precedence paths: the "--exec-path" option, the PERF_EXEC_PATH
+ * environment, and the $(perfexecdir) from the Makefile at build
+ * time.
+ */
+ setup_path();
+ /*
+ * Block SIGWINCH notifications so that the thread that wants it can
+ * unblock and get syscalls like select interrupted instead of waiting
+ * forever while the signal goes to some other non interested thread.
+ */
+ pthread__block_sigwinch();
+
+ while (1) {
+ static int done_help;
+ int was_alias = run_argv(&argc, &argv);
+
+ if (errno != ENOENT)
+ break;
+
+ if (was_alias) {
+ fprintf(stderr, "Expansion of alias '%s' failed; "
+ "'%s' is not a perf-command\n",
+ cmd, argv[0]);
+ goto out;
+ }
+ if (!done_help) {
+ cmd = argv[0] = help_unknown_cmd(cmd);
+ done_help = 1;
+ } else
+ break;
+ }
+
+ fprintf(stderr, "Failed to run command '%s': %s\n",
+ cmd, strerror(errno));
+out:
+ return 1;
+}
diff --git a/tools/perf/perf.h b/tools/perf/perf.h
new file mode 100644
index 00000000000..510c65f7285
--- /dev/null
+++ b/tools/perf/perf.h
@@ -0,0 +1,68 @@
+#ifndef _PERF_PERF_H
+#define _PERF_PERF_H
+
+#include <time.h>
+#include <stdbool.h>
+#include <linux/types.h>
+#include <linux/perf_event.h>
+
+extern bool test_attr__enabled;
+void test_attr__init(void);
+void test_attr__open(struct perf_event_attr *attr, pid_t pid, int cpu,
+ int fd, int group_fd, unsigned long flags);
+
+#define HAVE_ATTR_TEST
+#include "perf-sys.h"
+
+#ifndef NSEC_PER_SEC
+# define NSEC_PER_SEC 1000000000ULL
+#endif
+#ifndef NSEC_PER_USEC
+# define NSEC_PER_USEC 1000ULL
+#endif
+
+static inline unsigned long long rdclock(void)
+{
+ struct timespec ts;
+
+ clock_gettime(CLOCK_MONOTONIC, &ts);
+ return ts.tv_sec * 1000000000ULL + ts.tv_nsec;
+}
+
+#define MAX_NR_CPUS 256
+
+extern const char *input_name;
+extern bool perf_host, perf_guest;
+extern const char perf_version_string[];
+
+void pthread__unblock_sigwinch(void);
+
+#include "util/target.h"
+
+struct record_opts {
+ struct target target;
+ int call_graph;
+ bool call_graph_enabled;
+ bool group;
+ bool inherit_stat;
+ bool no_buffering;
+ bool no_inherit;
+ bool no_inherit_set;
+ bool no_samples;
+ bool raw_samples;
+ bool sample_address;
+ bool sample_weight;
+ bool sample_time;
+ bool period;
+ unsigned int freq;
+ unsigned int mmap_pages;
+ unsigned int user_freq;
+ u64 branch_stack;
+ u64 default_interval;
+ u64 user_interval;
+ u16 stack_dump_size;
+ bool sample_transaction;
+ unsigned initial_delay;
+};
+
+#endif
diff --git a/tools/perf/python/twatch.py b/tools/perf/python/twatch.py
new file mode 100755
index 00000000000..2225162ee1f
--- /dev/null
+++ b/tools/perf/python/twatch.py
@@ -0,0 +1,41 @@
+#! /usr/bin/python
+# -*- python -*-
+# -*- coding: utf-8 -*-
+# twatch - Experimental use of the perf python interface
+# Copyright (C) 2011 Arnaldo Carvalho de Melo <acme@redhat.com>
+#
+# This application is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; version 2.
+#
+# This application is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+
+import perf
+
+def main():
+ cpus = perf.cpu_map()
+ threads = perf.thread_map()
+ evsel = perf.evsel(task = 1, comm = 1, mmap = 0,
+ wakeup_events = 1, watermark = 1,
+ sample_id_all = 1,
+ sample_type = perf.SAMPLE_PERIOD | perf.SAMPLE_TID | perf.SAMPLE_CPU)
+ evsel.open(cpus = cpus, threads = threads);
+ evlist = perf.evlist(cpus, threads)
+ evlist.add(evsel)
+ evlist.mmap()
+ while True:
+ evlist.poll(timeout = -1)
+ for cpu in cpus:
+ event = evlist.read_on_cpu(cpu)
+ if not event:
+ continue
+ print "cpu: %2d, pid: %4d, tid: %4d" % (event.sample_cpu,
+ event.sample_pid,
+ event.sample_tid),
+ print event
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/Context.c b/tools/perf/scripts/perl/Perf-Trace-Util/Context.c
new file mode 100644
index 00000000000..790ceba6ad3
--- /dev/null
+++ b/tools/perf/scripts/perl/Perf-Trace-Util/Context.c
@@ -0,0 +1,135 @@
+/*
+ * This file was generated automatically by ExtUtils::ParseXS version 2.18_02 from the
+ * contents of Context.xs. Do not edit this file, edit Context.xs instead.
+ *
+ * ANY CHANGES MADE HERE WILL BE LOST!
+ *
+ */
+
+#line 1 "Context.xs"
+/*
+ * Context.xs. XS interfaces for perf script.
+ *
+ * Copyright (C) 2009 Tom Zanussi <tzanussi@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+#include "../../../perf.h"
+#include "../../../util/trace-event.h"
+
+#ifndef PERL_UNUSED_VAR
+# define PERL_UNUSED_VAR(var) if (0) var = var
+#endif
+
+#line 42 "Context.c"
+
+XS(XS_Perf__Trace__Context_common_pc); /* prototype to pass -Wmissing-prototypes */
+XS(XS_Perf__Trace__Context_common_pc)
+{
+#ifdef dVAR
+ dVAR; dXSARGS;
+#else
+ dXSARGS;
+#endif
+ if (items != 1)
+ Perl_croak(aTHX_ "Usage: %s(%s)", "Perf::Trace::Context::common_pc", "context");
+ PERL_UNUSED_VAR(cv); /* -W */
+ {
+ struct scripting_context * context = INT2PTR(struct scripting_context *,SvIV(ST(0)));
+ int RETVAL;
+ dXSTARG;
+
+ RETVAL = common_pc(context);
+ XSprePUSH; PUSHi((IV)RETVAL);
+ }
+ XSRETURN(1);
+}
+
+
+XS(XS_Perf__Trace__Context_common_flags); /* prototype to pass -Wmissing-prototypes */
+XS(XS_Perf__Trace__Context_common_flags)
+{
+#ifdef dVAR
+ dVAR; dXSARGS;
+#else
+ dXSARGS;
+#endif
+ if (items != 1)
+ Perl_croak(aTHX_ "Usage: %s(%s)", "Perf::Trace::Context::common_flags", "context");
+ PERL_UNUSED_VAR(cv); /* -W */
+ {
+ struct scripting_context * context = INT2PTR(struct scripting_context *,SvIV(ST(0)));
+ int RETVAL;
+ dXSTARG;
+
+ RETVAL = common_flags(context);
+ XSprePUSH; PUSHi((IV)RETVAL);
+ }
+ XSRETURN(1);
+}
+
+
+XS(XS_Perf__Trace__Context_common_lock_depth); /* prototype to pass -Wmissing-prototypes */
+XS(XS_Perf__Trace__Context_common_lock_depth)
+{
+#ifdef dVAR
+ dVAR; dXSARGS;
+#else
+ dXSARGS;
+#endif
+ if (items != 1)
+ Perl_croak(aTHX_ "Usage: %s(%s)", "Perf::Trace::Context::common_lock_depth", "context");
+ PERL_UNUSED_VAR(cv); /* -W */
+ {
+ struct scripting_context * context = INT2PTR(struct scripting_context *,SvIV(ST(0)));
+ int RETVAL;
+ dXSTARG;
+
+ RETVAL = common_lock_depth(context);
+ XSprePUSH; PUSHi((IV)RETVAL);
+ }
+ XSRETURN(1);
+}
+
+#ifdef __cplusplus
+extern "C"
+#endif
+XS(boot_Perf__Trace__Context); /* prototype to pass -Wmissing-prototypes */
+XS(boot_Perf__Trace__Context)
+{
+#ifdef dVAR
+ dVAR; dXSARGS;
+#else
+ dXSARGS;
+#endif
+ const char* file = __FILE__;
+
+ PERL_UNUSED_VAR(cv); /* -W */
+ PERL_UNUSED_VAR(items); /* -W */
+ XS_VERSION_BOOTCHECK ;
+
+ newXSproto("Perf::Trace::Context::common_pc", XS_Perf__Trace__Context_common_pc, file, "$");
+ newXSproto("Perf::Trace::Context::common_flags", XS_Perf__Trace__Context_common_flags, file, "$");
+ newXSproto("Perf::Trace::Context::common_lock_depth", XS_Perf__Trace__Context_common_lock_depth, file, "$");
+ if (PL_unitcheckav)
+ call_list(PL_scopestack_ix, PL_unitcheckav);
+ XSRETURN_YES;
+}
+
diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/Context.xs b/tools/perf/scripts/perl/Perf-Trace-Util/Context.xs
new file mode 100644
index 00000000000..8c7ea42444d
--- /dev/null
+++ b/tools/perf/scripts/perl/Perf-Trace-Util/Context.xs
@@ -0,0 +1,42 @@
+/*
+ * Context.xs. XS interfaces for perf script.
+ *
+ * Copyright (C) 2009 Tom Zanussi <tzanussi@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include "EXTERN.h"
+#include "perl.h"
+#include "XSUB.h"
+#include "../../../perf.h"
+#include "../../../util/trace-event.h"
+
+MODULE = Perf::Trace::Context PACKAGE = Perf::Trace::Context
+PROTOTYPES: ENABLE
+
+int
+common_pc(context)
+ struct scripting_context * context
+
+int
+common_flags(context)
+ struct scripting_context * context
+
+int
+common_lock_depth(context)
+ struct scripting_context * context
+
diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/Makefile.PL b/tools/perf/scripts/perl/Perf-Trace-Util/Makefile.PL
new file mode 100644
index 00000000000..decdeb0f678
--- /dev/null
+++ b/tools/perf/scripts/perl/Perf-Trace-Util/Makefile.PL
@@ -0,0 +1,17 @@
+use 5.010000;
+use ExtUtils::MakeMaker;
+# See lib/ExtUtils/MakeMaker.pm for details of how to influence
+# the contents of the Makefile that is written.
+WriteMakefile(
+ NAME => 'Perf::Trace::Context',
+ VERSION_FROM => 'lib/Perf/Trace/Context.pm', # finds $VERSION
+ PREREQ_PM => {}, # e.g., Module::Name => 1.1
+ ($] >= 5.005 ? ## Add these new keywords supported since 5.005
+ (ABSTRACT_FROM => 'lib/Perf/Trace/Context.pm', # retrieve abstract from module
+ AUTHOR => 'Tom Zanussi <tzanussi@gmail.com>') : ()),
+ LIBS => [''], # e.g., '-lm'
+ DEFINE => '-I ../..', # e.g., '-DHAVE_SOMETHING'
+ INC => '-I.', # e.g., '-I. -I/usr/include/other'
+ # Un-comment this if you add C files to link with later:
+ OBJECT => 'Context.o', # link all the C files too
+);
diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/README b/tools/perf/scripts/perl/Perf-Trace-Util/README
new file mode 100644
index 00000000000..2f0c7f3043e
--- /dev/null
+++ b/tools/perf/scripts/perl/Perf-Trace-Util/README
@@ -0,0 +1,59 @@
+Perf-Trace-Util version 0.01
+============================
+
+This module contains utility functions for use with perf script.
+
+Core.pm and Util.pm are pure Perl modules; Core.pm contains routines
+that the core perf support for Perl calls on and should always be
+'used', while Util.pm contains useful but optional utility functions
+that scripts may want to use. Context.pm contains the Perl->C
+interface that allows scripts to access data in the embedding perf
+executable; scripts wishing to do that should 'use Context.pm'.
+
+The Perl->C perf interface is completely driven by Context.xs. If you
+want to add new Perl functions that end up accessing C data in the
+perf executable, you add desciptions of the new functions here.
+scripting_context is a pointer to the perf data in the perf executable
+that you want to access - it's passed as the second parameter,
+$context, to all handler functions.
+
+After you do that:
+
+ perl Makefile.PL # to create a Makefile for the next step
+ make # to create Context.c
+
+ edit Context.c to add const to the char* file = __FILE__ line in
+ XS(boot_Perf__Trace__Context) to silence a warning/error.
+
+ You can delete the Makefile, object files and anything else that was
+ generated e.g. blib and shared library, etc, except for of course
+ Context.c
+
+ You should then be able to run the normal perf make as usual.
+
+INSTALLATION
+
+Building perf with perf script Perl scripting should install this
+module in the right place.
+
+You should make sure libperl and ExtUtils/Embed.pm are installed first
+e.g. apt-get install libperl-dev or yum install perl-ExtUtils-Embed.
+
+DEPENDENCIES
+
+This module requires these other modules and libraries:
+
+ None
+
+COPYRIGHT AND LICENCE
+
+Copyright (C) 2009 by Tom Zanussi <tzanussi@gmail.com>
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+Alternatively, this software may be distributed under the terms of the
+GNU General Public License ("GPL") version 2 as published by the Free
+Software Foundation.
+
diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Context.pm b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Context.pm
new file mode 100644
index 00000000000..4e2f6039ac9
--- /dev/null
+++ b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Context.pm
@@ -0,0 +1,55 @@
+package Perf::Trace::Context;
+
+use 5.010000;
+use strict;
+use warnings;
+
+require Exporter;
+
+our @ISA = qw(Exporter);
+
+our %EXPORT_TAGS = ( 'all' => [ qw(
+) ] );
+
+our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
+
+our @EXPORT = qw(
+ common_pc common_flags common_lock_depth
+);
+
+our $VERSION = '0.01';
+
+require XSLoader;
+XSLoader::load('Perf::Trace::Context', $VERSION);
+
+1;
+__END__
+=head1 NAME
+
+Perf::Trace::Context - Perl extension for accessing functions in perf.
+
+=head1 SYNOPSIS
+
+ use Perf::Trace::Context;
+
+=head1 SEE ALSO
+
+Perf (script) documentation
+
+=head1 AUTHOR
+
+Tom Zanussi, E<lt>tzanussi@gmail.com<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009 by Tom Zanussi
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+Alternatively, this software may be distributed under the terms of the
+GNU General Public License ("GPL") version 2 as published by the Free
+Software Foundation.
+
+=cut
diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Core.pm b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Core.pm
new file mode 100644
index 00000000000..9158458d3ee
--- /dev/null
+++ b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Core.pm
@@ -0,0 +1,192 @@
+package Perf::Trace::Core;
+
+use 5.010000;
+use strict;
+use warnings;
+
+require Exporter;
+
+our @ISA = qw(Exporter);
+
+our %EXPORT_TAGS = ( 'all' => [ qw(
+) ] );
+
+our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
+
+our @EXPORT = qw(
+define_flag_field define_flag_value flag_str dump_flag_fields
+define_symbolic_field define_symbolic_value symbol_str dump_symbolic_fields
+trace_flag_str
+);
+
+our $VERSION = '0.01';
+
+my %trace_flags = (0x00 => "NONE",
+ 0x01 => "IRQS_OFF",
+ 0x02 => "IRQS_NOSUPPORT",
+ 0x04 => "NEED_RESCHED",
+ 0x08 => "HARDIRQ",
+ 0x10 => "SOFTIRQ");
+
+sub trace_flag_str
+{
+ my ($value) = @_;
+
+ my $string;
+
+ my $print_delim = 0;
+
+ foreach my $idx (sort {$a <=> $b} keys %trace_flags) {
+ if (!$value && !$idx) {
+ $string .= "NONE";
+ last;
+ }
+
+ if ($idx && ($value & $idx) == $idx) {
+ if ($print_delim) {
+ $string .= " | ";
+ }
+ $string .= "$trace_flags{$idx}";
+ $print_delim = 1;
+ $value &= ~$idx;
+ }
+ }
+
+ return $string;
+}
+
+my %flag_fields;
+my %symbolic_fields;
+
+sub flag_str
+{
+ my ($event_name, $field_name, $value) = @_;
+
+ my $string;
+
+ if ($flag_fields{$event_name}{$field_name}) {
+ my $print_delim = 0;
+ foreach my $idx (sort {$a <=> $b} keys %{$flag_fields{$event_name}{$field_name}{"values"}}) {
+ if (!$value && !$idx) {
+ $string .= "$flag_fields{$event_name}{$field_name}{'values'}{$idx}";
+ last;
+ }
+ if ($idx && ($value & $idx) == $idx) {
+ if ($print_delim && $flag_fields{$event_name}{$field_name}{'delim'}) {
+ $string .= " $flag_fields{$event_name}{$field_name}{'delim'} ";
+ }
+ $string .= "$flag_fields{$event_name}{$field_name}{'values'}{$idx}";
+ $print_delim = 1;
+ $value &= ~$idx;
+ }
+ }
+ }
+
+ return $string;
+}
+
+sub define_flag_field
+{
+ my ($event_name, $field_name, $delim) = @_;
+
+ $flag_fields{$event_name}{$field_name}{"delim"} = $delim;
+}
+
+sub define_flag_value
+{
+ my ($event_name, $field_name, $value, $field_str) = @_;
+
+ $flag_fields{$event_name}{$field_name}{"values"}{$value} = $field_str;
+}
+
+sub dump_flag_fields
+{
+ for my $event (keys %flag_fields) {
+ print "event $event:\n";
+ for my $field (keys %{$flag_fields{$event}}) {
+ print " field: $field:\n";
+ print " delim: $flag_fields{$event}{$field}{'delim'}\n";
+ foreach my $idx (sort {$a <=> $b} keys %{$flag_fields{$event}{$field}{"values"}}) {
+ print " value $idx: $flag_fields{$event}{$field}{'values'}{$idx}\n";
+ }
+ }
+ }
+}
+
+sub symbol_str
+{
+ my ($event_name, $field_name, $value) = @_;
+
+ if ($symbolic_fields{$event_name}{$field_name}) {
+ foreach my $idx (sort {$a <=> $b} keys %{$symbolic_fields{$event_name}{$field_name}{"values"}}) {
+ if (!$value && !$idx) {
+ return "$symbolic_fields{$event_name}{$field_name}{'values'}{$idx}";
+ last;
+ }
+ if ($value == $idx) {
+ return "$symbolic_fields{$event_name}{$field_name}{'values'}{$idx}";
+ }
+ }
+ }
+
+ return undef;
+}
+
+sub define_symbolic_field
+{
+ my ($event_name, $field_name) = @_;
+
+ # nothing to do, really
+}
+
+sub define_symbolic_value
+{
+ my ($event_name, $field_name, $value, $field_str) = @_;
+
+ $symbolic_fields{$event_name}{$field_name}{"values"}{$value} = $field_str;
+}
+
+sub dump_symbolic_fields
+{
+ for my $event (keys %symbolic_fields) {
+ print "event $event:\n";
+ for my $field (keys %{$symbolic_fields{$event}}) {
+ print " field: $field:\n";
+ foreach my $idx (sort {$a <=> $b} keys %{$symbolic_fields{$event}{$field}{"values"}}) {
+ print " value $idx: $symbolic_fields{$event}{$field}{'values'}{$idx}\n";
+ }
+ }
+ }
+}
+
+1;
+__END__
+=head1 NAME
+
+Perf::Trace::Core - Perl extension for perf script
+
+=head1 SYNOPSIS
+
+ use Perf::Trace::Core
+
+=head1 SEE ALSO
+
+Perf (script) documentation
+
+=head1 AUTHOR
+
+Tom Zanussi, E<lt>tzanussi@gmail.com<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009 by Tom Zanussi
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+Alternatively, this software may be distributed under the terms of the
+GNU General Public License ("GPL") version 2 as published by the Free
+Software Foundation.
+
+=cut
diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Util.pm b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Util.pm
new file mode 100644
index 00000000000..05350011462
--- /dev/null
+++ b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Util.pm
@@ -0,0 +1,94 @@
+package Perf::Trace::Util;
+
+use 5.010000;
+use strict;
+use warnings;
+
+require Exporter;
+
+our @ISA = qw(Exporter);
+
+our %EXPORT_TAGS = ( 'all' => [ qw(
+) ] );
+
+our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
+
+our @EXPORT = qw(
+avg nsecs nsecs_secs nsecs_nsecs nsecs_usecs print_nsecs
+clear_term
+);
+
+our $VERSION = '0.01';
+
+sub avg
+{
+ my ($total, $n) = @_;
+
+ return $total / $n;
+}
+
+my $NSECS_PER_SEC = 1000000000;
+
+sub nsecs
+{
+ my ($secs, $nsecs) = @_;
+
+ return $secs * $NSECS_PER_SEC + $nsecs;
+}
+
+sub nsecs_secs {
+ my ($nsecs) = @_;
+
+ return $nsecs / $NSECS_PER_SEC;
+}
+
+sub nsecs_nsecs {
+ my ($nsecs) = @_;
+
+ return $nsecs % $NSECS_PER_SEC;
+}
+
+sub nsecs_str {
+ my ($nsecs) = @_;
+
+ my $str = sprintf("%5u.%09u", nsecs_secs($nsecs), nsecs_nsecs($nsecs));
+
+ return $str;
+}
+
+sub clear_term
+{
+ print "\x1b[H\x1b[2J";
+}
+
+1;
+__END__
+=head1 NAME
+
+Perf::Trace::Util - Perl extension for perf script
+
+=head1 SYNOPSIS
+
+ use Perf::Trace::Util;
+
+=head1 SEE ALSO
+
+Perf (script) documentation
+
+=head1 AUTHOR
+
+Tom Zanussi, E<lt>tzanussi@gmail.com<gt>
+
+=head1 COPYRIGHT AND LICENSE
+
+Copyright (C) 2009 by Tom Zanussi
+
+This library is free software; you can redistribute it and/or modify
+it under the same terms as Perl itself, either Perl version 5.10.0 or,
+at your option, any later version of Perl 5 you may have available.
+
+Alternatively, this software may be distributed under the terms of the
+GNU General Public License ("GPL") version 2 as published by the Free
+Software Foundation.
+
+=cut
diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/typemap b/tools/perf/scripts/perl/Perf-Trace-Util/typemap
new file mode 100644
index 00000000000..840836804aa
--- /dev/null
+++ b/tools/perf/scripts/perl/Perf-Trace-Util/typemap
@@ -0,0 +1 @@
+struct scripting_context * T_PTR
diff --git a/tools/perf/scripts/perl/bin/check-perf-trace-record b/tools/perf/scripts/perl/bin/check-perf-trace-record
new file mode 100644
index 00000000000..423ad6aed05
--- /dev/null
+++ b/tools/perf/scripts/perl/bin/check-perf-trace-record
@@ -0,0 +1,2 @@
+#!/bin/bash
+perf record -a -e kmem:kmalloc -e irq:softirq_entry -e kmem:kfree
diff --git a/tools/perf/scripts/perl/bin/failed-syscalls-record b/tools/perf/scripts/perl/bin/failed-syscalls-record
new file mode 100644
index 00000000000..8104895a7b6
--- /dev/null
+++ b/tools/perf/scripts/perl/bin/failed-syscalls-record
@@ -0,0 +1,2 @@
+#!/bin/bash
+perf record -e raw_syscalls:sys_exit $@
diff --git a/tools/perf/scripts/perl/bin/failed-syscalls-report b/tools/perf/scripts/perl/bin/failed-syscalls-report
new file mode 100644
index 00000000000..9f83cc1ad8b
--- /dev/null
+++ b/tools/perf/scripts/perl/bin/failed-syscalls-report
@@ -0,0 +1,10 @@
+#!/bin/bash
+# description: system-wide failed syscalls
+# args: [comm]
+if [ $# -gt 0 ] ; then
+ if ! expr match "$1" "-" > /dev/null ; then
+ comm=$1
+ shift
+ fi
+fi
+perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/failed-syscalls.pl $comm
diff --git a/tools/perf/scripts/perl/bin/rw-by-file-record b/tools/perf/scripts/perl/bin/rw-by-file-record
new file mode 100644
index 00000000000..33efc8673aa
--- /dev/null
+++ b/tools/perf/scripts/perl/bin/rw-by-file-record
@@ -0,0 +1,3 @@
+#!/bin/bash
+perf record -e syscalls:sys_enter_read -e syscalls:sys_enter_write $@
+
diff --git a/tools/perf/scripts/perl/bin/rw-by-file-report b/tools/perf/scripts/perl/bin/rw-by-file-report
new file mode 100644
index 00000000000..77200b3f310
--- /dev/null
+++ b/tools/perf/scripts/perl/bin/rw-by-file-report
@@ -0,0 +1,10 @@
+#!/bin/bash
+# description: r/w activity for a program, by file
+# args: <comm>
+if [ $# -lt 1 ] ; then
+ echo "usage: rw-by-file <comm>"
+ exit
+fi
+comm=$1
+shift
+perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/rw-by-file.pl $comm
diff --git a/tools/perf/scripts/perl/bin/rw-by-pid-record b/tools/perf/scripts/perl/bin/rw-by-pid-record
new file mode 100644
index 00000000000..7cb9db23044
--- /dev/null
+++ b/tools/perf/scripts/perl/bin/rw-by-pid-record
@@ -0,0 +1,2 @@
+#!/bin/bash
+perf record -e syscalls:sys_enter_read -e syscalls:sys_exit_read -e syscalls:sys_enter_write -e syscalls:sys_exit_write $@
diff --git a/tools/perf/scripts/perl/bin/rw-by-pid-report b/tools/perf/scripts/perl/bin/rw-by-pid-report
new file mode 100644
index 00000000000..a27b9f311f9
--- /dev/null
+++ b/tools/perf/scripts/perl/bin/rw-by-pid-report
@@ -0,0 +1,3 @@
+#!/bin/bash
+# description: system-wide r/w activity
+perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/rw-by-pid.pl
diff --git a/tools/perf/scripts/perl/bin/rwtop-record b/tools/perf/scripts/perl/bin/rwtop-record
new file mode 100644
index 00000000000..7cb9db23044
--- /dev/null
+++ b/tools/perf/scripts/perl/bin/rwtop-record
@@ -0,0 +1,2 @@
+#!/bin/bash
+perf record -e syscalls:sys_enter_read -e syscalls:sys_exit_read -e syscalls:sys_enter_write -e syscalls:sys_exit_write $@
diff --git a/tools/perf/scripts/perl/bin/rwtop-report b/tools/perf/scripts/perl/bin/rwtop-report
new file mode 100644
index 00000000000..83e11ec2e19
--- /dev/null
+++ b/tools/perf/scripts/perl/bin/rwtop-report
@@ -0,0 +1,20 @@
+#!/bin/bash
+# description: system-wide r/w top
+# args: [interval]
+n_args=0
+for i in "$@"
+do
+ if expr match "$i" "-" > /dev/null ; then
+ break
+ fi
+ n_args=$(( $n_args + 1 ))
+done
+if [ "$n_args" -gt 1 ] ; then
+ echo "usage: rwtop-report [interval]"
+ exit
+fi
+if [ "$n_args" -gt 0 ] ; then
+ interval=$1
+ shift
+fi
+perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/rwtop.pl $interval
diff --git a/tools/perf/scripts/perl/bin/wakeup-latency-record b/tools/perf/scripts/perl/bin/wakeup-latency-record
new file mode 100644
index 00000000000..464251a1bd7
--- /dev/null
+++ b/tools/perf/scripts/perl/bin/wakeup-latency-record
@@ -0,0 +1,6 @@
+#!/bin/bash
+perf record -e sched:sched_switch -e sched:sched_wakeup $@
+
+
+
+
diff --git a/tools/perf/scripts/perl/bin/wakeup-latency-report b/tools/perf/scripts/perl/bin/wakeup-latency-report
new file mode 100644
index 00000000000..889e8130cca
--- /dev/null
+++ b/tools/perf/scripts/perl/bin/wakeup-latency-report
@@ -0,0 +1,3 @@
+#!/bin/bash
+# description: system-wide min/max/avg wakeup latency
+perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/wakeup-latency.pl
diff --git a/tools/perf/scripts/perl/check-perf-trace.pl b/tools/perf/scripts/perl/check-perf-trace.pl
new file mode 100644
index 00000000000..4e7076c2061
--- /dev/null
+++ b/tools/perf/scripts/perl/check-perf-trace.pl
@@ -0,0 +1,106 @@
+# perf script event handlers, generated by perf script -g perl
+# (c) 2009, Tom Zanussi <tzanussi@gmail.com>
+# Licensed under the terms of the GNU GPL License version 2
+
+# This script tests basic functionality such as flag and symbol
+# strings, common_xxx() calls back into perf, begin, end, unhandled
+# events, etc. Basically, if this script runs successfully and
+# displays expected results, perl scripting support should be ok.
+
+use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib";
+use lib "./Perf-Trace-Util/lib";
+use Perf::Trace::Core;
+use Perf::Trace::Context;
+use Perf::Trace::Util;
+
+sub trace_begin
+{
+ print "trace_begin\n";
+}
+
+sub trace_end
+{
+ print "trace_end\n";
+
+ print_unhandled();
+}
+
+sub irq::softirq_entry
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm,
+ $vec) = @_;
+
+ print_header($event_name, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm);
+
+ print_uncommon($context);
+
+ printf("vec=%s\n",
+ symbol_str("irq::softirq_entry", "vec", $vec));
+}
+
+sub kmem::kmalloc
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm,
+ $call_site, $ptr, $bytes_req, $bytes_alloc,
+ $gfp_flags) = @_;
+
+ print_header($event_name, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm);
+
+ print_uncommon($context);
+
+ printf("call_site=%p, ptr=%p, bytes_req=%u, bytes_alloc=%u, ".
+ "gfp_flags=%s\n",
+ $call_site, $ptr, $bytes_req, $bytes_alloc,
+
+ flag_str("kmem::kmalloc", "gfp_flags", $gfp_flags));
+}
+
+# print trace fields not included in handler args
+sub print_uncommon
+{
+ my ($context) = @_;
+
+ printf("common_preempt_count=%d, common_flags=%s, common_lock_depth=%d, ",
+ common_pc($context), trace_flag_str(common_flags($context)),
+ common_lock_depth($context));
+
+}
+
+my %unhandled;
+
+sub print_unhandled
+{
+ if ((scalar keys %unhandled) == 0) {
+ return;
+ }
+
+ print "\nunhandled events:\n\n";
+
+ printf("%-40s %10s\n", "event", "count");
+ printf("%-40s %10s\n", "----------------------------------------",
+ "-----------");
+
+ foreach my $event_name (keys %unhandled) {
+ printf("%-40s %10d\n", $event_name, $unhandled{$event_name});
+ }
+}
+
+sub trace_unhandled
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm) = @_;
+
+ $unhandled{$event_name}++;
+}
+
+sub print_header
+{
+ my ($event_name, $cpu, $secs, $nsecs, $pid, $comm) = @_;
+
+ printf("%-20s %5u %05u.%09u %8u %-20s ",
+ $event_name, $cpu, $secs, $nsecs, $pid, $comm);
+}
diff --git a/tools/perf/scripts/perl/failed-syscalls.pl b/tools/perf/scripts/perl/failed-syscalls.pl
new file mode 100644
index 00000000000..94bc25a347e
--- /dev/null
+++ b/tools/perf/scripts/perl/failed-syscalls.pl
@@ -0,0 +1,42 @@
+# failed system call counts
+# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
+# Licensed under the terms of the GNU GPL License version 2
+#
+# Displays system-wide failed system call totals
+# If a [comm] arg is specified, only syscalls called by [comm] are displayed.
+
+use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib";
+use lib "./Perf-Trace-Util/lib";
+use Perf::Trace::Core;
+use Perf::Trace::Context;
+use Perf::Trace::Util;
+
+my $for_comm = shift;
+
+my %failed_syscalls;
+
+sub raw_syscalls::sys_exit
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm,
+ $id, $ret) = @_;
+
+ if ($ret < 0) {
+ $failed_syscalls{$common_comm}++;
+ }
+}
+
+sub trace_end
+{
+ printf("\nfailed syscalls by comm:\n\n");
+
+ printf("%-20s %10s\n", "comm", "# errors");
+ printf("%-20s %6s %10s\n", "--------------------", "----------");
+
+ foreach my $comm (sort {$failed_syscalls{$b} <=> $failed_syscalls{$a}}
+ keys %failed_syscalls) {
+ next if ($for_comm && $comm ne $for_comm);
+
+ printf("%-20s %10s\n", $comm, $failed_syscalls{$comm});
+ }
+}
diff --git a/tools/perf/scripts/perl/rw-by-file.pl b/tools/perf/scripts/perl/rw-by-file.pl
new file mode 100644
index 00000000000..74844ee2be3
--- /dev/null
+++ b/tools/perf/scripts/perl/rw-by-file.pl
@@ -0,0 +1,106 @@
+#!/usr/bin/perl -w
+# (c) 2009, Tom Zanussi <tzanussi@gmail.com>
+# Licensed under the terms of the GNU GPL License version 2
+
+# Display r/w activity for files read/written to for a given program
+
+# The common_* event handler fields are the most useful fields common to
+# all events. They don't necessarily correspond to the 'common_*' fields
+# in the status files. Those fields not available as handler params can
+# be retrieved via script functions of the form get_common_*().
+
+use 5.010000;
+use strict;
+use warnings;
+
+use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib";
+use lib "./Perf-Trace-Util/lib";
+use Perf::Trace::Core;
+use Perf::Trace::Util;
+
+my $usage = "perf script -s rw-by-file.pl <comm>\n";
+
+my $for_comm = shift or die $usage;
+
+my %reads;
+my %writes;
+
+sub syscalls::sys_enter_read
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm, $nr, $fd, $buf, $count) = @_;
+
+ if ($common_comm eq $for_comm) {
+ $reads{$fd}{bytes_requested} += $count;
+ $reads{$fd}{total_reads}++;
+ }
+}
+
+sub syscalls::sys_enter_write
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm, $nr, $fd, $buf, $count) = @_;
+
+ if ($common_comm eq $for_comm) {
+ $writes{$fd}{bytes_written} += $count;
+ $writes{$fd}{total_writes}++;
+ }
+}
+
+sub trace_end
+{
+ printf("file read counts for $for_comm:\n\n");
+
+ printf("%6s %10s %10s\n", "fd", "# reads", "bytes_requested");
+ printf("%6s %10s %10s\n", "------", "----------", "-----------");
+
+ foreach my $fd (sort {$reads{$b}{bytes_requested} <=>
+ $reads{$a}{bytes_requested}} keys %reads) {
+ my $total_reads = $reads{$fd}{total_reads};
+ my $bytes_requested = $reads{$fd}{bytes_requested};
+ printf("%6u %10u %10u\n", $fd, $total_reads, $bytes_requested);
+ }
+
+ printf("\nfile write counts for $for_comm:\n\n");
+
+ printf("%6s %10s %10s\n", "fd", "# writes", "bytes_written");
+ printf("%6s %10s %10s\n", "------", "----------", "-----------");
+
+ foreach my $fd (sort {$writes{$b}{bytes_written} <=>
+ $writes{$a}{bytes_written}} keys %writes) {
+ my $total_writes = $writes{$fd}{total_writes};
+ my $bytes_written = $writes{$fd}{bytes_written};
+ printf("%6u %10u %10u\n", $fd, $total_writes, $bytes_written);
+ }
+
+ print_unhandled();
+}
+
+my %unhandled;
+
+sub print_unhandled
+{
+ if ((scalar keys %unhandled) == 0) {
+ return;
+ }
+
+ print "\nunhandled events:\n\n";
+
+ printf("%-40s %10s\n", "event", "count");
+ printf("%-40s %10s\n", "----------------------------------------",
+ "-----------");
+
+ foreach my $event_name (keys %unhandled) {
+ printf("%-40s %10d\n", $event_name, $unhandled{$event_name});
+ }
+}
+
+sub trace_unhandled
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm) = @_;
+
+ $unhandled{$event_name}++;
+}
+
+
diff --git a/tools/perf/scripts/perl/rw-by-pid.pl b/tools/perf/scripts/perl/rw-by-pid.pl
new file mode 100644
index 00000000000..9db23c9daf5
--- /dev/null
+++ b/tools/perf/scripts/perl/rw-by-pid.pl
@@ -0,0 +1,184 @@
+#!/usr/bin/perl -w
+# (c) 2009, Tom Zanussi <tzanussi@gmail.com>
+# Licensed under the terms of the GNU GPL License version 2
+
+# Display r/w activity for all processes
+
+# The common_* event handler fields are the most useful fields common to
+# all events. They don't necessarily correspond to the 'common_*' fields
+# in the status files. Those fields not available as handler params can
+# be retrieved via script functions of the form get_common_*().
+
+use 5.010000;
+use strict;
+use warnings;
+
+use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib";
+use lib "./Perf-Trace-Util/lib";
+use Perf::Trace::Core;
+use Perf::Trace::Util;
+
+my %reads;
+my %writes;
+
+sub syscalls::sys_exit_read
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm,
+ $nr, $ret) = @_;
+
+ if ($ret > 0) {
+ $reads{$common_pid}{bytes_read} += $ret;
+ } else {
+ if (!defined ($reads{$common_pid}{bytes_read})) {
+ $reads{$common_pid}{bytes_read} = 0;
+ }
+ $reads{$common_pid}{errors}{$ret}++;
+ }
+}
+
+sub syscalls::sys_enter_read
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm,
+ $nr, $fd, $buf, $count) = @_;
+
+ $reads{$common_pid}{bytes_requested} += $count;
+ $reads{$common_pid}{total_reads}++;
+ $reads{$common_pid}{comm} = $common_comm;
+}
+
+sub syscalls::sys_exit_write
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm,
+ $nr, $ret) = @_;
+
+ if ($ret <= 0) {
+ $writes{$common_pid}{errors}{$ret}++;
+ }
+}
+
+sub syscalls::sys_enter_write
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm,
+ $nr, $fd, $buf, $count) = @_;
+
+ $writes{$common_pid}{bytes_written} += $count;
+ $writes{$common_pid}{total_writes}++;
+ $writes{$common_pid}{comm} = $common_comm;
+}
+
+sub trace_end
+{
+ printf("read counts by pid:\n\n");
+
+ printf("%6s %20s %10s %10s %10s\n", "pid", "comm",
+ "# reads", "bytes_requested", "bytes_read");
+ printf("%6s %-20s %10s %10s %10s\n", "------", "--------------------",
+ "-----------", "----------", "----------");
+
+ foreach my $pid (sort { ($reads{$b}{bytes_read} || 0) <=>
+ ($reads{$a}{bytes_read} || 0) } keys %reads) {
+ my $comm = $reads{$pid}{comm} || "";
+ my $total_reads = $reads{$pid}{total_reads} || 0;
+ my $bytes_requested = $reads{$pid}{bytes_requested} || 0;
+ my $bytes_read = $reads{$pid}{bytes_read} || 0;
+
+ printf("%6s %-20s %10s %10s %10s\n", $pid, $comm,
+ $total_reads, $bytes_requested, $bytes_read);
+ }
+
+ printf("\nfailed reads by pid:\n\n");
+
+ printf("%6s %20s %6s %10s\n", "pid", "comm", "error #", "# errors");
+ printf("%6s %20s %6s %10s\n", "------", "--------------------",
+ "------", "----------");
+
+ my @errcounts = ();
+
+ foreach my $pid (keys %reads) {
+ foreach my $error (keys %{$reads{$pid}{errors}}) {
+ my $comm = $reads{$pid}{comm} || "";
+ my $errcount = $reads{$pid}{errors}{$error} || 0;
+ push @errcounts, [$pid, $comm, $error, $errcount];
+ }
+ }
+
+ @errcounts = sort { $b->[3] <=> $a->[3] } @errcounts;
+
+ for my $i (0 .. $#errcounts) {
+ printf("%6d %-20s %6d %10s\n", $errcounts[$i][0],
+ $errcounts[$i][1], $errcounts[$i][2], $errcounts[$i][3]);
+ }
+
+ printf("\nwrite counts by pid:\n\n");
+
+ printf("%6s %20s %10s %10s\n", "pid", "comm",
+ "# writes", "bytes_written");
+ printf("%6s %-20s %10s %10s\n", "------", "--------------------",
+ "-----------", "----------");
+
+ foreach my $pid (sort { ($writes{$b}{bytes_written} || 0) <=>
+ ($writes{$a}{bytes_written} || 0)} keys %writes) {
+ my $comm = $writes{$pid}{comm} || "";
+ my $total_writes = $writes{$pid}{total_writes} || 0;
+ my $bytes_written = $writes{$pid}{bytes_written} || 0;
+
+ printf("%6s %-20s %10s %10s\n", $pid, $comm,
+ $total_writes, $bytes_written);
+ }
+
+ printf("\nfailed writes by pid:\n\n");
+
+ printf("%6s %20s %6s %10s\n", "pid", "comm", "error #", "# errors");
+ printf("%6s %20s %6s %10s\n", "------", "--------------------",
+ "------", "----------");
+
+ @errcounts = ();
+
+ foreach my $pid (keys %writes) {
+ foreach my $error (keys %{$writes{$pid}{errors}}) {
+ my $comm = $writes{$pid}{comm} || "";
+ my $errcount = $writes{$pid}{errors}{$error} || 0;
+ push @errcounts, [$pid, $comm, $error, $errcount];
+ }
+ }
+
+ @errcounts = sort { $b->[3] <=> $a->[3] } @errcounts;
+
+ for my $i (0 .. $#errcounts) {
+ printf("%6d %-20s %6d %10s\n", $errcounts[$i][0],
+ $errcounts[$i][1], $errcounts[$i][2], $errcounts[$i][3]);
+ }
+
+ print_unhandled();
+}
+
+my %unhandled;
+
+sub print_unhandled
+{
+ if ((scalar keys %unhandled) == 0) {
+ return;
+ }
+
+ print "\nunhandled events:\n\n";
+
+ printf("%-40s %10s\n", "event", "count");
+ printf("%-40s %10s\n", "----------------------------------------",
+ "-----------");
+
+ foreach my $event_name (keys %unhandled) {
+ printf("%-40s %10d\n", $event_name, $unhandled{$event_name});
+ }
+}
+
+sub trace_unhandled
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm) = @_;
+
+ $unhandled{$event_name}++;
+}
diff --git a/tools/perf/scripts/perl/rwtop.pl b/tools/perf/scripts/perl/rwtop.pl
new file mode 100644
index 00000000000..8b20787021c
--- /dev/null
+++ b/tools/perf/scripts/perl/rwtop.pl
@@ -0,0 +1,203 @@
+#!/usr/bin/perl -w
+# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
+# Licensed under the terms of the GNU GPL License version 2
+
+# read/write top
+#
+# Periodically displays system-wide r/w call activity, broken down by
+# pid. If an [interval] arg is specified, the display will be
+# refreshed every [interval] seconds. The default interval is 3
+# seconds.
+
+use 5.010000;
+use strict;
+use warnings;
+
+use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib";
+use lib "./Perf-Trace-Util/lib";
+use Perf::Trace::Core;
+use Perf::Trace::Util;
+use POSIX qw/SIGALRM SA_RESTART/;
+
+my $default_interval = 3;
+my $nlines = 20;
+my $print_thread;
+my $print_pending = 0;
+
+my %reads;
+my %writes;
+
+my $interval = shift;
+if (!$interval) {
+ $interval = $default_interval;
+}
+
+sub syscalls::sys_exit_read
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm,
+ $nr, $ret) = @_;
+
+ print_check();
+
+ if ($ret > 0) {
+ $reads{$common_pid}{bytes_read} += $ret;
+ } else {
+ if (!defined ($reads{$common_pid}{bytes_read})) {
+ $reads{$common_pid}{bytes_read} = 0;
+ }
+ $reads{$common_pid}{errors}{$ret}++;
+ }
+}
+
+sub syscalls::sys_enter_read
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm,
+ $nr, $fd, $buf, $count) = @_;
+
+ print_check();
+
+ $reads{$common_pid}{bytes_requested} += $count;
+ $reads{$common_pid}{total_reads}++;
+ $reads{$common_pid}{comm} = $common_comm;
+}
+
+sub syscalls::sys_exit_write
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm,
+ $nr, $ret) = @_;
+
+ print_check();
+
+ if ($ret <= 0) {
+ $writes{$common_pid}{errors}{$ret}++;
+ }
+}
+
+sub syscalls::sys_enter_write
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm,
+ $nr, $fd, $buf, $count) = @_;
+
+ print_check();
+
+ $writes{$common_pid}{bytes_written} += $count;
+ $writes{$common_pid}{total_writes}++;
+ $writes{$common_pid}{comm} = $common_comm;
+}
+
+sub trace_begin
+{
+ my $sa = POSIX::SigAction->new(\&set_print_pending);
+ $sa->flags(SA_RESTART);
+ $sa->safe(1);
+ POSIX::sigaction(SIGALRM, $sa) or die "Can't set SIGALRM handler: $!\n";
+ alarm 1;
+}
+
+sub trace_end
+{
+ print_unhandled();
+ print_totals();
+}
+
+sub print_check()
+{
+ if ($print_pending == 1) {
+ $print_pending = 0;
+ print_totals();
+ }
+}
+
+sub set_print_pending()
+{
+ $print_pending = 1;
+ alarm $interval;
+}
+
+sub print_totals
+{
+ my $count;
+
+ $count = 0;
+
+ clear_term();
+
+ printf("\nread counts by pid:\n\n");
+
+ printf("%6s %20s %10s %10s %10s\n", "pid", "comm",
+ "# reads", "bytes_req", "bytes_read");
+ printf("%6s %-20s %10s %10s %10s\n", "------", "--------------------",
+ "----------", "----------", "----------");
+
+ foreach my $pid (sort { ($reads{$b}{bytes_read} || 0) <=>
+ ($reads{$a}{bytes_read} || 0) } keys %reads) {
+ my $comm = $reads{$pid}{comm} || "";
+ my $total_reads = $reads{$pid}{total_reads} || 0;
+ my $bytes_requested = $reads{$pid}{bytes_requested} || 0;
+ my $bytes_read = $reads{$pid}{bytes_read} || 0;
+
+ printf("%6s %-20s %10s %10s %10s\n", $pid, $comm,
+ $total_reads, $bytes_requested, $bytes_read);
+
+ if (++$count == $nlines) {
+ last;
+ }
+ }
+
+ $count = 0;
+
+ printf("\nwrite counts by pid:\n\n");
+
+ printf("%6s %20s %10s %13s\n", "pid", "comm",
+ "# writes", "bytes_written");
+ printf("%6s %-20s %10s %13s\n", "------", "--------------------",
+ "----------", "-------------");
+
+ foreach my $pid (sort { ($writes{$b}{bytes_written} || 0) <=>
+ ($writes{$a}{bytes_written} || 0)} keys %writes) {
+ my $comm = $writes{$pid}{comm} || "";
+ my $total_writes = $writes{$pid}{total_writes} || 0;
+ my $bytes_written = $writes{$pid}{bytes_written} || 0;
+
+ printf("%6s %-20s %10s %13s\n", $pid, $comm,
+ $total_writes, $bytes_written);
+
+ if (++$count == $nlines) {
+ last;
+ }
+ }
+
+ %reads = ();
+ %writes = ();
+}
+
+my %unhandled;
+
+sub print_unhandled
+{
+ if ((scalar keys %unhandled) == 0) {
+ return;
+ }
+
+ print "\nunhandled events:\n\n";
+
+ printf("%-40s %10s\n", "event", "count");
+ printf("%-40s %10s\n", "----------------------------------------",
+ "-----------");
+
+ foreach my $event_name (keys %unhandled) {
+ printf("%-40s %10d\n", $event_name, $unhandled{$event_name});
+ }
+}
+
+sub trace_unhandled
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm) = @_;
+
+ $unhandled{$event_name}++;
+}
diff --git a/tools/perf/scripts/perl/wakeup-latency.pl b/tools/perf/scripts/perl/wakeup-latency.pl
new file mode 100644
index 00000000000..d9143dcec6c
--- /dev/null
+++ b/tools/perf/scripts/perl/wakeup-latency.pl
@@ -0,0 +1,107 @@
+#!/usr/bin/perl -w
+# (c) 2009, Tom Zanussi <tzanussi@gmail.com>
+# Licensed under the terms of the GNU GPL License version 2
+
+# Display avg/min/max wakeup latency
+
+# The common_* event handler fields are the most useful fields common to
+# all events. They don't necessarily correspond to the 'common_*' fields
+# in the status files. Those fields not available as handler params can
+# be retrieved via script functions of the form get_common_*().
+
+use 5.010000;
+use strict;
+use warnings;
+
+use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib";
+use lib "./Perf-Trace-Util/lib";
+use Perf::Trace::Core;
+use Perf::Trace::Util;
+
+my %last_wakeup;
+
+my $max_wakeup_latency;
+my $min_wakeup_latency;
+my $total_wakeup_latency = 0;
+my $total_wakeups = 0;
+
+sub sched::sched_switch
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm,
+ $prev_comm, $prev_pid, $prev_prio, $prev_state, $next_comm, $next_pid,
+ $next_prio) = @_;
+
+ my $wakeup_ts = $last_wakeup{$common_cpu}{ts};
+ if ($wakeup_ts) {
+ my $switch_ts = nsecs($common_secs, $common_nsecs);
+ my $wakeup_latency = $switch_ts - $wakeup_ts;
+ if ($wakeup_latency > $max_wakeup_latency) {
+ $max_wakeup_latency = $wakeup_latency;
+ }
+ if ($wakeup_latency < $min_wakeup_latency) {
+ $min_wakeup_latency = $wakeup_latency;
+ }
+ $total_wakeup_latency += $wakeup_latency;
+ $total_wakeups++;
+ }
+ $last_wakeup{$common_cpu}{ts} = 0;
+}
+
+sub sched::sched_wakeup
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm,
+ $comm, $pid, $prio, $success, $target_cpu) = @_;
+
+ $last_wakeup{$target_cpu}{ts} = nsecs($common_secs, $common_nsecs);
+}
+
+sub trace_begin
+{
+ $min_wakeup_latency = 1000000000;
+ $max_wakeup_latency = 0;
+}
+
+sub trace_end
+{
+ printf("wakeup_latency stats:\n\n");
+ print "total_wakeups: $total_wakeups\n";
+ if ($total_wakeups) {
+ printf("avg_wakeup_latency (ns): %u\n",
+ avg($total_wakeup_latency, $total_wakeups));
+ } else {
+ printf("avg_wakeup_latency (ns): N/A\n");
+ }
+ printf("min_wakeup_latency (ns): %u\n", $min_wakeup_latency);
+ printf("max_wakeup_latency (ns): %u\n", $max_wakeup_latency);
+
+ print_unhandled();
+}
+
+my %unhandled;
+
+sub print_unhandled
+{
+ if ((scalar keys %unhandled) == 0) {
+ return;
+ }
+
+ print "\nunhandled events:\n\n";
+
+ printf("%-40s %10s\n", "event", "count");
+ printf("%-40s %10s\n", "----------------------------------------",
+ "-----------");
+
+ foreach my $event_name (keys %unhandled) {
+ printf("%-40s %10d\n", $event_name, $unhandled{$event_name});
+ }
+}
+
+sub trace_unhandled
+{
+ my ($event_name, $context, $common_cpu, $common_secs, $common_nsecs,
+ $common_pid, $common_comm) = @_;
+
+ $unhandled{$event_name}++;
+}
diff --git a/tools/perf/scripts/python/Perf-Trace-Util/Context.c b/tools/perf/scripts/python/Perf-Trace-Util/Context.c
new file mode 100644
index 00000000000..fcd1dd66790
--- /dev/null
+++ b/tools/perf/scripts/python/Perf-Trace-Util/Context.c
@@ -0,0 +1,88 @@
+/*
+ * Context.c. Python interfaces for perf script.
+ *
+ * Copyright (C) 2010 Tom Zanussi <tzanussi@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <Python.h>
+#include "../../../perf.h"
+#include "../../../util/trace-event.h"
+
+PyMODINIT_FUNC initperf_trace_context(void);
+
+static PyObject *perf_trace_context_common_pc(PyObject *obj, PyObject *args)
+{
+ static struct scripting_context *scripting_context;
+ PyObject *context;
+ int retval;
+
+ if (!PyArg_ParseTuple(args, "O", &context))
+ return NULL;
+
+ scripting_context = PyCObject_AsVoidPtr(context);
+ retval = common_pc(scripting_context);
+
+ return Py_BuildValue("i", retval);
+}
+
+static PyObject *perf_trace_context_common_flags(PyObject *obj,
+ PyObject *args)
+{
+ static struct scripting_context *scripting_context;
+ PyObject *context;
+ int retval;
+
+ if (!PyArg_ParseTuple(args, "O", &context))
+ return NULL;
+
+ scripting_context = PyCObject_AsVoidPtr(context);
+ retval = common_flags(scripting_context);
+
+ return Py_BuildValue("i", retval);
+}
+
+static PyObject *perf_trace_context_common_lock_depth(PyObject *obj,
+ PyObject *args)
+{
+ static struct scripting_context *scripting_context;
+ PyObject *context;
+ int retval;
+
+ if (!PyArg_ParseTuple(args, "O", &context))
+ return NULL;
+
+ scripting_context = PyCObject_AsVoidPtr(context);
+ retval = common_lock_depth(scripting_context);
+
+ return Py_BuildValue("i", retval);
+}
+
+static PyMethodDef ContextMethods[] = {
+ { "common_pc", perf_trace_context_common_pc, METH_VARARGS,
+ "Get the common preempt count event field value."},
+ { "common_flags", perf_trace_context_common_flags, METH_VARARGS,
+ "Get the common flags event field value."},
+ { "common_lock_depth", perf_trace_context_common_lock_depth,
+ METH_VARARGS, "Get the common lock depth event field value."},
+ { NULL, NULL, 0, NULL}
+};
+
+PyMODINIT_FUNC initperf_trace_context(void)
+{
+ (void) Py_InitModule("perf_trace_context", ContextMethods);
+}
diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py
new file mode 100644
index 00000000000..de7211e4fa4
--- /dev/null
+++ b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py
@@ -0,0 +1,121 @@
+# Core.py - Python extension for perf script, core functions
+#
+# Copyright (C) 2010 by Tom Zanussi <tzanussi@gmail.com>
+#
+# This software may be distributed under the terms of the GNU General
+# Public License ("GPL") version 2 as published by the Free Software
+# Foundation.
+
+from collections import defaultdict
+
+def autodict():
+ return defaultdict(autodict)
+
+flag_fields = autodict()
+symbolic_fields = autodict()
+
+def define_flag_field(event_name, field_name, delim):
+ flag_fields[event_name][field_name]['delim'] = delim
+
+def define_flag_value(event_name, field_name, value, field_str):
+ flag_fields[event_name][field_name]['values'][value] = field_str
+
+def define_symbolic_field(event_name, field_name):
+ # nothing to do, really
+ pass
+
+def define_symbolic_value(event_name, field_name, value, field_str):
+ symbolic_fields[event_name][field_name]['values'][value] = field_str
+
+def flag_str(event_name, field_name, value):
+ string = ""
+
+ if flag_fields[event_name][field_name]:
+ print_delim = 0
+ keys = flag_fields[event_name][field_name]['values'].keys()
+ keys.sort()
+ for idx in keys:
+ if not value and not idx:
+ string += flag_fields[event_name][field_name]['values'][idx]
+ break
+ if idx and (value & idx) == idx:
+ if print_delim and flag_fields[event_name][field_name]['delim']:
+ string += " " + flag_fields[event_name][field_name]['delim'] + " "
+ string += flag_fields[event_name][field_name]['values'][idx]
+ print_delim = 1
+ value &= ~idx
+
+ return string
+
+def symbol_str(event_name, field_name, value):
+ string = ""
+
+ if symbolic_fields[event_name][field_name]:
+ keys = symbolic_fields[event_name][field_name]['values'].keys()
+ keys.sort()
+ for idx in keys:
+ if not value and not idx:
+ string = symbolic_fields[event_name][field_name]['values'][idx]
+ break
+ if (value == idx):
+ string = symbolic_fields[event_name][field_name]['values'][idx]
+ break
+
+ return string
+
+trace_flags = { 0x00: "NONE", \
+ 0x01: "IRQS_OFF", \
+ 0x02: "IRQS_NOSUPPORT", \
+ 0x04: "NEED_RESCHED", \
+ 0x08: "HARDIRQ", \
+ 0x10: "SOFTIRQ" }
+
+def trace_flag_str(value):
+ string = ""
+ print_delim = 0
+
+ keys = trace_flags.keys()
+
+ for idx in keys:
+ if not value and not idx:
+ string += "NONE"
+ break
+
+ if idx and (value & idx) == idx:
+ if print_delim:
+ string += " | ";
+ string += trace_flags[idx]
+ print_delim = 1
+ value &= ~idx
+
+ return string
+
+
+def taskState(state):
+ states = {
+ 0 : "R",
+ 1 : "S",
+ 2 : "D",
+ 64: "DEAD"
+ }
+
+ if state not in states:
+ return "Unknown"
+
+ return states[state]
+
+
+class EventHeaders:
+ def __init__(self, common_cpu, common_secs, common_nsecs,
+ common_pid, common_comm):
+ self.cpu = common_cpu
+ self.secs = common_secs
+ self.nsecs = common_nsecs
+ self.pid = common_pid
+ self.comm = common_comm
+
+ def ts(self):
+ return (self.secs * (10 ** 9)) + self.nsecs
+
+ def ts_format(self):
+ return "%d.%d" % (self.secs, int(self.nsecs / 1000))
diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/EventClass.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/EventClass.py
new file mode 100755
index 00000000000..9e0985794e2
--- /dev/null
+++ b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/EventClass.py
@@ -0,0 +1,94 @@
+# EventClass.py
+#
+# This is a library defining some events types classes, which could
+# be used by other scripts to analyzing the perf samples.
+#
+# Currently there are just a few classes defined for examples,
+# PerfEvent is the base class for all perf event sample, PebsEvent
+# is a HW base Intel x86 PEBS event, and user could add more SW/HW
+# event classes based on requirements.
+
+import struct
+
+# Event types, user could add more here
+EVTYPE_GENERIC = 0
+EVTYPE_PEBS = 1 # Basic PEBS event
+EVTYPE_PEBS_LL = 2 # PEBS event with load latency info
+EVTYPE_IBS = 3
+
+#
+# Currently we don't have good way to tell the event type, but by
+# the size of raw buffer, raw PEBS event with load latency data's
+# size is 176 bytes, while the pure PEBS event's size is 144 bytes.
+#
+def create_event(name, comm, dso, symbol, raw_buf):
+ if (len(raw_buf) == 144):
+ event = PebsEvent(name, comm, dso, symbol, raw_buf)
+ elif (len(raw_buf) == 176):
+ event = PebsNHM(name, comm, dso, symbol, raw_buf)
+ else:
+ event = PerfEvent(name, comm, dso, symbol, raw_buf)
+
+ return event
+
+class PerfEvent(object):
+ event_num = 0
+ def __init__(self, name, comm, dso, symbol, raw_buf, ev_type=EVTYPE_GENERIC):
+ self.name = name
+ self.comm = comm
+ self.dso = dso
+ self.symbol = symbol
+ self.raw_buf = raw_buf
+ self.ev_type = ev_type
+ PerfEvent.event_num += 1
+
+ def show(self):
+ print "PMU event: name=%12s, symbol=%24s, comm=%8s, dso=%12s" % (self.name, self.symbol, self.comm, self.dso)
+
+#
+# Basic Intel PEBS (Precise Event-based Sampling) event, whose raw buffer
+# contains the context info when that event happened: the EFLAGS and
+# linear IP info, as well as all the registers.
+#
+class PebsEvent(PerfEvent):
+ pebs_num = 0
+ def __init__(self, name, comm, dso, symbol, raw_buf, ev_type=EVTYPE_PEBS):
+ tmp_buf=raw_buf[0:80]
+ flags, ip, ax, bx, cx, dx, si, di, bp, sp = struct.unpack('QQQQQQQQQQ', tmp_buf)
+ self.flags = flags
+ self.ip = ip
+ self.ax = ax
+ self.bx = bx
+ self.cx = cx
+ self.dx = dx
+ self.si = si
+ self.di = di
+ self.bp = bp
+ self.sp = sp
+
+ PerfEvent.__init__(self, name, comm, dso, symbol, raw_buf, ev_type)
+ PebsEvent.pebs_num += 1
+ del tmp_buf
+
+#
+# Intel Nehalem and Westmere support PEBS plus Load Latency info which lie
+# in the four 64 bit words write after the PEBS data:
+# Status: records the IA32_PERF_GLOBAL_STATUS register value
+# DLA: Data Linear Address (EIP)
+# DSE: Data Source Encoding, where the latency happens, hit or miss
+# in L1/L2/L3 or IO operations
+# LAT: the actual latency in cycles
+#
+class PebsNHM(PebsEvent):
+ pebs_nhm_num = 0
+ def __init__(self, name, comm, dso, symbol, raw_buf, ev_type=EVTYPE_PEBS_LL):
+ tmp_buf=raw_buf[144:176]
+ status, dla, dse, lat = struct.unpack('QQQQ', tmp_buf)
+ self.status = status
+ self.dla = dla
+ self.dse = dse
+ self.lat = lat
+
+ PebsEvent.__init__(self, name, comm, dso, symbol, raw_buf, ev_type)
+ PebsNHM.pebs_nhm_num += 1
+ del tmp_buf
diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py
new file mode 100644
index 00000000000..fdd92f69905
--- /dev/null
+++ b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py
@@ -0,0 +1,184 @@
+# SchedGui.py - Python extension for perf script, basic GUI code for
+# traces drawing and overview.
+#
+# Copyright (C) 2010 by Frederic Weisbecker <fweisbec@gmail.com>
+#
+# This software is distributed under the terms of the GNU General
+# Public License ("GPL") version 2 as published by the Free Software
+# Foundation.
+
+
+try:
+ import wx
+except ImportError:
+ raise ImportError, "You need to install the wxpython lib for this script"
+
+
+class RootFrame(wx.Frame):
+ Y_OFFSET = 100
+ RECT_HEIGHT = 100
+ RECT_SPACE = 50
+ EVENT_MARKING_WIDTH = 5
+
+ def __init__(self, sched_tracer, title, parent = None, id = -1):
+ wx.Frame.__init__(self, parent, id, title)
+
+ (self.screen_width, self.screen_height) = wx.GetDisplaySize()
+ self.screen_width -= 10
+ self.screen_height -= 10
+ self.zoom = 0.5
+ self.scroll_scale = 20
+ self.sched_tracer = sched_tracer
+ self.sched_tracer.set_root_win(self)
+ (self.ts_start, self.ts_end) = sched_tracer.interval()
+ self.update_width_virtual()
+ self.nr_rects = sched_tracer.nr_rectangles() + 1
+ self.height_virtual = RootFrame.Y_OFFSET + (self.nr_rects * (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE))
+
+ # whole window panel
+ self.panel = wx.Panel(self, size=(self.screen_width, self.screen_height))
+
+ # scrollable container
+ self.scroll = wx.ScrolledWindow(self.panel)
+ self.scroll.SetScrollbars(self.scroll_scale, self.scroll_scale, self.width_virtual / self.scroll_scale, self.height_virtual / self.scroll_scale)
+ self.scroll.EnableScrolling(True, True)
+ self.scroll.SetFocus()
+
+ # scrollable drawing area
+ self.scroll_panel = wx.Panel(self.scroll, size=(self.screen_width - 15, self.screen_height / 2))
+ self.scroll_panel.Bind(wx.EVT_PAINT, self.on_paint)
+ self.scroll_panel.Bind(wx.EVT_KEY_DOWN, self.on_key_press)
+ self.scroll_panel.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
+ self.scroll.Bind(wx.EVT_PAINT, self.on_paint)
+ self.scroll.Bind(wx.EVT_KEY_DOWN, self.on_key_press)
+ self.scroll.Bind(wx.EVT_LEFT_DOWN, self.on_mouse_down)
+
+ self.scroll.Fit()
+ self.Fit()
+
+ self.scroll_panel.SetDimensions(-1, -1, self.width_virtual, self.height_virtual, wx.SIZE_USE_EXISTING)
+
+ self.txt = None
+
+ self.Show(True)
+
+ def us_to_px(self, val):
+ return val / (10 ** 3) * self.zoom
+
+ def px_to_us(self, val):
+ return (val / self.zoom) * (10 ** 3)
+
+ def scroll_start(self):
+ (x, y) = self.scroll.GetViewStart()
+ return (x * self.scroll_scale, y * self.scroll_scale)
+
+ def scroll_start_us(self):
+ (x, y) = self.scroll_start()
+ return self.px_to_us(x)
+
+ def paint_rectangle_zone(self, nr, color, top_color, start, end):
+ offset_px = self.us_to_px(start - self.ts_start)
+ width_px = self.us_to_px(end - self.ts_start)
+
+ offset_py = RootFrame.Y_OFFSET + (nr * (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE))
+ width_py = RootFrame.RECT_HEIGHT
+
+ dc = self.dc
+
+ if top_color is not None:
+ (r, g, b) = top_color
+ top_color = wx.Colour(r, g, b)
+ brush = wx.Brush(top_color, wx.SOLID)
+ dc.SetBrush(brush)
+ dc.DrawRectangle(offset_px, offset_py, width_px, RootFrame.EVENT_MARKING_WIDTH)
+ width_py -= RootFrame.EVENT_MARKING_WIDTH
+ offset_py += RootFrame.EVENT_MARKING_WIDTH
+
+ (r ,g, b) = color
+ color = wx.Colour(r, g, b)
+ brush = wx.Brush(color, wx.SOLID)
+ dc.SetBrush(brush)
+ dc.DrawRectangle(offset_px, offset_py, width_px, width_py)
+
+ def update_rectangles(self, dc, start, end):
+ start += self.ts_start
+ end += self.ts_start
+ self.sched_tracer.fill_zone(start, end)
+
+ def on_paint(self, event):
+ dc = wx.PaintDC(self.scroll_panel)
+ self.dc = dc
+
+ width = min(self.width_virtual, self.screen_width)
+ (x, y) = self.scroll_start()
+ start = self.px_to_us(x)
+ end = self.px_to_us(x + width)
+ self.update_rectangles(dc, start, end)
+
+ def rect_from_ypixel(self, y):
+ y -= RootFrame.Y_OFFSET
+ rect = y / (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE)
+ height = y % (RootFrame.RECT_HEIGHT + RootFrame.RECT_SPACE)
+
+ if rect < 0 or rect > self.nr_rects - 1 or height > RootFrame.RECT_HEIGHT:
+ return -1
+
+ return rect
+
+ def update_summary(self, txt):
+ if self.txt:
+ self.txt.Destroy()
+ self.txt = wx.StaticText(self.panel, -1, txt, (0, (self.screen_height / 2) + 50))
+
+
+ def on_mouse_down(self, event):
+ (x, y) = event.GetPositionTuple()
+ rect = self.rect_from_ypixel(y)
+ if rect == -1:
+ return
+
+ t = self.px_to_us(x) + self.ts_start
+
+ self.sched_tracer.mouse_down(rect, t)
+
+
+ def update_width_virtual(self):
+ self.width_virtual = self.us_to_px(self.ts_end - self.ts_start)
+
+ def __zoom(self, x):
+ self.update_width_virtual()
+ (xpos, ypos) = self.scroll.GetViewStart()
+ xpos = self.us_to_px(x) / self.scroll_scale
+ self.scroll.SetScrollbars(self.scroll_scale, self.scroll_scale, self.width_virtual / self.scroll_scale, self.height_virtual / self.scroll_scale, xpos, ypos)
+ self.Refresh()
+
+ def zoom_in(self):
+ x = self.scroll_start_us()
+ self.zoom *= 2
+ self.__zoom(x)
+
+ def zoom_out(self):
+ x = self.scroll_start_us()
+ self.zoom /= 2
+ self.__zoom(x)
+
+
+ def on_key_press(self, event):
+ key = event.GetRawKeyCode()
+ if key == ord("+"):
+ self.zoom_in()
+ return
+ if key == ord("-"):
+ self.zoom_out()
+ return
+
+ key = event.GetKeyCode()
+ (x, y) = self.scroll.GetViewStart()
+ if key == wx.WXK_RIGHT:
+ self.scroll.Scroll(x + 1, y)
+ elif key == wx.WXK_LEFT:
+ self.scroll.Scroll(x - 1, y)
+ elif key == wx.WXK_DOWN:
+ self.scroll.Scroll(x, y + 1)
+ elif key == wx.WXK_UP:
+ self.scroll.Scroll(x, y - 1)
diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py
new file mode 100644
index 00000000000..15c8400240f
--- /dev/null
+++ b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py
@@ -0,0 +1,86 @@
+# Util.py - Python extension for perf script, miscellaneous utility code
+#
+# Copyright (C) 2010 by Tom Zanussi <tzanussi@gmail.com>
+#
+# This software may be distributed under the terms of the GNU General
+# Public License ("GPL") version 2 as published by the Free Software
+# Foundation.
+
+import errno, os
+
+FUTEX_WAIT = 0
+FUTEX_WAKE = 1
+FUTEX_PRIVATE_FLAG = 128
+FUTEX_CLOCK_REALTIME = 256
+FUTEX_CMD_MASK = ~(FUTEX_PRIVATE_FLAG | FUTEX_CLOCK_REALTIME)
+
+NSECS_PER_SEC = 1000000000
+
+def avg(total, n):
+ return total / n
+
+def nsecs(secs, nsecs):
+ return secs * NSECS_PER_SEC + nsecs
+
+def nsecs_secs(nsecs):
+ return nsecs / NSECS_PER_SEC
+
+def nsecs_nsecs(nsecs):
+ return nsecs % NSECS_PER_SEC
+
+def nsecs_str(nsecs):
+ str = "%5u.%09u" % (nsecs_secs(nsecs), nsecs_nsecs(nsecs)),
+ return str
+
+def add_stats(dict, key, value):
+ if not dict.has_key(key):
+ dict[key] = (value, value, value, 1)
+ else:
+ min, max, avg, count = dict[key]
+ if value < min:
+ min = value
+ if value > max:
+ max = value
+ avg = (avg + value) / 2
+ dict[key] = (min, max, avg, count + 1)
+
+def clear_term():
+ print("\x1b[H\x1b[2J")
+
+audit_package_warned = False
+
+try:
+ import audit
+ machine_to_id = {
+ 'x86_64': audit.MACH_86_64,
+ 'alpha' : audit.MACH_ALPHA,
+ 'ia64' : audit.MACH_IA64,
+ 'ppc' : audit.MACH_PPC,
+ 'ppc64' : audit.MACH_PPC64,
+ 's390' : audit.MACH_S390,
+ 's390x' : audit.MACH_S390X,
+ 'i386' : audit.MACH_X86,
+ 'i586' : audit.MACH_X86,
+ 'i686' : audit.MACH_X86,
+ }
+ try:
+ machine_to_id['armeb'] = audit.MACH_ARMEB
+ except:
+ pass
+ machine_id = machine_to_id[os.uname()[4]]
+except:
+ if not audit_package_warned:
+ audit_package_warned = True
+ print "Install the audit-libs-python package to get syscall names"
+
+def syscall_name(id):
+ try:
+ return audit.audit_syscall_to_name(id, machine_id)
+ except:
+ return str(id)
+
+def strerror(nr):
+ try:
+ return errno.errorcode[abs(nr)]
+ except:
+ return "Unknown %d errno" % nr
diff --git a/tools/perf/scripts/python/bin/event_analyzing_sample-record b/tools/perf/scripts/python/bin/event_analyzing_sample-record
new file mode 100644
index 00000000000..5ce652dabd0
--- /dev/null
+++ b/tools/perf/scripts/python/bin/event_analyzing_sample-record
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+#
+# event_analyzing_sample.py can cover all type of perf samples including
+# the tracepoints, so no special record requirements, just record what
+# you want to analyze.
+#
+perf record $@
diff --git a/tools/perf/scripts/python/bin/event_analyzing_sample-report b/tools/perf/scripts/python/bin/event_analyzing_sample-report
new file mode 100644
index 00000000000..0941fc94e15
--- /dev/null
+++ b/tools/perf/scripts/python/bin/event_analyzing_sample-report
@@ -0,0 +1,3 @@
+#!/bin/bash
+# description: analyze all perf samples
+perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/event_analyzing_sample.py
diff --git a/tools/perf/scripts/python/bin/failed-syscalls-by-pid-record b/tools/perf/scripts/python/bin/failed-syscalls-by-pid-record
new file mode 100644
index 00000000000..8104895a7b6
--- /dev/null
+++ b/tools/perf/scripts/python/bin/failed-syscalls-by-pid-record
@@ -0,0 +1,2 @@
+#!/bin/bash
+perf record -e raw_syscalls:sys_exit $@
diff --git a/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report b/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report
new file mode 100644
index 00000000000..fda5096d0cb
--- /dev/null
+++ b/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report
@@ -0,0 +1,10 @@
+#!/bin/bash
+# description: system-wide failed syscalls, by pid
+# args: [comm]
+if [ $# -gt 0 ] ; then
+ if ! expr match "$1" "-" > /dev/null ; then
+ comm=$1
+ shift
+ fi
+fi
+perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/failed-syscalls-by-pid.py $comm
diff --git a/tools/perf/scripts/python/bin/futex-contention-record b/tools/perf/scripts/python/bin/futex-contention-record
new file mode 100644
index 00000000000..b1495c9a9b2
--- /dev/null
+++ b/tools/perf/scripts/python/bin/futex-contention-record
@@ -0,0 +1,2 @@
+#!/bin/bash
+perf record -e syscalls:sys_enter_futex -e syscalls:sys_exit_futex $@
diff --git a/tools/perf/scripts/python/bin/futex-contention-report b/tools/perf/scripts/python/bin/futex-contention-report
new file mode 100644
index 00000000000..6c44271091a
--- /dev/null
+++ b/tools/perf/scripts/python/bin/futex-contention-report
@@ -0,0 +1,4 @@
+#!/bin/bash
+# description: futext contention measurement
+
+perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/futex-contention.py
diff --git a/tools/perf/scripts/python/bin/net_dropmonitor-record b/tools/perf/scripts/python/bin/net_dropmonitor-record
new file mode 100755
index 00000000000..423fb81dada
--- /dev/null
+++ b/tools/perf/scripts/python/bin/net_dropmonitor-record
@@ -0,0 +1,2 @@
+#!/bin/bash
+perf record -e skb:kfree_skb $@
diff --git a/tools/perf/scripts/python/bin/net_dropmonitor-report b/tools/perf/scripts/python/bin/net_dropmonitor-report
new file mode 100755
index 00000000000..8d698f5a06a
--- /dev/null
+++ b/tools/perf/scripts/python/bin/net_dropmonitor-report
@@ -0,0 +1,4 @@
+#!/bin/bash
+# description: display a table of dropped frames
+
+perf script -s "$PERF_EXEC_PATH"/scripts/python/net_dropmonitor.py $@
diff --git a/tools/perf/scripts/python/bin/netdev-times-record b/tools/perf/scripts/python/bin/netdev-times-record
new file mode 100644
index 00000000000..558754b840a
--- /dev/null
+++ b/tools/perf/scripts/python/bin/netdev-times-record
@@ -0,0 +1,8 @@
+#!/bin/bash
+perf record -e net:net_dev_xmit -e net:net_dev_queue \
+ -e net:netif_receive_skb -e net:netif_rx \
+ -e skb:consume_skb -e skb:kfree_skb \
+ -e skb:skb_copy_datagram_iovec -e napi:napi_poll \
+ -e irq:irq_handler_entry -e irq:irq_handler_exit \
+ -e irq:softirq_entry -e irq:softirq_exit \
+ -e irq:softirq_raise $@
diff --git a/tools/perf/scripts/python/bin/netdev-times-report b/tools/perf/scripts/python/bin/netdev-times-report
new file mode 100644
index 00000000000..8f759291da8
--- /dev/null
+++ b/tools/perf/scripts/python/bin/netdev-times-report
@@ -0,0 +1,5 @@
+#!/bin/bash
+# description: display a process of packet and processing time
+# args: [tx] [rx] [dev=] [debug]
+
+perf script -s "$PERF_EXEC_PATH"/scripts/python/netdev-times.py $@
diff --git a/tools/perf/scripts/python/bin/sched-migration-record b/tools/perf/scripts/python/bin/sched-migration-record
new file mode 100644
index 00000000000..7493fddbe99
--- /dev/null
+++ b/tools/perf/scripts/python/bin/sched-migration-record
@@ -0,0 +1,2 @@
+#!/bin/bash
+perf record -m 16384 -e sched:sched_wakeup -e sched:sched_wakeup_new -e sched:sched_switch -e sched:sched_migrate_task $@
diff --git a/tools/perf/scripts/python/bin/sched-migration-report b/tools/perf/scripts/python/bin/sched-migration-report
new file mode 100644
index 00000000000..68b037a1849
--- /dev/null
+++ b/tools/perf/scripts/python/bin/sched-migration-report
@@ -0,0 +1,3 @@
+#!/bin/bash
+# description: sched migration overview
+perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/sched-migration.py
diff --git a/tools/perf/scripts/python/bin/sctop-record b/tools/perf/scripts/python/bin/sctop-record
new file mode 100644
index 00000000000..4efbfaa7f6a
--- /dev/null
+++ b/tools/perf/scripts/python/bin/sctop-record
@@ -0,0 +1,2 @@
+#!/bin/bash
+perf record -e raw_syscalls:sys_enter $@
diff --git a/tools/perf/scripts/python/bin/sctop-report b/tools/perf/scripts/python/bin/sctop-report
new file mode 100644
index 00000000000..c32db294124
--- /dev/null
+++ b/tools/perf/scripts/python/bin/sctop-report
@@ -0,0 +1,24 @@
+#!/bin/bash
+# description: syscall top
+# args: [comm] [interval]
+n_args=0
+for i in "$@"
+do
+ if expr match "$i" "-" > /dev/null ; then
+ break
+ fi
+ n_args=$(( $n_args + 1 ))
+done
+if [ "$n_args" -gt 2 ] ; then
+ echo "usage: sctop-report [comm] [interval]"
+ exit
+fi
+if [ "$n_args" -gt 1 ] ; then
+ comm=$1
+ interval=$2
+ shift 2
+elif [ "$n_args" -gt 0 ] ; then
+ interval=$1
+ shift
+fi
+perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/sctop.py $comm $interval
diff --git a/tools/perf/scripts/python/bin/syscall-counts-by-pid-record b/tools/perf/scripts/python/bin/syscall-counts-by-pid-record
new file mode 100644
index 00000000000..4efbfaa7f6a
--- /dev/null
+++ b/tools/perf/scripts/python/bin/syscall-counts-by-pid-record
@@ -0,0 +1,2 @@
+#!/bin/bash
+perf record -e raw_syscalls:sys_enter $@
diff --git a/tools/perf/scripts/python/bin/syscall-counts-by-pid-report b/tools/perf/scripts/python/bin/syscall-counts-by-pid-report
new file mode 100644
index 00000000000..16eb8d65c54
--- /dev/null
+++ b/tools/perf/scripts/python/bin/syscall-counts-by-pid-report
@@ -0,0 +1,10 @@
+#!/bin/bash
+# description: system-wide syscall counts, by pid
+# args: [comm]
+if [ $# -gt 0 ] ; then
+ if ! expr match "$1" "-" > /dev/null ; then
+ comm=$1
+ shift
+ fi
+fi
+perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/syscall-counts-by-pid.py $comm
diff --git a/tools/perf/scripts/python/bin/syscall-counts-record b/tools/perf/scripts/python/bin/syscall-counts-record
new file mode 100644
index 00000000000..4efbfaa7f6a
--- /dev/null
+++ b/tools/perf/scripts/python/bin/syscall-counts-record
@@ -0,0 +1,2 @@
+#!/bin/bash
+perf record -e raw_syscalls:sys_enter $@
diff --git a/tools/perf/scripts/python/bin/syscall-counts-report b/tools/perf/scripts/python/bin/syscall-counts-report
new file mode 100644
index 00000000000..0f0e9d453bb
--- /dev/null
+++ b/tools/perf/scripts/python/bin/syscall-counts-report
@@ -0,0 +1,10 @@
+#!/bin/bash
+# description: system-wide syscall counts
+# args: [comm]
+if [ $# -gt 0 ] ; then
+ if ! expr match "$1" "-" > /dev/null ; then
+ comm=$1
+ shift
+ fi
+fi
+perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/syscall-counts.py $comm
diff --git a/tools/perf/scripts/python/check-perf-trace.py b/tools/perf/scripts/python/check-perf-trace.py
new file mode 100644
index 00000000000..4647a7694cf
--- /dev/null
+++ b/tools/perf/scripts/python/check-perf-trace.py
@@ -0,0 +1,82 @@
+# perf script event handlers, generated by perf script -g python
+# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
+# Licensed under the terms of the GNU GPL License version 2
+#
+# This script tests basic functionality such as flag and symbol
+# strings, common_xxx() calls back into perf, begin, end, unhandled
+# events, etc. Basically, if this script runs successfully and
+# displays expected results, Python scripting support should be ok.
+
+import os
+import sys
+
+sys.path.append(os.environ['PERF_EXEC_PATH'] + \
+ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+
+from Core import *
+from perf_trace_context import *
+
+unhandled = autodict()
+
+def trace_begin():
+ print "trace_begin"
+ pass
+
+def trace_end():
+ print_unhandled()
+
+def irq__softirq_entry(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ vec):
+ print_header(event_name, common_cpu, common_secs, common_nsecs,
+ common_pid, common_comm)
+
+ print_uncommon(context)
+
+ print "vec=%s\n" % \
+ (symbol_str("irq__softirq_entry", "vec", vec)),
+
+def kmem__kmalloc(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ call_site, ptr, bytes_req, bytes_alloc,
+ gfp_flags):
+ print_header(event_name, common_cpu, common_secs, common_nsecs,
+ common_pid, common_comm)
+
+ print_uncommon(context)
+
+ print "call_site=%u, ptr=%u, bytes_req=%u, " \
+ "bytes_alloc=%u, gfp_flags=%s\n" % \
+ (call_site, ptr, bytes_req, bytes_alloc,
+
+ flag_str("kmem__kmalloc", "gfp_flags", gfp_flags)),
+
+def trace_unhandled(event_name, context, event_fields_dict):
+ try:
+ unhandled[event_name] += 1
+ except TypeError:
+ unhandled[event_name] = 1
+
+def print_header(event_name, cpu, secs, nsecs, pid, comm):
+ print "%-20s %5u %05u.%09u %8u %-20s " % \
+ (event_name, cpu, secs, nsecs, pid, comm),
+
+# print trace fields not included in handler args
+def print_uncommon(context):
+ print "common_preempt_count=%d, common_flags=%s, common_lock_depth=%d, " \
+ % (common_pc(context), trace_flag_str(common_flags(context)), \
+ common_lock_depth(context))
+
+def print_unhandled():
+ keys = unhandled.keys()
+ if not keys:
+ return
+
+ print "\nunhandled events:\n\n",
+
+ print "%-40s %10s\n" % ("event", "count"),
+ print "%-40s %10s\n" % ("----------------------------------------", \
+ "-----------"),
+
+ for event_name in keys:
+ print "%-40s %10d\n" % (event_name, unhandled[event_name])
diff --git a/tools/perf/scripts/python/event_analyzing_sample.py b/tools/perf/scripts/python/event_analyzing_sample.py
new file mode 100644
index 00000000000..163c39fa12d
--- /dev/null
+++ b/tools/perf/scripts/python/event_analyzing_sample.py
@@ -0,0 +1,189 @@
+# event_analyzing_sample.py: general event handler in python
+#
+# Current perf report is already very powerful with the annotation integrated,
+# and this script is not trying to be as powerful as perf report, but
+# providing end user/developer a flexible way to analyze the events other
+# than trace points.
+#
+# The 2 database related functions in this script just show how to gather
+# the basic information, and users can modify and write their own functions
+# according to their specific requirement.
+#
+# The first function "show_general_events" just does a basic grouping for all
+# generic events with the help of sqlite, and the 2nd one "show_pebs_ll" is
+# for a x86 HW PMU event: PEBS with load latency data.
+#
+
+import os
+import sys
+import math
+import struct
+import sqlite3
+
+sys.path.append(os.environ['PERF_EXEC_PATH'] + \
+ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+
+from perf_trace_context import *
+from EventClass import *
+
+#
+# If the perf.data has a big number of samples, then the insert operation
+# will be very time consuming (about 10+ minutes for 10000 samples) if the
+# .db database is on disk. Move the .db file to RAM based FS to speedup
+# the handling, which will cut the time down to several seconds.
+#
+con = sqlite3.connect("/dev/shm/perf.db")
+con.isolation_level = None
+
+def trace_begin():
+ print "In trace_begin:\n"
+
+ #
+ # Will create several tables at the start, pebs_ll is for PEBS data with
+ # load latency info, while gen_events is for general event.
+ #
+ con.execute("""
+ create table if not exists gen_events (
+ name text,
+ symbol text,
+ comm text,
+ dso text
+ );""")
+ con.execute("""
+ create table if not exists pebs_ll (
+ name text,
+ symbol text,
+ comm text,
+ dso text,
+ flags integer,
+ ip integer,
+ status integer,
+ dse integer,
+ dla integer,
+ lat integer
+ );""")
+
+#
+# Create and insert event object to a database so that user could
+# do more analysis with simple database commands.
+#
+def process_event(param_dict):
+ event_attr = param_dict["attr"]
+ sample = param_dict["sample"]
+ raw_buf = param_dict["raw_buf"]
+ comm = param_dict["comm"]
+ name = param_dict["ev_name"]
+
+ # Symbol and dso info are not always resolved
+ if (param_dict.has_key("dso")):
+ dso = param_dict["dso"]
+ else:
+ dso = "Unknown_dso"
+
+ if (param_dict.has_key("symbol")):
+ symbol = param_dict["symbol"]
+ else:
+ symbol = "Unknown_symbol"
+
+ # Create the event object and insert it to the right table in database
+ event = create_event(name, comm, dso, symbol, raw_buf)
+ insert_db(event)
+
+def insert_db(event):
+ if event.ev_type == EVTYPE_GENERIC:
+ con.execute("insert into gen_events values(?, ?, ?, ?)",
+ (event.name, event.symbol, event.comm, event.dso))
+ elif event.ev_type == EVTYPE_PEBS_LL:
+ event.ip &= 0x7fffffffffffffff
+ event.dla &= 0x7fffffffffffffff
+ con.execute("insert into pebs_ll values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ (event.name, event.symbol, event.comm, event.dso, event.flags,
+ event.ip, event.status, event.dse, event.dla, event.lat))
+
+def trace_end():
+ print "In trace_end:\n"
+ # We show the basic info for the 2 type of event classes
+ show_general_events()
+ show_pebs_ll()
+ con.close()
+
+#
+# As the event number may be very big, so we can't use linear way
+# to show the histogram in real number, but use a log2 algorithm.
+#
+
+def num2sym(num):
+ # Each number will have at least one '#'
+ snum = '#' * (int)(math.log(num, 2) + 1)
+ return snum
+
+def show_general_events():
+
+ # Check the total record number in the table
+ count = con.execute("select count(*) from gen_events")
+ for t in count:
+ print "There is %d records in gen_events table" % t[0]
+ if t[0] == 0:
+ return
+
+ print "Statistics about the general events grouped by thread/symbol/dso: \n"
+
+ # Group by thread
+ commq = con.execute("select comm, count(comm) from gen_events group by comm order by -count(comm)")
+ print "\n%16s %8s %16s\n%s" % ("comm", "number", "histogram", "="*42)
+ for row in commq:
+ print "%16s %8d %s" % (row[0], row[1], num2sym(row[1]))
+
+ # Group by symbol
+ print "\n%32s %8s %16s\n%s" % ("symbol", "number", "histogram", "="*58)
+ symbolq = con.execute("select symbol, count(symbol) from gen_events group by symbol order by -count(symbol)")
+ for row in symbolq:
+ print "%32s %8d %s" % (row[0], row[1], num2sym(row[1]))
+
+ # Group by dso
+ print "\n%40s %8s %16s\n%s" % ("dso", "number", "histogram", "="*74)
+ dsoq = con.execute("select dso, count(dso) from gen_events group by dso order by -count(dso)")
+ for row in dsoq:
+ print "%40s %8d %s" % (row[0], row[1], num2sym(row[1]))
+
+#
+# This function just shows the basic info, and we could do more with the
+# data in the tables, like checking the function parameters when some
+# big latency events happen.
+#
+def show_pebs_ll():
+
+ count = con.execute("select count(*) from pebs_ll")
+ for t in count:
+ print "There is %d records in pebs_ll table" % t[0]
+ if t[0] == 0:
+ return
+
+ print "Statistics about the PEBS Load Latency events grouped by thread/symbol/dse/latency: \n"
+
+ # Group by thread
+ commq = con.execute("select comm, count(comm) from pebs_ll group by comm order by -count(comm)")
+ print "\n%16s %8s %16s\n%s" % ("comm", "number", "histogram", "="*42)
+ for row in commq:
+ print "%16s %8d %s" % (row[0], row[1], num2sym(row[1]))
+
+ # Group by symbol
+ print "\n%32s %8s %16s\n%s" % ("symbol", "number", "histogram", "="*58)
+ symbolq = con.execute("select symbol, count(symbol) from pebs_ll group by symbol order by -count(symbol)")
+ for row in symbolq:
+ print "%32s %8d %s" % (row[0], row[1], num2sym(row[1]))
+
+ # Group by dse
+ dseq = con.execute("select dse, count(dse) from pebs_ll group by dse order by -count(dse)")
+ print "\n%32s %8s %16s\n%s" % ("dse", "number", "histogram", "="*58)
+ for row in dseq:
+ print "%32s %8d %s" % (row[0], row[1], num2sym(row[1]))
+
+ # Group by latency
+ latq = con.execute("select lat, count(lat) from pebs_ll group by lat order by lat")
+ print "\n%32s %8s %16s\n%s" % ("latency", "number", "histogram", "="*58)
+ for row in latq:
+ print "%32s %8d %s" % (row[0], row[1], num2sym(row[1]))
+
+def trace_unhandled(event_name, context, event_fields_dict):
+ print ' '.join(['%s=%s'%(k,str(v))for k,v in sorted(event_fields_dict.items())])
diff --git a/tools/perf/scripts/python/failed-syscalls-by-pid.py b/tools/perf/scripts/python/failed-syscalls-by-pid.py
new file mode 100644
index 00000000000..85805fac411
--- /dev/null
+++ b/tools/perf/scripts/python/failed-syscalls-by-pid.py
@@ -0,0 +1,73 @@
+# failed system call counts, by pid
+# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
+# Licensed under the terms of the GNU GPL License version 2
+#
+# Displays system-wide failed system call totals, broken down by pid.
+# If a [comm] arg is specified, only syscalls called by [comm] are displayed.
+
+import os
+import sys
+
+sys.path.append(os.environ['PERF_EXEC_PATH'] + \
+ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+
+from perf_trace_context import *
+from Core import *
+from Util import *
+
+usage = "perf script -s syscall-counts-by-pid.py [comm|pid]\n";
+
+for_comm = None
+for_pid = None
+
+if len(sys.argv) > 2:
+ sys.exit(usage)
+
+if len(sys.argv) > 1:
+ try:
+ for_pid = int(sys.argv[1])
+ except:
+ for_comm = sys.argv[1]
+
+syscalls = autodict()
+
+def trace_begin():
+ print "Press control+C to stop and show the summary"
+
+def trace_end():
+ print_error_totals()
+
+def raw_syscalls__sys_exit(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ id, ret):
+ if (for_comm and common_comm != for_comm) or \
+ (for_pid and common_pid != for_pid ):
+ return
+
+ if ret < 0:
+ try:
+ syscalls[common_comm][common_pid][id][ret] += 1
+ except TypeError:
+ syscalls[common_comm][common_pid][id][ret] = 1
+
+def print_error_totals():
+ if for_comm is not None:
+ print "\nsyscall errors for %s:\n\n" % (for_comm),
+ else:
+ print "\nsyscall errors:\n\n",
+
+ print "%-30s %10s\n" % ("comm [pid]", "count"),
+ print "%-30s %10s\n" % ("------------------------------", \
+ "----------"),
+
+ comm_keys = syscalls.keys()
+ for comm in comm_keys:
+ pid_keys = syscalls[comm].keys()
+ for pid in pid_keys:
+ print "\n%s [%d]\n" % (comm, pid),
+ id_keys = syscalls[comm][pid].keys()
+ for id in id_keys:
+ print " syscall: %-16s\n" % syscall_name(id),
+ ret_keys = syscalls[comm][pid][id].keys()
+ for ret, val in sorted(syscalls[comm][pid][id].iteritems(), key = lambda(k, v): (v, k), reverse = True):
+ print " err = %-20s %10d\n" % (strerror(ret), val),
diff --git a/tools/perf/scripts/python/futex-contention.py b/tools/perf/scripts/python/futex-contention.py
new file mode 100644
index 00000000000..11e70a388d4
--- /dev/null
+++ b/tools/perf/scripts/python/futex-contention.py
@@ -0,0 +1,50 @@
+# futex contention
+# (c) 2010, Arnaldo Carvalho de Melo <acme@redhat.com>
+# Licensed under the terms of the GNU GPL License version 2
+#
+# Translation of:
+#
+# http://sourceware.org/systemtap/wiki/WSFutexContention
+#
+# to perf python scripting.
+#
+# Measures futex contention
+
+import os, sys
+sys.path.append(os.environ['PERF_EXEC_PATH'] + '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+from Util import *
+
+process_names = {}
+thread_thislock = {}
+thread_blocktime = {}
+
+lock_waits = {} # long-lived stats on (tid,lock) blockage elapsed time
+process_names = {} # long-lived pid-to-execname mapping
+
+def syscalls__sys_enter_futex(event, ctxt, cpu, s, ns, tid, comm,
+ nr, uaddr, op, val, utime, uaddr2, val3):
+ cmd = op & FUTEX_CMD_MASK
+ if cmd != FUTEX_WAIT:
+ return # we don't care about originators of WAKE events
+
+ process_names[tid] = comm
+ thread_thislock[tid] = uaddr
+ thread_blocktime[tid] = nsecs(s, ns)
+
+def syscalls__sys_exit_futex(event, ctxt, cpu, s, ns, tid, comm,
+ nr, ret):
+ if thread_blocktime.has_key(tid):
+ elapsed = nsecs(s, ns) - thread_blocktime[tid]
+ add_stats(lock_waits, (tid, thread_thislock[tid]), elapsed)
+ del thread_blocktime[tid]
+ del thread_thislock[tid]
+
+def trace_begin():
+ print "Press control+C to stop and show the summary"
+
+def trace_end():
+ for (tid, lock) in lock_waits:
+ min, max, avg, count = lock_waits[tid, lock]
+ print "%s[%d] lock %x contended %d times, %d avg ns" % \
+ (process_names[tid], tid, lock, count, avg)
+
diff --git a/tools/perf/scripts/python/net_dropmonitor.py b/tools/perf/scripts/python/net_dropmonitor.py
new file mode 100755
index 00000000000..b5740599aab
--- /dev/null
+++ b/tools/perf/scripts/python/net_dropmonitor.py
@@ -0,0 +1,75 @@
+# Monitor the system for dropped packets and proudce a report of drop locations and counts
+
+import os
+import sys
+
+sys.path.append(os.environ['PERF_EXEC_PATH'] + \
+ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+
+from perf_trace_context import *
+from Core import *
+from Util import *
+
+drop_log = {}
+kallsyms = []
+
+def get_kallsyms_table():
+ global kallsyms
+
+ try:
+ f = open("/proc/kallsyms", "r")
+ except:
+ return
+
+ for line in f:
+ loc = int(line.split()[0], 16)
+ name = line.split()[2]
+ kallsyms.append((loc, name))
+ kallsyms.sort()
+
+def get_sym(sloc):
+ loc = int(sloc)
+
+ # Invariant: kallsyms[i][0] <= loc for all 0 <= i <= start
+ # kallsyms[i][0] > loc for all end <= i < len(kallsyms)
+ start, end = -1, len(kallsyms)
+ while end != start + 1:
+ pivot = (start + end) // 2
+ if loc < kallsyms[pivot][0]:
+ end = pivot
+ else:
+ start = pivot
+
+ # Now (start == -1 or kallsyms[start][0] <= loc)
+ # and (start == len(kallsyms) - 1 or loc < kallsyms[start + 1][0])
+ if start >= 0:
+ symloc, name = kallsyms[start]
+ return (name, loc - symloc)
+ else:
+ return (None, 0)
+
+def print_drop_table():
+ print "%25s %25s %25s" % ("LOCATION", "OFFSET", "COUNT")
+ for i in drop_log.keys():
+ (sym, off) = get_sym(i)
+ if sym == None:
+ sym = i
+ print "%25s %25s %25s" % (sym, off, drop_log[i])
+
+
+def trace_begin():
+ print "Starting trace (Ctrl-C to dump results)"
+
+def trace_end():
+ print "Gathering kallsyms data"
+ get_kallsyms_table()
+ print_drop_table()
+
+# called from perf, when it finds a correspoinding event
+def skb__kfree_skb(name, context, cpu, sec, nsec, pid, comm,
+ skbaddr, location, protocol):
+ slocation = str(location)
+ try:
+ drop_log[slocation] = drop_log[slocation] + 1
+ except:
+ drop_log[slocation] = 1
diff --git a/tools/perf/scripts/python/netdev-times.py b/tools/perf/scripts/python/netdev-times.py
new file mode 100644
index 00000000000..9aa0a32972e
--- /dev/null
+++ b/tools/perf/scripts/python/netdev-times.py
@@ -0,0 +1,464 @@
+# Display a process of packets and processed time.
+# It helps us to investigate networking or network device.
+#
+# options
+# tx: show only tx chart
+# rx: show only rx chart
+# dev=: show only thing related to specified device
+# debug: work with debug mode. It shows buffer status.
+
+import os
+import sys
+
+sys.path.append(os.environ['PERF_EXEC_PATH'] + \
+ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+
+from perf_trace_context import *
+from Core import *
+from Util import *
+
+all_event_list = []; # insert all tracepoint event related with this script
+irq_dic = {}; # key is cpu and value is a list which stacks irqs
+ # which raise NET_RX softirq
+net_rx_dic = {}; # key is cpu and value include time of NET_RX softirq-entry
+ # and a list which stacks receive
+receive_hunk_list = []; # a list which include a sequence of receive events
+rx_skb_list = []; # received packet list for matching
+ # skb_copy_datagram_iovec
+
+buffer_budget = 65536; # the budget of rx_skb_list, tx_queue_list and
+ # tx_xmit_list
+of_count_rx_skb_list = 0; # overflow count
+
+tx_queue_list = []; # list of packets which pass through dev_queue_xmit
+of_count_tx_queue_list = 0; # overflow count
+
+tx_xmit_list = []; # list of packets which pass through dev_hard_start_xmit
+of_count_tx_xmit_list = 0; # overflow count
+
+tx_free_list = []; # list of packets which is freed
+
+# options
+show_tx = 0;
+show_rx = 0;
+dev = 0; # store a name of device specified by option "dev="
+debug = 0;
+
+# indices of event_info tuple
+EINFO_IDX_NAME= 0
+EINFO_IDX_CONTEXT=1
+EINFO_IDX_CPU= 2
+EINFO_IDX_TIME= 3
+EINFO_IDX_PID= 4
+EINFO_IDX_COMM= 5
+
+# Calculate a time interval(msec) from src(nsec) to dst(nsec)
+def diff_msec(src, dst):
+ return (dst - src) / 1000000.0
+
+# Display a process of transmitting a packet
+def print_transmit(hunk):
+ if dev != 0 and hunk['dev'].find(dev) < 0:
+ return
+ print "%7s %5d %6d.%06dsec %12.3fmsec %12.3fmsec" % \
+ (hunk['dev'], hunk['len'],
+ nsecs_secs(hunk['queue_t']),
+ nsecs_nsecs(hunk['queue_t'])/1000,
+ diff_msec(hunk['queue_t'], hunk['xmit_t']),
+ diff_msec(hunk['xmit_t'], hunk['free_t']))
+
+# Format for displaying rx packet processing
+PF_IRQ_ENTRY= " irq_entry(+%.3fmsec irq=%d:%s)"
+PF_SOFT_ENTRY=" softirq_entry(+%.3fmsec)"
+PF_NAPI_POLL= " napi_poll_exit(+%.3fmsec %s)"
+PF_JOINT= " |"
+PF_WJOINT= " | |"
+PF_NET_RECV= " |---netif_receive_skb(+%.3fmsec skb=%x len=%d)"
+PF_NET_RX= " |---netif_rx(+%.3fmsec skb=%x)"
+PF_CPY_DGRAM= " | skb_copy_datagram_iovec(+%.3fmsec %d:%s)"
+PF_KFREE_SKB= " | kfree_skb(+%.3fmsec location=%x)"
+PF_CONS_SKB= " | consume_skb(+%.3fmsec)"
+
+# Display a process of received packets and interrputs associated with
+# a NET_RX softirq
+def print_receive(hunk):
+ show_hunk = 0
+ irq_list = hunk['irq_list']
+ cpu = irq_list[0]['cpu']
+ base_t = irq_list[0]['irq_ent_t']
+ # check if this hunk should be showed
+ if dev != 0:
+ for i in range(len(irq_list)):
+ if irq_list[i]['name'].find(dev) >= 0:
+ show_hunk = 1
+ break
+ else:
+ show_hunk = 1
+ if show_hunk == 0:
+ return
+
+ print "%d.%06dsec cpu=%d" % \
+ (nsecs_secs(base_t), nsecs_nsecs(base_t)/1000, cpu)
+ for i in range(len(irq_list)):
+ print PF_IRQ_ENTRY % \
+ (diff_msec(base_t, irq_list[i]['irq_ent_t']),
+ irq_list[i]['irq'], irq_list[i]['name'])
+ print PF_JOINT
+ irq_event_list = irq_list[i]['event_list']
+ for j in range(len(irq_event_list)):
+ irq_event = irq_event_list[j]
+ if irq_event['event'] == 'netif_rx':
+ print PF_NET_RX % \
+ (diff_msec(base_t, irq_event['time']),
+ irq_event['skbaddr'])
+ print PF_JOINT
+ print PF_SOFT_ENTRY % \
+ diff_msec(base_t, hunk['sirq_ent_t'])
+ print PF_JOINT
+ event_list = hunk['event_list']
+ for i in range(len(event_list)):
+ event = event_list[i]
+ if event['event_name'] == 'napi_poll':
+ print PF_NAPI_POLL % \
+ (diff_msec(base_t, event['event_t']), event['dev'])
+ if i == len(event_list) - 1:
+ print ""
+ else:
+ print PF_JOINT
+ else:
+ print PF_NET_RECV % \
+ (diff_msec(base_t, event['event_t']), event['skbaddr'],
+ event['len'])
+ if 'comm' in event.keys():
+ print PF_WJOINT
+ print PF_CPY_DGRAM % \
+ (diff_msec(base_t, event['comm_t']),
+ event['pid'], event['comm'])
+ elif 'handle' in event.keys():
+ print PF_WJOINT
+ if event['handle'] == "kfree_skb":
+ print PF_KFREE_SKB % \
+ (diff_msec(base_t,
+ event['comm_t']),
+ event['location'])
+ elif event['handle'] == "consume_skb":
+ print PF_CONS_SKB % \
+ diff_msec(base_t,
+ event['comm_t'])
+ print PF_JOINT
+
+def trace_begin():
+ global show_tx
+ global show_rx
+ global dev
+ global debug
+
+ for i in range(len(sys.argv)):
+ if i == 0:
+ continue
+ arg = sys.argv[i]
+ if arg == 'tx':
+ show_tx = 1
+ elif arg =='rx':
+ show_rx = 1
+ elif arg.find('dev=',0, 4) >= 0:
+ dev = arg[4:]
+ elif arg == 'debug':
+ debug = 1
+ if show_tx == 0 and show_rx == 0:
+ show_tx = 1
+ show_rx = 1
+
+def trace_end():
+ # order all events in time
+ all_event_list.sort(lambda a,b :cmp(a[EINFO_IDX_TIME],
+ b[EINFO_IDX_TIME]))
+ # process all events
+ for i in range(len(all_event_list)):
+ event_info = all_event_list[i]
+ name = event_info[EINFO_IDX_NAME]
+ if name == 'irq__softirq_exit':
+ handle_irq_softirq_exit(event_info)
+ elif name == 'irq__softirq_entry':
+ handle_irq_softirq_entry(event_info)
+ elif name == 'irq__softirq_raise':
+ handle_irq_softirq_raise(event_info)
+ elif name == 'irq__irq_handler_entry':
+ handle_irq_handler_entry(event_info)
+ elif name == 'irq__irq_handler_exit':
+ handle_irq_handler_exit(event_info)
+ elif name == 'napi__napi_poll':
+ handle_napi_poll(event_info)
+ elif name == 'net__netif_receive_skb':
+ handle_netif_receive_skb(event_info)
+ elif name == 'net__netif_rx':
+ handle_netif_rx(event_info)
+ elif name == 'skb__skb_copy_datagram_iovec':
+ handle_skb_copy_datagram_iovec(event_info)
+ elif name == 'net__net_dev_queue':
+ handle_net_dev_queue(event_info)
+ elif name == 'net__net_dev_xmit':
+ handle_net_dev_xmit(event_info)
+ elif name == 'skb__kfree_skb':
+ handle_kfree_skb(event_info)
+ elif name == 'skb__consume_skb':
+ handle_consume_skb(event_info)
+ # display receive hunks
+ if show_rx:
+ for i in range(len(receive_hunk_list)):
+ print_receive(receive_hunk_list[i])
+ # display transmit hunks
+ if show_tx:
+ print " dev len Qdisc " \
+ " netdevice free"
+ for i in range(len(tx_free_list)):
+ print_transmit(tx_free_list[i])
+ if debug:
+ print "debug buffer status"
+ print "----------------------------"
+ print "xmit Qdisc:remain:%d overflow:%d" % \
+ (len(tx_queue_list), of_count_tx_queue_list)
+ print "xmit netdevice:remain:%d overflow:%d" % \
+ (len(tx_xmit_list), of_count_tx_xmit_list)
+ print "receive:remain:%d overflow:%d" % \
+ (len(rx_skb_list), of_count_rx_skb_list)
+
+# called from perf, when it finds a correspoinding event
+def irq__softirq_entry(name, context, cpu, sec, nsec, pid, comm, vec):
+ if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX":
+ return
+ event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec)
+ all_event_list.append(event_info)
+
+def irq__softirq_exit(name, context, cpu, sec, nsec, pid, comm, vec):
+ if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX":
+ return
+ event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec)
+ all_event_list.append(event_info)
+
+def irq__softirq_raise(name, context, cpu, sec, nsec, pid, comm, vec):
+ if symbol_str("irq__softirq_entry", "vec", vec) != "NET_RX":
+ return
+ event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, vec)
+ all_event_list.append(event_info)
+
+def irq__irq_handler_entry(name, context, cpu, sec, nsec, pid, comm,
+ irq, irq_name):
+ event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
+ irq, irq_name)
+ all_event_list.append(event_info)
+
+def irq__irq_handler_exit(name, context, cpu, sec, nsec, pid, comm, irq, ret):
+ event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm, irq, ret)
+ all_event_list.append(event_info)
+
+def napi__napi_poll(name, context, cpu, sec, nsec, pid, comm, napi, dev_name):
+ event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
+ napi, dev_name)
+ all_event_list.append(event_info)
+
+def net__netif_receive_skb(name, context, cpu, sec, nsec, pid, comm, skbaddr,
+ skblen, dev_name):
+ event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
+ skbaddr, skblen, dev_name)
+ all_event_list.append(event_info)
+
+def net__netif_rx(name, context, cpu, sec, nsec, pid, comm, skbaddr,
+ skblen, dev_name):
+ event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
+ skbaddr, skblen, dev_name)
+ all_event_list.append(event_info)
+
+def net__net_dev_queue(name, context, cpu, sec, nsec, pid, comm,
+ skbaddr, skblen, dev_name):
+ event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
+ skbaddr, skblen, dev_name)
+ all_event_list.append(event_info)
+
+def net__net_dev_xmit(name, context, cpu, sec, nsec, pid, comm,
+ skbaddr, skblen, rc, dev_name):
+ event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
+ skbaddr, skblen, rc ,dev_name)
+ all_event_list.append(event_info)
+
+def skb__kfree_skb(name, context, cpu, sec, nsec, pid, comm,
+ skbaddr, protocol, location):
+ event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
+ skbaddr, protocol, location)
+ all_event_list.append(event_info)
+
+def skb__consume_skb(name, context, cpu, sec, nsec, pid, comm, skbaddr):
+ event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
+ skbaddr)
+ all_event_list.append(event_info)
+
+def skb__skb_copy_datagram_iovec(name, context, cpu, sec, nsec, pid, comm,
+ skbaddr, skblen):
+ event_info = (name, context, cpu, nsecs(sec, nsec), pid, comm,
+ skbaddr, skblen)
+ all_event_list.append(event_info)
+
+def handle_irq_handler_entry(event_info):
+ (name, context, cpu, time, pid, comm, irq, irq_name) = event_info
+ if cpu not in irq_dic.keys():
+ irq_dic[cpu] = []
+ irq_record = {'irq':irq, 'name':irq_name, 'cpu':cpu, 'irq_ent_t':time}
+ irq_dic[cpu].append(irq_record)
+
+def handle_irq_handler_exit(event_info):
+ (name, context, cpu, time, pid, comm, irq, ret) = event_info
+ if cpu not in irq_dic.keys():
+ return
+ irq_record = irq_dic[cpu].pop()
+ if irq != irq_record['irq']:
+ return
+ irq_record.update({'irq_ext_t':time})
+ # if an irq doesn't include NET_RX softirq, drop.
+ if 'event_list' in irq_record.keys():
+ irq_dic[cpu].append(irq_record)
+
+def handle_irq_softirq_raise(event_info):
+ (name, context, cpu, time, pid, comm, vec) = event_info
+ if cpu not in irq_dic.keys() \
+ or len(irq_dic[cpu]) == 0:
+ return
+ irq_record = irq_dic[cpu].pop()
+ if 'event_list' in irq_record.keys():
+ irq_event_list = irq_record['event_list']
+ else:
+ irq_event_list = []
+ irq_event_list.append({'time':time, 'event':'sirq_raise'})
+ irq_record.update({'event_list':irq_event_list})
+ irq_dic[cpu].append(irq_record)
+
+def handle_irq_softirq_entry(event_info):
+ (name, context, cpu, time, pid, comm, vec) = event_info
+ net_rx_dic[cpu] = {'sirq_ent_t':time, 'event_list':[]}
+
+def handle_irq_softirq_exit(event_info):
+ (name, context, cpu, time, pid, comm, vec) = event_info
+ irq_list = []
+ event_list = 0
+ if cpu in irq_dic.keys():
+ irq_list = irq_dic[cpu]
+ del irq_dic[cpu]
+ if cpu in net_rx_dic.keys():
+ sirq_ent_t = net_rx_dic[cpu]['sirq_ent_t']
+ event_list = net_rx_dic[cpu]['event_list']
+ del net_rx_dic[cpu]
+ if irq_list == [] or event_list == 0:
+ return
+ rec_data = {'sirq_ent_t':sirq_ent_t, 'sirq_ext_t':time,
+ 'irq_list':irq_list, 'event_list':event_list}
+ # merge information realted to a NET_RX softirq
+ receive_hunk_list.append(rec_data)
+
+def handle_napi_poll(event_info):
+ (name, context, cpu, time, pid, comm, napi, dev_name) = event_info
+ if cpu in net_rx_dic.keys():
+ event_list = net_rx_dic[cpu]['event_list']
+ rec_data = {'event_name':'napi_poll',
+ 'dev':dev_name, 'event_t':time}
+ event_list.append(rec_data)
+
+def handle_netif_rx(event_info):
+ (name, context, cpu, time, pid, comm,
+ skbaddr, skblen, dev_name) = event_info
+ if cpu not in irq_dic.keys() \
+ or len(irq_dic[cpu]) == 0:
+ return
+ irq_record = irq_dic[cpu].pop()
+ if 'event_list' in irq_record.keys():
+ irq_event_list = irq_record['event_list']
+ else:
+ irq_event_list = []
+ irq_event_list.append({'time':time, 'event':'netif_rx',
+ 'skbaddr':skbaddr, 'skblen':skblen, 'dev_name':dev_name})
+ irq_record.update({'event_list':irq_event_list})
+ irq_dic[cpu].append(irq_record)
+
+def handle_netif_receive_skb(event_info):
+ global of_count_rx_skb_list
+
+ (name, context, cpu, time, pid, comm,
+ skbaddr, skblen, dev_name) = event_info
+ if cpu in net_rx_dic.keys():
+ rec_data = {'event_name':'netif_receive_skb',
+ 'event_t':time, 'skbaddr':skbaddr, 'len':skblen}
+ event_list = net_rx_dic[cpu]['event_list']
+ event_list.append(rec_data)
+ rx_skb_list.insert(0, rec_data)
+ if len(rx_skb_list) > buffer_budget:
+ rx_skb_list.pop()
+ of_count_rx_skb_list += 1
+
+def handle_net_dev_queue(event_info):
+ global of_count_tx_queue_list
+
+ (name, context, cpu, time, pid, comm,
+ skbaddr, skblen, dev_name) = event_info
+ skb = {'dev':dev_name, 'skbaddr':skbaddr, 'len':skblen, 'queue_t':time}
+ tx_queue_list.insert(0, skb)
+ if len(tx_queue_list) > buffer_budget:
+ tx_queue_list.pop()
+ of_count_tx_queue_list += 1
+
+def handle_net_dev_xmit(event_info):
+ global of_count_tx_xmit_list
+
+ (name, context, cpu, time, pid, comm,
+ skbaddr, skblen, rc, dev_name) = event_info
+ if rc == 0: # NETDEV_TX_OK
+ for i in range(len(tx_queue_list)):
+ skb = tx_queue_list[i]
+ if skb['skbaddr'] == skbaddr:
+ skb['xmit_t'] = time
+ tx_xmit_list.insert(0, skb)
+ del tx_queue_list[i]
+ if len(tx_xmit_list) > buffer_budget:
+ tx_xmit_list.pop()
+ of_count_tx_xmit_list += 1
+ return
+
+def handle_kfree_skb(event_info):
+ (name, context, cpu, time, pid, comm,
+ skbaddr, protocol, location) = event_info
+ for i in range(len(tx_queue_list)):
+ skb = tx_queue_list[i]
+ if skb['skbaddr'] == skbaddr:
+ del tx_queue_list[i]
+ return
+ for i in range(len(tx_xmit_list)):
+ skb = tx_xmit_list[i]
+ if skb['skbaddr'] == skbaddr:
+ skb['free_t'] = time
+ tx_free_list.append(skb)
+ del tx_xmit_list[i]
+ return
+ for i in range(len(rx_skb_list)):
+ rec_data = rx_skb_list[i]
+ if rec_data['skbaddr'] == skbaddr:
+ rec_data.update({'handle':"kfree_skb",
+ 'comm':comm, 'pid':pid, 'comm_t':time})
+ del rx_skb_list[i]
+ return
+
+def handle_consume_skb(event_info):
+ (name, context, cpu, time, pid, comm, skbaddr) = event_info
+ for i in range(len(tx_xmit_list)):
+ skb = tx_xmit_list[i]
+ if skb['skbaddr'] == skbaddr:
+ skb['free_t'] = time
+ tx_free_list.append(skb)
+ del tx_xmit_list[i]
+ return
+
+def handle_skb_copy_datagram_iovec(event_info):
+ (name, context, cpu, time, pid, comm, skbaddr, skblen) = event_info
+ for i in range(len(rx_skb_list)):
+ rec_data = rx_skb_list[i]
+ if skbaddr == rec_data['skbaddr']:
+ rec_data.update({'handle':"skb_copy_datagram_iovec",
+ 'comm':comm, 'pid':pid, 'comm_t':time})
+ del rx_skb_list[i]
+ return
diff --git a/tools/perf/scripts/python/sched-migration.py b/tools/perf/scripts/python/sched-migration.py
new file mode 100644
index 00000000000..74d55ec08ae
--- /dev/null
+++ b/tools/perf/scripts/python/sched-migration.py
@@ -0,0 +1,461 @@
+#!/usr/bin/python
+#
+# Cpu task migration overview toy
+#
+# Copyright (C) 2010 Frederic Weisbecker <fweisbec@gmail.com>
+#
+# perf script event handlers have been generated by perf script -g python
+#
+# This software is distributed under the terms of the GNU General
+# Public License ("GPL") version 2 as published by the Free Software
+# Foundation.
+
+
+import os
+import sys
+
+from collections import defaultdict
+from UserList import UserList
+
+sys.path.append(os.environ['PERF_EXEC_PATH'] + \
+ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+sys.path.append('scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+
+from perf_trace_context import *
+from Core import *
+from SchedGui import *
+
+
+threads = { 0 : "idle"}
+
+def thread_name(pid):
+ return "%s:%d" % (threads[pid], pid)
+
+class RunqueueEventUnknown:
+ @staticmethod
+ def color():
+ return None
+
+ def __repr__(self):
+ return "unknown"
+
+class RunqueueEventSleep:
+ @staticmethod
+ def color():
+ return (0, 0, 0xff)
+
+ def __init__(self, sleeper):
+ self.sleeper = sleeper
+
+ def __repr__(self):
+ return "%s gone to sleep" % thread_name(self.sleeper)
+
+class RunqueueEventWakeup:
+ @staticmethod
+ def color():
+ return (0xff, 0xff, 0)
+
+ def __init__(self, wakee):
+ self.wakee = wakee
+
+ def __repr__(self):
+ return "%s woke up" % thread_name(self.wakee)
+
+class RunqueueEventFork:
+ @staticmethod
+ def color():
+ return (0, 0xff, 0)
+
+ def __init__(self, child):
+ self.child = child
+
+ def __repr__(self):
+ return "new forked task %s" % thread_name(self.child)
+
+class RunqueueMigrateIn:
+ @staticmethod
+ def color():
+ return (0, 0xf0, 0xff)
+
+ def __init__(self, new):
+ self.new = new
+
+ def __repr__(self):
+ return "task migrated in %s" % thread_name(self.new)
+
+class RunqueueMigrateOut:
+ @staticmethod
+ def color():
+ return (0xff, 0, 0xff)
+
+ def __init__(self, old):
+ self.old = old
+
+ def __repr__(self):
+ return "task migrated out %s" % thread_name(self.old)
+
+class RunqueueSnapshot:
+ def __init__(self, tasks = [0], event = RunqueueEventUnknown()):
+ self.tasks = tuple(tasks)
+ self.event = event
+
+ def sched_switch(self, prev, prev_state, next):
+ event = RunqueueEventUnknown()
+
+ if taskState(prev_state) == "R" and next in self.tasks \
+ and prev in self.tasks:
+ return self
+
+ if taskState(prev_state) != "R":
+ event = RunqueueEventSleep(prev)
+
+ next_tasks = list(self.tasks[:])
+ if prev in self.tasks:
+ if taskState(prev_state) != "R":
+ next_tasks.remove(prev)
+ elif taskState(prev_state) == "R":
+ next_tasks.append(prev)
+
+ if next not in next_tasks:
+ next_tasks.append(next)
+
+ return RunqueueSnapshot(next_tasks, event)
+
+ def migrate_out(self, old):
+ if old not in self.tasks:
+ return self
+ next_tasks = [task for task in self.tasks if task != old]
+
+ return RunqueueSnapshot(next_tasks, RunqueueMigrateOut(old))
+
+ def __migrate_in(self, new, event):
+ if new in self.tasks:
+ self.event = event
+ return self
+ next_tasks = self.tasks[:] + tuple([new])
+
+ return RunqueueSnapshot(next_tasks, event)
+
+ def migrate_in(self, new):
+ return self.__migrate_in(new, RunqueueMigrateIn(new))
+
+ def wake_up(self, new):
+ return self.__migrate_in(new, RunqueueEventWakeup(new))
+
+ def wake_up_new(self, new):
+ return self.__migrate_in(new, RunqueueEventFork(new))
+
+ def load(self):
+ """ Provide the number of tasks on the runqueue.
+ Don't count idle"""
+ return len(self.tasks) - 1
+
+ def __repr__(self):
+ ret = self.tasks.__repr__()
+ ret += self.origin_tostring()
+
+ return ret
+
+class TimeSlice:
+ def __init__(self, start, prev):
+ self.start = start
+ self.prev = prev
+ self.end = start
+ # cpus that triggered the event
+ self.event_cpus = []
+ if prev is not None:
+ self.total_load = prev.total_load
+ self.rqs = prev.rqs.copy()
+ else:
+ self.rqs = defaultdict(RunqueueSnapshot)
+ self.total_load = 0
+
+ def __update_total_load(self, old_rq, new_rq):
+ diff = new_rq.load() - old_rq.load()
+ self.total_load += diff
+
+ def sched_switch(self, ts_list, prev, prev_state, next, cpu):
+ old_rq = self.prev.rqs[cpu]
+ new_rq = old_rq.sched_switch(prev, prev_state, next)
+
+ if old_rq is new_rq:
+ return
+
+ self.rqs[cpu] = new_rq
+ self.__update_total_load(old_rq, new_rq)
+ ts_list.append(self)
+ self.event_cpus = [cpu]
+
+ def migrate(self, ts_list, new, old_cpu, new_cpu):
+ if old_cpu == new_cpu:
+ return
+ old_rq = self.prev.rqs[old_cpu]
+ out_rq = old_rq.migrate_out(new)
+ self.rqs[old_cpu] = out_rq
+ self.__update_total_load(old_rq, out_rq)
+
+ new_rq = self.prev.rqs[new_cpu]
+ in_rq = new_rq.migrate_in(new)
+ self.rqs[new_cpu] = in_rq
+ self.__update_total_load(new_rq, in_rq)
+
+ ts_list.append(self)
+
+ if old_rq is not out_rq:
+ self.event_cpus.append(old_cpu)
+ self.event_cpus.append(new_cpu)
+
+ def wake_up(self, ts_list, pid, cpu, fork):
+ old_rq = self.prev.rqs[cpu]
+ if fork:
+ new_rq = old_rq.wake_up_new(pid)
+ else:
+ new_rq = old_rq.wake_up(pid)
+
+ if new_rq is old_rq:
+ return
+ self.rqs[cpu] = new_rq
+ self.__update_total_load(old_rq, new_rq)
+ ts_list.append(self)
+ self.event_cpus = [cpu]
+
+ def next(self, t):
+ self.end = t
+ return TimeSlice(t, self)
+
+class TimeSliceList(UserList):
+ def __init__(self, arg = []):
+ self.data = arg
+
+ def get_time_slice(self, ts):
+ if len(self.data) == 0:
+ slice = TimeSlice(ts, TimeSlice(-1, None))
+ else:
+ slice = self.data[-1].next(ts)
+ return slice
+
+ def find_time_slice(self, ts):
+ start = 0
+ end = len(self.data)
+ found = -1
+ searching = True
+ while searching:
+ if start == end or start == end - 1:
+ searching = False
+
+ i = (end + start) / 2
+ if self.data[i].start <= ts and self.data[i].end >= ts:
+ found = i
+ end = i
+ continue
+
+ if self.data[i].end < ts:
+ start = i
+
+ elif self.data[i].start > ts:
+ end = i
+
+ return found
+
+ def set_root_win(self, win):
+ self.root_win = win
+
+ def mouse_down(self, cpu, t):
+ idx = self.find_time_slice(t)
+ if idx == -1:
+ return
+
+ ts = self[idx]
+ rq = ts.rqs[cpu]
+ raw = "CPU: %d\n" % cpu
+ raw += "Last event : %s\n" % rq.event.__repr__()
+ raw += "Timestamp : %d.%06d\n" % (ts.start / (10 ** 9), (ts.start % (10 ** 9)) / 1000)
+ raw += "Duration : %6d us\n" % ((ts.end - ts.start) / (10 ** 6))
+ raw += "Load = %d\n" % rq.load()
+ for t in rq.tasks:
+ raw += "%s \n" % thread_name(t)
+
+ self.root_win.update_summary(raw)
+
+ def update_rectangle_cpu(self, slice, cpu):
+ rq = slice.rqs[cpu]
+
+ if slice.total_load != 0:
+ load_rate = rq.load() / float(slice.total_load)
+ else:
+ load_rate = 0
+
+ red_power = int(0xff - (0xff * load_rate))
+ color = (0xff, red_power, red_power)
+
+ top_color = None
+
+ if cpu in slice.event_cpus:
+ top_color = rq.event.color()
+
+ self.root_win.paint_rectangle_zone(cpu, color, top_color, slice.start, slice.end)
+
+ def fill_zone(self, start, end):
+ i = self.find_time_slice(start)
+ if i == -1:
+ return
+
+ for i in xrange(i, len(self.data)):
+ timeslice = self.data[i]
+ if timeslice.start > end:
+ return
+
+ for cpu in timeslice.rqs:
+ self.update_rectangle_cpu(timeslice, cpu)
+
+ def interval(self):
+ if len(self.data) == 0:
+ return (0, 0)
+
+ return (self.data[0].start, self.data[-1].end)
+
+ def nr_rectangles(self):
+ last_ts = self.data[-1]
+ max_cpu = 0
+ for cpu in last_ts.rqs:
+ if cpu > max_cpu:
+ max_cpu = cpu
+ return max_cpu
+
+
+class SchedEventProxy:
+ def __init__(self):
+ self.current_tsk = defaultdict(lambda : -1)
+ self.timeslices = TimeSliceList()
+
+ def sched_switch(self, headers, prev_comm, prev_pid, prev_prio, prev_state,
+ next_comm, next_pid, next_prio):
+ """ Ensure the task we sched out this cpu is really the one
+ we logged. Otherwise we may have missed traces """
+
+ on_cpu_task = self.current_tsk[headers.cpu]
+
+ if on_cpu_task != -1 and on_cpu_task != prev_pid:
+ print "Sched switch event rejected ts: %s cpu: %d prev: %s(%d) next: %s(%d)" % \
+ (headers.ts_format(), headers.cpu, prev_comm, prev_pid, next_comm, next_pid)
+
+ threads[prev_pid] = prev_comm
+ threads[next_pid] = next_comm
+ self.current_tsk[headers.cpu] = next_pid
+
+ ts = self.timeslices.get_time_slice(headers.ts())
+ ts.sched_switch(self.timeslices, prev_pid, prev_state, next_pid, headers.cpu)
+
+ def migrate(self, headers, pid, prio, orig_cpu, dest_cpu):
+ ts = self.timeslices.get_time_slice(headers.ts())
+ ts.migrate(self.timeslices, pid, orig_cpu, dest_cpu)
+
+ def wake_up(self, headers, comm, pid, success, target_cpu, fork):
+ if success == 0:
+ return
+ ts = self.timeslices.get_time_slice(headers.ts())
+ ts.wake_up(self.timeslices, pid, target_cpu, fork)
+
+
+def trace_begin():
+ global parser
+ parser = SchedEventProxy()
+
+def trace_end():
+ app = wx.App(False)
+ timeslices = parser.timeslices
+ frame = RootFrame(timeslices, "Migration")
+ app.MainLoop()
+
+def sched__sched_stat_runtime(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ comm, pid, runtime, vruntime):
+ pass
+
+def sched__sched_stat_iowait(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ comm, pid, delay):
+ pass
+
+def sched__sched_stat_sleep(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ comm, pid, delay):
+ pass
+
+def sched__sched_stat_wait(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ comm, pid, delay):
+ pass
+
+def sched__sched_process_fork(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ parent_comm, parent_pid, child_comm, child_pid):
+ pass
+
+def sched__sched_process_wait(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ comm, pid, prio):
+ pass
+
+def sched__sched_process_exit(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ comm, pid, prio):
+ pass
+
+def sched__sched_process_free(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ comm, pid, prio):
+ pass
+
+def sched__sched_migrate_task(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ comm, pid, prio, orig_cpu,
+ dest_cpu):
+ headers = EventHeaders(common_cpu, common_secs, common_nsecs,
+ common_pid, common_comm)
+ parser.migrate(headers, pid, prio, orig_cpu, dest_cpu)
+
+def sched__sched_switch(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ prev_comm, prev_pid, prev_prio, prev_state,
+ next_comm, next_pid, next_prio):
+
+ headers = EventHeaders(common_cpu, common_secs, common_nsecs,
+ common_pid, common_comm)
+ parser.sched_switch(headers, prev_comm, prev_pid, prev_prio, prev_state,
+ next_comm, next_pid, next_prio)
+
+def sched__sched_wakeup_new(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ comm, pid, prio, success,
+ target_cpu):
+ headers = EventHeaders(common_cpu, common_secs, common_nsecs,
+ common_pid, common_comm)
+ parser.wake_up(headers, comm, pid, success, target_cpu, 1)
+
+def sched__sched_wakeup(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ comm, pid, prio, success,
+ target_cpu):
+ headers = EventHeaders(common_cpu, common_secs, common_nsecs,
+ common_pid, common_comm)
+ parser.wake_up(headers, comm, pid, success, target_cpu, 0)
+
+def sched__sched_wait_task(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ comm, pid, prio):
+ pass
+
+def sched__sched_kthread_stop_ret(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ ret):
+ pass
+
+def sched__sched_kthread_stop(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ comm, pid):
+ pass
+
+def trace_unhandled(event_name, context, common_cpu, common_secs, common_nsecs,
+ common_pid, common_comm):
+ pass
diff --git a/tools/perf/scripts/python/sctop.py b/tools/perf/scripts/python/sctop.py
new file mode 100644
index 00000000000..42c267e292f
--- /dev/null
+++ b/tools/perf/scripts/python/sctop.py
@@ -0,0 +1,75 @@
+# system call top
+# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
+# Licensed under the terms of the GNU GPL License version 2
+#
+# Periodically displays system-wide system call totals, broken down by
+# syscall. If a [comm] arg is specified, only syscalls called by
+# [comm] are displayed. If an [interval] arg is specified, the display
+# will be refreshed every [interval] seconds. The default interval is
+# 3 seconds.
+
+import os, sys, thread, time
+
+sys.path.append(os.environ['PERF_EXEC_PATH'] + \
+ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+
+from perf_trace_context import *
+from Core import *
+from Util import *
+
+usage = "perf script -s sctop.py [comm] [interval]\n";
+
+for_comm = None
+default_interval = 3
+interval = default_interval
+
+if len(sys.argv) > 3:
+ sys.exit(usage)
+
+if len(sys.argv) > 2:
+ for_comm = sys.argv[1]
+ interval = int(sys.argv[2])
+elif len(sys.argv) > 1:
+ try:
+ interval = int(sys.argv[1])
+ except ValueError:
+ for_comm = sys.argv[1]
+ interval = default_interval
+
+syscalls = autodict()
+
+def trace_begin():
+ thread.start_new_thread(print_syscall_totals, (interval,))
+ pass
+
+def raw_syscalls__sys_enter(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ id, args):
+ if for_comm is not None:
+ if common_comm != for_comm:
+ return
+ try:
+ syscalls[id] += 1
+ except TypeError:
+ syscalls[id] = 1
+
+def print_syscall_totals(interval):
+ while 1:
+ clear_term()
+ if for_comm is not None:
+ print "\nsyscall events for %s:\n\n" % (for_comm),
+ else:
+ print "\nsyscall events:\n\n",
+
+ print "%-40s %10s\n" % ("event", "count"),
+ print "%-40s %10s\n" % ("----------------------------------------", \
+ "----------"),
+
+ for id, val in sorted(syscalls.iteritems(), key = lambda(k, v): (v, k), \
+ reverse = True):
+ try:
+ print "%-40s %10d\n" % (syscall_name(id), val),
+ except TypeError:
+ pass
+ syscalls.clear()
+ time.sleep(interval)
diff --git a/tools/perf/scripts/python/syscall-counts-by-pid.py b/tools/perf/scripts/python/syscall-counts-by-pid.py
new file mode 100644
index 00000000000..c64d1c55d74
--- /dev/null
+++ b/tools/perf/scripts/python/syscall-counts-by-pid.py
@@ -0,0 +1,69 @@
+# system call counts, by pid
+# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
+# Licensed under the terms of the GNU GPL License version 2
+#
+# Displays system-wide system call totals, broken down by syscall.
+# If a [comm] arg is specified, only syscalls called by [comm] are displayed.
+
+import os, sys
+
+sys.path.append(os.environ['PERF_EXEC_PATH'] + \
+ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+
+from perf_trace_context import *
+from Core import *
+from Util import syscall_name
+
+usage = "perf script -s syscall-counts-by-pid.py [comm]\n";
+
+for_comm = None
+for_pid = None
+
+if len(sys.argv) > 2:
+ sys.exit(usage)
+
+if len(sys.argv) > 1:
+ try:
+ for_pid = int(sys.argv[1])
+ except:
+ for_comm = sys.argv[1]
+
+syscalls = autodict()
+
+def trace_begin():
+ print "Press control+C to stop and show the summary"
+
+def trace_end():
+ print_syscall_totals()
+
+def raw_syscalls__sys_enter(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ id, args):
+
+ if (for_comm and common_comm != for_comm) or \
+ (for_pid and common_pid != for_pid ):
+ return
+ try:
+ syscalls[common_comm][common_pid][id] += 1
+ except TypeError:
+ syscalls[common_comm][common_pid][id] = 1
+
+def print_syscall_totals():
+ if for_comm is not None:
+ print "\nsyscall events for %s:\n\n" % (for_comm),
+ else:
+ print "\nsyscall events by comm/pid:\n\n",
+
+ print "%-40s %10s\n" % ("comm [pid]/syscalls", "count"),
+ print "%-40s %10s\n" % ("----------------------------------------", \
+ "----------"),
+
+ comm_keys = syscalls.keys()
+ for comm in comm_keys:
+ pid_keys = syscalls[comm].keys()
+ for pid in pid_keys:
+ print "\n%s [%d]\n" % (comm, pid),
+ id_keys = syscalls[comm][pid].keys()
+ for id, val in sorted(syscalls[comm][pid].iteritems(), \
+ key = lambda(k, v): (v, k), reverse = True):
+ print " %-38s %10d\n" % (syscall_name(id), val),
diff --git a/tools/perf/scripts/python/syscall-counts.py b/tools/perf/scripts/python/syscall-counts.py
new file mode 100644
index 00000000000..b435d3f188e
--- /dev/null
+++ b/tools/perf/scripts/python/syscall-counts.py
@@ -0,0 +1,59 @@
+# system call counts
+# (c) 2010, Tom Zanussi <tzanussi@gmail.com>
+# Licensed under the terms of the GNU GPL License version 2
+#
+# Displays system-wide system call totals, broken down by syscall.
+# If a [comm] arg is specified, only syscalls called by [comm] are displayed.
+
+import os
+import sys
+
+sys.path.append(os.environ['PERF_EXEC_PATH'] + \
+ '/scripts/python/Perf-Trace-Util/lib/Perf/Trace')
+
+from perf_trace_context import *
+from Core import *
+from Util import syscall_name
+
+usage = "perf script -s syscall-counts.py [comm]\n";
+
+for_comm = None
+
+if len(sys.argv) > 2:
+ sys.exit(usage)
+
+if len(sys.argv) > 1:
+ for_comm = sys.argv[1]
+
+syscalls = autodict()
+
+def trace_begin():
+ print "Press control+C to stop and show the summary"
+
+def trace_end():
+ print_syscall_totals()
+
+def raw_syscalls__sys_enter(event_name, context, common_cpu,
+ common_secs, common_nsecs, common_pid, common_comm,
+ id, args):
+ if for_comm is not None:
+ if common_comm != for_comm:
+ return
+ try:
+ syscalls[id] += 1
+ except TypeError:
+ syscalls[id] = 1
+
+def print_syscall_totals():
+ if for_comm is not None:
+ print "\nsyscall events for %s:\n\n" % (for_comm),
+ else:
+ print "\nsyscall events:\n\n",
+
+ print "%-40s %10s\n" % ("event", "count"),
+ print "%-40s %10s\n" % ("----------------------------------------", \
+ "-----------"),
+
+ for id, val in sorted(syscalls.iteritems(), key = lambda(k, v): (v, k), \
+ reverse = True):
+ print "%-40s %10d\n" % (syscall_name(id), val),
diff --git a/tools/perf/tests/attr.c b/tools/perf/tests/attr.c
new file mode 100644
index 00000000000..2dfc9ad0e6f
--- /dev/null
+++ b/tools/perf/tests/attr.c
@@ -0,0 +1,176 @@
+/*
+ * The struct perf_event_attr test support.
+ *
+ * This test is embedded inside into perf directly and is governed
+ * by the PERF_TEST_ATTR environment variable and hook inside
+ * sys_perf_event_open function.
+ *
+ * The general idea is to store 'struct perf_event_attr' details for
+ * each event created within single perf command. Each event details
+ * are stored into separate text file. Once perf command is finished
+ * these files can be checked for values we expect for command.
+ *
+ * Besides 'struct perf_event_attr' values we also store 'fd' and
+ * 'group_fd' values to allow checking for groups created.
+ *
+ * This all is triggered by setting PERF_TEST_ATTR environment variable.
+ * It must contain name of existing directory with access and write
+ * permissions. All the event text files are stored there.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include "../perf.h"
+#include "util.h"
+#include "exec_cmd.h"
+#include "tests.h"
+
+#define ENV "PERF_TEST_ATTR"
+
+extern int verbose;
+
+static char *dir;
+
+void test_attr__init(void)
+{
+ dir = getenv(ENV);
+ test_attr__enabled = (dir != NULL);
+}
+
+#define BUFSIZE 1024
+
+#define __WRITE_ASS(str, fmt, data) \
+do { \
+ char buf[BUFSIZE]; \
+ size_t size; \
+ \
+ size = snprintf(buf, BUFSIZE, #str "=%"fmt "\n", data); \
+ if (1 != fwrite(buf, size, 1, file)) { \
+ perror("test attr - failed to write event file"); \
+ fclose(file); \
+ return -1; \
+ } \
+ \
+} while (0)
+
+#define WRITE_ASS(field, fmt) __WRITE_ASS(field, fmt, attr->field)
+
+static int store_event(struct perf_event_attr *attr, pid_t pid, int cpu,
+ int fd, int group_fd, unsigned long flags)
+{
+ FILE *file;
+ char path[PATH_MAX];
+
+ snprintf(path, PATH_MAX, "%s/event-%d-%llu-%d", dir,
+ attr->type, attr->config, fd);
+
+ file = fopen(path, "w+");
+ if (!file) {
+ perror("test attr - failed to open event file");
+ return -1;
+ }
+
+ if (fprintf(file, "[event-%d-%llu-%d]\n",
+ attr->type, attr->config, fd) < 0) {
+ perror("test attr - failed to write event file");
+ fclose(file);
+ return -1;
+ }
+
+ /* syscall arguments */
+ __WRITE_ASS(fd, "d", fd);
+ __WRITE_ASS(group_fd, "d", group_fd);
+ __WRITE_ASS(cpu, "d", cpu);
+ __WRITE_ASS(pid, "d", pid);
+ __WRITE_ASS(flags, "lu", flags);
+
+ /* struct perf_event_attr */
+ WRITE_ASS(type, PRIu32);
+ WRITE_ASS(size, PRIu32);
+ WRITE_ASS(config, "llu");
+ WRITE_ASS(sample_period, "llu");
+ WRITE_ASS(sample_type, "llu");
+ WRITE_ASS(read_format, "llu");
+ WRITE_ASS(disabled, "d");
+ WRITE_ASS(inherit, "d");
+ WRITE_ASS(pinned, "d");
+ WRITE_ASS(exclusive, "d");
+ WRITE_ASS(exclude_user, "d");
+ WRITE_ASS(exclude_kernel, "d");
+ WRITE_ASS(exclude_hv, "d");
+ WRITE_ASS(exclude_idle, "d");
+ WRITE_ASS(mmap, "d");
+ WRITE_ASS(comm, "d");
+ WRITE_ASS(freq, "d");
+ WRITE_ASS(inherit_stat, "d");
+ WRITE_ASS(enable_on_exec, "d");
+ WRITE_ASS(task, "d");
+ WRITE_ASS(watermark, "d");
+ WRITE_ASS(precise_ip, "d");
+ WRITE_ASS(mmap_data, "d");
+ WRITE_ASS(sample_id_all, "d");
+ WRITE_ASS(exclude_host, "d");
+ WRITE_ASS(exclude_guest, "d");
+ WRITE_ASS(exclude_callchain_kernel, "d");
+ WRITE_ASS(exclude_callchain_user, "d");
+ WRITE_ASS(wakeup_events, PRIu32);
+ WRITE_ASS(bp_type, PRIu32);
+ WRITE_ASS(config1, "llu");
+ WRITE_ASS(config2, "llu");
+ WRITE_ASS(branch_sample_type, "llu");
+ WRITE_ASS(sample_regs_user, "llu");
+ WRITE_ASS(sample_stack_user, PRIu32);
+
+ fclose(file);
+ return 0;
+}
+
+void test_attr__open(struct perf_event_attr *attr, pid_t pid, int cpu,
+ int fd, int group_fd, unsigned long flags)
+{
+ int errno_saved = errno;
+
+ if (store_event(attr, pid, cpu, fd, group_fd, flags))
+ die("test attr FAILED");
+
+ errno = errno_saved;
+}
+
+static int run_dir(const char *d, const char *perf)
+{
+ char v[] = "-vvvvv";
+ int vcnt = min(verbose, (int) sizeof(v) - 1);
+ char cmd[3*PATH_MAX];
+
+ if (verbose)
+ vcnt++;
+
+ snprintf(cmd, 3*PATH_MAX, PYTHON " %s/attr.py -d %s/attr/ -p %s %.*s",
+ d, d, perf, vcnt, v);
+
+ return system(cmd);
+}
+
+int test__attr(void)
+{
+ struct stat st;
+ char path_perf[PATH_MAX];
+ char path_dir[PATH_MAX];
+
+ /* First try developement tree tests. */
+ if (!lstat("./tests", &st))
+ return run_dir("./tests", "./perf");
+
+ /* Then installed path. */
+ snprintf(path_dir, PATH_MAX, "%s/tests", perf_exec_path());
+ snprintf(path_perf, PATH_MAX, "%s/perf", BINDIR);
+
+ if (!lstat(path_dir, &st) &&
+ !lstat(path_perf, &st))
+ return run_dir(path_dir, path_perf);
+
+ fprintf(stderr, " (omitted)");
+ return 0;
+}
diff --git a/tools/perf/tests/attr.py b/tools/perf/tests/attr.py
new file mode 100644
index 00000000000..c9b4b6269b5
--- /dev/null
+++ b/tools/perf/tests/attr.py
@@ -0,0 +1,332 @@
+#! /usr/bin/python
+
+import os
+import sys
+import glob
+import optparse
+import tempfile
+import logging
+import shutil
+import ConfigParser
+
+class Fail(Exception):
+ def __init__(self, test, msg):
+ self.msg = msg
+ self.test = test
+ def getMsg(self):
+ return '\'%s\' - %s' % (self.test.path, self.msg)
+
+class Unsup(Exception):
+ def __init__(self, test):
+ self.test = test
+ def getMsg(self):
+ return '\'%s\'' % self.test.path
+
+class Event(dict):
+ terms = [
+ 'cpu',
+ 'flags',
+ 'type',
+ 'size',
+ 'config',
+ 'sample_period',
+ 'sample_type',
+ 'read_format',
+ 'disabled',
+ 'inherit',
+ 'pinned',
+ 'exclusive',
+ 'exclude_user',
+ 'exclude_kernel',
+ 'exclude_hv',
+ 'exclude_idle',
+ 'mmap',
+ 'comm',
+ 'freq',
+ 'inherit_stat',
+ 'enable_on_exec',
+ 'task',
+ 'watermark',
+ 'precise_ip',
+ 'mmap_data',
+ 'sample_id_all',
+ 'exclude_host',
+ 'exclude_guest',
+ 'exclude_callchain_kernel',
+ 'exclude_callchain_user',
+ 'wakeup_events',
+ 'bp_type',
+ 'config1',
+ 'config2',
+ 'branch_sample_type',
+ 'sample_regs_user',
+ 'sample_stack_user',
+ ]
+
+ def add(self, data):
+ for key, val in data:
+ log.debug(" %s = %s" % (key, val))
+ self[key] = val
+
+ def __init__(self, name, data, base):
+ log.debug(" Event %s" % name);
+ self.name = name;
+ self.group = ''
+ self.add(base)
+ self.add(data)
+
+ def compare_data(self, a, b):
+ # Allow multiple values in assignment separated by '|'
+ a_list = a.split('|')
+ b_list = b.split('|')
+
+ for a_item in a_list:
+ for b_item in b_list:
+ if (a_item == b_item):
+ return True
+ elif (a_item == '*') or (b_item == '*'):
+ return True
+
+ return False
+
+ def equal(self, other):
+ for t in Event.terms:
+ log.debug(" [%s] %s %s" % (t, self[t], other[t]));
+ if not self.has_key(t) or not other.has_key(t):
+ return False
+ if not self.compare_data(self[t], other[t]):
+ return False
+ return True
+
+ def diff(self, other):
+ for t in Event.terms:
+ if not self.has_key(t) or not other.has_key(t):
+ continue
+ if not self.compare_data(self[t], other[t]):
+ log.warning("expected %s=%s, got %s" % (t, self[t], other[t]))
+
+
+# Test file description needs to have following sections:
+# [config]
+# - just single instance in file
+# - needs to specify:
+# 'command' - perf command name
+# 'args' - special command arguments
+# 'ret' - expected command return value (0 by default)
+#
+# [eventX:base]
+# - one or multiple instances in file
+# - expected values assignments
+class Test(object):
+ def __init__(self, path, options):
+ parser = ConfigParser.SafeConfigParser()
+ parser.read(path)
+
+ log.warning("running '%s'" % path)
+
+ self.path = path
+ self.test_dir = options.test_dir
+ self.perf = options.perf
+ self.command = parser.get('config', 'command')
+ self.args = parser.get('config', 'args')
+
+ try:
+ self.ret = parser.get('config', 'ret')
+ except:
+ self.ret = 0
+
+ self.expect = {}
+ self.result = {}
+ log.debug(" loading expected events");
+ self.load_events(path, self.expect)
+
+ def is_event(self, name):
+ if name.find("event") == -1:
+ return False
+ else:
+ return True
+
+ def load_events(self, path, events):
+ parser_event = ConfigParser.SafeConfigParser()
+ parser_event.read(path)
+
+ # The event record section header contains 'event' word,
+ # optionaly followed by ':' allowing to load 'parent
+ # event' first as a base
+ for section in filter(self.is_event, parser_event.sections()):
+
+ parser_items = parser_event.items(section);
+ base_items = {}
+
+ # Read parent event if there's any
+ if (':' in section):
+ base = section[section.index(':') + 1:]
+ parser_base = ConfigParser.SafeConfigParser()
+ parser_base.read(self.test_dir + '/' + base)
+ base_items = parser_base.items('event')
+
+ e = Event(section, parser_items, base_items)
+ events[section] = e
+
+ def run_cmd(self, tempdir):
+ cmd = "PERF_TEST_ATTR=%s %s %s -o %s/perf.data %s" % (tempdir,
+ self.perf, self.command, tempdir, self.args)
+ ret = os.WEXITSTATUS(os.system(cmd))
+
+ log.info(" '%s' ret %d " % (cmd, ret))
+
+ if ret != int(self.ret):
+ raise Unsup(self)
+
+ def compare(self, expect, result):
+ match = {}
+
+ log.debug(" compare");
+
+ # For each expected event find all matching
+ # events in result. Fail if there's not any.
+ for exp_name, exp_event in expect.items():
+ exp_list = []
+ log.debug(" matching [%s]" % exp_name)
+ for res_name, res_event in result.items():
+ log.debug(" to [%s]" % res_name)
+ if (exp_event.equal(res_event)):
+ exp_list.append(res_name)
+ log.debug(" ->OK")
+ else:
+ log.debug(" ->FAIL");
+
+ log.debug(" match: [%s] matches %s" % (exp_name, str(exp_list)))
+
+ # we did not any matching event - fail
+ if (not exp_list):
+ exp_event.diff(res_event)
+ raise Fail(self, 'match failure');
+
+ match[exp_name] = exp_list
+
+ # For each defined group in the expected events
+ # check we match the same group in the result.
+ for exp_name, exp_event in expect.items():
+ group = exp_event.group
+
+ if (group == ''):
+ continue
+
+ for res_name in match[exp_name]:
+ res_group = result[res_name].group
+ if res_group not in match[group]:
+ raise Fail(self, 'group failure')
+
+ log.debug(" group: [%s] matches group leader %s" %
+ (exp_name, str(match[group])))
+
+ log.debug(" matched")
+
+ def resolve_groups(self, events):
+ for name, event in events.items():
+ group_fd = event['group_fd'];
+ if group_fd == '-1':
+ continue;
+
+ for iname, ievent in events.items():
+ if (ievent['fd'] == group_fd):
+ event.group = iname
+ log.debug('[%s] has group leader [%s]' % (name, iname))
+ break;
+
+ def run(self):
+ tempdir = tempfile.mkdtemp();
+
+ try:
+ # run the test script
+ self.run_cmd(tempdir);
+
+ # load events expectation for the test
+ log.debug(" loading result events");
+ for f in glob.glob(tempdir + '/event*'):
+ self.load_events(f, self.result);
+
+ # resolve group_fd to event names
+ self.resolve_groups(self.expect);
+ self.resolve_groups(self.result);
+
+ # do the expectation - results matching - both ways
+ self.compare(self.expect, self.result)
+ self.compare(self.result, self.expect)
+
+ finally:
+ # cleanup
+ shutil.rmtree(tempdir)
+
+
+def run_tests(options):
+ for f in glob.glob(options.test_dir + '/' + options.test):
+ try:
+ Test(f, options).run()
+ except Unsup, obj:
+ log.warning("unsupp %s" % obj.getMsg())
+
+def setup_log(verbose):
+ global log
+ level = logging.CRITICAL
+
+ if verbose == 1:
+ level = logging.WARNING
+ if verbose == 2:
+ level = logging.INFO
+ if verbose >= 3:
+ level = logging.DEBUG
+
+ log = logging.getLogger('test')
+ log.setLevel(level)
+ ch = logging.StreamHandler()
+ ch.setLevel(level)
+ formatter = logging.Formatter('%(message)s')
+ ch.setFormatter(formatter)
+ log.addHandler(ch)
+
+USAGE = '''%s [OPTIONS]
+ -d dir # tests dir
+ -p path # perf binary
+ -t test # single test
+ -v # verbose level
+''' % sys.argv[0]
+
+def main():
+ parser = optparse.OptionParser(usage=USAGE)
+
+ parser.add_option("-t", "--test",
+ action="store", type="string", dest="test")
+ parser.add_option("-d", "--test-dir",
+ action="store", type="string", dest="test_dir")
+ parser.add_option("-p", "--perf",
+ action="store", type="string", dest="perf")
+ parser.add_option("-v", "--verbose",
+ action="count", dest="verbose")
+
+ options, args = parser.parse_args()
+ if args:
+ parser.error('FAILED wrong arguments %s' % ' '.join(args))
+ return -1
+
+ setup_log(options.verbose)
+
+ if not options.test_dir:
+ print 'FAILED no -d option specified'
+ sys.exit(-1)
+
+ if not options.test:
+ options.test = 'test*'
+
+ try:
+ run_tests(options)
+
+ except Fail, obj:
+ print "FAILED %s" % obj.getMsg();
+ sys.exit(-1)
+
+ sys.exit(0)
+
+if __name__ == '__main__':
+ main()
diff --git a/tools/perf/tests/attr/README b/tools/perf/tests/attr/README
new file mode 100644
index 00000000000..430024f618f
--- /dev/null
+++ b/tools/perf/tests/attr/README
@@ -0,0 +1,64 @@
+The struct perf_event_attr test (attr tests) support
+====================================================
+This testing support is embedded into perf directly and is governed
+by the PERF_TEST_ATTR environment variable and hook inside the
+sys_perf_event_open function.
+
+The general idea is to store 'struct perf_event_attr' details for
+each event created within single perf command. Each event details
+are stored into separate text file. Once perf command is finished
+these files are checked for values we expect for command.
+
+The attr tests consist of following parts:
+
+tests/attr.c
+------------
+This is the sys_perf_event_open hook implementation. The hook
+is triggered when the PERF_TEST_ATTR environment variable is
+defined. It must contain name of existing directory with access
+and write permissions.
+
+For each sys_perf_event_open call event details are stored in
+separate file. Besides 'struct perf_event_attr' values we also
+store 'fd' and 'group_fd' values to allow checking for groups.
+
+tests/attr.py
+-------------
+This is the python script that does all the hard work. It reads
+the test definition, executes it and checks results.
+
+tests/attr/
+-----------
+Directory containing all attr test definitions.
+Following tests are defined (with perf commands):
+
+ perf record kill (test-record-basic)
+ perf record -b kill (test-record-branch-any)
+ perf record -j any kill (test-record-branch-filter-any)
+ perf record -j any_call kill (test-record-branch-filter-any_call)
+ perf record -j any_ret kill (test-record-branch-filter-any_ret)
+ perf record -j hv kill (test-record-branch-filter-hv)
+ perf record -j ind_call kill (test-record-branch-filter-ind_call)
+ perf record -j k kill (test-record-branch-filter-k)
+ perf record -j u kill (test-record-branch-filter-u)
+ perf record -c 123 kill (test-record-count)
+ perf record -d kill (test-record-data)
+ perf record -F 100 kill (test-record-freq)
+ perf record -g kill (test-record-graph-default)
+ perf record --call-graph dwarf kill (test-record-graph-dwarf)
+ perf record --call-graph fp kill (test-record-graph-fp)
+ perf record --group -e cycles,instructions kill (test-record-group)
+ perf record -e '{cycles,instructions}' kill (test-record-group1)
+ perf record -D kill (test-record-no-delay)
+ perf record -i kill (test-record-no-inherit)
+ perf record -n kill (test-record-no-samples)
+ perf record -c 100 -P kill (test-record-period)
+ perf record -R kill (test-record-raw)
+ perf stat -e cycles kill (test-stat-basic)
+ perf stat kill (test-stat-default)
+ perf stat -d kill (test-stat-detailed-1)
+ perf stat -dd kill (test-stat-detailed-2)
+ perf stat -ddd kill (test-stat-detailed-3)
+ perf stat --group -e cycles,instructions kill (test-stat-group)
+ perf stat -e '{cycles,instructions}' kill (test-stat-group1)
+ perf stat -i -e cycles kill (test-stat-no-inherit)
diff --git a/tools/perf/tests/attr/base-record b/tools/perf/tests/attr/base-record
new file mode 100644
index 00000000000..e9bd6391f2a
--- /dev/null
+++ b/tools/perf/tests/attr/base-record
@@ -0,0 +1,40 @@
+[event]
+fd=1
+group_fd=-1
+flags=0
+cpu=*
+type=0|1
+size=96
+config=0
+sample_period=4000
+sample_type=263
+read_format=0
+disabled=1
+inherit=1
+pinned=0
+exclusive=0
+exclude_user=0
+exclude_kernel=0
+exclude_hv=0
+exclude_idle=0
+mmap=1
+comm=1
+freq=1
+inherit_stat=0
+enable_on_exec=1
+task=0
+watermark=0
+precise_ip=0
+mmap_data=0
+sample_id_all=1
+exclude_host=0|1
+exclude_guest=0|1
+exclude_callchain_kernel=0
+exclude_callchain_user=0
+wakeup_events=0
+bp_type=0
+config1=0
+config2=0
+branch_sample_type=0
+sample_regs_user=0
+sample_stack_user=0
diff --git a/tools/perf/tests/attr/base-stat b/tools/perf/tests/attr/base-stat
new file mode 100644
index 00000000000..91cd48b399f
--- /dev/null
+++ b/tools/perf/tests/attr/base-stat
@@ -0,0 +1,40 @@
+[event]
+fd=1
+group_fd=-1
+flags=0
+cpu=*
+type=0
+size=96
+config=0
+sample_period=0
+sample_type=0
+read_format=3
+disabled=1
+inherit=1
+pinned=0
+exclusive=0
+exclude_user=0
+exclude_kernel=0
+exclude_hv=0
+exclude_idle=0
+mmap=0
+comm=0
+freq=0
+inherit_stat=0
+enable_on_exec=1
+task=0
+watermark=0
+precise_ip=0
+mmap_data=0
+sample_id_all=0
+exclude_host=0|1
+exclude_guest=0|1
+exclude_callchain_kernel=0
+exclude_callchain_user=0
+wakeup_events=0
+bp_type=0
+config1=0
+config2=0
+branch_sample_type=0
+sample_regs_user=0
+sample_stack_user=0
diff --git a/tools/perf/tests/attr/test-record-C0 b/tools/perf/tests/attr/test-record-C0
new file mode 100644
index 00000000000..d6a7e43f61b
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-C0
@@ -0,0 +1,13 @@
+[config]
+command = record
+args = -C 0 kill >/dev/null 2>&1
+
+[event:base-record]
+cpu=0
+
+# no enable on exec for CPU attached
+enable_on_exec=0
+
+# PERF_SAMPLE_IP | PERF_SAMPLE_TID PERF_SAMPLE_TIME | # PERF_SAMPLE_PERIOD
+# + PERF_SAMPLE_CPU added by -C 0
+sample_type=391
diff --git a/tools/perf/tests/attr/test-record-basic b/tools/perf/tests/attr/test-record-basic
new file mode 100644
index 00000000000..55c0428370c
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-basic
@@ -0,0 +1,5 @@
+[config]
+command = record
+args = kill >/dev/null 2>&1
+
+[event:base-record]
diff --git a/tools/perf/tests/attr/test-record-branch-any b/tools/perf/tests/attr/test-record-branch-any
new file mode 100644
index 00000000000..1421960ed4e
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-branch-any
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = -b kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=4000
+sample_type=2311
+branch_sample_type=8
diff --git a/tools/perf/tests/attr/test-record-branch-filter-any b/tools/perf/tests/attr/test-record-branch-filter-any
new file mode 100644
index 00000000000..915c4df0e0c
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-branch-filter-any
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = -j any kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=4000
+sample_type=2311
+branch_sample_type=8
diff --git a/tools/perf/tests/attr/test-record-branch-filter-any_call b/tools/perf/tests/attr/test-record-branch-filter-any_call
new file mode 100644
index 00000000000..8708dbd4f37
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-branch-filter-any_call
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = -j any_call kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=4000
+sample_type=2311
+branch_sample_type=16
diff --git a/tools/perf/tests/attr/test-record-branch-filter-any_ret b/tools/perf/tests/attr/test-record-branch-filter-any_ret
new file mode 100644
index 00000000000..0d3607a6dcb
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-branch-filter-any_ret
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = -j any_ret kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=4000
+sample_type=2311
+branch_sample_type=32
diff --git a/tools/perf/tests/attr/test-record-branch-filter-hv b/tools/perf/tests/attr/test-record-branch-filter-hv
new file mode 100644
index 00000000000..f25526740ce
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-branch-filter-hv
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = -j hv kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=4000
+sample_type=2311
+branch_sample_type=8
diff --git a/tools/perf/tests/attr/test-record-branch-filter-ind_call b/tools/perf/tests/attr/test-record-branch-filter-ind_call
new file mode 100644
index 00000000000..e862dd17912
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-branch-filter-ind_call
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = -j ind_call kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=4000
+sample_type=2311
+branch_sample_type=64
diff --git a/tools/perf/tests/attr/test-record-branch-filter-k b/tools/perf/tests/attr/test-record-branch-filter-k
new file mode 100644
index 00000000000..182971e898f
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-branch-filter-k
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = -j k kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=4000
+sample_type=2311
+branch_sample_type=8
diff --git a/tools/perf/tests/attr/test-record-branch-filter-u b/tools/perf/tests/attr/test-record-branch-filter-u
new file mode 100644
index 00000000000..83449ef9e68
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-branch-filter-u
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = -j u kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=4000
+sample_type=2311
+branch_sample_type=8
diff --git a/tools/perf/tests/attr/test-record-count b/tools/perf/tests/attr/test-record-count
new file mode 100644
index 00000000000..2f841de56f6
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-count
@@ -0,0 +1,8 @@
+[config]
+command = record
+args = -c 123 kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=123
+sample_type=7
+freq=0
diff --git a/tools/perf/tests/attr/test-record-data b/tools/perf/tests/attr/test-record-data
new file mode 100644
index 00000000000..716e143b529
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-data
@@ -0,0 +1,11 @@
+[config]
+command = record
+args = -d kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=4000
+
+# sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID | PERF_SAMPLE_TIME |
+# PERF_SAMPLE_ADDR | PERF_SAMPLE_PERIOD | PERF_SAMPLE_DATA_SRC
+sample_type=33039
+mmap_data=1
diff --git a/tools/perf/tests/attr/test-record-freq b/tools/perf/tests/attr/test-record-freq
new file mode 100644
index 00000000000..600d0f8f258
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-freq
@@ -0,0 +1,6 @@
+[config]
+command = record
+args = -F 100 kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=100
diff --git a/tools/perf/tests/attr/test-record-graph-default b/tools/perf/tests/attr/test-record-graph-default
new file mode 100644
index 00000000000..853597a9a8f
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-graph-default
@@ -0,0 +1,6 @@
+[config]
+command = record
+args = -g kill >/dev/null 2>&1
+
+[event:base-record]
+sample_type=295
diff --git a/tools/perf/tests/attr/test-record-graph-dwarf b/tools/perf/tests/attr/test-record-graph-dwarf
new file mode 100644
index 00000000000..d6f324ea578
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-graph-dwarf
@@ -0,0 +1,10 @@
+[config]
+command = record
+args = --call-graph dwarf -- kill >/dev/null 2>&1
+
+[event:base-record]
+sample_type=12583
+exclude_callchain_user=1
+sample_stack_user=8192
+# TODO different for each arch, no support for that now
+sample_regs_user=*
diff --git a/tools/perf/tests/attr/test-record-graph-fp b/tools/perf/tests/attr/test-record-graph-fp
new file mode 100644
index 00000000000..055e3bee799
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-graph-fp
@@ -0,0 +1,6 @@
+[config]
+command = record
+args = --call-graph fp kill >/dev/null 2>&1
+
+[event:base-record]
+sample_type=295
diff --git a/tools/perf/tests/attr/test-record-group b/tools/perf/tests/attr/test-record-group
new file mode 100644
index 00000000000..57739cacdb2
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-group
@@ -0,0 +1,20 @@
+[config]
+command = record
+args = --group -e cycles,instructions kill >/dev/null 2>&1
+
+[event-1:base-record]
+fd=1
+group_fd=-1
+sample_type=327
+read_format=4
+
+[event-2:base-record]
+fd=2
+group_fd=1
+config=1
+sample_type=327
+read_format=4
+mmap=0
+comm=0
+enable_on_exec=0
+disabled=0
diff --git a/tools/perf/tests/attr/test-record-group-sampling b/tools/perf/tests/attr/test-record-group-sampling
new file mode 100644
index 00000000000..658f5d60c87
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-group-sampling
@@ -0,0 +1,36 @@
+[config]
+command = record
+args = -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1
+
+[event-1:base-record]
+fd=1
+group_fd=-1
+sample_type=343
+read_format=12
+inherit=0
+
+[event-2:base-record]
+fd=2
+group_fd=1
+
+# cache-misses
+type=0
+config=3
+
+# default | PERF_SAMPLE_READ
+sample_type=343
+
+# PERF_FORMAT_ID | PERF_FORMAT_GROUP
+read_format=12
+
+mmap=0
+comm=0
+enable_on_exec=0
+disabled=0
+
+# inherit is disabled for group sampling
+inherit=0
+
+# sampling disabled
+sample_freq=0
+sample_period=0
diff --git a/tools/perf/tests/attr/test-record-group1 b/tools/perf/tests/attr/test-record-group1
new file mode 100644
index 00000000000..c5548d054af
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-group1
@@ -0,0 +1,21 @@
+[config]
+command = record
+args = -e '{cycles,instructions}' kill >/dev/null 2>&1
+
+[event-1:base-record]
+fd=1
+group_fd=-1
+sample_type=327
+read_format=4
+
+[event-2:base-record]
+fd=2
+group_fd=1
+type=0
+config=1
+sample_type=327
+read_format=4
+mmap=0
+comm=0
+enable_on_exec=0
+disabled=0
diff --git a/tools/perf/tests/attr/test-record-no-delay b/tools/perf/tests/attr/test-record-no-delay
new file mode 100644
index 00000000000..f253b78cdbf
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-no-delay
@@ -0,0 +1,9 @@
+[config]
+command = record
+args = -D kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=4000
+sample_type=263
+watermark=0
+wakeup_events=1
diff --git a/tools/perf/tests/attr/test-record-no-inherit b/tools/perf/tests/attr/test-record-no-inherit
new file mode 100644
index 00000000000..44edcb2edcd
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-no-inherit
@@ -0,0 +1,7 @@
+[config]
+command = record
+args = -i kill >/dev/null 2>&1
+
+[event:base-record]
+sample_type=263
+inherit=0
diff --git a/tools/perf/tests/attr/test-record-no-samples b/tools/perf/tests/attr/test-record-no-samples
new file mode 100644
index 00000000000..d0141b2418b
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-no-samples
@@ -0,0 +1,6 @@
+[config]
+command = record
+args = -n kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=0
diff --git a/tools/perf/tests/attr/test-record-period b/tools/perf/tests/attr/test-record-period
new file mode 100644
index 00000000000..8abc5314fc5
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-period
@@ -0,0 +1,7 @@
+[config]
+command = record
+args = -c 100 -P kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=100
+freq=0
diff --git a/tools/perf/tests/attr/test-record-raw b/tools/perf/tests/attr/test-record-raw
new file mode 100644
index 00000000000..4a8ef25b5f4
--- /dev/null
+++ b/tools/perf/tests/attr/test-record-raw
@@ -0,0 +1,7 @@
+[config]
+command = record
+args = -R kill >/dev/null 2>&1
+
+[event:base-record]
+sample_period=4000
+sample_type=1415
diff --git a/tools/perf/tests/attr/test-stat-C0 b/tools/perf/tests/attr/test-stat-C0
new file mode 100644
index 00000000000..aa835950751
--- /dev/null
+++ b/tools/perf/tests/attr/test-stat-C0
@@ -0,0 +1,9 @@
+[config]
+command = stat
+args = -e cycles -C 0 kill >/dev/null 2>&1
+ret = 1
+
+[event:base-stat]
+# events are enabled by default when attached to cpu
+disabled=0
+enable_on_exec=0
diff --git a/tools/perf/tests/attr/test-stat-basic b/tools/perf/tests/attr/test-stat-basic
new file mode 100644
index 00000000000..74e17881f2b
--- /dev/null
+++ b/tools/perf/tests/attr/test-stat-basic
@@ -0,0 +1,6 @@
+[config]
+command = stat
+args = -e cycles kill >/dev/null 2>&1
+ret = 1
+
+[event:base-stat]
diff --git a/tools/perf/tests/attr/test-stat-default b/tools/perf/tests/attr/test-stat-default
new file mode 100644
index 00000000000..19270f54c96
--- /dev/null
+++ b/tools/perf/tests/attr/test-stat-default
@@ -0,0 +1,64 @@
+[config]
+command = stat
+args = kill >/dev/null 2>&1
+ret = 1
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
+[event1:base-stat]
+fd=1
+type=1
+config=1
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES
+[event2:base-stat]
+fd=2
+type=1
+config=3
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS
+[event3:base-stat]
+fd=3
+type=1
+config=4
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS
+[event4:base-stat]
+fd=4
+type=1
+config=2
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES
+[event5:base-stat]
+fd=5
+type=0
+config=0
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
+[event6:base-stat]
+fd=6
+type=0
+config=7
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND
+[event7:base-stat]
+fd=7
+type=0
+config=8
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS
+[event8:base-stat]
+fd=8
+type=0
+config=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS
+[event9:base-stat]
+fd=9
+type=0
+config=4
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES
+[event10:base-stat]
+fd=10
+type=0
+config=5
diff --git a/tools/perf/tests/attr/test-stat-detailed-1 b/tools/perf/tests/attr/test-stat-detailed-1
new file mode 100644
index 00000000000..51426b87153
--- /dev/null
+++ b/tools/perf/tests/attr/test-stat-detailed-1
@@ -0,0 +1,101 @@
+[config]
+command = stat
+args = -d kill >/dev/null 2>&1
+ret = 1
+
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
+[event1:base-stat]
+fd=1
+type=1
+config=1
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES
+[event2:base-stat]
+fd=2
+type=1
+config=3
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS
+[event3:base-stat]
+fd=3
+type=1
+config=4
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS
+[event4:base-stat]
+fd=4
+type=1
+config=2
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES
+[event5:base-stat]
+fd=5
+type=0
+config=0
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
+[event6:base-stat]
+fd=6
+type=0
+config=7
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND
+[event7:base-stat]
+fd=7
+type=0
+config=8
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS
+[event8:base-stat]
+fd=8
+type=0
+config=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS
+[event9:base-stat]
+fd=9
+type=0
+config=4
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES
+[event10:base-stat]
+fd=10
+type=0
+config=5
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event11:base-stat]
+fd=11
+type=3
+config=0
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event12:base-stat]
+fd=12
+type=3
+config=65536
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_LL << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event13:base-stat]
+fd=13
+type=3
+config=2
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_LL << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event14:base-stat]
+fd=14
+type=3
+config=65538
diff --git a/tools/perf/tests/attr/test-stat-detailed-2 b/tools/perf/tests/attr/test-stat-detailed-2
new file mode 100644
index 00000000000..8de5acc31c2
--- /dev/null
+++ b/tools/perf/tests/attr/test-stat-detailed-2
@@ -0,0 +1,155 @@
+[config]
+command = stat
+args = -dd kill >/dev/null 2>&1
+ret = 1
+
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
+[event1:base-stat]
+fd=1
+type=1
+config=1
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES
+[event2:base-stat]
+fd=2
+type=1
+config=3
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS
+[event3:base-stat]
+fd=3
+type=1
+config=4
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS
+[event4:base-stat]
+fd=4
+type=1
+config=2
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES
+[event5:base-stat]
+fd=5
+type=0
+config=0
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
+[event6:base-stat]
+fd=6
+type=0
+config=7
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND
+[event7:base-stat]
+fd=7
+type=0
+config=8
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS
+[event8:base-stat]
+fd=8
+type=0
+config=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS
+[event9:base-stat]
+fd=9
+type=0
+config=4
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES
+[event10:base-stat]
+fd=10
+type=0
+config=5
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event11:base-stat]
+fd=11
+type=3
+config=0
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event12:base-stat]
+fd=12
+type=3
+config=65536
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_LL << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event13:base-stat]
+fd=13
+type=3
+config=2
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_LL << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event14:base-stat]
+fd=14
+type=3
+config=65538
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_L1I << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event15:base-stat]
+fd=15
+type=3
+config=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_L1I << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event16:base-stat]
+fd=16
+type=3
+config=65537
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_DTLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event17:base-stat]
+fd=17
+type=3
+config=3
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_DTLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event18:base-stat]
+fd=18
+type=3
+config=65539
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_ITLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event19:base-stat]
+fd=19
+type=3
+config=4
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_ITLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event20:base-stat]
+fd=20
+type=3
+config=65540
diff --git a/tools/perf/tests/attr/test-stat-detailed-3 b/tools/perf/tests/attr/test-stat-detailed-3
new file mode 100644
index 00000000000..0a1f45bf7d7
--- /dev/null
+++ b/tools/perf/tests/attr/test-stat-detailed-3
@@ -0,0 +1,173 @@
+[config]
+command = stat
+args = -ddd kill >/dev/null 2>&1
+ret = 1
+
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_TASK_CLOCK
+[event1:base-stat]
+fd=1
+type=1
+config=1
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CONTEXT_SWITCHES
+[event2:base-stat]
+fd=2
+type=1
+config=3
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_MIGRATIONS
+[event3:base-stat]
+fd=3
+type=1
+config=4
+
+# PERF_TYPE_SOFTWARE / PERF_COUNT_SW_PAGE_FAULTS
+[event4:base-stat]
+fd=4
+type=1
+config=2
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_CPU_CYCLES
+[event5:base-stat]
+fd=5
+type=0
+config=0
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_FRONTEND
+[event6:base-stat]
+fd=6
+type=0
+config=7
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_STALLED_CYCLES_BACKEND
+[event7:base-stat]
+fd=7
+type=0
+config=8
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_INSTRUCTIONS
+[event8:base-stat]
+fd=8
+type=0
+config=1
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_INSTRUCTIONS
+[event9:base-stat]
+fd=9
+type=0
+config=4
+
+# PERF_TYPE_HARDWARE / PERF_COUNT_HW_BRANCH_MISSES
+[event10:base-stat]
+fd=10
+type=0
+config=5
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event11:base-stat]
+fd=11
+type=3
+config=0
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event12:base-stat]
+fd=12
+type=3
+config=65536
+
+# PERF_TYPE_HW_CACHE /
+# PERF_COUNT_HW_CACHE_LL << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event13:base-stat]
+fd=13
+type=3
+config=2
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_LL << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event14:base-stat]
+fd=14
+type=3
+config=65538
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_L1I << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event15:base-stat]
+fd=15
+type=3
+config=1
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_L1I << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event16:base-stat]
+fd=16
+type=3
+config=65537
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_DTLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event17:base-stat]
+fd=17
+type=3
+config=3
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_DTLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event18:base-stat]
+fd=18
+type=3
+config=65539
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_ITLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event19:base-stat]
+fd=19
+type=3
+config=4
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_ITLB << 0 |
+# (PERF_COUNT_HW_CACHE_OP_READ << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event20:base-stat]
+fd=20
+type=3
+config=65540
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_ACCESS << 16)
+[event21:base-stat]
+fd=21
+type=3
+config=512
+
+# PERF_TYPE_HW_CACHE,
+# PERF_COUNT_HW_CACHE_L1D << 0 |
+# (PERF_COUNT_HW_CACHE_OP_PREFETCH << 8) |
+# (PERF_COUNT_HW_CACHE_RESULT_MISS << 16)
+[event22:base-stat]
+fd=22
+type=3
+config=66048
diff --git a/tools/perf/tests/attr/test-stat-group b/tools/perf/tests/attr/test-stat-group
new file mode 100644
index 00000000000..fdc1596a886
--- /dev/null
+++ b/tools/perf/tests/attr/test-stat-group
@@ -0,0 +1,15 @@
+[config]
+command = stat
+args = --group -e cycles,instructions kill >/dev/null 2>&1
+ret = 1
+
+[event-1:base-stat]
+fd=1
+group_fd=-1
+
+[event-2:base-stat]
+fd=2
+group_fd=1
+config=1
+disabled=0
+enable_on_exec=0
diff --git a/tools/perf/tests/attr/test-stat-group1 b/tools/perf/tests/attr/test-stat-group1
new file mode 100644
index 00000000000..2a1f86e4a90
--- /dev/null
+++ b/tools/perf/tests/attr/test-stat-group1
@@ -0,0 +1,15 @@
+[config]
+command = stat
+args = -e '{cycles,instructions}' kill >/dev/null 2>&1
+ret = 1
+
+[event-1:base-stat]
+fd=1
+group_fd=-1
+
+[event-2:base-stat]
+fd=2
+group_fd=1
+config=1
+disabled=0
+enable_on_exec=0
diff --git a/tools/perf/tests/attr/test-stat-no-inherit b/tools/perf/tests/attr/test-stat-no-inherit
new file mode 100644
index 00000000000..d54b2a1e3e2
--- /dev/null
+++ b/tools/perf/tests/attr/test-stat-no-inherit
@@ -0,0 +1,7 @@
+[config]
+command = stat
+args = -i -e cycles kill >/dev/null 2>&1
+ret = 1
+
+[event:base-stat]
+inherit=0
diff --git a/tools/perf/tests/bp_signal.c b/tools/perf/tests/bp_signal.c
new file mode 100644
index 00000000000..aba09548919
--- /dev/null
+++ b/tools/perf/tests/bp_signal.c
@@ -0,0 +1,192 @@
+/*
+ * Inspired by breakpoint overflow test done by
+ * Vince Weaver <vincent.weaver@maine.edu> for perf_event_tests
+ * (git://github.com/deater/perf_event_tests)
+ */
+
+/*
+ * Powerpc needs __SANE_USERSPACE_TYPES__ before <linux/types.h> to select
+ * 'int-ll64.h' and avoid compile warnings when printing __u64 with %llu.
+ */
+#define __SANE_USERSPACE_TYPES__
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <linux/compiler.h>
+#include <linux/hw_breakpoint.h>
+
+#include "tests.h"
+#include "debug.h"
+#include "perf.h"
+
+static int fd1;
+static int fd2;
+static int overflows;
+
+__attribute__ ((noinline))
+static int test_function(void)
+{
+ return time(NULL);
+}
+
+static void sig_handler(int signum __maybe_unused,
+ siginfo_t *oh __maybe_unused,
+ void *uc __maybe_unused)
+{
+ overflows++;
+
+ if (overflows > 10) {
+ /*
+ * This should be executed only once during
+ * this test, if we are here for the 10th
+ * time, consider this the recursive issue.
+ *
+ * We can get out of here by disable events,
+ * so no new SIGIO is delivered.
+ */
+ ioctl(fd1, PERF_EVENT_IOC_DISABLE, 0);
+ ioctl(fd2, PERF_EVENT_IOC_DISABLE, 0);
+ }
+}
+
+static int bp_event(void *fn, int setup_signal)
+{
+ struct perf_event_attr pe;
+ int fd;
+
+ memset(&pe, 0, sizeof(struct perf_event_attr));
+ pe.type = PERF_TYPE_BREAKPOINT;
+ pe.size = sizeof(struct perf_event_attr);
+
+ pe.config = 0;
+ pe.bp_type = HW_BREAKPOINT_X;
+ pe.bp_addr = (unsigned long) fn;
+ pe.bp_len = sizeof(long);
+
+ pe.sample_period = 1;
+ pe.sample_type = PERF_SAMPLE_IP;
+ pe.wakeup_events = 1;
+
+ pe.disabled = 1;
+ pe.exclude_kernel = 1;
+ pe.exclude_hv = 1;
+
+ fd = sys_perf_event_open(&pe, 0, -1, -1, 0);
+ if (fd < 0) {
+ pr_debug("failed opening event %llx\n", pe.config);
+ return TEST_FAIL;
+ }
+
+ if (setup_signal) {
+ fcntl(fd, F_SETFL, O_RDWR|O_NONBLOCK|O_ASYNC);
+ fcntl(fd, F_SETSIG, SIGIO);
+ fcntl(fd, F_SETOWN, getpid());
+ }
+
+ ioctl(fd, PERF_EVENT_IOC_RESET, 0);
+
+ return fd;
+}
+
+static long long bp_count(int fd)
+{
+ long long count;
+ int ret;
+
+ ret = read(fd, &count, sizeof(long long));
+ if (ret != sizeof(long long)) {
+ pr_debug("failed to read: %d\n", ret);
+ return TEST_FAIL;
+ }
+
+ return count;
+}
+
+int test__bp_signal(void)
+{
+ struct sigaction sa;
+ long long count1, count2;
+
+ /* setup SIGIO signal handler */
+ memset(&sa, 0, sizeof(struct sigaction));
+ sa.sa_sigaction = (void *) sig_handler;
+ sa.sa_flags = SA_SIGINFO;
+
+ if (sigaction(SIGIO, &sa, NULL) < 0) {
+ pr_debug("failed setting up signal handler\n");
+ return TEST_FAIL;
+ }
+
+ /*
+ * We create following events:
+ *
+ * fd1 - breakpoint event on test_function with SIGIO
+ * signal configured. We should get signal
+ * notification each time the breakpoint is hit
+ *
+ * fd2 - breakpoint event on sig_handler without SIGIO
+ * configured.
+ *
+ * Following processing should happen:
+ * - execute test_function
+ * - fd1 event breakpoint hit -> count1 == 1
+ * - SIGIO is delivered -> overflows == 1
+ * - fd2 event breakpoint hit -> count2 == 1
+ *
+ * The test case check following error conditions:
+ * - we get stuck in signal handler because of debug
+ * exception being triggered receursively due to
+ * the wrong RF EFLAG management
+ *
+ * - we never trigger the sig_handler breakpoint due
+ * to the rong RF EFLAG management
+ *
+ */
+
+ fd1 = bp_event(test_function, 1);
+ fd2 = bp_event(sig_handler, 0);
+
+ ioctl(fd1, PERF_EVENT_IOC_ENABLE, 0);
+ ioctl(fd2, PERF_EVENT_IOC_ENABLE, 0);
+
+ /*
+ * Kick off the test by trigering 'fd1'
+ * breakpoint.
+ */
+ test_function();
+
+ ioctl(fd1, PERF_EVENT_IOC_DISABLE, 0);
+ ioctl(fd2, PERF_EVENT_IOC_DISABLE, 0);
+
+ count1 = bp_count(fd1);
+ count2 = bp_count(fd2);
+
+ close(fd1);
+ close(fd2);
+
+ pr_debug("count1 %lld, count2 %lld, overflow %d\n",
+ count1, count2, overflows);
+
+ if (count1 != 1) {
+ if (count1 == 11)
+ pr_debug("failed: RF EFLAG recursion issue detected\n");
+ else
+ pr_debug("failed: wrong count for bp1%lld\n", count1);
+ }
+
+ if (overflows != 1)
+ pr_debug("failed: wrong overflow hit\n");
+
+ if (count2 != 1)
+ pr_debug("failed: wrong count for bp2\n");
+
+ return count1 == 1 && overflows == 1 && count2 == 1 ?
+ TEST_OK : TEST_FAIL;
+}
diff --git a/tools/perf/tests/bp_signal_overflow.c b/tools/perf/tests/bp_signal_overflow.c
new file mode 100644
index 00000000000..44ac8217970
--- /dev/null
+++ b/tools/perf/tests/bp_signal_overflow.c
@@ -0,0 +1,132 @@
+/*
+ * Originally done by Vince Weaver <vincent.weaver@maine.edu> for
+ * perf_event_tests (git://github.com/deater/perf_event_tests)
+ */
+
+/*
+ * Powerpc needs __SANE_USERSPACE_TYPES__ before <linux/types.h> to select
+ * 'int-ll64.h' and avoid compile warnings when printing __u64 with %llu.
+ */
+#define __SANE_USERSPACE_TYPES__
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <time.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <linux/compiler.h>
+#include <linux/hw_breakpoint.h>
+
+#include "tests.h"
+#include "debug.h"
+#include "perf.h"
+
+static int overflows;
+
+__attribute__ ((noinline))
+static int test_function(void)
+{
+ return time(NULL);
+}
+
+static void sig_handler(int signum __maybe_unused,
+ siginfo_t *oh __maybe_unused,
+ void *uc __maybe_unused)
+{
+ overflows++;
+}
+
+static long long bp_count(int fd)
+{
+ long long count;
+ int ret;
+
+ ret = read(fd, &count, sizeof(long long));
+ if (ret != sizeof(long long)) {
+ pr_debug("failed to read: %d\n", ret);
+ return TEST_FAIL;
+ }
+
+ return count;
+}
+
+#define EXECUTIONS 10000
+#define THRESHOLD 100
+
+int test__bp_signal_overflow(void)
+{
+ struct perf_event_attr pe;
+ struct sigaction sa;
+ long long count;
+ int fd, i, fails = 0;
+
+ /* setup SIGIO signal handler */
+ memset(&sa, 0, sizeof(struct sigaction));
+ sa.sa_sigaction = (void *) sig_handler;
+ sa.sa_flags = SA_SIGINFO;
+
+ if (sigaction(SIGIO, &sa, NULL) < 0) {
+ pr_debug("failed setting up signal handler\n");
+ return TEST_FAIL;
+ }
+
+ memset(&pe, 0, sizeof(struct perf_event_attr));
+ pe.type = PERF_TYPE_BREAKPOINT;
+ pe.size = sizeof(struct perf_event_attr);
+
+ pe.config = 0;
+ pe.bp_type = HW_BREAKPOINT_X;
+ pe.bp_addr = (unsigned long) test_function;
+ pe.bp_len = sizeof(long);
+
+ pe.sample_period = THRESHOLD;
+ pe.sample_type = PERF_SAMPLE_IP;
+ pe.wakeup_events = 1;
+
+ pe.disabled = 1;
+ pe.exclude_kernel = 1;
+ pe.exclude_hv = 1;
+
+ fd = sys_perf_event_open(&pe, 0, -1, -1, 0);
+ if (fd < 0) {
+ pr_debug("failed opening event %llx\n", pe.config);
+ return TEST_FAIL;
+ }
+
+ fcntl(fd, F_SETFL, O_RDWR|O_NONBLOCK|O_ASYNC);
+ fcntl(fd, F_SETSIG, SIGIO);
+ fcntl(fd, F_SETOWN, getpid());
+
+ ioctl(fd, PERF_EVENT_IOC_RESET, 0);
+ ioctl(fd, PERF_EVENT_IOC_ENABLE, 0);
+
+ for (i = 0; i < EXECUTIONS; i++)
+ test_function();
+
+ ioctl(fd, PERF_EVENT_IOC_DISABLE, 0);
+
+ count = bp_count(fd);
+
+ close(fd);
+
+ pr_debug("count %lld, overflow %d\n",
+ count, overflows);
+
+ if (count != EXECUTIONS) {
+ pr_debug("\tWrong number of executions %lld != %d\n",
+ count, EXECUTIONS);
+ fails++;
+ }
+
+ if (overflows != EXECUTIONS / THRESHOLD) {
+ pr_debug("\tWrong number of overflows %d != %d\n",
+ overflows, EXECUTIONS / THRESHOLD);
+ fails++;
+ }
+
+ return fails ? TEST_FAIL : TEST_OK;
+}
diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c
new file mode 100644
index 00000000000..6f8b01bc603
--- /dev/null
+++ b/tools/perf/tests/builtin-test.c
@@ -0,0 +1,307 @@
+/*
+ * builtin-test.c
+ *
+ * Builtin regression testing command: ever growing number of sanity tests
+ */
+#include <unistd.h>
+#include <string.h>
+#include "builtin.h"
+#include "intlist.h"
+#include "tests.h"
+#include "debug.h"
+#include "color.h"
+#include "parse-options.h"
+#include "symbol.h"
+
+static struct test {
+ const char *desc;
+ int (*func)(void);
+} tests[] = {
+ {
+ .desc = "vmlinux symtab matches kallsyms",
+ .func = test__vmlinux_matches_kallsyms,
+ },
+ {
+ .desc = "detect open syscall event",
+ .func = test__open_syscall_event,
+ },
+ {
+ .desc = "detect open syscall event on all cpus",
+ .func = test__open_syscall_event_on_all_cpus,
+ },
+ {
+ .desc = "read samples using the mmap interface",
+ .func = test__basic_mmap,
+ },
+ {
+ .desc = "parse events tests",
+ .func = test__parse_events,
+ },
+#if defined(__x86_64__) || defined(__i386__)
+ {
+ .desc = "x86 rdpmc test",
+ .func = test__rdpmc,
+ },
+#endif
+ {
+ .desc = "Validate PERF_RECORD_* events & perf_sample fields",
+ .func = test__PERF_RECORD,
+ },
+ {
+ .desc = "Test perf pmu format parsing",
+ .func = test__pmu,
+ },
+ {
+ .desc = "Test dso data read",
+ .func = test__dso_data,
+ },
+ {
+ .desc = "Test dso data cache",
+ .func = test__dso_data_cache,
+ },
+ {
+ .desc = "Test dso data reopen",
+ .func = test__dso_data_reopen,
+ },
+ {
+ .desc = "roundtrip evsel->name check",
+ .func = test__perf_evsel__roundtrip_name_test,
+ },
+ {
+ .desc = "Check parsing of sched tracepoints fields",
+ .func = test__perf_evsel__tp_sched_test,
+ },
+ {
+ .desc = "Generate and check syscalls:sys_enter_open event fields",
+ .func = test__syscall_open_tp_fields,
+ },
+ {
+ .desc = "struct perf_event_attr setup",
+ .func = test__attr,
+ },
+ {
+ .desc = "Test matching and linking multiple hists",
+ .func = test__hists_link,
+ },
+ {
+ .desc = "Try 'use perf' in python, checking link problems",
+ .func = test__python_use,
+ },
+ {
+ .desc = "Test breakpoint overflow signal handler",
+ .func = test__bp_signal,
+ },
+ {
+ .desc = "Test breakpoint overflow sampling",
+ .func = test__bp_signal_overflow,
+ },
+ {
+ .desc = "Test number of exit event of a simple workload",
+ .func = test__task_exit,
+ },
+ {
+ .desc = "Test software clock events have valid period values",
+ .func = test__sw_clock_freq,
+ },
+#if defined(__x86_64__) || defined(__i386__)
+ {
+ .desc = "Test converting perf time to TSC",
+ .func = test__perf_time_to_tsc,
+ },
+#endif
+ {
+ .desc = "Test object code reading",
+ .func = test__code_reading,
+ },
+ {
+ .desc = "Test sample parsing",
+ .func = test__sample_parsing,
+ },
+ {
+ .desc = "Test using a dummy software event to keep tracking",
+ .func = test__keep_tracking,
+ },
+ {
+ .desc = "Test parsing with no sample_id_all bit set",
+ .func = test__parse_no_sample_id_all,
+ },
+#if defined(__x86_64__) || defined(__i386__) || defined(__arm__)
+#ifdef HAVE_DWARF_UNWIND_SUPPORT
+ {
+ .desc = "Test dwarf unwind",
+ .func = test__dwarf_unwind,
+ },
+#endif
+#endif
+ {
+ .desc = "Test filtering hist entries",
+ .func = test__hists_filter,
+ },
+ {
+ .desc = "Test mmap thread lookup",
+ .func = test__mmap_thread_lookup,
+ },
+ {
+ .desc = "Test thread mg sharing",
+ .func = test__thread_mg_share,
+ },
+ {
+ .desc = "Test output sorting of hist entries",
+ .func = test__hists_output,
+ },
+ {
+ .desc = "Test cumulation of child hist entries",
+ .func = test__hists_cumulate,
+ },
+ {
+ .func = NULL,
+ },
+};
+
+static bool perf_test__matches(int curr, int argc, const char *argv[])
+{
+ int i;
+
+ if (argc == 0)
+ return true;
+
+ for (i = 0; i < argc; ++i) {
+ char *end;
+ long nr = strtoul(argv[i], &end, 10);
+
+ if (*end == '\0') {
+ if (nr == curr + 1)
+ return true;
+ continue;
+ }
+
+ if (strstr(tests[curr].desc, argv[i]))
+ return true;
+ }
+
+ return false;
+}
+
+static int run_test(struct test *test)
+{
+ int status, err = -1, child = fork();
+
+ if (child < 0) {
+ pr_err("failed to fork test: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (!child) {
+ pr_debug("test child forked, pid %d\n", getpid());
+ err = test->func();
+ exit(err);
+ }
+
+ wait(&status);
+
+ if (WIFEXITED(status)) {
+ err = WEXITSTATUS(status);
+ pr_debug("test child finished with %d\n", err);
+ } else if (WIFSIGNALED(status)) {
+ err = -1;
+ pr_debug("test child interrupted\n");
+ }
+
+ return err;
+}
+
+static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist)
+{
+ int i = 0;
+ int width = 0;
+
+ while (tests[i].func) {
+ int len = strlen(tests[i].desc);
+
+ if (width < len)
+ width = len;
+ ++i;
+ }
+
+ i = 0;
+ while (tests[i].func) {
+ int curr = i++, err;
+
+ if (!perf_test__matches(curr, argc, argv))
+ continue;
+
+ pr_info("%2d: %-*s:", i, width, tests[curr].desc);
+
+ if (intlist__find(skiplist, i)) {
+ color_fprintf(stderr, PERF_COLOR_YELLOW, " Skip (user override)\n");
+ continue;
+ }
+
+ pr_debug("\n--- start ---\n");
+ err = run_test(&tests[curr]);
+ pr_debug("---- end ----\n%s:", tests[curr].desc);
+
+ switch (err) {
+ case TEST_OK:
+ pr_info(" Ok\n");
+ break;
+ case TEST_SKIP:
+ color_fprintf(stderr, PERF_COLOR_YELLOW, " Skip\n");
+ break;
+ case TEST_FAIL:
+ default:
+ color_fprintf(stderr, PERF_COLOR_RED, " FAILED!\n");
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static int perf_test__list(int argc, const char **argv)
+{
+ int i = 0;
+
+ while (tests[i].func) {
+ int curr = i++;
+
+ if (argc > 1 && !strstr(tests[curr].desc, argv[1]))
+ continue;
+
+ pr_info("%2d: %s\n", i, tests[curr].desc);
+ }
+
+ return 0;
+}
+
+int cmd_test(int argc, const char **argv, const char *prefix __maybe_unused)
+{
+ const char * const test_usage[] = {
+ "perf test [<options>] [{list <test-name-fragment>|[<test-name-fragments>|<test-numbers>]}]",
+ NULL,
+ };
+ const char *skip = NULL;
+ const struct option test_options[] = {
+ OPT_STRING('s', "skip", &skip, "tests", "tests to skip"),
+ OPT_INCR('v', "verbose", &verbose,
+ "be more verbose (show symbol address, etc)"),
+ OPT_END()
+ };
+ struct intlist *skiplist = NULL;
+
+ argc = parse_options(argc, argv, test_options, test_usage, 0);
+ if (argc >= 1 && !strcmp(argv[0], "list"))
+ return perf_test__list(argc, argv);
+
+ symbol_conf.priv_size = sizeof(int);
+ symbol_conf.sort_by_name = true;
+ symbol_conf.try_vmlinux_path = true;
+
+ if (symbol__init() < 0)
+ return -1;
+
+ if (skip != NULL)
+ skiplist = intlist__new(skip);
+
+ return __cmd_test(argc, argv, skiplist);
+}
diff --git a/tools/perf/tests/code-reading.c b/tools/perf/tests/code-reading.c
new file mode 100644
index 00000000000..67f2d632355
--- /dev/null
+++ b/tools/perf/tests/code-reading.c
@@ -0,0 +1,581 @@
+#include <linux/types.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#include "parse-events.h"
+#include "evlist.h"
+#include "evsel.h"
+#include "thread_map.h"
+#include "cpumap.h"
+#include "machine.h"
+#include "event.h"
+#include "thread.h"
+
+#include "tests.h"
+
+#define BUFSZ 1024
+#define READLEN 128
+
+struct state {
+ u64 done[1024];
+ size_t done_cnt;
+};
+
+static unsigned int hex(char c)
+{
+ if (c >= '0' && c <= '9')
+ return c - '0';
+ if (c >= 'a' && c <= 'f')
+ return c - 'a' + 10;
+ return c - 'A' + 10;
+}
+
+static void read_objdump_line(const char *line, size_t line_len, void **buf,
+ size_t *len)
+{
+ const char *p;
+ size_t i;
+
+ /* Skip to a colon */
+ p = strchr(line, ':');
+ if (!p)
+ return;
+ i = p + 1 - line;
+
+ /* Read bytes */
+ while (*len) {
+ char c1, c2;
+
+ /* Skip spaces */
+ for (; i < line_len; i++) {
+ if (!isspace(line[i]))
+ break;
+ }
+ /* Get 2 hex digits */
+ if (i >= line_len || !isxdigit(line[i]))
+ break;
+ c1 = line[i++];
+ if (i >= line_len || !isxdigit(line[i]))
+ break;
+ c2 = line[i++];
+ /* Followed by a space */
+ if (i < line_len && line[i] && !isspace(line[i]))
+ break;
+ /* Store byte */
+ *(unsigned char *)*buf = (hex(c1) << 4) | hex(c2);
+ *buf += 1;
+ *len -= 1;
+ }
+}
+
+static int read_objdump_output(FILE *f, void **buf, size_t *len)
+{
+ char *line = NULL;
+ size_t line_len;
+ ssize_t ret;
+ int err = 0;
+
+ while (1) {
+ ret = getline(&line, &line_len, f);
+ if (feof(f))
+ break;
+ if (ret < 0) {
+ pr_debug("getline failed\n");
+ err = -1;
+ break;
+ }
+ read_objdump_line(line, ret, buf, len);
+ }
+
+ free(line);
+
+ return err;
+}
+
+static int read_via_objdump(const char *filename, u64 addr, void *buf,
+ size_t len)
+{
+ char cmd[PATH_MAX * 2];
+ const char *fmt;
+ FILE *f;
+ int ret;
+
+ fmt = "%s -d --start-address=0x%"PRIx64" --stop-address=0x%"PRIx64" %s";
+ ret = snprintf(cmd, sizeof(cmd), fmt, "objdump", addr, addr + len,
+ filename);
+ if (ret <= 0 || (size_t)ret >= sizeof(cmd))
+ return -1;
+
+ pr_debug("Objdump command is: %s\n", cmd);
+
+ /* Ignore objdump errors */
+ strcat(cmd, " 2>/dev/null");
+
+ f = popen(cmd, "r");
+ if (!f) {
+ pr_debug("popen failed\n");
+ return -1;
+ }
+
+ ret = read_objdump_output(f, &buf, &len);
+ if (len) {
+ pr_debug("objdump read too few bytes\n");
+ if (!ret)
+ ret = len;
+ }
+
+ pclose(f);
+
+ return ret;
+}
+
+static int read_object_code(u64 addr, size_t len, u8 cpumode,
+ struct thread *thread, struct machine *machine,
+ struct state *state)
+{
+ struct addr_location al;
+ unsigned char buf1[BUFSZ];
+ unsigned char buf2[BUFSZ];
+ size_t ret_len;
+ u64 objdump_addr;
+ int ret;
+
+ pr_debug("Reading object code for memory address: %#"PRIx64"\n", addr);
+
+ thread__find_addr_map(thread, machine, cpumode, MAP__FUNCTION, addr,
+ &al);
+ if (!al.map || !al.map->dso) {
+ pr_debug("thread__find_addr_map failed\n");
+ return -1;
+ }
+
+ pr_debug("File is: %s\n", al.map->dso->long_name);
+
+ if (al.map->dso->symtab_type == DSO_BINARY_TYPE__KALLSYMS &&
+ !dso__is_kcore(al.map->dso)) {
+ pr_debug("Unexpected kernel address - skipping\n");
+ return 0;
+ }
+
+ pr_debug("On file address is: %#"PRIx64"\n", al.addr);
+
+ if (len > BUFSZ)
+ len = BUFSZ;
+
+ /* Do not go off the map */
+ if (addr + len > al.map->end)
+ len = al.map->end - addr;
+
+ /* Read the object code using perf */
+ ret_len = dso__data_read_offset(al.map->dso, machine, al.addr, buf1,
+ len);
+ if (ret_len != len) {
+ pr_debug("dso__data_read_offset failed\n");
+ return -1;
+ }
+
+ /*
+ * Converting addresses for use by objdump requires more information.
+ * map__load() does that. See map__rip_2objdump() for details.
+ */
+ if (map__load(al.map, NULL))
+ return -1;
+
+ /* objdump struggles with kcore - try each map only once */
+ if (dso__is_kcore(al.map->dso)) {
+ size_t d;
+
+ for (d = 0; d < state->done_cnt; d++) {
+ if (state->done[d] == al.map->start) {
+ pr_debug("kcore map tested already");
+ pr_debug(" - skipping\n");
+ return 0;
+ }
+ }
+ if (state->done_cnt >= ARRAY_SIZE(state->done)) {
+ pr_debug("Too many kcore maps - skipping\n");
+ return 0;
+ }
+ state->done[state->done_cnt++] = al.map->start;
+ }
+
+ /* Read the object code using objdump */
+ objdump_addr = map__rip_2objdump(al.map, al.addr);
+ ret = read_via_objdump(al.map->dso->long_name, objdump_addr, buf2, len);
+ if (ret > 0) {
+ /*
+ * The kernel maps are inaccurate - assume objdump is right in
+ * that case.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) {
+ len -= ret;
+ if (len) {
+ pr_debug("Reducing len to %zu\n", len);
+ } else if (dso__is_kcore(al.map->dso)) {
+ /*
+ * objdump cannot handle very large segments
+ * that may be found in kcore.
+ */
+ pr_debug("objdump failed for kcore");
+ pr_debug(" - skipping\n");
+ return 0;
+ } else {
+ return -1;
+ }
+ }
+ }
+ if (ret < 0) {
+ pr_debug("read_via_objdump failed\n");
+ return -1;
+ }
+
+ /* The results should be identical */
+ if (memcmp(buf1, buf2, len)) {
+ pr_debug("Bytes read differ from those read by objdump\n");
+ return -1;
+ }
+ pr_debug("Bytes read match those read by objdump\n");
+
+ return 0;
+}
+
+static int process_sample_event(struct machine *machine,
+ struct perf_evlist *evlist,
+ union perf_event *event, struct state *state)
+{
+ struct perf_sample sample;
+ struct thread *thread;
+ u8 cpumode;
+
+ if (perf_evlist__parse_sample(evlist, event, &sample)) {
+ pr_debug("perf_evlist__parse_sample failed\n");
+ return -1;
+ }
+
+ thread = machine__findnew_thread(machine, sample.pid, sample.tid);
+ if (!thread) {
+ pr_debug("machine__findnew_thread failed\n");
+ return -1;
+ }
+
+ cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ return read_object_code(sample.ip, READLEN, cpumode, thread, machine,
+ state);
+}
+
+static int process_event(struct machine *machine, struct perf_evlist *evlist,
+ union perf_event *event, struct state *state)
+{
+ if (event->header.type == PERF_RECORD_SAMPLE)
+ return process_sample_event(machine, evlist, event, state);
+
+ if (event->header.type == PERF_RECORD_THROTTLE ||
+ event->header.type == PERF_RECORD_UNTHROTTLE)
+ return 0;
+
+ if (event->header.type < PERF_RECORD_MAX) {
+ int ret;
+
+ ret = machine__process_event(machine, event, NULL);
+ if (ret < 0)
+ pr_debug("machine__process_event failed, event type %u\n",
+ event->header.type);
+ return ret;
+ }
+
+ return 0;
+}
+
+static int process_events(struct machine *machine, struct perf_evlist *evlist,
+ struct state *state)
+{
+ union perf_event *event;
+ int i, ret;
+
+ for (i = 0; i < evlist->nr_mmaps; i++) {
+ while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) {
+ ret = process_event(machine, evlist, event, state);
+ perf_evlist__mmap_consume(evlist, i);
+ if (ret < 0)
+ return ret;
+ }
+ }
+ return 0;
+}
+
+static int comp(const void *a, const void *b)
+{
+ return *(int *)a - *(int *)b;
+}
+
+static void do_sort_something(void)
+{
+ int buf[40960], i;
+
+ for (i = 0; i < (int)ARRAY_SIZE(buf); i++)
+ buf[i] = ARRAY_SIZE(buf) - i - 1;
+
+ qsort(buf, ARRAY_SIZE(buf), sizeof(int), comp);
+
+ for (i = 0; i < (int)ARRAY_SIZE(buf); i++) {
+ if (buf[i] != i) {
+ pr_debug("qsort failed\n");
+ break;
+ }
+ }
+}
+
+static void sort_something(void)
+{
+ int i;
+
+ for (i = 0; i < 10; i++)
+ do_sort_something();
+}
+
+static void syscall_something(void)
+{
+ int pipefd[2];
+ int i;
+
+ for (i = 0; i < 1000; i++) {
+ if (pipe(pipefd) < 0) {
+ pr_debug("pipe failed\n");
+ break;
+ }
+ close(pipefd[1]);
+ close(pipefd[0]);
+ }
+}
+
+static void fs_something(void)
+{
+ const char *test_file_name = "temp-perf-code-reading-test-file--";
+ FILE *f;
+ int i;
+
+ for (i = 0; i < 1000; i++) {
+ f = fopen(test_file_name, "w+");
+ if (f) {
+ fclose(f);
+ unlink(test_file_name);
+ }
+ }
+}
+
+static void do_something(void)
+{
+ fs_something();
+
+ sort_something();
+
+ syscall_something();
+}
+
+enum {
+ TEST_CODE_READING_OK,
+ TEST_CODE_READING_NO_VMLINUX,
+ TEST_CODE_READING_NO_KCORE,
+ TEST_CODE_READING_NO_ACCESS,
+ TEST_CODE_READING_NO_KERNEL_OBJ,
+};
+
+static int do_test_code_reading(bool try_kcore)
+{
+ struct machines machines;
+ struct machine *machine;
+ struct thread *thread;
+ struct record_opts opts = {
+ .mmap_pages = UINT_MAX,
+ .user_freq = UINT_MAX,
+ .user_interval = ULLONG_MAX,
+ .freq = 4000,
+ .target = {
+ .uses_mmap = true,
+ },
+ };
+ struct state state = {
+ .done_cnt = 0,
+ };
+ struct thread_map *threads = NULL;
+ struct cpu_map *cpus = NULL;
+ struct perf_evlist *evlist = NULL;
+ struct perf_evsel *evsel = NULL;
+ int err = -1, ret;
+ pid_t pid;
+ struct map *map;
+ bool have_vmlinux, have_kcore, excl_kernel = false;
+
+ pid = getpid();
+
+ machines__init(&machines);
+ machine = &machines.host;
+
+ ret = machine__create_kernel_maps(machine);
+ if (ret < 0) {
+ pr_debug("machine__create_kernel_maps failed\n");
+ goto out_err;
+ }
+
+ /* Force the use of kallsyms instead of vmlinux to try kcore */
+ if (try_kcore)
+ symbol_conf.kallsyms_name = "/proc/kallsyms";
+
+ /* Load kernel map */
+ map = machine->vmlinux_maps[MAP__FUNCTION];
+ ret = map__load(map, NULL);
+ if (ret < 0) {
+ pr_debug("map__load failed\n");
+ goto out_err;
+ }
+ have_vmlinux = dso__is_vmlinux(map->dso);
+ have_kcore = dso__is_kcore(map->dso);
+
+ /* 2nd time through we just try kcore */
+ if (try_kcore && !have_kcore)
+ return TEST_CODE_READING_NO_KCORE;
+
+ /* No point getting kernel events if there is no kernel object */
+ if (!have_vmlinux && !have_kcore)
+ excl_kernel = true;
+
+ threads = thread_map__new_by_tid(pid);
+ if (!threads) {
+ pr_debug("thread_map__new_by_tid failed\n");
+ goto out_err;
+ }
+
+ ret = perf_event__synthesize_thread_map(NULL, threads,
+ perf_event__process, machine, false);
+ if (ret < 0) {
+ pr_debug("perf_event__synthesize_thread_map failed\n");
+ goto out_err;
+ }
+
+ thread = machine__findnew_thread(machine, pid, pid);
+ if (!thread) {
+ pr_debug("machine__findnew_thread failed\n");
+ goto out_err;
+ }
+
+ cpus = cpu_map__new(NULL);
+ if (!cpus) {
+ pr_debug("cpu_map__new failed\n");
+ goto out_err;
+ }
+
+ while (1) {
+ const char *str;
+
+ evlist = perf_evlist__new();
+ if (!evlist) {
+ pr_debug("perf_evlist__new failed\n");
+ goto out_err;
+ }
+
+ perf_evlist__set_maps(evlist, cpus, threads);
+
+ if (excl_kernel)
+ str = "cycles:u";
+ else
+ str = "cycles";
+ pr_debug("Parsing event '%s'\n", str);
+ ret = parse_events(evlist, str);
+ if (ret < 0) {
+ pr_debug("parse_events failed\n");
+ goto out_err;
+ }
+
+ perf_evlist__config(evlist, &opts);
+
+ evsel = perf_evlist__first(evlist);
+
+ evsel->attr.comm = 1;
+ evsel->attr.disabled = 1;
+ evsel->attr.enable_on_exec = 0;
+
+ ret = perf_evlist__open(evlist);
+ if (ret < 0) {
+ if (!excl_kernel) {
+ excl_kernel = true;
+ perf_evlist__set_maps(evlist, NULL, NULL);
+ perf_evlist__delete(evlist);
+ evlist = NULL;
+ continue;
+ }
+ pr_debug("perf_evlist__open failed\n");
+ goto out_err;
+ }
+ break;
+ }
+
+ ret = perf_evlist__mmap(evlist, UINT_MAX, false);
+ if (ret < 0) {
+ pr_debug("perf_evlist__mmap failed\n");
+ goto out_err;
+ }
+
+ perf_evlist__enable(evlist);
+
+ do_something();
+
+ perf_evlist__disable(evlist);
+
+ ret = process_events(machine, evlist, &state);
+ if (ret < 0)
+ goto out_err;
+
+ if (!have_vmlinux && !have_kcore && !try_kcore)
+ err = TEST_CODE_READING_NO_KERNEL_OBJ;
+ else if (!have_vmlinux && !try_kcore)
+ err = TEST_CODE_READING_NO_VMLINUX;
+ else if (excl_kernel)
+ err = TEST_CODE_READING_NO_ACCESS;
+ else
+ err = TEST_CODE_READING_OK;
+out_err:
+ if (evlist) {
+ perf_evlist__delete(evlist);
+ } else {
+ cpu_map__delete(cpus);
+ thread_map__delete(threads);
+ }
+ machines__destroy_kernel_maps(&machines);
+ machine__delete_threads(machine);
+ machines__exit(&machines);
+
+ return err;
+}
+
+int test__code_reading(void)
+{
+ int ret;
+
+ ret = do_test_code_reading(false);
+ if (!ret)
+ ret = do_test_code_reading(true);
+
+ switch (ret) {
+ case TEST_CODE_READING_OK:
+ return 0;
+ case TEST_CODE_READING_NO_VMLINUX:
+ fprintf(stderr, " (no vmlinux)");
+ return 0;
+ case TEST_CODE_READING_NO_KCORE:
+ fprintf(stderr, " (no kcore)");
+ return 0;
+ case TEST_CODE_READING_NO_ACCESS:
+ fprintf(stderr, " (no access)");
+ return 0;
+ case TEST_CODE_READING_NO_KERNEL_OBJ:
+ fprintf(stderr, " (no kernel obj)");
+ return 0;
+ default:
+ return -1;
+ };
+}
diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c
new file mode 100644
index 00000000000..630808cd7cc
--- /dev/null
+++ b/tools/perf/tests/dso-data.c
@@ -0,0 +1,358 @@
+#include <stdlib.h>
+#include <linux/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <string.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <api/fs/fs.h>
+#include "util.h"
+#include "machine.h"
+#include "symbol.h"
+#include "tests.h"
+
+static char *test_file(int size)
+{
+#define TEMPL "/tmp/perf-test-XXXXXX"
+ static char buf_templ[sizeof(TEMPL)];
+ char *templ = buf_templ;
+ int fd, i;
+ unsigned char *buf;
+
+ strcpy(buf_templ, TEMPL);
+#undef TEMPL
+
+ fd = mkstemp(templ);
+ if (fd < 0) {
+ perror("mkstemp failed");
+ return NULL;
+ }
+
+ buf = malloc(size);
+ if (!buf) {
+ close(fd);
+ return NULL;
+ }
+
+ for (i = 0; i < size; i++)
+ buf[i] = (unsigned char) ((int) i % 10);
+
+ if (size != write(fd, buf, size))
+ templ = NULL;
+
+ free(buf);
+ close(fd);
+ return templ;
+}
+
+#define TEST_FILE_SIZE (DSO__DATA_CACHE_SIZE * 20)
+
+struct test_data_offset {
+ off_t offset;
+ u8 data[10];
+ int size;
+};
+
+struct test_data_offset offsets[] = {
+ /* Fill first cache page. */
+ {
+ .offset = 10,
+ .data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
+ .size = 10,
+ },
+ /* Read first cache page. */
+ {
+ .offset = 10,
+ .data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
+ .size = 10,
+ },
+ /* Fill cache boundary pages. */
+ {
+ .offset = DSO__DATA_CACHE_SIZE - DSO__DATA_CACHE_SIZE % 10,
+ .data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
+ .size = 10,
+ },
+ /* Read cache boundary pages. */
+ {
+ .offset = DSO__DATA_CACHE_SIZE - DSO__DATA_CACHE_SIZE % 10,
+ .data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
+ .size = 10,
+ },
+ /* Fill final cache page. */
+ {
+ .offset = TEST_FILE_SIZE - 10,
+ .data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
+ .size = 10,
+ },
+ /* Read final cache page. */
+ {
+ .offset = TEST_FILE_SIZE - 10,
+ .data = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
+ .size = 10,
+ },
+ /* Read final cache page. */
+ {
+ .offset = TEST_FILE_SIZE - 3,
+ .data = { 7, 8, 9, 0, 0, 0, 0, 0, 0, 0 },
+ .size = 3,
+ },
+};
+
+int test__dso_data(void)
+{
+ struct machine machine;
+ struct dso *dso;
+ char *file = test_file(TEST_FILE_SIZE);
+ size_t i;
+
+ TEST_ASSERT_VAL("No test file", file);
+
+ memset(&machine, 0, sizeof(machine));
+
+ dso = dso__new((const char *)file);
+
+ /* Basic 10 bytes tests. */
+ for (i = 0; i < ARRAY_SIZE(offsets); i++) {
+ struct test_data_offset *data = &offsets[i];
+ ssize_t size;
+ u8 buf[10];
+
+ memset(buf, 0, 10);
+ size = dso__data_read_offset(dso, &machine, data->offset,
+ buf, 10);
+
+ TEST_ASSERT_VAL("Wrong size", size == data->size);
+ TEST_ASSERT_VAL("Wrong data", !memcmp(buf, data->data, 10));
+ }
+
+ /* Read cross multiple cache pages. */
+ {
+ ssize_t size;
+ int c;
+ u8 *buf;
+
+ buf = malloc(TEST_FILE_SIZE);
+ TEST_ASSERT_VAL("ENOMEM\n", buf);
+
+ /* First iteration to fill caches, second one to read them. */
+ for (c = 0; c < 2; c++) {
+ memset(buf, 0, TEST_FILE_SIZE);
+ size = dso__data_read_offset(dso, &machine, 10,
+ buf, TEST_FILE_SIZE);
+
+ TEST_ASSERT_VAL("Wrong size",
+ size == (TEST_FILE_SIZE - 10));
+
+ for (i = 0; i < (size_t)size; i++)
+ TEST_ASSERT_VAL("Wrong data",
+ buf[i] == (i % 10));
+ }
+
+ free(buf);
+ }
+
+ dso__delete(dso);
+ unlink(file);
+ return 0;
+}
+
+static long open_files_cnt(void)
+{
+ char path[PATH_MAX];
+ struct dirent *dent;
+ DIR *dir;
+ long nr = 0;
+
+ scnprintf(path, PATH_MAX, "%s/self/fd", procfs__mountpoint());
+ pr_debug("fd path: %s\n", path);
+
+ dir = opendir(path);
+ TEST_ASSERT_VAL("failed to open fd directory", dir);
+
+ while ((dent = readdir(dir)) != NULL) {
+ if (!strcmp(dent->d_name, ".") ||
+ !strcmp(dent->d_name, ".."))
+ continue;
+
+ nr++;
+ }
+
+ closedir(dir);
+ return nr - 1;
+}
+
+static struct dso **dsos;
+
+static int dsos__create(int cnt, int size)
+{
+ int i;
+
+ dsos = malloc(sizeof(dsos) * cnt);
+ TEST_ASSERT_VAL("failed to alloc dsos array", dsos);
+
+ for (i = 0; i < cnt; i++) {
+ char *file;
+
+ file = test_file(size);
+ TEST_ASSERT_VAL("failed to get dso file", file);
+
+ dsos[i] = dso__new(file);
+ TEST_ASSERT_VAL("failed to get dso", dsos[i]);
+ }
+
+ return 0;
+}
+
+static void dsos__delete(int cnt)
+{
+ int i;
+
+ for (i = 0; i < cnt; i++) {
+ struct dso *dso = dsos[i];
+
+ unlink(dso->name);
+ dso__delete(dso);
+ }
+
+ free(dsos);
+}
+
+static int set_fd_limit(int n)
+{
+ struct rlimit rlim;
+
+ if (getrlimit(RLIMIT_NOFILE, &rlim))
+ return -1;
+
+ pr_debug("file limit %ld, new %d\n", (long) rlim.rlim_cur, n);
+
+ rlim.rlim_cur = n;
+ return setrlimit(RLIMIT_NOFILE, &rlim);
+}
+
+int test__dso_data_cache(void)
+{
+ struct machine machine;
+ long nr_end, nr = open_files_cnt();
+ int dso_cnt, limit, i, fd;
+
+ memset(&machine, 0, sizeof(machine));
+
+ /* set as system limit */
+ limit = nr * 4;
+ TEST_ASSERT_VAL("failed to set file limit", !set_fd_limit(limit));
+
+ /* and this is now our dso open FDs limit + 1 extra */
+ dso_cnt = limit / 2 + 1;
+ TEST_ASSERT_VAL("failed to create dsos\n",
+ !dsos__create(dso_cnt, TEST_FILE_SIZE));
+
+ for (i = 0; i < (dso_cnt - 1); i++) {
+ struct dso *dso = dsos[i];
+
+ /*
+ * Open dsos via dso__data_fd or dso__data_read_offset.
+ * Both opens the data file and keep it open.
+ */
+ if (i % 2) {
+ fd = dso__data_fd(dso, &machine);
+ TEST_ASSERT_VAL("failed to get fd", fd > 0);
+ } else {
+ #define BUFSIZE 10
+ u8 buf[BUFSIZE];
+ ssize_t n;
+
+ n = dso__data_read_offset(dso, &machine, 0, buf, BUFSIZE);
+ TEST_ASSERT_VAL("failed to read dso", n == BUFSIZE);
+ }
+ }
+
+ /* open +1 dso over the allowed limit */
+ fd = dso__data_fd(dsos[i], &machine);
+ TEST_ASSERT_VAL("failed to get fd", fd > 0);
+
+ /* should force the first one to be closed */
+ TEST_ASSERT_VAL("failed to close dsos[0]", dsos[0]->data.fd == -1);
+
+ /* cleanup everything */
+ dsos__delete(dso_cnt);
+
+ /* Make sure we did not leak any file descriptor. */
+ nr_end = open_files_cnt();
+ pr_debug("nr start %ld, nr stop %ld\n", nr, nr_end);
+ TEST_ASSERT_VAL("failed leadking files", nr == nr_end);
+ return 0;
+}
+
+int test__dso_data_reopen(void)
+{
+ struct machine machine;
+ long nr_end, nr = open_files_cnt();
+ int fd, fd_extra;
+
+#define dso_0 (dsos[0])
+#define dso_1 (dsos[1])
+#define dso_2 (dsos[2])
+
+ memset(&machine, 0, sizeof(machine));
+
+ /*
+ * Test scenario:
+ * - create 3 dso objects
+ * - set process file descriptor limit to current
+ * files count + 3
+ * - test that the first dso gets closed when we
+ * reach the files count limit
+ */
+
+ /* Make sure we are able to open 3 fds anyway */
+ TEST_ASSERT_VAL("failed to set file limit",
+ !set_fd_limit((nr + 3)));
+
+ TEST_ASSERT_VAL("failed to create dsos\n", !dsos__create(3, TEST_FILE_SIZE));
+
+ /* open dso_0 */
+ fd = dso__data_fd(dso_0, &machine);
+ TEST_ASSERT_VAL("failed to get fd", fd > 0);
+
+ /* open dso_1 */
+ fd = dso__data_fd(dso_1, &machine);
+ TEST_ASSERT_VAL("failed to get fd", fd > 0);
+
+ /*
+ * open extra file descriptor and we just
+ * reached the files count limit
+ */
+ fd_extra = open("/dev/null", O_RDONLY);
+ TEST_ASSERT_VAL("failed to open extra fd", fd_extra > 0);
+
+ /* open dso_2 */
+ fd = dso__data_fd(dso_2, &machine);
+ TEST_ASSERT_VAL("failed to get fd", fd > 0);
+
+ /*
+ * dso_0 should get closed, because we reached
+ * the file descriptor limit
+ */
+ TEST_ASSERT_VAL("failed to close dso_0", dso_0->data.fd == -1);
+
+ /* open dso_0 */
+ fd = dso__data_fd(dso_0, &machine);
+ TEST_ASSERT_VAL("failed to get fd", fd > 0);
+
+ /*
+ * dso_1 should get closed, because we reached
+ * the file descriptor limit
+ */
+ TEST_ASSERT_VAL("failed to close dso_1", dso_1->data.fd == -1);
+
+ /* cleanup everything */
+ close(fd_extra);
+ dsos__delete(3);
+
+ /* Make sure we did not leak any file descriptor. */
+ nr_end = open_files_cnt();
+ pr_debug("nr start %ld, nr stop %ld\n", nr, nr_end);
+ TEST_ASSERT_VAL("failed leadking files", nr == nr_end);
+ return 0;
+}
diff --git a/tools/perf/tests/dwarf-unwind.c b/tools/perf/tests/dwarf-unwind.c
new file mode 100644
index 00000000000..96adb730b74
--- /dev/null
+++ b/tools/perf/tests/dwarf-unwind.c
@@ -0,0 +1,144 @@
+#include <linux/compiler.h>
+#include <linux/types.h>
+#include <unistd.h>
+#include "tests.h"
+#include "debug.h"
+#include "machine.h"
+#include "event.h"
+#include "unwind.h"
+#include "perf_regs.h"
+#include "map.h"
+#include "thread.h"
+
+static int mmap_handler(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine)
+{
+ return machine__process_mmap2_event(machine, event, NULL);
+}
+
+static int init_live_machine(struct machine *machine)
+{
+ union perf_event event;
+ pid_t pid = getpid();
+
+ return perf_event__synthesize_mmap_events(NULL, &event, pid, pid,
+ mmap_handler, machine, true);
+}
+
+#define MAX_STACK 6
+
+static int unwind_entry(struct unwind_entry *entry, void *arg)
+{
+ unsigned long *cnt = (unsigned long *) arg;
+ char *symbol = entry->sym ? entry->sym->name : NULL;
+ static const char *funcs[MAX_STACK] = {
+ "test__arch_unwind_sample",
+ "unwind_thread",
+ "krava_3",
+ "krava_2",
+ "krava_1",
+ "test__dwarf_unwind"
+ };
+
+ if (*cnt >= MAX_STACK) {
+ pr_debug("failed: crossed the max stack value %d\n", MAX_STACK);
+ return -1;
+ }
+
+ if (!symbol) {
+ pr_debug("failed: got unresolved address 0x%" PRIx64 "\n",
+ entry->ip);
+ return -1;
+ }
+
+ pr_debug("got: %s 0x%" PRIx64 "\n", symbol, entry->ip);
+ return strcmp((const char *) symbol, funcs[(*cnt)++]);
+}
+
+__attribute__ ((noinline))
+static int unwind_thread(struct thread *thread, struct machine *machine)
+{
+ struct perf_sample sample;
+ unsigned long cnt = 0;
+ int err = -1;
+
+ memset(&sample, 0, sizeof(sample));
+
+ if (test__arch_unwind_sample(&sample, thread)) {
+ pr_debug("failed to get unwind sample\n");
+ goto out;
+ }
+
+ err = unwind__get_entries(unwind_entry, &cnt, machine, thread,
+ &sample, MAX_STACK);
+ if (err)
+ pr_debug("unwind failed\n");
+ else if (cnt != MAX_STACK) {
+ pr_debug("got wrong number of stack entries %lu != %d\n",
+ cnt, MAX_STACK);
+ err = -1;
+ }
+
+ out:
+ free(sample.user_stack.data);
+ free(sample.user_regs.regs);
+ return err;
+}
+
+__attribute__ ((noinline))
+static int krava_3(struct thread *thread, struct machine *machine)
+{
+ return unwind_thread(thread, machine);
+}
+
+__attribute__ ((noinline))
+static int krava_2(struct thread *thread, struct machine *machine)
+{
+ return krava_3(thread, machine);
+}
+
+__attribute__ ((noinline))
+static int krava_1(struct thread *thread, struct machine *machine)
+{
+ return krava_2(thread, machine);
+}
+
+int test__dwarf_unwind(void)
+{
+ struct machines machines;
+ struct machine *machine;
+ struct thread *thread;
+ int err = -1;
+
+ machines__init(&machines);
+
+ machine = machines__find(&machines, HOST_KERNEL_ID);
+ if (!machine) {
+ pr_err("Could not get machine\n");
+ return -1;
+ }
+
+ if (init_live_machine(machine)) {
+ pr_err("Could not init machine\n");
+ goto out;
+ }
+
+ if (verbose > 1)
+ machine__fprintf(machine, stderr);
+
+ thread = machine__find_thread(machine, getpid(), getpid());
+ if (!thread) {
+ pr_err("Could not get thread\n");
+ goto out;
+ }
+
+ err = krava_1(thread, machine);
+
+ out:
+ machine__delete_threads(machine);
+ machine__exit(machine);
+ machines__exit(&machines);
+ return err;
+}
diff --git a/tools/perf/tests/evsel-roundtrip-name.c b/tools/perf/tests/evsel-roundtrip-name.c
new file mode 100644
index 00000000000..465cdbc345c
--- /dev/null
+++ b/tools/perf/tests/evsel-roundtrip-name.c
@@ -0,0 +1,114 @@
+#include "evlist.h"
+#include "evsel.h"
+#include "parse-events.h"
+#include "tests.h"
+
+static int perf_evsel__roundtrip_cache_name_test(void)
+{
+ char name[128];
+ int type, op, err = 0, ret = 0, i, idx;
+ struct perf_evsel *evsel;
+ struct perf_evlist *evlist = perf_evlist__new();
+
+ if (evlist == NULL)
+ return -ENOMEM;
+
+ for (type = 0; type < PERF_COUNT_HW_CACHE_MAX; type++) {
+ for (op = 0; op < PERF_COUNT_HW_CACHE_OP_MAX; op++) {
+ /* skip invalid cache type */
+ if (!perf_evsel__is_cache_op_valid(type, op))
+ continue;
+
+ for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) {
+ __perf_evsel__hw_cache_type_op_res_name(type, op, i,
+ name, sizeof(name));
+ err = parse_events(evlist, name);
+ if (err)
+ ret = err;
+ }
+ }
+ }
+
+ idx = 0;
+ evsel = perf_evlist__first(evlist);
+
+ for (type = 0; type < PERF_COUNT_HW_CACHE_MAX; type++) {
+ for (op = 0; op < PERF_COUNT_HW_CACHE_OP_MAX; op++) {
+ /* skip invalid cache type */
+ if (!perf_evsel__is_cache_op_valid(type, op))
+ continue;
+
+ for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) {
+ __perf_evsel__hw_cache_type_op_res_name(type, op, i,
+ name, sizeof(name));
+ if (evsel->idx != idx)
+ continue;
+
+ ++idx;
+
+ if (strcmp(perf_evsel__name(evsel), name)) {
+ pr_debug("%s != %s\n", perf_evsel__name(evsel), name);
+ ret = -1;
+ }
+
+ evsel = perf_evsel__next(evsel);
+ }
+ }
+ }
+
+ perf_evlist__delete(evlist);
+ return ret;
+}
+
+static int __perf_evsel__name_array_test(const char *names[], int nr_names)
+{
+ int i, err;
+ struct perf_evsel *evsel;
+ struct perf_evlist *evlist = perf_evlist__new();
+
+ if (evlist == NULL)
+ return -ENOMEM;
+
+ for (i = 0; i < nr_names; ++i) {
+ err = parse_events(evlist, names[i]);
+ if (err) {
+ pr_debug("failed to parse event '%s', err %d\n",
+ names[i], err);
+ goto out_delete_evlist;
+ }
+ }
+
+ err = 0;
+ evlist__for_each(evlist, evsel) {
+ if (strcmp(perf_evsel__name(evsel), names[evsel->idx])) {
+ --err;
+ pr_debug("%s != %s\n", perf_evsel__name(evsel), names[evsel->idx]);
+ }
+ }
+
+out_delete_evlist:
+ perf_evlist__delete(evlist);
+ return err;
+}
+
+#define perf_evsel__name_array_test(names) \
+ __perf_evsel__name_array_test(names, ARRAY_SIZE(names))
+
+int test__perf_evsel__roundtrip_name_test(void)
+{
+ int err = 0, ret = 0;
+
+ err = perf_evsel__name_array_test(perf_evsel__hw_names);
+ if (err)
+ ret = err;
+
+ err = perf_evsel__name_array_test(perf_evsel__sw_names);
+ if (err)
+ ret = err;
+
+ err = perf_evsel__roundtrip_cache_name_test();
+ if (err)
+ ret = err;
+
+ return ret;
+}
diff --git a/tools/perf/tests/evsel-tp-sched.c b/tools/perf/tests/evsel-tp-sched.c
new file mode 100644
index 00000000000..35d7fdb2328
--- /dev/null
+++ b/tools/perf/tests/evsel-tp-sched.c
@@ -0,0 +1,81 @@
+#include <traceevent/event-parse.h>
+#include "evsel.h"
+#include "tests.h"
+
+static int perf_evsel__test_field(struct perf_evsel *evsel, const char *name,
+ int size, bool should_be_signed)
+{
+ struct format_field *field = perf_evsel__field(evsel, name);
+ int is_signed;
+ int ret = 0;
+
+ if (field == NULL) {
+ pr_debug("%s: \"%s\" field not found!\n", evsel->name, name);
+ return -1;
+ }
+
+ is_signed = !!(field->flags | FIELD_IS_SIGNED);
+ if (should_be_signed && !is_signed) {
+ pr_debug("%s: \"%s\" signedness(%d) is wrong, should be %d\n",
+ evsel->name, name, is_signed, should_be_signed);
+ ret = -1;
+ }
+
+ if (field->size != size) {
+ pr_debug("%s: \"%s\" size (%d) should be %d!\n",
+ evsel->name, name, field->size, size);
+ ret = -1;
+ }
+
+ return ret;
+}
+
+int test__perf_evsel__tp_sched_test(void)
+{
+ struct perf_evsel *evsel = perf_evsel__newtp("sched", "sched_switch");
+ int ret = 0;
+
+ if (evsel == NULL) {
+ pr_debug("perf_evsel__new\n");
+ return -1;
+ }
+
+ if (perf_evsel__test_field(evsel, "prev_comm", 16, true))
+ ret = -1;
+
+ if (perf_evsel__test_field(evsel, "prev_pid", 4, true))
+ ret = -1;
+
+ if (perf_evsel__test_field(evsel, "prev_prio", 4, true))
+ ret = -1;
+
+ if (perf_evsel__test_field(evsel, "prev_state", sizeof(long), true))
+ ret = -1;
+
+ if (perf_evsel__test_field(evsel, "next_comm", 16, true))
+ ret = -1;
+
+ if (perf_evsel__test_field(evsel, "next_pid", 4, true))
+ ret = -1;
+
+ if (perf_evsel__test_field(evsel, "next_prio", 4, true))
+ ret = -1;
+
+ perf_evsel__delete(evsel);
+
+ evsel = perf_evsel__newtp("sched", "sched_wakeup");
+
+ if (perf_evsel__test_field(evsel, "comm", 16, true))
+ ret = -1;
+
+ if (perf_evsel__test_field(evsel, "pid", 4, true))
+ ret = -1;
+
+ if (perf_evsel__test_field(evsel, "prio", 4, true))
+ ret = -1;
+
+ if (perf_evsel__test_field(evsel, "target_cpu", 4, true))
+ ret = -1;
+
+ return ret;
+}
diff --git a/tools/perf/tests/hists_common.c b/tools/perf/tests/hists_common.c
new file mode 100644
index 00000000000..a62c0913451
--- /dev/null
+++ b/tools/perf/tests/hists_common.c
@@ -0,0 +1,209 @@
+#include "perf.h"
+#include "util/debug.h"
+#include "util/symbol.h"
+#include "util/sort.h"
+#include "util/evsel.h"
+#include "util/evlist.h"
+#include "util/machine.h"
+#include "util/thread.h"
+#include "tests/hists_common.h"
+
+static struct {
+ u32 pid;
+ const char *comm;
+} fake_threads[] = {
+ { FAKE_PID_PERF1, "perf" },
+ { FAKE_PID_PERF2, "perf" },
+ { FAKE_PID_BASH, "bash" },
+};
+
+static struct {
+ u32 pid;
+ u64 start;
+ const char *filename;
+} fake_mmap_info[] = {
+ { FAKE_PID_PERF1, FAKE_MAP_PERF, "perf" },
+ { FAKE_PID_PERF1, FAKE_MAP_LIBC, "libc" },
+ { FAKE_PID_PERF1, FAKE_MAP_KERNEL, "[kernel]" },
+ { FAKE_PID_PERF2, FAKE_MAP_PERF, "perf" },
+ { FAKE_PID_PERF2, FAKE_MAP_LIBC, "libc" },
+ { FAKE_PID_PERF2, FAKE_MAP_KERNEL, "[kernel]" },
+ { FAKE_PID_BASH, FAKE_MAP_BASH, "bash" },
+ { FAKE_PID_BASH, FAKE_MAP_LIBC, "libc" },
+ { FAKE_PID_BASH, FAKE_MAP_KERNEL, "[kernel]" },
+};
+
+struct fake_sym {
+ u64 start;
+ u64 length;
+ const char *name;
+};
+
+static struct fake_sym perf_syms[] = {
+ { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "main" },
+ { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "run_command" },
+ { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "cmd_record" },
+};
+
+static struct fake_sym bash_syms[] = {
+ { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "main" },
+ { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "xmalloc" },
+ { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "xfree" },
+};
+
+static struct fake_sym libc_syms[] = {
+ { 700, 100, "malloc" },
+ { 800, 100, "free" },
+ { 900, 100, "realloc" },
+ { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "malloc" },
+ { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "free" },
+ { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "realloc" },
+};
+
+static struct fake_sym kernel_syms[] = {
+ { FAKE_SYM_OFFSET1, FAKE_SYM_LENGTH, "schedule" },
+ { FAKE_SYM_OFFSET2, FAKE_SYM_LENGTH, "page_fault" },
+ { FAKE_SYM_OFFSET3, FAKE_SYM_LENGTH, "sys_perf_event_open" },
+};
+
+static struct {
+ const char *dso_name;
+ struct fake_sym *syms;
+ size_t nr_syms;
+} fake_symbols[] = {
+ { "perf", perf_syms, ARRAY_SIZE(perf_syms) },
+ { "bash", bash_syms, ARRAY_SIZE(bash_syms) },
+ { "libc", libc_syms, ARRAY_SIZE(libc_syms) },
+ { "[kernel]", kernel_syms, ARRAY_SIZE(kernel_syms) },
+};
+
+struct machine *setup_fake_machine(struct machines *machines)
+{
+ struct machine *machine = machines__find(machines, HOST_KERNEL_ID);
+ size_t i;
+
+ if (machine == NULL) {
+ pr_debug("Not enough memory for machine setup\n");
+ return NULL;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(fake_threads); i++) {
+ struct thread *thread;
+
+ thread = machine__findnew_thread(machine, fake_threads[i].pid,
+ fake_threads[i].pid);
+ if (thread == NULL)
+ goto out;
+
+ thread__set_comm(thread, fake_threads[i].comm, 0);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(fake_mmap_info); i++) {
+ union perf_event fake_mmap_event = {
+ .mmap = {
+ .header = { .misc = PERF_RECORD_MISC_USER, },
+ .pid = fake_mmap_info[i].pid,
+ .tid = fake_mmap_info[i].pid,
+ .start = fake_mmap_info[i].start,
+ .len = FAKE_MAP_LENGTH,
+ .pgoff = 0ULL,
+ },
+ };
+
+ strcpy(fake_mmap_event.mmap.filename,
+ fake_mmap_info[i].filename);
+
+ machine__process_mmap_event(machine, &fake_mmap_event, NULL);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(fake_symbols); i++) {
+ size_t k;
+ struct dso *dso;
+
+ dso = __dsos__findnew(&machine->user_dsos,
+ fake_symbols[i].dso_name);
+ if (dso == NULL)
+ goto out;
+
+ /* emulate dso__load() */
+ dso__set_loaded(dso, MAP__FUNCTION);
+
+ for (k = 0; k < fake_symbols[i].nr_syms; k++) {
+ struct symbol *sym;
+ struct fake_sym *fsym = &fake_symbols[i].syms[k];
+
+ sym = symbol__new(fsym->start, fsym->length,
+ STB_GLOBAL, fsym->name);
+ if (sym == NULL)
+ goto out;
+
+ symbols__insert(&dso->symbols[MAP__FUNCTION], sym);
+ }
+ }
+
+ return machine;
+
+out:
+ pr_debug("Not enough memory for machine setup\n");
+ machine__delete_threads(machine);
+ machine__delete(machine);
+ return NULL;
+}
+
+void print_hists_in(struct hists *hists)
+{
+ int i = 0;
+ struct rb_root *root;
+ struct rb_node *node;
+
+ if (sort__need_collapse)
+ root = &hists->entries_collapsed;
+ else
+ root = hists->entries_in;
+
+ pr_info("----- %s --------\n", __func__);
+ node = rb_first(root);
+ while (node) {
+ struct hist_entry *he;
+
+ he = rb_entry(node, struct hist_entry, rb_node_in);
+
+ if (!he->filtered) {
+ pr_info("%2d: entry: %-8s [%-8s] %20s: period = %"PRIu64"\n",
+ i, thread__comm_str(he->thread),
+ he->ms.map->dso->short_name,
+ he->ms.sym->name, he->stat.period);
+ }
+
+ i++;
+ node = rb_next(node);
+ }
+}
+
+void print_hists_out(struct hists *hists)
+{
+ int i = 0;
+ struct rb_root *root;
+ struct rb_node *node;
+
+ root = &hists->entries;
+
+ pr_info("----- %s --------\n", __func__);
+ node = rb_first(root);
+ while (node) {
+ struct hist_entry *he;
+
+ he = rb_entry(node, struct hist_entry, rb_node);
+
+ if (!he->filtered) {
+ pr_info("%2d: entry: %8s:%5d [%-8s] %20s: period = %"PRIu64"/%"PRIu64"\n",
+ i, thread__comm_str(he->thread), he->thread->tid,
+ he->ms.map->dso->short_name,
+ he->ms.sym->name, he->stat.period,
+ he->stat_acc ? he->stat_acc->period : 0);
+ }
+
+ i++;
+ node = rb_next(node);
+ }
+}
diff --git a/tools/perf/tests/hists_common.h b/tools/perf/tests/hists_common.h
new file mode 100644
index 00000000000..888254e8665
--- /dev/null
+++ b/tools/perf/tests/hists_common.h
@@ -0,0 +1,75 @@
+#ifndef __PERF_TESTS__HISTS_COMMON_H__
+#define __PERF_TESTS__HISTS_COMMON_H__
+
+struct machine;
+struct machines;
+
+#define FAKE_PID_PERF1 100
+#define FAKE_PID_PERF2 200
+#define FAKE_PID_BASH 300
+
+#define FAKE_MAP_PERF 0x400000
+#define FAKE_MAP_BASH 0x400000
+#define FAKE_MAP_LIBC 0x500000
+#define FAKE_MAP_KERNEL 0xf00000
+#define FAKE_MAP_LENGTH 0x100000
+
+#define FAKE_SYM_OFFSET1 700
+#define FAKE_SYM_OFFSET2 800
+#define FAKE_SYM_OFFSET3 900
+#define FAKE_SYM_LENGTH 100
+
+#define FAKE_IP_PERF_MAIN FAKE_MAP_PERF + FAKE_SYM_OFFSET1
+#define FAKE_IP_PERF_RUN_COMMAND FAKE_MAP_PERF + FAKE_SYM_OFFSET2
+#define FAKE_IP_PERF_CMD_RECORD FAKE_MAP_PERF + FAKE_SYM_OFFSET3
+#define FAKE_IP_BASH_MAIN FAKE_MAP_BASH + FAKE_SYM_OFFSET1
+#define FAKE_IP_BASH_XMALLOC FAKE_MAP_BASH + FAKE_SYM_OFFSET2
+#define FAKE_IP_BASH_XFREE FAKE_MAP_BASH + FAKE_SYM_OFFSET3
+#define FAKE_IP_LIBC_MALLOC FAKE_MAP_LIBC + FAKE_SYM_OFFSET1
+#define FAKE_IP_LIBC_FREE FAKE_MAP_LIBC + FAKE_SYM_OFFSET2
+#define FAKE_IP_LIBC_REALLOC FAKE_MAP_LIBC + FAKE_SYM_OFFSET3
+#define FAKE_IP_KERNEL_SCHEDULE FAKE_MAP_KERNEL + FAKE_SYM_OFFSET1
+#define FAKE_IP_KERNEL_PAGE_FAULT FAKE_MAP_KERNEL + FAKE_SYM_OFFSET2
+#define FAKE_IP_KERNEL_SYS_PERF_EVENT_OPEN FAKE_MAP_KERNEL + FAKE_SYM_OFFSET3
+
+/*
+ * The setup_fake_machine() provides a test environment which consists
+ * of 3 processes that have 3 mappings and in turn, have 3 symbols
+ * respectively. See below table:
+ *
+ * Command: Pid Shared Object Symbol
+ * ............. ............. ...................
+ * perf: 100 perf main
+ * perf: 100 perf run_command
+ * perf: 100 perf cmd_record
+ * perf: 100 libc malloc
+ * perf: 100 libc free
+ * perf: 100 libc realloc
+ * perf: 100 [kernel] schedule
+ * perf: 100 [kernel] page_fault
+ * perf: 100 [kernel] sys_perf_event_open
+ * perf: 200 perf main
+ * perf: 200 perf run_command
+ * perf: 200 perf cmd_record
+ * perf: 200 libc malloc
+ * perf: 200 libc free
+ * perf: 200 libc realloc
+ * perf: 200 [kernel] schedule
+ * perf: 200 [kernel] page_fault
+ * perf: 200 [kernel] sys_perf_event_open
+ * bash: 300 bash main
+ * bash: 300 bash xmalloc
+ * bash: 300 bash xfree
+ * bash: 300 libc malloc
+ * bash: 300 libc free
+ * bash: 300 libc realloc
+ * bash: 300 [kernel] schedule
+ * bash: 300 [kernel] page_fault
+ * bash: 300 [kernel] sys_perf_event_open
+ */
+struct machine *setup_fake_machine(struct machines *machines);
+
+void print_hists_in(struct hists *hists);
+void print_hists_out(struct hists *hists);
+
+#endif /* __PERF_TESTS__HISTS_COMMON_H__ */
diff --git a/tools/perf/tests/hists_cumulate.c b/tools/perf/tests/hists_cumulate.c
new file mode 100644
index 00000000000..0ac240db2e2
--- /dev/null
+++ b/tools/perf/tests/hists_cumulate.c
@@ -0,0 +1,726 @@
+#include "perf.h"
+#include "util/debug.h"
+#include "util/symbol.h"
+#include "util/sort.h"
+#include "util/evsel.h"
+#include "util/evlist.h"
+#include "util/machine.h"
+#include "util/thread.h"
+#include "util/parse-events.h"
+#include "tests/tests.h"
+#include "tests/hists_common.h"
+
+struct sample {
+ u32 pid;
+ u64 ip;
+ struct thread *thread;
+ struct map *map;
+ struct symbol *sym;
+};
+
+/* For the numbers, see hists_common.c */
+static struct sample fake_samples[] = {
+ /* perf [kernel] schedule() */
+ { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, },
+ /* perf [perf] main() */
+ { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, },
+ /* perf [perf] cmd_record() */
+ { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_CMD_RECORD, },
+ /* perf [libc] malloc() */
+ { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, },
+ /* perf [libc] free() */
+ { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_FREE, },
+ /* perf [perf] main() */
+ { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, },
+ /* perf [kernel] page_fault() */
+ { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, },
+ /* bash [bash] main() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, },
+ /* bash [bash] xmalloc() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, },
+ /* bash [kernel] page_fault() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, },
+};
+
+/*
+ * Will be casted to struct ip_callchain which has all 64 bit entries
+ * of nr and ips[].
+ */
+static u64 fake_callchains[][10] = {
+ /* schedule => run_command => main */
+ { 3, FAKE_IP_KERNEL_SCHEDULE, FAKE_IP_PERF_RUN_COMMAND, FAKE_IP_PERF_MAIN, },
+ /* main */
+ { 1, FAKE_IP_PERF_MAIN, },
+ /* cmd_record => run_command => main */
+ { 3, FAKE_IP_PERF_CMD_RECORD, FAKE_IP_PERF_RUN_COMMAND, FAKE_IP_PERF_MAIN, },
+ /* malloc => cmd_record => run_command => main */
+ { 4, FAKE_IP_LIBC_MALLOC, FAKE_IP_PERF_CMD_RECORD, FAKE_IP_PERF_RUN_COMMAND,
+ FAKE_IP_PERF_MAIN, },
+ /* free => cmd_record => run_command => main */
+ { 4, FAKE_IP_LIBC_FREE, FAKE_IP_PERF_CMD_RECORD, FAKE_IP_PERF_RUN_COMMAND,
+ FAKE_IP_PERF_MAIN, },
+ /* main */
+ { 1, FAKE_IP_PERF_MAIN, },
+ /* page_fault => sys_perf_event_open => run_command => main */
+ { 4, FAKE_IP_KERNEL_PAGE_FAULT, FAKE_IP_KERNEL_SYS_PERF_EVENT_OPEN,
+ FAKE_IP_PERF_RUN_COMMAND, FAKE_IP_PERF_MAIN, },
+ /* main */
+ { 1, FAKE_IP_BASH_MAIN, },
+ /* xmalloc => malloc => xmalloc => malloc => xmalloc => main */
+ { 6, FAKE_IP_BASH_XMALLOC, FAKE_IP_LIBC_MALLOC, FAKE_IP_BASH_XMALLOC,
+ FAKE_IP_LIBC_MALLOC, FAKE_IP_BASH_XMALLOC, FAKE_IP_BASH_MAIN, },
+ /* page_fault => malloc => main */
+ { 3, FAKE_IP_KERNEL_PAGE_FAULT, FAKE_IP_LIBC_MALLOC, FAKE_IP_BASH_MAIN, },
+};
+
+static int add_hist_entries(struct hists *hists, struct machine *machine)
+{
+ struct addr_location al;
+ struct perf_evsel *evsel = hists_to_evsel(hists);
+ struct perf_sample sample = { .period = 1000, };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(fake_samples); i++) {
+ const union perf_event event = {
+ .header = {
+ .misc = PERF_RECORD_MISC_USER,
+ },
+ };
+ struct hist_entry_iter iter = {
+ .hide_unresolved = false,
+ };
+
+ if (symbol_conf.cumulate_callchain)
+ iter.ops = &hist_iter_cumulative;
+ else
+ iter.ops = &hist_iter_normal;
+
+ sample.pid = fake_samples[i].pid;
+ sample.tid = fake_samples[i].pid;
+ sample.ip = fake_samples[i].ip;
+ sample.callchain = (struct ip_callchain *)fake_callchains[i];
+
+ if (perf_event__preprocess_sample(&event, machine, &al,
+ &sample) < 0)
+ goto out;
+
+ if (hist_entry_iter__add(&iter, &al, evsel, &sample,
+ PERF_MAX_STACK_DEPTH, NULL) < 0)
+ goto out;
+
+ fake_samples[i].thread = al.thread;
+ fake_samples[i].map = al.map;
+ fake_samples[i].sym = al.sym;
+ }
+
+ return TEST_OK;
+
+out:
+ pr_debug("Not enough memory for adding a hist entry\n");
+ return TEST_FAIL;
+}
+
+static void del_hist_entries(struct hists *hists)
+{
+ struct hist_entry *he;
+ struct rb_root *root_in;
+ struct rb_root *root_out;
+ struct rb_node *node;
+
+ if (sort__need_collapse)
+ root_in = &hists->entries_collapsed;
+ else
+ root_in = hists->entries_in;
+
+ root_out = &hists->entries;
+
+ while (!RB_EMPTY_ROOT(root_out)) {
+ node = rb_first(root_out);
+
+ he = rb_entry(node, struct hist_entry, rb_node);
+ rb_erase(node, root_out);
+ rb_erase(&he->rb_node_in, root_in);
+ hist_entry__free(he);
+ }
+}
+
+typedef int (*test_fn_t)(struct perf_evsel *, struct machine *);
+
+#define COMM(he) (thread__comm_str(he->thread))
+#define DSO(he) (he->ms.map->dso->short_name)
+#define SYM(he) (he->ms.sym->name)
+#define CPU(he) (he->cpu)
+#define PID(he) (he->thread->tid)
+#define DEPTH(he) (he->callchain->max_depth)
+#define CDSO(cl) (cl->ms.map->dso->short_name)
+#define CSYM(cl) (cl->ms.sym->name)
+
+struct result {
+ u64 children;
+ u64 self;
+ const char *comm;
+ const char *dso;
+ const char *sym;
+};
+
+struct callchain_result {
+ u64 nr;
+ struct {
+ const char *dso;
+ const char *sym;
+ } node[10];
+};
+
+static int do_test(struct hists *hists, struct result *expected, size_t nr_expected,
+ struct callchain_result *expected_callchain, size_t nr_callchain)
+{
+ char buf[32];
+ size_t i, c;
+ struct hist_entry *he;
+ struct rb_root *root;
+ struct rb_node *node;
+ struct callchain_node *cnode;
+ struct callchain_list *clist;
+
+ /*
+ * adding and deleting hist entries must be done outside of this
+ * function since TEST_ASSERT_VAL() returns in case of failure.
+ */
+ hists__collapse_resort(hists, NULL);
+ hists__output_resort(hists);
+
+ if (verbose > 2) {
+ pr_info("use callchain: %d, cumulate callchain: %d\n",
+ symbol_conf.use_callchain,
+ symbol_conf.cumulate_callchain);
+ print_hists_out(hists);
+ }
+
+ root = &hists->entries;
+ for (node = rb_first(root), i = 0;
+ node && (he = rb_entry(node, struct hist_entry, rb_node));
+ node = rb_next(node), i++) {
+ scnprintf(buf, sizeof(buf), "Invalid hist entry #%zd", i);
+
+ TEST_ASSERT_VAL("Incorrect number of hist entry",
+ i < nr_expected);
+ TEST_ASSERT_VAL(buf, he->stat.period == expected[i].self &&
+ !strcmp(COMM(he), expected[i].comm) &&
+ !strcmp(DSO(he), expected[i].dso) &&
+ !strcmp(SYM(he), expected[i].sym));
+
+ if (symbol_conf.cumulate_callchain)
+ TEST_ASSERT_VAL(buf, he->stat_acc->period == expected[i].children);
+
+ if (!symbol_conf.use_callchain)
+ continue;
+
+ /* check callchain entries */
+ root = &he->callchain->node.rb_root;
+ cnode = rb_entry(rb_first(root), struct callchain_node, rb_node);
+
+ c = 0;
+ list_for_each_entry(clist, &cnode->val, list) {
+ scnprintf(buf, sizeof(buf), "Invalid callchain entry #%zd/%zd", i, c);
+
+ TEST_ASSERT_VAL("Incorrect number of callchain entry",
+ c < expected_callchain[i].nr);
+ TEST_ASSERT_VAL(buf,
+ !strcmp(CDSO(clist), expected_callchain[i].node[c].dso) &&
+ !strcmp(CSYM(clist), expected_callchain[i].node[c].sym));
+ c++;
+ }
+ /* TODO: handle multiple child nodes properly */
+ TEST_ASSERT_VAL("Incorrect number of callchain entry",
+ c <= expected_callchain[i].nr);
+ }
+ TEST_ASSERT_VAL("Incorrect number of hist entry",
+ i == nr_expected);
+ TEST_ASSERT_VAL("Incorrect number of callchain entry",
+ !symbol_conf.use_callchain || nr_expected == nr_callchain);
+ return 0;
+}
+
+/* NO callchain + NO children */
+static int test1(struct perf_evsel *evsel, struct machine *machine)
+{
+ int err;
+ struct hists *hists = &evsel->hists;
+ /*
+ * expected output:
+ *
+ * Overhead Command Shared Object Symbol
+ * ======== ======= ============= ==============
+ * 20.00% perf perf [.] main
+ * 10.00% bash [kernel] [k] page_fault
+ * 10.00% bash bash [.] main
+ * 10.00% bash bash [.] xmalloc
+ * 10.00% perf [kernel] [k] page_fault
+ * 10.00% perf [kernel] [k] schedule
+ * 10.00% perf libc [.] free
+ * 10.00% perf libc [.] malloc
+ * 10.00% perf perf [.] cmd_record
+ */
+ struct result expected[] = {
+ { 0, 2000, "perf", "perf", "main" },
+ { 0, 1000, "bash", "[kernel]", "page_fault" },
+ { 0, 1000, "bash", "bash", "main" },
+ { 0, 1000, "bash", "bash", "xmalloc" },
+ { 0, 1000, "perf", "[kernel]", "page_fault" },
+ { 0, 1000, "perf", "[kernel]", "schedule" },
+ { 0, 1000, "perf", "libc", "free" },
+ { 0, 1000, "perf", "libc", "malloc" },
+ { 0, 1000, "perf", "perf", "cmd_record" },
+ };
+
+ symbol_conf.use_callchain = false;
+ symbol_conf.cumulate_callchain = false;
+
+ setup_sorting();
+ callchain_register_param(&callchain_param);
+
+ err = add_hist_entries(hists, machine);
+ if (err < 0)
+ goto out;
+
+ err = do_test(hists, expected, ARRAY_SIZE(expected), NULL, 0);
+
+out:
+ del_hist_entries(hists);
+ reset_output_field();
+ return err;
+}
+
+/* callcain + NO children */
+static int test2(struct perf_evsel *evsel, struct machine *machine)
+{
+ int err;
+ struct hists *hists = &evsel->hists;
+ /*
+ * expected output:
+ *
+ * Overhead Command Shared Object Symbol
+ * ======== ======= ============= ==============
+ * 20.00% perf perf [.] main
+ * |
+ * --- main
+ *
+ * 10.00% bash [kernel] [k] page_fault
+ * |
+ * --- page_fault
+ * malloc
+ * main
+ *
+ * 10.00% bash bash [.] main
+ * |
+ * --- main
+ *
+ * 10.00% bash bash [.] xmalloc
+ * |
+ * --- xmalloc
+ * malloc
+ * xmalloc <--- NOTE: there's a cycle
+ * malloc
+ * xmalloc
+ * main
+ *
+ * 10.00% perf [kernel] [k] page_fault
+ * |
+ * --- page_fault
+ * sys_perf_event_open
+ * run_command
+ * main
+ *
+ * 10.00% perf [kernel] [k] schedule
+ * |
+ * --- schedule
+ * run_command
+ * main
+ *
+ * 10.00% perf libc [.] free
+ * |
+ * --- free
+ * cmd_record
+ * run_command
+ * main
+ *
+ * 10.00% perf libc [.] malloc
+ * |
+ * --- malloc
+ * cmd_record
+ * run_command
+ * main
+ *
+ * 10.00% perf perf [.] cmd_record
+ * |
+ * --- cmd_record
+ * run_command
+ * main
+ *
+ */
+ struct result expected[] = {
+ { 0, 2000, "perf", "perf", "main" },
+ { 0, 1000, "bash", "[kernel]", "page_fault" },
+ { 0, 1000, "bash", "bash", "main" },
+ { 0, 1000, "bash", "bash", "xmalloc" },
+ { 0, 1000, "perf", "[kernel]", "page_fault" },
+ { 0, 1000, "perf", "[kernel]", "schedule" },
+ { 0, 1000, "perf", "libc", "free" },
+ { 0, 1000, "perf", "libc", "malloc" },
+ { 0, 1000, "perf", "perf", "cmd_record" },
+ };
+ struct callchain_result expected_callchain[] = {
+ {
+ 1, { { "perf", "main" }, },
+ },
+ {
+ 3, { { "[kernel]", "page_fault" },
+ { "libc", "malloc" },
+ { "bash", "main" }, },
+ },
+ {
+ 1, { { "bash", "main" }, },
+ },
+ {
+ 6, { { "bash", "xmalloc" },
+ { "libc", "malloc" },
+ { "bash", "xmalloc" },
+ { "libc", "malloc" },
+ { "bash", "xmalloc" },
+ { "bash", "main" }, },
+ },
+ {
+ 4, { { "[kernel]", "page_fault" },
+ { "[kernel]", "sys_perf_event_open" },
+ { "perf", "run_command" },
+ { "perf", "main" }, },
+ },
+ {
+ 3, { { "[kernel]", "schedule" },
+ { "perf", "run_command" },
+ { "perf", "main" }, },
+ },
+ {
+ 4, { { "libc", "free" },
+ { "perf", "cmd_record" },
+ { "perf", "run_command" },
+ { "perf", "main" }, },
+ },
+ {
+ 4, { { "libc", "malloc" },
+ { "perf", "cmd_record" },
+ { "perf", "run_command" },
+ { "perf", "main" }, },
+ },
+ {
+ 3, { { "perf", "cmd_record" },
+ { "perf", "run_command" },
+ { "perf", "main" }, },
+ },
+ };
+
+ symbol_conf.use_callchain = true;
+ symbol_conf.cumulate_callchain = false;
+
+ setup_sorting();
+ callchain_register_param(&callchain_param);
+
+ err = add_hist_entries(hists, machine);
+ if (err < 0)
+ goto out;
+
+ err = do_test(hists, expected, ARRAY_SIZE(expected),
+ expected_callchain, ARRAY_SIZE(expected_callchain));
+
+out:
+ del_hist_entries(hists);
+ reset_output_field();
+ return err;
+}
+
+/* NO callchain + children */
+static int test3(struct perf_evsel *evsel, struct machine *machine)
+{
+ int err;
+ struct hists *hists = &evsel->hists;
+ /*
+ * expected output:
+ *
+ * Children Self Command Shared Object Symbol
+ * ======== ======== ======= ============= =======================
+ * 70.00% 20.00% perf perf [.] main
+ * 50.00% 0.00% perf perf [.] run_command
+ * 30.00% 10.00% bash bash [.] main
+ * 30.00% 10.00% perf perf [.] cmd_record
+ * 20.00% 0.00% bash libc [.] malloc
+ * 10.00% 10.00% bash [kernel] [k] page_fault
+ * 10.00% 10.00% perf [kernel] [k] schedule
+ * 10.00% 0.00% perf [kernel] [k] sys_perf_event_open
+ * 10.00% 10.00% perf [kernel] [k] page_fault
+ * 10.00% 10.00% perf libc [.] free
+ * 10.00% 10.00% perf libc [.] malloc
+ * 10.00% 10.00% bash bash [.] xmalloc
+ */
+ struct result expected[] = {
+ { 7000, 2000, "perf", "perf", "main" },
+ { 5000, 0, "perf", "perf", "run_command" },
+ { 3000, 1000, "bash", "bash", "main" },
+ { 3000, 1000, "perf", "perf", "cmd_record" },
+ { 2000, 0, "bash", "libc", "malloc" },
+ { 1000, 1000, "bash", "[kernel]", "page_fault" },
+ { 1000, 1000, "perf", "[kernel]", "schedule" },
+ { 1000, 0, "perf", "[kernel]", "sys_perf_event_open" },
+ { 1000, 1000, "perf", "[kernel]", "page_fault" },
+ { 1000, 1000, "perf", "libc", "free" },
+ { 1000, 1000, "perf", "libc", "malloc" },
+ { 1000, 1000, "bash", "bash", "xmalloc" },
+ };
+
+ symbol_conf.use_callchain = false;
+ symbol_conf.cumulate_callchain = true;
+
+ setup_sorting();
+ callchain_register_param(&callchain_param);
+
+ err = add_hist_entries(hists, machine);
+ if (err < 0)
+ goto out;
+
+ err = do_test(hists, expected, ARRAY_SIZE(expected), NULL, 0);
+
+out:
+ del_hist_entries(hists);
+ reset_output_field();
+ return err;
+}
+
+/* callchain + children */
+static int test4(struct perf_evsel *evsel, struct machine *machine)
+{
+ int err;
+ struct hists *hists = &evsel->hists;
+ /*
+ * expected output:
+ *
+ * Children Self Command Shared Object Symbol
+ * ======== ======== ======= ============= =======================
+ * 70.00% 20.00% perf perf [.] main
+ * |
+ * --- main
+ *
+ * 50.00% 0.00% perf perf [.] run_command
+ * |
+ * --- run_command
+ * main
+ *
+ * 30.00% 10.00% bash bash [.] main
+ * |
+ * --- main
+ *
+ * 30.00% 10.00% perf perf [.] cmd_record
+ * |
+ * --- cmd_record
+ * run_command
+ * main
+ *
+ * 20.00% 0.00% bash libc [.] malloc
+ * |
+ * --- malloc
+ * |
+ * |--50.00%-- xmalloc
+ * | main
+ * --50.00%-- main
+ *
+ * 10.00% 10.00% bash [kernel] [k] page_fault
+ * |
+ * --- page_fault
+ * malloc
+ * main
+ *
+ * 10.00% 10.00% perf [kernel] [k] schedule
+ * |
+ * --- schedule
+ * run_command
+ * main
+ *
+ * 10.00% 0.00% perf [kernel] [k] sys_perf_event_open
+ * |
+ * --- sys_perf_event_open
+ * run_command
+ * main
+ *
+ * 10.00% 10.00% perf [kernel] [k] page_fault
+ * |
+ * --- page_fault
+ * sys_perf_event_open
+ * run_command
+ * main
+ *
+ * 10.00% 10.00% perf libc [.] free
+ * |
+ * --- free
+ * cmd_record
+ * run_command
+ * main
+ *
+ * 10.00% 10.00% perf libc [.] malloc
+ * |
+ * --- malloc
+ * cmd_record
+ * run_command
+ * main
+ *
+ * 10.00% 10.00% bash bash [.] xmalloc
+ * |
+ * --- xmalloc
+ * malloc
+ * xmalloc <--- NOTE: there's a cycle
+ * malloc
+ * xmalloc
+ * main
+ *
+ */
+ struct result expected[] = {
+ { 7000, 2000, "perf", "perf", "main" },
+ { 5000, 0, "perf", "perf", "run_command" },
+ { 3000, 1000, "bash", "bash", "main" },
+ { 3000, 1000, "perf", "perf", "cmd_record" },
+ { 2000, 0, "bash", "libc", "malloc" },
+ { 1000, 1000, "bash", "[kernel]", "page_fault" },
+ { 1000, 1000, "perf", "[kernel]", "schedule" },
+ { 1000, 0, "perf", "[kernel]", "sys_perf_event_open" },
+ { 1000, 1000, "perf", "[kernel]", "page_fault" },
+ { 1000, 1000, "perf", "libc", "free" },
+ { 1000, 1000, "perf", "libc", "malloc" },
+ { 1000, 1000, "bash", "bash", "xmalloc" },
+ };
+ struct callchain_result expected_callchain[] = {
+ {
+ 1, { { "perf", "main" }, },
+ },
+ {
+ 2, { { "perf", "run_command" },
+ { "perf", "main" }, },
+ },
+ {
+ 1, { { "bash", "main" }, },
+ },
+ {
+ 3, { { "perf", "cmd_record" },
+ { "perf", "run_command" },
+ { "perf", "main" }, },
+ },
+ {
+ 4, { { "libc", "malloc" },
+ { "bash", "xmalloc" },
+ { "bash", "main" },
+ { "bash", "main" }, },
+ },
+ {
+ 3, { { "[kernel]", "page_fault" },
+ { "libc", "malloc" },
+ { "bash", "main" }, },
+ },
+ {
+ 3, { { "[kernel]", "schedule" },
+ { "perf", "run_command" },
+ { "perf", "main" }, },
+ },
+ {
+ 3, { { "[kernel]", "sys_perf_event_open" },
+ { "perf", "run_command" },
+ { "perf", "main" }, },
+ },
+ {
+ 4, { { "[kernel]", "page_fault" },
+ { "[kernel]", "sys_perf_event_open" },
+ { "perf", "run_command" },
+ { "perf", "main" }, },
+ },
+ {
+ 4, { { "libc", "free" },
+ { "perf", "cmd_record" },
+ { "perf", "run_command" },
+ { "perf", "main" }, },
+ },
+ {
+ 4, { { "libc", "malloc" },
+ { "perf", "cmd_record" },
+ { "perf", "run_command" },
+ { "perf", "main" }, },
+ },
+ {
+ 6, { { "bash", "xmalloc" },
+ { "libc", "malloc" },
+ { "bash", "xmalloc" },
+ { "libc", "malloc" },
+ { "bash", "xmalloc" },
+ { "bash", "main" }, },
+ },
+ };
+
+ symbol_conf.use_callchain = true;
+ symbol_conf.cumulate_callchain = true;
+
+ setup_sorting();
+ callchain_register_param(&callchain_param);
+
+ err = add_hist_entries(hists, machine);
+ if (err < 0)
+ goto out;
+
+ err = do_test(hists, expected, ARRAY_SIZE(expected),
+ expected_callchain, ARRAY_SIZE(expected_callchain));
+
+out:
+ del_hist_entries(hists);
+ reset_output_field();
+ return err;
+}
+
+int test__hists_cumulate(void)
+{
+ int err = TEST_FAIL;
+ struct machines machines;
+ struct machine *machine;
+ struct perf_evsel *evsel;
+ struct perf_evlist *evlist = perf_evlist__new();
+ size_t i;
+ test_fn_t testcases[] = {
+ test1,
+ test2,
+ test3,
+ test4,
+ };
+
+ TEST_ASSERT_VAL("No memory", evlist);
+
+ err = parse_events(evlist, "cpu-clock");
+ if (err)
+ goto out;
+
+ machines__init(&machines);
+
+ /* setup threads/dso/map/symbols also */
+ machine = setup_fake_machine(&machines);
+ if (!machine)
+ goto out;
+
+ if (verbose > 1)
+ machine__fprintf(machine, stderr);
+
+ evsel = perf_evlist__first(evlist);
+
+ for (i = 0; i < ARRAY_SIZE(testcases); i++) {
+ err = testcases[i](evsel, machine);
+ if (err < 0)
+ break;
+ }
+
+out:
+ /* tear down everything */
+ perf_evlist__delete(evlist);
+ machines__exit(&machines);
+
+ return err;
+}
diff --git a/tools/perf/tests/hists_filter.c b/tools/perf/tests/hists_filter.c
new file mode 100644
index 00000000000..821f581fd93
--- /dev/null
+++ b/tools/perf/tests/hists_filter.c
@@ -0,0 +1,289 @@
+#include "perf.h"
+#include "util/debug.h"
+#include "util/symbol.h"
+#include "util/sort.h"
+#include "util/evsel.h"
+#include "util/evlist.h"
+#include "util/machine.h"
+#include "util/thread.h"
+#include "util/parse-events.h"
+#include "tests/tests.h"
+#include "tests/hists_common.h"
+
+struct sample {
+ u32 pid;
+ u64 ip;
+ struct thread *thread;
+ struct map *map;
+ struct symbol *sym;
+};
+
+/* For the numbers, see hists_common.c */
+static struct sample fake_samples[] = {
+ /* perf [kernel] schedule() */
+ { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, },
+ /* perf [perf] main() */
+ { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, },
+ /* perf [libc] malloc() */
+ { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, },
+ /* perf [perf] main() */
+ { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, }, /* will be merged */
+ /* perf [perf] cmd_record() */
+ { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_CMD_RECORD, },
+ /* perf [kernel] page_fault() */
+ { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, },
+ /* bash [bash] main() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, },
+ /* bash [bash] xmalloc() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, },
+ /* bash [libc] malloc() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, },
+ /* bash [kernel] page_fault() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, },
+};
+
+static int add_hist_entries(struct perf_evlist *evlist,
+ struct machine *machine __maybe_unused)
+{
+ struct perf_evsel *evsel;
+ struct addr_location al;
+ struct perf_sample sample = { .period = 100, };
+ size_t i;
+
+ /*
+ * each evsel will have 10 samples but the 4th sample
+ * (perf [perf] main) will be collapsed to an existing entry
+ * so total 9 entries will be in the tree.
+ */
+ evlist__for_each(evlist, evsel) {
+ for (i = 0; i < ARRAY_SIZE(fake_samples); i++) {
+ const union perf_event event = {
+ .header = {
+ .misc = PERF_RECORD_MISC_USER,
+ },
+ };
+ struct hist_entry_iter iter = {
+ .ops = &hist_iter_normal,
+ .hide_unresolved = false,
+ };
+
+ /* make sure it has no filter at first */
+ evsel->hists.thread_filter = NULL;
+ evsel->hists.dso_filter = NULL;
+ evsel->hists.symbol_filter_str = NULL;
+
+ sample.pid = fake_samples[i].pid;
+ sample.tid = fake_samples[i].pid;
+ sample.ip = fake_samples[i].ip;
+
+ if (perf_event__preprocess_sample(&event, machine, &al,
+ &sample) < 0)
+ goto out;
+
+ if (hist_entry_iter__add(&iter, &al, evsel, &sample,
+ PERF_MAX_STACK_DEPTH, NULL) < 0)
+ goto out;
+
+ fake_samples[i].thread = al.thread;
+ fake_samples[i].map = al.map;
+ fake_samples[i].sym = al.sym;
+ }
+ }
+
+ return 0;
+
+out:
+ pr_debug("Not enough memory for adding a hist entry\n");
+ return TEST_FAIL;
+}
+
+int test__hists_filter(void)
+{
+ int err = TEST_FAIL;
+ struct machines machines;
+ struct machine *machine;
+ struct perf_evsel *evsel;
+ struct perf_evlist *evlist = perf_evlist__new();
+
+ TEST_ASSERT_VAL("No memory", evlist);
+
+ err = parse_events(evlist, "cpu-clock");
+ if (err)
+ goto out;
+ err = parse_events(evlist, "task-clock");
+ if (err)
+ goto out;
+
+ /* default sort order (comm,dso,sym) will be used */
+ if (setup_sorting() < 0)
+ goto out;
+
+ machines__init(&machines);
+
+ /* setup threads/dso/map/symbols also */
+ machine = setup_fake_machine(&machines);
+ if (!machine)
+ goto out;
+
+ if (verbose > 1)
+ machine__fprintf(machine, stderr);
+
+ /* process sample events */
+ err = add_hist_entries(evlist, machine);
+ if (err < 0)
+ goto out;
+
+ evlist__for_each(evlist, evsel) {
+ struct hists *hists = &evsel->hists;
+
+ hists__collapse_resort(hists, NULL);
+ hists__output_resort(hists);
+
+ if (verbose > 2) {
+ pr_info("Normal histogram\n");
+ print_hists_out(hists);
+ }
+
+ TEST_ASSERT_VAL("Invalid nr samples",
+ hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10);
+ TEST_ASSERT_VAL("Invalid nr hist entries",
+ hists->nr_entries == 9);
+ TEST_ASSERT_VAL("Invalid total period",
+ hists->stats.total_period == 1000);
+ TEST_ASSERT_VAL("Unmatched nr samples",
+ hists->stats.nr_events[PERF_RECORD_SAMPLE] ==
+ hists->stats.nr_non_filtered_samples);
+ TEST_ASSERT_VAL("Unmatched nr hist entries",
+ hists->nr_entries == hists->nr_non_filtered_entries);
+ TEST_ASSERT_VAL("Unmatched total period",
+ hists->stats.total_period ==
+ hists->stats.total_non_filtered_period);
+
+ /* now applying thread filter for 'bash' */
+ evsel->hists.thread_filter = fake_samples[9].thread;
+ hists__filter_by_thread(hists);
+
+ if (verbose > 2) {
+ pr_info("Histogram for thread filter\n");
+ print_hists_out(hists);
+ }
+
+ /* normal stats should be invariant */
+ TEST_ASSERT_VAL("Invalid nr samples",
+ hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10);
+ TEST_ASSERT_VAL("Invalid nr hist entries",
+ hists->nr_entries == 9);
+ TEST_ASSERT_VAL("Invalid total period",
+ hists->stats.total_period == 1000);
+
+ /* but filter stats are changed */
+ TEST_ASSERT_VAL("Unmatched nr samples for thread filter",
+ hists->stats.nr_non_filtered_samples == 4);
+ TEST_ASSERT_VAL("Unmatched nr hist entries for thread filter",
+ hists->nr_non_filtered_entries == 4);
+ TEST_ASSERT_VAL("Unmatched total period for thread filter",
+ hists->stats.total_non_filtered_period == 400);
+
+ /* remove thread filter first */
+ evsel->hists.thread_filter = NULL;
+ hists__filter_by_thread(hists);
+
+ /* now applying dso filter for 'kernel' */
+ evsel->hists.dso_filter = fake_samples[0].map->dso;
+ hists__filter_by_dso(hists);
+
+ if (verbose > 2) {
+ pr_info("Histogram for dso filter\n");
+ print_hists_out(hists);
+ }
+
+ /* normal stats should be invariant */
+ TEST_ASSERT_VAL("Invalid nr samples",
+ hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10);
+ TEST_ASSERT_VAL("Invalid nr hist entries",
+ hists->nr_entries == 9);
+ TEST_ASSERT_VAL("Invalid total period",
+ hists->stats.total_period == 1000);
+
+ /* but filter stats are changed */
+ TEST_ASSERT_VAL("Unmatched nr samples for dso filter",
+ hists->stats.nr_non_filtered_samples == 3);
+ TEST_ASSERT_VAL("Unmatched nr hist entries for dso filter",
+ hists->nr_non_filtered_entries == 3);
+ TEST_ASSERT_VAL("Unmatched total period for dso filter",
+ hists->stats.total_non_filtered_period == 300);
+
+ /* remove dso filter first */
+ evsel->hists.dso_filter = NULL;
+ hists__filter_by_dso(hists);
+
+ /*
+ * now applying symbol filter for 'main'. Also note that
+ * there's 3 samples that have 'main' symbol but the 4th
+ * entry of fake_samples was collapsed already so it won't
+ * be counted as a separate entry but the sample count and
+ * total period will be remained.
+ */
+ evsel->hists.symbol_filter_str = "main";
+ hists__filter_by_symbol(hists);
+
+ if (verbose > 2) {
+ pr_info("Histogram for symbol filter\n");
+ print_hists_out(hists);
+ }
+
+ /* normal stats should be invariant */
+ TEST_ASSERT_VAL("Invalid nr samples",
+ hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10);
+ TEST_ASSERT_VAL("Invalid nr hist entries",
+ hists->nr_entries == 9);
+ TEST_ASSERT_VAL("Invalid total period",
+ hists->stats.total_period == 1000);
+
+ /* but filter stats are changed */
+ TEST_ASSERT_VAL("Unmatched nr samples for symbol filter",
+ hists->stats.nr_non_filtered_samples == 3);
+ TEST_ASSERT_VAL("Unmatched nr hist entries for symbol filter",
+ hists->nr_non_filtered_entries == 2);
+ TEST_ASSERT_VAL("Unmatched total period for symbol filter",
+ hists->stats.total_non_filtered_period == 300);
+
+ /* now applying all filters at once. */
+ evsel->hists.thread_filter = fake_samples[1].thread;
+ evsel->hists.dso_filter = fake_samples[1].map->dso;
+ hists__filter_by_thread(hists);
+ hists__filter_by_dso(hists);
+
+ if (verbose > 2) {
+ pr_info("Histogram for all filters\n");
+ print_hists_out(hists);
+ }
+
+ /* normal stats should be invariant */
+ TEST_ASSERT_VAL("Invalid nr samples",
+ hists->stats.nr_events[PERF_RECORD_SAMPLE] == 10);
+ TEST_ASSERT_VAL("Invalid nr hist entries",
+ hists->nr_entries == 9);
+ TEST_ASSERT_VAL("Invalid total period",
+ hists->stats.total_period == 1000);
+
+ /* but filter stats are changed */
+ TEST_ASSERT_VAL("Unmatched nr samples for all filter",
+ hists->stats.nr_non_filtered_samples == 2);
+ TEST_ASSERT_VAL("Unmatched nr hist entries for all filter",
+ hists->nr_non_filtered_entries == 1);
+ TEST_ASSERT_VAL("Unmatched total period for all filter",
+ hists->stats.total_non_filtered_period == 200);
+ }
+
+
+ err = TEST_OK;
+
+out:
+ /* tear down everything */
+ perf_evlist__delete(evlist);
+ reset_output_field();
+ machines__exit(&machines);
+
+ return err;
+}
diff --git a/tools/perf/tests/hists_link.c b/tools/perf/tests/hists_link.c
new file mode 100644
index 00000000000..d4b34b0f50a
--- /dev/null
+++ b/tools/perf/tests/hists_link.c
@@ -0,0 +1,339 @@
+#include "perf.h"
+#include "tests.h"
+#include "debug.h"
+#include "symbol.h"
+#include "sort.h"
+#include "evsel.h"
+#include "evlist.h"
+#include "machine.h"
+#include "thread.h"
+#include "parse-events.h"
+#include "hists_common.h"
+
+struct sample {
+ u32 pid;
+ u64 ip;
+ struct thread *thread;
+ struct map *map;
+ struct symbol *sym;
+};
+
+/* For the numbers, see hists_common.c */
+static struct sample fake_common_samples[] = {
+ /* perf [kernel] schedule() */
+ { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, },
+ /* perf [perf] main() */
+ { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, },
+ /* perf [perf] cmd_record() */
+ { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_CMD_RECORD, },
+ /* bash [bash] xmalloc() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, },
+ /* bash [libc] malloc() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, },
+};
+
+static struct sample fake_samples[][5] = {
+ {
+ /* perf [perf] run_command() */
+ { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_RUN_COMMAND, },
+ /* perf [libc] malloc() */
+ { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, },
+ /* perf [kernel] page_fault() */
+ { .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_PAGE_FAULT, },
+ /* perf [kernel] sys_perf_event_open() */
+ { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_SYS_PERF_EVENT_OPEN, },
+ /* bash [libc] free() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_FREE, },
+ },
+ {
+ /* perf [libc] free() */
+ { .pid = FAKE_PID_PERF2, .ip = FAKE_IP_LIBC_FREE, },
+ /* bash [libc] malloc() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_MALLOC, }, /* will be merged */
+ /* bash [bash] xfee() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XFREE, },
+ /* bash [libc] realloc() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_LIBC_REALLOC, },
+ /* bash [kernel] page_fault() */
+ { .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, },
+ },
+};
+
+static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine)
+{
+ struct perf_evsel *evsel;
+ struct addr_location al;
+ struct hist_entry *he;
+ struct perf_sample sample = { .period = 1, };
+ size_t i = 0, k;
+
+ /*
+ * each evsel will have 10 samples - 5 common and 5 distinct.
+ * However the second evsel also has a collapsed entry for
+ * "bash [libc] malloc" so total 9 entries will be in the tree.
+ */
+ evlist__for_each(evlist, evsel) {
+ for (k = 0; k < ARRAY_SIZE(fake_common_samples); k++) {
+ const union perf_event event = {
+ .header = {
+ .misc = PERF_RECORD_MISC_USER,
+ },
+ };
+
+ sample.pid = fake_common_samples[k].pid;
+ sample.tid = fake_common_samples[k].pid;
+ sample.ip = fake_common_samples[k].ip;
+ if (perf_event__preprocess_sample(&event, machine, &al,
+ &sample) < 0)
+ goto out;
+
+ he = __hists__add_entry(&evsel->hists, &al, NULL,
+ NULL, NULL, 1, 1, 0, true);
+ if (he == NULL)
+ goto out;
+
+ fake_common_samples[k].thread = al.thread;
+ fake_common_samples[k].map = al.map;
+ fake_common_samples[k].sym = al.sym;
+ }
+
+ for (k = 0; k < ARRAY_SIZE(fake_samples[i]); k++) {
+ const union perf_event event = {
+ .header = {
+ .misc = PERF_RECORD_MISC_USER,
+ },
+ };
+
+ sample.pid = fake_samples[i][k].pid;
+ sample.tid = fake_samples[i][k].pid;
+ sample.ip = fake_samples[i][k].ip;
+ if (perf_event__preprocess_sample(&event, machine, &al,
+ &sample) < 0)
+ goto out;
+
+ he = __hists__add_entry(&evsel->hists, &al, NULL,
+ NULL, NULL, 1, 1, 0, true);
+ if (he == NULL)
+ goto out;
+
+ fake_samples[i][k].thread = al.thread;
+ fake_samples[i][k].map = al.map;
+ fake_samples[i][k].sym = al.sym;
+ }
+ i++;
+ }
+
+ return 0;
+
+out:
+ pr_debug("Not enough memory for adding a hist entry\n");
+ return -1;
+}
+
+static int find_sample(struct sample *samples, size_t nr_samples,
+ struct thread *t, struct map *m, struct symbol *s)
+{
+ while (nr_samples--) {
+ if (samples->thread == t && samples->map == m &&
+ samples->sym == s)
+ return 1;
+ samples++;
+ }
+ return 0;
+}
+
+static int __validate_match(struct hists *hists)
+{
+ size_t count = 0;
+ struct rb_root *root;
+ struct rb_node *node;
+
+ /*
+ * Only entries from fake_common_samples should have a pair.
+ */
+ if (sort__need_collapse)
+ root = &hists->entries_collapsed;
+ else
+ root = hists->entries_in;
+
+ node = rb_first(root);
+ while (node) {
+ struct hist_entry *he;
+
+ he = rb_entry(node, struct hist_entry, rb_node_in);
+
+ if (hist_entry__has_pairs(he)) {
+ if (find_sample(fake_common_samples,
+ ARRAY_SIZE(fake_common_samples),
+ he->thread, he->ms.map, he->ms.sym)) {
+ count++;
+ } else {
+ pr_debug("Can't find the matched entry\n");
+ return -1;
+ }
+ }
+
+ node = rb_next(node);
+ }
+
+ if (count != ARRAY_SIZE(fake_common_samples)) {
+ pr_debug("Invalid count for matched entries: %zd of %zd\n",
+ count, ARRAY_SIZE(fake_common_samples));
+ return -1;
+ }
+
+ return 0;
+}
+
+static int validate_match(struct hists *leader, struct hists *other)
+{
+ return __validate_match(leader) || __validate_match(other);
+}
+
+static int __validate_link(struct hists *hists, int idx)
+{
+ size_t count = 0;
+ size_t count_pair = 0;
+ size_t count_dummy = 0;
+ struct rb_root *root;
+ struct rb_node *node;
+
+ /*
+ * Leader hists (idx = 0) will have dummy entries from other,
+ * and some entries will have no pair. However every entry
+ * in other hists should have (dummy) pair.
+ */
+ if (sort__need_collapse)
+ root = &hists->entries_collapsed;
+ else
+ root = hists->entries_in;
+
+ node = rb_first(root);
+ while (node) {
+ struct hist_entry *he;
+
+ he = rb_entry(node, struct hist_entry, rb_node_in);
+
+ if (hist_entry__has_pairs(he)) {
+ if (!find_sample(fake_common_samples,
+ ARRAY_SIZE(fake_common_samples),
+ he->thread, he->ms.map, he->ms.sym) &&
+ !find_sample(fake_samples[idx],
+ ARRAY_SIZE(fake_samples[idx]),
+ he->thread, he->ms.map, he->ms.sym)) {
+ count_dummy++;
+ }
+ count_pair++;
+ } else if (idx) {
+ pr_debug("A entry from the other hists should have pair\n");
+ return -1;
+ }
+
+ count++;
+ node = rb_next(node);
+ }
+
+ /*
+ * Note that we have a entry collapsed in the other (idx = 1) hists.
+ */
+ if (idx == 0) {
+ if (count_dummy != ARRAY_SIZE(fake_samples[1]) - 1) {
+ pr_debug("Invalid count of dummy entries: %zd of %zd\n",
+ count_dummy, ARRAY_SIZE(fake_samples[1]) - 1);
+ return -1;
+ }
+ if (count != count_pair + ARRAY_SIZE(fake_samples[0])) {
+ pr_debug("Invalid count of total leader entries: %zd of %zd\n",
+ count, count_pair + ARRAY_SIZE(fake_samples[0]));
+ return -1;
+ }
+ } else {
+ if (count != count_pair) {
+ pr_debug("Invalid count of total other entries: %zd of %zd\n",
+ count, count_pair);
+ return -1;
+ }
+ if (count_dummy > 0) {
+ pr_debug("Other hists should not have dummy entries: %zd\n",
+ count_dummy);
+ return -1;
+ }
+ }
+
+ return 0;
+}
+
+static int validate_link(struct hists *leader, struct hists *other)
+{
+ return __validate_link(leader, 0) || __validate_link(other, 1);
+}
+
+int test__hists_link(void)
+{
+ int err = -1;
+ struct machines machines;
+ struct machine *machine = NULL;
+ struct perf_evsel *evsel, *first;
+ struct perf_evlist *evlist = perf_evlist__new();
+
+ if (evlist == NULL)
+ return -ENOMEM;
+
+ err = parse_events(evlist, "cpu-clock");
+ if (err)
+ goto out;
+ err = parse_events(evlist, "task-clock");
+ if (err)
+ goto out;
+
+ /* default sort order (comm,dso,sym) will be used */
+ if (setup_sorting() < 0)
+ goto out;
+
+ machines__init(&machines);
+
+ /* setup threads/dso/map/symbols also */
+ machine = setup_fake_machine(&machines);
+ if (!machine)
+ goto out;
+
+ if (verbose > 1)
+ machine__fprintf(machine, stderr);
+
+ /* process sample events */
+ err = add_hist_entries(evlist, machine);
+ if (err < 0)
+ goto out;
+
+ evlist__for_each(evlist, evsel) {
+ hists__collapse_resort(&evsel->hists, NULL);
+
+ if (verbose > 2)
+ print_hists_in(&evsel->hists);
+ }
+
+ first = perf_evlist__first(evlist);
+ evsel = perf_evlist__last(evlist);
+
+ /* match common entries */
+ hists__match(&first->hists, &evsel->hists);
+ err = validate_match(&first->hists, &evsel->hists);
+ if (err)
+ goto out;
+
+ /* link common and/or dummy entries */
+ hists__link(&first->hists, &evsel->hists);
+ err = validate_link(&first->hists, &evsel->hists);
+ if (err)
+ goto out;
+
+ err = 0;
+
+out:
+ /* tear down everything */
+ perf_evlist__delete(evlist);
+ reset_output_field();
+ machines__exit(&machines);
+
+ return err;
+}
diff --git a/tools/perf/tests/hists_output.c b/tools/perf/tests/hists_output.c
new file mode 100644
index 00000000000..e3bbd6c54c1
--- /dev/null
+++ b/tools/perf/tests/hists_output.c
@@ -0,0 +1,621 @@
+#include "perf.h"
+#include "util/debug.h"
+#include "util/symbol.h"
+#include "util/sort.h"
+#include "util/evsel.h"
+#include "util/evlist.h"
+#include "util/machine.h"
+#include "util/thread.h"
+#include "util/parse-events.h"
+#include "tests/tests.h"
+#include "tests/hists_common.h"
+
+struct sample {
+ u32 cpu;
+ u32 pid;
+ u64 ip;
+ struct thread *thread;
+ struct map *map;
+ struct symbol *sym;
+};
+
+/* For the numbers, see hists_common.c */
+static struct sample fake_samples[] = {
+ /* perf [kernel] schedule() */
+ { .cpu = 0, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_KERNEL_SCHEDULE, },
+ /* perf [perf] main() */
+ { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_MAIN, },
+ /* perf [perf] cmd_record() */
+ { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_PERF_CMD_RECORD, },
+ /* perf [libc] malloc() */
+ { .cpu = 1, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_MALLOC, },
+ /* perf [libc] free() */
+ { .cpu = 2, .pid = FAKE_PID_PERF1, .ip = FAKE_IP_LIBC_FREE, },
+ /* perf [perf] main() */
+ { .cpu = 2, .pid = FAKE_PID_PERF2, .ip = FAKE_IP_PERF_MAIN, },
+ /* perf [kernel] page_fault() */
+ { .cpu = 2, .pid = FAKE_PID_PERF2, .ip = FAKE_IP_KERNEL_PAGE_FAULT, },
+ /* bash [bash] main() */
+ { .cpu = 3, .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_MAIN, },
+ /* bash [bash] xmalloc() */
+ { .cpu = 0, .pid = FAKE_PID_BASH, .ip = FAKE_IP_BASH_XMALLOC, },
+ /* bash [kernel] page_fault() */
+ { .cpu = 1, .pid = FAKE_PID_BASH, .ip = FAKE_IP_KERNEL_PAGE_FAULT, },
+};
+
+static int add_hist_entries(struct hists *hists, struct machine *machine)
+{
+ struct addr_location al;
+ struct perf_evsel *evsel = hists_to_evsel(hists);
+ struct perf_sample sample = { .period = 100, };
+ size_t i;
+
+ for (i = 0; i < ARRAY_SIZE(fake_samples); i++) {
+ const union perf_event event = {
+ .header = {
+ .misc = PERF_RECORD_MISC_USER,
+ },
+ };
+ struct hist_entry_iter iter = {
+ .ops = &hist_iter_normal,
+ .hide_unresolved = false,
+ };
+
+ sample.cpu = fake_samples[i].cpu;
+ sample.pid = fake_samples[i].pid;
+ sample.tid = fake_samples[i].pid;
+ sample.ip = fake_samples[i].ip;
+
+ if (perf_event__preprocess_sample(&event, machine, &al,
+ &sample) < 0)
+ goto out;
+
+ if (hist_entry_iter__add(&iter, &al, evsel, &sample,
+ PERF_MAX_STACK_DEPTH, NULL) < 0)
+ goto out;
+
+ fake_samples[i].thread = al.thread;
+ fake_samples[i].map = al.map;
+ fake_samples[i].sym = al.sym;
+ }
+
+ return TEST_OK;
+
+out:
+ pr_debug("Not enough memory for adding a hist entry\n");
+ return TEST_FAIL;
+}
+
+static void del_hist_entries(struct hists *hists)
+{
+ struct hist_entry *he;
+ struct rb_root *root_in;
+ struct rb_root *root_out;
+ struct rb_node *node;
+
+ if (sort__need_collapse)
+ root_in = &hists->entries_collapsed;
+ else
+ root_in = hists->entries_in;
+
+ root_out = &hists->entries;
+
+ while (!RB_EMPTY_ROOT(root_out)) {
+ node = rb_first(root_out);
+
+ he = rb_entry(node, struct hist_entry, rb_node);
+ rb_erase(node, root_out);
+ rb_erase(&he->rb_node_in, root_in);
+ hist_entry__free(he);
+ }
+}
+
+typedef int (*test_fn_t)(struct perf_evsel *, struct machine *);
+
+#define COMM(he) (thread__comm_str(he->thread))
+#define DSO(he) (he->ms.map->dso->short_name)
+#define SYM(he) (he->ms.sym->name)
+#define CPU(he) (he->cpu)
+#define PID(he) (he->thread->tid)
+
+/* default sort keys (no field) */
+static int test1(struct perf_evsel *evsel, struct machine *machine)
+{
+ int err;
+ struct hists *hists = &evsel->hists;
+ struct hist_entry *he;
+ struct rb_root *root;
+ struct rb_node *node;
+
+ field_order = NULL;
+ sort_order = NULL; /* equivalent to sort_order = "comm,dso,sym" */
+
+ setup_sorting();
+
+ /*
+ * expected output:
+ *
+ * Overhead Command Shared Object Symbol
+ * ======== ======= ============= ==============
+ * 20.00% perf perf [.] main
+ * 10.00% bash [kernel] [k] page_fault
+ * 10.00% bash bash [.] main
+ * 10.00% bash bash [.] xmalloc
+ * 10.00% perf [kernel] [k] page_fault
+ * 10.00% perf [kernel] [k] schedule
+ * 10.00% perf libc [.] free
+ * 10.00% perf libc [.] malloc
+ * 10.00% perf perf [.] cmd_record
+ */
+ err = add_hist_entries(hists, machine);
+ if (err < 0)
+ goto out;
+
+ hists__collapse_resort(hists, NULL);
+ hists__output_resort(hists);
+
+ if (verbose > 2) {
+ pr_info("[fields = %s, sort = %s]\n", field_order, sort_order);
+ print_hists_out(hists);
+ }
+
+ root = &evsel->hists.entries;
+ node = rb_first(root);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") &&
+ !strcmp(SYM(he), "main") && he->stat.period == 200);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") &&
+ !strcmp(SYM(he), "page_fault") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") &&
+ !strcmp(SYM(he), "main") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") &&
+ !strcmp(SYM(he), "xmalloc") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") &&
+ !strcmp(SYM(he), "page_fault") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") &&
+ !strcmp(SYM(he), "schedule") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") &&
+ !strcmp(SYM(he), "free") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") &&
+ !strcmp(SYM(he), "malloc") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") &&
+ !strcmp(SYM(he), "cmd_record") && he->stat.period == 100);
+
+out:
+ del_hist_entries(hists);
+ reset_output_field();
+ return err;
+}
+
+/* mixed fields and sort keys */
+static int test2(struct perf_evsel *evsel, struct machine *machine)
+{
+ int err;
+ struct hists *hists = &evsel->hists;
+ struct hist_entry *he;
+ struct rb_root *root;
+ struct rb_node *node;
+
+ field_order = "overhead,cpu";
+ sort_order = "pid";
+
+ setup_sorting();
+
+ /*
+ * expected output:
+ *
+ * Overhead CPU Command: Pid
+ * ======== === =============
+ * 30.00% 1 perf : 100
+ * 10.00% 0 perf : 100
+ * 10.00% 2 perf : 100
+ * 20.00% 2 perf : 200
+ * 10.00% 0 bash : 300
+ * 10.00% 1 bash : 300
+ * 10.00% 3 bash : 300
+ */
+ err = add_hist_entries(hists, machine);
+ if (err < 0)
+ goto out;
+
+ hists__collapse_resort(hists, NULL);
+ hists__output_resort(hists);
+
+ if (verbose > 2) {
+ pr_info("[fields = %s, sort = %s]\n", field_order, sort_order);
+ print_hists_out(hists);
+ }
+
+ root = &evsel->hists.entries;
+ node = rb_first(root);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ CPU(he) == 1 && PID(he) == 100 && he->stat.period == 300);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ CPU(he) == 0 && PID(he) == 100 && he->stat.period == 100);
+
+out:
+ del_hist_entries(hists);
+ reset_output_field();
+ return err;
+}
+
+/* fields only (no sort key) */
+static int test3(struct perf_evsel *evsel, struct machine *machine)
+{
+ int err;
+ struct hists *hists = &evsel->hists;
+ struct hist_entry *he;
+ struct rb_root *root;
+ struct rb_node *node;
+
+ field_order = "comm,overhead,dso";
+ sort_order = NULL;
+
+ setup_sorting();
+
+ /*
+ * expected output:
+ *
+ * Command Overhead Shared Object
+ * ======= ======== =============
+ * bash 20.00% bash
+ * bash 10.00% [kernel]
+ * perf 30.00% perf
+ * perf 20.00% [kernel]
+ * perf 20.00% libc
+ */
+ err = add_hist_entries(hists, machine);
+ if (err < 0)
+ goto out;
+
+ hists__collapse_resort(hists, NULL);
+ hists__output_resort(hists);
+
+ if (verbose > 2) {
+ pr_info("[fields = %s, sort = %s]\n", field_order, sort_order);
+ print_hists_out(hists);
+ }
+
+ root = &evsel->hists.entries;
+ node = rb_first(root);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") &&
+ he->stat.period == 200);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") &&
+ he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") &&
+ he->stat.period == 300);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") &&
+ he->stat.period == 200);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") &&
+ he->stat.period == 200);
+
+out:
+ del_hist_entries(hists);
+ reset_output_field();
+ return err;
+}
+
+/* handle duplicate 'dso' field */
+static int test4(struct perf_evsel *evsel, struct machine *machine)
+{
+ int err;
+ struct hists *hists = &evsel->hists;
+ struct hist_entry *he;
+ struct rb_root *root;
+ struct rb_node *node;
+
+ field_order = "dso,sym,comm,overhead,dso";
+ sort_order = "sym";
+
+ setup_sorting();
+
+ /*
+ * expected output:
+ *
+ * Shared Object Symbol Command Overhead
+ * ============= ============== ======= ========
+ * perf [.] cmd_record perf 10.00%
+ * libc [.] free perf 10.00%
+ * bash [.] main bash 10.00%
+ * perf [.] main perf 20.00%
+ * libc [.] malloc perf 10.00%
+ * [kernel] [k] page_fault bash 10.00%
+ * [kernel] [k] page_fault perf 10.00%
+ * [kernel] [k] schedule perf 10.00%
+ * bash [.] xmalloc bash 10.00%
+ */
+ err = add_hist_entries(hists, machine);
+ if (err < 0)
+ goto out;
+
+ hists__collapse_resort(hists, NULL);
+ hists__output_resort(hists);
+
+ if (verbose > 2) {
+ pr_info("[fields = %s, sort = %s]\n", field_order, sort_order);
+ print_hists_out(hists);
+ }
+
+ root = &evsel->hists.entries;
+ node = rb_first(root);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(DSO(he), "perf") && !strcmp(SYM(he), "cmd_record") &&
+ !strcmp(COMM(he), "perf") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(DSO(he), "libc") && !strcmp(SYM(he), "free") &&
+ !strcmp(COMM(he), "perf") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(DSO(he), "bash") && !strcmp(SYM(he), "main") &&
+ !strcmp(COMM(he), "bash") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(DSO(he), "perf") && !strcmp(SYM(he), "main") &&
+ !strcmp(COMM(he), "perf") && he->stat.period == 200);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(DSO(he), "libc") && !strcmp(SYM(he), "malloc") &&
+ !strcmp(COMM(he), "perf") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "page_fault") &&
+ !strcmp(COMM(he), "bash") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "page_fault") &&
+ !strcmp(COMM(he), "perf") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(DSO(he), "[kernel]") && !strcmp(SYM(he), "schedule") &&
+ !strcmp(COMM(he), "perf") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ !strcmp(DSO(he), "bash") && !strcmp(SYM(he), "xmalloc") &&
+ !strcmp(COMM(he), "bash") && he->stat.period == 100);
+
+out:
+ del_hist_entries(hists);
+ reset_output_field();
+ return err;
+}
+
+/* full sort keys w/o overhead field */
+static int test5(struct perf_evsel *evsel, struct machine *machine)
+{
+ int err;
+ struct hists *hists = &evsel->hists;
+ struct hist_entry *he;
+ struct rb_root *root;
+ struct rb_node *node;
+
+ field_order = "cpu,pid,comm,dso,sym";
+ sort_order = "dso,pid";
+
+ setup_sorting();
+
+ /*
+ * expected output:
+ *
+ * CPU Command: Pid Command Shared Object Symbol
+ * === ============= ======= ============= ==============
+ * 0 perf: 100 perf [kernel] [k] schedule
+ * 2 perf: 200 perf [kernel] [k] page_fault
+ * 1 bash: 300 bash [kernel] [k] page_fault
+ * 0 bash: 300 bash bash [.] xmalloc
+ * 3 bash: 300 bash bash [.] main
+ * 1 perf: 100 perf libc [.] malloc
+ * 2 perf: 100 perf libc [.] free
+ * 1 perf: 100 perf perf [.] cmd_record
+ * 1 perf: 100 perf perf [.] main
+ * 2 perf: 200 perf perf [.] main
+ */
+ err = add_hist_entries(hists, machine);
+ if (err < 0)
+ goto out;
+
+ hists__collapse_resort(hists, NULL);
+ hists__output_resort(hists);
+
+ if (verbose > 2) {
+ pr_info("[fields = %s, sort = %s]\n", field_order, sort_order);
+ print_hists_out(hists);
+ }
+
+ root = &evsel->hists.entries;
+ node = rb_first(root);
+ he = rb_entry(node, struct hist_entry, rb_node);
+
+ TEST_ASSERT_VAL("Invalid hist entry",
+ CPU(he) == 0 && PID(he) == 100 &&
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") &&
+ !strcmp(SYM(he), "schedule") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ CPU(he) == 2 && PID(he) == 200 &&
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "[kernel]") &&
+ !strcmp(SYM(he), "page_fault") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ CPU(he) == 1 && PID(he) == 300 &&
+ !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "[kernel]") &&
+ !strcmp(SYM(he), "page_fault") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ CPU(he) == 0 && PID(he) == 300 &&
+ !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") &&
+ !strcmp(SYM(he), "xmalloc") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ CPU(he) == 3 && PID(he) == 300 &&
+ !strcmp(COMM(he), "bash") && !strcmp(DSO(he), "bash") &&
+ !strcmp(SYM(he), "main") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ CPU(he) == 1 && PID(he) == 100 &&
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") &&
+ !strcmp(SYM(he), "malloc") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ CPU(he) == 2 && PID(he) == 100 &&
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "libc") &&
+ !strcmp(SYM(he), "free") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ CPU(he) == 1 && PID(he) == 100 &&
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") &&
+ !strcmp(SYM(he), "cmd_record") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ CPU(he) == 1 && PID(he) == 100 &&
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") &&
+ !strcmp(SYM(he), "main") && he->stat.period == 100);
+
+ node = rb_next(node);
+ he = rb_entry(node, struct hist_entry, rb_node);
+ TEST_ASSERT_VAL("Invalid hist entry",
+ CPU(he) == 2 && PID(he) == 200 &&
+ !strcmp(COMM(he), "perf") && !strcmp(DSO(he), "perf") &&
+ !strcmp(SYM(he), "main") && he->stat.period == 100);
+
+out:
+ del_hist_entries(hists);
+ reset_output_field();
+ return err;
+}
+
+int test__hists_output(void)
+{
+ int err = TEST_FAIL;
+ struct machines machines;
+ struct machine *machine;
+ struct perf_evsel *evsel;
+ struct perf_evlist *evlist = perf_evlist__new();
+ size_t i;
+ test_fn_t testcases[] = {
+ test1,
+ test2,
+ test3,
+ test4,
+ test5,
+ };
+
+ TEST_ASSERT_VAL("No memory", evlist);
+
+ err = parse_events(evlist, "cpu-clock");
+ if (err)
+ goto out;
+
+ machines__init(&machines);
+
+ /* setup threads/dso/map/symbols also */
+ machine = setup_fake_machine(&machines);
+ if (!machine)
+ goto out;
+
+ if (verbose > 1)
+ machine__fprintf(machine, stderr);
+
+ evsel = perf_evlist__first(evlist);
+
+ for (i = 0; i < ARRAY_SIZE(testcases); i++) {
+ err = testcases[i](evsel, machine);
+ if (err < 0)
+ break;
+ }
+
+out:
+ /* tear down everything */
+ perf_evlist__delete(evlist);
+ machines__exit(&machines);
+
+ return err;
+}
diff --git a/tools/perf/tests/keep-tracking.c b/tools/perf/tests/keep-tracking.c
new file mode 100644
index 00000000000..7a5ab7b0b8f
--- /dev/null
+++ b/tools/perf/tests/keep-tracking.c
@@ -0,0 +1,152 @@
+#include <linux/types.h>
+#include <unistd.h>
+#include <sys/prctl.h>
+
+#include "parse-events.h"
+#include "evlist.h"
+#include "evsel.h"
+#include "thread_map.h"
+#include "cpumap.h"
+#include "tests.h"
+
+#define CHECK__(x) { \
+ while ((x) < 0) { \
+ pr_debug(#x " failed!\n"); \
+ goto out_err; \
+ } \
+}
+
+#define CHECK_NOT_NULL__(x) { \
+ while ((x) == NULL) { \
+ pr_debug(#x " failed!\n"); \
+ goto out_err; \
+ } \
+}
+
+static int find_comm(struct perf_evlist *evlist, const char *comm)
+{
+ union perf_event *event;
+ int i, found;
+
+ found = 0;
+ for (i = 0; i < evlist->nr_mmaps; i++) {
+ while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) {
+ if (event->header.type == PERF_RECORD_COMM &&
+ (pid_t)event->comm.pid == getpid() &&
+ (pid_t)event->comm.tid == getpid() &&
+ strcmp(event->comm.comm, comm) == 0)
+ found += 1;
+ perf_evlist__mmap_consume(evlist, i);
+ }
+ }
+ return found;
+}
+
+/**
+ * test__keep_tracking - test using a dummy software event to keep tracking.
+ *
+ * This function implements a test that checks that tracking events continue
+ * when an event is disabled but a dummy software event is not disabled. If the
+ * test passes %0 is returned, otherwise %-1 is returned.
+ */
+int test__keep_tracking(void)
+{
+ struct record_opts opts = {
+ .mmap_pages = UINT_MAX,
+ .user_freq = UINT_MAX,
+ .user_interval = ULLONG_MAX,
+ .freq = 4000,
+ .target = {
+ .uses_mmap = true,
+ },
+ };
+ struct thread_map *threads = NULL;
+ struct cpu_map *cpus = NULL;
+ struct perf_evlist *evlist = NULL;
+ struct perf_evsel *evsel = NULL;
+ int found, err = -1;
+ const char *comm;
+
+ threads = thread_map__new(-1, getpid(), UINT_MAX);
+ CHECK_NOT_NULL__(threads);
+
+ cpus = cpu_map__new(NULL);
+ CHECK_NOT_NULL__(cpus);
+
+ evlist = perf_evlist__new();
+ CHECK_NOT_NULL__(evlist);
+
+ perf_evlist__set_maps(evlist, cpus, threads);
+
+ CHECK__(parse_events(evlist, "dummy:u"));
+ CHECK__(parse_events(evlist, "cycles:u"));
+
+ perf_evlist__config(evlist, &opts);
+
+ evsel = perf_evlist__first(evlist);
+
+ evsel->attr.comm = 1;
+ evsel->attr.disabled = 1;
+ evsel->attr.enable_on_exec = 0;
+
+ if (perf_evlist__open(evlist) < 0) {
+ fprintf(stderr, " (not supported)");
+ err = 0;
+ goto out_err;
+ }
+
+ CHECK__(perf_evlist__mmap(evlist, UINT_MAX, false));
+
+ /*
+ * First, test that a 'comm' event can be found when the event is
+ * enabled.
+ */
+
+ perf_evlist__enable(evlist);
+
+ comm = "Test COMM 1";
+ CHECK__(prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0));
+
+ perf_evlist__disable(evlist);
+
+ found = find_comm(evlist, comm);
+ if (found != 1) {
+ pr_debug("First time, failed to find tracking event.\n");
+ goto out_err;
+ }
+
+ /*
+ * Secondly, test that a 'comm' event can be found when the event is
+ * disabled with the dummy event still enabled.
+ */
+
+ perf_evlist__enable(evlist);
+
+ evsel = perf_evlist__last(evlist);
+
+ CHECK__(perf_evlist__disable_event(evlist, evsel));
+
+ comm = "Test COMM 2";
+ CHECK__(prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0));
+
+ perf_evlist__disable(evlist);
+
+ found = find_comm(evlist, comm);
+ if (found != 1) {
+ pr_debug("Seconf time, failed to find tracking event.\n");
+ goto out_err;
+ }
+
+ err = 0;
+
+out_err:
+ if (evlist) {
+ perf_evlist__disable(evlist);
+ perf_evlist__delete(evlist);
+ } else {
+ cpu_map__delete(cpus);
+ thread_map__delete(threads);
+ }
+
+ return err;
+}
diff --git a/tools/perf/tests/make b/tools/perf/tests/make
new file mode 100644
index 00000000000..69a71ff84e0
--- /dev/null
+++ b/tools/perf/tests/make
@@ -0,0 +1,233 @@
+PERF := .
+MK := Makefile
+
+include config/Makefile.arch
+
+# FIXME looks like x86 is the only arch running tests ;-)
+# we need some IS_(32/64) flag to make this generic
+ifeq ($(IS_X86_64),1)
+lib = lib64
+else
+lib = lib
+endif
+
+has = $(shell which $1 2>/dev/null)
+
+# standard single make variable specified
+make_clean_all := clean all
+make_python_perf_so := python/perf.so
+make_debug := DEBUG=1
+make_no_libperl := NO_LIBPERL=1
+make_no_libpython := NO_LIBPYTHON=1
+make_no_scripts := NO_LIBPYTHON=1 NO_LIBPERL=1
+make_no_newt := NO_NEWT=1
+make_no_slang := NO_SLANG=1
+make_no_gtk2 := NO_GTK2=1
+make_no_ui := NO_NEWT=1 NO_SLANG=1 NO_GTK2=1
+make_no_demangle := NO_DEMANGLE=1
+make_no_libelf := NO_LIBELF=1
+make_no_libunwind := NO_LIBUNWIND=1
+make_no_libdw_dwarf_unwind := NO_LIBDW_DWARF_UNWIND=1
+make_no_backtrace := NO_BACKTRACE=1
+make_no_libnuma := NO_LIBNUMA=1
+make_no_libaudit := NO_LIBAUDIT=1
+make_no_libbionic := NO_LIBBIONIC=1
+make_tags := tags
+make_cscope := cscope
+make_help := help
+make_doc := doc
+make_perf_o := perf.o
+make_util_map_o := util/map.o
+make_util_pmu_bison_o := util/pmu-bison.o
+make_install := install
+make_install_bin := install-bin
+make_install_doc := install-doc
+make_install_man := install-man
+make_install_html := install-html
+make_install_info := install-info
+make_install_pdf := install-pdf
+make_static := LDFLAGS=-static
+
+# all the NO_* variable combined
+make_minimal := NO_LIBPERL=1 NO_LIBPYTHON=1 NO_NEWT=1 NO_GTK2=1
+make_minimal += NO_DEMANGLE=1 NO_LIBELF=1 NO_LIBUNWIND=1 NO_BACKTRACE=1
+make_minimal += NO_LIBNUMA=1 NO_LIBAUDIT=1 NO_LIBBIONIC=1
+make_minimal += NO_LIBDW_DWARF_UNWIND=1
+
+# $(run) contains all available tests
+run := make_pure
+run += make_clean_all
+run += make_python_perf_so
+run += make_debug
+run += make_no_libperl
+run += make_no_libpython
+run += make_no_scripts
+run += make_no_newt
+run += make_no_slang
+run += make_no_gtk2
+run += make_no_ui
+run += make_no_demangle
+run += make_no_libelf
+run += make_no_libunwind
+run += make_no_libdw_dwarf_unwind
+run += make_no_backtrace
+run += make_no_libnuma
+run += make_no_libaudit
+run += make_no_libbionic
+run += make_help
+run += make_doc
+run += make_perf_o
+run += make_util_map_o
+run += make_util_pmu_bison_o
+run += make_install
+run += make_install_bin
+# FIXME 'install-*' commented out till they're fixed
+# run += make_install_doc
+# run += make_install_man
+# run += make_install_html
+# run += make_install_info
+# run += make_install_pdf
+run += make_minimal
+run += make_static
+
+ifneq ($(call has,ctags),)
+run += make_tags
+endif
+ifneq ($(call has,cscope),)
+run += make_cscope
+endif
+
+# $(run_O) contains same portion of $(run) tests with '_O' attached
+# to distinguish O=... tests
+run_O := $(addsuffix _O,$(run))
+
+# disable some tests for O=...
+run_O := $(filter-out make_python_perf_so_O,$(run_O))
+
+# define test for each compile as 'test_NAME' variable
+# with the test itself as a value
+test_make_tags = test -f tags
+test_make_cscope = test -f cscope.out
+
+test_make_tags_O := $(test_make_tags)
+test_make_cscope_O := $(test_make_cscope)
+
+test_ok := true
+test_make_help := $(test_ok)
+test_make_doc := $(test_ok)
+test_make_help_O := $(test_ok)
+test_make_doc_O := $(test_ok)
+
+test_make_python_perf_so := test -f $(PERF)/python/perf.so
+
+test_make_perf_o := test -f $(PERF)/perf.o
+test_make_util_map_o := test -f $(PERF)/util/map.o
+test_make_util_pmu_bison_o := test -f $(PERF)/util/pmu-bison.o
+
+define test_dest_files
+ for file in $(1); do \
+ if [ ! -x $$TMP_DEST/$$file ]; then \
+ echo " failed to find: $$file"; \
+ fi \
+ done
+endef
+
+installed_files_bin := bin/perf
+installed_files_bin += etc/bash_completion.d/perf
+installed_files_bin += libexec/perf-core/perf-archive
+
+installed_files_plugins := $(lib)/traceevent/plugins/plugin_cfg80211.so
+installed_files_plugins += $(lib)/traceevent/plugins/plugin_scsi.so
+installed_files_plugins += $(lib)/traceevent/plugins/plugin_xen.so
+installed_files_plugins += $(lib)/traceevent/plugins/plugin_function.so
+installed_files_plugins += $(lib)/traceevent/plugins/plugin_sched_switch.so
+installed_files_plugins += $(lib)/traceevent/plugins/plugin_mac80211.so
+installed_files_plugins += $(lib)/traceevent/plugins/plugin_kvm.so
+installed_files_plugins += $(lib)/traceevent/plugins/plugin_kmem.so
+installed_files_plugins += $(lib)/traceevent/plugins/plugin_hrtimer.so
+installed_files_plugins += $(lib)/traceevent/plugins/plugin_jbd2.so
+
+installed_files_all := $(installed_files_bin)
+installed_files_all += $(installed_files_plugins)
+
+test_make_install := $(call test_dest_files,$(installed_files_all))
+test_make_install_O := $(call test_dest_files,$(installed_files_all))
+test_make_install_bin := $(call test_dest_files,$(installed_files_bin))
+test_make_install_bin_O := $(call test_dest_files,$(installed_files_bin))
+
+# FIXME nothing gets installed
+test_make_install_man := test -f $$TMP_DEST/share/man/man1/perf.1
+test_make_install_man_O := $(test_make_install_man)
+
+# FIXME nothing gets installed
+test_make_install_doc := $(test_ok)
+test_make_install_doc_O := $(test_ok)
+
+# FIXME nothing gets installed
+test_make_install_html := $(test_ok)
+test_make_install_html_O := $(test_ok)
+
+# FIXME nothing gets installed
+test_make_install_info := $(test_ok)
+test_make_install_info_O := $(test_ok)
+
+# FIXME nothing gets installed
+test_make_install_pdf := $(test_ok)
+test_make_install_pdf_O := $(test_ok)
+
+test_make_python_perf_so_O := test -f $$TMP_O/python/perf.so
+test_make_perf_o_O := test -f $$TMP_O/perf.o
+test_make_util_map_o_O := test -f $$TMP_O/util/map.o
+test_make_util_pmu_bison_o_O := test -f $$TMP_O/util/pmu-bison.o
+
+test_default = test -x $(PERF)/perf
+test = $(if $(test_$1),$(test_$1),$(test_default))
+
+test_default_O = test -x $$TMP_O/perf
+test_O = $(if $(test_$1),$(test_$1),$(test_default_O))
+
+all:
+
+ifdef DEBUG
+d := $(info run $(run))
+d := $(info run_O $(run_O))
+endif
+
+MAKEFLAGS := --no-print-directory
+
+clean := @(cd $(PERF); make -s -f $(MK) clean >/dev/null)
+
+$(run):
+ $(call clean)
+ @TMP_DEST=$$(mktemp -d); \
+ cmd="cd $(PERF) && make -f $(MK) DESTDIR=$$TMP_DEST $($@)"; \
+ echo "- $@: $$cmd" && echo $$cmd > $@ && \
+ ( eval $$cmd ) >> $@ 2>&1; \
+ echo " test: $(call test,$@)" >> $@ 2>&1; \
+ $(call test,$@) && \
+ rm -rf $@ $$TMP_DEST || (cat $@ ; false)
+
+$(run_O):
+ $(call clean)
+ @TMP_O=$$(mktemp -d); \
+ TMP_DEST=$$(mktemp -d); \
+ cmd="cd $(PERF) && make -f $(MK) O=$$TMP_O DESTDIR=$$TMP_DEST $($(patsubst %_O,%,$@))"; \
+ echo "- $@: $$cmd" && echo $$cmd > $@ && \
+ ( eval $$cmd ) >> $@ 2>&1 && \
+ echo " test: $(call test_O,$@)" >> $@ 2>&1; \
+ $(call test_O,$@) && \
+ rm -rf $@ $$TMP_O $$TMP_DEST || (cat $@ ; false)
+
+tarpkg:
+ @cmd="$(PERF)/tests/perf-targz-src-pkg $(PERF)"; \
+ echo "- $@: $$cmd" && echo $$cmd > $@ && \
+ ( eval $$cmd ) >> $@ 2>&1
+
+
+all: $(run) $(run_O) tarpkg
+ @echo OK
+
+out: $(run_O)
+ @echo OK
+
+.PHONY: all $(run) $(run_O) tarpkg clean
diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c
new file mode 100644
index 00000000000..142263492f6
--- /dev/null
+++ b/tools/perf/tests/mmap-basic.c
@@ -0,0 +1,148 @@
+#include "evlist.h"
+#include "evsel.h"
+#include "thread_map.h"
+#include "cpumap.h"
+#include "tests.h"
+
+/*
+ * This test will generate random numbers of calls to some getpid syscalls,
+ * then establish an mmap for a group of events that are created to monitor
+ * the syscalls.
+ *
+ * It will receive the events, using mmap, use its PERF_SAMPLE_ID generated
+ * sample.id field to map back to its respective perf_evsel instance.
+ *
+ * Then it checks if the number of syscalls reported as perf events by
+ * the kernel corresponds to the number of syscalls made.
+ */
+int test__basic_mmap(void)
+{
+ int err = -1;
+ union perf_event *event;
+ struct thread_map *threads;
+ struct cpu_map *cpus;
+ struct perf_evlist *evlist;
+ cpu_set_t cpu_set;
+ const char *syscall_names[] = { "getsid", "getppid", "getpgrp",
+ "getpgid", };
+ pid_t (*syscalls[])(void) = { (void *)getsid, getppid, getpgrp,
+ (void*)getpgid };
+#define nsyscalls ARRAY_SIZE(syscall_names)
+ unsigned int nr_events[nsyscalls],
+ expected_nr_events[nsyscalls], i, j;
+ struct perf_evsel *evsels[nsyscalls], *evsel;
+
+ threads = thread_map__new(-1, getpid(), UINT_MAX);
+ if (threads == NULL) {
+ pr_debug("thread_map__new\n");
+ return -1;
+ }
+
+ cpus = cpu_map__new(NULL);
+ if (cpus == NULL) {
+ pr_debug("cpu_map__new\n");
+ goto out_free_threads;
+ }
+
+ CPU_ZERO(&cpu_set);
+ CPU_SET(cpus->map[0], &cpu_set);
+ sched_setaffinity(0, sizeof(cpu_set), &cpu_set);
+ if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) {
+ pr_debug("sched_setaffinity() failed on CPU %d: %s ",
+ cpus->map[0], strerror(errno));
+ goto out_free_cpus;
+ }
+
+ evlist = perf_evlist__new();
+ if (evlist == NULL) {
+ pr_debug("perf_evlist__new\n");
+ goto out_free_cpus;
+ }
+
+ perf_evlist__set_maps(evlist, cpus, threads);
+
+ for (i = 0; i < nsyscalls; ++i) {
+ char name[64];
+
+ snprintf(name, sizeof(name), "sys_enter_%s", syscall_names[i]);
+ evsels[i] = perf_evsel__newtp("syscalls", name);
+ if (evsels[i] == NULL) {
+ pr_debug("perf_evsel__new\n");
+ goto out_delete_evlist;
+ }
+
+ evsels[i]->attr.wakeup_events = 1;
+ perf_evsel__set_sample_id(evsels[i], false);
+
+ perf_evlist__add(evlist, evsels[i]);
+
+ if (perf_evsel__open(evsels[i], cpus, threads) < 0) {
+ pr_debug("failed to open counter: %s, "
+ "tweak /proc/sys/kernel/perf_event_paranoid?\n",
+ strerror(errno));
+ goto out_delete_evlist;
+ }
+
+ nr_events[i] = 0;
+ expected_nr_events[i] = 1 + rand() % 127;
+ }
+
+ if (perf_evlist__mmap(evlist, 128, true) < 0) {
+ pr_debug("failed to mmap events: %d (%s)\n", errno,
+ strerror(errno));
+ goto out_delete_evlist;
+ }
+
+ for (i = 0; i < nsyscalls; ++i)
+ for (j = 0; j < expected_nr_events[i]; ++j) {
+ int foo = syscalls[i]();
+ ++foo;
+ }
+
+ while ((event = perf_evlist__mmap_read(evlist, 0)) != NULL) {
+ struct perf_sample sample;
+
+ if (event->header.type != PERF_RECORD_SAMPLE) {
+ pr_debug("unexpected %s event\n",
+ perf_event__name(event->header.type));
+ goto out_delete_evlist;
+ }
+
+ err = perf_evlist__parse_sample(evlist, event, &sample);
+ if (err) {
+ pr_err("Can't parse sample, err = %d\n", err);
+ goto out_delete_evlist;
+ }
+
+ err = -1;
+ evsel = perf_evlist__id2evsel(evlist, sample.id);
+ if (evsel == NULL) {
+ pr_debug("event with id %" PRIu64
+ " doesn't map to an evsel\n", sample.id);
+ goto out_delete_evlist;
+ }
+ nr_events[evsel->idx]++;
+ perf_evlist__mmap_consume(evlist, 0);
+ }
+
+ err = 0;
+ evlist__for_each(evlist, evsel) {
+ if (nr_events[evsel->idx] != expected_nr_events[evsel->idx]) {
+ pr_debug("expected %d %s events, got %d\n",
+ expected_nr_events[evsel->idx],
+ perf_evsel__name(evsel), nr_events[evsel->idx]);
+ err = -1;
+ goto out_delete_evlist;
+ }
+ }
+
+out_delete_evlist:
+ perf_evlist__delete(evlist);
+ cpus = NULL;
+ threads = NULL;
+out_free_cpus:
+ cpu_map__delete(cpus);
+out_free_threads:
+ thread_map__delete(threads);
+ return err;
+}
diff --git a/tools/perf/tests/mmap-thread-lookup.c b/tools/perf/tests/mmap-thread-lookup.c
new file mode 100644
index 00000000000..4a456fef66c
--- /dev/null
+++ b/tools/perf/tests/mmap-thread-lookup.c
@@ -0,0 +1,233 @@
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include "debug.h"
+#include "tests.h"
+#include "machine.h"
+#include "thread_map.h"
+#include "symbol.h"
+#include "thread.h"
+
+#define THREADS 4
+
+static int go_away;
+
+struct thread_data {
+ pthread_t pt;
+ pid_t tid;
+ void *map;
+ int ready[2];
+};
+
+static struct thread_data threads[THREADS];
+
+static int thread_init(struct thread_data *td)
+{
+ void *map;
+
+ map = mmap(NULL, page_size,
+ PROT_READ|PROT_WRITE|PROT_EXEC,
+ MAP_SHARED|MAP_ANONYMOUS, -1, 0);
+
+ if (map == MAP_FAILED) {
+ perror("mmap failed");
+ return -1;
+ }
+
+ td->map = map;
+ td->tid = syscall(SYS_gettid);
+
+ pr_debug("tid = %d, map = %p\n", td->tid, map);
+ return 0;
+}
+
+static void *thread_fn(void *arg)
+{
+ struct thread_data *td = arg;
+ ssize_t ret;
+ int go;
+
+ if (thread_init(td))
+ return NULL;
+
+ /* Signal thread_create thread is initialized. */
+ ret = write(td->ready[1], &go, sizeof(int));
+ if (ret != sizeof(int)) {
+ pr_err("failed to notify\n");
+ return NULL;
+ }
+
+ while (!go_away) {
+ /* Waiting for main thread to kill us. */
+ usleep(100);
+ }
+
+ munmap(td->map, page_size);
+ return NULL;
+}
+
+static int thread_create(int i)
+{
+ struct thread_data *td = &threads[i];
+ int err, go;
+
+ if (pipe(td->ready))
+ return -1;
+
+ err = pthread_create(&td->pt, NULL, thread_fn, td);
+ if (!err) {
+ /* Wait for thread initialization. */
+ ssize_t ret = read(td->ready[0], &go, sizeof(int));
+ err = ret != sizeof(int);
+ }
+
+ close(td->ready[0]);
+ close(td->ready[1]);
+ return err;
+}
+
+static int threads_create(void)
+{
+ struct thread_data *td0 = &threads[0];
+ int i, err = 0;
+
+ go_away = 0;
+
+ /* 0 is main thread */
+ if (thread_init(td0))
+ return -1;
+
+ for (i = 1; !err && i < THREADS; i++)
+ err = thread_create(i);
+
+ return err;
+}
+
+static int threads_destroy(void)
+{
+ struct thread_data *td0 = &threads[0];
+ int i, err = 0;
+
+ /* cleanup the main thread */
+ munmap(td0->map, page_size);
+
+ go_away = 1;
+
+ for (i = 1; !err && i < THREADS; i++)
+ err = pthread_join(threads[i].pt, NULL);
+
+ return err;
+}
+
+typedef int (*synth_cb)(struct machine *machine);
+
+static int synth_all(struct machine *machine)
+{
+ return perf_event__synthesize_threads(NULL,
+ perf_event__process,
+ machine, 0);
+}
+
+static int synth_process(struct machine *machine)
+{
+ struct thread_map *map;
+ int err;
+
+ map = thread_map__new_by_pid(getpid());
+
+ err = perf_event__synthesize_thread_map(NULL, map,
+ perf_event__process,
+ machine, 0);
+
+ thread_map__delete(map);
+ return err;
+}
+
+static int mmap_events(synth_cb synth)
+{
+ struct machines machines;
+ struct machine *machine;
+ int err, i;
+
+ /*
+ * The threads_create will not return before all threads
+ * are spawned and all created memory map.
+ *
+ * They will loop until threads_destroy is called, so we
+ * can safely run synthesizing function.
+ */
+ TEST_ASSERT_VAL("failed to create threads", !threads_create());
+
+ machines__init(&machines);
+ machine = &machines.host;
+
+ dump_trace = verbose > 1 ? 1 : 0;
+
+ err = synth(machine);
+
+ dump_trace = 0;
+
+ TEST_ASSERT_VAL("failed to destroy threads", !threads_destroy());
+ TEST_ASSERT_VAL("failed to synthesize maps", !err);
+
+ /*
+ * All data is synthesized, try to find map for each
+ * thread object.
+ */
+ for (i = 0; i < THREADS; i++) {
+ struct thread_data *td = &threads[i];
+ struct addr_location al;
+ struct thread *thread;
+
+ thread = machine__findnew_thread(machine, getpid(), td->tid);
+
+ pr_debug("looking for map %p\n", td->map);
+
+ thread__find_addr_map(thread, machine,
+ PERF_RECORD_MISC_USER, MAP__FUNCTION,
+ (unsigned long) (td->map + 1), &al);
+
+ if (!al.map) {
+ pr_debug("failed, couldn't find map\n");
+ err = -1;
+ break;
+ }
+
+ pr_debug("map %p, addr %" PRIx64 "\n", al.map, al.map->start);
+ }
+
+ machine__delete_threads(machine);
+ machines__exit(&machines);
+ return err;
+}
+
+/*
+ * This test creates 'THREADS' number of threads (including
+ * main thread) and each thread creates memory map.
+ *
+ * When threads are created, we synthesize them with both
+ * (separate tests):
+ * perf_event__synthesize_thread_map (process based)
+ * perf_event__synthesize_threads (global)
+ *
+ * We test we can find all memory maps via:
+ * thread__find_addr_map
+ *
+ * by using all thread objects.
+ */
+int test__mmap_thread_lookup(void)
+{
+ /* perf_event__synthesize_threads synthesize */
+ TEST_ASSERT_VAL("failed with sythesizing all",
+ !mmap_events(synth_all));
+
+ /* perf_event__synthesize_thread_map synthesize */
+ TEST_ASSERT_VAL("failed with sythesizing process",
+ !mmap_events(synth_process));
+
+ return 0;
+}
diff --git a/tools/perf/tests/open-syscall-all-cpus.c b/tools/perf/tests/open-syscall-all-cpus.c
new file mode 100644
index 00000000000..5fecdbd2f5f
--- /dev/null
+++ b/tools/perf/tests/open-syscall-all-cpus.c
@@ -0,0 +1,109 @@
+#include "evsel.h"
+#include "tests.h"
+#include "thread_map.h"
+#include "cpumap.h"
+#include "debug.h"
+
+int test__open_syscall_event_on_all_cpus(void)
+{
+ int err = -1, fd, cpu;
+ struct cpu_map *cpus;
+ struct perf_evsel *evsel;
+ unsigned int nr_open_calls = 111, i;
+ cpu_set_t cpu_set;
+ struct thread_map *threads = thread_map__new(-1, getpid(), UINT_MAX);
+
+ if (threads == NULL) {
+ pr_debug("thread_map__new\n");
+ return -1;
+ }
+
+ cpus = cpu_map__new(NULL);
+ if (cpus == NULL) {
+ pr_debug("cpu_map__new\n");
+ goto out_thread_map_delete;
+ }
+
+ CPU_ZERO(&cpu_set);
+
+ evsel = perf_evsel__newtp("syscalls", "sys_enter_open");
+ if (evsel == NULL) {
+ pr_debug("is debugfs mounted on /sys/kernel/debug?\n");
+ goto out_thread_map_delete;
+ }
+
+ if (perf_evsel__open(evsel, cpus, threads) < 0) {
+ pr_debug("failed to open counter: %s, "
+ "tweak /proc/sys/kernel/perf_event_paranoid?\n",
+ strerror(errno));
+ goto out_evsel_delete;
+ }
+
+ for (cpu = 0; cpu < cpus->nr; ++cpu) {
+ unsigned int ncalls = nr_open_calls + cpu;
+ /*
+ * XXX eventually lift this restriction in a way that
+ * keeps perf building on older glibc installations
+ * without CPU_ALLOC. 1024 cpus in 2010 still seems
+ * a reasonable upper limit tho :-)
+ */
+ if (cpus->map[cpu] >= CPU_SETSIZE) {
+ pr_debug("Ignoring CPU %d\n", cpus->map[cpu]);
+ continue;
+ }
+
+ CPU_SET(cpus->map[cpu], &cpu_set);
+ if (sched_setaffinity(0, sizeof(cpu_set), &cpu_set) < 0) {
+ pr_debug("sched_setaffinity() failed on CPU %d: %s ",
+ cpus->map[cpu],
+ strerror(errno));
+ goto out_close_fd;
+ }
+ for (i = 0; i < ncalls; ++i) {
+ fd = open("/etc/passwd", O_RDONLY);
+ close(fd);
+ }
+ CPU_CLR(cpus->map[cpu], &cpu_set);
+ }
+
+ /*
+ * Here we need to explicitely preallocate the counts, as if
+ * we use the auto allocation it will allocate just for 1 cpu,
+ * as we start by cpu 0.
+ */
+ if (perf_evsel__alloc_counts(evsel, cpus->nr) < 0) {
+ pr_debug("perf_evsel__alloc_counts(ncpus=%d)\n", cpus->nr);
+ goto out_close_fd;
+ }
+
+ err = 0;
+
+ for (cpu = 0; cpu < cpus->nr; ++cpu) {
+ unsigned int expected;
+
+ if (cpus->map[cpu] >= CPU_SETSIZE)
+ continue;
+
+ if (perf_evsel__read_on_cpu(evsel, cpu, 0) < 0) {
+ pr_debug("perf_evsel__read_on_cpu\n");
+ err = -1;
+ break;
+ }
+
+ expected = nr_open_calls + cpu;
+ if (evsel->counts->cpu[cpu].val != expected) {
+ pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls on cpu %d, got %" PRIu64 "\n",
+ expected, cpus->map[cpu], evsel->counts->cpu[cpu].val);
+ err = -1;
+ }
+ }
+
+ perf_evsel__free_counts(evsel);
+out_close_fd:
+ perf_evsel__close_fd(evsel, 1, threads->nr);
+out_evsel_delete:
+ perf_evsel__delete(evsel);
+out_thread_map_delete:
+ thread_map__delete(threads);
+ return err;
+}
diff --git a/tools/perf/tests/open-syscall-tp-fields.c b/tools/perf/tests/open-syscall-tp-fields.c
new file mode 100644
index 00000000000..c505ef2af24
--- /dev/null
+++ b/tools/perf/tests/open-syscall-tp-fields.c
@@ -0,0 +1,117 @@
+#include "perf.h"
+#include "evlist.h"
+#include "evsel.h"
+#include "thread_map.h"
+#include "tests.h"
+
+int test__syscall_open_tp_fields(void)
+{
+ struct record_opts opts = {
+ .target = {
+ .uid = UINT_MAX,
+ .uses_mmap = true,
+ },
+ .no_buffering = true,
+ .freq = 1,
+ .mmap_pages = 256,
+ .raw_samples = true,
+ };
+ const char *filename = "/etc/passwd";
+ int flags = O_RDONLY | O_DIRECTORY;
+ struct perf_evlist *evlist = perf_evlist__new();
+ struct perf_evsel *evsel;
+ int err = -1, i, nr_events = 0, nr_polls = 0;
+
+ if (evlist == NULL) {
+ pr_debug("%s: perf_evlist__new\n", __func__);
+ goto out;
+ }
+
+ evsel = perf_evsel__newtp("syscalls", "sys_enter_open");
+ if (evsel == NULL) {
+ pr_debug("%s: perf_evsel__newtp\n", __func__);
+ goto out_delete_evlist;
+ }
+
+ perf_evlist__add(evlist, evsel);
+
+ err = perf_evlist__create_maps(evlist, &opts.target);
+ if (err < 0) {
+ pr_debug("%s: perf_evlist__create_maps\n", __func__);
+ goto out_delete_evlist;
+ }
+
+ perf_evsel__config(evsel, &opts);
+
+ evlist->threads->map[0] = getpid();
+
+ err = perf_evlist__open(evlist);
+ if (err < 0) {
+ pr_debug("perf_evlist__open: %s\n", strerror(errno));
+ goto out_delete_evlist;
+ }
+
+ err = perf_evlist__mmap(evlist, UINT_MAX, false);
+ if (err < 0) {
+ pr_debug("perf_evlist__mmap: %s\n", strerror(errno));
+ goto out_delete_evlist;
+ }
+
+ perf_evlist__enable(evlist);
+
+ /*
+ * Generate the event:
+ */
+ open(filename, flags);
+
+ while (1) {
+ int before = nr_events;
+
+ for (i = 0; i < evlist->nr_mmaps; i++) {
+ union perf_event *event;
+
+ while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) {
+ const u32 type = event->header.type;
+ int tp_flags;
+ struct perf_sample sample;
+
+ ++nr_events;
+
+ if (type != PERF_RECORD_SAMPLE) {
+ perf_evlist__mmap_consume(evlist, i);
+ continue;
+ }
+
+ err = perf_evsel__parse_sample(evsel, event, &sample);
+ if (err) {
+ pr_err("Can't parse sample, err = %d\n", err);
+ goto out_delete_evlist;
+ }
+
+ tp_flags = perf_evsel__intval(evsel, &sample, "flags");
+
+ if (flags != tp_flags) {
+ pr_debug("%s: Expected flags=%#x, got %#x\n",
+ __func__, flags, tp_flags);
+ goto out_delete_evlist;
+ }
+
+ goto out_ok;
+ }
+ }
+
+ if (nr_events == before)
+ poll(evlist->pollfd, evlist->nr_fds, 10);
+
+ if (++nr_polls > 5) {
+ pr_debug("%s: no events!\n", __func__);
+ goto out_delete_evlist;
+ }
+ }
+out_ok:
+ err = 0;
+out_delete_evlist:
+ perf_evlist__delete(evlist);
+out:
+ return err;
+}
diff --git a/tools/perf/tests/open-syscall.c b/tools/perf/tests/open-syscall.c
new file mode 100644
index 00000000000..c1dc7d25f38
--- /dev/null
+++ b/tools/perf/tests/open-syscall.c
@@ -0,0 +1,55 @@
+#include "thread_map.h"
+#include "evsel.h"
+#include "debug.h"
+#include "tests.h"
+
+int test__open_syscall_event(void)
+{
+ int err = -1, fd;
+ struct perf_evsel *evsel;
+ unsigned int nr_open_calls = 111, i;
+ struct thread_map *threads = thread_map__new(-1, getpid(), UINT_MAX);
+
+ if (threads == NULL) {
+ pr_debug("thread_map__new\n");
+ return -1;
+ }
+
+ evsel = perf_evsel__newtp("syscalls", "sys_enter_open");
+ if (evsel == NULL) {
+ pr_debug("is debugfs mounted on /sys/kernel/debug?\n");
+ goto out_thread_map_delete;
+ }
+
+ if (perf_evsel__open_per_thread(evsel, threads) < 0) {
+ pr_debug("failed to open counter: %s, "
+ "tweak /proc/sys/kernel/perf_event_paranoid?\n",
+ strerror(errno));
+ goto out_evsel_delete;
+ }
+
+ for (i = 0; i < nr_open_calls; ++i) {
+ fd = open("/etc/passwd", O_RDONLY);
+ close(fd);
+ }
+
+ if (perf_evsel__read_on_cpu(evsel, 0, 0) < 0) {
+ pr_debug("perf_evsel__read_on_cpu\n");
+ goto out_close_fd;
+ }
+
+ if (evsel->counts->cpu[0].val != nr_open_calls) {
+ pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls, got %" PRIu64 "\n",
+ nr_open_calls, evsel->counts->cpu[0].val);
+ goto out_close_fd;
+ }
+
+ err = 0;
+out_close_fd:
+ perf_evsel__close_fd(evsel, 1, threads->nr);
+out_evsel_delete:
+ perf_evsel__delete(evsel);
+out_thread_map_delete:
+ thread_map__delete(threads);
+ return err;
+}
diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c
new file mode 100644
index 00000000000..deba66955f8
--- /dev/null
+++ b/tools/perf/tests/parse-events.c
@@ -0,0 +1,1590 @@
+
+#include "parse-events.h"
+#include "evsel.h"
+#include "evlist.h"
+#include <api/fs/fs.h>
+#include <api/fs/debugfs.h>
+#include "tests.h"
+#include <linux/hw_breakpoint.h>
+
+#define PERF_TP_SAMPLE_TYPE (PERF_SAMPLE_RAW | PERF_SAMPLE_TIME | \
+ PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD)
+
+static int test__checkevent_tracepoint(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong number of groups", 0 == evlist->nr_groups);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_TRACEPOINT == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong sample_type",
+ PERF_TP_SAMPLE_TYPE == evsel->attr.sample_type);
+ TEST_ASSERT_VAL("wrong sample_period", 1 == evsel->attr.sample_period);
+ return 0;
+}
+
+static int test__checkevent_tracepoint_multi(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel;
+
+ TEST_ASSERT_VAL("wrong number of entries", evlist->nr_entries > 1);
+ TEST_ASSERT_VAL("wrong number of groups", 0 == evlist->nr_groups);
+
+ evlist__for_each(evlist, evsel) {
+ TEST_ASSERT_VAL("wrong type",
+ PERF_TYPE_TRACEPOINT == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong sample_type",
+ PERF_TP_SAMPLE_TYPE == evsel->attr.sample_type);
+ TEST_ASSERT_VAL("wrong sample_period",
+ 1 == evsel->attr.sample_period);
+ }
+ return 0;
+}
+
+static int test__checkevent_raw(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", 0x1a == evsel->attr.config);
+ return 0;
+}
+
+static int test__checkevent_numeric(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type", 1 == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config);
+ return 0;
+}
+
+static int test__checkevent_symbolic_name(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config);
+ return 0;
+}
+
+static int test__checkevent_symbolic_name_config(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong period",
+ 100000 == evsel->attr.sample_period);
+ TEST_ASSERT_VAL("wrong config1",
+ 0 == evsel->attr.config1);
+ TEST_ASSERT_VAL("wrong config2",
+ 1 == evsel->attr.config2);
+ return 0;
+}
+
+static int test__checkevent_symbolic_alias(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_SOFTWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_SW_PAGE_FAULTS == evsel->attr.config);
+ return 0;
+}
+
+static int test__checkevent_genhw(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HW_CACHE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", (1 << 16) == evsel->attr.config);
+ return 0;
+}
+
+static int test__checkevent_breakpoint(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong bp_type", (HW_BREAKPOINT_R | HW_BREAKPOINT_W) ==
+ evsel->attr.bp_type);
+ TEST_ASSERT_VAL("wrong bp_len", HW_BREAKPOINT_LEN_4 ==
+ evsel->attr.bp_len);
+ return 0;
+}
+
+static int test__checkevent_breakpoint_x(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_BREAKPOINT == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong bp_type",
+ HW_BREAKPOINT_X == evsel->attr.bp_type);
+ TEST_ASSERT_VAL("wrong bp_len", sizeof(long) == evsel->attr.bp_len);
+ return 0;
+}
+
+static int test__checkevent_breakpoint_r(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type",
+ PERF_TYPE_BREAKPOINT == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong bp_type",
+ HW_BREAKPOINT_R == evsel->attr.bp_type);
+ TEST_ASSERT_VAL("wrong bp_len",
+ HW_BREAKPOINT_LEN_4 == evsel->attr.bp_len);
+ return 0;
+}
+
+static int test__checkevent_breakpoint_w(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type",
+ PERF_TYPE_BREAKPOINT == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong bp_type",
+ HW_BREAKPOINT_W == evsel->attr.bp_type);
+ TEST_ASSERT_VAL("wrong bp_len",
+ HW_BREAKPOINT_LEN_4 == evsel->attr.bp_len);
+ return 0;
+}
+
+static int test__checkevent_breakpoint_rw(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type",
+ PERF_TYPE_BREAKPOINT == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", 0 == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong bp_type",
+ (HW_BREAKPOINT_R|HW_BREAKPOINT_W) == evsel->attr.bp_type);
+ TEST_ASSERT_VAL("wrong bp_len",
+ HW_BREAKPOINT_LEN_4 == evsel->attr.bp_len);
+ return 0;
+}
+
+static int test__checkevent_tracepoint_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+
+ return test__checkevent_tracepoint(evlist);
+}
+
+static int
+test__checkevent_tracepoint_multi_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel;
+
+ TEST_ASSERT_VAL("wrong number of entries", evlist->nr_entries > 1);
+
+ evlist__for_each(evlist, evsel) {
+ TEST_ASSERT_VAL("wrong exclude_user",
+ !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel",
+ evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ }
+
+ return test__checkevent_tracepoint_multi(evlist);
+}
+
+static int test__checkevent_raw_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip);
+
+ return test__checkevent_raw(evlist);
+}
+
+static int test__checkevent_numeric_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip);
+
+ return test__checkevent_numeric(evlist);
+}
+
+static int test__checkevent_symbolic_name_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+
+ return test__checkevent_symbolic_name(evlist);
+}
+
+static int test__checkevent_exclude_host_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host);
+
+ return test__checkevent_symbolic_name(evlist);
+}
+
+static int test__checkevent_exclude_guest_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+
+ return test__checkevent_symbolic_name(evlist);
+}
+
+static int test__checkevent_symbolic_alias_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+
+ return test__checkevent_symbolic_alias(evlist);
+}
+
+static int test__checkevent_genhw_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip);
+
+ return test__checkevent_genhw(evlist);
+}
+
+static int test__checkevent_breakpoint_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong name",
+ !strcmp(perf_evsel__name(evsel), "mem:0:u"));
+
+ return test__checkevent_breakpoint(evlist);
+}
+
+static int test__checkevent_breakpoint_x_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong name",
+ !strcmp(perf_evsel__name(evsel), "mem:0:x:k"));
+
+ return test__checkevent_breakpoint_x(evlist);
+}
+
+static int test__checkevent_breakpoint_r_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong name",
+ !strcmp(perf_evsel__name(evsel), "mem:0:r:hp"));
+
+ return test__checkevent_breakpoint_r(evlist);
+}
+
+static int test__checkevent_breakpoint_w_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong name",
+ !strcmp(perf_evsel__name(evsel), "mem:0:w:up"));
+
+ return test__checkevent_breakpoint_w(evlist);
+}
+
+static int test__checkevent_breakpoint_rw_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong name",
+ !strcmp(perf_evsel__name(evsel), "mem:0:rw:kp"));
+
+ return test__checkevent_breakpoint_rw(evlist);
+}
+
+static int test__checkevent_pmu(struct perf_evlist *evlist)
+{
+
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", 10 == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong config1", 1 == evsel->attr.config1);
+ TEST_ASSERT_VAL("wrong config2", 3 == evsel->attr.config2);
+ TEST_ASSERT_VAL("wrong period", 1000 == evsel->attr.sample_period);
+
+ return 0;
+}
+
+static int test__checkevent_list(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->nr_entries);
+
+ /* r1 */
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong config1", 0 == evsel->attr.config1);
+ TEST_ASSERT_VAL("wrong config2", 0 == evsel->attr.config2);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+
+ /* syscalls:sys_enter_open:k */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_TRACEPOINT == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong sample_type",
+ PERF_TP_SAMPLE_TYPE == evsel->attr.sample_type);
+ TEST_ASSERT_VAL("wrong sample_period", 1 == evsel->attr.sample_period);
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+
+ /* 1:1:hp */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", 1 == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip);
+
+ return 0;
+}
+
+static int test__checkevent_pmu_name(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ /* cpu/config=1,name=krava/u */
+ TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", 1 == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong name", !strcmp(perf_evsel__name(evsel), "krava"));
+
+ /* cpu/config=2/u" */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", 2 == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong name",
+ !strcmp(perf_evsel__name(evsel), "cpu/config=2/u"));
+
+ return 0;
+}
+
+static int test__checkevent_pmu_events(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong number of entries", 1 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_RAW == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong exclude_user",
+ !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel",
+ evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong pinned", !evsel->attr.pinned);
+
+ return 0;
+}
+
+static int test__checkterms_simple(struct list_head *terms)
+{
+ struct parse_events_term *term;
+
+ /* config=10 */
+ term = list_entry(terms->next, struct parse_events_term, list);
+ TEST_ASSERT_VAL("wrong type term",
+ term->type_term == PARSE_EVENTS__TERM_TYPE_CONFIG);
+ TEST_ASSERT_VAL("wrong type val",
+ term->type_val == PARSE_EVENTS__TERM_TYPE_NUM);
+ TEST_ASSERT_VAL("wrong val", term->val.num == 10);
+ TEST_ASSERT_VAL("wrong config", !term->config);
+
+ /* config1 */
+ term = list_entry(term->list.next, struct parse_events_term, list);
+ TEST_ASSERT_VAL("wrong type term",
+ term->type_term == PARSE_EVENTS__TERM_TYPE_CONFIG1);
+ TEST_ASSERT_VAL("wrong type val",
+ term->type_val == PARSE_EVENTS__TERM_TYPE_NUM);
+ TEST_ASSERT_VAL("wrong val", term->val.num == 1);
+ TEST_ASSERT_VAL("wrong config", !term->config);
+
+ /* config2=3 */
+ term = list_entry(term->list.next, struct parse_events_term, list);
+ TEST_ASSERT_VAL("wrong type term",
+ term->type_term == PARSE_EVENTS__TERM_TYPE_CONFIG2);
+ TEST_ASSERT_VAL("wrong type val",
+ term->type_val == PARSE_EVENTS__TERM_TYPE_NUM);
+ TEST_ASSERT_VAL("wrong val", term->val.num == 3);
+ TEST_ASSERT_VAL("wrong config", !term->config);
+
+ /* umask=1*/
+ term = list_entry(term->list.next, struct parse_events_term, list);
+ TEST_ASSERT_VAL("wrong type term",
+ term->type_term == PARSE_EVENTS__TERM_TYPE_USER);
+ TEST_ASSERT_VAL("wrong type val",
+ term->type_val == PARSE_EVENTS__TERM_TYPE_NUM);
+ TEST_ASSERT_VAL("wrong val", term->val.num == 1);
+ TEST_ASSERT_VAL("wrong config", !strcmp(term->config, "umask"));
+
+ return 0;
+}
+
+static int test__group1(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel, *leader;
+
+ TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups);
+
+ /* instructions:k */
+ evsel = leader = perf_evlist__first(evlist);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+ TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0);
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ /* cycles:upp */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ /* use of precise requires exclude_guest */
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip == 2);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1);
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ return 0;
+}
+
+static int test__group2(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel, *leader;
+
+ TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups);
+
+ /* faults + :ku modifier */
+ evsel = leader = perf_evlist__first(evlist);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_SOFTWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_SW_PAGE_FAULTS == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+ TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0);
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ /* cache-references + :u modifier */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CACHE_REFERENCES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1);
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ /* cycles:k */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ return 0;
+}
+
+static int test__group3(struct perf_evlist *evlist __maybe_unused)
+{
+ struct perf_evsel *evsel, *leader;
+
+ TEST_ASSERT_VAL("wrong number of entries", 5 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong number of groups", 2 == evlist->nr_groups);
+
+ /* group1 syscalls:sys_enter_open:H */
+ evsel = leader = perf_evlist__first(evlist);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_TRACEPOINT == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong sample_type",
+ PERF_TP_SAMPLE_TYPE == evsel->attr.sample_type);
+ TEST_ASSERT_VAL("wrong sample_period", 1 == evsel->attr.sample_period);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+ TEST_ASSERT_VAL("wrong group name",
+ !strcmp(leader->group_name, "group1"));
+ TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0);
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ /* group1 cycles:kppp */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ /* use of precise requires exclude_guest */
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip == 3);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong group name", !evsel->group_name);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1);
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ /* group2 cycles + G modifier */
+ evsel = leader = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+ TEST_ASSERT_VAL("wrong group name",
+ !strcmp(leader->group_name, "group2"));
+ TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0);
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ /* group2 1:3 + G modifier */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", 1 == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config", 3 == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1);
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ /* instructions:u */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ return 0;
+}
+
+static int test__group4(struct perf_evlist *evlist __maybe_unused)
+{
+ struct perf_evsel *evsel, *leader;
+
+ TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups);
+
+ /* cycles:u + p */
+ evsel = leader = perf_evlist__first(evlist);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ /* use of precise requires exclude_guest */
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip == 1);
+ TEST_ASSERT_VAL("wrong group name", !evsel->group_name);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+ TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0);
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ /* instructions:kp + p */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ /* use of precise requires exclude_guest */
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip == 2);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1);
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ return 0;
+}
+
+static int test__group5(struct perf_evlist *evlist __maybe_unused)
+{
+ struct perf_evsel *evsel, *leader;
+
+ TEST_ASSERT_VAL("wrong number of entries", 5 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong number of groups", 2 == evlist->nr_groups);
+
+ /* cycles + G */
+ evsel = leader = perf_evlist__first(evlist);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong group name", !evsel->group_name);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+ TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0);
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ /* instructions + G */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1);
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ /* cycles:G */
+ evsel = leader = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong group name", !evsel->group_name);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+ TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0);
+ TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read);
+
+ /* instructions:G */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1);
+
+ /* cycles */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+
+ return 0;
+}
+
+static int test__group_gh1(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel, *leader;
+
+ TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups);
+
+ /* cycles + :H group modifier */
+ evsel = leader = perf_evlist__first(evlist);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong group name", !evsel->group_name);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+ TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0);
+
+ /* cache-misses:G + :H group modifier */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1);
+
+ return 0;
+}
+
+static int test__group_gh2(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel, *leader;
+
+ TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups);
+
+ /* cycles + :G group modifier */
+ evsel = leader = perf_evlist__first(evlist);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong group name", !evsel->group_name);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+ TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0);
+
+ /* cache-misses:H + :G group modifier */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1);
+
+ return 0;
+}
+
+static int test__group_gh3(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel, *leader;
+
+ TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups);
+
+ /* cycles:G + :u group modifier */
+ evsel = leader = perf_evlist__first(evlist);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong group name", !evsel->group_name);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+ TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0);
+
+ /* cache-misses:H + :u group modifier */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1);
+
+ return 0;
+}
+
+static int test__group_gh4(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel, *leader;
+
+ TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries);
+ TEST_ASSERT_VAL("wrong number of groups", 1 == evlist->nr_groups);
+
+ /* cycles:G + :uG group modifier */
+ evsel = leader = perf_evlist__first(evlist);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong group name", !evsel->group_name);
+ TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel));
+ TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0);
+
+ /* cache-misses:H + :uG group modifier */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", !evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1);
+
+ return 0;
+}
+
+static int test__leader_sample1(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel, *leader;
+
+ TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->nr_entries);
+
+ /* cycles - sampling group leader */
+ evsel = leader = perf_evlist__first(evlist);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong group name", !evsel->group_name);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read);
+
+ /* cache-misses - not sampling */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read);
+
+ /* branch-misses - not sampling */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_BRANCH_MISSES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong group name", !evsel->group_name);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read);
+
+ return 0;
+}
+
+static int test__leader_sample2(struct perf_evlist *evlist __maybe_unused)
+{
+ struct perf_evsel *evsel, *leader;
+
+ TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries);
+
+ /* instructions - sampling group leader */
+ evsel = leader = perf_evlist__first(evlist);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong group name", !evsel->group_name);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read);
+
+ /* branch-misses - not sampling */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_BRANCH_MISSES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest);
+ TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host);
+ TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong group name", !evsel->group_name);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read);
+
+ return 0;
+}
+
+static int test__checkevent_pinned_modifier(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel = perf_evlist__first(evlist);
+
+ TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user);
+ TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel);
+ TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv);
+ TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip);
+ TEST_ASSERT_VAL("wrong pinned", evsel->attr.pinned);
+
+ return test__checkevent_symbolic_name(evlist);
+}
+
+static int test__pinned_group(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel, *leader;
+
+ TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->nr_entries);
+
+ /* cycles - group leader */
+ evsel = leader = perf_evlist__first(evlist);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong group name", !evsel->group_name);
+ TEST_ASSERT_VAL("wrong leader", evsel->leader == leader);
+ TEST_ASSERT_VAL("wrong pinned", evsel->attr.pinned);
+
+ /* cache-misses - can not be pinned, but will go on with the leader */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong pinned", !evsel->attr.pinned);
+
+ /* branch-misses - ditto */
+ evsel = perf_evsel__next(evsel);
+ TEST_ASSERT_VAL("wrong config",
+ PERF_COUNT_HW_BRANCH_MISSES == evsel->attr.config);
+ TEST_ASSERT_VAL("wrong pinned", !evsel->attr.pinned);
+
+ return 0;
+}
+
+static int count_tracepoints(void)
+{
+ char events_path[PATH_MAX];
+ struct dirent *events_ent;
+ DIR *events_dir;
+ int cnt = 0;
+
+ scnprintf(events_path, PATH_MAX, "%s/tracing/events",
+ debugfs_find_mountpoint());
+
+ events_dir = opendir(events_path);
+
+ TEST_ASSERT_VAL("Can't open events dir", events_dir);
+
+ while ((events_ent = readdir(events_dir))) {
+ char sys_path[PATH_MAX];
+ struct dirent *sys_ent;
+ DIR *sys_dir;
+
+ if (!strcmp(events_ent->d_name, ".")
+ || !strcmp(events_ent->d_name, "..")
+ || !strcmp(events_ent->d_name, "enable")
+ || !strcmp(events_ent->d_name, "header_event")
+ || !strcmp(events_ent->d_name, "header_page"))
+ continue;
+
+ scnprintf(sys_path, PATH_MAX, "%s/%s",
+ events_path, events_ent->d_name);
+
+ sys_dir = opendir(sys_path);
+ TEST_ASSERT_VAL("Can't open sys dir", sys_dir);
+
+ while ((sys_ent = readdir(sys_dir))) {
+ if (!strcmp(sys_ent->d_name, ".")
+ || !strcmp(sys_ent->d_name, "..")
+ || !strcmp(sys_ent->d_name, "enable")
+ || !strcmp(sys_ent->d_name, "filter"))
+ continue;
+
+ cnt++;
+ }
+
+ closedir(sys_dir);
+ }
+
+ closedir(events_dir);
+ return cnt;
+}
+
+static int test__all_tracepoints(struct perf_evlist *evlist)
+{
+ TEST_ASSERT_VAL("wrong events count",
+ count_tracepoints() == evlist->nr_entries);
+
+ return test__checkevent_tracepoint_multi(evlist);
+}
+
+struct evlist_test {
+ const char *name;
+ __u32 type;
+ const int id;
+ int (*check)(struct perf_evlist *evlist);
+};
+
+static struct evlist_test test__events[] = {
+ {
+ .name = "syscalls:sys_enter_open",
+ .check = test__checkevent_tracepoint,
+ .id = 0,
+ },
+ {
+ .name = "syscalls:*",
+ .check = test__checkevent_tracepoint_multi,
+ .id = 1,
+ },
+ {
+ .name = "r1a",
+ .check = test__checkevent_raw,
+ .id = 2,
+ },
+ {
+ .name = "1:1",
+ .check = test__checkevent_numeric,
+ .id = 3,
+ },
+ {
+ .name = "instructions",
+ .check = test__checkevent_symbolic_name,
+ .id = 4,
+ },
+ {
+ .name = "cycles/period=100000,config2/",
+ .check = test__checkevent_symbolic_name_config,
+ .id = 5,
+ },
+ {
+ .name = "faults",
+ .check = test__checkevent_symbolic_alias,
+ .id = 6,
+ },
+ {
+ .name = "L1-dcache-load-miss",
+ .check = test__checkevent_genhw,
+ .id = 7,
+ },
+ {
+ .name = "mem:0",
+ .check = test__checkevent_breakpoint,
+ .id = 8,
+ },
+ {
+ .name = "mem:0:x",
+ .check = test__checkevent_breakpoint_x,
+ .id = 9,
+ },
+ {
+ .name = "mem:0:r",
+ .check = test__checkevent_breakpoint_r,
+ .id = 10,
+ },
+ {
+ .name = "mem:0:w",
+ .check = test__checkevent_breakpoint_w,
+ .id = 11,
+ },
+ {
+ .name = "syscalls:sys_enter_open:k",
+ .check = test__checkevent_tracepoint_modifier,
+ .id = 12,
+ },
+ {
+ .name = "syscalls:*:u",
+ .check = test__checkevent_tracepoint_multi_modifier,
+ .id = 13,
+ },
+ {
+ .name = "r1a:kp",
+ .check = test__checkevent_raw_modifier,
+ .id = 14,
+ },
+ {
+ .name = "1:1:hp",
+ .check = test__checkevent_numeric_modifier,
+ .id = 15,
+ },
+ {
+ .name = "instructions:h",
+ .check = test__checkevent_symbolic_name_modifier,
+ .id = 16,
+ },
+ {
+ .name = "faults:u",
+ .check = test__checkevent_symbolic_alias_modifier,
+ .id = 17,
+ },
+ {
+ .name = "L1-dcache-load-miss:kp",
+ .check = test__checkevent_genhw_modifier,
+ .id = 18,
+ },
+ {
+ .name = "mem:0:u",
+ .check = test__checkevent_breakpoint_modifier,
+ .id = 19,
+ },
+ {
+ .name = "mem:0:x:k",
+ .check = test__checkevent_breakpoint_x_modifier,
+ .id = 20,
+ },
+ {
+ .name = "mem:0:r:hp",
+ .check = test__checkevent_breakpoint_r_modifier,
+ .id = 21,
+ },
+ {
+ .name = "mem:0:w:up",
+ .check = test__checkevent_breakpoint_w_modifier,
+ .id = 22,
+ },
+ {
+ .name = "r1,syscalls:sys_enter_open:k,1:1:hp",
+ .check = test__checkevent_list,
+ .id = 23,
+ },
+ {
+ .name = "instructions:G",
+ .check = test__checkevent_exclude_host_modifier,
+ .id = 24,
+ },
+ {
+ .name = "instructions:H",
+ .check = test__checkevent_exclude_guest_modifier,
+ .id = 25,
+ },
+ {
+ .name = "mem:0:rw",
+ .check = test__checkevent_breakpoint_rw,
+ .id = 26,
+ },
+ {
+ .name = "mem:0:rw:kp",
+ .check = test__checkevent_breakpoint_rw_modifier,
+ .id = 27,
+ },
+ {
+ .name = "{instructions:k,cycles:upp}",
+ .check = test__group1,
+ .id = 28,
+ },
+ {
+ .name = "{faults:k,cache-references}:u,cycles:k",
+ .check = test__group2,
+ .id = 29,
+ },
+ {
+ .name = "group1{syscalls:sys_enter_open:H,cycles:kppp},group2{cycles,1:3}:G,instructions:u",
+ .check = test__group3,
+ .id = 30,
+ },
+ {
+ .name = "{cycles:u,instructions:kp}:p",
+ .check = test__group4,
+ .id = 31,
+ },
+ {
+ .name = "{cycles,instructions}:G,{cycles:G,instructions:G},cycles",
+ .check = test__group5,
+ .id = 32,
+ },
+ {
+ .name = "*:*",
+ .check = test__all_tracepoints,
+ .id = 33,
+ },
+ {
+ .name = "{cycles,cache-misses:G}:H",
+ .check = test__group_gh1,
+ .id = 34,
+ },
+ {
+ .name = "{cycles,cache-misses:H}:G",
+ .check = test__group_gh2,
+ .id = 35,
+ },
+ {
+ .name = "{cycles:G,cache-misses:H}:u",
+ .check = test__group_gh3,
+ .id = 36,
+ },
+ {
+ .name = "{cycles:G,cache-misses:H}:uG",
+ .check = test__group_gh4,
+ .id = 37,
+ },
+ {
+ .name = "{cycles,cache-misses,branch-misses}:S",
+ .check = test__leader_sample1,
+ .id = 38,
+ },
+ {
+ .name = "{instructions,branch-misses}:Su",
+ .check = test__leader_sample2,
+ .id = 39,
+ },
+ {
+ .name = "instructions:uDp",
+ .check = test__checkevent_pinned_modifier,
+ .id = 40,
+ },
+ {
+ .name = "{cycles,cache-misses,branch-misses}:D",
+ .check = test__pinned_group,
+ .id = 41,
+ },
+#if defined(__s390x__)
+ {
+ .name = "kvm-s390:kvm_s390_create_vm",
+ .check = test__checkevent_tracepoint,
+ .id = 100,
+ },
+#endif
+};
+
+static struct evlist_test test__events_pmu[] = {
+ {
+ .name = "cpu/config=10,config1,config2=3,period=1000/u",
+ .check = test__checkevent_pmu,
+ .id = 0,
+ },
+ {
+ .name = "cpu/config=1,name=krava/u,cpu/config=2/u",
+ .check = test__checkevent_pmu_name,
+ .id = 1,
+ },
+};
+
+struct terms_test {
+ const char *str;
+ __u32 type;
+ int (*check)(struct list_head *terms);
+};
+
+static struct terms_test test__terms[] = {
+ [0] = {
+ .str = "config=10,config1,config2=3,umask=1",
+ .check = test__checkterms_simple,
+ },
+};
+
+static int test_event(struct evlist_test *e)
+{
+ struct perf_evlist *evlist;
+ int ret;
+
+ evlist = perf_evlist__new();
+ if (evlist == NULL)
+ return -ENOMEM;
+
+ ret = parse_events(evlist, e->name);
+ if (ret) {
+ pr_debug("failed to parse event '%s', err %d\n",
+ e->name, ret);
+ } else {
+ ret = e->check(evlist);
+ }
+
+ perf_evlist__delete(evlist);
+
+ return ret;
+}
+
+static int test_events(struct evlist_test *events, unsigned cnt)
+{
+ int ret1, ret2 = 0;
+ unsigned i;
+
+ for (i = 0; i < cnt; i++) {
+ struct evlist_test *e = &events[i];
+
+ pr_debug("running test %d '%s'\n", e->id, e->name);
+ ret1 = test_event(e);
+ if (ret1)
+ ret2 = ret1;
+ }
+
+ return ret2;
+}
+
+static int test_term(struct terms_test *t)
+{
+ struct list_head terms;
+ int ret;
+
+ INIT_LIST_HEAD(&terms);
+
+ ret = parse_events_terms(&terms, t->str);
+ if (ret) {
+ pr_debug("failed to parse terms '%s', err %d\n",
+ t->str , ret);
+ return ret;
+ }
+
+ ret = t->check(&terms);
+ parse_events__free_terms(&terms);
+
+ return ret;
+}
+
+static int test_terms(struct terms_test *terms, unsigned cnt)
+{
+ int ret = 0;
+ unsigned i;
+
+ for (i = 0; i < cnt; i++) {
+ struct terms_test *t = &terms[i];
+
+ pr_debug("running test %d '%s'\n", i, t->str);
+ ret = test_term(t);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+static int test_pmu(void)
+{
+ struct stat st;
+ char path[PATH_MAX];
+ int ret;
+
+ snprintf(path, PATH_MAX, "%s/bus/event_source/devices/cpu/format/",
+ sysfs__mountpoint());
+
+ ret = stat(path, &st);
+ if (ret)
+ pr_debug("omitting PMU cpu tests\n");
+ return !ret;
+}
+
+static int test_pmu_events(void)
+{
+ struct stat st;
+ char path[PATH_MAX];
+ struct dirent *ent;
+ DIR *dir;
+ int ret;
+
+ snprintf(path, PATH_MAX, "%s/bus/event_source/devices/cpu/events/",
+ sysfs__mountpoint());
+
+ ret = stat(path, &st);
+ if (ret) {
+ pr_debug("omitting PMU cpu events tests\n");
+ return 0;
+ }
+
+ dir = opendir(path);
+ if (!dir) {
+ pr_debug("can't open pmu event dir");
+ return -1;
+ }
+
+ while (!ret && (ent = readdir(dir))) {
+#define MAX_NAME 100
+ struct evlist_test e;
+ char name[MAX_NAME];
+
+ if (!strcmp(ent->d_name, ".") ||
+ !strcmp(ent->d_name, ".."))
+ continue;
+
+ snprintf(name, MAX_NAME, "cpu/event=%s/u", ent->d_name);
+
+ e.name = name;
+ e.check = test__checkevent_pmu_events;
+
+ ret = test_event(&e);
+#undef MAX_NAME
+ }
+
+ closedir(dir);
+ return ret;
+}
+
+int test__parse_events(void)
+{
+ int ret1, ret2 = 0;
+
+#define TEST_EVENTS(tests) \
+do { \
+ ret1 = test_events(tests, ARRAY_SIZE(tests)); \
+ if (!ret2) \
+ ret2 = ret1; \
+} while (0)
+
+ TEST_EVENTS(test__events);
+
+ if (test_pmu())
+ TEST_EVENTS(test__events_pmu);
+
+ if (test_pmu()) {
+ int ret = test_pmu_events();
+ if (ret)
+ return ret;
+ }
+
+ ret1 = test_terms(test__terms, ARRAY_SIZE(test__terms));
+ if (!ret2)
+ ret2 = ret1;
+
+ return ret2;
+}
diff --git a/tools/perf/tests/parse-no-sample-id-all.c b/tools/perf/tests/parse-no-sample-id-all.c
new file mode 100644
index 00000000000..905019f9b74
--- /dev/null
+++ b/tools/perf/tests/parse-no-sample-id-all.c
@@ -0,0 +1,108 @@
+#include <linux/types.h>
+#include <stddef.h>
+
+#include "tests.h"
+
+#include "event.h"
+#include "evlist.h"
+#include "header.h"
+#include "util.h"
+
+static int process_event(struct perf_evlist **pevlist, union perf_event *event)
+{
+ struct perf_sample sample;
+
+ if (event->header.type == PERF_RECORD_HEADER_ATTR) {
+ if (perf_event__process_attr(NULL, event, pevlist)) {
+ pr_debug("perf_event__process_attr failed\n");
+ return -1;
+ }
+ return 0;
+ }
+
+ if (event->header.type >= PERF_RECORD_USER_TYPE_START)
+ return -1;
+
+ if (!*pevlist)
+ return -1;
+
+ if (perf_evlist__parse_sample(*pevlist, event, &sample)) {
+ pr_debug("perf_evlist__parse_sample failed\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static int process_events(union perf_event **events, size_t count)
+{
+ struct perf_evlist *evlist = NULL;
+ int err = 0;
+ size_t i;
+
+ for (i = 0; i < count && !err; i++)
+ err = process_event(&evlist, events[i]);
+
+ if (evlist)
+ perf_evlist__delete(evlist);
+
+ return err;
+}
+
+struct test_attr_event {
+ struct attr_event attr;
+ u64 id;
+};
+
+/**
+ * test__parse_no_sample_id_all - test parsing with no sample_id_all bit set.
+ *
+ * This function tests parsing data produced on kernel's that do not support the
+ * sample_id_all bit. Without the sample_id_all bit, non-sample events (such as
+ * mmap events) do not have an id sample appended, and consequently logic
+ * designed to determine the id will not work. That case happens when there is
+ * more than one selected event, so this test processes three events: 2
+ * attributes representing the selected events and one mmap event.
+ *
+ * Return: %0 on success, %-1 if the test fails.
+ */
+int test__parse_no_sample_id_all(void)
+{
+ int err;
+
+ struct test_attr_event event1 = {
+ .attr = {
+ .header = {
+ .type = PERF_RECORD_HEADER_ATTR,
+ .size = sizeof(struct test_attr_event),
+ },
+ },
+ .id = 1,
+ };
+ struct test_attr_event event2 = {
+ .attr = {
+ .header = {
+ .type = PERF_RECORD_HEADER_ATTR,
+ .size = sizeof(struct test_attr_event),
+ },
+ },
+ .id = 2,
+ };
+ struct mmap_event event3 = {
+ .header = {
+ .type = PERF_RECORD_MMAP,
+ .size = sizeof(struct mmap_event),
+ },
+ };
+ union perf_event *events[] = {
+ (union perf_event *)&event1,
+ (union perf_event *)&event2,
+ (union perf_event *)&event3,
+ };
+
+ err = process_events(events, ARRAY_SIZE(events));
+ if (err)
+ return -1;
+
+ return 0;
+}
diff --git a/tools/perf/tests/perf-record.c b/tools/perf/tests/perf-record.c
new file mode 100644
index 00000000000..aca1a83dd13
--- /dev/null
+++ b/tools/perf/tests/perf-record.c
@@ -0,0 +1,309 @@
+#include <sched.h>
+#include "evlist.h"
+#include "evsel.h"
+#include "perf.h"
+#include "debug.h"
+#include "tests.h"
+
+static int sched__get_first_possible_cpu(pid_t pid, cpu_set_t *maskp)
+{
+ int i, cpu = -1, nrcpus = 1024;
+realloc:
+ CPU_ZERO(maskp);
+
+ if (sched_getaffinity(pid, sizeof(*maskp), maskp) == -1) {
+ if (errno == EINVAL && nrcpus < (1024 << 8)) {
+ nrcpus = nrcpus << 2;
+ goto realloc;
+ }
+ perror("sched_getaffinity");
+ return -1;
+ }
+
+ for (i = 0; i < nrcpus; i++) {
+ if (CPU_ISSET(i, maskp)) {
+ if (cpu == -1)
+ cpu = i;
+ else
+ CPU_CLR(i, maskp);
+ }
+ }
+
+ return cpu;
+}
+
+int test__PERF_RECORD(void)
+{
+ struct record_opts opts = {
+ .target = {
+ .uid = UINT_MAX,
+ .uses_mmap = true,
+ },
+ .no_buffering = true,
+ .freq = 10,
+ .mmap_pages = 256,
+ };
+ cpu_set_t cpu_mask;
+ size_t cpu_mask_size = sizeof(cpu_mask);
+ struct perf_evlist *evlist = perf_evlist__new_default();
+ struct perf_evsel *evsel;
+ struct perf_sample sample;
+ const char *cmd = "sleep";
+ const char *argv[] = { cmd, "1", NULL, };
+ char *bname, *mmap_filename;
+ u64 prev_time = 0;
+ bool found_cmd_mmap = false,
+ found_libc_mmap = false,
+ found_vdso_mmap = false,
+ found_ld_mmap = false;
+ int err = -1, errs = 0, i, wakeups = 0;
+ u32 cpu;
+ int total_events = 0, nr_events[PERF_RECORD_MAX] = { 0, };
+
+ if (evlist == NULL || argv == NULL) {
+ pr_debug("Not enough memory to create evlist\n");
+ goto out;
+ }
+
+ /*
+ * Create maps of threads and cpus to monitor. In this case
+ * we start with all threads and cpus (-1, -1) but then in
+ * perf_evlist__prepare_workload we'll fill in the only thread
+ * we're monitoring, the one forked there.
+ */
+ err = perf_evlist__create_maps(evlist, &opts.target);
+ if (err < 0) {
+ pr_debug("Not enough memory to create thread/cpu maps\n");
+ goto out_delete_evlist;
+ }
+
+ /*
+ * Prepare the workload in argv[] to run, it'll fork it, and then wait
+ * for perf_evlist__start_workload() to exec it. This is done this way
+ * so that we have time to open the evlist (calling sys_perf_event_open
+ * on all the fds) and then mmap them.
+ */
+ err = perf_evlist__prepare_workload(evlist, &opts.target, argv, false, NULL);
+ if (err < 0) {
+ pr_debug("Couldn't run the workload!\n");
+ goto out_delete_evlist;
+ }
+
+ /*
+ * Config the evsels, setting attr->comm on the first one, etc.
+ */
+ evsel = perf_evlist__first(evlist);
+ perf_evsel__set_sample_bit(evsel, CPU);
+ perf_evsel__set_sample_bit(evsel, TID);
+ perf_evsel__set_sample_bit(evsel, TIME);
+ perf_evlist__config(evlist, &opts);
+
+ err = sched__get_first_possible_cpu(evlist->workload.pid, &cpu_mask);
+ if (err < 0) {
+ pr_debug("sched__get_first_possible_cpu: %s\n", strerror(errno));
+ goto out_delete_evlist;
+ }
+
+ cpu = err;
+
+ /*
+ * So that we can check perf_sample.cpu on all the samples.
+ */
+ if (sched_setaffinity(evlist->workload.pid, cpu_mask_size, &cpu_mask) < 0) {
+ pr_debug("sched_setaffinity: %s\n", strerror(errno));
+ goto out_delete_evlist;
+ }
+
+ /*
+ * Call sys_perf_event_open on all the fds on all the evsels,
+ * grouping them if asked to.
+ */
+ err = perf_evlist__open(evlist);
+ if (err < 0) {
+ pr_debug("perf_evlist__open: %s\n", strerror(errno));
+ goto out_delete_evlist;
+ }
+
+ /*
+ * mmap the first fd on a given CPU and ask for events for the other
+ * fds in the same CPU to be injected in the same mmap ring buffer
+ * (using ioctl(PERF_EVENT_IOC_SET_OUTPUT)).
+ */
+ err = perf_evlist__mmap(evlist, opts.mmap_pages, false);
+ if (err < 0) {
+ pr_debug("perf_evlist__mmap: %s\n", strerror(errno));
+ goto out_delete_evlist;
+ }
+
+ /*
+ * Now that all is properly set up, enable the events, they will
+ * count just on workload.pid, which will start...
+ */
+ perf_evlist__enable(evlist);
+
+ /*
+ * Now!
+ */
+ perf_evlist__start_workload(evlist);
+
+ while (1) {
+ int before = total_events;
+
+ for (i = 0; i < evlist->nr_mmaps; i++) {
+ union perf_event *event;
+
+ while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) {
+ const u32 type = event->header.type;
+ const char *name = perf_event__name(type);
+
+ ++total_events;
+ if (type < PERF_RECORD_MAX)
+ nr_events[type]++;
+
+ err = perf_evlist__parse_sample(evlist, event, &sample);
+ if (err < 0) {
+ if (verbose)
+ perf_event__fprintf(event, stderr);
+ pr_debug("Couldn't parse sample\n");
+ goto out_delete_evlist;
+ }
+
+ if (verbose) {
+ pr_info("%" PRIu64" %d ", sample.time, sample.cpu);
+ perf_event__fprintf(event, stderr);
+ }
+
+ if (prev_time > sample.time) {
+ pr_debug("%s going backwards in time, prev=%" PRIu64 ", curr=%" PRIu64 "\n",
+ name, prev_time, sample.time);
+ ++errs;
+ }
+
+ prev_time = sample.time;
+
+ if (sample.cpu != cpu) {
+ pr_debug("%s with unexpected cpu, expected %d, got %d\n",
+ name, cpu, sample.cpu);
+ ++errs;
+ }
+
+ if ((pid_t)sample.pid != evlist->workload.pid) {
+ pr_debug("%s with unexpected pid, expected %d, got %d\n",
+ name, evlist->workload.pid, sample.pid);
+ ++errs;
+ }
+
+ if ((pid_t)sample.tid != evlist->workload.pid) {
+ pr_debug("%s with unexpected tid, expected %d, got %d\n",
+ name, evlist->workload.pid, sample.tid);
+ ++errs;
+ }
+
+ if ((type == PERF_RECORD_COMM ||
+ type == PERF_RECORD_MMAP ||
+ type == PERF_RECORD_MMAP2 ||
+ type == PERF_RECORD_FORK ||
+ type == PERF_RECORD_EXIT) &&
+ (pid_t)event->comm.pid != evlist->workload.pid) {
+ pr_debug("%s with unexpected pid/tid\n", name);
+ ++errs;
+ }
+
+ if ((type == PERF_RECORD_COMM ||
+ type == PERF_RECORD_MMAP ||
+ type == PERF_RECORD_MMAP2) &&
+ event->comm.pid != event->comm.tid) {
+ pr_debug("%s with different pid/tid!\n", name);
+ ++errs;
+ }
+
+ switch (type) {
+ case PERF_RECORD_COMM:
+ if (strcmp(event->comm.comm, cmd)) {
+ pr_debug("%s with unexpected comm!\n", name);
+ ++errs;
+ }
+ break;
+ case PERF_RECORD_EXIT:
+ goto found_exit;
+ case PERF_RECORD_MMAP:
+ mmap_filename = event->mmap.filename;
+ goto check_bname;
+ case PERF_RECORD_MMAP2:
+ mmap_filename = event->mmap2.filename;
+ check_bname:
+ bname = strrchr(mmap_filename, '/');
+ if (bname != NULL) {
+ if (!found_cmd_mmap)
+ found_cmd_mmap = !strcmp(bname + 1, cmd);
+ if (!found_libc_mmap)
+ found_libc_mmap = !strncmp(bname + 1, "libc", 4);
+ if (!found_ld_mmap)
+ found_ld_mmap = !strncmp(bname + 1, "ld", 2);
+ } else if (!found_vdso_mmap)
+ found_vdso_mmap = !strcmp(mmap_filename, "[vdso]");
+ break;
+
+ case PERF_RECORD_SAMPLE:
+ /* Just ignore samples for now */
+ break;
+ default:
+ pr_debug("Unexpected perf_event->header.type %d!\n",
+ type);
+ ++errs;
+ }
+
+ perf_evlist__mmap_consume(evlist, i);
+ }
+ }
+
+ /*
+ * We don't use poll here because at least at 3.1 times the
+ * PERF_RECORD_{!SAMPLE} events don't honour
+ * perf_event_attr.wakeup_events, just PERF_EVENT_SAMPLE does.
+ */
+ if (total_events == before && false)
+ poll(evlist->pollfd, evlist->nr_fds, -1);
+
+ sleep(1);
+ if (++wakeups > 5) {
+ pr_debug("No PERF_RECORD_EXIT event!\n");
+ break;
+ }
+ }
+
+found_exit:
+ if (nr_events[PERF_RECORD_COMM] > 1) {
+ pr_debug("Excessive number of PERF_RECORD_COMM events!\n");
+ ++errs;
+ }
+
+ if (nr_events[PERF_RECORD_COMM] == 0) {
+ pr_debug("Missing PERF_RECORD_COMM for %s!\n", cmd);
+ ++errs;
+ }
+
+ if (!found_cmd_mmap) {
+ pr_debug("PERF_RECORD_MMAP for %s missing!\n", cmd);
+ ++errs;
+ }
+
+ if (!found_libc_mmap) {
+ pr_debug("PERF_RECORD_MMAP for %s missing!\n", "libc");
+ ++errs;
+ }
+
+ if (!found_ld_mmap) {
+ pr_debug("PERF_RECORD_MMAP for %s missing!\n", "ld");
+ ++errs;
+ }
+
+ if (!found_vdso_mmap) {
+ pr_debug("PERF_RECORD_MMAP for %s missing!\n", "[vdso]");
+ ++errs;
+ }
+out_delete_evlist:
+ perf_evlist__delete(evlist);
+out:
+ return (err < 0 || errs > 0) ? -1 : 0;
+}
diff --git a/tools/perf/tests/perf-targz-src-pkg b/tools/perf/tests/perf-targz-src-pkg
new file mode 100755
index 00000000000..238aa3927c7
--- /dev/null
+++ b/tools/perf/tests/perf-targz-src-pkg
@@ -0,0 +1,21 @@
+#!/bin/sh
+# Test one of the main kernel Makefile targets to generate a perf sources tarball
+# suitable for build outside the full kernel sources.
+#
+# This is to test that the tools/perf/MANIFEST file lists all the files needed to
+# be in such tarball, which sometimes gets broken when we move files around,
+# like when we made some files that were in tools/perf/ available to other tools/
+# codebases by moving it to tools/include/, etc.
+
+PERF=$1
+cd ${PERF}/../..
+make perf-targz-src-pkg > /dev/null
+TARBALL=$(ls -rt perf-*.tar.gz)
+TMP_DEST=$(mktemp -d)
+tar xf ${TARBALL} -C $TMP_DEST
+rm -f ${TARBALL}
+cd - > /dev/null
+make -C $TMP_DEST/perf*/tools/perf > /dev/null 2>&1
+RC=$?
+rm -rf ${TMP_DEST}
+exit $RC
diff --git a/tools/perf/tests/perf-time-to-tsc.c b/tools/perf/tests/perf-time-to-tsc.c
new file mode 100644
index 00000000000..3b7cd4d32dc
--- /dev/null
+++ b/tools/perf/tests/perf-time-to-tsc.c
@@ -0,0 +1,172 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <linux/types.h>
+#include <sys/prctl.h>
+
+#include "parse-events.h"
+#include "evlist.h"
+#include "evsel.h"
+#include "thread_map.h"
+#include "cpumap.h"
+#include "tests.h"
+
+#include "../arch/x86/util/tsc.h"
+
+#define CHECK__(x) { \
+ while ((x) < 0) { \
+ pr_debug(#x " failed!\n"); \
+ goto out_err; \
+ } \
+}
+
+#define CHECK_NOT_NULL__(x) { \
+ while ((x) == NULL) { \
+ pr_debug(#x " failed!\n"); \
+ goto out_err; \
+ } \
+}
+
+static u64 rdtsc(void)
+{
+ unsigned int low, high;
+
+ asm volatile("rdtsc" : "=a" (low), "=d" (high));
+
+ return low | ((u64)high) << 32;
+}
+
+/**
+ * test__perf_time_to_tsc - test converting perf time to TSC.
+ *
+ * This function implements a test that checks that the conversion of perf time
+ * to and from TSC is consistent with the order of events. If the test passes
+ * %0 is returned, otherwise %-1 is returned. If TSC conversion is not
+ * supported then then the test passes but " (not supported)" is printed.
+ */
+int test__perf_time_to_tsc(void)
+{
+ struct record_opts opts = {
+ .mmap_pages = UINT_MAX,
+ .user_freq = UINT_MAX,
+ .user_interval = ULLONG_MAX,
+ .freq = 4000,
+ .target = {
+ .uses_mmap = true,
+ },
+ .sample_time = true,
+ };
+ struct thread_map *threads = NULL;
+ struct cpu_map *cpus = NULL;
+ struct perf_evlist *evlist = NULL;
+ struct perf_evsel *evsel = NULL;
+ int err = -1, ret, i;
+ const char *comm1, *comm2;
+ struct perf_tsc_conversion tc;
+ struct perf_event_mmap_page *pc;
+ union perf_event *event;
+ u64 test_tsc, comm1_tsc, comm2_tsc;
+ u64 test_time, comm1_time = 0, comm2_time = 0;
+
+ threads = thread_map__new(-1, getpid(), UINT_MAX);
+ CHECK_NOT_NULL__(threads);
+
+ cpus = cpu_map__new(NULL);
+ CHECK_NOT_NULL__(cpus);
+
+ evlist = perf_evlist__new();
+ CHECK_NOT_NULL__(evlist);
+
+ perf_evlist__set_maps(evlist, cpus, threads);
+
+ CHECK__(parse_events(evlist, "cycles:u"));
+
+ perf_evlist__config(evlist, &opts);
+
+ evsel = perf_evlist__first(evlist);
+
+ evsel->attr.comm = 1;
+ evsel->attr.disabled = 1;
+ evsel->attr.enable_on_exec = 0;
+
+ CHECK__(perf_evlist__open(evlist));
+
+ CHECK__(perf_evlist__mmap(evlist, UINT_MAX, false));
+
+ pc = evlist->mmap[0].base;
+ ret = perf_read_tsc_conversion(pc, &tc);
+ if (ret) {
+ if (ret == -EOPNOTSUPP) {
+ fprintf(stderr, " (not supported)");
+ return 0;
+ }
+ goto out_err;
+ }
+
+ perf_evlist__enable(evlist);
+
+ comm1 = "Test COMM 1";
+ CHECK__(prctl(PR_SET_NAME, (unsigned long)comm1, 0, 0, 0));
+
+ test_tsc = rdtsc();
+
+ comm2 = "Test COMM 2";
+ CHECK__(prctl(PR_SET_NAME, (unsigned long)comm2, 0, 0, 0));
+
+ perf_evlist__disable(evlist);
+
+ for (i = 0; i < evlist->nr_mmaps; i++) {
+ while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) {
+ struct perf_sample sample;
+
+ if (event->header.type != PERF_RECORD_COMM ||
+ (pid_t)event->comm.pid != getpid() ||
+ (pid_t)event->comm.tid != getpid())
+ goto next_event;
+
+ if (strcmp(event->comm.comm, comm1) == 0) {
+ CHECK__(perf_evsel__parse_sample(evsel, event,
+ &sample));
+ comm1_time = sample.time;
+ }
+ if (strcmp(event->comm.comm, comm2) == 0) {
+ CHECK__(perf_evsel__parse_sample(evsel, event,
+ &sample));
+ comm2_time = sample.time;
+ }
+next_event:
+ perf_evlist__mmap_consume(evlist, i);
+ }
+ }
+
+ if (!comm1_time || !comm2_time)
+ goto out_err;
+
+ test_time = tsc_to_perf_time(test_tsc, &tc);
+ comm1_tsc = perf_time_to_tsc(comm1_time, &tc);
+ comm2_tsc = perf_time_to_tsc(comm2_time, &tc);
+
+ pr_debug("1st event perf time %"PRIu64" tsc %"PRIu64"\n",
+ comm1_time, comm1_tsc);
+ pr_debug("rdtsc time %"PRIu64" tsc %"PRIu64"\n",
+ test_time, test_tsc);
+ pr_debug("2nd event perf time %"PRIu64" tsc %"PRIu64"\n",
+ comm2_time, comm2_tsc);
+
+ if (test_time <= comm1_time ||
+ test_time >= comm2_time)
+ goto out_err;
+
+ if (test_tsc <= comm1_tsc ||
+ test_tsc >= comm2_tsc)
+ goto out_err;
+
+ err = 0;
+
+out_err:
+ if (evlist) {
+ perf_evlist__disable(evlist);
+ perf_evlist__delete(evlist);
+ }
+
+ return err;
+}
diff --git a/tools/perf/tests/pmu.c b/tools/perf/tests/pmu.c
new file mode 100644
index 00000000000..12b322fa347
--- /dev/null
+++ b/tools/perf/tests/pmu.c
@@ -0,0 +1,173 @@
+#include "parse-events.h"
+#include "pmu.h"
+#include "util.h"
+#include "tests.h"
+
+/* Simulated format definitions. */
+static struct test_format {
+ const char *name;
+ const char *value;
+} test_formats[] = {
+ { "krava01", "config:0-1,62-63\n", },
+ { "krava02", "config:10-17\n", },
+ { "krava03", "config:5\n", },
+ { "krava11", "config1:0,2,4,6,8,20-28\n", },
+ { "krava12", "config1:63\n", },
+ { "krava13", "config1:45-47\n", },
+ { "krava21", "config2:0-3,10-13,20-23,30-33,40-43,50-53,60-63\n", },
+ { "krava22", "config2:8,18,48,58\n", },
+ { "krava23", "config2:28-29,38\n", },
+};
+
+/* Simulated users input. */
+static struct parse_events_term test_terms[] = {
+ {
+ .config = (char *) "krava01",
+ .val.num = 15,
+ .type_val = PARSE_EVENTS__TERM_TYPE_NUM,
+ .type_term = PARSE_EVENTS__TERM_TYPE_USER,
+ },
+ {
+ .config = (char *) "krava02",
+ .val.num = 170,
+ .type_val = PARSE_EVENTS__TERM_TYPE_NUM,
+ .type_term = PARSE_EVENTS__TERM_TYPE_USER,
+ },
+ {
+ .config = (char *) "krava03",
+ .val.num = 1,
+ .type_val = PARSE_EVENTS__TERM_TYPE_NUM,
+ .type_term = PARSE_EVENTS__TERM_TYPE_USER,
+ },
+ {
+ .config = (char *) "krava11",
+ .val.num = 27,
+ .type_val = PARSE_EVENTS__TERM_TYPE_NUM,
+ .type_term = PARSE_EVENTS__TERM_TYPE_USER,
+ },
+ {
+ .config = (char *) "krava12",
+ .val.num = 1,
+ .type_val = PARSE_EVENTS__TERM_TYPE_NUM,
+ .type_term = PARSE_EVENTS__TERM_TYPE_USER,
+ },
+ {
+ .config = (char *) "krava13",
+ .val.num = 2,
+ .type_val = PARSE_EVENTS__TERM_TYPE_NUM,
+ .type_term = PARSE_EVENTS__TERM_TYPE_USER,
+ },
+ {
+ .config = (char *) "krava21",
+ .val.num = 119,
+ .type_val = PARSE_EVENTS__TERM_TYPE_NUM,
+ .type_term = PARSE_EVENTS__TERM_TYPE_USER,
+ },
+ {
+ .config = (char *) "krava22",
+ .val.num = 11,
+ .type_val = PARSE_EVENTS__TERM_TYPE_NUM,
+ .type_term = PARSE_EVENTS__TERM_TYPE_USER,
+ },
+ {
+ .config = (char *) "krava23",
+ .val.num = 2,
+ .type_val = PARSE_EVENTS__TERM_TYPE_NUM,
+ .type_term = PARSE_EVENTS__TERM_TYPE_USER,
+ },
+};
+
+/*
+ * Prepare format directory data, exported by kernel
+ * at /sys/bus/event_source/devices/<dev>/format.
+ */
+static char *test_format_dir_get(void)
+{
+ static char dir[PATH_MAX];
+ unsigned int i;
+
+ snprintf(dir, PATH_MAX, "/tmp/perf-pmu-test-format-XXXXXX");
+ if (!mkdtemp(dir))
+ return NULL;
+
+ for (i = 0; i < ARRAY_SIZE(test_formats); i++) {
+ static char name[PATH_MAX];
+ struct test_format *format = &test_formats[i];
+ FILE *file;
+
+ snprintf(name, PATH_MAX, "%s/%s", dir, format->name);
+
+ file = fopen(name, "w");
+ if (!file)
+ return NULL;
+
+ if (1 != fwrite(format->value, strlen(format->value), 1, file))
+ break;
+
+ fclose(file);
+ }
+
+ return dir;
+}
+
+/* Cleanup format directory. */
+static int test_format_dir_put(char *dir)
+{
+ char buf[PATH_MAX];
+ snprintf(buf, PATH_MAX, "rm -f %s/*\n", dir);
+ if (system(buf))
+ return -1;
+
+ snprintf(buf, PATH_MAX, "rmdir %s\n", dir);
+ return system(buf);
+}
+
+static struct list_head *test_terms_list(void)
+{
+ static LIST_HEAD(terms);
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(test_terms); i++)
+ list_add_tail(&test_terms[i].list, &terms);
+
+ return &terms;
+}
+
+int test__pmu(void)
+{
+ char *format = test_format_dir_get();
+ LIST_HEAD(formats);
+ struct list_head *terms = test_terms_list();
+ int ret;
+
+ if (!format)
+ return -EINVAL;
+
+ do {
+ struct perf_event_attr attr;
+
+ memset(&attr, 0, sizeof(attr));
+
+ ret = perf_pmu__format_parse(format, &formats);
+ if (ret)
+ break;
+
+ ret = perf_pmu__config_terms(&formats, &attr, terms);
+ if (ret)
+ break;
+
+ ret = -EINVAL;
+
+ if (attr.config != 0xc00000000002a823)
+ break;
+ if (attr.config1 != 0x8000400000000145)
+ break;
+ if (attr.config2 != 0x0400000020041d07)
+ break;
+
+ ret = 0;
+ } while (0);
+
+ test_format_dir_put(format);
+ return ret;
+}
diff --git a/tools/perf/tests/python-use.c b/tools/perf/tests/python-use.c
new file mode 100644
index 00000000000..7760277c6de
--- /dev/null
+++ b/tools/perf/tests/python-use.c
@@ -0,0 +1,23 @@
+/*
+ * Just test if we can load the python binding.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include "tests.h"
+
+extern int verbose;
+
+int test__python_use(void)
+{
+ char *cmd;
+ int ret;
+
+ if (asprintf(&cmd, "echo \"import sys ; sys.path.append('%s'); import perf\" | %s %s",
+ PYTHONPATH, PYTHON, verbose ? "" : "2> /dev/null") < 0)
+ return -1;
+
+ ret = system(cmd) ? -1 : 0;
+ free(cmd);
+ return ret;
+}
diff --git a/tools/perf/tests/rdpmc.c b/tools/perf/tests/rdpmc.c
new file mode 100644
index 00000000000..e59143fd9e7
--- /dev/null
+++ b/tools/perf/tests/rdpmc.c
@@ -0,0 +1,173 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/mman.h>
+#include <linux/types.h>
+#include "perf.h"
+#include "debug.h"
+#include "tests.h"
+
+#if defined(__x86_64__) || defined(__i386__)
+
+static u64 rdpmc(unsigned int counter)
+{
+ unsigned int low, high;
+
+ asm volatile("rdpmc" : "=a" (low), "=d" (high) : "c" (counter));
+
+ return low | ((u64)high) << 32;
+}
+
+static u64 rdtsc(void)
+{
+ unsigned int low, high;
+
+ asm volatile("rdtsc" : "=a" (low), "=d" (high));
+
+ return low | ((u64)high) << 32;
+}
+
+static u64 mmap_read_self(void *addr)
+{
+ struct perf_event_mmap_page *pc = addr;
+ u32 seq, idx, time_mult = 0, time_shift = 0;
+ u64 count, cyc = 0, time_offset = 0, enabled, running, delta;
+
+ do {
+ seq = pc->lock;
+ barrier();
+
+ enabled = pc->time_enabled;
+ running = pc->time_running;
+
+ if (enabled != running) {
+ cyc = rdtsc();
+ time_mult = pc->time_mult;
+ time_shift = pc->time_shift;
+ time_offset = pc->time_offset;
+ }
+
+ idx = pc->index;
+ count = pc->offset;
+ if (idx)
+ count += rdpmc(idx - 1);
+
+ barrier();
+ } while (pc->lock != seq);
+
+ if (enabled != running) {
+ u64 quot, rem;
+
+ quot = (cyc >> time_shift);
+ rem = cyc & ((1 << time_shift) - 1);
+ delta = time_offset + quot * time_mult +
+ ((rem * time_mult) >> time_shift);
+
+ enabled += delta;
+ if (idx)
+ running += delta;
+
+ quot = count / running;
+ rem = count % running;
+ count = quot * enabled + (rem * enabled) / running;
+ }
+
+ return count;
+}
+
+/*
+ * If the RDPMC instruction faults then signal this back to the test parent task:
+ */
+static void segfault_handler(int sig __maybe_unused,
+ siginfo_t *info __maybe_unused,
+ void *uc __maybe_unused)
+{
+ exit(-1);
+}
+
+static int __test__rdpmc(void)
+{
+ volatile int tmp = 0;
+ u64 i, loops = 1000;
+ int n;
+ int fd;
+ void *addr;
+ struct perf_event_attr attr = {
+ .type = PERF_TYPE_HARDWARE,
+ .config = PERF_COUNT_HW_INSTRUCTIONS,
+ .exclude_kernel = 1,
+ };
+ u64 delta_sum = 0;
+ struct sigaction sa;
+
+ sigfillset(&sa.sa_mask);
+ sa.sa_sigaction = segfault_handler;
+ sigaction(SIGSEGV, &sa, NULL);
+
+ fd = sys_perf_event_open(&attr, 0, -1, -1, 0);
+ if (fd < 0) {
+ pr_err("Error: sys_perf_event_open() syscall returned "
+ "with %d (%s)\n", fd, strerror(errno));
+ return -1;
+ }
+
+ addr = mmap(NULL, page_size, PROT_READ, MAP_SHARED, fd, 0);
+ if (addr == (void *)(-1)) {
+ pr_err("Error: mmap() syscall returned with (%s)\n",
+ strerror(errno));
+ goto out_close;
+ }
+
+ for (n = 0; n < 6; n++) {
+ u64 stamp, now, delta;
+
+ stamp = mmap_read_self(addr);
+
+ for (i = 0; i < loops; i++)
+ tmp++;
+
+ now = mmap_read_self(addr);
+ loops *= 10;
+
+ delta = now - stamp;
+ pr_debug("%14d: %14Lu\n", n, (long long)delta);
+
+ delta_sum += delta;
+ }
+
+ munmap(addr, page_size);
+ pr_debug(" ");
+out_close:
+ close(fd);
+
+ if (!delta_sum)
+ return -1;
+
+ return 0;
+}
+
+int test__rdpmc(void)
+{
+ int status = 0;
+ int wret = 0;
+ int ret;
+ int pid;
+
+ pid = fork();
+ if (pid < 0)
+ return -1;
+
+ if (!pid) {
+ ret = __test__rdpmc();
+
+ exit(ret);
+ }
+
+ wret = waitpid(pid, &status, 0);
+ if (wret < 0 || status)
+ return -1;
+
+ return 0;
+}
+
+#endif
diff --git a/tools/perf/tests/sample-parsing.c b/tools/perf/tests/sample-parsing.c
new file mode 100644
index 00000000000..7ae8d17db3d
--- /dev/null
+++ b/tools/perf/tests/sample-parsing.c
@@ -0,0 +1,320 @@
+#include <stdbool.h>
+#include <linux/types.h>
+
+#include "util.h"
+#include "event.h"
+#include "evsel.h"
+
+#include "tests.h"
+
+#define COMP(m) do { \
+ if (s1->m != s2->m) { \
+ pr_debug("Samples differ at '"#m"'\n"); \
+ return false; \
+ } \
+} while (0)
+
+#define MCOMP(m) do { \
+ if (memcmp(&s1->m, &s2->m, sizeof(s1->m))) { \
+ pr_debug("Samples differ at '"#m"'\n"); \
+ return false; \
+ } \
+} while (0)
+
+static bool samples_same(const struct perf_sample *s1,
+ const struct perf_sample *s2,
+ u64 type, u64 read_format)
+{
+ size_t i;
+
+ if (type & PERF_SAMPLE_IDENTIFIER)
+ COMP(id);
+
+ if (type & PERF_SAMPLE_IP)
+ COMP(ip);
+
+ if (type & PERF_SAMPLE_TID) {
+ COMP(pid);
+ COMP(tid);
+ }
+
+ if (type & PERF_SAMPLE_TIME)
+ COMP(time);
+
+ if (type & PERF_SAMPLE_ADDR)
+ COMP(addr);
+
+ if (type & PERF_SAMPLE_ID)
+ COMP(id);
+
+ if (type & PERF_SAMPLE_STREAM_ID)
+ COMP(stream_id);
+
+ if (type & PERF_SAMPLE_CPU)
+ COMP(cpu);
+
+ if (type & PERF_SAMPLE_PERIOD)
+ COMP(period);
+
+ if (type & PERF_SAMPLE_READ) {
+ if (read_format & PERF_FORMAT_GROUP)
+ COMP(read.group.nr);
+ else
+ COMP(read.one.value);
+ if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ COMP(read.time_enabled);
+ if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ COMP(read.time_running);
+ /* PERF_FORMAT_ID is forced for PERF_SAMPLE_READ */
+ if (read_format & PERF_FORMAT_GROUP) {
+ for (i = 0; i < s1->read.group.nr; i++)
+ MCOMP(read.group.values[i]);
+ } else {
+ COMP(read.one.id);
+ }
+ }
+
+ if (type & PERF_SAMPLE_CALLCHAIN) {
+ COMP(callchain->nr);
+ for (i = 0; i < s1->callchain->nr; i++)
+ COMP(callchain->ips[i]);
+ }
+
+ if (type & PERF_SAMPLE_RAW) {
+ COMP(raw_size);
+ if (memcmp(s1->raw_data, s2->raw_data, s1->raw_size)) {
+ pr_debug("Samples differ at 'raw_data'\n");
+ return false;
+ }
+ }
+
+ if (type & PERF_SAMPLE_BRANCH_STACK) {
+ COMP(branch_stack->nr);
+ for (i = 0; i < s1->branch_stack->nr; i++)
+ MCOMP(branch_stack->entries[i]);
+ }
+
+ if (type & PERF_SAMPLE_REGS_USER) {
+ size_t sz = hweight_long(s1->user_regs.mask) * sizeof(u64);
+
+ COMP(user_regs.mask);
+ COMP(user_regs.abi);
+ if (s1->user_regs.abi &&
+ (!s1->user_regs.regs || !s2->user_regs.regs ||
+ memcmp(s1->user_regs.regs, s2->user_regs.regs, sz))) {
+ pr_debug("Samples differ at 'user_regs'\n");
+ return false;
+ }
+ }
+
+ if (type & PERF_SAMPLE_STACK_USER) {
+ COMP(user_stack.size);
+ if (memcmp(s1->user_stack.data, s1->user_stack.data,
+ s1->user_stack.size)) {
+ pr_debug("Samples differ at 'user_stack'\n");
+ return false;
+ }
+ }
+
+ if (type & PERF_SAMPLE_WEIGHT)
+ COMP(weight);
+
+ if (type & PERF_SAMPLE_DATA_SRC)
+ COMP(data_src);
+
+ if (type & PERF_SAMPLE_TRANSACTION)
+ COMP(transaction);
+
+ return true;
+}
+
+static int do_test(u64 sample_type, u64 sample_regs_user, u64 read_format)
+{
+ struct perf_evsel evsel = {
+ .needs_swap = false,
+ .attr = {
+ .sample_type = sample_type,
+ .sample_regs_user = sample_regs_user,
+ .read_format = read_format,
+ },
+ };
+ union perf_event *event;
+ union {
+ struct ip_callchain callchain;
+ u64 data[64];
+ } callchain = {
+ /* 3 ips */
+ .data = {3, 201, 202, 203},
+ };
+ union {
+ struct branch_stack branch_stack;
+ u64 data[64];
+ } branch_stack = {
+ /* 1 branch_entry */
+ .data = {1, 211, 212, 213},
+ };
+ u64 user_regs[64];
+ const u64 raw_data[] = {0x123456780a0b0c0dULL, 0x1102030405060708ULL};
+ const u64 data[] = {0x2211443366558877ULL, 0, 0xaabbccddeeff4321ULL};
+ struct perf_sample sample = {
+ .ip = 101,
+ .pid = 102,
+ .tid = 103,
+ .time = 104,
+ .addr = 105,
+ .id = 106,
+ .stream_id = 107,
+ .period = 108,
+ .weight = 109,
+ .cpu = 110,
+ .raw_size = sizeof(raw_data),
+ .data_src = 111,
+ .transaction = 112,
+ .raw_data = (void *)raw_data,
+ .callchain = &callchain.callchain,
+ .branch_stack = &branch_stack.branch_stack,
+ .user_regs = {
+ .abi = PERF_SAMPLE_REGS_ABI_64,
+ .mask = sample_regs_user,
+ .regs = user_regs,
+ },
+ .user_stack = {
+ .size = sizeof(data),
+ .data = (void *)data,
+ },
+ .read = {
+ .time_enabled = 0x030a59d664fca7deULL,
+ .time_running = 0x011b6ae553eb98edULL,
+ },
+ };
+ struct sample_read_value values[] = {{1, 5}, {9, 3}, {2, 7}, {6, 4},};
+ struct perf_sample sample_out;
+ size_t i, sz, bufsz;
+ int err, ret = -1;
+
+ for (i = 0; i < sizeof(user_regs); i++)
+ *(i + (u8 *)user_regs) = i & 0xfe;
+
+ if (read_format & PERF_FORMAT_GROUP) {
+ sample.read.group.nr = 4;
+ sample.read.group.values = values;
+ } else {
+ sample.read.one.value = 0x08789faeb786aa87ULL;
+ sample.read.one.id = 99;
+ }
+
+ sz = perf_event__sample_event_size(&sample, sample_type, read_format);
+ bufsz = sz + 4096; /* Add a bit for overrun checking */
+ event = malloc(bufsz);
+ if (!event) {
+ pr_debug("malloc failed\n");
+ return -1;
+ }
+
+ memset(event, 0xff, bufsz);
+ event->header.type = PERF_RECORD_SAMPLE;
+ event->header.misc = 0;
+ event->header.size = sz;
+
+ err = perf_event__synthesize_sample(event, sample_type, read_format,
+ &sample, false);
+ if (err) {
+ pr_debug("%s failed for sample_type %#"PRIx64", error %d\n",
+ "perf_event__synthesize_sample", sample_type, err);
+ goto out_free;
+ }
+
+ /* The data does not contain 0xff so we use that to check the size */
+ for (i = bufsz; i > 0; i--) {
+ if (*(i - 1 + (u8 *)event) != 0xff)
+ break;
+ }
+ if (i != sz) {
+ pr_debug("Event size mismatch: actual %zu vs expected %zu\n",
+ i, sz);
+ goto out_free;
+ }
+
+ evsel.sample_size = __perf_evsel__sample_size(sample_type);
+
+ err = perf_evsel__parse_sample(&evsel, event, &sample_out);
+ if (err) {
+ pr_debug("%s failed for sample_type %#"PRIx64", error %d\n",
+ "perf_evsel__parse_sample", sample_type, err);
+ goto out_free;
+ }
+
+ if (!samples_same(&sample, &sample_out, sample_type, read_format)) {
+ pr_debug("parsing failed for sample_type %#"PRIx64"\n",
+ sample_type);
+ goto out_free;
+ }
+
+ ret = 0;
+out_free:
+ free(event);
+ if (ret && read_format)
+ pr_debug("read_format %#"PRIx64"\n", read_format);
+ return ret;
+}
+
+/**
+ * test__sample_parsing - test sample parsing.
+ *
+ * This function implements a test that synthesizes a sample event, parses it
+ * and then checks that the parsed sample matches the original sample. The test
+ * checks sample format bits separately and together. If the test passes %0 is
+ * returned, otherwise %-1 is returned.
+ */
+int test__sample_parsing(void)
+{
+ const u64 rf[] = {4, 5, 6, 7, 12, 13, 14, 15};
+ u64 sample_type;
+ u64 sample_regs_user;
+ size_t i;
+ int err;
+
+ /*
+ * Fail the test if it has not been updated when new sample format bits
+ * were added. Please actually update the test rather than just change
+ * the condition below.
+ */
+ if (PERF_SAMPLE_MAX > PERF_SAMPLE_TRANSACTION << 1) {
+ pr_debug("sample format has changed, some new PERF_SAMPLE_ bit was introduced - test needs updating\n");
+ return -1;
+ }
+
+ /* Test each sample format bit separately */
+ for (sample_type = 1; sample_type != PERF_SAMPLE_MAX;
+ sample_type <<= 1) {
+ /* Test read_format variations */
+ if (sample_type == PERF_SAMPLE_READ) {
+ for (i = 0; i < ARRAY_SIZE(rf); i++) {
+ err = do_test(sample_type, 0, rf[i]);
+ if (err)
+ return err;
+ }
+ continue;
+ }
+
+ if (sample_type == PERF_SAMPLE_REGS_USER)
+ sample_regs_user = 0x3fff;
+ else
+ sample_regs_user = 0;
+
+ err = do_test(sample_type, sample_regs_user, 0);
+ if (err)
+ return err;
+ }
+
+ /* Test all sample format bits together */
+ sample_type = PERF_SAMPLE_MAX - 1;
+ sample_regs_user = 0x3fff;
+ for (i = 0; i < ARRAY_SIZE(rf); i++) {
+ err = do_test(sample_type, sample_regs_user, rf[i]);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
diff --git a/tools/perf/tests/sw-clock.c b/tools/perf/tests/sw-clock.c
new file mode 100644
index 00000000000..983d6b8562a
--- /dev/null
+++ b/tools/perf/tests/sw-clock.c
@@ -0,0 +1,122 @@
+#include <unistd.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/mman.h>
+
+#include "tests.h"
+#include "util/evsel.h"
+#include "util/evlist.h"
+#include "util/cpumap.h"
+#include "util/thread_map.h"
+
+#define NR_LOOPS 10000000
+
+/*
+ * This test will open software clock events (cpu-clock, task-clock)
+ * then check their frequency -> period conversion has no artifact of
+ * setting period to 1 forcefully.
+ */
+static int __test__sw_clock_freq(enum perf_sw_ids clock_id)
+{
+ int i, err = -1;
+ volatile int tmp = 0;
+ u64 total_periods = 0;
+ int nr_samples = 0;
+ union perf_event *event;
+ struct perf_evsel *evsel;
+ struct perf_evlist *evlist;
+ struct perf_event_attr attr = {
+ .type = PERF_TYPE_SOFTWARE,
+ .config = clock_id,
+ .sample_type = PERF_SAMPLE_PERIOD,
+ .exclude_kernel = 1,
+ .disabled = 1,
+ .freq = 1,
+ };
+
+ attr.sample_freq = 500;
+
+ evlist = perf_evlist__new();
+ if (evlist == NULL) {
+ pr_debug("perf_evlist__new\n");
+ return -1;
+ }
+
+ evsel = perf_evsel__new(&attr);
+ if (evsel == NULL) {
+ pr_debug("perf_evsel__new\n");
+ goto out_delete_evlist;
+ }
+ perf_evlist__add(evlist, evsel);
+
+ evlist->cpus = cpu_map__dummy_new();
+ evlist->threads = thread_map__new_by_tid(getpid());
+ if (!evlist->cpus || !evlist->threads) {
+ err = -ENOMEM;
+ pr_debug("Not enough memory to create thread/cpu maps\n");
+ goto out_delete_evlist;
+ }
+
+ if (perf_evlist__open(evlist)) {
+ const char *knob = "/proc/sys/kernel/perf_event_max_sample_rate";
+
+ err = -errno;
+ pr_debug("Couldn't open evlist: %s\nHint: check %s, using %" PRIu64 " in this test.\n",
+ strerror(errno), knob, (u64)attr.sample_freq);
+ goto out_delete_evlist;
+ }
+
+ err = perf_evlist__mmap(evlist, 128, true);
+ if (err < 0) {
+ pr_debug("failed to mmap event: %d (%s)\n", errno,
+ strerror(errno));
+ goto out_delete_evlist;
+ }
+
+ perf_evlist__enable(evlist);
+
+ /* collect samples */
+ for (i = 0; i < NR_LOOPS; i++)
+ tmp++;
+
+ perf_evlist__disable(evlist);
+
+ while ((event = perf_evlist__mmap_read(evlist, 0)) != NULL) {
+ struct perf_sample sample;
+
+ if (event->header.type != PERF_RECORD_SAMPLE)
+ goto next_event;
+
+ err = perf_evlist__parse_sample(evlist, event, &sample);
+ if (err < 0) {
+ pr_debug("Error during parse sample\n");
+ goto out_delete_evlist;
+ }
+
+ total_periods += sample.period;
+ nr_samples++;
+next_event:
+ perf_evlist__mmap_consume(evlist, 0);
+ }
+
+ if ((u64) nr_samples == total_periods) {
+ pr_debug("All (%d) samples have period value of 1!\n",
+ nr_samples);
+ err = -1;
+ }
+
+out_delete_evlist:
+ perf_evlist__delete(evlist);
+ return err;
+}
+
+int test__sw_clock_freq(void)
+{
+ int ret;
+
+ ret = __test__sw_clock_freq(PERF_COUNT_SW_CPU_CLOCK);
+ if (!ret)
+ ret = __test__sw_clock_freq(PERF_COUNT_SW_TASK_CLOCK);
+
+ return ret;
+}
diff --git a/tools/perf/tests/task-exit.c b/tools/perf/tests/task-exit.c
new file mode 100644
index 00000000000..5ff3db318f1
--- /dev/null
+++ b/tools/perf/tests/task-exit.c
@@ -0,0 +1,118 @@
+#include "evlist.h"
+#include "evsel.h"
+#include "thread_map.h"
+#include "cpumap.h"
+#include "tests.h"
+
+#include <signal.h>
+
+static int exited;
+static int nr_exit;
+
+static void sig_handler(int sig __maybe_unused)
+{
+ exited = 1;
+}
+
+/*
+ * perf_evlist__prepare_workload will send a SIGUSR1 if the fork fails, since
+ * we asked by setting its exec_error to this handler.
+ */
+static void workload_exec_failed_signal(int signo __maybe_unused,
+ siginfo_t *info __maybe_unused,
+ void *ucontext __maybe_unused)
+{
+ exited = 1;
+ nr_exit = -1;
+}
+
+/*
+ * This test will start a workload that does nothing then it checks
+ * if the number of exit event reported by the kernel is 1 or not
+ * in order to check the kernel returns correct number of event.
+ */
+int test__task_exit(void)
+{
+ int err = -1;
+ union perf_event *event;
+ struct perf_evsel *evsel;
+ struct perf_evlist *evlist;
+ struct target target = {
+ .uid = UINT_MAX,
+ .uses_mmap = true,
+ };
+ const char *argv[] = { "true", NULL };
+
+ signal(SIGCHLD, sig_handler);
+
+ evlist = perf_evlist__new_default();
+ if (evlist == NULL) {
+ pr_debug("perf_evlist__new_default\n");
+ return -1;
+ }
+
+ /*
+ * Create maps of threads and cpus to monitor. In this case
+ * we start with all threads and cpus (-1, -1) but then in
+ * perf_evlist__prepare_workload we'll fill in the only thread
+ * we're monitoring, the one forked there.
+ */
+ evlist->cpus = cpu_map__dummy_new();
+ evlist->threads = thread_map__new_by_tid(-1);
+ if (!evlist->cpus || !evlist->threads) {
+ err = -ENOMEM;
+ pr_debug("Not enough memory to create thread/cpu maps\n");
+ goto out_delete_evlist;
+ }
+
+ err = perf_evlist__prepare_workload(evlist, &target, argv, false,
+ workload_exec_failed_signal);
+ if (err < 0) {
+ pr_debug("Couldn't run the workload!\n");
+ goto out_delete_evlist;
+ }
+
+ evsel = perf_evlist__first(evlist);
+ evsel->attr.task = 1;
+ evsel->attr.sample_freq = 0;
+ evsel->attr.inherit = 0;
+ evsel->attr.watermark = 0;
+ evsel->attr.wakeup_events = 1;
+ evsel->attr.exclude_kernel = 1;
+
+ err = perf_evlist__open(evlist);
+ if (err < 0) {
+ pr_debug("Couldn't open the evlist: %s\n", strerror(-err));
+ goto out_delete_evlist;
+ }
+
+ if (perf_evlist__mmap(evlist, 128, true) < 0) {
+ pr_debug("failed to mmap events: %d (%s)\n", errno,
+ strerror(errno));
+ goto out_delete_evlist;
+ }
+
+ perf_evlist__start_workload(evlist);
+
+retry:
+ while ((event = perf_evlist__mmap_read(evlist, 0)) != NULL) {
+ if (event->header.type == PERF_RECORD_EXIT)
+ nr_exit++;
+
+ perf_evlist__mmap_consume(evlist, 0);
+ }
+
+ if (!exited || !nr_exit) {
+ poll(evlist->pollfd, evlist->nr_fds, -1);
+ goto retry;
+ }
+
+ if (nr_exit != 1) {
+ pr_debug("received %d EXIT records\n", nr_exit);
+ err = -1;
+ }
+
+out_delete_evlist:
+ perf_evlist__delete(evlist);
+ return err;
+}
diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h
new file mode 100644
index 00000000000..ed64790a395
--- /dev/null
+++ b/tools/perf/tests/tests.h
@@ -0,0 +1,60 @@
+#ifndef TESTS_H
+#define TESTS_H
+
+#define TEST_ASSERT_VAL(text, cond) \
+do { \
+ if (!(cond)) { \
+ pr_debug("FAILED %s:%d %s\n", __FILE__, __LINE__, text); \
+ return -1; \
+ } \
+} while (0)
+
+enum {
+ TEST_OK = 0,
+ TEST_FAIL = -1,
+ TEST_SKIP = -2,
+};
+
+/* Tests */
+int test__vmlinux_matches_kallsyms(void);
+int test__open_syscall_event(void);
+int test__open_syscall_event_on_all_cpus(void);
+int test__basic_mmap(void);
+int test__PERF_RECORD(void);
+int test__rdpmc(void);
+int test__perf_evsel__roundtrip_name_test(void);
+int test__perf_evsel__tp_sched_test(void);
+int test__syscall_open_tp_fields(void);
+int test__pmu(void);
+int test__attr(void);
+int test__dso_data(void);
+int test__dso_data_cache(void);
+int test__dso_data_reopen(void);
+int test__parse_events(void);
+int test__hists_link(void);
+int test__python_use(void);
+int test__bp_signal(void);
+int test__bp_signal_overflow(void);
+int test__task_exit(void);
+int test__sw_clock_freq(void);
+int test__perf_time_to_tsc(void);
+int test__code_reading(void);
+int test__sample_parsing(void);
+int test__keep_tracking(void);
+int test__parse_no_sample_id_all(void);
+int test__dwarf_unwind(void);
+int test__hists_filter(void);
+int test__mmap_thread_lookup(void);
+int test__thread_mg_share(void);
+int test__hists_output(void);
+int test__hists_cumulate(void);
+
+#if defined(__x86_64__) || defined(__i386__) || defined(__arm__)
+#ifdef HAVE_DWARF_UNWIND_SUPPORT
+struct thread;
+struct perf_sample;
+int test__arch_unwind_sample(struct perf_sample *sample,
+ struct thread *thread);
+#endif
+#endif
+#endif /* TESTS_H */
diff --git a/tools/perf/tests/thread-mg-share.c b/tools/perf/tests/thread-mg-share.c
new file mode 100644
index 00000000000..2b2e0dbe114
--- /dev/null
+++ b/tools/perf/tests/thread-mg-share.c
@@ -0,0 +1,90 @@
+#include "tests.h"
+#include "machine.h"
+#include "thread.h"
+#include "map.h"
+
+int test__thread_mg_share(void)
+{
+ struct machines machines;
+ struct machine *machine;
+
+ /* thread group */
+ struct thread *leader;
+ struct thread *t1, *t2, *t3;
+ struct map_groups *mg;
+
+ /* other process */
+ struct thread *other, *other_leader;
+ struct map_groups *other_mg;
+
+ /*
+ * This test create 2 processes abstractions (struct thread)
+ * with several threads and checks they properly share and
+ * maintain map groups info (struct map_groups).
+ *
+ * thread group (pid: 0, tids: 0, 1, 2, 3)
+ * other group (pid: 4, tids: 4, 5)
+ */
+
+ machines__init(&machines);
+ machine = &machines.host;
+
+ /* create process with 4 threads */
+ leader = machine__findnew_thread(machine, 0, 0);
+ t1 = machine__findnew_thread(machine, 0, 1);
+ t2 = machine__findnew_thread(machine, 0, 2);
+ t3 = machine__findnew_thread(machine, 0, 3);
+
+ /* and create 1 separated process, without thread leader */
+ other = machine__findnew_thread(machine, 4, 5);
+
+ TEST_ASSERT_VAL("failed to create threads",
+ leader && t1 && t2 && t3 && other);
+
+ mg = leader->mg;
+ TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 4);
+
+ /* test the map groups pointer is shared */
+ TEST_ASSERT_VAL("map groups don't match", mg == t1->mg);
+ TEST_ASSERT_VAL("map groups don't match", mg == t2->mg);
+ TEST_ASSERT_VAL("map groups don't match", mg == t3->mg);
+
+ /*
+ * Verify the other leader was created by previous call.
+ * It should have shared map groups with no change in
+ * refcnt.
+ */
+ other_leader = machine__find_thread(machine, 4, 4);
+ TEST_ASSERT_VAL("failed to find other leader", other_leader);
+
+ other_mg = other->mg;
+ TEST_ASSERT_VAL("wrong refcnt", other_mg->refcnt == 2);
+
+ TEST_ASSERT_VAL("map groups don't match", other_mg == other_leader->mg);
+
+ /* release thread group */
+ thread__delete(leader);
+ TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 3);
+
+ thread__delete(t1);
+ TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 2);
+
+ thread__delete(t2);
+ TEST_ASSERT_VAL("wrong refcnt", mg->refcnt == 1);
+
+ thread__delete(t3);
+
+ /* release other group */
+ thread__delete(other_leader);
+ TEST_ASSERT_VAL("wrong refcnt", other_mg->refcnt == 1);
+
+ thread__delete(other);
+
+ /*
+ * Cannot call machine__delete_threads(machine) now,
+ * because we've already released all the threads.
+ */
+
+ machines__exit(&machines);
+ return 0;
+}
diff --git a/tools/perf/tests/vmlinux-kallsyms.c b/tools/perf/tests/vmlinux-kallsyms.c
new file mode 100644
index 00000000000..3d9088003a5
--- /dev/null
+++ b/tools/perf/tests/vmlinux-kallsyms.c
@@ -0,0 +1,242 @@
+#include <linux/compiler.h>
+#include <linux/rbtree.h>
+#include <string.h>
+#include "map.h"
+#include "symbol.h"
+#include "util.h"
+#include "tests.h"
+#include "debug.h"
+#include "machine.h"
+
+static int vmlinux_matches_kallsyms_filter(struct map *map __maybe_unused,
+ struct symbol *sym)
+{
+ bool *visited = symbol__priv(sym);
+ *visited = true;
+ return 0;
+}
+
+#define UM(x) kallsyms_map->unmap_ip(kallsyms_map, (x))
+
+int test__vmlinux_matches_kallsyms(void)
+{
+ int err = -1;
+ struct rb_node *nd;
+ struct symbol *sym;
+ struct map *kallsyms_map, *vmlinux_map;
+ struct machine kallsyms, vmlinux;
+ enum map_type type = MAP__FUNCTION;
+ u64 mem_start, mem_end;
+
+ /*
+ * Step 1:
+ *
+ * Init the machines that will hold kernel, modules obtained from
+ * both vmlinux + .ko files and from /proc/kallsyms split by modules.
+ */
+ machine__init(&kallsyms, "", HOST_KERNEL_ID);
+ machine__init(&vmlinux, "", HOST_KERNEL_ID);
+
+ /*
+ * Step 2:
+ *
+ * Create the kernel maps for kallsyms and the DSO where we will then
+ * load /proc/kallsyms. Also create the modules maps from /proc/modules
+ * and find the .ko files that match them in /lib/modules/`uname -r`/.
+ */
+ if (machine__create_kernel_maps(&kallsyms) < 0) {
+ pr_debug("machine__create_kernel_maps ");
+ goto out;
+ }
+
+ /*
+ * Step 3:
+ *
+ * Load and split /proc/kallsyms into multiple maps, one per module.
+ */
+ if (machine__load_kallsyms(&kallsyms, "/proc/kallsyms", type, NULL) <= 0) {
+ pr_debug("dso__load_kallsyms ");
+ goto out;
+ }
+
+ /*
+ * Step 4:
+ *
+ * kallsyms will be internally on demand sorted by name so that we can
+ * find the reference relocation * symbol, i.e. the symbol we will use
+ * to see if the running kernel was relocated by checking if it has the
+ * same value in the vmlinux file we load.
+ */
+ kallsyms_map = machine__kernel_map(&kallsyms, type);
+
+ /*
+ * Step 5:
+ *
+ * Now repeat step 2, this time for the vmlinux file we'll auto-locate.
+ */
+ if (machine__create_kernel_maps(&vmlinux) < 0) {
+ pr_debug("machine__create_kernel_maps ");
+ goto out;
+ }
+
+ vmlinux_map = machine__kernel_map(&vmlinux, type);
+
+ /*
+ * Step 6:
+ *
+ * Locate a vmlinux file in the vmlinux path that has a buildid that
+ * matches the one of the running kernel.
+ *
+ * While doing that look if we find the ref reloc symbol, if we find it
+ * we'll have its ref_reloc_symbol.unrelocated_addr and then
+ * maps__reloc_vmlinux will notice and set proper ->[un]map_ip routines
+ * to fixup the symbols.
+ */
+ if (machine__load_vmlinux_path(&vmlinux, type,
+ vmlinux_matches_kallsyms_filter) <= 0) {
+ pr_debug("Couldn't find a vmlinux that matches the kernel running on this machine, skipping test\n");
+ err = TEST_SKIP;
+ goto out;
+ }
+
+ err = 0;
+ /*
+ * Step 7:
+ *
+ * Now look at the symbols in the vmlinux DSO and check if we find all of them
+ * in the kallsyms dso. For the ones that are in both, check its names and
+ * end addresses too.
+ */
+ for (nd = rb_first(&vmlinux_map->dso->symbols[type]); nd; nd = rb_next(nd)) {
+ struct symbol *pair, *first_pair;
+ bool backwards = true;
+
+ sym = rb_entry(nd, struct symbol, rb_node);
+
+ if (sym->start == sym->end)
+ continue;
+
+ mem_start = vmlinux_map->unmap_ip(vmlinux_map, sym->start);
+ mem_end = vmlinux_map->unmap_ip(vmlinux_map, sym->end);
+
+ first_pair = machine__find_kernel_symbol(&kallsyms, type,
+ mem_start, NULL, NULL);
+ pair = first_pair;
+
+ if (pair && UM(pair->start) == mem_start) {
+next_pair:
+ if (strcmp(sym->name, pair->name) == 0) {
+ /*
+ * kallsyms don't have the symbol end, so we
+ * set that by using the next symbol start - 1,
+ * in some cases we get this up to a page
+ * wrong, trace_kmalloc when I was developing
+ * this code was one such example, 2106 bytes
+ * off the real size. More than that and we
+ * _really_ have a problem.
+ */
+ s64 skew = mem_end - UM(pair->end);
+ if (llabs(skew) >= page_size)
+ pr_debug("%#" PRIx64 ": diff end addr for %s v: %#" PRIx64 " k: %#" PRIx64 "\n",
+ mem_start, sym->name, mem_end,
+ UM(pair->end));
+
+ /*
+ * Do not count this as a failure, because we
+ * could really find a case where it's not
+ * possible to get proper function end from
+ * kallsyms.
+ */
+ continue;
+
+ } else {
+ struct rb_node *nnd;
+detour:
+ nnd = backwards ? rb_prev(&pair->rb_node) :
+ rb_next(&pair->rb_node);
+ if (nnd) {
+ struct symbol *next = rb_entry(nnd, struct symbol, rb_node);
+
+ if (UM(next->start) == mem_start) {
+ pair = next;
+ goto next_pair;
+ }
+ }
+
+ if (backwards) {
+ backwards = false;
+ pair = first_pair;
+ goto detour;
+ }
+
+ pr_debug("%#" PRIx64 ": diff name v: %s k: %s\n",
+ mem_start, sym->name, pair->name);
+ }
+ } else
+ pr_debug("%#" PRIx64 ": %s not on kallsyms\n",
+ mem_start, sym->name);
+
+ err = -1;
+ }
+
+ if (!verbose)
+ goto out;
+
+ pr_info("Maps only in vmlinux:\n");
+
+ for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) {
+ struct map *pos = rb_entry(nd, struct map, rb_node), *pair;
+ /*
+ * If it is the kernel, kallsyms is always "[kernel.kallsyms]", while
+ * the kernel will have the path for the vmlinux file being used,
+ * so use the short name, less descriptive but the same ("[kernel]" in
+ * both cases.
+ */
+ pair = map_groups__find_by_name(&kallsyms.kmaps, type,
+ (pos->dso->kernel ?
+ pos->dso->short_name :
+ pos->dso->name));
+ if (pair)
+ pair->priv = 1;
+ else
+ map__fprintf(pos, stderr);
+ }
+
+ pr_info("Maps in vmlinux with a different name in kallsyms:\n");
+
+ for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) {
+ struct map *pos = rb_entry(nd, struct map, rb_node), *pair;
+
+ mem_start = vmlinux_map->unmap_ip(vmlinux_map, pos->start);
+ mem_end = vmlinux_map->unmap_ip(vmlinux_map, pos->end);
+
+ pair = map_groups__find(&kallsyms.kmaps, type, mem_start);
+ if (pair == NULL || pair->priv)
+ continue;
+
+ if (pair->start == mem_start) {
+ pair->priv = 1;
+ pr_info(" %" PRIx64 "-%" PRIx64 " %" PRIx64 " %s in kallsyms as",
+ pos->start, pos->end, pos->pgoff, pos->dso->name);
+ if (mem_end != pair->end)
+ pr_info(":\n*%" PRIx64 "-%" PRIx64 " %" PRIx64,
+ pair->start, pair->end, pair->pgoff);
+ pr_info(" %s\n", pair->dso->name);
+ pair->priv = 1;
+ }
+ }
+
+ pr_info("Maps only in kallsyms:\n");
+
+ for (nd = rb_first(&kallsyms.kmaps.maps[type]);
+ nd; nd = rb_next(nd)) {
+ struct map *pos = rb_entry(nd, struct map, rb_node);
+
+ if (!pos->priv)
+ map__fprintf(pos, stderr);
+ }
+out:
+ machine__exit(&kallsyms);
+ machine__exit(&vmlinux);
+ return err;
+}
diff --git a/tools/perf/ui/browser.c b/tools/perf/ui/browser.c
new file mode 100644
index 00000000000..3ccf6e14f89
--- /dev/null
+++ b/tools/perf/ui/browser.c
@@ -0,0 +1,719 @@
+#include "../util.h"
+#include "../cache.h"
+#include "../../perf.h"
+#include "libslang.h"
+#include "ui.h"
+#include "util.h"
+#include <linux/compiler.h>
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <stdlib.h>
+#include <sys/ttydefaults.h>
+#include "browser.h"
+#include "helpline.h"
+#include "keysyms.h"
+#include "../color.h"
+
+static int ui_browser__percent_color(struct ui_browser *browser,
+ double percent, bool current)
+{
+ if (current && (!browser->use_navkeypressed || browser->navkeypressed))
+ return HE_COLORSET_SELECTED;
+ if (percent >= MIN_RED)
+ return HE_COLORSET_TOP;
+ if (percent >= MIN_GREEN)
+ return HE_COLORSET_MEDIUM;
+ return HE_COLORSET_NORMAL;
+}
+
+int ui_browser__set_color(struct ui_browser *browser, int color)
+{
+ int ret = browser->current_color;
+ browser->current_color = color;
+ SLsmg_set_color(color);
+ return ret;
+}
+
+void ui_browser__set_percent_color(struct ui_browser *browser,
+ double percent, bool current)
+{
+ int color = ui_browser__percent_color(browser, percent, current);
+ ui_browser__set_color(browser, color);
+}
+
+void ui_browser__gotorc(struct ui_browser *browser, int y, int x)
+{
+ SLsmg_gotorc(browser->y + y, browser->x + x);
+}
+
+static struct list_head *
+ui_browser__list_head_filter_entries(struct ui_browser *browser,
+ struct list_head *pos)
+{
+ do {
+ if (!browser->filter || !browser->filter(browser, pos))
+ return pos;
+ pos = pos->next;
+ } while (pos != browser->entries);
+
+ return NULL;
+}
+
+static struct list_head *
+ui_browser__list_head_filter_prev_entries(struct ui_browser *browser,
+ struct list_head *pos)
+{
+ do {
+ if (!browser->filter || !browser->filter(browser, pos))
+ return pos;
+ pos = pos->prev;
+ } while (pos != browser->entries);
+
+ return NULL;
+}
+
+void ui_browser__list_head_seek(struct ui_browser *browser, off_t offset, int whence)
+{
+ struct list_head *head = browser->entries;
+ struct list_head *pos;
+
+ if (browser->nr_entries == 0)
+ return;
+
+ switch (whence) {
+ case SEEK_SET:
+ pos = ui_browser__list_head_filter_entries(browser, head->next);
+ break;
+ case SEEK_CUR:
+ pos = browser->top;
+ break;
+ case SEEK_END:
+ pos = ui_browser__list_head_filter_prev_entries(browser, head->prev);
+ break;
+ default:
+ return;
+ }
+
+ assert(pos != NULL);
+
+ if (offset > 0) {
+ while (offset-- != 0)
+ pos = ui_browser__list_head_filter_entries(browser, pos->next);
+ } else {
+ while (offset++ != 0)
+ pos = ui_browser__list_head_filter_prev_entries(browser, pos->prev);
+ }
+
+ browser->top = pos;
+}
+
+void ui_browser__rb_tree_seek(struct ui_browser *browser, off_t offset, int whence)
+{
+ struct rb_root *root = browser->entries;
+ struct rb_node *nd;
+
+ switch (whence) {
+ case SEEK_SET:
+ nd = rb_first(root);
+ break;
+ case SEEK_CUR:
+ nd = browser->top;
+ break;
+ case SEEK_END:
+ nd = rb_last(root);
+ break;
+ default:
+ return;
+ }
+
+ if (offset > 0) {
+ while (offset-- != 0)
+ nd = rb_next(nd);
+ } else {
+ while (offset++ != 0)
+ nd = rb_prev(nd);
+ }
+
+ browser->top = nd;
+}
+
+unsigned int ui_browser__rb_tree_refresh(struct ui_browser *browser)
+{
+ struct rb_node *nd;
+ int row = 0;
+
+ if (browser->top == NULL)
+ browser->top = rb_first(browser->entries);
+
+ nd = browser->top;
+
+ while (nd != NULL) {
+ ui_browser__gotorc(browser, row, 0);
+ browser->write(browser, nd, row);
+ if (++row == browser->height)
+ break;
+ nd = rb_next(nd);
+ }
+
+ return row;
+}
+
+bool ui_browser__is_current_entry(struct ui_browser *browser, unsigned row)
+{
+ return browser->top_idx + row == browser->index;
+}
+
+void ui_browser__refresh_dimensions(struct ui_browser *browser)
+{
+ browser->width = SLtt_Screen_Cols - 1;
+ browser->height = SLtt_Screen_Rows - 2;
+ browser->y = 1;
+ browser->x = 0;
+}
+
+void ui_browser__handle_resize(struct ui_browser *browser)
+{
+ ui__refresh_dimensions(false);
+ ui_browser__show(browser, browser->title, ui_helpline__current);
+ ui_browser__refresh(browser);
+}
+
+int ui_browser__warning(struct ui_browser *browser, int timeout,
+ const char *format, ...)
+{
+ va_list args;
+ char *text;
+ int key = 0, err;
+
+ va_start(args, format);
+ err = vasprintf(&text, format, args);
+ va_end(args);
+
+ if (err < 0) {
+ va_start(args, format);
+ ui_helpline__vpush(format, args);
+ va_end(args);
+ } else {
+ while ((key = ui__question_window("Warning!", text,
+ "Press any key...",
+ timeout)) == K_RESIZE)
+ ui_browser__handle_resize(browser);
+ free(text);
+ }
+
+ return key;
+}
+
+int ui_browser__help_window(struct ui_browser *browser, const char *text)
+{
+ int key;
+
+ while ((key = ui__help_window(text)) == K_RESIZE)
+ ui_browser__handle_resize(browser);
+
+ return key;
+}
+
+bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text)
+{
+ int key;
+
+ while ((key = ui__dialog_yesno(text)) == K_RESIZE)
+ ui_browser__handle_resize(browser);
+
+ return key == K_ENTER || toupper(key) == 'Y';
+}
+
+void ui_browser__reset_index(struct ui_browser *browser)
+{
+ browser->index = browser->top_idx = 0;
+ browser->seek(browser, 0, SEEK_SET);
+}
+
+void __ui_browser__show_title(struct ui_browser *browser, const char *title)
+{
+ SLsmg_gotorc(0, 0);
+ ui_browser__set_color(browser, HE_COLORSET_ROOT);
+ slsmg_write_nstring(title, browser->width + 1);
+}
+
+void ui_browser__show_title(struct ui_browser *browser, const char *title)
+{
+ pthread_mutex_lock(&ui__lock);
+ __ui_browser__show_title(browser, title);
+ pthread_mutex_unlock(&ui__lock);
+}
+
+int ui_browser__show(struct ui_browser *browser, const char *title,
+ const char *helpline, ...)
+{
+ int err;
+ va_list ap;
+
+ ui_browser__refresh_dimensions(browser);
+
+ pthread_mutex_lock(&ui__lock);
+ __ui_browser__show_title(browser, title);
+
+ browser->title = title;
+ zfree(&browser->helpline);
+
+ va_start(ap, helpline);
+ err = vasprintf(&browser->helpline, helpline, ap);
+ va_end(ap);
+ if (err > 0)
+ ui_helpline__push(browser->helpline);
+ pthread_mutex_unlock(&ui__lock);
+ return err ? 0 : -1;
+}
+
+void ui_browser__hide(struct ui_browser *browser)
+{
+ pthread_mutex_lock(&ui__lock);
+ ui_helpline__pop();
+ zfree(&browser->helpline);
+ pthread_mutex_unlock(&ui__lock);
+}
+
+static void ui_browser__scrollbar_set(struct ui_browser *browser)
+{
+ int height = browser->height, h = 0, pct = 0,
+ col = browser->width,
+ row = browser->y - 1;
+
+ if (browser->nr_entries > 1) {
+ pct = ((browser->index * (browser->height - 1)) /
+ (browser->nr_entries - 1));
+ }
+
+ SLsmg_set_char_set(1);
+
+ while (h < height) {
+ ui_browser__gotorc(browser, row++, col);
+ SLsmg_write_char(h == pct ? SLSMG_DIAMOND_CHAR : SLSMG_CKBRD_CHAR);
+ ++h;
+ }
+
+ SLsmg_set_char_set(0);
+}
+
+static int __ui_browser__refresh(struct ui_browser *browser)
+{
+ int row;
+ int width = browser->width;
+
+ row = browser->refresh(browser);
+ ui_browser__set_color(browser, HE_COLORSET_NORMAL);
+
+ if (!browser->use_navkeypressed || browser->navkeypressed)
+ ui_browser__scrollbar_set(browser);
+ else
+ width += 1;
+
+ SLsmg_fill_region(browser->y + row, browser->x,
+ browser->height - row, width, ' ');
+
+ return 0;
+}
+
+int ui_browser__refresh(struct ui_browser *browser)
+{
+ pthread_mutex_lock(&ui__lock);
+ __ui_browser__refresh(browser);
+ pthread_mutex_unlock(&ui__lock);
+
+ return 0;
+}
+
+/*
+ * Here we're updating nr_entries _after_ we started browsing, i.e. we have to
+ * forget about any reference to any entry in the underlying data structure,
+ * that is why we do a SEEK_SET. Think about 'perf top' in the hists browser
+ * after an output_resort and hist decay.
+ */
+void ui_browser__update_nr_entries(struct ui_browser *browser, u32 nr_entries)
+{
+ off_t offset = nr_entries - browser->nr_entries;
+
+ browser->nr_entries = nr_entries;
+
+ if (offset < 0) {
+ if (browser->top_idx < (u64)-offset)
+ offset = -browser->top_idx;
+
+ browser->index += offset;
+ browser->top_idx += offset;
+ }
+
+ browser->top = NULL;
+ browser->seek(browser, browser->top_idx, SEEK_SET);
+}
+
+int ui_browser__run(struct ui_browser *browser, int delay_secs)
+{
+ int err, key;
+
+ while (1) {
+ off_t offset;
+
+ pthread_mutex_lock(&ui__lock);
+ err = __ui_browser__refresh(browser);
+ SLsmg_refresh();
+ pthread_mutex_unlock(&ui__lock);
+ if (err < 0)
+ break;
+
+ key = ui__getch(delay_secs);
+
+ if (key == K_RESIZE) {
+ ui__refresh_dimensions(false);
+ ui_browser__refresh_dimensions(browser);
+ __ui_browser__show_title(browser, browser->title);
+ ui_helpline__puts(browser->helpline);
+ continue;
+ }
+
+ if (browser->use_navkeypressed && !browser->navkeypressed) {
+ if (key == K_DOWN || key == K_UP ||
+ key == K_PGDN || key == K_PGUP ||
+ key == K_HOME || key == K_END ||
+ key == ' ') {
+ browser->navkeypressed = true;
+ continue;
+ } else
+ return key;
+ }
+
+ switch (key) {
+ case K_DOWN:
+ if (browser->index == browser->nr_entries - 1)
+ break;
+ ++browser->index;
+ if (browser->index == browser->top_idx + browser->height) {
+ ++browser->top_idx;
+ browser->seek(browser, +1, SEEK_CUR);
+ }
+ break;
+ case K_UP:
+ if (browser->index == 0)
+ break;
+ --browser->index;
+ if (browser->index < browser->top_idx) {
+ --browser->top_idx;
+ browser->seek(browser, -1, SEEK_CUR);
+ }
+ break;
+ case K_PGDN:
+ case ' ':
+ if (browser->top_idx + browser->height > browser->nr_entries - 1)
+ break;
+
+ offset = browser->height;
+ if (browser->index + offset > browser->nr_entries - 1)
+ offset = browser->nr_entries - 1 - browser->index;
+ browser->index += offset;
+ browser->top_idx += offset;
+ browser->seek(browser, +offset, SEEK_CUR);
+ break;
+ case K_PGUP:
+ if (browser->top_idx == 0)
+ break;
+
+ if (browser->top_idx < browser->height)
+ offset = browser->top_idx;
+ else
+ offset = browser->height;
+
+ browser->index -= offset;
+ browser->top_idx -= offset;
+ browser->seek(browser, -offset, SEEK_CUR);
+ break;
+ case K_HOME:
+ ui_browser__reset_index(browser);
+ break;
+ case K_END:
+ offset = browser->height - 1;
+ if (offset >= browser->nr_entries)
+ offset = browser->nr_entries - 1;
+
+ browser->index = browser->nr_entries - 1;
+ browser->top_idx = browser->index - offset;
+ browser->seek(browser, -offset, SEEK_END);
+ break;
+ default:
+ return key;
+ }
+ }
+ return -1;
+}
+
+unsigned int ui_browser__list_head_refresh(struct ui_browser *browser)
+{
+ struct list_head *pos;
+ struct list_head *head = browser->entries;
+ int row = 0;
+
+ if (browser->top == NULL || browser->top == browser->entries)
+ browser->top = ui_browser__list_head_filter_entries(browser, head->next);
+
+ pos = browser->top;
+
+ list_for_each_from(pos, head) {
+ if (!browser->filter || !browser->filter(browser, pos)) {
+ ui_browser__gotorc(browser, row, 0);
+ browser->write(browser, pos, row);
+ if (++row == browser->height)
+ break;
+ }
+ }
+
+ return row;
+}
+
+static struct ui_browser_colorset {
+ const char *name, *fg, *bg;
+ int colorset;
+} ui_browser__colorsets[] = {
+ {
+ .colorset = HE_COLORSET_TOP,
+ .name = "top",
+ .fg = "red",
+ .bg = "default",
+ },
+ {
+ .colorset = HE_COLORSET_MEDIUM,
+ .name = "medium",
+ .fg = "green",
+ .bg = "default",
+ },
+ {
+ .colorset = HE_COLORSET_NORMAL,
+ .name = "normal",
+ .fg = "default",
+ .bg = "default",
+ },
+ {
+ .colorset = HE_COLORSET_SELECTED,
+ .name = "selected",
+ .fg = "black",
+ .bg = "lightgray",
+ },
+ {
+ .colorset = HE_COLORSET_CODE,
+ .name = "code",
+ .fg = "blue",
+ .bg = "default",
+ },
+ {
+ .colorset = HE_COLORSET_ADDR,
+ .name = "addr",
+ .fg = "magenta",
+ .bg = "default",
+ },
+ {
+ .colorset = HE_COLORSET_ROOT,
+ .name = "root",
+ .fg = "white",
+ .bg = "blue",
+ },
+ {
+ .name = NULL,
+ }
+};
+
+
+static int ui_browser__color_config(const char *var, const char *value,
+ void *data __maybe_unused)
+{
+ char *fg = NULL, *bg;
+ int i;
+
+ /* same dir for all commands */
+ if (prefixcmp(var, "colors.") != 0)
+ return 0;
+
+ for (i = 0; ui_browser__colorsets[i].name != NULL; ++i) {
+ const char *name = var + 7;
+
+ if (strcmp(ui_browser__colorsets[i].name, name) != 0)
+ continue;
+
+ fg = strdup(value);
+ if (fg == NULL)
+ break;
+
+ bg = strchr(fg, ',');
+ if (bg == NULL)
+ break;
+
+ *bg = '\0';
+ while (isspace(*++bg));
+ ui_browser__colorsets[i].bg = bg;
+ ui_browser__colorsets[i].fg = fg;
+ return 0;
+ }
+
+ free(fg);
+ return -1;
+}
+
+void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence)
+{
+ switch (whence) {
+ case SEEK_SET:
+ browser->top = browser->entries;
+ break;
+ case SEEK_CUR:
+ browser->top = browser->top + browser->top_idx + offset;
+ break;
+ case SEEK_END:
+ browser->top = browser->top + browser->nr_entries - 1 + offset;
+ break;
+ default:
+ return;
+ }
+}
+
+unsigned int ui_browser__argv_refresh(struct ui_browser *browser)
+{
+ unsigned int row = 0, idx = browser->top_idx;
+ char **pos;
+
+ if (browser->top == NULL)
+ browser->top = browser->entries;
+
+ pos = (char **)browser->top;
+ while (idx < browser->nr_entries) {
+ if (!browser->filter || !browser->filter(browser, *pos)) {
+ ui_browser__gotorc(browser, row, 0);
+ browser->write(browser, pos, row);
+ if (++row == browser->height)
+ break;
+ }
+
+ ++idx;
+ ++pos;
+ }
+
+ return row;
+}
+
+void __ui_browser__vline(struct ui_browser *browser, unsigned int column,
+ u16 start, u16 end)
+{
+ SLsmg_set_char_set(1);
+ ui_browser__gotorc(browser, start, column);
+ SLsmg_draw_vline(end - start + 1);
+ SLsmg_set_char_set(0);
+}
+
+void ui_browser__write_graph(struct ui_browser *browser __maybe_unused,
+ int graph)
+{
+ SLsmg_set_char_set(1);
+ SLsmg_write_char(graph);
+ SLsmg_set_char_set(0);
+}
+
+static void __ui_browser__line_arrow_up(struct ui_browser *browser,
+ unsigned int column,
+ u64 start, u64 end)
+{
+ unsigned int row, end_row;
+
+ SLsmg_set_char_set(1);
+
+ if (start < browser->top_idx + browser->height) {
+ row = start - browser->top_idx;
+ ui_browser__gotorc(browser, row, column);
+ SLsmg_write_char(SLSMG_LLCORN_CHAR);
+ ui_browser__gotorc(browser, row, column + 1);
+ SLsmg_draw_hline(2);
+
+ if (row-- == 0)
+ goto out;
+ } else
+ row = browser->height - 1;
+
+ if (end > browser->top_idx)
+ end_row = end - browser->top_idx;
+ else
+ end_row = 0;
+
+ ui_browser__gotorc(browser, end_row, column);
+ SLsmg_draw_vline(row - end_row + 1);
+
+ ui_browser__gotorc(browser, end_row, column);
+ if (end >= browser->top_idx) {
+ SLsmg_write_char(SLSMG_ULCORN_CHAR);
+ ui_browser__gotorc(browser, end_row, column + 1);
+ SLsmg_write_char(SLSMG_HLINE_CHAR);
+ ui_browser__gotorc(browser, end_row, column + 2);
+ SLsmg_write_char(SLSMG_RARROW_CHAR);
+ }
+out:
+ SLsmg_set_char_set(0);
+}
+
+static void __ui_browser__line_arrow_down(struct ui_browser *browser,
+ unsigned int column,
+ u64 start, u64 end)
+{
+ unsigned int row, end_row;
+
+ SLsmg_set_char_set(1);
+
+ if (start >= browser->top_idx) {
+ row = start - browser->top_idx;
+ ui_browser__gotorc(browser, row, column);
+ SLsmg_write_char(SLSMG_ULCORN_CHAR);
+ ui_browser__gotorc(browser, row, column + 1);
+ SLsmg_draw_hline(2);
+
+ if (row++ == 0)
+ goto out;
+ } else
+ row = 0;
+
+ if (end >= browser->top_idx + browser->height)
+ end_row = browser->height - 1;
+ else
+ end_row = end - browser->top_idx;
+
+ ui_browser__gotorc(browser, row, column);
+ SLsmg_draw_vline(end_row - row + 1);
+
+ ui_browser__gotorc(browser, end_row, column);
+ if (end < browser->top_idx + browser->height) {
+ SLsmg_write_char(SLSMG_LLCORN_CHAR);
+ ui_browser__gotorc(browser, end_row, column + 1);
+ SLsmg_write_char(SLSMG_HLINE_CHAR);
+ ui_browser__gotorc(browser, end_row, column + 2);
+ SLsmg_write_char(SLSMG_RARROW_CHAR);
+ }
+out:
+ SLsmg_set_char_set(0);
+}
+
+void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column,
+ u64 start, u64 end)
+{
+ if (start > end)
+ __ui_browser__line_arrow_up(browser, column, start, end);
+ else
+ __ui_browser__line_arrow_down(browser, column, start, end);
+}
+
+void ui_browser__init(void)
+{
+ int i = 0;
+
+ perf_config(ui_browser__color_config, NULL);
+
+ while (ui_browser__colorsets[i].name) {
+ struct ui_browser_colorset *c = &ui_browser__colorsets[i++];
+ sltt_set_color(c->colorset, c->name, c->fg, c->bg);
+ }
+
+ annotate_browser__init();
+}
diff --git a/tools/perf/ui/browser.h b/tools/perf/ui/browser.h
new file mode 100644
index 00000000000..03d4d6295f1
--- /dev/null
+++ b/tools/perf/ui/browser.h
@@ -0,0 +1,74 @@
+#ifndef _PERF_UI_BROWSER_H_
+#define _PERF_UI_BROWSER_H_ 1
+
+#include <linux/types.h>
+
+#define HE_COLORSET_TOP 50
+#define HE_COLORSET_MEDIUM 51
+#define HE_COLORSET_NORMAL 52
+#define HE_COLORSET_SELECTED 53
+#define HE_COLORSET_CODE 54
+#define HE_COLORSET_ADDR 55
+#define HE_COLORSET_ROOT 56
+
+struct ui_browser {
+ u64 index, top_idx;
+ void *top, *entries;
+ u16 y, x, width, height;
+ int current_color;
+ void *priv;
+ const char *title;
+ char *helpline;
+ unsigned int (*refresh)(struct ui_browser *browser);
+ void (*write)(struct ui_browser *browser, void *entry, int row);
+ void (*seek)(struct ui_browser *browser, off_t offset, int whence);
+ bool (*filter)(struct ui_browser *browser, void *entry);
+ u32 nr_entries;
+ bool navkeypressed;
+ bool use_navkeypressed;
+};
+
+int ui_browser__set_color(struct ui_browser *browser, int color);
+void ui_browser__set_percent_color(struct ui_browser *browser,
+ double percent, bool current);
+bool ui_browser__is_current_entry(struct ui_browser *browser, unsigned row);
+void ui_browser__refresh_dimensions(struct ui_browser *browser);
+void ui_browser__reset_index(struct ui_browser *browser);
+
+void ui_browser__gotorc(struct ui_browser *browser, int y, int x);
+void ui_browser__write_graph(struct ui_browser *browser, int graph);
+void __ui_browser__line_arrow(struct ui_browser *browser, unsigned int column,
+ u64 start, u64 end);
+void __ui_browser__show_title(struct ui_browser *browser, const char *title);
+void ui_browser__show_title(struct ui_browser *browser, const char *title);
+int ui_browser__show(struct ui_browser *browser, const char *title,
+ const char *helpline, ...);
+void ui_browser__hide(struct ui_browser *browser);
+int ui_browser__refresh(struct ui_browser *browser);
+int ui_browser__run(struct ui_browser *browser, int delay_secs);
+void ui_browser__update_nr_entries(struct ui_browser *browser, u32 nr_entries);
+void ui_browser__handle_resize(struct ui_browser *browser);
+void __ui_browser__vline(struct ui_browser *browser, unsigned int column,
+ u16 start, u16 end);
+
+int ui_browser__warning(struct ui_browser *browser, int timeout,
+ const char *format, ...);
+int ui_browser__help_window(struct ui_browser *browser, const char *text);
+bool ui_browser__dialog_yesno(struct ui_browser *browser, const char *text);
+int ui_browser__input_window(const char *title, const char *text, char *input,
+ const char *exit_msg, int delay_sec);
+struct perf_session_env;
+int tui__header_window(struct perf_session_env *env);
+
+void ui_browser__argv_seek(struct ui_browser *browser, off_t offset, int whence);
+unsigned int ui_browser__argv_refresh(struct ui_browser *browser);
+
+void ui_browser__rb_tree_seek(struct ui_browser *browser, off_t offset, int whence);
+unsigned int ui_browser__rb_tree_refresh(struct ui_browser *browser);
+
+void ui_browser__list_head_seek(struct ui_browser *browser, off_t offset, int whence);
+unsigned int ui_browser__list_head_refresh(struct ui_browser *browser);
+
+void ui_browser__init(void);
+void annotate_browser__init(void);
+#endif /* _PERF_UI_BROWSER_H_ */
diff --git a/tools/perf/ui/browsers/annotate.c b/tools/perf/ui/browsers/annotate.c
new file mode 100644
index 00000000000..f0697a3aede
--- /dev/null
+++ b/tools/perf/ui/browsers/annotate.c
@@ -0,0 +1,1023 @@
+#include "../../util/util.h"
+#include "../browser.h"
+#include "../helpline.h"
+#include "../libslang.h"
+#include "../ui.h"
+#include "../util.h"
+#include "../../util/annotate.h"
+#include "../../util/hist.h"
+#include "../../util/sort.h"
+#include "../../util/symbol.h"
+#include "../../util/evsel.h"
+#include <pthread.h>
+
+struct browser_disasm_line {
+ struct rb_node rb_node;
+ u32 idx;
+ int idx_asm;
+ int jump_sources;
+ /*
+ * actual length of this array is saved on the nr_events field
+ * of the struct annotate_browser
+ */
+ double percent[1];
+};
+
+static struct annotate_browser_opt {
+ bool hide_src_code,
+ use_offset,
+ jump_arrows,
+ show_nr_jumps;
+} annotate_browser__opts = {
+ .use_offset = true,
+ .jump_arrows = true,
+};
+
+struct annotate_browser {
+ struct ui_browser b;
+ struct rb_root entries;
+ struct rb_node *curr_hot;
+ struct disasm_line *selection;
+ struct disasm_line **offsets;
+ int nr_events;
+ u64 start;
+ int nr_asm_entries;
+ int nr_entries;
+ int max_jump_sources;
+ int nr_jumps;
+ bool searching_backwards;
+ u8 addr_width;
+ u8 jumps_width;
+ u8 target_width;
+ u8 min_addr_width;
+ u8 max_addr_width;
+ char search_bf[128];
+};
+
+static inline struct browser_disasm_line *disasm_line__browser(struct disasm_line *dl)
+{
+ return (struct browser_disasm_line *)(dl + 1);
+}
+
+static bool disasm_line__filter(struct ui_browser *browser __maybe_unused,
+ void *entry)
+{
+ if (annotate_browser__opts.hide_src_code) {
+ struct disasm_line *dl = list_entry(entry, struct disasm_line, node);
+ return dl->offset == -1;
+ }
+
+ return false;
+}
+
+static int annotate_browser__jumps_percent_color(struct annotate_browser *browser,
+ int nr, bool current)
+{
+ if (current && (!browser->b.use_navkeypressed || browser->b.navkeypressed))
+ return HE_COLORSET_SELECTED;
+ if (nr == browser->max_jump_sources)
+ return HE_COLORSET_TOP;
+ if (nr > 1)
+ return HE_COLORSET_MEDIUM;
+ return HE_COLORSET_NORMAL;
+}
+
+static int annotate_browser__set_jumps_percent_color(struct annotate_browser *browser,
+ int nr, bool current)
+{
+ int color = annotate_browser__jumps_percent_color(browser, nr, current);
+ return ui_browser__set_color(&browser->b, color);
+}
+
+static void annotate_browser__write(struct ui_browser *browser, void *entry, int row)
+{
+ struct annotate_browser *ab = container_of(browser, struct annotate_browser, b);
+ struct disasm_line *dl = list_entry(entry, struct disasm_line, node);
+ struct browser_disasm_line *bdl = disasm_line__browser(dl);
+ bool current_entry = ui_browser__is_current_entry(browser, row);
+ bool change_color = (!annotate_browser__opts.hide_src_code &&
+ (!current_entry || (browser->use_navkeypressed &&
+ !browser->navkeypressed)));
+ int width = browser->width, printed;
+ int i, pcnt_width = 7 * ab->nr_events;
+ double percent_max = 0.0;
+ char bf[256];
+
+ for (i = 0; i < ab->nr_events; i++) {
+ if (bdl->percent[i] > percent_max)
+ percent_max = bdl->percent[i];
+ }
+
+ if (dl->offset != -1 && percent_max != 0.0) {
+ for (i = 0; i < ab->nr_events; i++) {
+ ui_browser__set_percent_color(browser, bdl->percent[i],
+ current_entry);
+ slsmg_printf("%6.2f ", bdl->percent[i]);
+ }
+ } else {
+ ui_browser__set_percent_color(browser, 0, current_entry);
+ slsmg_write_nstring(" ", pcnt_width);
+ }
+
+ SLsmg_write_char(' ');
+
+ /* The scroll bar isn't being used */
+ if (!browser->navkeypressed)
+ width += 1;
+
+ if (!*dl->line)
+ slsmg_write_nstring(" ", width - pcnt_width);
+ else if (dl->offset == -1) {
+ printed = scnprintf(bf, sizeof(bf), "%*s ",
+ ab->addr_width, " ");
+ slsmg_write_nstring(bf, printed);
+ slsmg_write_nstring(dl->line, width - printed - pcnt_width + 1);
+ } else {
+ u64 addr = dl->offset;
+ int color = -1;
+
+ if (!annotate_browser__opts.use_offset)
+ addr += ab->start;
+
+ if (!annotate_browser__opts.use_offset) {
+ printed = scnprintf(bf, sizeof(bf), "%" PRIx64 ": ", addr);
+ } else {
+ if (bdl->jump_sources) {
+ if (annotate_browser__opts.show_nr_jumps) {
+ int prev;
+ printed = scnprintf(bf, sizeof(bf), "%*d ",
+ ab->jumps_width,
+ bdl->jump_sources);
+ prev = annotate_browser__set_jumps_percent_color(ab, bdl->jump_sources,
+ current_entry);
+ slsmg_write_nstring(bf, printed);
+ ui_browser__set_color(browser, prev);
+ }
+
+ printed = scnprintf(bf, sizeof(bf), "%*" PRIx64 ": ",
+ ab->target_width, addr);
+ } else {
+ printed = scnprintf(bf, sizeof(bf), "%*s ",
+ ab->addr_width, " ");
+ }
+ }
+
+ if (change_color)
+ color = ui_browser__set_color(browser, HE_COLORSET_ADDR);
+ slsmg_write_nstring(bf, printed);
+ if (change_color)
+ ui_browser__set_color(browser, color);
+ if (dl->ins && dl->ins->ops->scnprintf) {
+ if (ins__is_jump(dl->ins)) {
+ bool fwd = dl->ops.target.offset > (u64)dl->offset;
+
+ ui_browser__write_graph(browser, fwd ? SLSMG_DARROW_CHAR :
+ SLSMG_UARROW_CHAR);
+ SLsmg_write_char(' ');
+ } else if (ins__is_call(dl->ins)) {
+ ui_browser__write_graph(browser, SLSMG_RARROW_CHAR);
+ SLsmg_write_char(' ');
+ } else {
+ slsmg_write_nstring(" ", 2);
+ }
+ } else {
+ if (strcmp(dl->name, "retq")) {
+ slsmg_write_nstring(" ", 2);
+ } else {
+ ui_browser__write_graph(browser, SLSMG_LARROW_CHAR);
+ SLsmg_write_char(' ');
+ }
+ }
+
+ disasm_line__scnprintf(dl, bf, sizeof(bf), !annotate_browser__opts.use_offset);
+ slsmg_write_nstring(bf, width - pcnt_width - 3 - printed);
+ }
+
+ if (current_entry)
+ ab->selection = dl;
+}
+
+static bool disasm_line__is_valid_jump(struct disasm_line *dl, struct symbol *sym)
+{
+ if (!dl || !dl->ins || !ins__is_jump(dl->ins)
+ || !disasm_line__has_offset(dl)
+ || dl->ops.target.offset >= symbol__size(sym))
+ return false;
+
+ return true;
+}
+
+static void annotate_browser__draw_current_jump(struct ui_browser *browser)
+{
+ struct annotate_browser *ab = container_of(browser, struct annotate_browser, b);
+ struct disasm_line *cursor = ab->selection, *target;
+ struct browser_disasm_line *btarget, *bcursor;
+ unsigned int from, to;
+ struct map_symbol *ms = ab->b.priv;
+ struct symbol *sym = ms->sym;
+ u8 pcnt_width = 7;
+
+ /* PLT symbols contain external offsets */
+ if (strstr(sym->name, "@plt"))
+ return;
+
+ if (!disasm_line__is_valid_jump(cursor, sym))
+ return;
+
+ target = ab->offsets[cursor->ops.target.offset];
+ if (!target)
+ return;
+
+ bcursor = disasm_line__browser(cursor);
+ btarget = disasm_line__browser(target);
+
+ if (annotate_browser__opts.hide_src_code) {
+ from = bcursor->idx_asm;
+ to = btarget->idx_asm;
+ } else {
+ from = (u64)bcursor->idx;
+ to = (u64)btarget->idx;
+ }
+
+ pcnt_width *= ab->nr_events;
+
+ ui_browser__set_color(browser, HE_COLORSET_CODE);
+ __ui_browser__line_arrow(browser, pcnt_width + 2 + ab->addr_width,
+ from, to);
+}
+
+static unsigned int annotate_browser__refresh(struct ui_browser *browser)
+{
+ struct annotate_browser *ab = container_of(browser, struct annotate_browser, b);
+ int ret = ui_browser__list_head_refresh(browser);
+ int pcnt_width;
+
+ pcnt_width = 7 * ab->nr_events;
+
+ if (annotate_browser__opts.jump_arrows)
+ annotate_browser__draw_current_jump(browser);
+
+ ui_browser__set_color(browser, HE_COLORSET_NORMAL);
+ __ui_browser__vline(browser, pcnt_width, 0, browser->height - 1);
+ return ret;
+}
+
+static int disasm__cmp(struct browser_disasm_line *a,
+ struct browser_disasm_line *b, int nr_pcnt)
+{
+ int i;
+
+ for (i = 0; i < nr_pcnt; i++) {
+ if (a->percent[i] == b->percent[i])
+ continue;
+ return a->percent[i] < b->percent[i];
+ }
+ return 0;
+}
+
+static void disasm_rb_tree__insert(struct rb_root *root, struct browser_disasm_line *bdl,
+ int nr_events)
+{
+ struct rb_node **p = &root->rb_node;
+ struct rb_node *parent = NULL;
+ struct browser_disasm_line *l;
+
+ while (*p != NULL) {
+ parent = *p;
+ l = rb_entry(parent, struct browser_disasm_line, rb_node);
+
+ if (disasm__cmp(bdl, l, nr_events))
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+ rb_link_node(&bdl->rb_node, parent, p);
+ rb_insert_color(&bdl->rb_node, root);
+}
+
+static void annotate_browser__set_top(struct annotate_browser *browser,
+ struct disasm_line *pos, u32 idx)
+{
+ unsigned back;
+
+ ui_browser__refresh_dimensions(&browser->b);
+ back = browser->b.height / 2;
+ browser->b.top_idx = browser->b.index = idx;
+
+ while (browser->b.top_idx != 0 && back != 0) {
+ pos = list_entry(pos->node.prev, struct disasm_line, node);
+
+ if (disasm_line__filter(&browser->b, &pos->node))
+ continue;
+
+ --browser->b.top_idx;
+ --back;
+ }
+
+ browser->b.top = pos;
+ browser->b.navkeypressed = true;
+}
+
+static void annotate_browser__set_rb_top(struct annotate_browser *browser,
+ struct rb_node *nd)
+{
+ struct browser_disasm_line *bpos;
+ struct disasm_line *pos;
+ u32 idx;
+
+ bpos = rb_entry(nd, struct browser_disasm_line, rb_node);
+ pos = ((struct disasm_line *)bpos) - 1;
+ idx = bpos->idx;
+ if (annotate_browser__opts.hide_src_code)
+ idx = bpos->idx_asm;
+ annotate_browser__set_top(browser, pos, idx);
+ browser->curr_hot = nd;
+}
+
+static void annotate_browser__calc_percent(struct annotate_browser *browser,
+ struct perf_evsel *evsel)
+{
+ struct map_symbol *ms = browser->b.priv;
+ struct symbol *sym = ms->sym;
+ struct annotation *notes = symbol__annotation(sym);
+ struct disasm_line *pos, *next;
+ s64 len = symbol__size(sym);
+
+ browser->entries = RB_ROOT;
+
+ pthread_mutex_lock(&notes->lock);
+
+ list_for_each_entry(pos, &notes->src->source, node) {
+ struct browser_disasm_line *bpos = disasm_line__browser(pos);
+ const char *path = NULL;
+ double max_percent = 0.0;
+ int i;
+
+ if (pos->offset == -1) {
+ RB_CLEAR_NODE(&bpos->rb_node);
+ continue;
+ }
+
+ next = disasm__get_next_ip_line(&notes->src->source, pos);
+
+ for (i = 0; i < browser->nr_events; i++) {
+ bpos->percent[i] = disasm__calc_percent(notes,
+ evsel->idx + i,
+ pos->offset,
+ next ? next->offset : len,
+ &path);
+
+ if (max_percent < bpos->percent[i])
+ max_percent = bpos->percent[i];
+ }
+
+ if (max_percent < 0.01) {
+ RB_CLEAR_NODE(&bpos->rb_node);
+ continue;
+ }
+ disasm_rb_tree__insert(&browser->entries, bpos,
+ browser->nr_events);
+ }
+ pthread_mutex_unlock(&notes->lock);
+
+ browser->curr_hot = rb_last(&browser->entries);
+}
+
+static bool annotate_browser__toggle_source(struct annotate_browser *browser)
+{
+ struct disasm_line *dl;
+ struct browser_disasm_line *bdl;
+ off_t offset = browser->b.index - browser->b.top_idx;
+
+ browser->b.seek(&browser->b, offset, SEEK_CUR);
+ dl = list_entry(browser->b.top, struct disasm_line, node);
+ bdl = disasm_line__browser(dl);
+
+ if (annotate_browser__opts.hide_src_code) {
+ if (bdl->idx_asm < offset)
+ offset = bdl->idx;
+
+ browser->b.nr_entries = browser->nr_entries;
+ annotate_browser__opts.hide_src_code = false;
+ browser->b.seek(&browser->b, -offset, SEEK_CUR);
+ browser->b.top_idx = bdl->idx - offset;
+ browser->b.index = bdl->idx;
+ } else {
+ if (bdl->idx_asm < 0) {
+ ui_helpline__puts("Only available for assembly lines.");
+ browser->b.seek(&browser->b, -offset, SEEK_CUR);
+ return false;
+ }
+
+ if (bdl->idx_asm < offset)
+ offset = bdl->idx_asm;
+
+ browser->b.nr_entries = browser->nr_asm_entries;
+ annotate_browser__opts.hide_src_code = true;
+ browser->b.seek(&browser->b, -offset, SEEK_CUR);
+ browser->b.top_idx = bdl->idx_asm - offset;
+ browser->b.index = bdl->idx_asm;
+ }
+
+ return true;
+}
+
+static void annotate_browser__init_asm_mode(struct annotate_browser *browser)
+{
+ ui_browser__reset_index(&browser->b);
+ browser->b.nr_entries = browser->nr_asm_entries;
+}
+
+#define SYM_TITLE_MAX_SIZE (PATH_MAX + 64)
+
+static int sym_title(struct symbol *sym, struct map *map, char *title,
+ size_t sz)
+{
+ return snprintf(title, sz, "%s %s", sym->name, map->dso->long_name);
+}
+
+static bool annotate_browser__callq(struct annotate_browser *browser,
+ struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt)
+{
+ struct map_symbol *ms = browser->b.priv;
+ struct disasm_line *dl = browser->selection;
+ struct annotation *notes;
+ struct addr_map_symbol target = {
+ .map = ms->map,
+ .addr = map__objdump_2mem(ms->map, dl->ops.target.addr),
+ };
+ char title[SYM_TITLE_MAX_SIZE];
+
+ if (!ins__is_call(dl->ins))
+ return false;
+
+ if (map_groups__find_ams(&target, NULL) ||
+ map__rip_2objdump(target.map, target.map->map_ip(target.map,
+ target.addr)) !=
+ dl->ops.target.addr) {
+ ui_helpline__puts("The called function was not found.");
+ return true;
+ }
+
+ notes = symbol__annotation(target.sym);
+ pthread_mutex_lock(&notes->lock);
+
+ if (notes->src == NULL && symbol__alloc_hist(target.sym) < 0) {
+ pthread_mutex_unlock(&notes->lock);
+ ui__warning("Not enough memory for annotating '%s' symbol!\n",
+ target.sym->name);
+ return true;
+ }
+
+ pthread_mutex_unlock(&notes->lock);
+ symbol__tui_annotate(target.sym, target.map, evsel, hbt);
+ sym_title(ms->sym, ms->map, title, sizeof(title));
+ ui_browser__show_title(&browser->b, title);
+ return true;
+}
+
+static
+struct disasm_line *annotate_browser__find_offset(struct annotate_browser *browser,
+ s64 offset, s64 *idx)
+{
+ struct map_symbol *ms = browser->b.priv;
+ struct symbol *sym = ms->sym;
+ struct annotation *notes = symbol__annotation(sym);
+ struct disasm_line *pos;
+
+ *idx = 0;
+ list_for_each_entry(pos, &notes->src->source, node) {
+ if (pos->offset == offset)
+ return pos;
+ if (!disasm_line__filter(&browser->b, &pos->node))
+ ++*idx;
+ }
+
+ return NULL;
+}
+
+static bool annotate_browser__jump(struct annotate_browser *browser)
+{
+ struct disasm_line *dl = browser->selection;
+ s64 idx;
+
+ if (!ins__is_jump(dl->ins))
+ return false;
+
+ dl = annotate_browser__find_offset(browser, dl->ops.target.offset, &idx);
+ if (dl == NULL) {
+ ui_helpline__puts("Invalid jump offset");
+ return true;
+ }
+
+ annotate_browser__set_top(browser, dl, idx);
+
+ return true;
+}
+
+static
+struct disasm_line *annotate_browser__find_string(struct annotate_browser *browser,
+ char *s, s64 *idx)
+{
+ struct map_symbol *ms = browser->b.priv;
+ struct symbol *sym = ms->sym;
+ struct annotation *notes = symbol__annotation(sym);
+ struct disasm_line *pos = browser->selection;
+
+ *idx = browser->b.index;
+ list_for_each_entry_continue(pos, &notes->src->source, node) {
+ if (disasm_line__filter(&browser->b, &pos->node))
+ continue;
+
+ ++*idx;
+
+ if (pos->line && strstr(pos->line, s) != NULL)
+ return pos;
+ }
+
+ return NULL;
+}
+
+static bool __annotate_browser__search(struct annotate_browser *browser)
+{
+ struct disasm_line *dl;
+ s64 idx;
+
+ dl = annotate_browser__find_string(browser, browser->search_bf, &idx);
+ if (dl == NULL) {
+ ui_helpline__puts("String not found!");
+ return false;
+ }
+
+ annotate_browser__set_top(browser, dl, idx);
+ browser->searching_backwards = false;
+ return true;
+}
+
+static
+struct disasm_line *annotate_browser__find_string_reverse(struct annotate_browser *browser,
+ char *s, s64 *idx)
+{
+ struct map_symbol *ms = browser->b.priv;
+ struct symbol *sym = ms->sym;
+ struct annotation *notes = symbol__annotation(sym);
+ struct disasm_line *pos = browser->selection;
+
+ *idx = browser->b.index;
+ list_for_each_entry_continue_reverse(pos, &notes->src->source, node) {
+ if (disasm_line__filter(&browser->b, &pos->node))
+ continue;
+
+ --*idx;
+
+ if (pos->line && strstr(pos->line, s) != NULL)
+ return pos;
+ }
+
+ return NULL;
+}
+
+static bool __annotate_browser__search_reverse(struct annotate_browser *browser)
+{
+ struct disasm_line *dl;
+ s64 idx;
+
+ dl = annotate_browser__find_string_reverse(browser, browser->search_bf, &idx);
+ if (dl == NULL) {
+ ui_helpline__puts("String not found!");
+ return false;
+ }
+
+ annotate_browser__set_top(browser, dl, idx);
+ browser->searching_backwards = true;
+ return true;
+}
+
+static bool annotate_browser__search_window(struct annotate_browser *browser,
+ int delay_secs)
+{
+ if (ui_browser__input_window("Search", "String: ", browser->search_bf,
+ "ENTER: OK, ESC: Cancel",
+ delay_secs * 2) != K_ENTER ||
+ !*browser->search_bf)
+ return false;
+
+ return true;
+}
+
+static bool annotate_browser__search(struct annotate_browser *browser, int delay_secs)
+{
+ if (annotate_browser__search_window(browser, delay_secs))
+ return __annotate_browser__search(browser);
+
+ return false;
+}
+
+static bool annotate_browser__continue_search(struct annotate_browser *browser,
+ int delay_secs)
+{
+ if (!*browser->search_bf)
+ return annotate_browser__search(browser, delay_secs);
+
+ return __annotate_browser__search(browser);
+}
+
+static bool annotate_browser__search_reverse(struct annotate_browser *browser,
+ int delay_secs)
+{
+ if (annotate_browser__search_window(browser, delay_secs))
+ return __annotate_browser__search_reverse(browser);
+
+ return false;
+}
+
+static
+bool annotate_browser__continue_search_reverse(struct annotate_browser *browser,
+ int delay_secs)
+{
+ if (!*browser->search_bf)
+ return annotate_browser__search_reverse(browser, delay_secs);
+
+ return __annotate_browser__search_reverse(browser);
+}
+
+static void annotate_browser__update_addr_width(struct annotate_browser *browser)
+{
+ if (annotate_browser__opts.use_offset)
+ browser->target_width = browser->min_addr_width;
+ else
+ browser->target_width = browser->max_addr_width;
+
+ browser->addr_width = browser->target_width;
+
+ if (annotate_browser__opts.show_nr_jumps)
+ browser->addr_width += browser->jumps_width + 1;
+}
+
+static int annotate_browser__run(struct annotate_browser *browser,
+ struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt)
+{
+ struct rb_node *nd = NULL;
+ struct map_symbol *ms = browser->b.priv;
+ struct symbol *sym = ms->sym;
+ const char *help = "Press 'h' for help on key bindings";
+ int delay_secs = hbt ? hbt->refresh : 0;
+ int key;
+ char title[SYM_TITLE_MAX_SIZE];
+
+ sym_title(sym, ms->map, title, sizeof(title));
+ if (ui_browser__show(&browser->b, title, help) < 0)
+ return -1;
+
+ annotate_browser__calc_percent(browser, evsel);
+
+ if (browser->curr_hot) {
+ annotate_browser__set_rb_top(browser, browser->curr_hot);
+ browser->b.navkeypressed = false;
+ }
+
+ nd = browser->curr_hot;
+
+ while (1) {
+ key = ui_browser__run(&browser->b, delay_secs);
+
+ if (delay_secs != 0) {
+ annotate_browser__calc_percent(browser, evsel);
+ /*
+ * Current line focus got out of the list of most active
+ * lines, NULL it so that if TAB|UNTAB is pressed, we
+ * move to curr_hot (current hottest line).
+ */
+ if (nd != NULL && RB_EMPTY_NODE(nd))
+ nd = NULL;
+ }
+
+ switch (key) {
+ case K_TIMER:
+ if (hbt)
+ hbt->timer(hbt->arg);
+
+ if (delay_secs != 0)
+ symbol__annotate_decay_histogram(sym, evsel->idx);
+ continue;
+ case K_TAB:
+ if (nd != NULL) {
+ nd = rb_prev(nd);
+ if (nd == NULL)
+ nd = rb_last(&browser->entries);
+ } else
+ nd = browser->curr_hot;
+ break;
+ case K_UNTAB:
+ if (nd != NULL)
+ nd = rb_next(nd);
+ if (nd == NULL)
+ nd = rb_first(&browser->entries);
+ else
+ nd = browser->curr_hot;
+ break;
+ case K_F1:
+ case 'h':
+ ui_browser__help_window(&browser->b,
+ "UP/DOWN/PGUP\n"
+ "PGDN/SPACE Navigate\n"
+ "q/ESC/CTRL+C Exit\n\n"
+ "-> Go to target\n"
+ "<- Exit\n"
+ "H Cycle thru hottest instructions\n"
+ "j Toggle showing jump to target arrows\n"
+ "J Toggle showing number of jump sources on targets\n"
+ "n Search next string\n"
+ "o Toggle disassembler output/simplified view\n"
+ "s Toggle source code view\n"
+ "/ Search string\n"
+ "r Run available scripts\n"
+ "? Search string backwards\n");
+ continue;
+ case 'r':
+ {
+ script_browse(NULL);
+ continue;
+ }
+ case 'H':
+ nd = browser->curr_hot;
+ break;
+ case 's':
+ if (annotate_browser__toggle_source(browser))
+ ui_helpline__puts(help);
+ continue;
+ case 'o':
+ annotate_browser__opts.use_offset = !annotate_browser__opts.use_offset;
+ annotate_browser__update_addr_width(browser);
+ continue;
+ case 'j':
+ annotate_browser__opts.jump_arrows = !annotate_browser__opts.jump_arrows;
+ continue;
+ case 'J':
+ annotate_browser__opts.show_nr_jumps = !annotate_browser__opts.show_nr_jumps;
+ annotate_browser__update_addr_width(browser);
+ continue;
+ case '/':
+ if (annotate_browser__search(browser, delay_secs)) {
+show_help:
+ ui_helpline__puts(help);
+ }
+ continue;
+ case 'n':
+ if (browser->searching_backwards ?
+ annotate_browser__continue_search_reverse(browser, delay_secs) :
+ annotate_browser__continue_search(browser, delay_secs))
+ goto show_help;
+ continue;
+ case '?':
+ if (annotate_browser__search_reverse(browser, delay_secs))
+ goto show_help;
+ continue;
+ case 'D': {
+ static int seq;
+ ui_helpline__pop();
+ ui_helpline__fpush("%d: nr_ent=%d, height=%d, idx=%d, top_idx=%d, nr_asm_entries=%d",
+ seq++, browser->b.nr_entries,
+ browser->b.height,
+ browser->b.index,
+ browser->b.top_idx,
+ browser->nr_asm_entries);
+ }
+ continue;
+ case K_ENTER:
+ case K_RIGHT:
+ if (browser->selection == NULL)
+ ui_helpline__puts("Huh? No selection. Report to linux-kernel@vger.kernel.org");
+ else if (browser->selection->offset == -1)
+ ui_helpline__puts("Actions are only available for assembly lines.");
+ else if (!browser->selection->ins) {
+ if (strcmp(browser->selection->name, "retq"))
+ goto show_sup_ins;
+ goto out;
+ } else if (!(annotate_browser__jump(browser) ||
+ annotate_browser__callq(browser, evsel, hbt))) {
+show_sup_ins:
+ ui_helpline__puts("Actions are only available for 'callq', 'retq' & jump instructions.");
+ }
+ continue;
+ case K_LEFT:
+ case K_ESC:
+ case 'q':
+ case CTRL('c'):
+ goto out;
+ default:
+ continue;
+ }
+
+ if (nd != NULL)
+ annotate_browser__set_rb_top(browser, nd);
+ }
+out:
+ ui_browser__hide(&browser->b);
+ return key;
+}
+
+int hist_entry__tui_annotate(struct hist_entry *he, struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt)
+{
+ return symbol__tui_annotate(he->ms.sym, he->ms.map, evsel, hbt);
+}
+
+static void annotate_browser__mark_jump_targets(struct annotate_browser *browser,
+ size_t size)
+{
+ u64 offset;
+ struct map_symbol *ms = browser->b.priv;
+ struct symbol *sym = ms->sym;
+
+ /* PLT symbols contain external offsets */
+ if (strstr(sym->name, "@plt"))
+ return;
+
+ for (offset = 0; offset < size; ++offset) {
+ struct disasm_line *dl = browser->offsets[offset], *dlt;
+ struct browser_disasm_line *bdlt;
+
+ if (!disasm_line__is_valid_jump(dl, sym))
+ continue;
+
+ dlt = browser->offsets[dl->ops.target.offset];
+ /*
+ * FIXME: Oops, no jump target? Buggy disassembler? Or do we
+ * have to adjust to the previous offset?
+ */
+ if (dlt == NULL)
+ continue;
+
+ bdlt = disasm_line__browser(dlt);
+ if (++bdlt->jump_sources > browser->max_jump_sources)
+ browser->max_jump_sources = bdlt->jump_sources;
+
+ ++browser->nr_jumps;
+ }
+
+}
+
+static inline int width_jumps(int n)
+{
+ if (n >= 100)
+ return 5;
+ if (n / 10)
+ return 2;
+ return 1;
+}
+
+int symbol__tui_annotate(struct symbol *sym, struct map *map,
+ struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt)
+{
+ struct disasm_line *pos, *n;
+ struct annotation *notes;
+ size_t size;
+ struct map_symbol ms = {
+ .map = map,
+ .sym = sym,
+ };
+ struct annotate_browser browser = {
+ .b = {
+ .refresh = annotate_browser__refresh,
+ .seek = ui_browser__list_head_seek,
+ .write = annotate_browser__write,
+ .filter = disasm_line__filter,
+ .priv = &ms,
+ .use_navkeypressed = true,
+ },
+ };
+ int ret = -1;
+ int nr_pcnt = 1;
+ size_t sizeof_bdl = sizeof(struct browser_disasm_line);
+
+ if (sym == NULL)
+ return -1;
+
+ size = symbol__size(sym);
+
+ if (map->dso->annotate_warned)
+ return -1;
+
+ browser.offsets = zalloc(size * sizeof(struct disasm_line *));
+ if (browser.offsets == NULL) {
+ ui__error("Not enough memory!");
+ return -1;
+ }
+
+ if (perf_evsel__is_group_event(evsel)) {
+ nr_pcnt = evsel->nr_members;
+ sizeof_bdl += sizeof(double) * (nr_pcnt - 1);
+ }
+
+ if (symbol__annotate(sym, map, sizeof_bdl) < 0) {
+ ui__error("%s", ui_helpline__last_msg);
+ goto out_free_offsets;
+ }
+
+ ui_helpline__push("Press <- or ESC to exit");
+
+ notes = symbol__annotation(sym);
+ browser.start = map__rip_2objdump(map, sym->start);
+
+ list_for_each_entry(pos, &notes->src->source, node) {
+ struct browser_disasm_line *bpos;
+ size_t line_len = strlen(pos->line);
+
+ if (browser.b.width < line_len)
+ browser.b.width = line_len;
+ bpos = disasm_line__browser(pos);
+ bpos->idx = browser.nr_entries++;
+ if (pos->offset != -1) {
+ bpos->idx_asm = browser.nr_asm_entries++;
+ /*
+ * FIXME: short term bandaid to cope with assembly
+ * routines that comes with labels in the same column
+ * as the address in objdump, sigh.
+ *
+ * E.g. copy_user_generic_unrolled
+ */
+ if (pos->offset < (s64)size)
+ browser.offsets[pos->offset] = pos;
+ } else
+ bpos->idx_asm = -1;
+ }
+
+ annotate_browser__mark_jump_targets(&browser, size);
+
+ browser.addr_width = browser.target_width = browser.min_addr_width = hex_width(size);
+ browser.max_addr_width = hex_width(sym->end);
+ browser.jumps_width = width_jumps(browser.max_jump_sources);
+ browser.nr_events = nr_pcnt;
+ browser.b.nr_entries = browser.nr_entries;
+ browser.b.entries = &notes->src->source,
+ browser.b.width += 18; /* Percentage */
+
+ if (annotate_browser__opts.hide_src_code)
+ annotate_browser__init_asm_mode(&browser);
+
+ annotate_browser__update_addr_width(&browser);
+
+ ret = annotate_browser__run(&browser, evsel, hbt);
+ list_for_each_entry_safe(pos, n, &notes->src->source, node) {
+ list_del(&pos->node);
+ disasm_line__free(pos);
+ }
+
+out_free_offsets:
+ free(browser.offsets);
+ return ret;
+}
+
+#define ANNOTATE_CFG(n) \
+ { .name = #n, .value = &annotate_browser__opts.n, }
+
+/*
+ * Keep the entries sorted, they are bsearch'ed
+ */
+static struct annotate_config {
+ const char *name;
+ bool *value;
+} annotate__configs[] = {
+ ANNOTATE_CFG(hide_src_code),
+ ANNOTATE_CFG(jump_arrows),
+ ANNOTATE_CFG(show_nr_jumps),
+ ANNOTATE_CFG(use_offset),
+};
+
+#undef ANNOTATE_CFG
+
+static int annotate_config__cmp(const void *name, const void *cfgp)
+{
+ const struct annotate_config *cfg = cfgp;
+
+ return strcmp(name, cfg->name);
+}
+
+static int annotate__config(const char *var, const char *value,
+ void *data __maybe_unused)
+{
+ struct annotate_config *cfg;
+ const char *name;
+
+ if (prefixcmp(var, "annotate.") != 0)
+ return 0;
+
+ name = var + 9;
+ cfg = bsearch(name, annotate__configs, ARRAY_SIZE(annotate__configs),
+ sizeof(struct annotate_config), annotate_config__cmp);
+
+ if (cfg == NULL)
+ return -1;
+
+ *cfg->value = perf_config_bool(name, value);
+ return 0;
+}
+
+void annotate_browser__init(void)
+{
+ perf_config(annotate__config, NULL);
+}
diff --git a/tools/perf/ui/browsers/header.c b/tools/perf/ui/browsers/header.c
new file mode 100644
index 00000000000..89c16b98861
--- /dev/null
+++ b/tools/perf/ui/browsers/header.c
@@ -0,0 +1,127 @@
+#include "util/cache.h"
+#include "util/debug.h"
+#include "ui/browser.h"
+#include "ui/ui.h"
+#include "ui/util.h"
+#include "ui/libslang.h"
+#include "util/header.h"
+#include "util/session.h"
+
+static void ui_browser__argv_write(struct ui_browser *browser,
+ void *entry, int row)
+{
+ char **arg = entry;
+ char *str = *arg;
+ char empty[] = " ";
+ bool current_entry = ui_browser__is_current_entry(browser, row);
+ unsigned long offset = (unsigned long)browser->priv;
+
+ if (offset >= strlen(str))
+ str = empty;
+ else
+ str = str + offset;
+
+ ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
+ HE_COLORSET_NORMAL);
+
+ slsmg_write_nstring(str, browser->width);
+}
+
+static int list_menu__run(struct ui_browser *menu)
+{
+ int key;
+ unsigned long offset;
+ const char help[] =
+ "h/?/F1 Show this window\n"
+ "UP/DOWN/PGUP\n"
+ "PGDN/SPACE\n"
+ "LEFT/RIGHT Navigate\n"
+ "q/ESC/CTRL+C Exit browser";
+
+ if (ui_browser__show(menu, "Header information", "Press 'q' to exit") < 0)
+ return -1;
+
+ while (1) {
+ key = ui_browser__run(menu, 0);
+
+ switch (key) {
+ case K_RIGHT:
+ offset = (unsigned long)menu->priv;
+ offset += 10;
+ menu->priv = (void *)offset;
+ continue;
+ case K_LEFT:
+ offset = (unsigned long)menu->priv;
+ if (offset >= 10)
+ offset -= 10;
+ menu->priv = (void *)offset;
+ continue;
+ case K_F1:
+ case 'h':
+ case '?':
+ ui_browser__help_window(menu, help);
+ continue;
+ case K_ESC:
+ case 'q':
+ case CTRL('c'):
+ key = -1;
+ break;
+ default:
+ continue;
+ }
+
+ break;
+ }
+
+ ui_browser__hide(menu);
+ return key;
+}
+
+static int ui__list_menu(int argc, char * const argv[])
+{
+ struct ui_browser menu = {
+ .entries = (void *)argv,
+ .refresh = ui_browser__argv_refresh,
+ .seek = ui_browser__argv_seek,
+ .write = ui_browser__argv_write,
+ .nr_entries = argc,
+ };
+
+ return list_menu__run(&menu);
+}
+
+int tui__header_window(struct perf_session_env *env)
+{
+ int i, argc = 0;
+ char **argv;
+ struct perf_session *session;
+ char *ptr, *pos;
+ size_t size;
+ FILE *fp = open_memstream(&ptr, &size);
+
+ session = container_of(env, struct perf_session, header.env);
+ perf_header__fprintf_info(session, fp, true);
+ fclose(fp);
+
+ for (pos = ptr, argc = 0; (pos = strchr(pos, '\n')) != NULL; pos++)
+ argc++;
+
+ argv = calloc(argc + 1, sizeof(*argv));
+ if (argv == NULL)
+ goto out;
+
+ argv[0] = pos = ptr;
+ for (i = 1; (pos = strchr(pos, '\n')) != NULL; i++) {
+ *pos++ = '\0';
+ argv[i] = pos;
+ }
+
+ BUG_ON(i != argc + 1);
+
+ ui__list_menu(argc, argv);
+
+out:
+ free(argv);
+ free(ptr);
+ return 0;
+}
diff --git a/tools/perf/ui/browsers/hists.c b/tools/perf/ui/browsers/hists.c
new file mode 100644
index 00000000000..04a229aa5c0
--- /dev/null
+++ b/tools/perf/ui/browsers/hists.c
@@ -0,0 +1,2007 @@
+#include <stdio.h>
+#include "../libslang.h"
+#include <stdlib.h>
+#include <string.h>
+#include <linux/rbtree.h>
+
+#include "../../util/evsel.h"
+#include "../../util/evlist.h"
+#include "../../util/hist.h"
+#include "../../util/pstack.h"
+#include "../../util/sort.h"
+#include "../../util/util.h"
+#include "../../arch/common.h"
+
+#include "../browser.h"
+#include "../helpline.h"
+#include "../util.h"
+#include "../ui.h"
+#include "map.h"
+#include "annotate.h"
+
+struct hist_browser {
+ struct ui_browser b;
+ struct hists *hists;
+ struct hist_entry *he_selection;
+ struct map_symbol *selection;
+ int print_seq;
+ bool show_dso;
+ float min_pcnt;
+ u64 nr_non_filtered_entries;
+ u64 nr_callchain_rows;
+};
+
+extern void hist_browser__init_hpp(void);
+
+static int hists__browser_title(struct hists *hists, char *bf, size_t size,
+ const char *ev_name);
+static void hist_browser__update_nr_entries(struct hist_browser *hb);
+
+static struct rb_node *hists__filter_entries(struct rb_node *nd,
+ float min_pcnt);
+
+static bool hist_browser__has_filter(struct hist_browser *hb)
+{
+ return hists__has_filter(hb->hists) || hb->min_pcnt;
+}
+
+static u32 hist_browser__nr_entries(struct hist_browser *hb)
+{
+ u32 nr_entries;
+
+ if (hist_browser__has_filter(hb))
+ nr_entries = hb->nr_non_filtered_entries;
+ else
+ nr_entries = hb->hists->nr_entries;
+
+ return nr_entries + hb->nr_callchain_rows;
+}
+
+static void hist_browser__refresh_dimensions(struct hist_browser *browser)
+{
+ /* 3 == +/- toggle symbol before actual hist_entry rendering */
+ browser->b.width = 3 + (hists__sort_list_width(browser->hists) +
+ sizeof("[k]"));
+}
+
+static void hist_browser__reset(struct hist_browser *browser)
+{
+ /*
+ * The hists__remove_entry_filter() already folds non-filtered
+ * entries so we can assume it has 0 callchain rows.
+ */
+ browser->nr_callchain_rows = 0;
+
+ hist_browser__update_nr_entries(browser);
+ browser->b.nr_entries = hist_browser__nr_entries(browser);
+ hist_browser__refresh_dimensions(browser);
+ ui_browser__reset_index(&browser->b);
+}
+
+static char tree__folded_sign(bool unfolded)
+{
+ return unfolded ? '-' : '+';
+}
+
+static char map_symbol__folded(const struct map_symbol *ms)
+{
+ return ms->has_children ? tree__folded_sign(ms->unfolded) : ' ';
+}
+
+static char hist_entry__folded(const struct hist_entry *he)
+{
+ return map_symbol__folded(&he->ms);
+}
+
+static char callchain_list__folded(const struct callchain_list *cl)
+{
+ return map_symbol__folded(&cl->ms);
+}
+
+static void map_symbol__set_folding(struct map_symbol *ms, bool unfold)
+{
+ ms->unfolded = unfold ? ms->has_children : false;
+}
+
+static int callchain_node__count_rows_rb_tree(struct callchain_node *node)
+{
+ int n = 0;
+ struct rb_node *nd;
+
+ for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
+ struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
+ struct callchain_list *chain;
+ char folded_sign = ' '; /* No children */
+
+ list_for_each_entry(chain, &child->val, list) {
+ ++n;
+ /* We need this because we may not have children */
+ folded_sign = callchain_list__folded(chain);
+ if (folded_sign == '+')
+ break;
+ }
+
+ if (folded_sign == '-') /* Have children and they're unfolded */
+ n += callchain_node__count_rows_rb_tree(child);
+ }
+
+ return n;
+}
+
+static int callchain_node__count_rows(struct callchain_node *node)
+{
+ struct callchain_list *chain;
+ bool unfolded = false;
+ int n = 0;
+
+ list_for_each_entry(chain, &node->val, list) {
+ ++n;
+ unfolded = chain->ms.unfolded;
+ }
+
+ if (unfolded)
+ n += callchain_node__count_rows_rb_tree(node);
+
+ return n;
+}
+
+static int callchain__count_rows(struct rb_root *chain)
+{
+ struct rb_node *nd;
+ int n = 0;
+
+ for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
+ struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
+ n += callchain_node__count_rows(node);
+ }
+
+ return n;
+}
+
+static bool map_symbol__toggle_fold(struct map_symbol *ms)
+{
+ if (!ms)
+ return false;
+
+ if (!ms->has_children)
+ return false;
+
+ ms->unfolded = !ms->unfolded;
+ return true;
+}
+
+static void callchain_node__init_have_children_rb_tree(struct callchain_node *node)
+{
+ struct rb_node *nd = rb_first(&node->rb_root);
+
+ for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
+ struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
+ struct callchain_list *chain;
+ bool first = true;
+
+ list_for_each_entry(chain, &child->val, list) {
+ if (first) {
+ first = false;
+ chain->ms.has_children = chain->list.next != &child->val ||
+ !RB_EMPTY_ROOT(&child->rb_root);
+ } else
+ chain->ms.has_children = chain->list.next == &child->val &&
+ !RB_EMPTY_ROOT(&child->rb_root);
+ }
+
+ callchain_node__init_have_children_rb_tree(child);
+ }
+}
+
+static void callchain_node__init_have_children(struct callchain_node *node)
+{
+ struct callchain_list *chain;
+
+ list_for_each_entry(chain, &node->val, list)
+ chain->ms.has_children = !RB_EMPTY_ROOT(&node->rb_root);
+
+ callchain_node__init_have_children_rb_tree(node);
+}
+
+static void callchain__init_have_children(struct rb_root *root)
+{
+ struct rb_node *nd;
+
+ for (nd = rb_first(root); nd; nd = rb_next(nd)) {
+ struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
+ callchain_node__init_have_children(node);
+ }
+}
+
+static void hist_entry__init_have_children(struct hist_entry *he)
+{
+ if (!he->init_have_children) {
+ he->ms.has_children = !RB_EMPTY_ROOT(&he->sorted_chain);
+ callchain__init_have_children(&he->sorted_chain);
+ he->init_have_children = true;
+ }
+}
+
+static bool hist_browser__toggle_fold(struct hist_browser *browser)
+{
+ if (map_symbol__toggle_fold(browser->selection)) {
+ struct hist_entry *he = browser->he_selection;
+
+ hist_entry__init_have_children(he);
+ browser->b.nr_entries -= he->nr_rows;
+ browser->nr_callchain_rows -= he->nr_rows;
+
+ if (he->ms.unfolded)
+ he->nr_rows = callchain__count_rows(&he->sorted_chain);
+ else
+ he->nr_rows = 0;
+
+ browser->b.nr_entries += he->nr_rows;
+ browser->nr_callchain_rows += he->nr_rows;
+
+ return true;
+ }
+
+ /* If it doesn't have children, no toggling performed */
+ return false;
+}
+
+static int callchain_node__set_folding_rb_tree(struct callchain_node *node, bool unfold)
+{
+ int n = 0;
+ struct rb_node *nd;
+
+ for (nd = rb_first(&node->rb_root); nd; nd = rb_next(nd)) {
+ struct callchain_node *child = rb_entry(nd, struct callchain_node, rb_node);
+ struct callchain_list *chain;
+ bool has_children = false;
+
+ list_for_each_entry(chain, &child->val, list) {
+ ++n;
+ map_symbol__set_folding(&chain->ms, unfold);
+ has_children = chain->ms.has_children;
+ }
+
+ if (has_children)
+ n += callchain_node__set_folding_rb_tree(child, unfold);
+ }
+
+ return n;
+}
+
+static int callchain_node__set_folding(struct callchain_node *node, bool unfold)
+{
+ struct callchain_list *chain;
+ bool has_children = false;
+ int n = 0;
+
+ list_for_each_entry(chain, &node->val, list) {
+ ++n;
+ map_symbol__set_folding(&chain->ms, unfold);
+ has_children = chain->ms.has_children;
+ }
+
+ if (has_children)
+ n += callchain_node__set_folding_rb_tree(node, unfold);
+
+ return n;
+}
+
+static int callchain__set_folding(struct rb_root *chain, bool unfold)
+{
+ struct rb_node *nd;
+ int n = 0;
+
+ for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
+ struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
+ n += callchain_node__set_folding(node, unfold);
+ }
+
+ return n;
+}
+
+static void hist_entry__set_folding(struct hist_entry *he, bool unfold)
+{
+ hist_entry__init_have_children(he);
+ map_symbol__set_folding(&he->ms, unfold);
+
+ if (he->ms.has_children) {
+ int n = callchain__set_folding(&he->sorted_chain, unfold);
+ he->nr_rows = unfold ? n : 0;
+ } else
+ he->nr_rows = 0;
+}
+
+static void
+__hist_browser__set_folding(struct hist_browser *browser, bool unfold)
+{
+ struct rb_node *nd;
+ struct hists *hists = browser->hists;
+
+ for (nd = rb_first(&hists->entries);
+ (nd = hists__filter_entries(nd, browser->min_pcnt)) != NULL;
+ nd = rb_next(nd)) {
+ struct hist_entry *he = rb_entry(nd, struct hist_entry, rb_node);
+ hist_entry__set_folding(he, unfold);
+ browser->nr_callchain_rows += he->nr_rows;
+ }
+}
+
+static void hist_browser__set_folding(struct hist_browser *browser, bool unfold)
+{
+ browser->nr_callchain_rows = 0;
+ __hist_browser__set_folding(browser, unfold);
+
+ browser->b.nr_entries = hist_browser__nr_entries(browser);
+ /* Go to the start, we may be way after valid entries after a collapse */
+ ui_browser__reset_index(&browser->b);
+}
+
+static void ui_browser__warn_lost_events(struct ui_browser *browser)
+{
+ ui_browser__warning(browser, 4,
+ "Events are being lost, check IO/CPU overload!\n\n"
+ "You may want to run 'perf' using a RT scheduler policy:\n\n"
+ " perf top -r 80\n\n"
+ "Or reduce the sampling frequency.");
+}
+
+static int hist_browser__run(struct hist_browser *browser, const char *ev_name,
+ struct hist_browser_timer *hbt)
+{
+ int key;
+ char title[160];
+ int delay_secs = hbt ? hbt->refresh : 0;
+
+ browser->b.entries = &browser->hists->entries;
+ browser->b.nr_entries = hist_browser__nr_entries(browser);
+
+ hist_browser__refresh_dimensions(browser);
+ hists__browser_title(browser->hists, title, sizeof(title), ev_name);
+
+ if (ui_browser__show(&browser->b, title,
+ "Press '?' for help on key bindings") < 0)
+ return -1;
+
+ while (1) {
+ key = ui_browser__run(&browser->b, delay_secs);
+
+ switch (key) {
+ case K_TIMER: {
+ u64 nr_entries;
+ hbt->timer(hbt->arg);
+
+ if (hist_browser__has_filter(browser))
+ hist_browser__update_nr_entries(browser);
+
+ nr_entries = hist_browser__nr_entries(browser);
+ ui_browser__update_nr_entries(&browser->b, nr_entries);
+
+ if (browser->hists->stats.nr_lost_warned !=
+ browser->hists->stats.nr_events[PERF_RECORD_LOST]) {
+ browser->hists->stats.nr_lost_warned =
+ browser->hists->stats.nr_events[PERF_RECORD_LOST];
+ ui_browser__warn_lost_events(&browser->b);
+ }
+
+ hists__browser_title(browser->hists, title, sizeof(title), ev_name);
+ ui_browser__show_title(&browser->b, title);
+ continue;
+ }
+ case 'D': { /* Debug */
+ static int seq;
+ struct hist_entry *h = rb_entry(browser->b.top,
+ struct hist_entry, rb_node);
+ ui_helpline__pop();
+ ui_helpline__fpush("%d: nr_ent=(%d,%d), height=%d, idx=%d, fve: idx=%d, row_off=%d, nrows=%d",
+ seq++, browser->b.nr_entries,
+ browser->hists->nr_entries,
+ browser->b.height,
+ browser->b.index,
+ browser->b.top_idx,
+ h->row_offset, h->nr_rows);
+ }
+ break;
+ case 'C':
+ /* Collapse the whole world. */
+ hist_browser__set_folding(browser, false);
+ break;
+ case 'E':
+ /* Expand the whole world. */
+ hist_browser__set_folding(browser, true);
+ break;
+ case K_ENTER:
+ if (hist_browser__toggle_fold(browser))
+ break;
+ /* fall thru */
+ default:
+ goto out;
+ }
+ }
+out:
+ ui_browser__hide(&browser->b);
+ return key;
+}
+
+static char *callchain_list__sym_name(struct callchain_list *cl,
+ char *bf, size_t bfsize, bool show_dso)
+{
+ int printed;
+
+ if (cl->ms.sym)
+ printed = scnprintf(bf, bfsize, "%s", cl->ms.sym->name);
+ else
+ printed = scnprintf(bf, bfsize, "%#" PRIx64, cl->ip);
+
+ if (show_dso)
+ scnprintf(bf + printed, bfsize - printed, " %s",
+ cl->ms.map ? cl->ms.map->dso->short_name : "unknown");
+
+ return bf;
+}
+
+#define LEVEL_OFFSET_STEP 3
+
+static int hist_browser__show_callchain_node_rb_tree(struct hist_browser *browser,
+ struct callchain_node *chain_node,
+ u64 total, int level,
+ unsigned short row,
+ off_t *row_offset,
+ bool *is_current_entry)
+{
+ struct rb_node *node;
+ int first_row = row, width, offset = level * LEVEL_OFFSET_STEP;
+ u64 new_total, remaining;
+
+ if (callchain_param.mode == CHAIN_GRAPH_REL)
+ new_total = chain_node->children_hit;
+ else
+ new_total = total;
+
+ remaining = new_total;
+ node = rb_first(&chain_node->rb_root);
+ while (node) {
+ struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
+ struct rb_node *next = rb_next(node);
+ u64 cumul = callchain_cumul_hits(child);
+ struct callchain_list *chain;
+ char folded_sign = ' ';
+ int first = true;
+ int extra_offset = 0;
+
+ remaining -= cumul;
+
+ list_for_each_entry(chain, &child->val, list) {
+ char bf[1024], *alloc_str;
+ const char *str;
+ int color;
+ bool was_first = first;
+
+ if (first)
+ first = false;
+ else
+ extra_offset = LEVEL_OFFSET_STEP;
+
+ folded_sign = callchain_list__folded(chain);
+ if (*row_offset != 0) {
+ --*row_offset;
+ goto do_next;
+ }
+
+ alloc_str = NULL;
+ str = callchain_list__sym_name(chain, bf, sizeof(bf),
+ browser->show_dso);
+ if (was_first) {
+ double percent = cumul * 100.0 / new_total;
+
+ if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
+ str = "Not enough memory!";
+ else
+ str = alloc_str;
+ }
+
+ color = HE_COLORSET_NORMAL;
+ width = browser->b.width - (offset + extra_offset + 2);
+ if (ui_browser__is_current_entry(&browser->b, row)) {
+ browser->selection = &chain->ms;
+ color = HE_COLORSET_SELECTED;
+ *is_current_entry = true;
+ }
+
+ ui_browser__set_color(&browser->b, color);
+ ui_browser__gotorc(&browser->b, row, 0);
+ slsmg_write_nstring(" ", offset + extra_offset);
+ slsmg_printf("%c ", folded_sign);
+ slsmg_write_nstring(str, width);
+ free(alloc_str);
+
+ if (++row == browser->b.height)
+ goto out;
+do_next:
+ if (folded_sign == '+')
+ break;
+ }
+
+ if (folded_sign == '-') {
+ const int new_level = level + (extra_offset ? 2 : 1);
+ row += hist_browser__show_callchain_node_rb_tree(browser, child, new_total,
+ new_level, row, row_offset,
+ is_current_entry);
+ }
+ if (row == browser->b.height)
+ goto out;
+ node = next;
+ }
+out:
+ return row - first_row;
+}
+
+static int hist_browser__show_callchain_node(struct hist_browser *browser,
+ struct callchain_node *node,
+ int level, unsigned short row,
+ off_t *row_offset,
+ bool *is_current_entry)
+{
+ struct callchain_list *chain;
+ int first_row = row,
+ offset = level * LEVEL_OFFSET_STEP,
+ width = browser->b.width - offset;
+ char folded_sign = ' ';
+
+ list_for_each_entry(chain, &node->val, list) {
+ char bf[1024], *s;
+ int color;
+
+ folded_sign = callchain_list__folded(chain);
+
+ if (*row_offset != 0) {
+ --*row_offset;
+ continue;
+ }
+
+ color = HE_COLORSET_NORMAL;
+ if (ui_browser__is_current_entry(&browser->b, row)) {
+ browser->selection = &chain->ms;
+ color = HE_COLORSET_SELECTED;
+ *is_current_entry = true;
+ }
+
+ s = callchain_list__sym_name(chain, bf, sizeof(bf),
+ browser->show_dso);
+ ui_browser__gotorc(&browser->b, row, 0);
+ ui_browser__set_color(&browser->b, color);
+ slsmg_write_nstring(" ", offset);
+ slsmg_printf("%c ", folded_sign);
+ slsmg_write_nstring(s, width - 2);
+
+ if (++row == browser->b.height)
+ goto out;
+ }
+
+ if (folded_sign == '-')
+ row += hist_browser__show_callchain_node_rb_tree(browser, node,
+ browser->hists->stats.total_period,
+ level + 1, row,
+ row_offset,
+ is_current_entry);
+out:
+ return row - first_row;
+}
+
+static int hist_browser__show_callchain(struct hist_browser *browser,
+ struct rb_root *chain,
+ int level, unsigned short row,
+ off_t *row_offset,
+ bool *is_current_entry)
+{
+ struct rb_node *nd;
+ int first_row = row;
+
+ for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
+ struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
+
+ row += hist_browser__show_callchain_node(browser, node, level,
+ row, row_offset,
+ is_current_entry);
+ if (row == browser->b.height)
+ break;
+ }
+
+ return row - first_row;
+}
+
+struct hpp_arg {
+ struct ui_browser *b;
+ char folded_sign;
+ bool current_entry;
+};
+
+static int __hpp__slsmg_color_printf(struct perf_hpp *hpp, const char *fmt, ...)
+{
+ struct hpp_arg *arg = hpp->ptr;
+ int ret;
+ va_list args;
+ double percent;
+
+ va_start(args, fmt);
+ percent = va_arg(args, double);
+ va_end(args);
+
+ ui_browser__set_percent_color(arg->b, percent, arg->current_entry);
+
+ ret = scnprintf(hpp->buf, hpp->size, fmt, percent);
+ slsmg_printf("%s", hpp->buf);
+
+ advance_hpp(hpp, ret);
+ return ret;
+}
+
+#define __HPP_COLOR_PERCENT_FN(_type, _field) \
+static u64 __hpp_get_##_field(struct hist_entry *he) \
+{ \
+ return he->stat._field; \
+} \
+ \
+static int \
+hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\
+ struct perf_hpp *hpp, \
+ struct hist_entry *he) \
+{ \
+ return __hpp__fmt(hpp, he, __hpp_get_##_field, " %6.2f%%", \
+ __hpp__slsmg_color_printf, true); \
+}
+
+#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \
+static u64 __hpp_get_acc_##_field(struct hist_entry *he) \
+{ \
+ return he->stat_acc->_field; \
+} \
+ \
+static int \
+hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\
+ struct perf_hpp *hpp, \
+ struct hist_entry *he) \
+{ \
+ if (!symbol_conf.cumulate_callchain) { \
+ int ret = scnprintf(hpp->buf, hpp->size, "%8s", "N/A"); \
+ slsmg_printf("%s", hpp->buf); \
+ \
+ return ret; \
+ } \
+ return __hpp__fmt(hpp, he, __hpp_get_acc_##_field, " %6.2f%%", \
+ __hpp__slsmg_color_printf, true); \
+}
+
+__HPP_COLOR_PERCENT_FN(overhead, period)
+__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys)
+__HPP_COLOR_PERCENT_FN(overhead_us, period_us)
+__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys)
+__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us)
+__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period)
+
+#undef __HPP_COLOR_PERCENT_FN
+#undef __HPP_COLOR_ACC_PERCENT_FN
+
+void hist_browser__init_hpp(void)
+{
+ perf_hpp__format[PERF_HPP__OVERHEAD].color =
+ hist_browser__hpp_color_overhead;
+ perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color =
+ hist_browser__hpp_color_overhead_sys;
+ perf_hpp__format[PERF_HPP__OVERHEAD_US].color =
+ hist_browser__hpp_color_overhead_us;
+ perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color =
+ hist_browser__hpp_color_overhead_guest_sys;
+ perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color =
+ hist_browser__hpp_color_overhead_guest_us;
+ perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color =
+ hist_browser__hpp_color_overhead_acc;
+}
+
+static int hist_browser__show_entry(struct hist_browser *browser,
+ struct hist_entry *entry,
+ unsigned short row)
+{
+ char s[256];
+ int printed = 0;
+ int width = browser->b.width;
+ char folded_sign = ' ';
+ bool current_entry = ui_browser__is_current_entry(&browser->b, row);
+ off_t row_offset = entry->row_offset;
+ bool first = true;
+ struct perf_hpp_fmt *fmt;
+
+ if (current_entry) {
+ browser->he_selection = entry;
+ browser->selection = &entry->ms;
+ }
+
+ if (symbol_conf.use_callchain) {
+ hist_entry__init_have_children(entry);
+ folded_sign = hist_entry__folded(entry);
+ }
+
+ if (row_offset == 0) {
+ struct hpp_arg arg = {
+ .b = &browser->b,
+ .folded_sign = folded_sign,
+ .current_entry = current_entry,
+ };
+ struct perf_hpp hpp = {
+ .buf = s,
+ .size = sizeof(s),
+ .ptr = &arg,
+ };
+
+ ui_browser__gotorc(&browser->b, row, 0);
+
+ perf_hpp__for_each_format(fmt) {
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ if (current_entry && browser->b.navkeypressed) {
+ ui_browser__set_color(&browser->b,
+ HE_COLORSET_SELECTED);
+ } else {
+ ui_browser__set_color(&browser->b,
+ HE_COLORSET_NORMAL);
+ }
+
+ if (first) {
+ if (symbol_conf.use_callchain) {
+ slsmg_printf("%c ", folded_sign);
+ width -= 2;
+ }
+ first = false;
+ } else {
+ slsmg_printf(" ");
+ width -= 2;
+ }
+
+ if (fmt->color) {
+ width -= fmt->color(fmt, &hpp, entry);
+ } else {
+ width -= fmt->entry(fmt, &hpp, entry);
+ slsmg_printf("%s", s);
+ }
+ }
+
+ /* The scroll bar isn't being used */
+ if (!browser->b.navkeypressed)
+ width += 1;
+
+ slsmg_write_nstring("", width);
+
+ ++row;
+ ++printed;
+ } else
+ --row_offset;
+
+ if (folded_sign == '-' && row != browser->b.height) {
+ printed += hist_browser__show_callchain(browser, &entry->sorted_chain,
+ 1, row, &row_offset,
+ &current_entry);
+ if (current_entry)
+ browser->he_selection = entry;
+ }
+
+ return printed;
+}
+
+static void ui_browser__hists_init_top(struct ui_browser *browser)
+{
+ if (browser->top == NULL) {
+ struct hist_browser *hb;
+
+ hb = container_of(browser, struct hist_browser, b);
+ browser->top = rb_first(&hb->hists->entries);
+ }
+}
+
+static unsigned int hist_browser__refresh(struct ui_browser *browser)
+{
+ unsigned row = 0;
+ struct rb_node *nd;
+ struct hist_browser *hb = container_of(browser, struct hist_browser, b);
+
+ ui_browser__hists_init_top(browser);
+
+ for (nd = browser->top; nd; nd = rb_next(nd)) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+ float percent;
+
+ if (h->filtered)
+ continue;
+
+ percent = hist_entry__get_percent_limit(h);
+ if (percent < hb->min_pcnt)
+ continue;
+
+ row += hist_browser__show_entry(hb, h, row);
+ if (row == browser->height)
+ break;
+ }
+
+ return row;
+}
+
+static struct rb_node *hists__filter_entries(struct rb_node *nd,
+ float min_pcnt)
+{
+ while (nd != NULL) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+ float percent = hist_entry__get_percent_limit(h);
+
+ if (!h->filtered && percent >= min_pcnt)
+ return nd;
+
+ nd = rb_next(nd);
+ }
+
+ return NULL;
+}
+
+static struct rb_node *hists__filter_prev_entries(struct rb_node *nd,
+ float min_pcnt)
+{
+ while (nd != NULL) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+ float percent = hist_entry__get_percent_limit(h);
+
+ if (!h->filtered && percent >= min_pcnt)
+ return nd;
+
+ nd = rb_prev(nd);
+ }
+
+ return NULL;
+}
+
+static void ui_browser__hists_seek(struct ui_browser *browser,
+ off_t offset, int whence)
+{
+ struct hist_entry *h;
+ struct rb_node *nd;
+ bool first = true;
+ struct hist_browser *hb;
+
+ hb = container_of(browser, struct hist_browser, b);
+
+ if (browser->nr_entries == 0)
+ return;
+
+ ui_browser__hists_init_top(browser);
+
+ switch (whence) {
+ case SEEK_SET:
+ nd = hists__filter_entries(rb_first(browser->entries),
+ hb->min_pcnt);
+ break;
+ case SEEK_CUR:
+ nd = browser->top;
+ goto do_offset;
+ case SEEK_END:
+ nd = hists__filter_prev_entries(rb_last(browser->entries),
+ hb->min_pcnt);
+ first = false;
+ break;
+ default:
+ return;
+ }
+
+ /*
+ * Moves not relative to the first visible entry invalidates its
+ * row_offset:
+ */
+ h = rb_entry(browser->top, struct hist_entry, rb_node);
+ h->row_offset = 0;
+
+ /*
+ * Here we have to check if nd is expanded (+), if it is we can't go
+ * the next top level hist_entry, instead we must compute an offset of
+ * what _not_ to show and not change the first visible entry.
+ *
+ * This offset increments when we are going from top to bottom and
+ * decreases when we're going from bottom to top.
+ *
+ * As we don't have backpointers to the top level in the callchains
+ * structure, we need to always print the whole hist_entry callchain,
+ * skipping the first ones that are before the first visible entry
+ * and stop when we printed enough lines to fill the screen.
+ */
+do_offset:
+ if (offset > 0) {
+ do {
+ h = rb_entry(nd, struct hist_entry, rb_node);
+ if (h->ms.unfolded) {
+ u16 remaining = h->nr_rows - h->row_offset;
+ if (offset > remaining) {
+ offset -= remaining;
+ h->row_offset = 0;
+ } else {
+ h->row_offset += offset;
+ offset = 0;
+ browser->top = nd;
+ break;
+ }
+ }
+ nd = hists__filter_entries(rb_next(nd), hb->min_pcnt);
+ if (nd == NULL)
+ break;
+ --offset;
+ browser->top = nd;
+ } while (offset != 0);
+ } else if (offset < 0) {
+ while (1) {
+ h = rb_entry(nd, struct hist_entry, rb_node);
+ if (h->ms.unfolded) {
+ if (first) {
+ if (-offset > h->row_offset) {
+ offset += h->row_offset;
+ h->row_offset = 0;
+ } else {
+ h->row_offset += offset;
+ offset = 0;
+ browser->top = nd;
+ break;
+ }
+ } else {
+ if (-offset > h->nr_rows) {
+ offset += h->nr_rows;
+ h->row_offset = 0;
+ } else {
+ h->row_offset = h->nr_rows + offset;
+ offset = 0;
+ browser->top = nd;
+ break;
+ }
+ }
+ }
+
+ nd = hists__filter_prev_entries(rb_prev(nd),
+ hb->min_pcnt);
+ if (nd == NULL)
+ break;
+ ++offset;
+ browser->top = nd;
+ if (offset == 0) {
+ /*
+ * Last unfiltered hist_entry, check if it is
+ * unfolded, if it is then we should have
+ * row_offset at its last entry.
+ */
+ h = rb_entry(nd, struct hist_entry, rb_node);
+ if (h->ms.unfolded)
+ h->row_offset = h->nr_rows;
+ break;
+ }
+ first = false;
+ }
+ } else {
+ browser->top = nd;
+ h = rb_entry(nd, struct hist_entry, rb_node);
+ h->row_offset = 0;
+ }
+}
+
+static int hist_browser__fprintf_callchain_node_rb_tree(struct hist_browser *browser,
+ struct callchain_node *chain_node,
+ u64 total, int level,
+ FILE *fp)
+{
+ struct rb_node *node;
+ int offset = level * LEVEL_OFFSET_STEP;
+ u64 new_total, remaining;
+ int printed = 0;
+
+ if (callchain_param.mode == CHAIN_GRAPH_REL)
+ new_total = chain_node->children_hit;
+ else
+ new_total = total;
+
+ remaining = new_total;
+ node = rb_first(&chain_node->rb_root);
+ while (node) {
+ struct callchain_node *child = rb_entry(node, struct callchain_node, rb_node);
+ struct rb_node *next = rb_next(node);
+ u64 cumul = callchain_cumul_hits(child);
+ struct callchain_list *chain;
+ char folded_sign = ' ';
+ int first = true;
+ int extra_offset = 0;
+
+ remaining -= cumul;
+
+ list_for_each_entry(chain, &child->val, list) {
+ char bf[1024], *alloc_str;
+ const char *str;
+ bool was_first = first;
+
+ if (first)
+ first = false;
+ else
+ extra_offset = LEVEL_OFFSET_STEP;
+
+ folded_sign = callchain_list__folded(chain);
+
+ alloc_str = NULL;
+ str = callchain_list__sym_name(chain, bf, sizeof(bf),
+ browser->show_dso);
+ if (was_first) {
+ double percent = cumul * 100.0 / new_total;
+
+ if (asprintf(&alloc_str, "%2.2f%% %s", percent, str) < 0)
+ str = "Not enough memory!";
+ else
+ str = alloc_str;
+ }
+
+ printed += fprintf(fp, "%*s%c %s\n", offset + extra_offset, " ", folded_sign, str);
+ free(alloc_str);
+ if (folded_sign == '+')
+ break;
+ }
+
+ if (folded_sign == '-') {
+ const int new_level = level + (extra_offset ? 2 : 1);
+ printed += hist_browser__fprintf_callchain_node_rb_tree(browser, child, new_total,
+ new_level, fp);
+ }
+
+ node = next;
+ }
+
+ return printed;
+}
+
+static int hist_browser__fprintf_callchain_node(struct hist_browser *browser,
+ struct callchain_node *node,
+ int level, FILE *fp)
+{
+ struct callchain_list *chain;
+ int offset = level * LEVEL_OFFSET_STEP;
+ char folded_sign = ' ';
+ int printed = 0;
+
+ list_for_each_entry(chain, &node->val, list) {
+ char bf[1024], *s;
+
+ folded_sign = callchain_list__folded(chain);
+ s = callchain_list__sym_name(chain, bf, sizeof(bf), browser->show_dso);
+ printed += fprintf(fp, "%*s%c %s\n", offset, " ", folded_sign, s);
+ }
+
+ if (folded_sign == '-')
+ printed += hist_browser__fprintf_callchain_node_rb_tree(browser, node,
+ browser->hists->stats.total_period,
+ level + 1, fp);
+ return printed;
+}
+
+static int hist_browser__fprintf_callchain(struct hist_browser *browser,
+ struct rb_root *chain, int level, FILE *fp)
+{
+ struct rb_node *nd;
+ int printed = 0;
+
+ for (nd = rb_first(chain); nd; nd = rb_next(nd)) {
+ struct callchain_node *node = rb_entry(nd, struct callchain_node, rb_node);
+
+ printed += hist_browser__fprintf_callchain_node(browser, node, level, fp);
+ }
+
+ return printed;
+}
+
+static int hist_browser__fprintf_entry(struct hist_browser *browser,
+ struct hist_entry *he, FILE *fp)
+{
+ char s[8192];
+ int printed = 0;
+ char folded_sign = ' ';
+ struct perf_hpp hpp = {
+ .buf = s,
+ .size = sizeof(s),
+ };
+ struct perf_hpp_fmt *fmt;
+ bool first = true;
+ int ret;
+
+ if (symbol_conf.use_callchain)
+ folded_sign = hist_entry__folded(he);
+
+ if (symbol_conf.use_callchain)
+ printed += fprintf(fp, "%c ", folded_sign);
+
+ perf_hpp__for_each_format(fmt) {
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ if (!first) {
+ ret = scnprintf(hpp.buf, hpp.size, " ");
+ advance_hpp(&hpp, ret);
+ } else
+ first = false;
+
+ ret = fmt->entry(fmt, &hpp, he);
+ advance_hpp(&hpp, ret);
+ }
+ printed += fprintf(fp, "%s\n", rtrim(s));
+
+ if (folded_sign == '-')
+ printed += hist_browser__fprintf_callchain(browser, &he->sorted_chain, 1, fp);
+
+ return printed;
+}
+
+static int hist_browser__fprintf(struct hist_browser *browser, FILE *fp)
+{
+ struct rb_node *nd = hists__filter_entries(rb_first(browser->b.entries),
+ browser->min_pcnt);
+ int printed = 0;
+
+ while (nd) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+
+ printed += hist_browser__fprintf_entry(browser, h, fp);
+ nd = hists__filter_entries(rb_next(nd), browser->min_pcnt);
+ }
+
+ return printed;
+}
+
+static int hist_browser__dump(struct hist_browser *browser)
+{
+ char filename[64];
+ FILE *fp;
+
+ while (1) {
+ scnprintf(filename, sizeof(filename), "perf.hist.%d", browser->print_seq);
+ if (access(filename, F_OK))
+ break;
+ /*
+ * XXX: Just an arbitrary lazy upper limit
+ */
+ if (++browser->print_seq == 8192) {
+ ui_helpline__fpush("Too many perf.hist.N files, nothing written!");
+ return -1;
+ }
+ }
+
+ fp = fopen(filename, "w");
+ if (fp == NULL) {
+ char bf[64];
+ const char *err = strerror_r(errno, bf, sizeof(bf));
+ ui_helpline__fpush("Couldn't write to %s: %s", filename, err);
+ return -1;
+ }
+
+ ++browser->print_seq;
+ hist_browser__fprintf(browser, fp);
+ fclose(fp);
+ ui_helpline__fpush("%s written!", filename);
+
+ return 0;
+}
+
+static struct hist_browser *hist_browser__new(struct hists *hists)
+{
+ struct hist_browser *browser = zalloc(sizeof(*browser));
+
+ if (browser) {
+ browser->hists = hists;
+ browser->b.refresh = hist_browser__refresh;
+ browser->b.seek = ui_browser__hists_seek;
+ browser->b.use_navkeypressed = true;
+ }
+
+ return browser;
+}
+
+static void hist_browser__delete(struct hist_browser *browser)
+{
+ free(browser);
+}
+
+static struct hist_entry *hist_browser__selected_entry(struct hist_browser *browser)
+{
+ return browser->he_selection;
+}
+
+static struct thread *hist_browser__selected_thread(struct hist_browser *browser)
+{
+ return browser->he_selection->thread;
+}
+
+static int hists__browser_title(struct hists *hists, char *bf, size_t size,
+ const char *ev_name)
+{
+ char unit;
+ int printed;
+ const struct dso *dso = hists->dso_filter;
+ const struct thread *thread = hists->thread_filter;
+ unsigned long nr_samples = hists->stats.nr_events[PERF_RECORD_SAMPLE];
+ u64 nr_events = hists->stats.total_period;
+ struct perf_evsel *evsel = hists_to_evsel(hists);
+ char buf[512];
+ size_t buflen = sizeof(buf);
+
+ if (symbol_conf.filter_relative) {
+ nr_samples = hists->stats.nr_non_filtered_samples;
+ nr_events = hists->stats.total_non_filtered_period;
+ }
+
+ if (perf_evsel__is_group_event(evsel)) {
+ struct perf_evsel *pos;
+
+ perf_evsel__group_desc(evsel, buf, buflen);
+ ev_name = buf;
+
+ for_each_group_member(pos, evsel) {
+ if (symbol_conf.filter_relative) {
+ nr_samples += pos->hists.stats.nr_non_filtered_samples;
+ nr_events += pos->hists.stats.total_non_filtered_period;
+ } else {
+ nr_samples += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE];
+ nr_events += pos->hists.stats.total_period;
+ }
+ }
+ }
+
+ nr_samples = convert_unit(nr_samples, &unit);
+ printed = scnprintf(bf, size,
+ "Samples: %lu%c of event '%s', Event count (approx.): %lu",
+ nr_samples, unit, ev_name, nr_events);
+
+
+ if (hists->uid_filter_str)
+ printed += snprintf(bf + printed, size - printed,
+ ", UID: %s", hists->uid_filter_str);
+ if (thread)
+ printed += scnprintf(bf + printed, size - printed,
+ ", Thread: %s(%d)",
+ (thread->comm_set ? thread__comm_str(thread) : ""),
+ thread->tid);
+ if (dso)
+ printed += scnprintf(bf + printed, size - printed,
+ ", DSO: %s", dso->short_name);
+ return printed;
+}
+
+static inline void free_popup_options(char **options, int n)
+{
+ int i;
+
+ for (i = 0; i < n; ++i)
+ zfree(&options[i]);
+}
+
+/* Check whether the browser is for 'top' or 'report' */
+static inline bool is_report_browser(void *timer)
+{
+ return timer == NULL;
+}
+
+/*
+ * Only runtime switching of perf data file will make "input_name" point
+ * to a malloced buffer. So add "is_input_name_malloced" flag to decide
+ * whether we need to call free() for current "input_name" during the switch.
+ */
+static bool is_input_name_malloced = false;
+
+static int switch_data_file(void)
+{
+ char *pwd, *options[32], *abs_path[32], *tmp;
+ DIR *pwd_dir;
+ int nr_options = 0, choice = -1, ret = -1;
+ struct dirent *dent;
+
+ pwd = getenv("PWD");
+ if (!pwd)
+ return ret;
+
+ pwd_dir = opendir(pwd);
+ if (!pwd_dir)
+ return ret;
+
+ memset(options, 0, sizeof(options));
+ memset(options, 0, sizeof(abs_path));
+
+ while ((dent = readdir(pwd_dir))) {
+ char path[PATH_MAX];
+ u64 magic;
+ char *name = dent->d_name;
+ FILE *file;
+
+ if (!(dent->d_type == DT_REG))
+ continue;
+
+ snprintf(path, sizeof(path), "%s/%s", pwd, name);
+
+ file = fopen(path, "r");
+ if (!file)
+ continue;
+
+ if (fread(&magic, 1, 8, file) < 8)
+ goto close_file_and_continue;
+
+ if (is_perf_magic(magic)) {
+ options[nr_options] = strdup(name);
+ if (!options[nr_options])
+ goto close_file_and_continue;
+
+ abs_path[nr_options] = strdup(path);
+ if (!abs_path[nr_options]) {
+ zfree(&options[nr_options]);
+ ui__warning("Can't search all data files due to memory shortage.\n");
+ fclose(file);
+ break;
+ }
+
+ nr_options++;
+ }
+
+close_file_and_continue:
+ fclose(file);
+ if (nr_options >= 32) {
+ ui__warning("Too many perf data files in PWD!\n"
+ "Only the first 32 files will be listed.\n");
+ break;
+ }
+ }
+ closedir(pwd_dir);
+
+ if (nr_options) {
+ choice = ui__popup_menu(nr_options, options);
+ if (choice < nr_options && choice >= 0) {
+ tmp = strdup(abs_path[choice]);
+ if (tmp) {
+ if (is_input_name_malloced)
+ free((void *)input_name);
+ input_name = tmp;
+ is_input_name_malloced = true;
+ ret = 0;
+ } else
+ ui__warning("Data switch failed due to memory shortage!\n");
+ }
+ }
+
+ free_popup_options(options, nr_options);
+ free_popup_options(abs_path, nr_options);
+ return ret;
+}
+
+static void hist_browser__update_nr_entries(struct hist_browser *hb)
+{
+ u64 nr_entries = 0;
+ struct rb_node *nd = rb_first(&hb->hists->entries);
+
+ if (hb->min_pcnt == 0) {
+ hb->nr_non_filtered_entries = hb->hists->nr_non_filtered_entries;
+ return;
+ }
+
+ while ((nd = hists__filter_entries(nd, hb->min_pcnt)) != NULL) {
+ nr_entries++;
+ nd = rb_next(nd);
+ }
+
+ hb->nr_non_filtered_entries = nr_entries;
+}
+
+static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events,
+ const char *helpline, const char *ev_name,
+ bool left_exits,
+ struct hist_browser_timer *hbt,
+ float min_pcnt,
+ struct perf_session_env *env)
+{
+ struct hists *hists = &evsel->hists;
+ struct hist_browser *browser = hist_browser__new(hists);
+ struct branch_info *bi;
+ struct pstack *fstack;
+ char *options[16];
+ int nr_options = 0;
+ int key = -1;
+ char buf[64];
+ char script_opt[64];
+ int delay_secs = hbt ? hbt->refresh : 0;
+
+#define HIST_BROWSER_HELP_COMMON \
+ "h/?/F1 Show this window\n" \
+ "UP/DOWN/PGUP\n" \
+ "PGDN/SPACE Navigate\n" \
+ "q/ESC/CTRL+C Exit browser\n\n" \
+ "For multiple event sessions:\n\n" \
+ "TAB/UNTAB Switch events\n\n" \
+ "For symbolic views (--sort has sym):\n\n" \
+ "-> Zoom into DSO/Threads & Annotate current symbol\n" \
+ "<- Zoom out\n" \
+ "a Annotate current symbol\n" \
+ "C Collapse all callchains\n" \
+ "d Zoom into current DSO\n" \
+ "E Expand all callchains\n" \
+ "F Toggle percentage of filtered entries\n" \
+
+ /* help messages are sorted by lexical order of the hotkey */
+ const char report_help[] = HIST_BROWSER_HELP_COMMON
+ "i Show header information\n"
+ "P Print histograms to perf.hist.N\n"
+ "r Run available scripts\n"
+ "s Switch to another data file in PWD\n"
+ "t Zoom into current Thread\n"
+ "V Verbose (DSO names in callchains, etc)\n"
+ "/ Filter symbol by name";
+ const char top_help[] = HIST_BROWSER_HELP_COMMON
+ "P Print histograms to perf.hist.N\n"
+ "t Zoom into current Thread\n"
+ "V Verbose (DSO names in callchains, etc)\n"
+ "/ Filter symbol by name";
+
+ if (browser == NULL)
+ return -1;
+
+ if (min_pcnt) {
+ browser->min_pcnt = min_pcnt;
+ hist_browser__update_nr_entries(browser);
+ }
+
+ fstack = pstack__new(2);
+ if (fstack == NULL)
+ goto out;
+
+ ui_helpline__push(helpline);
+
+ memset(options, 0, sizeof(options));
+
+ while (1) {
+ const struct thread *thread = NULL;
+ const struct dso *dso = NULL;
+ int choice = 0,
+ annotate = -2, zoom_dso = -2, zoom_thread = -2,
+ annotate_f = -2, annotate_t = -2, browse_map = -2;
+ int scripts_comm = -2, scripts_symbol = -2,
+ scripts_all = -2, switch_data = -2;
+
+ nr_options = 0;
+
+ key = hist_browser__run(browser, ev_name, hbt);
+
+ if (browser->he_selection != NULL) {
+ thread = hist_browser__selected_thread(browser);
+ dso = browser->selection->map ? browser->selection->map->dso : NULL;
+ }
+ switch (key) {
+ case K_TAB:
+ case K_UNTAB:
+ if (nr_events == 1)
+ continue;
+ /*
+ * Exit the browser, let hists__browser_tree
+ * go to the next or previous
+ */
+ goto out_free_stack;
+ case 'a':
+ if (!sort__has_sym) {
+ ui_browser__warning(&browser->b, delay_secs * 2,
+ "Annotation is only available for symbolic views, "
+ "include \"sym*\" in --sort to use it.");
+ continue;
+ }
+
+ if (browser->selection == NULL ||
+ browser->selection->sym == NULL ||
+ browser->selection->map->dso->annotate_warned)
+ continue;
+ goto do_annotate;
+ case 'P':
+ hist_browser__dump(browser);
+ continue;
+ case 'd':
+ goto zoom_dso;
+ case 'V':
+ browser->show_dso = !browser->show_dso;
+ continue;
+ case 't':
+ goto zoom_thread;
+ case '/':
+ if (ui_browser__input_window("Symbol to show",
+ "Please enter the name of symbol you want to see",
+ buf, "ENTER: OK, ESC: Cancel",
+ delay_secs * 2) == K_ENTER) {
+ hists->symbol_filter_str = *buf ? buf : NULL;
+ hists__filter_by_symbol(hists);
+ hist_browser__reset(browser);
+ }
+ continue;
+ case 'r':
+ if (is_report_browser(hbt))
+ goto do_scripts;
+ continue;
+ case 's':
+ if (is_report_browser(hbt))
+ goto do_data_switch;
+ continue;
+ case 'i':
+ /* env->arch is NULL for live-mode (i.e. perf top) */
+ if (env->arch)
+ tui__header_window(env);
+ continue;
+ case 'F':
+ symbol_conf.filter_relative ^= 1;
+ continue;
+ case K_F1:
+ case 'h':
+ case '?':
+ ui_browser__help_window(&browser->b,
+ is_report_browser(hbt) ? report_help : top_help);
+ continue;
+ case K_ENTER:
+ case K_RIGHT:
+ /* menu */
+ break;
+ case K_LEFT: {
+ const void *top;
+
+ if (pstack__empty(fstack)) {
+ /*
+ * Go back to the perf_evsel_menu__run or other user
+ */
+ if (left_exits)
+ goto out_free_stack;
+ continue;
+ }
+ top = pstack__pop(fstack);
+ if (top == &browser->hists->dso_filter)
+ goto zoom_out_dso;
+ if (top == &browser->hists->thread_filter)
+ goto zoom_out_thread;
+ continue;
+ }
+ case K_ESC:
+ if (!left_exits &&
+ !ui_browser__dialog_yesno(&browser->b,
+ "Do you really want to exit?"))
+ continue;
+ /* Fall thru */
+ case 'q':
+ case CTRL('c'):
+ goto out_free_stack;
+ default:
+ continue;
+ }
+
+ if (!sort__has_sym)
+ goto add_exit_option;
+
+ if (sort__mode == SORT_MODE__BRANCH) {
+ bi = browser->he_selection->branch_info;
+ if (browser->selection != NULL &&
+ bi &&
+ bi->from.sym != NULL &&
+ !bi->from.map->dso->annotate_warned &&
+ asprintf(&options[nr_options], "Annotate %s",
+ bi->from.sym->name) > 0)
+ annotate_f = nr_options++;
+
+ if (browser->selection != NULL &&
+ bi &&
+ bi->to.sym != NULL &&
+ !bi->to.map->dso->annotate_warned &&
+ (bi->to.sym != bi->from.sym ||
+ bi->to.map->dso != bi->from.map->dso) &&
+ asprintf(&options[nr_options], "Annotate %s",
+ bi->to.sym->name) > 0)
+ annotate_t = nr_options++;
+ } else {
+ if (browser->selection != NULL &&
+ browser->selection->sym != NULL &&
+ !browser->selection->map->dso->annotate_warned) {
+ struct annotation *notes;
+
+ notes = symbol__annotation(browser->selection->sym);
+
+ if (notes->src &&
+ asprintf(&options[nr_options], "Annotate %s",
+ browser->selection->sym->name) > 0)
+ annotate = nr_options++;
+ }
+ }
+
+ if (thread != NULL &&
+ asprintf(&options[nr_options], "Zoom %s %s(%d) thread",
+ (browser->hists->thread_filter ? "out of" : "into"),
+ (thread->comm_set ? thread__comm_str(thread) : ""),
+ thread->tid) > 0)
+ zoom_thread = nr_options++;
+
+ if (dso != NULL &&
+ asprintf(&options[nr_options], "Zoom %s %s DSO",
+ (browser->hists->dso_filter ? "out of" : "into"),
+ (dso->kernel ? "the Kernel" : dso->short_name)) > 0)
+ zoom_dso = nr_options++;
+
+ if (browser->selection != NULL &&
+ browser->selection->map != NULL &&
+ asprintf(&options[nr_options], "Browse map details") > 0)
+ browse_map = nr_options++;
+
+ /* perf script support */
+ if (browser->he_selection) {
+ struct symbol *sym;
+
+ if (asprintf(&options[nr_options], "Run scripts for samples of thread [%s]",
+ thread__comm_str(browser->he_selection->thread)) > 0)
+ scripts_comm = nr_options++;
+
+ sym = browser->he_selection->ms.sym;
+ if (sym && sym->namelen &&
+ asprintf(&options[nr_options], "Run scripts for samples of symbol [%s]",
+ sym->name) > 0)
+ scripts_symbol = nr_options++;
+ }
+
+ if (asprintf(&options[nr_options], "Run scripts for all samples") > 0)
+ scripts_all = nr_options++;
+
+ if (is_report_browser(hbt) && asprintf(&options[nr_options],
+ "Switch to another data file in PWD") > 0)
+ switch_data = nr_options++;
+add_exit_option:
+ options[nr_options++] = (char *)"Exit";
+retry_popup_menu:
+ choice = ui__popup_menu(nr_options, options);
+
+ if (choice == nr_options - 1)
+ break;
+
+ if (choice == -1) {
+ free_popup_options(options, nr_options - 1);
+ continue;
+ }
+
+ if (choice == annotate || choice == annotate_t || choice == annotate_f) {
+ struct hist_entry *he;
+ struct annotation *notes;
+ int err;
+do_annotate:
+ if (!objdump_path && perf_session_env__lookup_objdump(env))
+ continue;
+
+ he = hist_browser__selected_entry(browser);
+ if (he == NULL)
+ continue;
+
+ /*
+ * we stash the branch_info symbol + map into the
+ * the ms so we don't have to rewrite all the annotation
+ * code to use branch_info.
+ * in branch mode, the ms struct is not used
+ */
+ if (choice == annotate_f) {
+ he->ms.sym = he->branch_info->from.sym;
+ he->ms.map = he->branch_info->from.map;
+ } else if (choice == annotate_t) {
+ he->ms.sym = he->branch_info->to.sym;
+ he->ms.map = he->branch_info->to.map;
+ }
+
+ notes = symbol__annotation(he->ms.sym);
+ if (!notes->src)
+ continue;
+
+ /*
+ * Don't let this be freed, say, by hists__decay_entry.
+ */
+ he->used = true;
+ err = hist_entry__tui_annotate(he, evsel, hbt);
+ he->used = false;
+ /*
+ * offer option to annotate the other branch source or target
+ * (if they exists) when returning from annotate
+ */
+ if ((err == 'q' || err == CTRL('c'))
+ && annotate_t != -2 && annotate_f != -2)
+ goto retry_popup_menu;
+
+ ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries);
+ if (err)
+ ui_browser__handle_resize(&browser->b);
+
+ } else if (choice == browse_map)
+ map__browse(browser->selection->map);
+ else if (choice == zoom_dso) {
+zoom_dso:
+ if (browser->hists->dso_filter) {
+ pstack__remove(fstack, &browser->hists->dso_filter);
+zoom_out_dso:
+ ui_helpline__pop();
+ browser->hists->dso_filter = NULL;
+ perf_hpp__set_elide(HISTC_DSO, false);
+ } else {
+ if (dso == NULL)
+ continue;
+ ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s DSO\"",
+ dso->kernel ? "the Kernel" : dso->short_name);
+ browser->hists->dso_filter = dso;
+ perf_hpp__set_elide(HISTC_DSO, true);
+ pstack__push(fstack, &browser->hists->dso_filter);
+ }
+ hists__filter_by_dso(hists);
+ hist_browser__reset(browser);
+ } else if (choice == zoom_thread) {
+zoom_thread:
+ if (browser->hists->thread_filter) {
+ pstack__remove(fstack, &browser->hists->thread_filter);
+zoom_out_thread:
+ ui_helpline__pop();
+ browser->hists->thread_filter = NULL;
+ perf_hpp__set_elide(HISTC_THREAD, false);
+ } else {
+ ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"",
+ thread->comm_set ? thread__comm_str(thread) : "",
+ thread->tid);
+ browser->hists->thread_filter = thread;
+ perf_hpp__set_elide(HISTC_THREAD, false);
+ pstack__push(fstack, &browser->hists->thread_filter);
+ }
+ hists__filter_by_thread(hists);
+ hist_browser__reset(browser);
+ }
+ /* perf scripts support */
+ else if (choice == scripts_all || choice == scripts_comm ||
+ choice == scripts_symbol) {
+do_scripts:
+ memset(script_opt, 0, 64);
+
+ if (choice == scripts_comm)
+ sprintf(script_opt, " -c %s ", thread__comm_str(browser->he_selection->thread));
+
+ if (choice == scripts_symbol)
+ sprintf(script_opt, " -S %s ", browser->he_selection->ms.sym->name);
+
+ script_browse(script_opt);
+ }
+ /* Switch to another data file */
+ else if (choice == switch_data) {
+do_data_switch:
+ if (!switch_data_file()) {
+ key = K_SWITCH_INPUT_DATA;
+ break;
+ } else
+ ui__warning("Won't switch the data files due to\n"
+ "no valid data file get selected!\n");
+ }
+ }
+out_free_stack:
+ pstack__delete(fstack);
+out:
+ hist_browser__delete(browser);
+ free_popup_options(options, nr_options - 1);
+ return key;
+}
+
+struct perf_evsel_menu {
+ struct ui_browser b;
+ struct perf_evsel *selection;
+ bool lost_events, lost_events_warned;
+ float min_pcnt;
+ struct perf_session_env *env;
+};
+
+static void perf_evsel_menu__write(struct ui_browser *browser,
+ void *entry, int row)
+{
+ struct perf_evsel_menu *menu = container_of(browser,
+ struct perf_evsel_menu, b);
+ struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
+ bool current_entry = ui_browser__is_current_entry(browser, row);
+ unsigned long nr_events = evsel->hists.stats.nr_events[PERF_RECORD_SAMPLE];
+ const char *ev_name = perf_evsel__name(evsel);
+ char bf[256], unit;
+ const char *warn = " ";
+ size_t printed;
+
+ ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
+ HE_COLORSET_NORMAL);
+
+ if (perf_evsel__is_group_event(evsel)) {
+ struct perf_evsel *pos;
+
+ ev_name = perf_evsel__group_name(evsel);
+
+ for_each_group_member(pos, evsel) {
+ nr_events += pos->hists.stats.nr_events[PERF_RECORD_SAMPLE];
+ }
+ }
+
+ nr_events = convert_unit(nr_events, &unit);
+ printed = scnprintf(bf, sizeof(bf), "%lu%c%s%s", nr_events,
+ unit, unit == ' ' ? "" : " ", ev_name);
+ slsmg_printf("%s", bf);
+
+ nr_events = evsel->hists.stats.nr_events[PERF_RECORD_LOST];
+ if (nr_events != 0) {
+ menu->lost_events = true;
+ if (!current_entry)
+ ui_browser__set_color(browser, HE_COLORSET_TOP);
+ nr_events = convert_unit(nr_events, &unit);
+ printed += scnprintf(bf, sizeof(bf), ": %ld%c%schunks LOST!",
+ nr_events, unit, unit == ' ' ? "" : " ");
+ warn = bf;
+ }
+
+ slsmg_write_nstring(warn, browser->width - printed);
+
+ if (current_entry)
+ menu->selection = evsel;
+}
+
+static int perf_evsel_menu__run(struct perf_evsel_menu *menu,
+ int nr_events, const char *help,
+ struct hist_browser_timer *hbt)
+{
+ struct perf_evlist *evlist = menu->b.priv;
+ struct perf_evsel *pos;
+ const char *ev_name, *title = "Available samples";
+ int delay_secs = hbt ? hbt->refresh : 0;
+ int key;
+
+ if (ui_browser__show(&menu->b, title,
+ "ESC: exit, ENTER|->: Browse histograms") < 0)
+ return -1;
+
+ while (1) {
+ key = ui_browser__run(&menu->b, delay_secs);
+
+ switch (key) {
+ case K_TIMER:
+ hbt->timer(hbt->arg);
+
+ if (!menu->lost_events_warned && menu->lost_events) {
+ ui_browser__warn_lost_events(&menu->b);
+ menu->lost_events_warned = true;
+ }
+ continue;
+ case K_RIGHT:
+ case K_ENTER:
+ if (!menu->selection)
+ continue;
+ pos = menu->selection;
+browse_hists:
+ perf_evlist__set_selected(evlist, pos);
+ /*
+ * Give the calling tool a chance to populate the non
+ * default evsel resorted hists tree.
+ */
+ if (hbt)
+ hbt->timer(hbt->arg);
+ ev_name = perf_evsel__name(pos);
+ key = perf_evsel__hists_browse(pos, nr_events, help,
+ ev_name, true, hbt,
+ menu->min_pcnt,
+ menu->env);
+ ui_browser__show_title(&menu->b, title);
+ switch (key) {
+ case K_TAB:
+ if (pos->node.next == &evlist->entries)
+ pos = perf_evlist__first(evlist);
+ else
+ pos = perf_evsel__next(pos);
+ goto browse_hists;
+ case K_UNTAB:
+ if (pos->node.prev == &evlist->entries)
+ pos = perf_evlist__last(evlist);
+ else
+ pos = perf_evsel__prev(pos);
+ goto browse_hists;
+ case K_ESC:
+ if (!ui_browser__dialog_yesno(&menu->b,
+ "Do you really want to exit?"))
+ continue;
+ /* Fall thru */
+ case K_SWITCH_INPUT_DATA:
+ case 'q':
+ case CTRL('c'):
+ goto out;
+ default:
+ continue;
+ }
+ case K_LEFT:
+ continue;
+ case K_ESC:
+ if (!ui_browser__dialog_yesno(&menu->b,
+ "Do you really want to exit?"))
+ continue;
+ /* Fall thru */
+ case 'q':
+ case CTRL('c'):
+ goto out;
+ default:
+ continue;
+ }
+ }
+
+out:
+ ui_browser__hide(&menu->b);
+ return key;
+}
+
+static bool filter_group_entries(struct ui_browser *browser __maybe_unused,
+ void *entry)
+{
+ struct perf_evsel *evsel = list_entry(entry, struct perf_evsel, node);
+
+ if (symbol_conf.event_group && !perf_evsel__is_group_leader(evsel))
+ return true;
+
+ return false;
+}
+
+static int __perf_evlist__tui_browse_hists(struct perf_evlist *evlist,
+ int nr_entries, const char *help,
+ struct hist_browser_timer *hbt,
+ float min_pcnt,
+ struct perf_session_env *env)
+{
+ struct perf_evsel *pos;
+ struct perf_evsel_menu menu = {
+ .b = {
+ .entries = &evlist->entries,
+ .refresh = ui_browser__list_head_refresh,
+ .seek = ui_browser__list_head_seek,
+ .write = perf_evsel_menu__write,
+ .filter = filter_group_entries,
+ .nr_entries = nr_entries,
+ .priv = evlist,
+ },
+ .min_pcnt = min_pcnt,
+ .env = env,
+ };
+
+ ui_helpline__push("Press ESC to exit");
+
+ evlist__for_each(evlist, pos) {
+ const char *ev_name = perf_evsel__name(pos);
+ size_t line_len = strlen(ev_name) + 7;
+
+ if (menu.b.width < line_len)
+ menu.b.width = line_len;
+ }
+
+ return perf_evsel_menu__run(&menu, nr_entries, help, hbt);
+}
+
+int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
+ struct hist_browser_timer *hbt,
+ float min_pcnt,
+ struct perf_session_env *env)
+{
+ int nr_entries = evlist->nr_entries;
+
+single_entry:
+ if (nr_entries == 1) {
+ struct perf_evsel *first = perf_evlist__first(evlist);
+ const char *ev_name = perf_evsel__name(first);
+
+ return perf_evsel__hists_browse(first, nr_entries, help,
+ ev_name, false, hbt, min_pcnt,
+ env);
+ }
+
+ if (symbol_conf.event_group) {
+ struct perf_evsel *pos;
+
+ nr_entries = 0;
+ evlist__for_each(evlist, pos) {
+ if (perf_evsel__is_group_leader(pos))
+ nr_entries++;
+ }
+
+ if (nr_entries == 1)
+ goto single_entry;
+ }
+
+ return __perf_evlist__tui_browse_hists(evlist, nr_entries, help,
+ hbt, min_pcnt, env);
+}
diff --git a/tools/perf/ui/browsers/map.c b/tools/perf/ui/browsers/map.c
new file mode 100644
index 00000000000..b11639f3368
--- /dev/null
+++ b/tools/perf/ui/browsers/map.c
@@ -0,0 +1,130 @@
+#include "../libslang.h"
+#include <elf.h>
+#include <inttypes.h>
+#include <sys/ttydefaults.h>
+#include <string.h>
+#include <linux/bitops.h>
+#include "../../util/util.h"
+#include "../../util/debug.h"
+#include "../../util/symbol.h"
+#include "../browser.h"
+#include "../helpline.h"
+#include "../keysyms.h"
+#include "map.h"
+
+struct map_browser {
+ struct ui_browser b;
+ struct map *map;
+ u8 addrlen;
+};
+
+static void map_browser__write(struct ui_browser *browser, void *nd, int row)
+{
+ struct symbol *sym = rb_entry(nd, struct symbol, rb_node);
+ struct map_browser *mb = container_of(browser, struct map_browser, b);
+ bool current_entry = ui_browser__is_current_entry(browser, row);
+ int width;
+
+ ui_browser__set_percent_color(browser, 0, current_entry);
+ slsmg_printf("%*" PRIx64 " %*" PRIx64 " %c ",
+ mb->addrlen, sym->start, mb->addrlen, sym->end,
+ sym->binding == STB_GLOBAL ? 'g' :
+ sym->binding == STB_LOCAL ? 'l' : 'w');
+ width = browser->width - ((mb->addrlen * 2) + 4);
+ if (width > 0)
+ slsmg_write_nstring(sym->name, width);
+}
+
+/* FIXME uber-kludgy, see comment on cmd_report... */
+static u32 *symbol__browser_index(struct symbol *browser)
+{
+ return ((void *)browser) - sizeof(struct rb_node) - sizeof(u32);
+}
+
+static int map_browser__search(struct map_browser *browser)
+{
+ char target[512];
+ struct symbol *sym;
+ int err = ui_browser__input_window("Search by name/addr",
+ "Prefix with 0x to search by address",
+ target, "ENTER: OK, ESC: Cancel", 0);
+ if (err != K_ENTER)
+ return -1;
+
+ if (target[0] == '0' && tolower(target[1]) == 'x') {
+ u64 addr = strtoull(target, NULL, 16);
+ sym = map__find_symbol(browser->map, addr, NULL);
+ } else
+ sym = map__find_symbol_by_name(browser->map, target, NULL);
+
+ if (sym != NULL) {
+ u32 *idx = symbol__browser_index(sym);
+
+ browser->b.top = &sym->rb_node;
+ browser->b.index = browser->b.top_idx = *idx;
+ } else
+ ui_helpline__fpush("%s not found!", target);
+
+ return 0;
+}
+
+static int map_browser__run(struct map_browser *browser)
+{
+ int key;
+
+ if (ui_browser__show(&browser->b, browser->map->dso->long_name,
+ "Press <- or ESC to exit, %s / to search",
+ verbose ? "" : "restart with -v to use") < 0)
+ return -1;
+
+ while (1) {
+ key = ui_browser__run(&browser->b, 0);
+
+ switch (key) {
+ case '/':
+ if (verbose)
+ map_browser__search(browser);
+ default:
+ break;
+ case K_LEFT:
+ case K_ESC:
+ case 'q':
+ case CTRL('c'):
+ goto out;
+ }
+ }
+out:
+ ui_browser__hide(&browser->b);
+ return key;
+}
+
+int map__browse(struct map *map)
+{
+ struct map_browser mb = {
+ .b = {
+ .entries = &map->dso->symbols[map->type],
+ .refresh = ui_browser__rb_tree_refresh,
+ .seek = ui_browser__rb_tree_seek,
+ .write = map_browser__write,
+ },
+ .map = map,
+ };
+ struct rb_node *nd;
+ char tmp[BITS_PER_LONG / 4];
+ u64 maxaddr = 0;
+
+ for (nd = rb_first(mb.b.entries); nd; nd = rb_next(nd)) {
+ struct symbol *pos = rb_entry(nd, struct symbol, rb_node);
+
+ if (maxaddr < pos->end)
+ maxaddr = pos->end;
+ if (verbose) {
+ u32 *idx = symbol__browser_index(pos);
+ *idx = mb.b.nr_entries;
+ }
+ ++mb.b.nr_entries;
+ }
+
+ mb.addrlen = snprintf(tmp, sizeof(tmp), "%" PRIx64, maxaddr);
+ return map_browser__run(&mb);
+}
diff --git a/tools/perf/ui/browsers/map.h b/tools/perf/ui/browsers/map.h
new file mode 100644
index 00000000000..2d58e4b3eb6
--- /dev/null
+++ b/tools/perf/ui/browsers/map.h
@@ -0,0 +1,6 @@
+#ifndef _PERF_UI_MAP_BROWSER_H_
+#define _PERF_UI_MAP_BROWSER_H_ 1
+struct map;
+
+int map__browse(struct map *map);
+#endif /* _PERF_UI_MAP_BROWSER_H_ */
diff --git a/tools/perf/ui/browsers/scripts.c b/tools/perf/ui/browsers/scripts.c
new file mode 100644
index 00000000000..402d2bd30b0
--- /dev/null
+++ b/tools/perf/ui/browsers/scripts.c
@@ -0,0 +1,187 @@
+#include <elf.h>
+#include <inttypes.h>
+#include <sys/ttydefaults.h>
+#include <string.h>
+#include "../../util/sort.h"
+#include "../../util/util.h"
+#include "../../util/hist.h"
+#include "../../util/debug.h"
+#include "../../util/symbol.h"
+#include "../browser.h"
+#include "../helpline.h"
+#include "../libslang.h"
+
+/* 2048 lines should be enough for a script output */
+#define MAX_LINES 2048
+
+/* 160 bytes for one output line */
+#define AVERAGE_LINE_LEN 160
+
+struct script_line {
+ struct list_head node;
+ char line[AVERAGE_LINE_LEN];
+};
+
+struct perf_script_browser {
+ struct ui_browser b;
+ struct list_head entries;
+ const char *script_name;
+ int nr_lines;
+};
+
+#define SCRIPT_NAMELEN 128
+#define SCRIPT_MAX_NO 64
+/*
+ * Usually the full path for a script is:
+ * /home/username/libexec/perf-core/scripts/python/xxx.py
+ * /home/username/libexec/perf-core/scripts/perl/xxx.pl
+ * So 256 should be long enough to contain the full path.
+ */
+#define SCRIPT_FULLPATH_LEN 256
+
+/*
+ * When success, will copy the full path of the selected script
+ * into the buffer pointed by script_name, and return 0.
+ * Return -1 on failure.
+ */
+static int list_scripts(char *script_name)
+{
+ char *buf, *names[SCRIPT_MAX_NO], *paths[SCRIPT_MAX_NO];
+ int i, num, choice, ret = -1;
+
+ /* Preset the script name to SCRIPT_NAMELEN */
+ buf = malloc(SCRIPT_MAX_NO * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN));
+ if (!buf)
+ return ret;
+
+ for (i = 0; i < SCRIPT_MAX_NO; i++) {
+ names[i] = buf + i * (SCRIPT_NAMELEN + SCRIPT_FULLPATH_LEN);
+ paths[i] = names[i] + SCRIPT_NAMELEN;
+ }
+
+ num = find_scripts(names, paths);
+ if (num > 0) {
+ choice = ui__popup_menu(num, names);
+ if (choice < num && choice >= 0) {
+ strcpy(script_name, paths[choice]);
+ ret = 0;
+ }
+ }
+
+ free(buf);
+ return ret;
+}
+
+static void script_browser__write(struct ui_browser *browser,
+ void *entry, int row)
+{
+ struct script_line *sline = list_entry(entry, struct script_line, node);
+ bool current_entry = ui_browser__is_current_entry(browser, row);
+
+ ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
+ HE_COLORSET_NORMAL);
+
+ slsmg_write_nstring(sline->line, browser->width);
+}
+
+static int script_browser__run(struct perf_script_browser *browser)
+{
+ int key;
+
+ if (ui_browser__show(&browser->b, browser->script_name,
+ "Press <- or ESC to exit") < 0)
+ return -1;
+
+ while (1) {
+ key = ui_browser__run(&browser->b, 0);
+
+ /* We can add some special key handling here if needed */
+ break;
+ }
+
+ ui_browser__hide(&browser->b);
+ return key;
+}
+
+
+int script_browse(const char *script_opt)
+{
+ char cmd[SCRIPT_FULLPATH_LEN*2], script_name[SCRIPT_FULLPATH_LEN];
+ char *line = NULL;
+ size_t len = 0;
+ ssize_t retlen;
+ int ret = -1, nr_entries = 0;
+ FILE *fp;
+ void *buf;
+ struct script_line *sline;
+
+ struct perf_script_browser script = {
+ .b = {
+ .refresh = ui_browser__list_head_refresh,
+ .seek = ui_browser__list_head_seek,
+ .write = script_browser__write,
+ },
+ .script_name = script_name,
+ };
+
+ INIT_LIST_HEAD(&script.entries);
+
+ /* Save each line of the output in one struct script_line object. */
+ buf = zalloc((sizeof(*sline)) * MAX_LINES);
+ if (!buf)
+ return -1;
+ sline = buf;
+
+ memset(script_name, 0, SCRIPT_FULLPATH_LEN);
+ if (list_scripts(script_name))
+ goto exit;
+
+ sprintf(cmd, "perf script -s %s ", script_name);
+
+ if (script_opt)
+ strcat(cmd, script_opt);
+
+ if (input_name) {
+ strcat(cmd, " -i ");
+ strcat(cmd, input_name);
+ }
+
+ strcat(cmd, " 2>&1");
+
+ fp = popen(cmd, "r");
+ if (!fp)
+ goto exit;
+
+ while ((retlen = getline(&line, &len, fp)) != -1) {
+ strncpy(sline->line, line, AVERAGE_LINE_LEN);
+
+ /* If one output line is very large, just cut it short */
+ if (retlen >= AVERAGE_LINE_LEN) {
+ sline->line[AVERAGE_LINE_LEN - 1] = '\0';
+ sline->line[AVERAGE_LINE_LEN - 2] = '\n';
+ }
+ list_add_tail(&sline->node, &script.entries);
+
+ if (script.b.width < retlen)
+ script.b.width = retlen;
+
+ if (nr_entries++ >= MAX_LINES - 1)
+ break;
+ sline++;
+ }
+
+ if (script.b.width > AVERAGE_LINE_LEN)
+ script.b.width = AVERAGE_LINE_LEN;
+
+ free(line);
+ pclose(fp);
+
+ script.nr_lines = nr_entries;
+ script.b.nr_entries = nr_entries;
+ script.b.entries = &script.entries;
+
+ ret = script_browser__run(&script);
+exit:
+ free(buf);
+ return ret;
+}
diff --git a/tools/perf/ui/gtk/annotate.c b/tools/perf/ui/gtk/annotate.c
new file mode 100644
index 00000000000..9c7ff8d31b2
--- /dev/null
+++ b/tools/perf/ui/gtk/annotate.c
@@ -0,0 +1,252 @@
+#include "gtk.h"
+#include "util/debug.h"
+#include "util/annotate.h"
+#include "util/evsel.h"
+#include "ui/helpline.h"
+
+
+enum {
+ ANN_COL__PERCENT,
+ ANN_COL__OFFSET,
+ ANN_COL__LINE,
+
+ MAX_ANN_COLS
+};
+
+static const char *const col_names[] = {
+ "Overhead",
+ "Offset",
+ "Line"
+};
+
+static int perf_gtk__get_percent(char *buf, size_t size, struct symbol *sym,
+ struct disasm_line *dl, int evidx)
+{
+ struct sym_hist *symhist;
+ double percent = 0.0;
+ const char *markup;
+ int ret = 0;
+
+ strcpy(buf, "");
+
+ if (dl->offset == (s64) -1)
+ return 0;
+
+ symhist = annotation__histogram(symbol__annotation(sym), evidx);
+ if (!symbol_conf.event_group && !symhist->addr[dl->offset])
+ return 0;
+
+ percent = 100.0 * symhist->addr[dl->offset] / symhist->sum;
+
+ markup = perf_gtk__get_percent_color(percent);
+ if (markup)
+ ret += scnprintf(buf, size, "%s", markup);
+ ret += scnprintf(buf + ret, size - ret, "%6.2f%%", percent);
+ if (markup)
+ ret += scnprintf(buf + ret, size - ret, "</span>");
+
+ return ret;
+}
+
+static int perf_gtk__get_offset(char *buf, size_t size, struct symbol *sym,
+ struct map *map, struct disasm_line *dl)
+{
+ u64 start = map__rip_2objdump(map, sym->start);
+
+ strcpy(buf, "");
+
+ if (dl->offset == (s64) -1)
+ return 0;
+
+ return scnprintf(buf, size, "%"PRIx64, start + dl->offset);
+}
+
+static int perf_gtk__get_line(char *buf, size_t size, struct disasm_line *dl)
+{
+ int ret = 0;
+ char *line = g_markup_escape_text(dl->line, -1);
+ const char *markup = "<span fgcolor='gray'>";
+
+ strcpy(buf, "");
+
+ if (!line)
+ return 0;
+
+ if (dl->offset != (s64) -1)
+ markup = NULL;
+
+ if (markup)
+ ret += scnprintf(buf, size, "%s", markup);
+ ret += scnprintf(buf + ret, size - ret, "%s", line);
+ if (markup)
+ ret += scnprintf(buf + ret, size - ret, "</span>");
+
+ g_free(line);
+ return ret;
+}
+
+static int perf_gtk__annotate_symbol(GtkWidget *window, struct symbol *sym,
+ struct map *map, struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt __maybe_unused)
+{
+ struct disasm_line *pos, *n;
+ struct annotation *notes;
+ GType col_types[MAX_ANN_COLS];
+ GtkCellRenderer *renderer;
+ GtkListStore *store;
+ GtkWidget *view;
+ int i;
+ char s[512];
+
+ notes = symbol__annotation(sym);
+
+ for (i = 0; i < MAX_ANN_COLS; i++) {
+ col_types[i] = G_TYPE_STRING;
+ }
+ store = gtk_list_store_newv(MAX_ANN_COLS, col_types);
+
+ view = gtk_tree_view_new();
+ renderer = gtk_cell_renderer_text_new();
+
+ for (i = 0; i < MAX_ANN_COLS; i++) {
+ gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
+ -1, col_names[i], renderer, "markup",
+ i, NULL);
+ }
+
+ gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store));
+ g_object_unref(GTK_TREE_MODEL(store));
+
+ list_for_each_entry(pos, &notes->src->source, node) {
+ GtkTreeIter iter;
+ int ret = 0;
+
+ gtk_list_store_append(store, &iter);
+
+ if (perf_evsel__is_group_event(evsel)) {
+ for (i = 0; i < evsel->nr_members; i++) {
+ ret += perf_gtk__get_percent(s + ret,
+ sizeof(s) - ret,
+ sym, pos,
+ evsel->idx + i);
+ ret += scnprintf(s + ret, sizeof(s) - ret, " ");
+ }
+ } else {
+ ret = perf_gtk__get_percent(s, sizeof(s), sym, pos,
+ evsel->idx);
+ }
+
+ if (ret)
+ gtk_list_store_set(store, &iter, ANN_COL__PERCENT, s, -1);
+ if (perf_gtk__get_offset(s, sizeof(s), sym, map, pos))
+ gtk_list_store_set(store, &iter, ANN_COL__OFFSET, s, -1);
+ if (perf_gtk__get_line(s, sizeof(s), pos))
+ gtk_list_store_set(store, &iter, ANN_COL__LINE, s, -1);
+ }
+
+ gtk_container_add(GTK_CONTAINER(window), view);
+
+ list_for_each_entry_safe(pos, n, &notes->src->source, node) {
+ list_del(&pos->node);
+ disasm_line__free(pos);
+ }
+
+ return 0;
+}
+
+static int symbol__gtk_annotate(struct symbol *sym, struct map *map,
+ struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt)
+{
+ GtkWidget *window;
+ GtkWidget *notebook;
+ GtkWidget *scrolled_window;
+ GtkWidget *tab_label;
+
+ if (map->dso->annotate_warned)
+ return -1;
+
+ if (symbol__annotate(sym, map, 0) < 0) {
+ ui__error("%s", ui_helpline__current);
+ return -1;
+ }
+
+ if (perf_gtk__is_active_context(pgctx)) {
+ window = pgctx->main_window;
+ notebook = pgctx->notebook;
+ } else {
+ GtkWidget *vbox;
+ GtkWidget *infobar;
+ GtkWidget *statbar;
+
+ signal(SIGSEGV, perf_gtk__signal);
+ signal(SIGFPE, perf_gtk__signal);
+ signal(SIGINT, perf_gtk__signal);
+ signal(SIGQUIT, perf_gtk__signal);
+ signal(SIGTERM, perf_gtk__signal);
+
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_title(GTK_WINDOW(window), "perf annotate");
+
+ g_signal_connect(window, "delete_event", gtk_main_quit, NULL);
+
+ pgctx = perf_gtk__activate_context(window);
+ if (!pgctx)
+ return -1;
+
+ vbox = gtk_vbox_new(FALSE, 0);
+ notebook = gtk_notebook_new();
+ pgctx->notebook = notebook;
+
+ gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0);
+
+ infobar = perf_gtk__setup_info_bar();
+ if (infobar) {
+ gtk_box_pack_start(GTK_BOX(vbox), infobar,
+ FALSE, FALSE, 0);
+ }
+
+ statbar = perf_gtk__setup_statusbar();
+ gtk_box_pack_start(GTK_BOX(vbox), statbar, FALSE, FALSE, 0);
+
+ gtk_container_add(GTK_CONTAINER(window), vbox);
+ }
+
+ scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+ tab_label = gtk_label_new(sym->name);
+
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled_window,
+ tab_label);
+
+ perf_gtk__annotate_symbol(scrolled_window, sym, map, evsel, hbt);
+ return 0;
+}
+
+int hist_entry__gtk_annotate(struct hist_entry *he,
+ struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt)
+{
+ return symbol__gtk_annotate(he->ms.sym, he->ms.map, evsel, hbt);
+}
+
+void perf_gtk__show_annotations(void)
+{
+ GtkWidget *window;
+
+ if (!perf_gtk__is_active_context(pgctx))
+ return;
+
+ window = pgctx->main_window;
+ gtk_widget_show_all(window);
+
+ perf_gtk__resize_window(window);
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+
+ gtk_main();
+
+ perf_gtk__deactivate_context(&pgctx);
+}
diff --git a/tools/perf/ui/gtk/browser.c b/tools/perf/ui/gtk/browser.c
new file mode 100644
index 00000000000..c24d9122129
--- /dev/null
+++ b/tools/perf/ui/gtk/browser.c
@@ -0,0 +1,87 @@
+#include "../evlist.h"
+#include "../cache.h"
+#include "../evsel.h"
+#include "../sort.h"
+#include "../hist.h"
+#include "../helpline.h"
+#include "gtk.h"
+
+#include <signal.h>
+
+void perf_gtk__signal(int sig)
+{
+ perf_gtk__exit(false);
+ psignal(sig, "perf");
+}
+
+void perf_gtk__resize_window(GtkWidget *window)
+{
+ GdkRectangle rect;
+ GdkScreen *screen;
+ int monitor;
+ int height;
+ int width;
+
+ screen = gtk_widget_get_screen(window);
+
+ monitor = gdk_screen_get_monitor_at_window(screen, window->window);
+
+ gdk_screen_get_monitor_geometry(screen, monitor, &rect);
+
+ width = rect.width * 3 / 4;
+ height = rect.height * 3 / 4;
+
+ gtk_window_resize(GTK_WINDOW(window), width, height);
+}
+
+const char *perf_gtk__get_percent_color(double percent)
+{
+ if (percent >= MIN_RED)
+ return "<span fgcolor='red'>";
+ if (percent >= MIN_GREEN)
+ return "<span fgcolor='dark green'>";
+ return NULL;
+}
+
+#ifdef HAVE_GTK_INFO_BAR_SUPPORT
+GtkWidget *perf_gtk__setup_info_bar(void)
+{
+ GtkWidget *info_bar;
+ GtkWidget *label;
+ GtkWidget *content_area;
+
+ info_bar = gtk_info_bar_new();
+ gtk_widget_set_no_show_all(info_bar, TRUE);
+
+ label = gtk_label_new("");
+ gtk_widget_show(label);
+
+ content_area = gtk_info_bar_get_content_area(GTK_INFO_BAR(info_bar));
+ gtk_container_add(GTK_CONTAINER(content_area), label);
+
+ gtk_info_bar_add_button(GTK_INFO_BAR(info_bar), GTK_STOCK_OK,
+ GTK_RESPONSE_OK);
+ g_signal_connect(info_bar, "response",
+ G_CALLBACK(gtk_widget_hide), NULL);
+
+ pgctx->info_bar = info_bar;
+ pgctx->message_label = label;
+
+ return info_bar;
+}
+#endif
+
+GtkWidget *perf_gtk__setup_statusbar(void)
+{
+ GtkWidget *stbar;
+ unsigned ctxid;
+
+ stbar = gtk_statusbar_new();
+
+ ctxid = gtk_statusbar_get_context_id(GTK_STATUSBAR(stbar),
+ "perf report");
+ pgctx->statbar = stbar;
+ pgctx->statbar_ctx_id = ctxid;
+
+ return stbar;
+}
diff --git a/tools/perf/ui/gtk/gtk.h b/tools/perf/ui/gtk/gtk.h
new file mode 100644
index 00000000000..0a9173ff9a6
--- /dev/null
+++ b/tools/perf/ui/gtk/gtk.h
@@ -0,0 +1,67 @@
+#ifndef _PERF_GTK_H_
+#define _PERF_GTK_H_ 1
+
+#include <stdbool.h>
+
+#pragma GCC diagnostic ignored "-Wstrict-prototypes"
+#include <gtk/gtk.h>
+#pragma GCC diagnostic error "-Wstrict-prototypes"
+
+
+struct perf_gtk_context {
+ GtkWidget *main_window;
+ GtkWidget *notebook;
+
+#ifdef HAVE_GTK_INFO_BAR_SUPPORT
+ GtkWidget *info_bar;
+ GtkWidget *message_label;
+#endif
+ GtkWidget *statbar;
+ guint statbar_ctx_id;
+};
+
+int perf_gtk__init(void);
+void perf_gtk__exit(bool wait_for_ok);
+
+extern struct perf_gtk_context *pgctx;
+
+static inline bool perf_gtk__is_active_context(struct perf_gtk_context *ctx)
+{
+ return ctx && ctx->main_window;
+}
+
+struct perf_gtk_context *perf_gtk__activate_context(GtkWidget *window);
+int perf_gtk__deactivate_context(struct perf_gtk_context **ctx);
+
+void perf_gtk__init_helpline(void);
+void gtk_ui_progress__init(void);
+void perf_gtk__init_hpp(void);
+
+void perf_gtk__signal(int sig);
+void perf_gtk__resize_window(GtkWidget *window);
+const char *perf_gtk__get_percent_color(double percent);
+GtkWidget *perf_gtk__setup_statusbar(void);
+
+#ifdef HAVE_GTK_INFO_BAR_SUPPORT
+GtkWidget *perf_gtk__setup_info_bar(void);
+#else
+static inline GtkWidget *perf_gtk__setup_info_bar(void)
+{
+ return NULL;
+}
+#endif
+
+struct perf_evsel;
+struct perf_evlist;
+struct hist_entry;
+struct hist_browser_timer;
+
+int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist, const char *help,
+ struct hist_browser_timer *hbt,
+ float min_pcnt);
+int hist_entry__gtk_annotate(struct hist_entry *he,
+ struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt);
+void perf_gtk__show_annotations(void);
+
+#endif /* _PERF_GTK_H_ */
diff --git a/tools/perf/ui/gtk/helpline.c b/tools/perf/ui/gtk/helpline.c
new file mode 100644
index 00000000000..3388cbd1218
--- /dev/null
+++ b/tools/perf/ui/gtk/helpline.c
@@ -0,0 +1,57 @@
+#include <stdio.h>
+#include <string.h>
+
+#include "gtk.h"
+#include "../ui.h"
+#include "../helpline.h"
+#include "../../util/debug.h"
+
+static void gtk_helpline_pop(void)
+{
+ if (!perf_gtk__is_active_context(pgctx))
+ return;
+
+ gtk_statusbar_pop(GTK_STATUSBAR(pgctx->statbar),
+ pgctx->statbar_ctx_id);
+}
+
+static void gtk_helpline_push(const char *msg)
+{
+ if (!perf_gtk__is_active_context(pgctx))
+ return;
+
+ gtk_statusbar_push(GTK_STATUSBAR(pgctx->statbar),
+ pgctx->statbar_ctx_id, msg);
+}
+
+static int gtk_helpline_show(const char *fmt, va_list ap)
+{
+ int ret;
+ char *ptr;
+ static int backlog;
+
+ ret = vscnprintf(ui_helpline__current + backlog,
+ sizeof(ui_helpline__current) - backlog, fmt, ap);
+ backlog += ret;
+
+ /* only first line can be displayed */
+ ptr = strchr(ui_helpline__current, '\n');
+ if (ptr && (ptr - ui_helpline__current) <= backlog) {
+ *ptr = '\0';
+ ui_helpline__puts(ui_helpline__current);
+ backlog = 0;
+ }
+
+ return ret;
+}
+
+static struct ui_helpline gtk_helpline_fns = {
+ .pop = gtk_helpline_pop,
+ .push = gtk_helpline_push,
+ .show = gtk_helpline_show,
+};
+
+void perf_gtk__init_helpline(void)
+{
+ helpline_fns = &gtk_helpline_fns;
+}
diff --git a/tools/perf/ui/gtk/hists.c b/tools/perf/ui/gtk/hists.c
new file mode 100644
index 00000000000..6ca60e482cd
--- /dev/null
+++ b/tools/perf/ui/gtk/hists.c
@@ -0,0 +1,365 @@
+#include "../evlist.h"
+#include "../cache.h"
+#include "../evsel.h"
+#include "../sort.h"
+#include "../hist.h"
+#include "../helpline.h"
+#include "gtk.h"
+
+#define MAX_COLUMNS 32
+
+static int __percent_color_snprintf(struct perf_hpp *hpp, const char *fmt, ...)
+{
+ int ret = 0;
+ va_list args;
+ double percent;
+ const char *markup;
+ char *buf = hpp->buf;
+ size_t size = hpp->size;
+
+ va_start(args, fmt);
+ percent = va_arg(args, double);
+ va_end(args);
+
+ markup = perf_gtk__get_percent_color(percent);
+ if (markup)
+ ret += scnprintf(buf, size, markup);
+
+ ret += scnprintf(buf + ret, size - ret, fmt, percent);
+
+ if (markup)
+ ret += scnprintf(buf + ret, size - ret, "</span>");
+
+ return ret;
+}
+
+#define __HPP_COLOR_PERCENT_FN(_type, _field) \
+static u64 he_get_##_field(struct hist_entry *he) \
+{ \
+ return he->stat._field; \
+} \
+ \
+static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \
+ struct perf_hpp *hpp, \
+ struct hist_entry *he) \
+{ \
+ return __hpp__fmt(hpp, he, he_get_##_field, " %6.2f%%", \
+ __percent_color_snprintf, true); \
+}
+
+#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \
+static u64 he_get_acc_##_field(struct hist_entry *he) \
+{ \
+ return he->stat_acc->_field; \
+} \
+ \
+static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \
+ struct perf_hpp *hpp, \
+ struct hist_entry *he) \
+{ \
+ return __hpp__fmt_acc(hpp, he, he_get_acc_##_field, " %6.2f%%", \
+ __percent_color_snprintf, true); \
+}
+
+__HPP_COLOR_PERCENT_FN(overhead, period)
+__HPP_COLOR_PERCENT_FN(overhead_sys, period_sys)
+__HPP_COLOR_PERCENT_FN(overhead_us, period_us)
+__HPP_COLOR_PERCENT_FN(overhead_guest_sys, period_guest_sys)
+__HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us)
+__HPP_COLOR_ACC_PERCENT_FN(overhead_acc, period)
+
+#undef __HPP_COLOR_PERCENT_FN
+
+
+void perf_gtk__init_hpp(void)
+{
+ perf_hpp__format[PERF_HPP__OVERHEAD].color =
+ perf_gtk__hpp_color_overhead;
+ perf_hpp__format[PERF_HPP__OVERHEAD_SYS].color =
+ perf_gtk__hpp_color_overhead_sys;
+ perf_hpp__format[PERF_HPP__OVERHEAD_US].color =
+ perf_gtk__hpp_color_overhead_us;
+ perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_SYS].color =
+ perf_gtk__hpp_color_overhead_guest_sys;
+ perf_hpp__format[PERF_HPP__OVERHEAD_GUEST_US].color =
+ perf_gtk__hpp_color_overhead_guest_us;
+ perf_hpp__format[PERF_HPP__OVERHEAD_ACC].color =
+ perf_gtk__hpp_color_overhead_acc;
+}
+
+static void callchain_list__sym_name(struct callchain_list *cl,
+ char *bf, size_t bfsize)
+{
+ if (cl->ms.sym)
+ scnprintf(bf, bfsize, "%s", cl->ms.sym->name);
+ else
+ scnprintf(bf, bfsize, "%#" PRIx64, cl->ip);
+}
+
+static void perf_gtk__add_callchain(struct rb_root *root, GtkTreeStore *store,
+ GtkTreeIter *parent, int col, u64 total)
+{
+ struct rb_node *nd;
+ bool has_single_node = (rb_first(root) == rb_last(root));
+
+ for (nd = rb_first(root); nd; nd = rb_next(nd)) {
+ struct callchain_node *node;
+ struct callchain_list *chain;
+ GtkTreeIter iter, new_parent;
+ bool need_new_parent;
+ double percent;
+ u64 hits, child_total;
+
+ node = rb_entry(nd, struct callchain_node, rb_node);
+
+ hits = callchain_cumul_hits(node);
+ percent = 100.0 * hits / total;
+
+ new_parent = *parent;
+ need_new_parent = !has_single_node && (node->val_nr > 1);
+
+ list_for_each_entry(chain, &node->val, list) {
+ char buf[128];
+
+ gtk_tree_store_append(store, &iter, &new_parent);
+
+ scnprintf(buf, sizeof(buf), "%5.2f%%", percent);
+ gtk_tree_store_set(store, &iter, 0, buf, -1);
+
+ callchain_list__sym_name(chain, buf, sizeof(buf));
+ gtk_tree_store_set(store, &iter, col, buf, -1);
+
+ if (need_new_parent) {
+ /*
+ * Only show the top-most symbol in a callchain
+ * if it's not the only callchain.
+ */
+ new_parent = iter;
+ need_new_parent = false;
+ }
+ }
+
+ if (callchain_param.mode == CHAIN_GRAPH_REL)
+ child_total = node->children_hit;
+ else
+ child_total = total;
+
+ /* Now 'iter' contains info of the last callchain_list */
+ perf_gtk__add_callchain(&node->rb_root, store, &iter, col,
+ child_total);
+ }
+}
+
+static void on_row_activated(GtkTreeView *view, GtkTreePath *path,
+ GtkTreeViewColumn *col __maybe_unused,
+ gpointer user_data __maybe_unused)
+{
+ bool expanded = gtk_tree_view_row_expanded(view, path);
+
+ if (expanded)
+ gtk_tree_view_collapse_row(view, path);
+ else
+ gtk_tree_view_expand_row(view, path, FALSE);
+}
+
+static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists,
+ float min_pcnt)
+{
+ struct perf_hpp_fmt *fmt;
+ GType col_types[MAX_COLUMNS];
+ GtkCellRenderer *renderer;
+ GtkTreeStore *store;
+ struct rb_node *nd;
+ GtkWidget *view;
+ int col_idx;
+ int sym_col = -1;
+ int nr_cols;
+ char s[512];
+
+ struct perf_hpp hpp = {
+ .buf = s,
+ .size = sizeof(s),
+ };
+
+ nr_cols = 0;
+
+ perf_hpp__for_each_format(fmt)
+ col_types[nr_cols++] = G_TYPE_STRING;
+
+ store = gtk_tree_store_newv(nr_cols, col_types);
+
+ view = gtk_tree_view_new();
+
+ renderer = gtk_cell_renderer_text_new();
+
+ col_idx = 0;
+
+ perf_hpp__for_each_format(fmt) {
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ /*
+ * XXX no way to determine where symcol column is..
+ * Just use last column for now.
+ */
+ if (perf_hpp__is_sort_entry(fmt))
+ sym_col = col_idx;
+
+ fmt->header(fmt, &hpp, hists_to_evsel(hists));
+
+ gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view),
+ -1, ltrim(s),
+ renderer, "markup",
+ col_idx++, NULL);
+ }
+
+ for (col_idx = 0; col_idx < nr_cols; col_idx++) {
+ GtkTreeViewColumn *column;
+
+ column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), col_idx);
+ gtk_tree_view_column_set_resizable(column, TRUE);
+
+ if (col_idx == sym_col) {
+ gtk_tree_view_set_expander_column(GTK_TREE_VIEW(view),
+ column);
+ }
+ }
+
+ gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store));
+
+ g_object_unref(GTK_TREE_MODEL(store));
+
+ for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+ GtkTreeIter iter;
+ u64 total = hists__total_period(h->hists);
+ float percent;
+
+ if (h->filtered)
+ continue;
+
+ percent = hist_entry__get_percent_limit(h);
+ if (percent < min_pcnt)
+ continue;
+
+ gtk_tree_store_append(store, &iter, NULL);
+
+ col_idx = 0;
+
+ perf_hpp__for_each_format(fmt) {
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ if (fmt->color)
+ fmt->color(fmt, &hpp, h);
+ else
+ fmt->entry(fmt, &hpp, h);
+
+ gtk_tree_store_set(store, &iter, col_idx++, s, -1);
+ }
+
+ if (symbol_conf.use_callchain && sort__has_sym) {
+ if (callchain_param.mode == CHAIN_GRAPH_REL)
+ total = symbol_conf.cumulate_callchain ?
+ h->stat_acc->period : h->stat.period;
+
+ perf_gtk__add_callchain(&h->sorted_chain, store, &iter,
+ sym_col, total);
+ }
+ }
+
+ gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE);
+
+ g_signal_connect(view, "row-activated",
+ G_CALLBACK(on_row_activated), NULL);
+ gtk_container_add(GTK_CONTAINER(window), view);
+}
+
+int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist,
+ const char *help,
+ struct hist_browser_timer *hbt __maybe_unused,
+ float min_pcnt)
+{
+ struct perf_evsel *pos;
+ GtkWidget *vbox;
+ GtkWidget *notebook;
+ GtkWidget *info_bar;
+ GtkWidget *statbar;
+ GtkWidget *window;
+
+ signal(SIGSEGV, perf_gtk__signal);
+ signal(SIGFPE, perf_gtk__signal);
+ signal(SIGINT, perf_gtk__signal);
+ signal(SIGQUIT, perf_gtk__signal);
+ signal(SIGTERM, perf_gtk__signal);
+
+ window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+
+ gtk_window_set_title(GTK_WINDOW(window), "perf report");
+
+ g_signal_connect(window, "delete_event", gtk_main_quit, NULL);
+
+ pgctx = perf_gtk__activate_context(window);
+ if (!pgctx)
+ return -1;
+
+ vbox = gtk_vbox_new(FALSE, 0);
+
+ notebook = gtk_notebook_new();
+
+ gtk_box_pack_start(GTK_BOX(vbox), notebook, TRUE, TRUE, 0);
+
+ info_bar = perf_gtk__setup_info_bar();
+ if (info_bar)
+ gtk_box_pack_start(GTK_BOX(vbox), info_bar, FALSE, FALSE, 0);
+
+ statbar = perf_gtk__setup_statusbar();
+ gtk_box_pack_start(GTK_BOX(vbox), statbar, FALSE, FALSE, 0);
+
+ gtk_container_add(GTK_CONTAINER(window), vbox);
+
+ evlist__for_each(evlist, pos) {
+ struct hists *hists = &pos->hists;
+ const char *evname = perf_evsel__name(pos);
+ GtkWidget *scrolled_window;
+ GtkWidget *tab_label;
+ char buf[512];
+ size_t size = sizeof(buf);
+
+ if (symbol_conf.event_group) {
+ if (!perf_evsel__is_group_leader(pos))
+ continue;
+
+ if (pos->nr_members > 1) {
+ perf_evsel__group_desc(pos, buf, size);
+ evname = buf;
+ }
+ }
+
+ scrolled_window = gtk_scrolled_window_new(NULL, NULL);
+
+ gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(scrolled_window),
+ GTK_POLICY_AUTOMATIC,
+ GTK_POLICY_AUTOMATIC);
+
+ perf_gtk__show_hists(scrolled_window, hists, min_pcnt);
+
+ tab_label = gtk_label_new(evname);
+
+ gtk_notebook_append_page(GTK_NOTEBOOK(notebook), scrolled_window, tab_label);
+ }
+
+ gtk_widget_show_all(window);
+
+ perf_gtk__resize_window(window);
+
+ gtk_window_set_position(GTK_WINDOW(window), GTK_WIN_POS_CENTER);
+
+ ui_helpline__push(help);
+
+ gtk_main();
+
+ perf_gtk__deactivate_context(&pgctx);
+
+ return 0;
+}
diff --git a/tools/perf/ui/gtk/progress.c b/tools/perf/ui/gtk/progress.c
new file mode 100644
index 00000000000..b656655fbc3
--- /dev/null
+++ b/tools/perf/ui/gtk/progress.c
@@ -0,0 +1,59 @@
+#include <inttypes.h>
+
+#include "gtk.h"
+#include "../progress.h"
+#include "util.h"
+
+static GtkWidget *dialog;
+static GtkWidget *progress;
+
+static void gtk_ui_progress__update(struct ui_progress *p)
+{
+ double fraction = p->total ? 1.0 * p->curr / p->total : 0.0;
+ char buf[1024];
+
+ if (dialog == NULL) {
+ GtkWidget *vbox = gtk_vbox_new(TRUE, 5);
+ GtkWidget *label = gtk_label_new(p->title);
+
+ dialog = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ progress = gtk_progress_bar_new();
+
+ gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, FALSE, 3);
+ gtk_box_pack_start(GTK_BOX(vbox), progress, TRUE, TRUE, 3);
+
+ gtk_container_add(GTK_CONTAINER(dialog), vbox);
+
+ gtk_window_set_title(GTK_WINDOW(dialog), "perf");
+ gtk_window_resize(GTK_WINDOW(dialog), 300, 80);
+ gtk_window_set_position(GTK_WINDOW(dialog), GTK_WIN_POS_CENTER);
+
+ gtk_widget_show_all(dialog);
+ }
+
+ gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(progress), fraction);
+ snprintf(buf, sizeof(buf), "%"PRIu64" / %"PRIu64, p->curr, p->total);
+ gtk_progress_bar_set_text(GTK_PROGRESS_BAR(progress), buf);
+
+ /* we didn't call gtk_main yet, so do it manually */
+ while (gtk_events_pending())
+ gtk_main_iteration();
+}
+
+static void gtk_ui_progress__finish(void)
+{
+ /* this will also destroy all of its children */
+ gtk_widget_destroy(dialog);
+
+ dialog = NULL;
+}
+
+static struct ui_progress_ops gtk_ui_progress__ops = {
+ .update = gtk_ui_progress__update,
+ .finish = gtk_ui_progress__finish,
+};
+
+void gtk_ui_progress__init(void)
+{
+ ui_progress__ops = &gtk_ui_progress__ops;
+}
diff --git a/tools/perf/ui/gtk/setup.c b/tools/perf/ui/gtk/setup.c
new file mode 100644
index 00000000000..1d57676f821
--- /dev/null
+++ b/tools/perf/ui/gtk/setup.c
@@ -0,0 +1,23 @@
+#include "gtk.h"
+#include "../../util/cache.h"
+#include "../../util/debug.h"
+
+extern struct perf_error_ops perf_gtk_eops;
+
+int perf_gtk__init(void)
+{
+ perf_error__register(&perf_gtk_eops);
+ perf_gtk__init_helpline();
+ gtk_ui_progress__init();
+ perf_gtk__init_hpp();
+
+ return gtk_init_check(NULL, NULL) ? 0 : -1;
+}
+
+void perf_gtk__exit(bool wait_for_ok __maybe_unused)
+{
+ if (!perf_gtk__is_active_context(pgctx))
+ return;
+ perf_error__unregister(&perf_gtk_eops);
+ gtk_main_quit();
+}
diff --git a/tools/perf/ui/gtk/util.c b/tools/perf/ui/gtk/util.c
new file mode 100644
index 00000000000..52e7fc48af9
--- /dev/null
+++ b/tools/perf/ui/gtk/util.c
@@ -0,0 +1,112 @@
+#include "../util.h"
+#include "../../util/debug.h"
+#include "gtk.h"
+
+#include <string.h>
+
+
+struct perf_gtk_context *pgctx;
+
+struct perf_gtk_context *perf_gtk__activate_context(GtkWidget *window)
+{
+ struct perf_gtk_context *ctx;
+
+ ctx = malloc(sizeof(*pgctx));
+ if (ctx)
+ ctx->main_window = window;
+
+ return ctx;
+}
+
+int perf_gtk__deactivate_context(struct perf_gtk_context **ctx)
+{
+ if (!perf_gtk__is_active_context(*ctx))
+ return -1;
+
+ zfree(ctx);
+ return 0;
+}
+
+static int perf_gtk__error(const char *format, va_list args)
+{
+ char *msg;
+ GtkWidget *dialog;
+
+ if (!perf_gtk__is_active_context(pgctx) ||
+ vasprintf(&msg, format, args) < 0) {
+ fprintf(stderr, "Error:\n");
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+ return -1;
+ }
+
+ dialog = gtk_message_dialog_new_with_markup(GTK_WINDOW(pgctx->main_window),
+ GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_CLOSE,
+ "<b>Error</b>\n\n%s", msg);
+ gtk_dialog_run(GTK_DIALOG(dialog));
+
+ gtk_widget_destroy(dialog);
+ free(msg);
+ return 0;
+}
+
+#ifdef HAVE_GTK_INFO_BAR_SUPPORT
+static int perf_gtk__warning_info_bar(const char *format, va_list args)
+{
+ char *msg;
+
+ if (!perf_gtk__is_active_context(pgctx) ||
+ vasprintf(&msg, format, args) < 0) {
+ fprintf(stderr, "Warning:\n");
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+ return -1;
+ }
+
+ gtk_label_set_text(GTK_LABEL(pgctx->message_label), msg);
+ gtk_info_bar_set_message_type(GTK_INFO_BAR(pgctx->info_bar),
+ GTK_MESSAGE_WARNING);
+ gtk_widget_show(pgctx->info_bar);
+
+ free(msg);
+ return 0;
+}
+#else
+static int perf_gtk__warning_statusbar(const char *format, va_list args)
+{
+ char *msg, *p;
+
+ if (!perf_gtk__is_active_context(pgctx) ||
+ vasprintf(&msg, format, args) < 0) {
+ fprintf(stderr, "Warning:\n");
+ vfprintf(stderr, format, args);
+ fprintf(stderr, "\n");
+ return -1;
+ }
+
+ gtk_statusbar_pop(GTK_STATUSBAR(pgctx->statbar),
+ pgctx->statbar_ctx_id);
+
+ /* Only first line can be displayed */
+ p = strchr(msg, '\n');
+ if (p)
+ *p = '\0';
+
+ gtk_statusbar_push(GTK_STATUSBAR(pgctx->statbar),
+ pgctx->statbar_ctx_id, msg);
+
+ free(msg);
+ return 0;
+}
+#endif
+
+struct perf_error_ops perf_gtk_eops = {
+ .error = perf_gtk__error,
+#ifdef HAVE_GTK_INFO_BAR_SUPPORT
+ .warning = perf_gtk__warning_info_bar,
+#else
+ .warning = perf_gtk__warning_statusbar,
+#endif
+};
diff --git a/tools/perf/ui/helpline.c b/tools/perf/ui/helpline.c
new file mode 100644
index 00000000000..700fb3cfa1c
--- /dev/null
+++ b/tools/perf/ui/helpline.c
@@ -0,0 +1,73 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../debug.h"
+#include "helpline.h"
+#include "ui.h"
+
+char ui_helpline__current[512];
+
+static void nop_helpline__pop(void)
+{
+}
+
+static void nop_helpline__push(const char *msg __maybe_unused)
+{
+}
+
+static int nop_helpline__show(const char *fmt __maybe_unused,
+ va_list ap __maybe_unused)
+{
+ return 0;
+}
+
+static struct ui_helpline default_helpline_fns = {
+ .pop = nop_helpline__pop,
+ .push = nop_helpline__push,
+ .show = nop_helpline__show,
+};
+
+struct ui_helpline *helpline_fns = &default_helpline_fns;
+
+void ui_helpline__pop(void)
+{
+ helpline_fns->pop();
+}
+
+void ui_helpline__push(const char *msg)
+{
+ helpline_fns->push(msg);
+}
+
+void ui_helpline__vpush(const char *fmt, va_list ap)
+{
+ char *s;
+
+ if (vasprintf(&s, fmt, ap) < 0)
+ vfprintf(stderr, fmt, ap);
+ else {
+ ui_helpline__push(s);
+ free(s);
+ }
+}
+
+void ui_helpline__fpush(const char *fmt, ...)
+{
+ va_list ap;
+
+ va_start(ap, fmt);
+ ui_helpline__vpush(fmt, ap);
+ va_end(ap);
+}
+
+void ui_helpline__puts(const char *msg)
+{
+ ui_helpline__pop();
+ ui_helpline__push(msg);
+}
+
+int ui_helpline__vshow(const char *fmt, va_list ap)
+{
+ return helpline_fns->show(fmt, ap);
+}
diff --git a/tools/perf/ui/helpline.h b/tools/perf/ui/helpline.h
new file mode 100644
index 00000000000..46181f4fc07
--- /dev/null
+++ b/tools/perf/ui/helpline.h
@@ -0,0 +1,29 @@
+#ifndef _PERF_UI_HELPLINE_H_
+#define _PERF_UI_HELPLINE_H_ 1
+
+#include <stdio.h>
+#include <stdarg.h>
+
+#include "../util/cache.h"
+
+struct ui_helpline {
+ void (*pop)(void);
+ void (*push)(const char *msg);
+ int (*show)(const char *fmt, va_list ap);
+};
+
+extern struct ui_helpline *helpline_fns;
+
+void ui_helpline__init(void);
+
+void ui_helpline__pop(void);
+void ui_helpline__push(const char *msg);
+void ui_helpline__vpush(const char *fmt, va_list ap);
+void ui_helpline__fpush(const char *fmt, ...);
+void ui_helpline__puts(const char *msg);
+int ui_helpline__vshow(const char *fmt, va_list ap);
+
+extern char ui_helpline__current[512];
+extern char ui_helpline__last_msg[];
+
+#endif /* _PERF_UI_HELPLINE_H_ */
diff --git a/tools/perf/ui/hist.c b/tools/perf/ui/hist.c
new file mode 100644
index 00000000000..498adb23c02
--- /dev/null
+++ b/tools/perf/ui/hist.c
@@ -0,0 +1,624 @@
+#include <math.h>
+#include <linux/compiler.h>
+
+#include "../util/hist.h"
+#include "../util/util.h"
+#include "../util/sort.h"
+#include "../util/evsel.h"
+
+/* hist period print (hpp) functions */
+
+#define hpp__call_print_fn(hpp, fn, fmt, ...) \
+({ \
+ int __ret = fn(hpp, fmt, ##__VA_ARGS__); \
+ advance_hpp(hpp, __ret); \
+ __ret; \
+})
+
+int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he,
+ hpp_field_fn get_field, const char *fmt,
+ hpp_snprint_fn print_fn, bool fmt_percent)
+{
+ int ret;
+ struct hists *hists = he->hists;
+ struct perf_evsel *evsel = hists_to_evsel(hists);
+ char *buf = hpp->buf;
+ size_t size = hpp->size;
+
+ if (fmt_percent) {
+ double percent = 0.0;
+ u64 total = hists__total_period(hists);
+
+ if (total)
+ percent = 100.0 * get_field(he) / total;
+
+ ret = hpp__call_print_fn(hpp, print_fn, fmt, percent);
+ } else
+ ret = hpp__call_print_fn(hpp, print_fn, fmt, get_field(he));
+
+ if (perf_evsel__is_group_event(evsel)) {
+ int prev_idx, idx_delta;
+ struct hist_entry *pair;
+ int nr_members = evsel->nr_members;
+
+ prev_idx = perf_evsel__group_idx(evsel);
+
+ list_for_each_entry(pair, &he->pairs.head, pairs.node) {
+ u64 period = get_field(pair);
+ u64 total = hists__total_period(pair->hists);
+
+ if (!total)
+ continue;
+
+ evsel = hists_to_evsel(pair->hists);
+ idx_delta = perf_evsel__group_idx(evsel) - prev_idx - 1;
+
+ while (idx_delta--) {
+ /*
+ * zero-fill group members in the middle which
+ * have no sample
+ */
+ if (fmt_percent) {
+ ret += hpp__call_print_fn(hpp, print_fn,
+ fmt, 0.0);
+ } else {
+ ret += hpp__call_print_fn(hpp, print_fn,
+ fmt, 0ULL);
+ }
+ }
+
+ if (fmt_percent) {
+ ret += hpp__call_print_fn(hpp, print_fn, fmt,
+ 100.0 * period / total);
+ } else {
+ ret += hpp__call_print_fn(hpp, print_fn, fmt,
+ period);
+ }
+
+ prev_idx = perf_evsel__group_idx(evsel);
+ }
+
+ idx_delta = nr_members - prev_idx - 1;
+
+ while (idx_delta--) {
+ /*
+ * zero-fill group members at last which have no sample
+ */
+ if (fmt_percent) {
+ ret += hpp__call_print_fn(hpp, print_fn,
+ fmt, 0.0);
+ } else {
+ ret += hpp__call_print_fn(hpp, print_fn,
+ fmt, 0ULL);
+ }
+ }
+ }
+
+ /*
+ * Restore original buf and size as it's where caller expects
+ * the result will be saved.
+ */
+ hpp->buf = buf;
+ hpp->size = size;
+
+ return ret;
+}
+
+int __hpp__fmt_acc(struct perf_hpp *hpp, struct hist_entry *he,
+ hpp_field_fn get_field, const char *fmt,
+ hpp_snprint_fn print_fn, bool fmt_percent)
+{
+ if (!symbol_conf.cumulate_callchain) {
+ return snprintf(hpp->buf, hpp->size, "%*s",
+ fmt_percent ? 8 : 12, "N/A");
+ }
+
+ return __hpp__fmt(hpp, he, get_field, fmt, print_fn, fmt_percent);
+}
+
+static int field_cmp(u64 field_a, u64 field_b)
+{
+ if (field_a > field_b)
+ return 1;
+ if (field_a < field_b)
+ return -1;
+ return 0;
+}
+
+static int __hpp__sort(struct hist_entry *a, struct hist_entry *b,
+ hpp_field_fn get_field)
+{
+ s64 ret;
+ int i, nr_members;
+ struct perf_evsel *evsel;
+ struct hist_entry *pair;
+ u64 *fields_a, *fields_b;
+
+ ret = field_cmp(get_field(a), get_field(b));
+ if (ret || !symbol_conf.event_group)
+ return ret;
+
+ evsel = hists_to_evsel(a->hists);
+ if (!perf_evsel__is_group_event(evsel))
+ return ret;
+
+ nr_members = evsel->nr_members;
+ fields_a = calloc(sizeof(*fields_a), nr_members);
+ fields_b = calloc(sizeof(*fields_b), nr_members);
+
+ if (!fields_a || !fields_b)
+ goto out;
+
+ list_for_each_entry(pair, &a->pairs.head, pairs.node) {
+ evsel = hists_to_evsel(pair->hists);
+ fields_a[perf_evsel__group_idx(evsel)] = get_field(pair);
+ }
+
+ list_for_each_entry(pair, &b->pairs.head, pairs.node) {
+ evsel = hists_to_evsel(pair->hists);
+ fields_b[perf_evsel__group_idx(evsel)] = get_field(pair);
+ }
+
+ for (i = 1; i < nr_members; i++) {
+ ret = field_cmp(fields_a[i], fields_b[i]);
+ if (ret)
+ break;
+ }
+
+out:
+ free(fields_a);
+ free(fields_b);
+
+ return ret;
+}
+
+static int __hpp__sort_acc(struct hist_entry *a, struct hist_entry *b,
+ hpp_field_fn get_field)
+{
+ s64 ret = 0;
+
+ if (symbol_conf.cumulate_callchain) {
+ /*
+ * Put caller above callee when they have equal period.
+ */
+ ret = field_cmp(get_field(a), get_field(b));
+ if (ret)
+ return ret;
+
+ ret = b->callchain->max_depth - a->callchain->max_depth;
+ }
+ return ret;
+}
+
+#define __HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \
+static int hpp__header_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \
+ struct perf_hpp *hpp, \
+ struct perf_evsel *evsel) \
+{ \
+ int len = _min_width; \
+ \
+ if (symbol_conf.event_group) \
+ len = max(len, evsel->nr_members * _unit_width); \
+ \
+ return scnprintf(hpp->buf, hpp->size, "%*s", len, _str); \
+}
+
+#define __HPP_WIDTH_FN(_type, _min_width, _unit_width) \
+static int hpp__width_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \
+ struct perf_hpp *hpp __maybe_unused, \
+ struct perf_evsel *evsel) \
+{ \
+ int len = _min_width; \
+ \
+ if (symbol_conf.event_group) \
+ len = max(len, evsel->nr_members * _unit_width); \
+ \
+ return len; \
+}
+
+static int hpp_color_scnprintf(struct perf_hpp *hpp, const char *fmt, ...)
+{
+ va_list args;
+ ssize_t ssize = hpp->size;
+ double percent;
+ int ret;
+
+ va_start(args, fmt);
+ percent = va_arg(args, double);
+ ret = value_color_snprintf(hpp->buf, hpp->size, fmt, percent);
+ va_end(args);
+
+ return (ret >= ssize) ? (ssize - 1) : ret;
+}
+
+static int hpp_entry_scnprintf(struct perf_hpp *hpp, const char *fmt, ...)
+{
+ va_list args;
+ ssize_t ssize = hpp->size;
+ int ret;
+
+ va_start(args, fmt);
+ ret = vsnprintf(hpp->buf, hpp->size, fmt, args);
+ va_end(args);
+
+ return (ret >= ssize) ? (ssize - 1) : ret;
+}
+
+#define __HPP_COLOR_PERCENT_FN(_type, _field) \
+static u64 he_get_##_field(struct hist_entry *he) \
+{ \
+ return he->stat._field; \
+} \
+ \
+static int hpp__color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \
+ struct perf_hpp *hpp, struct hist_entry *he) \
+{ \
+ return __hpp__fmt(hpp, he, he_get_##_field, " %6.2f%%", \
+ hpp_color_scnprintf, true); \
+}
+
+#define __HPP_ENTRY_PERCENT_FN(_type, _field) \
+static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused, \
+ struct perf_hpp *hpp, struct hist_entry *he) \
+{ \
+ const char *fmt = symbol_conf.field_sep ? " %.2f" : " %6.2f%%"; \
+ return __hpp__fmt(hpp, he, he_get_##_field, fmt, \
+ hpp_entry_scnprintf, true); \
+}
+
+#define __HPP_SORT_FN(_type, _field) \
+static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b) \
+{ \
+ return __hpp__sort(a, b, he_get_##_field); \
+}
+
+#define __HPP_COLOR_ACC_PERCENT_FN(_type, _field) \
+static u64 he_get_acc_##_field(struct hist_entry *he) \
+{ \
+ return he->stat_acc->_field; \
+} \
+ \
+static int hpp__color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \
+ struct perf_hpp *hpp, struct hist_entry *he) \
+{ \
+ return __hpp__fmt_acc(hpp, he, he_get_acc_##_field, " %6.2f%%", \
+ hpp_color_scnprintf, true); \
+}
+
+#define __HPP_ENTRY_ACC_PERCENT_FN(_type, _field) \
+static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused, \
+ struct perf_hpp *hpp, struct hist_entry *he) \
+{ \
+ const char *fmt = symbol_conf.field_sep ? " %.2f" : " %6.2f%%"; \
+ return __hpp__fmt_acc(hpp, he, he_get_acc_##_field, fmt, \
+ hpp_entry_scnprintf, true); \
+}
+
+#define __HPP_SORT_ACC_FN(_type, _field) \
+static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b) \
+{ \
+ return __hpp__sort_acc(a, b, he_get_acc_##_field); \
+}
+
+#define __HPP_ENTRY_RAW_FN(_type, _field) \
+static u64 he_get_raw_##_field(struct hist_entry *he) \
+{ \
+ return he->stat._field; \
+} \
+ \
+static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused, \
+ struct perf_hpp *hpp, struct hist_entry *he) \
+{ \
+ const char *fmt = symbol_conf.field_sep ? " %"PRIu64 : " %11"PRIu64; \
+ return __hpp__fmt(hpp, he, he_get_raw_##_field, fmt, \
+ hpp_entry_scnprintf, false); \
+}
+
+#define __HPP_SORT_RAW_FN(_type, _field) \
+static int64_t hpp__sort_##_type(struct hist_entry *a, struct hist_entry *b) \
+{ \
+ return __hpp__sort(a, b, he_get_raw_##_field); \
+}
+
+
+#define HPP_PERCENT_FNS(_type, _str, _field, _min_width, _unit_width) \
+__HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \
+__HPP_WIDTH_FN(_type, _min_width, _unit_width) \
+__HPP_COLOR_PERCENT_FN(_type, _field) \
+__HPP_ENTRY_PERCENT_FN(_type, _field) \
+__HPP_SORT_FN(_type, _field)
+
+#define HPP_PERCENT_ACC_FNS(_type, _str, _field, _min_width, _unit_width)\
+__HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \
+__HPP_WIDTH_FN(_type, _min_width, _unit_width) \
+__HPP_COLOR_ACC_PERCENT_FN(_type, _field) \
+__HPP_ENTRY_ACC_PERCENT_FN(_type, _field) \
+__HPP_SORT_ACC_FN(_type, _field)
+
+#define HPP_RAW_FNS(_type, _str, _field, _min_width, _unit_width) \
+__HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \
+__HPP_WIDTH_FN(_type, _min_width, _unit_width) \
+__HPP_ENTRY_RAW_FN(_type, _field) \
+__HPP_SORT_RAW_FN(_type, _field)
+
+__HPP_HEADER_FN(overhead_self, "Self", 8, 8)
+
+HPP_PERCENT_FNS(overhead, "Overhead", period, 8, 8)
+HPP_PERCENT_FNS(overhead_sys, "sys", period_sys, 8, 8)
+HPP_PERCENT_FNS(overhead_us, "usr", period_us, 8, 8)
+HPP_PERCENT_FNS(overhead_guest_sys, "guest sys", period_guest_sys, 9, 8)
+HPP_PERCENT_FNS(overhead_guest_us, "guest usr", period_guest_us, 9, 8)
+HPP_PERCENT_ACC_FNS(overhead_acc, "Children", period, 8, 8)
+
+HPP_RAW_FNS(samples, "Samples", nr_events, 12, 12)
+HPP_RAW_FNS(period, "Period", period, 12, 12)
+
+static int64_t hpp__nop_cmp(struct hist_entry *a __maybe_unused,
+ struct hist_entry *b __maybe_unused)
+{
+ return 0;
+}
+
+#define HPP__COLOR_PRINT_FNS(_name) \
+ { \
+ .header = hpp__header_ ## _name, \
+ .width = hpp__width_ ## _name, \
+ .color = hpp__color_ ## _name, \
+ .entry = hpp__entry_ ## _name, \
+ .cmp = hpp__nop_cmp, \
+ .collapse = hpp__nop_cmp, \
+ .sort = hpp__sort_ ## _name, \
+ }
+
+#define HPP__COLOR_ACC_PRINT_FNS(_name) \
+ { \
+ .header = hpp__header_ ## _name, \
+ .width = hpp__width_ ## _name, \
+ .color = hpp__color_ ## _name, \
+ .entry = hpp__entry_ ## _name, \
+ .cmp = hpp__nop_cmp, \
+ .collapse = hpp__nop_cmp, \
+ .sort = hpp__sort_ ## _name, \
+ }
+
+#define HPP__PRINT_FNS(_name) \
+ { \
+ .header = hpp__header_ ## _name, \
+ .width = hpp__width_ ## _name, \
+ .entry = hpp__entry_ ## _name, \
+ .cmp = hpp__nop_cmp, \
+ .collapse = hpp__nop_cmp, \
+ .sort = hpp__sort_ ## _name, \
+ }
+
+struct perf_hpp_fmt perf_hpp__format[] = {
+ HPP__COLOR_PRINT_FNS(overhead),
+ HPP__COLOR_PRINT_FNS(overhead_sys),
+ HPP__COLOR_PRINT_FNS(overhead_us),
+ HPP__COLOR_PRINT_FNS(overhead_guest_sys),
+ HPP__COLOR_PRINT_FNS(overhead_guest_us),
+ HPP__COLOR_ACC_PRINT_FNS(overhead_acc),
+ HPP__PRINT_FNS(samples),
+ HPP__PRINT_FNS(period)
+};
+
+LIST_HEAD(perf_hpp__list);
+LIST_HEAD(perf_hpp__sort_list);
+
+
+#undef HPP__COLOR_PRINT_FNS
+#undef HPP__COLOR_ACC_PRINT_FNS
+#undef HPP__PRINT_FNS
+
+#undef HPP_PERCENT_FNS
+#undef HPP_PERCENT_ACC_FNS
+#undef HPP_RAW_FNS
+
+#undef __HPP_HEADER_FN
+#undef __HPP_WIDTH_FN
+#undef __HPP_COLOR_PERCENT_FN
+#undef __HPP_ENTRY_PERCENT_FN
+#undef __HPP_COLOR_ACC_PERCENT_FN
+#undef __HPP_ENTRY_ACC_PERCENT_FN
+#undef __HPP_ENTRY_RAW_FN
+#undef __HPP_SORT_FN
+#undef __HPP_SORT_ACC_FN
+#undef __HPP_SORT_RAW_FN
+
+
+void perf_hpp__init(void)
+{
+ struct list_head *list;
+ int i;
+
+ for (i = 0; i < PERF_HPP__MAX_INDEX; i++) {
+ struct perf_hpp_fmt *fmt = &perf_hpp__format[i];
+
+ INIT_LIST_HEAD(&fmt->list);
+
+ /* sort_list may be linked by setup_sorting() */
+ if (fmt->sort_list.next == NULL)
+ INIT_LIST_HEAD(&fmt->sort_list);
+ }
+
+ /*
+ * If user specified field order, no need to setup default fields.
+ */
+ if (field_order)
+ return;
+
+ if (symbol_conf.cumulate_callchain) {
+ perf_hpp__column_enable(PERF_HPP__OVERHEAD_ACC);
+
+ perf_hpp__format[PERF_HPP__OVERHEAD].header =
+ hpp__header_overhead_self;
+ }
+
+ perf_hpp__column_enable(PERF_HPP__OVERHEAD);
+
+ if (symbol_conf.show_cpu_utilization) {
+ perf_hpp__column_enable(PERF_HPP__OVERHEAD_SYS);
+ perf_hpp__column_enable(PERF_HPP__OVERHEAD_US);
+
+ if (perf_guest) {
+ perf_hpp__column_enable(PERF_HPP__OVERHEAD_GUEST_SYS);
+ perf_hpp__column_enable(PERF_HPP__OVERHEAD_GUEST_US);
+ }
+ }
+
+ if (symbol_conf.show_nr_samples)
+ perf_hpp__column_enable(PERF_HPP__SAMPLES);
+
+ if (symbol_conf.show_total_period)
+ perf_hpp__column_enable(PERF_HPP__PERIOD);
+
+ /* prepend overhead field for backward compatiblity. */
+ list = &perf_hpp__format[PERF_HPP__OVERHEAD].sort_list;
+ if (list_empty(list))
+ list_add(list, &perf_hpp__sort_list);
+
+ if (symbol_conf.cumulate_callchain) {
+ list = &perf_hpp__format[PERF_HPP__OVERHEAD_ACC].sort_list;
+ if (list_empty(list))
+ list_add(list, &perf_hpp__sort_list);
+ }
+}
+
+void perf_hpp__column_register(struct perf_hpp_fmt *format)
+{
+ list_add_tail(&format->list, &perf_hpp__list);
+}
+
+void perf_hpp__column_unregister(struct perf_hpp_fmt *format)
+{
+ list_del(&format->list);
+}
+
+void perf_hpp__register_sort_field(struct perf_hpp_fmt *format)
+{
+ list_add_tail(&format->sort_list, &perf_hpp__sort_list);
+}
+
+void perf_hpp__column_enable(unsigned col)
+{
+ BUG_ON(col >= PERF_HPP__MAX_INDEX);
+ perf_hpp__column_register(&perf_hpp__format[col]);
+}
+
+void perf_hpp__column_disable(unsigned col)
+{
+ BUG_ON(col >= PERF_HPP__MAX_INDEX);
+ perf_hpp__column_unregister(&perf_hpp__format[col]);
+}
+
+void perf_hpp__cancel_cumulate(void)
+{
+ if (field_order)
+ return;
+
+ perf_hpp__column_disable(PERF_HPP__OVERHEAD_ACC);
+ perf_hpp__format[PERF_HPP__OVERHEAD].header = hpp__header_overhead;
+}
+
+void perf_hpp__setup_output_field(void)
+{
+ struct perf_hpp_fmt *fmt;
+
+ /* append sort keys to output field */
+ perf_hpp__for_each_sort_list(fmt) {
+ if (!list_empty(&fmt->list))
+ continue;
+
+ /*
+ * sort entry fields are dynamically created,
+ * so they can share a same sort key even though
+ * the list is empty.
+ */
+ if (perf_hpp__is_sort_entry(fmt)) {
+ struct perf_hpp_fmt *pos;
+
+ perf_hpp__for_each_format(pos) {
+ if (perf_hpp__same_sort_entry(pos, fmt))
+ goto next;
+ }
+ }
+
+ perf_hpp__column_register(fmt);
+next:
+ continue;
+ }
+}
+
+void perf_hpp__append_sort_keys(void)
+{
+ struct perf_hpp_fmt *fmt;
+
+ /* append output fields to sort keys */
+ perf_hpp__for_each_format(fmt) {
+ if (!list_empty(&fmt->sort_list))
+ continue;
+
+ /*
+ * sort entry fields are dynamically created,
+ * so they can share a same sort key even though
+ * the list is empty.
+ */
+ if (perf_hpp__is_sort_entry(fmt)) {
+ struct perf_hpp_fmt *pos;
+
+ perf_hpp__for_each_sort_list(pos) {
+ if (perf_hpp__same_sort_entry(pos, fmt))
+ goto next;
+ }
+ }
+
+ perf_hpp__register_sort_field(fmt);
+next:
+ continue;
+ }
+}
+
+void perf_hpp__reset_output_field(void)
+{
+ struct perf_hpp_fmt *fmt, *tmp;
+
+ /* reset output fields */
+ perf_hpp__for_each_format_safe(fmt, tmp) {
+ list_del_init(&fmt->list);
+ list_del_init(&fmt->sort_list);
+ }
+
+ /* reset sort keys */
+ perf_hpp__for_each_sort_list_safe(fmt, tmp) {
+ list_del_init(&fmt->list);
+ list_del_init(&fmt->sort_list);
+ }
+}
+
+/*
+ * See hists__fprintf to match the column widths
+ */
+unsigned int hists__sort_list_width(struct hists *hists)
+{
+ struct perf_hpp_fmt *fmt;
+ int ret = 0;
+ bool first = true;
+ struct perf_hpp dummy_hpp;
+
+ perf_hpp__for_each_format(fmt) {
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ if (first)
+ first = false;
+ else
+ ret += 2;
+
+ ret += fmt->width(fmt, &dummy_hpp, hists_to_evsel(hists));
+ }
+
+ if (verbose && sort__has_sym) /* Addr + origin */
+ ret += 3 + BITS_PER_LONG / 4;
+
+ return ret;
+}
diff --git a/tools/perf/ui/keysyms.h b/tools/perf/ui/keysyms.h
new file mode 100644
index 00000000000..65092d576b4
--- /dev/null
+++ b/tools/perf/ui/keysyms.h
@@ -0,0 +1,28 @@
+#ifndef _PERF_KEYSYMS_H_
+#define _PERF_KEYSYMS_H_ 1
+
+#include "libslang.h"
+
+#define K_DOWN SL_KEY_DOWN
+#define K_END SL_KEY_END
+#define K_ENTER '\r'
+#define K_ESC 033
+#define K_F1 SL_KEY_F(1)
+#define K_HOME SL_KEY_HOME
+#define K_LEFT SL_KEY_LEFT
+#define K_PGDN SL_KEY_NPAGE
+#define K_PGUP SL_KEY_PPAGE
+#define K_RIGHT SL_KEY_RIGHT
+#define K_TAB '\t'
+#define K_UNTAB SL_KEY_UNTAB
+#define K_UP SL_KEY_UP
+#define K_BKSPC 0x7f
+#define K_DEL SL_KEY_DELETE
+
+/* Not really keys */
+#define K_TIMER -1
+#define K_ERROR -2
+#define K_RESIZE -3
+#define K_SWITCH_INPUT_DATA -4
+
+#endif /* _PERF_KEYSYMS_H_ */
diff --git a/tools/perf/ui/libslang.h b/tools/perf/ui/libslang.h
new file mode 100644
index 00000000000..4d54b6450f5
--- /dev/null
+++ b/tools/perf/ui/libslang.h
@@ -0,0 +1,29 @@
+#ifndef _PERF_UI_SLANG_H_
+#define _PERF_UI_SLANG_H_ 1
+/*
+ * slang versions <= 2.0.6 have a "#if HAVE_LONG_LONG" that breaks
+ * the build if it isn't defined. Use the equivalent one that glibc
+ * has on features.h.
+ */
+#include <features.h>
+#ifndef HAVE_LONG_LONG
+#define HAVE_LONG_LONG __GLIBC_HAVE_LONG_LONG
+#endif
+#include <slang.h>
+
+#if SLANG_VERSION < 20104
+#define slsmg_printf(msg, args...) \
+ SLsmg_printf((char *)(msg), ##args)
+#define slsmg_write_nstring(msg, len) \
+ SLsmg_write_nstring((char *)(msg), len)
+#define sltt_set_color(obj, name, fg, bg) \
+ SLtt_set_color(obj,(char *)(name), (char *)(fg), (char *)(bg))
+#else
+#define slsmg_printf SLsmg_printf
+#define slsmg_write_nstring SLsmg_write_nstring
+#define sltt_set_color SLtt_set_color
+#endif
+
+#define SL_KEY_UNTAB 0x1000
+
+#endif /* _PERF_UI_SLANG_H_ */
diff --git a/tools/perf/ui/progress.c b/tools/perf/ui/progress.c
new file mode 100644
index 00000000000..a0f24c7115c
--- /dev/null
+++ b/tools/perf/ui/progress.c
@@ -0,0 +1,38 @@
+#include "../cache.h"
+#include "progress.h"
+
+static void null_progress__update(struct ui_progress *p __maybe_unused)
+{
+}
+
+static struct ui_progress_ops null_progress__ops =
+{
+ .update = null_progress__update,
+};
+
+struct ui_progress_ops *ui_progress__ops = &null_progress__ops;
+
+void ui_progress__update(struct ui_progress *p, u64 adv)
+{
+ p->curr += adv;
+
+ if (p->curr >= p->next) {
+ p->next += p->step;
+ ui_progress__ops->update(p);
+ }
+}
+
+void ui_progress__init(struct ui_progress *p, u64 total, const char *title)
+{
+ p->curr = 0;
+ p->next = p->step = total / 16;
+ p->total = total;
+ p->title = title;
+
+}
+
+void ui_progress__finish(void)
+{
+ if (ui_progress__ops->finish)
+ ui_progress__ops->finish();
+}
diff --git a/tools/perf/ui/progress.h b/tools/perf/ui/progress.h
new file mode 100644
index 00000000000..f34f89eb607
--- /dev/null
+++ b/tools/perf/ui/progress.h
@@ -0,0 +1,23 @@
+#ifndef _PERF_UI_PROGRESS_H_
+#define _PERF_UI_PROGRESS_H_ 1
+
+#include <linux/types.h>
+
+void ui_progress__finish(void);
+
+struct ui_progress {
+ const char *title;
+ u64 curr, next, step, total;
+};
+
+void ui_progress__init(struct ui_progress *p, u64 total, const char *title);
+void ui_progress__update(struct ui_progress *p, u64 adv);
+
+struct ui_progress_ops {
+ void (*update)(struct ui_progress *p);
+ void (*finish)(void);
+};
+
+extern struct ui_progress_ops *ui_progress__ops;
+
+#endif
diff --git a/tools/perf/ui/setup.c b/tools/perf/ui/setup.c
new file mode 100644
index 00000000000..ba51fa8a117
--- /dev/null
+++ b/tools/perf/ui/setup.c
@@ -0,0 +1,107 @@
+#include <pthread.h>
+#include <dlfcn.h>
+
+#include "../util/cache.h"
+#include "../util/debug.h"
+#include "../util/hist.h"
+
+pthread_mutex_t ui__lock = PTHREAD_MUTEX_INITIALIZER;
+void *perf_gtk_handle;
+
+#ifdef HAVE_GTK2_SUPPORT
+static int setup_gtk_browser(void)
+{
+ int (*perf_ui_init)(void);
+
+ if (perf_gtk_handle)
+ return 0;
+
+ perf_gtk_handle = dlopen(PERF_GTK_DSO, RTLD_LAZY);
+ if (perf_gtk_handle == NULL) {
+ char buf[PATH_MAX];
+ scnprintf(buf, sizeof(buf), "%s/%s", LIBDIR, PERF_GTK_DSO);
+ perf_gtk_handle = dlopen(buf, RTLD_LAZY);
+ }
+ if (perf_gtk_handle == NULL)
+ return -1;
+
+ perf_ui_init = dlsym(perf_gtk_handle, "perf_gtk__init");
+ if (perf_ui_init == NULL)
+ goto out_close;
+
+ if (perf_ui_init() == 0)
+ return 0;
+
+out_close:
+ dlclose(perf_gtk_handle);
+ return -1;
+}
+
+static void exit_gtk_browser(bool wait_for_ok)
+{
+ void (*perf_ui_exit)(bool);
+
+ if (perf_gtk_handle == NULL)
+ return;
+
+ perf_ui_exit = dlsym(perf_gtk_handle, "perf_gtk__exit");
+ if (perf_ui_exit == NULL)
+ goto out_close;
+
+ perf_ui_exit(wait_for_ok);
+
+out_close:
+ dlclose(perf_gtk_handle);
+
+ perf_gtk_handle = NULL;
+}
+#else
+static inline int setup_gtk_browser(void) { return -1; }
+static inline void exit_gtk_browser(bool wait_for_ok __maybe_unused) {}
+#endif
+
+void setup_browser(bool fallback_to_pager)
+{
+ if (use_browser < 2 && (!isatty(1) || dump_trace))
+ use_browser = 0;
+
+ /* default to TUI */
+ if (use_browser < 0)
+ use_browser = 1;
+
+ switch (use_browser) {
+ case 2:
+ if (setup_gtk_browser() == 0)
+ break;
+ printf("GTK browser requested but could not find %s\n",
+ PERF_GTK_DSO);
+ sleep(1);
+ /* fall through */
+ case 1:
+ use_browser = 1;
+ if (ui__init() == 0)
+ break;
+ /* fall through */
+ default:
+ use_browser = 0;
+ if (fallback_to_pager)
+ setup_pager();
+ break;
+ }
+}
+
+void exit_browser(bool wait_for_ok)
+{
+ switch (use_browser) {
+ case 2:
+ exit_gtk_browser(wait_for_ok);
+ break;
+
+ case 1:
+ ui__exit(wait_for_ok);
+ break;
+
+ default:
+ break;
+ }
+}
diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c
new file mode 100644
index 00000000000..90122abd372
--- /dev/null
+++ b/tools/perf/ui/stdio/hist.c
@@ -0,0 +1,514 @@
+#include <stdio.h>
+
+#include "../../util/util.h"
+#include "../../util/hist.h"
+#include "../../util/sort.h"
+#include "../../util/evsel.h"
+
+
+static size_t callchain__fprintf_left_margin(FILE *fp, int left_margin)
+{
+ int i;
+ int ret = fprintf(fp, " ");
+
+ for (i = 0; i < left_margin; i++)
+ ret += fprintf(fp, " ");
+
+ return ret;
+}
+
+static size_t ipchain__fprintf_graph_line(FILE *fp, int depth, int depth_mask,
+ int left_margin)
+{
+ int i;
+ size_t ret = callchain__fprintf_left_margin(fp, left_margin);
+
+ for (i = 0; i < depth; i++)
+ if (depth_mask & (1 << i))
+ ret += fprintf(fp, "| ");
+ else
+ ret += fprintf(fp, " ");
+
+ ret += fprintf(fp, "\n");
+
+ return ret;
+}
+
+static size_t ipchain__fprintf_graph(FILE *fp, struct callchain_list *chain,
+ int depth, int depth_mask, int period,
+ u64 total_samples, u64 hits,
+ int left_margin)
+{
+ int i;
+ size_t ret = 0;
+
+ ret += callchain__fprintf_left_margin(fp, left_margin);
+ for (i = 0; i < depth; i++) {
+ if (depth_mask & (1 << i))
+ ret += fprintf(fp, "|");
+ else
+ ret += fprintf(fp, " ");
+ if (!period && i == depth - 1) {
+ double percent;
+
+ percent = hits * 100.0 / total_samples;
+ ret += percent_color_fprintf(fp, "--%2.2f%%-- ", percent);
+ } else
+ ret += fprintf(fp, "%s", " ");
+ }
+ if (chain->ms.sym)
+ ret += fprintf(fp, "%s\n", chain->ms.sym->name);
+ else
+ ret += fprintf(fp, "0x%0" PRIx64 "\n", chain->ip);
+
+ return ret;
+}
+
+static struct symbol *rem_sq_bracket;
+static struct callchain_list rem_hits;
+
+static void init_rem_hits(void)
+{
+ rem_sq_bracket = malloc(sizeof(*rem_sq_bracket) + 6);
+ if (!rem_sq_bracket) {
+ fprintf(stderr, "Not enough memory to display remaining hits\n");
+ return;
+ }
+
+ strcpy(rem_sq_bracket->name, "[...]");
+ rem_hits.ms.sym = rem_sq_bracket;
+}
+
+static size_t __callchain__fprintf_graph(FILE *fp, struct rb_root *root,
+ u64 total_samples, int depth,
+ int depth_mask, int left_margin)
+{
+ struct rb_node *node, *next;
+ struct callchain_node *child;
+ struct callchain_list *chain;
+ int new_depth_mask = depth_mask;
+ u64 remaining;
+ size_t ret = 0;
+ int i;
+ uint entries_printed = 0;
+
+ remaining = total_samples;
+
+ node = rb_first(root);
+ while (node) {
+ u64 new_total;
+ u64 cumul;
+
+ child = rb_entry(node, struct callchain_node, rb_node);
+ cumul = callchain_cumul_hits(child);
+ remaining -= cumul;
+
+ /*
+ * The depth mask manages the output of pipes that show
+ * the depth. We don't want to keep the pipes of the current
+ * level for the last child of this depth.
+ * Except if we have remaining filtered hits. They will
+ * supersede the last child
+ */
+ next = rb_next(node);
+ if (!next && (callchain_param.mode != CHAIN_GRAPH_REL || !remaining))
+ new_depth_mask &= ~(1 << (depth - 1));
+
+ /*
+ * But we keep the older depth mask for the line separator
+ * to keep the level link until we reach the last child
+ */
+ ret += ipchain__fprintf_graph_line(fp, depth, depth_mask,
+ left_margin);
+ i = 0;
+ list_for_each_entry(chain, &child->val, list) {
+ ret += ipchain__fprintf_graph(fp, chain, depth,
+ new_depth_mask, i++,
+ total_samples,
+ cumul,
+ left_margin);
+ }
+
+ if (callchain_param.mode == CHAIN_GRAPH_REL)
+ new_total = child->children_hit;
+ else
+ new_total = total_samples;
+
+ ret += __callchain__fprintf_graph(fp, &child->rb_root, new_total,
+ depth + 1,
+ new_depth_mask | (1 << depth),
+ left_margin);
+ node = next;
+ if (++entries_printed == callchain_param.print_limit)
+ break;
+ }
+
+ if (callchain_param.mode == CHAIN_GRAPH_REL &&
+ remaining && remaining != total_samples) {
+
+ if (!rem_sq_bracket)
+ return ret;
+
+ new_depth_mask &= ~(1 << (depth - 1));
+ ret += ipchain__fprintf_graph(fp, &rem_hits, depth,
+ new_depth_mask, 0, total_samples,
+ remaining, left_margin);
+ }
+
+ return ret;
+}
+
+static size_t callchain__fprintf_graph(FILE *fp, struct rb_root *root,
+ u64 total_samples, int left_margin)
+{
+ struct callchain_node *cnode;
+ struct callchain_list *chain;
+ u32 entries_printed = 0;
+ bool printed = false;
+ struct rb_node *node;
+ int i = 0;
+ int ret = 0;
+
+ /*
+ * If have one single callchain root, don't bother printing
+ * its percentage (100 % in fractal mode and the same percentage
+ * than the hist in graph mode). This also avoid one level of column.
+ */
+ node = rb_first(root);
+ if (node && !rb_next(node)) {
+ cnode = rb_entry(node, struct callchain_node, rb_node);
+ list_for_each_entry(chain, &cnode->val, list) {
+ /*
+ * If we sort by symbol, the first entry is the same than
+ * the symbol. No need to print it otherwise it appears as
+ * displayed twice.
+ */
+ if (!i++ && field_order == NULL &&
+ sort_order && !prefixcmp(sort_order, "sym"))
+ continue;
+ if (!printed) {
+ ret += callchain__fprintf_left_margin(fp, left_margin);
+ ret += fprintf(fp, "|\n");
+ ret += callchain__fprintf_left_margin(fp, left_margin);
+ ret += fprintf(fp, "---");
+ left_margin += 3;
+ printed = true;
+ } else
+ ret += callchain__fprintf_left_margin(fp, left_margin);
+
+ if (chain->ms.sym)
+ ret += fprintf(fp, " %s\n", chain->ms.sym->name);
+ else
+ ret += fprintf(fp, " %p\n", (void *)(long)chain->ip);
+
+ if (++entries_printed == callchain_param.print_limit)
+ break;
+ }
+ root = &cnode->rb_root;
+ }
+
+ ret += __callchain__fprintf_graph(fp, root, total_samples,
+ 1, 1, left_margin);
+ ret += fprintf(fp, "\n");
+
+ return ret;
+}
+
+static size_t __callchain__fprintf_flat(FILE *fp, struct callchain_node *node,
+ u64 total_samples)
+{
+ struct callchain_list *chain;
+ size_t ret = 0;
+
+ if (!node)
+ return 0;
+
+ ret += __callchain__fprintf_flat(fp, node->parent, total_samples);
+
+
+ list_for_each_entry(chain, &node->val, list) {
+ if (chain->ip >= PERF_CONTEXT_MAX)
+ continue;
+ if (chain->ms.sym)
+ ret += fprintf(fp, " %s\n", chain->ms.sym->name);
+ else
+ ret += fprintf(fp, " %p\n",
+ (void *)(long)chain->ip);
+ }
+
+ return ret;
+}
+
+static size_t callchain__fprintf_flat(FILE *fp, struct rb_root *tree,
+ u64 total_samples)
+{
+ size_t ret = 0;
+ u32 entries_printed = 0;
+ struct callchain_node *chain;
+ struct rb_node *rb_node = rb_first(tree);
+
+ while (rb_node) {
+ double percent;
+
+ chain = rb_entry(rb_node, struct callchain_node, rb_node);
+ percent = chain->hit * 100.0 / total_samples;
+
+ ret = percent_color_fprintf(fp, " %6.2f%%\n", percent);
+ ret += __callchain__fprintf_flat(fp, chain, total_samples);
+ ret += fprintf(fp, "\n");
+ if (++entries_printed == callchain_param.print_limit)
+ break;
+
+ rb_node = rb_next(rb_node);
+ }
+
+ return ret;
+}
+
+static size_t hist_entry_callchain__fprintf(struct hist_entry *he,
+ u64 total_samples, int left_margin,
+ FILE *fp)
+{
+ switch (callchain_param.mode) {
+ case CHAIN_GRAPH_REL:
+ return callchain__fprintf_graph(fp, &he->sorted_chain,
+ symbol_conf.cumulate_callchain ?
+ he->stat_acc->period : he->stat.period,
+ left_margin);
+ break;
+ case CHAIN_GRAPH_ABS:
+ return callchain__fprintf_graph(fp, &he->sorted_chain, total_samples,
+ left_margin);
+ break;
+ case CHAIN_FLAT:
+ return callchain__fprintf_flat(fp, &he->sorted_chain, total_samples);
+ break;
+ case CHAIN_NONE:
+ break;
+ default:
+ pr_err("Bad callchain mode\n");
+ }
+
+ return 0;
+}
+
+static size_t hist_entry__callchain_fprintf(struct hist_entry *he,
+ struct hists *hists,
+ FILE *fp)
+{
+ int left_margin = 0;
+ u64 total_period = hists->stats.total_period;
+
+ if (field_order == NULL && (sort_order == NULL ||
+ !prefixcmp(sort_order, "comm"))) {
+ struct perf_hpp_fmt *fmt;
+
+ perf_hpp__for_each_format(fmt) {
+ if (!perf_hpp__is_sort_entry(fmt))
+ continue;
+
+ /* must be 'comm' sort entry */
+ left_margin = fmt->width(fmt, NULL, hists_to_evsel(hists));
+ left_margin -= thread__comm_len(he->thread);
+ break;
+ }
+ }
+ return hist_entry_callchain__fprintf(he, total_period, left_margin, fp);
+}
+
+static int hist_entry__snprintf(struct hist_entry *he, struct perf_hpp *hpp)
+{
+ const char *sep = symbol_conf.field_sep;
+ struct perf_hpp_fmt *fmt;
+ char *start = hpp->buf;
+ int ret;
+ bool first = true;
+
+ if (symbol_conf.exclude_other && !he->parent)
+ return 0;
+
+ perf_hpp__for_each_format(fmt) {
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ /*
+ * If there's no field_sep, we still need
+ * to display initial ' '.
+ */
+ if (!sep || !first) {
+ ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " ");
+ advance_hpp(hpp, ret);
+ } else
+ first = false;
+
+ if (perf_hpp__use_color() && fmt->color)
+ ret = fmt->color(fmt, hpp, he);
+ else
+ ret = fmt->entry(fmt, hpp, he);
+
+ advance_hpp(hpp, ret);
+ }
+
+ return hpp->buf - start;
+}
+
+static int hist_entry__fprintf(struct hist_entry *he, size_t size,
+ struct hists *hists,
+ char *bf, size_t bfsz, FILE *fp)
+{
+ int ret;
+ struct perf_hpp hpp = {
+ .buf = bf,
+ .size = size,
+ };
+
+ if (size == 0 || size > bfsz)
+ size = hpp.size = bfsz;
+
+ hist_entry__snprintf(he, &hpp);
+
+ ret = fprintf(fp, "%s\n", bf);
+
+ if (symbol_conf.use_callchain)
+ ret += hist_entry__callchain_fprintf(he, hists, fp);
+
+ return ret;
+}
+
+size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows,
+ int max_cols, float min_pcnt, FILE *fp)
+{
+ struct perf_hpp_fmt *fmt;
+ struct rb_node *nd;
+ size_t ret = 0;
+ unsigned int width;
+ const char *sep = symbol_conf.field_sep;
+ int nr_rows = 0;
+ char bf[96];
+ struct perf_hpp dummy_hpp = {
+ .buf = bf,
+ .size = sizeof(bf),
+ };
+ bool first = true;
+ size_t linesz;
+ char *line = NULL;
+
+ init_rem_hits();
+
+
+ perf_hpp__for_each_format(fmt)
+ perf_hpp__reset_width(fmt, hists);
+
+ if (!show_header)
+ goto print_entries;
+
+ fprintf(fp, "# ");
+
+ perf_hpp__for_each_format(fmt) {
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ if (!first)
+ fprintf(fp, "%s", sep ?: " ");
+ else
+ first = false;
+
+ fmt->header(fmt, &dummy_hpp, hists_to_evsel(hists));
+ fprintf(fp, "%s", bf);
+ }
+
+ fprintf(fp, "\n");
+ if (max_rows && ++nr_rows >= max_rows)
+ goto out;
+
+ if (sep)
+ goto print_entries;
+
+ first = true;
+
+ fprintf(fp, "# ");
+
+ perf_hpp__for_each_format(fmt) {
+ unsigned int i;
+
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ if (!first)
+ fprintf(fp, "%s", sep ?: " ");
+ else
+ first = false;
+
+ width = fmt->width(fmt, &dummy_hpp, hists_to_evsel(hists));
+ for (i = 0; i < width; i++)
+ fprintf(fp, ".");
+ }
+
+ fprintf(fp, "\n");
+ if (max_rows && ++nr_rows >= max_rows)
+ goto out;
+
+ fprintf(fp, "#\n");
+ if (max_rows && ++nr_rows >= max_rows)
+ goto out;
+
+print_entries:
+ linesz = hists__sort_list_width(hists) + 3 + 1;
+ linesz += perf_hpp__color_overhead();
+ line = malloc(linesz);
+ if (line == NULL) {
+ ret = -1;
+ goto out;
+ }
+
+ for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+ float percent;
+
+ if (h->filtered)
+ continue;
+
+ percent = hist_entry__get_percent_limit(h);
+ if (percent < min_pcnt)
+ continue;
+
+ ret += hist_entry__fprintf(h, max_cols, hists, line, linesz, fp);
+
+ if (max_rows && ++nr_rows >= max_rows)
+ break;
+
+ if (h->ms.map == NULL && verbose > 1) {
+ __map_groups__fprintf_maps(h->thread->mg,
+ MAP__FUNCTION, verbose, fp);
+ fprintf(fp, "%.10s end\n", graph_dotted_line);
+ }
+ }
+
+ free(line);
+out:
+ zfree(&rem_sq_bracket);
+
+ return ret;
+}
+
+size_t events_stats__fprintf(struct events_stats *stats, FILE *fp)
+{
+ int i;
+ size_t ret = 0;
+
+ for (i = 0; i < PERF_RECORD_HEADER_MAX; ++i) {
+ const char *name;
+
+ if (stats->nr_events[i] == 0)
+ continue;
+
+ name = perf_event__name(i);
+ if (!strcmp(name, "UNKNOWN"))
+ continue;
+
+ ret += fprintf(fp, "%16s events: %10d\n", name,
+ stats->nr_events[i]);
+ }
+
+ return ret;
+}
diff --git a/tools/perf/ui/tui/helpline.c b/tools/perf/ui/tui/helpline.c
new file mode 100644
index 00000000000..1c8b9afd5d6
--- /dev/null
+++ b/tools/perf/ui/tui/helpline.c
@@ -0,0 +1,58 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <pthread.h>
+
+#include "../../util/debug.h"
+#include "../helpline.h"
+#include "../ui.h"
+#include "../libslang.h"
+
+char ui_helpline__last_msg[1024];
+
+static void tui_helpline__pop(void)
+{
+}
+
+static void tui_helpline__push(const char *msg)
+{
+ const size_t sz = sizeof(ui_helpline__current);
+
+ SLsmg_gotorc(SLtt_Screen_Rows - 1, 0);
+ SLsmg_set_color(0);
+ SLsmg_write_nstring((char *)msg, SLtt_Screen_Cols);
+ SLsmg_refresh();
+ strncpy(ui_helpline__current, msg, sz)[sz - 1] = '\0';
+}
+
+static int tui_helpline__show(const char *format, va_list ap)
+{
+ int ret;
+ static int backlog;
+
+ pthread_mutex_lock(&ui__lock);
+ ret = vscnprintf(ui_helpline__last_msg + backlog,
+ sizeof(ui_helpline__last_msg) - backlog, format, ap);
+ backlog += ret;
+
+ if (ui_helpline__last_msg[backlog - 1] == '\n') {
+ ui_helpline__puts(ui_helpline__last_msg);
+ SLsmg_refresh();
+ backlog = 0;
+ }
+ pthread_mutex_unlock(&ui__lock);
+
+ return ret;
+}
+
+struct ui_helpline tui_helpline_fns = {
+ .pop = tui_helpline__pop,
+ .push = tui_helpline__push,
+ .show = tui_helpline__show,
+};
+
+void ui_helpline__init(void)
+{
+ helpline_fns = &tui_helpline_fns;
+ ui_helpline__puts(" ");
+}
diff --git a/tools/perf/ui/tui/progress.c b/tools/perf/ui/tui/progress.c
new file mode 100644
index 00000000000..c61d14b101e
--- /dev/null
+++ b/tools/perf/ui/tui/progress.c
@@ -0,0 +1,44 @@
+#include "../cache.h"
+#include "../progress.h"
+#include "../libslang.h"
+#include "../ui.h"
+#include "tui.h"
+#include "../browser.h"
+
+static void tui_progress__update(struct ui_progress *p)
+{
+ int bar, y;
+ /*
+ * FIXME: We should have a per UI backend way of showing progress,
+ * stdio will just show a percentage as NN%, etc.
+ */
+ if (use_browser <= 0)
+ return;
+
+ if (p->total == 0)
+ return;
+
+ ui__refresh_dimensions(false);
+ pthread_mutex_lock(&ui__lock);
+ y = SLtt_Screen_Rows / 2 - 2;
+ SLsmg_set_color(0);
+ SLsmg_draw_box(y, 0, 3, SLtt_Screen_Cols);
+ SLsmg_gotorc(y++, 1);
+ SLsmg_write_string((char *)p->title);
+ SLsmg_fill_region(y, 1, 1, SLtt_Screen_Cols - 2, ' ');
+ SLsmg_set_color(HE_COLORSET_SELECTED);
+ bar = ((SLtt_Screen_Cols - 2) * p->curr) / p->total;
+ SLsmg_fill_region(y, 1, 1, bar, ' ');
+ SLsmg_refresh();
+ pthread_mutex_unlock(&ui__lock);
+}
+
+static struct ui_progress_ops tui_progress__ops =
+{
+ .update = tui_progress__update,
+};
+
+void tui_progress__init(void)
+{
+ ui_progress__ops = &tui_progress__ops;
+}
diff --git a/tools/perf/ui/tui/setup.c b/tools/perf/ui/tui/setup.c
new file mode 100644
index 00000000000..2f612562978
--- /dev/null
+++ b/tools/perf/ui/tui/setup.c
@@ -0,0 +1,151 @@
+#include <signal.h>
+#include <stdbool.h>
+
+#include "../../util/cache.h"
+#include "../../util/debug.h"
+#include "../browser.h"
+#include "../helpline.h"
+#include "../ui.h"
+#include "../util.h"
+#include "../libslang.h"
+#include "../keysyms.h"
+#include "tui.h"
+
+static volatile int ui__need_resize;
+
+extern struct perf_error_ops perf_tui_eops;
+
+extern void hist_browser__init_hpp(void);
+
+void ui__refresh_dimensions(bool force)
+{
+ if (force || ui__need_resize) {
+ ui__need_resize = 0;
+ pthread_mutex_lock(&ui__lock);
+ SLtt_get_screen_size();
+ SLsmg_reinit_smg();
+ pthread_mutex_unlock(&ui__lock);
+ }
+}
+
+static void ui__sigwinch(int sig __maybe_unused)
+{
+ ui__need_resize = 1;
+}
+
+static void ui__setup_sigwinch(void)
+{
+ static bool done;
+
+ if (done)
+ return;
+
+ done = true;
+ pthread__unblock_sigwinch();
+ signal(SIGWINCH, ui__sigwinch);
+}
+
+int ui__getch(int delay_secs)
+{
+ struct timeval timeout, *ptimeout = delay_secs ? &timeout : NULL;
+ fd_set read_set;
+ int err, key;
+
+ ui__setup_sigwinch();
+
+ FD_ZERO(&read_set);
+ FD_SET(0, &read_set);
+
+ if (delay_secs) {
+ timeout.tv_sec = delay_secs;
+ timeout.tv_usec = 0;
+ }
+
+ err = select(1, &read_set, NULL, NULL, ptimeout);
+
+ if (err == 0)
+ return K_TIMER;
+
+ if (err == -1) {
+ if (errno == EINTR)
+ return K_RESIZE;
+ return K_ERROR;
+ }
+
+ key = SLang_getkey();
+ if (key != K_ESC)
+ return key;
+
+ FD_ZERO(&read_set);
+ FD_SET(0, &read_set);
+ timeout.tv_sec = 0;
+ timeout.tv_usec = 20;
+ err = select(1, &read_set, NULL, NULL, &timeout);
+ if (err == 0)
+ return K_ESC;
+
+ SLang_ungetkey(key);
+ return SLkp_getkey();
+}
+
+static void ui__signal(int sig)
+{
+ ui__exit(false);
+ psignal(sig, "perf");
+ exit(0);
+}
+
+int ui__init(void)
+{
+ int err;
+
+ SLutf8_enable(-1);
+ SLtt_get_terminfo();
+ SLtt_get_screen_size();
+
+ err = SLsmg_init_smg();
+ if (err < 0)
+ goto out;
+ err = SLang_init_tty(0, 0, 0);
+ if (err < 0)
+ goto out;
+
+ err = SLkp_init();
+ if (err < 0) {
+ pr_err("TUI initialization failed.\n");
+ goto out;
+ }
+
+ SLkp_define_keysym((char *)"^(kB)", SL_KEY_UNTAB);
+
+ ui_helpline__init();
+ ui_browser__init();
+ tui_progress__init();
+
+ signal(SIGSEGV, ui__signal);
+ signal(SIGFPE, ui__signal);
+ signal(SIGINT, ui__signal);
+ signal(SIGQUIT, ui__signal);
+ signal(SIGTERM, ui__signal);
+
+ perf_error__register(&perf_tui_eops);
+
+ hist_browser__init_hpp();
+out:
+ return err;
+}
+
+void ui__exit(bool wait_for_ok)
+{
+ if (wait_for_ok)
+ ui__question_window("Fatal Error",
+ ui_helpline__last_msg,
+ "Press any key...", 0);
+
+ SLtt_set_cursor_visibility(1);
+ SLsmg_refresh();
+ SLsmg_reset_smg();
+ SLang_reset_tty();
+
+ perf_error__unregister(&perf_tui_eops);
+}
diff --git a/tools/perf/ui/tui/tui.h b/tools/perf/ui/tui/tui.h
new file mode 100644
index 00000000000..18961c7b6ec
--- /dev/null
+++ b/tools/perf/ui/tui/tui.h
@@ -0,0 +1,6 @@
+#ifndef _PERF_TUI_H_
+#define _PERF_TUI_H_ 1
+
+void tui_progress__init(void);
+
+#endif /* _PERF_TUI_H_ */
diff --git a/tools/perf/ui/tui/util.c b/tools/perf/ui/tui/util.c
new file mode 100644
index 00000000000..bf890f72fe8
--- /dev/null
+++ b/tools/perf/ui/tui/util.c
@@ -0,0 +1,256 @@
+#include "../../util/util.h"
+#include <signal.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/ttydefaults.h>
+
+#include "../../util/cache.h"
+#include "../../util/debug.h"
+#include "../browser.h"
+#include "../keysyms.h"
+#include "../helpline.h"
+#include "../ui.h"
+#include "../util.h"
+#include "../libslang.h"
+
+static void ui_browser__argv_write(struct ui_browser *browser,
+ void *entry, int row)
+{
+ char **arg = entry;
+ bool current_entry = ui_browser__is_current_entry(browser, row);
+
+ ui_browser__set_color(browser, current_entry ? HE_COLORSET_SELECTED :
+ HE_COLORSET_NORMAL);
+ slsmg_write_nstring(*arg, browser->width);
+}
+
+static int popup_menu__run(struct ui_browser *menu)
+{
+ int key;
+
+ if (ui_browser__show(menu, " ", "ESC: exit, ENTER|->: Select option") < 0)
+ return -1;
+
+ while (1) {
+ key = ui_browser__run(menu, 0);
+
+ switch (key) {
+ case K_RIGHT:
+ case K_ENTER:
+ key = menu->index;
+ break;
+ case K_LEFT:
+ case K_ESC:
+ case 'q':
+ case CTRL('c'):
+ key = -1;
+ break;
+ default:
+ continue;
+ }
+
+ break;
+ }
+
+ ui_browser__hide(menu);
+ return key;
+}
+
+int ui__popup_menu(int argc, char * const argv[])
+{
+ struct ui_browser menu = {
+ .entries = (void *)argv,
+ .refresh = ui_browser__argv_refresh,
+ .seek = ui_browser__argv_seek,
+ .write = ui_browser__argv_write,
+ .nr_entries = argc,
+ };
+
+ return popup_menu__run(&menu);
+}
+
+int ui_browser__input_window(const char *title, const char *text, char *input,
+ const char *exit_msg, int delay_secs)
+{
+ int x, y, len, key;
+ int max_len = 60, nr_lines = 0;
+ static char buf[50];
+ const char *t;
+
+ t = text;
+ while (1) {
+ const char *sep = strchr(t, '\n');
+
+ if (sep == NULL)
+ sep = strchr(t, '\0');
+ len = sep - t;
+ if (max_len < len)
+ max_len = len;
+ ++nr_lines;
+ if (*sep == '\0')
+ break;
+ t = sep + 1;
+ }
+
+ pthread_mutex_lock(&ui__lock);
+
+ max_len += 2;
+ nr_lines += 8;
+ y = SLtt_Screen_Rows / 2 - nr_lines / 2;
+ x = SLtt_Screen_Cols / 2 - max_len / 2;
+
+ SLsmg_set_color(0);
+ SLsmg_draw_box(y, x++, nr_lines, max_len);
+ if (title) {
+ SLsmg_gotorc(y, x + 1);
+ SLsmg_write_string((char *)title);
+ }
+ SLsmg_gotorc(++y, x);
+ nr_lines -= 7;
+ max_len -= 2;
+ SLsmg_write_wrapped_string((unsigned char *)text, y, x,
+ nr_lines, max_len, 1);
+ y += nr_lines;
+ len = 5;
+ while (len--) {
+ SLsmg_gotorc(y + len - 1, x);
+ SLsmg_write_nstring((char *)" ", max_len);
+ }
+ SLsmg_draw_box(y++, x + 1, 3, max_len - 2);
+
+ SLsmg_gotorc(y + 3, x);
+ SLsmg_write_nstring((char *)exit_msg, max_len);
+ SLsmg_refresh();
+
+ pthread_mutex_unlock(&ui__lock);
+
+ x += 2;
+ len = 0;
+ key = ui__getch(delay_secs);
+ while (key != K_TIMER && key != K_ENTER && key != K_ESC) {
+ pthread_mutex_lock(&ui__lock);
+
+ if (key == K_BKSPC) {
+ if (len == 0) {
+ pthread_mutex_unlock(&ui__lock);
+ goto next_key;
+ }
+ SLsmg_gotorc(y, x + --len);
+ SLsmg_write_char(' ');
+ } else {
+ buf[len] = key;
+ SLsmg_gotorc(y, x + len++);
+ SLsmg_write_char(key);
+ }
+ SLsmg_refresh();
+
+ pthread_mutex_unlock(&ui__lock);
+
+ /* XXX more graceful overflow handling needed */
+ if (len == sizeof(buf) - 1) {
+ ui_helpline__push("maximum size of symbol name reached!");
+ key = K_ENTER;
+ break;
+ }
+next_key:
+ key = ui__getch(delay_secs);
+ }
+
+ buf[len] = '\0';
+ strncpy(input, buf, len+1);
+ return key;
+}
+
+int ui__question_window(const char *title, const char *text,
+ const char *exit_msg, int delay_secs)
+{
+ int x, y;
+ int max_len = 0, nr_lines = 0;
+ const char *t;
+
+ t = text;
+ while (1) {
+ const char *sep = strchr(t, '\n');
+ int len;
+
+ if (sep == NULL)
+ sep = strchr(t, '\0');
+ len = sep - t;
+ if (max_len < len)
+ max_len = len;
+ ++nr_lines;
+ if (*sep == '\0')
+ break;
+ t = sep + 1;
+ }
+
+ pthread_mutex_lock(&ui__lock);
+
+ max_len += 2;
+ nr_lines += 4;
+ y = SLtt_Screen_Rows / 2 - nr_lines / 2,
+ x = SLtt_Screen_Cols / 2 - max_len / 2;
+
+ SLsmg_set_color(0);
+ SLsmg_draw_box(y, x++, nr_lines, max_len);
+ if (title) {
+ SLsmg_gotorc(y, x + 1);
+ SLsmg_write_string((char *)title);
+ }
+ SLsmg_gotorc(++y, x);
+ nr_lines -= 2;
+ max_len -= 2;
+ SLsmg_write_wrapped_string((unsigned char *)text, y, x,
+ nr_lines, max_len, 1);
+ SLsmg_gotorc(y + nr_lines - 2, x);
+ SLsmg_write_nstring((char *)" ", max_len);
+ SLsmg_gotorc(y + nr_lines - 1, x);
+ SLsmg_write_nstring((char *)exit_msg, max_len);
+ SLsmg_refresh();
+
+ pthread_mutex_unlock(&ui__lock);
+
+ return ui__getch(delay_secs);
+}
+
+int ui__help_window(const char *text)
+{
+ return ui__question_window("Help", text, "Press any key...", 0);
+}
+
+int ui__dialog_yesno(const char *msg)
+{
+ return ui__question_window(NULL, msg, "Enter: Yes, ESC: No", 0);
+}
+
+static int __ui__warning(const char *title, const char *format, va_list args)
+{
+ char *s;
+
+ if (vasprintf(&s, format, args) > 0) {
+ int key;
+
+ key = ui__question_window(title, s, "Press any key...", 0);
+ free(s);
+ return key;
+ }
+
+ fprintf(stderr, "%s\n", title);
+ vfprintf(stderr, format, args);
+ return K_ESC;
+}
+
+static int perf_tui__error(const char *format, va_list args)
+{
+ return __ui__warning("Error:", format, args);
+}
+
+static int perf_tui__warning(const char *format, va_list args)
+{
+ return __ui__warning("Warning:", format, args);
+}
+
+struct perf_error_ops perf_tui_eops = {
+ .error = perf_tui__error,
+ .warning = perf_tui__warning,
+};
diff --git a/tools/perf/ui/ui.h b/tools/perf/ui/ui.h
new file mode 100644
index 00000000000..ab88383f8be
--- /dev/null
+++ b/tools/perf/ui/ui.h
@@ -0,0 +1,29 @@
+#ifndef _PERF_UI_H_
+#define _PERF_UI_H_ 1
+
+#include <pthread.h>
+#include <stdbool.h>
+#include <linux/compiler.h>
+
+extern pthread_mutex_t ui__lock;
+extern void *perf_gtk_handle;
+
+extern int use_browser;
+
+void setup_browser(bool fallback_to_pager);
+void exit_browser(bool wait_for_ok);
+
+#ifdef HAVE_SLANG_SUPPORT
+int ui__init(void);
+void ui__exit(bool wait_for_ok);
+#else
+static inline int ui__init(void)
+{
+ return -1;
+}
+static inline void ui__exit(bool wait_for_ok __maybe_unused) {}
+#endif
+
+void ui__refresh_dimensions(bool force);
+
+#endif /* _PERF_UI_H_ */
diff --git a/tools/perf/ui/util.c b/tools/perf/ui/util.c
new file mode 100644
index 00000000000..e3e0a963d03
--- /dev/null
+++ b/tools/perf/ui/util.c
@@ -0,0 +1,84 @@
+#include "util.h"
+#include "../debug.h"
+
+
+/*
+ * Default error logging functions
+ */
+static int perf_stdio__error(const char *format, va_list args)
+{
+ fprintf(stderr, "Error:\n");
+ vfprintf(stderr, format, args);
+ return 0;
+}
+
+static int perf_stdio__warning(const char *format, va_list args)
+{
+ fprintf(stderr, "Warning:\n");
+ vfprintf(stderr, format, args);
+ return 0;
+}
+
+static struct perf_error_ops default_eops =
+{
+ .error = perf_stdio__error,
+ .warning = perf_stdio__warning,
+};
+
+static struct perf_error_ops *perf_eops = &default_eops;
+
+
+int ui__error(const char *format, ...)
+{
+ int ret;
+ va_list args;
+
+ va_start(args, format);
+ ret = perf_eops->error(format, args);
+ va_end(args);
+
+ return ret;
+}
+
+int ui__warning(const char *format, ...)
+{
+ int ret;
+ va_list args;
+
+ va_start(args, format);
+ ret = perf_eops->warning(format, args);
+ va_end(args);
+
+ return ret;
+}
+
+/**
+ * perf_error__register - Register error logging functions
+ * @eops: The pointer to error logging function struct
+ *
+ * Register UI-specific error logging functions. Before calling this,
+ * other logging functions should be unregistered, if any.
+ */
+int perf_error__register(struct perf_error_ops *eops)
+{
+ if (perf_eops != &default_eops)
+ return -1;
+
+ perf_eops = eops;
+ return 0;
+}
+
+/**
+ * perf_error__unregister - Unregister error logging functions
+ * @eops: The pointer to error logging function struct
+ *
+ * Unregister already registered error logging functions.
+ */
+int perf_error__unregister(struct perf_error_ops *eops)
+{
+ if (perf_eops != eops)
+ return -1;
+
+ perf_eops = &default_eops;
+ return 0;
+}
diff --git a/tools/perf/ui/util.h b/tools/perf/ui/util.h
new file mode 100644
index 00000000000..361f08c52d3
--- /dev/null
+++ b/tools/perf/ui/util.h
@@ -0,0 +1,21 @@
+#ifndef _PERF_UI_UTIL_H_
+#define _PERF_UI_UTIL_H_ 1
+
+#include <stdarg.h>
+
+int ui__getch(int delay_secs);
+int ui__popup_menu(int argc, char * const argv[]);
+int ui__help_window(const char *text);
+int ui__dialog_yesno(const char *msg);
+int ui__question_window(const char *title, const char *text,
+ const char *exit_msg, int delay_secs);
+
+struct perf_error_ops {
+ int (*error)(const char *format, va_list args);
+ int (*warning)(const char *format, va_list args);
+};
+
+int perf_error__register(struct perf_error_ops *eops);
+int perf_error__unregister(struct perf_error_ops *eops);
+
+#endif /* _PERF_UI_UTIL_H_ */
diff --git a/tools/perf/util/PERF-VERSION-GEN b/tools/perf/util/PERF-VERSION-GEN
new file mode 100755
index 00000000000..39f17507578
--- /dev/null
+++ b/tools/perf/util/PERF-VERSION-GEN
@@ -0,0 +1,50 @@
+#!/bin/sh
+
+if [ $# -eq 1 ] ; then
+ OUTPUT=$1
+fi
+
+GVF=${OUTPUT}PERF-VERSION-FILE
+
+LF='
+'
+
+#
+# First check if there is a .git to get the version from git describe
+# otherwise try to get the version from the kernel Makefile
+#
+CID=
+TAG=
+if test -d ../../.git -o -f ../../.git
+then
+ TAG=$(git describe --abbrev=0 --match "v[0-9].[0-9]*" 2>/dev/null )
+ CID=$(git log -1 --abbrev=4 --pretty=format:"%h" 2>/dev/null) && CID="-g$CID"
+elif test -f ../../PERF-VERSION-FILE
+then
+ TAG=$(cut -d' ' -f3 ../../PERF-VERSION-FILE | sed -e 's/\"//g')
+fi
+if test -z "$TAG"
+then
+ TAG=$(MAKEFLAGS= make -sC ../.. kernelversion)
+fi
+VN="$TAG$CID"
+if test -n "$CID"
+then
+ # format version string, strip trailing zero of sublevel:
+ VN=$(echo "$VN" | sed -e 's/-/./g;s/\([0-9]*[.][0-9]*\)[.]0/\1/')
+fi
+
+VN=$(expr "$VN" : v*'\(.*\)')
+
+if test -r $GVF
+then
+ VC=$(sed -e 's/^#define PERF_VERSION "\(.*\)"/\1/' <$GVF)
+else
+ VC=unset
+fi
+test "$VN" = "$VC" || {
+ echo >&2 " PERF_VERSION = $VN"
+ echo "#define PERF_VERSION \"$VN\"" >$GVF
+}
+
+
diff --git a/tools/perf/util/abspath.c b/tools/perf/util/abspath.c
new file mode 100644
index 00000000000..0e76affe9c3
--- /dev/null
+++ b/tools/perf/util/abspath.c
@@ -0,0 +1,37 @@
+#include "cache.h"
+
+static const char *get_pwd_cwd(void)
+{
+ static char cwd[PATH_MAX + 1];
+ char *pwd;
+ struct stat cwd_stat, pwd_stat;
+ if (getcwd(cwd, PATH_MAX) == NULL)
+ return NULL;
+ pwd = getenv("PWD");
+ if (pwd && strcmp(pwd, cwd)) {
+ stat(cwd, &cwd_stat);
+ if (!stat(pwd, &pwd_stat) &&
+ pwd_stat.st_dev == cwd_stat.st_dev &&
+ pwd_stat.st_ino == cwd_stat.st_ino) {
+ strlcpy(cwd, pwd, PATH_MAX);
+ }
+ }
+ return cwd;
+}
+
+const char *make_nonrelative_path(const char *path)
+{
+ static char buf[PATH_MAX + 1];
+
+ if (is_absolute_path(path)) {
+ if (strlcpy(buf, path, PATH_MAX) >= PATH_MAX)
+ die("Too long path: %.*s", 60, path);
+ } else {
+ const char *cwd = get_pwd_cwd();
+ if (!cwd)
+ die("Cannot determine the current working directory");
+ if (snprintf(buf, PATH_MAX, "%s/%s", cwd, path) >= PATH_MAX)
+ die("Too long path: %.*s", 60, path);
+ }
+ return buf;
+}
diff --git a/tools/perf/util/alias.c b/tools/perf/util/alias.c
new file mode 100644
index 00000000000..c0b43ee40d9
--- /dev/null
+++ b/tools/perf/util/alias.c
@@ -0,0 +1,76 @@
+#include "cache.h"
+
+static const char *alias_key;
+static char *alias_val;
+
+static int alias_lookup_cb(const char *k, const char *v,
+ void *cb __maybe_unused)
+{
+ if (!prefixcmp(k, "alias.") && !strcmp(k+6, alias_key)) {
+ if (!v)
+ return config_error_nonbool(k);
+ alias_val = strdup(v);
+ return 0;
+ }
+ return 0;
+}
+
+char *alias_lookup(const char *alias)
+{
+ alias_key = alias;
+ alias_val = NULL;
+ perf_config(alias_lookup_cb, NULL);
+ return alias_val;
+}
+
+int split_cmdline(char *cmdline, const char ***argv)
+{
+ int src, dst, count = 0, size = 16;
+ char quoted = 0;
+
+ *argv = malloc(sizeof(char*) * size);
+
+ /* split alias_string */
+ (*argv)[count++] = cmdline;
+ for (src = dst = 0; cmdline[src];) {
+ char c = cmdline[src];
+ if (!quoted && isspace(c)) {
+ cmdline[dst++] = 0;
+ while (cmdline[++src]
+ && isspace(cmdline[src]))
+ ; /* skip */
+ if (count >= size) {
+ size += 16;
+ *argv = realloc(*argv, sizeof(char*) * size);
+ }
+ (*argv)[count++] = cmdline + dst;
+ } else if (!quoted && (c == '\'' || c == '"')) {
+ quoted = c;
+ src++;
+ } else if (c == quoted) {
+ quoted = 0;
+ src++;
+ } else {
+ if (c == '\\' && quoted != '\'') {
+ src++;
+ c = cmdline[src];
+ if (!c) {
+ zfree(argv);
+ return error("cmdline ends with \\");
+ }
+ }
+ cmdline[dst++] = c;
+ src++;
+ }
+ }
+
+ cmdline[dst] = 0;
+
+ if (quoted) {
+ zfree(argv);
+ return error("unclosed quote");
+ }
+
+ return count;
+}
+
diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c
new file mode 100644
index 00000000000..809b4c50bea
--- /dev/null
+++ b/tools/perf/util/annotate.c
@@ -0,0 +1,1412 @@
+/*
+ * Copyright (C) 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
+ *
+ * Parts came from builtin-annotate.c, see those files for further
+ * copyright notes.
+ *
+ * Released under the GPL v2. (and only v2, not any later version)
+ */
+
+#include "util.h"
+#include "ui/ui.h"
+#include "sort.h"
+#include "build-id.h"
+#include "color.h"
+#include "cache.h"
+#include "symbol.h"
+#include "debug.h"
+#include "annotate.h"
+#include "evsel.h"
+#include <pthread.h>
+#include <linux/bitops.h>
+
+const char *disassembler_style;
+const char *objdump_path;
+
+static struct ins *ins__find(const char *name);
+static int disasm_line__parse(char *line, char **namep, char **rawp);
+
+static void ins__delete(struct ins_operands *ops)
+{
+ zfree(&ops->source.raw);
+ zfree(&ops->source.name);
+ zfree(&ops->target.raw);
+ zfree(&ops->target.name);
+}
+
+static int ins__raw_scnprintf(struct ins *ins, char *bf, size_t size,
+ struct ins_operands *ops)
+{
+ return scnprintf(bf, size, "%-6.6s %s", ins->name, ops->raw);
+}
+
+int ins__scnprintf(struct ins *ins, char *bf, size_t size,
+ struct ins_operands *ops)
+{
+ if (ins->ops->scnprintf)
+ return ins->ops->scnprintf(ins, bf, size, ops);
+
+ return ins__raw_scnprintf(ins, bf, size, ops);
+}
+
+static int call__parse(struct ins_operands *ops)
+{
+ char *endptr, *tok, *name;
+
+ ops->target.addr = strtoull(ops->raw, &endptr, 16);
+
+ name = strchr(endptr, '<');
+ if (name == NULL)
+ goto indirect_call;
+
+ name++;
+
+ tok = strchr(name, '>');
+ if (tok == NULL)
+ return -1;
+
+ *tok = '\0';
+ ops->target.name = strdup(name);
+ *tok = '>';
+
+ return ops->target.name == NULL ? -1 : 0;
+
+indirect_call:
+ tok = strchr(endptr, '(');
+ if (tok != NULL) {
+ ops->target.addr = 0;
+ return 0;
+ }
+
+ tok = strchr(endptr, '*');
+ if (tok == NULL)
+ return -1;
+
+ ops->target.addr = strtoull(tok + 1, NULL, 16);
+ return 0;
+}
+
+static int call__scnprintf(struct ins *ins, char *bf, size_t size,
+ struct ins_operands *ops)
+{
+ if (ops->target.name)
+ return scnprintf(bf, size, "%-6.6s %s", ins->name, ops->target.name);
+
+ if (ops->target.addr == 0)
+ return ins__raw_scnprintf(ins, bf, size, ops);
+
+ return scnprintf(bf, size, "%-6.6s *%" PRIx64, ins->name, ops->target.addr);
+}
+
+static struct ins_ops call_ops = {
+ .parse = call__parse,
+ .scnprintf = call__scnprintf,
+};
+
+bool ins__is_call(const struct ins *ins)
+{
+ return ins->ops == &call_ops;
+}
+
+static int jump__parse(struct ins_operands *ops)
+{
+ const char *s = strchr(ops->raw, '+');
+
+ ops->target.addr = strtoull(ops->raw, NULL, 16);
+
+ if (s++ != NULL)
+ ops->target.offset = strtoull(s, NULL, 16);
+ else
+ ops->target.offset = UINT64_MAX;
+
+ return 0;
+}
+
+static int jump__scnprintf(struct ins *ins, char *bf, size_t size,
+ struct ins_operands *ops)
+{
+ return scnprintf(bf, size, "%-6.6s %" PRIx64, ins->name, ops->target.offset);
+}
+
+static struct ins_ops jump_ops = {
+ .parse = jump__parse,
+ .scnprintf = jump__scnprintf,
+};
+
+bool ins__is_jump(const struct ins *ins)
+{
+ return ins->ops == &jump_ops;
+}
+
+static int comment__symbol(char *raw, char *comment, u64 *addrp, char **namep)
+{
+ char *endptr, *name, *t;
+
+ if (strstr(raw, "(%rip)") == NULL)
+ return 0;
+
+ *addrp = strtoull(comment, &endptr, 16);
+ name = strchr(endptr, '<');
+ if (name == NULL)
+ return -1;
+
+ name++;
+
+ t = strchr(name, '>');
+ if (t == NULL)
+ return 0;
+
+ *t = '\0';
+ *namep = strdup(name);
+ *t = '>';
+
+ return 0;
+}
+
+static int lock__parse(struct ins_operands *ops)
+{
+ char *name;
+
+ ops->locked.ops = zalloc(sizeof(*ops->locked.ops));
+ if (ops->locked.ops == NULL)
+ return 0;
+
+ if (disasm_line__parse(ops->raw, &name, &ops->locked.ops->raw) < 0)
+ goto out_free_ops;
+
+ ops->locked.ins = ins__find(name);
+ if (ops->locked.ins == NULL)
+ goto out_free_ops;
+
+ if (!ops->locked.ins->ops)
+ return 0;
+
+ if (ops->locked.ins->ops->parse)
+ ops->locked.ins->ops->parse(ops->locked.ops);
+
+ return 0;
+
+out_free_ops:
+ zfree(&ops->locked.ops);
+ return 0;
+}
+
+static int lock__scnprintf(struct ins *ins, char *bf, size_t size,
+ struct ins_operands *ops)
+{
+ int printed;
+
+ if (ops->locked.ins == NULL)
+ return ins__raw_scnprintf(ins, bf, size, ops);
+
+ printed = scnprintf(bf, size, "%-6.6s ", ins->name);
+ return printed + ins__scnprintf(ops->locked.ins, bf + printed,
+ size - printed, ops->locked.ops);
+}
+
+static void lock__delete(struct ins_operands *ops)
+{
+ zfree(&ops->locked.ops);
+ zfree(&ops->target.raw);
+ zfree(&ops->target.name);
+}
+
+static struct ins_ops lock_ops = {
+ .free = lock__delete,
+ .parse = lock__parse,
+ .scnprintf = lock__scnprintf,
+};
+
+static int mov__parse(struct ins_operands *ops)
+{
+ char *s = strchr(ops->raw, ','), *target, *comment, prev;
+
+ if (s == NULL)
+ return -1;
+
+ *s = '\0';
+ ops->source.raw = strdup(ops->raw);
+ *s = ',';
+
+ if (ops->source.raw == NULL)
+ return -1;
+
+ target = ++s;
+
+ while (s[0] != '\0' && !isspace(s[0]))
+ ++s;
+ prev = *s;
+ *s = '\0';
+
+ ops->target.raw = strdup(target);
+ *s = prev;
+
+ if (ops->target.raw == NULL)
+ goto out_free_source;
+
+ comment = strchr(s, '#');
+ if (comment == NULL)
+ return 0;
+
+ while (comment[0] != '\0' && isspace(comment[0]))
+ ++comment;
+
+ comment__symbol(ops->source.raw, comment, &ops->source.addr, &ops->source.name);
+ comment__symbol(ops->target.raw, comment, &ops->target.addr, &ops->target.name);
+
+ return 0;
+
+out_free_source:
+ zfree(&ops->source.raw);
+ return -1;
+}
+
+static int mov__scnprintf(struct ins *ins, char *bf, size_t size,
+ struct ins_operands *ops)
+{
+ return scnprintf(bf, size, "%-6.6s %s,%s", ins->name,
+ ops->source.name ?: ops->source.raw,
+ ops->target.name ?: ops->target.raw);
+}
+
+static struct ins_ops mov_ops = {
+ .parse = mov__parse,
+ .scnprintf = mov__scnprintf,
+};
+
+static int dec__parse(struct ins_operands *ops)
+{
+ char *target, *comment, *s, prev;
+
+ target = s = ops->raw;
+
+ while (s[0] != '\0' && !isspace(s[0]))
+ ++s;
+ prev = *s;
+ *s = '\0';
+
+ ops->target.raw = strdup(target);
+ *s = prev;
+
+ if (ops->target.raw == NULL)
+ return -1;
+
+ comment = strchr(s, '#');
+ if (comment == NULL)
+ return 0;
+
+ while (comment[0] != '\0' && isspace(comment[0]))
+ ++comment;
+
+ comment__symbol(ops->target.raw, comment, &ops->target.addr, &ops->target.name);
+
+ return 0;
+}
+
+static int dec__scnprintf(struct ins *ins, char *bf, size_t size,
+ struct ins_operands *ops)
+{
+ return scnprintf(bf, size, "%-6.6s %s", ins->name,
+ ops->target.name ?: ops->target.raw);
+}
+
+static struct ins_ops dec_ops = {
+ .parse = dec__parse,
+ .scnprintf = dec__scnprintf,
+};
+
+static int nop__scnprintf(struct ins *ins __maybe_unused, char *bf, size_t size,
+ struct ins_operands *ops __maybe_unused)
+{
+ return scnprintf(bf, size, "%-6.6s", "nop");
+}
+
+static struct ins_ops nop_ops = {
+ .scnprintf = nop__scnprintf,
+};
+
+/*
+ * Must be sorted by name!
+ */
+static struct ins instructions[] = {
+ { .name = "add", .ops = &mov_ops, },
+ { .name = "addl", .ops = &mov_ops, },
+ { .name = "addq", .ops = &mov_ops, },
+ { .name = "addw", .ops = &mov_ops, },
+ { .name = "and", .ops = &mov_ops, },
+ { .name = "bts", .ops = &mov_ops, },
+ { .name = "call", .ops = &call_ops, },
+ { .name = "callq", .ops = &call_ops, },
+ { .name = "cmp", .ops = &mov_ops, },
+ { .name = "cmpb", .ops = &mov_ops, },
+ { .name = "cmpl", .ops = &mov_ops, },
+ { .name = "cmpq", .ops = &mov_ops, },
+ { .name = "cmpw", .ops = &mov_ops, },
+ { .name = "cmpxch", .ops = &mov_ops, },
+ { .name = "dec", .ops = &dec_ops, },
+ { .name = "decl", .ops = &dec_ops, },
+ { .name = "imul", .ops = &mov_ops, },
+ { .name = "inc", .ops = &dec_ops, },
+ { .name = "incl", .ops = &dec_ops, },
+ { .name = "ja", .ops = &jump_ops, },
+ { .name = "jae", .ops = &jump_ops, },
+ { .name = "jb", .ops = &jump_ops, },
+ { .name = "jbe", .ops = &jump_ops, },
+ { .name = "jc", .ops = &jump_ops, },
+ { .name = "jcxz", .ops = &jump_ops, },
+ { .name = "je", .ops = &jump_ops, },
+ { .name = "jecxz", .ops = &jump_ops, },
+ { .name = "jg", .ops = &jump_ops, },
+ { .name = "jge", .ops = &jump_ops, },
+ { .name = "jl", .ops = &jump_ops, },
+ { .name = "jle", .ops = &jump_ops, },
+ { .name = "jmp", .ops = &jump_ops, },
+ { .name = "jmpq", .ops = &jump_ops, },
+ { .name = "jna", .ops = &jump_ops, },
+ { .name = "jnae", .ops = &jump_ops, },
+ { .name = "jnb", .ops = &jump_ops, },
+ { .name = "jnbe", .ops = &jump_ops, },
+ { .name = "jnc", .ops = &jump_ops, },
+ { .name = "jne", .ops = &jump_ops, },
+ { .name = "jng", .ops = &jump_ops, },
+ { .name = "jnge", .ops = &jump_ops, },
+ { .name = "jnl", .ops = &jump_ops, },
+ { .name = "jnle", .ops = &jump_ops, },
+ { .name = "jno", .ops = &jump_ops, },
+ { .name = "jnp", .ops = &jump_ops, },
+ { .name = "jns", .ops = &jump_ops, },
+ { .name = "jnz", .ops = &jump_ops, },
+ { .name = "jo", .ops = &jump_ops, },
+ { .name = "jp", .ops = &jump_ops, },
+ { .name = "jpe", .ops = &jump_ops, },
+ { .name = "jpo", .ops = &jump_ops, },
+ { .name = "jrcxz", .ops = &jump_ops, },
+ { .name = "js", .ops = &jump_ops, },
+ { .name = "jz", .ops = &jump_ops, },
+ { .name = "lea", .ops = &mov_ops, },
+ { .name = "lock", .ops = &lock_ops, },
+ { .name = "mov", .ops = &mov_ops, },
+ { .name = "movb", .ops = &mov_ops, },
+ { .name = "movdqa",.ops = &mov_ops, },
+ { .name = "movl", .ops = &mov_ops, },
+ { .name = "movq", .ops = &mov_ops, },
+ { .name = "movslq", .ops = &mov_ops, },
+ { .name = "movzbl", .ops = &mov_ops, },
+ { .name = "movzwl", .ops = &mov_ops, },
+ { .name = "nop", .ops = &nop_ops, },
+ { .name = "nopl", .ops = &nop_ops, },
+ { .name = "nopw", .ops = &nop_ops, },
+ { .name = "or", .ops = &mov_ops, },
+ { .name = "orl", .ops = &mov_ops, },
+ { .name = "test", .ops = &mov_ops, },
+ { .name = "testb", .ops = &mov_ops, },
+ { .name = "testl", .ops = &mov_ops, },
+ { .name = "xadd", .ops = &mov_ops, },
+ { .name = "xbeginl", .ops = &jump_ops, },
+ { .name = "xbeginq", .ops = &jump_ops, },
+};
+
+static int ins__cmp(const void *name, const void *insp)
+{
+ const struct ins *ins = insp;
+
+ return strcmp(name, ins->name);
+}
+
+static struct ins *ins__find(const char *name)
+{
+ const int nmemb = ARRAY_SIZE(instructions);
+
+ return bsearch(name, instructions, nmemb, sizeof(struct ins), ins__cmp);
+}
+
+int symbol__annotate_init(struct map *map __maybe_unused, struct symbol *sym)
+{
+ struct annotation *notes = symbol__annotation(sym);
+ pthread_mutex_init(&notes->lock, NULL);
+ return 0;
+}
+
+int symbol__alloc_hist(struct symbol *sym)
+{
+ struct annotation *notes = symbol__annotation(sym);
+ const size_t size = symbol__size(sym);
+ size_t sizeof_sym_hist;
+
+ /* Check for overflow when calculating sizeof_sym_hist */
+ if (size > (SIZE_MAX - sizeof(struct sym_hist)) / sizeof(u64))
+ return -1;
+
+ sizeof_sym_hist = (sizeof(struct sym_hist) + size * sizeof(u64));
+
+ /* Check for overflow in zalloc argument */
+ if (sizeof_sym_hist > (SIZE_MAX - sizeof(*notes->src))
+ / symbol_conf.nr_events)
+ return -1;
+
+ notes->src = zalloc(sizeof(*notes->src) + symbol_conf.nr_events * sizeof_sym_hist);
+ if (notes->src == NULL)
+ return -1;
+ notes->src->sizeof_sym_hist = sizeof_sym_hist;
+ notes->src->nr_histograms = symbol_conf.nr_events;
+ INIT_LIST_HEAD(&notes->src->source);
+ return 0;
+}
+
+void symbol__annotate_zero_histograms(struct symbol *sym)
+{
+ struct annotation *notes = symbol__annotation(sym);
+
+ pthread_mutex_lock(&notes->lock);
+ if (notes->src != NULL)
+ memset(notes->src->histograms, 0,
+ notes->src->nr_histograms * notes->src->sizeof_sym_hist);
+ pthread_mutex_unlock(&notes->lock);
+}
+
+static int __symbol__inc_addr_samples(struct symbol *sym, struct map *map,
+ struct annotation *notes, int evidx, u64 addr)
+{
+ unsigned offset;
+ struct sym_hist *h;
+
+ pr_debug3("%s: addr=%#" PRIx64 "\n", __func__, map->unmap_ip(map, addr));
+
+ if (addr < sym->start || addr > sym->end)
+ return -ERANGE;
+
+ offset = addr - sym->start;
+ h = annotation__histogram(notes, evidx);
+ h->sum++;
+ h->addr[offset]++;
+
+ pr_debug3("%#" PRIx64 " %s: period++ [addr: %#" PRIx64 ", %#" PRIx64
+ ", evidx=%d] => %" PRIu64 "\n", sym->start, sym->name,
+ addr, addr - sym->start, evidx, h->addr[offset]);
+ return 0;
+}
+
+static int symbol__inc_addr_samples(struct symbol *sym, struct map *map,
+ int evidx, u64 addr)
+{
+ struct annotation *notes;
+
+ if (sym == NULL)
+ return 0;
+
+ notes = symbol__annotation(sym);
+ if (notes->src == NULL) {
+ if (symbol__alloc_hist(sym) < 0)
+ return -ENOMEM;
+ }
+
+ return __symbol__inc_addr_samples(sym, map, notes, evidx, addr);
+}
+
+int addr_map_symbol__inc_samples(struct addr_map_symbol *ams, int evidx)
+{
+ return symbol__inc_addr_samples(ams->sym, ams->map, evidx, ams->al_addr);
+}
+
+int hist_entry__inc_addr_samples(struct hist_entry *he, int evidx, u64 ip)
+{
+ return symbol__inc_addr_samples(he->ms.sym, he->ms.map, evidx, ip);
+}
+
+static void disasm_line__init_ins(struct disasm_line *dl)
+{
+ dl->ins = ins__find(dl->name);
+
+ if (dl->ins == NULL)
+ return;
+
+ if (!dl->ins->ops)
+ return;
+
+ if (dl->ins->ops->parse)
+ dl->ins->ops->parse(&dl->ops);
+}
+
+static int disasm_line__parse(char *line, char **namep, char **rawp)
+{
+ char *name = line, tmp;
+
+ while (isspace(name[0]))
+ ++name;
+
+ if (name[0] == '\0')
+ return -1;
+
+ *rawp = name + 1;
+
+ while ((*rawp)[0] != '\0' && !isspace((*rawp)[0]))
+ ++*rawp;
+
+ tmp = (*rawp)[0];
+ (*rawp)[0] = '\0';
+ *namep = strdup(name);
+
+ if (*namep == NULL)
+ goto out_free_name;
+
+ (*rawp)[0] = tmp;
+
+ if ((*rawp)[0] != '\0') {
+ (*rawp)++;
+ while (isspace((*rawp)[0]))
+ ++(*rawp);
+ }
+
+ return 0;
+
+out_free_name:
+ zfree(namep);
+ return -1;
+}
+
+static struct disasm_line *disasm_line__new(s64 offset, char *line, size_t privsize)
+{
+ struct disasm_line *dl = zalloc(sizeof(*dl) + privsize);
+
+ if (dl != NULL) {
+ dl->offset = offset;
+ dl->line = strdup(line);
+ if (dl->line == NULL)
+ goto out_delete;
+
+ if (offset != -1) {
+ if (disasm_line__parse(dl->line, &dl->name, &dl->ops.raw) < 0)
+ goto out_free_line;
+
+ disasm_line__init_ins(dl);
+ }
+ }
+
+ return dl;
+
+out_free_line:
+ zfree(&dl->line);
+out_delete:
+ free(dl);
+ return NULL;
+}
+
+void disasm_line__free(struct disasm_line *dl)
+{
+ zfree(&dl->line);
+ zfree(&dl->name);
+ if (dl->ins && dl->ins->ops->free)
+ dl->ins->ops->free(&dl->ops);
+ else
+ ins__delete(&dl->ops);
+ free(dl);
+}
+
+int disasm_line__scnprintf(struct disasm_line *dl, char *bf, size_t size, bool raw)
+{
+ if (raw || !dl->ins)
+ return scnprintf(bf, size, "%-6.6s %s", dl->name, dl->ops.raw);
+
+ return ins__scnprintf(dl->ins, bf, size, &dl->ops);
+}
+
+static void disasm__add(struct list_head *head, struct disasm_line *line)
+{
+ list_add_tail(&line->node, head);
+}
+
+struct disasm_line *disasm__get_next_ip_line(struct list_head *head, struct disasm_line *pos)
+{
+ list_for_each_entry_continue(pos, head, node)
+ if (pos->offset >= 0)
+ return pos;
+
+ return NULL;
+}
+
+double disasm__calc_percent(struct annotation *notes, int evidx, s64 offset,
+ s64 end, const char **path)
+{
+ struct source_line *src_line = notes->src->lines;
+ double percent = 0.0;
+
+ if (src_line) {
+ size_t sizeof_src_line = sizeof(*src_line) +
+ sizeof(src_line->p) * (src_line->nr_pcnt - 1);
+
+ while (offset < end) {
+ src_line = (void *)notes->src->lines +
+ (sizeof_src_line * offset);
+
+ if (*path == NULL)
+ *path = src_line->path;
+
+ percent += src_line->p[evidx].percent;
+ offset++;
+ }
+ } else {
+ struct sym_hist *h = annotation__histogram(notes, evidx);
+ unsigned int hits = 0;
+
+ while (offset < end)
+ hits += h->addr[offset++];
+
+ if (h->sum)
+ percent = 100.0 * hits / h->sum;
+ }
+
+ return percent;
+}
+
+static int disasm_line__print(struct disasm_line *dl, struct symbol *sym, u64 start,
+ struct perf_evsel *evsel, u64 len, int min_pcnt, int printed,
+ int max_lines, struct disasm_line *queue)
+{
+ static const char *prev_line;
+ static const char *prev_color;
+
+ if (dl->offset != -1) {
+ const char *path = NULL;
+ double percent, max_percent = 0.0;
+ double *ppercents = &percent;
+ int i, nr_percent = 1;
+ const char *color;
+ struct annotation *notes = symbol__annotation(sym);
+ s64 offset = dl->offset;
+ const u64 addr = start + offset;
+ struct disasm_line *next;
+
+ next = disasm__get_next_ip_line(&notes->src->source, dl);
+
+ if (perf_evsel__is_group_event(evsel)) {
+ nr_percent = evsel->nr_members;
+ ppercents = calloc(nr_percent, sizeof(double));
+ if (ppercents == NULL)
+ return -1;
+ }
+
+ for (i = 0; i < nr_percent; i++) {
+ percent = disasm__calc_percent(notes,
+ notes->src->lines ? i : evsel->idx + i,
+ offset,
+ next ? next->offset : (s64) len,
+ &path);
+
+ ppercents[i] = percent;
+ if (percent > max_percent)
+ max_percent = percent;
+ }
+
+ if (max_percent < min_pcnt)
+ return -1;
+
+ if (max_lines && printed >= max_lines)
+ return 1;
+
+ if (queue != NULL) {
+ list_for_each_entry_from(queue, &notes->src->source, node) {
+ if (queue == dl)
+ break;
+ disasm_line__print(queue, sym, start, evsel, len,
+ 0, 0, 1, NULL);
+ }
+ }
+
+ color = get_percent_color(max_percent);
+
+ /*
+ * Also color the filename and line if needed, with
+ * the same color than the percentage. Don't print it
+ * twice for close colored addr with the same filename:line
+ */
+ if (path) {
+ if (!prev_line || strcmp(prev_line, path)
+ || color != prev_color) {
+ color_fprintf(stdout, color, " %s", path);
+ prev_line = path;
+ prev_color = color;
+ }
+ }
+
+ for (i = 0; i < nr_percent; i++) {
+ percent = ppercents[i];
+ color = get_percent_color(percent);
+ color_fprintf(stdout, color, " %7.2f", percent);
+ }
+
+ printf(" : ");
+ color_fprintf(stdout, PERF_COLOR_MAGENTA, " %" PRIx64 ":", addr);
+ color_fprintf(stdout, PERF_COLOR_BLUE, "%s\n", dl->line);
+
+ if (ppercents != &percent)
+ free(ppercents);
+
+ } else if (max_lines && printed >= max_lines)
+ return 1;
+ else {
+ int width = 8;
+
+ if (queue)
+ return -1;
+
+ if (perf_evsel__is_group_event(evsel))
+ width *= evsel->nr_members;
+
+ if (!*dl->line)
+ printf(" %*s:\n", width, " ");
+ else
+ printf(" %*s: %s\n", width, " ", dl->line);
+ }
+
+ return 0;
+}
+
+/*
+ * symbol__parse_objdump_line() parses objdump output (with -d --no-show-raw)
+ * which looks like following
+ *
+ * 0000000000415500 <_init>:
+ * 415500: sub $0x8,%rsp
+ * 415504: mov 0x2f5ad5(%rip),%rax # 70afe0 <_DYNAMIC+0x2f8>
+ * 41550b: test %rax,%rax
+ * 41550e: je 415515 <_init+0x15>
+ * 415510: callq 416e70 <__gmon_start__@plt>
+ * 415515: add $0x8,%rsp
+ * 415519: retq
+ *
+ * it will be parsed and saved into struct disasm_line as
+ * <offset> <name> <ops.raw>
+ *
+ * The offset will be a relative offset from the start of the symbol and -1
+ * means that it's not a disassembly line so should be treated differently.
+ * The ops.raw part will be parsed further according to type of the instruction.
+ */
+static int symbol__parse_objdump_line(struct symbol *sym, struct map *map,
+ FILE *file, size_t privsize)
+{
+ struct annotation *notes = symbol__annotation(sym);
+ struct disasm_line *dl;
+ char *line = NULL, *parsed_line, *tmp, *tmp2, *c;
+ size_t line_len;
+ s64 line_ip, offset = -1;
+
+ if (getline(&line, &line_len, file) < 0)
+ return -1;
+
+ if (!line)
+ return -1;
+
+ while (line_len != 0 && isspace(line[line_len - 1]))
+ line[--line_len] = '\0';
+
+ c = strchr(line, '\n');
+ if (c)
+ *c = 0;
+
+ line_ip = -1;
+ parsed_line = line;
+
+ /*
+ * Strip leading spaces:
+ */
+ tmp = line;
+ while (*tmp) {
+ if (*tmp != ' ')
+ break;
+ tmp++;
+ }
+
+ if (*tmp) {
+ /*
+ * Parse hexa addresses followed by ':'
+ */
+ line_ip = strtoull(tmp, &tmp2, 16);
+ if (*tmp2 != ':' || tmp == tmp2 || tmp2[1] == '\0')
+ line_ip = -1;
+ }
+
+ if (line_ip != -1) {
+ u64 start = map__rip_2objdump(map, sym->start),
+ end = map__rip_2objdump(map, sym->end);
+
+ offset = line_ip - start;
+ if ((u64)line_ip < start || (u64)line_ip > end)
+ offset = -1;
+ else
+ parsed_line = tmp2 + 1;
+ }
+
+ dl = disasm_line__new(offset, parsed_line, privsize);
+ free(line);
+
+ if (dl == NULL)
+ return -1;
+
+ if (dl->ops.target.offset == UINT64_MAX)
+ dl->ops.target.offset = dl->ops.target.addr -
+ map__rip_2objdump(map, sym->start);
+
+ /* kcore has no symbols, so add the call target name */
+ if (dl->ins && ins__is_call(dl->ins) && !dl->ops.target.name) {
+ struct addr_map_symbol target = {
+ .map = map,
+ .addr = dl->ops.target.addr,
+ };
+
+ if (!map_groups__find_ams(&target, NULL) &&
+ target.sym->start == target.al_addr)
+ dl->ops.target.name = strdup(target.sym->name);
+ }
+
+ disasm__add(&notes->src->source, dl);
+
+ return 0;
+}
+
+static void delete_last_nop(struct symbol *sym)
+{
+ struct annotation *notes = symbol__annotation(sym);
+ struct list_head *list = &notes->src->source;
+ struct disasm_line *dl;
+
+ while (!list_empty(list)) {
+ dl = list_entry(list->prev, struct disasm_line, node);
+
+ if (dl->ins && dl->ins->ops) {
+ if (dl->ins->ops != &nop_ops)
+ return;
+ } else {
+ if (!strstr(dl->line, " nop ") &&
+ !strstr(dl->line, " nopl ") &&
+ !strstr(dl->line, " nopw "))
+ return;
+ }
+
+ list_del(&dl->node);
+ disasm_line__free(dl);
+ }
+}
+
+int symbol__annotate(struct symbol *sym, struct map *map, size_t privsize)
+{
+ struct dso *dso = map->dso;
+ char *filename = dso__build_id_filename(dso, NULL, 0);
+ bool free_filename = true;
+ char command[PATH_MAX * 2];
+ FILE *file;
+ int err = 0;
+ char symfs_filename[PATH_MAX];
+ struct kcore_extract kce;
+ bool delete_extract = false;
+
+ if (filename) {
+ snprintf(symfs_filename, sizeof(symfs_filename), "%s%s",
+ symbol_conf.symfs, filename);
+ }
+
+ if (filename == NULL) {
+ if (dso->has_build_id) {
+ pr_err("Can't annotate %s: not enough memory\n",
+ sym->name);
+ return -ENOMEM;
+ }
+ goto fallback;
+ } else if (readlink(symfs_filename, command, sizeof(command)) < 0 ||
+ strstr(command, "[kernel.kallsyms]") ||
+ access(symfs_filename, R_OK)) {
+ free(filename);
+fallback:
+ /*
+ * If we don't have build-ids or the build-id file isn't in the
+ * cache, or is just a kallsyms file, well, lets hope that this
+ * DSO is the same as when 'perf record' ran.
+ */
+ filename = (char *)dso->long_name;
+ snprintf(symfs_filename, sizeof(symfs_filename), "%s%s",
+ symbol_conf.symfs, filename);
+ free_filename = false;
+ }
+
+ if (dso->symtab_type == DSO_BINARY_TYPE__KALLSYMS &&
+ !dso__is_kcore(dso)) {
+ char bf[BUILD_ID_SIZE * 2 + 16] = " with build id ";
+ char *build_id_msg = NULL;
+
+ if (dso->annotate_warned)
+ goto out_free_filename;
+
+ if (dso->has_build_id) {
+ build_id__sprintf(dso->build_id,
+ sizeof(dso->build_id), bf + 15);
+ build_id_msg = bf;
+ }
+ err = -ENOENT;
+ dso->annotate_warned = 1;
+ pr_err("Can't annotate %s:\n\n"
+ "No vmlinux file%s\nwas found in the path.\n\n"
+ "Please use:\n\n"
+ " perf buildid-cache -vu vmlinux\n\n"
+ "or:\n\n"
+ " --vmlinux vmlinux\n",
+ sym->name, build_id_msg ?: "");
+ goto out_free_filename;
+ }
+
+ pr_debug("%s: filename=%s, sym=%s, start=%#" PRIx64 ", end=%#" PRIx64 "\n", __func__,
+ filename, sym->name, map->unmap_ip(map, sym->start),
+ map->unmap_ip(map, sym->end));
+
+ pr_debug("annotating [%p] %30s : [%p] %30s\n",
+ dso, dso->long_name, sym, sym->name);
+
+ if (dso__is_kcore(dso)) {
+ kce.kcore_filename = symfs_filename;
+ kce.addr = map__rip_2objdump(map, sym->start);
+ kce.offs = sym->start;
+ kce.len = sym->end + 1 - sym->start;
+ if (!kcore_extract__create(&kce)) {
+ delete_extract = true;
+ strlcpy(symfs_filename, kce.extract_filename,
+ sizeof(symfs_filename));
+ if (free_filename) {
+ free(filename);
+ free_filename = false;
+ }
+ filename = symfs_filename;
+ }
+ }
+
+ snprintf(command, sizeof(command),
+ "%s %s%s --start-address=0x%016" PRIx64
+ " --stop-address=0x%016" PRIx64
+ " -d %s %s -C %s 2>/dev/null|grep -v %s|expand",
+ objdump_path ? objdump_path : "objdump",
+ disassembler_style ? "-M " : "",
+ disassembler_style ? disassembler_style : "",
+ map__rip_2objdump(map, sym->start),
+ map__rip_2objdump(map, sym->end+1),
+ symbol_conf.annotate_asm_raw ? "" : "--no-show-raw",
+ symbol_conf.annotate_src ? "-S" : "",
+ symfs_filename, filename);
+
+ pr_debug("Executing: %s\n", command);
+
+ file = popen(command, "r");
+ if (!file)
+ goto out_free_filename;
+
+ while (!feof(file))
+ if (symbol__parse_objdump_line(sym, map, file, privsize) < 0)
+ break;
+
+ /*
+ * kallsyms does not have symbol sizes so there may a nop at the end.
+ * Remove it.
+ */
+ if (dso__is_kcore(dso))
+ delete_last_nop(sym);
+
+ pclose(file);
+out_free_filename:
+ if (delete_extract)
+ kcore_extract__delete(&kce);
+ if (free_filename)
+ free(filename);
+ return err;
+}
+
+static void insert_source_line(struct rb_root *root, struct source_line *src_line)
+{
+ struct source_line *iter;
+ struct rb_node **p = &root->rb_node;
+ struct rb_node *parent = NULL;
+ int i, ret;
+
+ while (*p != NULL) {
+ parent = *p;
+ iter = rb_entry(parent, struct source_line, node);
+
+ ret = strcmp(iter->path, src_line->path);
+ if (ret == 0) {
+ for (i = 0; i < src_line->nr_pcnt; i++)
+ iter->p[i].percent_sum += src_line->p[i].percent;
+ return;
+ }
+
+ if (ret < 0)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ for (i = 0; i < src_line->nr_pcnt; i++)
+ src_line->p[i].percent_sum = src_line->p[i].percent;
+
+ rb_link_node(&src_line->node, parent, p);
+ rb_insert_color(&src_line->node, root);
+}
+
+static int cmp_source_line(struct source_line *a, struct source_line *b)
+{
+ int i;
+
+ for (i = 0; i < a->nr_pcnt; i++) {
+ if (a->p[i].percent_sum == b->p[i].percent_sum)
+ continue;
+ return a->p[i].percent_sum > b->p[i].percent_sum;
+ }
+
+ return 0;
+}
+
+static void __resort_source_line(struct rb_root *root, struct source_line *src_line)
+{
+ struct source_line *iter;
+ struct rb_node **p = &root->rb_node;
+ struct rb_node *parent = NULL;
+
+ while (*p != NULL) {
+ parent = *p;
+ iter = rb_entry(parent, struct source_line, node);
+
+ if (cmp_source_line(src_line, iter))
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ rb_link_node(&src_line->node, parent, p);
+ rb_insert_color(&src_line->node, root);
+}
+
+static void resort_source_line(struct rb_root *dest_root, struct rb_root *src_root)
+{
+ struct source_line *src_line;
+ struct rb_node *node;
+
+ node = rb_first(src_root);
+ while (node) {
+ struct rb_node *next;
+
+ src_line = rb_entry(node, struct source_line, node);
+ next = rb_next(node);
+ rb_erase(node, src_root);
+
+ __resort_source_line(dest_root, src_line);
+ node = next;
+ }
+}
+
+static void symbol__free_source_line(struct symbol *sym, int len)
+{
+ struct annotation *notes = symbol__annotation(sym);
+ struct source_line *src_line = notes->src->lines;
+ size_t sizeof_src_line;
+ int i;
+
+ sizeof_src_line = sizeof(*src_line) +
+ (sizeof(src_line->p) * (src_line->nr_pcnt - 1));
+
+ for (i = 0; i < len; i++) {
+ free_srcline(src_line->path);
+ src_line = (void *)src_line + sizeof_src_line;
+ }
+
+ zfree(&notes->src->lines);
+}
+
+/* Get the filename:line for the colored entries */
+static int symbol__get_source_line(struct symbol *sym, struct map *map,
+ struct perf_evsel *evsel,
+ struct rb_root *root, int len)
+{
+ u64 start;
+ int i, k;
+ int evidx = evsel->idx;
+ struct source_line *src_line;
+ struct annotation *notes = symbol__annotation(sym);
+ struct sym_hist *h = annotation__histogram(notes, evidx);
+ struct rb_root tmp_root = RB_ROOT;
+ int nr_pcnt = 1;
+ u64 h_sum = h->sum;
+ size_t sizeof_src_line = sizeof(struct source_line);
+
+ if (perf_evsel__is_group_event(evsel)) {
+ for (i = 1; i < evsel->nr_members; i++) {
+ h = annotation__histogram(notes, evidx + i);
+ h_sum += h->sum;
+ }
+ nr_pcnt = evsel->nr_members;
+ sizeof_src_line += (nr_pcnt - 1) * sizeof(src_line->p);
+ }
+
+ if (!h_sum)
+ return 0;
+
+ src_line = notes->src->lines = calloc(len, sizeof_src_line);
+ if (!notes->src->lines)
+ return -1;
+
+ start = map__rip_2objdump(map, sym->start);
+
+ for (i = 0; i < len; i++) {
+ u64 offset;
+ double percent_max = 0.0;
+
+ src_line->nr_pcnt = nr_pcnt;
+
+ for (k = 0; k < nr_pcnt; k++) {
+ h = annotation__histogram(notes, evidx + k);
+ src_line->p[k].percent = 100.0 * h->addr[i] / h->sum;
+
+ if (src_line->p[k].percent > percent_max)
+ percent_max = src_line->p[k].percent;
+ }
+
+ if (percent_max <= 0.5)
+ goto next;
+
+ offset = start + i;
+ src_line->path = get_srcline(map->dso, offset);
+ insert_source_line(&tmp_root, src_line);
+
+ next:
+ src_line = (void *)src_line + sizeof_src_line;
+ }
+
+ resort_source_line(root, &tmp_root);
+ return 0;
+}
+
+static void print_summary(struct rb_root *root, const char *filename)
+{
+ struct source_line *src_line;
+ struct rb_node *node;
+
+ printf("\nSorted summary for file %s\n", filename);
+ printf("----------------------------------------------\n\n");
+
+ if (RB_EMPTY_ROOT(root)) {
+ printf(" Nothing higher than %1.1f%%\n", MIN_GREEN);
+ return;
+ }
+
+ node = rb_first(root);
+ while (node) {
+ double percent, percent_max = 0.0;
+ const char *color;
+ char *path;
+ int i;
+
+ src_line = rb_entry(node, struct source_line, node);
+ for (i = 0; i < src_line->nr_pcnt; i++) {
+ percent = src_line->p[i].percent_sum;
+ color = get_percent_color(percent);
+ color_fprintf(stdout, color, " %7.2f", percent);
+
+ if (percent > percent_max)
+ percent_max = percent;
+ }
+
+ path = src_line->path;
+ color = get_percent_color(percent_max);
+ color_fprintf(stdout, color, " %s\n", path);
+
+ node = rb_next(node);
+ }
+}
+
+static void symbol__annotate_hits(struct symbol *sym, struct perf_evsel *evsel)
+{
+ struct annotation *notes = symbol__annotation(sym);
+ struct sym_hist *h = annotation__histogram(notes, evsel->idx);
+ u64 len = symbol__size(sym), offset;
+
+ for (offset = 0; offset < len; ++offset)
+ if (h->addr[offset] != 0)
+ printf("%*" PRIx64 ": %" PRIu64 "\n", BITS_PER_LONG / 2,
+ sym->start + offset, h->addr[offset]);
+ printf("%*s: %" PRIu64 "\n", BITS_PER_LONG / 2, "h->sum", h->sum);
+}
+
+int symbol__annotate_printf(struct symbol *sym, struct map *map,
+ struct perf_evsel *evsel, bool full_paths,
+ int min_pcnt, int max_lines, int context)
+{
+ struct dso *dso = map->dso;
+ char *filename;
+ const char *d_filename;
+ const char *evsel_name = perf_evsel__name(evsel);
+ struct annotation *notes = symbol__annotation(sym);
+ struct disasm_line *pos, *queue = NULL;
+ u64 start = map__rip_2objdump(map, sym->start);
+ int printed = 2, queue_len = 0;
+ int more = 0;
+ u64 len;
+ int width = 8;
+ int namelen, evsel_name_len, graph_dotted_len;
+
+ filename = strdup(dso->long_name);
+ if (!filename)
+ return -ENOMEM;
+
+ if (full_paths)
+ d_filename = filename;
+ else
+ d_filename = basename(filename);
+
+ len = symbol__size(sym);
+ namelen = strlen(d_filename);
+ evsel_name_len = strlen(evsel_name);
+
+ if (perf_evsel__is_group_event(evsel))
+ width *= evsel->nr_members;
+
+ printf(" %-*.*s| Source code & Disassembly of %s for %s\n",
+ width, width, "Percent", d_filename, evsel_name);
+
+ graph_dotted_len = width + namelen + evsel_name_len;
+ printf("-%-*.*s-----------------------------------------\n",
+ graph_dotted_len, graph_dotted_len, graph_dotted_line);
+
+ if (verbose)
+ symbol__annotate_hits(sym, evsel);
+
+ list_for_each_entry(pos, &notes->src->source, node) {
+ if (context && queue == NULL) {
+ queue = pos;
+ queue_len = 0;
+ }
+
+ switch (disasm_line__print(pos, sym, start, evsel, len,
+ min_pcnt, printed, max_lines,
+ queue)) {
+ case 0:
+ ++printed;
+ if (context) {
+ printed += queue_len;
+ queue = NULL;
+ queue_len = 0;
+ }
+ break;
+ case 1:
+ /* filtered by max_lines */
+ ++more;
+ break;
+ case -1:
+ default:
+ /*
+ * Filtered by min_pcnt or non IP lines when
+ * context != 0
+ */
+ if (!context)
+ break;
+ if (queue_len == context)
+ queue = list_entry(queue->node.next, typeof(*queue), node);
+ else
+ ++queue_len;
+ break;
+ }
+ }
+
+ free(filename);
+
+ return more;
+}
+
+void symbol__annotate_zero_histogram(struct symbol *sym, int evidx)
+{
+ struct annotation *notes = symbol__annotation(sym);
+ struct sym_hist *h = annotation__histogram(notes, evidx);
+
+ memset(h, 0, notes->src->sizeof_sym_hist);
+}
+
+void symbol__annotate_decay_histogram(struct symbol *sym, int evidx)
+{
+ struct annotation *notes = symbol__annotation(sym);
+ struct sym_hist *h = annotation__histogram(notes, evidx);
+ int len = symbol__size(sym), offset;
+
+ h->sum = 0;
+ for (offset = 0; offset < len; ++offset) {
+ h->addr[offset] = h->addr[offset] * 7 / 8;
+ h->sum += h->addr[offset];
+ }
+}
+
+void disasm__purge(struct list_head *head)
+{
+ struct disasm_line *pos, *n;
+
+ list_for_each_entry_safe(pos, n, head, node) {
+ list_del(&pos->node);
+ disasm_line__free(pos);
+ }
+}
+
+static size_t disasm_line__fprintf(struct disasm_line *dl, FILE *fp)
+{
+ size_t printed;
+
+ if (dl->offset == -1)
+ return fprintf(fp, "%s\n", dl->line);
+
+ printed = fprintf(fp, "%#" PRIx64 " %s", dl->offset, dl->name);
+
+ if (dl->ops.raw[0] != '\0') {
+ printed += fprintf(fp, "%.*s %s\n", 6 - (int)printed, " ",
+ dl->ops.raw);
+ }
+
+ return printed + fprintf(fp, "\n");
+}
+
+size_t disasm__fprintf(struct list_head *head, FILE *fp)
+{
+ struct disasm_line *pos;
+ size_t printed = 0;
+
+ list_for_each_entry(pos, head, node)
+ printed += disasm_line__fprintf(pos, fp);
+
+ return printed;
+}
+
+int symbol__tty_annotate(struct symbol *sym, struct map *map,
+ struct perf_evsel *evsel, bool print_lines,
+ bool full_paths, int min_pcnt, int max_lines)
+{
+ struct dso *dso = map->dso;
+ struct rb_root source_line = RB_ROOT;
+ u64 len;
+
+ if (symbol__annotate(sym, map, 0) < 0)
+ return -1;
+
+ len = symbol__size(sym);
+
+ if (print_lines) {
+ symbol__get_source_line(sym, map, evsel, &source_line, len);
+ print_summary(&source_line, dso->long_name);
+ }
+
+ symbol__annotate_printf(sym, map, evsel, full_paths,
+ min_pcnt, max_lines, 0);
+ if (print_lines)
+ symbol__free_source_line(sym, len);
+
+ disasm__purge(&symbol__annotation(sym)->src->source);
+
+ return 0;
+}
+
+int hist_entry__annotate(struct hist_entry *he, size_t privsize)
+{
+ return symbol__annotate(he->ms.sym, he->ms.map, privsize);
+}
+
+bool ui__has_annotation(void)
+{
+ return use_browser == 1 && sort__has_sym;
+}
diff --git a/tools/perf/util/annotate.h b/tools/perf/util/annotate.h
new file mode 100644
index 00000000000..112d6e26815
--- /dev/null
+++ b/tools/perf/util/annotate.h
@@ -0,0 +1,177 @@
+#ifndef __PERF_ANNOTATE_H
+#define __PERF_ANNOTATE_H
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <linux/types.h>
+#include "symbol.h"
+#include "hist.h"
+#include "sort.h"
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <pthread.h>
+
+struct ins;
+
+struct ins_operands {
+ char *raw;
+ struct {
+ char *raw;
+ char *name;
+ u64 addr;
+ u64 offset;
+ } target;
+ union {
+ struct {
+ char *raw;
+ char *name;
+ u64 addr;
+ } source;
+ struct {
+ struct ins *ins;
+ struct ins_operands *ops;
+ } locked;
+ };
+};
+
+struct ins_ops {
+ void (*free)(struct ins_operands *ops);
+ int (*parse)(struct ins_operands *ops);
+ int (*scnprintf)(struct ins *ins, char *bf, size_t size,
+ struct ins_operands *ops);
+};
+
+struct ins {
+ const char *name;
+ struct ins_ops *ops;
+};
+
+bool ins__is_jump(const struct ins *ins);
+bool ins__is_call(const struct ins *ins);
+int ins__scnprintf(struct ins *ins, char *bf, size_t size, struct ins_operands *ops);
+
+struct annotation;
+
+struct disasm_line {
+ struct list_head node;
+ s64 offset;
+ char *line;
+ char *name;
+ struct ins *ins;
+ struct ins_operands ops;
+};
+
+static inline bool disasm_line__has_offset(const struct disasm_line *dl)
+{
+ return dl->ops.target.offset != UINT64_MAX;
+}
+
+void disasm_line__free(struct disasm_line *dl);
+struct disasm_line *disasm__get_next_ip_line(struct list_head *head, struct disasm_line *pos);
+int disasm_line__scnprintf(struct disasm_line *dl, char *bf, size_t size, bool raw);
+size_t disasm__fprintf(struct list_head *head, FILE *fp);
+double disasm__calc_percent(struct annotation *notes, int evidx, s64 offset,
+ s64 end, const char **path);
+
+struct sym_hist {
+ u64 sum;
+ u64 addr[0];
+};
+
+struct source_line_percent {
+ double percent;
+ double percent_sum;
+};
+
+struct source_line {
+ struct rb_node node;
+ char *path;
+ int nr_pcnt;
+ struct source_line_percent p[1];
+};
+
+/** struct annotated_source - symbols with hits have this attached as in sannotation
+ *
+ * @histogram: Array of addr hit histograms per event being monitored
+ * @lines: If 'print_lines' is specified, per source code line percentages
+ * @source: source parsed from a disassembler like objdump -dS
+ *
+ * lines is allocated, percentages calculated and all sorted by percentage
+ * when the annotation is about to be presented, so the percentages are for
+ * one of the entries in the histogram array, i.e. for the event/counter being
+ * presented. It is deallocated right after symbol__{tui,tty,etc}_annotate
+ * returns.
+ */
+struct annotated_source {
+ struct list_head source;
+ struct source_line *lines;
+ int nr_histograms;
+ int sizeof_sym_hist;
+ struct sym_hist histograms[0];
+};
+
+struct annotation {
+ pthread_mutex_t lock;
+ struct annotated_source *src;
+};
+
+struct sannotation {
+ struct annotation annotation;
+ struct symbol symbol;
+};
+
+static inline struct sym_hist *annotation__histogram(struct annotation *notes, int idx)
+{
+ return (((void *)&notes->src->histograms) +
+ (notes->src->sizeof_sym_hist * idx));
+}
+
+static inline struct annotation *symbol__annotation(struct symbol *sym)
+{
+ struct sannotation *a = container_of(sym, struct sannotation, symbol);
+ return &a->annotation;
+}
+
+int addr_map_symbol__inc_samples(struct addr_map_symbol *ams, int evidx);
+
+int hist_entry__inc_addr_samples(struct hist_entry *he, int evidx, u64 addr);
+
+int symbol__alloc_hist(struct symbol *sym);
+void symbol__annotate_zero_histograms(struct symbol *sym);
+
+int symbol__annotate(struct symbol *sym, struct map *map, size_t privsize);
+
+int hist_entry__annotate(struct hist_entry *he, size_t privsize);
+
+int symbol__annotate_init(struct map *map __maybe_unused, struct symbol *sym);
+int symbol__annotate_printf(struct symbol *sym, struct map *map,
+ struct perf_evsel *evsel, bool full_paths,
+ int min_pcnt, int max_lines, int context);
+void symbol__annotate_zero_histogram(struct symbol *sym, int evidx);
+void symbol__annotate_decay_histogram(struct symbol *sym, int evidx);
+void disasm__purge(struct list_head *head);
+
+bool ui__has_annotation(void);
+
+int symbol__tty_annotate(struct symbol *sym, struct map *map,
+ struct perf_evsel *evsel, bool print_lines,
+ bool full_paths, int min_pcnt, int max_lines);
+
+#ifdef HAVE_SLANG_SUPPORT
+int symbol__tui_annotate(struct symbol *sym, struct map *map,
+ struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt);
+#else
+static inline int symbol__tui_annotate(struct symbol *sym __maybe_unused,
+ struct map *map __maybe_unused,
+ struct perf_evsel *evsel __maybe_unused,
+ struct hist_browser_timer *hbt
+ __maybe_unused)
+{
+ return 0;
+}
+#endif
+
+extern const char *disassembler_style;
+
+#endif /* __PERF_ANNOTATE_H */
diff --git a/tools/perf/util/bitmap.c b/tools/perf/util/bitmap.c
new file mode 100644
index 00000000000..0a1adc1111f
--- /dev/null
+++ b/tools/perf/util/bitmap.c
@@ -0,0 +1,31 @@
+/*
+ * From lib/bitmap.c
+ * Helper functions for bitmap.h.
+ *
+ * This source code is licensed under the GNU General Public License,
+ * Version 2. See the file COPYING for more details.
+ */
+#include <linux/bitmap.h>
+
+int __bitmap_weight(const unsigned long *bitmap, int bits)
+{
+ int k, w = 0, lim = bits/BITS_PER_LONG;
+
+ for (k = 0; k < lim; k++)
+ w += hweight_long(bitmap[k]);
+
+ if (bits % BITS_PER_LONG)
+ w += hweight_long(bitmap[k] & BITMAP_LAST_WORD_MASK(bits));
+
+ return w;
+}
+
+void __bitmap_or(unsigned long *dst, const unsigned long *bitmap1,
+ const unsigned long *bitmap2, int bits)
+{
+ int k;
+ int nr = BITS_TO_LONGS(bits);
+
+ for (k = 0; k < nr; k++)
+ dst[k] = bitmap1[k] | bitmap2[k];
+}
diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c
new file mode 100644
index 00000000000..a904a4cfe7d
--- /dev/null
+++ b/tools/perf/util/build-id.c
@@ -0,0 +1,108 @@
+/*
+ * build-id.c
+ *
+ * build-id support
+ *
+ * Copyright (C) 2009, 2010 Red Hat Inc.
+ * Copyright (C) 2009, 2010 Arnaldo Carvalho de Melo <acme@redhat.com>
+ */
+#include "util.h"
+#include <stdio.h>
+#include "build-id.h"
+#include "event.h"
+#include "symbol.h"
+#include <linux/kernel.h>
+#include "debug.h"
+#include "session.h"
+#include "tool.h"
+
+int build_id__mark_dso_hit(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel __maybe_unused,
+ struct machine *machine)
+{
+ struct addr_location al;
+ u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+ struct thread *thread = machine__findnew_thread(machine, sample->pid,
+ sample->tid);
+
+ if (thread == NULL) {
+ pr_err("problem processing %d event, skipping it.\n",
+ event->header.type);
+ return -1;
+ }
+
+ thread__find_addr_map(thread, machine, cpumode, MAP__FUNCTION,
+ sample->ip, &al);
+
+ if (al.map != NULL)
+ al.map->dso->hit = 1;
+
+ return 0;
+}
+
+static int perf_event__exit_del_thread(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample
+ __maybe_unused,
+ struct machine *machine)
+{
+ struct thread *thread = machine__findnew_thread(machine,
+ event->fork.pid,
+ event->fork.tid);
+
+ dump_printf("(%d:%d):(%d:%d)\n", event->fork.pid, event->fork.tid,
+ event->fork.ppid, event->fork.ptid);
+
+ if (thread) {
+ rb_erase(&thread->rb_node, &machine->threads);
+ machine->last_match = NULL;
+ thread__delete(thread);
+ }
+
+ return 0;
+}
+
+struct perf_tool build_id__mark_dso_hit_ops = {
+ .sample = build_id__mark_dso_hit,
+ .mmap = perf_event__process_mmap,
+ .mmap2 = perf_event__process_mmap2,
+ .fork = perf_event__process_fork,
+ .exit = perf_event__exit_del_thread,
+ .attr = perf_event__process_attr,
+ .build_id = perf_event__process_build_id,
+};
+
+int build_id__sprintf(const u8 *build_id, int len, char *bf)
+{
+ char *bid = bf;
+ const u8 *raw = build_id;
+ int i;
+
+ for (i = 0; i < len; ++i) {
+ sprintf(bid, "%02x", *raw);
+ ++raw;
+ bid += 2;
+ }
+
+ return raw - build_id;
+}
+
+char *dso__build_id_filename(const struct dso *dso, char *bf, size_t size)
+{
+ char build_id_hex[BUILD_ID_SIZE * 2 + 1];
+
+ if (!dso->has_build_id)
+ return NULL;
+
+ build_id__sprintf(dso->build_id, sizeof(dso->build_id), build_id_hex);
+ if (bf == NULL) {
+ if (asprintf(&bf, "%s/.build-id/%.2s/%s", buildid_dir,
+ build_id_hex, build_id_hex + 2) < 0)
+ return NULL;
+ } else
+ snprintf(bf, size, "%s/.build-id/%.2s/%s", buildid_dir,
+ build_id_hex, build_id_hex + 2);
+ return bf;
+}
diff --git a/tools/perf/util/build-id.h b/tools/perf/util/build-id.h
new file mode 100644
index 00000000000..ae392561470
--- /dev/null
+++ b/tools/perf/util/build-id.h
@@ -0,0 +1,18 @@
+#ifndef PERF_BUILD_ID_H_
+#define PERF_BUILD_ID_H_ 1
+
+#define BUILD_ID_SIZE 20
+
+#include "tool.h"
+#include <linux/types.h>
+
+extern struct perf_tool build_id__mark_dso_hit_ops;
+struct dso;
+
+int build_id__sprintf(const u8 *build_id, int len, char *bf);
+char *dso__build_id_filename(const struct dso *dso, char *bf, size_t size);
+
+int build_id__mark_dso_hit(struct perf_tool *tool, union perf_event *event,
+ struct perf_sample *sample, struct perf_evsel *evsel,
+ struct machine *machine);
+#endif
diff --git a/tools/perf/util/cache.h b/tools/perf/util/cache.h
new file mode 100644
index 00000000000..7b176dd02e1
--- /dev/null
+++ b/tools/perf/util/cache.h
@@ -0,0 +1,76 @@
+#ifndef __PERF_CACHE_H
+#define __PERF_CACHE_H
+
+#include <stdbool.h>
+#include "util.h"
+#include "strbuf.h"
+#include "../perf.h"
+#include "../ui/ui.h"
+
+#define CMD_EXEC_PATH "--exec-path"
+#define CMD_PERF_DIR "--perf-dir="
+#define CMD_WORK_TREE "--work-tree="
+#define CMD_DEBUGFS_DIR "--debugfs-dir="
+
+#define PERF_DIR_ENVIRONMENT "PERF_DIR"
+#define PERF_WORK_TREE_ENVIRONMENT "PERF_WORK_TREE"
+#define EXEC_PATH_ENVIRONMENT "PERF_EXEC_PATH"
+#define DEFAULT_PERF_DIR_ENVIRONMENT ".perf"
+#define PERF_DEBUGFS_ENVIRONMENT "PERF_DEBUGFS_DIR"
+
+typedef int (*config_fn_t)(const char *, const char *, void *);
+extern int perf_default_config(const char *, const char *, void *);
+extern int perf_config(config_fn_t fn, void *);
+extern int perf_config_int(const char *, const char *);
+extern int perf_config_bool(const char *, const char *);
+extern int config_error_nonbool(const char *);
+extern const char *perf_config_dirname(const char *, const char *);
+
+/* pager.c */
+extern void setup_pager(void);
+extern const char *pager_program;
+extern int pager_in_use(void);
+extern int pager_use_color;
+
+char *alias_lookup(const char *alias);
+int split_cmdline(char *cmdline, const char ***argv);
+
+#define alloc_nr(x) (((x)+16)*3/2)
+
+/*
+ * Realloc the buffer pointed at by variable 'x' so that it can hold
+ * at least 'nr' entries; the number of entries currently allocated
+ * is 'alloc', using the standard growing factor alloc_nr() macro.
+ *
+ * DO NOT USE any expression with side-effect for 'x' or 'alloc'.
+ */
+#define ALLOC_GROW(x, nr, alloc) \
+ do { \
+ if ((nr) > alloc) { \
+ if (alloc_nr(alloc) < (nr)) \
+ alloc = (nr); \
+ else \
+ alloc = alloc_nr(alloc); \
+ x = xrealloc((x), alloc * sizeof(*(x))); \
+ } \
+ } while(0)
+
+
+static inline int is_absolute_path(const char *path)
+{
+ return path[0] == '/';
+}
+
+const char *make_nonrelative_path(const char *path);
+char *strip_path_suffix(const char *path, const char *suffix);
+
+extern char *mkpath(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+extern char *perf_path(const char *fmt, ...) __attribute__((format (printf, 1, 2)));
+
+extern char *perf_pathdup(const char *fmt, ...)
+ __attribute__((format (printf, 1, 2)));
+
+/* Matches the libc/libbsd function attribute so we declare this unconditionally: */
+extern size_t strlcpy(char *dest, const char *src, size_t size);
+
+#endif /* __PERF_CACHE_H */
diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c
new file mode 100644
index 00000000000..48b6d3f5001
--- /dev/null
+++ b/tools/perf/util/callchain.c
@@ -0,0 +1,674 @@
+/*
+ * Copyright (C) 2009-2011, Frederic Weisbecker <fweisbec@gmail.com>
+ *
+ * Handle the callchains from the stream in an ad-hoc radix tree and then
+ * sort them in an rbtree.
+ *
+ * Using a radix for code path provides a fast retrieval and factorizes
+ * memory use. Also that lets us use the paths in a hierarchical graph view.
+ *
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <math.h>
+
+#include "asm/bug.h"
+
+#include "hist.h"
+#include "util.h"
+#include "sort.h"
+#include "machine.h"
+#include "callchain.h"
+
+__thread struct callchain_cursor callchain_cursor;
+
+int
+parse_callchain_report_opt(const char *arg)
+{
+ char *tok, *tok2;
+ char *endptr;
+
+ symbol_conf.use_callchain = true;
+
+ if (!arg)
+ return 0;
+
+ tok = strtok((char *)arg, ",");
+ if (!tok)
+ return -1;
+
+ /* get the output mode */
+ if (!strncmp(tok, "graph", strlen(arg))) {
+ callchain_param.mode = CHAIN_GRAPH_ABS;
+
+ } else if (!strncmp(tok, "flat", strlen(arg))) {
+ callchain_param.mode = CHAIN_FLAT;
+ } else if (!strncmp(tok, "fractal", strlen(arg))) {
+ callchain_param.mode = CHAIN_GRAPH_REL;
+ } else if (!strncmp(tok, "none", strlen(arg))) {
+ callchain_param.mode = CHAIN_NONE;
+ symbol_conf.use_callchain = false;
+ return 0;
+ } else {
+ return -1;
+ }
+
+ /* get the min percentage */
+ tok = strtok(NULL, ",");
+ if (!tok)
+ goto setup;
+
+ callchain_param.min_percent = strtod(tok, &endptr);
+ if (tok == endptr)
+ return -1;
+
+ /* get the print limit */
+ tok2 = strtok(NULL, ",");
+ if (!tok2)
+ goto setup;
+
+ if (tok2[0] != 'c') {
+ callchain_param.print_limit = strtoul(tok2, &endptr, 0);
+ tok2 = strtok(NULL, ",");
+ if (!tok2)
+ goto setup;
+ }
+
+ /* get the call chain order */
+ if (!strncmp(tok2, "caller", strlen("caller")))
+ callchain_param.order = ORDER_CALLER;
+ else if (!strncmp(tok2, "callee", strlen("callee")))
+ callchain_param.order = ORDER_CALLEE;
+ else
+ return -1;
+
+ /* Get the sort key */
+ tok2 = strtok(NULL, ",");
+ if (!tok2)
+ goto setup;
+ if (!strncmp(tok2, "function", strlen("function")))
+ callchain_param.key = CCKEY_FUNCTION;
+ else if (!strncmp(tok2, "address", strlen("address")))
+ callchain_param.key = CCKEY_ADDRESS;
+ else
+ return -1;
+setup:
+ if (callchain_register_param(&callchain_param) < 0) {
+ pr_err("Can't register callchain params\n");
+ return -1;
+ }
+ return 0;
+}
+
+static void
+rb_insert_callchain(struct rb_root *root, struct callchain_node *chain,
+ enum chain_mode mode)
+{
+ struct rb_node **p = &root->rb_node;
+ struct rb_node *parent = NULL;
+ struct callchain_node *rnode;
+ u64 chain_cumul = callchain_cumul_hits(chain);
+
+ while (*p) {
+ u64 rnode_cumul;
+
+ parent = *p;
+ rnode = rb_entry(parent, struct callchain_node, rb_node);
+ rnode_cumul = callchain_cumul_hits(rnode);
+
+ switch (mode) {
+ case CHAIN_FLAT:
+ if (rnode->hit < chain->hit)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ break;
+ case CHAIN_GRAPH_ABS: /* Falldown */
+ case CHAIN_GRAPH_REL:
+ if (rnode_cumul < chain_cumul)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ break;
+ case CHAIN_NONE:
+ default:
+ break;
+ }
+ }
+
+ rb_link_node(&chain->rb_node, parent, p);
+ rb_insert_color(&chain->rb_node, root);
+}
+
+static void
+__sort_chain_flat(struct rb_root *rb_root, struct callchain_node *node,
+ u64 min_hit)
+{
+ struct rb_node *n;
+ struct callchain_node *child;
+
+ n = rb_first(&node->rb_root_in);
+ while (n) {
+ child = rb_entry(n, struct callchain_node, rb_node_in);
+ n = rb_next(n);
+
+ __sort_chain_flat(rb_root, child, min_hit);
+ }
+
+ if (node->hit && node->hit >= min_hit)
+ rb_insert_callchain(rb_root, node, CHAIN_FLAT);
+}
+
+/*
+ * Once we get every callchains from the stream, we can now
+ * sort them by hit
+ */
+static void
+sort_chain_flat(struct rb_root *rb_root, struct callchain_root *root,
+ u64 min_hit, struct callchain_param *param __maybe_unused)
+{
+ __sort_chain_flat(rb_root, &root->node, min_hit);
+}
+
+static void __sort_chain_graph_abs(struct callchain_node *node,
+ u64 min_hit)
+{
+ struct rb_node *n;
+ struct callchain_node *child;
+
+ node->rb_root = RB_ROOT;
+ n = rb_first(&node->rb_root_in);
+
+ while (n) {
+ child = rb_entry(n, struct callchain_node, rb_node_in);
+ n = rb_next(n);
+
+ __sort_chain_graph_abs(child, min_hit);
+ if (callchain_cumul_hits(child) >= min_hit)
+ rb_insert_callchain(&node->rb_root, child,
+ CHAIN_GRAPH_ABS);
+ }
+}
+
+static void
+sort_chain_graph_abs(struct rb_root *rb_root, struct callchain_root *chain_root,
+ u64 min_hit, struct callchain_param *param __maybe_unused)
+{
+ __sort_chain_graph_abs(&chain_root->node, min_hit);
+ rb_root->rb_node = chain_root->node.rb_root.rb_node;
+}
+
+static void __sort_chain_graph_rel(struct callchain_node *node,
+ double min_percent)
+{
+ struct rb_node *n;
+ struct callchain_node *child;
+ u64 min_hit;
+
+ node->rb_root = RB_ROOT;
+ min_hit = ceil(node->children_hit * min_percent);
+
+ n = rb_first(&node->rb_root_in);
+ while (n) {
+ child = rb_entry(n, struct callchain_node, rb_node_in);
+ n = rb_next(n);
+
+ __sort_chain_graph_rel(child, min_percent);
+ if (callchain_cumul_hits(child) >= min_hit)
+ rb_insert_callchain(&node->rb_root, child,
+ CHAIN_GRAPH_REL);
+ }
+}
+
+static void
+sort_chain_graph_rel(struct rb_root *rb_root, struct callchain_root *chain_root,
+ u64 min_hit __maybe_unused, struct callchain_param *param)
+{
+ __sort_chain_graph_rel(&chain_root->node, param->min_percent / 100.0);
+ rb_root->rb_node = chain_root->node.rb_root.rb_node;
+}
+
+int callchain_register_param(struct callchain_param *param)
+{
+ switch (param->mode) {
+ case CHAIN_GRAPH_ABS:
+ param->sort = sort_chain_graph_abs;
+ break;
+ case CHAIN_GRAPH_REL:
+ param->sort = sort_chain_graph_rel;
+ break;
+ case CHAIN_FLAT:
+ param->sort = sort_chain_flat;
+ break;
+ case CHAIN_NONE:
+ default:
+ return -1;
+ }
+ return 0;
+}
+
+/*
+ * Create a child for a parent. If inherit_children, then the new child
+ * will become the new parent of it's parent children
+ */
+static struct callchain_node *
+create_child(struct callchain_node *parent, bool inherit_children)
+{
+ struct callchain_node *new;
+
+ new = zalloc(sizeof(*new));
+ if (!new) {
+ perror("not enough memory to create child for code path tree");
+ return NULL;
+ }
+ new->parent = parent;
+ INIT_LIST_HEAD(&new->val);
+
+ if (inherit_children) {
+ struct rb_node *n;
+ struct callchain_node *child;
+
+ new->rb_root_in = parent->rb_root_in;
+ parent->rb_root_in = RB_ROOT;
+
+ n = rb_first(&new->rb_root_in);
+ while (n) {
+ child = rb_entry(n, struct callchain_node, rb_node_in);
+ child->parent = new;
+ n = rb_next(n);
+ }
+
+ /* make it the first child */
+ rb_link_node(&new->rb_node_in, NULL, &parent->rb_root_in.rb_node);
+ rb_insert_color(&new->rb_node_in, &parent->rb_root_in);
+ }
+
+ return new;
+}
+
+
+/*
+ * Fill the node with callchain values
+ */
+static void
+fill_node(struct callchain_node *node, struct callchain_cursor *cursor)
+{
+ struct callchain_cursor_node *cursor_node;
+
+ node->val_nr = cursor->nr - cursor->pos;
+ if (!node->val_nr)
+ pr_warning("Warning: empty node in callchain tree\n");
+
+ cursor_node = callchain_cursor_current(cursor);
+
+ while (cursor_node) {
+ struct callchain_list *call;
+
+ call = zalloc(sizeof(*call));
+ if (!call) {
+ perror("not enough memory for the code path tree");
+ return;
+ }
+ call->ip = cursor_node->ip;
+ call->ms.sym = cursor_node->sym;
+ call->ms.map = cursor_node->map;
+ list_add_tail(&call->list, &node->val);
+
+ callchain_cursor_advance(cursor);
+ cursor_node = callchain_cursor_current(cursor);
+ }
+}
+
+static struct callchain_node *
+add_child(struct callchain_node *parent,
+ struct callchain_cursor *cursor,
+ u64 period)
+{
+ struct callchain_node *new;
+
+ new = create_child(parent, false);
+ fill_node(new, cursor);
+
+ new->children_hit = 0;
+ new->hit = period;
+ return new;
+}
+
+static s64 match_chain(struct callchain_cursor_node *node,
+ struct callchain_list *cnode)
+{
+ struct symbol *sym = node->sym;
+
+ if (cnode->ms.sym && sym &&
+ callchain_param.key == CCKEY_FUNCTION)
+ return cnode->ms.sym->start - sym->start;
+ else
+ return cnode->ip - node->ip;
+}
+
+/*
+ * Split the parent in two parts (a new child is created) and
+ * give a part of its callchain to the created child.
+ * Then create another child to host the given callchain of new branch
+ */
+static void
+split_add_child(struct callchain_node *parent,
+ struct callchain_cursor *cursor,
+ struct callchain_list *to_split,
+ u64 idx_parents, u64 idx_local, u64 period)
+{
+ struct callchain_node *new;
+ struct list_head *old_tail;
+ unsigned int idx_total = idx_parents + idx_local;
+
+ /* split */
+ new = create_child(parent, true);
+
+ /* split the callchain and move a part to the new child */
+ old_tail = parent->val.prev;
+ list_del_range(&to_split->list, old_tail);
+ new->val.next = &to_split->list;
+ new->val.prev = old_tail;
+ to_split->list.prev = &new->val;
+ old_tail->next = &new->val;
+
+ /* split the hits */
+ new->hit = parent->hit;
+ new->children_hit = parent->children_hit;
+ parent->children_hit = callchain_cumul_hits(new);
+ new->val_nr = parent->val_nr - idx_local;
+ parent->val_nr = idx_local;
+
+ /* create a new child for the new branch if any */
+ if (idx_total < cursor->nr) {
+ struct callchain_node *first;
+ struct callchain_list *cnode;
+ struct callchain_cursor_node *node;
+ struct rb_node *p, **pp;
+
+ parent->hit = 0;
+ parent->children_hit += period;
+
+ node = callchain_cursor_current(cursor);
+ new = add_child(parent, cursor, period);
+
+ /*
+ * This is second child since we moved parent's children
+ * to new (first) child above.
+ */
+ p = parent->rb_root_in.rb_node;
+ first = rb_entry(p, struct callchain_node, rb_node_in);
+ cnode = list_first_entry(&first->val, struct callchain_list,
+ list);
+
+ if (match_chain(node, cnode) < 0)
+ pp = &p->rb_left;
+ else
+ pp = &p->rb_right;
+
+ rb_link_node(&new->rb_node_in, p, pp);
+ rb_insert_color(&new->rb_node_in, &parent->rb_root_in);
+ } else {
+ parent->hit = period;
+ }
+}
+
+static int
+append_chain(struct callchain_node *root,
+ struct callchain_cursor *cursor,
+ u64 period);
+
+static void
+append_chain_children(struct callchain_node *root,
+ struct callchain_cursor *cursor,
+ u64 period)
+{
+ struct callchain_node *rnode;
+ struct callchain_cursor_node *node;
+ struct rb_node **p = &root->rb_root_in.rb_node;
+ struct rb_node *parent = NULL;
+
+ node = callchain_cursor_current(cursor);
+ if (!node)
+ return;
+
+ /* lookup in childrens */
+ while (*p) {
+ s64 ret;
+
+ parent = *p;
+ rnode = rb_entry(parent, struct callchain_node, rb_node_in);
+
+ /* If at least first entry matches, rely to children */
+ ret = append_chain(rnode, cursor, period);
+ if (ret == 0)
+ goto inc_children_hit;
+
+ if (ret < 0)
+ p = &parent->rb_left;
+ else
+ p = &parent->rb_right;
+ }
+ /* nothing in children, add to the current node */
+ rnode = add_child(root, cursor, period);
+ rb_link_node(&rnode->rb_node_in, parent, p);
+ rb_insert_color(&rnode->rb_node_in, &root->rb_root_in);
+
+inc_children_hit:
+ root->children_hit += period;
+}
+
+static int
+append_chain(struct callchain_node *root,
+ struct callchain_cursor *cursor,
+ u64 period)
+{
+ struct callchain_list *cnode;
+ u64 start = cursor->pos;
+ bool found = false;
+ u64 matches;
+ int cmp = 0;
+
+ /*
+ * Lookup in the current node
+ * If we have a symbol, then compare the start to match
+ * anywhere inside a function, unless function
+ * mode is disabled.
+ */
+ list_for_each_entry(cnode, &root->val, list) {
+ struct callchain_cursor_node *node;
+
+ node = callchain_cursor_current(cursor);
+ if (!node)
+ break;
+
+ cmp = match_chain(node, cnode);
+ if (cmp)
+ break;
+
+ found = true;
+
+ callchain_cursor_advance(cursor);
+ }
+
+ /* matches not, relay no the parent */
+ if (!found) {
+ WARN_ONCE(!cmp, "Chain comparison error\n");
+ return cmp;
+ }
+
+ matches = cursor->pos - start;
+
+ /* we match only a part of the node. Split it and add the new chain */
+ if (matches < root->val_nr) {
+ split_add_child(root, cursor, cnode, start, matches, period);
+ return 0;
+ }
+
+ /* we match 100% of the path, increment the hit */
+ if (matches == root->val_nr && cursor->pos == cursor->nr) {
+ root->hit += period;
+ return 0;
+ }
+
+ /* We match the node and still have a part remaining */
+ append_chain_children(root, cursor, period);
+
+ return 0;
+}
+
+int callchain_append(struct callchain_root *root,
+ struct callchain_cursor *cursor,
+ u64 period)
+{
+ if (!cursor->nr)
+ return 0;
+
+ callchain_cursor_commit(cursor);
+
+ append_chain_children(&root->node, cursor, period);
+
+ if (cursor->nr > root->max_depth)
+ root->max_depth = cursor->nr;
+
+ return 0;
+}
+
+static int
+merge_chain_branch(struct callchain_cursor *cursor,
+ struct callchain_node *dst, struct callchain_node *src)
+{
+ struct callchain_cursor_node **old_last = cursor->last;
+ struct callchain_node *child;
+ struct callchain_list *list, *next_list;
+ struct rb_node *n;
+ int old_pos = cursor->nr;
+ int err = 0;
+
+ list_for_each_entry_safe(list, next_list, &src->val, list) {
+ callchain_cursor_append(cursor, list->ip,
+ list->ms.map, list->ms.sym);
+ list_del(&list->list);
+ free(list);
+ }
+
+ if (src->hit) {
+ callchain_cursor_commit(cursor);
+ append_chain_children(dst, cursor, src->hit);
+ }
+
+ n = rb_first(&src->rb_root_in);
+ while (n) {
+ child = container_of(n, struct callchain_node, rb_node_in);
+ n = rb_next(n);
+ rb_erase(&child->rb_node_in, &src->rb_root_in);
+
+ err = merge_chain_branch(cursor, dst, child);
+ if (err)
+ break;
+
+ free(child);
+ }
+
+ cursor->nr = old_pos;
+ cursor->last = old_last;
+
+ return err;
+}
+
+int callchain_merge(struct callchain_cursor *cursor,
+ struct callchain_root *dst, struct callchain_root *src)
+{
+ return merge_chain_branch(cursor, &dst->node, &src->node);
+}
+
+int callchain_cursor_append(struct callchain_cursor *cursor,
+ u64 ip, struct map *map, struct symbol *sym)
+{
+ struct callchain_cursor_node *node = *cursor->last;
+
+ if (!node) {
+ node = calloc(1, sizeof(*node));
+ if (!node)
+ return -ENOMEM;
+
+ *cursor->last = node;
+ }
+
+ node->ip = ip;
+ node->map = map;
+ node->sym = sym;
+
+ cursor->nr++;
+
+ cursor->last = &node->next;
+
+ return 0;
+}
+
+int sample__resolve_callchain(struct perf_sample *sample, struct symbol **parent,
+ struct perf_evsel *evsel, struct addr_location *al,
+ int max_stack)
+{
+ if (sample->callchain == NULL)
+ return 0;
+
+ if (symbol_conf.use_callchain || symbol_conf.cumulate_callchain ||
+ sort__has_parent) {
+ return machine__resolve_callchain(al->machine, evsel, al->thread,
+ sample, parent, al, max_stack);
+ }
+ return 0;
+}
+
+int hist_entry__append_callchain(struct hist_entry *he, struct perf_sample *sample)
+{
+ if (!symbol_conf.use_callchain)
+ return 0;
+ return callchain_append(he->callchain, &callchain_cursor, sample->period);
+}
+
+int fill_callchain_info(struct addr_location *al, struct callchain_cursor_node *node,
+ bool hide_unresolved)
+{
+ al->map = node->map;
+ al->sym = node->sym;
+ if (node->map)
+ al->addr = node->map->map_ip(node->map, node->ip);
+ else
+ al->addr = node->ip;
+
+ if (al->sym == NULL) {
+ if (hide_unresolved)
+ return 0;
+ if (al->map == NULL)
+ goto out;
+ }
+
+ if (al->map->groups == &al->machine->kmaps) {
+ if (machine__is_host(al->machine)) {
+ al->cpumode = PERF_RECORD_MISC_KERNEL;
+ al->level = 'k';
+ } else {
+ al->cpumode = PERF_RECORD_MISC_GUEST_KERNEL;
+ al->level = 'g';
+ }
+ } else {
+ if (machine__is_host(al->machine)) {
+ al->cpumode = PERF_RECORD_MISC_USER;
+ al->level = '.';
+ } else if (perf_guest) {
+ al->cpumode = PERF_RECORD_MISC_GUEST_USER;
+ al->level = 'u';
+ } else {
+ al->cpumode = PERF_RECORD_MISC_HYPERVISOR;
+ al->level = 'H';
+ }
+ }
+
+out:
+ return 1;
+}
diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h
new file mode 100644
index 00000000000..8f84423a75d
--- /dev/null
+++ b/tools/perf/util/callchain.h
@@ -0,0 +1,179 @@
+#ifndef __PERF_CALLCHAIN_H
+#define __PERF_CALLCHAIN_H
+
+#include "../perf.h"
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include "event.h"
+#include "symbol.h"
+
+enum perf_call_graph_mode {
+ CALLCHAIN_NONE,
+ CALLCHAIN_FP,
+ CALLCHAIN_DWARF,
+ CALLCHAIN_MAX
+};
+
+enum chain_mode {
+ CHAIN_NONE,
+ CHAIN_FLAT,
+ CHAIN_GRAPH_ABS,
+ CHAIN_GRAPH_REL
+};
+
+enum chain_order {
+ ORDER_CALLER,
+ ORDER_CALLEE
+};
+
+struct callchain_node {
+ struct callchain_node *parent;
+ struct list_head val;
+ struct rb_node rb_node_in; /* to insert nodes in an rbtree */
+ struct rb_node rb_node; /* to sort nodes in an output tree */
+ struct rb_root rb_root_in; /* input tree of children */
+ struct rb_root rb_root; /* sorted output tree of children */
+ unsigned int val_nr;
+ u64 hit;
+ u64 children_hit;
+};
+
+struct callchain_root {
+ u64 max_depth;
+ struct callchain_node node;
+};
+
+struct callchain_param;
+
+typedef void (*sort_chain_func_t)(struct rb_root *, struct callchain_root *,
+ u64, struct callchain_param *);
+
+enum chain_key {
+ CCKEY_FUNCTION,
+ CCKEY_ADDRESS
+};
+
+struct callchain_param {
+ enum chain_mode mode;
+ u32 print_limit;
+ double min_percent;
+ sort_chain_func_t sort;
+ enum chain_order order;
+ enum chain_key key;
+};
+
+struct callchain_list {
+ u64 ip;
+ struct map_symbol ms;
+ struct list_head list;
+};
+
+/*
+ * A callchain cursor is a single linked list that
+ * let one feed a callchain progressively.
+ * It keeps persistent allocated entries to minimize
+ * allocations.
+ */
+struct callchain_cursor_node {
+ u64 ip;
+ struct map *map;
+ struct symbol *sym;
+ struct callchain_cursor_node *next;
+};
+
+struct callchain_cursor {
+ u64 nr;
+ struct callchain_cursor_node *first;
+ struct callchain_cursor_node **last;
+ u64 pos;
+ struct callchain_cursor_node *curr;
+};
+
+extern __thread struct callchain_cursor callchain_cursor;
+
+static inline void callchain_init(struct callchain_root *root)
+{
+ INIT_LIST_HEAD(&root->node.val);
+
+ root->node.parent = NULL;
+ root->node.hit = 0;
+ root->node.children_hit = 0;
+ root->node.rb_root_in = RB_ROOT;
+ root->max_depth = 0;
+}
+
+static inline u64 callchain_cumul_hits(struct callchain_node *node)
+{
+ return node->hit + node->children_hit;
+}
+
+int callchain_register_param(struct callchain_param *param);
+int callchain_append(struct callchain_root *root,
+ struct callchain_cursor *cursor,
+ u64 period);
+
+int callchain_merge(struct callchain_cursor *cursor,
+ struct callchain_root *dst, struct callchain_root *src);
+
+/*
+ * Initialize a cursor before adding entries inside, but keep
+ * the previously allocated entries as a cache.
+ */
+static inline void callchain_cursor_reset(struct callchain_cursor *cursor)
+{
+ cursor->nr = 0;
+ cursor->last = &cursor->first;
+}
+
+int callchain_cursor_append(struct callchain_cursor *cursor, u64 ip,
+ struct map *map, struct symbol *sym);
+
+/* Close a cursor writing session. Initialize for the reader */
+static inline void callchain_cursor_commit(struct callchain_cursor *cursor)
+{
+ cursor->curr = cursor->first;
+ cursor->pos = 0;
+}
+
+/* Cursor reading iteration helpers */
+static inline struct callchain_cursor_node *
+callchain_cursor_current(struct callchain_cursor *cursor)
+{
+ if (cursor->pos == cursor->nr)
+ return NULL;
+
+ return cursor->curr;
+}
+
+static inline void callchain_cursor_advance(struct callchain_cursor *cursor)
+{
+ cursor->curr = cursor->curr->next;
+ cursor->pos++;
+}
+
+struct option;
+struct hist_entry;
+
+int record_parse_callchain(const char *arg, struct record_opts *opts);
+int record_parse_callchain_opt(const struct option *opt, const char *arg, int unset);
+int record_callchain_opt(const struct option *opt, const char *arg, int unset);
+
+int sample__resolve_callchain(struct perf_sample *sample, struct symbol **parent,
+ struct perf_evsel *evsel, struct addr_location *al,
+ int max_stack);
+int hist_entry__append_callchain(struct hist_entry *he, struct perf_sample *sample);
+int fill_callchain_info(struct addr_location *al, struct callchain_cursor_node *node,
+ bool hide_unresolved);
+
+extern const char record_callchain_help[];
+int parse_callchain_report_opt(const char *arg);
+
+static inline void callchain_cursor_snapshot(struct callchain_cursor *dest,
+ struct callchain_cursor *src)
+{
+ *dest = *src;
+
+ dest->first = src->curr;
+ dest->nr -= src->pos;
+}
+#endif /* __PERF_CALLCHAIN_H */
diff --git a/tools/perf/util/cgroup.c b/tools/perf/util/cgroup.c
new file mode 100644
index 00000000000..88f7be39943
--- /dev/null
+++ b/tools/perf/util/cgroup.c
@@ -0,0 +1,177 @@
+#include "util.h"
+#include "../perf.h"
+#include "parse-options.h"
+#include "evsel.h"
+#include "cgroup.h"
+#include "evlist.h"
+
+int nr_cgroups;
+
+static int
+cgroupfs_find_mountpoint(char *buf, size_t maxlen)
+{
+ FILE *fp;
+ char mountpoint[PATH_MAX + 1], tokens[PATH_MAX + 1], type[PATH_MAX + 1];
+ char *token, *saved_ptr = NULL;
+ int found = 0;
+
+ fp = fopen("/proc/mounts", "r");
+ if (!fp)
+ return -1;
+
+ /*
+ * in order to handle split hierarchy, we need to scan /proc/mounts
+ * and inspect every cgroupfs mount point to find one that has
+ * perf_event subsystem
+ */
+ while (fscanf(fp, "%*s %"STR(PATH_MAX)"s %"STR(PATH_MAX)"s %"
+ STR(PATH_MAX)"s %*d %*d\n",
+ mountpoint, type, tokens) == 3) {
+
+ if (!strcmp(type, "cgroup")) {
+
+ token = strtok_r(tokens, ",", &saved_ptr);
+
+ while (token != NULL) {
+ if (!strcmp(token, "perf_event")) {
+ found = 1;
+ break;
+ }
+ token = strtok_r(NULL, ",", &saved_ptr);
+ }
+ }
+ if (found)
+ break;
+ }
+ fclose(fp);
+ if (!found)
+ return -1;
+
+ if (strlen(mountpoint) < maxlen) {
+ strcpy(buf, mountpoint);
+ return 0;
+ }
+ return -1;
+}
+
+static int open_cgroup(char *name)
+{
+ char path[PATH_MAX + 1];
+ char mnt[PATH_MAX + 1];
+ int fd;
+
+
+ if (cgroupfs_find_mountpoint(mnt, PATH_MAX + 1))
+ return -1;
+
+ snprintf(path, PATH_MAX, "%s/%s", mnt, name);
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ fprintf(stderr, "no access to cgroup %s\n", path);
+
+ return fd;
+}
+
+static int add_cgroup(struct perf_evlist *evlist, char *str)
+{
+ struct perf_evsel *counter;
+ struct cgroup_sel *cgrp = NULL;
+ int n;
+ /*
+ * check if cgrp is already defined, if so we reuse it
+ */
+ evlist__for_each(evlist, counter) {
+ cgrp = counter->cgrp;
+ if (!cgrp)
+ continue;
+ if (!strcmp(cgrp->name, str))
+ break;
+
+ cgrp = NULL;
+ }
+
+ if (!cgrp) {
+ cgrp = zalloc(sizeof(*cgrp));
+ if (!cgrp)
+ return -1;
+
+ cgrp->name = str;
+
+ cgrp->fd = open_cgroup(str);
+ if (cgrp->fd == -1) {
+ free(cgrp);
+ return -1;
+ }
+ }
+
+ /*
+ * find corresponding event
+ * if add cgroup N, then need to find event N
+ */
+ n = 0;
+ evlist__for_each(evlist, counter) {
+ if (n == nr_cgroups)
+ goto found;
+ n++;
+ }
+ if (cgrp->refcnt == 0)
+ free(cgrp);
+
+ return -1;
+found:
+ cgrp->refcnt++;
+ counter->cgrp = cgrp;
+ return 0;
+}
+
+void close_cgroup(struct cgroup_sel *cgrp)
+{
+ if (!cgrp)
+ return;
+
+ /* XXX: not reentrant */
+ if (--cgrp->refcnt == 0) {
+ close(cgrp->fd);
+ zfree(&cgrp->name);
+ free(cgrp);
+ }
+}
+
+int parse_cgroups(const struct option *opt __maybe_unused, const char *str,
+ int unset __maybe_unused)
+{
+ struct perf_evlist *evlist = *(struct perf_evlist **)opt->value;
+ const char *p, *e, *eos = str + strlen(str);
+ char *s;
+ int ret;
+
+ if (list_empty(&evlist->entries)) {
+ fprintf(stderr, "must define events before cgroups\n");
+ return -1;
+ }
+
+ for (;;) {
+ p = strchr(str, ',');
+ e = p ? p : eos;
+
+ /* allow empty cgroups, i.e., skip */
+ if (e - str) {
+ /* termination added */
+ s = strndup(str, e - str);
+ if (!s)
+ return -1;
+ ret = add_cgroup(evlist, s);
+ if (ret) {
+ free(s);
+ return -1;
+ }
+ }
+ /* nr_cgroups is increased een for empty cgroups */
+ nr_cgroups++;
+ if (!p)
+ break;
+ str = p+1;
+ }
+ return 0;
+}
diff --git a/tools/perf/util/cgroup.h b/tools/perf/util/cgroup.h
new file mode 100644
index 00000000000..89acd6debdc
--- /dev/null
+++ b/tools/perf/util/cgroup.h
@@ -0,0 +1,17 @@
+#ifndef __CGROUP_H__
+#define __CGROUP_H__
+
+struct option;
+
+struct cgroup_sel {
+ char *name;
+ int fd;
+ int refcnt;
+};
+
+
+extern int nr_cgroups; /* number of explicit cgroups defined */
+extern void close_cgroup(struct cgroup_sel *cgrp);
+extern int parse_cgroups(const struct option *opt, const char *str, int unset);
+
+#endif /* __CGROUP_H__ */
diff --git a/tools/perf/util/color.c b/tools/perf/util/color.c
new file mode 100644
index 00000000000..87b8672eb41
--- /dev/null
+++ b/tools/perf/util/color.c
@@ -0,0 +1,337 @@
+#include <linux/kernel.h>
+#include "cache.h"
+#include "color.h"
+#include <math.h>
+
+int perf_use_color_default = -1;
+
+static int parse_color(const char *name, int len)
+{
+ static const char * const color_names[] = {
+ "normal", "black", "red", "green", "yellow",
+ "blue", "magenta", "cyan", "white"
+ };
+ char *end;
+ int i;
+
+ for (i = 0; i < (int)ARRAY_SIZE(color_names); i++) {
+ const char *str = color_names[i];
+ if (!strncasecmp(name, str, len) && !str[len])
+ return i - 1;
+ }
+ i = strtol(name, &end, 10);
+ if (end - name == len && i >= -1 && i <= 255)
+ return i;
+ return -2;
+}
+
+static int parse_attr(const char *name, int len)
+{
+ static const int attr_values[] = { 1, 2, 4, 5, 7 };
+ static const char * const attr_names[] = {
+ "bold", "dim", "ul", "blink", "reverse"
+ };
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(attr_names); i++) {
+ const char *str = attr_names[i];
+ if (!strncasecmp(name, str, len) && !str[len])
+ return attr_values[i];
+ }
+ return -1;
+}
+
+void color_parse(const char *value, const char *var, char *dst)
+{
+ color_parse_mem(value, strlen(value), var, dst);
+}
+
+void color_parse_mem(const char *value, int value_len, const char *var,
+ char *dst)
+{
+ const char *ptr = value;
+ int len = value_len;
+ int attr = -1;
+ int fg = -2;
+ int bg = -2;
+
+ if (!strncasecmp(value, "reset", len)) {
+ strcpy(dst, PERF_COLOR_RESET);
+ return;
+ }
+
+ /* [fg [bg]] [attr] */
+ while (len > 0) {
+ const char *word = ptr;
+ int val, wordlen = 0;
+
+ while (len > 0 && !isspace(word[wordlen])) {
+ wordlen++;
+ len--;
+ }
+
+ ptr = word + wordlen;
+ while (len > 0 && isspace(*ptr)) {
+ ptr++;
+ len--;
+ }
+
+ val = parse_color(word, wordlen);
+ if (val >= -1) {
+ if (fg == -2) {
+ fg = val;
+ continue;
+ }
+ if (bg == -2) {
+ bg = val;
+ continue;
+ }
+ goto bad;
+ }
+ val = parse_attr(word, wordlen);
+ if (val < 0 || attr != -1)
+ goto bad;
+ attr = val;
+ }
+
+ if (attr >= 0 || fg >= 0 || bg >= 0) {
+ int sep = 0;
+
+ *dst++ = '\033';
+ *dst++ = '[';
+ if (attr >= 0) {
+ *dst++ = '0' + attr;
+ sep++;
+ }
+ if (fg >= 0) {
+ if (sep++)
+ *dst++ = ';';
+ if (fg < 8) {
+ *dst++ = '3';
+ *dst++ = '0' + fg;
+ } else {
+ dst += sprintf(dst, "38;5;%d", fg);
+ }
+ }
+ if (bg >= 0) {
+ if (sep++)
+ *dst++ = ';';
+ if (bg < 8) {
+ *dst++ = '4';
+ *dst++ = '0' + bg;
+ } else {
+ dst += sprintf(dst, "48;5;%d", bg);
+ }
+ }
+ *dst++ = 'm';
+ }
+ *dst = 0;
+ return;
+bad:
+ die("bad color value '%.*s' for variable '%s'", value_len, value, var);
+}
+
+int perf_config_colorbool(const char *var, const char *value, int stdout_is_tty)
+{
+ if (value) {
+ if (!strcasecmp(value, "never"))
+ return 0;
+ if (!strcasecmp(value, "always"))
+ return 1;
+ if (!strcasecmp(value, "auto"))
+ goto auto_color;
+ }
+
+ /* Missing or explicit false to turn off colorization */
+ if (!perf_config_bool(var, value))
+ return 0;
+
+ /* any normal truth value defaults to 'auto' */
+ auto_color:
+ if (stdout_is_tty < 0)
+ stdout_is_tty = isatty(1);
+ if (stdout_is_tty || (pager_in_use() && pager_use_color)) {
+ char *term = getenv("TERM");
+ if (term && strcmp(term, "dumb"))
+ return 1;
+ }
+ return 0;
+}
+
+int perf_color_default_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "color.ui")) {
+ perf_use_color_default = perf_config_colorbool(var, value, -1);
+ return 0;
+ }
+
+ return perf_default_config(var, value, cb);
+}
+
+static int __color_vsnprintf(char *bf, size_t size, const char *color,
+ const char *fmt, va_list args, const char *trail)
+{
+ int r = 0;
+
+ /*
+ * Auto-detect:
+ */
+ if (perf_use_color_default < 0) {
+ if (isatty(1) || pager_in_use())
+ perf_use_color_default = 1;
+ else
+ perf_use_color_default = 0;
+ }
+
+ if (perf_use_color_default && *color)
+ r += scnprintf(bf, size, "%s", color);
+ r += vscnprintf(bf + r, size - r, fmt, args);
+ if (perf_use_color_default && *color)
+ r += scnprintf(bf + r, size - r, "%s", PERF_COLOR_RESET);
+ if (trail)
+ r += scnprintf(bf + r, size - r, "%s", trail);
+ return r;
+}
+
+static int __color_vfprintf(FILE *fp, const char *color, const char *fmt,
+ va_list args, const char *trail)
+{
+ int r = 0;
+
+ /*
+ * Auto-detect:
+ */
+ if (perf_use_color_default < 0) {
+ if (isatty(fileno(fp)) || pager_in_use())
+ perf_use_color_default = 1;
+ else
+ perf_use_color_default = 0;
+ }
+
+ if (perf_use_color_default && *color)
+ r += fprintf(fp, "%s", color);
+ r += vfprintf(fp, fmt, args);
+ if (perf_use_color_default && *color)
+ r += fprintf(fp, "%s", PERF_COLOR_RESET);
+ if (trail)
+ r += fprintf(fp, "%s", trail);
+ return r;
+}
+
+int color_vsnprintf(char *bf, size_t size, const char *color,
+ const char *fmt, va_list args)
+{
+ return __color_vsnprintf(bf, size, color, fmt, args, NULL);
+}
+
+int color_vfprintf(FILE *fp, const char *color, const char *fmt, va_list args)
+{
+ return __color_vfprintf(fp, color, fmt, args, NULL);
+}
+
+int color_snprintf(char *bf, size_t size, const char *color,
+ const char *fmt, ...)
+{
+ va_list args;
+ int r;
+
+ va_start(args, fmt);
+ r = color_vsnprintf(bf, size, color, fmt, args);
+ va_end(args);
+ return r;
+}
+
+int color_fprintf(FILE *fp, const char *color, const char *fmt, ...)
+{
+ va_list args;
+ int r;
+
+ va_start(args, fmt);
+ r = color_vfprintf(fp, color, fmt, args);
+ va_end(args);
+ return r;
+}
+
+int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...)
+{
+ va_list args;
+ int r;
+ va_start(args, fmt);
+ r = __color_vfprintf(fp, color, fmt, args, "\n");
+ va_end(args);
+ return r;
+}
+
+/*
+ * This function splits the buffer by newlines and colors the lines individually.
+ *
+ * Returns 0 on success.
+ */
+int color_fwrite_lines(FILE *fp, const char *color,
+ size_t count, const char *buf)
+{
+ if (!*color)
+ return fwrite(buf, count, 1, fp) != 1;
+
+ while (count) {
+ char *p = memchr(buf, '\n', count);
+
+ if (p != buf && (fputs(color, fp) < 0 ||
+ fwrite(buf, p ? (size_t)(p - buf) : count, 1, fp) != 1 ||
+ fputs(PERF_COLOR_RESET, fp) < 0))
+ return -1;
+ if (!p)
+ return 0;
+ if (fputc('\n', fp) < 0)
+ return -1;
+ count -= p + 1 - buf;
+ buf = p + 1;
+ }
+ return 0;
+}
+
+const char *get_percent_color(double percent)
+{
+ const char *color = PERF_COLOR_NORMAL;
+
+ /*
+ * We color high-overhead entries in red, mid-overhead
+ * entries in green - and keep the low overhead places
+ * normal:
+ */
+ if (fabs(percent) >= MIN_RED)
+ color = PERF_COLOR_RED;
+ else {
+ if (fabs(percent) > MIN_GREEN)
+ color = PERF_COLOR_GREEN;
+ }
+ return color;
+}
+
+int percent_color_fprintf(FILE *fp, const char *fmt, double percent)
+{
+ int r;
+ const char *color;
+
+ color = get_percent_color(percent);
+ r = color_fprintf(fp, color, fmt, percent);
+
+ return r;
+}
+
+int value_color_snprintf(char *bf, size_t size, const char *fmt, double value)
+{
+ const char *color = get_percent_color(value);
+ return color_snprintf(bf, size, color, fmt, value);
+}
+
+int percent_color_snprintf(char *bf, size_t size, const char *fmt, ...)
+{
+ va_list args;
+ double percent;
+
+ va_start(args, fmt);
+ percent = va_arg(args, double);
+ va_end(args);
+ return value_color_snprintf(bf, size, fmt, percent);
+}
diff --git a/tools/perf/util/color.h b/tools/perf/util/color.h
new file mode 100644
index 00000000000..7ff30a62a13
--- /dev/null
+++ b/tools/perf/util/color.h
@@ -0,0 +1,47 @@
+#ifndef __PERF_COLOR_H
+#define __PERF_COLOR_H
+
+/* "\033[1;38;5;2xx;48;5;2xxm\0" is 23 bytes */
+#define COLOR_MAXLEN 24
+
+#define PERF_COLOR_NORMAL ""
+#define PERF_COLOR_RESET "\033[m"
+#define PERF_COLOR_BOLD "\033[1m"
+#define PERF_COLOR_RED "\033[31m"
+#define PERF_COLOR_GREEN "\033[32m"
+#define PERF_COLOR_YELLOW "\033[33m"
+#define PERF_COLOR_BLUE "\033[34m"
+#define PERF_COLOR_MAGENTA "\033[35m"
+#define PERF_COLOR_CYAN "\033[36m"
+#define PERF_COLOR_BG_RED "\033[41m"
+
+#define MIN_GREEN 0.5
+#define MIN_RED 5.0
+
+/*
+ * This variable stores the value of color.ui
+ */
+extern int perf_use_color_default;
+
+
+/*
+ * Use this instead of perf_default_config if you need the value of color.ui.
+ */
+int perf_color_default_config(const char *var, const char *value, void *cb);
+
+int perf_config_colorbool(const char *var, const char *value, int stdout_is_tty);
+void color_parse(const char *value, const char *var, char *dst);
+void color_parse_mem(const char *value, int len, const char *var, char *dst);
+int color_vsnprintf(char *bf, size_t size, const char *color,
+ const char *fmt, va_list args);
+int color_vfprintf(FILE *fp, const char *color, const char *fmt, va_list args);
+int color_fprintf(FILE *fp, const char *color, const char *fmt, ...);
+int color_snprintf(char *bf, size_t size, const char *color, const char *fmt, ...);
+int color_fprintf_ln(FILE *fp, const char *color, const char *fmt, ...);
+int color_fwrite_lines(FILE *fp, const char *color, size_t count, const char *buf);
+int value_color_snprintf(char *bf, size_t size, const char *fmt, double value);
+int percent_color_snprintf(char *bf, size_t size, const char *fmt, ...);
+int percent_color_fprintf(FILE *fp, const char *fmt, double percent);
+const char *get_percent_color(double percent);
+
+#endif /* __PERF_COLOR_H */
diff --git a/tools/perf/util/comm.c b/tools/perf/util/comm.c
new file mode 100644
index 00000000000..f9e777629e2
--- /dev/null
+++ b/tools/perf/util/comm.c
@@ -0,0 +1,122 @@
+#include "comm.h"
+#include "util.h"
+#include <stdlib.h>
+#include <stdio.h>
+
+struct comm_str {
+ char *str;
+ struct rb_node rb_node;
+ int ref;
+};
+
+/* Should perhaps be moved to struct machine */
+static struct rb_root comm_str_root;
+
+static void comm_str__get(struct comm_str *cs)
+{
+ cs->ref++;
+}
+
+static void comm_str__put(struct comm_str *cs)
+{
+ if (!--cs->ref) {
+ rb_erase(&cs->rb_node, &comm_str_root);
+ zfree(&cs->str);
+ free(cs);
+ }
+}
+
+static struct comm_str *comm_str__alloc(const char *str)
+{
+ struct comm_str *cs;
+
+ cs = zalloc(sizeof(*cs));
+ if (!cs)
+ return NULL;
+
+ cs->str = strdup(str);
+ if (!cs->str) {
+ free(cs);
+ return NULL;
+ }
+
+ return cs;
+}
+
+static struct comm_str *comm_str__findnew(const char *str, struct rb_root *root)
+{
+ struct rb_node **p = &root->rb_node;
+ struct rb_node *parent = NULL;
+ struct comm_str *iter, *new;
+ int cmp;
+
+ while (*p != NULL) {
+ parent = *p;
+ iter = rb_entry(parent, struct comm_str, rb_node);
+
+ cmp = strcmp(str, iter->str);
+ if (!cmp)
+ return iter;
+
+ if (cmp < 0)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ new = comm_str__alloc(str);
+ if (!new)
+ return NULL;
+
+ rb_link_node(&new->rb_node, parent, p);
+ rb_insert_color(&new->rb_node, root);
+
+ return new;
+}
+
+struct comm *comm__new(const char *str, u64 timestamp)
+{
+ struct comm *comm = zalloc(sizeof(*comm));
+
+ if (!comm)
+ return NULL;
+
+ comm->start = timestamp;
+
+ comm->comm_str = comm_str__findnew(str, &comm_str_root);
+ if (!comm->comm_str) {
+ free(comm);
+ return NULL;
+ }
+
+ comm_str__get(comm->comm_str);
+
+ return comm;
+}
+
+int comm__override(struct comm *comm, const char *str, u64 timestamp)
+{
+ struct comm_str *new, *old = comm->comm_str;
+
+ new = comm_str__findnew(str, &comm_str_root);
+ if (!new)
+ return -ENOMEM;
+
+ comm_str__get(new);
+ comm_str__put(old);
+ comm->comm_str = new;
+ comm->start = timestamp;
+
+ return 0;
+}
+
+void comm__free(struct comm *comm)
+{
+ comm_str__put(comm->comm_str);
+ free(comm);
+}
+
+const char *comm__str(const struct comm *comm)
+{
+ return comm->comm_str->str;
+}
diff --git a/tools/perf/util/comm.h b/tools/perf/util/comm.h
new file mode 100644
index 00000000000..fac5bd51bef
--- /dev/null
+++ b/tools/perf/util/comm.h
@@ -0,0 +1,21 @@
+#ifndef __PERF_COMM_H
+#define __PERF_COMM_H
+
+#include "../perf.h"
+#include <linux/rbtree.h>
+#include <linux/list.h>
+
+struct comm_str;
+
+struct comm {
+ struct comm_str *comm_str;
+ u64 start;
+ struct list_head list;
+};
+
+void comm__free(struct comm *comm);
+struct comm *comm__new(const char *str, u64 timestamp);
+const char *comm__str(const struct comm *comm);
+int comm__override(struct comm *comm, const char *str, u64 timestamp);
+
+#endif /* __PERF_COMM_H */
diff --git a/tools/perf/util/config.c b/tools/perf/util/config.c
new file mode 100644
index 00000000000..24519e14ac5
--- /dev/null
+++ b/tools/perf/util/config.c
@@ -0,0 +1,513 @@
+/*
+ * config.c
+ *
+ * Helper functions for parsing config items.
+ * Originally copied from GIT source.
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ * Copyright (C) Johannes Schindelin, 2005
+ *
+ */
+#include "util.h"
+#include "cache.h"
+#include "exec_cmd.h"
+#include "util/hist.h" /* perf_hist_config */
+
+#define MAXNAME (256)
+
+#define DEBUG_CACHE_DIR ".debug"
+
+
+char buildid_dir[MAXPATHLEN]; /* root dir for buildid, binary cache */
+
+static FILE *config_file;
+static const char *config_file_name;
+static int config_linenr;
+static int config_file_eof;
+
+static const char *config_exclusive_filename;
+
+static int get_next_char(void)
+{
+ int c;
+ FILE *f;
+
+ c = '\n';
+ if ((f = config_file) != NULL) {
+ c = fgetc(f);
+ if (c == '\r') {
+ /* DOS like systems */
+ c = fgetc(f);
+ if (c != '\n') {
+ ungetc(c, f);
+ c = '\r';
+ }
+ }
+ if (c == '\n')
+ config_linenr++;
+ if (c == EOF) {
+ config_file_eof = 1;
+ c = '\n';
+ }
+ }
+ return c;
+}
+
+static char *parse_value(void)
+{
+ static char value[1024];
+ int quote = 0, comment = 0, space = 0;
+ size_t len = 0;
+
+ for (;;) {
+ int c = get_next_char();
+
+ if (len >= sizeof(value) - 1)
+ return NULL;
+ if (c == '\n') {
+ if (quote)
+ return NULL;
+ value[len] = 0;
+ return value;
+ }
+ if (comment)
+ continue;
+ if (isspace(c) && !quote) {
+ space = 1;
+ continue;
+ }
+ if (!quote) {
+ if (c == ';' || c == '#') {
+ comment = 1;
+ continue;
+ }
+ }
+ if (space) {
+ if (len)
+ value[len++] = ' ';
+ space = 0;
+ }
+ if (c == '\\') {
+ c = get_next_char();
+ switch (c) {
+ case '\n':
+ continue;
+ case 't':
+ c = '\t';
+ break;
+ case 'b':
+ c = '\b';
+ break;
+ case 'n':
+ c = '\n';
+ break;
+ /* Some characters escape as themselves */
+ case '\\': case '"':
+ break;
+ /* Reject unknown escape sequences */
+ default:
+ return NULL;
+ }
+ value[len++] = c;
+ continue;
+ }
+ if (c == '"') {
+ quote = 1-quote;
+ continue;
+ }
+ value[len++] = c;
+ }
+}
+
+static inline int iskeychar(int c)
+{
+ return isalnum(c) || c == '-' || c == '_';
+}
+
+static int get_value(config_fn_t fn, void *data, char *name, unsigned int len)
+{
+ int c;
+ char *value;
+
+ /* Get the full name */
+ for (;;) {
+ c = get_next_char();
+ if (config_file_eof)
+ break;
+ if (!iskeychar(c))
+ break;
+ name[len++] = c;
+ if (len >= MAXNAME)
+ return -1;
+ }
+ name[len] = 0;
+ while (c == ' ' || c == '\t')
+ c = get_next_char();
+
+ value = NULL;
+ if (c != '\n') {
+ if (c != '=')
+ return -1;
+ value = parse_value();
+ if (!value)
+ return -1;
+ }
+ return fn(name, value, data);
+}
+
+static int get_extended_base_var(char *name, int baselen, int c)
+{
+ do {
+ if (c == '\n')
+ return -1;
+ c = get_next_char();
+ } while (isspace(c));
+
+ /* We require the format to be '[base "extension"]' */
+ if (c != '"')
+ return -1;
+ name[baselen++] = '.';
+
+ for (;;) {
+ int ch = get_next_char();
+
+ if (ch == '\n')
+ return -1;
+ if (ch == '"')
+ break;
+ if (ch == '\\') {
+ ch = get_next_char();
+ if (ch == '\n')
+ return -1;
+ }
+ name[baselen++] = ch;
+ if (baselen > MAXNAME / 2)
+ return -1;
+ }
+
+ /* Final ']' */
+ if (get_next_char() != ']')
+ return -1;
+ return baselen;
+}
+
+static int get_base_var(char *name)
+{
+ int baselen = 0;
+
+ for (;;) {
+ int c = get_next_char();
+ if (config_file_eof)
+ return -1;
+ if (c == ']')
+ return baselen;
+ if (isspace(c))
+ return get_extended_base_var(name, baselen, c);
+ if (!iskeychar(c) && c != '.')
+ return -1;
+ if (baselen > MAXNAME / 2)
+ return -1;
+ name[baselen++] = tolower(c);
+ }
+}
+
+static int perf_parse_file(config_fn_t fn, void *data)
+{
+ int comment = 0;
+ int baselen = 0;
+ static char var[MAXNAME];
+
+ /* U+FEFF Byte Order Mark in UTF8 */
+ static const unsigned char *utf8_bom = (unsigned char *) "\xef\xbb\xbf";
+ const unsigned char *bomptr = utf8_bom;
+
+ for (;;) {
+ int c = get_next_char();
+ if (bomptr && *bomptr) {
+ /* We are at the file beginning; skip UTF8-encoded BOM
+ * if present. Sane editors won't put this in on their
+ * own, but e.g. Windows Notepad will do it happily. */
+ if ((unsigned char) c == *bomptr) {
+ bomptr++;
+ continue;
+ } else {
+ /* Do not tolerate partial BOM. */
+ if (bomptr != utf8_bom)
+ break;
+ /* No BOM at file beginning. Cool. */
+ bomptr = NULL;
+ }
+ }
+ if (c == '\n') {
+ if (config_file_eof)
+ return 0;
+ comment = 0;
+ continue;
+ }
+ if (comment || isspace(c))
+ continue;
+ if (c == '#' || c == ';') {
+ comment = 1;
+ continue;
+ }
+ if (c == '[') {
+ baselen = get_base_var(var);
+ if (baselen <= 0)
+ break;
+ var[baselen++] = '.';
+ var[baselen] = 0;
+ continue;
+ }
+ if (!isalpha(c))
+ break;
+ var[baselen] = tolower(c);
+ if (get_value(fn, data, var, baselen+1) < 0)
+ break;
+ }
+ die("bad config file line %d in %s", config_linenr, config_file_name);
+}
+
+static int parse_unit_factor(const char *end, unsigned long *val)
+{
+ if (!*end)
+ return 1;
+ else if (!strcasecmp(end, "k")) {
+ *val *= 1024;
+ return 1;
+ }
+ else if (!strcasecmp(end, "m")) {
+ *val *= 1024 * 1024;
+ return 1;
+ }
+ else if (!strcasecmp(end, "g")) {
+ *val *= 1024 * 1024 * 1024;
+ return 1;
+ }
+ return 0;
+}
+
+static int perf_parse_long(const char *value, long *ret)
+{
+ if (value && *value) {
+ char *end;
+ long val = strtol(value, &end, 0);
+ unsigned long factor = 1;
+ if (!parse_unit_factor(end, &factor))
+ return 0;
+ *ret = val * factor;
+ return 1;
+ }
+ return 0;
+}
+
+static void die_bad_config(const char *name)
+{
+ if (config_file_name)
+ die("bad config value for '%s' in %s", name, config_file_name);
+ die("bad config value for '%s'", name);
+}
+
+int perf_config_int(const char *name, const char *value)
+{
+ long ret = 0;
+ if (!perf_parse_long(value, &ret))
+ die_bad_config(name);
+ return ret;
+}
+
+static int perf_config_bool_or_int(const char *name, const char *value, int *is_bool)
+{
+ *is_bool = 1;
+ if (!value)
+ return 1;
+ if (!*value)
+ return 0;
+ if (!strcasecmp(value, "true") || !strcasecmp(value, "yes") || !strcasecmp(value, "on"))
+ return 1;
+ if (!strcasecmp(value, "false") || !strcasecmp(value, "no") || !strcasecmp(value, "off"))
+ return 0;
+ *is_bool = 0;
+ return perf_config_int(name, value);
+}
+
+int perf_config_bool(const char *name, const char *value)
+{
+ int discard;
+ return !!perf_config_bool_or_int(name, value, &discard);
+}
+
+const char *perf_config_dirname(const char *name, const char *value)
+{
+ if (!name)
+ return NULL;
+ return value;
+}
+
+static int perf_default_core_config(const char *var __maybe_unused,
+ const char *value __maybe_unused)
+{
+ /* Add other config variables here. */
+ return 0;
+}
+
+int perf_default_config(const char *var, const char *value,
+ void *dummy __maybe_unused)
+{
+ if (!prefixcmp(var, "core."))
+ return perf_default_core_config(var, value);
+
+ if (!prefixcmp(var, "hist."))
+ return perf_hist_config(var, value);
+
+ /* Add other config variables here. */
+ return 0;
+}
+
+static int perf_config_from_file(config_fn_t fn, const char *filename, void *data)
+{
+ int ret;
+ FILE *f = fopen(filename, "r");
+
+ ret = -1;
+ if (f) {
+ config_file = f;
+ config_file_name = filename;
+ config_linenr = 1;
+ config_file_eof = 0;
+ ret = perf_parse_file(fn, data);
+ fclose(f);
+ config_file_name = NULL;
+ }
+ return ret;
+}
+
+static const char *perf_etc_perfconfig(void)
+{
+ static const char *system_wide;
+ if (!system_wide)
+ system_wide = system_path(ETC_PERFCONFIG);
+ return system_wide;
+}
+
+static int perf_env_bool(const char *k, int def)
+{
+ const char *v = getenv(k);
+ return v ? perf_config_bool(k, v) : def;
+}
+
+static int perf_config_system(void)
+{
+ return !perf_env_bool("PERF_CONFIG_NOSYSTEM", 0);
+}
+
+static int perf_config_global(void)
+{
+ return !perf_env_bool("PERF_CONFIG_NOGLOBAL", 0);
+}
+
+int perf_config(config_fn_t fn, void *data)
+{
+ int ret = 0, found = 0;
+ const char *home = NULL;
+
+ /* Setting $PERF_CONFIG makes perf read _only_ the given config file. */
+ if (config_exclusive_filename)
+ return perf_config_from_file(fn, config_exclusive_filename, data);
+ if (perf_config_system() && !access(perf_etc_perfconfig(), R_OK)) {
+ ret += perf_config_from_file(fn, perf_etc_perfconfig(),
+ data);
+ found += 1;
+ }
+
+ home = getenv("HOME");
+ if (perf_config_global() && home) {
+ char *user_config = strdup(mkpath("%s/.perfconfig", home));
+ struct stat st;
+
+ if (user_config == NULL) {
+ warning("Not enough memory to process %s/.perfconfig, "
+ "ignoring it.", home);
+ goto out;
+ }
+
+ if (stat(user_config, &st) < 0)
+ goto out_free;
+
+ if (st.st_uid && (st.st_uid != geteuid())) {
+ warning("File %s not owned by current user or root, "
+ "ignoring it.", user_config);
+ goto out_free;
+ }
+
+ if (!st.st_size)
+ goto out_free;
+
+ ret += perf_config_from_file(fn, user_config, data);
+ found += 1;
+out_free:
+ free(user_config);
+ }
+out:
+ if (found == 0)
+ return -1;
+ return ret;
+}
+
+/*
+ * Call this to report error for your variable that should not
+ * get a boolean value (i.e. "[my] var" means "true").
+ */
+int config_error_nonbool(const char *var)
+{
+ return error("Missing value for '%s'", var);
+}
+
+struct buildid_dir_config {
+ char *dir;
+};
+
+static int buildid_dir_command_config(const char *var, const char *value,
+ void *data)
+{
+ struct buildid_dir_config *c = data;
+ const char *v;
+
+ /* same dir for all commands */
+ if (!prefixcmp(var, "buildid.") && !strcmp(var + 8, "dir")) {
+ v = perf_config_dirname(var, value);
+ if (!v)
+ return -1;
+ strncpy(c->dir, v, MAXPATHLEN-1);
+ c->dir[MAXPATHLEN-1] = '\0';
+ }
+ return 0;
+}
+
+static void check_buildid_dir_config(void)
+{
+ struct buildid_dir_config c;
+ c.dir = buildid_dir;
+ perf_config(buildid_dir_command_config, &c);
+}
+
+void set_buildid_dir(void)
+{
+ buildid_dir[0] = '\0';
+
+ /* try config file */
+ check_buildid_dir_config();
+
+ /* default to $HOME/.debug */
+ if (buildid_dir[0] == '\0') {
+ char *v = getenv("HOME");
+ if (v) {
+ snprintf(buildid_dir, MAXPATHLEN-1, "%s/%s",
+ v, DEBUG_CACHE_DIR);
+ } else {
+ strncpy(buildid_dir, DEBUG_CACHE_DIR, MAXPATHLEN-1);
+ }
+ buildid_dir[MAXPATHLEN-1] = '\0';
+ }
+ /* for communicating with external commands */
+ setenv("PERF_BUILDID_DIR", buildid_dir, 1);
+}
diff --git a/tools/perf/util/cpumap.c b/tools/perf/util/cpumap.c
new file mode 100644
index 00000000000..c4e55b71010
--- /dev/null
+++ b/tools/perf/util/cpumap.c
@@ -0,0 +1,479 @@
+#include "util.h"
+#include <api/fs/fs.h>
+#include "../perf.h"
+#include "cpumap.h"
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+static struct cpu_map *cpu_map__default_new(void)
+{
+ struct cpu_map *cpus;
+ int nr_cpus;
+
+ nr_cpus = sysconf(_SC_NPROCESSORS_ONLN);
+ if (nr_cpus < 0)
+ return NULL;
+
+ cpus = malloc(sizeof(*cpus) + nr_cpus * sizeof(int));
+ if (cpus != NULL) {
+ int i;
+ for (i = 0; i < nr_cpus; ++i)
+ cpus->map[i] = i;
+
+ cpus->nr = nr_cpus;
+ }
+
+ return cpus;
+}
+
+static struct cpu_map *cpu_map__trim_new(int nr_cpus, int *tmp_cpus)
+{
+ size_t payload_size = nr_cpus * sizeof(int);
+ struct cpu_map *cpus = malloc(sizeof(*cpus) + payload_size);
+
+ if (cpus != NULL) {
+ cpus->nr = nr_cpus;
+ memcpy(cpus->map, tmp_cpus, payload_size);
+ }
+
+ return cpus;
+}
+
+struct cpu_map *cpu_map__read(FILE *file)
+{
+ struct cpu_map *cpus = NULL;
+ int nr_cpus = 0;
+ int *tmp_cpus = NULL, *tmp;
+ int max_entries = 0;
+ int n, cpu, prev;
+ char sep;
+
+ sep = 0;
+ prev = -1;
+ for (;;) {
+ n = fscanf(file, "%u%c", &cpu, &sep);
+ if (n <= 0)
+ break;
+ if (prev >= 0) {
+ int new_max = nr_cpus + cpu - prev - 1;
+
+ if (new_max >= max_entries) {
+ max_entries = new_max + MAX_NR_CPUS / 2;
+ tmp = realloc(tmp_cpus, max_entries * sizeof(int));
+ if (tmp == NULL)
+ goto out_free_tmp;
+ tmp_cpus = tmp;
+ }
+
+ while (++prev < cpu)
+ tmp_cpus[nr_cpus++] = prev;
+ }
+ if (nr_cpus == max_entries) {
+ max_entries += MAX_NR_CPUS;
+ tmp = realloc(tmp_cpus, max_entries * sizeof(int));
+ if (tmp == NULL)
+ goto out_free_tmp;
+ tmp_cpus = tmp;
+ }
+
+ tmp_cpus[nr_cpus++] = cpu;
+ if (n == 2 && sep == '-')
+ prev = cpu;
+ else
+ prev = -1;
+ if (n == 1 || sep == '\n')
+ break;
+ }
+
+ if (nr_cpus > 0)
+ cpus = cpu_map__trim_new(nr_cpus, tmp_cpus);
+ else
+ cpus = cpu_map__default_new();
+out_free_tmp:
+ free(tmp_cpus);
+ return cpus;
+}
+
+static struct cpu_map *cpu_map__read_all_cpu_map(void)
+{
+ struct cpu_map *cpus = NULL;
+ FILE *onlnf;
+
+ onlnf = fopen("/sys/devices/system/cpu/online", "r");
+ if (!onlnf)
+ return cpu_map__default_new();
+
+ cpus = cpu_map__read(onlnf);
+ fclose(onlnf);
+ return cpus;
+}
+
+struct cpu_map *cpu_map__new(const char *cpu_list)
+{
+ struct cpu_map *cpus = NULL;
+ unsigned long start_cpu, end_cpu = 0;
+ char *p = NULL;
+ int i, nr_cpus = 0;
+ int *tmp_cpus = NULL, *tmp;
+ int max_entries = 0;
+
+ if (!cpu_list)
+ return cpu_map__read_all_cpu_map();
+
+ if (!isdigit(*cpu_list))
+ goto out;
+
+ while (isdigit(*cpu_list)) {
+ p = NULL;
+ start_cpu = strtoul(cpu_list, &p, 0);
+ if (start_cpu >= INT_MAX
+ || (*p != '\0' && *p != ',' && *p != '-'))
+ goto invalid;
+
+ if (*p == '-') {
+ cpu_list = ++p;
+ p = NULL;
+ end_cpu = strtoul(cpu_list, &p, 0);
+
+ if (end_cpu >= INT_MAX || (*p != '\0' && *p != ','))
+ goto invalid;
+
+ if (end_cpu < start_cpu)
+ goto invalid;
+ } else {
+ end_cpu = start_cpu;
+ }
+
+ for (; start_cpu <= end_cpu; start_cpu++) {
+ /* check for duplicates */
+ for (i = 0; i < nr_cpus; i++)
+ if (tmp_cpus[i] == (int)start_cpu)
+ goto invalid;
+
+ if (nr_cpus == max_entries) {
+ max_entries += MAX_NR_CPUS;
+ tmp = realloc(tmp_cpus, max_entries * sizeof(int));
+ if (tmp == NULL)
+ goto invalid;
+ tmp_cpus = tmp;
+ }
+ tmp_cpus[nr_cpus++] = (int)start_cpu;
+ }
+ if (*p)
+ ++p;
+
+ cpu_list = p;
+ }
+
+ if (nr_cpus > 0)
+ cpus = cpu_map__trim_new(nr_cpus, tmp_cpus);
+ else
+ cpus = cpu_map__default_new();
+invalid:
+ free(tmp_cpus);
+out:
+ return cpus;
+}
+
+size_t cpu_map__fprintf(struct cpu_map *map, FILE *fp)
+{
+ int i;
+ size_t printed = fprintf(fp, "%d cpu%s: ",
+ map->nr, map->nr > 1 ? "s" : "");
+ for (i = 0; i < map->nr; ++i)
+ printed += fprintf(fp, "%s%d", i ? ", " : "", map->map[i]);
+
+ return printed + fprintf(fp, "\n");
+}
+
+struct cpu_map *cpu_map__dummy_new(void)
+{
+ struct cpu_map *cpus = malloc(sizeof(*cpus) + sizeof(int));
+
+ if (cpus != NULL) {
+ cpus->nr = 1;
+ cpus->map[0] = -1;
+ }
+
+ return cpus;
+}
+
+void cpu_map__delete(struct cpu_map *map)
+{
+ free(map);
+}
+
+int cpu_map__get_socket(struct cpu_map *map, int idx)
+{
+ FILE *fp;
+ const char *mnt;
+ char path[PATH_MAX];
+ int cpu, ret;
+
+ if (idx > map->nr)
+ return -1;
+
+ cpu = map->map[idx];
+
+ mnt = sysfs__mountpoint();
+ if (!mnt)
+ return -1;
+
+ snprintf(path, PATH_MAX,
+ "%s/devices/system/cpu/cpu%d/topology/physical_package_id",
+ mnt, cpu);
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return -1;
+ ret = fscanf(fp, "%d", &cpu);
+ fclose(fp);
+ return ret == 1 ? cpu : -1;
+}
+
+static int cmp_ids(const void *a, const void *b)
+{
+ return *(int *)a - *(int *)b;
+}
+
+static int cpu_map__build_map(struct cpu_map *cpus, struct cpu_map **res,
+ int (*f)(struct cpu_map *map, int cpu))
+{
+ struct cpu_map *c;
+ int nr = cpus->nr;
+ int cpu, s1, s2;
+
+ /* allocate as much as possible */
+ c = calloc(1, sizeof(*c) + nr * sizeof(int));
+ if (!c)
+ return -1;
+
+ for (cpu = 0; cpu < nr; cpu++) {
+ s1 = f(cpus, cpu);
+ for (s2 = 0; s2 < c->nr; s2++) {
+ if (s1 == c->map[s2])
+ break;
+ }
+ if (s2 == c->nr) {
+ c->map[c->nr] = s1;
+ c->nr++;
+ }
+ }
+ /* ensure we process id in increasing order */
+ qsort(c->map, c->nr, sizeof(int), cmp_ids);
+
+ *res = c;
+ return 0;
+}
+
+int cpu_map__get_core(struct cpu_map *map, int idx)
+{
+ FILE *fp;
+ const char *mnt;
+ char path[PATH_MAX];
+ int cpu, ret, s;
+
+ if (idx > map->nr)
+ return -1;
+
+ cpu = map->map[idx];
+
+ mnt = sysfs__mountpoint();
+ if (!mnt)
+ return -1;
+
+ snprintf(path, PATH_MAX,
+ "%s/devices/system/cpu/cpu%d/topology/core_id",
+ mnt, cpu);
+
+ fp = fopen(path, "r");
+ if (!fp)
+ return -1;
+ ret = fscanf(fp, "%d", &cpu);
+ fclose(fp);
+ if (ret != 1)
+ return -1;
+
+ s = cpu_map__get_socket(map, idx);
+ if (s == -1)
+ return -1;
+
+ /*
+ * encode socket in upper 16 bits
+ * core_id is relative to socket, and
+ * we need a global id. So we combine
+ * socket+ core id
+ */
+ return (s << 16) | (cpu & 0xffff);
+}
+
+int cpu_map__build_socket_map(struct cpu_map *cpus, struct cpu_map **sockp)
+{
+ return cpu_map__build_map(cpus, sockp, cpu_map__get_socket);
+}
+
+int cpu_map__build_core_map(struct cpu_map *cpus, struct cpu_map **corep)
+{
+ return cpu_map__build_map(cpus, corep, cpu_map__get_core);
+}
+
+/* setup simple routines to easily access node numbers given a cpu number */
+static int get_max_num(char *path, int *max)
+{
+ size_t num;
+ char *buf;
+ int err = 0;
+
+ if (filename__read_str(path, &buf, &num))
+ return -1;
+
+ buf[num] = '\0';
+
+ /* start on the right, to find highest node num */
+ while (--num) {
+ if ((buf[num] == ',') || (buf[num] == '-')) {
+ num++;
+ break;
+ }
+ }
+ if (sscanf(&buf[num], "%d", max) < 1) {
+ err = -1;
+ goto out;
+ }
+
+ /* convert from 0-based to 1-based */
+ (*max)++;
+
+out:
+ free(buf);
+ return err;
+}
+
+/* Determine highest possible cpu in the system for sparse allocation */
+static void set_max_cpu_num(void)
+{
+ const char *mnt;
+ char path[PATH_MAX];
+ int ret = -1;
+
+ /* set up default */
+ max_cpu_num = 4096;
+
+ mnt = sysfs__mountpoint();
+ if (!mnt)
+ goto out;
+
+ /* get the highest possible cpu number for a sparse allocation */
+ ret = snprintf(path, PATH_MAX, "%s/devices/system/cpu/possible", mnt);
+ if (ret == PATH_MAX) {
+ pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX);
+ goto out;
+ }
+
+ ret = get_max_num(path, &max_cpu_num);
+
+out:
+ if (ret)
+ pr_err("Failed to read max cpus, using default of %d\n", max_cpu_num);
+}
+
+/* Determine highest possible node in the system for sparse allocation */
+static void set_max_node_num(void)
+{
+ const char *mnt;
+ char path[PATH_MAX];
+ int ret = -1;
+
+ /* set up default */
+ max_node_num = 8;
+
+ mnt = sysfs__mountpoint();
+ if (!mnt)
+ goto out;
+
+ /* get the highest possible cpu number for a sparse allocation */
+ ret = snprintf(path, PATH_MAX, "%s/devices/system/node/possible", mnt);
+ if (ret == PATH_MAX) {
+ pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX);
+ goto out;
+ }
+
+ ret = get_max_num(path, &max_node_num);
+
+out:
+ if (ret)
+ pr_err("Failed to read max nodes, using default of %d\n", max_node_num);
+}
+
+static int init_cpunode_map(void)
+{
+ int i;
+
+ set_max_cpu_num();
+ set_max_node_num();
+
+ cpunode_map = calloc(max_cpu_num, sizeof(int));
+ if (!cpunode_map) {
+ pr_err("%s: calloc failed\n", __func__);
+ return -1;
+ }
+
+ for (i = 0; i < max_cpu_num; i++)
+ cpunode_map[i] = -1;
+
+ return 0;
+}
+
+int cpu__setup_cpunode_map(void)
+{
+ struct dirent *dent1, *dent2;
+ DIR *dir1, *dir2;
+ unsigned int cpu, mem;
+ char buf[PATH_MAX];
+ char path[PATH_MAX];
+ const char *mnt;
+ int n;
+
+ /* initialize globals */
+ if (init_cpunode_map())
+ return -1;
+
+ mnt = sysfs__mountpoint();
+ if (!mnt)
+ return 0;
+
+ n = snprintf(path, PATH_MAX, "%s/devices/system/node", mnt);
+ if (n == PATH_MAX) {
+ pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX);
+ return -1;
+ }
+
+ dir1 = opendir(path);
+ if (!dir1)
+ return 0;
+
+ /* walk tree and setup map */
+ while ((dent1 = readdir(dir1)) != NULL) {
+ if (dent1->d_type != DT_DIR || sscanf(dent1->d_name, "node%u", &mem) < 1)
+ continue;
+
+ n = snprintf(buf, PATH_MAX, "%s/%s", path, dent1->d_name);
+ if (n == PATH_MAX) {
+ pr_err("sysfs path crossed PATH_MAX(%d) size\n", PATH_MAX);
+ continue;
+ }
+
+ dir2 = opendir(buf);
+ if (!dir2)
+ continue;
+ while ((dent2 = readdir(dir2)) != NULL) {
+ if (dent2->d_type != DT_LNK || sscanf(dent2->d_name, "cpu%u", &cpu) < 1)
+ continue;
+ cpunode_map[cpu] = mem;
+ }
+ closedir(dir2);
+ }
+ closedir(dir1);
+ return 0;
+}
diff --git a/tools/perf/util/cpumap.h b/tools/perf/util/cpumap.h
new file mode 100644
index 00000000000..61a65484900
--- /dev/null
+++ b/tools/perf/util/cpumap.h
@@ -0,0 +1,84 @@
+#ifndef __PERF_CPUMAP_H
+#define __PERF_CPUMAP_H
+
+#include <stdio.h>
+#include <stdbool.h>
+
+#include "perf.h"
+#include "util/debug.h"
+
+struct cpu_map {
+ int nr;
+ int map[];
+};
+
+struct cpu_map *cpu_map__new(const char *cpu_list);
+struct cpu_map *cpu_map__dummy_new(void);
+void cpu_map__delete(struct cpu_map *map);
+struct cpu_map *cpu_map__read(FILE *file);
+size_t cpu_map__fprintf(struct cpu_map *map, FILE *fp);
+int cpu_map__get_socket(struct cpu_map *map, int idx);
+int cpu_map__get_core(struct cpu_map *map, int idx);
+int cpu_map__build_socket_map(struct cpu_map *cpus, struct cpu_map **sockp);
+int cpu_map__build_core_map(struct cpu_map *cpus, struct cpu_map **corep);
+
+static inline int cpu_map__socket(struct cpu_map *sock, int s)
+{
+ if (!sock || s > sock->nr || s < 0)
+ return 0;
+ return sock->map[s];
+}
+
+static inline int cpu_map__id_to_socket(int id)
+{
+ return id >> 16;
+}
+
+static inline int cpu_map__id_to_cpu(int id)
+{
+ return id & 0xffff;
+}
+
+static inline int cpu_map__nr(const struct cpu_map *map)
+{
+ return map ? map->nr : 1;
+}
+
+static inline bool cpu_map__empty(const struct cpu_map *map)
+{
+ return map ? map->map[0] == -1 : true;
+}
+
+int max_cpu_num;
+int max_node_num;
+int *cpunode_map;
+
+int cpu__setup_cpunode_map(void);
+
+static inline int cpu__max_node(void)
+{
+ if (unlikely(!max_node_num))
+ pr_debug("cpu_map not initialized\n");
+
+ return max_node_num;
+}
+
+static inline int cpu__max_cpu(void)
+{
+ if (unlikely(!max_cpu_num))
+ pr_debug("cpu_map not initialized\n");
+
+ return max_cpu_num;
+}
+
+static inline int cpu__get_node(int cpu)
+{
+ if (unlikely(cpunode_map == NULL)) {
+ pr_debug("cpu_map not initialized\n");
+ return -1;
+ }
+
+ return cpunode_map[cpu];
+}
+
+#endif /* __PERF_CPUMAP_H */
diff --git a/tools/perf/util/ctype.c b/tools/perf/util/ctype.c
new file mode 100644
index 00000000000..aada3ac5e89
--- /dev/null
+++ b/tools/perf/util/ctype.c
@@ -0,0 +1,39 @@
+/*
+ * Sane locale-independent, ASCII ctype.
+ *
+ * No surprises, and works with signed and unsigned chars.
+ */
+#include "util.h"
+
+enum {
+ S = GIT_SPACE,
+ A = GIT_ALPHA,
+ D = GIT_DIGIT,
+ G = GIT_GLOB_SPECIAL, /* *, ?, [, \\ */
+ R = GIT_REGEX_SPECIAL, /* $, (, ), +, ., ^, {, | * */
+ P = GIT_PRINT_EXTRA, /* printable - alpha - digit - glob - regex */
+
+ PS = GIT_SPACE | GIT_PRINT_EXTRA,
+};
+
+unsigned char sane_ctype[256] = {
+/* 0 1 2 3 4 5 6 7 8 9 A B C D E F */
+
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, S, S, 0, 0, S, 0, 0, /* 0.. 15 */
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 16.. 31 */
+ PS,P, P, P, R, P, P, P, R, R, G, R, P, P, R, P, /* 32.. 47 */
+ D, D, D, D, D, D, D, D, D, D, P, P, P, P, P, G, /* 48.. 63 */
+ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 64.. 79 */
+ A, A, A, A, A, A, A, A, A, A, A, G, G, P, R, P, /* 80.. 95 */
+ P, A, A, A, A, A, A, A, A, A, A, A, A, A, A, A, /* 96..111 */
+ A, A, A, A, A, A, A, A, A, A, A, R, R, P, P, 0, /* 112..127 */
+ /* Nothing in the 128.. range */
+};
+
+const char *graph_line =
+ "_____________________________________________________________________"
+ "_____________________________________________________________________";
+const char *graph_dotted_line =
+ "---------------------------------------------------------------------"
+ "---------------------------------------------------------------------"
+ "---------------------------------------------------------------------";
diff --git a/tools/perf/util/data.c b/tools/perf/util/data.c
new file mode 100644
index 00000000000..55de44ecebe
--- /dev/null
+++ b/tools/perf/util/data.c
@@ -0,0 +1,133 @@
+#include <linux/compiler.h>
+#include <linux/kernel.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <string.h>
+
+#include "data.h"
+#include "util.h"
+
+static bool check_pipe(struct perf_data_file *file)
+{
+ struct stat st;
+ bool is_pipe = false;
+ int fd = perf_data_file__is_read(file) ?
+ STDIN_FILENO : STDOUT_FILENO;
+
+ if (!file->path) {
+ if (!fstat(fd, &st) && S_ISFIFO(st.st_mode))
+ is_pipe = true;
+ } else {
+ if (!strcmp(file->path, "-"))
+ is_pipe = true;
+ }
+
+ if (is_pipe)
+ file->fd = fd;
+
+ return file->is_pipe = is_pipe;
+}
+
+static int check_backup(struct perf_data_file *file)
+{
+ struct stat st;
+
+ if (!stat(file->path, &st) && st.st_size) {
+ /* TODO check errors properly */
+ char oldname[PATH_MAX];
+ snprintf(oldname, sizeof(oldname), "%s.old",
+ file->path);
+ unlink(oldname);
+ rename(file->path, oldname);
+ }
+
+ return 0;
+}
+
+static int open_file_read(struct perf_data_file *file)
+{
+ struct stat st;
+ int fd;
+
+ fd = open(file->path, O_RDONLY);
+ if (fd < 0) {
+ int err = errno;
+
+ pr_err("failed to open %s: %s", file->path, strerror(err));
+ if (err == ENOENT && !strcmp(file->path, "perf.data"))
+ pr_err(" (try 'perf record' first)");
+ pr_err("\n");
+ return -err;
+ }
+
+ if (fstat(fd, &st) < 0)
+ goto out_close;
+
+ if (!file->force && st.st_uid && (st.st_uid != geteuid())) {
+ pr_err("file %s not owned by current user or root\n",
+ file->path);
+ goto out_close;
+ }
+
+ if (!st.st_size) {
+ pr_info("zero-sized file (%s), nothing to do!\n",
+ file->path);
+ goto out_close;
+ }
+
+ file->size = st.st_size;
+ return fd;
+
+ out_close:
+ close(fd);
+ return -1;
+}
+
+static int open_file_write(struct perf_data_file *file)
+{
+ int fd;
+
+ if (check_backup(file))
+ return -1;
+
+ fd = open(file->path, O_CREAT|O_RDWR|O_TRUNC, S_IRUSR|S_IWUSR);
+
+ if (fd < 0)
+ pr_err("failed to open %s : %s\n", file->path, strerror(errno));
+
+ return fd;
+}
+
+static int open_file(struct perf_data_file *file)
+{
+ int fd;
+
+ fd = perf_data_file__is_read(file) ?
+ open_file_read(file) : open_file_write(file);
+
+ file->fd = fd;
+ return fd < 0 ? -1 : 0;
+}
+
+int perf_data_file__open(struct perf_data_file *file)
+{
+ if (check_pipe(file))
+ return 0;
+
+ if (!file->path)
+ file->path = "perf.data";
+
+ return open_file(file);
+}
+
+void perf_data_file__close(struct perf_data_file *file)
+{
+ close(file->fd);
+}
+
+ssize_t perf_data_file__write(struct perf_data_file *file,
+ void *buf, size_t size)
+{
+ return writen(file->fd, buf, size);
+}
diff --git a/tools/perf/util/data.h b/tools/perf/util/data.h
new file mode 100644
index 00000000000..2b15d0c95c7
--- /dev/null
+++ b/tools/perf/util/data.h
@@ -0,0 +1,50 @@
+#ifndef __PERF_DATA_H
+#define __PERF_DATA_H
+
+#include <stdbool.h>
+
+enum perf_data_mode {
+ PERF_DATA_MODE_WRITE,
+ PERF_DATA_MODE_READ,
+};
+
+struct perf_data_file {
+ const char *path;
+ int fd;
+ bool is_pipe;
+ bool force;
+ unsigned long size;
+ enum perf_data_mode mode;
+};
+
+static inline bool perf_data_file__is_read(struct perf_data_file *file)
+{
+ return file->mode == PERF_DATA_MODE_READ;
+}
+
+static inline bool perf_data_file__is_write(struct perf_data_file *file)
+{
+ return file->mode == PERF_DATA_MODE_WRITE;
+}
+
+static inline int perf_data_file__is_pipe(struct perf_data_file *file)
+{
+ return file->is_pipe;
+}
+
+static inline int perf_data_file__fd(struct perf_data_file *file)
+{
+ return file->fd;
+}
+
+static inline unsigned long perf_data_file__size(struct perf_data_file *file)
+{
+ return file->size;
+}
+
+int perf_data_file__open(struct perf_data_file *file);
+void perf_data_file__close(struct perf_data_file *file);
+ssize_t perf_data_file__write(struct perf_data_file *file,
+ void *buf, size_t size);
+
+#endif /* __PERF_DATA_H */
diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c
new file mode 100644
index 00000000000..299b5558650
--- /dev/null
+++ b/tools/perf/util/debug.c
@@ -0,0 +1,107 @@
+/* For general debugging purposes */
+
+#include "../perf.h"
+
+#include <string.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include "cache.h"
+#include "color.h"
+#include "event.h"
+#include "debug.h"
+#include "util.h"
+#include "target.h"
+
+int verbose;
+bool dump_trace = false, quiet = false;
+
+static int _eprintf(int level, const char *fmt, va_list args)
+{
+ int ret = 0;
+
+ if (verbose >= level) {
+ if (use_browser >= 1)
+ ui_helpline__vshow(fmt, args);
+ else
+ ret = vfprintf(stderr, fmt, args);
+ }
+
+ return ret;
+}
+
+int eprintf(int level, const char *fmt, ...)
+{
+ va_list args;
+ int ret;
+
+ va_start(args, fmt);
+ ret = _eprintf(level, fmt, args);
+ va_end(args);
+
+ return ret;
+}
+
+/*
+ * Overloading libtraceevent standard info print
+ * function, display with -v in perf.
+ */
+void pr_stat(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ _eprintf(1, fmt, args);
+ va_end(args);
+ eprintf(1, "\n");
+}
+
+int dump_printf(const char *fmt, ...)
+{
+ va_list args;
+ int ret = 0;
+
+ if (dump_trace) {
+ va_start(args, fmt);
+ ret = vprintf(fmt, args);
+ va_end(args);
+ }
+
+ return ret;
+}
+
+void trace_event(union perf_event *event)
+{
+ unsigned char *raw_event = (void *)event;
+ const char *color = PERF_COLOR_BLUE;
+ int i, j;
+
+ if (!dump_trace)
+ return;
+
+ printf(".");
+ color_fprintf(stdout, color, "\n. ... raw event: size %d bytes\n",
+ event->header.size);
+
+ for (i = 0; i < event->header.size; i++) {
+ if ((i & 15) == 0) {
+ printf(".");
+ color_fprintf(stdout, color, " %04x: ", i);
+ }
+
+ color_fprintf(stdout, color, " %02x", raw_event[i]);
+
+ if (((i & 15) == 15) || i == event->header.size-1) {
+ color_fprintf(stdout, color, " ");
+ for (j = 0; j < 15-(i & 15); j++)
+ color_fprintf(stdout, color, " ");
+ for (j = i & ~15; j <= i; j++) {
+ color_fprintf(stdout, color, "%c",
+ isprint(raw_event[j]) ?
+ raw_event[j] : '.');
+ }
+ color_fprintf(stdout, color, "\n");
+ }
+ }
+ printf(".\n");
+}
diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h
new file mode 100644
index 00000000000..443694c36b0
--- /dev/null
+++ b/tools/perf/util/debug.h
@@ -0,0 +1,22 @@
+/* For debugging general purposes */
+#ifndef __PERF_DEBUG_H
+#define __PERF_DEBUG_H
+
+#include <stdbool.h>
+#include "event.h"
+#include "../ui/helpline.h"
+#include "../ui/progress.h"
+#include "../ui/util.h"
+
+extern int verbose;
+extern bool quiet, dump_trace;
+
+int dump_printf(const char *fmt, ...) __attribute__((format(printf, 1, 2)));
+void trace_event(union perf_event *event);
+
+int ui__error(const char *format, ...) __attribute__((format(printf, 1, 2)));
+int ui__warning(const char *format, ...) __attribute__((format(printf, 1, 2)));
+
+void pr_stat(const char *fmt, ...);
+
+#endif /* __PERF_DEBUG_H */
diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c
new file mode 100644
index 00000000000..819f10414f0
--- /dev/null
+++ b/tools/perf/util/dso.c
@@ -0,0 +1,900 @@
+#include <asm/bug.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include "symbol.h"
+#include "dso.h"
+#include "machine.h"
+#include "util.h"
+#include "debug.h"
+
+char dso__symtab_origin(const struct dso *dso)
+{
+ static const char origin[] = {
+ [DSO_BINARY_TYPE__KALLSYMS] = 'k',
+ [DSO_BINARY_TYPE__VMLINUX] = 'v',
+ [DSO_BINARY_TYPE__JAVA_JIT] = 'j',
+ [DSO_BINARY_TYPE__DEBUGLINK] = 'l',
+ [DSO_BINARY_TYPE__BUILD_ID_CACHE] = 'B',
+ [DSO_BINARY_TYPE__FEDORA_DEBUGINFO] = 'f',
+ [DSO_BINARY_TYPE__UBUNTU_DEBUGINFO] = 'u',
+ [DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO] = 'o',
+ [DSO_BINARY_TYPE__BUILDID_DEBUGINFO] = 'b',
+ [DSO_BINARY_TYPE__SYSTEM_PATH_DSO] = 'd',
+ [DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE] = 'K',
+ [DSO_BINARY_TYPE__GUEST_KALLSYMS] = 'g',
+ [DSO_BINARY_TYPE__GUEST_KMODULE] = 'G',
+ [DSO_BINARY_TYPE__GUEST_VMLINUX] = 'V',
+ };
+
+ if (dso == NULL || dso->symtab_type == DSO_BINARY_TYPE__NOT_FOUND)
+ return '!';
+ return origin[dso->symtab_type];
+}
+
+int dso__read_binary_type_filename(const struct dso *dso,
+ enum dso_binary_type type,
+ char *root_dir, char *filename, size_t size)
+{
+ char build_id_hex[BUILD_ID_SIZE * 2 + 1];
+ int ret = 0;
+
+ switch (type) {
+ case DSO_BINARY_TYPE__DEBUGLINK: {
+ char *debuglink;
+
+ strncpy(filename, dso->long_name, size);
+ debuglink = filename + dso->long_name_len;
+ while (debuglink != filename && *debuglink != '/')
+ debuglink--;
+ if (*debuglink == '/')
+ debuglink++;
+ ret = filename__read_debuglink(dso->long_name, debuglink,
+ size - (debuglink - filename));
+ }
+ break;
+ case DSO_BINARY_TYPE__BUILD_ID_CACHE:
+ /* skip the locally configured cache if a symfs is given */
+ if (symbol_conf.symfs[0] ||
+ (dso__build_id_filename(dso, filename, size) == NULL))
+ ret = -1;
+ break;
+
+ case DSO_BINARY_TYPE__FEDORA_DEBUGINFO:
+ snprintf(filename, size, "%s/usr/lib/debug%s.debug",
+ symbol_conf.symfs, dso->long_name);
+ break;
+
+ case DSO_BINARY_TYPE__UBUNTU_DEBUGINFO:
+ snprintf(filename, size, "%s/usr/lib/debug%s",
+ symbol_conf.symfs, dso->long_name);
+ break;
+
+ case DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO:
+ {
+ const char *last_slash;
+ size_t len;
+ size_t dir_size;
+
+ last_slash = dso->long_name + dso->long_name_len;
+ while (last_slash != dso->long_name && *last_slash != '/')
+ last_slash--;
+
+ len = scnprintf(filename, size, "%s", symbol_conf.symfs);
+ dir_size = last_slash - dso->long_name + 2;
+ if (dir_size > (size - len)) {
+ ret = -1;
+ break;
+ }
+ len += scnprintf(filename + len, dir_size, "%s", dso->long_name);
+ len += scnprintf(filename + len , size - len, ".debug%s",
+ last_slash);
+ break;
+ }
+
+ case DSO_BINARY_TYPE__BUILDID_DEBUGINFO:
+ if (!dso->has_build_id) {
+ ret = -1;
+ break;
+ }
+
+ build_id__sprintf(dso->build_id,
+ sizeof(dso->build_id),
+ build_id_hex);
+ snprintf(filename, size,
+ "%s/usr/lib/debug/.build-id/%.2s/%s.debug",
+ symbol_conf.symfs, build_id_hex, build_id_hex + 2);
+ break;
+
+ case DSO_BINARY_TYPE__VMLINUX:
+ case DSO_BINARY_TYPE__GUEST_VMLINUX:
+ case DSO_BINARY_TYPE__SYSTEM_PATH_DSO:
+ snprintf(filename, size, "%s%s",
+ symbol_conf.symfs, dso->long_name);
+ break;
+
+ case DSO_BINARY_TYPE__GUEST_KMODULE:
+ snprintf(filename, size, "%s%s%s", symbol_conf.symfs,
+ root_dir, dso->long_name);
+ break;
+
+ case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE:
+ snprintf(filename, size, "%s%s", symbol_conf.symfs,
+ dso->long_name);
+ break;
+
+ case DSO_BINARY_TYPE__KCORE:
+ case DSO_BINARY_TYPE__GUEST_KCORE:
+ snprintf(filename, size, "%s", dso->long_name);
+ break;
+
+ default:
+ case DSO_BINARY_TYPE__KALLSYMS:
+ case DSO_BINARY_TYPE__GUEST_KALLSYMS:
+ case DSO_BINARY_TYPE__JAVA_JIT:
+ case DSO_BINARY_TYPE__NOT_FOUND:
+ ret = -1;
+ break;
+ }
+
+ return ret;
+}
+
+/*
+ * Global list of open DSOs and the counter.
+ */
+static LIST_HEAD(dso__data_open);
+static long dso__data_open_cnt;
+
+static void dso__list_add(struct dso *dso)
+{
+ list_add_tail(&dso->data.open_entry, &dso__data_open);
+ dso__data_open_cnt++;
+}
+
+static void dso__list_del(struct dso *dso)
+{
+ list_del(&dso->data.open_entry);
+ WARN_ONCE(dso__data_open_cnt <= 0,
+ "DSO data fd counter out of bounds.");
+ dso__data_open_cnt--;
+}
+
+static void close_first_dso(void);
+
+static int do_open(char *name)
+{
+ int fd;
+
+ do {
+ fd = open(name, O_RDONLY);
+ if (fd >= 0)
+ return fd;
+
+ pr_debug("dso open failed, mmap: %s\n", strerror(errno));
+ if (!dso__data_open_cnt || errno != EMFILE)
+ break;
+
+ close_first_dso();
+ } while (1);
+
+ return -1;
+}
+
+static int __open_dso(struct dso *dso, struct machine *machine)
+{
+ int fd;
+ char *root_dir = (char *)"";
+ char *name = malloc(PATH_MAX);
+
+ if (!name)
+ return -ENOMEM;
+
+ if (machine)
+ root_dir = machine->root_dir;
+
+ if (dso__read_binary_type_filename(dso, dso->binary_type,
+ root_dir, name, PATH_MAX)) {
+ free(name);
+ return -EINVAL;
+ }
+
+ fd = do_open(name);
+ free(name);
+ return fd;
+}
+
+static void check_data_close(void);
+
+/**
+ * dso_close - Open DSO data file
+ * @dso: dso object
+ *
+ * Open @dso's data file descriptor and updates
+ * list/count of open DSO objects.
+ */
+static int open_dso(struct dso *dso, struct machine *machine)
+{
+ int fd = __open_dso(dso, machine);
+
+ if (fd > 0) {
+ dso__list_add(dso);
+ /*
+ * Check if we crossed the allowed number
+ * of opened DSOs and close one if needed.
+ */
+ check_data_close();
+ }
+
+ return fd;
+}
+
+static void close_data_fd(struct dso *dso)
+{
+ if (dso->data.fd >= 0) {
+ close(dso->data.fd);
+ dso->data.fd = -1;
+ dso->data.file_size = 0;
+ dso__list_del(dso);
+ }
+}
+
+/**
+ * dso_close - Close DSO data file
+ * @dso: dso object
+ *
+ * Close @dso's data file descriptor and updates
+ * list/count of open DSO objects.
+ */
+static void close_dso(struct dso *dso)
+{
+ close_data_fd(dso);
+}
+
+static void close_first_dso(void)
+{
+ struct dso *dso;
+
+ dso = list_first_entry(&dso__data_open, struct dso, data.open_entry);
+ close_dso(dso);
+}
+
+static rlim_t get_fd_limit(void)
+{
+ struct rlimit l;
+ rlim_t limit = 0;
+
+ /* Allow half of the current open fd limit. */
+ if (getrlimit(RLIMIT_NOFILE, &l) == 0) {
+ if (l.rlim_cur == RLIM_INFINITY)
+ limit = l.rlim_cur;
+ else
+ limit = l.rlim_cur / 2;
+ } else {
+ pr_err("failed to get fd limit\n");
+ limit = 1;
+ }
+
+ return limit;
+}
+
+static bool may_cache_fd(void)
+{
+ static rlim_t limit;
+
+ if (!limit)
+ limit = get_fd_limit();
+
+ if (limit == RLIM_INFINITY)
+ return true;
+
+ return limit > (rlim_t) dso__data_open_cnt;
+}
+
+/*
+ * Check and close LRU dso if we crossed allowed limit
+ * for opened dso file descriptors. The limit is half
+ * of the RLIMIT_NOFILE files opened.
+*/
+static void check_data_close(void)
+{
+ bool cache_fd = may_cache_fd();
+
+ if (!cache_fd)
+ close_first_dso();
+}
+
+/**
+ * dso__data_close - Close DSO data file
+ * @dso: dso object
+ *
+ * External interface to close @dso's data file descriptor.
+ */
+void dso__data_close(struct dso *dso)
+{
+ close_dso(dso);
+}
+
+/**
+ * dso__data_fd - Get dso's data file descriptor
+ * @dso: dso object
+ * @machine: machine object
+ *
+ * External interface to find dso's file, open it and
+ * returns file descriptor.
+ */
+int dso__data_fd(struct dso *dso, struct machine *machine)
+{
+ enum dso_binary_type binary_type_data[] = {
+ DSO_BINARY_TYPE__BUILD_ID_CACHE,
+ DSO_BINARY_TYPE__SYSTEM_PATH_DSO,
+ DSO_BINARY_TYPE__NOT_FOUND,
+ };
+ int i = 0;
+
+ if (dso->data.fd >= 0)
+ return dso->data.fd;
+
+ if (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND) {
+ dso->data.fd = open_dso(dso, machine);
+ return dso->data.fd;
+ }
+
+ do {
+ int fd;
+
+ dso->binary_type = binary_type_data[i++];
+
+ fd = open_dso(dso, machine);
+ if (fd >= 0)
+ return dso->data.fd = fd;
+
+ } while (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND);
+
+ return -EINVAL;
+}
+
+static void
+dso_cache__free(struct rb_root *root)
+{
+ struct rb_node *next = rb_first(root);
+
+ while (next) {
+ struct dso_cache *cache;
+
+ cache = rb_entry(next, struct dso_cache, rb_node);
+ next = rb_next(&cache->rb_node);
+ rb_erase(&cache->rb_node, root);
+ free(cache);
+ }
+}
+
+static struct dso_cache *dso_cache__find(const struct rb_root *root, u64 offset)
+{
+ struct rb_node * const *p = &root->rb_node;
+ const struct rb_node *parent = NULL;
+ struct dso_cache *cache;
+
+ while (*p != NULL) {
+ u64 end;
+
+ parent = *p;
+ cache = rb_entry(parent, struct dso_cache, rb_node);
+ end = cache->offset + DSO__DATA_CACHE_SIZE;
+
+ if (offset < cache->offset)
+ p = &(*p)->rb_left;
+ else if (offset >= end)
+ p = &(*p)->rb_right;
+ else
+ return cache;
+ }
+ return NULL;
+}
+
+static void
+dso_cache__insert(struct rb_root *root, struct dso_cache *new)
+{
+ struct rb_node **p = &root->rb_node;
+ struct rb_node *parent = NULL;
+ struct dso_cache *cache;
+ u64 offset = new->offset;
+
+ while (*p != NULL) {
+ u64 end;
+
+ parent = *p;
+ cache = rb_entry(parent, struct dso_cache, rb_node);
+ end = cache->offset + DSO__DATA_CACHE_SIZE;
+
+ if (offset < cache->offset)
+ p = &(*p)->rb_left;
+ else if (offset >= end)
+ p = &(*p)->rb_right;
+ }
+
+ rb_link_node(&new->rb_node, parent, p);
+ rb_insert_color(&new->rb_node, root);
+}
+
+static ssize_t
+dso_cache__memcpy(struct dso_cache *cache, u64 offset,
+ u8 *data, u64 size)
+{
+ u64 cache_offset = offset - cache->offset;
+ u64 cache_size = min(cache->size - cache_offset, size);
+
+ memcpy(data, cache->data + cache_offset, cache_size);
+ return cache_size;
+}
+
+static ssize_t
+dso_cache__read(struct dso *dso, u64 offset, u8 *data, ssize_t size)
+{
+ struct dso_cache *cache;
+ ssize_t ret;
+
+ do {
+ u64 cache_offset;
+
+ ret = -ENOMEM;
+
+ cache = zalloc(sizeof(*cache) + DSO__DATA_CACHE_SIZE);
+ if (!cache)
+ break;
+
+ cache_offset = offset & DSO__DATA_CACHE_MASK;
+ ret = -EINVAL;
+
+ if (-1 == lseek(dso->data.fd, cache_offset, SEEK_SET))
+ break;
+
+ ret = read(dso->data.fd, cache->data, DSO__DATA_CACHE_SIZE);
+ if (ret <= 0)
+ break;
+
+ cache->offset = cache_offset;
+ cache->size = ret;
+ dso_cache__insert(&dso->data.cache, cache);
+
+ ret = dso_cache__memcpy(cache, offset, data, size);
+
+ } while (0);
+
+ if (ret <= 0)
+ free(cache);
+
+ return ret;
+}
+
+static ssize_t dso_cache_read(struct dso *dso, u64 offset,
+ u8 *data, ssize_t size)
+{
+ struct dso_cache *cache;
+
+ cache = dso_cache__find(&dso->data.cache, offset);
+ if (cache)
+ return dso_cache__memcpy(cache, offset, data, size);
+ else
+ return dso_cache__read(dso, offset, data, size);
+}
+
+/*
+ * Reads and caches dso data DSO__DATA_CACHE_SIZE size chunks
+ * in the rb_tree. Any read to already cached data is served
+ * by cached data.
+ */
+static ssize_t cached_read(struct dso *dso, u64 offset, u8 *data, ssize_t size)
+{
+ ssize_t r = 0;
+ u8 *p = data;
+
+ do {
+ ssize_t ret;
+
+ ret = dso_cache_read(dso, offset, p, size);
+ if (ret < 0)
+ return ret;
+
+ /* Reached EOF, return what we have. */
+ if (!ret)
+ break;
+
+ BUG_ON(ret > size);
+
+ r += ret;
+ p += ret;
+ offset += ret;
+ size -= ret;
+
+ } while (size);
+
+ return r;
+}
+
+static int data_file_size(struct dso *dso)
+{
+ struct stat st;
+
+ if (!dso->data.file_size) {
+ if (fstat(dso->data.fd, &st)) {
+ pr_err("dso mmap failed, fstat: %s\n", strerror(errno));
+ return -1;
+ }
+ dso->data.file_size = st.st_size;
+ }
+
+ return 0;
+}
+
+static ssize_t data_read_offset(struct dso *dso, u64 offset,
+ u8 *data, ssize_t size)
+{
+ if (data_file_size(dso))
+ return -1;
+
+ /* Check the offset sanity. */
+ if (offset > dso->data.file_size)
+ return -1;
+
+ if (offset + size < offset)
+ return -1;
+
+ return cached_read(dso, offset, data, size);
+}
+
+/**
+ * dso__data_read_offset - Read data from dso file offset
+ * @dso: dso object
+ * @machine: machine object
+ * @offset: file offset
+ * @data: buffer to store data
+ * @size: size of the @data buffer
+ *
+ * External interface to read data from dso file offset. Open
+ * dso data file and use cached_read to get the data.
+ */
+ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine,
+ u64 offset, u8 *data, ssize_t size)
+{
+ if (dso__data_fd(dso, machine) < 0)
+ return -1;
+
+ return data_read_offset(dso, offset, data, size);
+}
+
+/**
+ * dso__data_read_addr - Read data from dso address
+ * @dso: dso object
+ * @machine: machine object
+ * @add: virtual memory address
+ * @data: buffer to store data
+ * @size: size of the @data buffer
+ *
+ * External interface to read data from dso address.
+ */
+ssize_t dso__data_read_addr(struct dso *dso, struct map *map,
+ struct machine *machine, u64 addr,
+ u8 *data, ssize_t size)
+{
+ u64 offset = map->map_ip(map, addr);
+ return dso__data_read_offset(dso, machine, offset, data, size);
+}
+
+struct map *dso__new_map(const char *name)
+{
+ struct map *map = NULL;
+ struct dso *dso = dso__new(name);
+
+ if (dso)
+ map = map__new2(0, dso, MAP__FUNCTION);
+
+ return map;
+}
+
+struct dso *dso__kernel_findnew(struct machine *machine, const char *name,
+ const char *short_name, int dso_type)
+{
+ /*
+ * The kernel dso could be created by build_id processing.
+ */
+ struct dso *dso = __dsos__findnew(&machine->kernel_dsos, name);
+
+ /*
+ * We need to run this in all cases, since during the build_id
+ * processing we had no idea this was the kernel dso.
+ */
+ if (dso != NULL) {
+ dso__set_short_name(dso, short_name, false);
+ dso->kernel = dso_type;
+ }
+
+ return dso;
+}
+
+void dso__set_long_name(struct dso *dso, const char *name, bool name_allocated)
+{
+ if (name == NULL)
+ return;
+
+ if (dso->long_name_allocated)
+ free((char *)dso->long_name);
+
+ dso->long_name = name;
+ dso->long_name_len = strlen(name);
+ dso->long_name_allocated = name_allocated;
+}
+
+void dso__set_short_name(struct dso *dso, const char *name, bool name_allocated)
+{
+ if (name == NULL)
+ return;
+
+ if (dso->short_name_allocated)
+ free((char *)dso->short_name);
+
+ dso->short_name = name;
+ dso->short_name_len = strlen(name);
+ dso->short_name_allocated = name_allocated;
+}
+
+static void dso__set_basename(struct dso *dso)
+{
+ /*
+ * basename() may modify path buffer, so we must pass
+ * a copy.
+ */
+ char *base, *lname = strdup(dso->long_name);
+
+ if (!lname)
+ return;
+
+ /*
+ * basename() may return a pointer to internal
+ * storage which is reused in subsequent calls
+ * so copy the result.
+ */
+ base = strdup(basename(lname));
+
+ free(lname);
+
+ if (!base)
+ return;
+
+ dso__set_short_name(dso, base, true);
+}
+
+int dso__name_len(const struct dso *dso)
+{
+ if (!dso)
+ return strlen("[unknown]");
+ if (verbose)
+ return dso->long_name_len;
+
+ return dso->short_name_len;
+}
+
+bool dso__loaded(const struct dso *dso, enum map_type type)
+{
+ return dso->loaded & (1 << type);
+}
+
+bool dso__sorted_by_name(const struct dso *dso, enum map_type type)
+{
+ return dso->sorted_by_name & (1 << type);
+}
+
+void dso__set_sorted_by_name(struct dso *dso, enum map_type type)
+{
+ dso->sorted_by_name |= (1 << type);
+}
+
+struct dso *dso__new(const char *name)
+{
+ struct dso *dso = calloc(1, sizeof(*dso) + strlen(name) + 1);
+
+ if (dso != NULL) {
+ int i;
+ strcpy(dso->name, name);
+ dso__set_long_name(dso, dso->name, false);
+ dso__set_short_name(dso, dso->name, false);
+ for (i = 0; i < MAP__NR_TYPES; ++i)
+ dso->symbols[i] = dso->symbol_names[i] = RB_ROOT;
+ dso->data.cache = RB_ROOT;
+ dso->data.fd = -1;
+ dso->symtab_type = DSO_BINARY_TYPE__NOT_FOUND;
+ dso->binary_type = DSO_BINARY_TYPE__NOT_FOUND;
+ dso->loaded = 0;
+ dso->rel = 0;
+ dso->sorted_by_name = 0;
+ dso->has_build_id = 0;
+ dso->has_srcline = 1;
+ dso->a2l_fails = 1;
+ dso->kernel = DSO_TYPE_USER;
+ dso->needs_swap = DSO_SWAP__UNSET;
+ INIT_LIST_HEAD(&dso->node);
+ INIT_LIST_HEAD(&dso->data.open_entry);
+ }
+
+ return dso;
+}
+
+void dso__delete(struct dso *dso)
+{
+ int i;
+ for (i = 0; i < MAP__NR_TYPES; ++i)
+ symbols__delete(&dso->symbols[i]);
+
+ if (dso->short_name_allocated) {
+ zfree((char **)&dso->short_name);
+ dso->short_name_allocated = false;
+ }
+
+ if (dso->long_name_allocated) {
+ zfree((char **)&dso->long_name);
+ dso->long_name_allocated = false;
+ }
+
+ dso__data_close(dso);
+ dso_cache__free(&dso->data.cache);
+ dso__free_a2l(dso);
+ zfree(&dso->symsrc_filename);
+ free(dso);
+}
+
+void dso__set_build_id(struct dso *dso, void *build_id)
+{
+ memcpy(dso->build_id, build_id, sizeof(dso->build_id));
+ dso->has_build_id = 1;
+}
+
+bool dso__build_id_equal(const struct dso *dso, u8 *build_id)
+{
+ return memcmp(dso->build_id, build_id, sizeof(dso->build_id)) == 0;
+}
+
+void dso__read_running_kernel_build_id(struct dso *dso, struct machine *machine)
+{
+ char path[PATH_MAX];
+
+ if (machine__is_default_guest(machine))
+ return;
+ sprintf(path, "%s/sys/kernel/notes", machine->root_dir);
+ if (sysfs__read_build_id(path, dso->build_id,
+ sizeof(dso->build_id)) == 0)
+ dso->has_build_id = true;
+}
+
+int dso__kernel_module_get_build_id(struct dso *dso,
+ const char *root_dir)
+{
+ char filename[PATH_MAX];
+ /*
+ * kernel module short names are of the form "[module]" and
+ * we need just "module" here.
+ */
+ const char *name = dso->short_name + 1;
+
+ snprintf(filename, sizeof(filename),
+ "%s/sys/module/%.*s/notes/.note.gnu.build-id",
+ root_dir, (int)strlen(name) - 1, name);
+
+ if (sysfs__read_build_id(filename, dso->build_id,
+ sizeof(dso->build_id)) == 0)
+ dso->has_build_id = true;
+
+ return 0;
+}
+
+bool __dsos__read_build_ids(struct list_head *head, bool with_hits)
+{
+ bool have_build_id = false;
+ struct dso *pos;
+
+ list_for_each_entry(pos, head, node) {
+ if (with_hits && !pos->hit)
+ continue;
+ if (pos->has_build_id) {
+ have_build_id = true;
+ continue;
+ }
+ if (filename__read_build_id(pos->long_name, pos->build_id,
+ sizeof(pos->build_id)) > 0) {
+ have_build_id = true;
+ pos->has_build_id = true;
+ }
+ }
+
+ return have_build_id;
+}
+
+void dsos__add(struct list_head *head, struct dso *dso)
+{
+ list_add_tail(&dso->node, head);
+}
+
+struct dso *dsos__find(const struct list_head *head, const char *name, bool cmp_short)
+{
+ struct dso *pos;
+
+ if (cmp_short) {
+ list_for_each_entry(pos, head, node)
+ if (strcmp(pos->short_name, name) == 0)
+ return pos;
+ return NULL;
+ }
+ list_for_each_entry(pos, head, node)
+ if (strcmp(pos->long_name, name) == 0)
+ return pos;
+ return NULL;
+}
+
+struct dso *__dsos__findnew(struct list_head *head, const char *name)
+{
+ struct dso *dso = dsos__find(head, name, false);
+
+ if (!dso) {
+ dso = dso__new(name);
+ if (dso != NULL) {
+ dsos__add(head, dso);
+ dso__set_basename(dso);
+ }
+ }
+
+ return dso;
+}
+
+size_t __dsos__fprintf_buildid(struct list_head *head, FILE *fp,
+ bool (skip)(struct dso *dso, int parm), int parm)
+{
+ struct dso *pos;
+ size_t ret = 0;
+
+ list_for_each_entry(pos, head, node) {
+ if (skip && skip(pos, parm))
+ continue;
+ ret += dso__fprintf_buildid(pos, fp);
+ ret += fprintf(fp, " %s\n", pos->long_name);
+ }
+ return ret;
+}
+
+size_t __dsos__fprintf(struct list_head *head, FILE *fp)
+{
+ struct dso *pos;
+ size_t ret = 0;
+
+ list_for_each_entry(pos, head, node) {
+ int i;
+ for (i = 0; i < MAP__NR_TYPES; ++i)
+ ret += dso__fprintf(pos, i, fp);
+ }
+
+ return ret;
+}
+
+size_t dso__fprintf_buildid(struct dso *dso, FILE *fp)
+{
+ char sbuild_id[BUILD_ID_SIZE * 2 + 1];
+
+ build_id__sprintf(dso->build_id, sizeof(dso->build_id), sbuild_id);
+ return fprintf(fp, "%s", sbuild_id);
+}
+
+size_t dso__fprintf(struct dso *dso, enum map_type type, FILE *fp)
+{
+ struct rb_node *nd;
+ size_t ret = fprintf(fp, "dso: %s (", dso->short_name);
+
+ if (dso->short_name != dso->long_name)
+ ret += fprintf(fp, "%s, ", dso->long_name);
+ ret += fprintf(fp, "%s, %sloaded, ", map_type__name[type],
+ dso__loaded(dso, type) ? "" : "NOT ");
+ ret += dso__fprintf_buildid(dso, fp);
+ ret += fprintf(fp, ")\n");
+ for (nd = rb_first(&dso->symbols[type]); nd; nd = rb_next(nd)) {
+ struct symbol *pos = rb_entry(nd, struct symbol, rb_node);
+ ret += symbol__fprintf(pos, fp);
+ }
+
+ return ret;
+}
diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h
new file mode 100644
index 00000000000..ad553ba257b
--- /dev/null
+++ b/tools/perf/util/dso.h
@@ -0,0 +1,232 @@
+#ifndef __PERF_DSO
+#define __PERF_DSO
+
+#include <linux/types.h>
+#include <linux/rbtree.h>
+#include <stdbool.h>
+#include <linux/types.h>
+#include "map.h"
+#include "build-id.h"
+
+enum dso_binary_type {
+ DSO_BINARY_TYPE__KALLSYMS = 0,
+ DSO_BINARY_TYPE__GUEST_KALLSYMS,
+ DSO_BINARY_TYPE__VMLINUX,
+ DSO_BINARY_TYPE__GUEST_VMLINUX,
+ DSO_BINARY_TYPE__JAVA_JIT,
+ DSO_BINARY_TYPE__DEBUGLINK,
+ DSO_BINARY_TYPE__BUILD_ID_CACHE,
+ DSO_BINARY_TYPE__FEDORA_DEBUGINFO,
+ DSO_BINARY_TYPE__UBUNTU_DEBUGINFO,
+ DSO_BINARY_TYPE__BUILDID_DEBUGINFO,
+ DSO_BINARY_TYPE__SYSTEM_PATH_DSO,
+ DSO_BINARY_TYPE__GUEST_KMODULE,
+ DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE,
+ DSO_BINARY_TYPE__KCORE,
+ DSO_BINARY_TYPE__GUEST_KCORE,
+ DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO,
+ DSO_BINARY_TYPE__NOT_FOUND,
+};
+
+enum dso_kernel_type {
+ DSO_TYPE_USER = 0,
+ DSO_TYPE_KERNEL,
+ DSO_TYPE_GUEST_KERNEL
+};
+
+enum dso_swap_type {
+ DSO_SWAP__UNSET,
+ DSO_SWAP__NO,
+ DSO_SWAP__YES,
+};
+
+#define DSO__SWAP(dso, type, val) \
+({ \
+ type ____r = val; \
+ BUG_ON(dso->needs_swap == DSO_SWAP__UNSET); \
+ if (dso->needs_swap == DSO_SWAP__YES) { \
+ switch (sizeof(____r)) { \
+ case 2: \
+ ____r = bswap_16(val); \
+ break; \
+ case 4: \
+ ____r = bswap_32(val); \
+ break; \
+ case 8: \
+ ____r = bswap_64(val); \
+ break; \
+ default: \
+ BUG_ON(1); \
+ } \
+ } \
+ ____r; \
+})
+
+#define DSO__DATA_CACHE_SIZE 4096
+#define DSO__DATA_CACHE_MASK ~(DSO__DATA_CACHE_SIZE - 1)
+
+struct dso_cache {
+ struct rb_node rb_node;
+ u64 offset;
+ u64 size;
+ char data[0];
+};
+
+struct dso {
+ struct list_head node;
+ struct rb_root symbols[MAP__NR_TYPES];
+ struct rb_root symbol_names[MAP__NR_TYPES];
+ void *a2l;
+ char *symsrc_filename;
+ unsigned int a2l_fails;
+ enum dso_kernel_type kernel;
+ enum dso_swap_type needs_swap;
+ enum dso_binary_type symtab_type;
+ enum dso_binary_type binary_type;
+ u8 adjust_symbols:1;
+ u8 has_build_id:1;
+ u8 has_srcline:1;
+ u8 hit:1;
+ u8 annotate_warned:1;
+ u8 short_name_allocated:1;
+ u8 long_name_allocated:1;
+ u8 sorted_by_name;
+ u8 loaded;
+ u8 rel;
+ u8 build_id[BUILD_ID_SIZE];
+ const char *short_name;
+ const char *long_name;
+ u16 long_name_len;
+ u16 short_name_len;
+
+ /* dso data file */
+ struct {
+ struct rb_root cache;
+ int fd;
+ size_t file_size;
+ struct list_head open_entry;
+ } data;
+
+ char name[0];
+};
+
+/* dso__for_each_symbol - iterate over the symbols of given type
+ *
+ * @dso: the 'struct dso *' in which symbols itereated
+ * @pos: the 'struct symbol *' to use as a loop cursor
+ * @n: the 'struct rb_node *' to use as a temporary storage
+ * @type: the 'enum map_type' type of symbols
+ */
+#define dso__for_each_symbol(dso, pos, n, type) \
+ symbols__for_each_entry(&(dso)->symbols[(type)], pos, n)
+
+static inline void dso__set_loaded(struct dso *dso, enum map_type type)
+{
+ dso->loaded |= (1 << type);
+}
+
+struct dso *dso__new(const char *name);
+void dso__delete(struct dso *dso);
+
+void dso__set_short_name(struct dso *dso, const char *name, bool name_allocated);
+void dso__set_long_name(struct dso *dso, const char *name, bool name_allocated);
+
+int dso__name_len(const struct dso *dso);
+
+bool dso__loaded(const struct dso *dso, enum map_type type);
+
+bool dso__sorted_by_name(const struct dso *dso, enum map_type type);
+void dso__set_sorted_by_name(struct dso *dso, enum map_type type);
+void dso__sort_by_name(struct dso *dso, enum map_type type);
+
+void dso__set_build_id(struct dso *dso, void *build_id);
+bool dso__build_id_equal(const struct dso *dso, u8 *build_id);
+void dso__read_running_kernel_build_id(struct dso *dso,
+ struct machine *machine);
+int dso__kernel_module_get_build_id(struct dso *dso, const char *root_dir);
+
+char dso__symtab_origin(const struct dso *dso);
+int dso__read_binary_type_filename(const struct dso *dso, enum dso_binary_type type,
+ char *root_dir, char *filename, size_t size);
+
+/*
+ * The dso__data_* external interface provides following functions:
+ * dso__data_fd
+ * dso__data_close
+ * dso__data_read_offset
+ * dso__data_read_addr
+ *
+ * Please refer to the dso.c object code for each function and
+ * arguments documentation. Following text tries to explain the
+ * dso file descriptor caching.
+ *
+ * The dso__data* interface allows caching of opened file descriptors
+ * to speed up the dso data accesses. The idea is to leave the file
+ * descriptor opened ideally for the whole life of the dso object.
+ *
+ * The current usage of the dso__data_* interface is as follows:
+ *
+ * Get DSO's fd:
+ * int fd = dso__data_fd(dso, machine);
+ * USE 'fd' SOMEHOW
+ *
+ * Read DSO's data:
+ * n = dso__data_read_offset(dso_0, &machine, 0, buf, BUFSIZE);
+ * n = dso__data_read_addr(dso_0, &machine, 0, buf, BUFSIZE);
+ *
+ * Eventually close DSO's fd:
+ * dso__data_close(dso);
+ *
+ * It is not necessary to close the DSO object data file. Each time new
+ * DSO data file is opened, the limit (RLIMIT_NOFILE/2) is checked. Once
+ * it is crossed, the oldest opened DSO object is closed.
+ *
+ * The dso__delete function calls close_dso function to ensure the
+ * data file descriptor gets closed/unmapped before the dso object
+ * is freed.
+ *
+ * TODO
+*/
+int dso__data_fd(struct dso *dso, struct machine *machine);
+void dso__data_close(struct dso *dso);
+
+ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine,
+ u64 offset, u8 *data, ssize_t size);
+ssize_t dso__data_read_addr(struct dso *dso, struct map *map,
+ struct machine *machine, u64 addr,
+ u8 *data, ssize_t size);
+
+struct map *dso__new_map(const char *name);
+struct dso *dso__kernel_findnew(struct machine *machine, const char *name,
+ const char *short_name, int dso_type);
+
+void dsos__add(struct list_head *head, struct dso *dso);
+struct dso *dsos__find(const struct list_head *head, const char *name,
+ bool cmp_short);
+struct dso *__dsos__findnew(struct list_head *head, const char *name);
+bool __dsos__read_build_ids(struct list_head *head, bool with_hits);
+
+size_t __dsos__fprintf_buildid(struct list_head *head, FILE *fp,
+ bool (skip)(struct dso *dso, int parm), int parm);
+size_t __dsos__fprintf(struct list_head *head, FILE *fp);
+
+size_t dso__fprintf_buildid(struct dso *dso, FILE *fp);
+size_t dso__fprintf_symbols_by_name(struct dso *dso,
+ enum map_type type, FILE *fp);
+size_t dso__fprintf(struct dso *dso, enum map_type type, FILE *fp);
+
+static inline bool dso__is_vmlinux(struct dso *dso)
+{
+ return dso->binary_type == DSO_BINARY_TYPE__VMLINUX ||
+ dso->binary_type == DSO_BINARY_TYPE__GUEST_VMLINUX;
+}
+
+static inline bool dso__is_kcore(struct dso *dso)
+{
+ return dso->binary_type == DSO_BINARY_TYPE__KCORE ||
+ dso->binary_type == DSO_BINARY_TYPE__GUEST_KCORE;
+}
+
+void dso__free_a2l(struct dso *dso);
+
+#endif /* __PERF_DSO */
diff --git a/tools/perf/util/dwarf-aux.c b/tools/perf/util/dwarf-aux.c
new file mode 100644
index 00000000000..cc66c4049e0
--- /dev/null
+++ b/tools/perf/util/dwarf-aux.c
@@ -0,0 +1,884 @@
+/*
+ * dwarf-aux.c : libdw auxiliary interfaces
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <stdbool.h>
+#include "util.h"
+#include "debug.h"
+#include "dwarf-aux.h"
+
+/**
+ * cu_find_realpath - Find the realpath of the target file
+ * @cu_die: A DIE(dwarf information entry) of CU(compilation Unit)
+ * @fname: The tail filename of the target file
+ *
+ * Find the real(long) path of @fname in @cu_die.
+ */
+const char *cu_find_realpath(Dwarf_Die *cu_die, const char *fname)
+{
+ Dwarf_Files *files;
+ size_t nfiles, i;
+ const char *src = NULL;
+ int ret;
+
+ if (!fname)
+ return NULL;
+
+ ret = dwarf_getsrcfiles(cu_die, &files, &nfiles);
+ if (ret != 0)
+ return NULL;
+
+ for (i = 0; i < nfiles; i++) {
+ src = dwarf_filesrc(files, i, NULL, NULL);
+ if (strtailcmp(src, fname) == 0)
+ break;
+ }
+ if (i == nfiles)
+ return NULL;
+ return src;
+}
+
+/**
+ * cu_get_comp_dir - Get the path of compilation directory
+ * @cu_die: a CU DIE
+ *
+ * Get the path of compilation directory of given @cu_die.
+ * Since this depends on DW_AT_comp_dir, older gcc will not
+ * embedded it. In that case, this returns NULL.
+ */
+const char *cu_get_comp_dir(Dwarf_Die *cu_die)
+{
+ Dwarf_Attribute attr;
+ if (dwarf_attr(cu_die, DW_AT_comp_dir, &attr) == NULL)
+ return NULL;
+ return dwarf_formstring(&attr);
+}
+
+/**
+ * cu_find_lineinfo - Get a line number and file name for given address
+ * @cu_die: a CU DIE
+ * @addr: An address
+ * @fname: a pointer which returns the file name string
+ * @lineno: a pointer which returns the line number
+ *
+ * Find a line number and file name for @addr in @cu_die.
+ */
+int cu_find_lineinfo(Dwarf_Die *cu_die, unsigned long addr,
+ const char **fname, int *lineno)
+{
+ Dwarf_Line *line;
+ Dwarf_Addr laddr;
+
+ line = dwarf_getsrc_die(cu_die, (Dwarf_Addr)addr);
+ if (line && dwarf_lineaddr(line, &laddr) == 0 &&
+ addr == (unsigned long)laddr && dwarf_lineno(line, lineno) == 0) {
+ *fname = dwarf_linesrc(line, NULL, NULL);
+ if (!*fname)
+ /* line number is useless without filename */
+ *lineno = 0;
+ }
+
+ return *lineno ?: -ENOENT;
+}
+
+static int __die_find_inline_cb(Dwarf_Die *die_mem, void *data);
+
+/**
+ * cu_walk_functions_at - Walk on function DIEs at given address
+ * @cu_die: A CU DIE
+ * @addr: An address
+ * @callback: A callback which called with found DIEs
+ * @data: A user data
+ *
+ * Walk on function DIEs at given @addr in @cu_die. Passed DIEs
+ * should be subprogram or inlined-subroutines.
+ */
+int cu_walk_functions_at(Dwarf_Die *cu_die, Dwarf_Addr addr,
+ int (*callback)(Dwarf_Die *, void *), void *data)
+{
+ Dwarf_Die die_mem;
+ Dwarf_Die *sc_die;
+ int ret = -ENOENT;
+
+ /* Inlined function could be recursive. Trace it until fail */
+ for (sc_die = die_find_realfunc(cu_die, addr, &die_mem);
+ sc_die != NULL;
+ sc_die = die_find_child(sc_die, __die_find_inline_cb, &addr,
+ &die_mem)) {
+ ret = callback(sc_die, data);
+ if (ret)
+ break;
+ }
+
+ return ret;
+
+}
+
+/**
+ * die_compare_name - Compare diename and tname
+ * @dw_die: a DIE
+ * @tname: a string of target name
+ *
+ * Compare the name of @dw_die and @tname. Return false if @dw_die has no name.
+ */
+bool die_compare_name(Dwarf_Die *dw_die, const char *tname)
+{
+ const char *name;
+ name = dwarf_diename(dw_die);
+ return name ? (strcmp(tname, name) == 0) : false;
+}
+
+/**
+ * die_get_call_lineno - Get callsite line number of inline-function instance
+ * @in_die: a DIE of an inlined function instance
+ *
+ * Get call-site line number of @in_die. This means from where the inline
+ * function is called.
+ */
+int die_get_call_lineno(Dwarf_Die *in_die)
+{
+ Dwarf_Attribute attr;
+ Dwarf_Word ret;
+
+ if (!dwarf_attr(in_die, DW_AT_call_line, &attr))
+ return -ENOENT;
+
+ dwarf_formudata(&attr, &ret);
+ return (int)ret;
+}
+
+/**
+ * die_get_type - Get type DIE
+ * @vr_die: a DIE of a variable
+ * @die_mem: where to store a type DIE
+ *
+ * Get a DIE of the type of given variable (@vr_die), and store
+ * it to die_mem. Return NULL if fails to get a type DIE.
+ */
+Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
+{
+ Dwarf_Attribute attr;
+
+ if (dwarf_attr_integrate(vr_die, DW_AT_type, &attr) &&
+ dwarf_formref_die(&attr, die_mem))
+ return die_mem;
+ else
+ return NULL;
+}
+
+/* Get a type die, but skip qualifiers */
+static Dwarf_Die *__die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
+{
+ int tag;
+
+ do {
+ vr_die = die_get_type(vr_die, die_mem);
+ if (!vr_die)
+ break;
+ tag = dwarf_tag(vr_die);
+ } while (tag == DW_TAG_const_type ||
+ tag == DW_TAG_restrict_type ||
+ tag == DW_TAG_volatile_type ||
+ tag == DW_TAG_shared_type);
+
+ return vr_die;
+}
+
+/**
+ * die_get_real_type - Get a type die, but skip qualifiers and typedef
+ * @vr_die: a DIE of a variable
+ * @die_mem: where to store a type DIE
+ *
+ * Get a DIE of the type of given variable (@vr_die), and store
+ * it to die_mem. Return NULL if fails to get a type DIE.
+ * If the type is qualifiers (e.g. const) or typedef, this skips it
+ * and tries to find real type (structure or basic types, e.g. int).
+ */
+Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem)
+{
+ do {
+ vr_die = __die_get_real_type(vr_die, die_mem);
+ } while (vr_die && dwarf_tag(vr_die) == DW_TAG_typedef);
+
+ return vr_die;
+}
+
+/* Get attribute and translate it as a udata */
+static int die_get_attr_udata(Dwarf_Die *tp_die, unsigned int attr_name,
+ Dwarf_Word *result)
+{
+ Dwarf_Attribute attr;
+
+ if (dwarf_attr(tp_die, attr_name, &attr) == NULL ||
+ dwarf_formudata(&attr, result) != 0)
+ return -ENOENT;
+
+ return 0;
+}
+
+/* Get attribute and translate it as a sdata */
+static int die_get_attr_sdata(Dwarf_Die *tp_die, unsigned int attr_name,
+ Dwarf_Sword *result)
+{
+ Dwarf_Attribute attr;
+
+ if (dwarf_attr(tp_die, attr_name, &attr) == NULL ||
+ dwarf_formsdata(&attr, result) != 0)
+ return -ENOENT;
+
+ return 0;
+}
+
+/**
+ * die_is_signed_type - Check whether a type DIE is signed or not
+ * @tp_die: a DIE of a type
+ *
+ * Get the encoding of @tp_die and return true if the encoding
+ * is signed.
+ */
+bool die_is_signed_type(Dwarf_Die *tp_die)
+{
+ Dwarf_Word ret;
+
+ if (die_get_attr_udata(tp_die, DW_AT_encoding, &ret))
+ return false;
+
+ return (ret == DW_ATE_signed_char || ret == DW_ATE_signed ||
+ ret == DW_ATE_signed_fixed);
+}
+
+/**
+ * die_is_func_def - Ensure that this DIE is a subprogram and definition
+ * @dw_die: a DIE
+ *
+ * Ensure that this DIE is a subprogram and NOT a declaration. This
+ * returns true if @dw_die is a function definition.
+ **/
+bool die_is_func_def(Dwarf_Die *dw_die)
+{
+ Dwarf_Attribute attr;
+
+ return (dwarf_tag(dw_die) == DW_TAG_subprogram &&
+ dwarf_attr(dw_die, DW_AT_declaration, &attr) == NULL);
+}
+
+/**
+ * die_get_data_member_location - Get the data-member offset
+ * @mb_die: a DIE of a member of a data structure
+ * @offs: The offset of the member in the data structure
+ *
+ * Get the offset of @mb_die in the data structure including @mb_die, and
+ * stores result offset to @offs. If any error occurs this returns errno.
+ */
+int die_get_data_member_location(Dwarf_Die *mb_die, Dwarf_Word *offs)
+{
+ Dwarf_Attribute attr;
+ Dwarf_Op *expr;
+ size_t nexpr;
+ int ret;
+
+ if (dwarf_attr(mb_die, DW_AT_data_member_location, &attr) == NULL)
+ return -ENOENT;
+
+ if (dwarf_formudata(&attr, offs) != 0) {
+ /* DW_AT_data_member_location should be DW_OP_plus_uconst */
+ ret = dwarf_getlocation(&attr, &expr, &nexpr);
+ if (ret < 0 || nexpr == 0)
+ return -ENOENT;
+
+ if (expr[0].atom != DW_OP_plus_uconst || nexpr != 1) {
+ pr_debug("Unable to get offset:Unexpected OP %x (%zd)\n",
+ expr[0].atom, nexpr);
+ return -ENOTSUP;
+ }
+ *offs = (Dwarf_Word)expr[0].number;
+ }
+ return 0;
+}
+
+/* Get the call file index number in CU DIE */
+static int die_get_call_fileno(Dwarf_Die *in_die)
+{
+ Dwarf_Sword idx;
+
+ if (die_get_attr_sdata(in_die, DW_AT_call_file, &idx) == 0)
+ return (int)idx;
+ else
+ return -ENOENT;
+}
+
+/* Get the declared file index number in CU DIE */
+static int die_get_decl_fileno(Dwarf_Die *pdie)
+{
+ Dwarf_Sword idx;
+
+ if (die_get_attr_sdata(pdie, DW_AT_decl_file, &idx) == 0)
+ return (int)idx;
+ else
+ return -ENOENT;
+}
+
+/**
+ * die_get_call_file - Get callsite file name of inlined function instance
+ * @in_die: a DIE of an inlined function instance
+ *
+ * Get call-site file name of @in_die. This means from which file the inline
+ * function is called.
+ */
+const char *die_get_call_file(Dwarf_Die *in_die)
+{
+ Dwarf_Die cu_die;
+ Dwarf_Files *files;
+ int idx;
+
+ idx = die_get_call_fileno(in_die);
+ if (idx < 0 || !dwarf_diecu(in_die, &cu_die, NULL, NULL) ||
+ dwarf_getsrcfiles(&cu_die, &files, NULL) != 0)
+ return NULL;
+
+ return dwarf_filesrc(files, idx, NULL, NULL);
+}
+
+
+/**
+ * die_find_child - Generic DIE search function in DIE tree
+ * @rt_die: a root DIE
+ * @callback: a callback function
+ * @data: a user data passed to the callback function
+ * @die_mem: a buffer for result DIE
+ *
+ * Trace DIE tree from @rt_die and call @callback for each child DIE.
+ * If @callback returns DIE_FIND_CB_END, this stores the DIE into
+ * @die_mem and returns it. If @callback returns DIE_FIND_CB_CONTINUE,
+ * this continues to trace the tree. Optionally, @callback can return
+ * DIE_FIND_CB_CHILD and DIE_FIND_CB_SIBLING, those means trace only
+ * the children and trace only the siblings respectively.
+ * Returns NULL if @callback can't find any appropriate DIE.
+ */
+Dwarf_Die *die_find_child(Dwarf_Die *rt_die,
+ int (*callback)(Dwarf_Die *, void *),
+ void *data, Dwarf_Die *die_mem)
+{
+ Dwarf_Die child_die;
+ int ret;
+
+ ret = dwarf_child(rt_die, die_mem);
+ if (ret != 0)
+ return NULL;
+
+ do {
+ ret = callback(die_mem, data);
+ if (ret == DIE_FIND_CB_END)
+ return die_mem;
+
+ if ((ret & DIE_FIND_CB_CHILD) &&
+ die_find_child(die_mem, callback, data, &child_die)) {
+ memcpy(die_mem, &child_die, sizeof(Dwarf_Die));
+ return die_mem;
+ }
+ } while ((ret & DIE_FIND_CB_SIBLING) &&
+ dwarf_siblingof(die_mem, die_mem) == 0);
+
+ return NULL;
+}
+
+struct __addr_die_search_param {
+ Dwarf_Addr addr;
+ Dwarf_Die *die_mem;
+};
+
+/* die_find callback for non-inlined function search */
+static int __die_search_func_cb(Dwarf_Die *fn_die, void *data)
+{
+ struct __addr_die_search_param *ad = data;
+
+ /*
+ * Since a declaration entry doesn't has given pc, this always returns
+ * function definition entry.
+ */
+ if (dwarf_tag(fn_die) == DW_TAG_subprogram &&
+ dwarf_haspc(fn_die, ad->addr)) {
+ memcpy(ad->die_mem, fn_die, sizeof(Dwarf_Die));
+ return DWARF_CB_ABORT;
+ }
+ return DWARF_CB_OK;
+}
+
+/**
+ * die_find_realfunc - Search a non-inlined function at given address
+ * @cu_die: a CU DIE which including @addr
+ * @addr: target address
+ * @die_mem: a buffer for result DIE
+ *
+ * Search a non-inlined function DIE which includes @addr. Stores the
+ * DIE to @die_mem and returns it if found. Returns NULL if failed.
+ */
+Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
+ Dwarf_Die *die_mem)
+{
+ struct __addr_die_search_param ad;
+ ad.addr = addr;
+ ad.die_mem = die_mem;
+ /* dwarf_getscopes can't find subprogram. */
+ if (!dwarf_getfuncs(cu_die, __die_search_func_cb, &ad, 0))
+ return NULL;
+ else
+ return die_mem;
+}
+
+/* die_find callback for inline function search */
+static int __die_find_inline_cb(Dwarf_Die *die_mem, void *data)
+{
+ Dwarf_Addr *addr = data;
+
+ if (dwarf_tag(die_mem) == DW_TAG_inlined_subroutine &&
+ dwarf_haspc(die_mem, *addr))
+ return DIE_FIND_CB_END;
+
+ return DIE_FIND_CB_CONTINUE;
+}
+
+/**
+ * die_find_top_inlinefunc - Search the top inlined function at given address
+ * @sp_die: a subprogram DIE which including @addr
+ * @addr: target address
+ * @die_mem: a buffer for result DIE
+ *
+ * Search an inlined function DIE which includes @addr. Stores the
+ * DIE to @die_mem and returns it if found. Returns NULL if failed.
+ * Even if several inlined functions are expanded recursively, this
+ * doesn't trace it down, and returns the topmost one.
+ */
+Dwarf_Die *die_find_top_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr,
+ Dwarf_Die *die_mem)
+{
+ return die_find_child(sp_die, __die_find_inline_cb, &addr, die_mem);
+}
+
+/**
+ * die_find_inlinefunc - Search an inlined function at given address
+ * @sp_die: a subprogram DIE which including @addr
+ * @addr: target address
+ * @die_mem: a buffer for result DIE
+ *
+ * Search an inlined function DIE which includes @addr. Stores the
+ * DIE to @die_mem and returns it if found. Returns NULL if failed.
+ * If several inlined functions are expanded recursively, this trace
+ * it down and returns deepest one.
+ */
+Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr,
+ Dwarf_Die *die_mem)
+{
+ Dwarf_Die tmp_die;
+
+ sp_die = die_find_child(sp_die, __die_find_inline_cb, &addr, &tmp_die);
+ if (!sp_die)
+ return NULL;
+
+ /* Inlined function could be recursive. Trace it until fail */
+ while (sp_die) {
+ memcpy(die_mem, sp_die, sizeof(Dwarf_Die));
+ sp_die = die_find_child(sp_die, __die_find_inline_cb, &addr,
+ &tmp_die);
+ }
+
+ return die_mem;
+}
+
+struct __instance_walk_param {
+ void *addr;
+ int (*callback)(Dwarf_Die *, void *);
+ void *data;
+ int retval;
+};
+
+static int __die_walk_instances_cb(Dwarf_Die *inst, void *data)
+{
+ struct __instance_walk_param *iwp = data;
+ Dwarf_Attribute attr_mem;
+ Dwarf_Die origin_mem;
+ Dwarf_Attribute *attr;
+ Dwarf_Die *origin;
+ int tmp;
+
+ attr = dwarf_attr(inst, DW_AT_abstract_origin, &attr_mem);
+ if (attr == NULL)
+ return DIE_FIND_CB_CONTINUE;
+
+ origin = dwarf_formref_die(attr, &origin_mem);
+ if (origin == NULL || origin->addr != iwp->addr)
+ return DIE_FIND_CB_CONTINUE;
+
+ /* Ignore redundant instances */
+ if (dwarf_tag(inst) == DW_TAG_inlined_subroutine) {
+ dwarf_decl_line(origin, &tmp);
+ if (die_get_call_lineno(inst) == tmp) {
+ tmp = die_get_decl_fileno(origin);
+ if (die_get_call_fileno(inst) == tmp)
+ return DIE_FIND_CB_CONTINUE;
+ }
+ }
+
+ iwp->retval = iwp->callback(inst, iwp->data);
+
+ return (iwp->retval) ? DIE_FIND_CB_END : DIE_FIND_CB_CONTINUE;
+}
+
+/**
+ * die_walk_instances - Walk on instances of given DIE
+ * @or_die: an abstract original DIE
+ * @callback: a callback function which is called with instance DIE
+ * @data: user data
+ *
+ * Walk on the instances of give @in_die. @in_die must be an inlined function
+ * declartion. This returns the return value of @callback if it returns
+ * non-zero value, or -ENOENT if there is no instance.
+ */
+int die_walk_instances(Dwarf_Die *or_die, int (*callback)(Dwarf_Die *, void *),
+ void *data)
+{
+ Dwarf_Die cu_die;
+ Dwarf_Die die_mem;
+ struct __instance_walk_param iwp = {
+ .addr = or_die->addr,
+ .callback = callback,
+ .data = data,
+ .retval = -ENOENT,
+ };
+
+ if (dwarf_diecu(or_die, &cu_die, NULL, NULL) == NULL)
+ return -ENOENT;
+
+ die_find_child(&cu_die, __die_walk_instances_cb, &iwp, &die_mem);
+
+ return iwp.retval;
+}
+
+/* Line walker internal parameters */
+struct __line_walk_param {
+ bool recursive;
+ line_walk_callback_t callback;
+ void *data;
+ int retval;
+};
+
+static int __die_walk_funclines_cb(Dwarf_Die *in_die, void *data)
+{
+ struct __line_walk_param *lw = data;
+ Dwarf_Addr addr = 0;
+ const char *fname;
+ int lineno;
+
+ if (dwarf_tag(in_die) == DW_TAG_inlined_subroutine) {
+ fname = die_get_call_file(in_die);
+ lineno = die_get_call_lineno(in_die);
+ if (fname && lineno > 0 && dwarf_entrypc(in_die, &addr) == 0) {
+ lw->retval = lw->callback(fname, lineno, addr, lw->data);
+ if (lw->retval != 0)
+ return DIE_FIND_CB_END;
+ }
+ }
+ if (!lw->recursive)
+ /* Don't need to search recursively */
+ return DIE_FIND_CB_SIBLING;
+
+ if (addr) {
+ fname = dwarf_decl_file(in_die);
+ if (fname && dwarf_decl_line(in_die, &lineno) == 0) {
+ lw->retval = lw->callback(fname, lineno, addr, lw->data);
+ if (lw->retval != 0)
+ return DIE_FIND_CB_END;
+ }
+ }
+
+ /* Continue to search nested inlined function call-sites */
+ return DIE_FIND_CB_CONTINUE;
+}
+
+/* Walk on lines of blocks included in given DIE */
+static int __die_walk_funclines(Dwarf_Die *sp_die, bool recursive,
+ line_walk_callback_t callback, void *data)
+{
+ struct __line_walk_param lw = {
+ .recursive = recursive,
+ .callback = callback,
+ .data = data,
+ .retval = 0,
+ };
+ Dwarf_Die die_mem;
+ Dwarf_Addr addr;
+ const char *fname;
+ int lineno;
+
+ /* Handle function declaration line */
+ fname = dwarf_decl_file(sp_die);
+ if (fname && dwarf_decl_line(sp_die, &lineno) == 0 &&
+ dwarf_entrypc(sp_die, &addr) == 0) {
+ lw.retval = callback(fname, lineno, addr, data);
+ if (lw.retval != 0)
+ goto done;
+ }
+ die_find_child(sp_die, __die_walk_funclines_cb, &lw, &die_mem);
+done:
+ return lw.retval;
+}
+
+static int __die_walk_culines_cb(Dwarf_Die *sp_die, void *data)
+{
+ struct __line_walk_param *lw = data;
+
+ lw->retval = __die_walk_funclines(sp_die, true, lw->callback, lw->data);
+ if (lw->retval != 0)
+ return DWARF_CB_ABORT;
+
+ return DWARF_CB_OK;
+}
+
+/**
+ * die_walk_lines - Walk on lines inside given DIE
+ * @rt_die: a root DIE (CU, subprogram or inlined_subroutine)
+ * @callback: callback routine
+ * @data: user data
+ *
+ * Walk on all lines inside given @rt_die and call @callback on each line.
+ * If the @rt_die is a function, walk only on the lines inside the function,
+ * otherwise @rt_die must be a CU DIE.
+ * Note that this walks not only dwarf line list, but also function entries
+ * and inline call-site.
+ */
+int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback, void *data)
+{
+ Dwarf_Lines *lines;
+ Dwarf_Line *line;
+ Dwarf_Addr addr;
+ const char *fname;
+ int lineno, ret = 0;
+ Dwarf_Die die_mem, *cu_die;
+ size_t nlines, i;
+
+ /* Get the CU die */
+ if (dwarf_tag(rt_die) != DW_TAG_compile_unit)
+ cu_die = dwarf_diecu(rt_die, &die_mem, NULL, NULL);
+ else
+ cu_die = rt_die;
+ if (!cu_die) {
+ pr_debug2("Failed to get CU from given DIE.\n");
+ return -EINVAL;
+ }
+
+ /* Get lines list in the CU */
+ if (dwarf_getsrclines(cu_die, &lines, &nlines) != 0) {
+ pr_debug2("Failed to get source lines on this CU.\n");
+ return -ENOENT;
+ }
+ pr_debug2("Get %zd lines from this CU\n", nlines);
+
+ /* Walk on the lines on lines list */
+ for (i = 0; i < nlines; i++) {
+ line = dwarf_onesrcline(lines, i);
+ if (line == NULL ||
+ dwarf_lineno(line, &lineno) != 0 ||
+ dwarf_lineaddr(line, &addr) != 0) {
+ pr_debug2("Failed to get line info. "
+ "Possible error in debuginfo.\n");
+ continue;
+ }
+ /* Filter lines based on address */
+ if (rt_die != cu_die)
+ /*
+ * Address filtering
+ * The line is included in given function, and
+ * no inline block includes it.
+ */
+ if (!dwarf_haspc(rt_die, addr) ||
+ die_find_inlinefunc(rt_die, addr, &die_mem))
+ continue;
+ /* Get source line */
+ fname = dwarf_linesrc(line, NULL, NULL);
+
+ ret = callback(fname, lineno, addr, data);
+ if (ret != 0)
+ return ret;
+ }
+
+ /*
+ * Dwarf lines doesn't include function declarations and inlined
+ * subroutines. We have to check functions list or given function.
+ */
+ if (rt_die != cu_die)
+ /*
+ * Don't need walk functions recursively, because nested
+ * inlined functions don't have lines of the specified DIE.
+ */
+ ret = __die_walk_funclines(rt_die, false, callback, data);
+ else {
+ struct __line_walk_param param = {
+ .callback = callback,
+ .data = data,
+ .retval = 0,
+ };
+ dwarf_getfuncs(cu_die, __die_walk_culines_cb, &param, 0);
+ ret = param.retval;
+ }
+
+ return ret;
+}
+
+struct __find_variable_param {
+ const char *name;
+ Dwarf_Addr addr;
+};
+
+static int __die_find_variable_cb(Dwarf_Die *die_mem, void *data)
+{
+ struct __find_variable_param *fvp = data;
+ Dwarf_Attribute attr;
+ int tag;
+
+ tag = dwarf_tag(die_mem);
+ if ((tag == DW_TAG_formal_parameter ||
+ tag == DW_TAG_variable) &&
+ die_compare_name(die_mem, fvp->name) &&
+ /* Does the DIE have location information or external instance? */
+ (dwarf_attr(die_mem, DW_AT_external, &attr) ||
+ dwarf_attr(die_mem, DW_AT_location, &attr)))
+ return DIE_FIND_CB_END;
+ if (dwarf_haspc(die_mem, fvp->addr))
+ return DIE_FIND_CB_CONTINUE;
+ else
+ return DIE_FIND_CB_SIBLING;
+}
+
+/**
+ * die_find_variable_at - Find a given name variable at given address
+ * @sp_die: a function DIE
+ * @name: variable name
+ * @addr: address
+ * @die_mem: a buffer for result DIE
+ *
+ * Find a variable DIE called @name at @addr in @sp_die.
+ */
+Dwarf_Die *die_find_variable_at(Dwarf_Die *sp_die, const char *name,
+ Dwarf_Addr addr, Dwarf_Die *die_mem)
+{
+ struct __find_variable_param fvp = { .name = name, .addr = addr};
+
+ return die_find_child(sp_die, __die_find_variable_cb, (void *)&fvp,
+ die_mem);
+}
+
+static int __die_find_member_cb(Dwarf_Die *die_mem, void *data)
+{
+ const char *name = data;
+
+ if ((dwarf_tag(die_mem) == DW_TAG_member) &&
+ die_compare_name(die_mem, name))
+ return DIE_FIND_CB_END;
+
+ return DIE_FIND_CB_SIBLING;
+}
+
+/**
+ * die_find_member - Find a given name member in a data structure
+ * @st_die: a data structure type DIE
+ * @name: member name
+ * @die_mem: a buffer for result DIE
+ *
+ * Find a member DIE called @name in @st_die.
+ */
+Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name,
+ Dwarf_Die *die_mem)
+{
+ return die_find_child(st_die, __die_find_member_cb, (void *)name,
+ die_mem);
+}
+
+/**
+ * die_get_typename - Get the name of given variable DIE
+ * @vr_die: a variable DIE
+ * @buf: a buffer for result type name
+ * @len: a max-length of @buf
+ *
+ * Get the name of @vr_die and stores it to @buf. Return the actual length
+ * of type name if succeeded. Return -E2BIG if @len is not enough long, and
+ * Return -ENOENT if failed to find type name.
+ * Note that the result will stores typedef name if possible, and stores
+ * "*(function_type)" if the type is a function pointer.
+ */
+int die_get_typename(Dwarf_Die *vr_die, char *buf, int len)
+{
+ Dwarf_Die type;
+ int tag, ret, ret2;
+ const char *tmp = "";
+
+ if (__die_get_real_type(vr_die, &type) == NULL)
+ return -ENOENT;
+
+ tag = dwarf_tag(&type);
+ if (tag == DW_TAG_array_type || tag == DW_TAG_pointer_type)
+ tmp = "*";
+ else if (tag == DW_TAG_subroutine_type) {
+ /* Function pointer */
+ ret = snprintf(buf, len, "(function_type)");
+ return (ret >= len) ? -E2BIG : ret;
+ } else {
+ if (!dwarf_diename(&type))
+ return -ENOENT;
+ if (tag == DW_TAG_union_type)
+ tmp = "union ";
+ else if (tag == DW_TAG_structure_type)
+ tmp = "struct ";
+ else if (tag == DW_TAG_enumeration_type)
+ tmp = "enum ";
+ /* Write a base name */
+ ret = snprintf(buf, len, "%s%s", tmp, dwarf_diename(&type));
+ return (ret >= len) ? -E2BIG : ret;
+ }
+ ret = die_get_typename(&type, buf, len);
+ if (ret > 0) {
+ ret2 = snprintf(buf + ret, len - ret, "%s", tmp);
+ ret = (ret2 >= len - ret) ? -E2BIG : ret2 + ret;
+ }
+ return ret;
+}
+
+/**
+ * die_get_varname - Get the name and type of given variable DIE
+ * @vr_die: a variable DIE
+ * @buf: a buffer for type and variable name
+ * @len: the max-length of @buf
+ *
+ * Get the name and type of @vr_die and stores it in @buf as "type\tname".
+ */
+int die_get_varname(Dwarf_Die *vr_die, char *buf, int len)
+{
+ int ret, ret2;
+
+ ret = die_get_typename(vr_die, buf, len);
+ if (ret < 0) {
+ pr_debug("Failed to get type, make it unknown.\n");
+ ret = snprintf(buf, len, "(unknown_type)");
+ }
+ if (ret > 0) {
+ ret2 = snprintf(buf + ret, len - ret, "\t%s",
+ dwarf_diename(vr_die));
+ ret = (ret2 >= len - ret) ? -E2BIG : ret2 + ret;
+ }
+ return ret;
+}
+
diff --git a/tools/perf/util/dwarf-aux.h b/tools/perf/util/dwarf-aux.h
new file mode 100644
index 00000000000..b4fe90c6cb2
--- /dev/null
+++ b/tools/perf/util/dwarf-aux.h
@@ -0,0 +1,118 @@
+#ifndef _DWARF_AUX_H
+#define _DWARF_AUX_H
+/*
+ * dwarf-aux.h : libdw auxiliary interfaces
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <dwarf.h>
+#include <elfutils/libdw.h>
+#include <elfutils/libdwfl.h>
+#include <elfutils/version.h>
+
+/* Find the realpath of the target file */
+extern const char *cu_find_realpath(Dwarf_Die *cu_die, const char *fname);
+
+/* Get DW_AT_comp_dir (should be NULL with older gcc) */
+extern const char *cu_get_comp_dir(Dwarf_Die *cu_die);
+
+/* Get a line number and file name for given address */
+extern int cu_find_lineinfo(Dwarf_Die *cudie, unsigned long addr,
+ const char **fname, int *lineno);
+
+/* Walk on funcitons at given address */
+extern int cu_walk_functions_at(Dwarf_Die *cu_die, Dwarf_Addr addr,
+ int (*callback)(Dwarf_Die *, void *), void *data);
+
+/* Ensure that this DIE is a subprogram and definition (not declaration) */
+extern bool die_is_func_def(Dwarf_Die *dw_die);
+
+/* Compare diename and tname */
+extern bool die_compare_name(Dwarf_Die *dw_die, const char *tname);
+
+/* Get callsite line number of inline-function instance */
+extern int die_get_call_lineno(Dwarf_Die *in_die);
+
+/* Get callsite file name of inlined function instance */
+extern const char *die_get_call_file(Dwarf_Die *in_die);
+
+/* Get type die */
+extern Dwarf_Die *die_get_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem);
+
+/* Get a type die, but skip qualifiers and typedef */
+extern Dwarf_Die *die_get_real_type(Dwarf_Die *vr_die, Dwarf_Die *die_mem);
+
+/* Check whether the DIE is signed or not */
+extern bool die_is_signed_type(Dwarf_Die *tp_die);
+
+/* Get data_member_location offset */
+extern int die_get_data_member_location(Dwarf_Die *mb_die, Dwarf_Word *offs);
+
+/* Return values for die_find_child() callbacks */
+enum {
+ DIE_FIND_CB_END = 0, /* End of Search */
+ DIE_FIND_CB_CHILD = 1, /* Search only children */
+ DIE_FIND_CB_SIBLING = 2, /* Search only siblings */
+ DIE_FIND_CB_CONTINUE = 3, /* Search children and siblings */
+};
+
+/* Search child DIEs */
+extern Dwarf_Die *die_find_child(Dwarf_Die *rt_die,
+ int (*callback)(Dwarf_Die *, void *),
+ void *data, Dwarf_Die *die_mem);
+
+/* Search a non-inlined function including given address */
+extern Dwarf_Die *die_find_realfunc(Dwarf_Die *cu_die, Dwarf_Addr addr,
+ Dwarf_Die *die_mem);
+
+/* Search the top inlined function including given address */
+extern Dwarf_Die *die_find_top_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr,
+ Dwarf_Die *die_mem);
+
+/* Search the deepest inlined function including given address */
+extern Dwarf_Die *die_find_inlinefunc(Dwarf_Die *sp_die, Dwarf_Addr addr,
+ Dwarf_Die *die_mem);
+
+/* Walk on the instances of given DIE */
+extern int die_walk_instances(Dwarf_Die *in_die,
+ int (*callback)(Dwarf_Die *, void *), void *data);
+
+/* Walker on lines (Note: line number will not be sorted) */
+typedef int (* line_walk_callback_t) (const char *fname, int lineno,
+ Dwarf_Addr addr, void *data);
+
+/*
+ * Walk on lines inside given DIE. If the DIE is a subprogram, walk only on
+ * the lines inside the subprogram, otherwise the DIE must be a CU DIE.
+ */
+extern int die_walk_lines(Dwarf_Die *rt_die, line_walk_callback_t callback,
+ void *data);
+
+/* Find a variable called 'name' at given address */
+extern Dwarf_Die *die_find_variable_at(Dwarf_Die *sp_die, const char *name,
+ Dwarf_Addr addr, Dwarf_Die *die_mem);
+
+/* Find a member called 'name' */
+extern Dwarf_Die *die_find_member(Dwarf_Die *st_die, const char *name,
+ Dwarf_Die *die_mem);
+
+/* Get the name of given variable DIE */
+extern int die_get_typename(Dwarf_Die *vr_die, char *buf, int len);
+
+/* Get the name and type of given variable DIE, stored as "type\tname" */
+extern int die_get_varname(Dwarf_Die *vr_die, char *buf, int len);
+#endif
diff --git a/tools/perf/util/environment.c b/tools/perf/util/environment.c
new file mode 100644
index 00000000000..275b0ee345f
--- /dev/null
+++ b/tools/perf/util/environment.c
@@ -0,0 +1,9 @@
+/*
+ * We put all the perf config variables in this same object
+ * file, so that programs can link against the config parser
+ * without having to link against all the rest of perf.
+ */
+#include "cache.h"
+
+const char *pager_program;
+int pager_use_color = 1;
diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c
new file mode 100644
index 00000000000..d0281bdfa58
--- /dev/null
+++ b/tools/perf/util/event.c
@@ -0,0 +1,868 @@
+#include <linux/types.h>
+#include <sys/mman.h>
+#include "event.h"
+#include "debug.h"
+#include "hist.h"
+#include "machine.h"
+#include "sort.h"
+#include "string.h"
+#include "strlist.h"
+#include "thread.h"
+#include "thread_map.h"
+#include "symbol/kallsyms.h"
+
+static const char *perf_event__names[] = {
+ [0] = "TOTAL",
+ [PERF_RECORD_MMAP] = "MMAP",
+ [PERF_RECORD_MMAP2] = "MMAP2",
+ [PERF_RECORD_LOST] = "LOST",
+ [PERF_RECORD_COMM] = "COMM",
+ [PERF_RECORD_EXIT] = "EXIT",
+ [PERF_RECORD_THROTTLE] = "THROTTLE",
+ [PERF_RECORD_UNTHROTTLE] = "UNTHROTTLE",
+ [PERF_RECORD_FORK] = "FORK",
+ [PERF_RECORD_READ] = "READ",
+ [PERF_RECORD_SAMPLE] = "SAMPLE",
+ [PERF_RECORD_HEADER_ATTR] = "ATTR",
+ [PERF_RECORD_HEADER_EVENT_TYPE] = "EVENT_TYPE",
+ [PERF_RECORD_HEADER_TRACING_DATA] = "TRACING_DATA",
+ [PERF_RECORD_HEADER_BUILD_ID] = "BUILD_ID",
+ [PERF_RECORD_FINISHED_ROUND] = "FINISHED_ROUND",
+};
+
+const char *perf_event__name(unsigned int id)
+{
+ if (id >= ARRAY_SIZE(perf_event__names))
+ return "INVALID";
+ if (!perf_event__names[id])
+ return "UNKNOWN";
+ return perf_event__names[id];
+}
+
+static struct perf_sample synth_sample = {
+ .pid = -1,
+ .tid = -1,
+ .time = -1,
+ .stream_id = -1,
+ .cpu = -1,
+ .period = 1,
+};
+
+static pid_t perf_event__get_comm_tgid(pid_t pid, char *comm, size_t len)
+{
+ char filename[PATH_MAX];
+ char bf[BUFSIZ];
+ FILE *fp;
+ size_t size = 0;
+ pid_t tgid = -1;
+
+ snprintf(filename, sizeof(filename), "/proc/%d/status", pid);
+
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+ pr_debug("couldn't open %s\n", filename);
+ return 0;
+ }
+
+ while (!comm[0] || (tgid < 0)) {
+ if (fgets(bf, sizeof(bf), fp) == NULL) {
+ pr_warning("couldn't get COMM and pgid, malformed %s\n",
+ filename);
+ break;
+ }
+
+ if (memcmp(bf, "Name:", 5) == 0) {
+ char *name = bf + 5;
+ while (*name && isspace(*name))
+ ++name;
+ size = strlen(name) - 1;
+ if (size >= len)
+ size = len - 1;
+ memcpy(comm, name, size);
+ comm[size] = '\0';
+
+ } else if (memcmp(bf, "Tgid:", 5) == 0) {
+ char *tgids = bf + 5;
+ while (*tgids && isspace(*tgids))
+ ++tgids;
+ tgid = atoi(tgids);
+ }
+ }
+
+ fclose(fp);
+
+ return tgid;
+}
+
+static pid_t perf_event__synthesize_comm(struct perf_tool *tool,
+ union perf_event *event, pid_t pid,
+ perf_event__handler_t process,
+ struct machine *machine)
+{
+ size_t size;
+ pid_t tgid;
+
+ memset(&event->comm, 0, sizeof(event->comm));
+
+ if (machine__is_host(machine))
+ tgid = perf_event__get_comm_tgid(pid, event->comm.comm,
+ sizeof(event->comm.comm));
+ else
+ tgid = machine->pid;
+
+ if (tgid < 0)
+ goto out;
+
+ event->comm.pid = tgid;
+ event->comm.header.type = PERF_RECORD_COMM;
+
+ size = strlen(event->comm.comm) + 1;
+ size = PERF_ALIGN(size, sizeof(u64));
+ memset(event->comm.comm + size, 0, machine->id_hdr_size);
+ event->comm.header.size = (sizeof(event->comm) -
+ (sizeof(event->comm.comm) - size) +
+ machine->id_hdr_size);
+ event->comm.tid = pid;
+
+ if (process(tool, event, &synth_sample, machine) != 0)
+ return -1;
+
+out:
+ return tgid;
+}
+
+static int perf_event__synthesize_fork(struct perf_tool *tool,
+ union perf_event *event, pid_t pid,
+ pid_t tgid, perf_event__handler_t process,
+ struct machine *machine)
+{
+ memset(&event->fork, 0, sizeof(event->fork) + machine->id_hdr_size);
+
+ /* this is really a clone event but we use fork to synthesize it */
+ event->fork.ppid = tgid;
+ event->fork.ptid = tgid;
+ event->fork.pid = tgid;
+ event->fork.tid = pid;
+ event->fork.header.type = PERF_RECORD_FORK;
+
+ event->fork.header.size = (sizeof(event->fork) + machine->id_hdr_size);
+
+ if (process(tool, event, &synth_sample, machine) != 0)
+ return -1;
+
+ return 0;
+}
+
+int perf_event__synthesize_mmap_events(struct perf_tool *tool,
+ union perf_event *event,
+ pid_t pid, pid_t tgid,
+ perf_event__handler_t process,
+ struct machine *machine,
+ bool mmap_data)
+{
+ char filename[PATH_MAX];
+ FILE *fp;
+ int rc = 0;
+
+ if (machine__is_default_guest(machine))
+ return 0;
+
+ snprintf(filename, sizeof(filename), "%s/proc/%d/maps",
+ machine->root_dir, pid);
+
+ fp = fopen(filename, "r");
+ if (fp == NULL) {
+ /*
+ * We raced with a task exiting - just return:
+ */
+ pr_debug("couldn't open %s\n", filename);
+ return -1;
+ }
+
+ event->header.type = PERF_RECORD_MMAP2;
+
+ while (1) {
+ char bf[BUFSIZ];
+ char prot[5];
+ char execname[PATH_MAX];
+ char anonstr[] = "//anon";
+ unsigned int ino;
+ size_t size;
+ ssize_t n;
+
+ if (fgets(bf, sizeof(bf), fp) == NULL)
+ break;
+
+ /* ensure null termination since stack will be reused. */
+ strcpy(execname, "");
+
+ /* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */
+ n = sscanf(bf, "%"PRIx64"-%"PRIx64" %s %"PRIx64" %x:%x %u %s\n",
+ &event->mmap2.start, &event->mmap2.len, prot,
+ &event->mmap2.pgoff, &event->mmap2.maj,
+ &event->mmap2.min,
+ &ino, execname);
+
+ /*
+ * Anon maps don't have the execname.
+ */
+ if (n < 7)
+ continue;
+
+ event->mmap2.ino = (u64)ino;
+
+ /*
+ * Just like the kernel, see __perf_event_mmap in kernel/perf_event.c
+ */
+ if (machine__is_host(machine))
+ event->header.misc = PERF_RECORD_MISC_USER;
+ else
+ event->header.misc = PERF_RECORD_MISC_GUEST_USER;
+
+ /* map protection and flags bits */
+ event->mmap2.prot = 0;
+ event->mmap2.flags = 0;
+ if (prot[0] == 'r')
+ event->mmap2.prot |= PROT_READ;
+ if (prot[1] == 'w')
+ event->mmap2.prot |= PROT_WRITE;
+ if (prot[2] == 'x')
+ event->mmap2.prot |= PROT_EXEC;
+
+ if (prot[3] == 's')
+ event->mmap2.flags |= MAP_SHARED;
+ else
+ event->mmap2.flags |= MAP_PRIVATE;
+
+ if (prot[2] != 'x') {
+ if (!mmap_data || prot[0] != 'r')
+ continue;
+
+ event->header.misc |= PERF_RECORD_MISC_MMAP_DATA;
+ }
+
+ if (!strcmp(execname, ""))
+ strcpy(execname, anonstr);
+
+ size = strlen(execname) + 1;
+ memcpy(event->mmap2.filename, execname, size);
+ size = PERF_ALIGN(size, sizeof(u64));
+ event->mmap2.len -= event->mmap.start;
+ event->mmap2.header.size = (sizeof(event->mmap2) -
+ (sizeof(event->mmap2.filename) - size));
+ memset(event->mmap2.filename + size, 0, machine->id_hdr_size);
+ event->mmap2.header.size += machine->id_hdr_size;
+ event->mmap2.pid = tgid;
+ event->mmap2.tid = pid;
+
+ if (process(tool, event, &synth_sample, machine) != 0) {
+ rc = -1;
+ break;
+ }
+ }
+
+ fclose(fp);
+ return rc;
+}
+
+int perf_event__synthesize_modules(struct perf_tool *tool,
+ perf_event__handler_t process,
+ struct machine *machine)
+{
+ int rc = 0;
+ struct rb_node *nd;
+ struct map_groups *kmaps = &machine->kmaps;
+ union perf_event *event = zalloc((sizeof(event->mmap) +
+ machine->id_hdr_size));
+ if (event == NULL) {
+ pr_debug("Not enough memory synthesizing mmap event "
+ "for kernel modules\n");
+ return -1;
+ }
+
+ event->header.type = PERF_RECORD_MMAP;
+
+ /*
+ * kernel uses 0 for user space maps, see kernel/perf_event.c
+ * __perf_event_mmap
+ */
+ if (machine__is_host(machine))
+ event->header.misc = PERF_RECORD_MISC_KERNEL;
+ else
+ event->header.misc = PERF_RECORD_MISC_GUEST_KERNEL;
+
+ for (nd = rb_first(&kmaps->maps[MAP__FUNCTION]);
+ nd; nd = rb_next(nd)) {
+ size_t size;
+ struct map *pos = rb_entry(nd, struct map, rb_node);
+
+ if (pos->dso->kernel)
+ continue;
+
+ size = PERF_ALIGN(pos->dso->long_name_len + 1, sizeof(u64));
+ event->mmap.header.type = PERF_RECORD_MMAP;
+ event->mmap.header.size = (sizeof(event->mmap) -
+ (sizeof(event->mmap.filename) - size));
+ memset(event->mmap.filename + size, 0, machine->id_hdr_size);
+ event->mmap.header.size += machine->id_hdr_size;
+ event->mmap.start = pos->start;
+ event->mmap.len = pos->end - pos->start;
+ event->mmap.pid = machine->pid;
+
+ memcpy(event->mmap.filename, pos->dso->long_name,
+ pos->dso->long_name_len + 1);
+ if (process(tool, event, &synth_sample, machine) != 0) {
+ rc = -1;
+ break;
+ }
+ }
+
+ free(event);
+ return rc;
+}
+
+static int __event__synthesize_thread(union perf_event *comm_event,
+ union perf_event *mmap_event,
+ union perf_event *fork_event,
+ pid_t pid, int full,
+ perf_event__handler_t process,
+ struct perf_tool *tool,
+ struct machine *machine, bool mmap_data)
+{
+ char filename[PATH_MAX];
+ DIR *tasks;
+ struct dirent dirent, *next;
+ pid_t tgid;
+
+ /* special case: only send one comm event using passed in pid */
+ if (!full) {
+ tgid = perf_event__synthesize_comm(tool, comm_event, pid,
+ process, machine);
+
+ if (tgid == -1)
+ return -1;
+
+ return perf_event__synthesize_mmap_events(tool, mmap_event, pid, tgid,
+ process, machine, mmap_data);
+ }
+
+ if (machine__is_default_guest(machine))
+ return 0;
+
+ snprintf(filename, sizeof(filename), "%s/proc/%d/task",
+ machine->root_dir, pid);
+
+ tasks = opendir(filename);
+ if (tasks == NULL) {
+ pr_debug("couldn't open %s\n", filename);
+ return 0;
+ }
+
+ while (!readdir_r(tasks, &dirent, &next) && next) {
+ char *end;
+ int rc = 0;
+ pid_t _pid;
+
+ _pid = strtol(dirent.d_name, &end, 10);
+ if (*end)
+ continue;
+
+ tgid = perf_event__synthesize_comm(tool, comm_event, _pid,
+ process, machine);
+ if (tgid == -1)
+ return -1;
+
+ if (_pid == pid) {
+ /* process the parent's maps too */
+ rc = perf_event__synthesize_mmap_events(tool, mmap_event, pid, tgid,
+ process, machine, mmap_data);
+ } else {
+ /* only fork the tid's map, to save time */
+ rc = perf_event__synthesize_fork(tool, fork_event, _pid, tgid,
+ process, machine);
+ }
+
+ if (rc)
+ return rc;
+ }
+
+ closedir(tasks);
+ return 0;
+}
+
+int perf_event__synthesize_thread_map(struct perf_tool *tool,
+ struct thread_map *threads,
+ perf_event__handler_t process,
+ struct machine *machine,
+ bool mmap_data)
+{
+ union perf_event *comm_event, *mmap_event, *fork_event;
+ int err = -1, thread, j;
+
+ comm_event = malloc(sizeof(comm_event->comm) + machine->id_hdr_size);
+ if (comm_event == NULL)
+ goto out;
+
+ mmap_event = malloc(sizeof(mmap_event->mmap) + machine->id_hdr_size);
+ if (mmap_event == NULL)
+ goto out_free_comm;
+
+ fork_event = malloc(sizeof(fork_event->fork) + machine->id_hdr_size);
+ if (fork_event == NULL)
+ goto out_free_mmap;
+
+ err = 0;
+ for (thread = 0; thread < threads->nr; ++thread) {
+ if (__event__synthesize_thread(comm_event, mmap_event,
+ fork_event,
+ threads->map[thread], 0,
+ process, tool, machine,
+ mmap_data)) {
+ err = -1;
+ break;
+ }
+
+ /*
+ * comm.pid is set to thread group id by
+ * perf_event__synthesize_comm
+ */
+ if ((int) comm_event->comm.pid != threads->map[thread]) {
+ bool need_leader = true;
+
+ /* is thread group leader in thread_map? */
+ for (j = 0; j < threads->nr; ++j) {
+ if ((int) comm_event->comm.pid == threads->map[j]) {
+ need_leader = false;
+ break;
+ }
+ }
+
+ /* if not, generate events for it */
+ if (need_leader &&
+ __event__synthesize_thread(comm_event, mmap_event,
+ fork_event,
+ comm_event->comm.pid, 0,
+ process, tool, machine,
+ mmap_data)) {
+ err = -1;
+ break;
+ }
+ }
+ }
+ free(fork_event);
+out_free_mmap:
+ free(mmap_event);
+out_free_comm:
+ free(comm_event);
+out:
+ return err;
+}
+
+int perf_event__synthesize_threads(struct perf_tool *tool,
+ perf_event__handler_t process,
+ struct machine *machine, bool mmap_data)
+{
+ DIR *proc;
+ char proc_path[PATH_MAX];
+ struct dirent dirent, *next;
+ union perf_event *comm_event, *mmap_event, *fork_event;
+ int err = -1;
+
+ if (machine__is_default_guest(machine))
+ return 0;
+
+ comm_event = malloc(sizeof(comm_event->comm) + machine->id_hdr_size);
+ if (comm_event == NULL)
+ goto out;
+
+ mmap_event = malloc(sizeof(mmap_event->mmap) + machine->id_hdr_size);
+ if (mmap_event == NULL)
+ goto out_free_comm;
+
+ fork_event = malloc(sizeof(fork_event->fork) + machine->id_hdr_size);
+ if (fork_event == NULL)
+ goto out_free_mmap;
+
+ snprintf(proc_path, sizeof(proc_path), "%s/proc", machine->root_dir);
+ proc = opendir(proc_path);
+
+ if (proc == NULL)
+ goto out_free_fork;
+
+ while (!readdir_r(proc, &dirent, &next) && next) {
+ char *end;
+ pid_t pid = strtol(dirent.d_name, &end, 10);
+
+ if (*end) /* only interested in proper numerical dirents */
+ continue;
+ /*
+ * We may race with exiting thread, so don't stop just because
+ * one thread couldn't be synthesized.
+ */
+ __event__synthesize_thread(comm_event, mmap_event, fork_event, pid,
+ 1, process, tool, machine, mmap_data);
+ }
+
+ err = 0;
+ closedir(proc);
+out_free_fork:
+ free(fork_event);
+out_free_mmap:
+ free(mmap_event);
+out_free_comm:
+ free(comm_event);
+out:
+ return err;
+}
+
+struct process_symbol_args {
+ const char *name;
+ u64 start;
+};
+
+static int find_symbol_cb(void *arg, const char *name, char type,
+ u64 start)
+{
+ struct process_symbol_args *args = arg;
+
+ /*
+ * Must be a function or at least an alias, as in PARISC64, where "_text" is
+ * an 'A' to the same address as "_stext".
+ */
+ if (!(symbol_type__is_a(type, MAP__FUNCTION) ||
+ type == 'A') || strcmp(name, args->name))
+ return 0;
+
+ args->start = start;
+ return 1;
+}
+
+u64 kallsyms__get_function_start(const char *kallsyms_filename,
+ const char *symbol_name)
+{
+ struct process_symbol_args args = { .name = symbol_name, };
+
+ if (kallsyms__parse(kallsyms_filename, &args, find_symbol_cb) <= 0)
+ return 0;
+
+ return args.start;
+}
+
+int perf_event__synthesize_kernel_mmap(struct perf_tool *tool,
+ perf_event__handler_t process,
+ struct machine *machine)
+{
+ size_t size;
+ const char *mmap_name;
+ char name_buff[PATH_MAX];
+ struct map *map;
+ struct kmap *kmap;
+ int err;
+ /*
+ * We should get this from /sys/kernel/sections/.text, but till that is
+ * available use this, and after it is use this as a fallback for older
+ * kernels.
+ */
+ union perf_event *event = zalloc((sizeof(event->mmap) +
+ machine->id_hdr_size));
+ if (event == NULL) {
+ pr_debug("Not enough memory synthesizing mmap event "
+ "for kernel modules\n");
+ return -1;
+ }
+
+ mmap_name = machine__mmap_name(machine, name_buff, sizeof(name_buff));
+ if (machine__is_host(machine)) {
+ /*
+ * kernel uses PERF_RECORD_MISC_USER for user space maps,
+ * see kernel/perf_event.c __perf_event_mmap
+ */
+ event->header.misc = PERF_RECORD_MISC_KERNEL;
+ } else {
+ event->header.misc = PERF_RECORD_MISC_GUEST_KERNEL;
+ }
+
+ map = machine->vmlinux_maps[MAP__FUNCTION];
+ kmap = map__kmap(map);
+ size = snprintf(event->mmap.filename, sizeof(event->mmap.filename),
+ "%s%s", mmap_name, kmap->ref_reloc_sym->name) + 1;
+ size = PERF_ALIGN(size, sizeof(u64));
+ event->mmap.header.type = PERF_RECORD_MMAP;
+ event->mmap.header.size = (sizeof(event->mmap) -
+ (sizeof(event->mmap.filename) - size) + machine->id_hdr_size);
+ event->mmap.pgoff = kmap->ref_reloc_sym->addr;
+ event->mmap.start = map->start;
+ event->mmap.len = map->end - event->mmap.start;
+ event->mmap.pid = machine->pid;
+
+ err = process(tool, event, &synth_sample, machine);
+ free(event);
+
+ return err;
+}
+
+size_t perf_event__fprintf_comm(union perf_event *event, FILE *fp)
+{
+ return fprintf(fp, ": %s:%d\n", event->comm.comm, event->comm.tid);
+}
+
+int perf_event__process_comm(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ return machine__process_comm_event(machine, event, sample);
+}
+
+int perf_event__process_lost(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ return machine__process_lost_event(machine, event, sample);
+}
+
+size_t perf_event__fprintf_mmap(union perf_event *event, FILE *fp)
+{
+ return fprintf(fp, " %d/%d: [%#" PRIx64 "(%#" PRIx64 ") @ %#" PRIx64 "]: %c %s\n",
+ event->mmap.pid, event->mmap.tid, event->mmap.start,
+ event->mmap.len, event->mmap.pgoff,
+ (event->header.misc & PERF_RECORD_MISC_MMAP_DATA) ? 'r' : 'x',
+ event->mmap.filename);
+}
+
+size_t perf_event__fprintf_mmap2(union perf_event *event, FILE *fp)
+{
+ return fprintf(fp, " %d/%d: [%#" PRIx64 "(%#" PRIx64 ") @ %#" PRIx64
+ " %02x:%02x %"PRIu64" %"PRIu64"]: %c%c%c%c %s\n",
+ event->mmap2.pid, event->mmap2.tid, event->mmap2.start,
+ event->mmap2.len, event->mmap2.pgoff, event->mmap2.maj,
+ event->mmap2.min, event->mmap2.ino,
+ event->mmap2.ino_generation,
+ (event->mmap2.prot & PROT_READ) ? 'r' : '-',
+ (event->mmap2.prot & PROT_WRITE) ? 'w' : '-',
+ (event->mmap2.prot & PROT_EXEC) ? 'x' : '-',
+ (event->mmap2.flags & MAP_SHARED) ? 's' : 'p',
+ event->mmap2.filename);
+}
+
+int perf_event__process_mmap(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ return machine__process_mmap_event(machine, event, sample);
+}
+
+int perf_event__process_mmap2(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ return machine__process_mmap2_event(machine, event, sample);
+}
+
+size_t perf_event__fprintf_task(union perf_event *event, FILE *fp)
+{
+ return fprintf(fp, "(%d:%d):(%d:%d)\n",
+ event->fork.pid, event->fork.tid,
+ event->fork.ppid, event->fork.ptid);
+}
+
+int perf_event__process_fork(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ return machine__process_fork_event(machine, event, sample);
+}
+
+int perf_event__process_exit(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ return machine__process_exit_event(machine, event, sample);
+}
+
+size_t perf_event__fprintf(union perf_event *event, FILE *fp)
+{
+ size_t ret = fprintf(fp, "PERF_RECORD_%s",
+ perf_event__name(event->header.type));
+
+ switch (event->header.type) {
+ case PERF_RECORD_COMM:
+ ret += perf_event__fprintf_comm(event, fp);
+ break;
+ case PERF_RECORD_FORK:
+ case PERF_RECORD_EXIT:
+ ret += perf_event__fprintf_task(event, fp);
+ break;
+ case PERF_RECORD_MMAP:
+ ret += perf_event__fprintf_mmap(event, fp);
+ break;
+ case PERF_RECORD_MMAP2:
+ ret += perf_event__fprintf_mmap2(event, fp);
+ break;
+ default:
+ ret += fprintf(fp, "\n");
+ }
+
+ return ret;
+}
+
+int perf_event__process(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ return machine__process_event(machine, event, sample);
+}
+
+void thread__find_addr_map(struct thread *thread,
+ struct machine *machine, u8 cpumode,
+ enum map_type type, u64 addr,
+ struct addr_location *al)
+{
+ struct map_groups *mg = thread->mg;
+ bool load_map = false;
+
+ al->machine = machine;
+ al->thread = thread;
+ al->addr = addr;
+ al->cpumode = cpumode;
+ al->filtered = 0;
+
+ if (machine == NULL) {
+ al->map = NULL;
+ return;
+ }
+
+ if (cpumode == PERF_RECORD_MISC_KERNEL && perf_host) {
+ al->level = 'k';
+ mg = &machine->kmaps;
+ load_map = true;
+ } else if (cpumode == PERF_RECORD_MISC_USER && perf_host) {
+ al->level = '.';
+ } else if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL && perf_guest) {
+ al->level = 'g';
+ mg = &machine->kmaps;
+ load_map = true;
+ } else if (cpumode == PERF_RECORD_MISC_GUEST_USER && perf_guest) {
+ al->level = 'u';
+ } else {
+ al->level = 'H';
+ al->map = NULL;
+
+ if ((cpumode == PERF_RECORD_MISC_GUEST_USER ||
+ cpumode == PERF_RECORD_MISC_GUEST_KERNEL) &&
+ !perf_guest)
+ al->filtered |= (1 << HIST_FILTER__GUEST);
+ if ((cpumode == PERF_RECORD_MISC_USER ||
+ cpumode == PERF_RECORD_MISC_KERNEL) &&
+ !perf_host)
+ al->filtered |= (1 << HIST_FILTER__HOST);
+
+ return;
+ }
+try_again:
+ al->map = map_groups__find(mg, type, al->addr);
+ if (al->map == NULL) {
+ /*
+ * If this is outside of all known maps, and is a negative
+ * address, try to look it up in the kernel dso, as it might be
+ * a vsyscall or vdso (which executes in user-mode).
+ *
+ * XXX This is nasty, we should have a symbol list in the
+ * "[vdso]" dso, but for now lets use the old trick of looking
+ * in the whole kernel symbol list.
+ */
+ if ((long long)al->addr < 0 &&
+ cpumode == PERF_RECORD_MISC_USER &&
+ machine && mg != &machine->kmaps) {
+ mg = &machine->kmaps;
+ goto try_again;
+ }
+ } else {
+ /*
+ * Kernel maps might be changed when loading symbols so loading
+ * must be done prior to using kernel maps.
+ */
+ if (load_map)
+ map__load(al->map, machine->symbol_filter);
+ al->addr = al->map->map_ip(al->map, al->addr);
+ }
+}
+
+void thread__find_addr_location(struct thread *thread, struct machine *machine,
+ u8 cpumode, enum map_type type, u64 addr,
+ struct addr_location *al)
+{
+ thread__find_addr_map(thread, machine, cpumode, type, addr, al);
+ if (al->map != NULL)
+ al->sym = map__find_symbol(al->map, al->addr,
+ machine->symbol_filter);
+ else
+ al->sym = NULL;
+}
+
+int perf_event__preprocess_sample(const union perf_event *event,
+ struct machine *machine,
+ struct addr_location *al,
+ struct perf_sample *sample)
+{
+ u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+ struct thread *thread = machine__findnew_thread(machine, sample->pid,
+ sample->tid);
+
+ if (thread == NULL)
+ return -1;
+
+ dump_printf(" ... thread: %s:%d\n", thread__comm_str(thread), thread->tid);
+ /*
+ * Have we already created the kernel maps for this machine?
+ *
+ * This should have happened earlier, when we processed the kernel MMAP
+ * events, but for older perf.data files there was no such thing, so do
+ * it now.
+ */
+ if (cpumode == PERF_RECORD_MISC_KERNEL &&
+ machine->vmlinux_maps[MAP__FUNCTION] == NULL)
+ machine__create_kernel_maps(machine);
+
+ thread__find_addr_map(thread, machine, cpumode, MAP__FUNCTION,
+ sample->ip, al);
+ dump_printf(" ...... dso: %s\n",
+ al->map ? al->map->dso->long_name :
+ al->level == 'H' ? "[hypervisor]" : "<not found>");
+
+ if (thread__is_filtered(thread))
+ al->filtered |= (1 << HIST_FILTER__THREAD);
+
+ al->sym = NULL;
+ al->cpu = sample->cpu;
+
+ if (al->map) {
+ struct dso *dso = al->map->dso;
+
+ if (symbol_conf.dso_list &&
+ (!dso || !(strlist__has_entry(symbol_conf.dso_list,
+ dso->short_name) ||
+ (dso->short_name != dso->long_name &&
+ strlist__has_entry(symbol_conf.dso_list,
+ dso->long_name))))) {
+ al->filtered |= (1 << HIST_FILTER__DSO);
+ }
+
+ al->sym = map__find_symbol(al->map, al->addr,
+ machine->symbol_filter);
+ }
+
+ if (symbol_conf.sym_list &&
+ (!al->sym || !strlist__has_entry(symbol_conf.sym_list,
+ al->sym->name))) {
+ al->filtered |= (1 << HIST_FILTER__SYMBOL);
+ }
+
+ return 0;
+}
diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h
new file mode 100644
index 00000000000..e5dd40addb3
--- /dev/null
+++ b/tools/perf/util/event.h
@@ -0,0 +1,316 @@
+#ifndef __PERF_RECORD_H
+#define __PERF_RECORD_H
+
+#include <limits.h>
+#include <stdio.h>
+
+#include "../perf.h"
+#include "map.h"
+#include "build-id.h"
+#include "perf_regs.h"
+
+struct mmap_event {
+ struct perf_event_header header;
+ u32 pid, tid;
+ u64 start;
+ u64 len;
+ u64 pgoff;
+ char filename[PATH_MAX];
+};
+
+struct mmap2_event {
+ struct perf_event_header header;
+ u32 pid, tid;
+ u64 start;
+ u64 len;
+ u64 pgoff;
+ u32 maj;
+ u32 min;
+ u64 ino;
+ u64 ino_generation;
+ u32 prot;
+ u32 flags;
+ char filename[PATH_MAX];
+};
+
+struct comm_event {
+ struct perf_event_header header;
+ u32 pid, tid;
+ char comm[16];
+};
+
+struct fork_event {
+ struct perf_event_header header;
+ u32 pid, ppid;
+ u32 tid, ptid;
+ u64 time;
+};
+
+struct lost_event {
+ struct perf_event_header header;
+ u64 id;
+ u64 lost;
+};
+
+/*
+ * PERF_FORMAT_ENABLED | PERF_FORMAT_RUNNING | PERF_FORMAT_ID
+ */
+struct read_event {
+ struct perf_event_header header;
+ u32 pid, tid;
+ u64 value;
+ u64 time_enabled;
+ u64 time_running;
+ u64 id;
+};
+
+struct throttle_event {
+ struct perf_event_header header;
+ u64 time;
+ u64 id;
+ u64 stream_id;
+};
+
+#define PERF_SAMPLE_MASK \
+ (PERF_SAMPLE_IP | PERF_SAMPLE_TID | \
+ PERF_SAMPLE_TIME | PERF_SAMPLE_ADDR | \
+ PERF_SAMPLE_ID | PERF_SAMPLE_STREAM_ID | \
+ PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD | \
+ PERF_SAMPLE_IDENTIFIER)
+
+/* perf sample has 16 bits size limit */
+#define PERF_SAMPLE_MAX_SIZE (1 << 16)
+
+struct sample_event {
+ struct perf_event_header header;
+ u64 array[];
+};
+
+struct regs_dump {
+ u64 abi;
+ u64 mask;
+ u64 *regs;
+
+ /* Cached values/mask filled by first register access. */
+ u64 cache_regs[PERF_REGS_MAX];
+ u64 cache_mask;
+};
+
+struct stack_dump {
+ u16 offset;
+ u64 size;
+ char *data;
+};
+
+struct sample_read_value {
+ u64 value;
+ u64 id;
+};
+
+struct sample_read {
+ u64 time_enabled;
+ u64 time_running;
+ union {
+ struct {
+ u64 nr;
+ struct sample_read_value *values;
+ } group;
+ struct sample_read_value one;
+ };
+};
+
+struct ip_callchain {
+ u64 nr;
+ u64 ips[0];
+};
+
+struct branch_flags {
+ u64 mispred:1;
+ u64 predicted:1;
+ u64 in_tx:1;
+ u64 abort:1;
+ u64 reserved:60;
+};
+
+struct branch_entry {
+ u64 from;
+ u64 to;
+ struct branch_flags flags;
+};
+
+struct branch_stack {
+ u64 nr;
+ struct branch_entry entries[0];
+};
+
+struct perf_sample {
+ u64 ip;
+ u32 pid, tid;
+ u64 time;
+ u64 addr;
+ u64 id;
+ u64 stream_id;
+ u64 period;
+ u64 weight;
+ u64 transaction;
+ u32 cpu;
+ u32 raw_size;
+ u64 data_src;
+ void *raw_data;
+ struct ip_callchain *callchain;
+ struct branch_stack *branch_stack;
+ struct regs_dump user_regs;
+ struct stack_dump user_stack;
+ struct sample_read read;
+};
+
+#define PERF_MEM_DATA_SRC_NONE \
+ (PERF_MEM_S(OP, NA) |\
+ PERF_MEM_S(LVL, NA) |\
+ PERF_MEM_S(SNOOP, NA) |\
+ PERF_MEM_S(LOCK, NA) |\
+ PERF_MEM_S(TLB, NA))
+
+struct build_id_event {
+ struct perf_event_header header;
+ pid_t pid;
+ u8 build_id[PERF_ALIGN(BUILD_ID_SIZE, sizeof(u64))];
+ char filename[];
+};
+
+enum perf_user_event_type { /* above any possible kernel type */
+ PERF_RECORD_USER_TYPE_START = 64,
+ PERF_RECORD_HEADER_ATTR = 64,
+ PERF_RECORD_HEADER_EVENT_TYPE = 65, /* depreceated */
+ PERF_RECORD_HEADER_TRACING_DATA = 66,
+ PERF_RECORD_HEADER_BUILD_ID = 67,
+ PERF_RECORD_FINISHED_ROUND = 68,
+ PERF_RECORD_HEADER_MAX
+};
+
+struct attr_event {
+ struct perf_event_header header;
+ struct perf_event_attr attr;
+ u64 id[];
+};
+
+#define MAX_EVENT_NAME 64
+
+struct perf_trace_event_type {
+ u64 event_id;
+ char name[MAX_EVENT_NAME];
+};
+
+struct event_type_event {
+ struct perf_event_header header;
+ struct perf_trace_event_type event_type;
+};
+
+struct tracing_data_event {
+ struct perf_event_header header;
+ u32 size;
+};
+
+union perf_event {
+ struct perf_event_header header;
+ struct mmap_event mmap;
+ struct mmap2_event mmap2;
+ struct comm_event comm;
+ struct fork_event fork;
+ struct lost_event lost;
+ struct read_event read;
+ struct throttle_event throttle;
+ struct sample_event sample;
+ struct attr_event attr;
+ struct event_type_event event_type;
+ struct tracing_data_event tracing_data;
+ struct build_id_event build_id;
+};
+
+void perf_event__print_totals(void);
+
+struct perf_tool;
+struct thread_map;
+
+typedef int (*perf_event__handler_t)(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine);
+
+int perf_event__synthesize_thread_map(struct perf_tool *tool,
+ struct thread_map *threads,
+ perf_event__handler_t process,
+ struct machine *machine, bool mmap_data);
+int perf_event__synthesize_threads(struct perf_tool *tool,
+ perf_event__handler_t process,
+ struct machine *machine, bool mmap_data);
+int perf_event__synthesize_kernel_mmap(struct perf_tool *tool,
+ perf_event__handler_t process,
+ struct machine *machine);
+
+int perf_event__synthesize_modules(struct perf_tool *tool,
+ perf_event__handler_t process,
+ struct machine *machine);
+
+int perf_event__process_comm(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine);
+int perf_event__process_lost(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine);
+int perf_event__process_mmap(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine);
+int perf_event__process_mmap2(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine);
+int perf_event__process_fork(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine);
+int perf_event__process_exit(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine);
+int perf_event__process(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine);
+
+struct addr_location;
+
+int perf_event__preprocess_sample(const union perf_event *event,
+ struct machine *machine,
+ struct addr_location *al,
+ struct perf_sample *sample);
+
+const char *perf_event__name(unsigned int id);
+
+size_t perf_event__sample_event_size(const struct perf_sample *sample, u64 type,
+ u64 read_format);
+int perf_event__synthesize_sample(union perf_event *event, u64 type,
+ u64 read_format,
+ const struct perf_sample *sample,
+ bool swapped);
+
+int perf_event__synthesize_mmap_events(struct perf_tool *tool,
+ union perf_event *event,
+ pid_t pid, pid_t tgid,
+ perf_event__handler_t process,
+ struct machine *machine,
+ bool mmap_data);
+
+size_t perf_event__fprintf_comm(union perf_event *event, FILE *fp);
+size_t perf_event__fprintf_mmap(union perf_event *event, FILE *fp);
+size_t perf_event__fprintf_mmap2(union perf_event *event, FILE *fp);
+size_t perf_event__fprintf_task(union perf_event *event, FILE *fp);
+size_t perf_event__fprintf(union perf_event *event, FILE *fp);
+
+u64 kallsyms__get_function_start(const char *kallsyms_filename,
+ const char *symbol_name);
+
+#endif /* __PERF_RECORD_H */
diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c
new file mode 100644
index 00000000000..59ef2802fcf
--- /dev/null
+++ b/tools/perf/util/evlist.c
@@ -0,0 +1,1245 @@
+/*
+ * Copyright (C) 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
+ *
+ * Parts came from builtin-{top,stat,record}.c, see those files for further
+ * copyright notes.
+ *
+ * Released under the GPL v2. (and only v2, not any later version)
+ */
+#include "util.h"
+#include <api/fs/debugfs.h>
+#include <poll.h>
+#include "cpumap.h"
+#include "thread_map.h"
+#include "target.h"
+#include "evlist.h"
+#include "evsel.h"
+#include "debug.h"
+#include <unistd.h>
+
+#include "parse-events.h"
+#include "parse-options.h"
+
+#include <sys/mman.h>
+
+#include <linux/bitops.h>
+#include <linux/hash.h>
+
+#define FD(e, x, y) (*(int *)xyarray__entry(e->fd, x, y))
+#define SID(e, x, y) xyarray__entry(e->sample_id, x, y)
+
+void perf_evlist__init(struct perf_evlist *evlist, struct cpu_map *cpus,
+ struct thread_map *threads)
+{
+ int i;
+
+ for (i = 0; i < PERF_EVLIST__HLIST_SIZE; ++i)
+ INIT_HLIST_HEAD(&evlist->heads[i]);
+ INIT_LIST_HEAD(&evlist->entries);
+ perf_evlist__set_maps(evlist, cpus, threads);
+ evlist->workload.pid = -1;
+}
+
+struct perf_evlist *perf_evlist__new(void)
+{
+ struct perf_evlist *evlist = zalloc(sizeof(*evlist));
+
+ if (evlist != NULL)
+ perf_evlist__init(evlist, NULL, NULL);
+
+ return evlist;
+}
+
+struct perf_evlist *perf_evlist__new_default(void)
+{
+ struct perf_evlist *evlist = perf_evlist__new();
+
+ if (evlist && perf_evlist__add_default(evlist)) {
+ perf_evlist__delete(evlist);
+ evlist = NULL;
+ }
+
+ return evlist;
+}
+
+/**
+ * perf_evlist__set_id_pos - set the positions of event ids.
+ * @evlist: selected event list
+ *
+ * Events with compatible sample types all have the same id_pos
+ * and is_pos. For convenience, put a copy on evlist.
+ */
+void perf_evlist__set_id_pos(struct perf_evlist *evlist)
+{
+ struct perf_evsel *first = perf_evlist__first(evlist);
+
+ evlist->id_pos = first->id_pos;
+ evlist->is_pos = first->is_pos;
+}
+
+static void perf_evlist__update_id_pos(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel;
+
+ evlist__for_each(evlist, evsel)
+ perf_evsel__calc_id_pos(evsel);
+
+ perf_evlist__set_id_pos(evlist);
+}
+
+static void perf_evlist__purge(struct perf_evlist *evlist)
+{
+ struct perf_evsel *pos, *n;
+
+ evlist__for_each_safe(evlist, n, pos) {
+ list_del_init(&pos->node);
+ perf_evsel__delete(pos);
+ }
+
+ evlist->nr_entries = 0;
+}
+
+void perf_evlist__exit(struct perf_evlist *evlist)
+{
+ zfree(&evlist->mmap);
+ zfree(&evlist->pollfd);
+}
+
+void perf_evlist__delete(struct perf_evlist *evlist)
+{
+ perf_evlist__munmap(evlist);
+ perf_evlist__close(evlist);
+ cpu_map__delete(evlist->cpus);
+ thread_map__delete(evlist->threads);
+ evlist->cpus = NULL;
+ evlist->threads = NULL;
+ perf_evlist__purge(evlist);
+ perf_evlist__exit(evlist);
+ free(evlist);
+}
+
+void perf_evlist__add(struct perf_evlist *evlist, struct perf_evsel *entry)
+{
+ list_add_tail(&entry->node, &evlist->entries);
+ entry->idx = evlist->nr_entries;
+
+ if (!evlist->nr_entries++)
+ perf_evlist__set_id_pos(evlist);
+}
+
+void perf_evlist__splice_list_tail(struct perf_evlist *evlist,
+ struct list_head *list,
+ int nr_entries)
+{
+ bool set_id_pos = !evlist->nr_entries;
+
+ list_splice_tail(list, &evlist->entries);
+ evlist->nr_entries += nr_entries;
+ if (set_id_pos)
+ perf_evlist__set_id_pos(evlist);
+}
+
+void __perf_evlist__set_leader(struct list_head *list)
+{
+ struct perf_evsel *evsel, *leader;
+
+ leader = list_entry(list->next, struct perf_evsel, node);
+ evsel = list_entry(list->prev, struct perf_evsel, node);
+
+ leader->nr_members = evsel->idx - leader->idx + 1;
+
+ __evlist__for_each(list, evsel) {
+ evsel->leader = leader;
+ }
+}
+
+void perf_evlist__set_leader(struct perf_evlist *evlist)
+{
+ if (evlist->nr_entries) {
+ evlist->nr_groups = evlist->nr_entries > 1 ? 1 : 0;
+ __perf_evlist__set_leader(&evlist->entries);
+ }
+}
+
+int perf_evlist__add_default(struct perf_evlist *evlist)
+{
+ struct perf_event_attr attr = {
+ .type = PERF_TYPE_HARDWARE,
+ .config = PERF_COUNT_HW_CPU_CYCLES,
+ };
+ struct perf_evsel *evsel;
+
+ event_attr_init(&attr);
+
+ evsel = perf_evsel__new(&attr);
+ if (evsel == NULL)
+ goto error;
+
+ /* use strdup() because free(evsel) assumes name is allocated */
+ evsel->name = strdup("cycles");
+ if (!evsel->name)
+ goto error_free;
+
+ perf_evlist__add(evlist, evsel);
+ return 0;
+error_free:
+ perf_evsel__delete(evsel);
+error:
+ return -ENOMEM;
+}
+
+static int perf_evlist__add_attrs(struct perf_evlist *evlist,
+ struct perf_event_attr *attrs, size_t nr_attrs)
+{
+ struct perf_evsel *evsel, *n;
+ LIST_HEAD(head);
+ size_t i;
+
+ for (i = 0; i < nr_attrs; i++) {
+ evsel = perf_evsel__new_idx(attrs + i, evlist->nr_entries + i);
+ if (evsel == NULL)
+ goto out_delete_partial_list;
+ list_add_tail(&evsel->node, &head);
+ }
+
+ perf_evlist__splice_list_tail(evlist, &head, nr_attrs);
+
+ return 0;
+
+out_delete_partial_list:
+ __evlist__for_each_safe(&head, n, evsel)
+ perf_evsel__delete(evsel);
+ return -1;
+}
+
+int __perf_evlist__add_default_attrs(struct perf_evlist *evlist,
+ struct perf_event_attr *attrs, size_t nr_attrs)
+{
+ size_t i;
+
+ for (i = 0; i < nr_attrs; i++)
+ event_attr_init(attrs + i);
+
+ return perf_evlist__add_attrs(evlist, attrs, nr_attrs);
+}
+
+struct perf_evsel *
+perf_evlist__find_tracepoint_by_id(struct perf_evlist *evlist, int id)
+{
+ struct perf_evsel *evsel;
+
+ evlist__for_each(evlist, evsel) {
+ if (evsel->attr.type == PERF_TYPE_TRACEPOINT &&
+ (int)evsel->attr.config == id)
+ return evsel;
+ }
+
+ return NULL;
+}
+
+struct perf_evsel *
+perf_evlist__find_tracepoint_by_name(struct perf_evlist *evlist,
+ const char *name)
+{
+ struct perf_evsel *evsel;
+
+ evlist__for_each(evlist, evsel) {
+ if ((evsel->attr.type == PERF_TYPE_TRACEPOINT) &&
+ (strcmp(evsel->name, name) == 0))
+ return evsel;
+ }
+
+ return NULL;
+}
+
+int perf_evlist__add_newtp(struct perf_evlist *evlist,
+ const char *sys, const char *name, void *handler)
+{
+ struct perf_evsel *evsel = perf_evsel__newtp(sys, name);
+
+ if (evsel == NULL)
+ return -1;
+
+ evsel->handler = handler;
+ perf_evlist__add(evlist, evsel);
+ return 0;
+}
+
+void perf_evlist__disable(struct perf_evlist *evlist)
+{
+ int cpu, thread;
+ struct perf_evsel *pos;
+ int nr_cpus = cpu_map__nr(evlist->cpus);
+ int nr_threads = thread_map__nr(evlist->threads);
+
+ for (cpu = 0; cpu < nr_cpus; cpu++) {
+ evlist__for_each(evlist, pos) {
+ if (!perf_evsel__is_group_leader(pos) || !pos->fd)
+ continue;
+ for (thread = 0; thread < nr_threads; thread++)
+ ioctl(FD(pos, cpu, thread),
+ PERF_EVENT_IOC_DISABLE, 0);
+ }
+ }
+}
+
+void perf_evlist__enable(struct perf_evlist *evlist)
+{
+ int cpu, thread;
+ struct perf_evsel *pos;
+ int nr_cpus = cpu_map__nr(evlist->cpus);
+ int nr_threads = thread_map__nr(evlist->threads);
+
+ for (cpu = 0; cpu < nr_cpus; cpu++) {
+ evlist__for_each(evlist, pos) {
+ if (!perf_evsel__is_group_leader(pos) || !pos->fd)
+ continue;
+ for (thread = 0; thread < nr_threads; thread++)
+ ioctl(FD(pos, cpu, thread),
+ PERF_EVENT_IOC_ENABLE, 0);
+ }
+ }
+}
+
+int perf_evlist__disable_event(struct perf_evlist *evlist,
+ struct perf_evsel *evsel)
+{
+ int cpu, thread, err;
+
+ if (!evsel->fd)
+ return 0;
+
+ for (cpu = 0; cpu < evlist->cpus->nr; cpu++) {
+ for (thread = 0; thread < evlist->threads->nr; thread++) {
+ err = ioctl(FD(evsel, cpu, thread),
+ PERF_EVENT_IOC_DISABLE, 0);
+ if (err)
+ return err;
+ }
+ }
+ return 0;
+}
+
+int perf_evlist__enable_event(struct perf_evlist *evlist,
+ struct perf_evsel *evsel)
+{
+ int cpu, thread, err;
+
+ if (!evsel->fd)
+ return -EINVAL;
+
+ for (cpu = 0; cpu < evlist->cpus->nr; cpu++) {
+ for (thread = 0; thread < evlist->threads->nr; thread++) {
+ err = ioctl(FD(evsel, cpu, thread),
+ PERF_EVENT_IOC_ENABLE, 0);
+ if (err)
+ return err;
+ }
+ }
+ return 0;
+}
+
+static int perf_evlist__alloc_pollfd(struct perf_evlist *evlist)
+{
+ int nr_cpus = cpu_map__nr(evlist->cpus);
+ int nr_threads = thread_map__nr(evlist->threads);
+ int nfds = nr_cpus * nr_threads * evlist->nr_entries;
+ evlist->pollfd = malloc(sizeof(struct pollfd) * nfds);
+ return evlist->pollfd != NULL ? 0 : -ENOMEM;
+}
+
+void perf_evlist__add_pollfd(struct perf_evlist *evlist, int fd)
+{
+ fcntl(fd, F_SETFL, O_NONBLOCK);
+ evlist->pollfd[evlist->nr_fds].fd = fd;
+ evlist->pollfd[evlist->nr_fds].events = POLLIN;
+ evlist->nr_fds++;
+}
+
+static void perf_evlist__id_hash(struct perf_evlist *evlist,
+ struct perf_evsel *evsel,
+ int cpu, int thread, u64 id)
+{
+ int hash;
+ struct perf_sample_id *sid = SID(evsel, cpu, thread);
+
+ sid->id = id;
+ sid->evsel = evsel;
+ hash = hash_64(sid->id, PERF_EVLIST__HLIST_BITS);
+ hlist_add_head(&sid->node, &evlist->heads[hash]);
+}
+
+void perf_evlist__id_add(struct perf_evlist *evlist, struct perf_evsel *evsel,
+ int cpu, int thread, u64 id)
+{
+ perf_evlist__id_hash(evlist, evsel, cpu, thread, id);
+ evsel->id[evsel->ids++] = id;
+}
+
+static int perf_evlist__id_add_fd(struct perf_evlist *evlist,
+ struct perf_evsel *evsel,
+ int cpu, int thread, int fd)
+{
+ u64 read_data[4] = { 0, };
+ int id_idx = 1; /* The first entry is the counter value */
+ u64 id;
+ int ret;
+
+ ret = ioctl(fd, PERF_EVENT_IOC_ID, &id);
+ if (!ret)
+ goto add;
+
+ if (errno != ENOTTY)
+ return -1;
+
+ /* Legacy way to get event id.. All hail to old kernels! */
+
+ /*
+ * This way does not work with group format read, so bail
+ * out in that case.
+ */
+ if (perf_evlist__read_format(evlist) & PERF_FORMAT_GROUP)
+ return -1;
+
+ if (!(evsel->attr.read_format & PERF_FORMAT_ID) ||
+ read(fd, &read_data, sizeof(read_data)) == -1)
+ return -1;
+
+ if (evsel->attr.read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ ++id_idx;
+ if (evsel->attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ ++id_idx;
+
+ id = read_data[id_idx];
+
+ add:
+ perf_evlist__id_add(evlist, evsel, cpu, thread, id);
+ return 0;
+}
+
+struct perf_sample_id *perf_evlist__id2sid(struct perf_evlist *evlist, u64 id)
+{
+ struct hlist_head *head;
+ struct perf_sample_id *sid;
+ int hash;
+
+ hash = hash_64(id, PERF_EVLIST__HLIST_BITS);
+ head = &evlist->heads[hash];
+
+ hlist_for_each_entry(sid, head, node)
+ if (sid->id == id)
+ return sid;
+
+ return NULL;
+}
+
+struct perf_evsel *perf_evlist__id2evsel(struct perf_evlist *evlist, u64 id)
+{
+ struct perf_sample_id *sid;
+
+ if (evlist->nr_entries == 1)
+ return perf_evlist__first(evlist);
+
+ sid = perf_evlist__id2sid(evlist, id);
+ if (sid)
+ return sid->evsel;
+
+ if (!perf_evlist__sample_id_all(evlist))
+ return perf_evlist__first(evlist);
+
+ return NULL;
+}
+
+static int perf_evlist__event2id(struct perf_evlist *evlist,
+ union perf_event *event, u64 *id)
+{
+ const u64 *array = event->sample.array;
+ ssize_t n;
+
+ n = (event->header.size - sizeof(event->header)) >> 3;
+
+ if (event->header.type == PERF_RECORD_SAMPLE) {
+ if (evlist->id_pos >= n)
+ return -1;
+ *id = array[evlist->id_pos];
+ } else {
+ if (evlist->is_pos > n)
+ return -1;
+ n -= evlist->is_pos;
+ *id = array[n];
+ }
+ return 0;
+}
+
+static struct perf_evsel *perf_evlist__event2evsel(struct perf_evlist *evlist,
+ union perf_event *event)
+{
+ struct perf_evsel *first = perf_evlist__first(evlist);
+ struct hlist_head *head;
+ struct perf_sample_id *sid;
+ int hash;
+ u64 id;
+
+ if (evlist->nr_entries == 1)
+ return first;
+
+ if (!first->attr.sample_id_all &&
+ event->header.type != PERF_RECORD_SAMPLE)
+ return first;
+
+ if (perf_evlist__event2id(evlist, event, &id))
+ return NULL;
+
+ /* Synthesized events have an id of zero */
+ if (!id)
+ return first;
+
+ hash = hash_64(id, PERF_EVLIST__HLIST_BITS);
+ head = &evlist->heads[hash];
+
+ hlist_for_each_entry(sid, head, node) {
+ if (sid->id == id)
+ return sid->evsel;
+ }
+ return NULL;
+}
+
+union perf_event *perf_evlist__mmap_read(struct perf_evlist *evlist, int idx)
+{
+ struct perf_mmap *md = &evlist->mmap[idx];
+ unsigned int head = perf_mmap__read_head(md);
+ unsigned int old = md->prev;
+ unsigned char *data = md->base + page_size;
+ union perf_event *event = NULL;
+
+ if (evlist->overwrite) {
+ /*
+ * If we're further behind than half the buffer, there's a chance
+ * the writer will bite our tail and mess up the samples under us.
+ *
+ * If we somehow ended up ahead of the head, we got messed up.
+ *
+ * In either case, truncate and restart at head.
+ */
+ int diff = head - old;
+ if (diff > md->mask / 2 || diff < 0) {
+ fprintf(stderr, "WARNING: failed to keep up with mmap data.\n");
+
+ /*
+ * head points to a known good entry, start there.
+ */
+ old = head;
+ }
+ }
+
+ if (old != head) {
+ size_t size;
+
+ event = (union perf_event *)&data[old & md->mask];
+ size = event->header.size;
+
+ /*
+ * Event straddles the mmap boundary -- header should always
+ * be inside due to u64 alignment of output.
+ */
+ if ((old & md->mask) + size != ((old + size) & md->mask)) {
+ unsigned int offset = old;
+ unsigned int len = min(sizeof(*event), size), cpy;
+ void *dst = md->event_copy;
+
+ do {
+ cpy = min(md->mask + 1 - (offset & md->mask), len);
+ memcpy(dst, &data[offset & md->mask], cpy);
+ offset += cpy;
+ dst += cpy;
+ len -= cpy;
+ } while (len);
+
+ event = (union perf_event *) md->event_copy;
+ }
+
+ old += size;
+ }
+
+ md->prev = old;
+
+ return event;
+}
+
+void perf_evlist__mmap_consume(struct perf_evlist *evlist, int idx)
+{
+ if (!evlist->overwrite) {
+ struct perf_mmap *md = &evlist->mmap[idx];
+ unsigned int old = md->prev;
+
+ perf_mmap__write_tail(md, old);
+ }
+}
+
+static void __perf_evlist__munmap(struct perf_evlist *evlist, int idx)
+{
+ if (evlist->mmap[idx].base != NULL) {
+ munmap(evlist->mmap[idx].base, evlist->mmap_len);
+ evlist->mmap[idx].base = NULL;
+ }
+}
+
+void perf_evlist__munmap(struct perf_evlist *evlist)
+{
+ int i;
+
+ if (evlist->mmap == NULL)
+ return;
+
+ for (i = 0; i < evlist->nr_mmaps; i++)
+ __perf_evlist__munmap(evlist, i);
+
+ zfree(&evlist->mmap);
+}
+
+static int perf_evlist__alloc_mmap(struct perf_evlist *evlist)
+{
+ evlist->nr_mmaps = cpu_map__nr(evlist->cpus);
+ if (cpu_map__empty(evlist->cpus))
+ evlist->nr_mmaps = thread_map__nr(evlist->threads);
+ evlist->mmap = zalloc(evlist->nr_mmaps * sizeof(struct perf_mmap));
+ return evlist->mmap != NULL ? 0 : -ENOMEM;
+}
+
+static int __perf_evlist__mmap(struct perf_evlist *evlist,
+ int idx, int prot, int mask, int fd)
+{
+ evlist->mmap[idx].prev = 0;
+ evlist->mmap[idx].mask = mask;
+ evlist->mmap[idx].base = mmap(NULL, evlist->mmap_len, prot,
+ MAP_SHARED, fd, 0);
+ if (evlist->mmap[idx].base == MAP_FAILED) {
+ pr_debug2("failed to mmap perf event ring buffer, error %d\n",
+ errno);
+ evlist->mmap[idx].base = NULL;
+ return -1;
+ }
+
+ perf_evlist__add_pollfd(evlist, fd);
+ return 0;
+}
+
+static int perf_evlist__mmap_per_evsel(struct perf_evlist *evlist, int idx,
+ int prot, int mask, int cpu, int thread,
+ int *output)
+{
+ struct perf_evsel *evsel;
+
+ evlist__for_each(evlist, evsel) {
+ int fd = FD(evsel, cpu, thread);
+
+ if (*output == -1) {
+ *output = fd;
+ if (__perf_evlist__mmap(evlist, idx, prot, mask,
+ *output) < 0)
+ return -1;
+ } else {
+ if (ioctl(fd, PERF_EVENT_IOC_SET_OUTPUT, *output) != 0)
+ return -1;
+ }
+
+ if ((evsel->attr.read_format & PERF_FORMAT_ID) &&
+ perf_evlist__id_add_fd(evlist, evsel, cpu, thread, fd) < 0)
+ return -1;
+ }
+
+ return 0;
+}
+
+static int perf_evlist__mmap_per_cpu(struct perf_evlist *evlist, int prot,
+ int mask)
+{
+ int cpu, thread;
+ int nr_cpus = cpu_map__nr(evlist->cpus);
+ int nr_threads = thread_map__nr(evlist->threads);
+
+ pr_debug2("perf event ring buffer mmapped per cpu\n");
+ for (cpu = 0; cpu < nr_cpus; cpu++) {
+ int output = -1;
+
+ for (thread = 0; thread < nr_threads; thread++) {
+ if (perf_evlist__mmap_per_evsel(evlist, cpu, prot, mask,
+ cpu, thread, &output))
+ goto out_unmap;
+ }
+ }
+
+ return 0;
+
+out_unmap:
+ for (cpu = 0; cpu < nr_cpus; cpu++)
+ __perf_evlist__munmap(evlist, cpu);
+ return -1;
+}
+
+static int perf_evlist__mmap_per_thread(struct perf_evlist *evlist, int prot,
+ int mask)
+{
+ int thread;
+ int nr_threads = thread_map__nr(evlist->threads);
+
+ pr_debug2("perf event ring buffer mmapped per thread\n");
+ for (thread = 0; thread < nr_threads; thread++) {
+ int output = -1;
+
+ if (perf_evlist__mmap_per_evsel(evlist, thread, prot, mask, 0,
+ thread, &output))
+ goto out_unmap;
+ }
+
+ return 0;
+
+out_unmap:
+ for (thread = 0; thread < nr_threads; thread++)
+ __perf_evlist__munmap(evlist, thread);
+ return -1;
+}
+
+static size_t perf_evlist__mmap_size(unsigned long pages)
+{
+ /* 512 kiB: default amount of unprivileged mlocked memory */
+ if (pages == UINT_MAX)
+ pages = (512 * 1024) / page_size;
+ else if (!is_power_of_2(pages))
+ return 0;
+
+ return (pages + 1) * page_size;
+}
+
+static long parse_pages_arg(const char *str, unsigned long min,
+ unsigned long max)
+{
+ unsigned long pages, val;
+ static struct parse_tag tags[] = {
+ { .tag = 'B', .mult = 1 },
+ { .tag = 'K', .mult = 1 << 10 },
+ { .tag = 'M', .mult = 1 << 20 },
+ { .tag = 'G', .mult = 1 << 30 },
+ { .tag = 0 },
+ };
+
+ if (str == NULL)
+ return -EINVAL;
+
+ val = parse_tag_value(str, tags);
+ if (val != (unsigned long) -1) {
+ /* we got file size value */
+ pages = PERF_ALIGN(val, page_size) / page_size;
+ } else {
+ /* we got pages count value */
+ char *eptr;
+ pages = strtoul(str, &eptr, 10);
+ if (*eptr != '\0')
+ return -EINVAL;
+ }
+
+ if (pages == 0 && min == 0) {
+ /* leave number of pages at 0 */
+ } else if (!is_power_of_2(pages)) {
+ /* round pages up to next power of 2 */
+ pages = next_pow2_l(pages);
+ if (!pages)
+ return -EINVAL;
+ pr_info("rounding mmap pages size to %lu bytes (%lu pages)\n",
+ pages * page_size, pages);
+ }
+
+ if (pages > max)
+ return -EINVAL;
+
+ return pages;
+}
+
+int perf_evlist__parse_mmap_pages(const struct option *opt, const char *str,
+ int unset __maybe_unused)
+{
+ unsigned int *mmap_pages = opt->value;
+ unsigned long max = UINT_MAX;
+ long pages;
+
+ if (max > SIZE_MAX / page_size)
+ max = SIZE_MAX / page_size;
+
+ pages = parse_pages_arg(str, 1, max);
+ if (pages < 0) {
+ pr_err("Invalid argument for --mmap_pages/-m\n");
+ return -1;
+ }
+
+ *mmap_pages = pages;
+ return 0;
+}
+
+/**
+ * perf_evlist__mmap - Create mmaps to receive events.
+ * @evlist: list of events
+ * @pages: map length in pages
+ * @overwrite: overwrite older events?
+ *
+ * If @overwrite is %false the user needs to signal event consumption using
+ * perf_mmap__write_tail(). Using perf_evlist__mmap_read() does this
+ * automatically.
+ *
+ * Return: %0 on success, negative error code otherwise.
+ */
+int perf_evlist__mmap(struct perf_evlist *evlist, unsigned int pages,
+ bool overwrite)
+{
+ struct perf_evsel *evsel;
+ const struct cpu_map *cpus = evlist->cpus;
+ const struct thread_map *threads = evlist->threads;
+ int prot = PROT_READ | (overwrite ? 0 : PROT_WRITE), mask;
+
+ if (evlist->mmap == NULL && perf_evlist__alloc_mmap(evlist) < 0)
+ return -ENOMEM;
+
+ if (evlist->pollfd == NULL && perf_evlist__alloc_pollfd(evlist) < 0)
+ return -ENOMEM;
+
+ evlist->overwrite = overwrite;
+ evlist->mmap_len = perf_evlist__mmap_size(pages);
+ pr_debug("mmap size %zuB\n", evlist->mmap_len);
+ mask = evlist->mmap_len - page_size - 1;
+
+ evlist__for_each(evlist, evsel) {
+ if ((evsel->attr.read_format & PERF_FORMAT_ID) &&
+ evsel->sample_id == NULL &&
+ perf_evsel__alloc_id(evsel, cpu_map__nr(cpus), threads->nr) < 0)
+ return -ENOMEM;
+ }
+
+ if (cpu_map__empty(cpus))
+ return perf_evlist__mmap_per_thread(evlist, prot, mask);
+
+ return perf_evlist__mmap_per_cpu(evlist, prot, mask);
+}
+
+int perf_evlist__create_maps(struct perf_evlist *evlist, struct target *target)
+{
+ evlist->threads = thread_map__new_str(target->pid, target->tid,
+ target->uid);
+
+ if (evlist->threads == NULL)
+ return -1;
+
+ if (target__uses_dummy_map(target))
+ evlist->cpus = cpu_map__dummy_new();
+ else
+ evlist->cpus = cpu_map__new(target->cpu_list);
+
+ if (evlist->cpus == NULL)
+ goto out_delete_threads;
+
+ return 0;
+
+out_delete_threads:
+ thread_map__delete(evlist->threads);
+ return -1;
+}
+
+int perf_evlist__apply_filters(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel;
+ int err = 0;
+ const int ncpus = cpu_map__nr(evlist->cpus),
+ nthreads = thread_map__nr(evlist->threads);
+
+ evlist__for_each(evlist, evsel) {
+ if (evsel->filter == NULL)
+ continue;
+
+ err = perf_evsel__set_filter(evsel, ncpus, nthreads, evsel->filter);
+ if (err)
+ break;
+ }
+
+ return err;
+}
+
+int perf_evlist__set_filter(struct perf_evlist *evlist, const char *filter)
+{
+ struct perf_evsel *evsel;
+ int err = 0;
+ const int ncpus = cpu_map__nr(evlist->cpus),
+ nthreads = thread_map__nr(evlist->threads);
+
+ evlist__for_each(evlist, evsel) {
+ err = perf_evsel__set_filter(evsel, ncpus, nthreads, filter);
+ if (err)
+ break;
+ }
+
+ return err;
+}
+
+bool perf_evlist__valid_sample_type(struct perf_evlist *evlist)
+{
+ struct perf_evsel *pos;
+
+ if (evlist->nr_entries == 1)
+ return true;
+
+ if (evlist->id_pos < 0 || evlist->is_pos < 0)
+ return false;
+
+ evlist__for_each(evlist, pos) {
+ if (pos->id_pos != evlist->id_pos ||
+ pos->is_pos != evlist->is_pos)
+ return false;
+ }
+
+ return true;
+}
+
+u64 __perf_evlist__combined_sample_type(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel;
+
+ if (evlist->combined_sample_type)
+ return evlist->combined_sample_type;
+
+ evlist__for_each(evlist, evsel)
+ evlist->combined_sample_type |= evsel->attr.sample_type;
+
+ return evlist->combined_sample_type;
+}
+
+u64 perf_evlist__combined_sample_type(struct perf_evlist *evlist)
+{
+ evlist->combined_sample_type = 0;
+ return __perf_evlist__combined_sample_type(evlist);
+}
+
+bool perf_evlist__valid_read_format(struct perf_evlist *evlist)
+{
+ struct perf_evsel *first = perf_evlist__first(evlist), *pos = first;
+ u64 read_format = first->attr.read_format;
+ u64 sample_type = first->attr.sample_type;
+
+ evlist__for_each(evlist, pos) {
+ if (read_format != pos->attr.read_format)
+ return false;
+ }
+
+ /* PERF_SAMPLE_READ imples PERF_FORMAT_ID. */
+ if ((sample_type & PERF_SAMPLE_READ) &&
+ !(read_format & PERF_FORMAT_ID)) {
+ return false;
+ }
+
+ return true;
+}
+
+u64 perf_evlist__read_format(struct perf_evlist *evlist)
+{
+ struct perf_evsel *first = perf_evlist__first(evlist);
+ return first->attr.read_format;
+}
+
+u16 perf_evlist__id_hdr_size(struct perf_evlist *evlist)
+{
+ struct perf_evsel *first = perf_evlist__first(evlist);
+ struct perf_sample *data;
+ u64 sample_type;
+ u16 size = 0;
+
+ if (!first->attr.sample_id_all)
+ goto out;
+
+ sample_type = first->attr.sample_type;
+
+ if (sample_type & PERF_SAMPLE_TID)
+ size += sizeof(data->tid) * 2;
+
+ if (sample_type & PERF_SAMPLE_TIME)
+ size += sizeof(data->time);
+
+ if (sample_type & PERF_SAMPLE_ID)
+ size += sizeof(data->id);
+
+ if (sample_type & PERF_SAMPLE_STREAM_ID)
+ size += sizeof(data->stream_id);
+
+ if (sample_type & PERF_SAMPLE_CPU)
+ size += sizeof(data->cpu) * 2;
+
+ if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ size += sizeof(data->id);
+out:
+ return size;
+}
+
+bool perf_evlist__valid_sample_id_all(struct perf_evlist *evlist)
+{
+ struct perf_evsel *first = perf_evlist__first(evlist), *pos = first;
+
+ evlist__for_each_continue(evlist, pos) {
+ if (first->attr.sample_id_all != pos->attr.sample_id_all)
+ return false;
+ }
+
+ return true;
+}
+
+bool perf_evlist__sample_id_all(struct perf_evlist *evlist)
+{
+ struct perf_evsel *first = perf_evlist__first(evlist);
+ return first->attr.sample_id_all;
+}
+
+void perf_evlist__set_selected(struct perf_evlist *evlist,
+ struct perf_evsel *evsel)
+{
+ evlist->selected = evsel;
+}
+
+void perf_evlist__close(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel;
+ int ncpus = cpu_map__nr(evlist->cpus);
+ int nthreads = thread_map__nr(evlist->threads);
+ int n;
+
+ evlist__for_each_reverse(evlist, evsel) {
+ n = evsel->cpus ? evsel->cpus->nr : ncpus;
+ perf_evsel__close(evsel, n, nthreads);
+ }
+}
+
+int perf_evlist__open(struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel;
+ int err;
+
+ perf_evlist__update_id_pos(evlist);
+
+ evlist__for_each(evlist, evsel) {
+ err = perf_evsel__open(evsel, evlist->cpus, evlist->threads);
+ if (err < 0)
+ goto out_err;
+ }
+
+ return 0;
+out_err:
+ perf_evlist__close(evlist);
+ errno = -err;
+ return err;
+}
+
+int perf_evlist__prepare_workload(struct perf_evlist *evlist, struct target *target,
+ const char *argv[], bool pipe_output,
+ void (*exec_error)(int signo, siginfo_t *info, void *ucontext))
+{
+ int child_ready_pipe[2], go_pipe[2];
+ char bf;
+
+ if (pipe(child_ready_pipe) < 0) {
+ perror("failed to create 'ready' pipe");
+ return -1;
+ }
+
+ if (pipe(go_pipe) < 0) {
+ perror("failed to create 'go' pipe");
+ goto out_close_ready_pipe;
+ }
+
+ evlist->workload.pid = fork();
+ if (evlist->workload.pid < 0) {
+ perror("failed to fork");
+ goto out_close_pipes;
+ }
+
+ if (!evlist->workload.pid) {
+ if (pipe_output)
+ dup2(2, 1);
+
+ signal(SIGTERM, SIG_DFL);
+
+ close(child_ready_pipe[0]);
+ close(go_pipe[1]);
+ fcntl(go_pipe[0], F_SETFD, FD_CLOEXEC);
+
+ /*
+ * Tell the parent we're ready to go
+ */
+ close(child_ready_pipe[1]);
+
+ /*
+ * Wait until the parent tells us to go.
+ */
+ if (read(go_pipe[0], &bf, 1) == -1)
+ perror("unable to read pipe");
+
+ execvp(argv[0], (char **)argv);
+
+ if (exec_error) {
+ union sigval val;
+
+ val.sival_int = errno;
+ if (sigqueue(getppid(), SIGUSR1, val))
+ perror(argv[0]);
+ } else
+ perror(argv[0]);
+ exit(-1);
+ }
+
+ if (exec_error) {
+ struct sigaction act = {
+ .sa_flags = SA_SIGINFO,
+ .sa_sigaction = exec_error,
+ };
+ sigaction(SIGUSR1, &act, NULL);
+ }
+
+ if (target__none(target))
+ evlist->threads->map[0] = evlist->workload.pid;
+
+ close(child_ready_pipe[1]);
+ close(go_pipe[0]);
+ /*
+ * wait for child to settle
+ */
+ if (read(child_ready_pipe[0], &bf, 1) == -1) {
+ perror("unable to read pipe");
+ goto out_close_pipes;
+ }
+
+ fcntl(go_pipe[1], F_SETFD, FD_CLOEXEC);
+ evlist->workload.cork_fd = go_pipe[1];
+ close(child_ready_pipe[0]);
+ return 0;
+
+out_close_pipes:
+ close(go_pipe[0]);
+ close(go_pipe[1]);
+out_close_ready_pipe:
+ close(child_ready_pipe[0]);
+ close(child_ready_pipe[1]);
+ return -1;
+}
+
+int perf_evlist__start_workload(struct perf_evlist *evlist)
+{
+ if (evlist->workload.cork_fd > 0) {
+ char bf = 0;
+ int ret;
+ /*
+ * Remove the cork, let it rip!
+ */
+ ret = write(evlist->workload.cork_fd, &bf, 1);
+ if (ret < 0)
+ perror("enable to write to pipe");
+
+ close(evlist->workload.cork_fd);
+ return ret;
+ }
+
+ return 0;
+}
+
+int perf_evlist__parse_sample(struct perf_evlist *evlist, union perf_event *event,
+ struct perf_sample *sample)
+{
+ struct perf_evsel *evsel = perf_evlist__event2evsel(evlist, event);
+
+ if (!evsel)
+ return -EFAULT;
+ return perf_evsel__parse_sample(evsel, event, sample);
+}
+
+size_t perf_evlist__fprintf(struct perf_evlist *evlist, FILE *fp)
+{
+ struct perf_evsel *evsel;
+ size_t printed = 0;
+
+ evlist__for_each(evlist, evsel) {
+ printed += fprintf(fp, "%s%s", evsel->idx ? ", " : "",
+ perf_evsel__name(evsel));
+ }
+
+ return printed + fprintf(fp, "\n");
+}
+
+int perf_evlist__strerror_tp(struct perf_evlist *evlist __maybe_unused,
+ int err, char *buf, size_t size)
+{
+ char sbuf[128];
+
+ switch (err) {
+ case ENOENT:
+ scnprintf(buf, size, "%s",
+ "Error:\tUnable to find debugfs\n"
+ "Hint:\tWas your kernel was compiled with debugfs support?\n"
+ "Hint:\tIs the debugfs filesystem mounted?\n"
+ "Hint:\tTry 'sudo mount -t debugfs nodev /sys/kernel/debug'");
+ break;
+ case EACCES:
+ scnprintf(buf, size,
+ "Error:\tNo permissions to read %s/tracing/events/raw_syscalls\n"
+ "Hint:\tTry 'sudo mount -o remount,mode=755 %s'\n",
+ debugfs_mountpoint, debugfs_mountpoint);
+ break;
+ default:
+ scnprintf(buf, size, "%s", strerror_r(err, sbuf, sizeof(sbuf)));
+ break;
+ }
+
+ return 0;
+}
+
+int perf_evlist__strerror_open(struct perf_evlist *evlist __maybe_unused,
+ int err, char *buf, size_t size)
+{
+ int printed, value;
+ char sbuf[128], *emsg = strerror_r(err, sbuf, sizeof(sbuf));
+
+ switch (err) {
+ case EACCES:
+ case EPERM:
+ printed = scnprintf(buf, size,
+ "Error:\t%s.\n"
+ "Hint:\tCheck /proc/sys/kernel/perf_event_paranoid setting.", emsg);
+
+ value = perf_event_paranoid();
+
+ printed += scnprintf(buf + printed, size - printed, "\nHint:\t");
+
+ if (value >= 2) {
+ printed += scnprintf(buf + printed, size - printed,
+ "For your workloads it needs to be <= 1\nHint:\t");
+ }
+ printed += scnprintf(buf + printed, size - printed,
+ "For system wide tracing it needs to be set to -1");
+
+ printed += scnprintf(buf + printed, size - printed,
+ ".\nHint:\tThe current value is %d.", value);
+ break;
+ default:
+ scnprintf(buf, size, "%s", emsg);
+ break;
+ }
+
+ return 0;
+}
+
+void perf_evlist__to_front(struct perf_evlist *evlist,
+ struct perf_evsel *move_evsel)
+{
+ struct perf_evsel *evsel, *n;
+ LIST_HEAD(move);
+
+ if (move_evsel == perf_evlist__first(evlist))
+ return;
+
+ evlist__for_each_safe(evlist, n, evsel) {
+ if (evsel->leader == move_evsel->leader)
+ list_move_tail(&evsel->node, &move);
+ }
+
+ list_splice(&move, &evlist->entries);
+}
diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h
new file mode 100644
index 00000000000..f5173cd6369
--- /dev/null
+++ b/tools/perf/util/evlist.h
@@ -0,0 +1,265 @@
+#ifndef __PERF_EVLIST_H
+#define __PERF_EVLIST_H 1
+
+#include <linux/list.h>
+#include <stdio.h>
+#include "../perf.h"
+#include "event.h"
+#include "evsel.h"
+#include "util.h"
+#include <unistd.h>
+
+struct pollfd;
+struct thread_map;
+struct cpu_map;
+struct record_opts;
+
+#define PERF_EVLIST__HLIST_BITS 8
+#define PERF_EVLIST__HLIST_SIZE (1 << PERF_EVLIST__HLIST_BITS)
+
+struct perf_mmap {
+ void *base;
+ int mask;
+ unsigned int prev;
+ char event_copy[PERF_SAMPLE_MAX_SIZE];
+};
+
+struct perf_evlist {
+ struct list_head entries;
+ struct hlist_head heads[PERF_EVLIST__HLIST_SIZE];
+ int nr_entries;
+ int nr_groups;
+ int nr_fds;
+ int nr_mmaps;
+ size_t mmap_len;
+ int id_pos;
+ int is_pos;
+ u64 combined_sample_type;
+ struct {
+ int cork_fd;
+ pid_t pid;
+ } workload;
+ bool overwrite;
+ struct perf_mmap *mmap;
+ struct pollfd *pollfd;
+ struct thread_map *threads;
+ struct cpu_map *cpus;
+ struct perf_evsel *selected;
+};
+
+struct perf_evsel_str_handler {
+ const char *name;
+ void *handler;
+};
+
+struct perf_evlist *perf_evlist__new(void);
+struct perf_evlist *perf_evlist__new_default(void);
+void perf_evlist__init(struct perf_evlist *evlist, struct cpu_map *cpus,
+ struct thread_map *threads);
+void perf_evlist__exit(struct perf_evlist *evlist);
+void perf_evlist__delete(struct perf_evlist *evlist);
+
+void perf_evlist__add(struct perf_evlist *evlist, struct perf_evsel *entry);
+int perf_evlist__add_default(struct perf_evlist *evlist);
+int __perf_evlist__add_default_attrs(struct perf_evlist *evlist,
+ struct perf_event_attr *attrs, size_t nr_attrs);
+
+#define perf_evlist__add_default_attrs(evlist, array) \
+ __perf_evlist__add_default_attrs(evlist, array, ARRAY_SIZE(array))
+
+int perf_evlist__add_newtp(struct perf_evlist *evlist,
+ const char *sys, const char *name, void *handler);
+
+int perf_evlist__set_filter(struct perf_evlist *evlist, const char *filter);
+
+struct perf_evsel *
+perf_evlist__find_tracepoint_by_id(struct perf_evlist *evlist, int id);
+
+struct perf_evsel *
+perf_evlist__find_tracepoint_by_name(struct perf_evlist *evlist,
+ const char *name);
+
+void perf_evlist__id_add(struct perf_evlist *evlist, struct perf_evsel *evsel,
+ int cpu, int thread, u64 id);
+
+void perf_evlist__add_pollfd(struct perf_evlist *evlist, int fd);
+
+struct perf_evsel *perf_evlist__id2evsel(struct perf_evlist *evlist, u64 id);
+
+struct perf_sample_id *perf_evlist__id2sid(struct perf_evlist *evlist, u64 id);
+
+union perf_event *perf_evlist__mmap_read(struct perf_evlist *evlist, int idx);
+
+void perf_evlist__mmap_consume(struct perf_evlist *evlist, int idx);
+
+int perf_evlist__open(struct perf_evlist *evlist);
+void perf_evlist__close(struct perf_evlist *evlist);
+
+void perf_evlist__set_id_pos(struct perf_evlist *evlist);
+bool perf_can_sample_identifier(void);
+void perf_evlist__config(struct perf_evlist *evlist, struct record_opts *opts);
+int record_opts__config(struct record_opts *opts);
+
+int perf_evlist__prepare_workload(struct perf_evlist *evlist,
+ struct target *target,
+ const char *argv[], bool pipe_output,
+ void (*exec_error)(int signo, siginfo_t *info,
+ void *ucontext));
+int perf_evlist__start_workload(struct perf_evlist *evlist);
+
+int perf_evlist__parse_mmap_pages(const struct option *opt,
+ const char *str,
+ int unset);
+
+int perf_evlist__mmap(struct perf_evlist *evlist, unsigned int pages,
+ bool overwrite);
+void perf_evlist__munmap(struct perf_evlist *evlist);
+
+void perf_evlist__disable(struct perf_evlist *evlist);
+void perf_evlist__enable(struct perf_evlist *evlist);
+
+int perf_evlist__disable_event(struct perf_evlist *evlist,
+ struct perf_evsel *evsel);
+int perf_evlist__enable_event(struct perf_evlist *evlist,
+ struct perf_evsel *evsel);
+
+void perf_evlist__set_selected(struct perf_evlist *evlist,
+ struct perf_evsel *evsel);
+
+static inline void perf_evlist__set_maps(struct perf_evlist *evlist,
+ struct cpu_map *cpus,
+ struct thread_map *threads)
+{
+ evlist->cpus = cpus;
+ evlist->threads = threads;
+}
+
+int perf_evlist__create_maps(struct perf_evlist *evlist, struct target *target);
+int perf_evlist__apply_filters(struct perf_evlist *evlist);
+
+void __perf_evlist__set_leader(struct list_head *list);
+void perf_evlist__set_leader(struct perf_evlist *evlist);
+
+u64 perf_evlist__read_format(struct perf_evlist *evlist);
+u64 __perf_evlist__combined_sample_type(struct perf_evlist *evlist);
+u64 perf_evlist__combined_sample_type(struct perf_evlist *evlist);
+bool perf_evlist__sample_id_all(struct perf_evlist *evlist);
+u16 perf_evlist__id_hdr_size(struct perf_evlist *evlist);
+
+int perf_evlist__parse_sample(struct perf_evlist *evlist, union perf_event *event,
+ struct perf_sample *sample);
+
+bool perf_evlist__valid_sample_type(struct perf_evlist *evlist);
+bool perf_evlist__valid_sample_id_all(struct perf_evlist *evlist);
+bool perf_evlist__valid_read_format(struct perf_evlist *evlist);
+
+void perf_evlist__splice_list_tail(struct perf_evlist *evlist,
+ struct list_head *list,
+ int nr_entries);
+
+static inline struct perf_evsel *perf_evlist__first(struct perf_evlist *evlist)
+{
+ return list_entry(evlist->entries.next, struct perf_evsel, node);
+}
+
+static inline struct perf_evsel *perf_evlist__last(struct perf_evlist *evlist)
+{
+ return list_entry(evlist->entries.prev, struct perf_evsel, node);
+}
+
+size_t perf_evlist__fprintf(struct perf_evlist *evlist, FILE *fp);
+
+int perf_evlist__strerror_tp(struct perf_evlist *evlist, int err, char *buf, size_t size);
+int perf_evlist__strerror_open(struct perf_evlist *evlist, int err, char *buf, size_t size);
+
+static inline unsigned int perf_mmap__read_head(struct perf_mmap *mm)
+{
+ struct perf_event_mmap_page *pc = mm->base;
+ int head = ACCESS_ONCE(pc->data_head);
+ rmb();
+ return head;
+}
+
+static inline void perf_mmap__write_tail(struct perf_mmap *md,
+ unsigned long tail)
+{
+ struct perf_event_mmap_page *pc = md->base;
+
+ /*
+ * ensure all reads are done before we write the tail out.
+ */
+ mb();
+ pc->data_tail = tail;
+}
+
+bool perf_evlist__can_select_event(struct perf_evlist *evlist, const char *str);
+void perf_evlist__to_front(struct perf_evlist *evlist,
+ struct perf_evsel *move_evsel);
+
+/**
+ * __evlist__for_each - iterate thru all the evsels
+ * @list: list_head instance to iterate
+ * @evsel: struct evsel iterator
+ */
+#define __evlist__for_each(list, evsel) \
+ list_for_each_entry(evsel, list, node)
+
+/**
+ * evlist__for_each - iterate thru all the evsels
+ * @evlist: evlist instance to iterate
+ * @evsel: struct evsel iterator
+ */
+#define evlist__for_each(evlist, evsel) \
+ __evlist__for_each(&(evlist)->entries, evsel)
+
+/**
+ * __evlist__for_each_continue - continue iteration thru all the evsels
+ * @list: list_head instance to iterate
+ * @evsel: struct evsel iterator
+ */
+#define __evlist__for_each_continue(list, evsel) \
+ list_for_each_entry_continue(evsel, list, node)
+
+/**
+ * evlist__for_each_continue - continue iteration thru all the evsels
+ * @evlist: evlist instance to iterate
+ * @evsel: struct evsel iterator
+ */
+#define evlist__for_each_continue(evlist, evsel) \
+ __evlist__for_each_continue(&(evlist)->entries, evsel)
+
+/**
+ * __evlist__for_each_reverse - iterate thru all the evsels in reverse order
+ * @list: list_head instance to iterate
+ * @evsel: struct evsel iterator
+ */
+#define __evlist__for_each_reverse(list, evsel) \
+ list_for_each_entry_reverse(evsel, list, node)
+
+/**
+ * evlist__for_each_reverse - iterate thru all the evsels in reverse order
+ * @evlist: evlist instance to iterate
+ * @evsel: struct evsel iterator
+ */
+#define evlist__for_each_reverse(evlist, evsel) \
+ __evlist__for_each_reverse(&(evlist)->entries, evsel)
+
+/**
+ * __evlist__for_each_safe - safely iterate thru all the evsels
+ * @list: list_head instance to iterate
+ * @tmp: struct evsel temp iterator
+ * @evsel: struct evsel iterator
+ */
+#define __evlist__for_each_safe(list, tmp, evsel) \
+ list_for_each_entry_safe(evsel, tmp, list, node)
+
+/**
+ * evlist__for_each_safe - safely iterate thru all the evsels
+ * @evlist: evlist instance to iterate
+ * @evsel: struct evsel iterator
+ * @tmp: struct evsel temp iterator
+ */
+#define evlist__for_each_safe(evlist, tmp, evsel) \
+ __evlist__for_each_safe(&(evlist)->entries, tmp, evsel)
+
+#endif /* __PERF_EVLIST_H */
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c
new file mode 100644
index 00000000000..8606175fe1e
--- /dev/null
+++ b/tools/perf/util/evsel.c
@@ -0,0 +1,2036 @@
+/*
+ * Copyright (C) 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
+ *
+ * Parts came from builtin-{top,stat,record}.c, see those files for further
+ * copyright notes.
+ *
+ * Released under the GPL v2. (and only v2, not any later version)
+ */
+
+#include <byteswap.h>
+#include <linux/bitops.h>
+#include <api/fs/debugfs.h>
+#include <traceevent/event-parse.h>
+#include <linux/hw_breakpoint.h>
+#include <linux/perf_event.h>
+#include <sys/resource.h>
+#include "asm/bug.h"
+#include "evsel.h"
+#include "evlist.h"
+#include "util.h"
+#include "cpumap.h"
+#include "thread_map.h"
+#include "target.h"
+#include "perf_regs.h"
+#include "debug.h"
+#include "trace-event.h"
+
+static struct {
+ bool sample_id_all;
+ bool exclude_guest;
+ bool mmap2;
+} perf_missing_features;
+
+#define FD(e, x, y) (*(int *)xyarray__entry(e->fd, x, y))
+
+int __perf_evsel__sample_size(u64 sample_type)
+{
+ u64 mask = sample_type & PERF_SAMPLE_MASK;
+ int size = 0;
+ int i;
+
+ for (i = 0; i < 64; i++) {
+ if (mask & (1ULL << i))
+ size++;
+ }
+
+ size *= sizeof(u64);
+
+ return size;
+}
+
+/**
+ * __perf_evsel__calc_id_pos - calculate id_pos.
+ * @sample_type: sample type
+ *
+ * This function returns the position of the event id (PERF_SAMPLE_ID or
+ * PERF_SAMPLE_IDENTIFIER) in a sample event i.e. in the array of struct
+ * sample_event.
+ */
+static int __perf_evsel__calc_id_pos(u64 sample_type)
+{
+ int idx = 0;
+
+ if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ return 0;
+
+ if (!(sample_type & PERF_SAMPLE_ID))
+ return -1;
+
+ if (sample_type & PERF_SAMPLE_IP)
+ idx += 1;
+
+ if (sample_type & PERF_SAMPLE_TID)
+ idx += 1;
+
+ if (sample_type & PERF_SAMPLE_TIME)
+ idx += 1;
+
+ if (sample_type & PERF_SAMPLE_ADDR)
+ idx += 1;
+
+ return idx;
+}
+
+/**
+ * __perf_evsel__calc_is_pos - calculate is_pos.
+ * @sample_type: sample type
+ *
+ * This function returns the position (counting backwards) of the event id
+ * (PERF_SAMPLE_ID or PERF_SAMPLE_IDENTIFIER) in a non-sample event i.e. if
+ * sample_id_all is used there is an id sample appended to non-sample events.
+ */
+static int __perf_evsel__calc_is_pos(u64 sample_type)
+{
+ int idx = 1;
+
+ if (sample_type & PERF_SAMPLE_IDENTIFIER)
+ return 1;
+
+ if (!(sample_type & PERF_SAMPLE_ID))
+ return -1;
+
+ if (sample_type & PERF_SAMPLE_CPU)
+ idx += 1;
+
+ if (sample_type & PERF_SAMPLE_STREAM_ID)
+ idx += 1;
+
+ return idx;
+}
+
+void perf_evsel__calc_id_pos(struct perf_evsel *evsel)
+{
+ evsel->id_pos = __perf_evsel__calc_id_pos(evsel->attr.sample_type);
+ evsel->is_pos = __perf_evsel__calc_is_pos(evsel->attr.sample_type);
+}
+
+void hists__init(struct hists *hists)
+{
+ memset(hists, 0, sizeof(*hists));
+ hists->entries_in_array[0] = hists->entries_in_array[1] = RB_ROOT;
+ hists->entries_in = &hists->entries_in_array[0];
+ hists->entries_collapsed = RB_ROOT;
+ hists->entries = RB_ROOT;
+ pthread_mutex_init(&hists->lock, NULL);
+}
+
+void __perf_evsel__set_sample_bit(struct perf_evsel *evsel,
+ enum perf_event_sample_format bit)
+{
+ if (!(evsel->attr.sample_type & bit)) {
+ evsel->attr.sample_type |= bit;
+ evsel->sample_size += sizeof(u64);
+ perf_evsel__calc_id_pos(evsel);
+ }
+}
+
+void __perf_evsel__reset_sample_bit(struct perf_evsel *evsel,
+ enum perf_event_sample_format bit)
+{
+ if (evsel->attr.sample_type & bit) {
+ evsel->attr.sample_type &= ~bit;
+ evsel->sample_size -= sizeof(u64);
+ perf_evsel__calc_id_pos(evsel);
+ }
+}
+
+void perf_evsel__set_sample_id(struct perf_evsel *evsel,
+ bool can_sample_identifier)
+{
+ if (can_sample_identifier) {
+ perf_evsel__reset_sample_bit(evsel, ID);
+ perf_evsel__set_sample_bit(evsel, IDENTIFIER);
+ } else {
+ perf_evsel__set_sample_bit(evsel, ID);
+ }
+ evsel->attr.read_format |= PERF_FORMAT_ID;
+}
+
+void perf_evsel__init(struct perf_evsel *evsel,
+ struct perf_event_attr *attr, int idx)
+{
+ evsel->idx = idx;
+ evsel->attr = *attr;
+ evsel->leader = evsel;
+ evsel->unit = "";
+ evsel->scale = 1.0;
+ INIT_LIST_HEAD(&evsel->node);
+ hists__init(&evsel->hists);
+ evsel->sample_size = __perf_evsel__sample_size(attr->sample_type);
+ perf_evsel__calc_id_pos(evsel);
+}
+
+struct perf_evsel *perf_evsel__new_idx(struct perf_event_attr *attr, int idx)
+{
+ struct perf_evsel *evsel = zalloc(sizeof(*evsel));
+
+ if (evsel != NULL)
+ perf_evsel__init(evsel, attr, idx);
+
+ return evsel;
+}
+
+struct perf_evsel *perf_evsel__newtp_idx(const char *sys, const char *name, int idx)
+{
+ struct perf_evsel *evsel = zalloc(sizeof(*evsel));
+
+ if (evsel != NULL) {
+ struct perf_event_attr attr = {
+ .type = PERF_TYPE_TRACEPOINT,
+ .sample_type = (PERF_SAMPLE_RAW | PERF_SAMPLE_TIME |
+ PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD),
+ };
+
+ if (asprintf(&evsel->name, "%s:%s", sys, name) < 0)
+ goto out_free;
+
+ evsel->tp_format = trace_event__tp_format(sys, name);
+ if (evsel->tp_format == NULL)
+ goto out_free;
+
+ event_attr_init(&attr);
+ attr.config = evsel->tp_format->id;
+ attr.sample_period = 1;
+ perf_evsel__init(evsel, &attr, idx);
+ }
+
+ return evsel;
+
+out_free:
+ zfree(&evsel->name);
+ free(evsel);
+ return NULL;
+}
+
+const char *perf_evsel__hw_names[PERF_COUNT_HW_MAX] = {
+ "cycles",
+ "instructions",
+ "cache-references",
+ "cache-misses",
+ "branches",
+ "branch-misses",
+ "bus-cycles",
+ "stalled-cycles-frontend",
+ "stalled-cycles-backend",
+ "ref-cycles",
+};
+
+static const char *__perf_evsel__hw_name(u64 config)
+{
+ if (config < PERF_COUNT_HW_MAX && perf_evsel__hw_names[config])
+ return perf_evsel__hw_names[config];
+
+ return "unknown-hardware";
+}
+
+static int perf_evsel__add_modifiers(struct perf_evsel *evsel, char *bf, size_t size)
+{
+ int colon = 0, r = 0;
+ struct perf_event_attr *attr = &evsel->attr;
+ bool exclude_guest_default = false;
+
+#define MOD_PRINT(context, mod) do { \
+ if (!attr->exclude_##context) { \
+ if (!colon) colon = ++r; \
+ r += scnprintf(bf + r, size - r, "%c", mod); \
+ } } while(0)
+
+ if (attr->exclude_kernel || attr->exclude_user || attr->exclude_hv) {
+ MOD_PRINT(kernel, 'k');
+ MOD_PRINT(user, 'u');
+ MOD_PRINT(hv, 'h');
+ exclude_guest_default = true;
+ }
+
+ if (attr->precise_ip) {
+ if (!colon)
+ colon = ++r;
+ r += scnprintf(bf + r, size - r, "%.*s", attr->precise_ip, "ppp");
+ exclude_guest_default = true;
+ }
+
+ if (attr->exclude_host || attr->exclude_guest == exclude_guest_default) {
+ MOD_PRINT(host, 'H');
+ MOD_PRINT(guest, 'G');
+ }
+#undef MOD_PRINT
+ if (colon)
+ bf[colon - 1] = ':';
+ return r;
+}
+
+static int perf_evsel__hw_name(struct perf_evsel *evsel, char *bf, size_t size)
+{
+ int r = scnprintf(bf, size, "%s", __perf_evsel__hw_name(evsel->attr.config));
+ return r + perf_evsel__add_modifiers(evsel, bf + r, size - r);
+}
+
+const char *perf_evsel__sw_names[PERF_COUNT_SW_MAX] = {
+ "cpu-clock",
+ "task-clock",
+ "page-faults",
+ "context-switches",
+ "cpu-migrations",
+ "minor-faults",
+ "major-faults",
+ "alignment-faults",
+ "emulation-faults",
+ "dummy",
+};
+
+static const char *__perf_evsel__sw_name(u64 config)
+{
+ if (config < PERF_COUNT_SW_MAX && perf_evsel__sw_names[config])
+ return perf_evsel__sw_names[config];
+ return "unknown-software";
+}
+
+static int perf_evsel__sw_name(struct perf_evsel *evsel, char *bf, size_t size)
+{
+ int r = scnprintf(bf, size, "%s", __perf_evsel__sw_name(evsel->attr.config));
+ return r + perf_evsel__add_modifiers(evsel, bf + r, size - r);
+}
+
+static int __perf_evsel__bp_name(char *bf, size_t size, u64 addr, u64 type)
+{
+ int r;
+
+ r = scnprintf(bf, size, "mem:0x%" PRIx64 ":", addr);
+
+ if (type & HW_BREAKPOINT_R)
+ r += scnprintf(bf + r, size - r, "r");
+
+ if (type & HW_BREAKPOINT_W)
+ r += scnprintf(bf + r, size - r, "w");
+
+ if (type & HW_BREAKPOINT_X)
+ r += scnprintf(bf + r, size - r, "x");
+
+ return r;
+}
+
+static int perf_evsel__bp_name(struct perf_evsel *evsel, char *bf, size_t size)
+{
+ struct perf_event_attr *attr = &evsel->attr;
+ int r = __perf_evsel__bp_name(bf, size, attr->bp_addr, attr->bp_type);
+ return r + perf_evsel__add_modifiers(evsel, bf + r, size - r);
+}
+
+const char *perf_evsel__hw_cache[PERF_COUNT_HW_CACHE_MAX]
+ [PERF_EVSEL__MAX_ALIASES] = {
+ { "L1-dcache", "l1-d", "l1d", "L1-data", },
+ { "L1-icache", "l1-i", "l1i", "L1-instruction", },
+ { "LLC", "L2", },
+ { "dTLB", "d-tlb", "Data-TLB", },
+ { "iTLB", "i-tlb", "Instruction-TLB", },
+ { "branch", "branches", "bpu", "btb", "bpc", },
+ { "node", },
+};
+
+const char *perf_evsel__hw_cache_op[PERF_COUNT_HW_CACHE_OP_MAX]
+ [PERF_EVSEL__MAX_ALIASES] = {
+ { "load", "loads", "read", },
+ { "store", "stores", "write", },
+ { "prefetch", "prefetches", "speculative-read", "speculative-load", },
+};
+
+const char *perf_evsel__hw_cache_result[PERF_COUNT_HW_CACHE_RESULT_MAX]
+ [PERF_EVSEL__MAX_ALIASES] = {
+ { "refs", "Reference", "ops", "access", },
+ { "misses", "miss", },
+};
+
+#define C(x) PERF_COUNT_HW_CACHE_##x
+#define CACHE_READ (1 << C(OP_READ))
+#define CACHE_WRITE (1 << C(OP_WRITE))
+#define CACHE_PREFETCH (1 << C(OP_PREFETCH))
+#define COP(x) (1 << x)
+
+/*
+ * cache operartion stat
+ * L1I : Read and prefetch only
+ * ITLB and BPU : Read-only
+ */
+static unsigned long perf_evsel__hw_cache_stat[C(MAX)] = {
+ [C(L1D)] = (CACHE_READ | CACHE_WRITE | CACHE_PREFETCH),
+ [C(L1I)] = (CACHE_READ | CACHE_PREFETCH),
+ [C(LL)] = (CACHE_READ | CACHE_WRITE | CACHE_PREFETCH),
+ [C(DTLB)] = (CACHE_READ | CACHE_WRITE | CACHE_PREFETCH),
+ [C(ITLB)] = (CACHE_READ),
+ [C(BPU)] = (CACHE_READ),
+ [C(NODE)] = (CACHE_READ | CACHE_WRITE | CACHE_PREFETCH),
+};
+
+bool perf_evsel__is_cache_op_valid(u8 type, u8 op)
+{
+ if (perf_evsel__hw_cache_stat[type] & COP(op))
+ return true; /* valid */
+ else
+ return false; /* invalid */
+}
+
+int __perf_evsel__hw_cache_type_op_res_name(u8 type, u8 op, u8 result,
+ char *bf, size_t size)
+{
+ if (result) {
+ return scnprintf(bf, size, "%s-%s-%s", perf_evsel__hw_cache[type][0],
+ perf_evsel__hw_cache_op[op][0],
+ perf_evsel__hw_cache_result[result][0]);
+ }
+
+ return scnprintf(bf, size, "%s-%s", perf_evsel__hw_cache[type][0],
+ perf_evsel__hw_cache_op[op][1]);
+}
+
+static int __perf_evsel__hw_cache_name(u64 config, char *bf, size_t size)
+{
+ u8 op, result, type = (config >> 0) & 0xff;
+ const char *err = "unknown-ext-hardware-cache-type";
+
+ if (type > PERF_COUNT_HW_CACHE_MAX)
+ goto out_err;
+
+ op = (config >> 8) & 0xff;
+ err = "unknown-ext-hardware-cache-op";
+ if (op > PERF_COUNT_HW_CACHE_OP_MAX)
+ goto out_err;
+
+ result = (config >> 16) & 0xff;
+ err = "unknown-ext-hardware-cache-result";
+ if (result > PERF_COUNT_HW_CACHE_RESULT_MAX)
+ goto out_err;
+
+ err = "invalid-cache";
+ if (!perf_evsel__is_cache_op_valid(type, op))
+ goto out_err;
+
+ return __perf_evsel__hw_cache_type_op_res_name(type, op, result, bf, size);
+out_err:
+ return scnprintf(bf, size, "%s", err);
+}
+
+static int perf_evsel__hw_cache_name(struct perf_evsel *evsel, char *bf, size_t size)
+{
+ int ret = __perf_evsel__hw_cache_name(evsel->attr.config, bf, size);
+ return ret + perf_evsel__add_modifiers(evsel, bf + ret, size - ret);
+}
+
+static int perf_evsel__raw_name(struct perf_evsel *evsel, char *bf, size_t size)
+{
+ int ret = scnprintf(bf, size, "raw 0x%" PRIx64, evsel->attr.config);
+ return ret + perf_evsel__add_modifiers(evsel, bf + ret, size - ret);
+}
+
+const char *perf_evsel__name(struct perf_evsel *evsel)
+{
+ char bf[128];
+
+ if (evsel->name)
+ return evsel->name;
+
+ switch (evsel->attr.type) {
+ case PERF_TYPE_RAW:
+ perf_evsel__raw_name(evsel, bf, sizeof(bf));
+ break;
+
+ case PERF_TYPE_HARDWARE:
+ perf_evsel__hw_name(evsel, bf, sizeof(bf));
+ break;
+
+ case PERF_TYPE_HW_CACHE:
+ perf_evsel__hw_cache_name(evsel, bf, sizeof(bf));
+ break;
+
+ case PERF_TYPE_SOFTWARE:
+ perf_evsel__sw_name(evsel, bf, sizeof(bf));
+ break;
+
+ case PERF_TYPE_TRACEPOINT:
+ scnprintf(bf, sizeof(bf), "%s", "unknown tracepoint");
+ break;
+
+ case PERF_TYPE_BREAKPOINT:
+ perf_evsel__bp_name(evsel, bf, sizeof(bf));
+ break;
+
+ default:
+ scnprintf(bf, sizeof(bf), "unknown attr type: %d",
+ evsel->attr.type);
+ break;
+ }
+
+ evsel->name = strdup(bf);
+
+ return evsel->name ?: "unknown";
+}
+
+const char *perf_evsel__group_name(struct perf_evsel *evsel)
+{
+ return evsel->group_name ?: "anon group";
+}
+
+int perf_evsel__group_desc(struct perf_evsel *evsel, char *buf, size_t size)
+{
+ int ret;
+ struct perf_evsel *pos;
+ const char *group_name = perf_evsel__group_name(evsel);
+
+ ret = scnprintf(buf, size, "%s", group_name);
+
+ ret += scnprintf(buf + ret, size - ret, " { %s",
+ perf_evsel__name(evsel));
+
+ for_each_group_member(pos, evsel)
+ ret += scnprintf(buf + ret, size - ret, ", %s",
+ perf_evsel__name(pos));
+
+ ret += scnprintf(buf + ret, size - ret, " }");
+
+ return ret;
+}
+
+static void
+perf_evsel__config_callgraph(struct perf_evsel *evsel,
+ struct record_opts *opts)
+{
+ bool function = perf_evsel__is_function_event(evsel);
+ struct perf_event_attr *attr = &evsel->attr;
+
+ perf_evsel__set_sample_bit(evsel, CALLCHAIN);
+
+ if (opts->call_graph == CALLCHAIN_DWARF) {
+ if (!function) {
+ perf_evsel__set_sample_bit(evsel, REGS_USER);
+ perf_evsel__set_sample_bit(evsel, STACK_USER);
+ attr->sample_regs_user = PERF_REGS_MASK;
+ attr->sample_stack_user = opts->stack_dump_size;
+ attr->exclude_callchain_user = 1;
+ } else {
+ pr_info("Cannot use DWARF unwind for function trace event,"
+ " falling back to framepointers.\n");
+ }
+ }
+
+ if (function) {
+ pr_info("Disabling user space callchains for function trace event.\n");
+ attr->exclude_callchain_user = 1;
+ }
+}
+
+/*
+ * The enable_on_exec/disabled value strategy:
+ *
+ * 1) For any type of traced program:
+ * - all independent events and group leaders are disabled
+ * - all group members are enabled
+ *
+ * Group members are ruled by group leaders. They need to
+ * be enabled, because the group scheduling relies on that.
+ *
+ * 2) For traced programs executed by perf:
+ * - all independent events and group leaders have
+ * enable_on_exec set
+ * - we don't specifically enable or disable any event during
+ * the record command
+ *
+ * Independent events and group leaders are initially disabled
+ * and get enabled by exec. Group members are ruled by group
+ * leaders as stated in 1).
+ *
+ * 3) For traced programs attached by perf (pid/tid):
+ * - we specifically enable or disable all events during
+ * the record command
+ *
+ * When attaching events to already running traced we
+ * enable/disable events specifically, as there's no
+ * initial traced exec call.
+ */
+void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts)
+{
+ struct perf_evsel *leader = evsel->leader;
+ struct perf_event_attr *attr = &evsel->attr;
+ int track = !evsel->idx; /* only the first counter needs these */
+ bool per_cpu = opts->target.default_per_cpu && !opts->target.per_thread;
+
+ attr->sample_id_all = perf_missing_features.sample_id_all ? 0 : 1;
+ attr->inherit = !opts->no_inherit;
+
+ perf_evsel__set_sample_bit(evsel, IP);
+ perf_evsel__set_sample_bit(evsel, TID);
+
+ if (evsel->sample_read) {
+ perf_evsel__set_sample_bit(evsel, READ);
+
+ /*
+ * We need ID even in case of single event, because
+ * PERF_SAMPLE_READ process ID specific data.
+ */
+ perf_evsel__set_sample_id(evsel, false);
+
+ /*
+ * Apply group format only if we belong to group
+ * with more than one members.
+ */
+ if (leader->nr_members > 1) {
+ attr->read_format |= PERF_FORMAT_GROUP;
+ attr->inherit = 0;
+ }
+ }
+
+ /*
+ * We default some events to have a default interval. But keep
+ * it a weak assumption overridable by the user.
+ */
+ if (!attr->sample_period || (opts->user_freq != UINT_MAX ||
+ opts->user_interval != ULLONG_MAX)) {
+ if (opts->freq) {
+ perf_evsel__set_sample_bit(evsel, PERIOD);
+ attr->freq = 1;
+ attr->sample_freq = opts->freq;
+ } else {
+ attr->sample_period = opts->default_interval;
+ }
+ }
+
+ /*
+ * Disable sampling for all group members other
+ * than leader in case leader 'leads' the sampling.
+ */
+ if ((leader != evsel) && leader->sample_read) {
+ attr->sample_freq = 0;
+ attr->sample_period = 0;
+ }
+
+ if (opts->no_samples)
+ attr->sample_freq = 0;
+
+ if (opts->inherit_stat)
+ attr->inherit_stat = 1;
+
+ if (opts->sample_address) {
+ perf_evsel__set_sample_bit(evsel, ADDR);
+ attr->mmap_data = track;
+ }
+
+ if (opts->call_graph_enabled)
+ perf_evsel__config_callgraph(evsel, opts);
+
+ if (target__has_cpu(&opts->target))
+ perf_evsel__set_sample_bit(evsel, CPU);
+
+ if (opts->period)
+ perf_evsel__set_sample_bit(evsel, PERIOD);
+
+ if (!perf_missing_features.sample_id_all &&
+ (opts->sample_time || !opts->no_inherit ||
+ target__has_cpu(&opts->target) || per_cpu))
+ perf_evsel__set_sample_bit(evsel, TIME);
+
+ if (opts->raw_samples) {
+ perf_evsel__set_sample_bit(evsel, TIME);
+ perf_evsel__set_sample_bit(evsel, RAW);
+ perf_evsel__set_sample_bit(evsel, CPU);
+ }
+
+ if (opts->sample_address)
+ perf_evsel__set_sample_bit(evsel, DATA_SRC);
+
+ if (opts->no_buffering) {
+ attr->watermark = 0;
+ attr->wakeup_events = 1;
+ }
+ if (opts->branch_stack) {
+ perf_evsel__set_sample_bit(evsel, BRANCH_STACK);
+ attr->branch_sample_type = opts->branch_stack;
+ }
+
+ if (opts->sample_weight)
+ perf_evsel__set_sample_bit(evsel, WEIGHT);
+
+ attr->mmap = track;
+ attr->mmap2 = track && !perf_missing_features.mmap2;
+ attr->comm = track;
+
+ if (opts->sample_transaction)
+ perf_evsel__set_sample_bit(evsel, TRANSACTION);
+
+ /*
+ * XXX see the function comment above
+ *
+ * Disabling only independent events or group leaders,
+ * keeping group members enabled.
+ */
+ if (perf_evsel__is_group_leader(evsel))
+ attr->disabled = 1;
+
+ /*
+ * Setting enable_on_exec for independent events and
+ * group leaders for traced executed by perf.
+ */
+ if (target__none(&opts->target) && perf_evsel__is_group_leader(evsel) &&
+ !opts->initial_delay)
+ attr->enable_on_exec = 1;
+}
+
+int perf_evsel__alloc_fd(struct perf_evsel *evsel, int ncpus, int nthreads)
+{
+ int cpu, thread;
+ evsel->fd = xyarray__new(ncpus, nthreads, sizeof(int));
+
+ if (evsel->fd) {
+ for (cpu = 0; cpu < ncpus; cpu++) {
+ for (thread = 0; thread < nthreads; thread++) {
+ FD(evsel, cpu, thread) = -1;
+ }
+ }
+ }
+
+ return evsel->fd != NULL ? 0 : -ENOMEM;
+}
+
+static int perf_evsel__run_ioctl(struct perf_evsel *evsel, int ncpus, int nthreads,
+ int ioc, void *arg)
+{
+ int cpu, thread;
+
+ for (cpu = 0; cpu < ncpus; cpu++) {
+ for (thread = 0; thread < nthreads; thread++) {
+ int fd = FD(evsel, cpu, thread),
+ err = ioctl(fd, ioc, arg);
+
+ if (err)
+ return err;
+ }
+ }
+
+ return 0;
+}
+
+int perf_evsel__set_filter(struct perf_evsel *evsel, int ncpus, int nthreads,
+ const char *filter)
+{
+ return perf_evsel__run_ioctl(evsel, ncpus, nthreads,
+ PERF_EVENT_IOC_SET_FILTER,
+ (void *)filter);
+}
+
+int perf_evsel__enable(struct perf_evsel *evsel, int ncpus, int nthreads)
+{
+ return perf_evsel__run_ioctl(evsel, ncpus, nthreads,
+ PERF_EVENT_IOC_ENABLE,
+ 0);
+}
+
+int perf_evsel__alloc_id(struct perf_evsel *evsel, int ncpus, int nthreads)
+{
+ evsel->sample_id = xyarray__new(ncpus, nthreads, sizeof(struct perf_sample_id));
+ if (evsel->sample_id == NULL)
+ return -ENOMEM;
+
+ evsel->id = zalloc(ncpus * nthreads * sizeof(u64));
+ if (evsel->id == NULL) {
+ xyarray__delete(evsel->sample_id);
+ evsel->sample_id = NULL;
+ return -ENOMEM;
+ }
+
+ return 0;
+}
+
+void perf_evsel__reset_counts(struct perf_evsel *evsel, int ncpus)
+{
+ memset(evsel->counts, 0, (sizeof(*evsel->counts) +
+ (ncpus * sizeof(struct perf_counts_values))));
+}
+
+int perf_evsel__alloc_counts(struct perf_evsel *evsel, int ncpus)
+{
+ evsel->counts = zalloc((sizeof(*evsel->counts) +
+ (ncpus * sizeof(struct perf_counts_values))));
+ return evsel->counts != NULL ? 0 : -ENOMEM;
+}
+
+void perf_evsel__free_fd(struct perf_evsel *evsel)
+{
+ xyarray__delete(evsel->fd);
+ evsel->fd = NULL;
+}
+
+void perf_evsel__free_id(struct perf_evsel *evsel)
+{
+ xyarray__delete(evsel->sample_id);
+ evsel->sample_id = NULL;
+ zfree(&evsel->id);
+}
+
+void perf_evsel__close_fd(struct perf_evsel *evsel, int ncpus, int nthreads)
+{
+ int cpu, thread;
+
+ for (cpu = 0; cpu < ncpus; cpu++)
+ for (thread = 0; thread < nthreads; ++thread) {
+ close(FD(evsel, cpu, thread));
+ FD(evsel, cpu, thread) = -1;
+ }
+}
+
+void perf_evsel__free_counts(struct perf_evsel *evsel)
+{
+ zfree(&evsel->counts);
+}
+
+void perf_evsel__exit(struct perf_evsel *evsel)
+{
+ assert(list_empty(&evsel->node));
+ perf_evsel__free_fd(evsel);
+ perf_evsel__free_id(evsel);
+}
+
+void perf_evsel__delete(struct perf_evsel *evsel)
+{
+ perf_evsel__exit(evsel);
+ close_cgroup(evsel->cgrp);
+ zfree(&evsel->group_name);
+ if (evsel->tp_format)
+ pevent_free_format(evsel->tp_format);
+ zfree(&evsel->name);
+ free(evsel);
+}
+
+static inline void compute_deltas(struct perf_evsel *evsel,
+ int cpu,
+ struct perf_counts_values *count)
+{
+ struct perf_counts_values tmp;
+
+ if (!evsel->prev_raw_counts)
+ return;
+
+ if (cpu == -1) {
+ tmp = evsel->prev_raw_counts->aggr;
+ evsel->prev_raw_counts->aggr = *count;
+ } else {
+ tmp = evsel->prev_raw_counts->cpu[cpu];
+ evsel->prev_raw_counts->cpu[cpu] = *count;
+ }
+
+ count->val = count->val - tmp.val;
+ count->ena = count->ena - tmp.ena;
+ count->run = count->run - tmp.run;
+}
+
+int __perf_evsel__read_on_cpu(struct perf_evsel *evsel,
+ int cpu, int thread, bool scale)
+{
+ struct perf_counts_values count;
+ size_t nv = scale ? 3 : 1;
+
+ if (FD(evsel, cpu, thread) < 0)
+ return -EINVAL;
+
+ if (evsel->counts == NULL && perf_evsel__alloc_counts(evsel, cpu + 1) < 0)
+ return -ENOMEM;
+
+ if (readn(FD(evsel, cpu, thread), &count, nv * sizeof(u64)) < 0)
+ return -errno;
+
+ compute_deltas(evsel, cpu, &count);
+
+ if (scale) {
+ if (count.run == 0)
+ count.val = 0;
+ else if (count.run < count.ena)
+ count.val = (u64)((double)count.val * count.ena / count.run + 0.5);
+ } else
+ count.ena = count.run = 0;
+
+ evsel->counts->cpu[cpu] = count;
+ return 0;
+}
+
+int __perf_evsel__read(struct perf_evsel *evsel,
+ int ncpus, int nthreads, bool scale)
+{
+ size_t nv = scale ? 3 : 1;
+ int cpu, thread;
+ struct perf_counts_values *aggr = &evsel->counts->aggr, count;
+
+ aggr->val = aggr->ena = aggr->run = 0;
+
+ for (cpu = 0; cpu < ncpus; cpu++) {
+ for (thread = 0; thread < nthreads; thread++) {
+ if (FD(evsel, cpu, thread) < 0)
+ continue;
+
+ if (readn(FD(evsel, cpu, thread),
+ &count, nv * sizeof(u64)) < 0)
+ return -errno;
+
+ aggr->val += count.val;
+ if (scale) {
+ aggr->ena += count.ena;
+ aggr->run += count.run;
+ }
+ }
+ }
+
+ compute_deltas(evsel, -1, aggr);
+
+ evsel->counts->scaled = 0;
+ if (scale) {
+ if (aggr->run == 0) {
+ evsel->counts->scaled = -1;
+ aggr->val = 0;
+ return 0;
+ }
+
+ if (aggr->run < aggr->ena) {
+ evsel->counts->scaled = 1;
+ aggr->val = (u64)((double)aggr->val * aggr->ena / aggr->run + 0.5);
+ }
+ } else
+ aggr->ena = aggr->run = 0;
+
+ return 0;
+}
+
+static int get_group_fd(struct perf_evsel *evsel, int cpu, int thread)
+{
+ struct perf_evsel *leader = evsel->leader;
+ int fd;
+
+ if (perf_evsel__is_group_leader(evsel))
+ return -1;
+
+ /*
+ * Leader must be already processed/open,
+ * if not it's a bug.
+ */
+ BUG_ON(!leader->fd);
+
+ fd = FD(leader, cpu, thread);
+ BUG_ON(fd == -1);
+
+ return fd;
+}
+
+#define __PRINT_ATTR(fmt, cast, field) \
+ fprintf(fp, " %-19s "fmt"\n", #field, cast attr->field)
+
+#define PRINT_ATTR_U32(field) __PRINT_ATTR("%u" , , field)
+#define PRINT_ATTR_X32(field) __PRINT_ATTR("%#x", , field)
+#define PRINT_ATTR_U64(field) __PRINT_ATTR("%" PRIu64, (uint64_t), field)
+#define PRINT_ATTR_X64(field) __PRINT_ATTR("%#"PRIx64, (uint64_t), field)
+
+#define PRINT_ATTR2N(name1, field1, name2, field2) \
+ fprintf(fp, " %-19s %u %-19s %u\n", \
+ name1, attr->field1, name2, attr->field2)
+
+#define PRINT_ATTR2(field1, field2) \
+ PRINT_ATTR2N(#field1, field1, #field2, field2)
+
+static size_t perf_event_attr__fprintf(struct perf_event_attr *attr, FILE *fp)
+{
+ size_t ret = 0;
+
+ ret += fprintf(fp, "%.60s\n", graph_dotted_line);
+ ret += fprintf(fp, "perf_event_attr:\n");
+
+ ret += PRINT_ATTR_U32(type);
+ ret += PRINT_ATTR_U32(size);
+ ret += PRINT_ATTR_X64(config);
+ ret += PRINT_ATTR_U64(sample_period);
+ ret += PRINT_ATTR_U64(sample_freq);
+ ret += PRINT_ATTR_X64(sample_type);
+ ret += PRINT_ATTR_X64(read_format);
+
+ ret += PRINT_ATTR2(disabled, inherit);
+ ret += PRINT_ATTR2(pinned, exclusive);
+ ret += PRINT_ATTR2(exclude_user, exclude_kernel);
+ ret += PRINT_ATTR2(exclude_hv, exclude_idle);
+ ret += PRINT_ATTR2(mmap, comm);
+ ret += PRINT_ATTR2(freq, inherit_stat);
+ ret += PRINT_ATTR2(enable_on_exec, task);
+ ret += PRINT_ATTR2(watermark, precise_ip);
+ ret += PRINT_ATTR2(mmap_data, sample_id_all);
+ ret += PRINT_ATTR2(exclude_host, exclude_guest);
+ ret += PRINT_ATTR2N("excl.callchain_kern", exclude_callchain_kernel,
+ "excl.callchain_user", exclude_callchain_user);
+ ret += PRINT_ATTR_U32(mmap2);
+
+ ret += PRINT_ATTR_U32(wakeup_events);
+ ret += PRINT_ATTR_U32(wakeup_watermark);
+ ret += PRINT_ATTR_X32(bp_type);
+ ret += PRINT_ATTR_X64(bp_addr);
+ ret += PRINT_ATTR_X64(config1);
+ ret += PRINT_ATTR_U64(bp_len);
+ ret += PRINT_ATTR_X64(config2);
+ ret += PRINT_ATTR_X64(branch_sample_type);
+ ret += PRINT_ATTR_X64(sample_regs_user);
+ ret += PRINT_ATTR_U32(sample_stack_user);
+
+ ret += fprintf(fp, "%.60s\n", graph_dotted_line);
+
+ return ret;
+}
+
+static int __perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus,
+ struct thread_map *threads)
+{
+ int cpu, thread;
+ unsigned long flags = 0;
+ int pid = -1, err;
+ enum { NO_CHANGE, SET_TO_MAX, INCREASED_MAX } set_rlimit = NO_CHANGE;
+
+ if (evsel->fd == NULL &&
+ perf_evsel__alloc_fd(evsel, cpus->nr, threads->nr) < 0)
+ return -ENOMEM;
+
+ if (evsel->cgrp) {
+ flags = PERF_FLAG_PID_CGROUP;
+ pid = evsel->cgrp->fd;
+ }
+
+fallback_missing_features:
+ if (perf_missing_features.mmap2)
+ evsel->attr.mmap2 = 0;
+ if (perf_missing_features.exclude_guest)
+ evsel->attr.exclude_guest = evsel->attr.exclude_host = 0;
+retry_sample_id:
+ if (perf_missing_features.sample_id_all)
+ evsel->attr.sample_id_all = 0;
+
+ if (verbose >= 2)
+ perf_event_attr__fprintf(&evsel->attr, stderr);
+
+ for (cpu = 0; cpu < cpus->nr; cpu++) {
+
+ for (thread = 0; thread < threads->nr; thread++) {
+ int group_fd;
+
+ if (!evsel->cgrp)
+ pid = threads->map[thread];
+
+ group_fd = get_group_fd(evsel, cpu, thread);
+retry_open:
+ pr_debug2("sys_perf_event_open: pid %d cpu %d group_fd %d flags %#lx\n",
+ pid, cpus->map[cpu], group_fd, flags);
+
+ FD(evsel, cpu, thread) = sys_perf_event_open(&evsel->attr,
+ pid,
+ cpus->map[cpu],
+ group_fd, flags);
+ if (FD(evsel, cpu, thread) < 0) {
+ err = -errno;
+ pr_debug2("sys_perf_event_open failed, error %d\n",
+ err);
+ goto try_fallback;
+ }
+ set_rlimit = NO_CHANGE;
+ }
+ }
+
+ return 0;
+
+try_fallback:
+ /*
+ * perf stat needs between 5 and 22 fds per CPU. When we run out
+ * of them try to increase the limits.
+ */
+ if (err == -EMFILE && set_rlimit < INCREASED_MAX) {
+ struct rlimit l;
+ int old_errno = errno;
+
+ if (getrlimit(RLIMIT_NOFILE, &l) == 0) {
+ if (set_rlimit == NO_CHANGE)
+ l.rlim_cur = l.rlim_max;
+ else {
+ l.rlim_cur = l.rlim_max + 1000;
+ l.rlim_max = l.rlim_cur;
+ }
+ if (setrlimit(RLIMIT_NOFILE, &l) == 0) {
+ set_rlimit++;
+ errno = old_errno;
+ goto retry_open;
+ }
+ }
+ errno = old_errno;
+ }
+
+ if (err != -EINVAL || cpu > 0 || thread > 0)
+ goto out_close;
+
+ if (!perf_missing_features.mmap2 && evsel->attr.mmap2) {
+ perf_missing_features.mmap2 = true;
+ goto fallback_missing_features;
+ } else if (!perf_missing_features.exclude_guest &&
+ (evsel->attr.exclude_guest || evsel->attr.exclude_host)) {
+ perf_missing_features.exclude_guest = true;
+ goto fallback_missing_features;
+ } else if (!perf_missing_features.sample_id_all) {
+ perf_missing_features.sample_id_all = true;
+ goto retry_sample_id;
+ }
+
+out_close:
+ do {
+ while (--thread >= 0) {
+ close(FD(evsel, cpu, thread));
+ FD(evsel, cpu, thread) = -1;
+ }
+ thread = threads->nr;
+ } while (--cpu >= 0);
+ return err;
+}
+
+void perf_evsel__close(struct perf_evsel *evsel, int ncpus, int nthreads)
+{
+ if (evsel->fd == NULL)
+ return;
+
+ perf_evsel__close_fd(evsel, ncpus, nthreads);
+ perf_evsel__free_fd(evsel);
+}
+
+static struct {
+ struct cpu_map map;
+ int cpus[1];
+} empty_cpu_map = {
+ .map.nr = 1,
+ .cpus = { -1, },
+};
+
+static struct {
+ struct thread_map map;
+ int threads[1];
+} empty_thread_map = {
+ .map.nr = 1,
+ .threads = { -1, },
+};
+
+int perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus,
+ struct thread_map *threads)
+{
+ if (cpus == NULL) {
+ /* Work around old compiler warnings about strict aliasing */
+ cpus = &empty_cpu_map.map;
+ }
+
+ if (threads == NULL)
+ threads = &empty_thread_map.map;
+
+ return __perf_evsel__open(evsel, cpus, threads);
+}
+
+int perf_evsel__open_per_cpu(struct perf_evsel *evsel,
+ struct cpu_map *cpus)
+{
+ return __perf_evsel__open(evsel, cpus, &empty_thread_map.map);
+}
+
+int perf_evsel__open_per_thread(struct perf_evsel *evsel,
+ struct thread_map *threads)
+{
+ return __perf_evsel__open(evsel, &empty_cpu_map.map, threads);
+}
+
+static int perf_evsel__parse_id_sample(const struct perf_evsel *evsel,
+ const union perf_event *event,
+ struct perf_sample *sample)
+{
+ u64 type = evsel->attr.sample_type;
+ const u64 *array = event->sample.array;
+ bool swapped = evsel->needs_swap;
+ union u64_swap u;
+
+ array += ((event->header.size -
+ sizeof(event->header)) / sizeof(u64)) - 1;
+
+ if (type & PERF_SAMPLE_IDENTIFIER) {
+ sample->id = *array;
+ array--;
+ }
+
+ if (type & PERF_SAMPLE_CPU) {
+ u.val64 = *array;
+ if (swapped) {
+ /* undo swap of u64, then swap on individual u32s */
+ u.val64 = bswap_64(u.val64);
+ u.val32[0] = bswap_32(u.val32[0]);
+ }
+
+ sample->cpu = u.val32[0];
+ array--;
+ }
+
+ if (type & PERF_SAMPLE_STREAM_ID) {
+ sample->stream_id = *array;
+ array--;
+ }
+
+ if (type & PERF_SAMPLE_ID) {
+ sample->id = *array;
+ array--;
+ }
+
+ if (type & PERF_SAMPLE_TIME) {
+ sample->time = *array;
+ array--;
+ }
+
+ if (type & PERF_SAMPLE_TID) {
+ u.val64 = *array;
+ if (swapped) {
+ /* undo swap of u64, then swap on individual u32s */
+ u.val64 = bswap_64(u.val64);
+ u.val32[0] = bswap_32(u.val32[0]);
+ u.val32[1] = bswap_32(u.val32[1]);
+ }
+
+ sample->pid = u.val32[0];
+ sample->tid = u.val32[1];
+ array--;
+ }
+
+ return 0;
+}
+
+static inline bool overflow(const void *endp, u16 max_size, const void *offset,
+ u64 size)
+{
+ return size > max_size || offset + size > endp;
+}
+
+#define OVERFLOW_CHECK(offset, size, max_size) \
+ do { \
+ if (overflow(endp, (max_size), (offset), (size))) \
+ return -EFAULT; \
+ } while (0)
+
+#define OVERFLOW_CHECK_u64(offset) \
+ OVERFLOW_CHECK(offset, sizeof(u64), sizeof(u64))
+
+int perf_evsel__parse_sample(struct perf_evsel *evsel, union perf_event *event,
+ struct perf_sample *data)
+{
+ u64 type = evsel->attr.sample_type;
+ bool swapped = evsel->needs_swap;
+ const u64 *array;
+ u16 max_size = event->header.size;
+ const void *endp = (void *)event + max_size;
+ u64 sz;
+
+ /*
+ * used for cross-endian analysis. See git commit 65014ab3
+ * for why this goofiness is needed.
+ */
+ union u64_swap u;
+
+ memset(data, 0, sizeof(*data));
+ data->cpu = data->pid = data->tid = -1;
+ data->stream_id = data->id = data->time = -1ULL;
+ data->period = evsel->attr.sample_period;
+ data->weight = 0;
+
+ if (event->header.type != PERF_RECORD_SAMPLE) {
+ if (!evsel->attr.sample_id_all)
+ return 0;
+ return perf_evsel__parse_id_sample(evsel, event, data);
+ }
+
+ array = event->sample.array;
+
+ /*
+ * The evsel's sample_size is based on PERF_SAMPLE_MASK which includes
+ * up to PERF_SAMPLE_PERIOD. After that overflow() must be used to
+ * check the format does not go past the end of the event.
+ */
+ if (evsel->sample_size + sizeof(event->header) > event->header.size)
+ return -EFAULT;
+
+ data->id = -1ULL;
+ if (type & PERF_SAMPLE_IDENTIFIER) {
+ data->id = *array;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_IP) {
+ data->ip = *array;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_TID) {
+ u.val64 = *array;
+ if (swapped) {
+ /* undo swap of u64, then swap on individual u32s */
+ u.val64 = bswap_64(u.val64);
+ u.val32[0] = bswap_32(u.val32[0]);
+ u.val32[1] = bswap_32(u.val32[1]);
+ }
+
+ data->pid = u.val32[0];
+ data->tid = u.val32[1];
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_TIME) {
+ data->time = *array;
+ array++;
+ }
+
+ data->addr = 0;
+ if (type & PERF_SAMPLE_ADDR) {
+ data->addr = *array;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_ID) {
+ data->id = *array;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_STREAM_ID) {
+ data->stream_id = *array;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_CPU) {
+
+ u.val64 = *array;
+ if (swapped) {
+ /* undo swap of u64, then swap on individual u32s */
+ u.val64 = bswap_64(u.val64);
+ u.val32[0] = bswap_32(u.val32[0]);
+ }
+
+ data->cpu = u.val32[0];
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_PERIOD) {
+ data->period = *array;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_READ) {
+ u64 read_format = evsel->attr.read_format;
+
+ OVERFLOW_CHECK_u64(array);
+ if (read_format & PERF_FORMAT_GROUP)
+ data->read.group.nr = *array;
+ else
+ data->read.one.value = *array;
+
+ array++;
+
+ if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) {
+ OVERFLOW_CHECK_u64(array);
+ data->read.time_enabled = *array;
+ array++;
+ }
+
+ if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) {
+ OVERFLOW_CHECK_u64(array);
+ data->read.time_running = *array;
+ array++;
+ }
+
+ /* PERF_FORMAT_ID is forced for PERF_SAMPLE_READ */
+ if (read_format & PERF_FORMAT_GROUP) {
+ const u64 max_group_nr = UINT64_MAX /
+ sizeof(struct sample_read_value);
+
+ if (data->read.group.nr > max_group_nr)
+ return -EFAULT;
+ sz = data->read.group.nr *
+ sizeof(struct sample_read_value);
+ OVERFLOW_CHECK(array, sz, max_size);
+ data->read.group.values =
+ (struct sample_read_value *)array;
+ array = (void *)array + sz;
+ } else {
+ OVERFLOW_CHECK_u64(array);
+ data->read.one.id = *array;
+ array++;
+ }
+ }
+
+ if (type & PERF_SAMPLE_CALLCHAIN) {
+ const u64 max_callchain_nr = UINT64_MAX / sizeof(u64);
+
+ OVERFLOW_CHECK_u64(array);
+ data->callchain = (struct ip_callchain *)array++;
+ if (data->callchain->nr > max_callchain_nr)
+ return -EFAULT;
+ sz = data->callchain->nr * sizeof(u64);
+ OVERFLOW_CHECK(array, sz, max_size);
+ array = (void *)array + sz;
+ }
+
+ if (type & PERF_SAMPLE_RAW) {
+ OVERFLOW_CHECK_u64(array);
+ u.val64 = *array;
+ if (WARN_ONCE(swapped,
+ "Endianness of raw data not corrected!\n")) {
+ /* undo swap of u64, then swap on individual u32s */
+ u.val64 = bswap_64(u.val64);
+ u.val32[0] = bswap_32(u.val32[0]);
+ u.val32[1] = bswap_32(u.val32[1]);
+ }
+ data->raw_size = u.val32[0];
+ array = (void *)array + sizeof(u32);
+
+ OVERFLOW_CHECK(array, data->raw_size, max_size);
+ data->raw_data = (void *)array;
+ array = (void *)array + data->raw_size;
+ }
+
+ if (type & PERF_SAMPLE_BRANCH_STACK) {
+ const u64 max_branch_nr = UINT64_MAX /
+ sizeof(struct branch_entry);
+
+ OVERFLOW_CHECK_u64(array);
+ data->branch_stack = (struct branch_stack *)array++;
+
+ if (data->branch_stack->nr > max_branch_nr)
+ return -EFAULT;
+ sz = data->branch_stack->nr * sizeof(struct branch_entry);
+ OVERFLOW_CHECK(array, sz, max_size);
+ array = (void *)array + sz;
+ }
+
+ if (type & PERF_SAMPLE_REGS_USER) {
+ OVERFLOW_CHECK_u64(array);
+ data->user_regs.abi = *array;
+ array++;
+
+ if (data->user_regs.abi) {
+ u64 mask = evsel->attr.sample_regs_user;
+
+ sz = hweight_long(mask) * sizeof(u64);
+ OVERFLOW_CHECK(array, sz, max_size);
+ data->user_regs.mask = mask;
+ data->user_regs.regs = (u64 *)array;
+ array = (void *)array + sz;
+ }
+ }
+
+ if (type & PERF_SAMPLE_STACK_USER) {
+ OVERFLOW_CHECK_u64(array);
+ sz = *array++;
+
+ data->user_stack.offset = ((char *)(array - 1)
+ - (char *) event);
+
+ if (!sz) {
+ data->user_stack.size = 0;
+ } else {
+ OVERFLOW_CHECK(array, sz, max_size);
+ data->user_stack.data = (char *)array;
+ array = (void *)array + sz;
+ OVERFLOW_CHECK_u64(array);
+ data->user_stack.size = *array++;
+ if (WARN_ONCE(data->user_stack.size > sz,
+ "user stack dump failure\n"))
+ return -EFAULT;
+ }
+ }
+
+ data->weight = 0;
+ if (type & PERF_SAMPLE_WEIGHT) {
+ OVERFLOW_CHECK_u64(array);
+ data->weight = *array;
+ array++;
+ }
+
+ data->data_src = PERF_MEM_DATA_SRC_NONE;
+ if (type & PERF_SAMPLE_DATA_SRC) {
+ OVERFLOW_CHECK_u64(array);
+ data->data_src = *array;
+ array++;
+ }
+
+ data->transaction = 0;
+ if (type & PERF_SAMPLE_TRANSACTION) {
+ OVERFLOW_CHECK_u64(array);
+ data->transaction = *array;
+ array++;
+ }
+
+ return 0;
+}
+
+size_t perf_event__sample_event_size(const struct perf_sample *sample, u64 type,
+ u64 read_format)
+{
+ size_t sz, result = sizeof(struct sample_event);
+
+ if (type & PERF_SAMPLE_IDENTIFIER)
+ result += sizeof(u64);
+
+ if (type & PERF_SAMPLE_IP)
+ result += sizeof(u64);
+
+ if (type & PERF_SAMPLE_TID)
+ result += sizeof(u64);
+
+ if (type & PERF_SAMPLE_TIME)
+ result += sizeof(u64);
+
+ if (type & PERF_SAMPLE_ADDR)
+ result += sizeof(u64);
+
+ if (type & PERF_SAMPLE_ID)
+ result += sizeof(u64);
+
+ if (type & PERF_SAMPLE_STREAM_ID)
+ result += sizeof(u64);
+
+ if (type & PERF_SAMPLE_CPU)
+ result += sizeof(u64);
+
+ if (type & PERF_SAMPLE_PERIOD)
+ result += sizeof(u64);
+
+ if (type & PERF_SAMPLE_READ) {
+ result += sizeof(u64);
+ if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ result += sizeof(u64);
+ if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ result += sizeof(u64);
+ /* PERF_FORMAT_ID is forced for PERF_SAMPLE_READ */
+ if (read_format & PERF_FORMAT_GROUP) {
+ sz = sample->read.group.nr *
+ sizeof(struct sample_read_value);
+ result += sz;
+ } else {
+ result += sizeof(u64);
+ }
+ }
+
+ if (type & PERF_SAMPLE_CALLCHAIN) {
+ sz = (sample->callchain->nr + 1) * sizeof(u64);
+ result += sz;
+ }
+
+ if (type & PERF_SAMPLE_RAW) {
+ result += sizeof(u32);
+ result += sample->raw_size;
+ }
+
+ if (type & PERF_SAMPLE_BRANCH_STACK) {
+ sz = sample->branch_stack->nr * sizeof(struct branch_entry);
+ sz += sizeof(u64);
+ result += sz;
+ }
+
+ if (type & PERF_SAMPLE_REGS_USER) {
+ if (sample->user_regs.abi) {
+ result += sizeof(u64);
+ sz = hweight_long(sample->user_regs.mask) * sizeof(u64);
+ result += sz;
+ } else {
+ result += sizeof(u64);
+ }
+ }
+
+ if (type & PERF_SAMPLE_STACK_USER) {
+ sz = sample->user_stack.size;
+ result += sizeof(u64);
+ if (sz) {
+ result += sz;
+ result += sizeof(u64);
+ }
+ }
+
+ if (type & PERF_SAMPLE_WEIGHT)
+ result += sizeof(u64);
+
+ if (type & PERF_SAMPLE_DATA_SRC)
+ result += sizeof(u64);
+
+ if (type & PERF_SAMPLE_TRANSACTION)
+ result += sizeof(u64);
+
+ return result;
+}
+
+int perf_event__synthesize_sample(union perf_event *event, u64 type,
+ u64 read_format,
+ const struct perf_sample *sample,
+ bool swapped)
+{
+ u64 *array;
+ size_t sz;
+ /*
+ * used for cross-endian analysis. See git commit 65014ab3
+ * for why this goofiness is needed.
+ */
+ union u64_swap u;
+
+ array = event->sample.array;
+
+ if (type & PERF_SAMPLE_IDENTIFIER) {
+ *array = sample->id;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_IP) {
+ *array = sample->ip;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_TID) {
+ u.val32[0] = sample->pid;
+ u.val32[1] = sample->tid;
+ if (swapped) {
+ /*
+ * Inverse of what is done in perf_evsel__parse_sample
+ */
+ u.val32[0] = bswap_32(u.val32[0]);
+ u.val32[1] = bswap_32(u.val32[1]);
+ u.val64 = bswap_64(u.val64);
+ }
+
+ *array = u.val64;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_TIME) {
+ *array = sample->time;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_ADDR) {
+ *array = sample->addr;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_ID) {
+ *array = sample->id;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_STREAM_ID) {
+ *array = sample->stream_id;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_CPU) {
+ u.val32[0] = sample->cpu;
+ if (swapped) {
+ /*
+ * Inverse of what is done in perf_evsel__parse_sample
+ */
+ u.val32[0] = bswap_32(u.val32[0]);
+ u.val64 = bswap_64(u.val64);
+ }
+ *array = u.val64;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_PERIOD) {
+ *array = sample->period;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_READ) {
+ if (read_format & PERF_FORMAT_GROUP)
+ *array = sample->read.group.nr;
+ else
+ *array = sample->read.one.value;
+ array++;
+
+ if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) {
+ *array = sample->read.time_enabled;
+ array++;
+ }
+
+ if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) {
+ *array = sample->read.time_running;
+ array++;
+ }
+
+ /* PERF_FORMAT_ID is forced for PERF_SAMPLE_READ */
+ if (read_format & PERF_FORMAT_GROUP) {
+ sz = sample->read.group.nr *
+ sizeof(struct sample_read_value);
+ memcpy(array, sample->read.group.values, sz);
+ array = (void *)array + sz;
+ } else {
+ *array = sample->read.one.id;
+ array++;
+ }
+ }
+
+ if (type & PERF_SAMPLE_CALLCHAIN) {
+ sz = (sample->callchain->nr + 1) * sizeof(u64);
+ memcpy(array, sample->callchain, sz);
+ array = (void *)array + sz;
+ }
+
+ if (type & PERF_SAMPLE_RAW) {
+ u.val32[0] = sample->raw_size;
+ if (WARN_ONCE(swapped,
+ "Endianness of raw data not corrected!\n")) {
+ /*
+ * Inverse of what is done in perf_evsel__parse_sample
+ */
+ u.val32[0] = bswap_32(u.val32[0]);
+ u.val32[1] = bswap_32(u.val32[1]);
+ u.val64 = bswap_64(u.val64);
+ }
+ *array = u.val64;
+ array = (void *)array + sizeof(u32);
+
+ memcpy(array, sample->raw_data, sample->raw_size);
+ array = (void *)array + sample->raw_size;
+ }
+
+ if (type & PERF_SAMPLE_BRANCH_STACK) {
+ sz = sample->branch_stack->nr * sizeof(struct branch_entry);
+ sz += sizeof(u64);
+ memcpy(array, sample->branch_stack, sz);
+ array = (void *)array + sz;
+ }
+
+ if (type & PERF_SAMPLE_REGS_USER) {
+ if (sample->user_regs.abi) {
+ *array++ = sample->user_regs.abi;
+ sz = hweight_long(sample->user_regs.mask) * sizeof(u64);
+ memcpy(array, sample->user_regs.regs, sz);
+ array = (void *)array + sz;
+ } else {
+ *array++ = 0;
+ }
+ }
+
+ if (type & PERF_SAMPLE_STACK_USER) {
+ sz = sample->user_stack.size;
+ *array++ = sz;
+ if (sz) {
+ memcpy(array, sample->user_stack.data, sz);
+ array = (void *)array + sz;
+ *array++ = sz;
+ }
+ }
+
+ if (type & PERF_SAMPLE_WEIGHT) {
+ *array = sample->weight;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_DATA_SRC) {
+ *array = sample->data_src;
+ array++;
+ }
+
+ if (type & PERF_SAMPLE_TRANSACTION) {
+ *array = sample->transaction;
+ array++;
+ }
+
+ return 0;
+}
+
+struct format_field *perf_evsel__field(struct perf_evsel *evsel, const char *name)
+{
+ return pevent_find_field(evsel->tp_format, name);
+}
+
+void *perf_evsel__rawptr(struct perf_evsel *evsel, struct perf_sample *sample,
+ const char *name)
+{
+ struct format_field *field = perf_evsel__field(evsel, name);
+ int offset;
+
+ if (!field)
+ return NULL;
+
+ offset = field->offset;
+
+ if (field->flags & FIELD_IS_DYNAMIC) {
+ offset = *(int *)(sample->raw_data + field->offset);
+ offset &= 0xffff;
+ }
+
+ return sample->raw_data + offset;
+}
+
+u64 perf_evsel__intval(struct perf_evsel *evsel, struct perf_sample *sample,
+ const char *name)
+{
+ struct format_field *field = perf_evsel__field(evsel, name);
+ void *ptr;
+ u64 value;
+
+ if (!field)
+ return 0;
+
+ ptr = sample->raw_data + field->offset;
+
+ switch (field->size) {
+ case 1:
+ return *(u8 *)ptr;
+ case 2:
+ value = *(u16 *)ptr;
+ break;
+ case 4:
+ value = *(u32 *)ptr;
+ break;
+ case 8:
+ value = *(u64 *)ptr;
+ break;
+ default:
+ return 0;
+ }
+
+ if (!evsel->needs_swap)
+ return value;
+
+ switch (field->size) {
+ case 2:
+ return bswap_16(value);
+ case 4:
+ return bswap_32(value);
+ case 8:
+ return bswap_64(value);
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static int comma_fprintf(FILE *fp, bool *first, const char *fmt, ...)
+{
+ va_list args;
+ int ret = 0;
+
+ if (!*first) {
+ ret += fprintf(fp, ",");
+ } else {
+ ret += fprintf(fp, ":");
+ *first = false;
+ }
+
+ va_start(args, fmt);
+ ret += vfprintf(fp, fmt, args);
+ va_end(args);
+ return ret;
+}
+
+static int __if_fprintf(FILE *fp, bool *first, const char *field, u64 value)
+{
+ if (value == 0)
+ return 0;
+
+ return comma_fprintf(fp, first, " %s: %" PRIu64, field, value);
+}
+
+#define if_print(field) printed += __if_fprintf(fp, &first, #field, evsel->attr.field)
+
+struct bit_names {
+ int bit;
+ const char *name;
+};
+
+static int bits__fprintf(FILE *fp, const char *field, u64 value,
+ struct bit_names *bits, bool *first)
+{
+ int i = 0, printed = comma_fprintf(fp, first, " %s: ", field);
+ bool first_bit = true;
+
+ do {
+ if (value & bits[i].bit) {
+ printed += fprintf(fp, "%s%s", first_bit ? "" : "|", bits[i].name);
+ first_bit = false;
+ }
+ } while (bits[++i].name != NULL);
+
+ return printed;
+}
+
+static int sample_type__fprintf(FILE *fp, bool *first, u64 value)
+{
+#define bit_name(n) { PERF_SAMPLE_##n, #n }
+ struct bit_names bits[] = {
+ bit_name(IP), bit_name(TID), bit_name(TIME), bit_name(ADDR),
+ bit_name(READ), bit_name(CALLCHAIN), bit_name(ID), bit_name(CPU),
+ bit_name(PERIOD), bit_name(STREAM_ID), bit_name(RAW),
+ bit_name(BRANCH_STACK), bit_name(REGS_USER), bit_name(STACK_USER),
+ bit_name(IDENTIFIER),
+ { .name = NULL, }
+ };
+#undef bit_name
+ return bits__fprintf(fp, "sample_type", value, bits, first);
+}
+
+static int read_format__fprintf(FILE *fp, bool *first, u64 value)
+{
+#define bit_name(n) { PERF_FORMAT_##n, #n }
+ struct bit_names bits[] = {
+ bit_name(TOTAL_TIME_ENABLED), bit_name(TOTAL_TIME_RUNNING),
+ bit_name(ID), bit_name(GROUP),
+ { .name = NULL, }
+ };
+#undef bit_name
+ return bits__fprintf(fp, "read_format", value, bits, first);
+}
+
+int perf_evsel__fprintf(struct perf_evsel *evsel,
+ struct perf_attr_details *details, FILE *fp)
+{
+ bool first = true;
+ int printed = 0;
+
+ if (details->event_group) {
+ struct perf_evsel *pos;
+
+ if (!perf_evsel__is_group_leader(evsel))
+ return 0;
+
+ if (evsel->nr_members > 1)
+ printed += fprintf(fp, "%s{", evsel->group_name ?: "");
+
+ printed += fprintf(fp, "%s", perf_evsel__name(evsel));
+ for_each_group_member(pos, evsel)
+ printed += fprintf(fp, ",%s", perf_evsel__name(pos));
+
+ if (evsel->nr_members > 1)
+ printed += fprintf(fp, "}");
+ goto out;
+ }
+
+ printed += fprintf(fp, "%s", perf_evsel__name(evsel));
+
+ if (details->verbose || details->freq) {
+ printed += comma_fprintf(fp, &first, " sample_freq=%" PRIu64,
+ (u64)evsel->attr.sample_freq);
+ }
+
+ if (details->verbose) {
+ if_print(type);
+ if_print(config);
+ if_print(config1);
+ if_print(config2);
+ if_print(size);
+ printed += sample_type__fprintf(fp, &first, evsel->attr.sample_type);
+ if (evsel->attr.read_format)
+ printed += read_format__fprintf(fp, &first, evsel->attr.read_format);
+ if_print(disabled);
+ if_print(inherit);
+ if_print(pinned);
+ if_print(exclusive);
+ if_print(exclude_user);
+ if_print(exclude_kernel);
+ if_print(exclude_hv);
+ if_print(exclude_idle);
+ if_print(mmap);
+ if_print(mmap2);
+ if_print(comm);
+ if_print(freq);
+ if_print(inherit_stat);
+ if_print(enable_on_exec);
+ if_print(task);
+ if_print(watermark);
+ if_print(precise_ip);
+ if_print(mmap_data);
+ if_print(sample_id_all);
+ if_print(exclude_host);
+ if_print(exclude_guest);
+ if_print(__reserved_1);
+ if_print(wakeup_events);
+ if_print(bp_type);
+ if_print(branch_sample_type);
+ }
+out:
+ fputc('\n', fp);
+ return ++printed;
+}
+
+bool perf_evsel__fallback(struct perf_evsel *evsel, int err,
+ char *msg, size_t msgsize)
+{
+ if ((err == ENOENT || err == ENXIO || err == ENODEV) &&
+ evsel->attr.type == PERF_TYPE_HARDWARE &&
+ evsel->attr.config == PERF_COUNT_HW_CPU_CYCLES) {
+ /*
+ * If it's cycles then fall back to hrtimer based
+ * cpu-clock-tick sw counter, which is always available even if
+ * no PMU support.
+ *
+ * PPC returns ENXIO until 2.6.37 (behavior changed with commit
+ * b0a873e).
+ */
+ scnprintf(msg, msgsize, "%s",
+"The cycles event is not supported, trying to fall back to cpu-clock-ticks");
+
+ evsel->attr.type = PERF_TYPE_SOFTWARE;
+ evsel->attr.config = PERF_COUNT_SW_CPU_CLOCK;
+
+ zfree(&evsel->name);
+ return true;
+ }
+
+ return false;
+}
+
+int perf_evsel__open_strerror(struct perf_evsel *evsel, struct target *target,
+ int err, char *msg, size_t size)
+{
+ switch (err) {
+ case EPERM:
+ case EACCES:
+ return scnprintf(msg, size,
+ "You may not have permission to collect %sstats.\n"
+ "Consider tweaking /proc/sys/kernel/perf_event_paranoid:\n"
+ " -1 - Not paranoid at all\n"
+ " 0 - Disallow raw tracepoint access for unpriv\n"
+ " 1 - Disallow cpu events for unpriv\n"
+ " 2 - Disallow kernel profiling for unpriv",
+ target->system_wide ? "system-wide " : "");
+ case ENOENT:
+ return scnprintf(msg, size, "The %s event is not supported.",
+ perf_evsel__name(evsel));
+ case EMFILE:
+ return scnprintf(msg, size, "%s",
+ "Too many events are opened.\n"
+ "Try again after reducing the number of events.");
+ case ENODEV:
+ if (target->cpu_list)
+ return scnprintf(msg, size, "%s",
+ "No such device - did you specify an out-of-range profile CPU?\n");
+ break;
+ case EOPNOTSUPP:
+ if (evsel->attr.precise_ip)
+ return scnprintf(msg, size, "%s",
+ "\'precise\' request may not be supported. Try removing 'p' modifier.");
+#if defined(__i386__) || defined(__x86_64__)
+ if (evsel->attr.type == PERF_TYPE_HARDWARE)
+ return scnprintf(msg, size, "%s",
+ "No hardware sampling interrupt available.\n"
+ "No APIC? If so then you can boot the kernel with the \"lapic\" boot parameter to force-enable it.");
+#endif
+ break;
+ default:
+ break;
+ }
+
+ return scnprintf(msg, size,
+ "The sys_perf_event_open() syscall returned with %d (%s) for event (%s). \n"
+ "/bin/dmesg may provide additional information.\n"
+ "No CONFIG_PERF_EVENTS=y kernel support configured?\n",
+ err, strerror(err), perf_evsel__name(evsel));
+}
diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h
new file mode 100644
index 00000000000..a52e9a5bb2d
--- /dev/null
+++ b/tools/perf/util/evsel.h
@@ -0,0 +1,365 @@
+#ifndef __PERF_EVSEL_H
+#define __PERF_EVSEL_H 1
+
+#include <linux/list.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <linux/perf_event.h>
+#include <linux/types.h>
+#include "xyarray.h"
+#include "cgroup.h"
+#include "hist.h"
+#include "symbol.h"
+
+struct perf_counts_values {
+ union {
+ struct {
+ u64 val;
+ u64 ena;
+ u64 run;
+ };
+ u64 values[3];
+ };
+};
+
+struct perf_counts {
+ s8 scaled;
+ struct perf_counts_values aggr;
+ struct perf_counts_values cpu[];
+};
+
+struct perf_evsel;
+
+/*
+ * Per fd, to map back from PERF_SAMPLE_ID to evsel, only used when there are
+ * more than one entry in the evlist.
+ */
+struct perf_sample_id {
+ struct hlist_node node;
+ u64 id;
+ struct perf_evsel *evsel;
+
+ /* Holds total ID period value for PERF_SAMPLE_READ processing. */
+ u64 period;
+};
+
+/** struct perf_evsel - event selector
+ *
+ * @name - Can be set to retain the original event name passed by the user,
+ * so that when showing results in tools such as 'perf stat', we
+ * show the name used, not some alias.
+ * @id_pos: the position of the event id (PERF_SAMPLE_ID or
+ * PERF_SAMPLE_IDENTIFIER) in a sample event i.e. in the array of
+ * struct sample_event
+ * @is_pos: the position (counting backwards) of the event id (PERF_SAMPLE_ID or
+ * PERF_SAMPLE_IDENTIFIER) in a non-sample event i.e. if sample_id_all
+ * is used there is an id sample appended to non-sample events
+ */
+struct perf_evsel {
+ struct list_head node;
+ struct perf_event_attr attr;
+ char *filter;
+ struct xyarray *fd;
+ struct xyarray *sample_id;
+ u64 *id;
+ struct perf_counts *counts;
+ struct perf_counts *prev_raw_counts;
+ int idx;
+ u32 ids;
+ struct hists hists;
+ char *name;
+ double scale;
+ const char *unit;
+ struct event_format *tp_format;
+ union {
+ void *priv;
+ off_t id_offset;
+ };
+ struct cgroup_sel *cgrp;
+ void *handler;
+ struct cpu_map *cpus;
+ unsigned int sample_size;
+ int id_pos;
+ int is_pos;
+ bool supported;
+ bool needs_swap;
+ /* parse modifier helper */
+ int exclude_GH;
+ int nr_members;
+ int sample_read;
+ struct perf_evsel *leader;
+ char *group_name;
+};
+
+union u64_swap {
+ u64 val64;
+ u32 val32[2];
+};
+
+#define hists_to_evsel(h) container_of(h, struct perf_evsel, hists)
+
+struct cpu_map;
+struct thread_map;
+struct perf_evlist;
+struct record_opts;
+
+struct perf_evsel *perf_evsel__new_idx(struct perf_event_attr *attr, int idx);
+
+static inline struct perf_evsel *perf_evsel__new(struct perf_event_attr *attr)
+{
+ return perf_evsel__new_idx(attr, 0);
+}
+
+struct perf_evsel *perf_evsel__newtp_idx(const char *sys, const char *name, int idx);
+
+static inline struct perf_evsel *perf_evsel__newtp(const char *sys, const char *name)
+{
+ return perf_evsel__newtp_idx(sys, name, 0);
+}
+
+struct event_format *event_format__new(const char *sys, const char *name);
+
+void perf_evsel__init(struct perf_evsel *evsel,
+ struct perf_event_attr *attr, int idx);
+void perf_evsel__exit(struct perf_evsel *evsel);
+void perf_evsel__delete(struct perf_evsel *evsel);
+
+void perf_evsel__config(struct perf_evsel *evsel,
+ struct record_opts *opts);
+
+int __perf_evsel__sample_size(u64 sample_type);
+void perf_evsel__calc_id_pos(struct perf_evsel *evsel);
+
+bool perf_evsel__is_cache_op_valid(u8 type, u8 op);
+
+#define PERF_EVSEL__MAX_ALIASES 8
+
+extern const char *perf_evsel__hw_cache[PERF_COUNT_HW_CACHE_MAX]
+ [PERF_EVSEL__MAX_ALIASES];
+extern const char *perf_evsel__hw_cache_op[PERF_COUNT_HW_CACHE_OP_MAX]
+ [PERF_EVSEL__MAX_ALIASES];
+extern const char *perf_evsel__hw_cache_result[PERF_COUNT_HW_CACHE_RESULT_MAX]
+ [PERF_EVSEL__MAX_ALIASES];
+extern const char *perf_evsel__hw_names[PERF_COUNT_HW_MAX];
+extern const char *perf_evsel__sw_names[PERF_COUNT_SW_MAX];
+int __perf_evsel__hw_cache_type_op_res_name(u8 type, u8 op, u8 result,
+ char *bf, size_t size);
+const char *perf_evsel__name(struct perf_evsel *evsel);
+
+const char *perf_evsel__group_name(struct perf_evsel *evsel);
+int perf_evsel__group_desc(struct perf_evsel *evsel, char *buf, size_t size);
+
+int perf_evsel__alloc_fd(struct perf_evsel *evsel, int ncpus, int nthreads);
+int perf_evsel__alloc_id(struct perf_evsel *evsel, int ncpus, int nthreads);
+int perf_evsel__alloc_counts(struct perf_evsel *evsel, int ncpus);
+void perf_evsel__reset_counts(struct perf_evsel *evsel, int ncpus);
+void perf_evsel__free_fd(struct perf_evsel *evsel);
+void perf_evsel__free_id(struct perf_evsel *evsel);
+void perf_evsel__free_counts(struct perf_evsel *evsel);
+void perf_evsel__close_fd(struct perf_evsel *evsel, int ncpus, int nthreads);
+
+void __perf_evsel__set_sample_bit(struct perf_evsel *evsel,
+ enum perf_event_sample_format bit);
+void __perf_evsel__reset_sample_bit(struct perf_evsel *evsel,
+ enum perf_event_sample_format bit);
+
+#define perf_evsel__set_sample_bit(evsel, bit) \
+ __perf_evsel__set_sample_bit(evsel, PERF_SAMPLE_##bit)
+
+#define perf_evsel__reset_sample_bit(evsel, bit) \
+ __perf_evsel__reset_sample_bit(evsel, PERF_SAMPLE_##bit)
+
+void perf_evsel__set_sample_id(struct perf_evsel *evsel,
+ bool use_sample_identifier);
+
+int perf_evsel__set_filter(struct perf_evsel *evsel, int ncpus, int nthreads,
+ const char *filter);
+int perf_evsel__enable(struct perf_evsel *evsel, int ncpus, int nthreads);
+
+int perf_evsel__open_per_cpu(struct perf_evsel *evsel,
+ struct cpu_map *cpus);
+int perf_evsel__open_per_thread(struct perf_evsel *evsel,
+ struct thread_map *threads);
+int perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus,
+ struct thread_map *threads);
+void perf_evsel__close(struct perf_evsel *evsel, int ncpus, int nthreads);
+
+struct perf_sample;
+
+void *perf_evsel__rawptr(struct perf_evsel *evsel, struct perf_sample *sample,
+ const char *name);
+u64 perf_evsel__intval(struct perf_evsel *evsel, struct perf_sample *sample,
+ const char *name);
+
+static inline char *perf_evsel__strval(struct perf_evsel *evsel,
+ struct perf_sample *sample,
+ const char *name)
+{
+ return perf_evsel__rawptr(evsel, sample, name);
+}
+
+struct format_field;
+
+struct format_field *perf_evsel__field(struct perf_evsel *evsel, const char *name);
+
+#define perf_evsel__match(evsel, t, c) \
+ (evsel->attr.type == PERF_TYPE_##t && \
+ evsel->attr.config == PERF_COUNT_##c)
+
+static inline bool perf_evsel__match2(struct perf_evsel *e1,
+ struct perf_evsel *e2)
+{
+ return (e1->attr.type == e2->attr.type) &&
+ (e1->attr.config == e2->attr.config);
+}
+
+#define perf_evsel__cmp(a, b) \
+ ((a) && \
+ (b) && \
+ (a)->attr.type == (b)->attr.type && \
+ (a)->attr.config == (b)->attr.config)
+
+int __perf_evsel__read_on_cpu(struct perf_evsel *evsel,
+ int cpu, int thread, bool scale);
+
+/**
+ * perf_evsel__read_on_cpu - Read out the results on a CPU and thread
+ *
+ * @evsel - event selector to read value
+ * @cpu - CPU of interest
+ * @thread - thread of interest
+ */
+static inline int perf_evsel__read_on_cpu(struct perf_evsel *evsel,
+ int cpu, int thread)
+{
+ return __perf_evsel__read_on_cpu(evsel, cpu, thread, false);
+}
+
+/**
+ * perf_evsel__read_on_cpu_scaled - Read out the results on a CPU and thread, scaled
+ *
+ * @evsel - event selector to read value
+ * @cpu - CPU of interest
+ * @thread - thread of interest
+ */
+static inline int perf_evsel__read_on_cpu_scaled(struct perf_evsel *evsel,
+ int cpu, int thread)
+{
+ return __perf_evsel__read_on_cpu(evsel, cpu, thread, true);
+}
+
+int __perf_evsel__read(struct perf_evsel *evsel, int ncpus, int nthreads,
+ bool scale);
+
+/**
+ * perf_evsel__read - Read the aggregate results on all CPUs
+ *
+ * @evsel - event selector to read value
+ * @ncpus - Number of cpus affected, from zero
+ * @nthreads - Number of threads affected, from zero
+ */
+static inline int perf_evsel__read(struct perf_evsel *evsel,
+ int ncpus, int nthreads)
+{
+ return __perf_evsel__read(evsel, ncpus, nthreads, false);
+}
+
+/**
+ * perf_evsel__read_scaled - Read the aggregate results on all CPUs, scaled
+ *
+ * @evsel - event selector to read value
+ * @ncpus - Number of cpus affected, from zero
+ * @nthreads - Number of threads affected, from zero
+ */
+static inline int perf_evsel__read_scaled(struct perf_evsel *evsel,
+ int ncpus, int nthreads)
+{
+ return __perf_evsel__read(evsel, ncpus, nthreads, true);
+}
+
+void hists__init(struct hists *hists);
+
+int perf_evsel__parse_sample(struct perf_evsel *evsel, union perf_event *event,
+ struct perf_sample *sample);
+
+static inline struct perf_evsel *perf_evsel__next(struct perf_evsel *evsel)
+{
+ return list_entry(evsel->node.next, struct perf_evsel, node);
+}
+
+static inline struct perf_evsel *perf_evsel__prev(struct perf_evsel *evsel)
+{
+ return list_entry(evsel->node.prev, struct perf_evsel, node);
+}
+
+/**
+ * perf_evsel__is_group_leader - Return whether given evsel is a leader event
+ *
+ * @evsel - evsel selector to be tested
+ *
+ * Return %true if @evsel is a group leader or a stand-alone event
+ */
+static inline bool perf_evsel__is_group_leader(const struct perf_evsel *evsel)
+{
+ return evsel->leader == evsel;
+}
+
+/**
+ * perf_evsel__is_group_event - Return whether given evsel is a group event
+ *
+ * @evsel - evsel selector to be tested
+ *
+ * Return %true iff event group view is enabled and @evsel is a actual group
+ * leader which has other members in the group
+ */
+static inline bool perf_evsel__is_group_event(struct perf_evsel *evsel)
+{
+ if (!symbol_conf.event_group)
+ return false;
+
+ return perf_evsel__is_group_leader(evsel) && evsel->nr_members > 1;
+}
+
+/**
+ * perf_evsel__is_function_event - Return whether given evsel is a function
+ * trace event
+ *
+ * @evsel - evsel selector to be tested
+ *
+ * Return %true if event is function trace event
+ */
+static inline bool perf_evsel__is_function_event(struct perf_evsel *evsel)
+{
+#define FUNCTION_EVENT "ftrace:function"
+
+ return evsel->name &&
+ !strncmp(FUNCTION_EVENT, evsel->name, sizeof(FUNCTION_EVENT));
+
+#undef FUNCTION_EVENT
+}
+
+struct perf_attr_details {
+ bool freq;
+ bool verbose;
+ bool event_group;
+};
+
+int perf_evsel__fprintf(struct perf_evsel *evsel,
+ struct perf_attr_details *details, FILE *fp);
+
+bool perf_evsel__fallback(struct perf_evsel *evsel, int err,
+ char *msg, size_t msgsize);
+int perf_evsel__open_strerror(struct perf_evsel *evsel, struct target *target,
+ int err, char *msg, size_t size);
+
+static inline int perf_evsel__group_idx(struct perf_evsel *evsel)
+{
+ return evsel->idx - evsel->leader->idx;
+}
+
+#define for_each_group_member(_evsel, _leader) \
+for ((_evsel) = list_entry((_leader)->node.next, struct perf_evsel, node); \
+ (_evsel) && (_evsel)->leader == (_leader); \
+ (_evsel) = list_entry((_evsel)->node.next, struct perf_evsel, node))
+
+#endif /* __PERF_EVSEL_H */
diff --git a/tools/perf/util/exec_cmd.c b/tools/perf/util/exec_cmd.c
new file mode 100644
index 00000000000..7adf4ad15d8
--- /dev/null
+++ b/tools/perf/util/exec_cmd.c
@@ -0,0 +1,148 @@
+#include "cache.h"
+#include "exec_cmd.h"
+#include "quote.h"
+
+#include <string.h>
+
+#define MAX_ARGS 32
+
+static const char *argv_exec_path;
+static const char *argv0_path;
+
+const char *system_path(const char *path)
+{
+ static const char *prefix = PREFIX;
+ struct strbuf d = STRBUF_INIT;
+
+ if (is_absolute_path(path))
+ return path;
+
+ strbuf_addf(&d, "%s/%s", prefix, path);
+ path = strbuf_detach(&d, NULL);
+ return path;
+}
+
+const char *perf_extract_argv0_path(const char *argv0)
+{
+ const char *slash;
+
+ if (!argv0 || !*argv0)
+ return NULL;
+ slash = argv0 + strlen(argv0);
+
+ while (argv0 <= slash && !is_dir_sep(*slash))
+ slash--;
+
+ if (slash >= argv0) {
+ argv0_path = strndup(argv0, slash - argv0);
+ return argv0_path ? slash + 1 : NULL;
+ }
+
+ return argv0;
+}
+
+void perf_set_argv_exec_path(const char *exec_path)
+{
+ argv_exec_path = exec_path;
+ /*
+ * Propagate this setting to external programs.
+ */
+ setenv(EXEC_PATH_ENVIRONMENT, exec_path, 1);
+}
+
+
+/* Returns the highest-priority, location to look for perf programs. */
+const char *perf_exec_path(void)
+{
+ const char *env;
+
+ if (argv_exec_path)
+ return argv_exec_path;
+
+ env = getenv(EXEC_PATH_ENVIRONMENT);
+ if (env && *env) {
+ return env;
+ }
+
+ return system_path(PERF_EXEC_PATH);
+}
+
+static void add_path(struct strbuf *out, const char *path)
+{
+ if (path && *path) {
+ if (is_absolute_path(path))
+ strbuf_addstr(out, path);
+ else
+ strbuf_addstr(out, make_nonrelative_path(path));
+
+ strbuf_addch(out, PATH_SEP);
+ }
+}
+
+void setup_path(void)
+{
+ const char *old_path = getenv("PATH");
+ struct strbuf new_path = STRBUF_INIT;
+
+ add_path(&new_path, perf_exec_path());
+ add_path(&new_path, argv0_path);
+
+ if (old_path)
+ strbuf_addstr(&new_path, old_path);
+ else
+ strbuf_addstr(&new_path, "/usr/local/bin:/usr/bin:/bin");
+
+ setenv("PATH", new_path.buf, 1);
+
+ strbuf_release(&new_path);
+}
+
+static const char **prepare_perf_cmd(const char **argv)
+{
+ int argc;
+ const char **nargv;
+
+ for (argc = 0; argv[argc]; argc++)
+ ; /* just counting */
+ nargv = malloc(sizeof(*nargv) * (argc + 2));
+
+ nargv[0] = "perf";
+ for (argc = 0; argv[argc]; argc++)
+ nargv[argc + 1] = argv[argc];
+ nargv[argc + 1] = NULL;
+ return nargv;
+}
+
+int execv_perf_cmd(const char **argv) {
+ const char **nargv = prepare_perf_cmd(argv);
+
+ /* execvp() can only ever return if it fails */
+ execvp("perf", (char **)nargv);
+
+ free(nargv);
+ return -1;
+}
+
+
+int execl_perf_cmd(const char *cmd,...)
+{
+ int argc;
+ const char *argv[MAX_ARGS + 1];
+ const char *arg;
+ va_list param;
+
+ va_start(param, cmd);
+ argv[0] = cmd;
+ argc = 1;
+ while (argc < MAX_ARGS) {
+ arg = argv[argc++] = va_arg(param, char *);
+ if (!arg)
+ break;
+ }
+ va_end(param);
+ if (MAX_ARGS <= argc)
+ return error("too many args to run %s", cmd);
+
+ argv[argc] = NULL;
+ return execv_perf_cmd(argv);
+}
diff --git a/tools/perf/util/exec_cmd.h b/tools/perf/util/exec_cmd.h
new file mode 100644
index 00000000000..bc4b915963f
--- /dev/null
+++ b/tools/perf/util/exec_cmd.h
@@ -0,0 +1,12 @@
+#ifndef __PERF_EXEC_CMD_H
+#define __PERF_EXEC_CMD_H
+
+extern void perf_set_argv_exec_path(const char *exec_path);
+extern const char *perf_extract_argv0_path(const char *path);
+extern const char *perf_exec_path(void);
+extern void setup_path(void);
+extern int execv_perf_cmd(const char **argv); /* NULL terminated */
+extern int execl_perf_cmd(const char *cmd, ...);
+extern const char *system_path(const char *path);
+
+#endif /* __PERF_EXEC_CMD_H */
diff --git a/tools/perf/util/generate-cmdlist.sh b/tools/perf/util/generate-cmdlist.sh
new file mode 100755
index 00000000000..36a885d2cd2
--- /dev/null
+++ b/tools/perf/util/generate-cmdlist.sh
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+echo "/* Automatically generated by $0 */
+struct cmdname_help
+{
+ char name[16];
+ char help[80];
+};
+
+static struct cmdname_help common_cmds[] = {"
+
+sed -n -e 's/^perf-\([^ ]*\)[ ].* common.*/\1/p' command-list.txt |
+sort |
+while read cmd
+do
+ sed -n '
+ /^NAME/,/perf-'"$cmd"'/H
+ ${
+ x
+ s/.*perf-'"$cmd"' - \(.*\)/ {"'"$cmd"'", "\1"},/
+ p
+ }' "Documentation/perf-$cmd.txt"
+done
+
+echo "#ifdef HAVE_LIBELF_SUPPORT"
+sed -n -e 's/^perf-\([^ ]*\)[ ].* full.*/\1/p' command-list.txt |
+sort |
+while read cmd
+do
+ sed -n '
+ /^NAME/,/perf-'"$cmd"'/H
+ ${
+ x
+ s/.*perf-'"$cmd"' - \(.*\)/ {"'"$cmd"'", "\1"},/
+ p
+ }' "Documentation/perf-$cmd.txt"
+done
+echo "#endif /* HAVE_LIBELF_SUPPORT */"
+echo "};"
diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c
new file mode 100644
index 00000000000..893f8e2df92
--- /dev/null
+++ b/tools/perf/util/header.c
@@ -0,0 +1,3071 @@
+#include "util.h"
+#include <sys/types.h>
+#include <byteswap.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <linux/list.h>
+#include <linux/kernel.h>
+#include <linux/bitops.h>
+#include <sys/utsname.h>
+
+#include "evlist.h"
+#include "evsel.h"
+#include "header.h"
+#include "../perf.h"
+#include "trace-event.h"
+#include "session.h"
+#include "symbol.h"
+#include "debug.h"
+#include "cpumap.h"
+#include "pmu.h"
+#include "vdso.h"
+#include "strbuf.h"
+#include "build-id.h"
+#include "data.h"
+
+static bool no_buildid_cache = false;
+
+static u32 header_argc;
+static const char **header_argv;
+
+/*
+ * magic2 = "PERFILE2"
+ * must be a numerical value to let the endianness
+ * determine the memory layout. That way we are able
+ * to detect endianness when reading the perf.data file
+ * back.
+ *
+ * we check for legacy (PERFFILE) format.
+ */
+static const char *__perf_magic1 = "PERFFILE";
+static const u64 __perf_magic2 = 0x32454c4946524550ULL;
+static const u64 __perf_magic2_sw = 0x50455246494c4532ULL;
+
+#define PERF_MAGIC __perf_magic2
+
+struct perf_file_attr {
+ struct perf_event_attr attr;
+ struct perf_file_section ids;
+};
+
+void perf_header__set_feat(struct perf_header *header, int feat)
+{
+ set_bit(feat, header->adds_features);
+}
+
+void perf_header__clear_feat(struct perf_header *header, int feat)
+{
+ clear_bit(feat, header->adds_features);
+}
+
+bool perf_header__has_feat(const struct perf_header *header, int feat)
+{
+ return test_bit(feat, header->adds_features);
+}
+
+static int do_write(int fd, const void *buf, size_t size)
+{
+ while (size) {
+ int ret = write(fd, buf, size);
+
+ if (ret < 0)
+ return -errno;
+
+ size -= ret;
+ buf += ret;
+ }
+
+ return 0;
+}
+
+#define NAME_ALIGN 64
+
+static int write_padded(int fd, const void *bf, size_t count,
+ size_t count_aligned)
+{
+ static const char zero_buf[NAME_ALIGN];
+ int err = do_write(fd, bf, count);
+
+ if (!err)
+ err = do_write(fd, zero_buf, count_aligned - count);
+
+ return err;
+}
+
+static int do_write_string(int fd, const char *str)
+{
+ u32 len, olen;
+ int ret;
+
+ olen = strlen(str) + 1;
+ len = PERF_ALIGN(olen, NAME_ALIGN);
+
+ /* write len, incl. \0 */
+ ret = do_write(fd, &len, sizeof(len));
+ if (ret < 0)
+ return ret;
+
+ return write_padded(fd, str, olen, len);
+}
+
+static char *do_read_string(int fd, struct perf_header *ph)
+{
+ ssize_t sz, ret;
+ u32 len;
+ char *buf;
+
+ sz = readn(fd, &len, sizeof(len));
+ if (sz < (ssize_t)sizeof(len))
+ return NULL;
+
+ if (ph->needs_swap)
+ len = bswap_32(len);
+
+ buf = malloc(len);
+ if (!buf)
+ return NULL;
+
+ ret = readn(fd, buf, len);
+ if (ret == (ssize_t)len) {
+ /*
+ * strings are padded by zeroes
+ * thus the actual strlen of buf
+ * may be less than len
+ */
+ return buf;
+ }
+
+ free(buf);
+ return NULL;
+}
+
+int
+perf_header__set_cmdline(int argc, const char **argv)
+{
+ int i;
+
+ /*
+ * If header_argv has already been set, do not override it.
+ * This allows a command to set the cmdline, parse args and
+ * then call another builtin function that implements a
+ * command -- e.g, cmd_kvm calling cmd_record.
+ */
+ if (header_argv)
+ return 0;
+
+ header_argc = (u32)argc;
+
+ /* do not include NULL termination */
+ header_argv = calloc(argc, sizeof(char *));
+ if (!header_argv)
+ return -ENOMEM;
+
+ /*
+ * must copy argv contents because it gets moved
+ * around during option parsing
+ */
+ for (i = 0; i < argc ; i++)
+ header_argv[i] = argv[i];
+
+ return 0;
+}
+
+#define dsos__for_each_with_build_id(pos, head) \
+ list_for_each_entry(pos, head, node) \
+ if (!pos->has_build_id) \
+ continue; \
+ else
+
+static int write_buildid(const char *name, size_t name_len, u8 *build_id,
+ pid_t pid, u16 misc, int fd)
+{
+ int err;
+ struct build_id_event b;
+ size_t len;
+
+ len = name_len + 1;
+ len = PERF_ALIGN(len, NAME_ALIGN);
+
+ memset(&b, 0, sizeof(b));
+ memcpy(&b.build_id, build_id, BUILD_ID_SIZE);
+ b.pid = pid;
+ b.header.misc = misc;
+ b.header.size = sizeof(b) + len;
+
+ err = do_write(fd, &b, sizeof(b));
+ if (err < 0)
+ return err;
+
+ return write_padded(fd, name, name_len + 1, len);
+}
+
+static int __dsos__write_buildid_table(struct list_head *head,
+ struct machine *machine,
+ pid_t pid, u16 misc, int fd)
+{
+ char nm[PATH_MAX];
+ struct dso *pos;
+
+ dsos__for_each_with_build_id(pos, head) {
+ int err;
+ const char *name;
+ size_t name_len;
+
+ if (!pos->hit)
+ continue;
+
+ if (is_vdso_map(pos->short_name)) {
+ name = (char *) VDSO__MAP_NAME;
+ name_len = sizeof(VDSO__MAP_NAME) + 1;
+ } else if (dso__is_kcore(pos)) {
+ machine__mmap_name(machine, nm, sizeof(nm));
+ name = nm;
+ name_len = strlen(nm) + 1;
+ } else {
+ name = pos->long_name;
+ name_len = pos->long_name_len + 1;
+ }
+
+ err = write_buildid(name, name_len, pos->build_id,
+ pid, misc, fd);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int machine__write_buildid_table(struct machine *machine, int fd)
+{
+ int err;
+ u16 kmisc = PERF_RECORD_MISC_KERNEL,
+ umisc = PERF_RECORD_MISC_USER;
+
+ if (!machine__is_host(machine)) {
+ kmisc = PERF_RECORD_MISC_GUEST_KERNEL;
+ umisc = PERF_RECORD_MISC_GUEST_USER;
+ }
+
+ err = __dsos__write_buildid_table(&machine->kernel_dsos, machine,
+ machine->pid, kmisc, fd);
+ if (err == 0)
+ err = __dsos__write_buildid_table(&machine->user_dsos, machine,
+ machine->pid, umisc, fd);
+ return err;
+}
+
+static int dsos__write_buildid_table(struct perf_header *header, int fd)
+{
+ struct perf_session *session = container_of(header,
+ struct perf_session, header);
+ struct rb_node *nd;
+ int err = machine__write_buildid_table(&session->machines.host, fd);
+
+ if (err)
+ return err;
+
+ for (nd = rb_first(&session->machines.guests); nd; nd = rb_next(nd)) {
+ struct machine *pos = rb_entry(nd, struct machine, rb_node);
+ err = machine__write_buildid_table(pos, fd);
+ if (err)
+ break;
+ }
+ return err;
+}
+
+int build_id_cache__add_s(const char *sbuild_id, const char *debugdir,
+ const char *name, bool is_kallsyms, bool is_vdso)
+{
+ const size_t size = PATH_MAX;
+ char *realname, *filename = zalloc(size),
+ *linkname = zalloc(size), *targetname;
+ int len, err = -1;
+ bool slash = is_kallsyms || is_vdso;
+
+ if (is_kallsyms) {
+ if (symbol_conf.kptr_restrict) {
+ pr_debug("Not caching a kptr_restrict'ed /proc/kallsyms\n");
+ err = 0;
+ goto out_free;
+ }
+ realname = (char *) name;
+ } else
+ realname = realpath(name, NULL);
+
+ if (realname == NULL || filename == NULL || linkname == NULL)
+ goto out_free;
+
+ len = scnprintf(filename, size, "%s%s%s",
+ debugdir, slash ? "/" : "",
+ is_vdso ? VDSO__MAP_NAME : realname);
+ if (mkdir_p(filename, 0755))
+ goto out_free;
+
+ snprintf(filename + len, size - len, "/%s", sbuild_id);
+
+ if (access(filename, F_OK)) {
+ if (is_kallsyms) {
+ if (copyfile("/proc/kallsyms", filename))
+ goto out_free;
+ } else if (link(realname, filename) && copyfile(name, filename))
+ goto out_free;
+ }
+
+ len = scnprintf(linkname, size, "%s/.build-id/%.2s",
+ debugdir, sbuild_id);
+
+ if (access(linkname, X_OK) && mkdir_p(linkname, 0755))
+ goto out_free;
+
+ snprintf(linkname + len, size - len, "/%s", sbuild_id + 2);
+ targetname = filename + strlen(debugdir) - 5;
+ memcpy(targetname, "../..", 5);
+
+ if (symlink(targetname, linkname) == 0)
+ err = 0;
+out_free:
+ if (!is_kallsyms)
+ free(realname);
+ free(filename);
+ free(linkname);
+ return err;
+}
+
+static int build_id_cache__add_b(const u8 *build_id, size_t build_id_size,
+ const char *name, const char *debugdir,
+ bool is_kallsyms, bool is_vdso)
+{
+ char sbuild_id[BUILD_ID_SIZE * 2 + 1];
+
+ build_id__sprintf(build_id, build_id_size, sbuild_id);
+
+ return build_id_cache__add_s(sbuild_id, debugdir, name,
+ is_kallsyms, is_vdso);
+}
+
+int build_id_cache__remove_s(const char *sbuild_id, const char *debugdir)
+{
+ const size_t size = PATH_MAX;
+ char *filename = zalloc(size),
+ *linkname = zalloc(size);
+ int err = -1;
+
+ if (filename == NULL || linkname == NULL)
+ goto out_free;
+
+ snprintf(linkname, size, "%s/.build-id/%.2s/%s",
+ debugdir, sbuild_id, sbuild_id + 2);
+
+ if (access(linkname, F_OK))
+ goto out_free;
+
+ if (readlink(linkname, filename, size - 1) < 0)
+ goto out_free;
+
+ if (unlink(linkname))
+ goto out_free;
+
+ /*
+ * Since the link is relative, we must make it absolute:
+ */
+ snprintf(linkname, size, "%s/.build-id/%.2s/%s",
+ debugdir, sbuild_id, filename);
+
+ if (unlink(linkname))
+ goto out_free;
+
+ err = 0;
+out_free:
+ free(filename);
+ free(linkname);
+ return err;
+}
+
+static int dso__cache_build_id(struct dso *dso, struct machine *machine,
+ const char *debugdir)
+{
+ bool is_kallsyms = dso->kernel && dso->long_name[0] != '/';
+ bool is_vdso = is_vdso_map(dso->short_name);
+ const char *name = dso->long_name;
+ char nm[PATH_MAX];
+
+ if (dso__is_kcore(dso)) {
+ is_kallsyms = true;
+ machine__mmap_name(machine, nm, sizeof(nm));
+ name = nm;
+ }
+ return build_id_cache__add_b(dso->build_id, sizeof(dso->build_id), name,
+ debugdir, is_kallsyms, is_vdso);
+}
+
+static int __dsos__cache_build_ids(struct list_head *head,
+ struct machine *machine, const char *debugdir)
+{
+ struct dso *pos;
+ int err = 0;
+
+ dsos__for_each_with_build_id(pos, head)
+ if (dso__cache_build_id(pos, machine, debugdir))
+ err = -1;
+
+ return err;
+}
+
+static int machine__cache_build_ids(struct machine *machine, const char *debugdir)
+{
+ int ret = __dsos__cache_build_ids(&machine->kernel_dsos, machine,
+ debugdir);
+ ret |= __dsos__cache_build_ids(&machine->user_dsos, machine, debugdir);
+ return ret;
+}
+
+static int perf_session__cache_build_ids(struct perf_session *session)
+{
+ struct rb_node *nd;
+ int ret;
+ char debugdir[PATH_MAX];
+
+ snprintf(debugdir, sizeof(debugdir), "%s", buildid_dir);
+
+ if (mkdir(debugdir, 0755) != 0 && errno != EEXIST)
+ return -1;
+
+ ret = machine__cache_build_ids(&session->machines.host, debugdir);
+
+ for (nd = rb_first(&session->machines.guests); nd; nd = rb_next(nd)) {
+ struct machine *pos = rb_entry(nd, struct machine, rb_node);
+ ret |= machine__cache_build_ids(pos, debugdir);
+ }
+ return ret ? -1 : 0;
+}
+
+static bool machine__read_build_ids(struct machine *machine, bool with_hits)
+{
+ bool ret = __dsos__read_build_ids(&machine->kernel_dsos, with_hits);
+ ret |= __dsos__read_build_ids(&machine->user_dsos, with_hits);
+ return ret;
+}
+
+static bool perf_session__read_build_ids(struct perf_session *session, bool with_hits)
+{
+ struct rb_node *nd;
+ bool ret = machine__read_build_ids(&session->machines.host, with_hits);
+
+ for (nd = rb_first(&session->machines.guests); nd; nd = rb_next(nd)) {
+ struct machine *pos = rb_entry(nd, struct machine, rb_node);
+ ret |= machine__read_build_ids(pos, with_hits);
+ }
+
+ return ret;
+}
+
+static int write_tracing_data(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist)
+{
+ return read_tracing_data(fd, &evlist->entries);
+}
+
+
+static int write_build_id(int fd, struct perf_header *h,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ struct perf_session *session;
+ int err;
+
+ session = container_of(h, struct perf_session, header);
+
+ if (!perf_session__read_build_ids(session, true))
+ return -1;
+
+ err = dsos__write_buildid_table(h, fd);
+ if (err < 0) {
+ pr_debug("failed to write buildid table\n");
+ return err;
+ }
+ if (!no_buildid_cache)
+ perf_session__cache_build_ids(session);
+
+ return 0;
+}
+
+static int write_hostname(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ struct utsname uts;
+ int ret;
+
+ ret = uname(&uts);
+ if (ret < 0)
+ return -1;
+
+ return do_write_string(fd, uts.nodename);
+}
+
+static int write_osrelease(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ struct utsname uts;
+ int ret;
+
+ ret = uname(&uts);
+ if (ret < 0)
+ return -1;
+
+ return do_write_string(fd, uts.release);
+}
+
+static int write_arch(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ struct utsname uts;
+ int ret;
+
+ ret = uname(&uts);
+ if (ret < 0)
+ return -1;
+
+ return do_write_string(fd, uts.machine);
+}
+
+static int write_version(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ return do_write_string(fd, perf_version_string);
+}
+
+static int write_cpudesc(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+#ifndef CPUINFO_PROC
+#define CPUINFO_PROC NULL
+#endif
+ FILE *file;
+ char *buf = NULL;
+ char *s, *p;
+ const char *search = CPUINFO_PROC;
+ size_t len = 0;
+ int ret = -1;
+
+ if (!search)
+ return -1;
+
+ file = fopen("/proc/cpuinfo", "r");
+ if (!file)
+ return -1;
+
+ while (getline(&buf, &len, file) > 0) {
+ ret = strncmp(buf, search, strlen(search));
+ if (!ret)
+ break;
+ }
+
+ if (ret)
+ goto done;
+
+ s = buf;
+
+ p = strchr(buf, ':');
+ if (p && *(p+1) == ' ' && *(p+2))
+ s = p + 2;
+ p = strchr(s, '\n');
+ if (p)
+ *p = '\0';
+
+ /* squash extra space characters (branding string) */
+ p = s;
+ while (*p) {
+ if (isspace(*p)) {
+ char *r = p + 1;
+ char *q = r;
+ *p = ' ';
+ while (*q && isspace(*q))
+ q++;
+ if (q != (p+1))
+ while ((*r++ = *q++));
+ }
+ p++;
+ }
+ ret = do_write_string(fd, s);
+done:
+ free(buf);
+ fclose(file);
+ return ret;
+}
+
+static int write_nrcpus(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ long nr;
+ u32 nrc, nra;
+ int ret;
+
+ nr = sysconf(_SC_NPROCESSORS_CONF);
+ if (nr < 0)
+ return -1;
+
+ nrc = (u32)(nr & UINT_MAX);
+
+ nr = sysconf(_SC_NPROCESSORS_ONLN);
+ if (nr < 0)
+ return -1;
+
+ nra = (u32)(nr & UINT_MAX);
+
+ ret = do_write(fd, &nrc, sizeof(nrc));
+ if (ret < 0)
+ return ret;
+
+ return do_write(fd, &nra, sizeof(nra));
+}
+
+static int write_event_desc(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist)
+{
+ struct perf_evsel *evsel;
+ u32 nre, nri, sz;
+ int ret;
+
+ nre = evlist->nr_entries;
+
+ /*
+ * write number of events
+ */
+ ret = do_write(fd, &nre, sizeof(nre));
+ if (ret < 0)
+ return ret;
+
+ /*
+ * size of perf_event_attr struct
+ */
+ sz = (u32)sizeof(evsel->attr);
+ ret = do_write(fd, &sz, sizeof(sz));
+ if (ret < 0)
+ return ret;
+
+ evlist__for_each(evlist, evsel) {
+ ret = do_write(fd, &evsel->attr, sz);
+ if (ret < 0)
+ return ret;
+ /*
+ * write number of unique id per event
+ * there is one id per instance of an event
+ *
+ * copy into an nri to be independent of the
+ * type of ids,
+ */
+ nri = evsel->ids;
+ ret = do_write(fd, &nri, sizeof(nri));
+ if (ret < 0)
+ return ret;
+
+ /*
+ * write event string as passed on cmdline
+ */
+ ret = do_write_string(fd, perf_evsel__name(evsel));
+ if (ret < 0)
+ return ret;
+ /*
+ * write unique ids for this event
+ */
+ ret = do_write(fd, evsel->id, evsel->ids * sizeof(u64));
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+static int write_cmdline(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ char buf[MAXPATHLEN];
+ char proc[32];
+ u32 i, n;
+ int ret;
+
+ /*
+ * actual atual path to perf binary
+ */
+ sprintf(proc, "/proc/%d/exe", getpid());
+ ret = readlink(proc, buf, sizeof(buf));
+ if (ret <= 0)
+ return -1;
+
+ /* readlink() does not add null termination */
+ buf[ret] = '\0';
+
+ /* account for binary path */
+ n = header_argc + 1;
+
+ ret = do_write(fd, &n, sizeof(n));
+ if (ret < 0)
+ return ret;
+
+ ret = do_write_string(fd, buf);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0 ; i < header_argc; i++) {
+ ret = do_write_string(fd, header_argv[i]);
+ if (ret < 0)
+ return ret;
+ }
+ return 0;
+}
+
+#define CORE_SIB_FMT \
+ "/sys/devices/system/cpu/cpu%d/topology/core_siblings_list"
+#define THRD_SIB_FMT \
+ "/sys/devices/system/cpu/cpu%d/topology/thread_siblings_list"
+
+struct cpu_topo {
+ u32 core_sib;
+ u32 thread_sib;
+ char **core_siblings;
+ char **thread_siblings;
+};
+
+static int build_cpu_topo(struct cpu_topo *tp, int cpu)
+{
+ FILE *fp;
+ char filename[MAXPATHLEN];
+ char *buf = NULL, *p;
+ size_t len = 0;
+ ssize_t sret;
+ u32 i = 0;
+ int ret = -1;
+
+ sprintf(filename, CORE_SIB_FMT, cpu);
+ fp = fopen(filename, "r");
+ if (!fp)
+ goto try_threads;
+
+ sret = getline(&buf, &len, fp);
+ fclose(fp);
+ if (sret <= 0)
+ goto try_threads;
+
+ p = strchr(buf, '\n');
+ if (p)
+ *p = '\0';
+
+ for (i = 0; i < tp->core_sib; i++) {
+ if (!strcmp(buf, tp->core_siblings[i]))
+ break;
+ }
+ if (i == tp->core_sib) {
+ tp->core_siblings[i] = buf;
+ tp->core_sib++;
+ buf = NULL;
+ len = 0;
+ }
+ ret = 0;
+
+try_threads:
+ sprintf(filename, THRD_SIB_FMT, cpu);
+ fp = fopen(filename, "r");
+ if (!fp)
+ goto done;
+
+ if (getline(&buf, &len, fp) <= 0)
+ goto done;
+
+ p = strchr(buf, '\n');
+ if (p)
+ *p = '\0';
+
+ for (i = 0; i < tp->thread_sib; i++) {
+ if (!strcmp(buf, tp->thread_siblings[i]))
+ break;
+ }
+ if (i == tp->thread_sib) {
+ tp->thread_siblings[i] = buf;
+ tp->thread_sib++;
+ buf = NULL;
+ }
+ ret = 0;
+done:
+ if(fp)
+ fclose(fp);
+ free(buf);
+ return ret;
+}
+
+static void free_cpu_topo(struct cpu_topo *tp)
+{
+ u32 i;
+
+ if (!tp)
+ return;
+
+ for (i = 0 ; i < tp->core_sib; i++)
+ zfree(&tp->core_siblings[i]);
+
+ for (i = 0 ; i < tp->thread_sib; i++)
+ zfree(&tp->thread_siblings[i]);
+
+ free(tp);
+}
+
+static struct cpu_topo *build_cpu_topology(void)
+{
+ struct cpu_topo *tp;
+ void *addr;
+ u32 nr, i;
+ size_t sz;
+ long ncpus;
+ int ret = -1;
+
+ ncpus = sysconf(_SC_NPROCESSORS_CONF);
+ if (ncpus < 0)
+ return NULL;
+
+ nr = (u32)(ncpus & UINT_MAX);
+
+ sz = nr * sizeof(char *);
+
+ addr = calloc(1, sizeof(*tp) + 2 * sz);
+ if (!addr)
+ return NULL;
+
+ tp = addr;
+
+ addr += sizeof(*tp);
+ tp->core_siblings = addr;
+ addr += sz;
+ tp->thread_siblings = addr;
+
+ for (i = 0; i < nr; i++) {
+ ret = build_cpu_topo(tp, i);
+ if (ret < 0)
+ break;
+ }
+ if (ret) {
+ free_cpu_topo(tp);
+ tp = NULL;
+ }
+ return tp;
+}
+
+static int write_cpu_topology(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ struct cpu_topo *tp;
+ u32 i;
+ int ret;
+
+ tp = build_cpu_topology();
+ if (!tp)
+ return -1;
+
+ ret = do_write(fd, &tp->core_sib, sizeof(tp->core_sib));
+ if (ret < 0)
+ goto done;
+
+ for (i = 0; i < tp->core_sib; i++) {
+ ret = do_write_string(fd, tp->core_siblings[i]);
+ if (ret < 0)
+ goto done;
+ }
+ ret = do_write(fd, &tp->thread_sib, sizeof(tp->thread_sib));
+ if (ret < 0)
+ goto done;
+
+ for (i = 0; i < tp->thread_sib; i++) {
+ ret = do_write_string(fd, tp->thread_siblings[i]);
+ if (ret < 0)
+ break;
+ }
+done:
+ free_cpu_topo(tp);
+ return ret;
+}
+
+
+
+static int write_total_mem(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ char *buf = NULL;
+ FILE *fp;
+ size_t len = 0;
+ int ret = -1, n;
+ uint64_t mem;
+
+ fp = fopen("/proc/meminfo", "r");
+ if (!fp)
+ return -1;
+
+ while (getline(&buf, &len, fp) > 0) {
+ ret = strncmp(buf, "MemTotal:", 9);
+ if (!ret)
+ break;
+ }
+ if (!ret) {
+ n = sscanf(buf, "%*s %"PRIu64, &mem);
+ if (n == 1)
+ ret = do_write(fd, &mem, sizeof(mem));
+ }
+ free(buf);
+ fclose(fp);
+ return ret;
+}
+
+static int write_topo_node(int fd, int node)
+{
+ char str[MAXPATHLEN];
+ char field[32];
+ char *buf = NULL, *p;
+ size_t len = 0;
+ FILE *fp;
+ u64 mem_total, mem_free, mem;
+ int ret = -1;
+
+ sprintf(str, "/sys/devices/system/node/node%d/meminfo", node);
+ fp = fopen(str, "r");
+ if (!fp)
+ return -1;
+
+ while (getline(&buf, &len, fp) > 0) {
+ /* skip over invalid lines */
+ if (!strchr(buf, ':'))
+ continue;
+ if (sscanf(buf, "%*s %*d %31s %"PRIu64, field, &mem) != 2)
+ goto done;
+ if (!strcmp(field, "MemTotal:"))
+ mem_total = mem;
+ if (!strcmp(field, "MemFree:"))
+ mem_free = mem;
+ }
+
+ fclose(fp);
+ fp = NULL;
+
+ ret = do_write(fd, &mem_total, sizeof(u64));
+ if (ret)
+ goto done;
+
+ ret = do_write(fd, &mem_free, sizeof(u64));
+ if (ret)
+ goto done;
+
+ ret = -1;
+ sprintf(str, "/sys/devices/system/node/node%d/cpulist", node);
+
+ fp = fopen(str, "r");
+ if (!fp)
+ goto done;
+
+ if (getline(&buf, &len, fp) <= 0)
+ goto done;
+
+ p = strchr(buf, '\n');
+ if (p)
+ *p = '\0';
+
+ ret = do_write_string(fd, buf);
+done:
+ free(buf);
+ if (fp)
+ fclose(fp);
+ return ret;
+}
+
+static int write_numa_topology(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ char *buf = NULL;
+ size_t len = 0;
+ FILE *fp;
+ struct cpu_map *node_map = NULL;
+ char *c;
+ u32 nr, i, j;
+ int ret = -1;
+
+ fp = fopen("/sys/devices/system/node/online", "r");
+ if (!fp)
+ return -1;
+
+ if (getline(&buf, &len, fp) <= 0)
+ goto done;
+
+ c = strchr(buf, '\n');
+ if (c)
+ *c = '\0';
+
+ node_map = cpu_map__new(buf);
+ if (!node_map)
+ goto done;
+
+ nr = (u32)node_map->nr;
+
+ ret = do_write(fd, &nr, sizeof(nr));
+ if (ret < 0)
+ goto done;
+
+ for (i = 0; i < nr; i++) {
+ j = (u32)node_map->map[i];
+ ret = do_write(fd, &j, sizeof(j));
+ if (ret < 0)
+ break;
+
+ ret = write_topo_node(fd, i);
+ if (ret < 0)
+ break;
+ }
+done:
+ free(buf);
+ fclose(fp);
+ free(node_map);
+ return ret;
+}
+
+/*
+ * File format:
+ *
+ * struct pmu_mappings {
+ * u32 pmu_num;
+ * struct pmu_map {
+ * u32 type;
+ * char name[];
+ * }[pmu_num];
+ * };
+ */
+
+static int write_pmu_mappings(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ struct perf_pmu *pmu = NULL;
+ off_t offset = lseek(fd, 0, SEEK_CUR);
+ __u32 pmu_num = 0;
+ int ret;
+
+ /* write real pmu_num later */
+ ret = do_write(fd, &pmu_num, sizeof(pmu_num));
+ if (ret < 0)
+ return ret;
+
+ while ((pmu = perf_pmu__scan(pmu))) {
+ if (!pmu->name)
+ continue;
+ pmu_num++;
+
+ ret = do_write(fd, &pmu->type, sizeof(pmu->type));
+ if (ret < 0)
+ return ret;
+
+ ret = do_write_string(fd, pmu->name);
+ if (ret < 0)
+ return ret;
+ }
+
+ if (pwrite(fd, &pmu_num, sizeof(pmu_num), offset) != sizeof(pmu_num)) {
+ /* discard all */
+ lseek(fd, offset, SEEK_SET);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * File format:
+ *
+ * struct group_descs {
+ * u32 nr_groups;
+ * struct group_desc {
+ * char name[];
+ * u32 leader_idx;
+ * u32 nr_members;
+ * }[nr_groups];
+ * };
+ */
+static int write_group_desc(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist)
+{
+ u32 nr_groups = evlist->nr_groups;
+ struct perf_evsel *evsel;
+ int ret;
+
+ ret = do_write(fd, &nr_groups, sizeof(nr_groups));
+ if (ret < 0)
+ return ret;
+
+ evlist__for_each(evlist, evsel) {
+ if (perf_evsel__is_group_leader(evsel) &&
+ evsel->nr_members > 1) {
+ const char *name = evsel->group_name ?: "{anon_group}";
+ u32 leader_idx = evsel->idx;
+ u32 nr_members = evsel->nr_members;
+
+ ret = do_write_string(fd, name);
+ if (ret < 0)
+ return ret;
+
+ ret = do_write(fd, &leader_idx, sizeof(leader_idx));
+ if (ret < 0)
+ return ret;
+
+ ret = do_write(fd, &nr_members, sizeof(nr_members));
+ if (ret < 0)
+ return ret;
+ }
+ }
+ return 0;
+}
+
+/*
+ * default get_cpuid(): nothing gets recorded
+ * actual implementation must be in arch/$(ARCH)/util/header.c
+ */
+int __attribute__ ((weak)) get_cpuid(char *buffer __maybe_unused,
+ size_t sz __maybe_unused)
+{
+ return -1;
+}
+
+static int write_cpuid(int fd, struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ char buffer[64];
+ int ret;
+
+ ret = get_cpuid(buffer, sizeof(buffer));
+ if (!ret)
+ goto write_it;
+
+ return -1;
+write_it:
+ return do_write_string(fd, buffer);
+}
+
+static int write_branch_stack(int fd __maybe_unused,
+ struct perf_header *h __maybe_unused,
+ struct perf_evlist *evlist __maybe_unused)
+{
+ return 0;
+}
+
+static void print_hostname(struct perf_header *ph, int fd __maybe_unused,
+ FILE *fp)
+{
+ fprintf(fp, "# hostname : %s\n", ph->env.hostname);
+}
+
+static void print_osrelease(struct perf_header *ph, int fd __maybe_unused,
+ FILE *fp)
+{
+ fprintf(fp, "# os release : %s\n", ph->env.os_release);
+}
+
+static void print_arch(struct perf_header *ph, int fd __maybe_unused, FILE *fp)
+{
+ fprintf(fp, "# arch : %s\n", ph->env.arch);
+}
+
+static void print_cpudesc(struct perf_header *ph, int fd __maybe_unused,
+ FILE *fp)
+{
+ fprintf(fp, "# cpudesc : %s\n", ph->env.cpu_desc);
+}
+
+static void print_nrcpus(struct perf_header *ph, int fd __maybe_unused,
+ FILE *fp)
+{
+ fprintf(fp, "# nrcpus online : %u\n", ph->env.nr_cpus_online);
+ fprintf(fp, "# nrcpus avail : %u\n", ph->env.nr_cpus_avail);
+}
+
+static void print_version(struct perf_header *ph, int fd __maybe_unused,
+ FILE *fp)
+{
+ fprintf(fp, "# perf version : %s\n", ph->env.version);
+}
+
+static void print_cmdline(struct perf_header *ph, int fd __maybe_unused,
+ FILE *fp)
+{
+ int nr, i;
+ char *str;
+
+ nr = ph->env.nr_cmdline;
+ str = ph->env.cmdline;
+
+ fprintf(fp, "# cmdline : ");
+
+ for (i = 0; i < nr; i++) {
+ fprintf(fp, "%s ", str);
+ str += strlen(str) + 1;
+ }
+ fputc('\n', fp);
+}
+
+static void print_cpu_topology(struct perf_header *ph, int fd __maybe_unused,
+ FILE *fp)
+{
+ int nr, i;
+ char *str;
+
+ nr = ph->env.nr_sibling_cores;
+ str = ph->env.sibling_cores;
+
+ for (i = 0; i < nr; i++) {
+ fprintf(fp, "# sibling cores : %s\n", str);
+ str += strlen(str) + 1;
+ }
+
+ nr = ph->env.nr_sibling_threads;
+ str = ph->env.sibling_threads;
+
+ for (i = 0; i < nr; i++) {
+ fprintf(fp, "# sibling threads : %s\n", str);
+ str += strlen(str) + 1;
+ }
+}
+
+static void free_event_desc(struct perf_evsel *events)
+{
+ struct perf_evsel *evsel;
+
+ if (!events)
+ return;
+
+ for (evsel = events; evsel->attr.size; evsel++) {
+ zfree(&evsel->name);
+ zfree(&evsel->id);
+ }
+
+ free(events);
+}
+
+static struct perf_evsel *
+read_event_desc(struct perf_header *ph, int fd)
+{
+ struct perf_evsel *evsel, *events = NULL;
+ u64 *id;
+ void *buf = NULL;
+ u32 nre, sz, nr, i, j;
+ ssize_t ret;
+ size_t msz;
+
+ /* number of events */
+ ret = readn(fd, &nre, sizeof(nre));
+ if (ret != (ssize_t)sizeof(nre))
+ goto error;
+
+ if (ph->needs_swap)
+ nre = bswap_32(nre);
+
+ ret = readn(fd, &sz, sizeof(sz));
+ if (ret != (ssize_t)sizeof(sz))
+ goto error;
+
+ if (ph->needs_swap)
+ sz = bswap_32(sz);
+
+ /* buffer to hold on file attr struct */
+ buf = malloc(sz);
+ if (!buf)
+ goto error;
+
+ /* the last event terminates with evsel->attr.size == 0: */
+ events = calloc(nre + 1, sizeof(*events));
+ if (!events)
+ goto error;
+
+ msz = sizeof(evsel->attr);
+ if (sz < msz)
+ msz = sz;
+
+ for (i = 0, evsel = events; i < nre; evsel++, i++) {
+ evsel->idx = i;
+
+ /*
+ * must read entire on-file attr struct to
+ * sync up with layout.
+ */
+ ret = readn(fd, buf, sz);
+ if (ret != (ssize_t)sz)
+ goto error;
+
+ if (ph->needs_swap)
+ perf_event__attr_swap(buf);
+
+ memcpy(&evsel->attr, buf, msz);
+
+ ret = readn(fd, &nr, sizeof(nr));
+ if (ret != (ssize_t)sizeof(nr))
+ goto error;
+
+ if (ph->needs_swap) {
+ nr = bswap_32(nr);
+ evsel->needs_swap = true;
+ }
+
+ evsel->name = do_read_string(fd, ph);
+
+ if (!nr)
+ continue;
+
+ id = calloc(nr, sizeof(*id));
+ if (!id)
+ goto error;
+ evsel->ids = nr;
+ evsel->id = id;
+
+ for (j = 0 ; j < nr; j++) {
+ ret = readn(fd, id, sizeof(*id));
+ if (ret != (ssize_t)sizeof(*id))
+ goto error;
+ if (ph->needs_swap)
+ *id = bswap_64(*id);
+ id++;
+ }
+ }
+out:
+ free(buf);
+ return events;
+error:
+ if (events)
+ free_event_desc(events);
+ events = NULL;
+ goto out;
+}
+
+static void print_event_desc(struct perf_header *ph, int fd, FILE *fp)
+{
+ struct perf_evsel *evsel, *events = read_event_desc(ph, fd);
+ u32 j;
+ u64 *id;
+
+ if (!events) {
+ fprintf(fp, "# event desc: not available or unable to read\n");
+ return;
+ }
+
+ for (evsel = events; evsel->attr.size; evsel++) {
+ fprintf(fp, "# event : name = %s, ", evsel->name);
+
+ fprintf(fp, "type = %d, config = 0x%"PRIx64
+ ", config1 = 0x%"PRIx64", config2 = 0x%"PRIx64,
+ evsel->attr.type,
+ (u64)evsel->attr.config,
+ (u64)evsel->attr.config1,
+ (u64)evsel->attr.config2);
+
+ fprintf(fp, ", excl_usr = %d, excl_kern = %d",
+ evsel->attr.exclude_user,
+ evsel->attr.exclude_kernel);
+
+ fprintf(fp, ", excl_host = %d, excl_guest = %d",
+ evsel->attr.exclude_host,
+ evsel->attr.exclude_guest);
+
+ fprintf(fp, ", precise_ip = %d", evsel->attr.precise_ip);
+
+ fprintf(fp, ", attr_mmap2 = %d", evsel->attr.mmap2);
+ fprintf(fp, ", attr_mmap = %d", evsel->attr.mmap);
+ fprintf(fp, ", attr_mmap_data = %d", evsel->attr.mmap_data);
+ if (evsel->ids) {
+ fprintf(fp, ", id = {");
+ for (j = 0, id = evsel->id; j < evsel->ids; j++, id++) {
+ if (j)
+ fputc(',', fp);
+ fprintf(fp, " %"PRIu64, *id);
+ }
+ fprintf(fp, " }");
+ }
+
+ fputc('\n', fp);
+ }
+
+ free_event_desc(events);
+}
+
+static void print_total_mem(struct perf_header *ph, int fd __maybe_unused,
+ FILE *fp)
+{
+ fprintf(fp, "# total memory : %Lu kB\n", ph->env.total_mem);
+}
+
+static void print_numa_topology(struct perf_header *ph, int fd __maybe_unused,
+ FILE *fp)
+{
+ u32 nr, c, i;
+ char *str, *tmp;
+ uint64_t mem_total, mem_free;
+
+ /* nr nodes */
+ nr = ph->env.nr_numa_nodes;
+ str = ph->env.numa_nodes;
+
+ for (i = 0; i < nr; i++) {
+ /* node number */
+ c = strtoul(str, &tmp, 0);
+ if (*tmp != ':')
+ goto error;
+
+ str = tmp + 1;
+ mem_total = strtoull(str, &tmp, 0);
+ if (*tmp != ':')
+ goto error;
+
+ str = tmp + 1;
+ mem_free = strtoull(str, &tmp, 0);
+ if (*tmp != ':')
+ goto error;
+
+ fprintf(fp, "# node%u meminfo : total = %"PRIu64" kB,"
+ " free = %"PRIu64" kB\n",
+ c, mem_total, mem_free);
+
+ str = tmp + 1;
+ fprintf(fp, "# node%u cpu list : %s\n", c, str);
+
+ str += strlen(str) + 1;
+ }
+ return;
+error:
+ fprintf(fp, "# numa topology : not available\n");
+}
+
+static void print_cpuid(struct perf_header *ph, int fd __maybe_unused, FILE *fp)
+{
+ fprintf(fp, "# cpuid : %s\n", ph->env.cpuid);
+}
+
+static void print_branch_stack(struct perf_header *ph __maybe_unused,
+ int fd __maybe_unused, FILE *fp)
+{
+ fprintf(fp, "# contains samples with branch stack\n");
+}
+
+static void print_pmu_mappings(struct perf_header *ph, int fd __maybe_unused,
+ FILE *fp)
+{
+ const char *delimiter = "# pmu mappings: ";
+ char *str, *tmp;
+ u32 pmu_num;
+ u32 type;
+
+ pmu_num = ph->env.nr_pmu_mappings;
+ if (!pmu_num) {
+ fprintf(fp, "# pmu mappings: not available\n");
+ return;
+ }
+
+ str = ph->env.pmu_mappings;
+
+ while (pmu_num) {
+ type = strtoul(str, &tmp, 0);
+ if (*tmp != ':')
+ goto error;
+
+ str = tmp + 1;
+ fprintf(fp, "%s%s = %" PRIu32, delimiter, str, type);
+
+ delimiter = ", ";
+ str += strlen(str) + 1;
+ pmu_num--;
+ }
+
+ fprintf(fp, "\n");
+
+ if (!pmu_num)
+ return;
+error:
+ fprintf(fp, "# pmu mappings: unable to read\n");
+}
+
+static void print_group_desc(struct perf_header *ph, int fd __maybe_unused,
+ FILE *fp)
+{
+ struct perf_session *session;
+ struct perf_evsel *evsel;
+ u32 nr = 0;
+
+ session = container_of(ph, struct perf_session, header);
+
+ evlist__for_each(session->evlist, evsel) {
+ if (perf_evsel__is_group_leader(evsel) &&
+ evsel->nr_members > 1) {
+ fprintf(fp, "# group: %s{%s", evsel->group_name ?: "",
+ perf_evsel__name(evsel));
+
+ nr = evsel->nr_members - 1;
+ } else if (nr) {
+ fprintf(fp, ",%s", perf_evsel__name(evsel));
+
+ if (--nr == 0)
+ fprintf(fp, "}\n");
+ }
+ }
+}
+
+static int __event_process_build_id(struct build_id_event *bev,
+ char *filename,
+ struct perf_session *session)
+{
+ int err = -1;
+ struct list_head *head;
+ struct machine *machine;
+ u16 misc;
+ struct dso *dso;
+ enum dso_kernel_type dso_type;
+
+ machine = perf_session__findnew_machine(session, bev->pid);
+ if (!machine)
+ goto out;
+
+ misc = bev->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+
+ switch (misc) {
+ case PERF_RECORD_MISC_KERNEL:
+ dso_type = DSO_TYPE_KERNEL;
+ head = &machine->kernel_dsos;
+ break;
+ case PERF_RECORD_MISC_GUEST_KERNEL:
+ dso_type = DSO_TYPE_GUEST_KERNEL;
+ head = &machine->kernel_dsos;
+ break;
+ case PERF_RECORD_MISC_USER:
+ case PERF_RECORD_MISC_GUEST_USER:
+ dso_type = DSO_TYPE_USER;
+ head = &machine->user_dsos;
+ break;
+ default:
+ goto out;
+ }
+
+ dso = __dsos__findnew(head, filename);
+ if (dso != NULL) {
+ char sbuild_id[BUILD_ID_SIZE * 2 + 1];
+
+ dso__set_build_id(dso, &bev->build_id);
+
+ if (filename[0] == '[')
+ dso->kernel = dso_type;
+
+ build_id__sprintf(dso->build_id, sizeof(dso->build_id),
+ sbuild_id);
+ pr_debug("build id event received for %s: %s\n",
+ dso->long_name, sbuild_id);
+ }
+
+ err = 0;
+out:
+ return err;
+}
+
+static int perf_header__read_build_ids_abi_quirk(struct perf_header *header,
+ int input, u64 offset, u64 size)
+{
+ struct perf_session *session = container_of(header, struct perf_session, header);
+ struct {
+ struct perf_event_header header;
+ u8 build_id[PERF_ALIGN(BUILD_ID_SIZE, sizeof(u64))];
+ char filename[0];
+ } old_bev;
+ struct build_id_event bev;
+ char filename[PATH_MAX];
+ u64 limit = offset + size;
+
+ while (offset < limit) {
+ ssize_t len;
+
+ if (readn(input, &old_bev, sizeof(old_bev)) != sizeof(old_bev))
+ return -1;
+
+ if (header->needs_swap)
+ perf_event_header__bswap(&old_bev.header);
+
+ len = old_bev.header.size - sizeof(old_bev);
+ if (readn(input, filename, len) != len)
+ return -1;
+
+ bev.header = old_bev.header;
+
+ /*
+ * As the pid is the missing value, we need to fill
+ * it properly. The header.misc value give us nice hint.
+ */
+ bev.pid = HOST_KERNEL_ID;
+ if (bev.header.misc == PERF_RECORD_MISC_GUEST_USER ||
+ bev.header.misc == PERF_RECORD_MISC_GUEST_KERNEL)
+ bev.pid = DEFAULT_GUEST_KERNEL_ID;
+
+ memcpy(bev.build_id, old_bev.build_id, sizeof(bev.build_id));
+ __event_process_build_id(&bev, filename, session);
+
+ offset += bev.header.size;
+ }
+
+ return 0;
+}
+
+static int perf_header__read_build_ids(struct perf_header *header,
+ int input, u64 offset, u64 size)
+{
+ struct perf_session *session = container_of(header, struct perf_session, header);
+ struct build_id_event bev;
+ char filename[PATH_MAX];
+ u64 limit = offset + size, orig_offset = offset;
+ int err = -1;
+
+ while (offset < limit) {
+ ssize_t len;
+
+ if (readn(input, &bev, sizeof(bev)) != sizeof(bev))
+ goto out;
+
+ if (header->needs_swap)
+ perf_event_header__bswap(&bev.header);
+
+ len = bev.header.size - sizeof(bev);
+ if (readn(input, filename, len) != len)
+ goto out;
+ /*
+ * The a1645ce1 changeset:
+ *
+ * "perf: 'perf kvm' tool for monitoring guest performance from host"
+ *
+ * Added a field to struct build_id_event that broke the file
+ * format.
+ *
+ * Since the kernel build-id is the first entry, process the
+ * table using the old format if the well known
+ * '[kernel.kallsyms]' string for the kernel build-id has the
+ * first 4 characters chopped off (where the pid_t sits).
+ */
+ if (memcmp(filename, "nel.kallsyms]", 13) == 0) {
+ if (lseek(input, orig_offset, SEEK_SET) == (off_t)-1)
+ return -1;
+ return perf_header__read_build_ids_abi_quirk(header, input, offset, size);
+ }
+
+ __event_process_build_id(&bev, filename, session);
+
+ offset += bev.header.size;
+ }
+ err = 0;
+out:
+ return err;
+}
+
+static int process_tracing_data(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph __maybe_unused,
+ int fd, void *data)
+{
+ ssize_t ret = trace_report(fd, data, false);
+ return ret < 0 ? -1 : 0;
+}
+
+static int process_build_id(struct perf_file_section *section,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ if (perf_header__read_build_ids(ph, fd, section->offset, section->size))
+ pr_debug("Failed to read buildids, continuing...\n");
+ return 0;
+}
+
+static int process_hostname(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ ph->env.hostname = do_read_string(fd, ph);
+ return ph->env.hostname ? 0 : -ENOMEM;
+}
+
+static int process_osrelease(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ ph->env.os_release = do_read_string(fd, ph);
+ return ph->env.os_release ? 0 : -ENOMEM;
+}
+
+static int process_version(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ ph->env.version = do_read_string(fd, ph);
+ return ph->env.version ? 0 : -ENOMEM;
+}
+
+static int process_arch(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ ph->env.arch = do_read_string(fd, ph);
+ return ph->env.arch ? 0 : -ENOMEM;
+}
+
+static int process_nrcpus(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ ssize_t ret;
+ u32 nr;
+
+ ret = readn(fd, &nr, sizeof(nr));
+ if (ret != sizeof(nr))
+ return -1;
+
+ if (ph->needs_swap)
+ nr = bswap_32(nr);
+
+ ph->env.nr_cpus_online = nr;
+
+ ret = readn(fd, &nr, sizeof(nr));
+ if (ret != sizeof(nr))
+ return -1;
+
+ if (ph->needs_swap)
+ nr = bswap_32(nr);
+
+ ph->env.nr_cpus_avail = nr;
+ return 0;
+}
+
+static int process_cpudesc(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ ph->env.cpu_desc = do_read_string(fd, ph);
+ return ph->env.cpu_desc ? 0 : -ENOMEM;
+}
+
+static int process_cpuid(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ ph->env.cpuid = do_read_string(fd, ph);
+ return ph->env.cpuid ? 0 : -ENOMEM;
+}
+
+static int process_total_mem(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ uint64_t mem;
+ ssize_t ret;
+
+ ret = readn(fd, &mem, sizeof(mem));
+ if (ret != sizeof(mem))
+ return -1;
+
+ if (ph->needs_swap)
+ mem = bswap_64(mem);
+
+ ph->env.total_mem = mem;
+ return 0;
+}
+
+static struct perf_evsel *
+perf_evlist__find_by_index(struct perf_evlist *evlist, int idx)
+{
+ struct perf_evsel *evsel;
+
+ evlist__for_each(evlist, evsel) {
+ if (evsel->idx == idx)
+ return evsel;
+ }
+
+ return NULL;
+}
+
+static void
+perf_evlist__set_event_name(struct perf_evlist *evlist,
+ struct perf_evsel *event)
+{
+ struct perf_evsel *evsel;
+
+ if (!event->name)
+ return;
+
+ evsel = perf_evlist__find_by_index(evlist, event->idx);
+ if (!evsel)
+ return;
+
+ if (evsel->name)
+ return;
+
+ evsel->name = strdup(event->name);
+}
+
+static int
+process_event_desc(struct perf_file_section *section __maybe_unused,
+ struct perf_header *header, int fd,
+ void *data __maybe_unused)
+{
+ struct perf_session *session;
+ struct perf_evsel *evsel, *events = read_event_desc(header, fd);
+
+ if (!events)
+ return 0;
+
+ session = container_of(header, struct perf_session, header);
+ for (evsel = events; evsel->attr.size; evsel++)
+ perf_evlist__set_event_name(session->evlist, evsel);
+
+ free_event_desc(events);
+
+ return 0;
+}
+
+static int process_cmdline(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ ssize_t ret;
+ char *str;
+ u32 nr, i;
+ struct strbuf sb;
+
+ ret = readn(fd, &nr, sizeof(nr));
+ if (ret != sizeof(nr))
+ return -1;
+
+ if (ph->needs_swap)
+ nr = bswap_32(nr);
+
+ ph->env.nr_cmdline = nr;
+ strbuf_init(&sb, 128);
+
+ for (i = 0; i < nr; i++) {
+ str = do_read_string(fd, ph);
+ if (!str)
+ goto error;
+
+ /* include a NULL character at the end */
+ strbuf_add(&sb, str, strlen(str) + 1);
+ free(str);
+ }
+ ph->env.cmdline = strbuf_detach(&sb, NULL);
+ return 0;
+
+error:
+ strbuf_release(&sb);
+ return -1;
+}
+
+static int process_cpu_topology(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ ssize_t ret;
+ u32 nr, i;
+ char *str;
+ struct strbuf sb;
+
+ ret = readn(fd, &nr, sizeof(nr));
+ if (ret != sizeof(nr))
+ return -1;
+
+ if (ph->needs_swap)
+ nr = bswap_32(nr);
+
+ ph->env.nr_sibling_cores = nr;
+ strbuf_init(&sb, 128);
+
+ for (i = 0; i < nr; i++) {
+ str = do_read_string(fd, ph);
+ if (!str)
+ goto error;
+
+ /* include a NULL character at the end */
+ strbuf_add(&sb, str, strlen(str) + 1);
+ free(str);
+ }
+ ph->env.sibling_cores = strbuf_detach(&sb, NULL);
+
+ ret = readn(fd, &nr, sizeof(nr));
+ if (ret != sizeof(nr))
+ return -1;
+
+ if (ph->needs_swap)
+ nr = bswap_32(nr);
+
+ ph->env.nr_sibling_threads = nr;
+
+ for (i = 0; i < nr; i++) {
+ str = do_read_string(fd, ph);
+ if (!str)
+ goto error;
+
+ /* include a NULL character at the end */
+ strbuf_add(&sb, str, strlen(str) + 1);
+ free(str);
+ }
+ ph->env.sibling_threads = strbuf_detach(&sb, NULL);
+ return 0;
+
+error:
+ strbuf_release(&sb);
+ return -1;
+}
+
+static int process_numa_topology(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ ssize_t ret;
+ u32 nr, node, i;
+ char *str;
+ uint64_t mem_total, mem_free;
+ struct strbuf sb;
+
+ /* nr nodes */
+ ret = readn(fd, &nr, sizeof(nr));
+ if (ret != sizeof(nr))
+ goto error;
+
+ if (ph->needs_swap)
+ nr = bswap_32(nr);
+
+ ph->env.nr_numa_nodes = nr;
+ strbuf_init(&sb, 256);
+
+ for (i = 0; i < nr; i++) {
+ /* node number */
+ ret = readn(fd, &node, sizeof(node));
+ if (ret != sizeof(node))
+ goto error;
+
+ ret = readn(fd, &mem_total, sizeof(u64));
+ if (ret != sizeof(u64))
+ goto error;
+
+ ret = readn(fd, &mem_free, sizeof(u64));
+ if (ret != sizeof(u64))
+ goto error;
+
+ if (ph->needs_swap) {
+ node = bswap_32(node);
+ mem_total = bswap_64(mem_total);
+ mem_free = bswap_64(mem_free);
+ }
+
+ strbuf_addf(&sb, "%u:%"PRIu64":%"PRIu64":",
+ node, mem_total, mem_free);
+
+ str = do_read_string(fd, ph);
+ if (!str)
+ goto error;
+
+ /* include a NULL character at the end */
+ strbuf_add(&sb, str, strlen(str) + 1);
+ free(str);
+ }
+ ph->env.numa_nodes = strbuf_detach(&sb, NULL);
+ return 0;
+
+error:
+ strbuf_release(&sb);
+ return -1;
+}
+
+static int process_pmu_mappings(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ ssize_t ret;
+ char *name;
+ u32 pmu_num;
+ u32 type;
+ struct strbuf sb;
+
+ ret = readn(fd, &pmu_num, sizeof(pmu_num));
+ if (ret != sizeof(pmu_num))
+ return -1;
+
+ if (ph->needs_swap)
+ pmu_num = bswap_32(pmu_num);
+
+ if (!pmu_num) {
+ pr_debug("pmu mappings not available\n");
+ return 0;
+ }
+
+ ph->env.nr_pmu_mappings = pmu_num;
+ strbuf_init(&sb, 128);
+
+ while (pmu_num) {
+ if (readn(fd, &type, sizeof(type)) != sizeof(type))
+ goto error;
+ if (ph->needs_swap)
+ type = bswap_32(type);
+
+ name = do_read_string(fd, ph);
+ if (!name)
+ goto error;
+
+ strbuf_addf(&sb, "%u:%s", type, name);
+ /* include a NULL character at the end */
+ strbuf_add(&sb, "", 1);
+
+ free(name);
+ pmu_num--;
+ }
+ ph->env.pmu_mappings = strbuf_detach(&sb, NULL);
+ return 0;
+
+error:
+ strbuf_release(&sb);
+ return -1;
+}
+
+static int process_group_desc(struct perf_file_section *section __maybe_unused,
+ struct perf_header *ph, int fd,
+ void *data __maybe_unused)
+{
+ size_t ret = -1;
+ u32 i, nr, nr_groups;
+ struct perf_session *session;
+ struct perf_evsel *evsel, *leader = NULL;
+ struct group_desc {
+ char *name;
+ u32 leader_idx;
+ u32 nr_members;
+ } *desc;
+
+ if (readn(fd, &nr_groups, sizeof(nr_groups)) != sizeof(nr_groups))
+ return -1;
+
+ if (ph->needs_swap)
+ nr_groups = bswap_32(nr_groups);
+
+ ph->env.nr_groups = nr_groups;
+ if (!nr_groups) {
+ pr_debug("group desc not available\n");
+ return 0;
+ }
+
+ desc = calloc(nr_groups, sizeof(*desc));
+ if (!desc)
+ return -1;
+
+ for (i = 0; i < nr_groups; i++) {
+ desc[i].name = do_read_string(fd, ph);
+ if (!desc[i].name)
+ goto out_free;
+
+ if (readn(fd, &desc[i].leader_idx, sizeof(u32)) != sizeof(u32))
+ goto out_free;
+
+ if (readn(fd, &desc[i].nr_members, sizeof(u32)) != sizeof(u32))
+ goto out_free;
+
+ if (ph->needs_swap) {
+ desc[i].leader_idx = bswap_32(desc[i].leader_idx);
+ desc[i].nr_members = bswap_32(desc[i].nr_members);
+ }
+ }
+
+ /*
+ * Rebuild group relationship based on the group_desc
+ */
+ session = container_of(ph, struct perf_session, header);
+ session->evlist->nr_groups = nr_groups;
+
+ i = nr = 0;
+ evlist__for_each(session->evlist, evsel) {
+ if (evsel->idx == (int) desc[i].leader_idx) {
+ evsel->leader = evsel;
+ /* {anon_group} is a dummy name */
+ if (strcmp(desc[i].name, "{anon_group}")) {
+ evsel->group_name = desc[i].name;
+ desc[i].name = NULL;
+ }
+ evsel->nr_members = desc[i].nr_members;
+
+ if (i >= nr_groups || nr > 0) {
+ pr_debug("invalid group desc\n");
+ goto out_free;
+ }
+
+ leader = evsel;
+ nr = evsel->nr_members - 1;
+ i++;
+ } else if (nr) {
+ /* This is a group member */
+ evsel->leader = leader;
+
+ nr--;
+ }
+ }
+
+ if (i != nr_groups || nr != 0) {
+ pr_debug("invalid group desc\n");
+ goto out_free;
+ }
+
+ ret = 0;
+out_free:
+ for (i = 0; i < nr_groups; i++)
+ zfree(&desc[i].name);
+ free(desc);
+
+ return ret;
+}
+
+struct feature_ops {
+ int (*write)(int fd, struct perf_header *h, struct perf_evlist *evlist);
+ void (*print)(struct perf_header *h, int fd, FILE *fp);
+ int (*process)(struct perf_file_section *section,
+ struct perf_header *h, int fd, void *data);
+ const char *name;
+ bool full_only;
+};
+
+#define FEAT_OPA(n, func) \
+ [n] = { .name = #n, .write = write_##func, .print = print_##func }
+#define FEAT_OPP(n, func) \
+ [n] = { .name = #n, .write = write_##func, .print = print_##func, \
+ .process = process_##func }
+#define FEAT_OPF(n, func) \
+ [n] = { .name = #n, .write = write_##func, .print = print_##func, \
+ .process = process_##func, .full_only = true }
+
+/* feature_ops not implemented: */
+#define print_tracing_data NULL
+#define print_build_id NULL
+
+static const struct feature_ops feat_ops[HEADER_LAST_FEATURE] = {
+ FEAT_OPP(HEADER_TRACING_DATA, tracing_data),
+ FEAT_OPP(HEADER_BUILD_ID, build_id),
+ FEAT_OPP(HEADER_HOSTNAME, hostname),
+ FEAT_OPP(HEADER_OSRELEASE, osrelease),
+ FEAT_OPP(HEADER_VERSION, version),
+ FEAT_OPP(HEADER_ARCH, arch),
+ FEAT_OPP(HEADER_NRCPUS, nrcpus),
+ FEAT_OPP(HEADER_CPUDESC, cpudesc),
+ FEAT_OPP(HEADER_CPUID, cpuid),
+ FEAT_OPP(HEADER_TOTAL_MEM, total_mem),
+ FEAT_OPP(HEADER_EVENT_DESC, event_desc),
+ FEAT_OPP(HEADER_CMDLINE, cmdline),
+ FEAT_OPF(HEADER_CPU_TOPOLOGY, cpu_topology),
+ FEAT_OPF(HEADER_NUMA_TOPOLOGY, numa_topology),
+ FEAT_OPA(HEADER_BRANCH_STACK, branch_stack),
+ FEAT_OPP(HEADER_PMU_MAPPINGS, pmu_mappings),
+ FEAT_OPP(HEADER_GROUP_DESC, group_desc),
+};
+
+struct header_print_data {
+ FILE *fp;
+ bool full; /* extended list of headers */
+};
+
+static int perf_file_section__fprintf_info(struct perf_file_section *section,
+ struct perf_header *ph,
+ int feat, int fd, void *data)
+{
+ struct header_print_data *hd = data;
+
+ if (lseek(fd, section->offset, SEEK_SET) == (off_t)-1) {
+ pr_debug("Failed to lseek to %" PRIu64 " offset for feature "
+ "%d, continuing...\n", section->offset, feat);
+ return 0;
+ }
+ if (feat >= HEADER_LAST_FEATURE) {
+ pr_warning("unknown feature %d\n", feat);
+ return 0;
+ }
+ if (!feat_ops[feat].print)
+ return 0;
+
+ if (!feat_ops[feat].full_only || hd->full)
+ feat_ops[feat].print(ph, fd, hd->fp);
+ else
+ fprintf(hd->fp, "# %s info available, use -I to display\n",
+ feat_ops[feat].name);
+
+ return 0;
+}
+
+int perf_header__fprintf_info(struct perf_session *session, FILE *fp, bool full)
+{
+ struct header_print_data hd;
+ struct perf_header *header = &session->header;
+ int fd = perf_data_file__fd(session->file);
+ hd.fp = fp;
+ hd.full = full;
+
+ perf_header__process_sections(header, fd, &hd,
+ perf_file_section__fprintf_info);
+ return 0;
+}
+
+static int do_write_feat(int fd, struct perf_header *h, int type,
+ struct perf_file_section **p,
+ struct perf_evlist *evlist)
+{
+ int err;
+ int ret = 0;
+
+ if (perf_header__has_feat(h, type)) {
+ if (!feat_ops[type].write)
+ return -1;
+
+ (*p)->offset = lseek(fd, 0, SEEK_CUR);
+
+ err = feat_ops[type].write(fd, h, evlist);
+ if (err < 0) {
+ pr_debug("failed to write feature %d\n", type);
+
+ /* undo anything written */
+ lseek(fd, (*p)->offset, SEEK_SET);
+
+ return -1;
+ }
+ (*p)->size = lseek(fd, 0, SEEK_CUR) - (*p)->offset;
+ (*p)++;
+ }
+ return ret;
+}
+
+static int perf_header__adds_write(struct perf_header *header,
+ struct perf_evlist *evlist, int fd)
+{
+ int nr_sections;
+ struct perf_file_section *feat_sec, *p;
+ int sec_size;
+ u64 sec_start;
+ int feat;
+ int err;
+
+ nr_sections = bitmap_weight(header->adds_features, HEADER_FEAT_BITS);
+ if (!nr_sections)
+ return 0;
+
+ feat_sec = p = calloc(nr_sections, sizeof(*feat_sec));
+ if (feat_sec == NULL)
+ return -ENOMEM;
+
+ sec_size = sizeof(*feat_sec) * nr_sections;
+
+ sec_start = header->feat_offset;
+ lseek(fd, sec_start + sec_size, SEEK_SET);
+
+ for_each_set_bit(feat, header->adds_features, HEADER_FEAT_BITS) {
+ if (do_write_feat(fd, header, feat, &p, evlist))
+ perf_header__clear_feat(header, feat);
+ }
+
+ lseek(fd, sec_start, SEEK_SET);
+ /*
+ * may write more than needed due to dropped feature, but
+ * this is okay, reader will skip the mising entries
+ */
+ err = do_write(fd, feat_sec, sec_size);
+ if (err < 0)
+ pr_debug("failed to write feature section\n");
+ free(feat_sec);
+ return err;
+}
+
+int perf_header__write_pipe(int fd)
+{
+ struct perf_pipe_file_header f_header;
+ int err;
+
+ f_header = (struct perf_pipe_file_header){
+ .magic = PERF_MAGIC,
+ .size = sizeof(f_header),
+ };
+
+ err = do_write(fd, &f_header, sizeof(f_header));
+ if (err < 0) {
+ pr_debug("failed to write perf pipe header\n");
+ return err;
+ }
+
+ return 0;
+}
+
+int perf_session__write_header(struct perf_session *session,
+ struct perf_evlist *evlist,
+ int fd, bool at_exit)
+{
+ struct perf_file_header f_header;
+ struct perf_file_attr f_attr;
+ struct perf_header *header = &session->header;
+ struct perf_evsel *evsel;
+ u64 attr_offset;
+ int err;
+
+ lseek(fd, sizeof(f_header), SEEK_SET);
+
+ evlist__for_each(session->evlist, evsel) {
+ evsel->id_offset = lseek(fd, 0, SEEK_CUR);
+ err = do_write(fd, evsel->id, evsel->ids * sizeof(u64));
+ if (err < 0) {
+ pr_debug("failed to write perf header\n");
+ return err;
+ }
+ }
+
+ attr_offset = lseek(fd, 0, SEEK_CUR);
+
+ evlist__for_each(evlist, evsel) {
+ f_attr = (struct perf_file_attr){
+ .attr = evsel->attr,
+ .ids = {
+ .offset = evsel->id_offset,
+ .size = evsel->ids * sizeof(u64),
+ }
+ };
+ err = do_write(fd, &f_attr, sizeof(f_attr));
+ if (err < 0) {
+ pr_debug("failed to write perf header attribute\n");
+ return err;
+ }
+ }
+
+ if (!header->data_offset)
+ header->data_offset = lseek(fd, 0, SEEK_CUR);
+ header->feat_offset = header->data_offset + header->data_size;
+
+ if (at_exit) {
+ err = perf_header__adds_write(header, evlist, fd);
+ if (err < 0)
+ return err;
+ }
+
+ f_header = (struct perf_file_header){
+ .magic = PERF_MAGIC,
+ .size = sizeof(f_header),
+ .attr_size = sizeof(f_attr),
+ .attrs = {
+ .offset = attr_offset,
+ .size = evlist->nr_entries * sizeof(f_attr),
+ },
+ .data = {
+ .offset = header->data_offset,
+ .size = header->data_size,
+ },
+ /* event_types is ignored, store zeros */
+ };
+
+ memcpy(&f_header.adds_features, &header->adds_features, sizeof(header->adds_features));
+
+ lseek(fd, 0, SEEK_SET);
+ err = do_write(fd, &f_header, sizeof(f_header));
+ if (err < 0) {
+ pr_debug("failed to write perf header\n");
+ return err;
+ }
+ lseek(fd, header->data_offset + header->data_size, SEEK_SET);
+
+ return 0;
+}
+
+static int perf_header__getbuffer64(struct perf_header *header,
+ int fd, void *buf, size_t size)
+{
+ if (readn(fd, buf, size) <= 0)
+ return -1;
+
+ if (header->needs_swap)
+ mem_bswap_64(buf, size);
+
+ return 0;
+}
+
+int perf_header__process_sections(struct perf_header *header, int fd,
+ void *data,
+ int (*process)(struct perf_file_section *section,
+ struct perf_header *ph,
+ int feat, int fd, void *data))
+{
+ struct perf_file_section *feat_sec, *sec;
+ int nr_sections;
+ int sec_size;
+ int feat;
+ int err;
+
+ nr_sections = bitmap_weight(header->adds_features, HEADER_FEAT_BITS);
+ if (!nr_sections)
+ return 0;
+
+ feat_sec = sec = calloc(nr_sections, sizeof(*feat_sec));
+ if (!feat_sec)
+ return -1;
+
+ sec_size = sizeof(*feat_sec) * nr_sections;
+
+ lseek(fd, header->feat_offset, SEEK_SET);
+
+ err = perf_header__getbuffer64(header, fd, feat_sec, sec_size);
+ if (err < 0)
+ goto out_free;
+
+ for_each_set_bit(feat, header->adds_features, HEADER_LAST_FEATURE) {
+ err = process(sec++, header, feat, fd, data);
+ if (err < 0)
+ goto out_free;
+ }
+ err = 0;
+out_free:
+ free(feat_sec);
+ return err;
+}
+
+static const int attr_file_abi_sizes[] = {
+ [0] = PERF_ATTR_SIZE_VER0,
+ [1] = PERF_ATTR_SIZE_VER1,
+ [2] = PERF_ATTR_SIZE_VER2,
+ [3] = PERF_ATTR_SIZE_VER3,
+ 0,
+};
+
+/*
+ * In the legacy file format, the magic number is not used to encode endianness.
+ * hdr_sz was used to encode endianness. But given that hdr_sz can vary based
+ * on ABI revisions, we need to try all combinations for all endianness to
+ * detect the endianness.
+ */
+static int try_all_file_abis(uint64_t hdr_sz, struct perf_header *ph)
+{
+ uint64_t ref_size, attr_size;
+ int i;
+
+ for (i = 0 ; attr_file_abi_sizes[i]; i++) {
+ ref_size = attr_file_abi_sizes[i]
+ + sizeof(struct perf_file_section);
+ if (hdr_sz != ref_size) {
+ attr_size = bswap_64(hdr_sz);
+ if (attr_size != ref_size)
+ continue;
+
+ ph->needs_swap = true;
+ }
+ pr_debug("ABI%d perf.data file detected, need_swap=%d\n",
+ i,
+ ph->needs_swap);
+ return 0;
+ }
+ /* could not determine endianness */
+ return -1;
+}
+
+#define PERF_PIPE_HDR_VER0 16
+
+static const size_t attr_pipe_abi_sizes[] = {
+ [0] = PERF_PIPE_HDR_VER0,
+ 0,
+};
+
+/*
+ * In the legacy pipe format, there is an implicit assumption that endiannesss
+ * between host recording the samples, and host parsing the samples is the
+ * same. This is not always the case given that the pipe output may always be
+ * redirected into a file and analyzed on a different machine with possibly a
+ * different endianness and perf_event ABI revsions in the perf tool itself.
+ */
+static int try_all_pipe_abis(uint64_t hdr_sz, struct perf_header *ph)
+{
+ u64 attr_size;
+ int i;
+
+ for (i = 0 ; attr_pipe_abi_sizes[i]; i++) {
+ if (hdr_sz != attr_pipe_abi_sizes[i]) {
+ attr_size = bswap_64(hdr_sz);
+ if (attr_size != hdr_sz)
+ continue;
+
+ ph->needs_swap = true;
+ }
+ pr_debug("Pipe ABI%d perf.data file detected\n", i);
+ return 0;
+ }
+ return -1;
+}
+
+bool is_perf_magic(u64 magic)
+{
+ if (!memcmp(&magic, __perf_magic1, sizeof(magic))
+ || magic == __perf_magic2
+ || magic == __perf_magic2_sw)
+ return true;
+
+ return false;
+}
+
+static int check_magic_endian(u64 magic, uint64_t hdr_sz,
+ bool is_pipe, struct perf_header *ph)
+{
+ int ret;
+
+ /* check for legacy format */
+ ret = memcmp(&magic, __perf_magic1, sizeof(magic));
+ if (ret == 0) {
+ ph->version = PERF_HEADER_VERSION_1;
+ pr_debug("legacy perf.data format\n");
+ if (is_pipe)
+ return try_all_pipe_abis(hdr_sz, ph);
+
+ return try_all_file_abis(hdr_sz, ph);
+ }
+ /*
+ * the new magic number serves two purposes:
+ * - unique number to identify actual perf.data files
+ * - encode endianness of file
+ */
+
+ /* check magic number with one endianness */
+ if (magic == __perf_magic2)
+ return 0;
+
+ /* check magic number with opposite endianness */
+ if (magic != __perf_magic2_sw)
+ return -1;
+
+ ph->needs_swap = true;
+ ph->version = PERF_HEADER_VERSION_2;
+
+ return 0;
+}
+
+int perf_file_header__read(struct perf_file_header *header,
+ struct perf_header *ph, int fd)
+{
+ ssize_t ret;
+
+ lseek(fd, 0, SEEK_SET);
+
+ ret = readn(fd, header, sizeof(*header));
+ if (ret <= 0)
+ return -1;
+
+ if (check_magic_endian(header->magic,
+ header->attr_size, false, ph) < 0) {
+ pr_debug("magic/endian check failed\n");
+ return -1;
+ }
+
+ if (ph->needs_swap) {
+ mem_bswap_64(header, offsetof(struct perf_file_header,
+ adds_features));
+ }
+
+ if (header->size != sizeof(*header)) {
+ /* Support the previous format */
+ if (header->size == offsetof(typeof(*header), adds_features))
+ bitmap_zero(header->adds_features, HEADER_FEAT_BITS);
+ else
+ return -1;
+ } else if (ph->needs_swap) {
+ /*
+ * feature bitmap is declared as an array of unsigned longs --
+ * not good since its size can differ between the host that
+ * generated the data file and the host analyzing the file.
+ *
+ * We need to handle endianness, but we don't know the size of
+ * the unsigned long where the file was generated. Take a best
+ * guess at determining it: try 64-bit swap first (ie., file
+ * created on a 64-bit host), and check if the hostname feature
+ * bit is set (this feature bit is forced on as of fbe96f2).
+ * If the bit is not, undo the 64-bit swap and try a 32-bit
+ * swap. If the hostname bit is still not set (e.g., older data
+ * file), punt and fallback to the original behavior --
+ * clearing all feature bits and setting buildid.
+ */
+ mem_bswap_64(&header->adds_features,
+ BITS_TO_U64(HEADER_FEAT_BITS));
+
+ if (!test_bit(HEADER_HOSTNAME, header->adds_features)) {
+ /* unswap as u64 */
+ mem_bswap_64(&header->adds_features,
+ BITS_TO_U64(HEADER_FEAT_BITS));
+
+ /* unswap as u32 */
+ mem_bswap_32(&header->adds_features,
+ BITS_TO_U32(HEADER_FEAT_BITS));
+ }
+
+ if (!test_bit(HEADER_HOSTNAME, header->adds_features)) {
+ bitmap_zero(header->adds_features, HEADER_FEAT_BITS);
+ set_bit(HEADER_BUILD_ID, header->adds_features);
+ }
+ }
+
+ memcpy(&ph->adds_features, &header->adds_features,
+ sizeof(ph->adds_features));
+
+ ph->data_offset = header->data.offset;
+ ph->data_size = header->data.size;
+ ph->feat_offset = header->data.offset + header->data.size;
+ return 0;
+}
+
+static int perf_file_section__process(struct perf_file_section *section,
+ struct perf_header *ph,
+ int feat, int fd, void *data)
+{
+ if (lseek(fd, section->offset, SEEK_SET) == (off_t)-1) {
+ pr_debug("Failed to lseek to %" PRIu64 " offset for feature "
+ "%d, continuing...\n", section->offset, feat);
+ return 0;
+ }
+
+ if (feat >= HEADER_LAST_FEATURE) {
+ pr_debug("unknown feature %d, continuing...\n", feat);
+ return 0;
+ }
+
+ if (!feat_ops[feat].process)
+ return 0;
+
+ return feat_ops[feat].process(section, ph, fd, data);
+}
+
+static int perf_file_header__read_pipe(struct perf_pipe_file_header *header,
+ struct perf_header *ph, int fd,
+ bool repipe)
+{
+ ssize_t ret;
+
+ ret = readn(fd, header, sizeof(*header));
+ if (ret <= 0)
+ return -1;
+
+ if (check_magic_endian(header->magic, header->size, true, ph) < 0) {
+ pr_debug("endian/magic failed\n");
+ return -1;
+ }
+
+ if (ph->needs_swap)
+ header->size = bswap_64(header->size);
+
+ if (repipe && do_write(STDOUT_FILENO, header, sizeof(*header)) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int perf_header__read_pipe(struct perf_session *session)
+{
+ struct perf_header *header = &session->header;
+ struct perf_pipe_file_header f_header;
+
+ if (perf_file_header__read_pipe(&f_header, header,
+ perf_data_file__fd(session->file),
+ session->repipe) < 0) {
+ pr_debug("incompatible file format\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int read_attr(int fd, struct perf_header *ph,
+ struct perf_file_attr *f_attr)
+{
+ struct perf_event_attr *attr = &f_attr->attr;
+ size_t sz, left;
+ size_t our_sz = sizeof(f_attr->attr);
+ ssize_t ret;
+
+ memset(f_attr, 0, sizeof(*f_attr));
+
+ /* read minimal guaranteed structure */
+ ret = readn(fd, attr, PERF_ATTR_SIZE_VER0);
+ if (ret <= 0) {
+ pr_debug("cannot read %d bytes of header attr\n",
+ PERF_ATTR_SIZE_VER0);
+ return -1;
+ }
+
+ /* on file perf_event_attr size */
+ sz = attr->size;
+
+ if (ph->needs_swap)
+ sz = bswap_32(sz);
+
+ if (sz == 0) {
+ /* assume ABI0 */
+ sz = PERF_ATTR_SIZE_VER0;
+ } else if (sz > our_sz) {
+ pr_debug("file uses a more recent and unsupported ABI"
+ " (%zu bytes extra)\n", sz - our_sz);
+ return -1;
+ }
+ /* what we have not yet read and that we know about */
+ left = sz - PERF_ATTR_SIZE_VER0;
+ if (left) {
+ void *ptr = attr;
+ ptr += PERF_ATTR_SIZE_VER0;
+
+ ret = readn(fd, ptr, left);
+ }
+ /* read perf_file_section, ids are read in caller */
+ ret = readn(fd, &f_attr->ids, sizeof(f_attr->ids));
+
+ return ret <= 0 ? -1 : 0;
+}
+
+static int perf_evsel__prepare_tracepoint_event(struct perf_evsel *evsel,
+ struct pevent *pevent)
+{
+ struct event_format *event;
+ char bf[128];
+
+ /* already prepared */
+ if (evsel->tp_format)
+ return 0;
+
+ if (pevent == NULL) {
+ pr_debug("broken or missing trace data\n");
+ return -1;
+ }
+
+ event = pevent_find_event(pevent, evsel->attr.config);
+ if (event == NULL)
+ return -1;
+
+ if (!evsel->name) {
+ snprintf(bf, sizeof(bf), "%s:%s", event->system, event->name);
+ evsel->name = strdup(bf);
+ if (evsel->name == NULL)
+ return -1;
+ }
+
+ evsel->tp_format = event;
+ return 0;
+}
+
+static int perf_evlist__prepare_tracepoint_events(struct perf_evlist *evlist,
+ struct pevent *pevent)
+{
+ struct perf_evsel *pos;
+
+ evlist__for_each(evlist, pos) {
+ if (pos->attr.type == PERF_TYPE_TRACEPOINT &&
+ perf_evsel__prepare_tracepoint_event(pos, pevent))
+ return -1;
+ }
+
+ return 0;
+}
+
+int perf_session__read_header(struct perf_session *session)
+{
+ struct perf_data_file *file = session->file;
+ struct perf_header *header = &session->header;
+ struct perf_file_header f_header;
+ struct perf_file_attr f_attr;
+ u64 f_id;
+ int nr_attrs, nr_ids, i, j;
+ int fd = perf_data_file__fd(file);
+
+ session->evlist = perf_evlist__new();
+ if (session->evlist == NULL)
+ return -ENOMEM;
+
+ if (perf_data_file__is_pipe(file))
+ return perf_header__read_pipe(session);
+
+ if (perf_file_header__read(&f_header, header, fd) < 0)
+ return -EINVAL;
+
+ /*
+ * Sanity check that perf.data was written cleanly; data size is
+ * initialized to 0 and updated only if the on_exit function is run.
+ * If data size is still 0 then the file contains only partial
+ * information. Just warn user and process it as much as it can.
+ */
+ if (f_header.data.size == 0) {
+ pr_warning("WARNING: The %s file's data size field is 0 which is unexpected.\n"
+ "Was the 'perf record' command properly terminated?\n",
+ file->path);
+ }
+
+ nr_attrs = f_header.attrs.size / f_header.attr_size;
+ lseek(fd, f_header.attrs.offset, SEEK_SET);
+
+ for (i = 0; i < nr_attrs; i++) {
+ struct perf_evsel *evsel;
+ off_t tmp;
+
+ if (read_attr(fd, header, &f_attr) < 0)
+ goto out_errno;
+
+ if (header->needs_swap)
+ perf_event__attr_swap(&f_attr.attr);
+
+ tmp = lseek(fd, 0, SEEK_CUR);
+ evsel = perf_evsel__new(&f_attr.attr);
+
+ if (evsel == NULL)
+ goto out_delete_evlist;
+
+ evsel->needs_swap = header->needs_swap;
+ /*
+ * Do it before so that if perf_evsel__alloc_id fails, this
+ * entry gets purged too at perf_evlist__delete().
+ */
+ perf_evlist__add(session->evlist, evsel);
+
+ nr_ids = f_attr.ids.size / sizeof(u64);
+ /*
+ * We don't have the cpu and thread maps on the header, so
+ * for allocating the perf_sample_id table we fake 1 cpu and
+ * hattr->ids threads.
+ */
+ if (perf_evsel__alloc_id(evsel, 1, nr_ids))
+ goto out_delete_evlist;
+
+ lseek(fd, f_attr.ids.offset, SEEK_SET);
+
+ for (j = 0; j < nr_ids; j++) {
+ if (perf_header__getbuffer64(header, fd, &f_id, sizeof(f_id)))
+ goto out_errno;
+
+ perf_evlist__id_add(session->evlist, evsel, 0, j, f_id);
+ }
+
+ lseek(fd, tmp, SEEK_SET);
+ }
+
+ symbol_conf.nr_events = nr_attrs;
+
+ perf_header__process_sections(header, fd, &session->tevent,
+ perf_file_section__process);
+
+ if (perf_evlist__prepare_tracepoint_events(session->evlist,
+ session->tevent.pevent))
+ goto out_delete_evlist;
+
+ return 0;
+out_errno:
+ return -errno;
+
+out_delete_evlist:
+ perf_evlist__delete(session->evlist);
+ session->evlist = NULL;
+ return -ENOMEM;
+}
+
+int perf_event__synthesize_attr(struct perf_tool *tool,
+ struct perf_event_attr *attr, u32 ids, u64 *id,
+ perf_event__handler_t process)
+{
+ union perf_event *ev;
+ size_t size;
+ int err;
+
+ size = sizeof(struct perf_event_attr);
+ size = PERF_ALIGN(size, sizeof(u64));
+ size += sizeof(struct perf_event_header);
+ size += ids * sizeof(u64);
+
+ ev = malloc(size);
+
+ if (ev == NULL)
+ return -ENOMEM;
+
+ ev->attr.attr = *attr;
+ memcpy(ev->attr.id, id, ids * sizeof(u64));
+
+ ev->attr.header.type = PERF_RECORD_HEADER_ATTR;
+ ev->attr.header.size = (u16)size;
+
+ if (ev->attr.header.size == size)
+ err = process(tool, ev, NULL, NULL);
+ else
+ err = -E2BIG;
+
+ free(ev);
+
+ return err;
+}
+
+int perf_event__synthesize_attrs(struct perf_tool *tool,
+ struct perf_session *session,
+ perf_event__handler_t process)
+{
+ struct perf_evsel *evsel;
+ int err = 0;
+
+ evlist__for_each(session->evlist, evsel) {
+ err = perf_event__synthesize_attr(tool, &evsel->attr, evsel->ids,
+ evsel->id, process);
+ if (err) {
+ pr_debug("failed to create perf header attribute\n");
+ return err;
+ }
+ }
+
+ return err;
+}
+
+int perf_event__process_attr(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_evlist **pevlist)
+{
+ u32 i, ids, n_ids;
+ struct perf_evsel *evsel;
+ struct perf_evlist *evlist = *pevlist;
+
+ if (evlist == NULL) {
+ *pevlist = evlist = perf_evlist__new();
+ if (evlist == NULL)
+ return -ENOMEM;
+ }
+
+ evsel = perf_evsel__new(&event->attr.attr);
+ if (evsel == NULL)
+ return -ENOMEM;
+
+ perf_evlist__add(evlist, evsel);
+
+ ids = event->header.size;
+ ids -= (void *)&event->attr.id - (void *)event;
+ n_ids = ids / sizeof(u64);
+ /*
+ * We don't have the cpu and thread maps on the header, so
+ * for allocating the perf_sample_id table we fake 1 cpu and
+ * hattr->ids threads.
+ */
+ if (perf_evsel__alloc_id(evsel, 1, n_ids))
+ return -ENOMEM;
+
+ for (i = 0; i < n_ids; i++) {
+ perf_evlist__id_add(evlist, evsel, 0, i, event->attr.id[i]);
+ }
+
+ symbol_conf.nr_events = evlist->nr_entries;
+
+ return 0;
+}
+
+int perf_event__synthesize_tracing_data(struct perf_tool *tool, int fd,
+ struct perf_evlist *evlist,
+ perf_event__handler_t process)
+{
+ union perf_event ev;
+ struct tracing_data *tdata;
+ ssize_t size = 0, aligned_size = 0, padding;
+ int err __maybe_unused = 0;
+
+ /*
+ * We are going to store the size of the data followed
+ * by the data contents. Since the fd descriptor is a pipe,
+ * we cannot seek back to store the size of the data once
+ * we know it. Instead we:
+ *
+ * - write the tracing data to the temp file
+ * - get/write the data size to pipe
+ * - write the tracing data from the temp file
+ * to the pipe
+ */
+ tdata = tracing_data_get(&evlist->entries, fd, true);
+ if (!tdata)
+ return -1;
+
+ memset(&ev, 0, sizeof(ev));
+
+ ev.tracing_data.header.type = PERF_RECORD_HEADER_TRACING_DATA;
+ size = tdata->size;
+ aligned_size = PERF_ALIGN(size, sizeof(u64));
+ padding = aligned_size - size;
+ ev.tracing_data.header.size = sizeof(ev.tracing_data);
+ ev.tracing_data.size = aligned_size;
+
+ process(tool, &ev, NULL, NULL);
+
+ /*
+ * The put function will copy all the tracing data
+ * stored in temp file to the pipe.
+ */
+ tracing_data_put(tdata);
+
+ write_padded(fd, NULL, 0, padding);
+
+ return aligned_size;
+}
+
+int perf_event__process_tracing_data(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_session *session)
+{
+ ssize_t size_read, padding, size = event->tracing_data.size;
+ int fd = perf_data_file__fd(session->file);
+ off_t offset = lseek(fd, 0, SEEK_CUR);
+ char buf[BUFSIZ];
+
+ /* setup for reading amidst mmap */
+ lseek(fd, offset + sizeof(struct tracing_data_event),
+ SEEK_SET);
+
+ size_read = trace_report(fd, &session->tevent,
+ session->repipe);
+ padding = PERF_ALIGN(size_read, sizeof(u64)) - size_read;
+
+ if (readn(fd, buf, padding) < 0) {
+ pr_err("%s: reading input file", __func__);
+ return -1;
+ }
+ if (session->repipe) {
+ int retw = write(STDOUT_FILENO, buf, padding);
+ if (retw <= 0 || retw != padding) {
+ pr_err("%s: repiping tracing data padding", __func__);
+ return -1;
+ }
+ }
+
+ if (size_read + padding != size) {
+ pr_err("%s: tracing data size mismatch", __func__);
+ return -1;
+ }
+
+ perf_evlist__prepare_tracepoint_events(session->evlist,
+ session->tevent.pevent);
+
+ return size_read + padding;
+}
+
+int perf_event__synthesize_build_id(struct perf_tool *tool,
+ struct dso *pos, u16 misc,
+ perf_event__handler_t process,
+ struct machine *machine)
+{
+ union perf_event ev;
+ size_t len;
+ int err = 0;
+
+ if (!pos->hit)
+ return err;
+
+ memset(&ev, 0, sizeof(ev));
+
+ len = pos->long_name_len + 1;
+ len = PERF_ALIGN(len, NAME_ALIGN);
+ memcpy(&ev.build_id.build_id, pos->build_id, sizeof(pos->build_id));
+ ev.build_id.header.type = PERF_RECORD_HEADER_BUILD_ID;
+ ev.build_id.header.misc = misc;
+ ev.build_id.pid = machine->pid;
+ ev.build_id.header.size = sizeof(ev.build_id) + len;
+ memcpy(&ev.build_id.filename, pos->long_name, pos->long_name_len);
+
+ err = process(tool, &ev, NULL, machine);
+
+ return err;
+}
+
+int perf_event__process_build_id(struct perf_tool *tool __maybe_unused,
+ union perf_event *event,
+ struct perf_session *session)
+{
+ __event_process_build_id(&event->build_id,
+ event->build_id.filename,
+ session);
+ return 0;
+}
+
+void disable_buildid_cache(void)
+{
+ no_buildid_cache = true;
+}
diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h
new file mode 100644
index 00000000000..d08cfe49940
--- /dev/null
+++ b/tools/perf/util/header.h
@@ -0,0 +1,159 @@
+#ifndef __PERF_HEADER_H
+#define __PERF_HEADER_H
+
+#include <linux/perf_event.h>
+#include <sys/types.h>
+#include <stdbool.h>
+#include <linux/bitmap.h>
+#include <linux/types.h>
+#include "event.h"
+
+
+enum {
+ HEADER_RESERVED = 0, /* always cleared */
+ HEADER_FIRST_FEATURE = 1,
+ HEADER_TRACING_DATA = 1,
+ HEADER_BUILD_ID,
+
+ HEADER_HOSTNAME,
+ HEADER_OSRELEASE,
+ HEADER_VERSION,
+ HEADER_ARCH,
+ HEADER_NRCPUS,
+ HEADER_CPUDESC,
+ HEADER_CPUID,
+ HEADER_TOTAL_MEM,
+ HEADER_CMDLINE,
+ HEADER_EVENT_DESC,
+ HEADER_CPU_TOPOLOGY,
+ HEADER_NUMA_TOPOLOGY,
+ HEADER_BRANCH_STACK,
+ HEADER_PMU_MAPPINGS,
+ HEADER_GROUP_DESC,
+ HEADER_LAST_FEATURE,
+ HEADER_FEAT_BITS = 256,
+};
+
+enum perf_header_version {
+ PERF_HEADER_VERSION_1,
+ PERF_HEADER_VERSION_2,
+};
+
+struct perf_file_section {
+ u64 offset;
+ u64 size;
+};
+
+struct perf_file_header {
+ u64 magic;
+ u64 size;
+ u64 attr_size;
+ struct perf_file_section attrs;
+ struct perf_file_section data;
+ /* event_types is ignored */
+ struct perf_file_section event_types;
+ DECLARE_BITMAP(adds_features, HEADER_FEAT_BITS);
+};
+
+struct perf_pipe_file_header {
+ u64 magic;
+ u64 size;
+};
+
+struct perf_header;
+
+int perf_file_header__read(struct perf_file_header *header,
+ struct perf_header *ph, int fd);
+
+struct perf_session_env {
+ char *hostname;
+ char *os_release;
+ char *version;
+ char *arch;
+ int nr_cpus_online;
+ int nr_cpus_avail;
+ char *cpu_desc;
+ char *cpuid;
+ unsigned long long total_mem;
+
+ int nr_cmdline;
+ int nr_sibling_cores;
+ int nr_sibling_threads;
+ int nr_numa_nodes;
+ int nr_pmu_mappings;
+ int nr_groups;
+ char *cmdline;
+ char *sibling_cores;
+ char *sibling_threads;
+ char *numa_nodes;
+ char *pmu_mappings;
+};
+
+struct perf_header {
+ enum perf_header_version version;
+ bool needs_swap;
+ u64 data_offset;
+ u64 data_size;
+ u64 feat_offset;
+ DECLARE_BITMAP(adds_features, HEADER_FEAT_BITS);
+ struct perf_session_env env;
+};
+
+struct perf_evlist;
+struct perf_session;
+
+int perf_session__read_header(struct perf_session *session);
+int perf_session__write_header(struct perf_session *session,
+ struct perf_evlist *evlist,
+ int fd, bool at_exit);
+int perf_header__write_pipe(int fd);
+
+void perf_header__set_feat(struct perf_header *header, int feat);
+void perf_header__clear_feat(struct perf_header *header, int feat);
+bool perf_header__has_feat(const struct perf_header *header, int feat);
+
+int perf_header__set_cmdline(int argc, const char **argv);
+
+int perf_header__process_sections(struct perf_header *header, int fd,
+ void *data,
+ int (*process)(struct perf_file_section *section,
+ struct perf_header *ph,
+ int feat, int fd, void *data));
+
+int perf_header__fprintf_info(struct perf_session *s, FILE *fp, bool full);
+
+int build_id_cache__add_s(const char *sbuild_id, const char *debugdir,
+ const char *name, bool is_kallsyms, bool is_vdso);
+int build_id_cache__remove_s(const char *sbuild_id, const char *debugdir);
+
+int perf_event__synthesize_attr(struct perf_tool *tool,
+ struct perf_event_attr *attr, u32 ids, u64 *id,
+ perf_event__handler_t process);
+int perf_event__synthesize_attrs(struct perf_tool *tool,
+ struct perf_session *session,
+ perf_event__handler_t process);
+int perf_event__process_attr(struct perf_tool *tool, union perf_event *event,
+ struct perf_evlist **pevlist);
+
+int perf_event__synthesize_tracing_data(struct perf_tool *tool,
+ int fd, struct perf_evlist *evlist,
+ perf_event__handler_t process);
+int perf_event__process_tracing_data(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_session *session);
+
+int perf_event__synthesize_build_id(struct perf_tool *tool,
+ struct dso *pos, u16 misc,
+ perf_event__handler_t process,
+ struct machine *machine);
+int perf_event__process_build_id(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_session *session);
+bool is_perf_magic(u64 magic);
+
+/*
+ * arch specific callback
+ */
+int get_cpuid(char *buffer, size_t sz);
+
+#endif /* __PERF_HEADER_H */
diff --git a/tools/perf/util/help.c b/tools/perf/util/help.c
new file mode 100644
index 00000000000..86c37c47226
--- /dev/null
+++ b/tools/perf/util/help.c
@@ -0,0 +1,339 @@
+#include "cache.h"
+#include "../builtin.h"
+#include "exec_cmd.h"
+#include "levenshtein.h"
+#include "help.h"
+#include <termios.h>
+
+void add_cmdname(struct cmdnames *cmds, const char *name, size_t len)
+{
+ struct cmdname *ent = malloc(sizeof(*ent) + len + 1);
+
+ ent->len = len;
+ memcpy(ent->name, name, len);
+ ent->name[len] = 0;
+
+ ALLOC_GROW(cmds->names, cmds->cnt + 1, cmds->alloc);
+ cmds->names[cmds->cnt++] = ent;
+}
+
+static void clean_cmdnames(struct cmdnames *cmds)
+{
+ unsigned int i;
+
+ for (i = 0; i < cmds->cnt; ++i)
+ zfree(&cmds->names[i]);
+ zfree(&cmds->names);
+ cmds->cnt = 0;
+ cmds->alloc = 0;
+}
+
+static int cmdname_compare(const void *a_, const void *b_)
+{
+ struct cmdname *a = *(struct cmdname **)a_;
+ struct cmdname *b = *(struct cmdname **)b_;
+ return strcmp(a->name, b->name);
+}
+
+static void uniq(struct cmdnames *cmds)
+{
+ unsigned int i, j;
+
+ if (!cmds->cnt)
+ return;
+
+ for (i = j = 1; i < cmds->cnt; i++)
+ if (strcmp(cmds->names[i]->name, cmds->names[i-1]->name))
+ cmds->names[j++] = cmds->names[i];
+
+ cmds->cnt = j;
+}
+
+void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes)
+{
+ size_t ci, cj, ei;
+ int cmp;
+
+ ci = cj = ei = 0;
+ while (ci < cmds->cnt && ei < excludes->cnt) {
+ cmp = strcmp(cmds->names[ci]->name, excludes->names[ei]->name);
+ if (cmp < 0)
+ cmds->names[cj++] = cmds->names[ci++];
+ else if (cmp == 0)
+ ci++, ei++;
+ else if (cmp > 0)
+ ei++;
+ }
+
+ while (ci < cmds->cnt)
+ cmds->names[cj++] = cmds->names[ci++];
+
+ cmds->cnt = cj;
+}
+
+static void pretty_print_string_list(struct cmdnames *cmds, int longest)
+{
+ int cols = 1, rows;
+ int space = longest + 1; /* min 1 SP between words */
+ struct winsize win;
+ int max_cols;
+ int i, j;
+
+ get_term_dimensions(&win);
+ max_cols = win.ws_col - 1; /* don't print *on* the edge */
+
+ if (space < max_cols)
+ cols = max_cols / space;
+ rows = (cmds->cnt + cols - 1) / cols;
+
+ for (i = 0; i < rows; i++) {
+ printf(" ");
+
+ for (j = 0; j < cols; j++) {
+ unsigned int n = j * rows + i;
+ unsigned int size = space;
+
+ if (n >= cmds->cnt)
+ break;
+ if (j == cols-1 || n + rows >= cmds->cnt)
+ size = 1;
+ printf("%-*s", size, cmds->names[n]->name);
+ }
+ putchar('\n');
+ }
+}
+
+static int is_executable(const char *name)
+{
+ struct stat st;
+
+ if (stat(name, &st) || /* stat, not lstat */
+ !S_ISREG(st.st_mode))
+ return 0;
+
+ return st.st_mode & S_IXUSR;
+}
+
+static void list_commands_in_dir(struct cmdnames *cmds,
+ const char *path,
+ const char *prefix)
+{
+ int prefix_len;
+ DIR *dir = opendir(path);
+ struct dirent *de;
+ struct strbuf buf = STRBUF_INIT;
+ int len;
+
+ if (!dir)
+ return;
+ if (!prefix)
+ prefix = "perf-";
+ prefix_len = strlen(prefix);
+
+ strbuf_addf(&buf, "%s/", path);
+ len = buf.len;
+
+ while ((de = readdir(dir)) != NULL) {
+ int entlen;
+
+ if (prefixcmp(de->d_name, prefix))
+ continue;
+
+ strbuf_setlen(&buf, len);
+ strbuf_addstr(&buf, de->d_name);
+ if (!is_executable(buf.buf))
+ continue;
+
+ entlen = strlen(de->d_name) - prefix_len;
+ if (has_extension(de->d_name, ".exe"))
+ entlen -= 4;
+
+ add_cmdname(cmds, de->d_name + prefix_len, entlen);
+ }
+ closedir(dir);
+ strbuf_release(&buf);
+}
+
+void load_command_list(const char *prefix,
+ struct cmdnames *main_cmds,
+ struct cmdnames *other_cmds)
+{
+ const char *env_path = getenv("PATH");
+ const char *exec_path = perf_exec_path();
+
+ if (exec_path) {
+ list_commands_in_dir(main_cmds, exec_path, prefix);
+ qsort(main_cmds->names, main_cmds->cnt,
+ sizeof(*main_cmds->names), cmdname_compare);
+ uniq(main_cmds);
+ }
+
+ if (env_path) {
+ char *paths, *path, *colon;
+ path = paths = strdup(env_path);
+ while (1) {
+ if ((colon = strchr(path, PATH_SEP)))
+ *colon = 0;
+ if (!exec_path || strcmp(path, exec_path))
+ list_commands_in_dir(other_cmds, path, prefix);
+
+ if (!colon)
+ break;
+ path = colon + 1;
+ }
+ free(paths);
+
+ qsort(other_cmds->names, other_cmds->cnt,
+ sizeof(*other_cmds->names), cmdname_compare);
+ uniq(other_cmds);
+ }
+ exclude_cmds(other_cmds, main_cmds);
+}
+
+void list_commands(const char *title, struct cmdnames *main_cmds,
+ struct cmdnames *other_cmds)
+{
+ unsigned int i, longest = 0;
+
+ for (i = 0; i < main_cmds->cnt; i++)
+ if (longest < main_cmds->names[i]->len)
+ longest = main_cmds->names[i]->len;
+ for (i = 0; i < other_cmds->cnt; i++)
+ if (longest < other_cmds->names[i]->len)
+ longest = other_cmds->names[i]->len;
+
+ if (main_cmds->cnt) {
+ const char *exec_path = perf_exec_path();
+ printf("available %s in '%s'\n", title, exec_path);
+ printf("----------------");
+ mput_char('-', strlen(title) + strlen(exec_path));
+ putchar('\n');
+ pretty_print_string_list(main_cmds, longest);
+ putchar('\n');
+ }
+
+ if (other_cmds->cnt) {
+ printf("%s available from elsewhere on your $PATH\n", title);
+ printf("---------------------------------------");
+ mput_char('-', strlen(title));
+ putchar('\n');
+ pretty_print_string_list(other_cmds, longest);
+ putchar('\n');
+ }
+}
+
+int is_in_cmdlist(struct cmdnames *c, const char *s)
+{
+ unsigned int i;
+
+ for (i = 0; i < c->cnt; i++)
+ if (!strcmp(s, c->names[i]->name))
+ return 1;
+ return 0;
+}
+
+static int autocorrect;
+static struct cmdnames aliases;
+
+static int perf_unknown_cmd_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, "help.autocorrect"))
+ autocorrect = perf_config_int(var,value);
+ /* Also use aliases for command lookup */
+ if (!prefixcmp(var, "alias."))
+ add_cmdname(&aliases, var + 6, strlen(var + 6));
+
+ return perf_default_config(var, value, cb);
+}
+
+static int levenshtein_compare(const void *p1, const void *p2)
+{
+ const struct cmdname *const *c1 = p1, *const *c2 = p2;
+ const char *s1 = (*c1)->name, *s2 = (*c2)->name;
+ int l1 = (*c1)->len;
+ int l2 = (*c2)->len;
+ return l1 != l2 ? l1 - l2 : strcmp(s1, s2);
+}
+
+static void add_cmd_list(struct cmdnames *cmds, struct cmdnames *old)
+{
+ unsigned int i;
+
+ ALLOC_GROW(cmds->names, cmds->cnt + old->cnt, cmds->alloc);
+
+ for (i = 0; i < old->cnt; i++)
+ cmds->names[cmds->cnt++] = old->names[i];
+ zfree(&old->names);
+ old->cnt = 0;
+}
+
+const char *help_unknown_cmd(const char *cmd)
+{
+ unsigned int i, n = 0, best_similarity = 0;
+ struct cmdnames main_cmds, other_cmds;
+
+ memset(&main_cmds, 0, sizeof(main_cmds));
+ memset(&other_cmds, 0, sizeof(main_cmds));
+ memset(&aliases, 0, sizeof(aliases));
+
+ perf_config(perf_unknown_cmd_config, NULL);
+
+ load_command_list("perf-", &main_cmds, &other_cmds);
+
+ add_cmd_list(&main_cmds, &aliases);
+ add_cmd_list(&main_cmds, &other_cmds);
+ qsort(main_cmds.names, main_cmds.cnt,
+ sizeof(main_cmds.names), cmdname_compare);
+ uniq(&main_cmds);
+
+ if (main_cmds.cnt) {
+ /* This reuses cmdname->len for similarity index */
+ for (i = 0; i < main_cmds.cnt; ++i)
+ main_cmds.names[i]->len =
+ levenshtein(cmd, main_cmds.names[i]->name, 0, 2, 1, 4);
+
+ qsort(main_cmds.names, main_cmds.cnt,
+ sizeof(*main_cmds.names), levenshtein_compare);
+
+ best_similarity = main_cmds.names[0]->len;
+ n = 1;
+ while (n < main_cmds.cnt && best_similarity == main_cmds.names[n]->len)
+ ++n;
+ }
+
+ if (autocorrect && n == 1) {
+ const char *assumed = main_cmds.names[0]->name;
+
+ main_cmds.names[0] = NULL;
+ clean_cmdnames(&main_cmds);
+ fprintf(stderr, "WARNING: You called a perf program named '%s', "
+ "which does not exist.\n"
+ "Continuing under the assumption that you meant '%s'\n",
+ cmd, assumed);
+ if (autocorrect > 0) {
+ fprintf(stderr, "in %0.1f seconds automatically...\n",
+ (float)autocorrect/10.0);
+ poll(NULL, 0, autocorrect * 100);
+ }
+ return assumed;
+ }
+
+ fprintf(stderr, "perf: '%s' is not a perf-command. See 'perf --help'.\n", cmd);
+
+ if (main_cmds.cnt && best_similarity < 6) {
+ fprintf(stderr, "\nDid you mean %s?\n",
+ n < 2 ? "this": "one of these");
+
+ for (i = 0; i < n; i++)
+ fprintf(stderr, "\t%s\n", main_cmds.names[i]->name);
+ }
+
+ exit(1);
+}
+
+int cmd_version(int argc __maybe_unused, const char **argv __maybe_unused,
+ const char *prefix __maybe_unused)
+{
+ printf("perf version %s\n", perf_version_string);
+ return 0;
+}
diff --git a/tools/perf/util/help.h b/tools/perf/util/help.h
new file mode 100644
index 00000000000..7f5c6dedd71
--- /dev/null
+++ b/tools/perf/util/help.h
@@ -0,0 +1,29 @@
+#ifndef __PERF_HELP_H
+#define __PERF_HELP_H
+
+struct cmdnames {
+ size_t alloc;
+ size_t cnt;
+ struct cmdname {
+ size_t len; /* also used for similarity index in help.c */
+ char name[FLEX_ARRAY];
+ } **names;
+};
+
+static inline void mput_char(char c, unsigned int num)
+{
+ while(num--)
+ putchar(c);
+}
+
+void load_command_list(const char *prefix,
+ struct cmdnames *main_cmds,
+ struct cmdnames *other_cmds);
+void add_cmdname(struct cmdnames *cmds, const char *name, size_t len);
+/* Here we require that excludes is a sorted list. */
+void exclude_cmds(struct cmdnames *cmds, struct cmdnames *excludes);
+int is_in_cmdlist(struct cmdnames *c, const char *s);
+void list_commands(const char *title, struct cmdnames *main_cmds,
+ struct cmdnames *other_cmds);
+
+#endif /* __PERF_HELP_H */
diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c
new file mode 100644
index 00000000000..30df6187ee0
--- /dev/null
+++ b/tools/perf/util/hist.c
@@ -0,0 +1,1414 @@
+#include "util.h"
+#include "build-id.h"
+#include "hist.h"
+#include "session.h"
+#include "sort.h"
+#include "evsel.h"
+#include "annotate.h"
+#include <math.h>
+
+static bool hists__filter_entry_by_dso(struct hists *hists,
+ struct hist_entry *he);
+static bool hists__filter_entry_by_thread(struct hists *hists,
+ struct hist_entry *he);
+static bool hists__filter_entry_by_symbol(struct hists *hists,
+ struct hist_entry *he);
+
+struct callchain_param callchain_param = {
+ .mode = CHAIN_GRAPH_REL,
+ .min_percent = 0.5,
+ .order = ORDER_CALLEE,
+ .key = CCKEY_FUNCTION
+};
+
+u16 hists__col_len(struct hists *hists, enum hist_column col)
+{
+ return hists->col_len[col];
+}
+
+void hists__set_col_len(struct hists *hists, enum hist_column col, u16 len)
+{
+ hists->col_len[col] = len;
+}
+
+bool hists__new_col_len(struct hists *hists, enum hist_column col, u16 len)
+{
+ if (len > hists__col_len(hists, col)) {
+ hists__set_col_len(hists, col, len);
+ return true;
+ }
+ return false;
+}
+
+void hists__reset_col_len(struct hists *hists)
+{
+ enum hist_column col;
+
+ for (col = 0; col < HISTC_NR_COLS; ++col)
+ hists__set_col_len(hists, col, 0);
+}
+
+static void hists__set_unres_dso_col_len(struct hists *hists, int dso)
+{
+ const unsigned int unresolved_col_width = BITS_PER_LONG / 4;
+
+ if (hists__col_len(hists, dso) < unresolved_col_width &&
+ !symbol_conf.col_width_list_str && !symbol_conf.field_sep &&
+ !symbol_conf.dso_list)
+ hists__set_col_len(hists, dso, unresolved_col_width);
+}
+
+void hists__calc_col_len(struct hists *hists, struct hist_entry *h)
+{
+ const unsigned int unresolved_col_width = BITS_PER_LONG / 4;
+ int symlen;
+ u16 len;
+
+ /*
+ * +4 accounts for '[x] ' priv level info
+ * +2 accounts for 0x prefix on raw addresses
+ * +3 accounts for ' y ' symtab origin info
+ */
+ if (h->ms.sym) {
+ symlen = h->ms.sym->namelen + 4;
+ if (verbose)
+ symlen += BITS_PER_LONG / 4 + 2 + 3;
+ hists__new_col_len(hists, HISTC_SYMBOL, symlen);
+ } else {
+ symlen = unresolved_col_width + 4 + 2;
+ hists__new_col_len(hists, HISTC_SYMBOL, symlen);
+ hists__set_unres_dso_col_len(hists, HISTC_DSO);
+ }
+
+ len = thread__comm_len(h->thread);
+ if (hists__new_col_len(hists, HISTC_COMM, len))
+ hists__set_col_len(hists, HISTC_THREAD, len + 6);
+
+ if (h->ms.map) {
+ len = dso__name_len(h->ms.map->dso);
+ hists__new_col_len(hists, HISTC_DSO, len);
+ }
+
+ if (h->parent)
+ hists__new_col_len(hists, HISTC_PARENT, h->parent->namelen);
+
+ if (h->branch_info) {
+ if (h->branch_info->from.sym) {
+ symlen = (int)h->branch_info->from.sym->namelen + 4;
+ if (verbose)
+ symlen += BITS_PER_LONG / 4 + 2 + 3;
+ hists__new_col_len(hists, HISTC_SYMBOL_FROM, symlen);
+
+ symlen = dso__name_len(h->branch_info->from.map->dso);
+ hists__new_col_len(hists, HISTC_DSO_FROM, symlen);
+ } else {
+ symlen = unresolved_col_width + 4 + 2;
+ hists__new_col_len(hists, HISTC_SYMBOL_FROM, symlen);
+ hists__set_unres_dso_col_len(hists, HISTC_DSO_FROM);
+ }
+
+ if (h->branch_info->to.sym) {
+ symlen = (int)h->branch_info->to.sym->namelen + 4;
+ if (verbose)
+ symlen += BITS_PER_LONG / 4 + 2 + 3;
+ hists__new_col_len(hists, HISTC_SYMBOL_TO, symlen);
+
+ symlen = dso__name_len(h->branch_info->to.map->dso);
+ hists__new_col_len(hists, HISTC_DSO_TO, symlen);
+ } else {
+ symlen = unresolved_col_width + 4 + 2;
+ hists__new_col_len(hists, HISTC_SYMBOL_TO, symlen);
+ hists__set_unres_dso_col_len(hists, HISTC_DSO_TO);
+ }
+ }
+
+ if (h->mem_info) {
+ if (h->mem_info->daddr.sym) {
+ symlen = (int)h->mem_info->daddr.sym->namelen + 4
+ + unresolved_col_width + 2;
+ hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL,
+ symlen);
+ hists__new_col_len(hists, HISTC_MEM_DCACHELINE,
+ symlen + 1);
+ } else {
+ symlen = unresolved_col_width + 4 + 2;
+ hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL,
+ symlen);
+ }
+ if (h->mem_info->daddr.map) {
+ symlen = dso__name_len(h->mem_info->daddr.map->dso);
+ hists__new_col_len(hists, HISTC_MEM_DADDR_DSO,
+ symlen);
+ } else {
+ symlen = unresolved_col_width + 4 + 2;
+ hists__set_unres_dso_col_len(hists, HISTC_MEM_DADDR_DSO);
+ }
+ } else {
+ symlen = unresolved_col_width + 4 + 2;
+ hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL, symlen);
+ hists__set_unres_dso_col_len(hists, HISTC_MEM_DADDR_DSO);
+ }
+
+ hists__new_col_len(hists, HISTC_MEM_LOCKED, 6);
+ hists__new_col_len(hists, HISTC_MEM_TLB, 22);
+ hists__new_col_len(hists, HISTC_MEM_SNOOP, 12);
+ hists__new_col_len(hists, HISTC_MEM_LVL, 21 + 3);
+ hists__new_col_len(hists, HISTC_LOCAL_WEIGHT, 12);
+ hists__new_col_len(hists, HISTC_GLOBAL_WEIGHT, 12);
+
+ if (h->transaction)
+ hists__new_col_len(hists, HISTC_TRANSACTION,
+ hist_entry__transaction_len());
+}
+
+void hists__output_recalc_col_len(struct hists *hists, int max_rows)
+{
+ struct rb_node *next = rb_first(&hists->entries);
+ struct hist_entry *n;
+ int row = 0;
+
+ hists__reset_col_len(hists);
+
+ while (next && row++ < max_rows) {
+ n = rb_entry(next, struct hist_entry, rb_node);
+ if (!n->filtered)
+ hists__calc_col_len(hists, n);
+ next = rb_next(&n->rb_node);
+ }
+}
+
+static void he_stat__add_cpumode_period(struct he_stat *he_stat,
+ unsigned int cpumode, u64 period)
+{
+ switch (cpumode) {
+ case PERF_RECORD_MISC_KERNEL:
+ he_stat->period_sys += period;
+ break;
+ case PERF_RECORD_MISC_USER:
+ he_stat->period_us += period;
+ break;
+ case PERF_RECORD_MISC_GUEST_KERNEL:
+ he_stat->period_guest_sys += period;
+ break;
+ case PERF_RECORD_MISC_GUEST_USER:
+ he_stat->period_guest_us += period;
+ break;
+ default:
+ break;
+ }
+}
+
+static void he_stat__add_period(struct he_stat *he_stat, u64 period,
+ u64 weight)
+{
+
+ he_stat->period += period;
+ he_stat->weight += weight;
+ he_stat->nr_events += 1;
+}
+
+static void he_stat__add_stat(struct he_stat *dest, struct he_stat *src)
+{
+ dest->period += src->period;
+ dest->period_sys += src->period_sys;
+ dest->period_us += src->period_us;
+ dest->period_guest_sys += src->period_guest_sys;
+ dest->period_guest_us += src->period_guest_us;
+ dest->nr_events += src->nr_events;
+ dest->weight += src->weight;
+}
+
+static void he_stat__decay(struct he_stat *he_stat)
+{
+ he_stat->period = (he_stat->period * 7) / 8;
+ he_stat->nr_events = (he_stat->nr_events * 7) / 8;
+ /* XXX need decay for weight too? */
+}
+
+static bool hists__decay_entry(struct hists *hists, struct hist_entry *he)
+{
+ u64 prev_period = he->stat.period;
+ u64 diff;
+
+ if (prev_period == 0)
+ return true;
+
+ he_stat__decay(&he->stat);
+ if (symbol_conf.cumulate_callchain)
+ he_stat__decay(he->stat_acc);
+
+ diff = prev_period - he->stat.period;
+
+ hists->stats.total_period -= diff;
+ if (!he->filtered)
+ hists->stats.total_non_filtered_period -= diff;
+
+ return he->stat.period == 0;
+}
+
+void hists__decay_entries(struct hists *hists, bool zap_user, bool zap_kernel)
+{
+ struct rb_node *next = rb_first(&hists->entries);
+ struct hist_entry *n;
+
+ while (next) {
+ n = rb_entry(next, struct hist_entry, rb_node);
+ next = rb_next(&n->rb_node);
+ /*
+ * We may be annotating this, for instance, so keep it here in
+ * case some it gets new samples, we'll eventually free it when
+ * the user stops browsing and it agains gets fully decayed.
+ */
+ if (((zap_user && n->level == '.') ||
+ (zap_kernel && n->level != '.') ||
+ hists__decay_entry(hists, n)) &&
+ !n->used) {
+ rb_erase(&n->rb_node, &hists->entries);
+
+ if (sort__need_collapse)
+ rb_erase(&n->rb_node_in, &hists->entries_collapsed);
+
+ --hists->nr_entries;
+ if (!n->filtered)
+ --hists->nr_non_filtered_entries;
+
+ hist_entry__free(n);
+ }
+ }
+}
+
+/*
+ * histogram, sorted on item, collects periods
+ */
+
+static struct hist_entry *hist_entry__new(struct hist_entry *template,
+ bool sample_self)
+{
+ size_t callchain_size = 0;
+ struct hist_entry *he;
+
+ if (symbol_conf.use_callchain || symbol_conf.cumulate_callchain)
+ callchain_size = sizeof(struct callchain_root);
+
+ he = zalloc(sizeof(*he) + callchain_size);
+
+ if (he != NULL) {
+ *he = *template;
+
+ if (symbol_conf.cumulate_callchain) {
+ he->stat_acc = malloc(sizeof(he->stat));
+ if (he->stat_acc == NULL) {
+ free(he);
+ return NULL;
+ }
+ memcpy(he->stat_acc, &he->stat, sizeof(he->stat));
+ if (!sample_self)
+ memset(&he->stat, 0, sizeof(he->stat));
+ }
+
+ if (he->ms.map)
+ he->ms.map->referenced = true;
+
+ if (he->branch_info) {
+ /*
+ * This branch info is (a part of) allocated from
+ * sample__resolve_bstack() and will be freed after
+ * adding new entries. So we need to save a copy.
+ */
+ he->branch_info = malloc(sizeof(*he->branch_info));
+ if (he->branch_info == NULL) {
+ free(he->stat_acc);
+ free(he);
+ return NULL;
+ }
+
+ memcpy(he->branch_info, template->branch_info,
+ sizeof(*he->branch_info));
+
+ if (he->branch_info->from.map)
+ he->branch_info->from.map->referenced = true;
+ if (he->branch_info->to.map)
+ he->branch_info->to.map->referenced = true;
+ }
+
+ if (he->mem_info) {
+ if (he->mem_info->iaddr.map)
+ he->mem_info->iaddr.map->referenced = true;
+ if (he->mem_info->daddr.map)
+ he->mem_info->daddr.map->referenced = true;
+ }
+
+ if (symbol_conf.use_callchain)
+ callchain_init(he->callchain);
+
+ INIT_LIST_HEAD(&he->pairs.node);
+ }
+
+ return he;
+}
+
+static u8 symbol__parent_filter(const struct symbol *parent)
+{
+ if (symbol_conf.exclude_other && parent == NULL)
+ return 1 << HIST_FILTER__PARENT;
+ return 0;
+}
+
+static struct hist_entry *add_hist_entry(struct hists *hists,
+ struct hist_entry *entry,
+ struct addr_location *al,
+ bool sample_self)
+{
+ struct rb_node **p;
+ struct rb_node *parent = NULL;
+ struct hist_entry *he;
+ int64_t cmp;
+ u64 period = entry->stat.period;
+ u64 weight = entry->stat.weight;
+
+ p = &hists->entries_in->rb_node;
+
+ while (*p != NULL) {
+ parent = *p;
+ he = rb_entry(parent, struct hist_entry, rb_node_in);
+
+ /*
+ * Make sure that it receives arguments in a same order as
+ * hist_entry__collapse() so that we can use an appropriate
+ * function when searching an entry regardless which sort
+ * keys were used.
+ */
+ cmp = hist_entry__cmp(he, entry);
+
+ if (!cmp) {
+ if (sample_self)
+ he_stat__add_period(&he->stat, period, weight);
+ if (symbol_conf.cumulate_callchain)
+ he_stat__add_period(he->stat_acc, period, weight);
+
+ /*
+ * This mem info was allocated from sample__resolve_mem
+ * and will not be used anymore.
+ */
+ zfree(&entry->mem_info);
+
+ /* If the map of an existing hist_entry has
+ * become out-of-date due to an exec() or
+ * similar, update it. Otherwise we will
+ * mis-adjust symbol addresses when computing
+ * the history counter to increment.
+ */
+ if (he->ms.map != entry->ms.map) {
+ he->ms.map = entry->ms.map;
+ if (he->ms.map)
+ he->ms.map->referenced = true;
+ }
+ goto out;
+ }
+
+ if (cmp < 0)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ he = hist_entry__new(entry, sample_self);
+ if (!he)
+ return NULL;
+
+ rb_link_node(&he->rb_node_in, parent, p);
+ rb_insert_color(&he->rb_node_in, hists->entries_in);
+out:
+ if (sample_self)
+ he_stat__add_cpumode_period(&he->stat, al->cpumode, period);
+ if (symbol_conf.cumulate_callchain)
+ he_stat__add_cpumode_period(he->stat_acc, al->cpumode, period);
+ return he;
+}
+
+struct hist_entry *__hists__add_entry(struct hists *hists,
+ struct addr_location *al,
+ struct symbol *sym_parent,
+ struct branch_info *bi,
+ struct mem_info *mi,
+ u64 period, u64 weight, u64 transaction,
+ bool sample_self)
+{
+ struct hist_entry entry = {
+ .thread = al->thread,
+ .comm = thread__comm(al->thread),
+ .ms = {
+ .map = al->map,
+ .sym = al->sym,
+ },
+ .cpu = al->cpu,
+ .cpumode = al->cpumode,
+ .ip = al->addr,
+ .level = al->level,
+ .stat = {
+ .nr_events = 1,
+ .period = period,
+ .weight = weight,
+ },
+ .parent = sym_parent,
+ .filtered = symbol__parent_filter(sym_parent) | al->filtered,
+ .hists = hists,
+ .branch_info = bi,
+ .mem_info = mi,
+ .transaction = transaction,
+ };
+
+ return add_hist_entry(hists, &entry, al, sample_self);
+}
+
+static int
+iter_next_nop_entry(struct hist_entry_iter *iter __maybe_unused,
+ struct addr_location *al __maybe_unused)
+{
+ return 0;
+}
+
+static int
+iter_add_next_nop_entry(struct hist_entry_iter *iter __maybe_unused,
+ struct addr_location *al __maybe_unused)
+{
+ return 0;
+}
+
+static int
+iter_prepare_mem_entry(struct hist_entry_iter *iter, struct addr_location *al)
+{
+ struct perf_sample *sample = iter->sample;
+ struct mem_info *mi;
+
+ mi = sample__resolve_mem(sample, al);
+ if (mi == NULL)
+ return -ENOMEM;
+
+ iter->priv = mi;
+ return 0;
+}
+
+static int
+iter_add_single_mem_entry(struct hist_entry_iter *iter, struct addr_location *al)
+{
+ u64 cost;
+ struct mem_info *mi = iter->priv;
+ struct hist_entry *he;
+
+ if (mi == NULL)
+ return -EINVAL;
+
+ cost = iter->sample->weight;
+ if (!cost)
+ cost = 1;
+
+ /*
+ * must pass period=weight in order to get the correct
+ * sorting from hists__collapse_resort() which is solely
+ * based on periods. We want sorting be done on nr_events * weight
+ * and this is indirectly achieved by passing period=weight here
+ * and the he_stat__add_period() function.
+ */
+ he = __hists__add_entry(&iter->evsel->hists, al, iter->parent, NULL, mi,
+ cost, cost, 0, true);
+ if (!he)
+ return -ENOMEM;
+
+ iter->he = he;
+ return 0;
+}
+
+static int
+iter_finish_mem_entry(struct hist_entry_iter *iter,
+ struct addr_location *al __maybe_unused)
+{
+ struct perf_evsel *evsel = iter->evsel;
+ struct hist_entry *he = iter->he;
+ int err = -EINVAL;
+
+ if (he == NULL)
+ goto out;
+
+ hists__inc_nr_samples(&evsel->hists, he->filtered);
+
+ err = hist_entry__append_callchain(he, iter->sample);
+
+out:
+ /*
+ * We don't need to free iter->priv (mem_info) here since
+ * the mem info was either already freed in add_hist_entry() or
+ * passed to a new hist entry by hist_entry__new().
+ */
+ iter->priv = NULL;
+
+ iter->he = NULL;
+ return err;
+}
+
+static int
+iter_prepare_branch_entry(struct hist_entry_iter *iter, struct addr_location *al)
+{
+ struct branch_info *bi;
+ struct perf_sample *sample = iter->sample;
+
+ bi = sample__resolve_bstack(sample, al);
+ if (!bi)
+ return -ENOMEM;
+
+ iter->curr = 0;
+ iter->total = sample->branch_stack->nr;
+
+ iter->priv = bi;
+ return 0;
+}
+
+static int
+iter_add_single_branch_entry(struct hist_entry_iter *iter __maybe_unused,
+ struct addr_location *al __maybe_unused)
+{
+ /* to avoid calling callback function */
+ iter->he = NULL;
+
+ return 0;
+}
+
+static int
+iter_next_branch_entry(struct hist_entry_iter *iter, struct addr_location *al)
+{
+ struct branch_info *bi = iter->priv;
+ int i = iter->curr;
+
+ if (bi == NULL)
+ return 0;
+
+ if (iter->curr >= iter->total)
+ return 0;
+
+ al->map = bi[i].to.map;
+ al->sym = bi[i].to.sym;
+ al->addr = bi[i].to.addr;
+ return 1;
+}
+
+static int
+iter_add_next_branch_entry(struct hist_entry_iter *iter, struct addr_location *al)
+{
+ struct branch_info *bi;
+ struct perf_evsel *evsel = iter->evsel;
+ struct hist_entry *he = NULL;
+ int i = iter->curr;
+ int err = 0;
+
+ bi = iter->priv;
+
+ if (iter->hide_unresolved && !(bi[i].from.sym && bi[i].to.sym))
+ goto out;
+
+ /*
+ * The report shows the percentage of total branches captured
+ * and not events sampled. Thus we use a pseudo period of 1.
+ */
+ he = __hists__add_entry(&evsel->hists, al, iter->parent, &bi[i], NULL,
+ 1, 1, 0, true);
+ if (he == NULL)
+ return -ENOMEM;
+
+ hists__inc_nr_samples(&evsel->hists, he->filtered);
+
+out:
+ iter->he = he;
+ iter->curr++;
+ return err;
+}
+
+static int
+iter_finish_branch_entry(struct hist_entry_iter *iter,
+ struct addr_location *al __maybe_unused)
+{
+ zfree(&iter->priv);
+ iter->he = NULL;
+
+ return iter->curr >= iter->total ? 0 : -1;
+}
+
+static int
+iter_prepare_normal_entry(struct hist_entry_iter *iter __maybe_unused,
+ struct addr_location *al __maybe_unused)
+{
+ return 0;
+}
+
+static int
+iter_add_single_normal_entry(struct hist_entry_iter *iter, struct addr_location *al)
+{
+ struct perf_evsel *evsel = iter->evsel;
+ struct perf_sample *sample = iter->sample;
+ struct hist_entry *he;
+
+ he = __hists__add_entry(&evsel->hists, al, iter->parent, NULL, NULL,
+ sample->period, sample->weight,
+ sample->transaction, true);
+ if (he == NULL)
+ return -ENOMEM;
+
+ iter->he = he;
+ return 0;
+}
+
+static int
+iter_finish_normal_entry(struct hist_entry_iter *iter,
+ struct addr_location *al __maybe_unused)
+{
+ struct hist_entry *he = iter->he;
+ struct perf_evsel *evsel = iter->evsel;
+ struct perf_sample *sample = iter->sample;
+
+ if (he == NULL)
+ return 0;
+
+ iter->he = NULL;
+
+ hists__inc_nr_samples(&evsel->hists, he->filtered);
+
+ return hist_entry__append_callchain(he, sample);
+}
+
+static int
+iter_prepare_cumulative_entry(struct hist_entry_iter *iter __maybe_unused,
+ struct addr_location *al __maybe_unused)
+{
+ struct hist_entry **he_cache;
+
+ callchain_cursor_commit(&callchain_cursor);
+
+ /*
+ * This is for detecting cycles or recursions so that they're
+ * cumulated only one time to prevent entries more than 100%
+ * overhead.
+ */
+ he_cache = malloc(sizeof(*he_cache) * (PERF_MAX_STACK_DEPTH + 1));
+ if (he_cache == NULL)
+ return -ENOMEM;
+
+ iter->priv = he_cache;
+ iter->curr = 0;
+
+ return 0;
+}
+
+static int
+iter_add_single_cumulative_entry(struct hist_entry_iter *iter,
+ struct addr_location *al)
+{
+ struct perf_evsel *evsel = iter->evsel;
+ struct perf_sample *sample = iter->sample;
+ struct hist_entry **he_cache = iter->priv;
+ struct hist_entry *he;
+ int err = 0;
+
+ he = __hists__add_entry(&evsel->hists, al, iter->parent, NULL, NULL,
+ sample->period, sample->weight,
+ sample->transaction, true);
+ if (he == NULL)
+ return -ENOMEM;
+
+ iter->he = he;
+ he_cache[iter->curr++] = he;
+
+ callchain_append(he->callchain, &callchain_cursor, sample->period);
+
+ /*
+ * We need to re-initialize the cursor since callchain_append()
+ * advanced the cursor to the end.
+ */
+ callchain_cursor_commit(&callchain_cursor);
+
+ hists__inc_nr_samples(&evsel->hists, he->filtered);
+
+ return err;
+}
+
+static int
+iter_next_cumulative_entry(struct hist_entry_iter *iter,
+ struct addr_location *al)
+{
+ struct callchain_cursor_node *node;
+
+ node = callchain_cursor_current(&callchain_cursor);
+ if (node == NULL)
+ return 0;
+
+ return fill_callchain_info(al, node, iter->hide_unresolved);
+}
+
+static int
+iter_add_next_cumulative_entry(struct hist_entry_iter *iter,
+ struct addr_location *al)
+{
+ struct perf_evsel *evsel = iter->evsel;
+ struct perf_sample *sample = iter->sample;
+ struct hist_entry **he_cache = iter->priv;
+ struct hist_entry *he;
+ struct hist_entry he_tmp = {
+ .cpu = al->cpu,
+ .thread = al->thread,
+ .comm = thread__comm(al->thread),
+ .ip = al->addr,
+ .ms = {
+ .map = al->map,
+ .sym = al->sym,
+ },
+ .parent = iter->parent,
+ };
+ int i;
+ struct callchain_cursor cursor;
+
+ callchain_cursor_snapshot(&cursor, &callchain_cursor);
+
+ callchain_cursor_advance(&callchain_cursor);
+
+ /*
+ * Check if there's duplicate entries in the callchain.
+ * It's possible that it has cycles or recursive calls.
+ */
+ for (i = 0; i < iter->curr; i++) {
+ if (hist_entry__cmp(he_cache[i], &he_tmp) == 0) {
+ /* to avoid calling callback function */
+ iter->he = NULL;
+ return 0;
+ }
+ }
+
+ he = __hists__add_entry(&evsel->hists, al, iter->parent, NULL, NULL,
+ sample->period, sample->weight,
+ sample->transaction, false);
+ if (he == NULL)
+ return -ENOMEM;
+
+ iter->he = he;
+ he_cache[iter->curr++] = he;
+
+ callchain_append(he->callchain, &cursor, sample->period);
+ return 0;
+}
+
+static int
+iter_finish_cumulative_entry(struct hist_entry_iter *iter,
+ struct addr_location *al __maybe_unused)
+{
+ zfree(&iter->priv);
+ iter->he = NULL;
+
+ return 0;
+}
+
+const struct hist_iter_ops hist_iter_mem = {
+ .prepare_entry = iter_prepare_mem_entry,
+ .add_single_entry = iter_add_single_mem_entry,
+ .next_entry = iter_next_nop_entry,
+ .add_next_entry = iter_add_next_nop_entry,
+ .finish_entry = iter_finish_mem_entry,
+};
+
+const struct hist_iter_ops hist_iter_branch = {
+ .prepare_entry = iter_prepare_branch_entry,
+ .add_single_entry = iter_add_single_branch_entry,
+ .next_entry = iter_next_branch_entry,
+ .add_next_entry = iter_add_next_branch_entry,
+ .finish_entry = iter_finish_branch_entry,
+};
+
+const struct hist_iter_ops hist_iter_normal = {
+ .prepare_entry = iter_prepare_normal_entry,
+ .add_single_entry = iter_add_single_normal_entry,
+ .next_entry = iter_next_nop_entry,
+ .add_next_entry = iter_add_next_nop_entry,
+ .finish_entry = iter_finish_normal_entry,
+};
+
+const struct hist_iter_ops hist_iter_cumulative = {
+ .prepare_entry = iter_prepare_cumulative_entry,
+ .add_single_entry = iter_add_single_cumulative_entry,
+ .next_entry = iter_next_cumulative_entry,
+ .add_next_entry = iter_add_next_cumulative_entry,
+ .finish_entry = iter_finish_cumulative_entry,
+};
+
+int hist_entry_iter__add(struct hist_entry_iter *iter, struct addr_location *al,
+ struct perf_evsel *evsel, struct perf_sample *sample,
+ int max_stack_depth, void *arg)
+{
+ int err, err2;
+
+ err = sample__resolve_callchain(sample, &iter->parent, evsel, al,
+ max_stack_depth);
+ if (err)
+ return err;
+
+ iter->evsel = evsel;
+ iter->sample = sample;
+
+ err = iter->ops->prepare_entry(iter, al);
+ if (err)
+ goto out;
+
+ err = iter->ops->add_single_entry(iter, al);
+ if (err)
+ goto out;
+
+ if (iter->he && iter->add_entry_cb) {
+ err = iter->add_entry_cb(iter, al, true, arg);
+ if (err)
+ goto out;
+ }
+
+ while (iter->ops->next_entry(iter, al)) {
+ err = iter->ops->add_next_entry(iter, al);
+ if (err)
+ break;
+
+ if (iter->he && iter->add_entry_cb) {
+ err = iter->add_entry_cb(iter, al, false, arg);
+ if (err)
+ goto out;
+ }
+ }
+
+out:
+ err2 = iter->ops->finish_entry(iter, al);
+ if (!err)
+ err = err2;
+
+ return err;
+}
+
+int64_t
+hist_entry__cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ struct perf_hpp_fmt *fmt;
+ int64_t cmp = 0;
+
+ perf_hpp__for_each_sort_list(fmt) {
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ cmp = fmt->cmp(left, right);
+ if (cmp)
+ break;
+ }
+
+ return cmp;
+}
+
+int64_t
+hist_entry__collapse(struct hist_entry *left, struct hist_entry *right)
+{
+ struct perf_hpp_fmt *fmt;
+ int64_t cmp = 0;
+
+ perf_hpp__for_each_sort_list(fmt) {
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ cmp = fmt->collapse(left, right);
+ if (cmp)
+ break;
+ }
+
+ return cmp;
+}
+
+void hist_entry__free(struct hist_entry *he)
+{
+ zfree(&he->branch_info);
+ zfree(&he->mem_info);
+ zfree(&he->stat_acc);
+ free_srcline(he->srcline);
+ free(he);
+}
+
+/*
+ * collapse the histogram
+ */
+
+static bool hists__collapse_insert_entry(struct hists *hists __maybe_unused,
+ struct rb_root *root,
+ struct hist_entry *he)
+{
+ struct rb_node **p = &root->rb_node;
+ struct rb_node *parent = NULL;
+ struct hist_entry *iter;
+ int64_t cmp;
+
+ while (*p != NULL) {
+ parent = *p;
+ iter = rb_entry(parent, struct hist_entry, rb_node_in);
+
+ cmp = hist_entry__collapse(iter, he);
+
+ if (!cmp) {
+ he_stat__add_stat(&iter->stat, &he->stat);
+ if (symbol_conf.cumulate_callchain)
+ he_stat__add_stat(iter->stat_acc, he->stat_acc);
+
+ if (symbol_conf.use_callchain) {
+ callchain_cursor_reset(&callchain_cursor);
+ callchain_merge(&callchain_cursor,
+ iter->callchain,
+ he->callchain);
+ }
+ hist_entry__free(he);
+ return false;
+ }
+
+ if (cmp < 0)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ rb_link_node(&he->rb_node_in, parent, p);
+ rb_insert_color(&he->rb_node_in, root);
+ return true;
+}
+
+static struct rb_root *hists__get_rotate_entries_in(struct hists *hists)
+{
+ struct rb_root *root;
+
+ pthread_mutex_lock(&hists->lock);
+
+ root = hists->entries_in;
+ if (++hists->entries_in > &hists->entries_in_array[1])
+ hists->entries_in = &hists->entries_in_array[0];
+
+ pthread_mutex_unlock(&hists->lock);
+
+ return root;
+}
+
+static void hists__apply_filters(struct hists *hists, struct hist_entry *he)
+{
+ hists__filter_entry_by_dso(hists, he);
+ hists__filter_entry_by_thread(hists, he);
+ hists__filter_entry_by_symbol(hists, he);
+}
+
+void hists__collapse_resort(struct hists *hists, struct ui_progress *prog)
+{
+ struct rb_root *root;
+ struct rb_node *next;
+ struct hist_entry *n;
+
+ if (!sort__need_collapse)
+ return;
+
+ root = hists__get_rotate_entries_in(hists);
+ next = rb_first(root);
+
+ while (next) {
+ if (session_done())
+ break;
+ n = rb_entry(next, struct hist_entry, rb_node_in);
+ next = rb_next(&n->rb_node_in);
+
+ rb_erase(&n->rb_node_in, root);
+ if (hists__collapse_insert_entry(hists, &hists->entries_collapsed, n)) {
+ /*
+ * If it wasn't combined with one of the entries already
+ * collapsed, we need to apply the filters that may have
+ * been set by, say, the hist_browser.
+ */
+ hists__apply_filters(hists, n);
+ }
+ if (prog)
+ ui_progress__update(prog, 1);
+ }
+}
+
+static int hist_entry__sort(struct hist_entry *a, struct hist_entry *b)
+{
+ struct perf_hpp_fmt *fmt;
+ int64_t cmp = 0;
+
+ perf_hpp__for_each_sort_list(fmt) {
+ if (perf_hpp__should_skip(fmt))
+ continue;
+
+ cmp = fmt->sort(a, b);
+ if (cmp)
+ break;
+ }
+
+ return cmp;
+}
+
+static void hists__reset_filter_stats(struct hists *hists)
+{
+ hists->nr_non_filtered_entries = 0;
+ hists->stats.total_non_filtered_period = 0;
+}
+
+void hists__reset_stats(struct hists *hists)
+{
+ hists->nr_entries = 0;
+ hists->stats.total_period = 0;
+
+ hists__reset_filter_stats(hists);
+}
+
+static void hists__inc_filter_stats(struct hists *hists, struct hist_entry *h)
+{
+ hists->nr_non_filtered_entries++;
+ hists->stats.total_non_filtered_period += h->stat.period;
+}
+
+void hists__inc_stats(struct hists *hists, struct hist_entry *h)
+{
+ if (!h->filtered)
+ hists__inc_filter_stats(hists, h);
+
+ hists->nr_entries++;
+ hists->stats.total_period += h->stat.period;
+}
+
+static void __hists__insert_output_entry(struct rb_root *entries,
+ struct hist_entry *he,
+ u64 min_callchain_hits)
+{
+ struct rb_node **p = &entries->rb_node;
+ struct rb_node *parent = NULL;
+ struct hist_entry *iter;
+
+ if (symbol_conf.use_callchain)
+ callchain_param.sort(&he->sorted_chain, he->callchain,
+ min_callchain_hits, &callchain_param);
+
+ while (*p != NULL) {
+ parent = *p;
+ iter = rb_entry(parent, struct hist_entry, rb_node);
+
+ if (hist_entry__sort(he, iter) > 0)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ rb_link_node(&he->rb_node, parent, p);
+ rb_insert_color(&he->rb_node, entries);
+}
+
+void hists__output_resort(struct hists *hists)
+{
+ struct rb_root *root;
+ struct rb_node *next;
+ struct hist_entry *n;
+ u64 min_callchain_hits;
+
+ min_callchain_hits = hists->stats.total_period * (callchain_param.min_percent / 100);
+
+ if (sort__need_collapse)
+ root = &hists->entries_collapsed;
+ else
+ root = hists->entries_in;
+
+ next = rb_first(root);
+ hists->entries = RB_ROOT;
+
+ hists__reset_stats(hists);
+ hists__reset_col_len(hists);
+
+ while (next) {
+ n = rb_entry(next, struct hist_entry, rb_node_in);
+ next = rb_next(&n->rb_node_in);
+
+ __hists__insert_output_entry(&hists->entries, n, min_callchain_hits);
+ hists__inc_stats(hists, n);
+
+ if (!n->filtered)
+ hists__calc_col_len(hists, n);
+ }
+}
+
+static void hists__remove_entry_filter(struct hists *hists, struct hist_entry *h,
+ enum hist_filter filter)
+{
+ h->filtered &= ~(1 << filter);
+ if (h->filtered)
+ return;
+
+ /* force fold unfiltered entry for simplicity */
+ h->ms.unfolded = false;
+ h->row_offset = 0;
+
+ hists->stats.nr_non_filtered_samples += h->stat.nr_events;
+
+ hists__inc_filter_stats(hists, h);
+ hists__calc_col_len(hists, h);
+}
+
+
+static bool hists__filter_entry_by_dso(struct hists *hists,
+ struct hist_entry *he)
+{
+ if (hists->dso_filter != NULL &&
+ (he->ms.map == NULL || he->ms.map->dso != hists->dso_filter)) {
+ he->filtered |= (1 << HIST_FILTER__DSO);
+ return true;
+ }
+
+ return false;
+}
+
+void hists__filter_by_dso(struct hists *hists)
+{
+ struct rb_node *nd;
+
+ hists->stats.nr_non_filtered_samples = 0;
+
+ hists__reset_filter_stats(hists);
+ hists__reset_col_len(hists);
+
+ for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+
+ if (symbol_conf.exclude_other && !h->parent)
+ continue;
+
+ if (hists__filter_entry_by_dso(hists, h))
+ continue;
+
+ hists__remove_entry_filter(hists, h, HIST_FILTER__DSO);
+ }
+}
+
+static bool hists__filter_entry_by_thread(struct hists *hists,
+ struct hist_entry *he)
+{
+ if (hists->thread_filter != NULL &&
+ he->thread != hists->thread_filter) {
+ he->filtered |= (1 << HIST_FILTER__THREAD);
+ return true;
+ }
+
+ return false;
+}
+
+void hists__filter_by_thread(struct hists *hists)
+{
+ struct rb_node *nd;
+
+ hists->stats.nr_non_filtered_samples = 0;
+
+ hists__reset_filter_stats(hists);
+ hists__reset_col_len(hists);
+
+ for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+
+ if (hists__filter_entry_by_thread(hists, h))
+ continue;
+
+ hists__remove_entry_filter(hists, h, HIST_FILTER__THREAD);
+ }
+}
+
+static bool hists__filter_entry_by_symbol(struct hists *hists,
+ struct hist_entry *he)
+{
+ if (hists->symbol_filter_str != NULL &&
+ (!he->ms.sym || strstr(he->ms.sym->name,
+ hists->symbol_filter_str) == NULL)) {
+ he->filtered |= (1 << HIST_FILTER__SYMBOL);
+ return true;
+ }
+
+ return false;
+}
+
+void hists__filter_by_symbol(struct hists *hists)
+{
+ struct rb_node *nd;
+
+ hists->stats.nr_non_filtered_samples = 0;
+
+ hists__reset_filter_stats(hists);
+ hists__reset_col_len(hists);
+
+ for (nd = rb_first(&hists->entries); nd; nd = rb_next(nd)) {
+ struct hist_entry *h = rb_entry(nd, struct hist_entry, rb_node);
+
+ if (hists__filter_entry_by_symbol(hists, h))
+ continue;
+
+ hists__remove_entry_filter(hists, h, HIST_FILTER__SYMBOL);
+ }
+}
+
+void events_stats__inc(struct events_stats *stats, u32 type)
+{
+ ++stats->nr_events[0];
+ ++stats->nr_events[type];
+}
+
+void hists__inc_nr_events(struct hists *hists, u32 type)
+{
+ events_stats__inc(&hists->stats, type);
+}
+
+void hists__inc_nr_samples(struct hists *hists, bool filtered)
+{
+ events_stats__inc(&hists->stats, PERF_RECORD_SAMPLE);
+ if (!filtered)
+ hists->stats.nr_non_filtered_samples++;
+}
+
+static struct hist_entry *hists__add_dummy_entry(struct hists *hists,
+ struct hist_entry *pair)
+{
+ struct rb_root *root;
+ struct rb_node **p;
+ struct rb_node *parent = NULL;
+ struct hist_entry *he;
+ int64_t cmp;
+
+ if (sort__need_collapse)
+ root = &hists->entries_collapsed;
+ else
+ root = hists->entries_in;
+
+ p = &root->rb_node;
+
+ while (*p != NULL) {
+ parent = *p;
+ he = rb_entry(parent, struct hist_entry, rb_node_in);
+
+ cmp = hist_entry__collapse(he, pair);
+
+ if (!cmp)
+ goto out;
+
+ if (cmp < 0)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ he = hist_entry__new(pair, true);
+ if (he) {
+ memset(&he->stat, 0, sizeof(he->stat));
+ he->hists = hists;
+ rb_link_node(&he->rb_node_in, parent, p);
+ rb_insert_color(&he->rb_node_in, root);
+ hists__inc_stats(hists, he);
+ he->dummy = true;
+ }
+out:
+ return he;
+}
+
+static struct hist_entry *hists__find_entry(struct hists *hists,
+ struct hist_entry *he)
+{
+ struct rb_node *n;
+
+ if (sort__need_collapse)
+ n = hists->entries_collapsed.rb_node;
+ else
+ n = hists->entries_in->rb_node;
+
+ while (n) {
+ struct hist_entry *iter = rb_entry(n, struct hist_entry, rb_node_in);
+ int64_t cmp = hist_entry__collapse(iter, he);
+
+ if (cmp < 0)
+ n = n->rb_left;
+ else if (cmp > 0)
+ n = n->rb_right;
+ else
+ return iter;
+ }
+
+ return NULL;
+}
+
+/*
+ * Look for pairs to link to the leader buckets (hist_entries):
+ */
+void hists__match(struct hists *leader, struct hists *other)
+{
+ struct rb_root *root;
+ struct rb_node *nd;
+ struct hist_entry *pos, *pair;
+
+ if (sort__need_collapse)
+ root = &leader->entries_collapsed;
+ else
+ root = leader->entries_in;
+
+ for (nd = rb_first(root); nd; nd = rb_next(nd)) {
+ pos = rb_entry(nd, struct hist_entry, rb_node_in);
+ pair = hists__find_entry(other, pos);
+
+ if (pair)
+ hist_entry__add_pair(pair, pos);
+ }
+}
+
+/*
+ * Look for entries in the other hists that are not present in the leader, if
+ * we find them, just add a dummy entry on the leader hists, with period=0,
+ * nr_events=0, to serve as the list header.
+ */
+int hists__link(struct hists *leader, struct hists *other)
+{
+ struct rb_root *root;
+ struct rb_node *nd;
+ struct hist_entry *pos, *pair;
+
+ if (sort__need_collapse)
+ root = &other->entries_collapsed;
+ else
+ root = other->entries_in;
+
+ for (nd = rb_first(root); nd; nd = rb_next(nd)) {
+ pos = rb_entry(nd, struct hist_entry, rb_node_in);
+
+ if (!hist_entry__has_pairs(pos)) {
+ pair = hists__add_dummy_entry(leader, pos);
+ if (pair == NULL)
+ return -1;
+ hist_entry__add_pair(pos, pair);
+ }
+ }
+
+ return 0;
+}
+
+u64 hists__total_period(struct hists *hists)
+{
+ return symbol_conf.filter_relative ? hists->stats.total_non_filtered_period :
+ hists->stats.total_period;
+}
+
+int parse_filter_percentage(const struct option *opt __maybe_unused,
+ const char *arg, int unset __maybe_unused)
+{
+ if (!strcmp(arg, "relative"))
+ symbol_conf.filter_relative = true;
+ else if (!strcmp(arg, "absolute"))
+ symbol_conf.filter_relative = false;
+ else
+ return -1;
+
+ return 0;
+}
+
+int perf_hist_config(const char *var, const char *value)
+{
+ if (!strcmp(var, "hist.percentage"))
+ return parse_filter_percentage(NULL, value, 0);
+
+ return 0;
+}
diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h
new file mode 100644
index 00000000000..742f49a8572
--- /dev/null
+++ b/tools/perf/util/hist.h
@@ -0,0 +1,347 @@
+#ifndef __PERF_HIST_H
+#define __PERF_HIST_H
+
+#include <linux/types.h>
+#include <pthread.h>
+#include "callchain.h"
+#include "header.h"
+#include "color.h"
+#include "ui/progress.h"
+
+extern struct callchain_param callchain_param;
+
+struct hist_entry;
+struct addr_location;
+struct symbol;
+
+enum hist_filter {
+ HIST_FILTER__DSO,
+ HIST_FILTER__THREAD,
+ HIST_FILTER__PARENT,
+ HIST_FILTER__SYMBOL,
+ HIST_FILTER__GUEST,
+ HIST_FILTER__HOST,
+};
+
+/*
+ * The kernel collects the number of events it couldn't send in a stretch and
+ * when possible sends this number in a PERF_RECORD_LOST event. The number of
+ * such "chunks" of lost events is stored in .nr_events[PERF_EVENT_LOST] while
+ * total_lost tells exactly how many events the kernel in fact lost, i.e. it is
+ * the sum of all struct lost_event.lost fields reported.
+ *
+ * The total_period is needed because by default auto-freq is used, so
+ * multipling nr_events[PERF_EVENT_SAMPLE] by a frequency isn't possible to get
+ * the total number of low level events, it is necessary to to sum all struct
+ * sample_event.period and stash the result in total_period.
+ */
+struct events_stats {
+ u64 total_period;
+ u64 total_non_filtered_period;
+ u64 total_lost;
+ u64 total_invalid_chains;
+ u32 nr_events[PERF_RECORD_HEADER_MAX];
+ u32 nr_non_filtered_samples;
+ u32 nr_lost_warned;
+ u32 nr_unknown_events;
+ u32 nr_invalid_chains;
+ u32 nr_unknown_id;
+ u32 nr_unprocessable_samples;
+};
+
+enum hist_column {
+ HISTC_SYMBOL,
+ HISTC_DSO,
+ HISTC_THREAD,
+ HISTC_COMM,
+ HISTC_PARENT,
+ HISTC_CPU,
+ HISTC_SRCLINE,
+ HISTC_MISPREDICT,
+ HISTC_IN_TX,
+ HISTC_ABORT,
+ HISTC_SYMBOL_FROM,
+ HISTC_SYMBOL_TO,
+ HISTC_DSO_FROM,
+ HISTC_DSO_TO,
+ HISTC_LOCAL_WEIGHT,
+ HISTC_GLOBAL_WEIGHT,
+ HISTC_MEM_DADDR_SYMBOL,
+ HISTC_MEM_DADDR_DSO,
+ HISTC_MEM_LOCKED,
+ HISTC_MEM_TLB,
+ HISTC_MEM_LVL,
+ HISTC_MEM_SNOOP,
+ HISTC_MEM_DCACHELINE,
+ HISTC_TRANSACTION,
+ HISTC_NR_COLS, /* Last entry */
+};
+
+struct thread;
+struct dso;
+
+struct hists {
+ struct rb_root entries_in_array[2];
+ struct rb_root *entries_in;
+ struct rb_root entries;
+ struct rb_root entries_collapsed;
+ u64 nr_entries;
+ u64 nr_non_filtered_entries;
+ const struct thread *thread_filter;
+ const struct dso *dso_filter;
+ const char *uid_filter_str;
+ const char *symbol_filter_str;
+ pthread_mutex_t lock;
+ struct events_stats stats;
+ u64 event_stream;
+ u16 col_len[HISTC_NR_COLS];
+};
+
+struct hist_entry_iter;
+
+struct hist_iter_ops {
+ int (*prepare_entry)(struct hist_entry_iter *, struct addr_location *);
+ int (*add_single_entry)(struct hist_entry_iter *, struct addr_location *);
+ int (*next_entry)(struct hist_entry_iter *, struct addr_location *);
+ int (*add_next_entry)(struct hist_entry_iter *, struct addr_location *);
+ int (*finish_entry)(struct hist_entry_iter *, struct addr_location *);
+};
+
+struct hist_entry_iter {
+ int total;
+ int curr;
+
+ bool hide_unresolved;
+
+ struct perf_evsel *evsel;
+ struct perf_sample *sample;
+ struct hist_entry *he;
+ struct symbol *parent;
+ void *priv;
+
+ const struct hist_iter_ops *ops;
+ /* user-defined callback function (optional) */
+ int (*add_entry_cb)(struct hist_entry_iter *iter,
+ struct addr_location *al, bool single, void *arg);
+};
+
+extern const struct hist_iter_ops hist_iter_normal;
+extern const struct hist_iter_ops hist_iter_branch;
+extern const struct hist_iter_ops hist_iter_mem;
+extern const struct hist_iter_ops hist_iter_cumulative;
+
+struct hist_entry *__hists__add_entry(struct hists *hists,
+ struct addr_location *al,
+ struct symbol *parent,
+ struct branch_info *bi,
+ struct mem_info *mi, u64 period,
+ u64 weight, u64 transaction,
+ bool sample_self);
+int hist_entry_iter__add(struct hist_entry_iter *iter, struct addr_location *al,
+ struct perf_evsel *evsel, struct perf_sample *sample,
+ int max_stack_depth, void *arg);
+
+int64_t hist_entry__cmp(struct hist_entry *left, struct hist_entry *right);
+int64_t hist_entry__collapse(struct hist_entry *left, struct hist_entry *right);
+int hist_entry__transaction_len(void);
+int hist_entry__sort_snprintf(struct hist_entry *he, char *bf, size_t size,
+ struct hists *hists);
+void hist_entry__free(struct hist_entry *);
+
+void hists__output_resort(struct hists *hists);
+void hists__collapse_resort(struct hists *hists, struct ui_progress *prog);
+
+void hists__decay_entries(struct hists *hists, bool zap_user, bool zap_kernel);
+void hists__output_recalc_col_len(struct hists *hists, int max_rows);
+
+u64 hists__total_period(struct hists *hists);
+void hists__reset_stats(struct hists *hists);
+void hists__inc_stats(struct hists *hists, struct hist_entry *h);
+void hists__inc_nr_events(struct hists *hists, u32 type);
+void hists__inc_nr_samples(struct hists *hists, bool filtered);
+void events_stats__inc(struct events_stats *stats, u32 type);
+size_t events_stats__fprintf(struct events_stats *stats, FILE *fp);
+
+size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows,
+ int max_cols, float min_pcnt, FILE *fp);
+
+void hists__filter_by_dso(struct hists *hists);
+void hists__filter_by_thread(struct hists *hists);
+void hists__filter_by_symbol(struct hists *hists);
+
+static inline bool hists__has_filter(struct hists *hists)
+{
+ return hists->thread_filter || hists->dso_filter ||
+ hists->symbol_filter_str;
+}
+
+u16 hists__col_len(struct hists *hists, enum hist_column col);
+void hists__set_col_len(struct hists *hists, enum hist_column col, u16 len);
+bool hists__new_col_len(struct hists *hists, enum hist_column col, u16 len);
+void hists__reset_col_len(struct hists *hists);
+void hists__calc_col_len(struct hists *hists, struct hist_entry *he);
+
+void hists__match(struct hists *leader, struct hists *other);
+int hists__link(struct hists *leader, struct hists *other);
+
+struct perf_hpp {
+ char *buf;
+ size_t size;
+ const char *sep;
+ void *ptr;
+};
+
+struct perf_hpp_fmt {
+ int (*header)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct perf_evsel *evsel);
+ int (*width)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct perf_evsel *evsel);
+ int (*color)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hist_entry *he);
+ int (*entry)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hist_entry *he);
+ int64_t (*cmp)(struct hist_entry *a, struct hist_entry *b);
+ int64_t (*collapse)(struct hist_entry *a, struct hist_entry *b);
+ int64_t (*sort)(struct hist_entry *a, struct hist_entry *b);
+
+ struct list_head list;
+ struct list_head sort_list;
+ bool elide;
+};
+
+extern struct list_head perf_hpp__list;
+extern struct list_head perf_hpp__sort_list;
+
+#define perf_hpp__for_each_format(format) \
+ list_for_each_entry(format, &perf_hpp__list, list)
+
+#define perf_hpp__for_each_format_safe(format, tmp) \
+ list_for_each_entry_safe(format, tmp, &perf_hpp__list, list)
+
+#define perf_hpp__for_each_sort_list(format) \
+ list_for_each_entry(format, &perf_hpp__sort_list, sort_list)
+
+#define perf_hpp__for_each_sort_list_safe(format, tmp) \
+ list_for_each_entry_safe(format, tmp, &perf_hpp__sort_list, sort_list)
+
+extern struct perf_hpp_fmt perf_hpp__format[];
+
+enum {
+ /* Matches perf_hpp__format array. */
+ PERF_HPP__OVERHEAD,
+ PERF_HPP__OVERHEAD_SYS,
+ PERF_HPP__OVERHEAD_US,
+ PERF_HPP__OVERHEAD_GUEST_SYS,
+ PERF_HPP__OVERHEAD_GUEST_US,
+ PERF_HPP__OVERHEAD_ACC,
+ PERF_HPP__SAMPLES,
+ PERF_HPP__PERIOD,
+
+ PERF_HPP__MAX_INDEX
+};
+
+void perf_hpp__init(void);
+void perf_hpp__column_register(struct perf_hpp_fmt *format);
+void perf_hpp__column_unregister(struct perf_hpp_fmt *format);
+void perf_hpp__column_enable(unsigned col);
+void perf_hpp__column_disable(unsigned col);
+void perf_hpp__cancel_cumulate(void);
+
+void perf_hpp__register_sort_field(struct perf_hpp_fmt *format);
+void perf_hpp__setup_output_field(void);
+void perf_hpp__reset_output_field(void);
+void perf_hpp__append_sort_keys(void);
+
+bool perf_hpp__is_sort_entry(struct perf_hpp_fmt *format);
+bool perf_hpp__same_sort_entry(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b);
+
+static inline bool perf_hpp__should_skip(struct perf_hpp_fmt *format)
+{
+ return format->elide;
+}
+
+void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists);
+
+typedef u64 (*hpp_field_fn)(struct hist_entry *he);
+typedef int (*hpp_callback_fn)(struct perf_hpp *hpp, bool front);
+typedef int (*hpp_snprint_fn)(struct perf_hpp *hpp, const char *fmt, ...);
+
+int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he,
+ hpp_field_fn get_field, const char *fmt,
+ hpp_snprint_fn print_fn, bool fmt_percent);
+int __hpp__fmt_acc(struct perf_hpp *hpp, struct hist_entry *he,
+ hpp_field_fn get_field, const char *fmt,
+ hpp_snprint_fn print_fn, bool fmt_percent);
+
+static inline void advance_hpp(struct perf_hpp *hpp, int inc)
+{
+ hpp->buf += inc;
+ hpp->size -= inc;
+}
+
+static inline size_t perf_hpp__use_color(void)
+{
+ return !symbol_conf.field_sep;
+}
+
+static inline size_t perf_hpp__color_overhead(void)
+{
+ return perf_hpp__use_color() ?
+ (COLOR_MAXLEN + sizeof(PERF_COLOR_RESET)) * PERF_HPP__MAX_INDEX
+ : 0;
+}
+
+struct perf_evlist;
+
+struct hist_browser_timer {
+ void (*timer)(void *arg);
+ void *arg;
+ int refresh;
+};
+
+#ifdef HAVE_SLANG_SUPPORT
+#include "../ui/keysyms.h"
+int hist_entry__tui_annotate(struct hist_entry *he, struct perf_evsel *evsel,
+ struct hist_browser_timer *hbt);
+
+int perf_evlist__tui_browse_hists(struct perf_evlist *evlist, const char *help,
+ struct hist_browser_timer *hbt,
+ float min_pcnt,
+ struct perf_session_env *env);
+int script_browse(const char *script_opt);
+#else
+static inline
+int perf_evlist__tui_browse_hists(struct perf_evlist *evlist __maybe_unused,
+ const char *help __maybe_unused,
+ struct hist_browser_timer *hbt __maybe_unused,
+ float min_pcnt __maybe_unused,
+ struct perf_session_env *env __maybe_unused)
+{
+ return 0;
+}
+
+static inline int hist_entry__tui_annotate(struct hist_entry *he __maybe_unused,
+ struct perf_evsel *evsel __maybe_unused,
+ struct hist_browser_timer *hbt __maybe_unused)
+{
+ return 0;
+}
+
+static inline int script_browse(const char *script_opt __maybe_unused)
+{
+ return 0;
+}
+
+#define K_LEFT -1000
+#define K_RIGHT -2000
+#define K_SWITCH_INPUT_DATA -3000
+#endif
+
+unsigned int hists__sort_list_width(struct hists *hists);
+
+struct option;
+int parse_filter_percentage(const struct option *opt __maybe_unused,
+ const char *arg, int unset __maybe_unused);
+int perf_hist_config(const char *var, const char *value);
+
+#endif /* __PERF_HIST_H */
diff --git a/tools/perf/util/hweight.c b/tools/perf/util/hweight.c
new file mode 100644
index 00000000000..5c1d0d099f0
--- /dev/null
+++ b/tools/perf/util/hweight.c
@@ -0,0 +1,31 @@
+#include <linux/bitops.h>
+
+/**
+ * hweightN - returns the hamming weight of a N-bit word
+ * @x: the word to weigh
+ *
+ * The Hamming Weight of a number is the total number of bits set in it.
+ */
+
+unsigned int hweight32(unsigned int w)
+{
+ unsigned int res = w - ((w >> 1) & 0x55555555);
+ res = (res & 0x33333333) + ((res >> 2) & 0x33333333);
+ res = (res + (res >> 4)) & 0x0F0F0F0F;
+ res = res + (res >> 8);
+ return (res + (res >> 16)) & 0x000000FF;
+}
+
+unsigned long hweight64(__u64 w)
+{
+#if BITS_PER_LONG == 32
+ return hweight32((unsigned int)(w >> 32)) + hweight32((unsigned int)w);
+#elif BITS_PER_LONG == 64
+ __u64 res = w - ((w >> 1) & 0x5555555555555555ul);
+ res = (res & 0x3333333333333333ul) + ((res >> 2) & 0x3333333333333333ul);
+ res = (res + (res >> 4)) & 0x0F0F0F0F0F0F0F0Ful;
+ res = res + (res >> 8);
+ res = res + (res >> 16);
+ return (res + (res >> 32)) & 0x00000000000000FFul;
+#endif
+}
diff --git a/tools/perf/util/include/asm/alternative-asm.h b/tools/perf/util/include/asm/alternative-asm.h
new file mode 100644
index 00000000000..6789d788d49
--- /dev/null
+++ b/tools/perf/util/include/asm/alternative-asm.h
@@ -0,0 +1,8 @@
+#ifndef _PERF_ASM_ALTERNATIVE_ASM_H
+#define _PERF_ASM_ALTERNATIVE_ASM_H
+
+/* Just disable it so we can build arch/x86/lib/memcpy_64.S for perf bench: */
+
+#define altinstruction_entry #
+
+#endif
diff --git a/tools/perf/util/include/asm/asm-offsets.h b/tools/perf/util/include/asm/asm-offsets.h
new file mode 100644
index 00000000000..ed538942523
--- /dev/null
+++ b/tools/perf/util/include/asm/asm-offsets.h
@@ -0,0 +1 @@
+/* stub */
diff --git a/tools/perf/util/include/asm/byteorder.h b/tools/perf/util/include/asm/byteorder.h
new file mode 100644
index 00000000000..2a9bdc06630
--- /dev/null
+++ b/tools/perf/util/include/asm/byteorder.h
@@ -0,0 +1,2 @@
+#include <asm/types.h>
+#include "../../../../include/uapi/linux/swab.h"
diff --git a/tools/perf/util/include/asm/cpufeature.h b/tools/perf/util/include/asm/cpufeature.h
new file mode 100644
index 00000000000..acffd5e4d1d
--- /dev/null
+++ b/tools/perf/util/include/asm/cpufeature.h
@@ -0,0 +1,9 @@
+
+#ifndef PERF_CPUFEATURE_H
+#define PERF_CPUFEATURE_H
+
+/* cpufeature.h ... dummy header file for including arch/x86/lib/memcpy_64.S */
+
+#define X86_FEATURE_REP_GOOD 0
+
+#endif /* PERF_CPUFEATURE_H */
diff --git a/tools/perf/util/include/asm/dwarf2.h b/tools/perf/util/include/asm/dwarf2.h
new file mode 100644
index 00000000000..afe38199e92
--- /dev/null
+++ b/tools/perf/util/include/asm/dwarf2.h
@@ -0,0 +1,13 @@
+
+#ifndef PERF_DWARF2_H
+#define PERF_DWARF2_H
+
+/* dwarf2.h ... dummy header file for including arch/x86/lib/mem{cpy,set}_64.S */
+
+#define CFI_STARTPROC
+#define CFI_ENDPROC
+#define CFI_REMEMBER_STATE
+#define CFI_RESTORE_STATE
+
+#endif /* PERF_DWARF2_H */
+
diff --git a/tools/perf/util/include/asm/hash.h b/tools/perf/util/include/asm/hash.h
new file mode 100644
index 00000000000..d82b170bb21
--- /dev/null
+++ b/tools/perf/util/include/asm/hash.h
@@ -0,0 +1,6 @@
+#ifndef __ASM_GENERIC_HASH_H
+#define __ASM_GENERIC_HASH_H
+
+/* Stub */
+
+#endif /* __ASM_GENERIC_HASH_H */
diff --git a/tools/perf/util/include/asm/hweight.h b/tools/perf/util/include/asm/hweight.h
new file mode 100644
index 00000000000..36cf26d434a
--- /dev/null
+++ b/tools/perf/util/include/asm/hweight.h
@@ -0,0 +1,8 @@
+#ifndef PERF_HWEIGHT_H
+#define PERF_HWEIGHT_H
+
+#include <linux/types.h>
+unsigned int hweight32(unsigned int w);
+unsigned long hweight64(__u64 w);
+
+#endif /* PERF_HWEIGHT_H */
diff --git a/tools/perf/util/include/asm/swab.h b/tools/perf/util/include/asm/swab.h
new file mode 100644
index 00000000000..ed538942523
--- /dev/null
+++ b/tools/perf/util/include/asm/swab.h
@@ -0,0 +1 @@
+/* stub */
diff --git a/tools/perf/util/include/asm/system.h b/tools/perf/util/include/asm/system.h
new file mode 100644
index 00000000000..710cecca972
--- /dev/null
+++ b/tools/perf/util/include/asm/system.h
@@ -0,0 +1 @@
+/* Empty */
diff --git a/tools/perf/util/include/asm/uaccess.h b/tools/perf/util/include/asm/uaccess.h
new file mode 100644
index 00000000000..d0f72b8fcc3
--- /dev/null
+++ b/tools/perf/util/include/asm/uaccess.h
@@ -0,0 +1,14 @@
+#ifndef _PERF_ASM_UACCESS_H_
+#define _PERF_ASM_UACCESS_H_
+
+#define __get_user(src, dest) \
+({ \
+ (src) = *dest; \
+ 0; \
+})
+
+#define get_user __get_user
+
+#define access_ok(type, addr, size) 1
+
+#endif
diff --git a/tools/perf/util/include/asm/unistd_32.h b/tools/perf/util/include/asm/unistd_32.h
new file mode 100644
index 00000000000..8b137891791
--- /dev/null
+++ b/tools/perf/util/include/asm/unistd_32.h
@@ -0,0 +1 @@
+
diff --git a/tools/perf/util/include/asm/unistd_64.h b/tools/perf/util/include/asm/unistd_64.h
new file mode 100644
index 00000000000..8b137891791
--- /dev/null
+++ b/tools/perf/util/include/asm/unistd_64.h
@@ -0,0 +1 @@
+
diff --git a/tools/perf/util/include/dwarf-regs.h b/tools/perf/util/include/dwarf-regs.h
new file mode 100644
index 00000000000..8f149655f49
--- /dev/null
+++ b/tools/perf/util/include/dwarf-regs.h
@@ -0,0 +1,8 @@
+#ifndef _PERF_DWARF_REGS_H_
+#define _PERF_DWARF_REGS_H_
+
+#ifdef HAVE_DWARF_SUPPORT
+const char *get_arch_regstr(unsigned int n);
+#endif
+
+#endif
diff --git a/tools/perf/util/include/linux/bitmap.h b/tools/perf/util/include/linux/bitmap.h
new file mode 100644
index 00000000000..01ffd12dc79
--- /dev/null
+++ b/tools/perf/util/include/linux/bitmap.h
@@ -0,0 +1,49 @@
+#ifndef _PERF_BITOPS_H
+#define _PERF_BITOPS_H
+
+#include <string.h>
+#include <linux/bitops.h>
+
+#define DECLARE_BITMAP(name,bits) \
+ unsigned long name[BITS_TO_LONGS(bits)]
+
+int __bitmap_weight(const unsigned long *bitmap, int bits);
+void __bitmap_or(unsigned long *dst, const unsigned long *bitmap1,
+ const unsigned long *bitmap2, int bits);
+
+#define BITMAP_LAST_WORD_MASK(nbits) \
+( \
+ ((nbits) % BITS_PER_LONG) ? \
+ (1UL<<((nbits) % BITS_PER_LONG))-1 : ~0UL \
+)
+
+#define small_const_nbits(nbits) \
+ (__builtin_constant_p(nbits) && (nbits) <= BITS_PER_LONG)
+
+static inline void bitmap_zero(unsigned long *dst, int nbits)
+{
+ if (small_const_nbits(nbits))
+ *dst = 0UL;
+ else {
+ int len = BITS_TO_LONGS(nbits) * sizeof(unsigned long);
+ memset(dst, 0, len);
+ }
+}
+
+static inline int bitmap_weight(const unsigned long *src, int nbits)
+{
+ if (small_const_nbits(nbits))
+ return hweight_long(*src & BITMAP_LAST_WORD_MASK(nbits));
+ return __bitmap_weight(src, nbits);
+}
+
+static inline void bitmap_or(unsigned long *dst, const unsigned long *src1,
+ const unsigned long *src2, int nbits)
+{
+ if (small_const_nbits(nbits))
+ *dst = *src1 | *src2;
+ else
+ __bitmap_or(dst, src1, src2, nbits);
+}
+
+#endif /* _PERF_BITOPS_H */
diff --git a/tools/perf/util/include/linux/bitops.h b/tools/perf/util/include/linux/bitops.h
new file mode 100644
index 00000000000..dadfa7e5428
--- /dev/null
+++ b/tools/perf/util/include/linux/bitops.h
@@ -0,0 +1,160 @@
+#ifndef _PERF_LINUX_BITOPS_H_
+#define _PERF_LINUX_BITOPS_H_
+
+#include <linux/kernel.h>
+#include <linux/compiler.h>
+#include <asm/hweight.h>
+
+#ifndef __WORDSIZE
+#define __WORDSIZE (__SIZEOF_LONG__ * 8)
+#endif
+
+#define BITS_PER_LONG __WORDSIZE
+#define BITS_PER_BYTE 8
+#define BITS_TO_LONGS(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(long))
+#define BITS_TO_U64(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(u64))
+#define BITS_TO_U32(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE * sizeof(u32))
+#define BITS_TO_BYTES(nr) DIV_ROUND_UP(nr, BITS_PER_BYTE)
+
+#define for_each_set_bit(bit, addr, size) \
+ for ((bit) = find_first_bit((addr), (size)); \
+ (bit) < (size); \
+ (bit) = find_next_bit((addr), (size), (bit) + 1))
+
+/* same as for_each_set_bit() but use bit as value to start with */
+#define for_each_set_bit_from(bit, addr, size) \
+ for ((bit) = find_next_bit((addr), (size), (bit)); \
+ (bit) < (size); \
+ (bit) = find_next_bit((addr), (size), (bit) + 1))
+
+static inline void set_bit(int nr, unsigned long *addr)
+{
+ addr[nr / BITS_PER_LONG] |= 1UL << (nr % BITS_PER_LONG);
+}
+
+static inline void clear_bit(int nr, unsigned long *addr)
+{
+ addr[nr / BITS_PER_LONG] &= ~(1UL << (nr % BITS_PER_LONG));
+}
+
+static __always_inline int test_bit(unsigned int nr, const unsigned long *addr)
+{
+ return ((1UL << (nr % BITS_PER_LONG)) &
+ (((unsigned long *)addr)[nr / BITS_PER_LONG])) != 0;
+}
+
+static inline unsigned long hweight_long(unsigned long w)
+{
+ return sizeof(w) == 4 ? hweight32(w) : hweight64(w);
+}
+
+#define BITOP_WORD(nr) ((nr) / BITS_PER_LONG)
+
+/**
+ * __ffs - find first bit in word.
+ * @word: The word to search
+ *
+ * Undefined if no bit exists, so code should check against 0 first.
+ */
+static __always_inline unsigned long __ffs(unsigned long word)
+{
+ int num = 0;
+
+#if BITS_PER_LONG == 64
+ if ((word & 0xffffffff) == 0) {
+ num += 32;
+ word >>= 32;
+ }
+#endif
+ if ((word & 0xffff) == 0) {
+ num += 16;
+ word >>= 16;
+ }
+ if ((word & 0xff) == 0) {
+ num += 8;
+ word >>= 8;
+ }
+ if ((word & 0xf) == 0) {
+ num += 4;
+ word >>= 4;
+ }
+ if ((word & 0x3) == 0) {
+ num += 2;
+ word >>= 2;
+ }
+ if ((word & 0x1) == 0)
+ num += 1;
+ return num;
+}
+
+typedef const unsigned long __attribute__((__may_alias__)) long_alias_t;
+
+/*
+ * Find the first set bit in a memory region.
+ */
+static inline unsigned long
+find_first_bit(const unsigned long *addr, unsigned long size)
+{
+ long_alias_t *p = (long_alias_t *) addr;
+ unsigned long result = 0;
+ unsigned long tmp;
+
+ while (size & ~(BITS_PER_LONG-1)) {
+ if ((tmp = *(p++)))
+ goto found;
+ result += BITS_PER_LONG;
+ size -= BITS_PER_LONG;
+ }
+ if (!size)
+ return result;
+
+ tmp = (*p) & (~0UL >> (BITS_PER_LONG - size));
+ if (tmp == 0UL) /* Are any bits set? */
+ return result + size; /* Nope. */
+found:
+ return result + __ffs(tmp);
+}
+
+/*
+ * Find the next set bit in a memory region.
+ */
+static inline unsigned long
+find_next_bit(const unsigned long *addr, unsigned long size, unsigned long offset)
+{
+ const unsigned long *p = addr + BITOP_WORD(offset);
+ unsigned long result = offset & ~(BITS_PER_LONG-1);
+ unsigned long tmp;
+
+ if (offset >= size)
+ return size;
+ size -= result;
+ offset %= BITS_PER_LONG;
+ if (offset) {
+ tmp = *(p++);
+ tmp &= (~0UL << offset);
+ if (size < BITS_PER_LONG)
+ goto found_first;
+ if (tmp)
+ goto found_middle;
+ size -= BITS_PER_LONG;
+ result += BITS_PER_LONG;
+ }
+ while (size & ~(BITS_PER_LONG-1)) {
+ if ((tmp = *(p++)))
+ goto found_middle;
+ result += BITS_PER_LONG;
+ size -= BITS_PER_LONG;
+ }
+ if (!size)
+ return result;
+ tmp = *p;
+
+found_first:
+ tmp &= (~0UL >> (BITS_PER_LONG - size));
+ if (tmp == 0UL) /* Are any bits set? */
+ return result + size; /* Nope. */
+found_middle:
+ return result + __ffs(tmp);
+}
+
+#endif
diff --git a/tools/perf/util/include/linux/const.h b/tools/perf/util/include/linux/const.h
new file mode 100644
index 00000000000..c10a35e1afb
--- /dev/null
+++ b/tools/perf/util/include/linux/const.h
@@ -0,0 +1 @@
+#include "../../../../include/uapi/linux/const.h"
diff --git a/tools/perf/util/include/linux/ctype.h b/tools/perf/util/include/linux/ctype.h
new file mode 100644
index 00000000000..a53d4ee1e0b
--- /dev/null
+++ b/tools/perf/util/include/linux/ctype.h
@@ -0,0 +1 @@
+#include "../util.h"
diff --git a/tools/perf/util/include/linux/kernel.h b/tools/perf/util/include/linux/kernel.h
new file mode 100644
index 00000000000..9844c31b7c2
--- /dev/null
+++ b/tools/perf/util/include/linux/kernel.h
@@ -0,0 +1,128 @@
+#ifndef PERF_LINUX_KERNEL_H_
+#define PERF_LINUX_KERNEL_H_
+
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+
+#define DIV_ROUND_UP(n,d) (((n) + (d) - 1) / (d))
+
+#define PERF_ALIGN(x, a) __PERF_ALIGN_MASK(x, (typeof(x))(a)-1)
+#define __PERF_ALIGN_MASK(x, mask) (((x)+(mask))&~(mask))
+
+#ifndef offsetof
+#define offsetof(TYPE, MEMBER) ((size_t) &((TYPE *)0)->MEMBER)
+#endif
+
+#ifndef container_of
+/**
+ * container_of - cast a member of a structure out to the containing structure
+ * @ptr: the pointer to the member.
+ * @type: the type of the container struct this is embedded in.
+ * @member: the name of the member within the struct.
+ *
+ */
+#define container_of(ptr, type, member) ({ \
+ const typeof(((type *)0)->member) * __mptr = (ptr); \
+ (type *)((char *)__mptr - offsetof(type, member)); })
+#endif
+
+#define BUILD_BUG_ON_ZERO(e) (sizeof(struct { int:-!!(e); }))
+
+#ifndef max
+#define max(x, y) ({ \
+ typeof(x) _max1 = (x); \
+ typeof(y) _max2 = (y); \
+ (void) (&_max1 == &_max2); \
+ _max1 > _max2 ? _max1 : _max2; })
+#endif
+
+#ifndef min
+#define min(x, y) ({ \
+ typeof(x) _min1 = (x); \
+ typeof(y) _min2 = (y); \
+ (void) (&_min1 == &_min2); \
+ _min1 < _min2 ? _min1 : _min2; })
+#endif
+
+#ifndef roundup
+#define roundup(x, y) ( \
+{ \
+ const typeof(y) __y = y; \
+ (((x) + (__y - 1)) / __y) * __y; \
+} \
+)
+#endif
+
+#ifndef BUG_ON
+#ifdef NDEBUG
+#define BUG_ON(cond) do { if (cond) {} } while (0)
+#else
+#define BUG_ON(cond) assert(!(cond))
+#endif
+#endif
+
+/*
+ * Both need more care to handle endianness
+ * (Don't use bitmap_copy_le() for now)
+ */
+#define cpu_to_le64(x) (x)
+#define cpu_to_le32(x) (x)
+
+static inline int
+vscnprintf(char *buf, size_t size, const char *fmt, va_list args)
+{
+ int i;
+ ssize_t ssize = size;
+
+ i = vsnprintf(buf, size, fmt, args);
+
+ return (i >= ssize) ? (ssize - 1) : i;
+}
+
+static inline int scnprintf(char * buf, size_t size, const char * fmt, ...)
+{
+ va_list args;
+ ssize_t ssize = size;
+ int i;
+
+ va_start(args, fmt);
+ i = vsnprintf(buf, size, fmt, args);
+ va_end(args);
+
+ return (i >= ssize) ? (ssize - 1) : i;
+}
+
+int eprintf(int level,
+ const char *fmt, ...) __attribute__((format(printf, 2, 3)));
+
+#ifndef pr_fmt
+#define pr_fmt(fmt) fmt
+#endif
+
+#define pr_err(fmt, ...) \
+ eprintf(0, pr_fmt(fmt), ##__VA_ARGS__)
+#define pr_warning(fmt, ...) \
+ eprintf(0, pr_fmt(fmt), ##__VA_ARGS__)
+#define pr_info(fmt, ...) \
+ eprintf(0, pr_fmt(fmt), ##__VA_ARGS__)
+#define pr_debug(fmt, ...) \
+ eprintf(1, pr_fmt(fmt), ##__VA_ARGS__)
+#define pr_debugN(n, fmt, ...) \
+ eprintf(n, pr_fmt(fmt), ##__VA_ARGS__)
+#define pr_debug2(fmt, ...) pr_debugN(2, pr_fmt(fmt), ##__VA_ARGS__)
+#define pr_debug3(fmt, ...) pr_debugN(3, pr_fmt(fmt), ##__VA_ARGS__)
+#define pr_debug4(fmt, ...) pr_debugN(4, pr_fmt(fmt), ##__VA_ARGS__)
+
+/*
+ * This looks more complex than it should be. But we need to
+ * get the type for the ~ right in round_down (it needs to be
+ * as wide as the result!), and we want to evaluate the macro
+ * arguments just once each.
+ */
+#define __round_mask(x, y) ((__typeof__(x))((y)-1))
+#define round_up(x, y) ((((x)-1) | __round_mask(x, y))+1)
+#define round_down(x, y) ((x) & ~__round_mask(x, y))
+
+#endif
diff --git a/tools/perf/util/include/linux/linkage.h b/tools/perf/util/include/linux/linkage.h
new file mode 100644
index 00000000000..06387cffe12
--- /dev/null
+++ b/tools/perf/util/include/linux/linkage.h
@@ -0,0 +1,13 @@
+
+#ifndef PERF_LINUX_LINKAGE_H_
+#define PERF_LINUX_LINKAGE_H_
+
+/* linkage.h ... for including arch/x86/lib/memcpy_64.S */
+
+#define ENTRY(name) \
+ .globl name; \
+ name:
+
+#define ENDPROC(name)
+
+#endif /* PERF_LINUX_LINKAGE_H_ */
diff --git a/tools/perf/util/include/linux/list.h b/tools/perf/util/include/linux/list.h
new file mode 100644
index 00000000000..76ddbc72634
--- /dev/null
+++ b/tools/perf/util/include/linux/list.h
@@ -0,0 +1,29 @@
+#include <linux/kernel.h>
+#include <linux/types.h>
+
+#include "../../../../include/linux/list.h"
+
+#ifndef PERF_LIST_H
+#define PERF_LIST_H
+/**
+ * list_del_range - deletes range of entries from list.
+ * @begin: first element in the range to delete from the list.
+ * @end: last element in the range to delete from the list.
+ * Note: list_empty on the range of entries does not return true after this,
+ * the entries is in an undefined state.
+ */
+static inline void list_del_range(struct list_head *begin,
+ struct list_head *end)
+{
+ begin->prev->next = end->next;
+ end->next->prev = begin->prev;
+}
+
+/**
+ * list_for_each_from - iterate over a list from one of its nodes
+ * @pos: the &struct list_head to use as a loop cursor, from where to start
+ * @head: the head for your list.
+ */
+#define list_for_each_from(pos, head) \
+ for (; pos != (head); pos = pos->next)
+#endif
diff --git a/tools/perf/util/include/linux/poison.h b/tools/perf/util/include/linux/poison.h
new file mode 100644
index 00000000000..fef6dbc9ce1
--- /dev/null
+++ b/tools/perf/util/include/linux/poison.h
@@ -0,0 +1 @@
+#include "../../../../include/linux/poison.h"
diff --git a/tools/perf/util/include/linux/rbtree.h b/tools/perf/util/include/linux/rbtree.h
new file mode 100644
index 00000000000..2a030c5af3a
--- /dev/null
+++ b/tools/perf/util/include/linux/rbtree.h
@@ -0,0 +1,2 @@
+#include <stdbool.h>
+#include "../../../../include/linux/rbtree.h"
diff --git a/tools/perf/util/include/linux/rbtree_augmented.h b/tools/perf/util/include/linux/rbtree_augmented.h
new file mode 100644
index 00000000000..9d6fcdf1788
--- /dev/null
+++ b/tools/perf/util/include/linux/rbtree_augmented.h
@@ -0,0 +1,2 @@
+#include <stdbool.h>
+#include "../../../../include/linux/rbtree_augmented.h"
diff --git a/tools/perf/util/include/linux/string.h b/tools/perf/util/include/linux/string.h
new file mode 100644
index 00000000000..97a80073822
--- /dev/null
+++ b/tools/perf/util/include/linux/string.h
@@ -0,0 +1,4 @@
+#include <string.h>
+
+void *memdup(const void *src, size_t len);
+int str_append(char **s, int *len, const char *a);
diff --git a/tools/perf/util/intlist.c b/tools/perf/util/intlist.c
new file mode 100644
index 00000000000..89715b64a31
--- /dev/null
+++ b/tools/perf/util/intlist.c
@@ -0,0 +1,146 @@
+/*
+ * Based on intlist.c by:
+ * (c) 2009 Arnaldo Carvalho de Melo <acme@redhat.com>
+ *
+ * Licensed under the GPLv2.
+ */
+
+#include <errno.h>
+#include <stdlib.h>
+#include <linux/compiler.h>
+
+#include "intlist.h"
+
+static struct rb_node *intlist__node_new(struct rblist *rblist __maybe_unused,
+ const void *entry)
+{
+ int i = (int)((long)entry);
+ struct rb_node *rc = NULL;
+ struct int_node *node = malloc(sizeof(*node));
+
+ if (node != NULL) {
+ node->i = i;
+ node->priv = NULL;
+ rc = &node->rb_node;
+ }
+
+ return rc;
+}
+
+static void int_node__delete(struct int_node *ilist)
+{
+ free(ilist);
+}
+
+static void intlist__node_delete(struct rblist *rblist __maybe_unused,
+ struct rb_node *rb_node)
+{
+ struct int_node *node = container_of(rb_node, struct int_node, rb_node);
+
+ int_node__delete(node);
+}
+
+static int intlist__node_cmp(struct rb_node *rb_node, const void *entry)
+{
+ int i = (int)((long)entry);
+ struct int_node *node = container_of(rb_node, struct int_node, rb_node);
+
+ return node->i - i;
+}
+
+int intlist__add(struct intlist *ilist, int i)
+{
+ return rblist__add_node(&ilist->rblist, (void *)((long)i));
+}
+
+void intlist__remove(struct intlist *ilist, struct int_node *node)
+{
+ rblist__remove_node(&ilist->rblist, &node->rb_node);
+}
+
+static struct int_node *__intlist__findnew(struct intlist *ilist,
+ int i, bool create)
+{
+ struct int_node *node = NULL;
+ struct rb_node *rb_node;
+
+ if (ilist == NULL)
+ return NULL;
+
+ if (create)
+ rb_node = rblist__findnew(&ilist->rblist, (void *)((long)i));
+ else
+ rb_node = rblist__find(&ilist->rblist, (void *)((long)i));
+
+ if (rb_node)
+ node = container_of(rb_node, struct int_node, rb_node);
+
+ return node;
+}
+
+struct int_node *intlist__find(struct intlist *ilist, int i)
+{
+ return __intlist__findnew(ilist, i, false);
+}
+
+struct int_node *intlist__findnew(struct intlist *ilist, int i)
+{
+ return __intlist__findnew(ilist, i, true);
+}
+
+static int intlist__parse_list(struct intlist *ilist, const char *s)
+{
+ char *sep;
+ int err;
+
+ do {
+ long value = strtol(s, &sep, 10);
+ err = -EINVAL;
+ if (*sep != ',' && *sep != '\0')
+ break;
+ err = intlist__add(ilist, value);
+ if (err)
+ break;
+ s = sep + 1;
+ } while (*sep != '\0');
+
+ return err;
+}
+
+struct intlist *intlist__new(const char *slist)
+{
+ struct intlist *ilist = malloc(sizeof(*ilist));
+
+ if (ilist != NULL) {
+ rblist__init(&ilist->rblist);
+ ilist->rblist.node_cmp = intlist__node_cmp;
+ ilist->rblist.node_new = intlist__node_new;
+ ilist->rblist.node_delete = intlist__node_delete;
+
+ if (slist && intlist__parse_list(ilist, slist))
+ goto out_delete;
+ }
+
+ return ilist;
+out_delete:
+ intlist__delete(ilist);
+ return NULL;
+}
+
+void intlist__delete(struct intlist *ilist)
+{
+ if (ilist != NULL)
+ rblist__delete(&ilist->rblist);
+}
+
+struct int_node *intlist__entry(const struct intlist *ilist, unsigned int idx)
+{
+ struct int_node *node = NULL;
+ struct rb_node *rb_node;
+
+ rb_node = rblist__entry(&ilist->rblist, idx);
+ if (rb_node)
+ node = container_of(rb_node, struct int_node, rb_node);
+
+ return node;
+}
diff --git a/tools/perf/util/intlist.h b/tools/perf/util/intlist.h
new file mode 100644
index 00000000000..aa6877d3685
--- /dev/null
+++ b/tools/perf/util/intlist.h
@@ -0,0 +1,77 @@
+#ifndef __PERF_INTLIST_H
+#define __PERF_INTLIST_H
+
+#include <linux/rbtree.h>
+#include <stdbool.h>
+
+#include "rblist.h"
+
+struct int_node {
+ struct rb_node rb_node;
+ int i;
+ void *priv;
+};
+
+struct intlist {
+ struct rblist rblist;
+};
+
+struct intlist *intlist__new(const char *slist);
+void intlist__delete(struct intlist *ilist);
+
+void intlist__remove(struct intlist *ilist, struct int_node *in);
+int intlist__add(struct intlist *ilist, int i);
+
+struct int_node *intlist__entry(const struct intlist *ilist, unsigned int idx);
+struct int_node *intlist__find(struct intlist *ilist, int i);
+struct int_node *intlist__findnew(struct intlist *ilist, int i);
+
+static inline bool intlist__has_entry(struct intlist *ilist, int i)
+{
+ return intlist__find(ilist, i) != NULL;
+}
+
+static inline bool intlist__empty(const struct intlist *ilist)
+{
+ return rblist__empty(&ilist->rblist);
+}
+
+static inline unsigned int intlist__nr_entries(const struct intlist *ilist)
+{
+ return rblist__nr_entries(&ilist->rblist);
+}
+
+/* For intlist iteration */
+static inline struct int_node *intlist__first(struct intlist *ilist)
+{
+ struct rb_node *rn = rb_first(&ilist->rblist.entries);
+ return rn ? rb_entry(rn, struct int_node, rb_node) : NULL;
+}
+static inline struct int_node *intlist__next(struct int_node *in)
+{
+ struct rb_node *rn;
+ if (!in)
+ return NULL;
+ rn = rb_next(&in->rb_node);
+ return rn ? rb_entry(rn, struct int_node, rb_node) : NULL;
+}
+
+/**
+ * intlist_for_each - iterate over a intlist
+ * @pos: the &struct int_node to use as a loop cursor.
+ * @ilist: the &struct intlist for loop.
+ */
+#define intlist__for_each(pos, ilist) \
+ for (pos = intlist__first(ilist); pos; pos = intlist__next(pos))
+
+/**
+ * intlist_for_each_safe - iterate over a intlist safe against removal of
+ * int_node
+ * @pos: the &struct int_node to use as a loop cursor.
+ * @n: another &struct int_node to use as temporary storage.
+ * @ilist: the &struct intlist for loop.
+ */
+#define intlist__for_each_safe(pos, n, ilist) \
+ for (pos = intlist__first(ilist), n = intlist__next(pos); pos;\
+ pos = n, n = intlist__next(n))
+#endif /* __PERF_INTLIST_H */
diff --git a/tools/perf/util/levenshtein.c b/tools/perf/util/levenshtein.c
new file mode 100644
index 00000000000..e521d1516df
--- /dev/null
+++ b/tools/perf/util/levenshtein.c
@@ -0,0 +1,84 @@
+#include "cache.h"
+#include "levenshtein.h"
+
+/*
+ * This function implements the Damerau-Levenshtein algorithm to
+ * calculate a distance between strings.
+ *
+ * Basically, it says how many letters need to be swapped, substituted,
+ * deleted from, or added to string1, at least, to get string2.
+ *
+ * The idea is to build a distance matrix for the substrings of both
+ * strings. To avoid a large space complexity, only the last three rows
+ * are kept in memory (if swaps had the same or higher cost as one deletion
+ * plus one insertion, only two rows would be needed).
+ *
+ * At any stage, "i + 1" denotes the length of the current substring of
+ * string1 that the distance is calculated for.
+ *
+ * row2 holds the current row, row1 the previous row (i.e. for the substring
+ * of string1 of length "i"), and row0 the row before that.
+ *
+ * In other words, at the start of the big loop, row2[j + 1] contains the
+ * Damerau-Levenshtein distance between the substring of string1 of length
+ * "i" and the substring of string2 of length "j + 1".
+ *
+ * All the big loop does is determine the partial minimum-cost paths.
+ *
+ * It does so by calculating the costs of the path ending in characters
+ * i (in string1) and j (in string2), respectively, given that the last
+ * operation is a substition, a swap, a deletion, or an insertion.
+ *
+ * This implementation allows the costs to be weighted:
+ *
+ * - w (as in "sWap")
+ * - s (as in "Substitution")
+ * - a (for insertion, AKA "Add")
+ * - d (as in "Deletion")
+ *
+ * Note that this algorithm calculates a distance _iff_ d == a.
+ */
+int levenshtein(const char *string1, const char *string2,
+ int w, int s, int a, int d)
+{
+ int len1 = strlen(string1), len2 = strlen(string2);
+ int *row0 = malloc(sizeof(int) * (len2 + 1));
+ int *row1 = malloc(sizeof(int) * (len2 + 1));
+ int *row2 = malloc(sizeof(int) * (len2 + 1));
+ int i, j;
+
+ for (j = 0; j <= len2; j++)
+ row1[j] = j * a;
+ for (i = 0; i < len1; i++) {
+ int *dummy;
+
+ row2[0] = (i + 1) * d;
+ for (j = 0; j < len2; j++) {
+ /* substitution */
+ row2[j + 1] = row1[j] + s * (string1[i] != string2[j]);
+ /* swap */
+ if (i > 0 && j > 0 && string1[i - 1] == string2[j] &&
+ string1[i] == string2[j - 1] &&
+ row2[j + 1] > row0[j - 1] + w)
+ row2[j + 1] = row0[j - 1] + w;
+ /* deletion */
+ if (row2[j + 1] > row1[j + 1] + d)
+ row2[j + 1] = row1[j + 1] + d;
+ /* insertion */
+ if (row2[j + 1] > row2[j] + a)
+ row2[j + 1] = row2[j] + a;
+ }
+
+ dummy = row0;
+ row0 = row1;
+ row1 = row2;
+ row2 = dummy;
+ }
+
+ i = row1[len2];
+ free(row0);
+ free(row1);
+ free(row2);
+
+ return i;
+}
diff --git a/tools/perf/util/levenshtein.h b/tools/perf/util/levenshtein.h
new file mode 100644
index 00000000000..b0fcb6d8a88
--- /dev/null
+++ b/tools/perf/util/levenshtein.h
@@ -0,0 +1,8 @@
+#ifndef __PERF_LEVENSHTEIN_H
+#define __PERF_LEVENSHTEIN_H
+
+int levenshtein(const char *string1, const char *string2,
+ int swap_penalty, int substition_penalty,
+ int insertion_penalty, int deletion_penalty);
+
+#endif /* __PERF_LEVENSHTEIN_H */
diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c
new file mode 100644
index 00000000000..c73e1fc12e5
--- /dev/null
+++ b/tools/perf/util/machine.c
@@ -0,0 +1,1422 @@
+#include "callchain.h"
+#include "debug.h"
+#include "event.h"
+#include "evsel.h"
+#include "hist.h"
+#include "machine.h"
+#include "map.h"
+#include "sort.h"
+#include "strlist.h"
+#include "thread.h"
+#include <stdbool.h>
+#include <symbol/kallsyms.h>
+#include "unwind.h"
+
+int machine__init(struct machine *machine, const char *root_dir, pid_t pid)
+{
+ map_groups__init(&machine->kmaps);
+ RB_CLEAR_NODE(&machine->rb_node);
+ INIT_LIST_HEAD(&machine->user_dsos);
+ INIT_LIST_HEAD(&machine->kernel_dsos);
+
+ machine->threads = RB_ROOT;
+ INIT_LIST_HEAD(&machine->dead_threads);
+ machine->last_match = NULL;
+
+ machine->kmaps.machine = machine;
+ machine->pid = pid;
+
+ machine->symbol_filter = NULL;
+ machine->id_hdr_size = 0;
+
+ machine->root_dir = strdup(root_dir);
+ if (machine->root_dir == NULL)
+ return -ENOMEM;
+
+ if (pid != HOST_KERNEL_ID) {
+ struct thread *thread = machine__findnew_thread(machine, 0,
+ pid);
+ char comm[64];
+
+ if (thread == NULL)
+ return -ENOMEM;
+
+ snprintf(comm, sizeof(comm), "[guest/%d]", pid);
+ thread__set_comm(thread, comm, 0);
+ }
+
+ return 0;
+}
+
+struct machine *machine__new_host(void)
+{
+ struct machine *machine = malloc(sizeof(*machine));
+
+ if (machine != NULL) {
+ machine__init(machine, "", HOST_KERNEL_ID);
+
+ if (machine__create_kernel_maps(machine) < 0)
+ goto out_delete;
+ }
+
+ return machine;
+out_delete:
+ free(machine);
+ return NULL;
+}
+
+static void dsos__delete(struct list_head *dsos)
+{
+ struct dso *pos, *n;
+
+ list_for_each_entry_safe(pos, n, dsos, node) {
+ list_del(&pos->node);
+ dso__delete(pos);
+ }
+}
+
+void machine__delete_dead_threads(struct machine *machine)
+{
+ struct thread *n, *t;
+
+ list_for_each_entry_safe(t, n, &machine->dead_threads, node) {
+ list_del(&t->node);
+ thread__delete(t);
+ }
+}
+
+void machine__delete_threads(struct machine *machine)
+{
+ struct rb_node *nd = rb_first(&machine->threads);
+
+ while (nd) {
+ struct thread *t = rb_entry(nd, struct thread, rb_node);
+
+ rb_erase(&t->rb_node, &machine->threads);
+ nd = rb_next(nd);
+ thread__delete(t);
+ }
+}
+
+void machine__exit(struct machine *machine)
+{
+ map_groups__exit(&machine->kmaps);
+ dsos__delete(&machine->user_dsos);
+ dsos__delete(&machine->kernel_dsos);
+ zfree(&machine->root_dir);
+}
+
+void machine__delete(struct machine *machine)
+{
+ machine__exit(machine);
+ free(machine);
+}
+
+void machines__init(struct machines *machines)
+{
+ machine__init(&machines->host, "", HOST_KERNEL_ID);
+ machines->guests = RB_ROOT;
+ machines->symbol_filter = NULL;
+}
+
+void machines__exit(struct machines *machines)
+{
+ machine__exit(&machines->host);
+ /* XXX exit guest */
+}
+
+struct machine *machines__add(struct machines *machines, pid_t pid,
+ const char *root_dir)
+{
+ struct rb_node **p = &machines->guests.rb_node;
+ struct rb_node *parent = NULL;
+ struct machine *pos, *machine = malloc(sizeof(*machine));
+
+ if (machine == NULL)
+ return NULL;
+
+ if (machine__init(machine, root_dir, pid) != 0) {
+ free(machine);
+ return NULL;
+ }
+
+ machine->symbol_filter = machines->symbol_filter;
+
+ while (*p != NULL) {
+ parent = *p;
+ pos = rb_entry(parent, struct machine, rb_node);
+ if (pid < pos->pid)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ rb_link_node(&machine->rb_node, parent, p);
+ rb_insert_color(&machine->rb_node, &machines->guests);
+
+ return machine;
+}
+
+void machines__set_symbol_filter(struct machines *machines,
+ symbol_filter_t symbol_filter)
+{
+ struct rb_node *nd;
+
+ machines->symbol_filter = symbol_filter;
+ machines->host.symbol_filter = symbol_filter;
+
+ for (nd = rb_first(&machines->guests); nd; nd = rb_next(nd)) {
+ struct machine *machine = rb_entry(nd, struct machine, rb_node);
+
+ machine->symbol_filter = symbol_filter;
+ }
+}
+
+struct machine *machines__find(struct machines *machines, pid_t pid)
+{
+ struct rb_node **p = &machines->guests.rb_node;
+ struct rb_node *parent = NULL;
+ struct machine *machine;
+ struct machine *default_machine = NULL;
+
+ if (pid == HOST_KERNEL_ID)
+ return &machines->host;
+
+ while (*p != NULL) {
+ parent = *p;
+ machine = rb_entry(parent, struct machine, rb_node);
+ if (pid < machine->pid)
+ p = &(*p)->rb_left;
+ else if (pid > machine->pid)
+ p = &(*p)->rb_right;
+ else
+ return machine;
+ if (!machine->pid)
+ default_machine = machine;
+ }
+
+ return default_machine;
+}
+
+struct machine *machines__findnew(struct machines *machines, pid_t pid)
+{
+ char path[PATH_MAX];
+ const char *root_dir = "";
+ struct machine *machine = machines__find(machines, pid);
+
+ if (machine && (machine->pid == pid))
+ goto out;
+
+ if ((pid != HOST_KERNEL_ID) &&
+ (pid != DEFAULT_GUEST_KERNEL_ID) &&
+ (symbol_conf.guestmount)) {
+ sprintf(path, "%s/%d", symbol_conf.guestmount, pid);
+ if (access(path, R_OK)) {
+ static struct strlist *seen;
+
+ if (!seen)
+ seen = strlist__new(true, NULL);
+
+ if (!strlist__has_entry(seen, path)) {
+ pr_err("Can't access file %s\n", path);
+ strlist__add(seen, path);
+ }
+ machine = NULL;
+ goto out;
+ }
+ root_dir = path;
+ }
+
+ machine = machines__add(machines, pid, root_dir);
+out:
+ return machine;
+}
+
+void machines__process_guests(struct machines *machines,
+ machine__process_t process, void *data)
+{
+ struct rb_node *nd;
+
+ for (nd = rb_first(&machines->guests); nd; nd = rb_next(nd)) {
+ struct machine *pos = rb_entry(nd, struct machine, rb_node);
+ process(pos, data);
+ }
+}
+
+char *machine__mmap_name(struct machine *machine, char *bf, size_t size)
+{
+ if (machine__is_host(machine))
+ snprintf(bf, size, "[%s]", "kernel.kallsyms");
+ else if (machine__is_default_guest(machine))
+ snprintf(bf, size, "[%s]", "guest.kernel.kallsyms");
+ else {
+ snprintf(bf, size, "[%s.%d]", "guest.kernel.kallsyms",
+ machine->pid);
+ }
+
+ return bf;
+}
+
+void machines__set_id_hdr_size(struct machines *machines, u16 id_hdr_size)
+{
+ struct rb_node *node;
+ struct machine *machine;
+
+ machines->host.id_hdr_size = id_hdr_size;
+
+ for (node = rb_first(&machines->guests); node; node = rb_next(node)) {
+ machine = rb_entry(node, struct machine, rb_node);
+ machine->id_hdr_size = id_hdr_size;
+ }
+
+ return;
+}
+
+static struct thread *__machine__findnew_thread(struct machine *machine,
+ pid_t pid, pid_t tid,
+ bool create)
+{
+ struct rb_node **p = &machine->threads.rb_node;
+ struct rb_node *parent = NULL;
+ struct thread *th;
+
+ /*
+ * Front-end cache - TID lookups come in blocks,
+ * so most of the time we dont have to look up
+ * the full rbtree:
+ */
+ if (machine->last_match && machine->last_match->tid == tid) {
+ if (pid && pid != machine->last_match->pid_)
+ machine->last_match->pid_ = pid;
+ return machine->last_match;
+ }
+
+ while (*p != NULL) {
+ parent = *p;
+ th = rb_entry(parent, struct thread, rb_node);
+
+ if (th->tid == tid) {
+ machine->last_match = th;
+ if (pid && pid != th->pid_)
+ th->pid_ = pid;
+ return th;
+ }
+
+ if (tid < th->tid)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ if (!create)
+ return NULL;
+
+ th = thread__new(pid, tid);
+ if (th != NULL) {
+ rb_link_node(&th->rb_node, parent, p);
+ rb_insert_color(&th->rb_node, &machine->threads);
+ machine->last_match = th;
+
+ /*
+ * We have to initialize map_groups separately
+ * after rb tree is updated.
+ *
+ * The reason is that we call machine__findnew_thread
+ * within thread__init_map_groups to find the thread
+ * leader and that would screwed the rb tree.
+ */
+ if (thread__init_map_groups(th, machine))
+ return NULL;
+ }
+
+ return th;
+}
+
+struct thread *machine__findnew_thread(struct machine *machine, pid_t pid,
+ pid_t tid)
+{
+ return __machine__findnew_thread(machine, pid, tid, true);
+}
+
+struct thread *machine__find_thread(struct machine *machine, pid_t pid,
+ pid_t tid)
+{
+ return __machine__findnew_thread(machine, pid, tid, false);
+}
+
+int machine__process_comm_event(struct machine *machine, union perf_event *event,
+ struct perf_sample *sample)
+{
+ struct thread *thread = machine__findnew_thread(machine,
+ event->comm.pid,
+ event->comm.tid);
+
+ if (dump_trace)
+ perf_event__fprintf_comm(event, stdout);
+
+ if (thread == NULL || thread__set_comm(thread, event->comm.comm, sample->time)) {
+ dump_printf("problem processing PERF_RECORD_COMM, skipping event.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+int machine__process_lost_event(struct machine *machine __maybe_unused,
+ union perf_event *event, struct perf_sample *sample __maybe_unused)
+{
+ dump_printf(": id:%" PRIu64 ": lost:%" PRIu64 "\n",
+ event->lost.id, event->lost.lost);
+ return 0;
+}
+
+struct map *machine__new_module(struct machine *machine, u64 start,
+ const char *filename)
+{
+ struct map *map;
+ struct dso *dso = __dsos__findnew(&machine->kernel_dsos, filename);
+
+ if (dso == NULL)
+ return NULL;
+
+ map = map__new2(start, dso, MAP__FUNCTION);
+ if (map == NULL)
+ return NULL;
+
+ if (machine__is_host(machine))
+ dso->symtab_type = DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE;
+ else
+ dso->symtab_type = DSO_BINARY_TYPE__GUEST_KMODULE;
+ map_groups__insert(&machine->kmaps, map);
+ return map;
+}
+
+size_t machines__fprintf_dsos(struct machines *machines, FILE *fp)
+{
+ struct rb_node *nd;
+ size_t ret = __dsos__fprintf(&machines->host.kernel_dsos, fp) +
+ __dsos__fprintf(&machines->host.user_dsos, fp);
+
+ for (nd = rb_first(&machines->guests); nd; nd = rb_next(nd)) {
+ struct machine *pos = rb_entry(nd, struct machine, rb_node);
+ ret += __dsos__fprintf(&pos->kernel_dsos, fp);
+ ret += __dsos__fprintf(&pos->user_dsos, fp);
+ }
+
+ return ret;
+}
+
+size_t machine__fprintf_dsos_buildid(struct machine *machine, FILE *fp,
+ bool (skip)(struct dso *dso, int parm), int parm)
+{
+ return __dsos__fprintf_buildid(&machine->kernel_dsos, fp, skip, parm) +
+ __dsos__fprintf_buildid(&machine->user_dsos, fp, skip, parm);
+}
+
+size_t machines__fprintf_dsos_buildid(struct machines *machines, FILE *fp,
+ bool (skip)(struct dso *dso, int parm), int parm)
+{
+ struct rb_node *nd;
+ size_t ret = machine__fprintf_dsos_buildid(&machines->host, fp, skip, parm);
+
+ for (nd = rb_first(&machines->guests); nd; nd = rb_next(nd)) {
+ struct machine *pos = rb_entry(nd, struct machine, rb_node);
+ ret += machine__fprintf_dsos_buildid(pos, fp, skip, parm);
+ }
+ return ret;
+}
+
+size_t machine__fprintf_vmlinux_path(struct machine *machine, FILE *fp)
+{
+ int i;
+ size_t printed = 0;
+ struct dso *kdso = machine->vmlinux_maps[MAP__FUNCTION]->dso;
+
+ if (kdso->has_build_id) {
+ char filename[PATH_MAX];
+ if (dso__build_id_filename(kdso, filename, sizeof(filename)))
+ printed += fprintf(fp, "[0] %s\n", filename);
+ }
+
+ for (i = 0; i < vmlinux_path__nr_entries; ++i)
+ printed += fprintf(fp, "[%d] %s\n",
+ i + kdso->has_build_id, vmlinux_path[i]);
+
+ return printed;
+}
+
+size_t machine__fprintf(struct machine *machine, FILE *fp)
+{
+ size_t ret = 0;
+ struct rb_node *nd;
+
+ for (nd = rb_first(&machine->threads); nd; nd = rb_next(nd)) {
+ struct thread *pos = rb_entry(nd, struct thread, rb_node);
+
+ ret += thread__fprintf(pos, fp);
+ }
+
+ return ret;
+}
+
+static struct dso *machine__get_kernel(struct machine *machine)
+{
+ const char *vmlinux_name = NULL;
+ struct dso *kernel;
+
+ if (machine__is_host(machine)) {
+ vmlinux_name = symbol_conf.vmlinux_name;
+ if (!vmlinux_name)
+ vmlinux_name = "[kernel.kallsyms]";
+
+ kernel = dso__kernel_findnew(machine, vmlinux_name,
+ "[kernel]",
+ DSO_TYPE_KERNEL);
+ } else {
+ char bf[PATH_MAX];
+
+ if (machine__is_default_guest(machine))
+ vmlinux_name = symbol_conf.default_guest_vmlinux_name;
+ if (!vmlinux_name)
+ vmlinux_name = machine__mmap_name(machine, bf,
+ sizeof(bf));
+
+ kernel = dso__kernel_findnew(machine, vmlinux_name,
+ "[guest.kernel]",
+ DSO_TYPE_GUEST_KERNEL);
+ }
+
+ if (kernel != NULL && (!kernel->has_build_id))
+ dso__read_running_kernel_build_id(kernel, machine);
+
+ return kernel;
+}
+
+struct process_args {
+ u64 start;
+};
+
+static void machine__get_kallsyms_filename(struct machine *machine, char *buf,
+ size_t bufsz)
+{
+ if (machine__is_default_guest(machine))
+ scnprintf(buf, bufsz, "%s", symbol_conf.default_guest_kallsyms);
+ else
+ scnprintf(buf, bufsz, "%s/proc/kallsyms", machine->root_dir);
+}
+
+const char *ref_reloc_sym_names[] = {"_text", "_stext", NULL};
+
+/* Figure out the start address of kernel map from /proc/kallsyms.
+ * Returns the name of the start symbol in *symbol_name. Pass in NULL as
+ * symbol_name if it's not that important.
+ */
+static u64 machine__get_kernel_start_addr(struct machine *machine,
+ const char **symbol_name)
+{
+ char filename[PATH_MAX];
+ int i;
+ const char *name;
+ u64 addr = 0;
+
+ machine__get_kallsyms_filename(machine, filename, PATH_MAX);
+
+ if (symbol__restricted_filename(filename, "/proc/kallsyms"))
+ return 0;
+
+ for (i = 0; (name = ref_reloc_sym_names[i]) != NULL; i++) {
+ addr = kallsyms__get_function_start(filename, name);
+ if (addr)
+ break;
+ }
+
+ if (symbol_name)
+ *symbol_name = name;
+
+ return addr;
+}
+
+int __machine__create_kernel_maps(struct machine *machine, struct dso *kernel)
+{
+ enum map_type type;
+ u64 start = machine__get_kernel_start_addr(machine, NULL);
+
+ for (type = 0; type < MAP__NR_TYPES; ++type) {
+ struct kmap *kmap;
+
+ machine->vmlinux_maps[type] = map__new2(start, kernel, type);
+ if (machine->vmlinux_maps[type] == NULL)
+ return -1;
+
+ machine->vmlinux_maps[type]->map_ip =
+ machine->vmlinux_maps[type]->unmap_ip =
+ identity__map_ip;
+ kmap = map__kmap(machine->vmlinux_maps[type]);
+ kmap->kmaps = &machine->kmaps;
+ map_groups__insert(&machine->kmaps,
+ machine->vmlinux_maps[type]);
+ }
+
+ return 0;
+}
+
+void machine__destroy_kernel_maps(struct machine *machine)
+{
+ enum map_type type;
+
+ for (type = 0; type < MAP__NR_TYPES; ++type) {
+ struct kmap *kmap;
+
+ if (machine->vmlinux_maps[type] == NULL)
+ continue;
+
+ kmap = map__kmap(machine->vmlinux_maps[type]);
+ map_groups__remove(&machine->kmaps,
+ machine->vmlinux_maps[type]);
+ if (kmap->ref_reloc_sym) {
+ /*
+ * ref_reloc_sym is shared among all maps, so free just
+ * on one of them.
+ */
+ if (type == MAP__FUNCTION) {
+ zfree((char **)&kmap->ref_reloc_sym->name);
+ zfree(&kmap->ref_reloc_sym);
+ } else
+ kmap->ref_reloc_sym = NULL;
+ }
+
+ map__delete(machine->vmlinux_maps[type]);
+ machine->vmlinux_maps[type] = NULL;
+ }
+}
+
+int machines__create_guest_kernel_maps(struct machines *machines)
+{
+ int ret = 0;
+ struct dirent **namelist = NULL;
+ int i, items = 0;
+ char path[PATH_MAX];
+ pid_t pid;
+ char *endp;
+
+ if (symbol_conf.default_guest_vmlinux_name ||
+ symbol_conf.default_guest_modules ||
+ symbol_conf.default_guest_kallsyms) {
+ machines__create_kernel_maps(machines, DEFAULT_GUEST_KERNEL_ID);
+ }
+
+ if (symbol_conf.guestmount) {
+ items = scandir(symbol_conf.guestmount, &namelist, NULL, NULL);
+ if (items <= 0)
+ return -ENOENT;
+ for (i = 0; i < items; i++) {
+ if (!isdigit(namelist[i]->d_name[0])) {
+ /* Filter out . and .. */
+ continue;
+ }
+ pid = (pid_t)strtol(namelist[i]->d_name, &endp, 10);
+ if ((*endp != '\0') ||
+ (endp == namelist[i]->d_name) ||
+ (errno == ERANGE)) {
+ pr_debug("invalid directory (%s). Skipping.\n",
+ namelist[i]->d_name);
+ continue;
+ }
+ sprintf(path, "%s/%s/proc/kallsyms",
+ symbol_conf.guestmount,
+ namelist[i]->d_name);
+ ret = access(path, R_OK);
+ if (ret) {
+ pr_debug("Can't access file %s\n", path);
+ goto failure;
+ }
+ machines__create_kernel_maps(machines, pid);
+ }
+failure:
+ free(namelist);
+ }
+
+ return ret;
+}
+
+void machines__destroy_kernel_maps(struct machines *machines)
+{
+ struct rb_node *next = rb_first(&machines->guests);
+
+ machine__destroy_kernel_maps(&machines->host);
+
+ while (next) {
+ struct machine *pos = rb_entry(next, struct machine, rb_node);
+
+ next = rb_next(&pos->rb_node);
+ rb_erase(&pos->rb_node, &machines->guests);
+ machine__delete(pos);
+ }
+}
+
+int machines__create_kernel_maps(struct machines *machines, pid_t pid)
+{
+ struct machine *machine = machines__findnew(machines, pid);
+
+ if (machine == NULL)
+ return -1;
+
+ return machine__create_kernel_maps(machine);
+}
+
+int machine__load_kallsyms(struct machine *machine, const char *filename,
+ enum map_type type, symbol_filter_t filter)
+{
+ struct map *map = machine->vmlinux_maps[type];
+ int ret = dso__load_kallsyms(map->dso, filename, map, filter);
+
+ if (ret > 0) {
+ dso__set_loaded(map->dso, type);
+ /*
+ * Since /proc/kallsyms will have multiple sessions for the
+ * kernel, with modules between them, fixup the end of all
+ * sections.
+ */
+ __map_groups__fixup_end(&machine->kmaps, type);
+ }
+
+ return ret;
+}
+
+int machine__load_vmlinux_path(struct machine *machine, enum map_type type,
+ symbol_filter_t filter)
+{
+ struct map *map = machine->vmlinux_maps[type];
+ int ret = dso__load_vmlinux_path(map->dso, map, filter);
+
+ if (ret > 0)
+ dso__set_loaded(map->dso, type);
+
+ return ret;
+}
+
+static void map_groups__fixup_end(struct map_groups *mg)
+{
+ int i;
+ for (i = 0; i < MAP__NR_TYPES; ++i)
+ __map_groups__fixup_end(mg, i);
+}
+
+static char *get_kernel_version(const char *root_dir)
+{
+ char version[PATH_MAX];
+ FILE *file;
+ char *name, *tmp;
+ const char *prefix = "Linux version ";
+
+ sprintf(version, "%s/proc/version", root_dir);
+ file = fopen(version, "r");
+ if (!file)
+ return NULL;
+
+ version[0] = '\0';
+ tmp = fgets(version, sizeof(version), file);
+ fclose(file);
+
+ name = strstr(version, prefix);
+ if (!name)
+ return NULL;
+ name += strlen(prefix);
+ tmp = strchr(name, ' ');
+ if (tmp)
+ *tmp = '\0';
+
+ return strdup(name);
+}
+
+static int map_groups__set_modules_path_dir(struct map_groups *mg,
+ const char *dir_name, int depth)
+{
+ struct dirent *dent;
+ DIR *dir = opendir(dir_name);
+ int ret = 0;
+
+ if (!dir) {
+ pr_debug("%s: cannot open %s dir\n", __func__, dir_name);
+ return -1;
+ }
+
+ while ((dent = readdir(dir)) != NULL) {
+ char path[PATH_MAX];
+ struct stat st;
+
+ /*sshfs might return bad dent->d_type, so we have to stat*/
+ snprintf(path, sizeof(path), "%s/%s", dir_name, dent->d_name);
+ if (stat(path, &st))
+ continue;
+
+ if (S_ISDIR(st.st_mode)) {
+ if (!strcmp(dent->d_name, ".") ||
+ !strcmp(dent->d_name, ".."))
+ continue;
+
+ /* Do not follow top-level source and build symlinks */
+ if (depth == 0) {
+ if (!strcmp(dent->d_name, "source") ||
+ !strcmp(dent->d_name, "build"))
+ continue;
+ }
+
+ ret = map_groups__set_modules_path_dir(mg, path,
+ depth + 1);
+ if (ret < 0)
+ goto out;
+ } else {
+ char *dot = strrchr(dent->d_name, '.'),
+ dso_name[PATH_MAX];
+ struct map *map;
+ char *long_name;
+
+ if (dot == NULL || strcmp(dot, ".ko"))
+ continue;
+ snprintf(dso_name, sizeof(dso_name), "[%.*s]",
+ (int)(dot - dent->d_name), dent->d_name);
+
+ strxfrchar(dso_name, '-', '_');
+ map = map_groups__find_by_name(mg, MAP__FUNCTION,
+ dso_name);
+ if (map == NULL)
+ continue;
+
+ long_name = strdup(path);
+ if (long_name == NULL) {
+ ret = -1;
+ goto out;
+ }
+ dso__set_long_name(map->dso, long_name, true);
+ dso__kernel_module_get_build_id(map->dso, "");
+ }
+ }
+
+out:
+ closedir(dir);
+ return ret;
+}
+
+static int machine__set_modules_path(struct machine *machine)
+{
+ char *version;
+ char modules_path[PATH_MAX];
+
+ version = get_kernel_version(machine->root_dir);
+ if (!version)
+ return -1;
+
+ snprintf(modules_path, sizeof(modules_path), "%s/lib/modules/%s",
+ machine->root_dir, version);
+ free(version);
+
+ return map_groups__set_modules_path_dir(&machine->kmaps, modules_path, 0);
+}
+
+static int machine__create_module(void *arg, const char *name, u64 start)
+{
+ struct machine *machine = arg;
+ struct map *map;
+
+ map = machine__new_module(machine, start, name);
+ if (map == NULL)
+ return -1;
+
+ dso__kernel_module_get_build_id(map->dso, machine->root_dir);
+
+ return 0;
+}
+
+static int machine__create_modules(struct machine *machine)
+{
+ const char *modules;
+ char path[PATH_MAX];
+
+ if (machine__is_default_guest(machine)) {
+ modules = symbol_conf.default_guest_modules;
+ } else {
+ snprintf(path, PATH_MAX, "%s/proc/modules", machine->root_dir);
+ modules = path;
+ }
+
+ if (symbol__restricted_filename(modules, "/proc/modules"))
+ return -1;
+
+ if (modules__parse(modules, machine, machine__create_module))
+ return -1;
+
+ if (!machine__set_modules_path(machine))
+ return 0;
+
+ pr_debug("Problems setting modules path maps, continuing anyway...\n");
+
+ return 0;
+}
+
+int machine__create_kernel_maps(struct machine *machine)
+{
+ struct dso *kernel = machine__get_kernel(machine);
+ const char *name;
+ u64 addr = machine__get_kernel_start_addr(machine, &name);
+ if (!addr)
+ return -1;
+
+ if (kernel == NULL ||
+ __machine__create_kernel_maps(machine, kernel) < 0)
+ return -1;
+
+ if (symbol_conf.use_modules && machine__create_modules(machine) < 0) {
+ if (machine__is_host(machine))
+ pr_debug("Problems creating module maps, "
+ "continuing anyway...\n");
+ else
+ pr_debug("Problems creating module maps for guest %d, "
+ "continuing anyway...\n", machine->pid);
+ }
+
+ /*
+ * Now that we have all the maps created, just set the ->end of them:
+ */
+ map_groups__fixup_end(&machine->kmaps);
+
+ if (maps__set_kallsyms_ref_reloc_sym(machine->vmlinux_maps, name,
+ addr)) {
+ machine__destroy_kernel_maps(machine);
+ return -1;
+ }
+
+ return 0;
+}
+
+static void machine__set_kernel_mmap_len(struct machine *machine,
+ union perf_event *event)
+{
+ int i;
+
+ for (i = 0; i < MAP__NR_TYPES; i++) {
+ machine->vmlinux_maps[i]->start = event->mmap.start;
+ machine->vmlinux_maps[i]->end = (event->mmap.start +
+ event->mmap.len);
+ /*
+ * Be a bit paranoid here, some perf.data file came with
+ * a zero sized synthesized MMAP event for the kernel.
+ */
+ if (machine->vmlinux_maps[i]->end == 0)
+ machine->vmlinux_maps[i]->end = ~0ULL;
+ }
+}
+
+static bool machine__uses_kcore(struct machine *machine)
+{
+ struct dso *dso;
+
+ list_for_each_entry(dso, &machine->kernel_dsos, node) {
+ if (dso__is_kcore(dso))
+ return true;
+ }
+
+ return false;
+}
+
+static int machine__process_kernel_mmap_event(struct machine *machine,
+ union perf_event *event)
+{
+ struct map *map;
+ char kmmap_prefix[PATH_MAX];
+ enum dso_kernel_type kernel_type;
+ bool is_kernel_mmap;
+
+ /* If we have maps from kcore then we do not need or want any others */
+ if (machine__uses_kcore(machine))
+ return 0;
+
+ machine__mmap_name(machine, kmmap_prefix, sizeof(kmmap_prefix));
+ if (machine__is_host(machine))
+ kernel_type = DSO_TYPE_KERNEL;
+ else
+ kernel_type = DSO_TYPE_GUEST_KERNEL;
+
+ is_kernel_mmap = memcmp(event->mmap.filename,
+ kmmap_prefix,
+ strlen(kmmap_prefix) - 1) == 0;
+ if (event->mmap.filename[0] == '/' ||
+ (!is_kernel_mmap && event->mmap.filename[0] == '[')) {
+
+ char short_module_name[1024];
+ char *name, *dot;
+
+ if (event->mmap.filename[0] == '/') {
+ name = strrchr(event->mmap.filename, '/');
+ if (name == NULL)
+ goto out_problem;
+
+ ++name; /* skip / */
+ dot = strrchr(name, '.');
+ if (dot == NULL)
+ goto out_problem;
+ snprintf(short_module_name, sizeof(short_module_name),
+ "[%.*s]", (int)(dot - name), name);
+ strxfrchar(short_module_name, '-', '_');
+ } else
+ strcpy(short_module_name, event->mmap.filename);
+
+ map = machine__new_module(machine, event->mmap.start,
+ event->mmap.filename);
+ if (map == NULL)
+ goto out_problem;
+
+ name = strdup(short_module_name);
+ if (name == NULL)
+ goto out_problem;
+
+ dso__set_short_name(map->dso, name, true);
+ map->end = map->start + event->mmap.len;
+ } else if (is_kernel_mmap) {
+ const char *symbol_name = (event->mmap.filename +
+ strlen(kmmap_prefix));
+ /*
+ * Should be there already, from the build-id table in
+ * the header.
+ */
+ struct dso *kernel = __dsos__findnew(&machine->kernel_dsos,
+ kmmap_prefix);
+ if (kernel == NULL)
+ goto out_problem;
+
+ kernel->kernel = kernel_type;
+ if (__machine__create_kernel_maps(machine, kernel) < 0)
+ goto out_problem;
+
+ machine__set_kernel_mmap_len(machine, event);
+
+ /*
+ * Avoid using a zero address (kptr_restrict) for the ref reloc
+ * symbol. Effectively having zero here means that at record
+ * time /proc/sys/kernel/kptr_restrict was non zero.
+ */
+ if (event->mmap.pgoff != 0) {
+ maps__set_kallsyms_ref_reloc_sym(machine->vmlinux_maps,
+ symbol_name,
+ event->mmap.pgoff);
+ }
+
+ if (machine__is_default_guest(machine)) {
+ /*
+ * preload dso of guest kernel and modules
+ */
+ dso__load(kernel, machine->vmlinux_maps[MAP__FUNCTION],
+ NULL);
+ }
+ }
+ return 0;
+out_problem:
+ return -1;
+}
+
+int machine__process_mmap2_event(struct machine *machine,
+ union perf_event *event,
+ struct perf_sample *sample __maybe_unused)
+{
+ u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+ struct thread *thread;
+ struct map *map;
+ enum map_type type;
+ int ret = 0;
+
+ if (dump_trace)
+ perf_event__fprintf_mmap2(event, stdout);
+
+ if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL ||
+ cpumode == PERF_RECORD_MISC_KERNEL) {
+ ret = machine__process_kernel_mmap_event(machine, event);
+ if (ret < 0)
+ goto out_problem;
+ return 0;
+ }
+
+ thread = machine__findnew_thread(machine, event->mmap2.pid,
+ event->mmap2.tid);
+ if (thread == NULL)
+ goto out_problem;
+
+ if (event->header.misc & PERF_RECORD_MISC_MMAP_DATA)
+ type = MAP__VARIABLE;
+ else
+ type = MAP__FUNCTION;
+
+ map = map__new(&machine->user_dsos, event->mmap2.start,
+ event->mmap2.len, event->mmap2.pgoff,
+ event->mmap2.pid, event->mmap2.maj,
+ event->mmap2.min, event->mmap2.ino,
+ event->mmap2.ino_generation,
+ event->mmap2.prot,
+ event->mmap2.flags,
+ event->mmap2.filename, type);
+
+ if (map == NULL)
+ goto out_problem;
+
+ thread__insert_map(thread, map);
+ return 0;
+
+out_problem:
+ dump_printf("problem processing PERF_RECORD_MMAP2, skipping event.\n");
+ return 0;
+}
+
+int machine__process_mmap_event(struct machine *machine, union perf_event *event,
+ struct perf_sample *sample __maybe_unused)
+{
+ u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+ struct thread *thread;
+ struct map *map;
+ enum map_type type;
+ int ret = 0;
+
+ if (dump_trace)
+ perf_event__fprintf_mmap(event, stdout);
+
+ if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL ||
+ cpumode == PERF_RECORD_MISC_KERNEL) {
+ ret = machine__process_kernel_mmap_event(machine, event);
+ if (ret < 0)
+ goto out_problem;
+ return 0;
+ }
+
+ thread = machine__findnew_thread(machine, event->mmap.pid,
+ event->mmap.tid);
+ if (thread == NULL)
+ goto out_problem;
+
+ if (event->header.misc & PERF_RECORD_MISC_MMAP_DATA)
+ type = MAP__VARIABLE;
+ else
+ type = MAP__FUNCTION;
+
+ map = map__new(&machine->user_dsos, event->mmap.start,
+ event->mmap.len, event->mmap.pgoff,
+ event->mmap.pid, 0, 0, 0, 0, 0, 0,
+ event->mmap.filename,
+ type);
+
+ if (map == NULL)
+ goto out_problem;
+
+ thread__insert_map(thread, map);
+ return 0;
+
+out_problem:
+ dump_printf("problem processing PERF_RECORD_MMAP, skipping event.\n");
+ return 0;
+}
+
+static void machine__remove_thread(struct machine *machine, struct thread *th)
+{
+ machine->last_match = NULL;
+ rb_erase(&th->rb_node, &machine->threads);
+ /*
+ * We may have references to this thread, for instance in some hist_entry
+ * instances, so just move them to a separate list.
+ */
+ list_add_tail(&th->node, &machine->dead_threads);
+}
+
+int machine__process_fork_event(struct machine *machine, union perf_event *event,
+ struct perf_sample *sample)
+{
+ struct thread *thread = machine__find_thread(machine,
+ event->fork.pid,
+ event->fork.tid);
+ struct thread *parent = machine__findnew_thread(machine,
+ event->fork.ppid,
+ event->fork.ptid);
+
+ /* if a thread currently exists for the thread id remove it */
+ if (thread != NULL)
+ machine__remove_thread(machine, thread);
+
+ thread = machine__findnew_thread(machine, event->fork.pid,
+ event->fork.tid);
+ if (dump_trace)
+ perf_event__fprintf_task(event, stdout);
+
+ if (thread == NULL || parent == NULL ||
+ thread__fork(thread, parent, sample->time) < 0) {
+ dump_printf("problem processing PERF_RECORD_FORK, skipping event.\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+int machine__process_exit_event(struct machine *machine, union perf_event *event,
+ struct perf_sample *sample __maybe_unused)
+{
+ struct thread *thread = machine__find_thread(machine,
+ event->fork.pid,
+ event->fork.tid);
+
+ if (dump_trace)
+ perf_event__fprintf_task(event, stdout);
+
+ if (thread != NULL)
+ thread__exited(thread);
+
+ return 0;
+}
+
+int machine__process_event(struct machine *machine, union perf_event *event,
+ struct perf_sample *sample)
+{
+ int ret;
+
+ switch (event->header.type) {
+ case PERF_RECORD_COMM:
+ ret = machine__process_comm_event(machine, event, sample); break;
+ case PERF_RECORD_MMAP:
+ ret = machine__process_mmap_event(machine, event, sample); break;
+ case PERF_RECORD_MMAP2:
+ ret = machine__process_mmap2_event(machine, event, sample); break;
+ case PERF_RECORD_FORK:
+ ret = machine__process_fork_event(machine, event, sample); break;
+ case PERF_RECORD_EXIT:
+ ret = machine__process_exit_event(machine, event, sample); break;
+ case PERF_RECORD_LOST:
+ ret = machine__process_lost_event(machine, event, sample); break;
+ default:
+ ret = -1;
+ break;
+ }
+
+ return ret;
+}
+
+static bool symbol__match_regex(struct symbol *sym, regex_t *regex)
+{
+ if (sym->name && !regexec(regex, sym->name, 0, NULL, 0))
+ return 1;
+ return 0;
+}
+
+static void ip__resolve_ams(struct machine *machine, struct thread *thread,
+ struct addr_map_symbol *ams,
+ u64 ip)
+{
+ struct addr_location al;
+
+ memset(&al, 0, sizeof(al));
+ /*
+ * We cannot use the header.misc hint to determine whether a
+ * branch stack address is user, kernel, guest, hypervisor.
+ * Branches may straddle the kernel/user/hypervisor boundaries.
+ * Thus, we have to try consecutively until we find a match
+ * or else, the symbol is unknown
+ */
+ thread__find_cpumode_addr_location(thread, machine, MAP__FUNCTION, ip, &al);
+
+ ams->addr = ip;
+ ams->al_addr = al.addr;
+ ams->sym = al.sym;
+ ams->map = al.map;
+}
+
+static void ip__resolve_data(struct machine *machine, struct thread *thread,
+ u8 m, struct addr_map_symbol *ams, u64 addr)
+{
+ struct addr_location al;
+
+ memset(&al, 0, sizeof(al));
+
+ thread__find_addr_location(thread, machine, m, MAP__VARIABLE, addr,
+ &al);
+ ams->addr = addr;
+ ams->al_addr = al.addr;
+ ams->sym = al.sym;
+ ams->map = al.map;
+}
+
+struct mem_info *sample__resolve_mem(struct perf_sample *sample,
+ struct addr_location *al)
+{
+ struct mem_info *mi = zalloc(sizeof(*mi));
+
+ if (!mi)
+ return NULL;
+
+ ip__resolve_ams(al->machine, al->thread, &mi->iaddr, sample->ip);
+ ip__resolve_data(al->machine, al->thread, al->cpumode,
+ &mi->daddr, sample->addr);
+ mi->data_src.val = sample->data_src;
+
+ return mi;
+}
+
+struct branch_info *sample__resolve_bstack(struct perf_sample *sample,
+ struct addr_location *al)
+{
+ unsigned int i;
+ const struct branch_stack *bs = sample->branch_stack;
+ struct branch_info *bi = calloc(bs->nr, sizeof(struct branch_info));
+
+ if (!bi)
+ return NULL;
+
+ for (i = 0; i < bs->nr; i++) {
+ ip__resolve_ams(al->machine, al->thread, &bi[i].to, bs->entries[i].to);
+ ip__resolve_ams(al->machine, al->thread, &bi[i].from, bs->entries[i].from);
+ bi[i].flags = bs->entries[i].flags;
+ }
+ return bi;
+}
+
+static int machine__resolve_callchain_sample(struct machine *machine,
+ struct thread *thread,
+ struct ip_callchain *chain,
+ struct symbol **parent,
+ struct addr_location *root_al,
+ int max_stack)
+{
+ u8 cpumode = PERF_RECORD_MISC_USER;
+ int chain_nr = min(max_stack, (int)chain->nr);
+ int i;
+ int err;
+
+ callchain_cursor_reset(&callchain_cursor);
+
+ if (chain->nr > PERF_MAX_STACK_DEPTH) {
+ pr_warning("corrupted callchain. skipping...\n");
+ return 0;
+ }
+
+ for (i = 0; i < chain_nr; i++) {
+ u64 ip;
+ struct addr_location al;
+
+ if (callchain_param.order == ORDER_CALLEE)
+ ip = chain->ips[i];
+ else
+ ip = chain->ips[chain->nr - i - 1];
+
+ if (ip >= PERF_CONTEXT_MAX) {
+ switch (ip) {
+ case PERF_CONTEXT_HV:
+ cpumode = PERF_RECORD_MISC_HYPERVISOR;
+ break;
+ case PERF_CONTEXT_KERNEL:
+ cpumode = PERF_RECORD_MISC_KERNEL;
+ break;
+ case PERF_CONTEXT_USER:
+ cpumode = PERF_RECORD_MISC_USER;
+ break;
+ default:
+ pr_debug("invalid callchain context: "
+ "%"PRId64"\n", (s64) ip);
+ /*
+ * It seems the callchain is corrupted.
+ * Discard all.
+ */
+ callchain_cursor_reset(&callchain_cursor);
+ return 0;
+ }
+ continue;
+ }
+
+ al.filtered = 0;
+ thread__find_addr_location(thread, machine, cpumode,
+ MAP__FUNCTION, ip, &al);
+ if (al.sym != NULL) {
+ if (sort__has_parent && !*parent &&
+ symbol__match_regex(al.sym, &parent_regex))
+ *parent = al.sym;
+ else if (have_ignore_callees && root_al &&
+ symbol__match_regex(al.sym, &ignore_callees_regex)) {
+ /* Treat this symbol as the root,
+ forgetting its callees. */
+ *root_al = al;
+ callchain_cursor_reset(&callchain_cursor);
+ }
+ }
+
+ err = callchain_cursor_append(&callchain_cursor,
+ ip, al.map, al.sym);
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+static int unwind_entry(struct unwind_entry *entry, void *arg)
+{
+ struct callchain_cursor *cursor = arg;
+ return callchain_cursor_append(cursor, entry->ip,
+ entry->map, entry->sym);
+}
+
+int machine__resolve_callchain(struct machine *machine,
+ struct perf_evsel *evsel,
+ struct thread *thread,
+ struct perf_sample *sample,
+ struct symbol **parent,
+ struct addr_location *root_al,
+ int max_stack)
+{
+ int ret;
+
+ ret = machine__resolve_callchain_sample(machine, thread,
+ sample->callchain, parent,
+ root_al, max_stack);
+ if (ret)
+ return ret;
+
+ /* Can we do dwarf post unwind? */
+ if (!((evsel->attr.sample_type & PERF_SAMPLE_REGS_USER) &&
+ (evsel->attr.sample_type & PERF_SAMPLE_STACK_USER)))
+ return 0;
+
+ /* Bail out if nothing was captured. */
+ if ((!sample->user_regs.regs) ||
+ (!sample->user_stack.size))
+ return 0;
+
+ return unwind__get_entries(unwind_entry, &callchain_cursor, machine,
+ thread, sample, max_stack);
+
+}
+
+int machine__for_each_thread(struct machine *machine,
+ int (*fn)(struct thread *thread, void *p),
+ void *priv)
+{
+ struct rb_node *nd;
+ struct thread *thread;
+ int rc = 0;
+
+ for (nd = rb_first(&machine->threads); nd; nd = rb_next(nd)) {
+ thread = rb_entry(nd, struct thread, rb_node);
+ rc = fn(thread, priv);
+ if (rc != 0)
+ return rc;
+ }
+
+ list_for_each_entry(thread, &machine->dead_threads, node) {
+ rc = fn(thread, priv);
+ if (rc != 0)
+ return rc;
+ }
+ return rc;
+}
+
+int __machine__synthesize_threads(struct machine *machine, struct perf_tool *tool,
+ struct target *target, struct thread_map *threads,
+ perf_event__handler_t process, bool data_mmap)
+{
+ if (target__has_task(target))
+ return perf_event__synthesize_thread_map(tool, threads, process, machine, data_mmap);
+ else if (target__has_cpu(target))
+ return perf_event__synthesize_threads(tool, process, machine, data_mmap);
+ /* command specified */
+ return 0;
+}
diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h
new file mode 100644
index 00000000000..c8c74a11939
--- /dev/null
+++ b/tools/perf/util/machine.h
@@ -0,0 +1,194 @@
+#ifndef __PERF_MACHINE_H
+#define __PERF_MACHINE_H
+
+#include <sys/types.h>
+#include <linux/rbtree.h>
+#include "map.h"
+#include "event.h"
+
+struct addr_location;
+struct branch_stack;
+struct perf_evsel;
+struct perf_sample;
+struct symbol;
+struct thread;
+union perf_event;
+
+/* Native host kernel uses -1 as pid index in machine */
+#define HOST_KERNEL_ID (-1)
+#define DEFAULT_GUEST_KERNEL_ID (0)
+
+extern const char *ref_reloc_sym_names[];
+
+struct machine {
+ struct rb_node rb_node;
+ pid_t pid;
+ u16 id_hdr_size;
+ char *root_dir;
+ struct rb_root threads;
+ struct list_head dead_threads;
+ struct thread *last_match;
+ struct list_head user_dsos;
+ struct list_head kernel_dsos;
+ struct map_groups kmaps;
+ struct map *vmlinux_maps[MAP__NR_TYPES];
+ symbol_filter_t symbol_filter;
+};
+
+static inline
+struct map *machine__kernel_map(struct machine *machine, enum map_type type)
+{
+ return machine->vmlinux_maps[type];
+}
+
+struct thread *machine__find_thread(struct machine *machine, pid_t pid,
+ pid_t tid);
+
+int machine__process_comm_event(struct machine *machine, union perf_event *event,
+ struct perf_sample *sample);
+int machine__process_exit_event(struct machine *machine, union perf_event *event,
+ struct perf_sample *sample);
+int machine__process_fork_event(struct machine *machine, union perf_event *event,
+ struct perf_sample *sample);
+int machine__process_lost_event(struct machine *machine, union perf_event *event,
+ struct perf_sample *sample);
+int machine__process_mmap_event(struct machine *machine, union perf_event *event,
+ struct perf_sample *sample);
+int machine__process_mmap2_event(struct machine *machine, union perf_event *event,
+ struct perf_sample *sample);
+int machine__process_event(struct machine *machine, union perf_event *event,
+ struct perf_sample *sample);
+
+typedef void (*machine__process_t)(struct machine *machine, void *data);
+
+struct machines {
+ struct machine host;
+ struct rb_root guests;
+ symbol_filter_t symbol_filter;
+};
+
+void machines__init(struct machines *machines);
+void machines__exit(struct machines *machines);
+
+void machines__process_guests(struct machines *machines,
+ machine__process_t process, void *data);
+
+struct machine *machines__add(struct machines *machines, pid_t pid,
+ const char *root_dir);
+struct machine *machines__find_host(struct machines *machines);
+struct machine *machines__find(struct machines *machines, pid_t pid);
+struct machine *machines__findnew(struct machines *machines, pid_t pid);
+
+void machines__set_id_hdr_size(struct machines *machines, u16 id_hdr_size);
+char *machine__mmap_name(struct machine *machine, char *bf, size_t size);
+
+void machines__set_symbol_filter(struct machines *machines,
+ symbol_filter_t symbol_filter);
+
+struct machine *machine__new_host(void);
+int machine__init(struct machine *machine, const char *root_dir, pid_t pid);
+void machine__exit(struct machine *machine);
+void machine__delete_dead_threads(struct machine *machine);
+void machine__delete_threads(struct machine *machine);
+void machine__delete(struct machine *machine);
+
+struct branch_info *sample__resolve_bstack(struct perf_sample *sample,
+ struct addr_location *al);
+struct mem_info *sample__resolve_mem(struct perf_sample *sample,
+ struct addr_location *al);
+int machine__resolve_callchain(struct machine *machine,
+ struct perf_evsel *evsel,
+ struct thread *thread,
+ struct perf_sample *sample,
+ struct symbol **parent,
+ struct addr_location *root_al,
+ int max_stack);
+
+/*
+ * Default guest kernel is defined by parameter --guestkallsyms
+ * and --guestmodules
+ */
+static inline bool machine__is_default_guest(struct machine *machine)
+{
+ return machine ? machine->pid == DEFAULT_GUEST_KERNEL_ID : false;
+}
+
+static inline bool machine__is_host(struct machine *machine)
+{
+ return machine ? machine->pid == HOST_KERNEL_ID : false;
+}
+
+struct thread *machine__findnew_thread(struct machine *machine, pid_t pid,
+ pid_t tid);
+
+size_t machine__fprintf(struct machine *machine, FILE *fp);
+
+static inline
+struct symbol *machine__find_kernel_symbol(struct machine *machine,
+ enum map_type type, u64 addr,
+ struct map **mapp,
+ symbol_filter_t filter)
+{
+ return map_groups__find_symbol(&machine->kmaps, type, addr,
+ mapp, filter);
+}
+
+static inline
+struct symbol *machine__find_kernel_function(struct machine *machine, u64 addr,
+ struct map **mapp,
+ symbol_filter_t filter)
+{
+ return machine__find_kernel_symbol(machine, MAP__FUNCTION, addr,
+ mapp, filter);
+}
+
+static inline
+struct symbol *machine__find_kernel_function_by_name(struct machine *machine,
+ const char *name,
+ struct map **mapp,
+ symbol_filter_t filter)
+{
+ return map_groups__find_function_by_name(&machine->kmaps, name, mapp,
+ filter);
+}
+
+struct map *machine__new_module(struct machine *machine, u64 start,
+ const char *filename);
+
+int machine__load_kallsyms(struct machine *machine, const char *filename,
+ enum map_type type, symbol_filter_t filter);
+int machine__load_vmlinux_path(struct machine *machine, enum map_type type,
+ symbol_filter_t filter);
+
+size_t machine__fprintf_dsos_buildid(struct machine *machine, FILE *fp,
+ bool (skip)(struct dso *dso, int parm), int parm);
+size_t machines__fprintf_dsos(struct machines *machines, FILE *fp);
+size_t machines__fprintf_dsos_buildid(struct machines *machines, FILE *fp,
+ bool (skip)(struct dso *dso, int parm), int parm);
+
+void machine__destroy_kernel_maps(struct machine *machine);
+int __machine__create_kernel_maps(struct machine *machine, struct dso *kernel);
+int machine__create_kernel_maps(struct machine *machine);
+
+int machines__create_kernel_maps(struct machines *machines, pid_t pid);
+int machines__create_guest_kernel_maps(struct machines *machines);
+void machines__destroy_kernel_maps(struct machines *machines);
+
+size_t machine__fprintf_vmlinux_path(struct machine *machine, FILE *fp);
+
+int machine__for_each_thread(struct machine *machine,
+ int (*fn)(struct thread *thread, void *p),
+ void *priv);
+
+int __machine__synthesize_threads(struct machine *machine, struct perf_tool *tool,
+ struct target *target, struct thread_map *threads,
+ perf_event__handler_t process, bool data_mmap);
+static inline
+int machine__synthesize_threads(struct machine *machine, struct target *target,
+ struct thread_map *threads, bool data_mmap)
+{
+ return __machine__synthesize_threads(machine, NULL, target, threads,
+ perf_event__process, data_mmap);
+}
+
+#endif /* __PERF_MACHINE_H */
diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c
new file mode 100644
index 00000000000..25c571f4cba
--- /dev/null
+++ b/tools/perf/util/map.c
@@ -0,0 +1,764 @@
+#include "symbol.h"
+#include <errno.h>
+#include <inttypes.h>
+#include <limits.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <unistd.h>
+#include "map.h"
+#include "thread.h"
+#include "strlist.h"
+#include "vdso.h"
+#include "build-id.h"
+#include "util.h"
+#include <linux/string.h>
+
+const char *map_type__name[MAP__NR_TYPES] = {
+ [MAP__FUNCTION] = "Functions",
+ [MAP__VARIABLE] = "Variables",
+};
+
+static inline int is_anon_memory(const char *filename)
+{
+ return !strcmp(filename, "//anon") ||
+ !strcmp(filename, "/dev/zero (deleted)") ||
+ !strcmp(filename, "/anon_hugepage (deleted)");
+}
+
+static inline int is_no_dso_memory(const char *filename)
+{
+ return !strncmp(filename, "[stack", 6) ||
+ !strcmp(filename, "[heap]");
+}
+
+static inline int is_android_lib(const char *filename)
+{
+ return !strncmp(filename, "/data/app-lib", 13) ||
+ !strncmp(filename, "/system/lib", 11);
+}
+
+static inline bool replace_android_lib(const char *filename, char *newfilename)
+{
+ const char *libname;
+ char *app_abi;
+ size_t app_abi_length, new_length;
+ size_t lib_length = 0;
+
+ libname = strrchr(filename, '/');
+ if (libname)
+ lib_length = strlen(libname);
+
+ app_abi = getenv("APP_ABI");
+ if (!app_abi)
+ return false;
+
+ app_abi_length = strlen(app_abi);
+
+ if (!strncmp(filename, "/data/app-lib", 13)) {
+ char *apk_path;
+
+ if (!app_abi_length)
+ return false;
+
+ new_length = 7 + app_abi_length + lib_length;
+
+ apk_path = getenv("APK_PATH");
+ if (apk_path) {
+ new_length += strlen(apk_path) + 1;
+ if (new_length > PATH_MAX)
+ return false;
+ snprintf(newfilename, new_length,
+ "%s/libs/%s/%s", apk_path, app_abi, libname);
+ } else {
+ if (new_length > PATH_MAX)
+ return false;
+ snprintf(newfilename, new_length,
+ "libs/%s/%s", app_abi, libname);
+ }
+ return true;
+ }
+
+ if (!strncmp(filename, "/system/lib/", 11)) {
+ char *ndk, *app;
+ const char *arch;
+ size_t ndk_length;
+ size_t app_length;
+
+ ndk = getenv("NDK_ROOT");
+ app = getenv("APP_PLATFORM");
+
+ if (!(ndk && app))
+ return false;
+
+ ndk_length = strlen(ndk);
+ app_length = strlen(app);
+
+ if (!(ndk_length && app_length && app_abi_length))
+ return false;
+
+ arch = !strncmp(app_abi, "arm", 3) ? "arm" :
+ !strncmp(app_abi, "mips", 4) ? "mips" :
+ !strncmp(app_abi, "x86", 3) ? "x86" : NULL;
+
+ if (!arch)
+ return false;
+
+ new_length = 27 + ndk_length +
+ app_length + lib_length
+ + strlen(arch);
+
+ if (new_length > PATH_MAX)
+ return false;
+ snprintf(newfilename, new_length,
+ "%s/platforms/%s/arch-%s/usr/lib/%s",
+ ndk, app, arch, libname);
+
+ return true;
+ }
+ return false;
+}
+
+void map__init(struct map *map, enum map_type type,
+ u64 start, u64 end, u64 pgoff, struct dso *dso)
+{
+ map->type = type;
+ map->start = start;
+ map->end = end;
+ map->pgoff = pgoff;
+ map->reloc = 0;
+ map->dso = dso;
+ map->map_ip = map__map_ip;
+ map->unmap_ip = map__unmap_ip;
+ RB_CLEAR_NODE(&map->rb_node);
+ map->groups = NULL;
+ map->referenced = false;
+ map->erange_warned = false;
+}
+
+struct map *map__new(struct list_head *dsos__list, u64 start, u64 len,
+ u64 pgoff, u32 pid, u32 d_maj, u32 d_min, u64 ino,
+ u64 ino_gen, u32 prot, u32 flags, char *filename,
+ enum map_type type)
+{
+ struct map *map = malloc(sizeof(*map));
+
+ if (map != NULL) {
+ char newfilename[PATH_MAX];
+ struct dso *dso;
+ int anon, no_dso, vdso, android;
+
+ android = is_android_lib(filename);
+ anon = is_anon_memory(filename);
+ vdso = is_vdso_map(filename);
+ no_dso = is_no_dso_memory(filename);
+
+ map->maj = d_maj;
+ map->min = d_min;
+ map->ino = ino;
+ map->ino_generation = ino_gen;
+ map->prot = prot;
+ map->flags = flags;
+
+ if ((anon || no_dso) && type == MAP__FUNCTION) {
+ snprintf(newfilename, sizeof(newfilename), "/tmp/perf-%d.map", pid);
+ filename = newfilename;
+ }
+
+ if (android) {
+ if (replace_android_lib(filename, newfilename))
+ filename = newfilename;
+ }
+
+ if (vdso) {
+ pgoff = 0;
+ dso = vdso__dso_findnew(dsos__list);
+ } else
+ dso = __dsos__findnew(dsos__list, filename);
+
+ if (dso == NULL)
+ goto out_delete;
+
+ map__init(map, type, start, start + len, pgoff, dso);
+
+ if (anon || no_dso) {
+ map->map_ip = map->unmap_ip = identity__map_ip;
+
+ /*
+ * Set memory without DSO as loaded. All map__find_*
+ * functions still return NULL, and we avoid the
+ * unnecessary map__load warning.
+ */
+ if (type != MAP__FUNCTION)
+ dso__set_loaded(dso, map->type);
+ }
+ }
+ return map;
+out_delete:
+ free(map);
+ return NULL;
+}
+
+/*
+ * Constructor variant for modules (where we know from /proc/modules where
+ * they are loaded) and for vmlinux, where only after we load all the
+ * symbols we'll know where it starts and ends.
+ */
+struct map *map__new2(u64 start, struct dso *dso, enum map_type type)
+{
+ struct map *map = calloc(1, (sizeof(*map) +
+ (dso->kernel ? sizeof(struct kmap) : 0)));
+ if (map != NULL) {
+ /*
+ * ->end will be filled after we load all the symbols
+ */
+ map__init(map, type, start, 0, 0, dso);
+ }
+
+ return map;
+}
+
+void map__delete(struct map *map)
+{
+ free(map);
+}
+
+void map__fixup_start(struct map *map)
+{
+ struct rb_root *symbols = &map->dso->symbols[map->type];
+ struct rb_node *nd = rb_first(symbols);
+ if (nd != NULL) {
+ struct symbol *sym = rb_entry(nd, struct symbol, rb_node);
+ map->start = sym->start;
+ }
+}
+
+void map__fixup_end(struct map *map)
+{
+ struct rb_root *symbols = &map->dso->symbols[map->type];
+ struct rb_node *nd = rb_last(symbols);
+ if (nd != NULL) {
+ struct symbol *sym = rb_entry(nd, struct symbol, rb_node);
+ map->end = sym->end;
+ }
+}
+
+#define DSO__DELETED "(deleted)"
+
+int map__load(struct map *map, symbol_filter_t filter)
+{
+ const char *name = map->dso->long_name;
+ int nr;
+
+ if (dso__loaded(map->dso, map->type))
+ return 0;
+
+ nr = dso__load(map->dso, map, filter);
+ if (nr < 0) {
+ if (map->dso->has_build_id) {
+ char sbuild_id[BUILD_ID_SIZE * 2 + 1];
+
+ build_id__sprintf(map->dso->build_id,
+ sizeof(map->dso->build_id),
+ sbuild_id);
+ pr_warning("%s with build id %s not found",
+ name, sbuild_id);
+ } else
+ pr_warning("Failed to open %s", name);
+
+ pr_warning(", continuing without symbols\n");
+ return -1;
+ } else if (nr == 0) {
+#ifdef HAVE_LIBELF_SUPPORT
+ const size_t len = strlen(name);
+ const size_t real_len = len - sizeof(DSO__DELETED);
+
+ if (len > sizeof(DSO__DELETED) &&
+ strcmp(name + real_len + 1, DSO__DELETED) == 0) {
+ pr_warning("%.*s was updated (is prelink enabled?). "
+ "Restart the long running apps that use it!\n",
+ (int)real_len, name);
+ } else {
+ pr_warning("no symbols found in %s, maybe install "
+ "a debug package?\n", name);
+ }
+#endif
+ return -1;
+ }
+
+ return 0;
+}
+
+struct symbol *map__find_symbol(struct map *map, u64 addr,
+ symbol_filter_t filter)
+{
+ if (map__load(map, filter) < 0)
+ return NULL;
+
+ return dso__find_symbol(map->dso, map->type, addr);
+}
+
+struct symbol *map__find_symbol_by_name(struct map *map, const char *name,
+ symbol_filter_t filter)
+{
+ if (map__load(map, filter) < 0)
+ return NULL;
+
+ if (!dso__sorted_by_name(map->dso, map->type))
+ dso__sort_by_name(map->dso, map->type);
+
+ return dso__find_symbol_by_name(map->dso, map->type, name);
+}
+
+struct map *map__clone(struct map *map)
+{
+ return memdup(map, sizeof(*map));
+}
+
+int map__overlap(struct map *l, struct map *r)
+{
+ if (l->start > r->start) {
+ struct map *t = l;
+ l = r;
+ r = t;
+ }
+
+ if (l->end > r->start)
+ return 1;
+
+ return 0;
+}
+
+size_t map__fprintf(struct map *map, FILE *fp)
+{
+ return fprintf(fp, " %" PRIx64 "-%" PRIx64 " %" PRIx64 " %s\n",
+ map->start, map->end, map->pgoff, map->dso->name);
+}
+
+size_t map__fprintf_dsoname(struct map *map, FILE *fp)
+{
+ const char *dsoname = "[unknown]";
+
+ if (map && map->dso && (map->dso->name || map->dso->long_name)) {
+ if (symbol_conf.show_kernel_path && map->dso->long_name)
+ dsoname = map->dso->long_name;
+ else if (map->dso->name)
+ dsoname = map->dso->name;
+ }
+
+ return fprintf(fp, "%s", dsoname);
+}
+
+int map__fprintf_srcline(struct map *map, u64 addr, const char *prefix,
+ FILE *fp)
+{
+ char *srcline;
+ int ret = 0;
+
+ if (map && map->dso) {
+ srcline = get_srcline(map->dso,
+ map__rip_2objdump(map, addr));
+ if (srcline != SRCLINE_UNKNOWN)
+ ret = fprintf(fp, "%s%s", prefix, srcline);
+ free_srcline(srcline);
+ }
+ return ret;
+}
+
+/**
+ * map__rip_2objdump - convert symbol start address to objdump address.
+ * @map: memory map
+ * @rip: symbol start address
+ *
+ * objdump wants/reports absolute IPs for ET_EXEC, and RIPs for ET_DYN.
+ * map->dso->adjust_symbols==1 for ET_EXEC-like cases except ET_REL which is
+ * relative to section start.
+ *
+ * Return: Address suitable for passing to "objdump --start-address="
+ */
+u64 map__rip_2objdump(struct map *map, u64 rip)
+{
+ if (!map->dso->adjust_symbols)
+ return rip;
+
+ if (map->dso->rel)
+ return rip - map->pgoff;
+
+ return map->unmap_ip(map, rip) - map->reloc;
+}
+
+/**
+ * map__objdump_2mem - convert objdump address to a memory address.
+ * @map: memory map
+ * @ip: objdump address
+ *
+ * Closely related to map__rip_2objdump(), this function takes an address from
+ * objdump and converts it to a memory address. Note this assumes that @map
+ * contains the address. To be sure the result is valid, check it forwards
+ * e.g. map__rip_2objdump(map->map_ip(map, map__objdump_2mem(map, ip))) == ip
+ *
+ * Return: Memory address.
+ */
+u64 map__objdump_2mem(struct map *map, u64 ip)
+{
+ if (!map->dso->adjust_symbols)
+ return map->unmap_ip(map, ip);
+
+ if (map->dso->rel)
+ return map->unmap_ip(map, ip + map->pgoff);
+
+ return ip + map->reloc;
+}
+
+void map_groups__init(struct map_groups *mg)
+{
+ int i;
+ for (i = 0; i < MAP__NR_TYPES; ++i) {
+ mg->maps[i] = RB_ROOT;
+ INIT_LIST_HEAD(&mg->removed_maps[i]);
+ }
+ mg->machine = NULL;
+ mg->refcnt = 1;
+}
+
+static void maps__delete(struct rb_root *maps)
+{
+ struct rb_node *next = rb_first(maps);
+
+ while (next) {
+ struct map *pos = rb_entry(next, struct map, rb_node);
+
+ next = rb_next(&pos->rb_node);
+ rb_erase(&pos->rb_node, maps);
+ map__delete(pos);
+ }
+}
+
+static void maps__delete_removed(struct list_head *maps)
+{
+ struct map *pos, *n;
+
+ list_for_each_entry_safe(pos, n, maps, node) {
+ list_del(&pos->node);
+ map__delete(pos);
+ }
+}
+
+void map_groups__exit(struct map_groups *mg)
+{
+ int i;
+
+ for (i = 0; i < MAP__NR_TYPES; ++i) {
+ maps__delete(&mg->maps[i]);
+ maps__delete_removed(&mg->removed_maps[i]);
+ }
+}
+
+struct map_groups *map_groups__new(void)
+{
+ struct map_groups *mg = malloc(sizeof(*mg));
+
+ if (mg != NULL)
+ map_groups__init(mg);
+
+ return mg;
+}
+
+void map_groups__delete(struct map_groups *mg)
+{
+ map_groups__exit(mg);
+ free(mg);
+}
+
+void map_groups__put(struct map_groups *mg)
+{
+ if (--mg->refcnt == 0)
+ map_groups__delete(mg);
+}
+
+void map_groups__flush(struct map_groups *mg)
+{
+ int type;
+
+ for (type = 0; type < MAP__NR_TYPES; type++) {
+ struct rb_root *root = &mg->maps[type];
+ struct rb_node *next = rb_first(root);
+
+ while (next) {
+ struct map *pos = rb_entry(next, struct map, rb_node);
+ next = rb_next(&pos->rb_node);
+ rb_erase(&pos->rb_node, root);
+ /*
+ * We may have references to this map, for
+ * instance in some hist_entry instances, so
+ * just move them to a separate list.
+ */
+ list_add_tail(&pos->node, &mg->removed_maps[pos->type]);
+ }
+ }
+}
+
+struct symbol *map_groups__find_symbol(struct map_groups *mg,
+ enum map_type type, u64 addr,
+ struct map **mapp,
+ symbol_filter_t filter)
+{
+ struct map *map = map_groups__find(mg, type, addr);
+
+ /* Ensure map is loaded before using map->map_ip */
+ if (map != NULL && map__load(map, filter) >= 0) {
+ if (mapp != NULL)
+ *mapp = map;
+ return map__find_symbol(map, map->map_ip(map, addr), filter);
+ }
+
+ return NULL;
+}
+
+struct symbol *map_groups__find_symbol_by_name(struct map_groups *mg,
+ enum map_type type,
+ const char *name,
+ struct map **mapp,
+ symbol_filter_t filter)
+{
+ struct rb_node *nd;
+
+ for (nd = rb_first(&mg->maps[type]); nd; nd = rb_next(nd)) {
+ struct map *pos = rb_entry(nd, struct map, rb_node);
+ struct symbol *sym = map__find_symbol_by_name(pos, name, filter);
+
+ if (sym == NULL)
+ continue;
+ if (mapp != NULL)
+ *mapp = pos;
+ return sym;
+ }
+
+ return NULL;
+}
+
+int map_groups__find_ams(struct addr_map_symbol *ams, symbol_filter_t filter)
+{
+ if (ams->addr < ams->map->start || ams->addr > ams->map->end) {
+ if (ams->map->groups == NULL)
+ return -1;
+ ams->map = map_groups__find(ams->map->groups, ams->map->type,
+ ams->addr);
+ if (ams->map == NULL)
+ return -1;
+ }
+
+ ams->al_addr = ams->map->map_ip(ams->map, ams->addr);
+ ams->sym = map__find_symbol(ams->map, ams->al_addr, filter);
+
+ return ams->sym ? 0 : -1;
+}
+
+size_t __map_groups__fprintf_maps(struct map_groups *mg,
+ enum map_type type, int verbose, FILE *fp)
+{
+ size_t printed = fprintf(fp, "%s:\n", map_type__name[type]);
+ struct rb_node *nd;
+
+ for (nd = rb_first(&mg->maps[type]); nd; nd = rb_next(nd)) {
+ struct map *pos = rb_entry(nd, struct map, rb_node);
+ printed += fprintf(fp, "Map:");
+ printed += map__fprintf(pos, fp);
+ if (verbose > 2) {
+ printed += dso__fprintf(pos->dso, type, fp);
+ printed += fprintf(fp, "--\n");
+ }
+ }
+
+ return printed;
+}
+
+size_t map_groups__fprintf_maps(struct map_groups *mg, int verbose, FILE *fp)
+{
+ size_t printed = 0, i;
+ for (i = 0; i < MAP__NR_TYPES; ++i)
+ printed += __map_groups__fprintf_maps(mg, i, verbose, fp);
+ return printed;
+}
+
+static size_t __map_groups__fprintf_removed_maps(struct map_groups *mg,
+ enum map_type type,
+ int verbose, FILE *fp)
+{
+ struct map *pos;
+ size_t printed = 0;
+
+ list_for_each_entry(pos, &mg->removed_maps[type], node) {
+ printed += fprintf(fp, "Map:");
+ printed += map__fprintf(pos, fp);
+ if (verbose > 1) {
+ printed += dso__fprintf(pos->dso, type, fp);
+ printed += fprintf(fp, "--\n");
+ }
+ }
+ return printed;
+}
+
+static size_t map_groups__fprintf_removed_maps(struct map_groups *mg,
+ int verbose, FILE *fp)
+{
+ size_t printed = 0, i;
+ for (i = 0; i < MAP__NR_TYPES; ++i)
+ printed += __map_groups__fprintf_removed_maps(mg, i, verbose, fp);
+ return printed;
+}
+
+size_t map_groups__fprintf(struct map_groups *mg, int verbose, FILE *fp)
+{
+ size_t printed = map_groups__fprintf_maps(mg, verbose, fp);
+ printed += fprintf(fp, "Removed maps:\n");
+ return printed + map_groups__fprintf_removed_maps(mg, verbose, fp);
+}
+
+int map_groups__fixup_overlappings(struct map_groups *mg, struct map *map,
+ int verbose, FILE *fp)
+{
+ struct rb_root *root = &mg->maps[map->type];
+ struct rb_node *next = rb_first(root);
+ int err = 0;
+
+ while (next) {
+ struct map *pos = rb_entry(next, struct map, rb_node);
+ next = rb_next(&pos->rb_node);
+
+ if (!map__overlap(pos, map))
+ continue;
+
+ if (verbose >= 2) {
+ fputs("overlapping maps:\n", fp);
+ map__fprintf(map, fp);
+ map__fprintf(pos, fp);
+ }
+
+ rb_erase(&pos->rb_node, root);
+ /*
+ * Now check if we need to create new maps for areas not
+ * overlapped by the new map:
+ */
+ if (map->start > pos->start) {
+ struct map *before = map__clone(pos);
+
+ if (before == NULL) {
+ err = -ENOMEM;
+ goto move_map;
+ }
+
+ before->end = map->start - 1;
+ map_groups__insert(mg, before);
+ if (verbose >= 2)
+ map__fprintf(before, fp);
+ }
+
+ if (map->end < pos->end) {
+ struct map *after = map__clone(pos);
+
+ if (after == NULL) {
+ err = -ENOMEM;
+ goto move_map;
+ }
+
+ after->start = map->end + 1;
+ map_groups__insert(mg, after);
+ if (verbose >= 2)
+ map__fprintf(after, fp);
+ }
+move_map:
+ /*
+ * If we have references, just move them to a separate list.
+ */
+ if (pos->referenced)
+ list_add_tail(&pos->node, &mg->removed_maps[map->type]);
+ else
+ map__delete(pos);
+
+ if (err)
+ return err;
+ }
+
+ return 0;
+}
+
+/*
+ * XXX This should not really _copy_ te maps, but refcount them.
+ */
+int map_groups__clone(struct map_groups *mg,
+ struct map_groups *parent, enum map_type type)
+{
+ struct rb_node *nd;
+ for (nd = rb_first(&parent->maps[type]); nd; nd = rb_next(nd)) {
+ struct map *map = rb_entry(nd, struct map, rb_node);
+ struct map *new = map__clone(map);
+ if (new == NULL)
+ return -ENOMEM;
+ map_groups__insert(mg, new);
+ }
+ return 0;
+}
+
+void maps__insert(struct rb_root *maps, struct map *map)
+{
+ struct rb_node **p = &maps->rb_node;
+ struct rb_node *parent = NULL;
+ const u64 ip = map->start;
+ struct map *m;
+
+ while (*p != NULL) {
+ parent = *p;
+ m = rb_entry(parent, struct map, rb_node);
+ if (ip < m->start)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+
+ rb_link_node(&map->rb_node, parent, p);
+ rb_insert_color(&map->rb_node, maps);
+}
+
+void maps__remove(struct rb_root *maps, struct map *map)
+{
+ rb_erase(&map->rb_node, maps);
+}
+
+struct map *maps__find(struct rb_root *maps, u64 ip)
+{
+ struct rb_node **p = &maps->rb_node;
+ struct rb_node *parent = NULL;
+ struct map *m;
+
+ while (*p != NULL) {
+ parent = *p;
+ m = rb_entry(parent, struct map, rb_node);
+ if (ip < m->start)
+ p = &(*p)->rb_left;
+ else if (ip > m->end)
+ p = &(*p)->rb_right;
+ else
+ return m;
+ }
+
+ return NULL;
+}
+
+struct map *maps__first(struct rb_root *maps)
+{
+ struct rb_node *first = rb_first(maps);
+
+ if (first)
+ return rb_entry(first, struct map, rb_node);
+ return NULL;
+}
+
+struct map *maps__next(struct map *map)
+{
+ struct rb_node *next = rb_next(&map->rb_node);
+
+ if (next)
+ return rb_entry(next, struct map, rb_node);
+ return NULL;
+}
diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h
new file mode 100644
index 00000000000..7758c72522e
--- /dev/null
+++ b/tools/perf/util/map.h
@@ -0,0 +1,220 @@
+#ifndef __PERF_MAP_H
+#define __PERF_MAP_H
+
+#include <linux/compiler.h>
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <stdio.h>
+#include <stdbool.h>
+#include <linux/types.h>
+
+enum map_type {
+ MAP__FUNCTION = 0,
+ MAP__VARIABLE,
+};
+
+#define MAP__NR_TYPES (MAP__VARIABLE + 1)
+
+extern const char *map_type__name[MAP__NR_TYPES];
+
+struct dso;
+struct ip_callchain;
+struct ref_reloc_sym;
+struct map_groups;
+struct machine;
+struct perf_evsel;
+
+struct map {
+ union {
+ struct rb_node rb_node;
+ struct list_head node;
+ };
+ u64 start;
+ u64 end;
+ u8 /* enum map_type */ type;
+ bool referenced;
+ bool erange_warned;
+ u32 priv;
+ u32 prot;
+ u32 flags;
+ u64 pgoff;
+ u64 reloc;
+ u32 maj, min; /* only valid for MMAP2 record */
+ u64 ino; /* only valid for MMAP2 record */
+ u64 ino_generation;/* only valid for MMAP2 record */
+
+ /* ip -> dso rip */
+ u64 (*map_ip)(struct map *, u64);
+ /* dso rip -> ip */
+ u64 (*unmap_ip)(struct map *, u64);
+
+ struct dso *dso;
+ struct map_groups *groups;
+};
+
+struct kmap {
+ struct ref_reloc_sym *ref_reloc_sym;
+ struct map_groups *kmaps;
+};
+
+struct map_groups {
+ struct rb_root maps[MAP__NR_TYPES];
+ struct list_head removed_maps[MAP__NR_TYPES];
+ struct machine *machine;
+ int refcnt;
+};
+
+struct map_groups *map_groups__new(void);
+void map_groups__delete(struct map_groups *mg);
+
+static inline struct map_groups *map_groups__get(struct map_groups *mg)
+{
+ ++mg->refcnt;
+ return mg;
+}
+
+void map_groups__put(struct map_groups *mg);
+
+static inline struct kmap *map__kmap(struct map *map)
+{
+ return (struct kmap *)(map + 1);
+}
+
+static inline u64 map__map_ip(struct map *map, u64 ip)
+{
+ return ip - map->start + map->pgoff;
+}
+
+static inline u64 map__unmap_ip(struct map *map, u64 ip)
+{
+ return ip + map->start - map->pgoff;
+}
+
+static inline u64 identity__map_ip(struct map *map __maybe_unused, u64 ip)
+{
+ return ip;
+}
+
+
+/* rip/ip <-> addr suitable for passing to `objdump --start-address=` */
+u64 map__rip_2objdump(struct map *map, u64 rip);
+
+/* objdump address -> memory address */
+u64 map__objdump_2mem(struct map *map, u64 ip);
+
+struct symbol;
+
+/* map__for_each_symbol - iterate over the symbols in the given map
+ *
+ * @map: the 'struct map *' in which symbols itereated
+ * @pos: the 'struct symbol *' to use as a loop cursor
+ * @n: the 'struct rb_node *' to use as a temporary storage
+ * Note: caller must ensure map->dso is not NULL (map is loaded).
+ */
+#define map__for_each_symbol(map, pos, n) \
+ dso__for_each_symbol(map->dso, pos, n, map->type)
+
+typedef int (*symbol_filter_t)(struct map *map, struct symbol *sym);
+
+void map__init(struct map *map, enum map_type type,
+ u64 start, u64 end, u64 pgoff, struct dso *dso);
+struct map *map__new(struct list_head *dsos__list, u64 start, u64 len,
+ u64 pgoff, u32 pid, u32 d_maj, u32 d_min, u64 ino,
+ u64 ino_gen, u32 prot, u32 flags,
+ char *filename, enum map_type type);
+struct map *map__new2(u64 start, struct dso *dso, enum map_type type);
+void map__delete(struct map *map);
+struct map *map__clone(struct map *map);
+int map__overlap(struct map *l, struct map *r);
+size_t map__fprintf(struct map *map, FILE *fp);
+size_t map__fprintf_dsoname(struct map *map, FILE *fp);
+int map__fprintf_srcline(struct map *map, u64 addr, const char *prefix,
+ FILE *fp);
+
+int map__load(struct map *map, symbol_filter_t filter);
+struct symbol *map__find_symbol(struct map *map,
+ u64 addr, symbol_filter_t filter);
+struct symbol *map__find_symbol_by_name(struct map *map, const char *name,
+ symbol_filter_t filter);
+void map__fixup_start(struct map *map);
+void map__fixup_end(struct map *map);
+
+void map__reloc_vmlinux(struct map *map);
+
+size_t __map_groups__fprintf_maps(struct map_groups *mg,
+ enum map_type type, int verbose, FILE *fp);
+void maps__insert(struct rb_root *maps, struct map *map);
+void maps__remove(struct rb_root *maps, struct map *map);
+struct map *maps__find(struct rb_root *maps, u64 addr);
+struct map *maps__first(struct rb_root *maps);
+struct map *maps__next(struct map *map);
+void map_groups__init(struct map_groups *mg);
+void map_groups__exit(struct map_groups *mg);
+int map_groups__clone(struct map_groups *mg,
+ struct map_groups *parent, enum map_type type);
+size_t map_groups__fprintf(struct map_groups *mg, int verbose, FILE *fp);
+size_t map_groups__fprintf_maps(struct map_groups *mg, int verbose, FILE *fp);
+
+int maps__set_kallsyms_ref_reloc_sym(struct map **maps, const char *symbol_name,
+ u64 addr);
+
+static inline void map_groups__insert(struct map_groups *mg, struct map *map)
+{
+ maps__insert(&mg->maps[map->type], map);
+ map->groups = mg;
+}
+
+static inline void map_groups__remove(struct map_groups *mg, struct map *map)
+{
+ maps__remove(&mg->maps[map->type], map);
+}
+
+static inline struct map *map_groups__find(struct map_groups *mg,
+ enum map_type type, u64 addr)
+{
+ return maps__find(&mg->maps[type], addr);
+}
+
+static inline struct map *map_groups__first(struct map_groups *mg,
+ enum map_type type)
+{
+ return maps__first(&mg->maps[type]);
+}
+
+static inline struct map *map_groups__next(struct map *map)
+{
+ return maps__next(map);
+}
+
+struct symbol *map_groups__find_symbol(struct map_groups *mg,
+ enum map_type type, u64 addr,
+ struct map **mapp,
+ symbol_filter_t filter);
+
+struct symbol *map_groups__find_symbol_by_name(struct map_groups *mg,
+ enum map_type type,
+ const char *name,
+ struct map **mapp,
+ symbol_filter_t filter);
+
+struct addr_map_symbol;
+
+int map_groups__find_ams(struct addr_map_symbol *ams, symbol_filter_t filter);
+
+static inline
+struct symbol *map_groups__find_function_by_name(struct map_groups *mg,
+ const char *name, struct map **mapp,
+ symbol_filter_t filter)
+{
+ return map_groups__find_symbol_by_name(mg, MAP__FUNCTION, name, mapp, filter);
+}
+
+int map_groups__fixup_overlappings(struct map_groups *mg, struct map *map,
+ int verbose, FILE *fp);
+
+struct map *map_groups__find_by_name(struct map_groups *mg,
+ enum map_type type, const char *name);
+
+void map_groups__flush(struct map_groups *mg);
+
+#endif /* __PERF_MAP_H */
diff --git a/tools/perf/util/pager.c b/tools/perf/util/pager.c
new file mode 100644
index 00000000000..31ee02d4e98
--- /dev/null
+++ b/tools/perf/util/pager.c
@@ -0,0 +1,100 @@
+#include "cache.h"
+#include "run-command.h"
+#include "sigchain.h"
+
+/*
+ * This is split up from the rest of git so that we can do
+ * something different on Windows.
+ */
+
+static int spawned_pager;
+
+static void pager_preexec(void)
+{
+ /*
+ * Work around bug in "less" by not starting it until we
+ * have real input
+ */
+ fd_set in;
+
+ FD_ZERO(&in);
+ FD_SET(0, &in);
+ select(1, &in, NULL, &in, NULL);
+
+ setenv("LESS", "FRSX", 0);
+}
+
+static const char *pager_argv[] = { "sh", "-c", NULL, NULL };
+static struct child_process pager_process;
+
+static void wait_for_pager(void)
+{
+ fflush(stdout);
+ fflush(stderr);
+ /* signal EOF to pager */
+ close(1);
+ close(2);
+ finish_command(&pager_process);
+}
+
+static void wait_for_pager_signal(int signo)
+{
+ wait_for_pager();
+ sigchain_pop(signo);
+ raise(signo);
+}
+
+void setup_pager(void)
+{
+ const char *pager = getenv("PERF_PAGER");
+
+ if (!isatty(1))
+ return;
+ if (!pager) {
+ if (!pager_program)
+ perf_config(perf_default_config, NULL);
+ pager = pager_program;
+ }
+ if (!pager)
+ pager = getenv("PAGER");
+ if (!(pager || access("/usr/bin/pager", X_OK)))
+ pager = "/usr/bin/pager";
+ if (!(pager || access("/usr/bin/less", X_OK)))
+ pager = "/usr/bin/less";
+ if (!pager)
+ pager = "cat";
+ if (!*pager || !strcmp(pager, "cat"))
+ return;
+
+ spawned_pager = 1; /* means we are emitting to terminal */
+
+ /* spawn the pager */
+ pager_argv[2] = pager;
+ pager_process.argv = pager_argv;
+ pager_process.in = -1;
+ pager_process.preexec_cb = pager_preexec;
+
+ if (start_command(&pager_process))
+ return;
+
+ /* original process continues, but writes to the pipe */
+ dup2(pager_process.in, 1);
+ if (isatty(2))
+ dup2(pager_process.in, 2);
+ close(pager_process.in);
+
+ /* this makes sure that the parent terminates after the pager */
+ sigchain_push_common(wait_for_pager_signal);
+ atexit(wait_for_pager);
+}
+
+int pager_in_use(void)
+{
+ const char *env;
+
+ if (spawned_pager)
+ return 1;
+
+ env = getenv("PERF_PAGER_IN_USE");
+ return env ? perf_config_bool("PERF_PAGER_IN_USE", env) : 0;
+}
diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c
new file mode 100644
index 00000000000..1e15df10a88
--- /dev/null
+++ b/tools/perf/util/parse-events.c
@@ -0,0 +1,1354 @@
+#include <linux/hw_breakpoint.h>
+#include "util.h"
+#include "../perf.h"
+#include "evlist.h"
+#include "evsel.h"
+#include "parse-options.h"
+#include "parse-events.h"
+#include "exec_cmd.h"
+#include "linux/string.h"
+#include "symbol.h"
+#include "cache.h"
+#include "header.h"
+#include <api/fs/debugfs.h>
+#include "parse-events-bison.h"
+#define YY_EXTRA_TYPE int
+#include "parse-events-flex.h"
+#include "pmu.h"
+#include "thread_map.h"
+
+#define MAX_NAME_LEN 100
+
+struct event_symbol {
+ const char *symbol;
+ const char *alias;
+};
+
+#ifdef PARSER_DEBUG
+extern int parse_events_debug;
+#endif
+int parse_events_parse(void *data, void *scanner);
+
+static struct event_symbol event_symbols_hw[PERF_COUNT_HW_MAX] = {
+ [PERF_COUNT_HW_CPU_CYCLES] = {
+ .symbol = "cpu-cycles",
+ .alias = "cycles",
+ },
+ [PERF_COUNT_HW_INSTRUCTIONS] = {
+ .symbol = "instructions",
+ .alias = "",
+ },
+ [PERF_COUNT_HW_CACHE_REFERENCES] = {
+ .symbol = "cache-references",
+ .alias = "",
+ },
+ [PERF_COUNT_HW_CACHE_MISSES] = {
+ .symbol = "cache-misses",
+ .alias = "",
+ },
+ [PERF_COUNT_HW_BRANCH_INSTRUCTIONS] = {
+ .symbol = "branch-instructions",
+ .alias = "branches",
+ },
+ [PERF_COUNT_HW_BRANCH_MISSES] = {
+ .symbol = "branch-misses",
+ .alias = "",
+ },
+ [PERF_COUNT_HW_BUS_CYCLES] = {
+ .symbol = "bus-cycles",
+ .alias = "",
+ },
+ [PERF_COUNT_HW_STALLED_CYCLES_FRONTEND] = {
+ .symbol = "stalled-cycles-frontend",
+ .alias = "idle-cycles-frontend",
+ },
+ [PERF_COUNT_HW_STALLED_CYCLES_BACKEND] = {
+ .symbol = "stalled-cycles-backend",
+ .alias = "idle-cycles-backend",
+ },
+ [PERF_COUNT_HW_REF_CPU_CYCLES] = {
+ .symbol = "ref-cycles",
+ .alias = "",
+ },
+};
+
+static struct event_symbol event_symbols_sw[PERF_COUNT_SW_MAX] = {
+ [PERF_COUNT_SW_CPU_CLOCK] = {
+ .symbol = "cpu-clock",
+ .alias = "",
+ },
+ [PERF_COUNT_SW_TASK_CLOCK] = {
+ .symbol = "task-clock",
+ .alias = "",
+ },
+ [PERF_COUNT_SW_PAGE_FAULTS] = {
+ .symbol = "page-faults",
+ .alias = "faults",
+ },
+ [PERF_COUNT_SW_CONTEXT_SWITCHES] = {
+ .symbol = "context-switches",
+ .alias = "cs",
+ },
+ [PERF_COUNT_SW_CPU_MIGRATIONS] = {
+ .symbol = "cpu-migrations",
+ .alias = "migrations",
+ },
+ [PERF_COUNT_SW_PAGE_FAULTS_MIN] = {
+ .symbol = "minor-faults",
+ .alias = "",
+ },
+ [PERF_COUNT_SW_PAGE_FAULTS_MAJ] = {
+ .symbol = "major-faults",
+ .alias = "",
+ },
+ [PERF_COUNT_SW_ALIGNMENT_FAULTS] = {
+ .symbol = "alignment-faults",
+ .alias = "",
+ },
+ [PERF_COUNT_SW_EMULATION_FAULTS] = {
+ .symbol = "emulation-faults",
+ .alias = "",
+ },
+ [PERF_COUNT_SW_DUMMY] = {
+ .symbol = "dummy",
+ .alias = "",
+ },
+};
+
+#define __PERF_EVENT_FIELD(config, name) \
+ ((config & PERF_EVENT_##name##_MASK) >> PERF_EVENT_##name##_SHIFT)
+
+#define PERF_EVENT_RAW(config) __PERF_EVENT_FIELD(config, RAW)
+#define PERF_EVENT_CONFIG(config) __PERF_EVENT_FIELD(config, CONFIG)
+#define PERF_EVENT_TYPE(config) __PERF_EVENT_FIELD(config, TYPE)
+#define PERF_EVENT_ID(config) __PERF_EVENT_FIELD(config, EVENT)
+
+#define for_each_subsystem(sys_dir, sys_dirent, sys_next) \
+ while (!readdir_r(sys_dir, &sys_dirent, &sys_next) && sys_next) \
+ if (sys_dirent.d_type == DT_DIR && \
+ (strcmp(sys_dirent.d_name, ".")) && \
+ (strcmp(sys_dirent.d_name, "..")))
+
+static int tp_event_has_id(struct dirent *sys_dir, struct dirent *evt_dir)
+{
+ char evt_path[MAXPATHLEN];
+ int fd;
+
+ snprintf(evt_path, MAXPATHLEN, "%s/%s/%s/id", tracing_events_path,
+ sys_dir->d_name, evt_dir->d_name);
+ fd = open(evt_path, O_RDONLY);
+ if (fd < 0)
+ return -EINVAL;
+ close(fd);
+
+ return 0;
+}
+
+#define for_each_event(sys_dirent, evt_dir, evt_dirent, evt_next) \
+ while (!readdir_r(evt_dir, &evt_dirent, &evt_next) && evt_next) \
+ if (evt_dirent.d_type == DT_DIR && \
+ (strcmp(evt_dirent.d_name, ".")) && \
+ (strcmp(evt_dirent.d_name, "..")) && \
+ (!tp_event_has_id(&sys_dirent, &evt_dirent)))
+
+#define MAX_EVENT_LENGTH 512
+
+
+struct tracepoint_path *tracepoint_id_to_path(u64 config)
+{
+ struct tracepoint_path *path = NULL;
+ DIR *sys_dir, *evt_dir;
+ struct dirent *sys_next, *evt_next, sys_dirent, evt_dirent;
+ char id_buf[24];
+ int fd;
+ u64 id;
+ char evt_path[MAXPATHLEN];
+ char dir_path[MAXPATHLEN];
+
+ if (debugfs_valid_mountpoint(tracing_events_path))
+ return NULL;
+
+ sys_dir = opendir(tracing_events_path);
+ if (!sys_dir)
+ return NULL;
+
+ for_each_subsystem(sys_dir, sys_dirent, sys_next) {
+
+ snprintf(dir_path, MAXPATHLEN, "%s/%s", tracing_events_path,
+ sys_dirent.d_name);
+ evt_dir = opendir(dir_path);
+ if (!evt_dir)
+ continue;
+
+ for_each_event(sys_dirent, evt_dir, evt_dirent, evt_next) {
+
+ snprintf(evt_path, MAXPATHLEN, "%s/%s/id", dir_path,
+ evt_dirent.d_name);
+ fd = open(evt_path, O_RDONLY);
+ if (fd < 0)
+ continue;
+ if (read(fd, id_buf, sizeof(id_buf)) < 0) {
+ close(fd);
+ continue;
+ }
+ close(fd);
+ id = atoll(id_buf);
+ if (id == config) {
+ closedir(evt_dir);
+ closedir(sys_dir);
+ path = zalloc(sizeof(*path));
+ path->system = malloc(MAX_EVENT_LENGTH);
+ if (!path->system) {
+ free(path);
+ return NULL;
+ }
+ path->name = malloc(MAX_EVENT_LENGTH);
+ if (!path->name) {
+ zfree(&path->system);
+ free(path);
+ return NULL;
+ }
+ strncpy(path->system, sys_dirent.d_name,
+ MAX_EVENT_LENGTH);
+ strncpy(path->name, evt_dirent.d_name,
+ MAX_EVENT_LENGTH);
+ return path;
+ }
+ }
+ closedir(evt_dir);
+ }
+
+ closedir(sys_dir);
+ return NULL;
+}
+
+struct tracepoint_path *tracepoint_name_to_path(const char *name)
+{
+ struct tracepoint_path *path = zalloc(sizeof(*path));
+ char *str = strchr(name, ':');
+
+ if (path == NULL || str == NULL) {
+ free(path);
+ return NULL;
+ }
+
+ path->system = strndup(name, str - name);
+ path->name = strdup(str+1);
+
+ if (path->system == NULL || path->name == NULL) {
+ zfree(&path->system);
+ zfree(&path->name);
+ free(path);
+ path = NULL;
+ }
+
+ return path;
+}
+
+const char *event_type(int type)
+{
+ switch (type) {
+ case PERF_TYPE_HARDWARE:
+ return "hardware";
+
+ case PERF_TYPE_SOFTWARE:
+ return "software";
+
+ case PERF_TYPE_TRACEPOINT:
+ return "tracepoint";
+
+ case PERF_TYPE_HW_CACHE:
+ return "hardware-cache";
+
+ default:
+ break;
+ }
+
+ return "unknown";
+}
+
+
+
+static struct perf_evsel *
+__add_event(struct list_head *list, int *idx,
+ struct perf_event_attr *attr,
+ char *name, struct cpu_map *cpus)
+{
+ struct perf_evsel *evsel;
+
+ event_attr_init(attr);
+
+ evsel = perf_evsel__new_idx(attr, (*idx)++);
+ if (!evsel)
+ return NULL;
+
+ evsel->cpus = cpus;
+ if (name)
+ evsel->name = strdup(name);
+ list_add_tail(&evsel->node, list);
+ return evsel;
+}
+
+static int add_event(struct list_head *list, int *idx,
+ struct perf_event_attr *attr, char *name)
+{
+ return __add_event(list, idx, attr, name, NULL) ? 0 : -ENOMEM;
+}
+
+static int parse_aliases(char *str, const char *names[][PERF_EVSEL__MAX_ALIASES], int size)
+{
+ int i, j;
+ int n, longest = -1;
+
+ for (i = 0; i < size; i++) {
+ for (j = 0; j < PERF_EVSEL__MAX_ALIASES && names[i][j]; j++) {
+ n = strlen(names[i][j]);
+ if (n > longest && !strncasecmp(str, names[i][j], n))
+ longest = n;
+ }
+ if (longest > 0)
+ return i;
+ }
+
+ return -1;
+}
+
+int parse_events_add_cache(struct list_head *list, int *idx,
+ char *type, char *op_result1, char *op_result2)
+{
+ struct perf_event_attr attr;
+ char name[MAX_NAME_LEN];
+ int cache_type = -1, cache_op = -1, cache_result = -1;
+ char *op_result[2] = { op_result1, op_result2 };
+ int i, n;
+
+ /*
+ * No fallback - if we cannot get a clear cache type
+ * then bail out:
+ */
+ cache_type = parse_aliases(type, perf_evsel__hw_cache,
+ PERF_COUNT_HW_CACHE_MAX);
+ if (cache_type == -1)
+ return -EINVAL;
+
+ n = snprintf(name, MAX_NAME_LEN, "%s", type);
+
+ for (i = 0; (i < 2) && (op_result[i]); i++) {
+ char *str = op_result[i];
+
+ n += snprintf(name + n, MAX_NAME_LEN - n, "-%s", str);
+
+ if (cache_op == -1) {
+ cache_op = parse_aliases(str, perf_evsel__hw_cache_op,
+ PERF_COUNT_HW_CACHE_OP_MAX);
+ if (cache_op >= 0) {
+ if (!perf_evsel__is_cache_op_valid(cache_type, cache_op))
+ return -EINVAL;
+ continue;
+ }
+ }
+
+ if (cache_result == -1) {
+ cache_result = parse_aliases(str, perf_evsel__hw_cache_result,
+ PERF_COUNT_HW_CACHE_RESULT_MAX);
+ if (cache_result >= 0)
+ continue;
+ }
+ }
+
+ /*
+ * Fall back to reads:
+ */
+ if (cache_op == -1)
+ cache_op = PERF_COUNT_HW_CACHE_OP_READ;
+
+ /*
+ * Fall back to accesses:
+ */
+ if (cache_result == -1)
+ cache_result = PERF_COUNT_HW_CACHE_RESULT_ACCESS;
+
+ memset(&attr, 0, sizeof(attr));
+ attr.config = cache_type | (cache_op << 8) | (cache_result << 16);
+ attr.type = PERF_TYPE_HW_CACHE;
+ return add_event(list, idx, &attr, name);
+}
+
+static int add_tracepoint(struct list_head *list, int *idx,
+ char *sys_name, char *evt_name)
+{
+ struct perf_evsel *evsel;
+
+ evsel = perf_evsel__newtp_idx(sys_name, evt_name, (*idx)++);
+ if (!evsel)
+ return -ENOMEM;
+
+ list_add_tail(&evsel->node, list);
+
+ return 0;
+}
+
+static int add_tracepoint_multi_event(struct list_head *list, int *idx,
+ char *sys_name, char *evt_name)
+{
+ char evt_path[MAXPATHLEN];
+ struct dirent *evt_ent;
+ DIR *evt_dir;
+ int ret = 0;
+
+ snprintf(evt_path, MAXPATHLEN, "%s/%s", tracing_events_path, sys_name);
+ evt_dir = opendir(evt_path);
+ if (!evt_dir) {
+ perror("Can't open event dir");
+ return -1;
+ }
+
+ while (!ret && (evt_ent = readdir(evt_dir))) {
+ if (!strcmp(evt_ent->d_name, ".")
+ || !strcmp(evt_ent->d_name, "..")
+ || !strcmp(evt_ent->d_name, "enable")
+ || !strcmp(evt_ent->d_name, "filter"))
+ continue;
+
+ if (!strglobmatch(evt_ent->d_name, evt_name))
+ continue;
+
+ ret = add_tracepoint(list, idx, sys_name, evt_ent->d_name);
+ }
+
+ closedir(evt_dir);
+ return ret;
+}
+
+static int add_tracepoint_event(struct list_head *list, int *idx,
+ char *sys_name, char *evt_name)
+{
+ return strpbrk(evt_name, "*?") ?
+ add_tracepoint_multi_event(list, idx, sys_name, evt_name) :
+ add_tracepoint(list, idx, sys_name, evt_name);
+}
+
+static int add_tracepoint_multi_sys(struct list_head *list, int *idx,
+ char *sys_name, char *evt_name)
+{
+ struct dirent *events_ent;
+ DIR *events_dir;
+ int ret = 0;
+
+ events_dir = opendir(tracing_events_path);
+ if (!events_dir) {
+ perror("Can't open event dir");
+ return -1;
+ }
+
+ while (!ret && (events_ent = readdir(events_dir))) {
+ if (!strcmp(events_ent->d_name, ".")
+ || !strcmp(events_ent->d_name, "..")
+ || !strcmp(events_ent->d_name, "enable")
+ || !strcmp(events_ent->d_name, "header_event")
+ || !strcmp(events_ent->d_name, "header_page"))
+ continue;
+
+ if (!strglobmatch(events_ent->d_name, sys_name))
+ continue;
+
+ ret = add_tracepoint_event(list, idx, events_ent->d_name,
+ evt_name);
+ }
+
+ closedir(events_dir);
+ return ret;
+}
+
+int parse_events_add_tracepoint(struct list_head *list, int *idx,
+ char *sys, char *event)
+{
+ int ret;
+
+ ret = debugfs_valid_mountpoint(tracing_events_path);
+ if (ret)
+ return ret;
+
+ if (strpbrk(sys, "*?"))
+ return add_tracepoint_multi_sys(list, idx, sys, event);
+ else
+ return add_tracepoint_event(list, idx, sys, event);
+}
+
+static int
+parse_breakpoint_type(const char *type, struct perf_event_attr *attr)
+{
+ int i;
+
+ for (i = 0; i < 3; i++) {
+ if (!type || !type[i])
+ break;
+
+#define CHECK_SET_TYPE(bit) \
+do { \
+ if (attr->bp_type & bit) \
+ return -EINVAL; \
+ else \
+ attr->bp_type |= bit; \
+} while (0)
+
+ switch (type[i]) {
+ case 'r':
+ CHECK_SET_TYPE(HW_BREAKPOINT_R);
+ break;
+ case 'w':
+ CHECK_SET_TYPE(HW_BREAKPOINT_W);
+ break;
+ case 'x':
+ CHECK_SET_TYPE(HW_BREAKPOINT_X);
+ break;
+ default:
+ return -EINVAL;
+ }
+ }
+
+#undef CHECK_SET_TYPE
+
+ if (!attr->bp_type) /* Default */
+ attr->bp_type = HW_BREAKPOINT_R | HW_BREAKPOINT_W;
+
+ return 0;
+}
+
+int parse_events_add_breakpoint(struct list_head *list, int *idx,
+ void *ptr, char *type)
+{
+ struct perf_event_attr attr;
+
+ memset(&attr, 0, sizeof(attr));
+ attr.bp_addr = (unsigned long) ptr;
+
+ if (parse_breakpoint_type(type, &attr))
+ return -EINVAL;
+
+ /*
+ * We should find a nice way to override the access length
+ * Provide some defaults for now
+ */
+ if (attr.bp_type == HW_BREAKPOINT_X)
+ attr.bp_len = sizeof(long);
+ else
+ attr.bp_len = HW_BREAKPOINT_LEN_4;
+
+ attr.type = PERF_TYPE_BREAKPOINT;
+ attr.sample_period = 1;
+
+ return add_event(list, idx, &attr, NULL);
+}
+
+static int config_term(struct perf_event_attr *attr,
+ struct parse_events_term *term)
+{
+#define CHECK_TYPE_VAL(type) \
+do { \
+ if (PARSE_EVENTS__TERM_TYPE_ ## type != term->type_val) \
+ return -EINVAL; \
+} while (0)
+
+ switch (term->type_term) {
+ case PARSE_EVENTS__TERM_TYPE_CONFIG:
+ CHECK_TYPE_VAL(NUM);
+ attr->config = term->val.num;
+ break;
+ case PARSE_EVENTS__TERM_TYPE_CONFIG1:
+ CHECK_TYPE_VAL(NUM);
+ attr->config1 = term->val.num;
+ break;
+ case PARSE_EVENTS__TERM_TYPE_CONFIG2:
+ CHECK_TYPE_VAL(NUM);
+ attr->config2 = term->val.num;
+ break;
+ case PARSE_EVENTS__TERM_TYPE_SAMPLE_PERIOD:
+ CHECK_TYPE_VAL(NUM);
+ attr->sample_period = term->val.num;
+ break;
+ case PARSE_EVENTS__TERM_TYPE_BRANCH_SAMPLE_TYPE:
+ /*
+ * TODO uncomment when the field is available
+ * attr->branch_sample_type = term->val.num;
+ */
+ break;
+ case PARSE_EVENTS__TERM_TYPE_NAME:
+ CHECK_TYPE_VAL(STR);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ return 0;
+#undef CHECK_TYPE_VAL
+}
+
+static int config_attr(struct perf_event_attr *attr,
+ struct list_head *head, int fail)
+{
+ struct parse_events_term *term;
+
+ list_for_each_entry(term, head, list)
+ if (config_term(attr, term) && fail)
+ return -EINVAL;
+
+ return 0;
+}
+
+int parse_events_add_numeric(struct list_head *list, int *idx,
+ u32 type, u64 config,
+ struct list_head *head_config)
+{
+ struct perf_event_attr attr;
+
+ memset(&attr, 0, sizeof(attr));
+ attr.type = type;
+ attr.config = config;
+
+ if (head_config &&
+ config_attr(&attr, head_config, 1))
+ return -EINVAL;
+
+ return add_event(list, idx, &attr, NULL);
+}
+
+static int parse_events__is_name_term(struct parse_events_term *term)
+{
+ return term->type_term == PARSE_EVENTS__TERM_TYPE_NAME;
+}
+
+static char *pmu_event_name(struct list_head *head_terms)
+{
+ struct parse_events_term *term;
+
+ list_for_each_entry(term, head_terms, list)
+ if (parse_events__is_name_term(term))
+ return term->val.str;
+
+ return NULL;
+}
+
+int parse_events_add_pmu(struct list_head *list, int *idx,
+ char *name, struct list_head *head_config)
+{
+ struct perf_event_attr attr;
+ struct perf_pmu *pmu;
+ struct perf_evsel *evsel;
+ const char *unit;
+ double scale;
+
+ pmu = perf_pmu__find(name);
+ if (!pmu)
+ return -EINVAL;
+
+ memset(&attr, 0, sizeof(attr));
+
+ if (perf_pmu__check_alias(pmu, head_config, &unit, &scale))
+ return -EINVAL;
+
+ /*
+ * Configure hardcoded terms first, no need to check
+ * return value when called with fail == 0 ;)
+ */
+ config_attr(&attr, head_config, 0);
+
+ if (perf_pmu__config(pmu, &attr, head_config))
+ return -EINVAL;
+
+ evsel = __add_event(list, idx, &attr, pmu_event_name(head_config),
+ pmu->cpus);
+ if (evsel) {
+ evsel->unit = unit;
+ evsel->scale = scale;
+ }
+
+ return evsel ? 0 : -ENOMEM;
+}
+
+int parse_events__modifier_group(struct list_head *list,
+ char *event_mod)
+{
+ return parse_events__modifier_event(list, event_mod, true);
+}
+
+void parse_events__set_leader(char *name, struct list_head *list)
+{
+ struct perf_evsel *leader;
+
+ __perf_evlist__set_leader(list);
+ leader = list_entry(list->next, struct perf_evsel, node);
+ leader->group_name = name ? strdup(name) : NULL;
+}
+
+/* list_event is assumed to point to malloc'ed memory */
+void parse_events_update_lists(struct list_head *list_event,
+ struct list_head *list_all)
+{
+ /*
+ * Called for single event definition. Update the
+ * 'all event' list, and reinit the 'single event'
+ * list, for next event definition.
+ */
+ list_splice_tail(list_event, list_all);
+ free(list_event);
+}
+
+struct event_modifier {
+ int eu;
+ int ek;
+ int eh;
+ int eH;
+ int eG;
+ int precise;
+ int exclude_GH;
+ int sample_read;
+ int pinned;
+};
+
+static int get_event_modifier(struct event_modifier *mod, char *str,
+ struct perf_evsel *evsel)
+{
+ int eu = evsel ? evsel->attr.exclude_user : 0;
+ int ek = evsel ? evsel->attr.exclude_kernel : 0;
+ int eh = evsel ? evsel->attr.exclude_hv : 0;
+ int eH = evsel ? evsel->attr.exclude_host : 0;
+ int eG = evsel ? evsel->attr.exclude_guest : 0;
+ int precise = evsel ? evsel->attr.precise_ip : 0;
+ int sample_read = 0;
+ int pinned = evsel ? evsel->attr.pinned : 0;
+
+ int exclude = eu | ek | eh;
+ int exclude_GH = evsel ? evsel->exclude_GH : 0;
+
+ memset(mod, 0, sizeof(*mod));
+
+ while (*str) {
+ if (*str == 'u') {
+ if (!exclude)
+ exclude = eu = ek = eh = 1;
+ eu = 0;
+ } else if (*str == 'k') {
+ if (!exclude)
+ exclude = eu = ek = eh = 1;
+ ek = 0;
+ } else if (*str == 'h') {
+ if (!exclude)
+ exclude = eu = ek = eh = 1;
+ eh = 0;
+ } else if (*str == 'G') {
+ if (!exclude_GH)
+ exclude_GH = eG = eH = 1;
+ eG = 0;
+ } else if (*str == 'H') {
+ if (!exclude_GH)
+ exclude_GH = eG = eH = 1;
+ eH = 0;
+ } else if (*str == 'p') {
+ precise++;
+ /* use of precise requires exclude_guest */
+ if (!exclude_GH)
+ eG = 1;
+ } else if (*str == 'S') {
+ sample_read = 1;
+ } else if (*str == 'D') {
+ pinned = 1;
+ } else
+ break;
+
+ ++str;
+ }
+
+ /*
+ * precise ip:
+ *
+ * 0 - SAMPLE_IP can have arbitrary skid
+ * 1 - SAMPLE_IP must have constant skid
+ * 2 - SAMPLE_IP requested to have 0 skid
+ * 3 - SAMPLE_IP must have 0 skid
+ *
+ * See also PERF_RECORD_MISC_EXACT_IP
+ */
+ if (precise > 3)
+ return -EINVAL;
+
+ mod->eu = eu;
+ mod->ek = ek;
+ mod->eh = eh;
+ mod->eH = eH;
+ mod->eG = eG;
+ mod->precise = precise;
+ mod->exclude_GH = exclude_GH;
+ mod->sample_read = sample_read;
+ mod->pinned = pinned;
+
+ return 0;
+}
+
+/*
+ * Basic modifier sanity check to validate it contains only one
+ * instance of any modifier (apart from 'p') present.
+ */
+static int check_modifier(char *str)
+{
+ char *p = str;
+
+ /* The sizeof includes 0 byte as well. */
+ if (strlen(str) > (sizeof("ukhGHpppSD") - 1))
+ return -1;
+
+ while (*p) {
+ if (*p != 'p' && strchr(p + 1, *p))
+ return -1;
+ p++;
+ }
+
+ return 0;
+}
+
+int parse_events__modifier_event(struct list_head *list, char *str, bool add)
+{
+ struct perf_evsel *evsel;
+ struct event_modifier mod;
+
+ if (str == NULL)
+ return 0;
+
+ if (check_modifier(str))
+ return -EINVAL;
+
+ if (!add && get_event_modifier(&mod, str, NULL))
+ return -EINVAL;
+
+ __evlist__for_each(list, evsel) {
+ if (add && get_event_modifier(&mod, str, evsel))
+ return -EINVAL;
+
+ evsel->attr.exclude_user = mod.eu;
+ evsel->attr.exclude_kernel = mod.ek;
+ evsel->attr.exclude_hv = mod.eh;
+ evsel->attr.precise_ip = mod.precise;
+ evsel->attr.exclude_host = mod.eH;
+ evsel->attr.exclude_guest = mod.eG;
+ evsel->exclude_GH = mod.exclude_GH;
+ evsel->sample_read = mod.sample_read;
+
+ if (perf_evsel__is_group_leader(evsel))
+ evsel->attr.pinned = mod.pinned;
+ }
+
+ return 0;
+}
+
+int parse_events_name(struct list_head *list, char *name)
+{
+ struct perf_evsel *evsel;
+
+ __evlist__for_each(list, evsel) {
+ if (!evsel->name)
+ evsel->name = strdup(name);
+ }
+
+ return 0;
+}
+
+static int parse_events__scanner(const char *str, void *data, int start_token);
+
+static int parse_events_fixup(int ret, const char *str, void *data,
+ int start_token)
+{
+ char *o = strdup(str);
+ char *s = NULL;
+ char *t = o;
+ char *p;
+ int len = 0;
+
+ if (!o)
+ return ret;
+ while ((p = strsep(&t, ",")) != NULL) {
+ if (s)
+ str_append(&s, &len, ",");
+ str_append(&s, &len, "cpu/");
+ str_append(&s, &len, p);
+ str_append(&s, &len, "/");
+ }
+ free(o);
+ if (!s)
+ return -ENOMEM;
+ return parse_events__scanner(s, data, start_token);
+}
+
+static int parse_events__scanner(const char *str, void *data, int start_token)
+{
+ YY_BUFFER_STATE buffer;
+ void *scanner;
+ int ret;
+
+ ret = parse_events_lex_init_extra(start_token, &scanner);
+ if (ret)
+ return ret;
+
+ buffer = parse_events__scan_string(str, scanner);
+
+#ifdef PARSER_DEBUG
+ parse_events_debug = 1;
+#endif
+ ret = parse_events_parse(data, scanner);
+
+ parse_events__flush_buffer(buffer, scanner);
+ parse_events__delete_buffer(buffer, scanner);
+ parse_events_lex_destroy(scanner);
+ if (ret && !strchr(str, '/'))
+ ret = parse_events_fixup(ret, str, data, start_token);
+ return ret;
+}
+
+/*
+ * parse event config string, return a list of event terms.
+ */
+int parse_events_terms(struct list_head *terms, const char *str)
+{
+ struct parse_events_terms data = {
+ .terms = NULL,
+ };
+ int ret;
+
+ ret = parse_events__scanner(str, &data, PE_START_TERMS);
+ if (!ret) {
+ list_splice(data.terms, terms);
+ zfree(&data.terms);
+ return 0;
+ }
+
+ if (data.terms)
+ parse_events__free_terms(data.terms);
+ return ret;
+}
+
+int parse_events(struct perf_evlist *evlist, const char *str)
+{
+ struct parse_events_evlist data = {
+ .list = LIST_HEAD_INIT(data.list),
+ .idx = evlist->nr_entries,
+ };
+ int ret;
+
+ ret = parse_events__scanner(str, &data, PE_START_EVENTS);
+ if (!ret) {
+ int entries = data.idx - evlist->nr_entries;
+ perf_evlist__splice_list_tail(evlist, &data.list, entries);
+ evlist->nr_groups += data.nr_groups;
+ return 0;
+ }
+
+ /*
+ * There are 2 users - builtin-record and builtin-test objects.
+ * Both call perf_evlist__delete in case of error, so we dont
+ * need to bother.
+ */
+ return ret;
+}
+
+int parse_events_option(const struct option *opt, const char *str,
+ int unset __maybe_unused)
+{
+ struct perf_evlist *evlist = *(struct perf_evlist **)opt->value;
+ int ret = parse_events(evlist, str);
+
+ if (ret) {
+ fprintf(stderr, "invalid or unsupported event: '%s'\n", str);
+ fprintf(stderr, "Run 'perf list' for a list of valid events\n");
+ }
+ return ret;
+}
+
+int parse_filter(const struct option *opt, const char *str,
+ int unset __maybe_unused)
+{
+ struct perf_evlist *evlist = *(struct perf_evlist **)opt->value;
+ struct perf_evsel *last = NULL;
+
+ if (evlist->nr_entries > 0)
+ last = perf_evlist__last(evlist);
+
+ if (last == NULL || last->attr.type != PERF_TYPE_TRACEPOINT) {
+ fprintf(stderr,
+ "-F option should follow a -e tracepoint option\n");
+ return -1;
+ }
+
+ last->filter = strdup(str);
+ if (last->filter == NULL) {
+ fprintf(stderr, "not enough memory to hold filter string\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+static const char * const event_type_descriptors[] = {
+ "Hardware event",
+ "Software event",
+ "Tracepoint event",
+ "Hardware cache event",
+ "Raw hardware event descriptor",
+ "Hardware breakpoint",
+};
+
+/*
+ * Print the events from <debugfs_mount_point>/tracing/events
+ */
+
+void print_tracepoint_events(const char *subsys_glob, const char *event_glob,
+ bool name_only)
+{
+ DIR *sys_dir, *evt_dir;
+ struct dirent *sys_next, *evt_next, sys_dirent, evt_dirent;
+ char evt_path[MAXPATHLEN];
+ char dir_path[MAXPATHLEN];
+
+ if (debugfs_valid_mountpoint(tracing_events_path)) {
+ printf(" [ Tracepoints not available: %s ]\n", strerror(errno));
+ return;
+ }
+
+ sys_dir = opendir(tracing_events_path);
+ if (!sys_dir)
+ return;
+
+ for_each_subsystem(sys_dir, sys_dirent, sys_next) {
+ if (subsys_glob != NULL &&
+ !strglobmatch(sys_dirent.d_name, subsys_glob))
+ continue;
+
+ snprintf(dir_path, MAXPATHLEN, "%s/%s", tracing_events_path,
+ sys_dirent.d_name);
+ evt_dir = opendir(dir_path);
+ if (!evt_dir)
+ continue;
+
+ for_each_event(sys_dirent, evt_dir, evt_dirent, evt_next) {
+ if (event_glob != NULL &&
+ !strglobmatch(evt_dirent.d_name, event_glob))
+ continue;
+
+ if (name_only) {
+ printf("%s:%s ", sys_dirent.d_name, evt_dirent.d_name);
+ continue;
+ }
+
+ snprintf(evt_path, MAXPATHLEN, "%s:%s",
+ sys_dirent.d_name, evt_dirent.d_name);
+ printf(" %-50s [%s]\n", evt_path,
+ event_type_descriptors[PERF_TYPE_TRACEPOINT]);
+ }
+ closedir(evt_dir);
+ }
+ closedir(sys_dir);
+}
+
+/*
+ * Check whether event is in <debugfs_mount_point>/tracing/events
+ */
+
+int is_valid_tracepoint(const char *event_string)
+{
+ DIR *sys_dir, *evt_dir;
+ struct dirent *sys_next, *evt_next, sys_dirent, evt_dirent;
+ char evt_path[MAXPATHLEN];
+ char dir_path[MAXPATHLEN];
+
+ if (debugfs_valid_mountpoint(tracing_events_path))
+ return 0;
+
+ sys_dir = opendir(tracing_events_path);
+ if (!sys_dir)
+ return 0;
+
+ for_each_subsystem(sys_dir, sys_dirent, sys_next) {
+
+ snprintf(dir_path, MAXPATHLEN, "%s/%s", tracing_events_path,
+ sys_dirent.d_name);
+ evt_dir = opendir(dir_path);
+ if (!evt_dir)
+ continue;
+
+ for_each_event(sys_dirent, evt_dir, evt_dirent, evt_next) {
+ snprintf(evt_path, MAXPATHLEN, "%s:%s",
+ sys_dirent.d_name, evt_dirent.d_name);
+ if (!strcmp(evt_path, event_string)) {
+ closedir(evt_dir);
+ closedir(sys_dir);
+ return 1;
+ }
+ }
+ closedir(evt_dir);
+ }
+ closedir(sys_dir);
+ return 0;
+}
+
+static bool is_event_supported(u8 type, unsigned config)
+{
+ bool ret = true;
+ int open_return;
+ struct perf_evsel *evsel;
+ struct perf_event_attr attr = {
+ .type = type,
+ .config = config,
+ .disabled = 1,
+ };
+ struct {
+ struct thread_map map;
+ int threads[1];
+ } tmap = {
+ .map.nr = 1,
+ .threads = { 0 },
+ };
+
+ evsel = perf_evsel__new(&attr);
+ if (evsel) {
+ open_return = perf_evsel__open(evsel, NULL, &tmap.map);
+ ret = open_return >= 0;
+
+ if (open_return == -EACCES) {
+ /*
+ * This happens if the paranoid value
+ * /proc/sys/kernel/perf_event_paranoid is set to 2
+ * Re-run with exclude_kernel set; we don't do that
+ * by default as some ARM machines do not support it.
+ *
+ */
+ evsel->attr.exclude_kernel = 1;
+ ret = perf_evsel__open(evsel, NULL, &tmap.map) >= 0;
+ }
+ perf_evsel__delete(evsel);
+ }
+
+ return ret;
+}
+
+static void __print_events_type(u8 type, struct event_symbol *syms,
+ unsigned max)
+{
+ char name[64];
+ unsigned i;
+
+ for (i = 0; i < max ; i++, syms++) {
+ if (!is_event_supported(type, i))
+ continue;
+
+ if (strlen(syms->alias))
+ snprintf(name, sizeof(name), "%s OR %s",
+ syms->symbol, syms->alias);
+ else
+ snprintf(name, sizeof(name), "%s", syms->symbol);
+
+ printf(" %-50s [%s]\n", name, event_type_descriptors[type]);
+ }
+}
+
+void print_events_type(u8 type)
+{
+ if (type == PERF_TYPE_SOFTWARE)
+ __print_events_type(type, event_symbols_sw, PERF_COUNT_SW_MAX);
+ else
+ __print_events_type(type, event_symbols_hw, PERF_COUNT_HW_MAX);
+}
+
+int print_hwcache_events(const char *event_glob, bool name_only)
+{
+ unsigned int type, op, i, printed = 0;
+ char name[64];
+
+ for (type = 0; type < PERF_COUNT_HW_CACHE_MAX; type++) {
+ for (op = 0; op < PERF_COUNT_HW_CACHE_OP_MAX; op++) {
+ /* skip invalid cache type */
+ if (!perf_evsel__is_cache_op_valid(type, op))
+ continue;
+
+ for (i = 0; i < PERF_COUNT_HW_CACHE_RESULT_MAX; i++) {
+ __perf_evsel__hw_cache_type_op_res_name(type, op, i,
+ name, sizeof(name));
+ if (event_glob != NULL && !strglobmatch(name, event_glob))
+ continue;
+
+ if (!is_event_supported(PERF_TYPE_HW_CACHE,
+ type | (op << 8) | (i << 16)))
+ continue;
+
+ if (name_only)
+ printf("%s ", name);
+ else
+ printf(" %-50s [%s]\n", name,
+ event_type_descriptors[PERF_TYPE_HW_CACHE]);
+ ++printed;
+ }
+ }
+ }
+
+ if (printed)
+ printf("\n");
+ return printed;
+}
+
+static void print_symbol_events(const char *event_glob, unsigned type,
+ struct event_symbol *syms, unsigned max,
+ bool name_only)
+{
+ unsigned i, printed = 0;
+ char name[MAX_NAME_LEN];
+
+ for (i = 0; i < max; i++, syms++) {
+
+ if (event_glob != NULL &&
+ !(strglobmatch(syms->symbol, event_glob) ||
+ (syms->alias && strglobmatch(syms->alias, event_glob))))
+ continue;
+
+ if (!is_event_supported(type, i))
+ continue;
+
+ if (name_only) {
+ printf("%s ", syms->symbol);
+ continue;
+ }
+
+ if (strlen(syms->alias))
+ snprintf(name, MAX_NAME_LEN, "%s OR %s", syms->symbol, syms->alias);
+ else
+ strncpy(name, syms->symbol, MAX_NAME_LEN);
+
+ printf(" %-50s [%s]\n", name, event_type_descriptors[type]);
+
+ printed++;
+ }
+
+ if (printed)
+ printf("\n");
+}
+
+/*
+ * Print the help text for the event symbols:
+ */
+void print_events(const char *event_glob, bool name_only)
+{
+ if (!name_only) {
+ printf("\n");
+ printf("List of pre-defined events (to be used in -e):\n");
+ }
+
+ print_symbol_events(event_glob, PERF_TYPE_HARDWARE,
+ event_symbols_hw, PERF_COUNT_HW_MAX, name_only);
+
+ print_symbol_events(event_glob, PERF_TYPE_SOFTWARE,
+ event_symbols_sw, PERF_COUNT_SW_MAX, name_only);
+
+ print_hwcache_events(event_glob, name_only);
+
+ print_pmu_events(event_glob, name_only);
+
+ if (event_glob != NULL)
+ return;
+
+ if (!name_only) {
+ printf(" %-50s [%s]\n",
+ "rNNN",
+ event_type_descriptors[PERF_TYPE_RAW]);
+ printf(" %-50s [%s]\n",
+ "cpu/t1=v1[,t2=v2,t3 ...]/modifier",
+ event_type_descriptors[PERF_TYPE_RAW]);
+ printf(" (see 'man perf-list' on how to encode it)\n");
+ printf("\n");
+
+ printf(" %-50s [%s]\n",
+ "mem:<addr>[:access]",
+ event_type_descriptors[PERF_TYPE_BREAKPOINT]);
+ printf("\n");
+ }
+
+ print_tracepoint_events(NULL, NULL, name_only);
+}
+
+int parse_events__is_hardcoded_term(struct parse_events_term *term)
+{
+ return term->type_term != PARSE_EVENTS__TERM_TYPE_USER;
+}
+
+static int new_term(struct parse_events_term **_term, int type_val,
+ int type_term, char *config,
+ char *str, u64 num)
+{
+ struct parse_events_term *term;
+
+ term = zalloc(sizeof(*term));
+ if (!term)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&term->list);
+ term->type_val = type_val;
+ term->type_term = type_term;
+ term->config = config;
+
+ switch (type_val) {
+ case PARSE_EVENTS__TERM_TYPE_NUM:
+ term->val.num = num;
+ break;
+ case PARSE_EVENTS__TERM_TYPE_STR:
+ term->val.str = str;
+ break;
+ default:
+ free(term);
+ return -EINVAL;
+ }
+
+ *_term = term;
+ return 0;
+}
+
+int parse_events_term__num(struct parse_events_term **term,
+ int type_term, char *config, u64 num)
+{
+ return new_term(term, PARSE_EVENTS__TERM_TYPE_NUM, type_term,
+ config, NULL, num);
+}
+
+int parse_events_term__str(struct parse_events_term **term,
+ int type_term, char *config, char *str)
+{
+ return new_term(term, PARSE_EVENTS__TERM_TYPE_STR, type_term,
+ config, str, 0);
+}
+
+int parse_events_term__sym_hw(struct parse_events_term **term,
+ char *config, unsigned idx)
+{
+ struct event_symbol *sym;
+
+ BUG_ON(idx >= PERF_COUNT_HW_MAX);
+ sym = &event_symbols_hw[idx];
+
+ if (config)
+ return new_term(term, PARSE_EVENTS__TERM_TYPE_STR,
+ PARSE_EVENTS__TERM_TYPE_USER, config,
+ (char *) sym->symbol, 0);
+ else
+ return new_term(term, PARSE_EVENTS__TERM_TYPE_STR,
+ PARSE_EVENTS__TERM_TYPE_USER,
+ (char *) "event", (char *) sym->symbol, 0);
+}
+
+int parse_events_term__clone(struct parse_events_term **new,
+ struct parse_events_term *term)
+{
+ return new_term(new, term->type_val, term->type_term, term->config,
+ term->val.str, term->val.num);
+}
+
+void parse_events__free_terms(struct list_head *terms)
+{
+ struct parse_events_term *term, *h;
+
+ list_for_each_entry_safe(term, h, terms, list)
+ free(term);
+}
diff --git a/tools/perf/util/parse-events.h b/tools/perf/util/parse-events.h
new file mode 100644
index 00000000000..df094b4ed5e
--- /dev/null
+++ b/tools/perf/util/parse-events.h
@@ -0,0 +1,112 @@
+#ifndef __PERF_PARSE_EVENTS_H
+#define __PERF_PARSE_EVENTS_H
+/*
+ * Parse symbolic events/counts passed in as options:
+ */
+
+#include <linux/list.h>
+#include <stdbool.h>
+#include <linux/types.h>
+#include <linux/perf_event.h>
+
+struct list_head;
+struct perf_evsel;
+struct perf_evlist;
+
+struct option;
+
+struct tracepoint_path {
+ char *system;
+ char *name;
+ struct tracepoint_path *next;
+};
+
+extern struct tracepoint_path *tracepoint_id_to_path(u64 config);
+extern struct tracepoint_path *tracepoint_name_to_path(const char *name);
+extern bool have_tracepoints(struct list_head *evlist);
+
+const char *event_type(int type);
+
+extern int parse_events_option(const struct option *opt, const char *str,
+ int unset);
+extern int parse_events(struct perf_evlist *evlist, const char *str);
+extern int parse_events_terms(struct list_head *terms, const char *str);
+extern int parse_filter(const struct option *opt, const char *str, int unset);
+
+#define EVENTS_HELP_MAX (128*1024)
+
+enum {
+ PARSE_EVENTS__TERM_TYPE_NUM,
+ PARSE_EVENTS__TERM_TYPE_STR,
+};
+
+enum {
+ PARSE_EVENTS__TERM_TYPE_USER,
+ PARSE_EVENTS__TERM_TYPE_CONFIG,
+ PARSE_EVENTS__TERM_TYPE_CONFIG1,
+ PARSE_EVENTS__TERM_TYPE_CONFIG2,
+ PARSE_EVENTS__TERM_TYPE_NAME,
+ PARSE_EVENTS__TERM_TYPE_SAMPLE_PERIOD,
+ PARSE_EVENTS__TERM_TYPE_BRANCH_SAMPLE_TYPE,
+};
+
+struct parse_events_term {
+ char *config;
+ union {
+ char *str;
+ u64 num;
+ } val;
+ int type_val;
+ int type_term;
+ struct list_head list;
+};
+
+struct parse_events_evlist {
+ struct list_head list;
+ int idx;
+ int nr_groups;
+};
+
+struct parse_events_terms {
+ struct list_head *terms;
+};
+
+int parse_events__is_hardcoded_term(struct parse_events_term *term);
+int parse_events_term__num(struct parse_events_term **_term,
+ int type_term, char *config, u64 num);
+int parse_events_term__str(struct parse_events_term **_term,
+ int type_term, char *config, char *str);
+int parse_events_term__sym_hw(struct parse_events_term **term,
+ char *config, unsigned idx);
+int parse_events_term__clone(struct parse_events_term **new,
+ struct parse_events_term *term);
+void parse_events__free_terms(struct list_head *terms);
+int parse_events__modifier_event(struct list_head *list, char *str, bool add);
+int parse_events__modifier_group(struct list_head *list, char *event_mod);
+int parse_events_name(struct list_head *list, char *name);
+int parse_events_add_tracepoint(struct list_head *list, int *idx,
+ char *sys, char *event);
+int parse_events_add_numeric(struct list_head *list, int *idx,
+ u32 type, u64 config,
+ struct list_head *head_config);
+int parse_events_add_cache(struct list_head *list, int *idx,
+ char *type, char *op_result1, char *op_result2);
+int parse_events_add_breakpoint(struct list_head *list, int *idx,
+ void *ptr, char *type);
+int parse_events_add_pmu(struct list_head *list, int *idx,
+ char *pmu , struct list_head *head_config);
+void parse_events__set_leader(char *name, struct list_head *list);
+void parse_events_update_lists(struct list_head *list_event,
+ struct list_head *list_all);
+void parse_events_error(void *data, void *scanner, char const *msg);
+
+void print_events(const char *event_glob, bool name_only);
+void print_events_type(u8 type);
+void print_tracepoint_events(const char *subsys_glob, const char *event_glob,
+ bool name_only);
+int print_hwcache_events(const char *event_glob, bool name_only);
+extern int is_valid_tracepoint(const char *event_string);
+
+extern int valid_debugfs_mount(const char *debugfs);
+
+#endif /* __PERF_PARSE_EVENTS_H */
diff --git a/tools/perf/util/parse-events.l b/tools/perf/util/parse-events.l
new file mode 100644
index 00000000000..343299575b3
--- /dev/null
+++ b/tools/perf/util/parse-events.l
@@ -0,0 +1,218 @@
+
+%option reentrant
+%option bison-bridge
+%option prefix="parse_events_"
+%option stack
+
+%{
+#include <errno.h>
+#include "../perf.h"
+#include "parse-events-bison.h"
+#include "parse-events.h"
+
+char *parse_events_get_text(yyscan_t yyscanner);
+YYSTYPE *parse_events_get_lval(yyscan_t yyscanner);
+
+static int __value(YYSTYPE *yylval, char *str, int base, int token)
+{
+ u64 num;
+
+ errno = 0;
+ num = strtoull(str, NULL, base);
+ if (errno)
+ return PE_ERROR;
+
+ yylval->num = num;
+ return token;
+}
+
+static int value(yyscan_t scanner, int base)
+{
+ YYSTYPE *yylval = parse_events_get_lval(scanner);
+ char *text = parse_events_get_text(scanner);
+
+ return __value(yylval, text, base, PE_VALUE);
+}
+
+static int raw(yyscan_t scanner)
+{
+ YYSTYPE *yylval = parse_events_get_lval(scanner);
+ char *text = parse_events_get_text(scanner);
+
+ return __value(yylval, text + 1, 16, PE_RAW);
+}
+
+static int str(yyscan_t scanner, int token)
+{
+ YYSTYPE *yylval = parse_events_get_lval(scanner);
+ char *text = parse_events_get_text(scanner);
+
+ yylval->str = strdup(text);
+ return token;
+}
+
+static int sym(yyscan_t scanner, int type, int config)
+{
+ YYSTYPE *yylval = parse_events_get_lval(scanner);
+
+ yylval->num = (type << 16) + config;
+ return type == PERF_TYPE_HARDWARE ? PE_VALUE_SYM_HW : PE_VALUE_SYM_SW;
+}
+
+static int term(yyscan_t scanner, int type)
+{
+ YYSTYPE *yylval = parse_events_get_lval(scanner);
+
+ yylval->num = type;
+ return PE_TERM;
+}
+
+%}
+
+%x mem
+%s config
+%x event
+
+group [^,{}/]*[{][^}]*[}][^,{}/]*
+event_pmu [^,{}/]+[/][^/]*[/][^,{}/]*
+event [^,{}/]+
+
+num_dec [0-9]+
+num_hex 0x[a-fA-F0-9]+
+num_raw_hex [a-fA-F0-9]+
+name [a-zA-Z_*?][a-zA-Z0-9_*?]*
+name_minus [a-zA-Z_*?][a-zA-Z0-9\-_*?]*
+/* If you add a modifier you need to update check_modifier() */
+modifier_event [ukhpGHSD]+
+modifier_bp [rwx]{1,3}
+
+%%
+
+%{
+ {
+ int start_token;
+
+ start_token = parse_events_get_extra(yyscanner);
+
+ if (start_token == PE_START_TERMS)
+ BEGIN(config);
+ else if (start_token == PE_START_EVENTS)
+ BEGIN(event);
+
+ if (start_token) {
+ parse_events_set_extra(NULL, yyscanner);
+ return start_token;
+ }
+ }
+%}
+
+<event>{
+
+{group} {
+ BEGIN(INITIAL); yyless(0);
+ }
+
+{event_pmu} |
+{event} {
+ str(yyscanner, PE_EVENT_NAME);
+ BEGIN(INITIAL); yyless(0);
+ return PE_EVENT_NAME;
+ }
+
+. |
+<<EOF>> {
+ BEGIN(INITIAL); yyless(0);
+ }
+
+}
+
+<config>{
+config { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_CONFIG); }
+config1 { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_CONFIG1); }
+config2 { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_CONFIG2); }
+name { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_NAME); }
+period { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_SAMPLE_PERIOD); }
+branch_type { return term(yyscanner, PARSE_EVENTS__TERM_TYPE_BRANCH_SAMPLE_TYPE); }
+, { return ','; }
+"/" { BEGIN(INITIAL); return '/'; }
+{name_minus} { return str(yyscanner, PE_NAME); }
+}
+
+<mem>{
+{modifier_bp} { return str(yyscanner, PE_MODIFIER_BP); }
+: { return ':'; }
+{num_dec} { return value(yyscanner, 10); }
+{num_hex} { return value(yyscanner, 16); }
+ /*
+ * We need to separate 'mem:' scanner part, in order to get specific
+ * modifier bits parsed out. Otherwise we would need to handle PE_NAME
+ * and we'd need to parse it manually. During the escape from <mem>
+ * state we need to put the escaping char back, so we dont miss it.
+ */
+. { unput(*yytext); BEGIN(INITIAL); }
+ /*
+ * We destroy the scanner after reaching EOF,
+ * but anyway just to be sure get back to INIT state.
+ */
+<<EOF>> { BEGIN(INITIAL); }
+}
+
+cpu-cycles|cycles { return sym(yyscanner, PERF_TYPE_HARDWARE, PERF_COUNT_HW_CPU_CYCLES); }
+stalled-cycles-frontend|idle-cycles-frontend { return sym(yyscanner, PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_FRONTEND); }
+stalled-cycles-backend|idle-cycles-backend { return sym(yyscanner, PERF_TYPE_HARDWARE, PERF_COUNT_HW_STALLED_CYCLES_BACKEND); }
+instructions { return sym(yyscanner, PERF_TYPE_HARDWARE, PERF_COUNT_HW_INSTRUCTIONS); }
+cache-references { return sym(yyscanner, PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_REFERENCES); }
+cache-misses { return sym(yyscanner, PERF_TYPE_HARDWARE, PERF_COUNT_HW_CACHE_MISSES); }
+branch-instructions|branches { return sym(yyscanner, PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_INSTRUCTIONS); }
+branch-misses { return sym(yyscanner, PERF_TYPE_HARDWARE, PERF_COUNT_HW_BRANCH_MISSES); }
+bus-cycles { return sym(yyscanner, PERF_TYPE_HARDWARE, PERF_COUNT_HW_BUS_CYCLES); }
+ref-cycles { return sym(yyscanner, PERF_TYPE_HARDWARE, PERF_COUNT_HW_REF_CPU_CYCLES); }
+cpu-clock { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_CLOCK); }
+task-clock { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_TASK_CLOCK); }
+page-faults|faults { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS); }
+minor-faults { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MIN); }
+major-faults { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_PAGE_FAULTS_MAJ); }
+context-switches|cs { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CONTEXT_SWITCHES); }
+cpu-migrations|migrations { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_MIGRATIONS); }
+alignment-faults { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_ALIGNMENT_FAULTS); }
+emulation-faults { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_EMULATION_FAULTS); }
+dummy { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_DUMMY); }
+
+L1-dcache|l1-d|l1d|L1-data |
+L1-icache|l1-i|l1i|L1-instruction |
+LLC|L2 |
+dTLB|d-tlb|Data-TLB |
+iTLB|i-tlb|Instruction-TLB |
+branch|branches|bpu|btb|bpc |
+node { return str(yyscanner, PE_NAME_CACHE_TYPE); }
+
+load|loads|read |
+store|stores|write |
+prefetch|prefetches |
+speculative-read|speculative-load |
+refs|Reference|ops|access |
+misses|miss { return str(yyscanner, PE_NAME_CACHE_OP_RESULT); }
+
+mem: { BEGIN(mem); return PE_PREFIX_MEM; }
+r{num_raw_hex} { return raw(yyscanner); }
+{num_dec} { return value(yyscanner, 10); }
+{num_hex} { return value(yyscanner, 16); }
+
+{modifier_event} { return str(yyscanner, PE_MODIFIER_EVENT); }
+{name} { return str(yyscanner, PE_NAME); }
+"/" { BEGIN(config); return '/'; }
+- { return '-'; }
+, { BEGIN(event); return ','; }
+: { return ':'; }
+"{" { BEGIN(event); return '{'; }
+"}" { return '}'; }
+= { return '='; }
+\n { }
+. { }
+
+%%
+
+int parse_events_wrap(void *scanner __maybe_unused)
+{
+ return 1;
+}
diff --git a/tools/perf/util/parse-events.y b/tools/perf/util/parse-events.y
new file mode 100644
index 00000000000..0bc87ba46bf
--- /dev/null
+++ b/tools/perf/util/parse-events.y
@@ -0,0 +1,454 @@
+%pure-parser
+%parse-param {void *_data}
+%parse-param {void *scanner}
+%lex-param {void* scanner}
+
+%{
+
+#define YYDEBUG 1
+
+#include <linux/compiler.h>
+#include <linux/list.h>
+#include <linux/types.h>
+#include "util.h"
+#include "parse-events.h"
+#include "parse-events-bison.h"
+
+extern int parse_events_lex (YYSTYPE* lvalp, void* scanner);
+
+#define ABORT_ON(val) \
+do { \
+ if (val) \
+ YYABORT; \
+} while (0)
+
+#define ALLOC_LIST(list) \
+do { \
+ list = malloc(sizeof(*list)); \
+ ABORT_ON(!list); \
+ INIT_LIST_HEAD(list); \
+} while (0)
+
+static inc_group_count(struct list_head *list,
+ struct parse_events_evlist *data)
+{
+ /* Count groups only have more than 1 members */
+ if (!list_is_last(list->next, list))
+ data->nr_groups++;
+}
+
+%}
+
+%token PE_START_EVENTS PE_START_TERMS
+%token PE_VALUE PE_VALUE_SYM_HW PE_VALUE_SYM_SW PE_RAW PE_TERM
+%token PE_EVENT_NAME
+%token PE_NAME
+%token PE_MODIFIER_EVENT PE_MODIFIER_BP
+%token PE_NAME_CACHE_TYPE PE_NAME_CACHE_OP_RESULT
+%token PE_PREFIX_MEM PE_PREFIX_RAW PE_PREFIX_GROUP
+%token PE_ERROR
+%type <num> PE_VALUE
+%type <num> PE_VALUE_SYM_HW
+%type <num> PE_VALUE_SYM_SW
+%type <num> PE_RAW
+%type <num> PE_TERM
+%type <str> PE_NAME
+%type <str> PE_NAME_CACHE_TYPE
+%type <str> PE_NAME_CACHE_OP_RESULT
+%type <str> PE_MODIFIER_EVENT
+%type <str> PE_MODIFIER_BP
+%type <str> PE_EVENT_NAME
+%type <num> value_sym
+%type <head> event_config
+%type <term> event_term
+%type <head> event_pmu
+%type <head> event_legacy_symbol
+%type <head> event_legacy_cache
+%type <head> event_legacy_mem
+%type <head> event_legacy_tracepoint
+%type <head> event_legacy_numeric
+%type <head> event_legacy_raw
+%type <head> event_def
+%type <head> event_mod
+%type <head> event_name
+%type <head> event
+%type <head> events
+%type <head> group_def
+%type <head> group
+%type <head> groups
+
+%union
+{
+ char *str;
+ u64 num;
+ struct list_head *head;
+ struct parse_events_term *term;
+}
+%%
+
+start:
+PE_START_EVENTS start_events
+|
+PE_START_TERMS start_terms
+
+start_events: groups
+{
+ struct parse_events_evlist *data = _data;
+
+ parse_events_update_lists($1, &data->list);
+}
+
+groups:
+groups ',' group
+{
+ struct list_head *list = $1;
+ struct list_head *group = $3;
+
+ parse_events_update_lists(group, list);
+ $$ = list;
+}
+|
+groups ',' event
+{
+ struct list_head *list = $1;
+ struct list_head *event = $3;
+
+ parse_events_update_lists(event, list);
+ $$ = list;
+}
+|
+group
+|
+event
+
+group:
+group_def ':' PE_MODIFIER_EVENT
+{
+ struct list_head *list = $1;
+
+ ABORT_ON(parse_events__modifier_group(list, $3));
+ $$ = list;
+}
+|
+group_def
+
+group_def:
+PE_NAME '{' events '}'
+{
+ struct list_head *list = $3;
+
+ inc_group_count(list, _data);
+ parse_events__set_leader($1, list);
+ $$ = list;
+}
+|
+'{' events '}'
+{
+ struct list_head *list = $2;
+
+ inc_group_count(list, _data);
+ parse_events__set_leader(NULL, list);
+ $$ = list;
+}
+
+events:
+events ',' event
+{
+ struct list_head *event = $3;
+ struct list_head *list = $1;
+
+ parse_events_update_lists(event, list);
+ $$ = list;
+}
+|
+event
+
+event: event_mod
+
+event_mod:
+event_name PE_MODIFIER_EVENT
+{
+ struct list_head *list = $1;
+
+ /*
+ * Apply modifier on all events added by single event definition
+ * (there could be more events added for multiple tracepoint
+ * definitions via '*?'.
+ */
+ ABORT_ON(parse_events__modifier_event(list, $2, false));
+ $$ = list;
+}
+|
+event_name
+
+event_name:
+PE_EVENT_NAME event_def
+{
+ ABORT_ON(parse_events_name($2, $1));
+ free($1);
+ $$ = $2;
+}
+|
+event_def
+
+event_def: event_pmu |
+ event_legacy_symbol |
+ event_legacy_cache sep_dc |
+ event_legacy_mem |
+ event_legacy_tracepoint sep_dc |
+ event_legacy_numeric sep_dc |
+ event_legacy_raw sep_dc
+
+event_pmu:
+PE_NAME '/' event_config '/'
+{
+ struct parse_events_evlist *data = _data;
+ struct list_head *list;
+
+ ALLOC_LIST(list);
+ ABORT_ON(parse_events_add_pmu(list, &data->idx, $1, $3));
+ parse_events__free_terms($3);
+ $$ = list;
+}
+
+value_sym:
+PE_VALUE_SYM_HW
+|
+PE_VALUE_SYM_SW
+
+event_legacy_symbol:
+value_sym '/' event_config '/'
+{
+ struct parse_events_evlist *data = _data;
+ struct list_head *list;
+ int type = $1 >> 16;
+ int config = $1 & 255;
+
+ ALLOC_LIST(list);
+ ABORT_ON(parse_events_add_numeric(list, &data->idx,
+ type, config, $3));
+ parse_events__free_terms($3);
+ $$ = list;
+}
+|
+value_sym sep_slash_dc
+{
+ struct parse_events_evlist *data = _data;
+ struct list_head *list;
+ int type = $1 >> 16;
+ int config = $1 & 255;
+
+ ALLOC_LIST(list);
+ ABORT_ON(parse_events_add_numeric(list, &data->idx,
+ type, config, NULL));
+ $$ = list;
+}
+
+event_legacy_cache:
+PE_NAME_CACHE_TYPE '-' PE_NAME_CACHE_OP_RESULT '-' PE_NAME_CACHE_OP_RESULT
+{
+ struct parse_events_evlist *data = _data;
+ struct list_head *list;
+
+ ALLOC_LIST(list);
+ ABORT_ON(parse_events_add_cache(list, &data->idx, $1, $3, $5));
+ $$ = list;
+}
+|
+PE_NAME_CACHE_TYPE '-' PE_NAME_CACHE_OP_RESULT
+{
+ struct parse_events_evlist *data = _data;
+ struct list_head *list;
+
+ ALLOC_LIST(list);
+ ABORT_ON(parse_events_add_cache(list, &data->idx, $1, $3, NULL));
+ $$ = list;
+}
+|
+PE_NAME_CACHE_TYPE
+{
+ struct parse_events_evlist *data = _data;
+ struct list_head *list;
+
+ ALLOC_LIST(list);
+ ABORT_ON(parse_events_add_cache(list, &data->idx, $1, NULL, NULL));
+ $$ = list;
+}
+
+event_legacy_mem:
+PE_PREFIX_MEM PE_VALUE ':' PE_MODIFIER_BP sep_dc
+{
+ struct parse_events_evlist *data = _data;
+ struct list_head *list;
+
+ ALLOC_LIST(list);
+ ABORT_ON(parse_events_add_breakpoint(list, &data->idx,
+ (void *) $2, $4));
+ $$ = list;
+}
+|
+PE_PREFIX_MEM PE_VALUE sep_dc
+{
+ struct parse_events_evlist *data = _data;
+ struct list_head *list;
+
+ ALLOC_LIST(list);
+ ABORT_ON(parse_events_add_breakpoint(list, &data->idx,
+ (void *) $2, NULL));
+ $$ = list;
+}
+
+event_legacy_tracepoint:
+PE_NAME '-' PE_NAME ':' PE_NAME
+{
+ struct parse_events_evlist *data = _data;
+ struct list_head *list;
+ char sys_name[128];
+ snprintf(&sys_name, 128, "%s-%s", $1, $3);
+
+ ALLOC_LIST(list);
+ ABORT_ON(parse_events_add_tracepoint(list, &data->idx, &sys_name, $5));
+ $$ = list;
+}
+|
+PE_NAME ':' PE_NAME
+{
+ struct parse_events_evlist *data = _data;
+ struct list_head *list;
+
+ ALLOC_LIST(list);
+ ABORT_ON(parse_events_add_tracepoint(list, &data->idx, $1, $3));
+ $$ = list;
+}
+
+event_legacy_numeric:
+PE_VALUE ':' PE_VALUE
+{
+ struct parse_events_evlist *data = _data;
+ struct list_head *list;
+
+ ALLOC_LIST(list);
+ ABORT_ON(parse_events_add_numeric(list, &data->idx, (u32)$1, $3, NULL));
+ $$ = list;
+}
+
+event_legacy_raw:
+PE_RAW
+{
+ struct parse_events_evlist *data = _data;
+ struct list_head *list;
+
+ ALLOC_LIST(list);
+ ABORT_ON(parse_events_add_numeric(list, &data->idx,
+ PERF_TYPE_RAW, $1, NULL));
+ $$ = list;
+}
+
+start_terms: event_config
+{
+ struct parse_events_terms *data = _data;
+ data->terms = $1;
+}
+
+event_config:
+event_config ',' event_term
+{
+ struct list_head *head = $1;
+ struct parse_events_term *term = $3;
+
+ ABORT_ON(!head);
+ list_add_tail(&term->list, head);
+ $$ = $1;
+}
+|
+event_term
+{
+ struct list_head *head = malloc(sizeof(*head));
+ struct parse_events_term *term = $1;
+
+ ABORT_ON(!head);
+ INIT_LIST_HEAD(head);
+ list_add_tail(&term->list, head);
+ $$ = head;
+}
+
+event_term:
+PE_NAME '=' PE_NAME
+{
+ struct parse_events_term *term;
+
+ ABORT_ON(parse_events_term__str(&term, PARSE_EVENTS__TERM_TYPE_USER,
+ $1, $3));
+ $$ = term;
+}
+|
+PE_NAME '=' PE_VALUE
+{
+ struct parse_events_term *term;
+
+ ABORT_ON(parse_events_term__num(&term, PARSE_EVENTS__TERM_TYPE_USER,
+ $1, $3));
+ $$ = term;
+}
+|
+PE_NAME '=' PE_VALUE_SYM_HW
+{
+ struct parse_events_term *term;
+ int config = $3 & 255;
+
+ ABORT_ON(parse_events_term__sym_hw(&term, $1, config));
+ $$ = term;
+}
+|
+PE_NAME
+{
+ struct parse_events_term *term;
+
+ ABORT_ON(parse_events_term__num(&term, PARSE_EVENTS__TERM_TYPE_USER,
+ $1, 1));
+ $$ = term;
+}
+|
+PE_VALUE_SYM_HW
+{
+ struct parse_events_term *term;
+ int config = $1 & 255;
+
+ ABORT_ON(parse_events_term__sym_hw(&term, NULL, config));
+ $$ = term;
+}
+|
+PE_TERM '=' PE_NAME
+{
+ struct parse_events_term *term;
+
+ ABORT_ON(parse_events_term__str(&term, (int)$1, NULL, $3));
+ $$ = term;
+}
+|
+PE_TERM '=' PE_VALUE
+{
+ struct parse_events_term *term;
+
+ ABORT_ON(parse_events_term__num(&term, (int)$1, NULL, $3));
+ $$ = term;
+}
+|
+PE_TERM
+{
+ struct parse_events_term *term;
+
+ ABORT_ON(parse_events_term__num(&term, (int)$1, NULL, 1));
+ $$ = term;
+}
+
+sep_dc: ':' |
+
+sep_slash_dc: '/' | ':' |
+
+%%
+
+void parse_events_error(void *data __maybe_unused, void *scanner __maybe_unused,
+ char const *msg __maybe_unused)
+{
+}
diff --git a/tools/perf/util/parse-options.c b/tools/perf/util/parse-options.c
new file mode 100644
index 00000000000..bf48092983c
--- /dev/null
+++ b/tools/perf/util/parse-options.c
@@ -0,0 +1,681 @@
+#include "util.h"
+#include "parse-options.h"
+#include "cache.h"
+#include "header.h"
+
+#define OPT_SHORT 1
+#define OPT_UNSET 2
+
+static int opterror(const struct option *opt, const char *reason, int flags)
+{
+ if (flags & OPT_SHORT)
+ return error("switch `%c' %s", opt->short_name, reason);
+ if (flags & OPT_UNSET)
+ return error("option `no-%s' %s", opt->long_name, reason);
+ return error("option `%s' %s", opt->long_name, reason);
+}
+
+static int get_arg(struct parse_opt_ctx_t *p, const struct option *opt,
+ int flags, const char **arg)
+{
+ if (p->opt) {
+ *arg = p->opt;
+ p->opt = NULL;
+ } else if ((opt->flags & PARSE_OPT_LASTARG_DEFAULT) && (p->argc == 1 ||
+ **(p->argv + 1) == '-')) {
+ *arg = (const char *)opt->defval;
+ } else if (p->argc > 1) {
+ p->argc--;
+ *arg = *++p->argv;
+ } else
+ return opterror(opt, "requires a value", flags);
+ return 0;
+}
+
+static int get_value(struct parse_opt_ctx_t *p,
+ const struct option *opt, int flags)
+{
+ const char *s, *arg = NULL;
+ const int unset = flags & OPT_UNSET;
+
+ if (unset && p->opt)
+ return opterror(opt, "takes no value", flags);
+ if (unset && (opt->flags & PARSE_OPT_NONEG))
+ return opterror(opt, "isn't available", flags);
+
+ if (!(flags & OPT_SHORT) && p->opt) {
+ switch (opt->type) {
+ case OPTION_CALLBACK:
+ if (!(opt->flags & PARSE_OPT_NOARG))
+ break;
+ /* FALLTHROUGH */
+ case OPTION_BOOLEAN:
+ case OPTION_INCR:
+ case OPTION_BIT:
+ case OPTION_SET_UINT:
+ case OPTION_SET_PTR:
+ return opterror(opt, "takes no value", flags);
+ case OPTION_END:
+ case OPTION_ARGUMENT:
+ case OPTION_GROUP:
+ case OPTION_STRING:
+ case OPTION_INTEGER:
+ case OPTION_UINTEGER:
+ case OPTION_LONG:
+ case OPTION_U64:
+ default:
+ break;
+ }
+ }
+
+ switch (opt->type) {
+ case OPTION_BIT:
+ if (unset)
+ *(int *)opt->value &= ~opt->defval;
+ else
+ *(int *)opt->value |= opt->defval;
+ return 0;
+
+ case OPTION_BOOLEAN:
+ *(bool *)opt->value = unset ? false : true;
+ if (opt->set)
+ *(bool *)opt->set = true;
+ return 0;
+
+ case OPTION_INCR:
+ *(int *)opt->value = unset ? 0 : *(int *)opt->value + 1;
+ return 0;
+
+ case OPTION_SET_UINT:
+ *(unsigned int *)opt->value = unset ? 0 : opt->defval;
+ return 0;
+
+ case OPTION_SET_PTR:
+ *(void **)opt->value = unset ? NULL : (void *)opt->defval;
+ return 0;
+
+ case OPTION_STRING:
+ if (unset)
+ *(const char **)opt->value = NULL;
+ else if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+ *(const char **)opt->value = (const char *)opt->defval;
+ else
+ return get_arg(p, opt, flags, (const char **)opt->value);
+ return 0;
+
+ case OPTION_CALLBACK:
+ if (unset)
+ return (*opt->callback)(opt, NULL, 1) ? (-1) : 0;
+ if (opt->flags & PARSE_OPT_NOARG)
+ return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
+ if (opt->flags & PARSE_OPT_OPTARG && !p->opt)
+ return (*opt->callback)(opt, NULL, 0) ? (-1) : 0;
+ if (get_arg(p, opt, flags, &arg))
+ return -1;
+ return (*opt->callback)(opt, arg, 0) ? (-1) : 0;
+
+ case OPTION_INTEGER:
+ if (unset) {
+ *(int *)opt->value = 0;
+ return 0;
+ }
+ if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+ *(int *)opt->value = opt->defval;
+ return 0;
+ }
+ if (get_arg(p, opt, flags, &arg))
+ return -1;
+ *(int *)opt->value = strtol(arg, (char **)&s, 10);
+ if (*s)
+ return opterror(opt, "expects a numerical value", flags);
+ return 0;
+
+ case OPTION_UINTEGER:
+ if (unset) {
+ *(unsigned int *)opt->value = 0;
+ return 0;
+ }
+ if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+ *(unsigned int *)opt->value = opt->defval;
+ return 0;
+ }
+ if (get_arg(p, opt, flags, &arg))
+ return -1;
+ *(unsigned int *)opt->value = strtol(arg, (char **)&s, 10);
+ if (*s)
+ return opterror(opt, "expects a numerical value", flags);
+ return 0;
+
+ case OPTION_LONG:
+ if (unset) {
+ *(long *)opt->value = 0;
+ return 0;
+ }
+ if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+ *(long *)opt->value = opt->defval;
+ return 0;
+ }
+ if (get_arg(p, opt, flags, &arg))
+ return -1;
+ *(long *)opt->value = strtol(arg, (char **)&s, 10);
+ if (*s)
+ return opterror(opt, "expects a numerical value", flags);
+ return 0;
+
+ case OPTION_U64:
+ if (unset) {
+ *(u64 *)opt->value = 0;
+ return 0;
+ }
+ if (opt->flags & PARSE_OPT_OPTARG && !p->opt) {
+ *(u64 *)opt->value = opt->defval;
+ return 0;
+ }
+ if (get_arg(p, opt, flags, &arg))
+ return -1;
+ *(u64 *)opt->value = strtoull(arg, (char **)&s, 10);
+ if (*s)
+ return opterror(opt, "expects a numerical value", flags);
+ return 0;
+
+ case OPTION_END:
+ case OPTION_ARGUMENT:
+ case OPTION_GROUP:
+ default:
+ die("should not happen, someone must be hit on the forehead");
+ }
+}
+
+static int parse_short_opt(struct parse_opt_ctx_t *p, const struct option *options)
+{
+ for (; options->type != OPTION_END; options++) {
+ if (options->short_name == *p->opt) {
+ p->opt = p->opt[1] ? p->opt + 1 : NULL;
+ return get_value(p, options, OPT_SHORT);
+ }
+ }
+ return -2;
+}
+
+static int parse_long_opt(struct parse_opt_ctx_t *p, const char *arg,
+ const struct option *options)
+{
+ const char *arg_end = strchr(arg, '=');
+ const struct option *abbrev_option = NULL, *ambiguous_option = NULL;
+ int abbrev_flags = 0, ambiguous_flags = 0;
+
+ if (!arg_end)
+ arg_end = arg + strlen(arg);
+
+ for (; options->type != OPTION_END; options++) {
+ const char *rest;
+ int flags = 0;
+
+ if (!options->long_name)
+ continue;
+
+ rest = skip_prefix(arg, options->long_name);
+ if (options->type == OPTION_ARGUMENT) {
+ if (!rest)
+ continue;
+ if (*rest == '=')
+ return opterror(options, "takes no value", flags);
+ if (*rest)
+ continue;
+ p->out[p->cpidx++] = arg - 2;
+ return 0;
+ }
+ if (!rest) {
+ if (!prefixcmp(options->long_name, "no-")) {
+ /*
+ * The long name itself starts with "no-", so
+ * accept the option without "no-" so that users
+ * do not have to enter "no-no-" to get the
+ * negation.
+ */
+ rest = skip_prefix(arg, options->long_name + 3);
+ if (rest) {
+ flags |= OPT_UNSET;
+ goto match;
+ }
+ /* Abbreviated case */
+ if (!prefixcmp(options->long_name + 3, arg)) {
+ flags |= OPT_UNSET;
+ goto is_abbreviated;
+ }
+ }
+ /* abbreviated? */
+ if (!strncmp(options->long_name, arg, arg_end - arg)) {
+is_abbreviated:
+ if (abbrev_option) {
+ /*
+ * If this is abbreviated, it is
+ * ambiguous. So when there is no
+ * exact match later, we need to
+ * error out.
+ */
+ ambiguous_option = abbrev_option;
+ ambiguous_flags = abbrev_flags;
+ }
+ if (!(flags & OPT_UNSET) && *arg_end)
+ p->opt = arg_end + 1;
+ abbrev_option = options;
+ abbrev_flags = flags;
+ continue;
+ }
+ /* negated and abbreviated very much? */
+ if (!prefixcmp("no-", arg)) {
+ flags |= OPT_UNSET;
+ goto is_abbreviated;
+ }
+ /* negated? */
+ if (strncmp(arg, "no-", 3))
+ continue;
+ flags |= OPT_UNSET;
+ rest = skip_prefix(arg + 3, options->long_name);
+ /* abbreviated and negated? */
+ if (!rest && !prefixcmp(options->long_name, arg + 3))
+ goto is_abbreviated;
+ if (!rest)
+ continue;
+ }
+match:
+ if (*rest) {
+ if (*rest != '=')
+ continue;
+ p->opt = rest + 1;
+ }
+ return get_value(p, options, flags);
+ }
+
+ if (ambiguous_option)
+ return error("Ambiguous option: %s "
+ "(could be --%s%s or --%s%s)",
+ arg,
+ (ambiguous_flags & OPT_UNSET) ? "no-" : "",
+ ambiguous_option->long_name,
+ (abbrev_flags & OPT_UNSET) ? "no-" : "",
+ abbrev_option->long_name);
+ if (abbrev_option)
+ return get_value(p, abbrev_option, abbrev_flags);
+ return -2;
+}
+
+static void check_typos(const char *arg, const struct option *options)
+{
+ if (strlen(arg) < 3)
+ return;
+
+ if (!prefixcmp(arg, "no-")) {
+ error ("did you mean `--%s` (with two dashes ?)", arg);
+ exit(129);
+ }
+
+ for (; options->type != OPTION_END; options++) {
+ if (!options->long_name)
+ continue;
+ if (!prefixcmp(options->long_name, arg)) {
+ error ("did you mean `--%s` (with two dashes ?)", arg);
+ exit(129);
+ }
+ }
+}
+
+void parse_options_start(struct parse_opt_ctx_t *ctx,
+ int argc, const char **argv, int flags)
+{
+ memset(ctx, 0, sizeof(*ctx));
+ ctx->argc = argc - 1;
+ ctx->argv = argv + 1;
+ ctx->out = argv;
+ ctx->cpidx = ((flags & PARSE_OPT_KEEP_ARGV0) != 0);
+ ctx->flags = flags;
+ if ((flags & PARSE_OPT_KEEP_UNKNOWN) &&
+ (flags & PARSE_OPT_STOP_AT_NON_OPTION))
+ die("STOP_AT_NON_OPTION and KEEP_UNKNOWN don't go together");
+}
+
+static int usage_with_options_internal(const char * const *,
+ const struct option *, int);
+
+int parse_options_step(struct parse_opt_ctx_t *ctx,
+ const struct option *options,
+ const char * const usagestr[])
+{
+ int internal_help = !(ctx->flags & PARSE_OPT_NO_INTERNAL_HELP);
+
+ /* we must reset ->opt, unknown short option leave it dangling */
+ ctx->opt = NULL;
+
+ for (; ctx->argc; ctx->argc--, ctx->argv++) {
+ const char *arg = ctx->argv[0];
+
+ if (*arg != '-' || !arg[1]) {
+ if (ctx->flags & PARSE_OPT_STOP_AT_NON_OPTION)
+ break;
+ ctx->out[ctx->cpidx++] = ctx->argv[0];
+ continue;
+ }
+
+ if (arg[1] != '-') {
+ ctx->opt = arg + 1;
+ if (internal_help && *ctx->opt == 'h')
+ return usage_with_options_internal(usagestr, options, 0);
+ switch (parse_short_opt(ctx, options)) {
+ case -1:
+ return parse_options_usage(usagestr, options, arg + 1, 1);
+ case -2:
+ goto unknown;
+ default:
+ break;
+ }
+ if (ctx->opt)
+ check_typos(arg + 1, options);
+ while (ctx->opt) {
+ if (internal_help && *ctx->opt == 'h')
+ return usage_with_options_internal(usagestr, options, 0);
+ arg = ctx->opt;
+ switch (parse_short_opt(ctx, options)) {
+ case -1:
+ return parse_options_usage(usagestr, options, arg, 1);
+ case -2:
+ /* fake a short option thing to hide the fact that we may have
+ * started to parse aggregated stuff
+ *
+ * This is leaky, too bad.
+ */
+ ctx->argv[0] = strdup(ctx->opt - 1);
+ *(char *)ctx->argv[0] = '-';
+ goto unknown;
+ default:
+ break;
+ }
+ }
+ continue;
+ }
+
+ if (!arg[2]) { /* "--" */
+ if (!(ctx->flags & PARSE_OPT_KEEP_DASHDASH)) {
+ ctx->argc--;
+ ctx->argv++;
+ }
+ break;
+ }
+
+ if (internal_help && !strcmp(arg + 2, "help-all"))
+ return usage_with_options_internal(usagestr, options, 1);
+ if (internal_help && !strcmp(arg + 2, "help"))
+ return usage_with_options_internal(usagestr, options, 0);
+ if (!strcmp(arg + 2, "list-opts"))
+ return PARSE_OPT_LIST_OPTS;
+ if (!strcmp(arg + 2, "list-cmds"))
+ return PARSE_OPT_LIST_SUBCMDS;
+ switch (parse_long_opt(ctx, arg + 2, options)) {
+ case -1:
+ return parse_options_usage(usagestr, options, arg + 2, 0);
+ case -2:
+ goto unknown;
+ default:
+ break;
+ }
+ continue;
+unknown:
+ if (!(ctx->flags & PARSE_OPT_KEEP_UNKNOWN))
+ return PARSE_OPT_UNKNOWN;
+ ctx->out[ctx->cpidx++] = ctx->argv[0];
+ ctx->opt = NULL;
+ }
+ return PARSE_OPT_DONE;
+}
+
+int parse_options_end(struct parse_opt_ctx_t *ctx)
+{
+ memmove(ctx->out + ctx->cpidx, ctx->argv, ctx->argc * sizeof(*ctx->out));
+ ctx->out[ctx->cpidx + ctx->argc] = NULL;
+ return ctx->cpidx + ctx->argc;
+}
+
+int parse_options_subcommand(int argc, const char **argv, const struct option *options,
+ const char *const subcommands[], const char *usagestr[], int flags)
+{
+ struct parse_opt_ctx_t ctx;
+
+ perf_header__set_cmdline(argc, argv);
+
+ /* build usage string if it's not provided */
+ if (subcommands && !usagestr[0]) {
+ struct strbuf buf = STRBUF_INIT;
+
+ strbuf_addf(&buf, "perf %s [<options>] {", argv[0]);
+ for (int i = 0; subcommands[i]; i++) {
+ if (i)
+ strbuf_addstr(&buf, "|");
+ strbuf_addstr(&buf, subcommands[i]);
+ }
+ strbuf_addstr(&buf, "}");
+
+ usagestr[0] = strdup(buf.buf);
+ strbuf_release(&buf);
+ }
+
+ parse_options_start(&ctx, argc, argv, flags);
+ switch (parse_options_step(&ctx, options, usagestr)) {
+ case PARSE_OPT_HELP:
+ exit(129);
+ case PARSE_OPT_DONE:
+ break;
+ case PARSE_OPT_LIST_OPTS:
+ while (options->type != OPTION_END) {
+ printf("--%s ", options->long_name);
+ options++;
+ }
+ exit(130);
+ case PARSE_OPT_LIST_SUBCMDS:
+ for (int i = 0; subcommands[i]; i++)
+ printf("%s ", subcommands[i]);
+ exit(130);
+ default: /* PARSE_OPT_UNKNOWN */
+ if (ctx.argv[0][1] == '-') {
+ error("unknown option `%s'", ctx.argv[0] + 2);
+ } else {
+ error("unknown switch `%c'", *ctx.opt);
+ }
+ usage_with_options(usagestr, options);
+ }
+
+ return parse_options_end(&ctx);
+}
+
+int parse_options(int argc, const char **argv, const struct option *options,
+ const char * const usagestr[], int flags)
+{
+ return parse_options_subcommand(argc, argv, options, NULL,
+ (const char **) usagestr, flags);
+}
+
+#define USAGE_OPTS_WIDTH 24
+#define USAGE_GAP 2
+
+static void print_option_help(const struct option *opts, int full)
+{
+ size_t pos;
+ int pad;
+
+ if (opts->type == OPTION_GROUP) {
+ fputc('\n', stderr);
+ if (*opts->help)
+ fprintf(stderr, "%s\n", opts->help);
+ return;
+ }
+ if (!full && (opts->flags & PARSE_OPT_HIDDEN))
+ return;
+
+ pos = fprintf(stderr, " ");
+ if (opts->short_name)
+ pos += fprintf(stderr, "-%c", opts->short_name);
+ else
+ pos += fprintf(stderr, " ");
+
+ if (opts->long_name && opts->short_name)
+ pos += fprintf(stderr, ", ");
+ if (opts->long_name)
+ pos += fprintf(stderr, "--%s", opts->long_name);
+
+ switch (opts->type) {
+ case OPTION_ARGUMENT:
+ break;
+ case OPTION_LONG:
+ case OPTION_U64:
+ case OPTION_INTEGER:
+ case OPTION_UINTEGER:
+ if (opts->flags & PARSE_OPT_OPTARG)
+ if (opts->long_name)
+ pos += fprintf(stderr, "[=<n>]");
+ else
+ pos += fprintf(stderr, "[<n>]");
+ else
+ pos += fprintf(stderr, " <n>");
+ break;
+ case OPTION_CALLBACK:
+ if (opts->flags & PARSE_OPT_NOARG)
+ break;
+ /* FALLTHROUGH */
+ case OPTION_STRING:
+ if (opts->argh) {
+ if (opts->flags & PARSE_OPT_OPTARG)
+ if (opts->long_name)
+ pos += fprintf(stderr, "[=<%s>]", opts->argh);
+ else
+ pos += fprintf(stderr, "[<%s>]", opts->argh);
+ else
+ pos += fprintf(stderr, " <%s>", opts->argh);
+ } else {
+ if (opts->flags & PARSE_OPT_OPTARG)
+ if (opts->long_name)
+ pos += fprintf(stderr, "[=...]");
+ else
+ pos += fprintf(stderr, "[...]");
+ else
+ pos += fprintf(stderr, " ...");
+ }
+ break;
+ default: /* OPTION_{BIT,BOOLEAN,SET_UINT,SET_PTR} */
+ case OPTION_END:
+ case OPTION_GROUP:
+ case OPTION_BIT:
+ case OPTION_BOOLEAN:
+ case OPTION_INCR:
+ case OPTION_SET_UINT:
+ case OPTION_SET_PTR:
+ break;
+ }
+
+ if (pos <= USAGE_OPTS_WIDTH)
+ pad = USAGE_OPTS_WIDTH - pos;
+ else {
+ fputc('\n', stderr);
+ pad = USAGE_OPTS_WIDTH;
+ }
+ fprintf(stderr, "%*s%s\n", pad + USAGE_GAP, "", opts->help);
+}
+
+int usage_with_options_internal(const char * const *usagestr,
+ const struct option *opts, int full)
+{
+ if (!usagestr)
+ return PARSE_OPT_HELP;
+
+ fprintf(stderr, "\n usage: %s\n", *usagestr++);
+ while (*usagestr && **usagestr)
+ fprintf(stderr, " or: %s\n", *usagestr++);
+ while (*usagestr) {
+ fprintf(stderr, "%s%s\n",
+ **usagestr ? " " : "",
+ *usagestr);
+ usagestr++;
+ }
+
+ if (opts->type != OPTION_GROUP)
+ fputc('\n', stderr);
+
+ for ( ; opts->type != OPTION_END; opts++)
+ print_option_help(opts, full);
+
+ fputc('\n', stderr);
+
+ return PARSE_OPT_HELP;
+}
+
+void usage_with_options(const char * const *usagestr,
+ const struct option *opts)
+{
+ exit_browser(false);
+ usage_with_options_internal(usagestr, opts, 0);
+ exit(129);
+}
+
+int parse_options_usage(const char * const *usagestr,
+ const struct option *opts,
+ const char *optstr, bool short_opt)
+{
+ if (!usagestr)
+ goto opt;
+
+ fprintf(stderr, "\n usage: %s\n", *usagestr++);
+ while (*usagestr && **usagestr)
+ fprintf(stderr, " or: %s\n", *usagestr++);
+ while (*usagestr) {
+ fprintf(stderr, "%s%s\n",
+ **usagestr ? " " : "",
+ *usagestr);
+ usagestr++;
+ }
+ fputc('\n', stderr);
+
+opt:
+ for ( ; opts->type != OPTION_END; opts++) {
+ if (short_opt) {
+ if (opts->short_name == *optstr)
+ break;
+ continue;
+ }
+
+ if (opts->long_name == NULL)
+ continue;
+
+ if (!prefixcmp(optstr, opts->long_name))
+ break;
+ if (!prefixcmp(optstr, "no-") &&
+ !prefixcmp(optstr + 3, opts->long_name))
+ break;
+ }
+
+ if (opts->type != OPTION_END)
+ print_option_help(opts, 0);
+
+ return PARSE_OPT_HELP;
+}
+
+
+int parse_opt_verbosity_cb(const struct option *opt,
+ const char *arg __maybe_unused,
+ int unset)
+{
+ int *target = opt->value;
+
+ if (unset)
+ /* --no-quiet, --no-verbose */
+ *target = 0;
+ else if (opt->short_name == 'v') {
+ if (*target >= 0)
+ (*target)++;
+ else
+ *target = 1;
+ } else {
+ if (*target <= 0)
+ (*target)--;
+ else
+ *target = -1;
+ }
+ return 0;
+}
diff --git a/tools/perf/util/parse-options.h b/tools/perf/util/parse-options.h
new file mode 100644
index 00000000000..d8dac8ac5f3
--- /dev/null
+++ b/tools/perf/util/parse-options.h
@@ -0,0 +1,209 @@
+#ifndef __PERF_PARSE_OPTIONS_H
+#define __PERF_PARSE_OPTIONS_H
+
+#include <linux/kernel.h>
+#include <stdbool.h>
+
+enum parse_opt_type {
+ /* special types */
+ OPTION_END,
+ OPTION_ARGUMENT,
+ OPTION_GROUP,
+ /* options with no arguments */
+ OPTION_BIT,
+ OPTION_BOOLEAN,
+ OPTION_INCR,
+ OPTION_SET_UINT,
+ OPTION_SET_PTR,
+ /* options with arguments (usually) */
+ OPTION_STRING,
+ OPTION_INTEGER,
+ OPTION_LONG,
+ OPTION_CALLBACK,
+ OPTION_U64,
+ OPTION_UINTEGER,
+};
+
+enum parse_opt_flags {
+ PARSE_OPT_KEEP_DASHDASH = 1,
+ PARSE_OPT_STOP_AT_NON_OPTION = 2,
+ PARSE_OPT_KEEP_ARGV0 = 4,
+ PARSE_OPT_KEEP_UNKNOWN = 8,
+ PARSE_OPT_NO_INTERNAL_HELP = 16,
+};
+
+enum parse_opt_option_flags {
+ PARSE_OPT_OPTARG = 1,
+ PARSE_OPT_NOARG = 2,
+ PARSE_OPT_NONEG = 4,
+ PARSE_OPT_HIDDEN = 8,
+ PARSE_OPT_LASTARG_DEFAULT = 16,
+};
+
+struct option;
+typedef int parse_opt_cb(const struct option *, const char *arg, int unset);
+
+/*
+ * `type`::
+ * holds the type of the option, you must have an OPTION_END last in your
+ * array.
+ *
+ * `short_name`::
+ * the character to use as a short option name, '\0' if none.
+ *
+ * `long_name`::
+ * the long option name, without the leading dashes, NULL if none.
+ *
+ * `value`::
+ * stores pointers to the values to be filled.
+ *
+ * `argh`::
+ * token to explain the kind of argument this option wants. Keep it
+ * homogenous across the repository.
+ *
+ * `help`::
+ * the short help associated to what the option does.
+ * Must never be NULL (except for OPTION_END).
+ * OPTION_GROUP uses this pointer to store the group header.
+ *
+ * `flags`::
+ * mask of parse_opt_option_flags.
+ * PARSE_OPT_OPTARG: says that the argument is optionnal (not for BOOLEANs)
+ * PARSE_OPT_NOARG: says that this option takes no argument, for CALLBACKs
+ * PARSE_OPT_NONEG: says that this option cannot be negated
+ * PARSE_OPT_HIDDEN this option is skipped in the default usage, showed in
+ * the long one.
+ *
+ * `callback`::
+ * pointer to the callback to use for OPTION_CALLBACK.
+ *
+ * `defval`::
+ * default value to fill (*->value) with for PARSE_OPT_OPTARG.
+ * OPTION_{BIT,SET_UINT,SET_PTR} store the {mask,integer,pointer} to put in
+ * the value when met.
+ * CALLBACKS can use it like they want.
+ *
+ * `set`::
+ * whether an option was set by the user
+ */
+struct option {
+ enum parse_opt_type type;
+ int short_name;
+ const char *long_name;
+ void *value;
+ const char *argh;
+ const char *help;
+
+ int flags;
+ parse_opt_cb *callback;
+ intptr_t defval;
+ bool *set;
+};
+
+#define check_vtype(v, type) ( BUILD_BUG_ON_ZERO(!__builtin_types_compatible_p(typeof(v), type)) + v )
+
+#define OPT_END() { .type = OPTION_END }
+#define OPT_ARGUMENT(l, h) { .type = OPTION_ARGUMENT, .long_name = (l), .help = (h) }
+#define OPT_GROUP(h) { .type = OPTION_GROUP, .help = (h) }
+#define OPT_BIT(s, l, v, h, b) { .type = OPTION_BIT, .short_name = (s), .long_name = (l), .value = check_vtype(v, int *), .help = (h), .defval = (b) }
+#define OPT_BOOLEAN(s, l, v, h) { .type = OPTION_BOOLEAN, .short_name = (s), .long_name = (l), .value = check_vtype(v, bool *), .help = (h) }
+#define OPT_BOOLEAN_SET(s, l, v, os, h) \
+ { .type = OPTION_BOOLEAN, .short_name = (s), .long_name = (l), \
+ .value = check_vtype(v, bool *), .help = (h), \
+ .set = check_vtype(os, bool *)}
+#define OPT_INCR(s, l, v, h) { .type = OPTION_INCR, .short_name = (s), .long_name = (l), .value = check_vtype(v, int *), .help = (h) }
+#define OPT_SET_UINT(s, l, v, h, i) { .type = OPTION_SET_UINT, .short_name = (s), .long_name = (l), .value = check_vtype(v, unsigned int *), .help = (h), .defval = (i) }
+#define OPT_SET_PTR(s, l, v, h, p) { .type = OPTION_SET_PTR, .short_name = (s), .long_name = (l), .value = (v), .help = (h), .defval = (p) }
+#define OPT_INTEGER(s, l, v, h) { .type = OPTION_INTEGER, .short_name = (s), .long_name = (l), .value = check_vtype(v, int *), .help = (h) }
+#define OPT_UINTEGER(s, l, v, h) { .type = OPTION_UINTEGER, .short_name = (s), .long_name = (l), .value = check_vtype(v, unsigned int *), .help = (h) }
+#define OPT_LONG(s, l, v, h) { .type = OPTION_LONG, .short_name = (s), .long_name = (l), .value = check_vtype(v, long *), .help = (h) }
+#define OPT_U64(s, l, v, h) { .type = OPTION_U64, .short_name = (s), .long_name = (l), .value = check_vtype(v, u64 *), .help = (h) }
+#define OPT_STRING(s, l, v, a, h) { .type = OPTION_STRING, .short_name = (s), .long_name = (l), .value = check_vtype(v, const char **), (a), .help = (h) }
+#define OPT_DATE(s, l, v, h) \
+ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), .argh = "time", .help = (h), .callback = parse_opt_approxidate_cb }
+#define OPT_CALLBACK(s, l, v, a, h, f) \
+ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), (a), .help = (h), .callback = (f) }
+#define OPT_CALLBACK_NOOPT(s, l, v, a, h, f) \
+ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), (a), .help = (h), .callback = (f), .flags = PARSE_OPT_NOARG }
+#define OPT_CALLBACK_DEFAULT(s, l, v, a, h, f, d) \
+ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), (a), .help = (h), .callback = (f), .defval = (intptr_t)d, .flags = PARSE_OPT_LASTARG_DEFAULT }
+#define OPT_CALLBACK_DEFAULT_NOOPT(s, l, v, a, h, f, d) \
+ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l),\
+ .value = (v), (a), .help = (h), .callback = (f), .defval = (intptr_t)d,\
+ .flags = PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NOARG}
+
+/* parse_options() will filter out the processed options and leave the
+ * non-option argments in argv[].
+ * Returns the number of arguments left in argv[].
+ */
+extern int parse_options(int argc, const char **argv,
+ const struct option *options,
+ const char * const usagestr[], int flags);
+
+extern int parse_options_subcommand(int argc, const char **argv,
+ const struct option *options,
+ const char *const subcommands[],
+ const char *usagestr[], int flags);
+
+extern NORETURN void usage_with_options(const char * const *usagestr,
+ const struct option *options);
+
+/*----- incremantal advanced APIs -----*/
+
+enum {
+ PARSE_OPT_HELP = -1,
+ PARSE_OPT_DONE,
+ PARSE_OPT_LIST_OPTS,
+ PARSE_OPT_LIST_SUBCMDS,
+ PARSE_OPT_UNKNOWN,
+};
+
+/*
+ * It's okay for the caller to consume argv/argc in the usual way.
+ * Other fields of that structure are private to parse-options and should not
+ * be modified in any way.
+ */
+struct parse_opt_ctx_t {
+ const char **argv;
+ const char **out;
+ int argc, cpidx;
+ const char *opt;
+ int flags;
+};
+
+extern int parse_options_usage(const char * const *usagestr,
+ const struct option *opts,
+ const char *optstr,
+ bool short_opt);
+
+extern void parse_options_start(struct parse_opt_ctx_t *ctx,
+ int argc, const char **argv, int flags);
+
+extern int parse_options_step(struct parse_opt_ctx_t *ctx,
+ const struct option *options,
+ const char * const usagestr[]);
+
+extern int parse_options_end(struct parse_opt_ctx_t *ctx);
+
+
+/*----- some often used options -----*/
+extern int parse_opt_abbrev_cb(const struct option *, const char *, int);
+extern int parse_opt_approxidate_cb(const struct option *, const char *, int);
+extern int parse_opt_verbosity_cb(const struct option *, const char *, int);
+
+#define OPT__VERBOSE(var) OPT_BOOLEAN('v', "verbose", (var), "be verbose")
+#define OPT__QUIET(var) OPT_BOOLEAN('q', "quiet", (var), "be quiet")
+#define OPT__VERBOSITY(var) \
+ { OPTION_CALLBACK, 'v', "verbose", (var), NULL, "be more verbose", \
+ PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }, \
+ { OPTION_CALLBACK, 'q', "quiet", (var), NULL, "be more quiet", \
+ PARSE_OPT_NOARG, &parse_opt_verbosity_cb, 0 }
+#define OPT__DRY_RUN(var) OPT_BOOLEAN('n', "dry-run", (var), "dry run")
+#define OPT__ABBREV(var) \
+ { OPTION_CALLBACK, 0, "abbrev", (var), "n", \
+ "use <n> digits to display SHA-1s", \
+ PARSE_OPT_OPTARG, &parse_opt_abbrev_cb, 0 }
+
+extern const char *parse_options_fix_filename(const char *prefix, const char *file);
+
+#endif /* __PERF_PARSE_OPTIONS_H */
diff --git a/tools/perf/util/path.c b/tools/perf/util/path.c
new file mode 100644
index 00000000000..5d13cb45b31
--- /dev/null
+++ b/tools/perf/util/path.c
@@ -0,0 +1,161 @@
+/*
+ * I'm tired of doing "vsnprintf()" etc just to open a
+ * file, so here's a "return static buffer with printf"
+ * interface for paths.
+ *
+ * It's obviously not thread-safe. Sue me. But it's quite
+ * useful for doing things like
+ *
+ * f = open(mkpath("%s/%s.perf", base, name), O_RDONLY);
+ *
+ * which is what it's designed for.
+ */
+#include "cache.h"
+
+static char bad_path[] = "/bad-path/";
+/*
+ * Two hacks:
+ */
+
+static const char *get_perf_dir(void)
+{
+ return ".";
+}
+
+/*
+ * If libc has strlcpy() then that version will override this
+ * implementation:
+ */
+size_t __weak strlcpy(char *dest, const char *src, size_t size)
+{
+ size_t ret = strlen(src);
+
+ if (size) {
+ size_t len = (ret >= size) ? size - 1 : ret;
+
+ memcpy(dest, src, len);
+ dest[len] = '\0';
+ }
+
+ return ret;
+}
+
+static char *get_pathname(void)
+{
+ static char pathname_array[4][PATH_MAX];
+ static int idx;
+
+ return pathname_array[3 & ++idx];
+}
+
+static char *cleanup_path(char *path)
+{
+ /* Clean it up */
+ if (!memcmp(path, "./", 2)) {
+ path += 2;
+ while (*path == '/')
+ path++;
+ }
+ return path;
+}
+
+static char *perf_vsnpath(char *buf, size_t n, const char *fmt, va_list args)
+{
+ const char *perf_dir = get_perf_dir();
+ size_t len;
+
+ len = strlen(perf_dir);
+ if (n < len + 1)
+ goto bad;
+ memcpy(buf, perf_dir, len);
+ if (len && !is_dir_sep(perf_dir[len-1]))
+ buf[len++] = '/';
+ len += vsnprintf(buf + len, n - len, fmt, args);
+ if (len >= n)
+ goto bad;
+ return cleanup_path(buf);
+bad:
+ strlcpy(buf, bad_path, n);
+ return buf;
+}
+
+char *perf_pathdup(const char *fmt, ...)
+{
+ char path[PATH_MAX];
+ va_list args;
+ va_start(args, fmt);
+ (void)perf_vsnpath(path, sizeof(path), fmt, args);
+ va_end(args);
+ return xstrdup(path);
+}
+
+char *mkpath(const char *fmt, ...)
+{
+ va_list args;
+ unsigned len;
+ char *pathname = get_pathname();
+
+ va_start(args, fmt);
+ len = vsnprintf(pathname, PATH_MAX, fmt, args);
+ va_end(args);
+ if (len >= PATH_MAX)
+ return bad_path;
+ return cleanup_path(pathname);
+}
+
+char *perf_path(const char *fmt, ...)
+{
+ const char *perf_dir = get_perf_dir();
+ char *pathname = get_pathname();
+ va_list args;
+ unsigned len;
+
+ len = strlen(perf_dir);
+ if (len > PATH_MAX-100)
+ return bad_path;
+ memcpy(pathname, perf_dir, len);
+ if (len && perf_dir[len-1] != '/')
+ pathname[len++] = '/';
+ va_start(args, fmt);
+ len += vsnprintf(pathname + len, PATH_MAX - len, fmt, args);
+ va_end(args);
+ if (len >= PATH_MAX)
+ return bad_path;
+ return cleanup_path(pathname);
+}
+
+/* strip arbitrary amount of directory separators at end of path */
+static inline int chomp_trailing_dir_sep(const char *path, int len)
+{
+ while (len && is_dir_sep(path[len - 1]))
+ len--;
+ return len;
+}
+
+/*
+ * If path ends with suffix (complete path components), returns the
+ * part before suffix (sans trailing directory separators).
+ * Otherwise returns NULL.
+ */
+char *strip_path_suffix(const char *path, const char *suffix)
+{
+ int path_len = strlen(path), suffix_len = strlen(suffix);
+
+ while (suffix_len) {
+ if (!path_len)
+ return NULL;
+
+ if (is_dir_sep(path[path_len - 1])) {
+ if (!is_dir_sep(suffix[suffix_len - 1]))
+ return NULL;
+ path_len = chomp_trailing_dir_sep(path, path_len);
+ suffix_len = chomp_trailing_dir_sep(suffix, suffix_len);
+ }
+ else if (path[--path_len] != suffix[--suffix_len])
+ return NULL;
+ }
+
+ if (path_len && !is_dir_sep(path[path_len - 1]))
+ return NULL;
+ return strndup(path, chomp_trailing_dir_sep(path, path_len));
+}
diff --git a/tools/perf/util/perf_regs.c b/tools/perf/util/perf_regs.c
new file mode 100644
index 00000000000..43168fb0d9a
--- /dev/null
+++ b/tools/perf/util/perf_regs.c
@@ -0,0 +1,27 @@
+#include <errno.h>
+#include "perf_regs.h"
+#include "event.h"
+
+int perf_reg_value(u64 *valp, struct regs_dump *regs, int id)
+{
+ int i, idx = 0;
+ u64 mask = regs->mask;
+
+ if (regs->cache_mask & (1 << id))
+ goto out;
+
+ if (!(mask & (1 << id)))
+ return -EINVAL;
+
+ for (i = 0; i < id; i++) {
+ if (mask & (1 << i))
+ idx++;
+ }
+
+ regs->cache_mask |= (1 << id);
+ regs->cache_regs[id] = regs->regs[idx];
+
+out:
+ *valp = regs->cache_regs[id];
+ return 0;
+}
diff --git a/tools/perf/util/perf_regs.h b/tools/perf/util/perf_regs.h
new file mode 100644
index 00000000000..980dbf76bc9
--- /dev/null
+++ b/tools/perf/util/perf_regs.h
@@ -0,0 +1,29 @@
+#ifndef __PERF_REGS_H
+#define __PERF_REGS_H
+
+#include <linux/types.h>
+
+struct regs_dump;
+
+#ifdef HAVE_PERF_REGS_SUPPORT
+#include <perf_regs.h>
+
+int perf_reg_value(u64 *valp, struct regs_dump *regs, int id);
+
+#else
+#define PERF_REGS_MASK 0
+#define PERF_REGS_MAX 0
+
+static inline const char *perf_reg_name(int id __maybe_unused)
+{
+ return NULL;
+}
+
+static inline int perf_reg_value(u64 *valp __maybe_unused,
+ struct regs_dump *regs __maybe_unused,
+ int id __maybe_unused)
+{
+ return 0;
+}
+#endif /* HAVE_PERF_REGS_SUPPORT */
+#endif /* __PERF_REGS_H */
diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c
new file mode 100644
index 00000000000..7a811eb61f7
--- /dev/null
+++ b/tools/perf/util/pmu.c
@@ -0,0 +1,796 @@
+#include <linux/list.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <api/fs/fs.h>
+#include <locale.h>
+#include "util.h"
+#include "pmu.h"
+#include "parse-events.h"
+#include "cpumap.h"
+
+#define UNIT_MAX_LEN 31 /* max length for event unit name */
+
+struct perf_pmu_alias {
+ char *name;
+ struct list_head terms;
+ struct list_head list;
+ char unit[UNIT_MAX_LEN+1];
+ double scale;
+};
+
+struct perf_pmu_format {
+ char *name;
+ int value;
+ DECLARE_BITMAP(bits, PERF_PMU_FORMAT_BITS);
+ struct list_head list;
+};
+
+#define EVENT_SOURCE_DEVICE_PATH "/bus/event_source/devices/"
+
+int perf_pmu_parse(struct list_head *list, char *name);
+extern FILE *perf_pmu_in;
+
+static LIST_HEAD(pmus);
+
+/*
+ * Parse & process all the sysfs attributes located under
+ * the directory specified in 'dir' parameter.
+ */
+int perf_pmu__format_parse(char *dir, struct list_head *head)
+{
+ struct dirent *evt_ent;
+ DIR *format_dir;
+ int ret = 0;
+
+ format_dir = opendir(dir);
+ if (!format_dir)
+ return -EINVAL;
+
+ while (!ret && (evt_ent = readdir(format_dir))) {
+ char path[PATH_MAX];
+ char *name = evt_ent->d_name;
+ FILE *file;
+
+ if (!strcmp(name, ".") || !strcmp(name, ".."))
+ continue;
+
+ snprintf(path, PATH_MAX, "%s/%s", dir, name);
+
+ ret = -EINVAL;
+ file = fopen(path, "r");
+ if (!file)
+ break;
+
+ perf_pmu_in = file;
+ ret = perf_pmu_parse(head, name);
+ fclose(file);
+ }
+
+ closedir(format_dir);
+ return ret;
+}
+
+/*
+ * Reading/parsing the default pmu format definition, which should be
+ * located at:
+ * /sys/bus/event_source/devices/<dev>/format as sysfs group attributes.
+ */
+static int pmu_format(const char *name, struct list_head *format)
+{
+ struct stat st;
+ char path[PATH_MAX];
+ const char *sysfs = sysfs__mountpoint();
+
+ if (!sysfs)
+ return -1;
+
+ snprintf(path, PATH_MAX,
+ "%s" EVENT_SOURCE_DEVICE_PATH "%s/format", sysfs, name);
+
+ if (stat(path, &st) < 0)
+ return 0; /* no error if format does not exist */
+
+ if (perf_pmu__format_parse(path, format))
+ return -1;
+
+ return 0;
+}
+
+static int perf_pmu__parse_scale(struct perf_pmu_alias *alias, char *dir, char *name)
+{
+ struct stat st;
+ ssize_t sret;
+ char scale[128];
+ int fd, ret = -1;
+ char path[PATH_MAX];
+ const char *lc;
+
+ snprintf(path, PATH_MAX, "%s/%s.scale", dir, name);
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return -1;
+
+ if (fstat(fd, &st) < 0)
+ goto error;
+
+ sret = read(fd, scale, sizeof(scale)-1);
+ if (sret < 0)
+ goto error;
+
+ scale[sret] = '\0';
+ /*
+ * save current locale
+ */
+ lc = setlocale(LC_NUMERIC, NULL);
+
+ /*
+ * force to C locale to ensure kernel
+ * scale string is converted correctly.
+ * kernel uses default C locale.
+ */
+ setlocale(LC_NUMERIC, "C");
+
+ alias->scale = strtod(scale, NULL);
+
+ /* restore locale */
+ setlocale(LC_NUMERIC, lc);
+
+ ret = 0;
+error:
+ close(fd);
+ return ret;
+}
+
+static int perf_pmu__parse_unit(struct perf_pmu_alias *alias, char *dir, char *name)
+{
+ char path[PATH_MAX];
+ ssize_t sret;
+ int fd;
+
+ snprintf(path, PATH_MAX, "%s/%s.unit", dir, name);
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return -1;
+
+ sret = read(fd, alias->unit, UNIT_MAX_LEN);
+ if (sret < 0)
+ goto error;
+
+ close(fd);
+
+ alias->unit[sret] = '\0';
+
+ return 0;
+error:
+ close(fd);
+ alias->unit[0] = '\0';
+ return -1;
+}
+
+static int perf_pmu__new_alias(struct list_head *list, char *dir, char *name, FILE *file)
+{
+ struct perf_pmu_alias *alias;
+ char buf[256];
+ int ret;
+
+ ret = fread(buf, 1, sizeof(buf), file);
+ if (ret == 0)
+ return -EINVAL;
+ buf[ret] = 0;
+
+ alias = malloc(sizeof(*alias));
+ if (!alias)
+ return -ENOMEM;
+
+ INIT_LIST_HEAD(&alias->terms);
+ alias->scale = 1.0;
+ alias->unit[0] = '\0';
+
+ ret = parse_events_terms(&alias->terms, buf);
+ if (ret) {
+ free(alias);
+ return ret;
+ }
+
+ alias->name = strdup(name);
+ /*
+ * load unit name and scale if available
+ */
+ perf_pmu__parse_unit(alias, dir, name);
+ perf_pmu__parse_scale(alias, dir, name);
+
+ list_add_tail(&alias->list, list);
+
+ return 0;
+}
+
+/*
+ * Process all the sysfs attributes located under the directory
+ * specified in 'dir' parameter.
+ */
+static int pmu_aliases_parse(char *dir, struct list_head *head)
+{
+ struct dirent *evt_ent;
+ DIR *event_dir;
+ size_t len;
+ int ret = 0;
+
+ event_dir = opendir(dir);
+ if (!event_dir)
+ return -EINVAL;
+
+ while (!ret && (evt_ent = readdir(event_dir))) {
+ char path[PATH_MAX];
+ char *name = evt_ent->d_name;
+ FILE *file;
+
+ if (!strcmp(name, ".") || !strcmp(name, ".."))
+ continue;
+
+ /*
+ * skip .unit and .scale info files
+ * parsed in perf_pmu__new_alias()
+ */
+ len = strlen(name);
+ if (len > 5 && !strcmp(name + len - 5, ".unit"))
+ continue;
+ if (len > 6 && !strcmp(name + len - 6, ".scale"))
+ continue;
+
+ snprintf(path, PATH_MAX, "%s/%s", dir, name);
+
+ ret = -EINVAL;
+ file = fopen(path, "r");
+ if (!file)
+ break;
+
+ ret = perf_pmu__new_alias(head, dir, name, file);
+ fclose(file);
+ }
+
+ closedir(event_dir);
+ return ret;
+}
+
+/*
+ * Reading the pmu event aliases definition, which should be located at:
+ * /sys/bus/event_source/devices/<dev>/events as sysfs group attributes.
+ */
+static int pmu_aliases(const char *name, struct list_head *head)
+{
+ struct stat st;
+ char path[PATH_MAX];
+ const char *sysfs = sysfs__mountpoint();
+
+ if (!sysfs)
+ return -1;
+
+ snprintf(path, PATH_MAX,
+ "%s/bus/event_source/devices/%s/events", sysfs, name);
+
+ if (stat(path, &st) < 0)
+ return 0; /* no error if 'events' does not exist */
+
+ if (pmu_aliases_parse(path, head))
+ return -1;
+
+ return 0;
+}
+
+static int pmu_alias_terms(struct perf_pmu_alias *alias,
+ struct list_head *terms)
+{
+ struct parse_events_term *term, *cloned;
+ LIST_HEAD(list);
+ int ret;
+
+ list_for_each_entry(term, &alias->terms, list) {
+ ret = parse_events_term__clone(&cloned, term);
+ if (ret) {
+ parse_events__free_terms(&list);
+ return ret;
+ }
+ list_add_tail(&cloned->list, &list);
+ }
+ list_splice(&list, terms);
+ return 0;
+}
+
+/*
+ * Reading/parsing the default pmu type value, which should be
+ * located at:
+ * /sys/bus/event_source/devices/<dev>/type as sysfs attribute.
+ */
+static int pmu_type(const char *name, __u32 *type)
+{
+ struct stat st;
+ char path[PATH_MAX];
+ FILE *file;
+ int ret = 0;
+ const char *sysfs = sysfs__mountpoint();
+
+ if (!sysfs)
+ return -1;
+
+ snprintf(path, PATH_MAX,
+ "%s" EVENT_SOURCE_DEVICE_PATH "%s/type", sysfs, name);
+
+ if (stat(path, &st) < 0)
+ return -1;
+
+ file = fopen(path, "r");
+ if (!file)
+ return -EINVAL;
+
+ if (1 != fscanf(file, "%u", type))
+ ret = -1;
+
+ fclose(file);
+ return ret;
+}
+
+/* Add all pmus in sysfs to pmu list: */
+static void pmu_read_sysfs(void)
+{
+ char path[PATH_MAX];
+ DIR *dir;
+ struct dirent *dent;
+ const char *sysfs = sysfs__mountpoint();
+
+ if (!sysfs)
+ return;
+
+ snprintf(path, PATH_MAX,
+ "%s" EVENT_SOURCE_DEVICE_PATH, sysfs);
+
+ dir = opendir(path);
+ if (!dir)
+ return;
+
+ while ((dent = readdir(dir))) {
+ if (!strcmp(dent->d_name, ".") || !strcmp(dent->d_name, ".."))
+ continue;
+ /* add to static LIST_HEAD(pmus): */
+ perf_pmu__find(dent->d_name);
+ }
+
+ closedir(dir);
+}
+
+static struct cpu_map *pmu_cpumask(const char *name)
+{
+ struct stat st;
+ char path[PATH_MAX];
+ FILE *file;
+ struct cpu_map *cpus;
+ const char *sysfs = sysfs__mountpoint();
+
+ if (!sysfs)
+ return NULL;
+
+ snprintf(path, PATH_MAX,
+ "%s/bus/event_source/devices/%s/cpumask", sysfs, name);
+
+ if (stat(path, &st) < 0)
+ return NULL;
+
+ file = fopen(path, "r");
+ if (!file)
+ return NULL;
+
+ cpus = cpu_map__read(file);
+ fclose(file);
+ return cpus;
+}
+
+static struct perf_pmu *pmu_lookup(const char *name)
+{
+ struct perf_pmu *pmu;
+ LIST_HEAD(format);
+ LIST_HEAD(aliases);
+ __u32 type;
+
+ /*
+ * The pmu data we store & need consists of the pmu
+ * type value and format definitions. Load both right
+ * now.
+ */
+ if (pmu_format(name, &format))
+ return NULL;
+
+ if (pmu_aliases(name, &aliases))
+ return NULL;
+
+ if (pmu_type(name, &type))
+ return NULL;
+
+ pmu = zalloc(sizeof(*pmu));
+ if (!pmu)
+ return NULL;
+
+ pmu->cpus = pmu_cpumask(name);
+
+ INIT_LIST_HEAD(&pmu->format);
+ INIT_LIST_HEAD(&pmu->aliases);
+ list_splice(&format, &pmu->format);
+ list_splice(&aliases, &pmu->aliases);
+ pmu->name = strdup(name);
+ pmu->type = type;
+ list_add_tail(&pmu->list, &pmus);
+ return pmu;
+}
+
+static struct perf_pmu *pmu_find(const char *name)
+{
+ struct perf_pmu *pmu;
+
+ list_for_each_entry(pmu, &pmus, list)
+ if (!strcmp(pmu->name, name))
+ return pmu;
+
+ return NULL;
+}
+
+struct perf_pmu *perf_pmu__scan(struct perf_pmu *pmu)
+{
+ /*
+ * pmu iterator: If pmu is NULL, we start at the begin,
+ * otherwise return the next pmu. Returns NULL on end.
+ */
+ if (!pmu) {
+ pmu_read_sysfs();
+ pmu = list_prepare_entry(pmu, &pmus, list);
+ }
+ list_for_each_entry_continue(pmu, &pmus, list)
+ return pmu;
+ return NULL;
+}
+
+struct perf_pmu *perf_pmu__find(const char *name)
+{
+ struct perf_pmu *pmu;
+
+ /*
+ * Once PMU is loaded it stays in the list,
+ * so we keep us from multiple reading/parsing
+ * the pmu format definitions.
+ */
+ pmu = pmu_find(name);
+ if (pmu)
+ return pmu;
+
+ return pmu_lookup(name);
+}
+
+static struct perf_pmu_format *
+pmu_find_format(struct list_head *formats, char *name)
+{
+ struct perf_pmu_format *format;
+
+ list_for_each_entry(format, formats, list)
+ if (!strcmp(format->name, name))
+ return format;
+
+ return NULL;
+}
+
+/*
+ * Returns value based on the format definition (format parameter)
+ * and unformated value (value parameter).
+ *
+ * TODO maybe optimize a little ;)
+ */
+static __u64 pmu_format_value(unsigned long *format, __u64 value)
+{
+ unsigned long fbit, vbit;
+ __u64 v = 0;
+
+ for (fbit = 0, vbit = 0; fbit < PERF_PMU_FORMAT_BITS; fbit++) {
+
+ if (!test_bit(fbit, format))
+ continue;
+
+ if (!(value & (1llu << vbit++)))
+ continue;
+
+ v |= (1llu << fbit);
+ }
+
+ return v;
+}
+
+/*
+ * Setup one of config[12] attr members based on the
+ * user input data - term parameter.
+ */
+static int pmu_config_term(struct list_head *formats,
+ struct perf_event_attr *attr,
+ struct parse_events_term *term)
+{
+ struct perf_pmu_format *format;
+ __u64 *vp;
+
+ /*
+ * Support only for hardcoded and numnerial terms.
+ * Hardcoded terms should be already in, so nothing
+ * to be done for them.
+ */
+ if (parse_events__is_hardcoded_term(term))
+ return 0;
+
+ if (term->type_val != PARSE_EVENTS__TERM_TYPE_NUM)
+ return -EINVAL;
+
+ format = pmu_find_format(formats, term->config);
+ if (!format)
+ return -EINVAL;
+
+ switch (format->value) {
+ case PERF_PMU_FORMAT_VALUE_CONFIG:
+ vp = &attr->config;
+ break;
+ case PERF_PMU_FORMAT_VALUE_CONFIG1:
+ vp = &attr->config1;
+ break;
+ case PERF_PMU_FORMAT_VALUE_CONFIG2:
+ vp = &attr->config2;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ /*
+ * XXX If we ever decide to go with string values for
+ * non-hardcoded terms, here's the place to translate
+ * them into value.
+ */
+ *vp |= pmu_format_value(format->bits, term->val.num);
+ return 0;
+}
+
+int perf_pmu__config_terms(struct list_head *formats,
+ struct perf_event_attr *attr,
+ struct list_head *head_terms)
+{
+ struct parse_events_term *term;
+
+ list_for_each_entry(term, head_terms, list)
+ if (pmu_config_term(formats, attr, term))
+ return -EINVAL;
+
+ return 0;
+}
+
+/*
+ * Configures event's 'attr' parameter based on the:
+ * 1) users input - specified in terms parameter
+ * 2) pmu format definitions - specified by pmu parameter
+ */
+int perf_pmu__config(struct perf_pmu *pmu, struct perf_event_attr *attr,
+ struct list_head *head_terms)
+{
+ attr->type = pmu->type;
+ return perf_pmu__config_terms(&pmu->format, attr, head_terms);
+}
+
+static struct perf_pmu_alias *pmu_find_alias(struct perf_pmu *pmu,
+ struct parse_events_term *term)
+{
+ struct perf_pmu_alias *alias;
+ char *name;
+
+ if (parse_events__is_hardcoded_term(term))
+ return NULL;
+
+ if (term->type_val == PARSE_EVENTS__TERM_TYPE_NUM) {
+ if (term->val.num != 1)
+ return NULL;
+ if (pmu_find_format(&pmu->format, term->config))
+ return NULL;
+ name = term->config;
+ } else if (term->type_val == PARSE_EVENTS__TERM_TYPE_STR) {
+ if (strcasecmp(term->config, "event"))
+ return NULL;
+ name = term->val.str;
+ } else {
+ return NULL;
+ }
+
+ list_for_each_entry(alias, &pmu->aliases, list) {
+ if (!strcasecmp(alias->name, name))
+ return alias;
+ }
+ return NULL;
+}
+
+
+static int check_unit_scale(struct perf_pmu_alias *alias,
+ const char **unit, double *scale)
+{
+ /*
+ * Only one term in event definition can
+ * define unit and scale, fail if there's
+ * more than one.
+ */
+ if ((*unit && alias->unit) ||
+ (*scale && alias->scale))
+ return -EINVAL;
+
+ if (alias->unit)
+ *unit = alias->unit;
+
+ if (alias->scale)
+ *scale = alias->scale;
+
+ return 0;
+}
+
+/*
+ * Find alias in the terms list and replace it with the terms
+ * defined for the alias
+ */
+int perf_pmu__check_alias(struct perf_pmu *pmu, struct list_head *head_terms,
+ const char **unit, double *scale)
+{
+ struct parse_events_term *term, *h;
+ struct perf_pmu_alias *alias;
+ int ret;
+
+ /*
+ * Mark unit and scale as not set
+ * (different from default values, see below)
+ */
+ *unit = NULL;
+ *scale = 0.0;
+
+ list_for_each_entry_safe(term, h, head_terms, list) {
+ alias = pmu_find_alias(pmu, term);
+ if (!alias)
+ continue;
+ ret = pmu_alias_terms(alias, &term->list);
+ if (ret)
+ return ret;
+
+ ret = check_unit_scale(alias, unit, scale);
+ if (ret)
+ return ret;
+
+ list_del(&term->list);
+ free(term);
+ }
+
+ /*
+ * if no unit or scale foundin aliases, then
+ * set defaults as for evsel
+ * unit cannot left to NULL
+ */
+ if (*unit == NULL)
+ *unit = "";
+
+ if (*scale == 0.0)
+ *scale = 1.0;
+
+ return 0;
+}
+
+int perf_pmu__new_format(struct list_head *list, char *name,
+ int config, unsigned long *bits)
+{
+ struct perf_pmu_format *format;
+
+ format = zalloc(sizeof(*format));
+ if (!format)
+ return -ENOMEM;
+
+ format->name = strdup(name);
+ format->value = config;
+ memcpy(format->bits, bits, sizeof(format->bits));
+
+ list_add_tail(&format->list, list);
+ return 0;
+}
+
+void perf_pmu__set_format(unsigned long *bits, long from, long to)
+{
+ long b;
+
+ if (!to)
+ to = from;
+
+ memset(bits, 0, BITS_TO_BYTES(PERF_PMU_FORMAT_BITS));
+ for (b = from; b <= to; b++)
+ set_bit(b, bits);
+}
+
+static char *format_alias(char *buf, int len, struct perf_pmu *pmu,
+ struct perf_pmu_alias *alias)
+{
+ snprintf(buf, len, "%s/%s/", pmu->name, alias->name);
+ return buf;
+}
+
+static char *format_alias_or(char *buf, int len, struct perf_pmu *pmu,
+ struct perf_pmu_alias *alias)
+{
+ snprintf(buf, len, "%s OR %s/%s/", alias->name, pmu->name, alias->name);
+ return buf;
+}
+
+static int cmp_string(const void *a, const void *b)
+{
+ const char * const *as = a;
+ const char * const *bs = b;
+ return strcmp(*as, *bs);
+}
+
+void print_pmu_events(const char *event_glob, bool name_only)
+{
+ struct perf_pmu *pmu;
+ struct perf_pmu_alias *alias;
+ char buf[1024];
+ int printed = 0;
+ int len, j;
+ char **aliases;
+
+ pmu = NULL;
+ len = 0;
+ while ((pmu = perf_pmu__scan(pmu)) != NULL)
+ list_for_each_entry(alias, &pmu->aliases, list)
+ len++;
+ aliases = malloc(sizeof(char *) * len);
+ if (!aliases)
+ return;
+ pmu = NULL;
+ j = 0;
+ while ((pmu = perf_pmu__scan(pmu)) != NULL)
+ list_for_each_entry(alias, &pmu->aliases, list) {
+ char *name = format_alias(buf, sizeof(buf), pmu, alias);
+ bool is_cpu = !strcmp(pmu->name, "cpu");
+
+ if (event_glob != NULL &&
+ !(strglobmatch(name, event_glob) ||
+ (!is_cpu && strglobmatch(alias->name,
+ event_glob))))
+ continue;
+ aliases[j] = name;
+ if (is_cpu && !name_only)
+ aliases[j] = format_alias_or(buf, sizeof(buf),
+ pmu, alias);
+ aliases[j] = strdup(aliases[j]);
+ j++;
+ }
+ len = j;
+ qsort(aliases, len, sizeof(char *), cmp_string);
+ for (j = 0; j < len; j++) {
+ if (name_only) {
+ printf("%s ", aliases[j]);
+ continue;
+ }
+ printf(" %-50s [Kernel PMU event]\n", aliases[j]);
+ zfree(&aliases[j]);
+ printed++;
+ }
+ if (printed)
+ printf("\n");
+ free(aliases);
+}
+
+bool pmu_have_event(const char *pname, const char *name)
+{
+ struct perf_pmu *pmu;
+ struct perf_pmu_alias *alias;
+
+ pmu = NULL;
+ while ((pmu = perf_pmu__scan(pmu)) != NULL) {
+ if (strcmp(pname, pmu->name))
+ continue;
+ list_for_each_entry(alias, &pmu->aliases, list)
+ if (!strcmp(alias->name, name))
+ return true;
+ }
+ return false;
+}
diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h
new file mode 100644
index 00000000000..c14a543ce1f
--- /dev/null
+++ b/tools/perf/util/pmu.h
@@ -0,0 +1,49 @@
+#ifndef __PMU_H
+#define __PMU_H
+
+#include <linux/bitmap.h>
+#include <linux/perf_event.h>
+#include <stdbool.h>
+
+enum {
+ PERF_PMU_FORMAT_VALUE_CONFIG,
+ PERF_PMU_FORMAT_VALUE_CONFIG1,
+ PERF_PMU_FORMAT_VALUE_CONFIG2,
+};
+
+#define PERF_PMU_FORMAT_BITS 64
+
+struct perf_pmu {
+ char *name;
+ __u32 type;
+ struct cpu_map *cpus;
+ struct list_head format;
+ struct list_head aliases;
+ struct list_head list;
+};
+
+struct perf_pmu *perf_pmu__find(const char *name);
+int perf_pmu__config(struct perf_pmu *pmu, struct perf_event_attr *attr,
+ struct list_head *head_terms);
+int perf_pmu__config_terms(struct list_head *formats,
+ struct perf_event_attr *attr,
+ struct list_head *head_terms);
+int perf_pmu__check_alias(struct perf_pmu *pmu, struct list_head *head_terms,
+ const char **unit, double *scale);
+struct list_head *perf_pmu__alias(struct perf_pmu *pmu,
+ struct list_head *head_terms);
+int perf_pmu_wrap(void);
+void perf_pmu_error(struct list_head *list, char *name, char const *msg);
+
+int perf_pmu__new_format(struct list_head *list, char *name,
+ int config, unsigned long *bits);
+void perf_pmu__set_format(unsigned long *bits, long from, long to);
+int perf_pmu__format_parse(char *dir, struct list_head *head);
+
+struct perf_pmu *perf_pmu__scan(struct perf_pmu *pmu);
+
+void print_pmu_events(const char *event_glob, bool name_only);
+bool pmu_have_event(const char *pname, const char *name);
+
+int perf_pmu__test(void);
+#endif /* __PMU_H */
diff --git a/tools/perf/util/pmu.l b/tools/perf/util/pmu.l
new file mode 100644
index 00000000000..a15d9fbd7c0
--- /dev/null
+++ b/tools/perf/util/pmu.l
@@ -0,0 +1,43 @@
+%option prefix="perf_pmu_"
+
+%{
+#include <stdlib.h>
+#include <linux/bitops.h>
+#include "pmu.h"
+#include "pmu-bison.h"
+
+static int value(int base)
+{
+ long num;
+
+ errno = 0;
+ num = strtoul(perf_pmu_text, NULL, base);
+ if (errno)
+ return PP_ERROR;
+
+ perf_pmu_lval.num = num;
+ return PP_VALUE;
+}
+
+%}
+
+num_dec [0-9]+
+
+%%
+
+{num_dec} { return value(10); }
+config { return PP_CONFIG; }
+config1 { return PP_CONFIG1; }
+config2 { return PP_CONFIG2; }
+- { return '-'; }
+: { return ':'; }
+, { return ','; }
+. { ; }
+\n { ; }
+
+%%
+
+int perf_pmu_wrap(void)
+{
+ return 1;
+}
diff --git a/tools/perf/util/pmu.y b/tools/perf/util/pmu.y
new file mode 100644
index 00000000000..bfd7e850986
--- /dev/null
+++ b/tools/perf/util/pmu.y
@@ -0,0 +1,92 @@
+
+%parse-param {struct list_head *format}
+%parse-param {char *name}
+
+%{
+
+#include <linux/compiler.h>
+#include <linux/list.h>
+#include <linux/bitmap.h>
+#include <string.h>
+#include "pmu.h"
+
+extern int perf_pmu_lex (void);
+
+#define ABORT_ON(val) \
+do { \
+ if (val) \
+ YYABORT; \
+} while (0)
+
+%}
+
+%token PP_CONFIG PP_CONFIG1 PP_CONFIG2
+%token PP_VALUE PP_ERROR
+%type <num> PP_VALUE
+%type <bits> bit_term
+%type <bits> bits
+
+%union
+{
+ unsigned long num;
+ DECLARE_BITMAP(bits, PERF_PMU_FORMAT_BITS);
+}
+
+%%
+
+format:
+format format_term
+|
+format_term
+
+format_term:
+PP_CONFIG ':' bits
+{
+ ABORT_ON(perf_pmu__new_format(format, name,
+ PERF_PMU_FORMAT_VALUE_CONFIG,
+ $3));
+}
+|
+PP_CONFIG1 ':' bits
+{
+ ABORT_ON(perf_pmu__new_format(format, name,
+ PERF_PMU_FORMAT_VALUE_CONFIG1,
+ $3));
+}
+|
+PP_CONFIG2 ':' bits
+{
+ ABORT_ON(perf_pmu__new_format(format, name,
+ PERF_PMU_FORMAT_VALUE_CONFIG2,
+ $3));
+}
+
+bits:
+bits ',' bit_term
+{
+ bitmap_or($$, $1, $3, 64);
+}
+|
+bit_term
+{
+ memcpy($$, $1, sizeof($1));
+}
+
+bit_term:
+PP_VALUE '-' PP_VALUE
+{
+ perf_pmu__set_format($$, $1, $3);
+}
+|
+PP_VALUE
+{
+ perf_pmu__set_format($$, $1, 0);
+}
+
+%%
+
+void perf_pmu_error(struct list_head *list __maybe_unused,
+ char *name __maybe_unused,
+ char const *msg __maybe_unused)
+{
+}
diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c
new file mode 100644
index 00000000000..9a0a1839a37
--- /dev/null
+++ b/tools/perf/util/probe-event.c
@@ -0,0 +1,2573 @@
+/*
+ * probe-event.c : perf-probe definition to probe_events format converter
+ *
+ * Written by Masami Hiramatsu <mhiramat@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <limits.h>
+#include <elf.h>
+
+#include "util.h"
+#include "event.h"
+#include "strlist.h"
+#include "debug.h"
+#include "cache.h"
+#include "color.h"
+#include "symbol.h"
+#include "thread.h"
+#include <api/fs/debugfs.h>
+#include "trace-event.h" /* For __maybe_unused */
+#include "probe-event.h"
+#include "probe-finder.h"
+#include "session.h"
+
+#define MAX_CMDLEN 256
+#define PERFPROBE_GROUP "probe"
+
+bool probe_event_dry_run; /* Dry run flag */
+
+#define semantic_error(msg ...) pr_err("Semantic error :" msg)
+
+/* If there is no space to write, returns -E2BIG. */
+static int e_snprintf(char *str, size_t size, const char *format, ...)
+ __attribute__((format(printf, 3, 4)));
+
+static int e_snprintf(char *str, size_t size, const char *format, ...)
+{
+ int ret;
+ va_list ap;
+ va_start(ap, format);
+ ret = vsnprintf(str, size, format, ap);
+ va_end(ap);
+ if (ret >= (int)size)
+ ret = -E2BIG;
+ return ret;
+}
+
+static char *synthesize_perf_probe_point(struct perf_probe_point *pp);
+static void clear_probe_trace_event(struct probe_trace_event *tev);
+static struct machine *host_machine;
+
+/* Initialize symbol maps and path of vmlinux/modules */
+static int init_symbol_maps(bool user_only)
+{
+ int ret;
+
+ symbol_conf.sort_by_name = true;
+ ret = symbol__init();
+ if (ret < 0) {
+ pr_debug("Failed to init symbol map.\n");
+ goto out;
+ }
+
+ if (host_machine || user_only) /* already initialized */
+ return 0;
+
+ if (symbol_conf.vmlinux_name)
+ pr_debug("Use vmlinux: %s\n", symbol_conf.vmlinux_name);
+
+ host_machine = machine__new_host();
+ if (!host_machine) {
+ pr_debug("machine__new_host() failed.\n");
+ symbol__exit();
+ ret = -1;
+ }
+out:
+ if (ret < 0)
+ pr_warning("Failed to init vmlinux path.\n");
+ return ret;
+}
+
+static void exit_symbol_maps(void)
+{
+ if (host_machine) {
+ machine__delete(host_machine);
+ host_machine = NULL;
+ }
+ symbol__exit();
+}
+
+static struct symbol *__find_kernel_function_by_name(const char *name,
+ struct map **mapp)
+{
+ return machine__find_kernel_function_by_name(host_machine, name, mapp,
+ NULL);
+}
+
+static struct symbol *__find_kernel_function(u64 addr, struct map **mapp)
+{
+ return machine__find_kernel_function(host_machine, addr, mapp, NULL);
+}
+
+static struct ref_reloc_sym *kernel_get_ref_reloc_sym(void)
+{
+ /* kmap->ref_reloc_sym should be set if host_machine is initialized */
+ struct kmap *kmap;
+
+ if (map__load(host_machine->vmlinux_maps[MAP__FUNCTION], NULL) < 0)
+ return NULL;
+
+ kmap = map__kmap(host_machine->vmlinux_maps[MAP__FUNCTION]);
+ return kmap->ref_reloc_sym;
+}
+
+static u64 kernel_get_symbol_address_by_name(const char *name, bool reloc)
+{
+ struct ref_reloc_sym *reloc_sym;
+ struct symbol *sym;
+ struct map *map;
+
+ /* ref_reloc_sym is just a label. Need a special fix*/
+ reloc_sym = kernel_get_ref_reloc_sym();
+ if (reloc_sym && strcmp(name, reloc_sym->name) == 0)
+ return (reloc) ? reloc_sym->addr : reloc_sym->unrelocated_addr;
+ else {
+ sym = __find_kernel_function_by_name(name, &map);
+ if (sym)
+ return map->unmap_ip(map, sym->start) -
+ (reloc) ? 0 : map->reloc;
+ }
+ return 0;
+}
+
+static struct map *kernel_get_module_map(const char *module)
+{
+ struct rb_node *nd;
+ struct map_groups *grp = &host_machine->kmaps;
+
+ /* A file path -- this is an offline module */
+ if (module && strchr(module, '/'))
+ return machine__new_module(host_machine, 0, module);
+
+ if (!module)
+ module = "kernel";
+
+ for (nd = rb_first(&grp->maps[MAP__FUNCTION]); nd; nd = rb_next(nd)) {
+ struct map *pos = rb_entry(nd, struct map, rb_node);
+ if (strncmp(pos->dso->short_name + 1, module,
+ pos->dso->short_name_len - 2) == 0) {
+ return pos;
+ }
+ }
+ return NULL;
+}
+
+static struct dso *kernel_get_module_dso(const char *module)
+{
+ struct dso *dso;
+ struct map *map;
+ const char *vmlinux_name;
+
+ if (module) {
+ list_for_each_entry(dso, &host_machine->kernel_dsos, node) {
+ if (strncmp(dso->short_name + 1, module,
+ dso->short_name_len - 2) == 0)
+ goto found;
+ }
+ pr_debug("Failed to find module %s.\n", module);
+ return NULL;
+ }
+
+ map = host_machine->vmlinux_maps[MAP__FUNCTION];
+ dso = map->dso;
+
+ vmlinux_name = symbol_conf.vmlinux_name;
+ if (vmlinux_name) {
+ if (dso__load_vmlinux(dso, map, vmlinux_name, false, NULL) <= 0)
+ return NULL;
+ } else {
+ if (dso__load_vmlinux_path(dso, map, NULL) <= 0) {
+ pr_debug("Failed to load kernel map.\n");
+ return NULL;
+ }
+ }
+found:
+ return dso;
+}
+
+const char *kernel_get_module_path(const char *module)
+{
+ struct dso *dso = kernel_get_module_dso(module);
+ return (dso) ? dso->long_name : NULL;
+}
+
+static int convert_exec_to_group(const char *exec, char **result)
+{
+ char *ptr1, *ptr2, *exec_copy;
+ char buf[64];
+ int ret;
+
+ exec_copy = strdup(exec);
+ if (!exec_copy)
+ return -ENOMEM;
+
+ ptr1 = basename(exec_copy);
+ if (!ptr1) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ptr2 = strpbrk(ptr1, "-._");
+ if (ptr2)
+ *ptr2 = '\0';
+ ret = e_snprintf(buf, 64, "%s_%s", PERFPROBE_GROUP, ptr1);
+ if (ret < 0)
+ goto out;
+
+ *result = strdup(buf);
+ ret = *result ? 0 : -ENOMEM;
+
+out:
+ free(exec_copy);
+ return ret;
+}
+
+static void clear_probe_trace_events(struct probe_trace_event *tevs, int ntevs)
+{
+ int i;
+
+ for (i = 0; i < ntevs; i++)
+ clear_probe_trace_event(tevs + i);
+}
+
+#ifdef HAVE_DWARF_SUPPORT
+
+/* Open new debuginfo of given module */
+static struct debuginfo *open_debuginfo(const char *module)
+{
+ const char *path = module;
+
+ if (!module || !strchr(module, '/')) {
+ path = kernel_get_module_path(module);
+ if (!path) {
+ pr_err("Failed to find path of %s module.\n",
+ module ?: "kernel");
+ return NULL;
+ }
+ }
+ return debuginfo__new(path);
+}
+
+static int get_text_start_address(const char *exec, unsigned long *address)
+{
+ Elf *elf;
+ GElf_Ehdr ehdr;
+ GElf_Shdr shdr;
+ int fd, ret = -ENOENT;
+
+ fd = open(exec, O_RDONLY);
+ if (fd < 0)
+ return -errno;
+
+ elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL);
+ if (elf == NULL)
+ return -EINVAL;
+
+ if (gelf_getehdr(elf, &ehdr) == NULL)
+ goto out;
+
+ if (!elf_section_by_name(elf, &ehdr, &shdr, ".text", NULL))
+ goto out;
+
+ *address = shdr.sh_addr - shdr.sh_offset;
+ ret = 0;
+out:
+ elf_end(elf);
+ return ret;
+}
+
+/*
+ * Convert trace point to probe point with debuginfo
+ */
+static int find_perf_probe_point_from_dwarf(struct probe_trace_point *tp,
+ struct perf_probe_point *pp,
+ bool is_kprobe)
+{
+ struct debuginfo *dinfo = NULL;
+ unsigned long stext = 0;
+ u64 addr = tp->address;
+ int ret = -ENOENT;
+
+ /* convert the address to dwarf address */
+ if (!is_kprobe) {
+ if (!addr) {
+ ret = -EINVAL;
+ goto error;
+ }
+ ret = get_text_start_address(tp->module, &stext);
+ if (ret < 0)
+ goto error;
+ addr += stext;
+ } else {
+ addr = kernel_get_symbol_address_by_name(tp->symbol, false);
+ if (addr == 0)
+ goto error;
+ addr += tp->offset;
+ }
+
+ pr_debug("try to find information at %" PRIx64 " in %s\n", addr,
+ tp->module ? : "kernel");
+
+ dinfo = open_debuginfo(tp->module);
+ if (dinfo) {
+ ret = debuginfo__find_probe_point(dinfo,
+ (unsigned long)addr, pp);
+ debuginfo__delete(dinfo);
+ } else {
+ pr_debug("Failed to open debuginfo at 0x%" PRIx64 "\n", addr);
+ ret = -ENOENT;
+ }
+
+ if (ret > 0) {
+ pp->retprobe = tp->retprobe;
+ return 0;
+ }
+error:
+ pr_debug("Failed to find corresponding probes from debuginfo.\n");
+ return ret ? : -ENOENT;
+}
+
+static int add_exec_to_probe_trace_events(struct probe_trace_event *tevs,
+ int ntevs, const char *exec)
+{
+ int i, ret = 0;
+ unsigned long stext = 0;
+
+ if (!exec)
+ return 0;
+
+ ret = get_text_start_address(exec, &stext);
+ if (ret < 0)
+ return ret;
+
+ for (i = 0; i < ntevs && ret >= 0; i++) {
+ /* point.address is the addres of point.symbol + point.offset */
+ tevs[i].point.address -= stext;
+ tevs[i].point.module = strdup(exec);
+ if (!tevs[i].point.module) {
+ ret = -ENOMEM;
+ break;
+ }
+ tevs[i].uprobes = true;
+ }
+
+ return ret;
+}
+
+static int add_module_to_probe_trace_events(struct probe_trace_event *tevs,
+ int ntevs, const char *module)
+{
+ int i, ret = 0;
+ char *tmp;
+
+ if (!module)
+ return 0;
+
+ tmp = strrchr(module, '/');
+ if (tmp) {
+ /* This is a module path -- get the module name */
+ module = strdup(tmp + 1);
+ if (!module)
+ return -ENOMEM;
+ tmp = strchr(module, '.');
+ if (tmp)
+ *tmp = '\0';
+ tmp = (char *)module; /* For free() */
+ }
+
+ for (i = 0; i < ntevs; i++) {
+ tevs[i].point.module = strdup(module);
+ if (!tevs[i].point.module) {
+ ret = -ENOMEM;
+ break;
+ }
+ }
+
+ free(tmp);
+ return ret;
+}
+
+/* Post processing the probe events */
+static int post_process_probe_trace_events(struct probe_trace_event *tevs,
+ int ntevs, const char *module,
+ bool uprobe)
+{
+ struct ref_reloc_sym *reloc_sym;
+ char *tmp;
+ int i;
+
+ if (uprobe)
+ return add_exec_to_probe_trace_events(tevs, ntevs, module);
+
+ /* Note that currently ref_reloc_sym based probe is not for drivers */
+ if (module)
+ return add_module_to_probe_trace_events(tevs, ntevs, module);
+
+ reloc_sym = kernel_get_ref_reloc_sym();
+ if (!reloc_sym) {
+ pr_warning("Relocated base symbol is not found!\n");
+ return -EINVAL;
+ }
+
+ for (i = 0; i < ntevs; i++) {
+ if (tevs[i].point.address) {
+ tmp = strdup(reloc_sym->name);
+ if (!tmp)
+ return -ENOMEM;
+ free(tevs[i].point.symbol);
+ tevs[i].point.symbol = tmp;
+ tevs[i].point.offset = tevs[i].point.address -
+ reloc_sym->unrelocated_addr;
+ }
+ }
+ return 0;
+}
+
+/* Try to find perf_probe_event with debuginfo */
+static int try_to_find_probe_trace_events(struct perf_probe_event *pev,
+ struct probe_trace_event **tevs,
+ int max_tevs, const char *target)
+{
+ bool need_dwarf = perf_probe_event_need_dwarf(pev);
+ struct debuginfo *dinfo;
+ int ntevs, ret = 0;
+
+ dinfo = open_debuginfo(target);
+
+ if (!dinfo) {
+ if (need_dwarf) {
+ pr_warning("Failed to open debuginfo file.\n");
+ return -ENOENT;
+ }
+ pr_debug("Could not open debuginfo. Try to use symbols.\n");
+ return 0;
+ }
+
+ pr_debug("Try to find probe point from debuginfo.\n");
+ /* Searching trace events corresponding to a probe event */
+ ntevs = debuginfo__find_trace_events(dinfo, pev, tevs, max_tevs);
+
+ debuginfo__delete(dinfo);
+
+ if (ntevs > 0) { /* Succeeded to find trace events */
+ pr_debug("Found %d probe_trace_events.\n", ntevs);
+ ret = post_process_probe_trace_events(*tevs, ntevs,
+ target, pev->uprobes);
+ if (ret < 0) {
+ clear_probe_trace_events(*tevs, ntevs);
+ zfree(tevs);
+ }
+ return ret < 0 ? ret : ntevs;
+ }
+
+ if (ntevs == 0) { /* No error but failed to find probe point. */
+ pr_warning("Probe point '%s' not found.\n",
+ synthesize_perf_probe_point(&pev->point));
+ return -ENOENT;
+ }
+ /* Error path : ntevs < 0 */
+ pr_debug("An error occurred in debuginfo analysis (%d).\n", ntevs);
+ if (ntevs == -EBADF) {
+ pr_warning("Warning: No dwarf info found in the vmlinux - "
+ "please rebuild kernel with CONFIG_DEBUG_INFO=y.\n");
+ if (!need_dwarf) {
+ pr_debug("Trying to use symbols.\n");
+ return 0;
+ }
+ }
+ return ntevs;
+}
+
+/*
+ * Find a src file from a DWARF tag path. Prepend optional source path prefix
+ * and chop off leading directories that do not exist. Result is passed back as
+ * a newly allocated path on success.
+ * Return 0 if file was found and readable, -errno otherwise.
+ */
+static int get_real_path(const char *raw_path, const char *comp_dir,
+ char **new_path)
+{
+ const char *prefix = symbol_conf.source_prefix;
+
+ if (!prefix) {
+ if (raw_path[0] != '/' && comp_dir)
+ /* If not an absolute path, try to use comp_dir */
+ prefix = comp_dir;
+ else {
+ if (access(raw_path, R_OK) == 0) {
+ *new_path = strdup(raw_path);
+ return 0;
+ } else
+ return -errno;
+ }
+ }
+
+ *new_path = malloc((strlen(prefix) + strlen(raw_path) + 2));
+ if (!*new_path)
+ return -ENOMEM;
+
+ for (;;) {
+ sprintf(*new_path, "%s/%s", prefix, raw_path);
+
+ if (access(*new_path, R_OK) == 0)
+ return 0;
+
+ if (!symbol_conf.source_prefix)
+ /* In case of searching comp_dir, don't retry */
+ return -errno;
+
+ switch (errno) {
+ case ENAMETOOLONG:
+ case ENOENT:
+ case EROFS:
+ case EFAULT:
+ raw_path = strchr(++raw_path, '/');
+ if (!raw_path) {
+ zfree(new_path);
+ return -ENOENT;
+ }
+ continue;
+
+ default:
+ zfree(new_path);
+ return -errno;
+ }
+ }
+}
+
+#define LINEBUF_SIZE 256
+#define NR_ADDITIONAL_LINES 2
+
+static int __show_one_line(FILE *fp, int l, bool skip, bool show_num)
+{
+ char buf[LINEBUF_SIZE];
+ const char *color = show_num ? "" : PERF_COLOR_BLUE;
+ const char *prefix = NULL;
+
+ do {
+ if (fgets(buf, LINEBUF_SIZE, fp) == NULL)
+ goto error;
+ if (skip)
+ continue;
+ if (!prefix) {
+ prefix = show_num ? "%7d " : " ";
+ color_fprintf(stdout, color, prefix, l);
+ }
+ color_fprintf(stdout, color, "%s", buf);
+
+ } while (strchr(buf, '\n') == NULL);
+
+ return 1;
+error:
+ if (ferror(fp)) {
+ pr_warning("File read error: %s\n", strerror(errno));
+ return -1;
+ }
+ return 0;
+}
+
+static int _show_one_line(FILE *fp, int l, bool skip, bool show_num)
+{
+ int rv = __show_one_line(fp, l, skip, show_num);
+ if (rv == 0) {
+ pr_warning("Source file is shorter than expected.\n");
+ rv = -1;
+ }
+ return rv;
+}
+
+#define show_one_line_with_num(f,l) _show_one_line(f,l,false,true)
+#define show_one_line(f,l) _show_one_line(f,l,false,false)
+#define skip_one_line(f,l) _show_one_line(f,l,true,false)
+#define show_one_line_or_eof(f,l) __show_one_line(f,l,false,false)
+
+/*
+ * Show line-range always requires debuginfo to find source file and
+ * line number.
+ */
+static int __show_line_range(struct line_range *lr, const char *module)
+{
+ int l = 1;
+ struct int_node *ln;
+ struct debuginfo *dinfo;
+ FILE *fp;
+ int ret;
+ char *tmp;
+
+ /* Search a line range */
+ dinfo = open_debuginfo(module);
+ if (!dinfo) {
+ pr_warning("Failed to open debuginfo file.\n");
+ return -ENOENT;
+ }
+
+ ret = debuginfo__find_line_range(dinfo, lr);
+ debuginfo__delete(dinfo);
+ if (ret == 0 || ret == -ENOENT) {
+ pr_warning("Specified source line is not found.\n");
+ return -ENOENT;
+ } else if (ret < 0) {
+ pr_warning("Debuginfo analysis failed.\n");
+ return ret;
+ }
+
+ /* Convert source file path */
+ tmp = lr->path;
+ ret = get_real_path(tmp, lr->comp_dir, &lr->path);
+ free(tmp); /* Free old path */
+ if (ret < 0) {
+ pr_warning("Failed to find source file path.\n");
+ return ret;
+ }
+
+ setup_pager();
+
+ if (lr->function)
+ fprintf(stdout, "<%s@%s:%d>\n", lr->function, lr->path,
+ lr->start - lr->offset);
+ else
+ fprintf(stdout, "<%s:%d>\n", lr->path, lr->start);
+
+ fp = fopen(lr->path, "r");
+ if (fp == NULL) {
+ pr_warning("Failed to open %s: %s\n", lr->path,
+ strerror(errno));
+ return -errno;
+ }
+ /* Skip to starting line number */
+ while (l < lr->start) {
+ ret = skip_one_line(fp, l++);
+ if (ret < 0)
+ goto end;
+ }
+
+ intlist__for_each(ln, lr->line_list) {
+ for (; ln->i > l; l++) {
+ ret = show_one_line(fp, l - lr->offset);
+ if (ret < 0)
+ goto end;
+ }
+ ret = show_one_line_with_num(fp, l++ - lr->offset);
+ if (ret < 0)
+ goto end;
+ }
+
+ if (lr->end == INT_MAX)
+ lr->end = l + NR_ADDITIONAL_LINES;
+ while (l <= lr->end) {
+ ret = show_one_line_or_eof(fp, l++ - lr->offset);
+ if (ret <= 0)
+ break;
+ }
+end:
+ fclose(fp);
+ return ret;
+}
+
+int show_line_range(struct line_range *lr, const char *module)
+{
+ int ret;
+
+ ret = init_symbol_maps(false);
+ if (ret < 0)
+ return ret;
+ ret = __show_line_range(lr, module);
+ exit_symbol_maps();
+
+ return ret;
+}
+
+static int show_available_vars_at(struct debuginfo *dinfo,
+ struct perf_probe_event *pev,
+ int max_vls, struct strfilter *_filter,
+ bool externs)
+{
+ char *buf;
+ int ret, i, nvars;
+ struct str_node *node;
+ struct variable_list *vls = NULL, *vl;
+ const char *var;
+
+ buf = synthesize_perf_probe_point(&pev->point);
+ if (!buf)
+ return -EINVAL;
+ pr_debug("Searching variables at %s\n", buf);
+
+ ret = debuginfo__find_available_vars_at(dinfo, pev, &vls,
+ max_vls, externs);
+ if (ret <= 0) {
+ if (ret == 0 || ret == -ENOENT) {
+ pr_err("Failed to find the address of %s\n", buf);
+ ret = -ENOENT;
+ } else
+ pr_warning("Debuginfo analysis failed.\n");
+ goto end;
+ }
+
+ /* Some variables are found */
+ fprintf(stdout, "Available variables at %s\n", buf);
+ for (i = 0; i < ret; i++) {
+ vl = &vls[i];
+ /*
+ * A probe point might be converted to
+ * several trace points.
+ */
+ fprintf(stdout, "\t@<%s+%lu>\n", vl->point.symbol,
+ vl->point.offset);
+ zfree(&vl->point.symbol);
+ nvars = 0;
+ if (vl->vars) {
+ strlist__for_each(node, vl->vars) {
+ var = strchr(node->s, '\t') + 1;
+ if (strfilter__compare(_filter, var)) {
+ fprintf(stdout, "\t\t%s\n", node->s);
+ nvars++;
+ }
+ }
+ strlist__delete(vl->vars);
+ }
+ if (nvars == 0)
+ fprintf(stdout, "\t\t(No matched variables)\n");
+ }
+ free(vls);
+end:
+ free(buf);
+ return ret;
+}
+
+/* Show available variables on given probe point */
+int show_available_vars(struct perf_probe_event *pevs, int npevs,
+ int max_vls, const char *module,
+ struct strfilter *_filter, bool externs)
+{
+ int i, ret = 0;
+ struct debuginfo *dinfo;
+
+ ret = init_symbol_maps(false);
+ if (ret < 0)
+ return ret;
+
+ dinfo = open_debuginfo(module);
+ if (!dinfo) {
+ pr_warning("Failed to open debuginfo file.\n");
+ ret = -ENOENT;
+ goto out;
+ }
+
+ setup_pager();
+
+ for (i = 0; i < npevs && ret >= 0; i++)
+ ret = show_available_vars_at(dinfo, &pevs[i], max_vls, _filter,
+ externs);
+
+ debuginfo__delete(dinfo);
+out:
+ exit_symbol_maps();
+ return ret;
+}
+
+#else /* !HAVE_DWARF_SUPPORT */
+
+static int
+find_perf_probe_point_from_dwarf(struct probe_trace_point *tp __maybe_unused,
+ struct perf_probe_point *pp __maybe_unused,
+ bool is_kprobe __maybe_unused)
+{
+ return -ENOSYS;
+}
+
+static int try_to_find_probe_trace_events(struct perf_probe_event *pev,
+ struct probe_trace_event **tevs __maybe_unused,
+ int max_tevs __maybe_unused,
+ const char *target __maybe_unused)
+{
+ if (perf_probe_event_need_dwarf(pev)) {
+ pr_warning("Debuginfo-analysis is not supported.\n");
+ return -ENOSYS;
+ }
+
+ return 0;
+}
+
+int show_line_range(struct line_range *lr __maybe_unused,
+ const char *module __maybe_unused)
+{
+ pr_warning("Debuginfo-analysis is not supported.\n");
+ return -ENOSYS;
+}
+
+int show_available_vars(struct perf_probe_event *pevs __maybe_unused,
+ int npevs __maybe_unused, int max_vls __maybe_unused,
+ const char *module __maybe_unused,
+ struct strfilter *filter __maybe_unused,
+ bool externs __maybe_unused)
+{
+ pr_warning("Debuginfo-analysis is not supported.\n");
+ return -ENOSYS;
+}
+#endif
+
+void line_range__clear(struct line_range *lr)
+{
+ free(lr->function);
+ free(lr->file);
+ free(lr->path);
+ free(lr->comp_dir);
+ intlist__delete(lr->line_list);
+ memset(lr, 0, sizeof(*lr));
+}
+
+int line_range__init(struct line_range *lr)
+{
+ memset(lr, 0, sizeof(*lr));
+ lr->line_list = intlist__new(NULL);
+ if (!lr->line_list)
+ return -ENOMEM;
+ else
+ return 0;
+}
+
+static int parse_line_num(char **ptr, int *val, const char *what)
+{
+ const char *start = *ptr;
+
+ errno = 0;
+ *val = strtol(*ptr, ptr, 0);
+ if (errno || *ptr == start) {
+ semantic_error("'%s' is not a valid number.\n", what);
+ return -EINVAL;
+ }
+ return 0;
+}
+
+/*
+ * Stuff 'lr' according to the line range described by 'arg'.
+ * The line range syntax is described by:
+ *
+ * SRC[:SLN[+NUM|-ELN]]
+ * FNC[@SRC][:SLN[+NUM|-ELN]]
+ */
+int parse_line_range_desc(const char *arg, struct line_range *lr)
+{
+ char *range, *file, *name = strdup(arg);
+ int err;
+
+ if (!name)
+ return -ENOMEM;
+
+ lr->start = 0;
+ lr->end = INT_MAX;
+
+ range = strchr(name, ':');
+ if (range) {
+ *range++ = '\0';
+
+ err = parse_line_num(&range, &lr->start, "start line");
+ if (err)
+ goto err;
+
+ if (*range == '+' || *range == '-') {
+ const char c = *range++;
+
+ err = parse_line_num(&range, &lr->end, "end line");
+ if (err)
+ goto err;
+
+ if (c == '+') {
+ lr->end += lr->start;
+ /*
+ * Adjust the number of lines here.
+ * If the number of lines == 1, the
+ * the end of line should be equal to
+ * the start of line.
+ */
+ lr->end--;
+ }
+ }
+
+ pr_debug("Line range is %d to %d\n", lr->start, lr->end);
+
+ err = -EINVAL;
+ if (lr->start > lr->end) {
+ semantic_error("Start line must be smaller"
+ " than end line.\n");
+ goto err;
+ }
+ if (*range != '\0') {
+ semantic_error("Tailing with invalid str '%s'.\n", range);
+ goto err;
+ }
+ }
+
+ file = strchr(name, '@');
+ if (file) {
+ *file = '\0';
+ lr->file = strdup(++file);
+ if (lr->file == NULL) {
+ err = -ENOMEM;
+ goto err;
+ }
+ lr->function = name;
+ } else if (strchr(name, '.'))
+ lr->file = name;
+ else
+ lr->function = name;
+
+ return 0;
+err:
+ free(name);
+ return err;
+}
+
+/* Check the name is good for event/group */
+static bool check_event_name(const char *name)
+{
+ if (!isalpha(*name) && *name != '_')
+ return false;
+ while (*++name != '\0') {
+ if (!isalpha(*name) && !isdigit(*name) && *name != '_')
+ return false;
+ }
+ return true;
+}
+
+/* Parse probepoint definition. */
+static int parse_perf_probe_point(char *arg, struct perf_probe_event *pev)
+{
+ struct perf_probe_point *pp = &pev->point;
+ char *ptr, *tmp;
+ char c, nc = 0;
+ /*
+ * <Syntax>
+ * perf probe [EVENT=]SRC[:LN|;PTN]
+ * perf probe [EVENT=]FUNC[@SRC][+OFFS|%return|:LN|;PAT]
+ *
+ * TODO:Group name support
+ */
+
+ ptr = strpbrk(arg, ";=@+%");
+ if (ptr && *ptr == '=') { /* Event name */
+ *ptr = '\0';
+ tmp = ptr + 1;
+ if (strchr(arg, ':')) {
+ semantic_error("Group name is not supported yet.\n");
+ return -ENOTSUP;
+ }
+ if (!check_event_name(arg)) {
+ semantic_error("%s is bad for event name -it must "
+ "follow C symbol-naming rule.\n", arg);
+ return -EINVAL;
+ }
+ pev->event = strdup(arg);
+ if (pev->event == NULL)
+ return -ENOMEM;
+ pev->group = NULL;
+ arg = tmp;
+ }
+
+ ptr = strpbrk(arg, ";:+@%");
+ if (ptr) {
+ nc = *ptr;
+ *ptr++ = '\0';
+ }
+
+ tmp = strdup(arg);
+ if (tmp == NULL)
+ return -ENOMEM;
+
+ /* Check arg is function or file and copy it */
+ if (strchr(tmp, '.')) /* File */
+ pp->file = tmp;
+ else /* Function */
+ pp->function = tmp;
+
+ /* Parse other options */
+ while (ptr) {
+ arg = ptr;
+ c = nc;
+ if (c == ';') { /* Lazy pattern must be the last part */
+ pp->lazy_line = strdup(arg);
+ if (pp->lazy_line == NULL)
+ return -ENOMEM;
+ break;
+ }
+ ptr = strpbrk(arg, ";:+@%");
+ if (ptr) {
+ nc = *ptr;
+ *ptr++ = '\0';
+ }
+ switch (c) {
+ case ':': /* Line number */
+ pp->line = strtoul(arg, &tmp, 0);
+ if (*tmp != '\0') {
+ semantic_error("There is non-digit char"
+ " in line number.\n");
+ return -EINVAL;
+ }
+ break;
+ case '+': /* Byte offset from a symbol */
+ pp->offset = strtoul(arg, &tmp, 0);
+ if (*tmp != '\0') {
+ semantic_error("There is non-digit character"
+ " in offset.\n");
+ return -EINVAL;
+ }
+ break;
+ case '@': /* File name */
+ if (pp->file) {
+ semantic_error("SRC@SRC is not allowed.\n");
+ return -EINVAL;
+ }
+ pp->file = strdup(arg);
+ if (pp->file == NULL)
+ return -ENOMEM;
+ break;
+ case '%': /* Probe places */
+ if (strcmp(arg, "return") == 0) {
+ pp->retprobe = 1;
+ } else { /* Others not supported yet */
+ semantic_error("%%%s is not supported.\n", arg);
+ return -ENOTSUP;
+ }
+ break;
+ default: /* Buggy case */
+ pr_err("This program has a bug at %s:%d.\n",
+ __FILE__, __LINE__);
+ return -ENOTSUP;
+ break;
+ }
+ }
+
+ /* Exclusion check */
+ if (pp->lazy_line && pp->line) {
+ semantic_error("Lazy pattern can't be used with"
+ " line number.\n");
+ return -EINVAL;
+ }
+
+ if (pp->lazy_line && pp->offset) {
+ semantic_error("Lazy pattern can't be used with offset.\n");
+ return -EINVAL;
+ }
+
+ if (pp->line && pp->offset) {
+ semantic_error("Offset can't be used with line number.\n");
+ return -EINVAL;
+ }
+
+ if (!pp->line && !pp->lazy_line && pp->file && !pp->function) {
+ semantic_error("File always requires line number or "
+ "lazy pattern.\n");
+ return -EINVAL;
+ }
+
+ if (pp->offset && !pp->function) {
+ semantic_error("Offset requires an entry function.\n");
+ return -EINVAL;
+ }
+
+ if (pp->retprobe && !pp->function) {
+ semantic_error("Return probe requires an entry function.\n");
+ return -EINVAL;
+ }
+
+ if ((pp->offset || pp->line || pp->lazy_line) && pp->retprobe) {
+ semantic_error("Offset/Line/Lazy pattern can't be used with "
+ "return probe.\n");
+ return -EINVAL;
+ }
+
+ pr_debug("symbol:%s file:%s line:%d offset:%lu return:%d lazy:%s\n",
+ pp->function, pp->file, pp->line, pp->offset, pp->retprobe,
+ pp->lazy_line);
+ return 0;
+}
+
+/* Parse perf-probe event argument */
+static int parse_perf_probe_arg(char *str, struct perf_probe_arg *arg)
+{
+ char *tmp, *goodname;
+ struct perf_probe_arg_field **fieldp;
+
+ pr_debug("parsing arg: %s into ", str);
+
+ tmp = strchr(str, '=');
+ if (tmp) {
+ arg->name = strndup(str, tmp - str);
+ if (arg->name == NULL)
+ return -ENOMEM;
+ pr_debug("name:%s ", arg->name);
+ str = tmp + 1;
+ }
+
+ tmp = strchr(str, ':');
+ if (tmp) { /* Type setting */
+ *tmp = '\0';
+ arg->type = strdup(tmp + 1);
+ if (arg->type == NULL)
+ return -ENOMEM;
+ pr_debug("type:%s ", arg->type);
+ }
+
+ tmp = strpbrk(str, "-.[");
+ if (!is_c_varname(str) || !tmp) {
+ /* A variable, register, symbol or special value */
+ arg->var = strdup(str);
+ if (arg->var == NULL)
+ return -ENOMEM;
+ pr_debug("%s\n", arg->var);
+ return 0;
+ }
+
+ /* Structure fields or array element */
+ arg->var = strndup(str, tmp - str);
+ if (arg->var == NULL)
+ return -ENOMEM;
+ goodname = arg->var;
+ pr_debug("%s, ", arg->var);
+ fieldp = &arg->field;
+
+ do {
+ *fieldp = zalloc(sizeof(struct perf_probe_arg_field));
+ if (*fieldp == NULL)
+ return -ENOMEM;
+ if (*tmp == '[') { /* Array */
+ str = tmp;
+ (*fieldp)->index = strtol(str + 1, &tmp, 0);
+ (*fieldp)->ref = true;
+ if (*tmp != ']' || tmp == str + 1) {
+ semantic_error("Array index must be a"
+ " number.\n");
+ return -EINVAL;
+ }
+ tmp++;
+ if (*tmp == '\0')
+ tmp = NULL;
+ } else { /* Structure */
+ if (*tmp == '.') {
+ str = tmp + 1;
+ (*fieldp)->ref = false;
+ } else if (tmp[1] == '>') {
+ str = tmp + 2;
+ (*fieldp)->ref = true;
+ } else {
+ semantic_error("Argument parse error: %s\n",
+ str);
+ return -EINVAL;
+ }
+ tmp = strpbrk(str, "-.[");
+ }
+ if (tmp) {
+ (*fieldp)->name = strndup(str, tmp - str);
+ if ((*fieldp)->name == NULL)
+ return -ENOMEM;
+ if (*str != '[')
+ goodname = (*fieldp)->name;
+ pr_debug("%s(%d), ", (*fieldp)->name, (*fieldp)->ref);
+ fieldp = &(*fieldp)->next;
+ }
+ } while (tmp);
+ (*fieldp)->name = strdup(str);
+ if ((*fieldp)->name == NULL)
+ return -ENOMEM;
+ if (*str != '[')
+ goodname = (*fieldp)->name;
+ pr_debug("%s(%d)\n", (*fieldp)->name, (*fieldp)->ref);
+
+ /* If no name is specified, set the last field name (not array index)*/
+ if (!arg->name) {
+ arg->name = strdup(goodname);
+ if (arg->name == NULL)
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+/* Parse perf-probe event command */
+int parse_perf_probe_command(const char *cmd, struct perf_probe_event *pev)
+{
+ char **argv;
+ int argc, i, ret = 0;
+
+ argv = argv_split(cmd, &argc);
+ if (!argv) {
+ pr_debug("Failed to split arguments.\n");
+ return -ENOMEM;
+ }
+ if (argc - 1 > MAX_PROBE_ARGS) {
+ semantic_error("Too many probe arguments (%d).\n", argc - 1);
+ ret = -ERANGE;
+ goto out;
+ }
+ /* Parse probe point */
+ ret = parse_perf_probe_point(argv[0], pev);
+ if (ret < 0)
+ goto out;
+
+ /* Copy arguments and ensure return probe has no C argument */
+ pev->nargs = argc - 1;
+ pev->args = zalloc(sizeof(struct perf_probe_arg) * pev->nargs);
+ if (pev->args == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ for (i = 0; i < pev->nargs && ret >= 0; i++) {
+ ret = parse_perf_probe_arg(argv[i + 1], &pev->args[i]);
+ if (ret >= 0 &&
+ is_c_varname(pev->args[i].var) && pev->point.retprobe) {
+ semantic_error("You can't specify local variable for"
+ " kretprobe.\n");
+ ret = -EINVAL;
+ }
+ }
+out:
+ argv_free(argv);
+
+ return ret;
+}
+
+/* Return true if this perf_probe_event requires debuginfo */
+bool perf_probe_event_need_dwarf(struct perf_probe_event *pev)
+{
+ int i;
+
+ if (pev->point.file || pev->point.line || pev->point.lazy_line)
+ return true;
+
+ for (i = 0; i < pev->nargs; i++)
+ if (is_c_varname(pev->args[i].var))
+ return true;
+
+ return false;
+}
+
+/* Parse probe_events event into struct probe_point */
+static int parse_probe_trace_command(const char *cmd,
+ struct probe_trace_event *tev)
+{
+ struct probe_trace_point *tp = &tev->point;
+ char pr;
+ char *p;
+ char *argv0_str = NULL, *fmt, *fmt1_str, *fmt2_str, *fmt3_str;
+ int ret, i, argc;
+ char **argv;
+
+ pr_debug("Parsing probe_events: %s\n", cmd);
+ argv = argv_split(cmd, &argc);
+ if (!argv) {
+ pr_debug("Failed to split arguments.\n");
+ return -ENOMEM;
+ }
+ if (argc < 2) {
+ semantic_error("Too few probe arguments.\n");
+ ret = -ERANGE;
+ goto out;
+ }
+
+ /* Scan event and group name. */
+ argv0_str = strdup(argv[0]);
+ if (argv0_str == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ fmt1_str = strtok_r(argv0_str, ":", &fmt);
+ fmt2_str = strtok_r(NULL, "/", &fmt);
+ fmt3_str = strtok_r(NULL, " \t", &fmt);
+ if (fmt1_str == NULL || strlen(fmt1_str) != 1 || fmt2_str == NULL
+ || fmt3_str == NULL) {
+ semantic_error("Failed to parse event name: %s\n", argv[0]);
+ ret = -EINVAL;
+ goto out;
+ }
+ pr = fmt1_str[0];
+ tev->group = strdup(fmt2_str);
+ tev->event = strdup(fmt3_str);
+ if (tev->group == NULL || tev->event == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ pr_debug("Group:%s Event:%s probe:%c\n", tev->group, tev->event, pr);
+
+ tp->retprobe = (pr == 'r');
+
+ /* Scan module name(if there), function name and offset */
+ p = strchr(argv[1], ':');
+ if (p) {
+ tp->module = strndup(argv[1], p - argv[1]);
+ p++;
+ } else
+ p = argv[1];
+ fmt1_str = strtok_r(p, "+", &fmt);
+ if (fmt1_str[0] == '0') /* only the address started with 0x */
+ tp->address = strtoul(fmt1_str, NULL, 0);
+ else {
+ /* Only the symbol-based probe has offset */
+ tp->symbol = strdup(fmt1_str);
+ if (tp->symbol == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ fmt2_str = strtok_r(NULL, "", &fmt);
+ if (fmt2_str == NULL)
+ tp->offset = 0;
+ else
+ tp->offset = strtoul(fmt2_str, NULL, 10);
+ }
+
+ tev->nargs = argc - 2;
+ tev->args = zalloc(sizeof(struct probe_trace_arg) * tev->nargs);
+ if (tev->args == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ for (i = 0; i < tev->nargs; i++) {
+ p = strchr(argv[i + 2], '=');
+ if (p) /* We don't need which register is assigned. */
+ *p++ = '\0';
+ else
+ p = argv[i + 2];
+ tev->args[i].name = strdup(argv[i + 2]);
+ /* TODO: parse regs and offset */
+ tev->args[i].value = strdup(p);
+ if (tev->args[i].name == NULL || tev->args[i].value == NULL) {
+ ret = -ENOMEM;
+ goto out;
+ }
+ }
+ ret = 0;
+out:
+ free(argv0_str);
+ argv_free(argv);
+ return ret;
+}
+
+/* Compose only probe arg */
+int synthesize_perf_probe_arg(struct perf_probe_arg *pa, char *buf, size_t len)
+{
+ struct perf_probe_arg_field *field = pa->field;
+ int ret;
+ char *tmp = buf;
+
+ if (pa->name && pa->var)
+ ret = e_snprintf(tmp, len, "%s=%s", pa->name, pa->var);
+ else
+ ret = e_snprintf(tmp, len, "%s", pa->name ? pa->name : pa->var);
+ if (ret <= 0)
+ goto error;
+ tmp += ret;
+ len -= ret;
+
+ while (field) {
+ if (field->name[0] == '[')
+ ret = e_snprintf(tmp, len, "%s", field->name);
+ else
+ ret = e_snprintf(tmp, len, "%s%s",
+ field->ref ? "->" : ".", field->name);
+ if (ret <= 0)
+ goto error;
+ tmp += ret;
+ len -= ret;
+ field = field->next;
+ }
+
+ if (pa->type) {
+ ret = e_snprintf(tmp, len, ":%s", pa->type);
+ if (ret <= 0)
+ goto error;
+ tmp += ret;
+ len -= ret;
+ }
+
+ return tmp - buf;
+error:
+ pr_debug("Failed to synthesize perf probe argument: %s\n",
+ strerror(-ret));
+ return ret;
+}
+
+/* Compose only probe point (not argument) */
+static char *synthesize_perf_probe_point(struct perf_probe_point *pp)
+{
+ char *buf, *tmp;
+ char offs[32] = "", line[32] = "", file[32] = "";
+ int ret, len;
+
+ buf = zalloc(MAX_CMDLEN);
+ if (buf == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ if (pp->offset) {
+ ret = e_snprintf(offs, 32, "+%lu", pp->offset);
+ if (ret <= 0)
+ goto error;
+ }
+ if (pp->line) {
+ ret = e_snprintf(line, 32, ":%d", pp->line);
+ if (ret <= 0)
+ goto error;
+ }
+ if (pp->file) {
+ tmp = pp->file;
+ len = strlen(tmp);
+ if (len > 30) {
+ tmp = strchr(pp->file + len - 30, '/');
+ tmp = tmp ? tmp + 1 : pp->file + len - 30;
+ }
+ ret = e_snprintf(file, 32, "@%s", tmp);
+ if (ret <= 0)
+ goto error;
+ }
+
+ if (pp->function)
+ ret = e_snprintf(buf, MAX_CMDLEN, "%s%s%s%s%s", pp->function,
+ offs, pp->retprobe ? "%return" : "", line,
+ file);
+ else
+ ret = e_snprintf(buf, MAX_CMDLEN, "%s%s", file, line);
+ if (ret <= 0)
+ goto error;
+
+ return buf;
+error:
+ pr_debug("Failed to synthesize perf probe point: %s\n",
+ strerror(-ret));
+ free(buf);
+ return NULL;
+}
+
+#if 0
+char *synthesize_perf_probe_command(struct perf_probe_event *pev)
+{
+ char *buf;
+ int i, len, ret;
+
+ buf = synthesize_perf_probe_point(&pev->point);
+ if (!buf)
+ return NULL;
+
+ len = strlen(buf);
+ for (i = 0; i < pev->nargs; i++) {
+ ret = e_snprintf(&buf[len], MAX_CMDLEN - len, " %s",
+ pev->args[i].name);
+ if (ret <= 0) {
+ free(buf);
+ return NULL;
+ }
+ len += ret;
+ }
+
+ return buf;
+}
+#endif
+
+static int __synthesize_probe_trace_arg_ref(struct probe_trace_arg_ref *ref,
+ char **buf, size_t *buflen,
+ int depth)
+{
+ int ret;
+ if (ref->next) {
+ depth = __synthesize_probe_trace_arg_ref(ref->next, buf,
+ buflen, depth + 1);
+ if (depth < 0)
+ goto out;
+ }
+
+ ret = e_snprintf(*buf, *buflen, "%+ld(", ref->offset);
+ if (ret < 0)
+ depth = ret;
+ else {
+ *buf += ret;
+ *buflen -= ret;
+ }
+out:
+ return depth;
+
+}
+
+static int synthesize_probe_trace_arg(struct probe_trace_arg *arg,
+ char *buf, size_t buflen)
+{
+ struct probe_trace_arg_ref *ref = arg->ref;
+ int ret, depth = 0;
+ char *tmp = buf;
+
+ /* Argument name or separator */
+ if (arg->name)
+ ret = e_snprintf(buf, buflen, " %s=", arg->name);
+ else
+ ret = e_snprintf(buf, buflen, " ");
+ if (ret < 0)
+ return ret;
+ buf += ret;
+ buflen -= ret;
+
+ /* Special case: @XXX */
+ if (arg->value[0] == '@' && arg->ref)
+ ref = ref->next;
+
+ /* Dereferencing arguments */
+ if (ref) {
+ depth = __synthesize_probe_trace_arg_ref(ref, &buf,
+ &buflen, 1);
+ if (depth < 0)
+ return depth;
+ }
+
+ /* Print argument value */
+ if (arg->value[0] == '@' && arg->ref)
+ ret = e_snprintf(buf, buflen, "%s%+ld", arg->value,
+ arg->ref->offset);
+ else
+ ret = e_snprintf(buf, buflen, "%s", arg->value);
+ if (ret < 0)
+ return ret;
+ buf += ret;
+ buflen -= ret;
+
+ /* Closing */
+ while (depth--) {
+ ret = e_snprintf(buf, buflen, ")");
+ if (ret < 0)
+ return ret;
+ buf += ret;
+ buflen -= ret;
+ }
+ /* Print argument type */
+ if (arg->type) {
+ ret = e_snprintf(buf, buflen, ":%s", arg->type);
+ if (ret <= 0)
+ return ret;
+ buf += ret;
+ }
+
+ return buf - tmp;
+}
+
+char *synthesize_probe_trace_command(struct probe_trace_event *tev)
+{
+ struct probe_trace_point *tp = &tev->point;
+ char *buf;
+ int i, len, ret;
+
+ buf = zalloc(MAX_CMDLEN);
+ if (buf == NULL)
+ return NULL;
+
+ len = e_snprintf(buf, MAX_CMDLEN, "%c:%s/%s ", tp->retprobe ? 'r' : 'p',
+ tev->group, tev->event);
+ if (len <= 0)
+ goto error;
+
+ /* Uprobes must have tp->address and tp->module */
+ if (tev->uprobes && (!tp->address || !tp->module))
+ goto error;
+
+ /* Use the tp->address for uprobes */
+ if (tev->uprobes)
+ ret = e_snprintf(buf + len, MAX_CMDLEN - len, "%s:0x%lx",
+ tp->module, tp->address);
+ else
+ ret = e_snprintf(buf + len, MAX_CMDLEN - len, "%s%s%s+%lu",
+ tp->module ?: "", tp->module ? ":" : "",
+ tp->symbol, tp->offset);
+
+ if (ret <= 0)
+ goto error;
+ len += ret;
+
+ for (i = 0; i < tev->nargs; i++) {
+ ret = synthesize_probe_trace_arg(&tev->args[i], buf + len,
+ MAX_CMDLEN - len);
+ if (ret <= 0)
+ goto error;
+ len += ret;
+ }
+
+ return buf;
+error:
+ free(buf);
+ return NULL;
+}
+
+static int find_perf_probe_point_from_map(struct probe_trace_point *tp,
+ struct perf_probe_point *pp,
+ bool is_kprobe)
+{
+ struct symbol *sym = NULL;
+ struct map *map;
+ u64 addr;
+ int ret = -ENOENT;
+
+ if (!is_kprobe) {
+ map = dso__new_map(tp->module);
+ if (!map)
+ goto out;
+ addr = tp->address;
+ sym = map__find_symbol(map, addr, NULL);
+ } else {
+ addr = kernel_get_symbol_address_by_name(tp->symbol, true);
+ if (addr) {
+ addr += tp->offset;
+ sym = __find_kernel_function(addr, &map);
+ }
+ }
+ if (!sym)
+ goto out;
+
+ pp->retprobe = tp->retprobe;
+ pp->offset = addr - map->unmap_ip(map, sym->start);
+ pp->function = strdup(sym->name);
+ ret = pp->function ? 0 : -ENOMEM;
+
+out:
+ if (map && !is_kprobe) {
+ dso__delete(map->dso);
+ map__delete(map);
+ }
+
+ return ret;
+}
+
+static int convert_to_perf_probe_point(struct probe_trace_point *tp,
+ struct perf_probe_point *pp,
+ bool is_kprobe)
+{
+ char buf[128];
+ int ret;
+
+ ret = find_perf_probe_point_from_dwarf(tp, pp, is_kprobe);
+ if (!ret)
+ return 0;
+ ret = find_perf_probe_point_from_map(tp, pp, is_kprobe);
+ if (!ret)
+ return 0;
+
+ pr_debug("Failed to find probe point from both of dwarf and map.\n");
+
+ if (tp->symbol) {
+ pp->function = strdup(tp->symbol);
+ pp->offset = tp->offset;
+ } else if (!tp->module && !is_kprobe) {
+ ret = e_snprintf(buf, 128, "0x%" PRIx64, (u64)tp->address);
+ if (ret < 0)
+ return ret;
+ pp->function = strdup(buf);
+ pp->offset = 0;
+ }
+ if (pp->function == NULL)
+ return -ENOMEM;
+
+ pp->retprobe = tp->retprobe;
+
+ return 0;
+}
+
+static int convert_to_perf_probe_event(struct probe_trace_event *tev,
+ struct perf_probe_event *pev, bool is_kprobe)
+{
+ char buf[64] = "";
+ int i, ret;
+
+ /* Convert event/group name */
+ pev->event = strdup(tev->event);
+ pev->group = strdup(tev->group);
+ if (pev->event == NULL || pev->group == NULL)
+ return -ENOMEM;
+
+ /* Convert trace_point to probe_point */
+ ret = convert_to_perf_probe_point(&tev->point, &pev->point, is_kprobe);
+ if (ret < 0)
+ return ret;
+
+ /* Convert trace_arg to probe_arg */
+ pev->nargs = tev->nargs;
+ pev->args = zalloc(sizeof(struct perf_probe_arg) * pev->nargs);
+ if (pev->args == NULL)
+ return -ENOMEM;
+ for (i = 0; i < tev->nargs && ret >= 0; i++) {
+ if (tev->args[i].name)
+ pev->args[i].name = strdup(tev->args[i].name);
+ else {
+ ret = synthesize_probe_trace_arg(&tev->args[i],
+ buf, 64);
+ pev->args[i].name = strdup(buf);
+ }
+ if (pev->args[i].name == NULL && ret >= 0)
+ ret = -ENOMEM;
+ }
+
+ if (ret < 0)
+ clear_perf_probe_event(pev);
+
+ return ret;
+}
+
+void clear_perf_probe_event(struct perf_probe_event *pev)
+{
+ struct perf_probe_point *pp = &pev->point;
+ struct perf_probe_arg_field *field, *next;
+ int i;
+
+ free(pev->event);
+ free(pev->group);
+ free(pp->file);
+ free(pp->function);
+ free(pp->lazy_line);
+
+ for (i = 0; i < pev->nargs; i++) {
+ free(pev->args[i].name);
+ free(pev->args[i].var);
+ free(pev->args[i].type);
+ field = pev->args[i].field;
+ while (field) {
+ next = field->next;
+ zfree(&field->name);
+ free(field);
+ field = next;
+ }
+ }
+ free(pev->args);
+ memset(pev, 0, sizeof(*pev));
+}
+
+static void clear_probe_trace_event(struct probe_trace_event *tev)
+{
+ struct probe_trace_arg_ref *ref, *next;
+ int i;
+
+ free(tev->event);
+ free(tev->group);
+ free(tev->point.symbol);
+ free(tev->point.module);
+ for (i = 0; i < tev->nargs; i++) {
+ free(tev->args[i].name);
+ free(tev->args[i].value);
+ free(tev->args[i].type);
+ ref = tev->args[i].ref;
+ while (ref) {
+ next = ref->next;
+ free(ref);
+ ref = next;
+ }
+ }
+ free(tev->args);
+ memset(tev, 0, sizeof(*tev));
+}
+
+static void print_warn_msg(const char *file, bool is_kprobe)
+{
+
+ if (errno == ENOENT) {
+ const char *config;
+
+ if (!is_kprobe)
+ config = "CONFIG_UPROBE_EVENTS";
+ else
+ config = "CONFIG_KPROBE_EVENTS";
+
+ pr_warning("%s file does not exist - please rebuild kernel"
+ " with %s.\n", file, config);
+ } else
+ pr_warning("Failed to open %s file: %s\n", file,
+ strerror(errno));
+}
+
+static int open_probe_events(const char *trace_file, bool readwrite,
+ bool is_kprobe)
+{
+ char buf[PATH_MAX];
+ const char *__debugfs;
+ int ret;
+
+ __debugfs = debugfs_find_mountpoint();
+ if (__debugfs == NULL) {
+ pr_warning("Debugfs is not mounted.\n");
+ return -ENOENT;
+ }
+
+ ret = e_snprintf(buf, PATH_MAX, "%s/%s", __debugfs, trace_file);
+ if (ret >= 0) {
+ pr_debug("Opening %s write=%d\n", buf, readwrite);
+ if (readwrite && !probe_event_dry_run)
+ ret = open(buf, O_RDWR, O_APPEND);
+ else
+ ret = open(buf, O_RDONLY, 0);
+
+ if (ret < 0)
+ print_warn_msg(buf, is_kprobe);
+ }
+ return ret;
+}
+
+static int open_kprobe_events(bool readwrite)
+{
+ return open_probe_events("tracing/kprobe_events", readwrite, true);
+}
+
+static int open_uprobe_events(bool readwrite)
+{
+ return open_probe_events("tracing/uprobe_events", readwrite, false);
+}
+
+/* Get raw string list of current kprobe_events or uprobe_events */
+static struct strlist *get_probe_trace_command_rawlist(int fd)
+{
+ int ret, idx;
+ FILE *fp;
+ char buf[MAX_CMDLEN];
+ char *p;
+ struct strlist *sl;
+
+ sl = strlist__new(true, NULL);
+
+ fp = fdopen(dup(fd), "r");
+ while (!feof(fp)) {
+ p = fgets(buf, MAX_CMDLEN, fp);
+ if (!p)
+ break;
+
+ idx = strlen(p) - 1;
+ if (p[idx] == '\n')
+ p[idx] = '\0';
+ ret = strlist__add(sl, buf);
+ if (ret < 0) {
+ pr_debug("strlist__add failed: %s\n", strerror(-ret));
+ strlist__delete(sl);
+ return NULL;
+ }
+ }
+ fclose(fp);
+
+ return sl;
+}
+
+/* Show an event */
+static int show_perf_probe_event(struct perf_probe_event *pev,
+ const char *module)
+{
+ int i, ret;
+ char buf[128];
+ char *place;
+
+ /* Synthesize only event probe point */
+ place = synthesize_perf_probe_point(&pev->point);
+ if (!place)
+ return -EINVAL;
+
+ ret = e_snprintf(buf, 128, "%s:%s", pev->group, pev->event);
+ if (ret < 0)
+ return ret;
+
+ printf(" %-20s (on %s", buf, place);
+ if (module)
+ printf(" in %s", module);
+
+ if (pev->nargs > 0) {
+ printf(" with");
+ for (i = 0; i < pev->nargs; i++) {
+ ret = synthesize_perf_probe_arg(&pev->args[i],
+ buf, 128);
+ if (ret < 0)
+ break;
+ printf(" %s", buf);
+ }
+ }
+ printf(")\n");
+ free(place);
+ return ret;
+}
+
+static int __show_perf_probe_events(int fd, bool is_kprobe)
+{
+ int ret = 0;
+ struct probe_trace_event tev;
+ struct perf_probe_event pev;
+ struct strlist *rawlist;
+ struct str_node *ent;
+
+ memset(&tev, 0, sizeof(tev));
+ memset(&pev, 0, sizeof(pev));
+
+ rawlist = get_probe_trace_command_rawlist(fd);
+ if (!rawlist)
+ return -ENOENT;
+
+ strlist__for_each(ent, rawlist) {
+ ret = parse_probe_trace_command(ent->s, &tev);
+ if (ret >= 0) {
+ ret = convert_to_perf_probe_event(&tev, &pev,
+ is_kprobe);
+ if (ret >= 0)
+ ret = show_perf_probe_event(&pev,
+ tev.point.module);
+ }
+ clear_perf_probe_event(&pev);
+ clear_probe_trace_event(&tev);
+ if (ret < 0)
+ break;
+ }
+ strlist__delete(rawlist);
+
+ return ret;
+}
+
+/* List up current perf-probe events */
+int show_perf_probe_events(void)
+{
+ int fd, ret;
+
+ setup_pager();
+ fd = open_kprobe_events(false);
+
+ if (fd < 0)
+ return fd;
+
+ ret = init_symbol_maps(false);
+ if (ret < 0)
+ return ret;
+
+ ret = __show_perf_probe_events(fd, true);
+ close(fd);
+
+ fd = open_uprobe_events(false);
+ if (fd >= 0) {
+ ret = __show_perf_probe_events(fd, false);
+ close(fd);
+ }
+
+ exit_symbol_maps();
+ return ret;
+}
+
+/* Get current perf-probe event names */
+static struct strlist *get_probe_trace_event_names(int fd, bool include_group)
+{
+ char buf[128];
+ struct strlist *sl, *rawlist;
+ struct str_node *ent;
+ struct probe_trace_event tev;
+ int ret = 0;
+
+ memset(&tev, 0, sizeof(tev));
+ rawlist = get_probe_trace_command_rawlist(fd);
+ sl = strlist__new(true, NULL);
+ strlist__for_each(ent, rawlist) {
+ ret = parse_probe_trace_command(ent->s, &tev);
+ if (ret < 0)
+ break;
+ if (include_group) {
+ ret = e_snprintf(buf, 128, "%s:%s", tev.group,
+ tev.event);
+ if (ret >= 0)
+ ret = strlist__add(sl, buf);
+ } else
+ ret = strlist__add(sl, tev.event);
+ clear_probe_trace_event(&tev);
+ if (ret < 0)
+ break;
+ }
+ strlist__delete(rawlist);
+
+ if (ret < 0) {
+ strlist__delete(sl);
+ return NULL;
+ }
+ return sl;
+}
+
+static int write_probe_trace_event(int fd, struct probe_trace_event *tev)
+{
+ int ret = 0;
+ char *buf = synthesize_probe_trace_command(tev);
+
+ if (!buf) {
+ pr_debug("Failed to synthesize probe trace event.\n");
+ return -EINVAL;
+ }
+
+ pr_debug("Writing event: %s\n", buf);
+ if (!probe_event_dry_run) {
+ ret = write(fd, buf, strlen(buf));
+ if (ret <= 0)
+ pr_warning("Failed to write event: %s\n",
+ strerror(errno));
+ }
+ free(buf);
+ return ret;
+}
+
+static int get_new_event_name(char *buf, size_t len, const char *base,
+ struct strlist *namelist, bool allow_suffix)
+{
+ int i, ret;
+
+ /* Try no suffix */
+ ret = e_snprintf(buf, len, "%s", base);
+ if (ret < 0) {
+ pr_debug("snprintf() failed: %s\n", strerror(-ret));
+ return ret;
+ }
+ if (!strlist__has_entry(namelist, buf))
+ return 0;
+
+ if (!allow_suffix) {
+ pr_warning("Error: event \"%s\" already exists. "
+ "(Use -f to force duplicates.)\n", base);
+ return -EEXIST;
+ }
+
+ /* Try to add suffix */
+ for (i = 1; i < MAX_EVENT_INDEX; i++) {
+ ret = e_snprintf(buf, len, "%s_%d", base, i);
+ if (ret < 0) {
+ pr_debug("snprintf() failed: %s\n", strerror(-ret));
+ return ret;
+ }
+ if (!strlist__has_entry(namelist, buf))
+ break;
+ }
+ if (i == MAX_EVENT_INDEX) {
+ pr_warning("Too many events are on the same function.\n");
+ ret = -ERANGE;
+ }
+
+ return ret;
+}
+
+static int __add_probe_trace_events(struct perf_probe_event *pev,
+ struct probe_trace_event *tevs,
+ int ntevs, bool allow_suffix)
+{
+ int i, fd, ret;
+ struct probe_trace_event *tev = NULL;
+ char buf[64];
+ const char *event, *group;
+ struct strlist *namelist;
+
+ if (pev->uprobes)
+ fd = open_uprobe_events(true);
+ else
+ fd = open_kprobe_events(true);
+
+ if (fd < 0)
+ return fd;
+ /* Get current event names */
+ namelist = get_probe_trace_event_names(fd, false);
+ if (!namelist) {
+ pr_debug("Failed to get current event list.\n");
+ return -EIO;
+ }
+
+ ret = 0;
+ printf("Added new event%s\n", (ntevs > 1) ? "s:" : ":");
+ for (i = 0; i < ntevs; i++) {
+ tev = &tevs[i];
+ if (pev->event)
+ event = pev->event;
+ else
+ if (pev->point.function)
+ event = pev->point.function;
+ else
+ event = tev->point.symbol;
+ if (pev->group)
+ group = pev->group;
+ else
+ group = PERFPROBE_GROUP;
+
+ /* Get an unused new event name */
+ ret = get_new_event_name(buf, 64, event,
+ namelist, allow_suffix);
+ if (ret < 0)
+ break;
+ event = buf;
+
+ tev->event = strdup(event);
+ tev->group = strdup(group);
+ if (tev->event == NULL || tev->group == NULL) {
+ ret = -ENOMEM;
+ break;
+ }
+ ret = write_probe_trace_event(fd, tev);
+ if (ret < 0)
+ break;
+ /* Add added event name to namelist */
+ strlist__add(namelist, event);
+
+ /* Trick here - save current event/group */
+ event = pev->event;
+ group = pev->group;
+ pev->event = tev->event;
+ pev->group = tev->group;
+ show_perf_probe_event(pev, tev->point.module);
+ /* Trick here - restore current event/group */
+ pev->event = (char *)event;
+ pev->group = (char *)group;
+
+ /*
+ * Probes after the first probe which comes from same
+ * user input are always allowed to add suffix, because
+ * there might be several addresses corresponding to
+ * one code line.
+ */
+ allow_suffix = true;
+ }
+
+ if (ret >= 0) {
+ /* Show how to use the event. */
+ printf("\nYou can now use it in all perf tools, such as:\n\n");
+ printf("\tperf record -e %s:%s -aR sleep 1\n\n", tev->group,
+ tev->event);
+ }
+
+ strlist__delete(namelist);
+ close(fd);
+ return ret;
+}
+
+static char *looking_function_name;
+static int num_matched_functions;
+
+static int probe_function_filter(struct map *map __maybe_unused,
+ struct symbol *sym)
+{
+ if ((sym->binding == STB_GLOBAL || sym->binding == STB_LOCAL) &&
+ strcmp(looking_function_name, sym->name) == 0) {
+ num_matched_functions++;
+ return 0;
+ }
+ return 1;
+}
+
+#define strdup_or_goto(str, label) \
+ ({ char *__p = strdup(str); if (!__p) goto label; __p; })
+
+/*
+ * Find probe function addresses from map.
+ * Return an error or the number of found probe_trace_event
+ */
+static int find_probe_trace_events_from_map(struct perf_probe_event *pev,
+ struct probe_trace_event **tevs,
+ int max_tevs, const char *target)
+{
+ struct map *map = NULL;
+ struct kmap *kmap = NULL;
+ struct ref_reloc_sym *reloc_sym = NULL;
+ struct symbol *sym;
+ struct rb_node *nd;
+ struct probe_trace_event *tev;
+ struct perf_probe_point *pp = &pev->point;
+ struct probe_trace_point *tp;
+ int ret, i;
+
+ /* Init maps of given executable or kernel */
+ if (pev->uprobes)
+ map = dso__new_map(target);
+ else
+ map = kernel_get_module_map(target);
+ if (!map) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ /*
+ * Load matched symbols: Since the different local symbols may have
+ * same name but different addresses, this lists all the symbols.
+ */
+ num_matched_functions = 0;
+ looking_function_name = pp->function;
+ ret = map__load(map, probe_function_filter);
+ if (ret || num_matched_functions == 0) {
+ pr_err("Failed to find symbol %s in %s\n", pp->function,
+ target ? : "kernel");
+ ret = -ENOENT;
+ goto out;
+ } else if (num_matched_functions > max_tevs) {
+ pr_err("Too many functions matched in %s\n",
+ target ? : "kernel");
+ ret = -E2BIG;
+ goto out;
+ }
+
+ if (!pev->uprobes) {
+ kmap = map__kmap(map);
+ reloc_sym = kmap->ref_reloc_sym;
+ if (!reloc_sym) {
+ pr_warning("Relocated base symbol is not found!\n");
+ ret = -EINVAL;
+ goto out;
+ }
+ }
+
+ /* Setup result trace-probe-events */
+ *tevs = zalloc(sizeof(*tev) * num_matched_functions);
+ if (!*tevs) {
+ ret = -ENOMEM;
+ goto out;
+ }
+
+ ret = 0;
+ map__for_each_symbol(map, sym, nd) {
+ tev = (*tevs) + ret;
+ tp = &tev->point;
+ if (ret == num_matched_functions) {
+ pr_warning("Too many symbols are listed. Skip it.\n");
+ break;
+ }
+ ret++;
+
+ if (pp->offset > sym->end - sym->start) {
+ pr_warning("Offset %ld is bigger than the size of %s\n",
+ pp->offset, sym->name);
+ ret = -ENOENT;
+ goto err_out;
+ }
+ /* Add one probe point */
+ tp->address = map->unmap_ip(map, sym->start) + pp->offset;
+ if (reloc_sym) {
+ tp->symbol = strdup_or_goto(reloc_sym->name, nomem_out);
+ tp->offset = tp->address - reloc_sym->addr;
+ } else {
+ tp->symbol = strdup_or_goto(sym->name, nomem_out);
+ tp->offset = pp->offset;
+ }
+ tp->retprobe = pp->retprobe;
+ if (target)
+ tev->point.module = strdup_or_goto(target, nomem_out);
+ tev->uprobes = pev->uprobes;
+ tev->nargs = pev->nargs;
+ if (tev->nargs) {
+ tev->args = zalloc(sizeof(struct probe_trace_arg) *
+ tev->nargs);
+ if (tev->args == NULL)
+ goto nomem_out;
+ }
+ for (i = 0; i < tev->nargs; i++) {
+ if (pev->args[i].name)
+ tev->args[i].name =
+ strdup_or_goto(pev->args[i].name,
+ nomem_out);
+
+ tev->args[i].value = strdup_or_goto(pev->args[i].var,
+ nomem_out);
+ if (pev->args[i].type)
+ tev->args[i].type =
+ strdup_or_goto(pev->args[i].type,
+ nomem_out);
+ }
+ }
+
+out:
+ if (map && pev->uprobes) {
+ /* Only when using uprobe(exec) map needs to be released */
+ dso__delete(map->dso);
+ map__delete(map);
+ }
+ return ret;
+
+nomem_out:
+ ret = -ENOMEM;
+err_out:
+ clear_probe_trace_events(*tevs, num_matched_functions);
+ zfree(tevs);
+ goto out;
+}
+
+static int convert_to_probe_trace_events(struct perf_probe_event *pev,
+ struct probe_trace_event **tevs,
+ int max_tevs, const char *target)
+{
+ int ret;
+
+ if (pev->uprobes && !pev->group) {
+ /* Replace group name if not given */
+ ret = convert_exec_to_group(target, &pev->group);
+ if (ret != 0) {
+ pr_warning("Failed to make a group name.\n");
+ return ret;
+ }
+ }
+
+ /* Convert perf_probe_event with debuginfo */
+ ret = try_to_find_probe_trace_events(pev, tevs, max_tevs, target);
+ if (ret != 0)
+ return ret; /* Found in debuginfo or got an error */
+
+ return find_probe_trace_events_from_map(pev, tevs, max_tevs, target);
+}
+
+struct __event_package {
+ struct perf_probe_event *pev;
+ struct probe_trace_event *tevs;
+ int ntevs;
+};
+
+int add_perf_probe_events(struct perf_probe_event *pevs, int npevs,
+ int max_tevs, const char *target, bool force_add)
+{
+ int i, j, ret;
+ struct __event_package *pkgs;
+
+ ret = 0;
+ pkgs = zalloc(sizeof(struct __event_package) * npevs);
+
+ if (pkgs == NULL)
+ return -ENOMEM;
+
+ ret = init_symbol_maps(pevs->uprobes);
+ if (ret < 0) {
+ free(pkgs);
+ return ret;
+ }
+
+ /* Loop 1: convert all events */
+ for (i = 0; i < npevs; i++) {
+ pkgs[i].pev = &pevs[i];
+ /* Convert with or without debuginfo */
+ ret = convert_to_probe_trace_events(pkgs[i].pev,
+ &pkgs[i].tevs,
+ max_tevs,
+ target);
+ if (ret < 0)
+ goto end;
+ pkgs[i].ntevs = ret;
+ }
+
+ /* Loop 2: add all events */
+ for (i = 0; i < npevs; i++) {
+ ret = __add_probe_trace_events(pkgs[i].pev, pkgs[i].tevs,
+ pkgs[i].ntevs, force_add);
+ if (ret < 0)
+ break;
+ }
+end:
+ /* Loop 3: cleanup and free trace events */
+ for (i = 0; i < npevs; i++) {
+ for (j = 0; j < pkgs[i].ntevs; j++)
+ clear_probe_trace_event(&pkgs[i].tevs[j]);
+ zfree(&pkgs[i].tevs);
+ }
+ free(pkgs);
+ exit_symbol_maps();
+
+ return ret;
+}
+
+static int __del_trace_probe_event(int fd, struct str_node *ent)
+{
+ char *p;
+ char buf[128];
+ int ret;
+
+ /* Convert from perf-probe event to trace-probe event */
+ ret = e_snprintf(buf, 128, "-:%s", ent->s);
+ if (ret < 0)
+ goto error;
+
+ p = strchr(buf + 2, ':');
+ if (!p) {
+ pr_debug("Internal error: %s should have ':' but not.\n",
+ ent->s);
+ ret = -ENOTSUP;
+ goto error;
+ }
+ *p = '/';
+
+ pr_debug("Writing event: %s\n", buf);
+ ret = write(fd, buf, strlen(buf));
+ if (ret < 0) {
+ ret = -errno;
+ goto error;
+ }
+
+ printf("Removed event: %s\n", ent->s);
+ return 0;
+error:
+ pr_warning("Failed to delete event: %s\n", strerror(-ret));
+ return ret;
+}
+
+static int del_trace_probe_event(int fd, const char *buf,
+ struct strlist *namelist)
+{
+ struct str_node *ent, *n;
+ int ret = -1;
+
+ if (strpbrk(buf, "*?")) { /* Glob-exp */
+ strlist__for_each_safe(ent, n, namelist)
+ if (strglobmatch(ent->s, buf)) {
+ ret = __del_trace_probe_event(fd, ent);
+ if (ret < 0)
+ break;
+ strlist__remove(namelist, ent);
+ }
+ } else {
+ ent = strlist__find(namelist, buf);
+ if (ent) {
+ ret = __del_trace_probe_event(fd, ent);
+ if (ret >= 0)
+ strlist__remove(namelist, ent);
+ }
+ }
+
+ return ret;
+}
+
+int del_perf_probe_events(struct strlist *dellist)
+{
+ int ret = -1, ufd = -1, kfd = -1;
+ char buf[128];
+ const char *group, *event;
+ char *p, *str;
+ struct str_node *ent;
+ struct strlist *namelist = NULL, *unamelist = NULL;
+
+ /* Get current event names */
+ kfd = open_kprobe_events(true);
+ if (kfd < 0)
+ return kfd;
+
+ namelist = get_probe_trace_event_names(kfd, true);
+ ufd = open_uprobe_events(true);
+
+ if (ufd >= 0)
+ unamelist = get_probe_trace_event_names(ufd, true);
+
+ if (namelist == NULL && unamelist == NULL)
+ goto error;
+
+ strlist__for_each(ent, dellist) {
+ str = strdup(ent->s);
+ if (str == NULL) {
+ ret = -ENOMEM;
+ goto error;
+ }
+ pr_debug("Parsing: %s\n", str);
+ p = strchr(str, ':');
+ if (p) {
+ group = str;
+ *p = '\0';
+ event = p + 1;
+ } else {
+ group = "*";
+ event = str;
+ }
+
+ ret = e_snprintf(buf, 128, "%s:%s", group, event);
+ if (ret < 0) {
+ pr_err("Failed to copy event.");
+ free(str);
+ goto error;
+ }
+
+ pr_debug("Group: %s, Event: %s\n", group, event);
+
+ if (namelist)
+ ret = del_trace_probe_event(kfd, buf, namelist);
+
+ if (unamelist && ret != 0)
+ ret = del_trace_probe_event(ufd, buf, unamelist);
+
+ if (ret != 0)
+ pr_info("Info: Event \"%s\" does not exist.\n", buf);
+
+ free(str);
+ }
+
+error:
+ if (kfd >= 0) {
+ strlist__delete(namelist);
+ close(kfd);
+ }
+
+ if (ufd >= 0) {
+ strlist__delete(unamelist);
+ close(ufd);
+ }
+
+ return ret;
+}
+
+/* TODO: don't use a global variable for filter ... */
+static struct strfilter *available_func_filter;
+
+/*
+ * If a symbol corresponds to a function with global binding and
+ * matches filter return 0. For all others return 1.
+ */
+static int filter_available_functions(struct map *map __maybe_unused,
+ struct symbol *sym)
+{
+ if ((sym->binding == STB_GLOBAL || sym->binding == STB_LOCAL) &&
+ strfilter__compare(available_func_filter, sym->name))
+ return 0;
+ return 1;
+}
+
+int show_available_funcs(const char *target, struct strfilter *_filter,
+ bool user)
+{
+ struct map *map;
+ int ret;
+
+ ret = init_symbol_maps(user);
+ if (ret < 0)
+ return ret;
+
+ /* Get a symbol map */
+ if (user)
+ map = dso__new_map(target);
+ else
+ map = kernel_get_module_map(target);
+ if (!map) {
+ pr_err("Failed to get a map for %s\n", (target) ? : "kernel");
+ return -EINVAL;
+ }
+
+ /* Load symbols with given filter */
+ available_func_filter = _filter;
+ if (map__load(map, filter_available_functions)) {
+ pr_err("Failed to load symbols in %s\n", (target) ? : "kernel");
+ goto end;
+ }
+ if (!dso__sorted_by_name(map->dso, map->type))
+ dso__sort_by_name(map->dso, map->type);
+
+ /* Show all (filtered) symbols */
+ setup_pager();
+ dso__fprintf_symbols_by_name(map->dso, map->type, stdout);
+end:
+ if (user) {
+ dso__delete(map->dso);
+ map__delete(map);
+ }
+ exit_symbol_maps();
+
+ return ret;
+}
+
diff --git a/tools/perf/util/probe-event.h b/tools/perf/util/probe-event.h
new file mode 100644
index 00000000000..776c9347a3b
--- /dev/null
+++ b/tools/perf/util/probe-event.h
@@ -0,0 +1,141 @@
+#ifndef _PROBE_EVENT_H
+#define _PROBE_EVENT_H
+
+#include <stdbool.h>
+#include "intlist.h"
+#include "strlist.h"
+#include "strfilter.h"
+
+extern bool probe_event_dry_run;
+
+/* kprobe-tracer and uprobe-tracer tracing point */
+struct probe_trace_point {
+ char *symbol; /* Base symbol */
+ char *module; /* Module name */
+ unsigned long offset; /* Offset from symbol */
+ unsigned long address; /* Actual address of the trace point */
+ bool retprobe; /* Return probe flag */
+};
+
+/* probe-tracer tracing argument referencing offset */
+struct probe_trace_arg_ref {
+ struct probe_trace_arg_ref *next; /* Next reference */
+ long offset; /* Offset value */
+};
+
+/* kprobe-tracer and uprobe-tracer tracing argument */
+struct probe_trace_arg {
+ char *name; /* Argument name */
+ char *value; /* Base value */
+ char *type; /* Type name */
+ struct probe_trace_arg_ref *ref; /* Referencing offset */
+};
+
+/* kprobe-tracer and uprobe-tracer tracing event (point + arg) */
+struct probe_trace_event {
+ char *event; /* Event name */
+ char *group; /* Group name */
+ struct probe_trace_point point; /* Trace point */
+ int nargs; /* Number of args */
+ bool uprobes; /* uprobes only */
+ struct probe_trace_arg *args; /* Arguments */
+};
+
+/* Perf probe probing point */
+struct perf_probe_point {
+ char *file; /* File path */
+ char *function; /* Function name */
+ int line; /* Line number */
+ bool retprobe; /* Return probe flag */
+ char *lazy_line; /* Lazy matching pattern */
+ unsigned long offset; /* Offset from function entry */
+};
+
+/* Perf probe probing argument field chain */
+struct perf_probe_arg_field {
+ struct perf_probe_arg_field *next; /* Next field */
+ char *name; /* Name of the field */
+ long index; /* Array index number */
+ bool ref; /* Referencing flag */
+};
+
+/* Perf probe probing argument */
+struct perf_probe_arg {
+ char *name; /* Argument name */
+ char *var; /* Variable name */
+ char *type; /* Type name */
+ struct perf_probe_arg_field *field; /* Structure fields */
+};
+
+/* Perf probe probing event (point + arg) */
+struct perf_probe_event {
+ char *event; /* Event name */
+ char *group; /* Group name */
+ struct perf_probe_point point; /* Probe point */
+ int nargs; /* Number of arguments */
+ bool uprobes;
+ struct perf_probe_arg *args; /* Arguments */
+};
+
+/* Line range */
+struct line_range {
+ char *file; /* File name */
+ char *function; /* Function name */
+ int start; /* Start line number */
+ int end; /* End line number */
+ int offset; /* Start line offset */
+ char *path; /* Real path name */
+ char *comp_dir; /* Compile directory */
+ struct intlist *line_list; /* Visible lines */
+};
+
+/* List of variables */
+struct variable_list {
+ struct probe_trace_point point; /* Actual probepoint */
+ struct strlist *vars; /* Available variables */
+};
+
+/* Command string to events */
+extern int parse_perf_probe_command(const char *cmd,
+ struct perf_probe_event *pev);
+
+/* Events to command string */
+extern char *synthesize_perf_probe_command(struct perf_probe_event *pev);
+extern char *synthesize_probe_trace_command(struct probe_trace_event *tev);
+extern int synthesize_perf_probe_arg(struct perf_probe_arg *pa, char *buf,
+ size_t len);
+
+/* Check the perf_probe_event needs debuginfo */
+extern bool perf_probe_event_need_dwarf(struct perf_probe_event *pev);
+
+/* Release event contents */
+extern void clear_perf_probe_event(struct perf_probe_event *pev);
+
+/* Command string to line-range */
+extern int parse_line_range_desc(const char *cmd, struct line_range *lr);
+
+/* Release line range members */
+extern void line_range__clear(struct line_range *lr);
+
+/* Initialize line range */
+extern int line_range__init(struct line_range *lr);
+
+/* Internal use: Return kernel/module path */
+extern const char *kernel_get_module_path(const char *module);
+
+extern int add_perf_probe_events(struct perf_probe_event *pevs, int npevs,
+ int max_probe_points, const char *module,
+ bool force_add);
+extern int del_perf_probe_events(struct strlist *dellist);
+extern int show_perf_probe_events(void);
+extern int show_line_range(struct line_range *lr, const char *module);
+extern int show_available_vars(struct perf_probe_event *pevs, int npevs,
+ int max_probe_points, const char *module,
+ struct strfilter *filter, bool externs);
+extern int show_available_funcs(const char *module, struct strfilter *filter,
+ bool user);
+
+/* Maximum index number of event-name postfix */
+#define MAX_EVENT_INDEX 1024
+
+#endif /*_PROBE_EVENT_H */
diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c
new file mode 100644
index 00000000000..98e30476641
--- /dev/null
+++ b/tools/perf/util/probe-finder.c
@@ -0,0 +1,1603 @@
+/*
+ * probe-finder.c : C expression to kprobe event converter
+ *
+ * Written by Masami Hiramatsu <mhiramat@redhat.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ */
+
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <dwarf-regs.h>
+
+#include <linux/bitops.h>
+#include "event.h"
+#include "dso.h"
+#include "debug.h"
+#include "intlist.h"
+#include "util.h"
+#include "symbol.h"
+#include "probe-finder.h"
+
+/* Kprobe tracer basic type is up to u64 */
+#define MAX_BASIC_TYPE_BITS 64
+
+/* Dwarf FL wrappers */
+static char *debuginfo_path; /* Currently dummy */
+
+static const Dwfl_Callbacks offline_callbacks = {
+ .find_debuginfo = dwfl_standard_find_debuginfo,
+ .debuginfo_path = &debuginfo_path,
+
+ .section_address = dwfl_offline_section_address,
+
+ /* We use this table for core files too. */
+ .find_elf = dwfl_build_id_find_elf,
+};
+
+/* Get a Dwarf from offline image */
+static int debuginfo__init_offline_dwarf(struct debuginfo *dbg,
+ const char *path)
+{
+ int fd;
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0)
+ return fd;
+
+ dbg->dwfl = dwfl_begin(&offline_callbacks);
+ if (!dbg->dwfl)
+ goto error;
+
+ dbg->mod = dwfl_report_offline(dbg->dwfl, "", "", fd);
+ if (!dbg->mod)
+ goto error;
+
+ dbg->dbg = dwfl_module_getdwarf(dbg->mod, &dbg->bias);
+ if (!dbg->dbg)
+ goto error;
+
+ return 0;
+error:
+ if (dbg->dwfl)
+ dwfl_end(dbg->dwfl);
+ else
+ close(fd);
+ memset(dbg, 0, sizeof(*dbg));
+
+ return -ENOENT;
+}
+
+static struct debuginfo *__debuginfo__new(const char *path)
+{
+ struct debuginfo *dbg = zalloc(sizeof(*dbg));
+ if (!dbg)
+ return NULL;
+
+ if (debuginfo__init_offline_dwarf(dbg, path) < 0)
+ zfree(&dbg);
+ if (dbg)
+ pr_debug("Open Debuginfo file: %s\n", path);
+ return dbg;
+}
+
+enum dso_binary_type distro_dwarf_types[] = {
+ DSO_BINARY_TYPE__FEDORA_DEBUGINFO,
+ DSO_BINARY_TYPE__UBUNTU_DEBUGINFO,
+ DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO,
+ DSO_BINARY_TYPE__BUILDID_DEBUGINFO,
+ DSO_BINARY_TYPE__NOT_FOUND,
+};
+
+struct debuginfo *debuginfo__new(const char *path)
+{
+ enum dso_binary_type *type;
+ char buf[PATH_MAX], nil = '\0';
+ struct dso *dso;
+ struct debuginfo *dinfo = NULL;
+
+ /* Try to open distro debuginfo files */
+ dso = dso__new(path);
+ if (!dso)
+ goto out;
+
+ for (type = distro_dwarf_types;
+ !dinfo && *type != DSO_BINARY_TYPE__NOT_FOUND;
+ type++) {
+ if (dso__read_binary_type_filename(dso, *type, &nil,
+ buf, PATH_MAX) < 0)
+ continue;
+ dinfo = __debuginfo__new(buf);
+ }
+ dso__delete(dso);
+
+out:
+ /* if failed to open all distro debuginfo, open given binary */
+ return dinfo ? : __debuginfo__new(path);
+}
+
+void debuginfo__delete(struct debuginfo *dbg)
+{
+ if (dbg) {
+ if (dbg->dwfl)
+ dwfl_end(dbg->dwfl);
+ free(dbg);
+ }
+}
+
+/*
+ * Probe finder related functions
+ */
+
+static struct probe_trace_arg_ref *alloc_trace_arg_ref(long offs)
+{
+ struct probe_trace_arg_ref *ref;
+ ref = zalloc(sizeof(struct probe_trace_arg_ref));
+ if (ref != NULL)
+ ref->offset = offs;
+ return ref;
+}
+
+/*
+ * Convert a location into trace_arg.
+ * If tvar == NULL, this just checks variable can be converted.
+ * If fentry == true and vr_die is a parameter, do huristic search
+ * for the location fuzzed by function entry mcount.
+ */
+static int convert_variable_location(Dwarf_Die *vr_die, Dwarf_Addr addr,
+ Dwarf_Op *fb_ops, Dwarf_Die *sp_die,
+ struct probe_trace_arg *tvar)
+{
+ Dwarf_Attribute attr;
+ Dwarf_Addr tmp = 0;
+ Dwarf_Op *op;
+ size_t nops;
+ unsigned int regn;
+ Dwarf_Word offs = 0;
+ bool ref = false;
+ const char *regs;
+ int ret;
+
+ if (dwarf_attr(vr_die, DW_AT_external, &attr) != NULL)
+ goto static_var;
+
+ /* TODO: handle more than 1 exprs */
+ if (dwarf_attr(vr_die, DW_AT_location, &attr) == NULL)
+ return -EINVAL; /* Broken DIE ? */
+ if (dwarf_getlocation_addr(&attr, addr, &op, &nops, 1) <= 0) {
+ ret = dwarf_entrypc(sp_die, &tmp);
+ if (ret || addr != tmp ||
+ dwarf_tag(vr_die) != DW_TAG_formal_parameter ||
+ dwarf_highpc(sp_die, &tmp))
+ return -ENOENT;
+ /*
+ * This is fuzzed by fentry mcount. We try to find the
+ * parameter location at the earliest address.
+ */
+ for (addr += 1; addr <= tmp; addr++) {
+ if (dwarf_getlocation_addr(&attr, addr, &op,
+ &nops, 1) > 0)
+ goto found;
+ }
+ return -ENOENT;
+ }
+found:
+ if (nops == 0)
+ /* TODO: Support const_value */
+ return -ENOENT;
+
+ if (op->atom == DW_OP_addr) {
+static_var:
+ if (!tvar)
+ return 0;
+ /* Static variables on memory (not stack), make @varname */
+ ret = strlen(dwarf_diename(vr_die));
+ tvar->value = zalloc(ret + 2);
+ if (tvar->value == NULL)
+ return -ENOMEM;
+ snprintf(tvar->value, ret + 2, "@%s", dwarf_diename(vr_die));
+ tvar->ref = alloc_trace_arg_ref((long)offs);
+ if (tvar->ref == NULL)
+ return -ENOMEM;
+ return 0;
+ }
+
+ /* If this is based on frame buffer, set the offset */
+ if (op->atom == DW_OP_fbreg) {
+ if (fb_ops == NULL)
+ return -ENOTSUP;
+ ref = true;
+ offs = op->number;
+ op = &fb_ops[0];
+ }
+
+ if (op->atom >= DW_OP_breg0 && op->atom <= DW_OP_breg31) {
+ regn = op->atom - DW_OP_breg0;
+ offs += op->number;
+ ref = true;
+ } else if (op->atom >= DW_OP_reg0 && op->atom <= DW_OP_reg31) {
+ regn = op->atom - DW_OP_reg0;
+ } else if (op->atom == DW_OP_bregx) {
+ regn = op->number;
+ offs += op->number2;
+ ref = true;
+ } else if (op->atom == DW_OP_regx) {
+ regn = op->number;
+ } else {
+ pr_debug("DW_OP %x is not supported.\n", op->atom);
+ return -ENOTSUP;
+ }
+
+ if (!tvar)
+ return 0;
+
+ regs = get_arch_regstr(regn);
+ if (!regs) {
+ /* This should be a bug in DWARF or this tool */
+ pr_warning("Mapping for the register number %u "
+ "missing on this architecture.\n", regn);
+ return -ERANGE;
+ }
+
+ tvar->value = strdup(regs);
+ if (tvar->value == NULL)
+ return -ENOMEM;
+
+ if (ref) {
+ tvar->ref = alloc_trace_arg_ref((long)offs);
+ if (tvar->ref == NULL)
+ return -ENOMEM;
+ }
+ return 0;
+}
+
+#define BYTES_TO_BITS(nb) ((nb) * BITS_PER_LONG / sizeof(long))
+
+static int convert_variable_type(Dwarf_Die *vr_die,
+ struct probe_trace_arg *tvar,
+ const char *cast)
+{
+ struct probe_trace_arg_ref **ref_ptr = &tvar->ref;
+ Dwarf_Die type;
+ char buf[16];
+ int bsize, boffs, total;
+ int ret;
+
+ /* TODO: check all types */
+ if (cast && strcmp(cast, "string") != 0) {
+ /* Non string type is OK */
+ tvar->type = strdup(cast);
+ return (tvar->type == NULL) ? -ENOMEM : 0;
+ }
+
+ bsize = dwarf_bitsize(vr_die);
+ if (bsize > 0) {
+ /* This is a bitfield */
+ boffs = dwarf_bitoffset(vr_die);
+ total = dwarf_bytesize(vr_die);
+ if (boffs < 0 || total < 0)
+ return -ENOENT;
+ ret = snprintf(buf, 16, "b%d@%d/%zd", bsize, boffs,
+ BYTES_TO_BITS(total));
+ goto formatted;
+ }
+
+ if (die_get_real_type(vr_die, &type) == NULL) {
+ pr_warning("Failed to get a type information of %s.\n",
+ dwarf_diename(vr_die));
+ return -ENOENT;
+ }
+
+ pr_debug("%s type is %s.\n",
+ dwarf_diename(vr_die), dwarf_diename(&type));
+
+ if (cast && strcmp(cast, "string") == 0) { /* String type */
+ ret = dwarf_tag(&type);
+ if (ret != DW_TAG_pointer_type &&
+ ret != DW_TAG_array_type) {
+ pr_warning("Failed to cast into string: "
+ "%s(%s) is not a pointer nor array.\n",
+ dwarf_diename(vr_die), dwarf_diename(&type));
+ return -EINVAL;
+ }
+ if (die_get_real_type(&type, &type) == NULL) {
+ pr_warning("Failed to get a type"
+ " information.\n");
+ return -ENOENT;
+ }
+ if (ret == DW_TAG_pointer_type) {
+ while (*ref_ptr)
+ ref_ptr = &(*ref_ptr)->next;
+ /* Add new reference with offset +0 */
+ *ref_ptr = zalloc(sizeof(struct probe_trace_arg_ref));
+ if (*ref_ptr == NULL) {
+ pr_warning("Out of memory error\n");
+ return -ENOMEM;
+ }
+ }
+ if (!die_compare_name(&type, "char") &&
+ !die_compare_name(&type, "unsigned char")) {
+ pr_warning("Failed to cast into string: "
+ "%s is not (unsigned) char *.\n",
+ dwarf_diename(vr_die));
+ return -EINVAL;
+ }
+ tvar->type = strdup(cast);
+ return (tvar->type == NULL) ? -ENOMEM : 0;
+ }
+
+ ret = dwarf_bytesize(&type);
+ if (ret <= 0)
+ /* No size ... try to use default type */
+ return 0;
+ ret = BYTES_TO_BITS(ret);
+
+ /* Check the bitwidth */
+ if (ret > MAX_BASIC_TYPE_BITS) {
+ pr_info("%s exceeds max-bitwidth. Cut down to %d bits.\n",
+ dwarf_diename(&type), MAX_BASIC_TYPE_BITS);
+ ret = MAX_BASIC_TYPE_BITS;
+ }
+ ret = snprintf(buf, 16, "%c%d",
+ die_is_signed_type(&type) ? 's' : 'u', ret);
+
+formatted:
+ if (ret < 0 || ret >= 16) {
+ if (ret >= 16)
+ ret = -E2BIG;
+ pr_warning("Failed to convert variable type: %s\n",
+ strerror(-ret));
+ return ret;
+ }
+ tvar->type = strdup(buf);
+ if (tvar->type == NULL)
+ return -ENOMEM;
+ return 0;
+}
+
+static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname,
+ struct perf_probe_arg_field *field,
+ struct probe_trace_arg_ref **ref_ptr,
+ Dwarf_Die *die_mem)
+{
+ struct probe_trace_arg_ref *ref = *ref_ptr;
+ Dwarf_Die type;
+ Dwarf_Word offs;
+ int ret, tag;
+
+ pr_debug("converting %s in %s\n", field->name, varname);
+ if (die_get_real_type(vr_die, &type) == NULL) {
+ pr_warning("Failed to get the type of %s.\n", varname);
+ return -ENOENT;
+ }
+ pr_debug2("Var real type: (%x)\n", (unsigned)dwarf_dieoffset(&type));
+ tag = dwarf_tag(&type);
+
+ if (field->name[0] == '[' &&
+ (tag == DW_TAG_array_type || tag == DW_TAG_pointer_type)) {
+ if (field->next)
+ /* Save original type for next field */
+ memcpy(die_mem, &type, sizeof(*die_mem));
+ /* Get the type of this array */
+ if (die_get_real_type(&type, &type) == NULL) {
+ pr_warning("Failed to get the type of %s.\n", varname);
+ return -ENOENT;
+ }
+ pr_debug2("Array real type: (%x)\n",
+ (unsigned)dwarf_dieoffset(&type));
+ if (tag == DW_TAG_pointer_type) {
+ ref = zalloc(sizeof(struct probe_trace_arg_ref));
+ if (ref == NULL)
+ return -ENOMEM;
+ if (*ref_ptr)
+ (*ref_ptr)->next = ref;
+ else
+ *ref_ptr = ref;
+ }
+ ref->offset += dwarf_bytesize(&type) * field->index;
+ if (!field->next)
+ /* Save vr_die for converting types */
+ memcpy(die_mem, vr_die, sizeof(*die_mem));
+ goto next;
+ } else if (tag == DW_TAG_pointer_type) {
+ /* Check the pointer and dereference */
+ if (!field->ref) {
+ pr_err("Semantic error: %s must be referred by '->'\n",
+ field->name);
+ return -EINVAL;
+ }
+ /* Get the type pointed by this pointer */
+ if (die_get_real_type(&type, &type) == NULL) {
+ pr_warning("Failed to get the type of %s.\n", varname);
+ return -ENOENT;
+ }
+ /* Verify it is a data structure */
+ tag = dwarf_tag(&type);
+ if (tag != DW_TAG_structure_type && tag != DW_TAG_union_type) {
+ pr_warning("%s is not a data structure nor an union.\n",
+ varname);
+ return -EINVAL;
+ }
+
+ ref = zalloc(sizeof(struct probe_trace_arg_ref));
+ if (ref == NULL)
+ return -ENOMEM;
+ if (*ref_ptr)
+ (*ref_ptr)->next = ref;
+ else
+ *ref_ptr = ref;
+ } else {
+ /* Verify it is a data structure */
+ if (tag != DW_TAG_structure_type && tag != DW_TAG_union_type) {
+ pr_warning("%s is not a data structure nor an union.\n",
+ varname);
+ return -EINVAL;
+ }
+ if (field->name[0] == '[') {
+ pr_err("Semantic error: %s is not a pointor"
+ " nor array.\n", varname);
+ return -EINVAL;
+ }
+ if (field->ref) {
+ pr_err("Semantic error: %s must be referred by '.'\n",
+ field->name);
+ return -EINVAL;
+ }
+ if (!ref) {
+ pr_warning("Structure on a register is not "
+ "supported yet.\n");
+ return -ENOTSUP;
+ }
+ }
+
+ if (die_find_member(&type, field->name, die_mem) == NULL) {
+ pr_warning("%s(type:%s) has no member %s.\n", varname,
+ dwarf_diename(&type), field->name);
+ return -EINVAL;
+ }
+
+ /* Get the offset of the field */
+ if (tag == DW_TAG_union_type) {
+ offs = 0;
+ } else {
+ ret = die_get_data_member_location(die_mem, &offs);
+ if (ret < 0) {
+ pr_warning("Failed to get the offset of %s.\n",
+ field->name);
+ return ret;
+ }
+ }
+ ref->offset += (long)offs;
+
+next:
+ /* Converting next field */
+ if (field->next)
+ return convert_variable_fields(die_mem, field->name,
+ field->next, &ref, die_mem);
+ else
+ return 0;
+}
+
+/* Show a variables in kprobe event format */
+static int convert_variable(Dwarf_Die *vr_die, struct probe_finder *pf)
+{
+ Dwarf_Die die_mem;
+ int ret;
+
+ pr_debug("Converting variable %s into trace event.\n",
+ dwarf_diename(vr_die));
+
+ ret = convert_variable_location(vr_die, pf->addr, pf->fb_ops,
+ &pf->sp_die, pf->tvar);
+ if (ret == -ENOENT || ret == -EINVAL)
+ pr_err("Failed to find the location of %s at this address.\n"
+ " Perhaps, it has been optimized out.\n", pf->pvar->var);
+ else if (ret == -ENOTSUP)
+ pr_err("Sorry, we don't support this variable location yet.\n");
+ else if (ret == 0 && pf->pvar->field) {
+ ret = convert_variable_fields(vr_die, pf->pvar->var,
+ pf->pvar->field, &pf->tvar->ref,
+ &die_mem);
+ vr_die = &die_mem;
+ }
+ if (ret == 0)
+ ret = convert_variable_type(vr_die, pf->tvar, pf->pvar->type);
+ /* *expr will be cached in libdw. Don't free it. */
+ return ret;
+}
+
+/* Find a variable in a scope DIE */
+static int find_variable(Dwarf_Die *sc_die, struct probe_finder *pf)
+{
+ Dwarf_Die vr_die;
+ char buf[32], *ptr;
+ int ret = 0;
+
+ if (!is_c_varname(pf->pvar->var)) {
+ /* Copy raw parameters */
+ pf->tvar->value = strdup(pf->pvar->var);
+ if (pf->tvar->value == NULL)
+ return -ENOMEM;
+ if (pf->pvar->type) {
+ pf->tvar->type = strdup(pf->pvar->type);
+ if (pf->tvar->type == NULL)
+ return -ENOMEM;
+ }
+ if (pf->pvar->name) {
+ pf->tvar->name = strdup(pf->pvar->name);
+ if (pf->tvar->name == NULL)
+ return -ENOMEM;
+ } else
+ pf->tvar->name = NULL;
+ return 0;
+ }
+
+ if (pf->pvar->name)
+ pf->tvar->name = strdup(pf->pvar->name);
+ else {
+ ret = synthesize_perf_probe_arg(pf->pvar, buf, 32);
+ if (ret < 0)
+ return ret;
+ ptr = strchr(buf, ':'); /* Change type separator to _ */
+ if (ptr)
+ *ptr = '_';
+ pf->tvar->name = strdup(buf);
+ }
+ if (pf->tvar->name == NULL)
+ return -ENOMEM;
+
+ pr_debug("Searching '%s' variable in context.\n", pf->pvar->var);
+ /* Search child die for local variables and parameters. */
+ if (!die_find_variable_at(sc_die, pf->pvar->var, pf->addr, &vr_die)) {
+ /* Search again in global variables */
+ if (!die_find_variable_at(&pf->cu_die, pf->pvar->var, 0, &vr_die))
+ pr_warning("Failed to find '%s' in this function.\n",
+ pf->pvar->var);
+ ret = -ENOENT;
+ }
+ if (ret >= 0)
+ ret = convert_variable(&vr_die, pf);
+
+ return ret;
+}
+
+/* Convert subprogram DIE to trace point */
+static int convert_to_trace_point(Dwarf_Die *sp_die, Dwfl_Module *mod,
+ Dwarf_Addr paddr, bool retprobe,
+ struct probe_trace_point *tp)
+{
+ Dwarf_Addr eaddr, highaddr;
+ GElf_Sym sym;
+ const char *symbol;
+
+ /* Verify the address is correct */
+ if (dwarf_entrypc(sp_die, &eaddr) != 0) {
+ pr_warning("Failed to get entry address of %s\n",
+ dwarf_diename(sp_die));
+ return -ENOENT;
+ }
+ if (dwarf_highpc(sp_die, &highaddr) != 0) {
+ pr_warning("Failed to get end address of %s\n",
+ dwarf_diename(sp_die));
+ return -ENOENT;
+ }
+ if (paddr > highaddr) {
+ pr_warning("Offset specified is greater than size of %s\n",
+ dwarf_diename(sp_die));
+ return -EINVAL;
+ }
+
+ /* Get an appropriate symbol from symtab */
+ symbol = dwfl_module_addrsym(mod, paddr, &sym, NULL);
+ if (!symbol) {
+ pr_warning("Failed to find symbol at 0x%lx\n",
+ (unsigned long)paddr);
+ return -ENOENT;
+ }
+ tp->offset = (unsigned long)(paddr - sym.st_value);
+ tp->address = (unsigned long)paddr;
+ tp->symbol = strdup(symbol);
+ if (!tp->symbol)
+ return -ENOMEM;
+
+ /* Return probe must be on the head of a subprogram */
+ if (retprobe) {
+ if (eaddr != paddr) {
+ pr_warning("Return probe must be on the head of"
+ " a real function.\n");
+ return -EINVAL;
+ }
+ tp->retprobe = true;
+ }
+
+ return 0;
+}
+
+/* Call probe_finder callback with scope DIE */
+static int call_probe_finder(Dwarf_Die *sc_die, struct probe_finder *pf)
+{
+ Dwarf_Attribute fb_attr;
+ size_t nops;
+ int ret;
+
+ if (!sc_die) {
+ pr_err("Caller must pass a scope DIE. Program error.\n");
+ return -EINVAL;
+ }
+
+ /* If not a real subprogram, find a real one */
+ if (!die_is_func_def(sc_die)) {
+ if (!die_find_realfunc(&pf->cu_die, pf->addr, &pf->sp_die)) {
+ pr_warning("Failed to find probe point in any "
+ "functions.\n");
+ return -ENOENT;
+ }
+ } else
+ memcpy(&pf->sp_die, sc_die, sizeof(Dwarf_Die));
+
+ /* Get the frame base attribute/ops from subprogram */
+ dwarf_attr(&pf->sp_die, DW_AT_frame_base, &fb_attr);
+ ret = dwarf_getlocation_addr(&fb_attr, pf->addr, &pf->fb_ops, &nops, 1);
+ if (ret <= 0 || nops == 0) {
+ pf->fb_ops = NULL;
+#if _ELFUTILS_PREREQ(0, 142)
+ } else if (nops == 1 && pf->fb_ops[0].atom == DW_OP_call_frame_cfa &&
+ pf->cfi != NULL) {
+ Dwarf_Frame *frame;
+ if (dwarf_cfi_addrframe(pf->cfi, pf->addr, &frame) != 0 ||
+ dwarf_frame_cfa(frame, &pf->fb_ops, &nops) != 0) {
+ pr_warning("Failed to get call frame on 0x%jx\n",
+ (uintmax_t)pf->addr);
+ return -ENOENT;
+ }
+#endif
+ }
+
+ /* Call finder's callback handler */
+ ret = pf->callback(sc_die, pf);
+
+ /* *pf->fb_ops will be cached in libdw. Don't free it. */
+ pf->fb_ops = NULL;
+
+ return ret;
+}
+
+struct find_scope_param {
+ const char *function;
+ const char *file;
+ int line;
+ int diff;
+ Dwarf_Die *die_mem;
+ bool found;
+};
+
+static int find_best_scope_cb(Dwarf_Die *fn_die, void *data)
+{
+ struct find_scope_param *fsp = data;
+ const char *file;
+ int lno;
+
+ /* Skip if declared file name does not match */
+ if (fsp->file) {
+ file = dwarf_decl_file(fn_die);
+ if (!file || strcmp(fsp->file, file) != 0)
+ return 0;
+ }
+ /* If the function name is given, that's what user expects */
+ if (fsp->function) {
+ if (die_compare_name(fn_die, fsp->function)) {
+ memcpy(fsp->die_mem, fn_die, sizeof(Dwarf_Die));
+ fsp->found = true;
+ return 1;
+ }
+ } else {
+ /* With the line number, find the nearest declared DIE */
+ dwarf_decl_line(fn_die, &lno);
+ if (lno < fsp->line && fsp->diff > fsp->line - lno) {
+ /* Keep a candidate and continue */
+ fsp->diff = fsp->line - lno;
+ memcpy(fsp->die_mem, fn_die, sizeof(Dwarf_Die));
+ fsp->found = true;
+ }
+ }
+ return 0;
+}
+
+/* Find an appropriate scope fits to given conditions */
+static Dwarf_Die *find_best_scope(struct probe_finder *pf, Dwarf_Die *die_mem)
+{
+ struct find_scope_param fsp = {
+ .function = pf->pev->point.function,
+ .file = pf->fname,
+ .line = pf->lno,
+ .diff = INT_MAX,
+ .die_mem = die_mem,
+ .found = false,
+ };
+
+ cu_walk_functions_at(&pf->cu_die, pf->addr, find_best_scope_cb, &fsp);
+
+ return fsp.found ? die_mem : NULL;
+}
+
+static int probe_point_line_walker(const char *fname, int lineno,
+ Dwarf_Addr addr, void *data)
+{
+ struct probe_finder *pf = data;
+ Dwarf_Die *sc_die, die_mem;
+ int ret;
+
+ if (lineno != pf->lno || strtailcmp(fname, pf->fname) != 0)
+ return 0;
+
+ pf->addr = addr;
+ sc_die = find_best_scope(pf, &die_mem);
+ if (!sc_die) {
+ pr_warning("Failed to find scope of probe point.\n");
+ return -ENOENT;
+ }
+
+ ret = call_probe_finder(sc_die, pf);
+
+ /* Continue if no error, because the line will be in inline function */
+ return ret < 0 ? ret : 0;
+}
+
+/* Find probe point from its line number */
+static int find_probe_point_by_line(struct probe_finder *pf)
+{
+ return die_walk_lines(&pf->cu_die, probe_point_line_walker, pf);
+}
+
+/* Find lines which match lazy pattern */
+static int find_lazy_match_lines(struct intlist *list,
+ const char *fname, const char *pat)
+{
+ FILE *fp;
+ char *line = NULL;
+ size_t line_len;
+ ssize_t len;
+ int count = 0, linenum = 1;
+
+ fp = fopen(fname, "r");
+ if (!fp) {
+ pr_warning("Failed to open %s: %s\n", fname, strerror(errno));
+ return -errno;
+ }
+
+ while ((len = getline(&line, &line_len, fp)) > 0) {
+
+ if (line[len - 1] == '\n')
+ line[len - 1] = '\0';
+
+ if (strlazymatch(line, pat)) {
+ intlist__add(list, linenum);
+ count++;
+ }
+ linenum++;
+ }
+
+ if (ferror(fp))
+ count = -errno;
+ free(line);
+ fclose(fp);
+
+ if (count == 0)
+ pr_debug("No matched lines found in %s.\n", fname);
+ return count;
+}
+
+static int probe_point_lazy_walker(const char *fname, int lineno,
+ Dwarf_Addr addr, void *data)
+{
+ struct probe_finder *pf = data;
+ Dwarf_Die *sc_die, die_mem;
+ int ret;
+
+ if (!intlist__has_entry(pf->lcache, lineno) ||
+ strtailcmp(fname, pf->fname) != 0)
+ return 0;
+
+ pr_debug("Probe line found: line:%d addr:0x%llx\n",
+ lineno, (unsigned long long)addr);
+ pf->addr = addr;
+ pf->lno = lineno;
+ sc_die = find_best_scope(pf, &die_mem);
+ if (!sc_die) {
+ pr_warning("Failed to find scope of probe point.\n");
+ return -ENOENT;
+ }
+
+ ret = call_probe_finder(sc_die, pf);
+
+ /*
+ * Continue if no error, because the lazy pattern will match
+ * to other lines
+ */
+ return ret < 0 ? ret : 0;
+}
+
+/* Find probe points from lazy pattern */
+static int find_probe_point_lazy(Dwarf_Die *sp_die, struct probe_finder *pf)
+{
+ int ret = 0;
+
+ if (intlist__empty(pf->lcache)) {
+ /* Matching lazy line pattern */
+ ret = find_lazy_match_lines(pf->lcache, pf->fname,
+ pf->pev->point.lazy_line);
+ if (ret <= 0)
+ return ret;
+ }
+
+ return die_walk_lines(sp_die, probe_point_lazy_walker, pf);
+}
+
+static int probe_point_inline_cb(Dwarf_Die *in_die, void *data)
+{
+ struct probe_finder *pf = data;
+ struct perf_probe_point *pp = &pf->pev->point;
+ Dwarf_Addr addr;
+ int ret;
+
+ if (pp->lazy_line)
+ ret = find_probe_point_lazy(in_die, pf);
+ else {
+ /* Get probe address */
+ if (dwarf_entrypc(in_die, &addr) != 0) {
+ pr_warning("Failed to get entry address of %s.\n",
+ dwarf_diename(in_die));
+ return -ENOENT;
+ }
+ pf->addr = addr;
+ pf->addr += pp->offset;
+ pr_debug("found inline addr: 0x%jx\n",
+ (uintmax_t)pf->addr);
+
+ ret = call_probe_finder(in_die, pf);
+ }
+
+ return ret;
+}
+
+/* Callback parameter with return value for libdw */
+struct dwarf_callback_param {
+ void *data;
+ int retval;
+};
+
+/* Search function from function name */
+static int probe_point_search_cb(Dwarf_Die *sp_die, void *data)
+{
+ struct dwarf_callback_param *param = data;
+ struct probe_finder *pf = param->data;
+ struct perf_probe_point *pp = &pf->pev->point;
+
+ /* Check tag and diename */
+ if (!die_is_func_def(sp_die) ||
+ !die_compare_name(sp_die, pp->function))
+ return DWARF_CB_OK;
+
+ /* Check declared file */
+ if (pp->file && strtailcmp(pp->file, dwarf_decl_file(sp_die)))
+ return DWARF_CB_OK;
+
+ pf->fname = dwarf_decl_file(sp_die);
+ if (pp->line) { /* Function relative line */
+ dwarf_decl_line(sp_die, &pf->lno);
+ pf->lno += pp->line;
+ param->retval = find_probe_point_by_line(pf);
+ } else if (!dwarf_func_inline(sp_die)) {
+ /* Real function */
+ if (pp->lazy_line)
+ param->retval = find_probe_point_lazy(sp_die, pf);
+ else {
+ if (dwarf_entrypc(sp_die, &pf->addr) != 0) {
+ pr_warning("Failed to get entry address of "
+ "%s.\n", dwarf_diename(sp_die));
+ param->retval = -ENOENT;
+ return DWARF_CB_ABORT;
+ }
+ pf->addr += pp->offset;
+ /* TODO: Check the address in this function */
+ param->retval = call_probe_finder(sp_die, pf);
+ }
+ } else
+ /* Inlined function: search instances */
+ param->retval = die_walk_instances(sp_die,
+ probe_point_inline_cb, (void *)pf);
+
+ return DWARF_CB_ABORT; /* Exit; no same symbol in this CU. */
+}
+
+static int find_probe_point_by_func(struct probe_finder *pf)
+{
+ struct dwarf_callback_param _param = {.data = (void *)pf,
+ .retval = 0};
+ dwarf_getfuncs(&pf->cu_die, probe_point_search_cb, &_param, 0);
+ return _param.retval;
+}
+
+struct pubname_callback_param {
+ char *function;
+ char *file;
+ Dwarf_Die *cu_die;
+ Dwarf_Die *sp_die;
+ int found;
+};
+
+static int pubname_search_cb(Dwarf *dbg, Dwarf_Global *gl, void *data)
+{
+ struct pubname_callback_param *param = data;
+
+ if (dwarf_offdie(dbg, gl->die_offset, param->sp_die)) {
+ if (dwarf_tag(param->sp_die) != DW_TAG_subprogram)
+ return DWARF_CB_OK;
+
+ if (die_compare_name(param->sp_die, param->function)) {
+ if (!dwarf_offdie(dbg, gl->cu_offset, param->cu_die))
+ return DWARF_CB_OK;
+
+ if (param->file &&
+ strtailcmp(param->file, dwarf_decl_file(param->sp_die)))
+ return DWARF_CB_OK;
+
+ param->found = 1;
+ return DWARF_CB_ABORT;
+ }
+ }
+
+ return DWARF_CB_OK;
+}
+
+/* Find probe points from debuginfo */
+static int debuginfo__find_probes(struct debuginfo *dbg,
+ struct probe_finder *pf)
+{
+ struct perf_probe_point *pp = &pf->pev->point;
+ Dwarf_Off off, noff;
+ size_t cuhl;
+ Dwarf_Die *diep;
+ int ret = 0;
+
+#if _ELFUTILS_PREREQ(0, 142)
+ /* Get the call frame information from this dwarf */
+ pf->cfi = dwarf_getcfi_elf(dwarf_getelf(dbg->dbg));
+#endif
+
+ off = 0;
+ pf->lcache = intlist__new(NULL);
+ if (!pf->lcache)
+ return -ENOMEM;
+
+ /* Fastpath: lookup by function name from .debug_pubnames section */
+ if (pp->function) {
+ struct pubname_callback_param pubname_param = {
+ .function = pp->function,
+ .file = pp->file,
+ .cu_die = &pf->cu_die,
+ .sp_die = &pf->sp_die,
+ .found = 0,
+ };
+ struct dwarf_callback_param probe_param = {
+ .data = pf,
+ };
+
+ dwarf_getpubnames(dbg->dbg, pubname_search_cb,
+ &pubname_param, 0);
+ if (pubname_param.found) {
+ ret = probe_point_search_cb(&pf->sp_die, &probe_param);
+ if (ret)
+ goto found;
+ }
+ }
+
+ /* Loop on CUs (Compilation Unit) */
+ while (!dwarf_nextcu(dbg->dbg, off, &noff, &cuhl, NULL, NULL, NULL)) {
+ /* Get the DIE(Debugging Information Entry) of this CU */
+ diep = dwarf_offdie(dbg->dbg, off + cuhl, &pf->cu_die);
+ if (!diep)
+ continue;
+
+ /* Check if target file is included. */
+ if (pp->file)
+ pf->fname = cu_find_realpath(&pf->cu_die, pp->file);
+ else
+ pf->fname = NULL;
+
+ if (!pp->file || pf->fname) {
+ if (pp->function)
+ ret = find_probe_point_by_func(pf);
+ else if (pp->lazy_line)
+ ret = find_probe_point_lazy(NULL, pf);
+ else {
+ pf->lno = pp->line;
+ ret = find_probe_point_by_line(pf);
+ }
+ if (ret < 0)
+ break;
+ }
+ off = noff;
+ }
+
+found:
+ intlist__delete(pf->lcache);
+ pf->lcache = NULL;
+
+ return ret;
+}
+
+struct local_vars_finder {
+ struct probe_finder *pf;
+ struct perf_probe_arg *args;
+ int max_args;
+ int nargs;
+ int ret;
+};
+
+/* Collect available variables in this scope */
+static int copy_variables_cb(Dwarf_Die *die_mem, void *data)
+{
+ struct local_vars_finder *vf = data;
+ struct probe_finder *pf = vf->pf;
+ int tag;
+
+ tag = dwarf_tag(die_mem);
+ if (tag == DW_TAG_formal_parameter ||
+ tag == DW_TAG_variable) {
+ if (convert_variable_location(die_mem, vf->pf->addr,
+ vf->pf->fb_ops, &pf->sp_die,
+ NULL) == 0) {
+ vf->args[vf->nargs].var = (char *)dwarf_diename(die_mem);
+ if (vf->args[vf->nargs].var == NULL) {
+ vf->ret = -ENOMEM;
+ return DIE_FIND_CB_END;
+ }
+ pr_debug(" %s", vf->args[vf->nargs].var);
+ vf->nargs++;
+ }
+ }
+
+ if (dwarf_haspc(die_mem, vf->pf->addr))
+ return DIE_FIND_CB_CONTINUE;
+ else
+ return DIE_FIND_CB_SIBLING;
+}
+
+static int expand_probe_args(Dwarf_Die *sc_die, struct probe_finder *pf,
+ struct perf_probe_arg *args)
+{
+ Dwarf_Die die_mem;
+ int i;
+ int n = 0;
+ struct local_vars_finder vf = {.pf = pf, .args = args,
+ .max_args = MAX_PROBE_ARGS, .ret = 0};
+
+ for (i = 0; i < pf->pev->nargs; i++) {
+ /* var never be NULL */
+ if (strcmp(pf->pev->args[i].var, "$vars") == 0) {
+ pr_debug("Expanding $vars into:");
+ vf.nargs = n;
+ /* Special local variables */
+ die_find_child(sc_die, copy_variables_cb, (void *)&vf,
+ &die_mem);
+ pr_debug(" (%d)\n", vf.nargs - n);
+ if (vf.ret < 0)
+ return vf.ret;
+ n = vf.nargs;
+ } else {
+ /* Copy normal argument */
+ args[n] = pf->pev->args[i];
+ n++;
+ }
+ }
+ return n;
+}
+
+/* Add a found probe point into trace event list */
+static int add_probe_trace_event(Dwarf_Die *sc_die, struct probe_finder *pf)
+{
+ struct trace_event_finder *tf =
+ container_of(pf, struct trace_event_finder, pf);
+ struct probe_trace_event *tev;
+ struct perf_probe_arg *args;
+ int ret, i;
+
+ /* Check number of tevs */
+ if (tf->ntevs == tf->max_tevs) {
+ pr_warning("Too many( > %d) probe point found.\n",
+ tf->max_tevs);
+ return -ERANGE;
+ }
+ tev = &tf->tevs[tf->ntevs++];
+
+ /* Trace point should be converted from subprogram DIE */
+ ret = convert_to_trace_point(&pf->sp_die, tf->mod, pf->addr,
+ pf->pev->point.retprobe, &tev->point);
+ if (ret < 0)
+ return ret;
+
+ pr_debug("Probe point found: %s+%lu\n", tev->point.symbol,
+ tev->point.offset);
+
+ /* Expand special probe argument if exist */
+ args = zalloc(sizeof(struct perf_probe_arg) * MAX_PROBE_ARGS);
+ if (args == NULL)
+ return -ENOMEM;
+
+ ret = expand_probe_args(sc_die, pf, args);
+ if (ret < 0)
+ goto end;
+
+ tev->nargs = ret;
+ tev->args = zalloc(sizeof(struct probe_trace_arg) * tev->nargs);
+ if (tev->args == NULL) {
+ ret = -ENOMEM;
+ goto end;
+ }
+
+ /* Find each argument */
+ for (i = 0; i < tev->nargs; i++) {
+ pf->pvar = &args[i];
+ pf->tvar = &tev->args[i];
+ /* Variable should be found from scope DIE */
+ ret = find_variable(sc_die, pf);
+ if (ret != 0)
+ break;
+ }
+
+end:
+ free(args);
+ return ret;
+}
+
+/* Find probe_trace_events specified by perf_probe_event from debuginfo */
+int debuginfo__find_trace_events(struct debuginfo *dbg,
+ struct perf_probe_event *pev,
+ struct probe_trace_event **tevs, int max_tevs)
+{
+ struct trace_event_finder tf = {
+ .pf = {.pev = pev, .callback = add_probe_trace_event},
+ .mod = dbg->mod, .max_tevs = max_tevs};
+ int ret;
+
+ /* Allocate result tevs array */
+ *tevs = zalloc(sizeof(struct probe_trace_event) * max_tevs);
+ if (*tevs == NULL)
+ return -ENOMEM;
+
+ tf.tevs = *tevs;
+ tf.ntevs = 0;
+
+ ret = debuginfo__find_probes(dbg, &tf.pf);
+ if (ret < 0) {
+ zfree(tevs);
+ return ret;
+ }
+
+ return (ret < 0) ? ret : tf.ntevs;
+}
+
+#define MAX_VAR_LEN 64
+
+/* Collect available variables in this scope */
+static int collect_variables_cb(Dwarf_Die *die_mem, void *data)
+{
+ struct available_var_finder *af = data;
+ struct variable_list *vl;
+ char buf[MAX_VAR_LEN];
+ int tag, ret;
+
+ vl = &af->vls[af->nvls - 1];
+
+ tag = dwarf_tag(die_mem);
+ if (tag == DW_TAG_formal_parameter ||
+ tag == DW_TAG_variable) {
+ ret = convert_variable_location(die_mem, af->pf.addr,
+ af->pf.fb_ops, &af->pf.sp_die,
+ NULL);
+ if (ret == 0) {
+ ret = die_get_varname(die_mem, buf, MAX_VAR_LEN);
+ pr_debug2("Add new var: %s\n", buf);
+ if (ret > 0)
+ strlist__add(vl->vars, buf);
+ }
+ }
+
+ if (af->child && dwarf_haspc(die_mem, af->pf.addr))
+ return DIE_FIND_CB_CONTINUE;
+ else
+ return DIE_FIND_CB_SIBLING;
+}
+
+/* Add a found vars into available variables list */
+static int add_available_vars(Dwarf_Die *sc_die, struct probe_finder *pf)
+{
+ struct available_var_finder *af =
+ container_of(pf, struct available_var_finder, pf);
+ struct variable_list *vl;
+ Dwarf_Die die_mem;
+ int ret;
+
+ /* Check number of tevs */
+ if (af->nvls == af->max_vls) {
+ pr_warning("Too many( > %d) probe point found.\n", af->max_vls);
+ return -ERANGE;
+ }
+ vl = &af->vls[af->nvls++];
+
+ /* Trace point should be converted from subprogram DIE */
+ ret = convert_to_trace_point(&pf->sp_die, af->mod, pf->addr,
+ pf->pev->point.retprobe, &vl->point);
+ if (ret < 0)
+ return ret;
+
+ pr_debug("Probe point found: %s+%lu\n", vl->point.symbol,
+ vl->point.offset);
+
+ /* Find local variables */
+ vl->vars = strlist__new(true, NULL);
+ if (vl->vars == NULL)
+ return -ENOMEM;
+ af->child = true;
+ die_find_child(sc_die, collect_variables_cb, (void *)af, &die_mem);
+
+ /* Find external variables */
+ if (!af->externs)
+ goto out;
+ /* Don't need to search child DIE for externs. */
+ af->child = false;
+ die_find_child(&pf->cu_die, collect_variables_cb, (void *)af, &die_mem);
+
+out:
+ if (strlist__empty(vl->vars)) {
+ strlist__delete(vl->vars);
+ vl->vars = NULL;
+ }
+
+ return ret;
+}
+
+/*
+ * Find available variables at given probe point
+ * Return the number of found probe points. Return 0 if there is no
+ * matched probe point. Return <0 if an error occurs.
+ */
+int debuginfo__find_available_vars_at(struct debuginfo *dbg,
+ struct perf_probe_event *pev,
+ struct variable_list **vls,
+ int max_vls, bool externs)
+{
+ struct available_var_finder af = {
+ .pf = {.pev = pev, .callback = add_available_vars},
+ .mod = dbg->mod,
+ .max_vls = max_vls, .externs = externs};
+ int ret;
+
+ /* Allocate result vls array */
+ *vls = zalloc(sizeof(struct variable_list) * max_vls);
+ if (*vls == NULL)
+ return -ENOMEM;
+
+ af.vls = *vls;
+ af.nvls = 0;
+
+ ret = debuginfo__find_probes(dbg, &af.pf);
+ if (ret < 0) {
+ /* Free vlist for error */
+ while (af.nvls--) {
+ zfree(&af.vls[af.nvls].point.symbol);
+ strlist__delete(af.vls[af.nvls].vars);
+ }
+ zfree(vls);
+ return ret;
+ }
+
+ return (ret < 0) ? ret : af.nvls;
+}
+
+/* Reverse search */
+int debuginfo__find_probe_point(struct debuginfo *dbg, unsigned long addr,
+ struct perf_probe_point *ppt)
+{
+ Dwarf_Die cudie, spdie, indie;
+ Dwarf_Addr _addr = 0, baseaddr = 0;
+ const char *fname = NULL, *func = NULL, *basefunc = NULL, *tmp;
+ int baseline = 0, lineno = 0, ret = 0;
+
+ /* Adjust address with bias */
+ addr += dbg->bias;
+
+ /* Find cu die */
+ if (!dwarf_addrdie(dbg->dbg, (Dwarf_Addr)addr - dbg->bias, &cudie)) {
+ pr_warning("Failed to find debug information for address %lx\n",
+ addr);
+ ret = -EINVAL;
+ goto end;
+ }
+
+ /* Find a corresponding line (filename and lineno) */
+ cu_find_lineinfo(&cudie, addr, &fname, &lineno);
+ /* Don't care whether it failed or not */
+
+ /* Find a corresponding function (name, baseline and baseaddr) */
+ if (die_find_realfunc(&cudie, (Dwarf_Addr)addr, &spdie)) {
+ /* Get function entry information */
+ func = basefunc = dwarf_diename(&spdie);
+ if (!func ||
+ dwarf_entrypc(&spdie, &baseaddr) != 0 ||
+ dwarf_decl_line(&spdie, &baseline) != 0) {
+ lineno = 0;
+ goto post;
+ }
+
+ fname = dwarf_decl_file(&spdie);
+ if (addr == (unsigned long)baseaddr) {
+ /* Function entry - Relative line number is 0 */
+ lineno = baseline;
+ goto post;
+ }
+
+ /* Track down the inline functions step by step */
+ while (die_find_top_inlinefunc(&spdie, (Dwarf_Addr)addr,
+ &indie)) {
+ /* There is an inline function */
+ if (dwarf_entrypc(&indie, &_addr) == 0 &&
+ _addr == addr) {
+ /*
+ * addr is at an inline function entry.
+ * In this case, lineno should be the call-site
+ * line number. (overwrite lineinfo)
+ */
+ lineno = die_get_call_lineno(&indie);
+ fname = die_get_call_file(&indie);
+ break;
+ } else {
+ /*
+ * addr is in an inline function body.
+ * Since lineno points one of the lines
+ * of the inline function, baseline should
+ * be the entry line of the inline function.
+ */
+ tmp = dwarf_diename(&indie);
+ if (!tmp ||
+ dwarf_decl_line(&indie, &baseline) != 0)
+ break;
+ func = tmp;
+ spdie = indie;
+ }
+ }
+ /* Verify the lineno and baseline are in a same file */
+ tmp = dwarf_decl_file(&spdie);
+ if (!tmp || strcmp(tmp, fname) != 0)
+ lineno = 0;
+ }
+
+post:
+ /* Make a relative line number or an offset */
+ if (lineno)
+ ppt->line = lineno - baseline;
+ else if (basefunc) {
+ ppt->offset = addr - (unsigned long)baseaddr;
+ func = basefunc;
+ }
+
+ /* Duplicate strings */
+ if (func) {
+ ppt->function = strdup(func);
+ if (ppt->function == NULL) {
+ ret = -ENOMEM;
+ goto end;
+ }
+ }
+ if (fname) {
+ ppt->file = strdup(fname);
+ if (ppt->file == NULL) {
+ zfree(&ppt->function);
+ ret = -ENOMEM;
+ goto end;
+ }
+ }
+end:
+ if (ret == 0 && (fname || func))
+ ret = 1; /* Found a point */
+ return ret;
+}
+
+/* Add a line and store the src path */
+static int line_range_add_line(const char *src, unsigned int lineno,
+ struct line_range *lr)
+{
+ /* Copy source path */
+ if (!lr->path) {
+ lr->path = strdup(src);
+ if (lr->path == NULL)
+ return -ENOMEM;
+ }
+ return intlist__add(lr->line_list, lineno);
+}
+
+static int line_range_walk_cb(const char *fname, int lineno,
+ Dwarf_Addr addr __maybe_unused,
+ void *data)
+{
+ struct line_finder *lf = data;
+ int err;
+
+ if ((strtailcmp(fname, lf->fname) != 0) ||
+ (lf->lno_s > lineno || lf->lno_e < lineno))
+ return 0;
+
+ err = line_range_add_line(fname, lineno, lf->lr);
+ if (err < 0 && err != -EEXIST)
+ return err;
+
+ return 0;
+}
+
+/* Find line range from its line number */
+static int find_line_range_by_line(Dwarf_Die *sp_die, struct line_finder *lf)
+{
+ int ret;
+
+ ret = die_walk_lines(sp_die ?: &lf->cu_die, line_range_walk_cb, lf);
+
+ /* Update status */
+ if (ret >= 0)
+ if (!intlist__empty(lf->lr->line_list))
+ ret = lf->found = 1;
+ else
+ ret = 0; /* Lines are not found */
+ else {
+ zfree(&lf->lr->path);
+ }
+ return ret;
+}
+
+static int line_range_inline_cb(Dwarf_Die *in_die, void *data)
+{
+ int ret = find_line_range_by_line(in_die, data);
+
+ /*
+ * We have to check all instances of inlined function, because
+ * some execution paths can be optimized out depends on the
+ * function argument of instances. However, if an error occurs,
+ * it should be handled by the caller.
+ */
+ return ret < 0 ? ret : 0;
+}
+
+/* Search function definition from function name */
+static int line_range_search_cb(Dwarf_Die *sp_die, void *data)
+{
+ struct dwarf_callback_param *param = data;
+ struct line_finder *lf = param->data;
+ struct line_range *lr = lf->lr;
+
+ /* Check declared file */
+ if (lr->file && strtailcmp(lr->file, dwarf_decl_file(sp_die)))
+ return DWARF_CB_OK;
+
+ if (die_is_func_def(sp_die) &&
+ die_compare_name(sp_die, lr->function)) {
+ lf->fname = dwarf_decl_file(sp_die);
+ dwarf_decl_line(sp_die, &lr->offset);
+ pr_debug("fname: %s, lineno:%d\n", lf->fname, lr->offset);
+ lf->lno_s = lr->offset + lr->start;
+ if (lf->lno_s < 0) /* Overflow */
+ lf->lno_s = INT_MAX;
+ lf->lno_e = lr->offset + lr->end;
+ if (lf->lno_e < 0) /* Overflow */
+ lf->lno_e = INT_MAX;
+ pr_debug("New line range: %d to %d\n", lf->lno_s, lf->lno_e);
+ lr->start = lf->lno_s;
+ lr->end = lf->lno_e;
+ if (dwarf_func_inline(sp_die))
+ param->retval = die_walk_instances(sp_die,
+ line_range_inline_cb, lf);
+ else
+ param->retval = find_line_range_by_line(sp_die, lf);
+ return DWARF_CB_ABORT;
+ }
+ return DWARF_CB_OK;
+}
+
+static int find_line_range_by_func(struct line_finder *lf)
+{
+ struct dwarf_callback_param param = {.data = (void *)lf, .retval = 0};
+ dwarf_getfuncs(&lf->cu_die, line_range_search_cb, &param, 0);
+ return param.retval;
+}
+
+int debuginfo__find_line_range(struct debuginfo *dbg, struct line_range *lr)
+{
+ struct line_finder lf = {.lr = lr, .found = 0};
+ int ret = 0;
+ Dwarf_Off off = 0, noff;
+ size_t cuhl;
+ Dwarf_Die *diep;
+ const char *comp_dir;
+
+ /* Fastpath: lookup by function name from .debug_pubnames section */
+ if (lr->function) {
+ struct pubname_callback_param pubname_param = {
+ .function = lr->function, .file = lr->file,
+ .cu_die = &lf.cu_die, .sp_die = &lf.sp_die, .found = 0};
+ struct dwarf_callback_param line_range_param = {
+ .data = (void *)&lf, .retval = 0};
+
+ dwarf_getpubnames(dbg->dbg, pubname_search_cb,
+ &pubname_param, 0);
+ if (pubname_param.found) {
+ line_range_search_cb(&lf.sp_die, &line_range_param);
+ if (lf.found)
+ goto found;
+ }
+ }
+
+ /* Loop on CUs (Compilation Unit) */
+ while (!lf.found && ret >= 0) {
+ if (dwarf_nextcu(dbg->dbg, off, &noff, &cuhl,
+ NULL, NULL, NULL) != 0)
+ break;
+
+ /* Get the DIE(Debugging Information Entry) of this CU */
+ diep = dwarf_offdie(dbg->dbg, off + cuhl, &lf.cu_die);
+ if (!diep)
+ continue;
+
+ /* Check if target file is included. */
+ if (lr->file)
+ lf.fname = cu_find_realpath(&lf.cu_die, lr->file);
+ else
+ lf.fname = 0;
+
+ if (!lr->file || lf.fname) {
+ if (lr->function)
+ ret = find_line_range_by_func(&lf);
+ else {
+ lf.lno_s = lr->start;
+ lf.lno_e = lr->end;
+ ret = find_line_range_by_line(NULL, &lf);
+ }
+ }
+ off = noff;
+ }
+
+found:
+ /* Store comp_dir */
+ if (lf.found) {
+ comp_dir = cu_get_comp_dir(&lf.cu_die);
+ if (comp_dir) {
+ lr->comp_dir = strdup(comp_dir);
+ if (!lr->comp_dir)
+ ret = -ENOMEM;
+ }
+ }
+
+ pr_debug("path: %s\n", lr->path);
+ return (ret < 0) ? ret : lf.found;
+}
+
diff --git a/tools/perf/util/probe-finder.h b/tools/perf/util/probe-finder.h
new file mode 100644
index 00000000000..92590b2c7e1
--- /dev/null
+++ b/tools/perf/util/probe-finder.h
@@ -0,0 +1,112 @@
+#ifndef _PROBE_FINDER_H
+#define _PROBE_FINDER_H
+
+#include <stdbool.h>
+#include "util.h"
+#include "intlist.h"
+#include "probe-event.h"
+
+#define MAX_PROBE_BUFFER 1024
+#define MAX_PROBES 128
+#define MAX_PROBE_ARGS 128
+
+static inline int is_c_varname(const char *name)
+{
+ /* TODO */
+ return isalpha(name[0]) || name[0] == '_';
+}
+
+#ifdef HAVE_DWARF_SUPPORT
+
+#include "dwarf-aux.h"
+
+/* TODO: export debuginfo data structure even if no dwarf support */
+
+/* debug information structure */
+struct debuginfo {
+ Dwarf *dbg;
+ Dwfl_Module *mod;
+ Dwfl *dwfl;
+ Dwarf_Addr bias;
+};
+
+/* This also tries to open distro debuginfo */
+extern struct debuginfo *debuginfo__new(const char *path);
+extern void debuginfo__delete(struct debuginfo *dbg);
+
+/* Find probe_trace_events specified by perf_probe_event from debuginfo */
+extern int debuginfo__find_trace_events(struct debuginfo *dbg,
+ struct perf_probe_event *pev,
+ struct probe_trace_event **tevs,
+ int max_tevs);
+
+/* Find a perf_probe_point from debuginfo */
+extern int debuginfo__find_probe_point(struct debuginfo *dbg,
+ unsigned long addr,
+ struct perf_probe_point *ppt);
+
+/* Find a line range */
+extern int debuginfo__find_line_range(struct debuginfo *dbg,
+ struct line_range *lr);
+
+/* Find available variables */
+extern int debuginfo__find_available_vars_at(struct debuginfo *dbg,
+ struct perf_probe_event *pev,
+ struct variable_list **vls,
+ int max_points, bool externs);
+
+struct probe_finder {
+ struct perf_probe_event *pev; /* Target probe event */
+
+ /* Callback when a probe point is found */
+ int (*callback)(Dwarf_Die *sc_die, struct probe_finder *pf);
+
+ /* For function searching */
+ int lno; /* Line number */
+ Dwarf_Addr addr; /* Address */
+ const char *fname; /* Real file name */
+ Dwarf_Die cu_die; /* Current CU */
+ Dwarf_Die sp_die;
+ struct intlist *lcache; /* Line cache for lazy match */
+
+ /* For variable searching */
+#if _ELFUTILS_PREREQ(0, 142)
+ Dwarf_CFI *cfi; /* Call Frame Information */
+#endif
+ Dwarf_Op *fb_ops; /* Frame base attribute */
+ struct perf_probe_arg *pvar; /* Current target variable */
+ struct probe_trace_arg *tvar; /* Current result variable */
+};
+
+struct trace_event_finder {
+ struct probe_finder pf;
+ Dwfl_Module *mod; /* For solving symbols */
+ struct probe_trace_event *tevs; /* Found trace events */
+ int ntevs; /* Number of trace events */
+ int max_tevs; /* Max number of trace events */
+};
+
+struct available_var_finder {
+ struct probe_finder pf;
+ Dwfl_Module *mod; /* For solving symbols */
+ struct variable_list *vls; /* Found variable lists */
+ int nvls; /* Number of variable lists */
+ int max_vls; /* Max no. of variable lists */
+ bool externs; /* Find external vars too */
+ bool child; /* Search child scopes */
+};
+
+struct line_finder {
+ struct line_range *lr; /* Target line range */
+
+ const char *fname; /* File name */
+ int lno_s; /* Start line number */
+ int lno_e; /* End line number */
+ Dwarf_Die cu_die; /* Current CU */
+ Dwarf_Die sp_die;
+ int found;
+};
+
+#endif /* HAVE_DWARF_SUPPORT */
+
+#endif /*_PROBE_FINDER_H */
diff --git a/tools/perf/util/pstack.c b/tools/perf/util/pstack.c
new file mode 100644
index 00000000000..daa17aeb6c6
--- /dev/null
+++ b/tools/perf/util/pstack.c
@@ -0,0 +1,75 @@
+/*
+ * Simple pointer stack
+ *
+ * (c) 2010 Arnaldo Carvalho de Melo <acme@redhat.com>
+ */
+
+#include "util.h"
+#include "pstack.h"
+#include <linux/kernel.h>
+#include <stdlib.h>
+
+struct pstack {
+ unsigned short top;
+ unsigned short max_nr_entries;
+ void *entries[0];
+};
+
+struct pstack *pstack__new(unsigned short max_nr_entries)
+{
+ struct pstack *pstack = zalloc((sizeof(*pstack) +
+ max_nr_entries * sizeof(void *)));
+ if (pstack != NULL)
+ pstack->max_nr_entries = max_nr_entries;
+ return pstack;
+}
+
+void pstack__delete(struct pstack *pstack)
+{
+ free(pstack);
+}
+
+bool pstack__empty(const struct pstack *pstack)
+{
+ return pstack->top == 0;
+}
+
+void pstack__remove(struct pstack *pstack, void *key)
+{
+ unsigned short i = pstack->top, last_index = pstack->top - 1;
+
+ while (i-- != 0) {
+ if (pstack->entries[i] == key) {
+ if (i < last_index)
+ memmove(pstack->entries + i,
+ pstack->entries + i + 1,
+ (last_index - i) * sizeof(void *));
+ --pstack->top;
+ return;
+ }
+ }
+ pr_err("%s: %p not on the pstack!\n", __func__, key);
+}
+
+void pstack__push(struct pstack *pstack, void *key)
+{
+ if (pstack->top == pstack->max_nr_entries) {
+ pr_err("%s: top=%d, overflow!\n", __func__, pstack->top);
+ return;
+ }
+ pstack->entries[pstack->top++] = key;
+}
+
+void *pstack__pop(struct pstack *pstack)
+{
+ void *ret;
+
+ if (pstack->top == 0) {
+ pr_err("%s: underflow!\n", __func__);
+ return NULL;
+ }
+
+ ret = pstack->entries[--pstack->top];
+ pstack->entries[pstack->top] = NULL;
+ return ret;
+}
diff --git a/tools/perf/util/pstack.h b/tools/perf/util/pstack.h
new file mode 100644
index 00000000000..c3cb6584d52
--- /dev/null
+++ b/tools/perf/util/pstack.h
@@ -0,0 +1,14 @@
+#ifndef _PERF_PSTACK_
+#define _PERF_PSTACK_
+
+#include <stdbool.h>
+
+struct pstack;
+struct pstack *pstack__new(unsigned short max_nr_entries);
+void pstack__delete(struct pstack *pstack);
+bool pstack__empty(const struct pstack *pstack);
+void pstack__remove(struct pstack *pstack, void *key);
+void pstack__push(struct pstack *pstack, void *key);
+void *pstack__pop(struct pstack *pstack);
+
+#endif /* _PERF_PSTACK_ */
diff --git a/tools/perf/util/python-ext-sources b/tools/perf/util/python-ext-sources
new file mode 100644
index 00000000000..16a475a7d49
--- /dev/null
+++ b/tools/perf/util/python-ext-sources
@@ -0,0 +1,22 @@
+#
+# List of files needed by perf python extension
+#
+# Each source file must be placed on its own line so that it can be
+# processed by Makefile and util/setup.py accordingly.
+#
+
+util/python.c
+util/ctype.c
+util/evlist.c
+util/evsel.c
+util/cpumap.c
+util/hweight.c
+util/thread_map.c
+util/util.c
+util/xyarray.c
+util/cgroup.c
+util/rblist.c
+util/strlist.c
+../lib/api/fs/fs.c
+util/trace-event.c
+../../lib/rbtree.c
diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c
new file mode 100644
index 00000000000..122669c18ff
--- /dev/null
+++ b/tools/perf/util/python.c
@@ -0,0 +1,1074 @@
+#include <Python.h>
+#include <structmember.h>
+#include <inttypes.h>
+#include <poll.h>
+#include "evlist.h"
+#include "evsel.h"
+#include "event.h"
+#include "cpumap.h"
+#include "thread_map.h"
+
+/*
+ * Support debug printing even though util/debug.c is not linked. That means
+ * implementing 'verbose' and 'eprintf'.
+ */
+int verbose;
+
+int eprintf(int level, const char *fmt, ...)
+{
+ va_list args;
+ int ret = 0;
+
+ if (verbose >= level) {
+ va_start(args, fmt);
+ ret = vfprintf(stderr, fmt, args);
+ va_end(args);
+ }
+
+ return ret;
+}
+
+/* Define PyVarObject_HEAD_INIT for python 2.5 */
+#ifndef PyVarObject_HEAD_INIT
+# define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size,
+#endif
+
+PyMODINIT_FUNC initperf(void);
+
+#define member_def(type, member, ptype, help) \
+ { #member, ptype, \
+ offsetof(struct pyrf_event, event) + offsetof(struct type, member), \
+ 0, help }
+
+#define sample_member_def(name, member, ptype, help) \
+ { #name, ptype, \
+ offsetof(struct pyrf_event, sample) + offsetof(struct perf_sample, member), \
+ 0, help }
+
+struct pyrf_event {
+ PyObject_HEAD
+ struct perf_sample sample;
+ union perf_event event;
+};
+
+#define sample_members \
+ sample_member_def(sample_ip, ip, T_ULONGLONG, "event type"), \
+ sample_member_def(sample_pid, pid, T_INT, "event pid"), \
+ sample_member_def(sample_tid, tid, T_INT, "event tid"), \
+ sample_member_def(sample_time, time, T_ULONGLONG, "event timestamp"), \
+ sample_member_def(sample_addr, addr, T_ULONGLONG, "event addr"), \
+ sample_member_def(sample_id, id, T_ULONGLONG, "event id"), \
+ sample_member_def(sample_stream_id, stream_id, T_ULONGLONG, "event stream id"), \
+ sample_member_def(sample_period, period, T_ULONGLONG, "event period"), \
+ sample_member_def(sample_cpu, cpu, T_UINT, "event cpu"),
+
+static char pyrf_mmap_event__doc[] = PyDoc_STR("perf mmap event object.");
+
+static PyMemberDef pyrf_mmap_event__members[] = {
+ sample_members
+ member_def(perf_event_header, type, T_UINT, "event type"),
+ member_def(mmap_event, pid, T_UINT, "event pid"),
+ member_def(mmap_event, tid, T_UINT, "event tid"),
+ member_def(mmap_event, start, T_ULONGLONG, "start of the map"),
+ member_def(mmap_event, len, T_ULONGLONG, "map length"),
+ member_def(mmap_event, pgoff, T_ULONGLONG, "page offset"),
+ member_def(mmap_event, filename, T_STRING_INPLACE, "backing store"),
+ { .name = NULL, },
+};
+
+static PyObject *pyrf_mmap_event__repr(struct pyrf_event *pevent)
+{
+ PyObject *ret;
+ char *s;
+
+ if (asprintf(&s, "{ type: mmap, pid: %u, tid: %u, start: %#" PRIx64 ", "
+ "length: %#" PRIx64 ", offset: %#" PRIx64 ", "
+ "filename: %s }",
+ pevent->event.mmap.pid, pevent->event.mmap.tid,
+ pevent->event.mmap.start, pevent->event.mmap.len,
+ pevent->event.mmap.pgoff, pevent->event.mmap.filename) < 0) {
+ ret = PyErr_NoMemory();
+ } else {
+ ret = PyString_FromString(s);
+ free(s);
+ }
+ return ret;
+}
+
+static PyTypeObject pyrf_mmap_event__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.mmap_event",
+ .tp_basicsize = sizeof(struct pyrf_event),
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = pyrf_mmap_event__doc,
+ .tp_members = pyrf_mmap_event__members,
+ .tp_repr = (reprfunc)pyrf_mmap_event__repr,
+};
+
+static char pyrf_task_event__doc[] = PyDoc_STR("perf task (fork/exit) event object.");
+
+static PyMemberDef pyrf_task_event__members[] = {
+ sample_members
+ member_def(perf_event_header, type, T_UINT, "event type"),
+ member_def(fork_event, pid, T_UINT, "event pid"),
+ member_def(fork_event, ppid, T_UINT, "event ppid"),
+ member_def(fork_event, tid, T_UINT, "event tid"),
+ member_def(fork_event, ptid, T_UINT, "event ptid"),
+ member_def(fork_event, time, T_ULONGLONG, "timestamp"),
+ { .name = NULL, },
+};
+
+static PyObject *pyrf_task_event__repr(struct pyrf_event *pevent)
+{
+ return PyString_FromFormat("{ type: %s, pid: %u, ppid: %u, tid: %u, "
+ "ptid: %u, time: %" PRIu64 "}",
+ pevent->event.header.type == PERF_RECORD_FORK ? "fork" : "exit",
+ pevent->event.fork.pid,
+ pevent->event.fork.ppid,
+ pevent->event.fork.tid,
+ pevent->event.fork.ptid,
+ pevent->event.fork.time);
+}
+
+static PyTypeObject pyrf_task_event__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.task_event",
+ .tp_basicsize = sizeof(struct pyrf_event),
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = pyrf_task_event__doc,
+ .tp_members = pyrf_task_event__members,
+ .tp_repr = (reprfunc)pyrf_task_event__repr,
+};
+
+static char pyrf_comm_event__doc[] = PyDoc_STR("perf comm event object.");
+
+static PyMemberDef pyrf_comm_event__members[] = {
+ sample_members
+ member_def(perf_event_header, type, T_UINT, "event type"),
+ member_def(comm_event, pid, T_UINT, "event pid"),
+ member_def(comm_event, tid, T_UINT, "event tid"),
+ member_def(comm_event, comm, T_STRING_INPLACE, "process name"),
+ { .name = NULL, },
+};
+
+static PyObject *pyrf_comm_event__repr(struct pyrf_event *pevent)
+{
+ return PyString_FromFormat("{ type: comm, pid: %u, tid: %u, comm: %s }",
+ pevent->event.comm.pid,
+ pevent->event.comm.tid,
+ pevent->event.comm.comm);
+}
+
+static PyTypeObject pyrf_comm_event__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.comm_event",
+ .tp_basicsize = sizeof(struct pyrf_event),
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = pyrf_comm_event__doc,
+ .tp_members = pyrf_comm_event__members,
+ .tp_repr = (reprfunc)pyrf_comm_event__repr,
+};
+
+static char pyrf_throttle_event__doc[] = PyDoc_STR("perf throttle event object.");
+
+static PyMemberDef pyrf_throttle_event__members[] = {
+ sample_members
+ member_def(perf_event_header, type, T_UINT, "event type"),
+ member_def(throttle_event, time, T_ULONGLONG, "timestamp"),
+ member_def(throttle_event, id, T_ULONGLONG, "event id"),
+ member_def(throttle_event, stream_id, T_ULONGLONG, "event stream id"),
+ { .name = NULL, },
+};
+
+static PyObject *pyrf_throttle_event__repr(struct pyrf_event *pevent)
+{
+ struct throttle_event *te = (struct throttle_event *)(&pevent->event.header + 1);
+
+ return PyString_FromFormat("{ type: %sthrottle, time: %" PRIu64 ", id: %" PRIu64
+ ", stream_id: %" PRIu64 " }",
+ pevent->event.header.type == PERF_RECORD_THROTTLE ? "" : "un",
+ te->time, te->id, te->stream_id);
+}
+
+static PyTypeObject pyrf_throttle_event__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.throttle_event",
+ .tp_basicsize = sizeof(struct pyrf_event),
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = pyrf_throttle_event__doc,
+ .tp_members = pyrf_throttle_event__members,
+ .tp_repr = (reprfunc)pyrf_throttle_event__repr,
+};
+
+static char pyrf_lost_event__doc[] = PyDoc_STR("perf lost event object.");
+
+static PyMemberDef pyrf_lost_event__members[] = {
+ sample_members
+ member_def(lost_event, id, T_ULONGLONG, "event id"),
+ member_def(lost_event, lost, T_ULONGLONG, "number of lost events"),
+ { .name = NULL, },
+};
+
+static PyObject *pyrf_lost_event__repr(struct pyrf_event *pevent)
+{
+ PyObject *ret;
+ char *s;
+
+ if (asprintf(&s, "{ type: lost, id: %#" PRIx64 ", "
+ "lost: %#" PRIx64 " }",
+ pevent->event.lost.id, pevent->event.lost.lost) < 0) {
+ ret = PyErr_NoMemory();
+ } else {
+ ret = PyString_FromString(s);
+ free(s);
+ }
+ return ret;
+}
+
+static PyTypeObject pyrf_lost_event__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.lost_event",
+ .tp_basicsize = sizeof(struct pyrf_event),
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = pyrf_lost_event__doc,
+ .tp_members = pyrf_lost_event__members,
+ .tp_repr = (reprfunc)pyrf_lost_event__repr,
+};
+
+static char pyrf_read_event__doc[] = PyDoc_STR("perf read event object.");
+
+static PyMemberDef pyrf_read_event__members[] = {
+ sample_members
+ member_def(read_event, pid, T_UINT, "event pid"),
+ member_def(read_event, tid, T_UINT, "event tid"),
+ { .name = NULL, },
+};
+
+static PyObject *pyrf_read_event__repr(struct pyrf_event *pevent)
+{
+ return PyString_FromFormat("{ type: read, pid: %u, tid: %u }",
+ pevent->event.read.pid,
+ pevent->event.read.tid);
+ /*
+ * FIXME: return the array of read values,
+ * making this method useful ;-)
+ */
+}
+
+static PyTypeObject pyrf_read_event__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.read_event",
+ .tp_basicsize = sizeof(struct pyrf_event),
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = pyrf_read_event__doc,
+ .tp_members = pyrf_read_event__members,
+ .tp_repr = (reprfunc)pyrf_read_event__repr,
+};
+
+static char pyrf_sample_event__doc[] = PyDoc_STR("perf sample event object.");
+
+static PyMemberDef pyrf_sample_event__members[] = {
+ sample_members
+ member_def(perf_event_header, type, T_UINT, "event type"),
+ { .name = NULL, },
+};
+
+static PyObject *pyrf_sample_event__repr(struct pyrf_event *pevent)
+{
+ PyObject *ret;
+ char *s;
+
+ if (asprintf(&s, "{ type: sample }") < 0) {
+ ret = PyErr_NoMemory();
+ } else {
+ ret = PyString_FromString(s);
+ free(s);
+ }
+ return ret;
+}
+
+static PyTypeObject pyrf_sample_event__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.sample_event",
+ .tp_basicsize = sizeof(struct pyrf_event),
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = pyrf_sample_event__doc,
+ .tp_members = pyrf_sample_event__members,
+ .tp_repr = (reprfunc)pyrf_sample_event__repr,
+};
+
+static int pyrf_event__setup_types(void)
+{
+ int err;
+ pyrf_mmap_event__type.tp_new =
+ pyrf_task_event__type.tp_new =
+ pyrf_comm_event__type.tp_new =
+ pyrf_lost_event__type.tp_new =
+ pyrf_read_event__type.tp_new =
+ pyrf_sample_event__type.tp_new =
+ pyrf_throttle_event__type.tp_new = PyType_GenericNew;
+ err = PyType_Ready(&pyrf_mmap_event__type);
+ if (err < 0)
+ goto out;
+ err = PyType_Ready(&pyrf_lost_event__type);
+ if (err < 0)
+ goto out;
+ err = PyType_Ready(&pyrf_task_event__type);
+ if (err < 0)
+ goto out;
+ err = PyType_Ready(&pyrf_comm_event__type);
+ if (err < 0)
+ goto out;
+ err = PyType_Ready(&pyrf_throttle_event__type);
+ if (err < 0)
+ goto out;
+ err = PyType_Ready(&pyrf_read_event__type);
+ if (err < 0)
+ goto out;
+ err = PyType_Ready(&pyrf_sample_event__type);
+ if (err < 0)
+ goto out;
+out:
+ return err;
+}
+
+static PyTypeObject *pyrf_event__type[] = {
+ [PERF_RECORD_MMAP] = &pyrf_mmap_event__type,
+ [PERF_RECORD_LOST] = &pyrf_lost_event__type,
+ [PERF_RECORD_COMM] = &pyrf_comm_event__type,
+ [PERF_RECORD_EXIT] = &pyrf_task_event__type,
+ [PERF_RECORD_THROTTLE] = &pyrf_throttle_event__type,
+ [PERF_RECORD_UNTHROTTLE] = &pyrf_throttle_event__type,
+ [PERF_RECORD_FORK] = &pyrf_task_event__type,
+ [PERF_RECORD_READ] = &pyrf_read_event__type,
+ [PERF_RECORD_SAMPLE] = &pyrf_sample_event__type,
+};
+
+static PyObject *pyrf_event__new(union perf_event *event)
+{
+ struct pyrf_event *pevent;
+ PyTypeObject *ptype;
+
+ if (event->header.type < PERF_RECORD_MMAP ||
+ event->header.type > PERF_RECORD_SAMPLE)
+ return NULL;
+
+ ptype = pyrf_event__type[event->header.type];
+ pevent = PyObject_New(struct pyrf_event, ptype);
+ if (pevent != NULL)
+ memcpy(&pevent->event, event, event->header.size);
+ return (PyObject *)pevent;
+}
+
+struct pyrf_cpu_map {
+ PyObject_HEAD
+
+ struct cpu_map *cpus;
+};
+
+static int pyrf_cpu_map__init(struct pyrf_cpu_map *pcpus,
+ PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = { "cpustr", NULL };
+ char *cpustr = NULL;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|s",
+ kwlist, &cpustr))
+ return -1;
+
+ pcpus->cpus = cpu_map__new(cpustr);
+ if (pcpus->cpus == NULL)
+ return -1;
+ return 0;
+}
+
+static void pyrf_cpu_map__delete(struct pyrf_cpu_map *pcpus)
+{
+ cpu_map__delete(pcpus->cpus);
+ pcpus->ob_type->tp_free((PyObject*)pcpus);
+}
+
+static Py_ssize_t pyrf_cpu_map__length(PyObject *obj)
+{
+ struct pyrf_cpu_map *pcpus = (void *)obj;
+
+ return pcpus->cpus->nr;
+}
+
+static PyObject *pyrf_cpu_map__item(PyObject *obj, Py_ssize_t i)
+{
+ struct pyrf_cpu_map *pcpus = (void *)obj;
+
+ if (i >= pcpus->cpus->nr)
+ return NULL;
+
+ return Py_BuildValue("i", pcpus->cpus->map[i]);
+}
+
+static PySequenceMethods pyrf_cpu_map__sequence_methods = {
+ .sq_length = pyrf_cpu_map__length,
+ .sq_item = pyrf_cpu_map__item,
+};
+
+static char pyrf_cpu_map__doc[] = PyDoc_STR("cpu map object.");
+
+static PyTypeObject pyrf_cpu_map__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.cpu_map",
+ .tp_basicsize = sizeof(struct pyrf_cpu_map),
+ .tp_dealloc = (destructor)pyrf_cpu_map__delete,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = pyrf_cpu_map__doc,
+ .tp_as_sequence = &pyrf_cpu_map__sequence_methods,
+ .tp_init = (initproc)pyrf_cpu_map__init,
+};
+
+static int pyrf_cpu_map__setup_types(void)
+{
+ pyrf_cpu_map__type.tp_new = PyType_GenericNew;
+ return PyType_Ready(&pyrf_cpu_map__type);
+}
+
+struct pyrf_thread_map {
+ PyObject_HEAD
+
+ struct thread_map *threads;
+};
+
+static int pyrf_thread_map__init(struct pyrf_thread_map *pthreads,
+ PyObject *args, PyObject *kwargs)
+{
+ static char *kwlist[] = { "pid", "tid", "uid", NULL };
+ int pid = -1, tid = -1, uid = UINT_MAX;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|iii",
+ kwlist, &pid, &tid, &uid))
+ return -1;
+
+ pthreads->threads = thread_map__new(pid, tid, uid);
+ if (pthreads->threads == NULL)
+ return -1;
+ return 0;
+}
+
+static void pyrf_thread_map__delete(struct pyrf_thread_map *pthreads)
+{
+ thread_map__delete(pthreads->threads);
+ pthreads->ob_type->tp_free((PyObject*)pthreads);
+}
+
+static Py_ssize_t pyrf_thread_map__length(PyObject *obj)
+{
+ struct pyrf_thread_map *pthreads = (void *)obj;
+
+ return pthreads->threads->nr;
+}
+
+static PyObject *pyrf_thread_map__item(PyObject *obj, Py_ssize_t i)
+{
+ struct pyrf_thread_map *pthreads = (void *)obj;
+
+ if (i >= pthreads->threads->nr)
+ return NULL;
+
+ return Py_BuildValue("i", pthreads->threads->map[i]);
+}
+
+static PySequenceMethods pyrf_thread_map__sequence_methods = {
+ .sq_length = pyrf_thread_map__length,
+ .sq_item = pyrf_thread_map__item,
+};
+
+static char pyrf_thread_map__doc[] = PyDoc_STR("thread map object.");
+
+static PyTypeObject pyrf_thread_map__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.thread_map",
+ .tp_basicsize = sizeof(struct pyrf_thread_map),
+ .tp_dealloc = (destructor)pyrf_thread_map__delete,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = pyrf_thread_map__doc,
+ .tp_as_sequence = &pyrf_thread_map__sequence_methods,
+ .tp_init = (initproc)pyrf_thread_map__init,
+};
+
+static int pyrf_thread_map__setup_types(void)
+{
+ pyrf_thread_map__type.tp_new = PyType_GenericNew;
+ return PyType_Ready(&pyrf_thread_map__type);
+}
+
+struct pyrf_evsel {
+ PyObject_HEAD
+
+ struct perf_evsel evsel;
+};
+
+static int pyrf_evsel__init(struct pyrf_evsel *pevsel,
+ PyObject *args, PyObject *kwargs)
+{
+ struct perf_event_attr attr = {
+ .type = PERF_TYPE_HARDWARE,
+ .config = PERF_COUNT_HW_CPU_CYCLES,
+ .sample_type = PERF_SAMPLE_PERIOD | PERF_SAMPLE_TID,
+ };
+ static char *kwlist[] = {
+ "type",
+ "config",
+ "sample_freq",
+ "sample_period",
+ "sample_type",
+ "read_format",
+ "disabled",
+ "inherit",
+ "pinned",
+ "exclusive",
+ "exclude_user",
+ "exclude_kernel",
+ "exclude_hv",
+ "exclude_idle",
+ "mmap",
+ "comm",
+ "freq",
+ "inherit_stat",
+ "enable_on_exec",
+ "task",
+ "watermark",
+ "precise_ip",
+ "mmap_data",
+ "sample_id_all",
+ "wakeup_events",
+ "bp_type",
+ "bp_addr",
+ "bp_len",
+ NULL
+ };
+ u64 sample_period = 0;
+ u32 disabled = 0,
+ inherit = 0,
+ pinned = 0,
+ exclusive = 0,
+ exclude_user = 0,
+ exclude_kernel = 0,
+ exclude_hv = 0,
+ exclude_idle = 0,
+ mmap = 0,
+ comm = 0,
+ freq = 1,
+ inherit_stat = 0,
+ enable_on_exec = 0,
+ task = 0,
+ watermark = 0,
+ precise_ip = 0,
+ mmap_data = 0,
+ sample_id_all = 1;
+ int idx = 0;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs,
+ "|iKiKKiiiiiiiiiiiiiiiiiiiiiKK", kwlist,
+ &attr.type, &attr.config, &attr.sample_freq,
+ &sample_period, &attr.sample_type,
+ &attr.read_format, &disabled, &inherit,
+ &pinned, &exclusive, &exclude_user,
+ &exclude_kernel, &exclude_hv, &exclude_idle,
+ &mmap, &comm, &freq, &inherit_stat,
+ &enable_on_exec, &task, &watermark,
+ &precise_ip, &mmap_data, &sample_id_all,
+ &attr.wakeup_events, &attr.bp_type,
+ &attr.bp_addr, &attr.bp_len, &idx))
+ return -1;
+
+ /* union... */
+ if (sample_period != 0) {
+ if (attr.sample_freq != 0)
+ return -1; /* FIXME: throw right exception */
+ attr.sample_period = sample_period;
+ }
+
+ /* Bitfields */
+ attr.disabled = disabled;
+ attr.inherit = inherit;
+ attr.pinned = pinned;
+ attr.exclusive = exclusive;
+ attr.exclude_user = exclude_user;
+ attr.exclude_kernel = exclude_kernel;
+ attr.exclude_hv = exclude_hv;
+ attr.exclude_idle = exclude_idle;
+ attr.mmap = mmap;
+ attr.comm = comm;
+ attr.freq = freq;
+ attr.inherit_stat = inherit_stat;
+ attr.enable_on_exec = enable_on_exec;
+ attr.task = task;
+ attr.watermark = watermark;
+ attr.precise_ip = precise_ip;
+ attr.mmap_data = mmap_data;
+ attr.sample_id_all = sample_id_all;
+
+ perf_evsel__init(&pevsel->evsel, &attr, idx);
+ return 0;
+}
+
+static void pyrf_evsel__delete(struct pyrf_evsel *pevsel)
+{
+ perf_evsel__exit(&pevsel->evsel);
+ pevsel->ob_type->tp_free((PyObject*)pevsel);
+}
+
+static PyObject *pyrf_evsel__open(struct pyrf_evsel *pevsel,
+ PyObject *args, PyObject *kwargs)
+{
+ struct perf_evsel *evsel = &pevsel->evsel;
+ struct cpu_map *cpus = NULL;
+ struct thread_map *threads = NULL;
+ PyObject *pcpus = NULL, *pthreads = NULL;
+ int group = 0, inherit = 0;
+ static char *kwlist[] = { "cpus", "threads", "group", "inherit", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOii", kwlist,
+ &pcpus, &pthreads, &group, &inherit))
+ return NULL;
+
+ if (pthreads != NULL)
+ threads = ((struct pyrf_thread_map *)pthreads)->threads;
+
+ if (pcpus != NULL)
+ cpus = ((struct pyrf_cpu_map *)pcpus)->cpus;
+
+ evsel->attr.inherit = inherit;
+ /*
+ * This will group just the fds for this single evsel, to group
+ * multiple events, use evlist.open().
+ */
+ if (perf_evsel__open(evsel, cpus, threads) < 0) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyMethodDef pyrf_evsel__methods[] = {
+ {
+ .ml_name = "open",
+ .ml_meth = (PyCFunction)pyrf_evsel__open,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = PyDoc_STR("open the event selector file descriptor table.")
+ },
+ { .ml_name = NULL, }
+};
+
+static char pyrf_evsel__doc[] = PyDoc_STR("perf event selector list object.");
+
+static PyTypeObject pyrf_evsel__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.evsel",
+ .tp_basicsize = sizeof(struct pyrf_evsel),
+ .tp_dealloc = (destructor)pyrf_evsel__delete,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_doc = pyrf_evsel__doc,
+ .tp_methods = pyrf_evsel__methods,
+ .tp_init = (initproc)pyrf_evsel__init,
+};
+
+static int pyrf_evsel__setup_types(void)
+{
+ pyrf_evsel__type.tp_new = PyType_GenericNew;
+ return PyType_Ready(&pyrf_evsel__type);
+}
+
+struct pyrf_evlist {
+ PyObject_HEAD
+
+ struct perf_evlist evlist;
+};
+
+static int pyrf_evlist__init(struct pyrf_evlist *pevlist,
+ PyObject *args, PyObject *kwargs __maybe_unused)
+{
+ PyObject *pcpus = NULL, *pthreads = NULL;
+ struct cpu_map *cpus;
+ struct thread_map *threads;
+
+ if (!PyArg_ParseTuple(args, "OO", &pcpus, &pthreads))
+ return -1;
+
+ threads = ((struct pyrf_thread_map *)pthreads)->threads;
+ cpus = ((struct pyrf_cpu_map *)pcpus)->cpus;
+ perf_evlist__init(&pevlist->evlist, cpus, threads);
+ return 0;
+}
+
+static void pyrf_evlist__delete(struct pyrf_evlist *pevlist)
+{
+ perf_evlist__exit(&pevlist->evlist);
+ pevlist->ob_type->tp_free((PyObject*)pevlist);
+}
+
+static PyObject *pyrf_evlist__mmap(struct pyrf_evlist *pevlist,
+ PyObject *args, PyObject *kwargs)
+{
+ struct perf_evlist *evlist = &pevlist->evlist;
+ static char *kwlist[] = { "pages", "overwrite", NULL };
+ int pages = 128, overwrite = false;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|ii", kwlist,
+ &pages, &overwrite))
+ return NULL;
+
+ if (perf_evlist__mmap(evlist, pages, overwrite) < 0) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *pyrf_evlist__poll(struct pyrf_evlist *pevlist,
+ PyObject *args, PyObject *kwargs)
+{
+ struct perf_evlist *evlist = &pevlist->evlist;
+ static char *kwlist[] = { "timeout", NULL };
+ int timeout = -1, n;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|i", kwlist, &timeout))
+ return NULL;
+
+ n = poll(evlist->pollfd, evlist->nr_fds, timeout);
+ if (n < 0) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ return Py_BuildValue("i", n);
+}
+
+static PyObject *pyrf_evlist__get_pollfd(struct pyrf_evlist *pevlist,
+ PyObject *args __maybe_unused,
+ PyObject *kwargs __maybe_unused)
+{
+ struct perf_evlist *evlist = &pevlist->evlist;
+ PyObject *list = PyList_New(0);
+ int i;
+
+ for (i = 0; i < evlist->nr_fds; ++i) {
+ PyObject *file;
+ FILE *fp = fdopen(evlist->pollfd[i].fd, "r");
+
+ if (fp == NULL)
+ goto free_list;
+
+ file = PyFile_FromFile(fp, "perf", "r", NULL);
+ if (file == NULL)
+ goto free_list;
+
+ if (PyList_Append(list, file) != 0) {
+ Py_DECREF(file);
+ goto free_list;
+ }
+
+ Py_DECREF(file);
+ }
+
+ return list;
+free_list:
+ return PyErr_NoMemory();
+}
+
+
+static PyObject *pyrf_evlist__add(struct pyrf_evlist *pevlist,
+ PyObject *args,
+ PyObject *kwargs __maybe_unused)
+{
+ struct perf_evlist *evlist = &pevlist->evlist;
+ PyObject *pevsel;
+ struct perf_evsel *evsel;
+
+ if (!PyArg_ParseTuple(args, "O", &pevsel))
+ return NULL;
+
+ Py_INCREF(pevsel);
+ evsel = &((struct pyrf_evsel *)pevsel)->evsel;
+ evsel->idx = evlist->nr_entries;
+ perf_evlist__add(evlist, evsel);
+
+ return Py_BuildValue("i", evlist->nr_entries);
+}
+
+static PyObject *pyrf_evlist__read_on_cpu(struct pyrf_evlist *pevlist,
+ PyObject *args, PyObject *kwargs)
+{
+ struct perf_evlist *evlist = &pevlist->evlist;
+ union perf_event *event;
+ int sample_id_all = 1, cpu;
+ static char *kwlist[] = { "cpu", "sample_id_all", NULL };
+ int err;
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "i|i", kwlist,
+ &cpu, &sample_id_all))
+ return NULL;
+
+ event = perf_evlist__mmap_read(evlist, cpu);
+ if (event != NULL) {
+ PyObject *pyevent = pyrf_event__new(event);
+ struct pyrf_event *pevent = (struct pyrf_event *)pyevent;
+
+ perf_evlist__mmap_consume(evlist, cpu);
+
+ if (pyevent == NULL)
+ return PyErr_NoMemory();
+
+ err = perf_evlist__parse_sample(evlist, event, &pevent->sample);
+ if (err)
+ return PyErr_Format(PyExc_OSError,
+ "perf: can't parse sample, err=%d", err);
+ return pyevent;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyObject *pyrf_evlist__open(struct pyrf_evlist *pevlist,
+ PyObject *args, PyObject *kwargs)
+{
+ struct perf_evlist *evlist = &pevlist->evlist;
+ int group = 0;
+ static char *kwlist[] = { "group", NULL };
+
+ if (!PyArg_ParseTupleAndKeywords(args, kwargs, "|OOii", kwlist, &group))
+ return NULL;
+
+ if (group)
+ perf_evlist__set_leader(evlist);
+
+ if (perf_evlist__open(evlist) < 0) {
+ PyErr_SetFromErrno(PyExc_OSError);
+ return NULL;
+ }
+
+ Py_INCREF(Py_None);
+ return Py_None;
+}
+
+static PyMethodDef pyrf_evlist__methods[] = {
+ {
+ .ml_name = "mmap",
+ .ml_meth = (PyCFunction)pyrf_evlist__mmap,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = PyDoc_STR("mmap the file descriptor table.")
+ },
+ {
+ .ml_name = "open",
+ .ml_meth = (PyCFunction)pyrf_evlist__open,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = PyDoc_STR("open the file descriptors.")
+ },
+ {
+ .ml_name = "poll",
+ .ml_meth = (PyCFunction)pyrf_evlist__poll,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = PyDoc_STR("poll the file descriptor table.")
+ },
+ {
+ .ml_name = "get_pollfd",
+ .ml_meth = (PyCFunction)pyrf_evlist__get_pollfd,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = PyDoc_STR("get the poll file descriptor table.")
+ },
+ {
+ .ml_name = "add",
+ .ml_meth = (PyCFunction)pyrf_evlist__add,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = PyDoc_STR("adds an event selector to the list.")
+ },
+ {
+ .ml_name = "read_on_cpu",
+ .ml_meth = (PyCFunction)pyrf_evlist__read_on_cpu,
+ .ml_flags = METH_VARARGS | METH_KEYWORDS,
+ .ml_doc = PyDoc_STR("reads an event.")
+ },
+ { .ml_name = NULL, }
+};
+
+static Py_ssize_t pyrf_evlist__length(PyObject *obj)
+{
+ struct pyrf_evlist *pevlist = (void *)obj;
+
+ return pevlist->evlist.nr_entries;
+}
+
+static PyObject *pyrf_evlist__item(PyObject *obj, Py_ssize_t i)
+{
+ struct pyrf_evlist *pevlist = (void *)obj;
+ struct perf_evsel *pos;
+
+ if (i >= pevlist->evlist.nr_entries)
+ return NULL;
+
+ evlist__for_each(&pevlist->evlist, pos) {
+ if (i-- == 0)
+ break;
+ }
+
+ return Py_BuildValue("O", container_of(pos, struct pyrf_evsel, evsel));
+}
+
+static PySequenceMethods pyrf_evlist__sequence_methods = {
+ .sq_length = pyrf_evlist__length,
+ .sq_item = pyrf_evlist__item,
+};
+
+static char pyrf_evlist__doc[] = PyDoc_STR("perf event selector list object.");
+
+static PyTypeObject pyrf_evlist__type = {
+ PyVarObject_HEAD_INIT(NULL, 0)
+ .tp_name = "perf.evlist",
+ .tp_basicsize = sizeof(struct pyrf_evlist),
+ .tp_dealloc = (destructor)pyrf_evlist__delete,
+ .tp_flags = Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE,
+ .tp_as_sequence = &pyrf_evlist__sequence_methods,
+ .tp_doc = pyrf_evlist__doc,
+ .tp_methods = pyrf_evlist__methods,
+ .tp_init = (initproc)pyrf_evlist__init,
+};
+
+static int pyrf_evlist__setup_types(void)
+{
+ pyrf_evlist__type.tp_new = PyType_GenericNew;
+ return PyType_Ready(&pyrf_evlist__type);
+}
+
+static struct {
+ const char *name;
+ int value;
+} perf__constants[] = {
+ { "TYPE_HARDWARE", PERF_TYPE_HARDWARE },
+ { "TYPE_SOFTWARE", PERF_TYPE_SOFTWARE },
+ { "TYPE_TRACEPOINT", PERF_TYPE_TRACEPOINT },
+ { "TYPE_HW_CACHE", PERF_TYPE_HW_CACHE },
+ { "TYPE_RAW", PERF_TYPE_RAW },
+ { "TYPE_BREAKPOINT", PERF_TYPE_BREAKPOINT },
+
+ { "COUNT_HW_CPU_CYCLES", PERF_COUNT_HW_CPU_CYCLES },
+ { "COUNT_HW_INSTRUCTIONS", PERF_COUNT_HW_INSTRUCTIONS },
+ { "COUNT_HW_CACHE_REFERENCES", PERF_COUNT_HW_CACHE_REFERENCES },
+ { "COUNT_HW_CACHE_MISSES", PERF_COUNT_HW_CACHE_MISSES },
+ { "COUNT_HW_BRANCH_INSTRUCTIONS", PERF_COUNT_HW_BRANCH_INSTRUCTIONS },
+ { "COUNT_HW_BRANCH_MISSES", PERF_COUNT_HW_BRANCH_MISSES },
+ { "COUNT_HW_BUS_CYCLES", PERF_COUNT_HW_BUS_CYCLES },
+ { "COUNT_HW_CACHE_L1D", PERF_COUNT_HW_CACHE_L1D },
+ { "COUNT_HW_CACHE_L1I", PERF_COUNT_HW_CACHE_L1I },
+ { "COUNT_HW_CACHE_LL", PERF_COUNT_HW_CACHE_LL },
+ { "COUNT_HW_CACHE_DTLB", PERF_COUNT_HW_CACHE_DTLB },
+ { "COUNT_HW_CACHE_ITLB", PERF_COUNT_HW_CACHE_ITLB },
+ { "COUNT_HW_CACHE_BPU", PERF_COUNT_HW_CACHE_BPU },
+ { "COUNT_HW_CACHE_OP_READ", PERF_COUNT_HW_CACHE_OP_READ },
+ { "COUNT_HW_CACHE_OP_WRITE", PERF_COUNT_HW_CACHE_OP_WRITE },
+ { "COUNT_HW_CACHE_OP_PREFETCH", PERF_COUNT_HW_CACHE_OP_PREFETCH },
+ { "COUNT_HW_CACHE_RESULT_ACCESS", PERF_COUNT_HW_CACHE_RESULT_ACCESS },
+ { "COUNT_HW_CACHE_RESULT_MISS", PERF_COUNT_HW_CACHE_RESULT_MISS },
+
+ { "COUNT_HW_STALLED_CYCLES_FRONTEND", PERF_COUNT_HW_STALLED_CYCLES_FRONTEND },
+ { "COUNT_HW_STALLED_CYCLES_BACKEND", PERF_COUNT_HW_STALLED_CYCLES_BACKEND },
+
+ { "COUNT_SW_CPU_CLOCK", PERF_COUNT_SW_CPU_CLOCK },
+ { "COUNT_SW_TASK_CLOCK", PERF_COUNT_SW_TASK_CLOCK },
+ { "COUNT_SW_PAGE_FAULTS", PERF_COUNT_SW_PAGE_FAULTS },
+ { "COUNT_SW_CONTEXT_SWITCHES", PERF_COUNT_SW_CONTEXT_SWITCHES },
+ { "COUNT_SW_CPU_MIGRATIONS", PERF_COUNT_SW_CPU_MIGRATIONS },
+ { "COUNT_SW_PAGE_FAULTS_MIN", PERF_COUNT_SW_PAGE_FAULTS_MIN },
+ { "COUNT_SW_PAGE_FAULTS_MAJ", PERF_COUNT_SW_PAGE_FAULTS_MAJ },
+ { "COUNT_SW_ALIGNMENT_FAULTS", PERF_COUNT_SW_ALIGNMENT_FAULTS },
+ { "COUNT_SW_EMULATION_FAULTS", PERF_COUNT_SW_EMULATION_FAULTS },
+ { "COUNT_SW_DUMMY", PERF_COUNT_SW_DUMMY },
+
+ { "SAMPLE_IP", PERF_SAMPLE_IP },
+ { "SAMPLE_TID", PERF_SAMPLE_TID },
+ { "SAMPLE_TIME", PERF_SAMPLE_TIME },
+ { "SAMPLE_ADDR", PERF_SAMPLE_ADDR },
+ { "SAMPLE_READ", PERF_SAMPLE_READ },
+ { "SAMPLE_CALLCHAIN", PERF_SAMPLE_CALLCHAIN },
+ { "SAMPLE_ID", PERF_SAMPLE_ID },
+ { "SAMPLE_CPU", PERF_SAMPLE_CPU },
+ { "SAMPLE_PERIOD", PERF_SAMPLE_PERIOD },
+ { "SAMPLE_STREAM_ID", PERF_SAMPLE_STREAM_ID },
+ { "SAMPLE_RAW", PERF_SAMPLE_RAW },
+
+ { "FORMAT_TOTAL_TIME_ENABLED", PERF_FORMAT_TOTAL_TIME_ENABLED },
+ { "FORMAT_TOTAL_TIME_RUNNING", PERF_FORMAT_TOTAL_TIME_RUNNING },
+ { "FORMAT_ID", PERF_FORMAT_ID },
+ { "FORMAT_GROUP", PERF_FORMAT_GROUP },
+
+ { "RECORD_MMAP", PERF_RECORD_MMAP },
+ { "RECORD_LOST", PERF_RECORD_LOST },
+ { "RECORD_COMM", PERF_RECORD_COMM },
+ { "RECORD_EXIT", PERF_RECORD_EXIT },
+ { "RECORD_THROTTLE", PERF_RECORD_THROTTLE },
+ { "RECORD_UNTHROTTLE", PERF_RECORD_UNTHROTTLE },
+ { "RECORD_FORK", PERF_RECORD_FORK },
+ { "RECORD_READ", PERF_RECORD_READ },
+ { "RECORD_SAMPLE", PERF_RECORD_SAMPLE },
+ { .name = NULL, },
+};
+
+static PyMethodDef perf__methods[] = {
+ { .ml_name = NULL, }
+};
+
+PyMODINIT_FUNC initperf(void)
+{
+ PyObject *obj;
+ int i;
+ PyObject *dict, *module = Py_InitModule("perf", perf__methods);
+
+ if (module == NULL ||
+ pyrf_event__setup_types() < 0 ||
+ pyrf_evlist__setup_types() < 0 ||
+ pyrf_evsel__setup_types() < 0 ||
+ pyrf_thread_map__setup_types() < 0 ||
+ pyrf_cpu_map__setup_types() < 0)
+ return;
+
+ /* The page_size is placed in util object. */
+ page_size = sysconf(_SC_PAGE_SIZE);
+
+ Py_INCREF(&pyrf_evlist__type);
+ PyModule_AddObject(module, "evlist", (PyObject*)&pyrf_evlist__type);
+
+ Py_INCREF(&pyrf_evsel__type);
+ PyModule_AddObject(module, "evsel", (PyObject*)&pyrf_evsel__type);
+
+ Py_INCREF(&pyrf_thread_map__type);
+ PyModule_AddObject(module, "thread_map", (PyObject*)&pyrf_thread_map__type);
+
+ Py_INCREF(&pyrf_cpu_map__type);
+ PyModule_AddObject(module, "cpu_map", (PyObject*)&pyrf_cpu_map__type);
+
+ dict = PyModule_GetDict(module);
+ if (dict == NULL)
+ goto error;
+
+ for (i = 0; perf__constants[i].name != NULL; i++) {
+ obj = PyInt_FromLong(perf__constants[i].value);
+ if (obj == NULL)
+ goto error;
+ PyDict_SetItemString(dict, perf__constants[i].name, obj);
+ Py_DECREF(obj);
+ }
+
+error:
+ if (PyErr_Occurred())
+ PyErr_SetString(PyExc_ImportError, "perf: Init failed!");
+}
+
+/*
+ * Dummy, to avoid dragging all the test_attr infrastructure in the python
+ * binding.
+ */
+void test_attr__open(struct perf_event_attr *attr, pid_t pid, int cpu,
+ int fd, int group_fd, unsigned long flags)
+{
+}
diff --git a/tools/perf/util/quote.c b/tools/perf/util/quote.c
new file mode 100644
index 00000000000..01f03242b86
--- /dev/null
+++ b/tools/perf/util/quote.c
@@ -0,0 +1,54 @@
+#include "cache.h"
+#include "quote.h"
+
+/* Help to copy the thing properly quoted for the shell safety.
+ * any single quote is replaced with '\'', any exclamation point
+ * is replaced with '\!', and the whole thing is enclosed in a
+ *
+ * E.g.
+ * original sq_quote result
+ * name ==> name ==> 'name'
+ * a b ==> a b ==> 'a b'
+ * a'b ==> a'\''b ==> 'a'\''b'
+ * a!b ==> a'\!'b ==> 'a'\!'b'
+ */
+static inline int need_bs_quote(char c)
+{
+ return (c == '\'' || c == '!');
+}
+
+static void sq_quote_buf(struct strbuf *dst, const char *src)
+{
+ char *to_free = NULL;
+
+ if (dst->buf == src)
+ to_free = strbuf_detach(dst, NULL);
+
+ strbuf_addch(dst, '\'');
+ while (*src) {
+ size_t len = strcspn(src, "'!");
+ strbuf_add(dst, src, len);
+ src += len;
+ while (need_bs_quote(*src)) {
+ strbuf_addstr(dst, "'\\");
+ strbuf_addch(dst, *src++);
+ strbuf_addch(dst, '\'');
+ }
+ }
+ strbuf_addch(dst, '\'');
+ free(to_free);
+}
+
+void sq_quote_argv(struct strbuf *dst, const char** argv, size_t maxlen)
+{
+ int i;
+
+ /* Copy into destination buffer. */
+ strbuf_grow(dst, 255);
+ for (i = 0; argv[i]; ++i) {
+ strbuf_addch(dst, ' ');
+ sq_quote_buf(dst, argv[i]);
+ if (maxlen && dst->len > maxlen)
+ die("Too many or long arguments");
+ }
+}
diff --git a/tools/perf/util/quote.h b/tools/perf/util/quote.h
new file mode 100644
index 00000000000..172889ea234
--- /dev/null
+++ b/tools/perf/util/quote.h
@@ -0,0 +1,29 @@
+#ifndef __PERF_QUOTE_H
+#define __PERF_QUOTE_H
+
+#include <stddef.h>
+#include <stdio.h>
+
+/* Help to copy the thing properly quoted for the shell safety.
+ * any single quote is replaced with '\'', any exclamation point
+ * is replaced with '\!', and the whole thing is enclosed in a
+ * single quote pair.
+ *
+ * For example, if you are passing the result to system() as an
+ * argument:
+ *
+ * sprintf(cmd, "foobar %s %s", sq_quote(arg0), sq_quote(arg1))
+ *
+ * would be appropriate. If the system() is going to call ssh to
+ * run the command on the other side:
+ *
+ * sprintf(cmd, "git-diff-tree %s %s", sq_quote(arg0), sq_quote(arg1));
+ * sprintf(rcmd, "ssh %s %s", sq_util/quote.host), sq_quote(cmd));
+ *
+ * Note that the above examples leak memory! Remember to free result from
+ * sq_quote() in a real application.
+ */
+
+extern void sq_quote_argv(struct strbuf *, const char **argv, size_t maxlen);
+
+#endif /* __PERF_QUOTE_H */
diff --git a/tools/perf/util/rblist.c b/tools/perf/util/rblist.c
new file mode 100644
index 00000000000..0dfe27d9945
--- /dev/null
+++ b/tools/perf/util/rblist.c
@@ -0,0 +1,128 @@
+/*
+ * Based on strlist.c by:
+ * (c) 2009 Arnaldo Carvalho de Melo <acme@redhat.com>
+ *
+ * Licensed under the GPLv2.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "rblist.h"
+
+int rblist__add_node(struct rblist *rblist, const void *new_entry)
+{
+ struct rb_node **p = &rblist->entries.rb_node;
+ struct rb_node *parent = NULL, *new_node;
+
+ while (*p != NULL) {
+ int rc;
+
+ parent = *p;
+
+ rc = rblist->node_cmp(parent, new_entry);
+ if (rc > 0)
+ p = &(*p)->rb_left;
+ else if (rc < 0)
+ p = &(*p)->rb_right;
+ else
+ return -EEXIST;
+ }
+
+ new_node = rblist->node_new(rblist, new_entry);
+ if (new_node == NULL)
+ return -ENOMEM;
+
+ rb_link_node(new_node, parent, p);
+ rb_insert_color(new_node, &rblist->entries);
+ ++rblist->nr_entries;
+
+ return 0;
+}
+
+void rblist__remove_node(struct rblist *rblist, struct rb_node *rb_node)
+{
+ rb_erase(rb_node, &rblist->entries);
+ --rblist->nr_entries;
+ rblist->node_delete(rblist, rb_node);
+}
+
+static struct rb_node *__rblist__findnew(struct rblist *rblist,
+ const void *entry,
+ bool create)
+{
+ struct rb_node **p = &rblist->entries.rb_node;
+ struct rb_node *parent = NULL, *new_node = NULL;
+
+ while (*p != NULL) {
+ int rc;
+
+ parent = *p;
+
+ rc = rblist->node_cmp(parent, entry);
+ if (rc > 0)
+ p = &(*p)->rb_left;
+ else if (rc < 0)
+ p = &(*p)->rb_right;
+ else
+ return parent;
+ }
+
+ if (create) {
+ new_node = rblist->node_new(rblist, entry);
+ if (new_node) {
+ rb_link_node(new_node, parent, p);
+ rb_insert_color(new_node, &rblist->entries);
+ ++rblist->nr_entries;
+ }
+ }
+
+ return new_node;
+}
+
+struct rb_node *rblist__find(struct rblist *rblist, const void *entry)
+{
+ return __rblist__findnew(rblist, entry, false);
+}
+
+struct rb_node *rblist__findnew(struct rblist *rblist, const void *entry)
+{
+ return __rblist__findnew(rblist, entry, true);
+}
+
+void rblist__init(struct rblist *rblist)
+{
+ if (rblist != NULL) {
+ rblist->entries = RB_ROOT;
+ rblist->nr_entries = 0;
+ }
+
+ return;
+}
+
+void rblist__delete(struct rblist *rblist)
+{
+ if (rblist != NULL) {
+ struct rb_node *pos, *next = rb_first(&rblist->entries);
+
+ while (next) {
+ pos = next;
+ next = rb_next(pos);
+ rblist__remove_node(rblist, pos);
+ }
+ free(rblist);
+ }
+}
+
+struct rb_node *rblist__entry(const struct rblist *rblist, unsigned int idx)
+{
+ struct rb_node *node;
+
+ for (node = rb_first(&rblist->entries); node; node = rb_next(node)) {
+ if (!idx--)
+ return node;
+ }
+
+ return NULL;
+}
diff --git a/tools/perf/util/rblist.h b/tools/perf/util/rblist.h
new file mode 100644
index 00000000000..ff9913b994c
--- /dev/null
+++ b/tools/perf/util/rblist.h
@@ -0,0 +1,48 @@
+#ifndef __PERF_RBLIST_H
+#define __PERF_RBLIST_H
+
+#include <linux/rbtree.h>
+#include <stdbool.h>
+
+/*
+ * create node structs of the form:
+ * struct my_node {
+ * struct rb_node rb_node;
+ * ... my data ...
+ * };
+ *
+ * create list structs of the form:
+ * struct mylist {
+ * struct rblist rblist;
+ * ... my data ...
+ * };
+ */
+
+struct rblist {
+ struct rb_root entries;
+ unsigned int nr_entries;
+
+ int (*node_cmp)(struct rb_node *rbn, const void *entry);
+ struct rb_node *(*node_new)(struct rblist *rlist, const void *new_entry);
+ void (*node_delete)(struct rblist *rblist, struct rb_node *rb_node);
+};
+
+void rblist__init(struct rblist *rblist);
+void rblist__delete(struct rblist *rblist);
+int rblist__add_node(struct rblist *rblist, const void *new_entry);
+void rblist__remove_node(struct rblist *rblist, struct rb_node *rb_node);
+struct rb_node *rblist__find(struct rblist *rblist, const void *entry);
+struct rb_node *rblist__findnew(struct rblist *rblist, const void *entry);
+struct rb_node *rblist__entry(const struct rblist *rblist, unsigned int idx);
+
+static inline bool rblist__empty(const struct rblist *rblist)
+{
+ return rblist->nr_entries == 0;
+}
+
+static inline unsigned int rblist__nr_entries(const struct rblist *rblist)
+{
+ return rblist->nr_entries;
+}
+
+#endif /* __PERF_RBLIST_H */
diff --git a/tools/perf/util/record.c b/tools/perf/util/record.c
new file mode 100644
index 00000000000..049e0a09ccd
--- /dev/null
+++ b/tools/perf/util/record.c
@@ -0,0 +1,215 @@
+#include "evlist.h"
+#include "evsel.h"
+#include "cpumap.h"
+#include "parse-events.h"
+#include <api/fs/fs.h>
+#include "util.h"
+
+typedef void (*setup_probe_fn_t)(struct perf_evsel *evsel);
+
+static int perf_do_probe_api(setup_probe_fn_t fn, int cpu, const char *str)
+{
+ struct perf_evlist *evlist;
+ struct perf_evsel *evsel;
+ int err = -EAGAIN, fd;
+
+ evlist = perf_evlist__new();
+ if (!evlist)
+ return -ENOMEM;
+
+ if (parse_events(evlist, str))
+ goto out_delete;
+
+ evsel = perf_evlist__first(evlist);
+
+ fd = sys_perf_event_open(&evsel->attr, -1, cpu, -1, 0);
+ if (fd < 0)
+ goto out_delete;
+ close(fd);
+
+ fn(evsel);
+
+ fd = sys_perf_event_open(&evsel->attr, -1, cpu, -1, 0);
+ if (fd < 0) {
+ if (errno == EINVAL)
+ err = -EINVAL;
+ goto out_delete;
+ }
+ close(fd);
+ err = 0;
+
+out_delete:
+ perf_evlist__delete(evlist);
+ return err;
+}
+
+static bool perf_probe_api(setup_probe_fn_t fn)
+{
+ const char *try[] = {"cycles:u", "instructions:u", "cpu-clock", NULL};
+ struct cpu_map *cpus;
+ int cpu, ret, i = 0;
+
+ cpus = cpu_map__new(NULL);
+ if (!cpus)
+ return false;
+ cpu = cpus->map[0];
+ cpu_map__delete(cpus);
+
+ do {
+ ret = perf_do_probe_api(fn, cpu, try[i++]);
+ if (!ret)
+ return true;
+ } while (ret == -EAGAIN && try[i]);
+
+ return false;
+}
+
+static void perf_probe_sample_identifier(struct perf_evsel *evsel)
+{
+ evsel->attr.sample_type |= PERF_SAMPLE_IDENTIFIER;
+}
+
+bool perf_can_sample_identifier(void)
+{
+ return perf_probe_api(perf_probe_sample_identifier);
+}
+
+void perf_evlist__config(struct perf_evlist *evlist, struct record_opts *opts)
+{
+ struct perf_evsel *evsel;
+ bool use_sample_identifier = false;
+
+ /*
+ * Set the evsel leader links before we configure attributes,
+ * since some might depend on this info.
+ */
+ if (opts->group)
+ perf_evlist__set_leader(evlist);
+
+ if (evlist->cpus->map[0] < 0)
+ opts->no_inherit = true;
+
+ evlist__for_each(evlist, evsel)
+ perf_evsel__config(evsel, opts);
+
+ if (evlist->nr_entries > 1) {
+ struct perf_evsel *first = perf_evlist__first(evlist);
+
+ evlist__for_each(evlist, evsel) {
+ if (evsel->attr.sample_type == first->attr.sample_type)
+ continue;
+ use_sample_identifier = perf_can_sample_identifier();
+ break;
+ }
+ evlist__for_each(evlist, evsel)
+ perf_evsel__set_sample_id(evsel, use_sample_identifier);
+ }
+
+ perf_evlist__set_id_pos(evlist);
+}
+
+static int get_max_rate(unsigned int *rate)
+{
+ char path[PATH_MAX];
+ const char *procfs = procfs__mountpoint();
+
+ if (!procfs)
+ return -1;
+
+ snprintf(path, PATH_MAX,
+ "%s/sys/kernel/perf_event_max_sample_rate", procfs);
+
+ return filename__read_int(path, (int *) rate);
+}
+
+static int record_opts__config_freq(struct record_opts *opts)
+{
+ bool user_freq = opts->user_freq != UINT_MAX;
+ unsigned int max_rate;
+
+ if (opts->user_interval != ULLONG_MAX)
+ opts->default_interval = opts->user_interval;
+ if (user_freq)
+ opts->freq = opts->user_freq;
+
+ /*
+ * User specified count overrides default frequency.
+ */
+ if (opts->default_interval)
+ opts->freq = 0;
+ else if (opts->freq) {
+ opts->default_interval = opts->freq;
+ } else {
+ pr_err("frequency and count are zero, aborting\n");
+ return -1;
+ }
+
+ if (get_max_rate(&max_rate))
+ return 0;
+
+ /*
+ * User specified frequency is over current maximum.
+ */
+ if (user_freq && (max_rate < opts->freq)) {
+ pr_err("Maximum frequency rate (%u) reached.\n"
+ "Please use -F freq option with lower value or consider\n"
+ "tweaking /proc/sys/kernel/perf_event_max_sample_rate.\n",
+ max_rate);
+ return -1;
+ }
+
+ /*
+ * Default frequency is over current maximum.
+ */
+ if (max_rate < opts->freq) {
+ pr_warning("Lowering default frequency rate to %u.\n"
+ "Please consider tweaking "
+ "/proc/sys/kernel/perf_event_max_sample_rate.\n",
+ max_rate);
+ opts->freq = max_rate;
+ }
+
+ return 0;
+}
+
+int record_opts__config(struct record_opts *opts)
+{
+ return record_opts__config_freq(opts);
+}
+
+bool perf_evlist__can_select_event(struct perf_evlist *evlist, const char *str)
+{
+ struct perf_evlist *temp_evlist;
+ struct perf_evsel *evsel;
+ int err, fd, cpu;
+ bool ret = false;
+
+ temp_evlist = perf_evlist__new();
+ if (!temp_evlist)
+ return false;
+
+ err = parse_events(temp_evlist, str);
+ if (err)
+ goto out_delete;
+
+ evsel = perf_evlist__last(temp_evlist);
+
+ if (!evlist || cpu_map__empty(evlist->cpus)) {
+ struct cpu_map *cpus = cpu_map__new(NULL);
+
+ cpu = cpus ? cpus->map[0] : 0;
+ cpu_map__delete(cpus);
+ } else {
+ cpu = evlist->cpus->map[0];
+ }
+
+ fd = sys_perf_event_open(&evsel->attr, -1, cpu, -1, 0);
+ if (fd >= 0) {
+ close(fd);
+ ret = true;
+ }
+
+out_delete:
+ perf_evlist__delete(temp_evlist);
+ return ret;
+}
diff --git a/tools/perf/util/run-command.c b/tools/perf/util/run-command.c
new file mode 100644
index 00000000000..da8e9b285f5
--- /dev/null
+++ b/tools/perf/util/run-command.c
@@ -0,0 +1,214 @@
+#include "cache.h"
+#include "run-command.h"
+#include "exec_cmd.h"
+
+static inline void close_pair(int fd[2])
+{
+ close(fd[0]);
+ close(fd[1]);
+}
+
+static inline void dup_devnull(int to)
+{
+ int fd = open("/dev/null", O_RDWR);
+ dup2(fd, to);
+ close(fd);
+}
+
+int start_command(struct child_process *cmd)
+{
+ int need_in, need_out, need_err;
+ int fdin[2], fdout[2], fderr[2];
+
+ /*
+ * In case of errors we must keep the promise to close FDs
+ * that have been passed in via ->in and ->out.
+ */
+
+ need_in = !cmd->no_stdin && cmd->in < 0;
+ if (need_in) {
+ if (pipe(fdin) < 0) {
+ if (cmd->out > 0)
+ close(cmd->out);
+ return -ERR_RUN_COMMAND_PIPE;
+ }
+ cmd->in = fdin[1];
+ }
+
+ need_out = !cmd->no_stdout
+ && !cmd->stdout_to_stderr
+ && cmd->out < 0;
+ if (need_out) {
+ if (pipe(fdout) < 0) {
+ if (need_in)
+ close_pair(fdin);
+ else if (cmd->in)
+ close(cmd->in);
+ return -ERR_RUN_COMMAND_PIPE;
+ }
+ cmd->out = fdout[0];
+ }
+
+ need_err = !cmd->no_stderr && cmd->err < 0;
+ if (need_err) {
+ if (pipe(fderr) < 0) {
+ if (need_in)
+ close_pair(fdin);
+ else if (cmd->in)
+ close(cmd->in);
+ if (need_out)
+ close_pair(fdout);
+ else if (cmd->out)
+ close(cmd->out);
+ return -ERR_RUN_COMMAND_PIPE;
+ }
+ cmd->err = fderr[0];
+ }
+
+ fflush(NULL);
+ cmd->pid = fork();
+ if (!cmd->pid) {
+ if (cmd->no_stdin)
+ dup_devnull(0);
+ else if (need_in) {
+ dup2(fdin[0], 0);
+ close_pair(fdin);
+ } else if (cmd->in) {
+ dup2(cmd->in, 0);
+ close(cmd->in);
+ }
+
+ if (cmd->no_stderr)
+ dup_devnull(2);
+ else if (need_err) {
+ dup2(fderr[1], 2);
+ close_pair(fderr);
+ }
+
+ if (cmd->no_stdout)
+ dup_devnull(1);
+ else if (cmd->stdout_to_stderr)
+ dup2(2, 1);
+ else if (need_out) {
+ dup2(fdout[1], 1);
+ close_pair(fdout);
+ } else if (cmd->out > 1) {
+ dup2(cmd->out, 1);
+ close(cmd->out);
+ }
+
+ if (cmd->dir && chdir(cmd->dir))
+ die("exec %s: cd to %s failed (%s)", cmd->argv[0],
+ cmd->dir, strerror(errno));
+ if (cmd->env) {
+ for (; *cmd->env; cmd->env++) {
+ if (strchr(*cmd->env, '='))
+ putenv((char*)*cmd->env);
+ else
+ unsetenv(*cmd->env);
+ }
+ }
+ if (cmd->preexec_cb)
+ cmd->preexec_cb();
+ if (cmd->perf_cmd) {
+ execv_perf_cmd(cmd->argv);
+ } else {
+ execvp(cmd->argv[0], (char *const*) cmd->argv);
+ }
+ exit(127);
+ }
+
+ if (cmd->pid < 0) {
+ int err = errno;
+ if (need_in)
+ close_pair(fdin);
+ else if (cmd->in)
+ close(cmd->in);
+ if (need_out)
+ close_pair(fdout);
+ else if (cmd->out)
+ close(cmd->out);
+ if (need_err)
+ close_pair(fderr);
+ return err == ENOENT ?
+ -ERR_RUN_COMMAND_EXEC :
+ -ERR_RUN_COMMAND_FORK;
+ }
+
+ if (need_in)
+ close(fdin[0]);
+ else if (cmd->in)
+ close(cmd->in);
+
+ if (need_out)
+ close(fdout[1]);
+ else if (cmd->out)
+ close(cmd->out);
+
+ if (need_err)
+ close(fderr[1]);
+
+ return 0;
+}
+
+static int wait_or_whine(pid_t pid)
+{
+ for (;;) {
+ int status, code;
+ pid_t waiting = waitpid(pid, &status, 0);
+
+ if (waiting < 0) {
+ if (errno == EINTR)
+ continue;
+ error("waitpid failed (%s)", strerror(errno));
+ return -ERR_RUN_COMMAND_WAITPID;
+ }
+ if (waiting != pid)
+ return -ERR_RUN_COMMAND_WAITPID_WRONG_PID;
+ if (WIFSIGNALED(status))
+ return -ERR_RUN_COMMAND_WAITPID_SIGNAL;
+
+ if (!WIFEXITED(status))
+ return -ERR_RUN_COMMAND_WAITPID_NOEXIT;
+ code = WEXITSTATUS(status);
+ switch (code) {
+ case 127:
+ return -ERR_RUN_COMMAND_EXEC;
+ case 0:
+ return 0;
+ default:
+ return -code;
+ }
+ }
+}
+
+int finish_command(struct child_process *cmd)
+{
+ return wait_or_whine(cmd->pid);
+}
+
+int run_command(struct child_process *cmd)
+{
+ int code = start_command(cmd);
+ if (code)
+ return code;
+ return finish_command(cmd);
+}
+
+static void prepare_run_command_v_opt(struct child_process *cmd,
+ const char **argv,
+ int opt)
+{
+ memset(cmd, 0, sizeof(*cmd));
+ cmd->argv = argv;
+ cmd->no_stdin = opt & RUN_COMMAND_NO_STDIN ? 1 : 0;
+ cmd->perf_cmd = opt & RUN_PERF_CMD ? 1 : 0;
+ cmd->stdout_to_stderr = opt & RUN_COMMAND_STDOUT_TO_STDERR ? 1 : 0;
+}
+
+int run_command_v_opt(const char **argv, int opt)
+{
+ struct child_process cmd;
+ prepare_run_command_v_opt(&cmd, argv, opt);
+ return run_command(&cmd);
+}
diff --git a/tools/perf/util/run-command.h b/tools/perf/util/run-command.h
new file mode 100644
index 00000000000..1ef264d5069
--- /dev/null
+++ b/tools/perf/util/run-command.h
@@ -0,0 +1,58 @@
+#ifndef __PERF_RUN_COMMAND_H
+#define __PERF_RUN_COMMAND_H
+
+enum {
+ ERR_RUN_COMMAND_FORK = 10000,
+ ERR_RUN_COMMAND_EXEC,
+ ERR_RUN_COMMAND_PIPE,
+ ERR_RUN_COMMAND_WAITPID,
+ ERR_RUN_COMMAND_WAITPID_WRONG_PID,
+ ERR_RUN_COMMAND_WAITPID_SIGNAL,
+ ERR_RUN_COMMAND_WAITPID_NOEXIT,
+};
+#define IS_RUN_COMMAND_ERR(x) (-(x) >= ERR_RUN_COMMAND_FORK)
+
+struct child_process {
+ const char **argv;
+ pid_t pid;
+ /*
+ * Using .in, .out, .err:
+ * - Specify 0 for no redirections (child inherits stdin, stdout,
+ * stderr from parent).
+ * - Specify -1 to have a pipe allocated as follows:
+ * .in: returns the writable pipe end; parent writes to it,
+ * the readable pipe end becomes child's stdin
+ * .out, .err: returns the readable pipe end; parent reads from
+ * it, the writable pipe end becomes child's stdout/stderr
+ * The caller of start_command() must close the returned FDs
+ * after it has completed reading from/writing to it!
+ * - Specify > 0 to set a channel to a particular FD as follows:
+ * .in: a readable FD, becomes child's stdin
+ * .out: a writable FD, becomes child's stdout/stderr
+ * .err > 0 not supported
+ * The specified FD is closed by start_command(), even in case
+ * of errors!
+ */
+ int in;
+ int out;
+ int err;
+ const char *dir;
+ const char *const *env;
+ unsigned no_stdin:1;
+ unsigned no_stdout:1;
+ unsigned no_stderr:1;
+ unsigned perf_cmd:1; /* if this is to be perf sub-command */
+ unsigned stdout_to_stderr:1;
+ void (*preexec_cb)(void);
+};
+
+int start_command(struct child_process *);
+int finish_command(struct child_process *);
+int run_command(struct child_process *);
+
+#define RUN_COMMAND_NO_STDIN 1
+#define RUN_PERF_CMD 2 /*If this is to be perf sub-command */
+#define RUN_COMMAND_STDOUT_TO_STDERR 4
+int run_command_v_opt(const char **argv, int opt);
+
+#endif /* __PERF_RUN_COMMAND_H */
diff --git a/tools/perf/util/scripting-engines/trace-event-perl.c b/tools/perf/util/scripting-engines/trace-event-perl.c
new file mode 100644
index 00000000000..af7da565a75
--- /dev/null
+++ b/tools/perf/util/scripting-engines/trace-event-perl.c
@@ -0,0 +1,638 @@
+/*
+ * trace-event-perl. Feed perf script events to an embedded Perl interpreter.
+ *
+ * Copyright (C) 2009 Tom Zanussi <tzanussi@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "../util.h"
+#include <EXTERN.h>
+#include <perl.h>
+
+#include "../../perf.h"
+#include "../thread.h"
+#include "../event.h"
+#include "../trace-event.h"
+#include "../evsel.h"
+
+void boot_Perf__Trace__Context(pTHX_ CV *cv);
+void boot_DynaLoader(pTHX_ CV *cv);
+typedef PerlInterpreter * INTERP;
+
+void xs_init(pTHX);
+
+void xs_init(pTHX)
+{
+ const char *file = __FILE__;
+ dXSUB_SYS;
+
+ newXS("Perf::Trace::Context::bootstrap", boot_Perf__Trace__Context,
+ file);
+ newXS("DynaLoader::boot_DynaLoader", boot_DynaLoader, file);
+}
+
+INTERP my_perl;
+
+#define FTRACE_MAX_EVENT \
+ ((1 << (sizeof(unsigned short) * 8)) - 1)
+
+struct event_format *events[FTRACE_MAX_EVENT];
+
+extern struct scripting_context *scripting_context;
+
+static char *cur_field_name;
+static int zero_flag_atom;
+
+static void define_symbolic_value(const char *ev_name,
+ const char *field_name,
+ const char *field_value,
+ const char *field_str)
+{
+ unsigned long long value;
+ dSP;
+
+ value = eval_flag(field_value);
+
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+
+ XPUSHs(sv_2mortal(newSVpv(ev_name, 0)));
+ XPUSHs(sv_2mortal(newSVpv(field_name, 0)));
+ XPUSHs(sv_2mortal(newSVuv(value)));
+ XPUSHs(sv_2mortal(newSVpv(field_str, 0)));
+
+ PUTBACK;
+ if (get_cv("main::define_symbolic_value", 0))
+ call_pv("main::define_symbolic_value", G_SCALAR);
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+static void define_symbolic_values(struct print_flag_sym *field,
+ const char *ev_name,
+ const char *field_name)
+{
+ define_symbolic_value(ev_name, field_name, field->value, field->str);
+ if (field->next)
+ define_symbolic_values(field->next, ev_name, field_name);
+}
+
+static void define_symbolic_field(const char *ev_name,
+ const char *field_name)
+{
+ dSP;
+
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+
+ XPUSHs(sv_2mortal(newSVpv(ev_name, 0)));
+ XPUSHs(sv_2mortal(newSVpv(field_name, 0)));
+
+ PUTBACK;
+ if (get_cv("main::define_symbolic_field", 0))
+ call_pv("main::define_symbolic_field", G_SCALAR);
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+static void define_flag_value(const char *ev_name,
+ const char *field_name,
+ const char *field_value,
+ const char *field_str)
+{
+ unsigned long long value;
+ dSP;
+
+ value = eval_flag(field_value);
+
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+
+ XPUSHs(sv_2mortal(newSVpv(ev_name, 0)));
+ XPUSHs(sv_2mortal(newSVpv(field_name, 0)));
+ XPUSHs(sv_2mortal(newSVuv(value)));
+ XPUSHs(sv_2mortal(newSVpv(field_str, 0)));
+
+ PUTBACK;
+ if (get_cv("main::define_flag_value", 0))
+ call_pv("main::define_flag_value", G_SCALAR);
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+static void define_flag_values(struct print_flag_sym *field,
+ const char *ev_name,
+ const char *field_name)
+{
+ define_flag_value(ev_name, field_name, field->value, field->str);
+ if (field->next)
+ define_flag_values(field->next, ev_name, field_name);
+}
+
+static void define_flag_field(const char *ev_name,
+ const char *field_name,
+ const char *delim)
+{
+ dSP;
+
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+
+ XPUSHs(sv_2mortal(newSVpv(ev_name, 0)));
+ XPUSHs(sv_2mortal(newSVpv(field_name, 0)));
+ XPUSHs(sv_2mortal(newSVpv(delim, 0)));
+
+ PUTBACK;
+ if (get_cv("main::define_flag_field", 0))
+ call_pv("main::define_flag_field", G_SCALAR);
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+static void define_event_symbols(struct event_format *event,
+ const char *ev_name,
+ struct print_arg *args)
+{
+ switch (args->type) {
+ case PRINT_NULL:
+ break;
+ case PRINT_ATOM:
+ define_flag_value(ev_name, cur_field_name, "0",
+ args->atom.atom);
+ zero_flag_atom = 0;
+ break;
+ case PRINT_FIELD:
+ free(cur_field_name);
+ cur_field_name = strdup(args->field.name);
+ break;
+ case PRINT_FLAGS:
+ define_event_symbols(event, ev_name, args->flags.field);
+ define_flag_field(ev_name, cur_field_name, args->flags.delim);
+ define_flag_values(args->flags.flags, ev_name, cur_field_name);
+ break;
+ case PRINT_SYMBOL:
+ define_event_symbols(event, ev_name, args->symbol.field);
+ define_symbolic_field(ev_name, cur_field_name);
+ define_symbolic_values(args->symbol.symbols, ev_name,
+ cur_field_name);
+ break;
+ case PRINT_HEX:
+ define_event_symbols(event, ev_name, args->hex.field);
+ define_event_symbols(event, ev_name, args->hex.size);
+ break;
+ case PRINT_BSTRING:
+ case PRINT_DYNAMIC_ARRAY:
+ case PRINT_STRING:
+ case PRINT_BITMASK:
+ break;
+ case PRINT_TYPE:
+ define_event_symbols(event, ev_name, args->typecast.item);
+ break;
+ case PRINT_OP:
+ if (strcmp(args->op.op, ":") == 0)
+ zero_flag_atom = 1;
+ define_event_symbols(event, ev_name, args->op.left);
+ define_event_symbols(event, ev_name, args->op.right);
+ break;
+ case PRINT_FUNC:
+ default:
+ pr_err("Unsupported print arg type\n");
+ /* we should warn... */
+ return;
+ }
+
+ if (args->next)
+ define_event_symbols(event, ev_name, args->next);
+}
+
+static inline struct event_format *find_cache_event(struct perf_evsel *evsel)
+{
+ static char ev_name[256];
+ struct event_format *event;
+ int type = evsel->attr.config;
+
+ if (events[type])
+ return events[type];
+
+ events[type] = event = evsel->tp_format;
+ if (!event)
+ return NULL;
+
+ sprintf(ev_name, "%s::%s", event->system, event->name);
+
+ define_event_symbols(event, ev_name, event->print_fmt.args);
+
+ return event;
+}
+
+static void perl_process_tracepoint(struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct thread *thread)
+{
+ struct format_field *field;
+ static char handler[256];
+ unsigned long long val;
+ unsigned long s, ns;
+ struct event_format *event;
+ int pid;
+ int cpu = sample->cpu;
+ void *data = sample->raw_data;
+ unsigned long long nsecs = sample->time;
+ const char *comm = thread__comm_str(thread);
+
+ dSP;
+
+ if (evsel->attr.type != PERF_TYPE_TRACEPOINT)
+ return;
+
+ event = find_cache_event(evsel);
+ if (!event)
+ die("ug! no event found for type %" PRIu64, (u64)evsel->attr.config);
+
+ pid = raw_field_value(event, "common_pid", data);
+
+ sprintf(handler, "%s::%s", event->system, event->name);
+
+ s = nsecs / NSECS_PER_SEC;
+ ns = nsecs - s * NSECS_PER_SEC;
+
+ scripting_context->event_data = data;
+ scripting_context->pevent = evsel->tp_format->pevent;
+
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+
+ XPUSHs(sv_2mortal(newSVpv(handler, 0)));
+ XPUSHs(sv_2mortal(newSViv(PTR2IV(scripting_context))));
+ XPUSHs(sv_2mortal(newSVuv(cpu)));
+ XPUSHs(sv_2mortal(newSVuv(s)));
+ XPUSHs(sv_2mortal(newSVuv(ns)));
+ XPUSHs(sv_2mortal(newSViv(pid)));
+ XPUSHs(sv_2mortal(newSVpv(comm, 0)));
+
+ /* common fields other than pid can be accessed via xsub fns */
+
+ for (field = event->format.fields; field; field = field->next) {
+ if (field->flags & FIELD_IS_STRING) {
+ int offset;
+ if (field->flags & FIELD_IS_DYNAMIC) {
+ offset = *(int *)(data + field->offset);
+ offset &= 0xffff;
+ } else
+ offset = field->offset;
+ XPUSHs(sv_2mortal(newSVpv((char *)data + offset, 0)));
+ } else { /* FIELD_IS_NUMERIC */
+ val = read_size(event, data + field->offset,
+ field->size);
+ if (field->flags & FIELD_IS_SIGNED) {
+ XPUSHs(sv_2mortal(newSViv(val)));
+ } else {
+ XPUSHs(sv_2mortal(newSVuv(val)));
+ }
+ }
+ }
+
+ PUTBACK;
+
+ if (get_cv(handler, 0))
+ call_pv(handler, G_SCALAR);
+ else if (get_cv("main::trace_unhandled", 0)) {
+ XPUSHs(sv_2mortal(newSVpv(handler, 0)));
+ XPUSHs(sv_2mortal(newSViv(PTR2IV(scripting_context))));
+ XPUSHs(sv_2mortal(newSVuv(cpu)));
+ XPUSHs(sv_2mortal(newSVuv(nsecs)));
+ XPUSHs(sv_2mortal(newSViv(pid)));
+ XPUSHs(sv_2mortal(newSVpv(comm, 0)));
+ call_pv("main::trace_unhandled", G_SCALAR);
+ }
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+static void perl_process_event_generic(union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel)
+{
+ dSP;
+
+ if (!get_cv("process_event", 0))
+ return;
+
+ ENTER;
+ SAVETMPS;
+ PUSHMARK(SP);
+ XPUSHs(sv_2mortal(newSVpvn((const char *)event, event->header.size)));
+ XPUSHs(sv_2mortal(newSVpvn((const char *)&evsel->attr, sizeof(evsel->attr))));
+ XPUSHs(sv_2mortal(newSVpvn((const char *)sample, sizeof(*sample))));
+ XPUSHs(sv_2mortal(newSVpvn((const char *)sample->raw_data, sample->raw_size)));
+ PUTBACK;
+ call_pv("process_event", G_SCALAR);
+ SPAGAIN;
+ PUTBACK;
+ FREETMPS;
+ LEAVE;
+}
+
+static void perl_process_event(union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct thread *thread,
+ struct addr_location *al __maybe_unused)
+{
+ perl_process_tracepoint(sample, evsel, thread);
+ perl_process_event_generic(event, sample, evsel);
+}
+
+static void run_start_sub(void)
+{
+ dSP; /* access to Perl stack */
+ PUSHMARK(SP);
+
+ if (get_cv("main::trace_begin", 0))
+ call_pv("main::trace_begin", G_DISCARD | G_NOARGS);
+}
+
+/*
+ * Start trace script
+ */
+static int perl_start_script(const char *script, int argc, const char **argv)
+{
+ const char **command_line;
+ int i, err = 0;
+
+ command_line = malloc((argc + 2) * sizeof(const char *));
+ command_line[0] = "";
+ command_line[1] = script;
+ for (i = 2; i < argc + 2; i++)
+ command_line[i] = argv[i - 2];
+
+ my_perl = perl_alloc();
+ perl_construct(my_perl);
+
+ if (perl_parse(my_perl, xs_init, argc + 2, (char **)command_line,
+ (char **)NULL)) {
+ err = -1;
+ goto error;
+ }
+
+ if (perl_run(my_perl)) {
+ err = -1;
+ goto error;
+ }
+
+ if (SvTRUE(ERRSV)) {
+ err = -1;
+ goto error;
+ }
+
+ run_start_sub();
+
+ free(command_line);
+ return 0;
+error:
+ perl_free(my_perl);
+ free(command_line);
+
+ return err;
+}
+
+/*
+ * Stop trace script
+ */
+static int perl_stop_script(void)
+{
+ dSP; /* access to Perl stack */
+ PUSHMARK(SP);
+
+ if (get_cv("main::trace_end", 0))
+ call_pv("main::trace_end", G_DISCARD | G_NOARGS);
+
+ perl_destruct(my_perl);
+ perl_free(my_perl);
+
+ return 0;
+}
+
+static int perl_generate_script(struct pevent *pevent, const char *outfile)
+{
+ struct event_format *event = NULL;
+ struct format_field *f;
+ char fname[PATH_MAX];
+ int not_first, count;
+ FILE *ofp;
+
+ sprintf(fname, "%s.pl", outfile);
+ ofp = fopen(fname, "w");
+ if (ofp == NULL) {
+ fprintf(stderr, "couldn't open %s\n", fname);
+ return -1;
+ }
+
+ fprintf(ofp, "# perf script event handlers, "
+ "generated by perf script -g perl\n");
+
+ fprintf(ofp, "# Licensed under the terms of the GNU GPL"
+ " License version 2\n\n");
+
+ fprintf(ofp, "# The common_* event handler fields are the most useful "
+ "fields common to\n");
+
+ fprintf(ofp, "# all events. They don't necessarily correspond to "
+ "the 'common_*' fields\n");
+
+ fprintf(ofp, "# in the format files. Those fields not available as "
+ "handler params can\n");
+
+ fprintf(ofp, "# be retrieved using Perl functions of the form "
+ "common_*($context).\n");
+
+ fprintf(ofp, "# See Context.pm for the list of available "
+ "functions.\n\n");
+
+ fprintf(ofp, "use lib \"$ENV{'PERF_EXEC_PATH'}/scripts/perl/"
+ "Perf-Trace-Util/lib\";\n");
+
+ fprintf(ofp, "use lib \"./Perf-Trace-Util/lib\";\n");
+ fprintf(ofp, "use Perf::Trace::Core;\n");
+ fprintf(ofp, "use Perf::Trace::Context;\n");
+ fprintf(ofp, "use Perf::Trace::Util;\n\n");
+
+ fprintf(ofp, "sub trace_begin\n{\n\t# optional\n}\n\n");
+ fprintf(ofp, "sub trace_end\n{\n\t# optional\n}\n\n");
+
+ while ((event = trace_find_next_event(pevent, event))) {
+ fprintf(ofp, "sub %s::%s\n{\n", event->system, event->name);
+ fprintf(ofp, "\tmy (");
+
+ fprintf(ofp, "$event_name, ");
+ fprintf(ofp, "$context, ");
+ fprintf(ofp, "$common_cpu, ");
+ fprintf(ofp, "$common_secs, ");
+ fprintf(ofp, "$common_nsecs,\n");
+ fprintf(ofp, "\t $common_pid, ");
+ fprintf(ofp, "$common_comm,\n\t ");
+
+ not_first = 0;
+ count = 0;
+
+ for (f = event->format.fields; f; f = f->next) {
+ if (not_first++)
+ fprintf(ofp, ", ");
+ if (++count % 5 == 0)
+ fprintf(ofp, "\n\t ");
+
+ fprintf(ofp, "$%s", f->name);
+ }
+ fprintf(ofp, ") = @_;\n\n");
+
+ fprintf(ofp, "\tprint_header($event_name, $common_cpu, "
+ "$common_secs, $common_nsecs,\n\t "
+ "$common_pid, $common_comm);\n\n");
+
+ fprintf(ofp, "\tprintf(\"");
+
+ not_first = 0;
+ count = 0;
+
+ for (f = event->format.fields; f; f = f->next) {
+ if (not_first++)
+ fprintf(ofp, ", ");
+ if (count && count % 4 == 0) {
+ fprintf(ofp, "\".\n\t \"");
+ }
+ count++;
+
+ fprintf(ofp, "%s=", f->name);
+ if (f->flags & FIELD_IS_STRING ||
+ f->flags & FIELD_IS_FLAG ||
+ f->flags & FIELD_IS_SYMBOLIC)
+ fprintf(ofp, "%%s");
+ else if (f->flags & FIELD_IS_SIGNED)
+ fprintf(ofp, "%%d");
+ else
+ fprintf(ofp, "%%u");
+ }
+
+ fprintf(ofp, "\\n\",\n\t ");
+
+ not_first = 0;
+ count = 0;
+
+ for (f = event->format.fields; f; f = f->next) {
+ if (not_first++)
+ fprintf(ofp, ", ");
+
+ if (++count % 5 == 0)
+ fprintf(ofp, "\n\t ");
+
+ if (f->flags & FIELD_IS_FLAG) {
+ if ((count - 1) % 5 != 0) {
+ fprintf(ofp, "\n\t ");
+ count = 4;
+ }
+ fprintf(ofp, "flag_str(\"");
+ fprintf(ofp, "%s::%s\", ", event->system,
+ event->name);
+ fprintf(ofp, "\"%s\", $%s)", f->name,
+ f->name);
+ } else if (f->flags & FIELD_IS_SYMBOLIC) {
+ if ((count - 1) % 5 != 0) {
+ fprintf(ofp, "\n\t ");
+ count = 4;
+ }
+ fprintf(ofp, "symbol_str(\"");
+ fprintf(ofp, "%s::%s\", ", event->system,
+ event->name);
+ fprintf(ofp, "\"%s\", $%s)", f->name,
+ f->name);
+ } else
+ fprintf(ofp, "$%s", f->name);
+ }
+
+ fprintf(ofp, ");\n");
+ fprintf(ofp, "}\n\n");
+ }
+
+ fprintf(ofp, "sub trace_unhandled\n{\n\tmy ($event_name, $context, "
+ "$common_cpu, $common_secs, $common_nsecs,\n\t "
+ "$common_pid, $common_comm) = @_;\n\n");
+
+ fprintf(ofp, "\tprint_header($event_name, $common_cpu, "
+ "$common_secs, $common_nsecs,\n\t $common_pid, "
+ "$common_comm);\n}\n\n");
+
+ fprintf(ofp, "sub print_header\n{\n"
+ "\tmy ($event_name, $cpu, $secs, $nsecs, $pid, $comm) = @_;\n\n"
+ "\tprintf(\"%%-20s %%5u %%05u.%%09u %%8u %%-20s \",\n\t "
+ "$event_name, $cpu, $secs, $nsecs, $pid, $comm);\n}\n");
+
+ fprintf(ofp,
+ "\n# Packed byte string args of process_event():\n"
+ "#\n"
+ "# $event:\tunion perf_event\tutil/event.h\n"
+ "# $attr:\tstruct perf_event_attr\tlinux/perf_event.h\n"
+ "# $sample:\tstruct perf_sample\tutil/event.h\n"
+ "# $raw_data:\tperf_sample->raw_data\tutil/event.h\n"
+ "\n"
+ "sub process_event\n"
+ "{\n"
+ "\tmy ($event, $attr, $sample, $raw_data) = @_;\n"
+ "\n"
+ "\tmy @event\t= unpack(\"LSS\", $event);\n"
+ "\tmy @attr\t= unpack(\"LLQQQQQLLQQ\", $attr);\n"
+ "\tmy @sample\t= unpack(\"QLLQQQQQLL\", $sample);\n"
+ "\tmy @raw_data\t= unpack(\"C*\", $raw_data);\n"
+ "\n"
+ "\tuse Data::Dumper;\n"
+ "\tprint Dumper \\@event, \\@attr, \\@sample, \\@raw_data;\n"
+ "}\n");
+
+ fclose(ofp);
+
+ fprintf(stderr, "generated Perl script: %s\n", fname);
+
+ return 0;
+}
+
+struct scripting_ops perl_scripting_ops = {
+ .name = "Perl",
+ .start_script = perl_start_script,
+ .stop_script = perl_stop_script,
+ .process_event = perl_process_event,
+ .generate_script = perl_generate_script,
+};
diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c
new file mode 100644
index 00000000000..1c419321f70
--- /dev/null
+++ b/tools/perf/util/scripting-engines/trace-event-python.c
@@ -0,0 +1,698 @@
+/*
+ * trace-event-python. Feed trace events to an embedded Python interpreter.
+ *
+ * Copyright (C) 2010 Tom Zanussi <tzanussi@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <Python.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "../../perf.h"
+#include "../evsel.h"
+#include "../util.h"
+#include "../event.h"
+#include "../thread.h"
+#include "../trace-event.h"
+
+PyMODINIT_FUNC initperf_trace_context(void);
+
+#define FTRACE_MAX_EVENT \
+ ((1 << (sizeof(unsigned short) * 8)) - 1)
+
+struct event_format *events[FTRACE_MAX_EVENT];
+
+#define MAX_FIELDS 64
+#define N_COMMON_FIELDS 7
+
+extern struct scripting_context *scripting_context;
+
+static char *cur_field_name;
+static int zero_flag_atom;
+
+static PyObject *main_module, *main_dict;
+
+static void handler_call_die(const char *handler_name)
+{
+ PyErr_Print();
+ Py_FatalError("problem in Python trace event handler");
+}
+
+/*
+ * Insert val into into the dictionary and decrement the reference counter.
+ * This is necessary for dictionaries since PyDict_SetItemString() does not
+ * steal a reference, as opposed to PyTuple_SetItem().
+ */
+static void pydict_set_item_string_decref(PyObject *dict, const char *key, PyObject *val)
+{
+ PyDict_SetItemString(dict, key, val);
+ Py_DECREF(val);
+}
+
+static void define_value(enum print_arg_type field_type,
+ const char *ev_name,
+ const char *field_name,
+ const char *field_value,
+ const char *field_str)
+{
+ const char *handler_name = "define_flag_value";
+ PyObject *handler, *t, *retval;
+ unsigned long long value;
+ unsigned n = 0;
+
+ if (field_type == PRINT_SYMBOL)
+ handler_name = "define_symbolic_value";
+
+ t = PyTuple_New(4);
+ if (!t)
+ Py_FatalError("couldn't create Python tuple");
+
+ value = eval_flag(field_value);
+
+ PyTuple_SetItem(t, n++, PyString_FromString(ev_name));
+ PyTuple_SetItem(t, n++, PyString_FromString(field_name));
+ PyTuple_SetItem(t, n++, PyInt_FromLong(value));
+ PyTuple_SetItem(t, n++, PyString_FromString(field_str));
+
+ handler = PyDict_GetItemString(main_dict, handler_name);
+ if (handler && PyCallable_Check(handler)) {
+ retval = PyObject_CallObject(handler, t);
+ if (retval == NULL)
+ handler_call_die(handler_name);
+ }
+
+ Py_DECREF(t);
+}
+
+static void define_values(enum print_arg_type field_type,
+ struct print_flag_sym *field,
+ const char *ev_name,
+ const char *field_name)
+{
+ define_value(field_type, ev_name, field_name, field->value,
+ field->str);
+
+ if (field->next)
+ define_values(field_type, field->next, ev_name, field_name);
+}
+
+static void define_field(enum print_arg_type field_type,
+ const char *ev_name,
+ const char *field_name,
+ const char *delim)
+{
+ const char *handler_name = "define_flag_field";
+ PyObject *handler, *t, *retval;
+ unsigned n = 0;
+
+ if (field_type == PRINT_SYMBOL)
+ handler_name = "define_symbolic_field";
+
+ if (field_type == PRINT_FLAGS)
+ t = PyTuple_New(3);
+ else
+ t = PyTuple_New(2);
+ if (!t)
+ Py_FatalError("couldn't create Python tuple");
+
+ PyTuple_SetItem(t, n++, PyString_FromString(ev_name));
+ PyTuple_SetItem(t, n++, PyString_FromString(field_name));
+ if (field_type == PRINT_FLAGS)
+ PyTuple_SetItem(t, n++, PyString_FromString(delim));
+
+ handler = PyDict_GetItemString(main_dict, handler_name);
+ if (handler && PyCallable_Check(handler)) {
+ retval = PyObject_CallObject(handler, t);
+ if (retval == NULL)
+ handler_call_die(handler_name);
+ }
+
+ Py_DECREF(t);
+}
+
+static void define_event_symbols(struct event_format *event,
+ const char *ev_name,
+ struct print_arg *args)
+{
+ switch (args->type) {
+ case PRINT_NULL:
+ break;
+ case PRINT_ATOM:
+ define_value(PRINT_FLAGS, ev_name, cur_field_name, "0",
+ args->atom.atom);
+ zero_flag_atom = 0;
+ break;
+ case PRINT_FIELD:
+ free(cur_field_name);
+ cur_field_name = strdup(args->field.name);
+ break;
+ case PRINT_FLAGS:
+ define_event_symbols(event, ev_name, args->flags.field);
+ define_field(PRINT_FLAGS, ev_name, cur_field_name,
+ args->flags.delim);
+ define_values(PRINT_FLAGS, args->flags.flags, ev_name,
+ cur_field_name);
+ break;
+ case PRINT_SYMBOL:
+ define_event_symbols(event, ev_name, args->symbol.field);
+ define_field(PRINT_SYMBOL, ev_name, cur_field_name, NULL);
+ define_values(PRINT_SYMBOL, args->symbol.symbols, ev_name,
+ cur_field_name);
+ break;
+ case PRINT_HEX:
+ define_event_symbols(event, ev_name, args->hex.field);
+ define_event_symbols(event, ev_name, args->hex.size);
+ break;
+ case PRINT_STRING:
+ break;
+ case PRINT_TYPE:
+ define_event_symbols(event, ev_name, args->typecast.item);
+ break;
+ case PRINT_OP:
+ if (strcmp(args->op.op, ":") == 0)
+ zero_flag_atom = 1;
+ define_event_symbols(event, ev_name, args->op.left);
+ define_event_symbols(event, ev_name, args->op.right);
+ break;
+ default:
+ /* gcc warns for these? */
+ case PRINT_BSTRING:
+ case PRINT_DYNAMIC_ARRAY:
+ case PRINT_FUNC:
+ case PRINT_BITMASK:
+ /* we should warn... */
+ return;
+ }
+
+ if (args->next)
+ define_event_symbols(event, ev_name, args->next);
+}
+
+static inline struct event_format *find_cache_event(struct perf_evsel *evsel)
+{
+ static char ev_name[256];
+ struct event_format *event;
+ int type = evsel->attr.config;
+
+ /*
+ * XXX: Do we really need to cache this since now we have evsel->tp_format
+ * cached already? Need to re-read this "cache" routine that as well calls
+ * define_event_symbols() :-\
+ */
+ if (events[type])
+ return events[type];
+
+ events[type] = event = evsel->tp_format;
+ if (!event)
+ return NULL;
+
+ sprintf(ev_name, "%s__%s", event->system, event->name);
+
+ define_event_symbols(event, ev_name, event->print_fmt.args);
+
+ return event;
+}
+
+static void python_process_tracepoint(struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct thread *thread,
+ struct addr_location *al)
+{
+ PyObject *handler, *retval, *context, *t, *obj, *dict = NULL;
+ static char handler_name[256];
+ struct format_field *field;
+ unsigned long long val;
+ unsigned long s, ns;
+ struct event_format *event;
+ unsigned n = 0;
+ int pid;
+ int cpu = sample->cpu;
+ void *data = sample->raw_data;
+ unsigned long long nsecs = sample->time;
+ const char *comm = thread__comm_str(thread);
+
+ t = PyTuple_New(MAX_FIELDS);
+ if (!t)
+ Py_FatalError("couldn't create Python tuple");
+
+ event = find_cache_event(evsel);
+ if (!event)
+ die("ug! no event found for type %d", (int)evsel->attr.config);
+
+ pid = raw_field_value(event, "common_pid", data);
+
+ sprintf(handler_name, "%s__%s", event->system, event->name);
+
+ handler = PyDict_GetItemString(main_dict, handler_name);
+ if (handler && !PyCallable_Check(handler))
+ handler = NULL;
+ if (!handler) {
+ dict = PyDict_New();
+ if (!dict)
+ Py_FatalError("couldn't create Python dict");
+ }
+ s = nsecs / NSECS_PER_SEC;
+ ns = nsecs - s * NSECS_PER_SEC;
+
+ scripting_context->event_data = data;
+ scripting_context->pevent = evsel->tp_format->pevent;
+
+ context = PyCObject_FromVoidPtr(scripting_context, NULL);
+
+ PyTuple_SetItem(t, n++, PyString_FromString(handler_name));
+ PyTuple_SetItem(t, n++, context);
+
+ if (handler) {
+ PyTuple_SetItem(t, n++, PyInt_FromLong(cpu));
+ PyTuple_SetItem(t, n++, PyInt_FromLong(s));
+ PyTuple_SetItem(t, n++, PyInt_FromLong(ns));
+ PyTuple_SetItem(t, n++, PyInt_FromLong(pid));
+ PyTuple_SetItem(t, n++, PyString_FromString(comm));
+ } else {
+ pydict_set_item_string_decref(dict, "common_cpu", PyInt_FromLong(cpu));
+ pydict_set_item_string_decref(dict, "common_s", PyInt_FromLong(s));
+ pydict_set_item_string_decref(dict, "common_ns", PyInt_FromLong(ns));
+ pydict_set_item_string_decref(dict, "common_pid", PyInt_FromLong(pid));
+ pydict_set_item_string_decref(dict, "common_comm", PyString_FromString(comm));
+ }
+ for (field = event->format.fields; field; field = field->next) {
+ if (field->flags & FIELD_IS_STRING) {
+ int offset;
+ if (field->flags & FIELD_IS_DYNAMIC) {
+ offset = *(int *)(data + field->offset);
+ offset &= 0xffff;
+ } else
+ offset = field->offset;
+ obj = PyString_FromString((char *)data + offset);
+ } else { /* FIELD_IS_NUMERIC */
+ val = read_size(event, data + field->offset,
+ field->size);
+ if (field->flags & FIELD_IS_SIGNED) {
+ if ((long long)val >= LONG_MIN &&
+ (long long)val <= LONG_MAX)
+ obj = PyInt_FromLong(val);
+ else
+ obj = PyLong_FromLongLong(val);
+ } else {
+ if (val <= LONG_MAX)
+ obj = PyInt_FromLong(val);
+ else
+ obj = PyLong_FromUnsignedLongLong(val);
+ }
+ }
+ if (handler)
+ PyTuple_SetItem(t, n++, obj);
+ else
+ pydict_set_item_string_decref(dict, field->name, obj);
+
+ }
+ if (!handler)
+ PyTuple_SetItem(t, n++, dict);
+
+ if (_PyTuple_Resize(&t, n) == -1)
+ Py_FatalError("error resizing Python tuple");
+
+ if (handler) {
+ retval = PyObject_CallObject(handler, t);
+ if (retval == NULL)
+ handler_call_die(handler_name);
+ } else {
+ handler = PyDict_GetItemString(main_dict, "trace_unhandled");
+ if (handler && PyCallable_Check(handler)) {
+
+ retval = PyObject_CallObject(handler, t);
+ if (retval == NULL)
+ handler_call_die("trace_unhandled");
+ }
+ Py_DECREF(dict);
+ }
+
+ Py_DECREF(t);
+}
+
+static void python_process_general_event(struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct thread *thread,
+ struct addr_location *al)
+{
+ PyObject *handler, *retval, *t, *dict;
+ static char handler_name[64];
+ unsigned n = 0;
+
+ /*
+ * Use the MAX_FIELDS to make the function expandable, though
+ * currently there is only one item for the tuple.
+ */
+ t = PyTuple_New(MAX_FIELDS);
+ if (!t)
+ Py_FatalError("couldn't create Python tuple");
+
+ dict = PyDict_New();
+ if (!dict)
+ Py_FatalError("couldn't create Python dictionary");
+
+ snprintf(handler_name, sizeof(handler_name), "%s", "process_event");
+
+ handler = PyDict_GetItemString(main_dict, handler_name);
+ if (!handler || !PyCallable_Check(handler))
+ goto exit;
+
+ pydict_set_item_string_decref(dict, "ev_name", PyString_FromString(perf_evsel__name(evsel)));
+ pydict_set_item_string_decref(dict, "attr", PyString_FromStringAndSize(
+ (const char *)&evsel->attr, sizeof(evsel->attr)));
+ pydict_set_item_string_decref(dict, "sample", PyString_FromStringAndSize(
+ (const char *)sample, sizeof(*sample)));
+ pydict_set_item_string_decref(dict, "raw_buf", PyString_FromStringAndSize(
+ (const char *)sample->raw_data, sample->raw_size));
+ pydict_set_item_string_decref(dict, "comm",
+ PyString_FromString(thread__comm_str(thread)));
+ if (al->map) {
+ pydict_set_item_string_decref(dict, "dso",
+ PyString_FromString(al->map->dso->name));
+ }
+ if (al->sym) {
+ pydict_set_item_string_decref(dict, "symbol",
+ PyString_FromString(al->sym->name));
+ }
+
+ PyTuple_SetItem(t, n++, dict);
+ if (_PyTuple_Resize(&t, n) == -1)
+ Py_FatalError("error resizing Python tuple");
+
+ retval = PyObject_CallObject(handler, t);
+ if (retval == NULL)
+ handler_call_die(handler_name);
+exit:
+ Py_DECREF(dict);
+ Py_DECREF(t);
+}
+
+static void python_process_event(union perf_event *event __maybe_unused,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct thread *thread,
+ struct addr_location *al)
+{
+ switch (evsel->attr.type) {
+ case PERF_TYPE_TRACEPOINT:
+ python_process_tracepoint(sample, evsel, thread, al);
+ break;
+ /* Reserve for future process_hw/sw/raw APIs */
+ default:
+ python_process_general_event(sample, evsel, thread, al);
+ }
+}
+
+static int run_start_sub(void)
+{
+ PyObject *handler, *retval;
+ int err = 0;
+
+ main_module = PyImport_AddModule("__main__");
+ if (main_module == NULL)
+ return -1;
+ Py_INCREF(main_module);
+
+ main_dict = PyModule_GetDict(main_module);
+ if (main_dict == NULL) {
+ err = -1;
+ goto error;
+ }
+ Py_INCREF(main_dict);
+
+ handler = PyDict_GetItemString(main_dict, "trace_begin");
+ if (handler == NULL || !PyCallable_Check(handler))
+ goto out;
+
+ retval = PyObject_CallObject(handler, NULL);
+ if (retval == NULL)
+ handler_call_die("trace_begin");
+
+ Py_DECREF(retval);
+ return err;
+error:
+ Py_XDECREF(main_dict);
+ Py_XDECREF(main_module);
+out:
+ return err;
+}
+
+/*
+ * Start trace script
+ */
+static int python_start_script(const char *script, int argc, const char **argv)
+{
+ const char **command_line;
+ char buf[PATH_MAX];
+ int i, err = 0;
+ FILE *fp;
+
+ command_line = malloc((argc + 1) * sizeof(const char *));
+ command_line[0] = script;
+ for (i = 1; i < argc + 1; i++)
+ command_line[i] = argv[i - 1];
+
+ Py_Initialize();
+
+ initperf_trace_context();
+
+ PySys_SetArgv(argc + 1, (char **)command_line);
+
+ fp = fopen(script, "r");
+ if (!fp) {
+ sprintf(buf, "Can't open python script \"%s\"", script);
+ perror(buf);
+ err = -1;
+ goto error;
+ }
+
+ err = PyRun_SimpleFile(fp, script);
+ if (err) {
+ fprintf(stderr, "Error running python script %s\n", script);
+ goto error;
+ }
+
+ err = run_start_sub();
+ if (err) {
+ fprintf(stderr, "Error starting python script %s\n", script);
+ goto error;
+ }
+
+ free(command_line);
+
+ return err;
+error:
+ Py_Finalize();
+ free(command_line);
+
+ return err;
+}
+
+/*
+ * Stop trace script
+ */
+static int python_stop_script(void)
+{
+ PyObject *handler, *retval;
+ int err = 0;
+
+ handler = PyDict_GetItemString(main_dict, "trace_end");
+ if (handler == NULL || !PyCallable_Check(handler))
+ goto out;
+
+ retval = PyObject_CallObject(handler, NULL);
+ if (retval == NULL)
+ handler_call_die("trace_end");
+ else
+ Py_DECREF(retval);
+out:
+ Py_XDECREF(main_dict);
+ Py_XDECREF(main_module);
+ Py_Finalize();
+
+ return err;
+}
+
+static int python_generate_script(struct pevent *pevent, const char *outfile)
+{
+ struct event_format *event = NULL;
+ struct format_field *f;
+ char fname[PATH_MAX];
+ int not_first, count;
+ FILE *ofp;
+
+ sprintf(fname, "%s.py", outfile);
+ ofp = fopen(fname, "w");
+ if (ofp == NULL) {
+ fprintf(stderr, "couldn't open %s\n", fname);
+ return -1;
+ }
+ fprintf(ofp, "# perf script event handlers, "
+ "generated by perf script -g python\n");
+
+ fprintf(ofp, "# Licensed under the terms of the GNU GPL"
+ " License version 2\n\n");
+
+ fprintf(ofp, "# The common_* event handler fields are the most useful "
+ "fields common to\n");
+
+ fprintf(ofp, "# all events. They don't necessarily correspond to "
+ "the 'common_*' fields\n");
+
+ fprintf(ofp, "# in the format files. Those fields not available as "
+ "handler params can\n");
+
+ fprintf(ofp, "# be retrieved using Python functions of the form "
+ "common_*(context).\n");
+
+ fprintf(ofp, "# See the perf-trace-python Documentation for the list "
+ "of available functions.\n\n");
+
+ fprintf(ofp, "import os\n");
+ fprintf(ofp, "import sys\n\n");
+
+ fprintf(ofp, "sys.path.append(os.environ['PERF_EXEC_PATH'] + \\\n");
+ fprintf(ofp, "\t'/scripts/python/Perf-Trace-Util/lib/Perf/Trace')\n");
+ fprintf(ofp, "\nfrom perf_trace_context import *\n");
+ fprintf(ofp, "from Core import *\n\n\n");
+
+ fprintf(ofp, "def trace_begin():\n");
+ fprintf(ofp, "\tprint \"in trace_begin\"\n\n");
+
+ fprintf(ofp, "def trace_end():\n");
+ fprintf(ofp, "\tprint \"in trace_end\"\n\n");
+
+ while ((event = trace_find_next_event(pevent, event))) {
+ fprintf(ofp, "def %s__%s(", event->system, event->name);
+ fprintf(ofp, "event_name, ");
+ fprintf(ofp, "context, ");
+ fprintf(ofp, "common_cpu,\n");
+ fprintf(ofp, "\tcommon_secs, ");
+ fprintf(ofp, "common_nsecs, ");
+ fprintf(ofp, "common_pid, ");
+ fprintf(ofp, "common_comm,\n\t");
+
+ not_first = 0;
+ count = 0;
+
+ for (f = event->format.fields; f; f = f->next) {
+ if (not_first++)
+ fprintf(ofp, ", ");
+ if (++count % 5 == 0)
+ fprintf(ofp, "\n\t");
+
+ fprintf(ofp, "%s", f->name);
+ }
+ fprintf(ofp, "):\n");
+
+ fprintf(ofp, "\t\tprint_header(event_name, common_cpu, "
+ "common_secs, common_nsecs,\n\t\t\t"
+ "common_pid, common_comm)\n\n");
+
+ fprintf(ofp, "\t\tprint \"");
+
+ not_first = 0;
+ count = 0;
+
+ for (f = event->format.fields; f; f = f->next) {
+ if (not_first++)
+ fprintf(ofp, ", ");
+ if (count && count % 3 == 0) {
+ fprintf(ofp, "\" \\\n\t\t\"");
+ }
+ count++;
+
+ fprintf(ofp, "%s=", f->name);
+ if (f->flags & FIELD_IS_STRING ||
+ f->flags & FIELD_IS_FLAG ||
+ f->flags & FIELD_IS_ARRAY ||
+ f->flags & FIELD_IS_SYMBOLIC)
+ fprintf(ofp, "%%s");
+ else if (f->flags & FIELD_IS_SIGNED)
+ fprintf(ofp, "%%d");
+ else
+ fprintf(ofp, "%%u");
+ }
+
+ fprintf(ofp, "\\n\" %% \\\n\t\t(");
+
+ not_first = 0;
+ count = 0;
+
+ for (f = event->format.fields; f; f = f->next) {
+ if (not_first++)
+ fprintf(ofp, ", ");
+
+ if (++count % 5 == 0)
+ fprintf(ofp, "\n\t\t");
+
+ if (f->flags & FIELD_IS_FLAG) {
+ if ((count - 1) % 5 != 0) {
+ fprintf(ofp, "\n\t\t");
+ count = 4;
+ }
+ fprintf(ofp, "flag_str(\"");
+ fprintf(ofp, "%s__%s\", ", event->system,
+ event->name);
+ fprintf(ofp, "\"%s\", %s)", f->name,
+ f->name);
+ } else if (f->flags & FIELD_IS_SYMBOLIC) {
+ if ((count - 1) % 5 != 0) {
+ fprintf(ofp, "\n\t\t");
+ count = 4;
+ }
+ fprintf(ofp, "symbol_str(\"");
+ fprintf(ofp, "%s__%s\", ", event->system,
+ event->name);
+ fprintf(ofp, "\"%s\", %s)", f->name,
+ f->name);
+ } else
+ fprintf(ofp, "%s", f->name);
+ }
+
+ fprintf(ofp, "),\n\n");
+ }
+
+ fprintf(ofp, "def trace_unhandled(event_name, context, "
+ "event_fields_dict):\n");
+
+ fprintf(ofp, "\t\tprint ' '.join(['%%s=%%s'%%(k,str(v))"
+ "for k,v in sorted(event_fields_dict.items())])\n\n");
+
+ fprintf(ofp, "def print_header("
+ "event_name, cpu, secs, nsecs, pid, comm):\n"
+ "\tprint \"%%-20s %%5u %%05u.%%09u %%8u %%-20s \" %% \\\n\t"
+ "(event_name, cpu, secs, nsecs, pid, comm),\n");
+
+ fclose(ofp);
+
+ fprintf(stderr, "generated Python script: %s\n", fname);
+
+ return 0;
+}
+
+struct scripting_ops python_scripting_ops = {
+ .name = "Python",
+ .start_script = python_start_script,
+ .stop_script = python_stop_script,
+ .process_event = python_process_event,
+ .generate_script = python_generate_script,
+};
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c
new file mode 100644
index 00000000000..64a186edc7b
--- /dev/null
+++ b/tools/perf/util/session.c
@@ -0,0 +1,1673 @@
+#include <linux/kernel.h>
+#include <traceevent/event-parse.h>
+
+#include <byteswap.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/mman.h>
+
+#include "evlist.h"
+#include "evsel.h"
+#include "session.h"
+#include "tool.h"
+#include "sort.h"
+#include "util.h"
+#include "cpumap.h"
+#include "perf_regs.h"
+#include "vdso.h"
+
+static int perf_session__open(struct perf_session *session)
+{
+ struct perf_data_file *file = session->file;
+
+ if (perf_session__read_header(session) < 0) {
+ pr_err("incompatible file format (rerun with -v to learn more)");
+ return -1;
+ }
+
+ if (perf_data_file__is_pipe(file))
+ return 0;
+
+ if (!perf_evlist__valid_sample_type(session->evlist)) {
+ pr_err("non matching sample_type");
+ return -1;
+ }
+
+ if (!perf_evlist__valid_sample_id_all(session->evlist)) {
+ pr_err("non matching sample_id_all");
+ return -1;
+ }
+
+ if (!perf_evlist__valid_read_format(session->evlist)) {
+ pr_err("non matching read_format");
+ return -1;
+ }
+
+ return 0;
+}
+
+void perf_session__set_id_hdr_size(struct perf_session *session)
+{
+ u16 id_hdr_size = perf_evlist__id_hdr_size(session->evlist);
+
+ machines__set_id_hdr_size(&session->machines, id_hdr_size);
+}
+
+int perf_session__create_kernel_maps(struct perf_session *session)
+{
+ int ret = machine__create_kernel_maps(&session->machines.host);
+
+ if (ret >= 0)
+ ret = machines__create_guest_kernel_maps(&session->machines);
+ return ret;
+}
+
+static void perf_session__destroy_kernel_maps(struct perf_session *session)
+{
+ machines__destroy_kernel_maps(&session->machines);
+}
+
+struct perf_session *perf_session__new(struct perf_data_file *file,
+ bool repipe, struct perf_tool *tool)
+{
+ struct perf_session *session = zalloc(sizeof(*session));
+
+ if (!session)
+ goto out;
+
+ session->repipe = repipe;
+ INIT_LIST_HEAD(&session->ordered_samples.samples);
+ INIT_LIST_HEAD(&session->ordered_samples.sample_cache);
+ INIT_LIST_HEAD(&session->ordered_samples.to_free);
+ machines__init(&session->machines);
+
+ if (file) {
+ if (perf_data_file__open(file))
+ goto out_delete;
+
+ session->file = file;
+
+ if (perf_data_file__is_read(file)) {
+ if (perf_session__open(session) < 0)
+ goto out_close;
+
+ perf_session__set_id_hdr_size(session);
+ }
+ }
+
+ if (!file || perf_data_file__is_write(file)) {
+ /*
+ * In O_RDONLY mode this will be performed when reading the
+ * kernel MMAP event, in perf_event__process_mmap().
+ */
+ if (perf_session__create_kernel_maps(session) < 0)
+ goto out_delete;
+ }
+
+ if (tool && tool->ordering_requires_timestamps &&
+ tool->ordered_samples && !perf_evlist__sample_id_all(session->evlist)) {
+ dump_printf("WARNING: No sample_id_all support, falling back to unordered processing\n");
+ tool->ordered_samples = false;
+ }
+
+ return session;
+
+ out_close:
+ perf_data_file__close(file);
+ out_delete:
+ perf_session__delete(session);
+ out:
+ return NULL;
+}
+
+static void perf_session__delete_dead_threads(struct perf_session *session)
+{
+ machine__delete_dead_threads(&session->machines.host);
+}
+
+static void perf_session__delete_threads(struct perf_session *session)
+{
+ machine__delete_threads(&session->machines.host);
+}
+
+static void perf_session_env__delete(struct perf_session_env *env)
+{
+ zfree(&env->hostname);
+ zfree(&env->os_release);
+ zfree(&env->version);
+ zfree(&env->arch);
+ zfree(&env->cpu_desc);
+ zfree(&env->cpuid);
+
+ zfree(&env->cmdline);
+ zfree(&env->sibling_cores);
+ zfree(&env->sibling_threads);
+ zfree(&env->numa_nodes);
+ zfree(&env->pmu_mappings);
+}
+
+void perf_session__delete(struct perf_session *session)
+{
+ perf_session__destroy_kernel_maps(session);
+ perf_session__delete_dead_threads(session);
+ perf_session__delete_threads(session);
+ perf_session_env__delete(&session->header.env);
+ machines__exit(&session->machines);
+ if (session->file)
+ perf_data_file__close(session->file);
+ free(session);
+ vdso__exit();
+}
+
+static int process_event_synth_tracing_data_stub(struct perf_tool *tool
+ __maybe_unused,
+ union perf_event *event
+ __maybe_unused,
+ struct perf_session *session
+ __maybe_unused)
+{
+ dump_printf(": unhandled!\n");
+ return 0;
+}
+
+static int process_event_synth_attr_stub(struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_evlist **pevlist
+ __maybe_unused)
+{
+ dump_printf(": unhandled!\n");
+ return 0;
+}
+
+static int process_event_sample_stub(struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct perf_evsel *evsel __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ dump_printf(": unhandled!\n");
+ return 0;
+}
+
+static int process_event_stub(struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct machine *machine __maybe_unused)
+{
+ dump_printf(": unhandled!\n");
+ return 0;
+}
+
+static int process_finished_round_stub(struct perf_tool *tool __maybe_unused,
+ union perf_event *event __maybe_unused,
+ struct perf_session *perf_session
+ __maybe_unused)
+{
+ dump_printf(": unhandled!\n");
+ return 0;
+}
+
+static int process_finished_round(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_session *session);
+
+void perf_tool__fill_defaults(struct perf_tool *tool)
+{
+ if (tool->sample == NULL)
+ tool->sample = process_event_sample_stub;
+ if (tool->mmap == NULL)
+ tool->mmap = process_event_stub;
+ if (tool->mmap2 == NULL)
+ tool->mmap2 = process_event_stub;
+ if (tool->comm == NULL)
+ tool->comm = process_event_stub;
+ if (tool->fork == NULL)
+ tool->fork = process_event_stub;
+ if (tool->exit == NULL)
+ tool->exit = process_event_stub;
+ if (tool->lost == NULL)
+ tool->lost = perf_event__process_lost;
+ if (tool->read == NULL)
+ tool->read = process_event_sample_stub;
+ if (tool->throttle == NULL)
+ tool->throttle = process_event_stub;
+ if (tool->unthrottle == NULL)
+ tool->unthrottle = process_event_stub;
+ if (tool->attr == NULL)
+ tool->attr = process_event_synth_attr_stub;
+ if (tool->tracing_data == NULL)
+ tool->tracing_data = process_event_synth_tracing_data_stub;
+ if (tool->build_id == NULL)
+ tool->build_id = process_finished_round_stub;
+ if (tool->finished_round == NULL) {
+ if (tool->ordered_samples)
+ tool->finished_round = process_finished_round;
+ else
+ tool->finished_round = process_finished_round_stub;
+ }
+}
+
+static void swap_sample_id_all(union perf_event *event, void *data)
+{
+ void *end = (void *) event + event->header.size;
+ int size = end - data;
+
+ BUG_ON(size % sizeof(u64));
+ mem_bswap_64(data, size);
+}
+
+static void perf_event__all64_swap(union perf_event *event,
+ bool sample_id_all __maybe_unused)
+{
+ struct perf_event_header *hdr = &event->header;
+ mem_bswap_64(hdr + 1, event->header.size - sizeof(*hdr));
+}
+
+static void perf_event__comm_swap(union perf_event *event, bool sample_id_all)
+{
+ event->comm.pid = bswap_32(event->comm.pid);
+ event->comm.tid = bswap_32(event->comm.tid);
+
+ if (sample_id_all) {
+ void *data = &event->comm.comm;
+
+ data += PERF_ALIGN(strlen(data) + 1, sizeof(u64));
+ swap_sample_id_all(event, data);
+ }
+}
+
+static void perf_event__mmap_swap(union perf_event *event,
+ bool sample_id_all)
+{
+ event->mmap.pid = bswap_32(event->mmap.pid);
+ event->mmap.tid = bswap_32(event->mmap.tid);
+ event->mmap.start = bswap_64(event->mmap.start);
+ event->mmap.len = bswap_64(event->mmap.len);
+ event->mmap.pgoff = bswap_64(event->mmap.pgoff);
+
+ if (sample_id_all) {
+ void *data = &event->mmap.filename;
+
+ data += PERF_ALIGN(strlen(data) + 1, sizeof(u64));
+ swap_sample_id_all(event, data);
+ }
+}
+
+static void perf_event__mmap2_swap(union perf_event *event,
+ bool sample_id_all)
+{
+ event->mmap2.pid = bswap_32(event->mmap2.pid);
+ event->mmap2.tid = bswap_32(event->mmap2.tid);
+ event->mmap2.start = bswap_64(event->mmap2.start);
+ event->mmap2.len = bswap_64(event->mmap2.len);
+ event->mmap2.pgoff = bswap_64(event->mmap2.pgoff);
+ event->mmap2.maj = bswap_32(event->mmap2.maj);
+ event->mmap2.min = bswap_32(event->mmap2.min);
+ event->mmap2.ino = bswap_64(event->mmap2.ino);
+
+ if (sample_id_all) {
+ void *data = &event->mmap2.filename;
+
+ data += PERF_ALIGN(strlen(data) + 1, sizeof(u64));
+ swap_sample_id_all(event, data);
+ }
+}
+static void perf_event__task_swap(union perf_event *event, bool sample_id_all)
+{
+ event->fork.pid = bswap_32(event->fork.pid);
+ event->fork.tid = bswap_32(event->fork.tid);
+ event->fork.ppid = bswap_32(event->fork.ppid);
+ event->fork.ptid = bswap_32(event->fork.ptid);
+ event->fork.time = bswap_64(event->fork.time);
+
+ if (sample_id_all)
+ swap_sample_id_all(event, &event->fork + 1);
+}
+
+static void perf_event__read_swap(union perf_event *event, bool sample_id_all)
+{
+ event->read.pid = bswap_32(event->read.pid);
+ event->read.tid = bswap_32(event->read.tid);
+ event->read.value = bswap_64(event->read.value);
+ event->read.time_enabled = bswap_64(event->read.time_enabled);
+ event->read.time_running = bswap_64(event->read.time_running);
+ event->read.id = bswap_64(event->read.id);
+
+ if (sample_id_all)
+ swap_sample_id_all(event, &event->read + 1);
+}
+
+static void perf_event__throttle_swap(union perf_event *event,
+ bool sample_id_all)
+{
+ event->throttle.time = bswap_64(event->throttle.time);
+ event->throttle.id = bswap_64(event->throttle.id);
+ event->throttle.stream_id = bswap_64(event->throttle.stream_id);
+
+ if (sample_id_all)
+ swap_sample_id_all(event, &event->throttle + 1);
+}
+
+static u8 revbyte(u8 b)
+{
+ int rev = (b >> 4) | ((b & 0xf) << 4);
+ rev = ((rev & 0xcc) >> 2) | ((rev & 0x33) << 2);
+ rev = ((rev & 0xaa) >> 1) | ((rev & 0x55) << 1);
+ return (u8) rev;
+}
+
+/*
+ * XXX this is hack in attempt to carry flags bitfield
+ * throught endian village. ABI says:
+ *
+ * Bit-fields are allocated from right to left (least to most significant)
+ * on little-endian implementations and from left to right (most to least
+ * significant) on big-endian implementations.
+ *
+ * The above seems to be byte specific, so we need to reverse each
+ * byte of the bitfield. 'Internet' also says this might be implementation
+ * specific and we probably need proper fix and carry perf_event_attr
+ * bitfield flags in separate data file FEAT_ section. Thought this seems
+ * to work for now.
+ */
+static void swap_bitfield(u8 *p, unsigned len)
+{
+ unsigned i;
+
+ for (i = 0; i < len; i++) {
+ *p = revbyte(*p);
+ p++;
+ }
+}
+
+/* exported for swapping attributes in file header */
+void perf_event__attr_swap(struct perf_event_attr *attr)
+{
+ attr->type = bswap_32(attr->type);
+ attr->size = bswap_32(attr->size);
+ attr->config = bswap_64(attr->config);
+ attr->sample_period = bswap_64(attr->sample_period);
+ attr->sample_type = bswap_64(attr->sample_type);
+ attr->read_format = bswap_64(attr->read_format);
+ attr->wakeup_events = bswap_32(attr->wakeup_events);
+ attr->bp_type = bswap_32(attr->bp_type);
+ attr->bp_addr = bswap_64(attr->bp_addr);
+ attr->bp_len = bswap_64(attr->bp_len);
+ attr->branch_sample_type = bswap_64(attr->branch_sample_type);
+ attr->sample_regs_user = bswap_64(attr->sample_regs_user);
+ attr->sample_stack_user = bswap_32(attr->sample_stack_user);
+
+ swap_bitfield((u8 *) (&attr->read_format + 1), sizeof(u64));
+}
+
+static void perf_event__hdr_attr_swap(union perf_event *event,
+ bool sample_id_all __maybe_unused)
+{
+ size_t size;
+
+ perf_event__attr_swap(&event->attr.attr);
+
+ size = event->header.size;
+ size -= (void *)&event->attr.id - (void *)event;
+ mem_bswap_64(event->attr.id, size);
+}
+
+static void perf_event__event_type_swap(union perf_event *event,
+ bool sample_id_all __maybe_unused)
+{
+ event->event_type.event_type.event_id =
+ bswap_64(event->event_type.event_type.event_id);
+}
+
+static void perf_event__tracing_data_swap(union perf_event *event,
+ bool sample_id_all __maybe_unused)
+{
+ event->tracing_data.size = bswap_32(event->tracing_data.size);
+}
+
+typedef void (*perf_event__swap_op)(union perf_event *event,
+ bool sample_id_all);
+
+static perf_event__swap_op perf_event__swap_ops[] = {
+ [PERF_RECORD_MMAP] = perf_event__mmap_swap,
+ [PERF_RECORD_MMAP2] = perf_event__mmap2_swap,
+ [PERF_RECORD_COMM] = perf_event__comm_swap,
+ [PERF_RECORD_FORK] = perf_event__task_swap,
+ [PERF_RECORD_EXIT] = perf_event__task_swap,
+ [PERF_RECORD_LOST] = perf_event__all64_swap,
+ [PERF_RECORD_READ] = perf_event__read_swap,
+ [PERF_RECORD_THROTTLE] = perf_event__throttle_swap,
+ [PERF_RECORD_UNTHROTTLE] = perf_event__throttle_swap,
+ [PERF_RECORD_SAMPLE] = perf_event__all64_swap,
+ [PERF_RECORD_HEADER_ATTR] = perf_event__hdr_attr_swap,
+ [PERF_RECORD_HEADER_EVENT_TYPE] = perf_event__event_type_swap,
+ [PERF_RECORD_HEADER_TRACING_DATA] = perf_event__tracing_data_swap,
+ [PERF_RECORD_HEADER_BUILD_ID] = NULL,
+ [PERF_RECORD_HEADER_MAX] = NULL,
+};
+
+struct sample_queue {
+ u64 timestamp;
+ u64 file_offset;
+ union perf_event *event;
+ struct list_head list;
+};
+
+static void perf_session_free_sample_buffers(struct perf_session *session)
+{
+ struct ordered_samples *os = &session->ordered_samples;
+
+ while (!list_empty(&os->to_free)) {
+ struct sample_queue *sq;
+
+ sq = list_entry(os->to_free.next, struct sample_queue, list);
+ list_del(&sq->list);
+ free(sq);
+ }
+}
+
+static int perf_session_deliver_event(struct perf_session *session,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_tool *tool,
+ u64 file_offset);
+
+static int flush_sample_queue(struct perf_session *s,
+ struct perf_tool *tool)
+{
+ struct ordered_samples *os = &s->ordered_samples;
+ struct list_head *head = &os->samples;
+ struct sample_queue *tmp, *iter;
+ struct perf_sample sample;
+ u64 limit = os->next_flush;
+ u64 last_ts = os->last_sample ? os->last_sample->timestamp : 0ULL;
+ bool show_progress = limit == ULLONG_MAX;
+ struct ui_progress prog;
+ int ret;
+
+ if (!tool->ordered_samples || !limit)
+ return 0;
+
+ if (show_progress)
+ ui_progress__init(&prog, os->nr_samples, "Processing time ordered events...");
+
+ list_for_each_entry_safe(iter, tmp, head, list) {
+ if (session_done())
+ return 0;
+
+ if (iter->timestamp > limit)
+ break;
+
+ ret = perf_evlist__parse_sample(s->evlist, iter->event, &sample);
+ if (ret)
+ pr_err("Can't parse sample, err = %d\n", ret);
+ else {
+ ret = perf_session_deliver_event(s, iter->event, &sample, tool,
+ iter->file_offset);
+ if (ret)
+ return ret;
+ }
+
+ os->last_flush = iter->timestamp;
+ list_del(&iter->list);
+ list_add(&iter->list, &os->sample_cache);
+
+ if (show_progress)
+ ui_progress__update(&prog, 1);
+ }
+
+ if (list_empty(head)) {
+ os->last_sample = NULL;
+ } else if (last_ts <= limit) {
+ os->last_sample =
+ list_entry(head->prev, struct sample_queue, list);
+ }
+
+ os->nr_samples = 0;
+
+ return 0;
+}
+
+/*
+ * When perf record finishes a pass on every buffers, it records this pseudo
+ * event.
+ * We record the max timestamp t found in the pass n.
+ * Assuming these timestamps are monotonic across cpus, we know that if
+ * a buffer still has events with timestamps below t, they will be all
+ * available and then read in the pass n + 1.
+ * Hence when we start to read the pass n + 2, we can safely flush every
+ * events with timestamps below t.
+ *
+ * ============ PASS n =================
+ * CPU 0 | CPU 1
+ * |
+ * cnt1 timestamps | cnt2 timestamps
+ * 1 | 2
+ * 2 | 3
+ * - | 4 <--- max recorded
+ *
+ * ============ PASS n + 1 ==============
+ * CPU 0 | CPU 1
+ * |
+ * cnt1 timestamps | cnt2 timestamps
+ * 3 | 5
+ * 4 | 6
+ * 5 | 7 <---- max recorded
+ *
+ * Flush every events below timestamp 4
+ *
+ * ============ PASS n + 2 ==============
+ * CPU 0 | CPU 1
+ * |
+ * cnt1 timestamps | cnt2 timestamps
+ * 6 | 8
+ * 7 | 9
+ * - | 10
+ *
+ * Flush every events below timestamp 7
+ * etc...
+ */
+static int process_finished_round(struct perf_tool *tool,
+ union perf_event *event __maybe_unused,
+ struct perf_session *session)
+{
+ int ret = flush_sample_queue(session, tool);
+ if (!ret)
+ session->ordered_samples.next_flush = session->ordered_samples.max_timestamp;
+
+ return ret;
+}
+
+/* The queue is ordered by time */
+static void __queue_event(struct sample_queue *new, struct perf_session *s)
+{
+ struct ordered_samples *os = &s->ordered_samples;
+ struct sample_queue *sample = os->last_sample;
+ u64 timestamp = new->timestamp;
+ struct list_head *p;
+
+ ++os->nr_samples;
+ os->last_sample = new;
+
+ if (!sample) {
+ list_add(&new->list, &os->samples);
+ os->max_timestamp = timestamp;
+ return;
+ }
+
+ /*
+ * last_sample might point to some random place in the list as it's
+ * the last queued event. We expect that the new event is close to
+ * this.
+ */
+ if (sample->timestamp <= timestamp) {
+ while (sample->timestamp <= timestamp) {
+ p = sample->list.next;
+ if (p == &os->samples) {
+ list_add_tail(&new->list, &os->samples);
+ os->max_timestamp = timestamp;
+ return;
+ }
+ sample = list_entry(p, struct sample_queue, list);
+ }
+ list_add_tail(&new->list, &sample->list);
+ } else {
+ while (sample->timestamp > timestamp) {
+ p = sample->list.prev;
+ if (p == &os->samples) {
+ list_add(&new->list, &os->samples);
+ return;
+ }
+ sample = list_entry(p, struct sample_queue, list);
+ }
+ list_add(&new->list, &sample->list);
+ }
+}
+
+#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct sample_queue))
+
+int perf_session_queue_event(struct perf_session *s, union perf_event *event,
+ struct perf_sample *sample, u64 file_offset)
+{
+ struct ordered_samples *os = &s->ordered_samples;
+ struct list_head *sc = &os->sample_cache;
+ u64 timestamp = sample->time;
+ struct sample_queue *new;
+
+ if (!timestamp || timestamp == ~0ULL)
+ return -ETIME;
+
+ if (timestamp < s->ordered_samples.last_flush) {
+ printf("Warning: Timestamp below last timeslice flush\n");
+ return -EINVAL;
+ }
+
+ if (!list_empty(sc)) {
+ new = list_entry(sc->next, struct sample_queue, list);
+ list_del(&new->list);
+ } else if (os->sample_buffer) {
+ new = os->sample_buffer + os->sample_buffer_idx;
+ if (++os->sample_buffer_idx == MAX_SAMPLE_BUFFER)
+ os->sample_buffer = NULL;
+ } else {
+ os->sample_buffer = malloc(MAX_SAMPLE_BUFFER * sizeof(*new));
+ if (!os->sample_buffer)
+ return -ENOMEM;
+ list_add(&os->sample_buffer->list, &os->to_free);
+ os->sample_buffer_idx = 2;
+ new = os->sample_buffer + 1;
+ }
+
+ new->timestamp = timestamp;
+ new->file_offset = file_offset;
+ new->event = event;
+
+ __queue_event(new, s);
+
+ return 0;
+}
+
+static void callchain__printf(struct perf_sample *sample)
+{
+ unsigned int i;
+
+ printf("... chain: nr:%" PRIu64 "\n", sample->callchain->nr);
+
+ for (i = 0; i < sample->callchain->nr; i++)
+ printf("..... %2d: %016" PRIx64 "\n",
+ i, sample->callchain->ips[i]);
+}
+
+static void branch_stack__printf(struct perf_sample *sample)
+{
+ uint64_t i;
+
+ printf("... branch stack: nr:%" PRIu64 "\n", sample->branch_stack->nr);
+
+ for (i = 0; i < sample->branch_stack->nr; i++)
+ printf("..... %2"PRIu64": %016" PRIx64 " -> %016" PRIx64 "\n",
+ i, sample->branch_stack->entries[i].from,
+ sample->branch_stack->entries[i].to);
+}
+
+static void regs_dump__printf(u64 mask, u64 *regs)
+{
+ unsigned rid, i = 0;
+
+ for_each_set_bit(rid, (unsigned long *) &mask, sizeof(mask) * 8) {
+ u64 val = regs[i++];
+
+ printf(".... %-5s 0x%" PRIx64 "\n",
+ perf_reg_name(rid), val);
+ }
+}
+
+static void regs_user__printf(struct perf_sample *sample)
+{
+ struct regs_dump *user_regs = &sample->user_regs;
+
+ if (user_regs->regs) {
+ u64 mask = user_regs->mask;
+ printf("... user regs: mask 0x%" PRIx64 "\n", mask);
+ regs_dump__printf(mask, user_regs->regs);
+ }
+}
+
+static void stack_user__printf(struct stack_dump *dump)
+{
+ printf("... ustack: size %" PRIu64 ", offset 0x%x\n",
+ dump->size, dump->offset);
+}
+
+static void perf_session__print_tstamp(struct perf_session *session,
+ union perf_event *event,
+ struct perf_sample *sample)
+{
+ u64 sample_type = __perf_evlist__combined_sample_type(session->evlist);
+
+ if (event->header.type != PERF_RECORD_SAMPLE &&
+ !perf_evlist__sample_id_all(session->evlist)) {
+ fputs("-1 -1 ", stdout);
+ return;
+ }
+
+ if ((sample_type & PERF_SAMPLE_CPU))
+ printf("%u ", sample->cpu);
+
+ if (sample_type & PERF_SAMPLE_TIME)
+ printf("%" PRIu64 " ", sample->time);
+}
+
+static void sample_read__printf(struct perf_sample *sample, u64 read_format)
+{
+ printf("... sample_read:\n");
+
+ if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED)
+ printf("...... time enabled %016" PRIx64 "\n",
+ sample->read.time_enabled);
+
+ if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING)
+ printf("...... time running %016" PRIx64 "\n",
+ sample->read.time_running);
+
+ if (read_format & PERF_FORMAT_GROUP) {
+ u64 i;
+
+ printf(".... group nr %" PRIu64 "\n", sample->read.group.nr);
+
+ for (i = 0; i < sample->read.group.nr; i++) {
+ struct sample_read_value *value;
+
+ value = &sample->read.group.values[i];
+ printf("..... id %016" PRIx64
+ ", value %016" PRIx64 "\n",
+ value->id, value->value);
+ }
+ } else
+ printf("..... id %016" PRIx64 ", value %016" PRIx64 "\n",
+ sample->read.one.id, sample->read.one.value);
+}
+
+static void dump_event(struct perf_session *session, union perf_event *event,
+ u64 file_offset, struct perf_sample *sample)
+{
+ if (!dump_trace)
+ return;
+
+ printf("\n%#" PRIx64 " [%#x]: event: %d\n",
+ file_offset, event->header.size, event->header.type);
+
+ trace_event(event);
+
+ if (sample)
+ perf_session__print_tstamp(session, event, sample);
+
+ printf("%#" PRIx64 " [%#x]: PERF_RECORD_%s", file_offset,
+ event->header.size, perf_event__name(event->header.type));
+}
+
+static void dump_sample(struct perf_evsel *evsel, union perf_event *event,
+ struct perf_sample *sample)
+{
+ u64 sample_type;
+
+ if (!dump_trace)
+ return;
+
+ printf("(IP, 0x%x): %d/%d: %#" PRIx64 " period: %" PRIu64 " addr: %#" PRIx64 "\n",
+ event->header.misc, sample->pid, sample->tid, sample->ip,
+ sample->period, sample->addr);
+
+ sample_type = evsel->attr.sample_type;
+
+ if (sample_type & PERF_SAMPLE_CALLCHAIN)
+ callchain__printf(sample);
+
+ if (sample_type & PERF_SAMPLE_BRANCH_STACK)
+ branch_stack__printf(sample);
+
+ if (sample_type & PERF_SAMPLE_REGS_USER)
+ regs_user__printf(sample);
+
+ if (sample_type & PERF_SAMPLE_STACK_USER)
+ stack_user__printf(&sample->user_stack);
+
+ if (sample_type & PERF_SAMPLE_WEIGHT)
+ printf("... weight: %" PRIu64 "\n", sample->weight);
+
+ if (sample_type & PERF_SAMPLE_DATA_SRC)
+ printf(" . data_src: 0x%"PRIx64"\n", sample->data_src);
+
+ if (sample_type & PERF_SAMPLE_TRANSACTION)
+ printf("... transaction: %" PRIx64 "\n", sample->transaction);
+
+ if (sample_type & PERF_SAMPLE_READ)
+ sample_read__printf(sample, evsel->attr.read_format);
+}
+
+static struct machine *
+ perf_session__find_machine_for_cpumode(struct perf_session *session,
+ union perf_event *event,
+ struct perf_sample *sample)
+{
+ const u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK;
+ struct machine *machine;
+
+ if (perf_guest &&
+ ((cpumode == PERF_RECORD_MISC_GUEST_KERNEL) ||
+ (cpumode == PERF_RECORD_MISC_GUEST_USER))) {
+ u32 pid;
+
+ if (event->header.type == PERF_RECORD_MMAP
+ || event->header.type == PERF_RECORD_MMAP2)
+ pid = event->mmap.pid;
+ else
+ pid = sample->pid;
+
+ machine = perf_session__find_machine(session, pid);
+ if (!machine)
+ machine = perf_session__findnew_machine(session,
+ DEFAULT_GUEST_KERNEL_ID);
+ return machine;
+ }
+
+ return &session->machines.host;
+}
+
+static int deliver_sample_value(struct perf_session *session,
+ struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct sample_read_value *v,
+ struct machine *machine)
+{
+ struct perf_sample_id *sid;
+
+ sid = perf_evlist__id2sid(session->evlist, v->id);
+ if (sid) {
+ sample->id = v->id;
+ sample->period = v->value - sid->period;
+ sid->period = v->value;
+ }
+
+ if (!sid || sid->evsel == NULL) {
+ ++session->stats.nr_unknown_id;
+ return 0;
+ }
+
+ return tool->sample(tool, event, sample, sid->evsel, machine);
+}
+
+static int deliver_sample_group(struct perf_session *session,
+ struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct machine *machine)
+{
+ int ret = -EINVAL;
+ u64 i;
+
+ for (i = 0; i < sample->read.group.nr; i++) {
+ ret = deliver_sample_value(session, tool, event, sample,
+ &sample->read.group.values[i],
+ machine);
+ if (ret)
+ break;
+ }
+
+ return ret;
+}
+
+static int
+perf_session__deliver_sample(struct perf_session *session,
+ struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct machine *machine)
+{
+ /* We know evsel != NULL. */
+ u64 sample_type = evsel->attr.sample_type;
+ u64 read_format = evsel->attr.read_format;
+
+ /* Standard sample delievery. */
+ if (!(sample_type & PERF_SAMPLE_READ))
+ return tool->sample(tool, event, sample, evsel, machine);
+
+ /* For PERF_SAMPLE_READ we have either single or group mode. */
+ if (read_format & PERF_FORMAT_GROUP)
+ return deliver_sample_group(session, tool, event, sample,
+ machine);
+ else
+ return deliver_sample_value(session, tool, event, sample,
+ &sample->read.one, machine);
+}
+
+static int perf_session_deliver_event(struct perf_session *session,
+ union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_tool *tool,
+ u64 file_offset)
+{
+ struct perf_evsel *evsel;
+ struct machine *machine;
+
+ dump_event(session, event, file_offset, sample);
+
+ evsel = perf_evlist__id2evsel(session->evlist, sample->id);
+ if (evsel != NULL && event->header.type != PERF_RECORD_SAMPLE) {
+ /*
+ * XXX We're leaving PERF_RECORD_SAMPLE unnacounted here
+ * because the tools right now may apply filters, discarding
+ * some of the samples. For consistency, in the future we
+ * should have something like nr_filtered_samples and remove
+ * the sample->period from total_sample_period, etc, KISS for
+ * now tho.
+ *
+ * Also testing against NULL allows us to handle files without
+ * attr.sample_id_all and/or without PERF_SAMPLE_ID. In the
+ * future probably it'll be a good idea to restrict event
+ * processing via perf_session to files with both set.
+ */
+ hists__inc_nr_events(&evsel->hists, event->header.type);
+ }
+
+ machine = perf_session__find_machine_for_cpumode(session, event,
+ sample);
+
+ switch (event->header.type) {
+ case PERF_RECORD_SAMPLE:
+ dump_sample(evsel, event, sample);
+ if (evsel == NULL) {
+ ++session->stats.nr_unknown_id;
+ return 0;
+ }
+ if (machine == NULL) {
+ ++session->stats.nr_unprocessable_samples;
+ return 0;
+ }
+ return perf_session__deliver_sample(session, tool, event,
+ sample, evsel, machine);
+ case PERF_RECORD_MMAP:
+ return tool->mmap(tool, event, sample, machine);
+ case PERF_RECORD_MMAP2:
+ return tool->mmap2(tool, event, sample, machine);
+ case PERF_RECORD_COMM:
+ return tool->comm(tool, event, sample, machine);
+ case PERF_RECORD_FORK:
+ return tool->fork(tool, event, sample, machine);
+ case PERF_RECORD_EXIT:
+ return tool->exit(tool, event, sample, machine);
+ case PERF_RECORD_LOST:
+ if (tool->lost == perf_event__process_lost)
+ session->stats.total_lost += event->lost.lost;
+ return tool->lost(tool, event, sample, machine);
+ case PERF_RECORD_READ:
+ return tool->read(tool, event, sample, evsel, machine);
+ case PERF_RECORD_THROTTLE:
+ return tool->throttle(tool, event, sample, machine);
+ case PERF_RECORD_UNTHROTTLE:
+ return tool->unthrottle(tool, event, sample, machine);
+ default:
+ ++session->stats.nr_unknown_events;
+ return -1;
+ }
+}
+
+static int perf_session__process_user_event(struct perf_session *session, union perf_event *event,
+ struct perf_tool *tool, u64 file_offset)
+{
+ int fd = perf_data_file__fd(session->file);
+ int err;
+
+ dump_event(session, event, file_offset, NULL);
+
+ /* These events are processed right away */
+ switch (event->header.type) {
+ case PERF_RECORD_HEADER_ATTR:
+ err = tool->attr(tool, event, &session->evlist);
+ if (err == 0)
+ perf_session__set_id_hdr_size(session);
+ return err;
+ case PERF_RECORD_HEADER_EVENT_TYPE:
+ /*
+ * Depreceated, but we need to handle it for sake
+ * of old data files create in pipe mode.
+ */
+ return 0;
+ case PERF_RECORD_HEADER_TRACING_DATA:
+ /* setup for reading amidst mmap */
+ lseek(fd, file_offset, SEEK_SET);
+ return tool->tracing_data(tool, event, session);
+ case PERF_RECORD_HEADER_BUILD_ID:
+ return tool->build_id(tool, event, session);
+ case PERF_RECORD_FINISHED_ROUND:
+ return tool->finished_round(tool, event, session);
+ default:
+ return -EINVAL;
+ }
+}
+
+static void event_swap(union perf_event *event, bool sample_id_all)
+{
+ perf_event__swap_op swap;
+
+ swap = perf_event__swap_ops[event->header.type];
+ if (swap)
+ swap(event, sample_id_all);
+}
+
+static int perf_session__process_event(struct perf_session *session,
+ union perf_event *event,
+ struct perf_tool *tool,
+ u64 file_offset)
+{
+ struct perf_sample sample;
+ int ret;
+
+ if (session->header.needs_swap)
+ event_swap(event, perf_evlist__sample_id_all(session->evlist));
+
+ if (event->header.type >= PERF_RECORD_HEADER_MAX)
+ return -EINVAL;
+
+ events_stats__inc(&session->stats, event->header.type);
+
+ if (event->header.type >= PERF_RECORD_USER_TYPE_START)
+ return perf_session__process_user_event(session, event, tool, file_offset);
+
+ /*
+ * For all kernel events we get the sample data
+ */
+ ret = perf_evlist__parse_sample(session->evlist, event, &sample);
+ if (ret)
+ return ret;
+
+ if (tool->ordered_samples) {
+ ret = perf_session_queue_event(session, event, &sample,
+ file_offset);
+ if (ret != -ETIME)
+ return ret;
+ }
+
+ return perf_session_deliver_event(session, event, &sample, tool,
+ file_offset);
+}
+
+void perf_event_header__bswap(struct perf_event_header *hdr)
+{
+ hdr->type = bswap_32(hdr->type);
+ hdr->misc = bswap_16(hdr->misc);
+ hdr->size = bswap_16(hdr->size);
+}
+
+struct thread *perf_session__findnew(struct perf_session *session, pid_t pid)
+{
+ return machine__findnew_thread(&session->machines.host, 0, pid);
+}
+
+static struct thread *perf_session__register_idle_thread(struct perf_session *session)
+{
+ struct thread *thread = perf_session__findnew(session, 0);
+
+ if (thread == NULL || thread__set_comm(thread, "swapper", 0)) {
+ pr_err("problem inserting idle task.\n");
+ thread = NULL;
+ }
+
+ return thread;
+}
+
+static void perf_session__warn_about_errors(const struct perf_session *session,
+ const struct perf_tool *tool)
+{
+ if (tool->lost == perf_event__process_lost &&
+ session->stats.nr_events[PERF_RECORD_LOST] != 0) {
+ ui__warning("Processed %d events and lost %d chunks!\n\n"
+ "Check IO/CPU overload!\n\n",
+ session->stats.nr_events[0],
+ session->stats.nr_events[PERF_RECORD_LOST]);
+ }
+
+ if (session->stats.nr_unknown_events != 0) {
+ ui__warning("Found %u unknown events!\n\n"
+ "Is this an older tool processing a perf.data "
+ "file generated by a more recent tool?\n\n"
+ "If that is not the case, consider "
+ "reporting to linux-kernel@vger.kernel.org.\n\n",
+ session->stats.nr_unknown_events);
+ }
+
+ if (session->stats.nr_unknown_id != 0) {
+ ui__warning("%u samples with id not present in the header\n",
+ session->stats.nr_unknown_id);
+ }
+
+ if (session->stats.nr_invalid_chains != 0) {
+ ui__warning("Found invalid callchains!\n\n"
+ "%u out of %u events were discarded for this reason.\n\n"
+ "Consider reporting to linux-kernel@vger.kernel.org.\n\n",
+ session->stats.nr_invalid_chains,
+ session->stats.nr_events[PERF_RECORD_SAMPLE]);
+ }
+
+ if (session->stats.nr_unprocessable_samples != 0) {
+ ui__warning("%u unprocessable samples recorded.\n"
+ "Do you have a KVM guest running and not using 'perf kvm'?\n",
+ session->stats.nr_unprocessable_samples);
+ }
+}
+
+volatile int session_done;
+
+static int __perf_session__process_pipe_events(struct perf_session *session,
+ struct perf_tool *tool)
+{
+ int fd = perf_data_file__fd(session->file);
+ union perf_event *event;
+ uint32_t size, cur_size = 0;
+ void *buf = NULL;
+ int skip = 0;
+ u64 head;
+ ssize_t err;
+ void *p;
+
+ perf_tool__fill_defaults(tool);
+
+ head = 0;
+ cur_size = sizeof(union perf_event);
+
+ buf = malloc(cur_size);
+ if (!buf)
+ return -errno;
+more:
+ event = buf;
+ err = readn(fd, event, sizeof(struct perf_event_header));
+ if (err <= 0) {
+ if (err == 0)
+ goto done;
+
+ pr_err("failed to read event header\n");
+ goto out_err;
+ }
+
+ if (session->header.needs_swap)
+ perf_event_header__bswap(&event->header);
+
+ size = event->header.size;
+ if (size < sizeof(struct perf_event_header)) {
+ pr_err("bad event header size\n");
+ goto out_err;
+ }
+
+ if (size > cur_size) {
+ void *new = realloc(buf, size);
+ if (!new) {
+ pr_err("failed to allocate memory to read event\n");
+ goto out_err;
+ }
+ buf = new;
+ cur_size = size;
+ event = buf;
+ }
+ p = event;
+ p += sizeof(struct perf_event_header);
+
+ if (size - sizeof(struct perf_event_header)) {
+ err = readn(fd, p, size - sizeof(struct perf_event_header));
+ if (err <= 0) {
+ if (err == 0) {
+ pr_err("unexpected end of event stream\n");
+ goto done;
+ }
+
+ pr_err("failed to read event data\n");
+ goto out_err;
+ }
+ }
+
+ if ((skip = perf_session__process_event(session, event, tool, head)) < 0) {
+ pr_err("%#" PRIx64 " [%#x]: failed to process type: %d\n",
+ head, event->header.size, event->header.type);
+ err = -EINVAL;
+ goto out_err;
+ }
+
+ head += size;
+
+ if (skip > 0)
+ head += skip;
+
+ if (!session_done())
+ goto more;
+done:
+ /* do the final flush for ordered samples */
+ session->ordered_samples.next_flush = ULLONG_MAX;
+ err = flush_sample_queue(session, tool);
+out_err:
+ free(buf);
+ perf_session__warn_about_errors(session, tool);
+ perf_session_free_sample_buffers(session);
+ return err;
+}
+
+static union perf_event *
+fetch_mmaped_event(struct perf_session *session,
+ u64 head, size_t mmap_size, char *buf)
+{
+ union perf_event *event;
+
+ /*
+ * Ensure we have enough space remaining to read
+ * the size of the event in the headers.
+ */
+ if (head + sizeof(event->header) > mmap_size)
+ return NULL;
+
+ event = (union perf_event *)(buf + head);
+
+ if (session->header.needs_swap)
+ perf_event_header__bswap(&event->header);
+
+ if (head + event->header.size > mmap_size) {
+ /* We're not fetching the event so swap back again */
+ if (session->header.needs_swap)
+ perf_event_header__bswap(&event->header);
+ return NULL;
+ }
+
+ return event;
+}
+
+/*
+ * On 64bit we can mmap the data file in one go. No need for tiny mmap
+ * slices. On 32bit we use 32MB.
+ */
+#if BITS_PER_LONG == 64
+#define MMAP_SIZE ULLONG_MAX
+#define NUM_MMAPS 1
+#else
+#define MMAP_SIZE (32 * 1024 * 1024ULL)
+#define NUM_MMAPS 128
+#endif
+
+int __perf_session__process_events(struct perf_session *session,
+ u64 data_offset, u64 data_size,
+ u64 file_size, struct perf_tool *tool)
+{
+ int fd = perf_data_file__fd(session->file);
+ u64 head, page_offset, file_offset, file_pos;
+ int err, mmap_prot, mmap_flags, map_idx = 0;
+ size_t mmap_size;
+ char *buf, *mmaps[NUM_MMAPS];
+ union perf_event *event;
+ uint32_t size;
+ struct ui_progress prog;
+
+ perf_tool__fill_defaults(tool);
+
+ page_offset = page_size * (data_offset / page_size);
+ file_offset = page_offset;
+ head = data_offset - page_offset;
+
+ if (data_size && (data_offset + data_size < file_size))
+ file_size = data_offset + data_size;
+
+ ui_progress__init(&prog, file_size, "Processing events...");
+
+ mmap_size = MMAP_SIZE;
+ if (mmap_size > file_size)
+ mmap_size = file_size;
+
+ memset(mmaps, 0, sizeof(mmaps));
+
+ mmap_prot = PROT_READ;
+ mmap_flags = MAP_SHARED;
+
+ if (session->header.needs_swap) {
+ mmap_prot |= PROT_WRITE;
+ mmap_flags = MAP_PRIVATE;
+ }
+remap:
+ buf = mmap(NULL, mmap_size, mmap_prot, mmap_flags, fd,
+ file_offset);
+ if (buf == MAP_FAILED) {
+ pr_err("failed to mmap file\n");
+ err = -errno;
+ goto out_err;
+ }
+ mmaps[map_idx] = buf;
+ map_idx = (map_idx + 1) & (ARRAY_SIZE(mmaps) - 1);
+ file_pos = file_offset + head;
+
+more:
+ event = fetch_mmaped_event(session, head, mmap_size, buf);
+ if (!event) {
+ if (mmaps[map_idx]) {
+ munmap(mmaps[map_idx], mmap_size);
+ mmaps[map_idx] = NULL;
+ }
+
+ page_offset = page_size * (head / page_size);
+ file_offset += page_offset;
+ head -= page_offset;
+ goto remap;
+ }
+
+ size = event->header.size;
+
+ if (size < sizeof(struct perf_event_header) ||
+ perf_session__process_event(session, event, tool, file_pos) < 0) {
+ pr_err("%#" PRIx64 " [%#x]: failed to process type: %d\n",
+ file_offset + head, event->header.size,
+ event->header.type);
+ err = -EINVAL;
+ goto out_err;
+ }
+
+ head += size;
+ file_pos += size;
+
+ ui_progress__update(&prog, size);
+
+ if (session_done())
+ goto out;
+
+ if (file_pos < file_size)
+ goto more;
+
+out:
+ /* do the final flush for ordered samples */
+ session->ordered_samples.next_flush = ULLONG_MAX;
+ err = flush_sample_queue(session, tool);
+out_err:
+ ui_progress__finish();
+ perf_session__warn_about_errors(session, tool);
+ perf_session_free_sample_buffers(session);
+ return err;
+}
+
+int perf_session__process_events(struct perf_session *session,
+ struct perf_tool *tool)
+{
+ u64 size = perf_data_file__size(session->file);
+ int err;
+
+ if (perf_session__register_idle_thread(session) == NULL)
+ return -ENOMEM;
+
+ if (!perf_data_file__is_pipe(session->file))
+ err = __perf_session__process_events(session,
+ session->header.data_offset,
+ session->header.data_size,
+ size, tool);
+ else
+ err = __perf_session__process_pipe_events(session, tool);
+
+ return err;
+}
+
+bool perf_session__has_traces(struct perf_session *session, const char *msg)
+{
+ struct perf_evsel *evsel;
+
+ evlist__for_each(session->evlist, evsel) {
+ if (evsel->attr.type == PERF_TYPE_TRACEPOINT)
+ return true;
+ }
+
+ pr_err("No trace sample to read. Did you call 'perf %s'?\n", msg);
+ return false;
+}
+
+int maps__set_kallsyms_ref_reloc_sym(struct map **maps,
+ const char *symbol_name, u64 addr)
+{
+ char *bracket;
+ enum map_type i;
+ struct ref_reloc_sym *ref;
+
+ ref = zalloc(sizeof(struct ref_reloc_sym));
+ if (ref == NULL)
+ return -ENOMEM;
+
+ ref->name = strdup(symbol_name);
+ if (ref->name == NULL) {
+ free(ref);
+ return -ENOMEM;
+ }
+
+ bracket = strchr(ref->name, ']');
+ if (bracket)
+ *bracket = '\0';
+
+ ref->addr = addr;
+
+ for (i = 0; i < MAP__NR_TYPES; ++i) {
+ struct kmap *kmap = map__kmap(maps[i]);
+ kmap->ref_reloc_sym = ref;
+ }
+
+ return 0;
+}
+
+size_t perf_session__fprintf_dsos(struct perf_session *session, FILE *fp)
+{
+ return machines__fprintf_dsos(&session->machines, fp);
+}
+
+size_t perf_session__fprintf_dsos_buildid(struct perf_session *session, FILE *fp,
+ bool (skip)(struct dso *dso, int parm), int parm)
+{
+ return machines__fprintf_dsos_buildid(&session->machines, fp, skip, parm);
+}
+
+size_t perf_session__fprintf_nr_events(struct perf_session *session, FILE *fp)
+{
+ struct perf_evsel *pos;
+ size_t ret = fprintf(fp, "Aggregated stats:\n");
+
+ ret += events_stats__fprintf(&session->stats, fp);
+
+ evlist__for_each(session->evlist, pos) {
+ ret += fprintf(fp, "%s stats:\n", perf_evsel__name(pos));
+ ret += events_stats__fprintf(&pos->hists.stats, fp);
+ }
+
+ return ret;
+}
+
+size_t perf_session__fprintf(struct perf_session *session, FILE *fp)
+{
+ /*
+ * FIXME: Here we have to actually print all the machines in this
+ * session, not just the host...
+ */
+ return machine__fprintf(&session->machines.host, fp);
+}
+
+struct perf_evsel *perf_session__find_first_evtype(struct perf_session *session,
+ unsigned int type)
+{
+ struct perf_evsel *pos;
+
+ evlist__for_each(session->evlist, pos) {
+ if (pos->attr.type == type)
+ return pos;
+ }
+ return NULL;
+}
+
+void perf_evsel__print_ip(struct perf_evsel *evsel, struct perf_sample *sample,
+ struct addr_location *al,
+ unsigned int print_opts, unsigned int stack_depth)
+{
+ struct callchain_cursor_node *node;
+ int print_ip = print_opts & PRINT_IP_OPT_IP;
+ int print_sym = print_opts & PRINT_IP_OPT_SYM;
+ int print_dso = print_opts & PRINT_IP_OPT_DSO;
+ int print_symoffset = print_opts & PRINT_IP_OPT_SYMOFFSET;
+ int print_oneline = print_opts & PRINT_IP_OPT_ONELINE;
+ int print_srcline = print_opts & PRINT_IP_OPT_SRCLINE;
+ char s = print_oneline ? ' ' : '\t';
+
+ if (symbol_conf.use_callchain && sample->callchain) {
+ struct addr_location node_al;
+
+ if (machine__resolve_callchain(al->machine, evsel, al->thread,
+ sample, NULL, NULL,
+ PERF_MAX_STACK_DEPTH) != 0) {
+ if (verbose)
+ error("Failed to resolve callchain. Skipping\n");
+ return;
+ }
+ callchain_cursor_commit(&callchain_cursor);
+
+ if (print_symoffset)
+ node_al = *al;
+
+ while (stack_depth) {
+ u64 addr = 0;
+
+ node = callchain_cursor_current(&callchain_cursor);
+ if (!node)
+ break;
+
+ if (node->sym && node->sym->ignore)
+ goto next;
+
+ if (print_ip)
+ printf("%c%16" PRIx64, s, node->ip);
+
+ if (node->map)
+ addr = node->map->map_ip(node->map, node->ip);
+
+ if (print_sym) {
+ printf(" ");
+ if (print_symoffset) {
+ node_al.addr = addr;
+ node_al.map = node->map;
+ symbol__fprintf_symname_offs(node->sym, &node_al, stdout);
+ } else
+ symbol__fprintf_symname(node->sym, stdout);
+ }
+
+ if (print_dso) {
+ printf(" (");
+ map__fprintf_dsoname(node->map, stdout);
+ printf(")");
+ }
+
+ if (print_srcline)
+ map__fprintf_srcline(node->map, addr, "\n ",
+ stdout);
+
+ if (!print_oneline)
+ printf("\n");
+
+ stack_depth--;
+next:
+ callchain_cursor_advance(&callchain_cursor);
+ }
+
+ } else {
+ if (al->sym && al->sym->ignore)
+ return;
+
+ if (print_ip)
+ printf("%16" PRIx64, sample->ip);
+
+ if (print_sym) {
+ printf(" ");
+ if (print_symoffset)
+ symbol__fprintf_symname_offs(al->sym, al,
+ stdout);
+ else
+ symbol__fprintf_symname(al->sym, stdout);
+ }
+
+ if (print_dso) {
+ printf(" (");
+ map__fprintf_dsoname(al->map, stdout);
+ printf(")");
+ }
+
+ if (print_srcline)
+ map__fprintf_srcline(al->map, al->addr, "\n ", stdout);
+ }
+}
+
+int perf_session__cpu_bitmap(struct perf_session *session,
+ const char *cpu_list, unsigned long *cpu_bitmap)
+{
+ int i, err = -1;
+ struct cpu_map *map;
+
+ for (i = 0; i < PERF_TYPE_MAX; ++i) {
+ struct perf_evsel *evsel;
+
+ evsel = perf_session__find_first_evtype(session, i);
+ if (!evsel)
+ continue;
+
+ if (!(evsel->attr.sample_type & PERF_SAMPLE_CPU)) {
+ pr_err("File does not contain CPU events. "
+ "Remove -c option to proceed.\n");
+ return -1;
+ }
+ }
+
+ map = cpu_map__new(cpu_list);
+ if (map == NULL) {
+ pr_err("Invalid cpu_list\n");
+ return -1;
+ }
+
+ for (i = 0; i < map->nr; i++) {
+ int cpu = map->map[i];
+
+ if (cpu >= MAX_NR_CPUS) {
+ pr_err("Requested CPU %d too large. "
+ "Consider raising MAX_NR_CPUS\n", cpu);
+ goto out_delete_map;
+ }
+
+ set_bit(cpu, cpu_bitmap);
+ }
+
+ err = 0;
+
+out_delete_map:
+ cpu_map__delete(map);
+ return err;
+}
+
+void perf_session__fprintf_info(struct perf_session *session, FILE *fp,
+ bool full)
+{
+ struct stat st;
+ int fd, ret;
+
+ if (session == NULL || fp == NULL)
+ return;
+
+ fd = perf_data_file__fd(session->file);
+
+ ret = fstat(fd, &st);
+ if (ret == -1)
+ return;
+
+ fprintf(fp, "# ========\n");
+ fprintf(fp, "# captured on: %s", ctime(&st.st_ctime));
+ perf_header__fprintf_info(session, fp, full);
+ fprintf(fp, "# ========\n#\n");
+}
+
+
+int __perf_session__set_tracepoints_handlers(struct perf_session *session,
+ const struct perf_evsel_str_handler *assocs,
+ size_t nr_assocs)
+{
+ struct perf_evsel *evsel;
+ size_t i;
+ int err;
+
+ for (i = 0; i < nr_assocs; i++) {
+ /*
+ * Adding a handler for an event not in the session,
+ * just ignore it.
+ */
+ evsel = perf_evlist__find_tracepoint_by_name(session->evlist, assocs[i].name);
+ if (evsel == NULL)
+ continue;
+
+ err = -EEXIST;
+ if (evsel->handler != NULL)
+ goto out;
+ evsel->handler = assocs[i].handler;
+ }
+
+ err = 0;
+out:
+ return err;
+}
diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h
new file mode 100644
index 00000000000..3140f8ae614
--- /dev/null
+++ b/tools/perf/util/session.h
@@ -0,0 +1,129 @@
+#ifndef __PERF_SESSION_H
+#define __PERF_SESSION_H
+
+#include "trace-event.h"
+#include "hist.h"
+#include "event.h"
+#include "header.h"
+#include "machine.h"
+#include "symbol.h"
+#include "thread.h"
+#include "data.h"
+#include <linux/rbtree.h>
+#include <linux/perf_event.h>
+
+struct sample_queue;
+struct ip_callchain;
+struct thread;
+
+struct ordered_samples {
+ u64 last_flush;
+ u64 next_flush;
+ u64 max_timestamp;
+ struct list_head samples;
+ struct list_head sample_cache;
+ struct list_head to_free;
+ struct sample_queue *sample_buffer;
+ struct sample_queue *last_sample;
+ int sample_buffer_idx;
+ unsigned int nr_samples;
+};
+
+struct perf_session {
+ struct perf_header header;
+ struct machines machines;
+ struct perf_evlist *evlist;
+ struct trace_event tevent;
+ struct events_stats stats;
+ bool repipe;
+ struct ordered_samples ordered_samples;
+ struct perf_data_file *file;
+};
+
+#define PRINT_IP_OPT_IP (1<<0)
+#define PRINT_IP_OPT_SYM (1<<1)
+#define PRINT_IP_OPT_DSO (1<<2)
+#define PRINT_IP_OPT_SYMOFFSET (1<<3)
+#define PRINT_IP_OPT_ONELINE (1<<4)
+#define PRINT_IP_OPT_SRCLINE (1<<5)
+
+struct perf_tool;
+
+struct perf_session *perf_session__new(struct perf_data_file *file,
+ bool repipe, struct perf_tool *tool);
+void perf_session__delete(struct perf_session *session);
+
+void perf_event_header__bswap(struct perf_event_header *hdr);
+
+int __perf_session__process_events(struct perf_session *session,
+ u64 data_offset, u64 data_size, u64 size,
+ struct perf_tool *tool);
+int perf_session__process_events(struct perf_session *session,
+ struct perf_tool *tool);
+
+int perf_session_queue_event(struct perf_session *s, union perf_event *event,
+ struct perf_sample *sample, u64 file_offset);
+
+void perf_tool__fill_defaults(struct perf_tool *tool);
+
+int perf_session__resolve_callchain(struct perf_session *session,
+ struct perf_evsel *evsel,
+ struct thread *thread,
+ struct ip_callchain *chain,
+ struct symbol **parent);
+
+bool perf_session__has_traces(struct perf_session *session, const char *msg);
+
+void perf_event__attr_swap(struct perf_event_attr *attr);
+
+int perf_session__create_kernel_maps(struct perf_session *session);
+
+void perf_session__set_id_hdr_size(struct perf_session *session);
+
+static inline
+struct machine *perf_session__find_machine(struct perf_session *session, pid_t pid)
+{
+ return machines__find(&session->machines, pid);
+}
+
+static inline
+struct machine *perf_session__findnew_machine(struct perf_session *session, pid_t pid)
+{
+ return machines__findnew(&session->machines, pid);
+}
+
+struct thread *perf_session__findnew(struct perf_session *session, pid_t pid);
+size_t perf_session__fprintf(struct perf_session *session, FILE *fp);
+
+size_t perf_session__fprintf_dsos(struct perf_session *session, FILE *fp);
+
+size_t perf_session__fprintf_dsos_buildid(struct perf_session *session, FILE *fp,
+ bool (fn)(struct dso *dso, int parm), int parm);
+
+size_t perf_session__fprintf_nr_events(struct perf_session *session, FILE *fp);
+
+struct perf_evsel *perf_session__find_first_evtype(struct perf_session *session,
+ unsigned int type);
+
+void perf_evsel__print_ip(struct perf_evsel *evsel, struct perf_sample *sample,
+ struct addr_location *al,
+ unsigned int print_opts, unsigned int stack_depth);
+
+int perf_session__cpu_bitmap(struct perf_session *session,
+ const char *cpu_list, unsigned long *cpu_bitmap);
+
+void perf_session__fprintf_info(struct perf_session *s, FILE *fp, bool full);
+
+struct perf_evsel_str_handler;
+
+int __perf_session__set_tracepoints_handlers(struct perf_session *session,
+ const struct perf_evsel_str_handler *assocs,
+ size_t nr_assocs);
+
+#define perf_session__set_tracepoints_handlers(session, array) \
+ __perf_session__set_tracepoints_handlers(session, array, ARRAY_SIZE(array))
+
+extern volatile int session_done;
+
+#define session_done() (*(volatile int *)(&session_done))
+#endif /* __PERF_SESSION_H */
diff --git a/tools/perf/util/setup.py b/tools/perf/util/setup.py
new file mode 100644
index 00000000000..d0aee4b9dfd
--- /dev/null
+++ b/tools/perf/util/setup.py
@@ -0,0 +1,48 @@
+#!/usr/bin/python2
+
+from distutils.core import setup, Extension
+from os import getenv
+
+from distutils.command.build_ext import build_ext as _build_ext
+from distutils.command.install_lib import install_lib as _install_lib
+
+class build_ext(_build_ext):
+ def finalize_options(self):
+ _build_ext.finalize_options(self)
+ self.build_lib = build_lib
+ self.build_temp = build_tmp
+
+class install_lib(_install_lib):
+ def finalize_options(self):
+ _install_lib.finalize_options(self)
+ self.build_dir = build_lib
+
+
+cflags = getenv('CFLAGS', '').split()
+# switch off several checks (need to be at the end of cflags list)
+cflags += ['-fno-strict-aliasing', '-Wno-write-strings', '-Wno-unused-parameter' ]
+
+build_lib = getenv('PYTHON_EXTBUILD_LIB')
+build_tmp = getenv('PYTHON_EXTBUILD_TMP')
+libtraceevent = getenv('LIBTRACEEVENT')
+libapikfs = getenv('LIBAPIKFS')
+
+ext_sources = [f.strip() for f in file('util/python-ext-sources')
+ if len(f.strip()) > 0 and f[0] != '#']
+
+perf = Extension('perf',
+ sources = ext_sources,
+ include_dirs = ['util/include'],
+ extra_compile_args = cflags,
+ extra_objects = [libtraceevent, libapikfs],
+ )
+
+setup(name='perf',
+ version='0.1',
+ description='Interface with the Linux profiling infrastructure',
+ author='Arnaldo Carvalho de Melo',
+ author_email='acme@redhat.com',
+ license='GPLv2',
+ url='http://perf.wiki.kernel.org',
+ ext_modules=[perf],
+ cmdclass={'build_ext': build_ext, 'install_lib': install_lib})
diff --git a/tools/perf/util/sigchain.c b/tools/perf/util/sigchain.c
new file mode 100644
index 00000000000..ba785e9b184
--- /dev/null
+++ b/tools/perf/util/sigchain.c
@@ -0,0 +1,52 @@
+#include "sigchain.h"
+#include "cache.h"
+
+#define SIGCHAIN_MAX_SIGNALS 32
+
+struct sigchain_signal {
+ sigchain_fun *old;
+ int n;
+ int alloc;
+};
+static struct sigchain_signal signals[SIGCHAIN_MAX_SIGNALS];
+
+static void check_signum(int sig)
+{
+ if (sig < 1 || sig >= SIGCHAIN_MAX_SIGNALS)
+ die("BUG: signal out of range: %d", sig);
+}
+
+static int sigchain_push(int sig, sigchain_fun f)
+{
+ struct sigchain_signal *s = signals + sig;
+ check_signum(sig);
+
+ ALLOC_GROW(s->old, s->n + 1, s->alloc);
+ s->old[s->n] = signal(sig, f);
+ if (s->old[s->n] == SIG_ERR)
+ return -1;
+ s->n++;
+ return 0;
+}
+
+int sigchain_pop(int sig)
+{
+ struct sigchain_signal *s = signals + sig;
+ check_signum(sig);
+ if (s->n < 1)
+ return 0;
+
+ if (signal(sig, s->old[s->n - 1]) == SIG_ERR)
+ return -1;
+ s->n--;
+ return 0;
+}
+
+void sigchain_push_common(sigchain_fun f)
+{
+ sigchain_push(SIGINT, f);
+ sigchain_push(SIGHUP, f);
+ sigchain_push(SIGTERM, f);
+ sigchain_push(SIGQUIT, f);
+ sigchain_push(SIGPIPE, f);
+}
diff --git a/tools/perf/util/sigchain.h b/tools/perf/util/sigchain.h
new file mode 100644
index 00000000000..959d64eb555
--- /dev/null
+++ b/tools/perf/util/sigchain.h
@@ -0,0 +1,10 @@
+#ifndef __PERF_SIGCHAIN_H
+#define __PERF_SIGCHAIN_H
+
+typedef void (*sigchain_fun)(int);
+
+int sigchain_pop(int sig);
+
+void sigchain_push_common(sigchain_fun f);
+
+#endif /* __PERF_SIGCHAIN_H */
diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c
new file mode 100644
index 00000000000..1ec57dd8228
--- /dev/null
+++ b/tools/perf/util/sort.c
@@ -0,0 +1,1706 @@
+#include <sys/mman.h>
+#include "sort.h"
+#include "hist.h"
+#include "comm.h"
+#include "symbol.h"
+#include "evsel.h"
+
+regex_t parent_regex;
+const char default_parent_pattern[] = "^sys_|^do_page_fault";
+const char *parent_pattern = default_parent_pattern;
+const char default_sort_order[] = "comm,dso,symbol";
+const char default_branch_sort_order[] = "comm,dso_from,symbol_from,dso_to,symbol_to";
+const char default_mem_sort_order[] = "local_weight,mem,sym,dso,symbol_daddr,dso_daddr,snoop,tlb,locked";
+const char default_top_sort_order[] = "dso,symbol";
+const char default_diff_sort_order[] = "dso,symbol";
+const char *sort_order;
+const char *field_order;
+regex_t ignore_callees_regex;
+int have_ignore_callees = 0;
+int sort__need_collapse = 0;
+int sort__has_parent = 0;
+int sort__has_sym = 0;
+int sort__has_dso = 0;
+enum sort_mode sort__mode = SORT_MODE__NORMAL;
+
+
+static int repsep_snprintf(char *bf, size_t size, const char *fmt, ...)
+{
+ int n;
+ va_list ap;
+
+ va_start(ap, fmt);
+ n = vsnprintf(bf, size, fmt, ap);
+ if (symbol_conf.field_sep && n > 0) {
+ char *sep = bf;
+
+ while (1) {
+ sep = strchr(sep, *symbol_conf.field_sep);
+ if (sep == NULL)
+ break;
+ *sep = '.';
+ }
+ }
+ va_end(ap);
+
+ if (n >= (int)size)
+ return size - 1;
+ return n;
+}
+
+static int64_t cmp_null(const void *l, const void *r)
+{
+ if (!l && !r)
+ return 0;
+ else if (!l)
+ return -1;
+ else
+ return 1;
+}
+
+/* --sort pid */
+
+static int64_t
+sort__thread_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ return right->thread->tid - left->thread->tid;
+}
+
+static int hist_entry__thread_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ const char *comm = thread__comm_str(he->thread);
+ return repsep_snprintf(bf, size, "%*s:%5d", width - 6,
+ comm ?: "", he->thread->tid);
+}
+
+struct sort_entry sort_thread = {
+ .se_header = "Command: Pid",
+ .se_cmp = sort__thread_cmp,
+ .se_snprintf = hist_entry__thread_snprintf,
+ .se_width_idx = HISTC_THREAD,
+};
+
+/* --sort comm */
+
+static int64_t
+sort__comm_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ /* Compare the addr that should be unique among comm */
+ return comm__str(right->comm) - comm__str(left->comm);
+}
+
+static int64_t
+sort__comm_collapse(struct hist_entry *left, struct hist_entry *right)
+{
+ /* Compare the addr that should be unique among comm */
+ return comm__str(right->comm) - comm__str(left->comm);
+}
+
+static int64_t
+sort__comm_sort(struct hist_entry *left, struct hist_entry *right)
+{
+ return strcmp(comm__str(right->comm), comm__str(left->comm));
+}
+
+static int hist_entry__comm_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ return repsep_snprintf(bf, size, "%*s", width, comm__str(he->comm));
+}
+
+struct sort_entry sort_comm = {
+ .se_header = "Command",
+ .se_cmp = sort__comm_cmp,
+ .se_collapse = sort__comm_collapse,
+ .se_sort = sort__comm_sort,
+ .se_snprintf = hist_entry__comm_snprintf,
+ .se_width_idx = HISTC_COMM,
+};
+
+/* --sort dso */
+
+static int64_t _sort__dso_cmp(struct map *map_l, struct map *map_r)
+{
+ struct dso *dso_l = map_l ? map_l->dso : NULL;
+ struct dso *dso_r = map_r ? map_r->dso : NULL;
+ const char *dso_name_l, *dso_name_r;
+
+ if (!dso_l || !dso_r)
+ return cmp_null(dso_r, dso_l);
+
+ if (verbose) {
+ dso_name_l = dso_l->long_name;
+ dso_name_r = dso_r->long_name;
+ } else {
+ dso_name_l = dso_l->short_name;
+ dso_name_r = dso_r->short_name;
+ }
+
+ return strcmp(dso_name_l, dso_name_r);
+}
+
+static int64_t
+sort__dso_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ return _sort__dso_cmp(right->ms.map, left->ms.map);
+}
+
+static int _hist_entry__dso_snprintf(struct map *map, char *bf,
+ size_t size, unsigned int width)
+{
+ if (map && map->dso) {
+ const char *dso_name = !verbose ? map->dso->short_name :
+ map->dso->long_name;
+ return repsep_snprintf(bf, size, "%-*s", width, dso_name);
+ }
+
+ return repsep_snprintf(bf, size, "%-*s", width, "[unknown]");
+}
+
+static int hist_entry__dso_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ return _hist_entry__dso_snprintf(he->ms.map, bf, size, width);
+}
+
+struct sort_entry sort_dso = {
+ .se_header = "Shared Object",
+ .se_cmp = sort__dso_cmp,
+ .se_snprintf = hist_entry__dso_snprintf,
+ .se_width_idx = HISTC_DSO,
+};
+
+/* --sort symbol */
+
+static int64_t _sort__addr_cmp(u64 left_ip, u64 right_ip)
+{
+ return (int64_t)(right_ip - left_ip);
+}
+
+static int64_t _sort__sym_cmp(struct symbol *sym_l, struct symbol *sym_r)
+{
+ u64 ip_l, ip_r;
+
+ if (!sym_l || !sym_r)
+ return cmp_null(sym_l, sym_r);
+
+ if (sym_l == sym_r)
+ return 0;
+
+ ip_l = sym_l->start;
+ ip_r = sym_r->start;
+
+ return (int64_t)(ip_r - ip_l);
+}
+
+static int64_t
+sort__sym_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ int64_t ret;
+
+ if (!left->ms.sym && !right->ms.sym)
+ return _sort__addr_cmp(left->ip, right->ip);
+
+ /*
+ * comparing symbol address alone is not enough since it's a
+ * relative address within a dso.
+ */
+ if (!sort__has_dso) {
+ ret = sort__dso_cmp(left, right);
+ if (ret != 0)
+ return ret;
+ }
+
+ return _sort__sym_cmp(left->ms.sym, right->ms.sym);
+}
+
+static int64_t
+sort__sym_sort(struct hist_entry *left, struct hist_entry *right)
+{
+ if (!left->ms.sym || !right->ms.sym)
+ return cmp_null(left->ms.sym, right->ms.sym);
+
+ return strcmp(right->ms.sym->name, left->ms.sym->name);
+}
+
+static int _hist_entry__sym_snprintf(struct map *map, struct symbol *sym,
+ u64 ip, char level, char *bf, size_t size,
+ unsigned int width)
+{
+ size_t ret = 0;
+
+ if (verbose) {
+ char o = map ? dso__symtab_origin(map->dso) : '!';
+ ret += repsep_snprintf(bf, size, "%-#*llx %c ",
+ BITS_PER_LONG / 4 + 2, ip, o);
+ }
+
+ ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", level);
+ if (sym && map) {
+ if (map->type == MAP__VARIABLE) {
+ ret += repsep_snprintf(bf + ret, size - ret, "%s", sym->name);
+ ret += repsep_snprintf(bf + ret, size - ret, "+0x%llx",
+ ip - map->unmap_ip(map, sym->start));
+ ret += repsep_snprintf(bf + ret, size - ret, "%-*s",
+ width - ret, "");
+ } else {
+ ret += repsep_snprintf(bf + ret, size - ret, "%-*s",
+ width - ret,
+ sym->name);
+ }
+ } else {
+ size_t len = BITS_PER_LONG / 4;
+ ret += repsep_snprintf(bf + ret, size - ret, "%-#.*llx",
+ len, ip);
+ ret += repsep_snprintf(bf + ret, size - ret, "%-*s",
+ width - ret, "");
+ }
+
+ return ret;
+}
+
+static int hist_entry__sym_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ return _hist_entry__sym_snprintf(he->ms.map, he->ms.sym, he->ip,
+ he->level, bf, size, width);
+}
+
+struct sort_entry sort_sym = {
+ .se_header = "Symbol",
+ .se_cmp = sort__sym_cmp,
+ .se_sort = sort__sym_sort,
+ .se_snprintf = hist_entry__sym_snprintf,
+ .se_width_idx = HISTC_SYMBOL,
+};
+
+/* --sort srcline */
+
+static int64_t
+sort__srcline_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ if (!left->srcline) {
+ if (!left->ms.map)
+ left->srcline = SRCLINE_UNKNOWN;
+ else {
+ struct map *map = left->ms.map;
+ left->srcline = get_srcline(map->dso,
+ map__rip_2objdump(map, left->ip));
+ }
+ }
+ if (!right->srcline) {
+ if (!right->ms.map)
+ right->srcline = SRCLINE_UNKNOWN;
+ else {
+ struct map *map = right->ms.map;
+ right->srcline = get_srcline(map->dso,
+ map__rip_2objdump(map, right->ip));
+ }
+ }
+ return strcmp(right->srcline, left->srcline);
+}
+
+static int hist_entry__srcline_snprintf(struct hist_entry *he, char *bf,
+ size_t size,
+ unsigned int width __maybe_unused)
+{
+ return repsep_snprintf(bf, size, "%s", he->srcline);
+}
+
+struct sort_entry sort_srcline = {
+ .se_header = "Source:Line",
+ .se_cmp = sort__srcline_cmp,
+ .se_snprintf = hist_entry__srcline_snprintf,
+ .se_width_idx = HISTC_SRCLINE,
+};
+
+/* --sort parent */
+
+static int64_t
+sort__parent_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ struct symbol *sym_l = left->parent;
+ struct symbol *sym_r = right->parent;
+
+ if (!sym_l || !sym_r)
+ return cmp_null(sym_l, sym_r);
+
+ return strcmp(sym_r->name, sym_l->name);
+}
+
+static int hist_entry__parent_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ return repsep_snprintf(bf, size, "%-*s", width,
+ he->parent ? he->parent->name : "[other]");
+}
+
+struct sort_entry sort_parent = {
+ .se_header = "Parent symbol",
+ .se_cmp = sort__parent_cmp,
+ .se_snprintf = hist_entry__parent_snprintf,
+ .se_width_idx = HISTC_PARENT,
+};
+
+/* --sort cpu */
+
+static int64_t
+sort__cpu_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ return right->cpu - left->cpu;
+}
+
+static int hist_entry__cpu_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ return repsep_snprintf(bf, size, "%*d", width, he->cpu);
+}
+
+struct sort_entry sort_cpu = {
+ .se_header = "CPU",
+ .se_cmp = sort__cpu_cmp,
+ .se_snprintf = hist_entry__cpu_snprintf,
+ .se_width_idx = HISTC_CPU,
+};
+
+/* sort keys for branch stacks */
+
+static int64_t
+sort__dso_from_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ return _sort__dso_cmp(left->branch_info->from.map,
+ right->branch_info->from.map);
+}
+
+static int hist_entry__dso_from_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ return _hist_entry__dso_snprintf(he->branch_info->from.map,
+ bf, size, width);
+}
+
+static int64_t
+sort__dso_to_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ return _sort__dso_cmp(left->branch_info->to.map,
+ right->branch_info->to.map);
+}
+
+static int hist_entry__dso_to_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ return _hist_entry__dso_snprintf(he->branch_info->to.map,
+ bf, size, width);
+}
+
+static int64_t
+sort__sym_from_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ struct addr_map_symbol *from_l = &left->branch_info->from;
+ struct addr_map_symbol *from_r = &right->branch_info->from;
+
+ if (!from_l->sym && !from_r->sym)
+ return _sort__addr_cmp(from_l->addr, from_r->addr);
+
+ return _sort__sym_cmp(from_l->sym, from_r->sym);
+}
+
+static int64_t
+sort__sym_to_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ struct addr_map_symbol *to_l = &left->branch_info->to;
+ struct addr_map_symbol *to_r = &right->branch_info->to;
+
+ if (!to_l->sym && !to_r->sym)
+ return _sort__addr_cmp(to_l->addr, to_r->addr);
+
+ return _sort__sym_cmp(to_l->sym, to_r->sym);
+}
+
+static int hist_entry__sym_from_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ struct addr_map_symbol *from = &he->branch_info->from;
+ return _hist_entry__sym_snprintf(from->map, from->sym, from->addr,
+ he->level, bf, size, width);
+
+}
+
+static int hist_entry__sym_to_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ struct addr_map_symbol *to = &he->branch_info->to;
+ return _hist_entry__sym_snprintf(to->map, to->sym, to->addr,
+ he->level, bf, size, width);
+
+}
+
+struct sort_entry sort_dso_from = {
+ .se_header = "Source Shared Object",
+ .se_cmp = sort__dso_from_cmp,
+ .se_snprintf = hist_entry__dso_from_snprintf,
+ .se_width_idx = HISTC_DSO_FROM,
+};
+
+struct sort_entry sort_dso_to = {
+ .se_header = "Target Shared Object",
+ .se_cmp = sort__dso_to_cmp,
+ .se_snprintf = hist_entry__dso_to_snprintf,
+ .se_width_idx = HISTC_DSO_TO,
+};
+
+struct sort_entry sort_sym_from = {
+ .se_header = "Source Symbol",
+ .se_cmp = sort__sym_from_cmp,
+ .se_snprintf = hist_entry__sym_from_snprintf,
+ .se_width_idx = HISTC_SYMBOL_FROM,
+};
+
+struct sort_entry sort_sym_to = {
+ .se_header = "Target Symbol",
+ .se_cmp = sort__sym_to_cmp,
+ .se_snprintf = hist_entry__sym_to_snprintf,
+ .se_width_idx = HISTC_SYMBOL_TO,
+};
+
+static int64_t
+sort__mispredict_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ const unsigned char mp = left->branch_info->flags.mispred !=
+ right->branch_info->flags.mispred;
+ const unsigned char p = left->branch_info->flags.predicted !=
+ right->branch_info->flags.predicted;
+
+ return mp || p;
+}
+
+static int hist_entry__mispredict_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width){
+ static const char *out = "N/A";
+
+ if (he->branch_info->flags.predicted)
+ out = "N";
+ else if (he->branch_info->flags.mispred)
+ out = "Y";
+
+ return repsep_snprintf(bf, size, "%-*s", width, out);
+}
+
+/* --sort daddr_sym */
+static int64_t
+sort__daddr_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ uint64_t l = 0, r = 0;
+
+ if (left->mem_info)
+ l = left->mem_info->daddr.addr;
+ if (right->mem_info)
+ r = right->mem_info->daddr.addr;
+
+ return (int64_t)(r - l);
+}
+
+static int hist_entry__daddr_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ uint64_t addr = 0;
+ struct map *map = NULL;
+ struct symbol *sym = NULL;
+
+ if (he->mem_info) {
+ addr = he->mem_info->daddr.addr;
+ map = he->mem_info->daddr.map;
+ sym = he->mem_info->daddr.sym;
+ }
+ return _hist_entry__sym_snprintf(map, sym, addr, he->level, bf, size,
+ width);
+}
+
+static int64_t
+sort__dso_daddr_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ struct map *map_l = NULL;
+ struct map *map_r = NULL;
+
+ if (left->mem_info)
+ map_l = left->mem_info->daddr.map;
+ if (right->mem_info)
+ map_r = right->mem_info->daddr.map;
+
+ return _sort__dso_cmp(map_l, map_r);
+}
+
+static int hist_entry__dso_daddr_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ struct map *map = NULL;
+
+ if (he->mem_info)
+ map = he->mem_info->daddr.map;
+
+ return _hist_entry__dso_snprintf(map, bf, size, width);
+}
+
+static int64_t
+sort__locked_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ union perf_mem_data_src data_src_l;
+ union perf_mem_data_src data_src_r;
+
+ if (left->mem_info)
+ data_src_l = left->mem_info->data_src;
+ else
+ data_src_l.mem_lock = PERF_MEM_LOCK_NA;
+
+ if (right->mem_info)
+ data_src_r = right->mem_info->data_src;
+ else
+ data_src_r.mem_lock = PERF_MEM_LOCK_NA;
+
+ return (int64_t)(data_src_r.mem_lock - data_src_l.mem_lock);
+}
+
+static int hist_entry__locked_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ const char *out;
+ u64 mask = PERF_MEM_LOCK_NA;
+
+ if (he->mem_info)
+ mask = he->mem_info->data_src.mem_lock;
+
+ if (mask & PERF_MEM_LOCK_NA)
+ out = "N/A";
+ else if (mask & PERF_MEM_LOCK_LOCKED)
+ out = "Yes";
+ else
+ out = "No";
+
+ return repsep_snprintf(bf, size, "%-*s", width, out);
+}
+
+static int64_t
+sort__tlb_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ union perf_mem_data_src data_src_l;
+ union perf_mem_data_src data_src_r;
+
+ if (left->mem_info)
+ data_src_l = left->mem_info->data_src;
+ else
+ data_src_l.mem_dtlb = PERF_MEM_TLB_NA;
+
+ if (right->mem_info)
+ data_src_r = right->mem_info->data_src;
+ else
+ data_src_r.mem_dtlb = PERF_MEM_TLB_NA;
+
+ return (int64_t)(data_src_r.mem_dtlb - data_src_l.mem_dtlb);
+}
+
+static const char * const tlb_access[] = {
+ "N/A",
+ "HIT",
+ "MISS",
+ "L1",
+ "L2",
+ "Walker",
+ "Fault",
+};
+#define NUM_TLB_ACCESS (sizeof(tlb_access)/sizeof(const char *))
+
+static int hist_entry__tlb_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ char out[64];
+ size_t sz = sizeof(out) - 1; /* -1 for null termination */
+ size_t l = 0, i;
+ u64 m = PERF_MEM_TLB_NA;
+ u64 hit, miss;
+
+ out[0] = '\0';
+
+ if (he->mem_info)
+ m = he->mem_info->data_src.mem_dtlb;
+
+ hit = m & PERF_MEM_TLB_HIT;
+ miss = m & PERF_MEM_TLB_MISS;
+
+ /* already taken care of */
+ m &= ~(PERF_MEM_TLB_HIT|PERF_MEM_TLB_MISS);
+
+ for (i = 0; m && i < NUM_TLB_ACCESS; i++, m >>= 1) {
+ if (!(m & 0x1))
+ continue;
+ if (l) {
+ strcat(out, " or ");
+ l += 4;
+ }
+ strncat(out, tlb_access[i], sz - l);
+ l += strlen(tlb_access[i]);
+ }
+ if (*out == '\0')
+ strcpy(out, "N/A");
+ if (hit)
+ strncat(out, " hit", sz - l);
+ if (miss)
+ strncat(out, " miss", sz - l);
+
+ return repsep_snprintf(bf, size, "%-*s", width, out);
+}
+
+static int64_t
+sort__lvl_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ union perf_mem_data_src data_src_l;
+ union perf_mem_data_src data_src_r;
+
+ if (left->mem_info)
+ data_src_l = left->mem_info->data_src;
+ else
+ data_src_l.mem_lvl = PERF_MEM_LVL_NA;
+
+ if (right->mem_info)
+ data_src_r = right->mem_info->data_src;
+ else
+ data_src_r.mem_lvl = PERF_MEM_LVL_NA;
+
+ return (int64_t)(data_src_r.mem_lvl - data_src_l.mem_lvl);
+}
+
+static const char * const mem_lvl[] = {
+ "N/A",
+ "HIT",
+ "MISS",
+ "L1",
+ "LFB",
+ "L2",
+ "L3",
+ "Local RAM",
+ "Remote RAM (1 hop)",
+ "Remote RAM (2 hops)",
+ "Remote Cache (1 hop)",
+ "Remote Cache (2 hops)",
+ "I/O",
+ "Uncached",
+};
+#define NUM_MEM_LVL (sizeof(mem_lvl)/sizeof(const char *))
+
+static int hist_entry__lvl_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ char out[64];
+ size_t sz = sizeof(out) - 1; /* -1 for null termination */
+ size_t i, l = 0;
+ u64 m = PERF_MEM_LVL_NA;
+ u64 hit, miss;
+
+ if (he->mem_info)
+ m = he->mem_info->data_src.mem_lvl;
+
+ out[0] = '\0';
+
+ hit = m & PERF_MEM_LVL_HIT;
+ miss = m & PERF_MEM_LVL_MISS;
+
+ /* already taken care of */
+ m &= ~(PERF_MEM_LVL_HIT|PERF_MEM_LVL_MISS);
+
+ for (i = 0; m && i < NUM_MEM_LVL; i++, m >>= 1) {
+ if (!(m & 0x1))
+ continue;
+ if (l) {
+ strcat(out, " or ");
+ l += 4;
+ }
+ strncat(out, mem_lvl[i], sz - l);
+ l += strlen(mem_lvl[i]);
+ }
+ if (*out == '\0')
+ strcpy(out, "N/A");
+ if (hit)
+ strncat(out, " hit", sz - l);
+ if (miss)
+ strncat(out, " miss", sz - l);
+
+ return repsep_snprintf(bf, size, "%-*s", width, out);
+}
+
+static int64_t
+sort__snoop_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ union perf_mem_data_src data_src_l;
+ union perf_mem_data_src data_src_r;
+
+ if (left->mem_info)
+ data_src_l = left->mem_info->data_src;
+ else
+ data_src_l.mem_snoop = PERF_MEM_SNOOP_NA;
+
+ if (right->mem_info)
+ data_src_r = right->mem_info->data_src;
+ else
+ data_src_r.mem_snoop = PERF_MEM_SNOOP_NA;
+
+ return (int64_t)(data_src_r.mem_snoop - data_src_l.mem_snoop);
+}
+
+static const char * const snoop_access[] = {
+ "N/A",
+ "None",
+ "Miss",
+ "Hit",
+ "HitM",
+};
+#define NUM_SNOOP_ACCESS (sizeof(snoop_access)/sizeof(const char *))
+
+static int hist_entry__snoop_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ char out[64];
+ size_t sz = sizeof(out) - 1; /* -1 for null termination */
+ size_t i, l = 0;
+ u64 m = PERF_MEM_SNOOP_NA;
+
+ out[0] = '\0';
+
+ if (he->mem_info)
+ m = he->mem_info->data_src.mem_snoop;
+
+ for (i = 0; m && i < NUM_SNOOP_ACCESS; i++, m >>= 1) {
+ if (!(m & 0x1))
+ continue;
+ if (l) {
+ strcat(out, " or ");
+ l += 4;
+ }
+ strncat(out, snoop_access[i], sz - l);
+ l += strlen(snoop_access[i]);
+ }
+
+ if (*out == '\0')
+ strcpy(out, "N/A");
+
+ return repsep_snprintf(bf, size, "%-*s", width, out);
+}
+
+static inline u64 cl_address(u64 address)
+{
+ /* return the cacheline of the address */
+ return (address & ~(cacheline_size - 1));
+}
+
+static int64_t
+sort__dcacheline_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ u64 l, r;
+ struct map *l_map, *r_map;
+
+ if (!left->mem_info) return -1;
+ if (!right->mem_info) return 1;
+
+ /* group event types together */
+ if (left->cpumode > right->cpumode) return -1;
+ if (left->cpumode < right->cpumode) return 1;
+
+ l_map = left->mem_info->daddr.map;
+ r_map = right->mem_info->daddr.map;
+
+ /* if both are NULL, jump to sort on al_addr instead */
+ if (!l_map && !r_map)
+ goto addr;
+
+ if (!l_map) return -1;
+ if (!r_map) return 1;
+
+ if (l_map->maj > r_map->maj) return -1;
+ if (l_map->maj < r_map->maj) return 1;
+
+ if (l_map->min > r_map->min) return -1;
+ if (l_map->min < r_map->min) return 1;
+
+ if (l_map->ino > r_map->ino) return -1;
+ if (l_map->ino < r_map->ino) return 1;
+
+ if (l_map->ino_generation > r_map->ino_generation) return -1;
+ if (l_map->ino_generation < r_map->ino_generation) return 1;
+
+ /*
+ * Addresses with no major/minor numbers are assumed to be
+ * anonymous in userspace. Sort those on pid then address.
+ *
+ * The kernel and non-zero major/minor mapped areas are
+ * assumed to be unity mapped. Sort those on address.
+ */
+
+ if ((left->cpumode != PERF_RECORD_MISC_KERNEL) &&
+ (!(l_map->flags & MAP_SHARED)) &&
+ !l_map->maj && !l_map->min && !l_map->ino &&
+ !l_map->ino_generation) {
+ /* userspace anonymous */
+
+ if (left->thread->pid_ > right->thread->pid_) return -1;
+ if (left->thread->pid_ < right->thread->pid_) return 1;
+ }
+
+addr:
+ /* al_addr does all the right addr - start + offset calculations */
+ l = cl_address(left->mem_info->daddr.al_addr);
+ r = cl_address(right->mem_info->daddr.al_addr);
+
+ if (l > r) return -1;
+ if (l < r) return 1;
+
+ return 0;
+}
+
+static int hist_entry__dcacheline_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+
+ uint64_t addr = 0;
+ struct map *map = NULL;
+ struct symbol *sym = NULL;
+ char level = he->level;
+
+ if (he->mem_info) {
+ addr = cl_address(he->mem_info->daddr.al_addr);
+ map = he->mem_info->daddr.map;
+ sym = he->mem_info->daddr.sym;
+
+ /* print [s] for shared data mmaps */
+ if ((he->cpumode != PERF_RECORD_MISC_KERNEL) &&
+ map && (map->type == MAP__VARIABLE) &&
+ (map->flags & MAP_SHARED) &&
+ (map->maj || map->min || map->ino ||
+ map->ino_generation))
+ level = 's';
+ else if (!map)
+ level = 'X';
+ }
+ return _hist_entry__sym_snprintf(map, sym, addr, level, bf, size,
+ width);
+}
+
+struct sort_entry sort_mispredict = {
+ .se_header = "Branch Mispredicted",
+ .se_cmp = sort__mispredict_cmp,
+ .se_snprintf = hist_entry__mispredict_snprintf,
+ .se_width_idx = HISTC_MISPREDICT,
+};
+
+static u64 he_weight(struct hist_entry *he)
+{
+ return he->stat.nr_events ? he->stat.weight / he->stat.nr_events : 0;
+}
+
+static int64_t
+sort__local_weight_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ return he_weight(left) - he_weight(right);
+}
+
+static int hist_entry__local_weight_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ return repsep_snprintf(bf, size, "%-*llu", width, he_weight(he));
+}
+
+struct sort_entry sort_local_weight = {
+ .se_header = "Local Weight",
+ .se_cmp = sort__local_weight_cmp,
+ .se_snprintf = hist_entry__local_weight_snprintf,
+ .se_width_idx = HISTC_LOCAL_WEIGHT,
+};
+
+static int64_t
+sort__global_weight_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ return left->stat.weight - right->stat.weight;
+}
+
+static int hist_entry__global_weight_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ return repsep_snprintf(bf, size, "%-*llu", width, he->stat.weight);
+}
+
+struct sort_entry sort_global_weight = {
+ .se_header = "Weight",
+ .se_cmp = sort__global_weight_cmp,
+ .se_snprintf = hist_entry__global_weight_snprintf,
+ .se_width_idx = HISTC_GLOBAL_WEIGHT,
+};
+
+struct sort_entry sort_mem_daddr_sym = {
+ .se_header = "Data Symbol",
+ .se_cmp = sort__daddr_cmp,
+ .se_snprintf = hist_entry__daddr_snprintf,
+ .se_width_idx = HISTC_MEM_DADDR_SYMBOL,
+};
+
+struct sort_entry sort_mem_daddr_dso = {
+ .se_header = "Data Object",
+ .se_cmp = sort__dso_daddr_cmp,
+ .se_snprintf = hist_entry__dso_daddr_snprintf,
+ .se_width_idx = HISTC_MEM_DADDR_SYMBOL,
+};
+
+struct sort_entry sort_mem_locked = {
+ .se_header = "Locked",
+ .se_cmp = sort__locked_cmp,
+ .se_snprintf = hist_entry__locked_snprintf,
+ .se_width_idx = HISTC_MEM_LOCKED,
+};
+
+struct sort_entry sort_mem_tlb = {
+ .se_header = "TLB access",
+ .se_cmp = sort__tlb_cmp,
+ .se_snprintf = hist_entry__tlb_snprintf,
+ .se_width_idx = HISTC_MEM_TLB,
+};
+
+struct sort_entry sort_mem_lvl = {
+ .se_header = "Memory access",
+ .se_cmp = sort__lvl_cmp,
+ .se_snprintf = hist_entry__lvl_snprintf,
+ .se_width_idx = HISTC_MEM_LVL,
+};
+
+struct sort_entry sort_mem_snoop = {
+ .se_header = "Snoop",
+ .se_cmp = sort__snoop_cmp,
+ .se_snprintf = hist_entry__snoop_snprintf,
+ .se_width_idx = HISTC_MEM_SNOOP,
+};
+
+struct sort_entry sort_mem_dcacheline = {
+ .se_header = "Data Cacheline",
+ .se_cmp = sort__dcacheline_cmp,
+ .se_snprintf = hist_entry__dcacheline_snprintf,
+ .se_width_idx = HISTC_MEM_DCACHELINE,
+};
+
+static int64_t
+sort__abort_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ return left->branch_info->flags.abort !=
+ right->branch_info->flags.abort;
+}
+
+static int hist_entry__abort_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ static const char *out = ".";
+
+ if (he->branch_info->flags.abort)
+ out = "A";
+ return repsep_snprintf(bf, size, "%-*s", width, out);
+}
+
+struct sort_entry sort_abort = {
+ .se_header = "Transaction abort",
+ .se_cmp = sort__abort_cmp,
+ .se_snprintf = hist_entry__abort_snprintf,
+ .se_width_idx = HISTC_ABORT,
+};
+
+static int64_t
+sort__in_tx_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ return left->branch_info->flags.in_tx !=
+ right->branch_info->flags.in_tx;
+}
+
+static int hist_entry__in_tx_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ static const char *out = ".";
+
+ if (he->branch_info->flags.in_tx)
+ out = "T";
+
+ return repsep_snprintf(bf, size, "%-*s", width, out);
+}
+
+struct sort_entry sort_in_tx = {
+ .se_header = "Branch in transaction",
+ .se_cmp = sort__in_tx_cmp,
+ .se_snprintf = hist_entry__in_tx_snprintf,
+ .se_width_idx = HISTC_IN_TX,
+};
+
+static int64_t
+sort__transaction_cmp(struct hist_entry *left, struct hist_entry *right)
+{
+ return left->transaction - right->transaction;
+}
+
+static inline char *add_str(char *p, const char *str)
+{
+ strcpy(p, str);
+ return p + strlen(str);
+}
+
+static struct txbit {
+ unsigned flag;
+ const char *name;
+ int skip_for_len;
+} txbits[] = {
+ { PERF_TXN_ELISION, "EL ", 0 },
+ { PERF_TXN_TRANSACTION, "TX ", 1 },
+ { PERF_TXN_SYNC, "SYNC ", 1 },
+ { PERF_TXN_ASYNC, "ASYNC ", 0 },
+ { PERF_TXN_RETRY, "RETRY ", 0 },
+ { PERF_TXN_CONFLICT, "CON ", 0 },
+ { PERF_TXN_CAPACITY_WRITE, "CAP-WRITE ", 1 },
+ { PERF_TXN_CAPACITY_READ, "CAP-READ ", 0 },
+ { 0, NULL, 0 }
+};
+
+int hist_entry__transaction_len(void)
+{
+ int i;
+ int len = 0;
+
+ for (i = 0; txbits[i].name; i++) {
+ if (!txbits[i].skip_for_len)
+ len += strlen(txbits[i].name);
+ }
+ len += 4; /* :XX<space> */
+ return len;
+}
+
+static int hist_entry__transaction_snprintf(struct hist_entry *he, char *bf,
+ size_t size, unsigned int width)
+{
+ u64 t = he->transaction;
+ char buf[128];
+ char *p = buf;
+ int i;
+
+ buf[0] = 0;
+ for (i = 0; txbits[i].name; i++)
+ if (txbits[i].flag & t)
+ p = add_str(p, txbits[i].name);
+ if (t && !(t & (PERF_TXN_SYNC|PERF_TXN_ASYNC)))
+ p = add_str(p, "NEITHER ");
+ if (t & PERF_TXN_ABORT_MASK) {
+ sprintf(p, ":%" PRIx64,
+ (t & PERF_TXN_ABORT_MASK) >>
+ PERF_TXN_ABORT_SHIFT);
+ p += strlen(p);
+ }
+
+ return repsep_snprintf(bf, size, "%-*s", width, buf);
+}
+
+struct sort_entry sort_transaction = {
+ .se_header = "Transaction ",
+ .se_cmp = sort__transaction_cmp,
+ .se_snprintf = hist_entry__transaction_snprintf,
+ .se_width_idx = HISTC_TRANSACTION,
+};
+
+struct sort_dimension {
+ const char *name;
+ struct sort_entry *entry;
+ int taken;
+};
+
+#define DIM(d, n, func) [d] = { .name = n, .entry = &(func) }
+
+static struct sort_dimension common_sort_dimensions[] = {
+ DIM(SORT_PID, "pid", sort_thread),
+ DIM(SORT_COMM, "comm", sort_comm),
+ DIM(SORT_DSO, "dso", sort_dso),
+ DIM(SORT_SYM, "symbol", sort_sym),
+ DIM(SORT_PARENT, "parent", sort_parent),
+ DIM(SORT_CPU, "cpu", sort_cpu),
+ DIM(SORT_SRCLINE, "srcline", sort_srcline),
+ DIM(SORT_LOCAL_WEIGHT, "local_weight", sort_local_weight),
+ DIM(SORT_GLOBAL_WEIGHT, "weight", sort_global_weight),
+ DIM(SORT_TRANSACTION, "transaction", sort_transaction),
+};
+
+#undef DIM
+
+#define DIM(d, n, func) [d - __SORT_BRANCH_STACK] = { .name = n, .entry = &(func) }
+
+static struct sort_dimension bstack_sort_dimensions[] = {
+ DIM(SORT_DSO_FROM, "dso_from", sort_dso_from),
+ DIM(SORT_DSO_TO, "dso_to", sort_dso_to),
+ DIM(SORT_SYM_FROM, "symbol_from", sort_sym_from),
+ DIM(SORT_SYM_TO, "symbol_to", sort_sym_to),
+ DIM(SORT_MISPREDICT, "mispredict", sort_mispredict),
+ DIM(SORT_IN_TX, "in_tx", sort_in_tx),
+ DIM(SORT_ABORT, "abort", sort_abort),
+};
+
+#undef DIM
+
+#define DIM(d, n, func) [d - __SORT_MEMORY_MODE] = { .name = n, .entry = &(func) }
+
+static struct sort_dimension memory_sort_dimensions[] = {
+ DIM(SORT_MEM_DADDR_SYMBOL, "symbol_daddr", sort_mem_daddr_sym),
+ DIM(SORT_MEM_DADDR_DSO, "dso_daddr", sort_mem_daddr_dso),
+ DIM(SORT_MEM_LOCKED, "locked", sort_mem_locked),
+ DIM(SORT_MEM_TLB, "tlb", sort_mem_tlb),
+ DIM(SORT_MEM_LVL, "mem", sort_mem_lvl),
+ DIM(SORT_MEM_SNOOP, "snoop", sort_mem_snoop),
+ DIM(SORT_MEM_DCACHELINE, "dcacheline", sort_mem_dcacheline),
+};
+
+#undef DIM
+
+struct hpp_dimension {
+ const char *name;
+ struct perf_hpp_fmt *fmt;
+ int taken;
+};
+
+#define DIM(d, n) { .name = n, .fmt = &perf_hpp__format[d], }
+
+static struct hpp_dimension hpp_sort_dimensions[] = {
+ DIM(PERF_HPP__OVERHEAD, "overhead"),
+ DIM(PERF_HPP__OVERHEAD_SYS, "overhead_sys"),
+ DIM(PERF_HPP__OVERHEAD_US, "overhead_us"),
+ DIM(PERF_HPP__OVERHEAD_GUEST_SYS, "overhead_guest_sys"),
+ DIM(PERF_HPP__OVERHEAD_GUEST_US, "overhead_guest_us"),
+ DIM(PERF_HPP__OVERHEAD_ACC, "overhead_children"),
+ DIM(PERF_HPP__SAMPLES, "sample"),
+ DIM(PERF_HPP__PERIOD, "period"),
+};
+
+#undef DIM
+
+struct hpp_sort_entry {
+ struct perf_hpp_fmt hpp;
+ struct sort_entry *se;
+};
+
+bool perf_hpp__same_sort_entry(struct perf_hpp_fmt *a, struct perf_hpp_fmt *b)
+{
+ struct hpp_sort_entry *hse_a;
+ struct hpp_sort_entry *hse_b;
+
+ if (!perf_hpp__is_sort_entry(a) || !perf_hpp__is_sort_entry(b))
+ return false;
+
+ hse_a = container_of(a, struct hpp_sort_entry, hpp);
+ hse_b = container_of(b, struct hpp_sort_entry, hpp);
+
+ return hse_a->se == hse_b->se;
+}
+
+void perf_hpp__reset_width(struct perf_hpp_fmt *fmt, struct hists *hists)
+{
+ struct hpp_sort_entry *hse;
+
+ if (!perf_hpp__is_sort_entry(fmt))
+ return;
+
+ hse = container_of(fmt, struct hpp_sort_entry, hpp);
+ hists__new_col_len(hists, hse->se->se_width_idx,
+ strlen(hse->se->se_header));
+}
+
+static int __sort__hpp_header(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct perf_evsel *evsel)
+{
+ struct hpp_sort_entry *hse;
+ size_t len;
+
+ hse = container_of(fmt, struct hpp_sort_entry, hpp);
+ len = hists__col_len(&evsel->hists, hse->se->se_width_idx);
+
+ return scnprintf(hpp->buf, hpp->size, "%*s", len, hse->se->se_header);
+}
+
+static int __sort__hpp_width(struct perf_hpp_fmt *fmt,
+ struct perf_hpp *hpp __maybe_unused,
+ struct perf_evsel *evsel)
+{
+ struct hpp_sort_entry *hse;
+
+ hse = container_of(fmt, struct hpp_sort_entry, hpp);
+
+ return hists__col_len(&evsel->hists, hse->se->se_width_idx);
+}
+
+static int __sort__hpp_entry(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp,
+ struct hist_entry *he)
+{
+ struct hpp_sort_entry *hse;
+ size_t len;
+
+ hse = container_of(fmt, struct hpp_sort_entry, hpp);
+ len = hists__col_len(he->hists, hse->se->se_width_idx);
+
+ return hse->se->se_snprintf(he, hpp->buf, hpp->size, len);
+}
+
+static struct hpp_sort_entry *
+__sort_dimension__alloc_hpp(struct sort_dimension *sd)
+{
+ struct hpp_sort_entry *hse;
+
+ hse = malloc(sizeof(*hse));
+ if (hse == NULL) {
+ pr_err("Memory allocation failed\n");
+ return NULL;
+ }
+
+ hse->se = sd->entry;
+ hse->hpp.header = __sort__hpp_header;
+ hse->hpp.width = __sort__hpp_width;
+ hse->hpp.entry = __sort__hpp_entry;
+ hse->hpp.color = NULL;
+
+ hse->hpp.cmp = sd->entry->se_cmp;
+ hse->hpp.collapse = sd->entry->se_collapse ? : sd->entry->se_cmp;
+ hse->hpp.sort = sd->entry->se_sort ? : hse->hpp.collapse;
+
+ INIT_LIST_HEAD(&hse->hpp.list);
+ INIT_LIST_HEAD(&hse->hpp.sort_list);
+ hse->hpp.elide = false;
+
+ return hse;
+}
+
+bool perf_hpp__is_sort_entry(struct perf_hpp_fmt *format)
+{
+ return format->header == __sort__hpp_header;
+}
+
+static int __sort_dimension__add_hpp_sort(struct sort_dimension *sd)
+{
+ struct hpp_sort_entry *hse = __sort_dimension__alloc_hpp(sd);
+
+ if (hse == NULL)
+ return -1;
+
+ perf_hpp__register_sort_field(&hse->hpp);
+ return 0;
+}
+
+static int __sort_dimension__add_hpp_output(struct sort_dimension *sd)
+{
+ struct hpp_sort_entry *hse = __sort_dimension__alloc_hpp(sd);
+
+ if (hse == NULL)
+ return -1;
+
+ perf_hpp__column_register(&hse->hpp);
+ return 0;
+}
+
+static int __sort_dimension__add(struct sort_dimension *sd)
+{
+ if (sd->taken)
+ return 0;
+
+ if (__sort_dimension__add_hpp_sort(sd) < 0)
+ return -1;
+
+ if (sd->entry->se_collapse)
+ sort__need_collapse = 1;
+
+ sd->taken = 1;
+
+ return 0;
+}
+
+static int __hpp_dimension__add(struct hpp_dimension *hd)
+{
+ if (!hd->taken) {
+ hd->taken = 1;
+
+ perf_hpp__register_sort_field(hd->fmt);
+ }
+ return 0;
+}
+
+static int __sort_dimension__add_output(struct sort_dimension *sd)
+{
+ if (sd->taken)
+ return 0;
+
+ if (__sort_dimension__add_hpp_output(sd) < 0)
+ return -1;
+
+ sd->taken = 1;
+ return 0;
+}
+
+static int __hpp_dimension__add_output(struct hpp_dimension *hd)
+{
+ if (!hd->taken) {
+ hd->taken = 1;
+
+ perf_hpp__column_register(hd->fmt);
+ }
+ return 0;
+}
+
+int sort_dimension__add(const char *tok)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(common_sort_dimensions); i++) {
+ struct sort_dimension *sd = &common_sort_dimensions[i];
+
+ if (strncasecmp(tok, sd->name, strlen(tok)))
+ continue;
+
+ if (sd->entry == &sort_parent) {
+ int ret = regcomp(&parent_regex, parent_pattern, REG_EXTENDED);
+ if (ret) {
+ char err[BUFSIZ];
+
+ regerror(ret, &parent_regex, err, sizeof(err));
+ pr_err("Invalid regex: %s\n%s", parent_pattern, err);
+ return -EINVAL;
+ }
+ sort__has_parent = 1;
+ } else if (sd->entry == &sort_sym) {
+ sort__has_sym = 1;
+ } else if (sd->entry == &sort_dso) {
+ sort__has_dso = 1;
+ }
+
+ return __sort_dimension__add(sd);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(hpp_sort_dimensions); i++) {
+ struct hpp_dimension *hd = &hpp_sort_dimensions[i];
+
+ if (strncasecmp(tok, hd->name, strlen(tok)))
+ continue;
+
+ return __hpp_dimension__add(hd);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(bstack_sort_dimensions); i++) {
+ struct sort_dimension *sd = &bstack_sort_dimensions[i];
+
+ if (strncasecmp(tok, sd->name, strlen(tok)))
+ continue;
+
+ if (sort__mode != SORT_MODE__BRANCH)
+ return -EINVAL;
+
+ if (sd->entry == &sort_sym_from || sd->entry == &sort_sym_to)
+ sort__has_sym = 1;
+
+ __sort_dimension__add(sd);
+ return 0;
+ }
+
+ for (i = 0; i < ARRAY_SIZE(memory_sort_dimensions); i++) {
+ struct sort_dimension *sd = &memory_sort_dimensions[i];
+
+ if (strncasecmp(tok, sd->name, strlen(tok)))
+ continue;
+
+ if (sort__mode != SORT_MODE__MEMORY)
+ return -EINVAL;
+
+ if (sd->entry == &sort_mem_daddr_sym)
+ sort__has_sym = 1;
+
+ __sort_dimension__add(sd);
+ return 0;
+ }
+
+ return -ESRCH;
+}
+
+static const char *get_default_sort_order(void)
+{
+ const char *default_sort_orders[] = {
+ default_sort_order,
+ default_branch_sort_order,
+ default_mem_sort_order,
+ default_top_sort_order,
+ default_diff_sort_order,
+ };
+
+ BUG_ON(sort__mode >= ARRAY_SIZE(default_sort_orders));
+
+ return default_sort_orders[sort__mode];
+}
+
+static int __setup_sorting(void)
+{
+ char *tmp, *tok, *str;
+ const char *sort_keys = sort_order;
+ int ret = 0;
+
+ if (sort_keys == NULL) {
+ if (field_order) {
+ /*
+ * If user specified field order but no sort order,
+ * we'll honor it and not add default sort orders.
+ */
+ return 0;
+ }
+
+ sort_keys = get_default_sort_order();
+ }
+
+ str = strdup(sort_keys);
+ if (str == NULL) {
+ error("Not enough memory to setup sort keys");
+ return -ENOMEM;
+ }
+
+ for (tok = strtok_r(str, ", ", &tmp);
+ tok; tok = strtok_r(NULL, ", ", &tmp)) {
+ ret = sort_dimension__add(tok);
+ if (ret == -EINVAL) {
+ error("Invalid --sort key: `%s'", tok);
+ break;
+ } else if (ret == -ESRCH) {
+ error("Unknown --sort key: `%s'", tok);
+ break;
+ }
+ }
+
+ free(str);
+ return ret;
+}
+
+void perf_hpp__set_elide(int idx, bool elide)
+{
+ struct perf_hpp_fmt *fmt;
+ struct hpp_sort_entry *hse;
+
+ perf_hpp__for_each_format(fmt) {
+ if (!perf_hpp__is_sort_entry(fmt))
+ continue;
+
+ hse = container_of(fmt, struct hpp_sort_entry, hpp);
+ if (hse->se->se_width_idx == idx) {
+ fmt->elide = elide;
+ break;
+ }
+ }
+}
+
+static bool __get_elide(struct strlist *list, const char *list_name, FILE *fp)
+{
+ if (list && strlist__nr_entries(list) == 1) {
+ if (fp != NULL)
+ fprintf(fp, "# %s: %s\n", list_name,
+ strlist__entry(list, 0)->s);
+ return true;
+ }
+ return false;
+}
+
+static bool get_elide(int idx, FILE *output)
+{
+ switch (idx) {
+ case HISTC_SYMBOL:
+ return __get_elide(symbol_conf.sym_list, "symbol", output);
+ case HISTC_DSO:
+ return __get_elide(symbol_conf.dso_list, "dso", output);
+ case HISTC_COMM:
+ return __get_elide(symbol_conf.comm_list, "comm", output);
+ default:
+ break;
+ }
+
+ if (sort__mode != SORT_MODE__BRANCH)
+ return false;
+
+ switch (idx) {
+ case HISTC_SYMBOL_FROM:
+ return __get_elide(symbol_conf.sym_from_list, "sym_from", output);
+ case HISTC_SYMBOL_TO:
+ return __get_elide(symbol_conf.sym_to_list, "sym_to", output);
+ case HISTC_DSO_FROM:
+ return __get_elide(symbol_conf.dso_from_list, "dso_from", output);
+ case HISTC_DSO_TO:
+ return __get_elide(symbol_conf.dso_to_list, "dso_to", output);
+ default:
+ break;
+ }
+
+ return false;
+}
+
+void sort__setup_elide(FILE *output)
+{
+ struct perf_hpp_fmt *fmt;
+ struct hpp_sort_entry *hse;
+
+ perf_hpp__for_each_format(fmt) {
+ if (!perf_hpp__is_sort_entry(fmt))
+ continue;
+
+ hse = container_of(fmt, struct hpp_sort_entry, hpp);
+ fmt->elide = get_elide(hse->se->se_width_idx, output);
+ }
+
+ /*
+ * It makes no sense to elide all of sort entries.
+ * Just revert them to show up again.
+ */
+ perf_hpp__for_each_format(fmt) {
+ if (!perf_hpp__is_sort_entry(fmt))
+ continue;
+
+ if (!fmt->elide)
+ return;
+ }
+
+ perf_hpp__for_each_format(fmt) {
+ if (!perf_hpp__is_sort_entry(fmt))
+ continue;
+
+ fmt->elide = false;
+ }
+}
+
+static int output_field_add(char *tok)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(common_sort_dimensions); i++) {
+ struct sort_dimension *sd = &common_sort_dimensions[i];
+
+ if (strncasecmp(tok, sd->name, strlen(tok)))
+ continue;
+
+ return __sort_dimension__add_output(sd);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(hpp_sort_dimensions); i++) {
+ struct hpp_dimension *hd = &hpp_sort_dimensions[i];
+
+ if (strncasecmp(tok, hd->name, strlen(tok)))
+ continue;
+
+ return __hpp_dimension__add_output(hd);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(bstack_sort_dimensions); i++) {
+ struct sort_dimension *sd = &bstack_sort_dimensions[i];
+
+ if (strncasecmp(tok, sd->name, strlen(tok)))
+ continue;
+
+ return __sort_dimension__add_output(sd);
+ }
+
+ for (i = 0; i < ARRAY_SIZE(memory_sort_dimensions); i++) {
+ struct sort_dimension *sd = &memory_sort_dimensions[i];
+
+ if (strncasecmp(tok, sd->name, strlen(tok)))
+ continue;
+
+ return __sort_dimension__add_output(sd);
+ }
+
+ return -ESRCH;
+}
+
+static void reset_dimensions(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < ARRAY_SIZE(common_sort_dimensions); i++)
+ common_sort_dimensions[i].taken = 0;
+
+ for (i = 0; i < ARRAY_SIZE(hpp_sort_dimensions); i++)
+ hpp_sort_dimensions[i].taken = 0;
+
+ for (i = 0; i < ARRAY_SIZE(bstack_sort_dimensions); i++)
+ bstack_sort_dimensions[i].taken = 0;
+
+ for (i = 0; i < ARRAY_SIZE(memory_sort_dimensions); i++)
+ memory_sort_dimensions[i].taken = 0;
+}
+
+static int __setup_output_field(void)
+{
+ char *tmp, *tok, *str;
+ int ret = 0;
+
+ if (field_order == NULL)
+ return 0;
+
+ reset_dimensions();
+
+ str = strdup(field_order);
+ if (str == NULL) {
+ error("Not enough memory to setup output fields");
+ return -ENOMEM;
+ }
+
+ for (tok = strtok_r(str, ", ", &tmp);
+ tok; tok = strtok_r(NULL, ", ", &tmp)) {
+ ret = output_field_add(tok);
+ if (ret == -EINVAL) {
+ error("Invalid --fields key: `%s'", tok);
+ break;
+ } else if (ret == -ESRCH) {
+ error("Unknown --fields key: `%s'", tok);
+ break;
+ }
+ }
+
+ free(str);
+ return ret;
+}
+
+int setup_sorting(void)
+{
+ int err;
+
+ err = __setup_sorting();
+ if (err < 0)
+ return err;
+
+ if (parent_pattern != default_parent_pattern) {
+ err = sort_dimension__add("parent");
+ if (err < 0)
+ return err;
+ }
+
+ reset_dimensions();
+
+ /*
+ * perf diff doesn't use default hpp output fields.
+ */
+ if (sort__mode != SORT_MODE__DIFF)
+ perf_hpp__init();
+
+ err = __setup_output_field();
+ if (err < 0)
+ return err;
+
+ /* copy sort keys to output fields */
+ perf_hpp__setup_output_field();
+ /* and then copy output fields to sort keys */
+ perf_hpp__append_sort_keys();
+
+ return 0;
+}
+
+void reset_output_field(void)
+{
+ sort__need_collapse = 0;
+ sort__has_parent = 0;
+ sort__has_sym = 0;
+ sort__has_dso = 0;
+
+ field_order = NULL;
+ sort_order = NULL;
+
+ reset_dimensions();
+ perf_hpp__reset_output_field();
+}
diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h
new file mode 100644
index 00000000000..041f0c9cea2
--- /dev/null
+++ b/tools/perf/util/sort.h
@@ -0,0 +1,221 @@
+#ifndef __PERF_SORT_H
+#define __PERF_SORT_H
+#include "../builtin.h"
+
+#include "util.h"
+
+#include "color.h"
+#include <linux/list.h>
+#include "cache.h"
+#include <linux/rbtree.h>
+#include "symbol.h"
+#include "string.h"
+#include "callchain.h"
+#include "strlist.h"
+#include "values.h"
+
+#include "../perf.h"
+#include "debug.h"
+#include "header.h"
+
+#include "parse-options.h"
+#include "parse-events.h"
+#include "hist.h"
+#include "thread.h"
+
+extern regex_t parent_regex;
+extern const char *sort_order;
+extern const char *field_order;
+extern const char default_parent_pattern[];
+extern const char *parent_pattern;
+extern const char default_sort_order[];
+extern regex_t ignore_callees_regex;
+extern int have_ignore_callees;
+extern int sort__need_collapse;
+extern int sort__has_parent;
+extern int sort__has_sym;
+extern enum sort_mode sort__mode;
+extern struct sort_entry sort_comm;
+extern struct sort_entry sort_dso;
+extern struct sort_entry sort_sym;
+extern struct sort_entry sort_parent;
+extern struct sort_entry sort_dso_from;
+extern struct sort_entry sort_dso_to;
+extern struct sort_entry sort_sym_from;
+extern struct sort_entry sort_sym_to;
+extern enum sort_type sort__first_dimension;
+
+struct he_stat {
+ u64 period;
+ u64 period_sys;
+ u64 period_us;
+ u64 period_guest_sys;
+ u64 period_guest_us;
+ u64 weight;
+ u32 nr_events;
+};
+
+struct hist_entry_diff {
+ bool computed;
+
+ /* PERF_HPP__DELTA */
+ double period_ratio_delta;
+
+ /* PERF_HPP__RATIO */
+ double period_ratio;
+
+ /* HISTC_WEIGHTED_DIFF */
+ s64 wdiff;
+};
+
+/**
+ * struct hist_entry - histogram entry
+ *
+ * @row_offset - offset from the first callchain expanded to appear on screen
+ * @nr_rows - rows expanded in callchain, recalculated on folding/unfolding
+ */
+struct hist_entry {
+ struct rb_node rb_node_in;
+ struct rb_node rb_node;
+ union {
+ struct list_head node;
+ struct list_head head;
+ } pairs;
+ struct he_stat stat;
+ struct he_stat *stat_acc;
+ struct map_symbol ms;
+ struct thread *thread;
+ struct comm *comm;
+ u64 ip;
+ u64 transaction;
+ s32 cpu;
+ u8 cpumode;
+
+ struct hist_entry_diff diff;
+
+ /* We are added by hists__add_dummy_entry. */
+ bool dummy;
+
+ /* XXX These two should move to some tree widget lib */
+ u16 row_offset;
+ u16 nr_rows;
+
+ bool init_have_children;
+ char level;
+ bool used;
+ u8 filtered;
+ char *srcline;
+ struct symbol *parent;
+ unsigned long position;
+ struct rb_root sorted_chain;
+ struct branch_info *branch_info;
+ struct hists *hists;
+ struct mem_info *mem_info;
+ struct callchain_root callchain[0]; /* must be last member */
+};
+
+static inline bool hist_entry__has_pairs(struct hist_entry *he)
+{
+ return !list_empty(&he->pairs.node);
+}
+
+static inline struct hist_entry *hist_entry__next_pair(struct hist_entry *he)
+{
+ if (hist_entry__has_pairs(he))
+ return list_entry(he->pairs.node.next, struct hist_entry, pairs.node);
+ return NULL;
+}
+
+static inline void hist_entry__add_pair(struct hist_entry *pair,
+ struct hist_entry *he)
+{
+ list_add_tail(&pair->pairs.node, &he->pairs.head);
+}
+
+static inline float hist_entry__get_percent_limit(struct hist_entry *he)
+{
+ u64 period = he->stat.period;
+ u64 total_period = hists__total_period(he->hists);
+
+ if (unlikely(total_period == 0))
+ return 0;
+
+ if (symbol_conf.cumulate_callchain)
+ period = he->stat_acc->period;
+
+ return period * 100.0 / total_period;
+}
+
+
+enum sort_mode {
+ SORT_MODE__NORMAL,
+ SORT_MODE__BRANCH,
+ SORT_MODE__MEMORY,
+ SORT_MODE__TOP,
+ SORT_MODE__DIFF,
+};
+
+enum sort_type {
+ /* common sort keys */
+ SORT_PID,
+ SORT_COMM,
+ SORT_DSO,
+ SORT_SYM,
+ SORT_PARENT,
+ SORT_CPU,
+ SORT_SRCLINE,
+ SORT_LOCAL_WEIGHT,
+ SORT_GLOBAL_WEIGHT,
+ SORT_TRANSACTION,
+
+ /* branch stack specific sort keys */
+ __SORT_BRANCH_STACK,
+ SORT_DSO_FROM = __SORT_BRANCH_STACK,
+ SORT_DSO_TO,
+ SORT_SYM_FROM,
+ SORT_SYM_TO,
+ SORT_MISPREDICT,
+ SORT_ABORT,
+ SORT_IN_TX,
+
+ /* memory mode specific sort keys */
+ __SORT_MEMORY_MODE,
+ SORT_MEM_DADDR_SYMBOL = __SORT_MEMORY_MODE,
+ SORT_MEM_DADDR_DSO,
+ SORT_MEM_LOCKED,
+ SORT_MEM_TLB,
+ SORT_MEM_LVL,
+ SORT_MEM_SNOOP,
+ SORT_MEM_DCACHELINE,
+};
+
+/*
+ * configurable sorting bits
+ */
+
+struct sort_entry {
+ struct list_head list;
+
+ const char *se_header;
+
+ int64_t (*se_cmp)(struct hist_entry *, struct hist_entry *);
+ int64_t (*se_collapse)(struct hist_entry *, struct hist_entry *);
+ int64_t (*se_sort)(struct hist_entry *, struct hist_entry *);
+ int (*se_snprintf)(struct hist_entry *he, char *bf, size_t size,
+ unsigned int width);
+ u8 se_width_idx;
+};
+
+extern struct sort_entry sort_thread;
+extern struct list_head hist_entry__sort_list;
+
+int setup_sorting(void);
+int setup_output_field(void);
+void reset_output_field(void);
+extern int sort_dimension__add(const char *);
+void sort__setup_elide(FILE *fp);
+void perf_hpp__set_elide(int idx, bool elide);
+
+int report_parse_ignore_callees_opt(const struct option *opt, const char *arg, int unset);
+
+#endif /* __PERF_SORT_H */
diff --git a/tools/perf/util/srcline.c b/tools/perf/util/srcline.c
new file mode 100644
index 00000000000..f3e4bc5fe5d
--- /dev/null
+++ b/tools/perf/util/srcline.c
@@ -0,0 +1,299 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <linux/kernel.h>
+
+#include "util/dso.h"
+#include "util/util.h"
+#include "util/debug.h"
+
+#ifdef HAVE_LIBBFD_SUPPORT
+
+/*
+ * Implement addr2line using libbfd.
+ */
+#define PACKAGE "perf"
+#include <bfd.h>
+
+struct a2l_data {
+ const char *input;
+ unsigned long addr;
+
+ bool found;
+ const char *filename;
+ const char *funcname;
+ unsigned line;
+
+ bfd *abfd;
+ asymbol **syms;
+};
+
+static int bfd_error(const char *string)
+{
+ const char *errmsg;
+
+ errmsg = bfd_errmsg(bfd_get_error());
+ fflush(stdout);
+
+ if (string)
+ pr_debug("%s: %s\n", string, errmsg);
+ else
+ pr_debug("%s\n", errmsg);
+
+ return -1;
+}
+
+static int slurp_symtab(bfd *abfd, struct a2l_data *a2l)
+{
+ long storage;
+ long symcount;
+ asymbol **syms;
+ bfd_boolean dynamic = FALSE;
+
+ if ((bfd_get_file_flags(abfd) & HAS_SYMS) == 0)
+ return bfd_error(bfd_get_filename(abfd));
+
+ storage = bfd_get_symtab_upper_bound(abfd);
+ if (storage == 0L) {
+ storage = bfd_get_dynamic_symtab_upper_bound(abfd);
+ dynamic = TRUE;
+ }
+ if (storage < 0L)
+ return bfd_error(bfd_get_filename(abfd));
+
+ syms = malloc(storage);
+ if (dynamic)
+ symcount = bfd_canonicalize_dynamic_symtab(abfd, syms);
+ else
+ symcount = bfd_canonicalize_symtab(abfd, syms);
+
+ if (symcount < 0) {
+ free(syms);
+ return bfd_error(bfd_get_filename(abfd));
+ }
+
+ a2l->syms = syms;
+ return 0;
+}
+
+static void find_address_in_section(bfd *abfd, asection *section, void *data)
+{
+ bfd_vma pc, vma;
+ bfd_size_type size;
+ struct a2l_data *a2l = data;
+
+ if (a2l->found)
+ return;
+
+ if ((bfd_get_section_flags(abfd, section) & SEC_ALLOC) == 0)
+ return;
+
+ pc = a2l->addr;
+ vma = bfd_get_section_vma(abfd, section);
+ size = bfd_get_section_size(section);
+
+ if (pc < vma || pc >= vma + size)
+ return;
+
+ a2l->found = bfd_find_nearest_line(abfd, section, a2l->syms, pc - vma,
+ &a2l->filename, &a2l->funcname,
+ &a2l->line);
+}
+
+static struct a2l_data *addr2line_init(const char *path)
+{
+ bfd *abfd;
+ struct a2l_data *a2l = NULL;
+
+ abfd = bfd_openr(path, NULL);
+ if (abfd == NULL)
+ return NULL;
+
+ if (!bfd_check_format(abfd, bfd_object))
+ goto out;
+
+ a2l = zalloc(sizeof(*a2l));
+ if (a2l == NULL)
+ goto out;
+
+ a2l->abfd = abfd;
+ a2l->input = strdup(path);
+ if (a2l->input == NULL)
+ goto out;
+
+ if (slurp_symtab(abfd, a2l))
+ goto out;
+
+ return a2l;
+
+out:
+ if (a2l) {
+ zfree((char **)&a2l->input);
+ free(a2l);
+ }
+ bfd_close(abfd);
+ return NULL;
+}
+
+static void addr2line_cleanup(struct a2l_data *a2l)
+{
+ if (a2l->abfd)
+ bfd_close(a2l->abfd);
+ zfree((char **)&a2l->input);
+ zfree(&a2l->syms);
+ free(a2l);
+}
+
+static int addr2line(const char *dso_name, unsigned long addr,
+ char **file, unsigned int *line, struct dso *dso)
+{
+ int ret = 0;
+ struct a2l_data *a2l = dso->a2l;
+
+ if (!a2l) {
+ dso->a2l = addr2line_init(dso_name);
+ a2l = dso->a2l;
+ }
+
+ if (a2l == NULL) {
+ pr_warning("addr2line_init failed for %s\n", dso_name);
+ return 0;
+ }
+
+ a2l->addr = addr;
+ a2l->found = false;
+
+ bfd_map_over_sections(a2l->abfd, find_address_in_section, a2l);
+
+ if (a2l->found && a2l->filename) {
+ *file = strdup(a2l->filename);
+ *line = a2l->line;
+
+ if (*file)
+ ret = 1;
+ }
+
+ return ret;
+}
+
+void dso__free_a2l(struct dso *dso)
+{
+ struct a2l_data *a2l = dso->a2l;
+
+ if (!a2l)
+ return;
+
+ addr2line_cleanup(a2l);
+
+ dso->a2l = NULL;
+}
+
+#else /* HAVE_LIBBFD_SUPPORT */
+
+static int addr2line(const char *dso_name, unsigned long addr,
+ char **file, unsigned int *line_nr,
+ struct dso *dso __maybe_unused)
+{
+ FILE *fp;
+ char cmd[PATH_MAX];
+ char *filename = NULL;
+ size_t len;
+ char *sep;
+ int ret = 0;
+
+ scnprintf(cmd, sizeof(cmd), "addr2line -e %s %016"PRIx64,
+ dso_name, addr);
+
+ fp = popen(cmd, "r");
+ if (fp == NULL) {
+ pr_warning("popen failed for %s\n", dso_name);
+ return 0;
+ }
+
+ if (getline(&filename, &len, fp) < 0 || !len) {
+ pr_warning("addr2line has no output for %s\n", dso_name);
+ goto out;
+ }
+
+ sep = strchr(filename, '\n');
+ if (sep)
+ *sep = '\0';
+
+ if (!strcmp(filename, "??:0")) {
+ pr_debug("no debugging info in %s\n", dso_name);
+ free(filename);
+ goto out;
+ }
+
+ sep = strchr(filename, ':');
+ if (sep) {
+ *sep++ = '\0';
+ *file = filename;
+ *line_nr = strtoul(sep, NULL, 0);
+ ret = 1;
+ }
+out:
+ pclose(fp);
+ return ret;
+}
+
+void dso__free_a2l(struct dso *dso __maybe_unused)
+{
+}
+
+#endif /* HAVE_LIBBFD_SUPPORT */
+
+/*
+ * Number of addr2line failures (without success) before disabling it for that
+ * dso.
+ */
+#define A2L_FAIL_LIMIT 123
+
+char *get_srcline(struct dso *dso, unsigned long addr)
+{
+ char *file = NULL;
+ unsigned line = 0;
+ char *srcline;
+ const char *dso_name;
+
+ if (!dso->has_srcline)
+ return SRCLINE_UNKNOWN;
+
+ if (dso->symsrc_filename)
+ dso_name = dso->symsrc_filename;
+ else
+ dso_name = dso->long_name;
+
+ if (dso_name[0] == '[')
+ goto out;
+
+ if (!strncmp(dso_name, "/tmp/perf-", 10))
+ goto out;
+
+ if (!addr2line(dso_name, addr, &file, &line, dso))
+ goto out;
+
+ if (asprintf(&srcline, "%s:%u", file, line) < 0) {
+ free(file);
+ goto out;
+ }
+
+ dso->a2l_fails = 0;
+
+ free(file);
+ return srcline;
+
+out:
+ if (dso->a2l_fails && ++dso->a2l_fails > A2L_FAIL_LIMIT) {
+ dso->has_srcline = 0;
+ dso__free_a2l(dso);
+ }
+ return SRCLINE_UNKNOWN;
+}
+
+void free_srcline(char *srcline)
+{
+ if (srcline && strcmp(srcline, SRCLINE_UNKNOWN) != 0)
+ free(srcline);
+}
diff --git a/tools/perf/util/stat.c b/tools/perf/util/stat.c
new file mode 100644
index 00000000000..6506b3dfb60
--- /dev/null
+++ b/tools/perf/util/stat.c
@@ -0,0 +1,63 @@
+#include <math.h>
+
+#include "stat.h"
+
+void update_stats(struct stats *stats, u64 val)
+{
+ double delta;
+
+ stats->n++;
+ delta = val - stats->mean;
+ stats->mean += delta / stats->n;
+ stats->M2 += delta*(val - stats->mean);
+
+ if (val > stats->max)
+ stats->max = val;
+
+ if (val < stats->min)
+ stats->min = val;
+}
+
+double avg_stats(struct stats *stats)
+{
+ return stats->mean;
+}
+
+/*
+ * http://en.wikipedia.org/wiki/Algorithms_for_calculating_variance
+ *
+ * (\Sum n_i^2) - ((\Sum n_i)^2)/n
+ * s^2 = -------------------------------
+ * n - 1
+ *
+ * http://en.wikipedia.org/wiki/Stddev
+ *
+ * The std dev of the mean is related to the std dev by:
+ *
+ * s
+ * s_mean = -------
+ * sqrt(n)
+ *
+ */
+double stddev_stats(struct stats *stats)
+{
+ double variance, variance_mean;
+
+ if (stats->n < 2)
+ return 0.0;
+
+ variance = stats->M2 / (stats->n - 1);
+ variance_mean = variance / stats->n;
+
+ return sqrt(variance_mean);
+}
+
+double rel_stddev_stats(double stddev, double avg)
+{
+ double pct = 0.0;
+
+ if (avg)
+ pct = 100.0 * stddev/avg;
+
+ return pct;
+}
diff --git a/tools/perf/util/stat.h b/tools/perf/util/stat.h
new file mode 100644
index 00000000000..5667fc3e39c
--- /dev/null
+++ b/tools/perf/util/stat.h
@@ -0,0 +1,25 @@
+#ifndef __PERF_STATS_H
+#define __PERF_STATS_H
+
+#include <linux/types.h>
+
+struct stats
+{
+ double n, mean, M2;
+ u64 max, min;
+};
+
+void update_stats(struct stats *stats, u64 val);
+double avg_stats(struct stats *stats);
+double stddev_stats(struct stats *stats);
+double rel_stddev_stats(double stddev, double avg);
+
+static inline void init_stats(struct stats *stats)
+{
+ stats->n = 0.0;
+ stats->mean = 0.0;
+ stats->M2 = 0.0;
+ stats->min = (u64) -1;
+ stats->max = 0;
+}
+#endif
diff --git a/tools/perf/util/strbuf.c b/tools/perf/util/strbuf.c
new file mode 100644
index 00000000000..4abe23550c7
--- /dev/null
+++ b/tools/perf/util/strbuf.c
@@ -0,0 +1,134 @@
+#include "cache.h"
+#include <linux/kernel.h>
+
+int prefixcmp(const char *str, const char *prefix)
+{
+ for (; ; str++, prefix++)
+ if (!*prefix)
+ return 0;
+ else if (*str != *prefix)
+ return (unsigned char)*prefix - (unsigned char)*str;
+}
+
+/*
+ * Used as the default ->buf value, so that people can always assume
+ * buf is non NULL and ->buf is NUL terminated even for a freshly
+ * initialized strbuf.
+ */
+char strbuf_slopbuf[1];
+
+void strbuf_init(struct strbuf *sb, ssize_t hint)
+{
+ sb->alloc = sb->len = 0;
+ sb->buf = strbuf_slopbuf;
+ if (hint)
+ strbuf_grow(sb, hint);
+}
+
+void strbuf_release(struct strbuf *sb)
+{
+ if (sb->alloc) {
+ zfree(&sb->buf);
+ strbuf_init(sb, 0);
+ }
+}
+
+char *strbuf_detach(struct strbuf *sb, size_t *sz)
+{
+ char *res = sb->alloc ? sb->buf : NULL;
+ if (sz)
+ *sz = sb->len;
+ strbuf_init(sb, 0);
+ return res;
+}
+
+void strbuf_grow(struct strbuf *sb, size_t extra)
+{
+ if (sb->len + extra + 1 <= sb->len)
+ die("you want to use way too much memory");
+ if (!sb->alloc)
+ sb->buf = NULL;
+ ALLOC_GROW(sb->buf, sb->len + extra + 1, sb->alloc);
+}
+
+static void strbuf_splice(struct strbuf *sb, size_t pos, size_t len,
+ const void *data, size_t dlen)
+{
+ if (pos + len < pos)
+ die("you want to use way too much memory");
+ if (pos > sb->len)
+ die("`pos' is too far after the end of the buffer");
+ if (pos + len > sb->len)
+ die("`pos + len' is too far after the end of the buffer");
+
+ if (dlen >= len)
+ strbuf_grow(sb, dlen - len);
+ memmove(sb->buf + pos + dlen,
+ sb->buf + pos + len,
+ sb->len - pos - len);
+ memcpy(sb->buf + pos, data, dlen);
+ strbuf_setlen(sb, sb->len + dlen - len);
+}
+
+void strbuf_remove(struct strbuf *sb, size_t pos, size_t len)
+{
+ strbuf_splice(sb, pos, len, NULL, 0);
+}
+
+void strbuf_add(struct strbuf *sb, const void *data, size_t len)
+{
+ strbuf_grow(sb, len);
+ memcpy(sb->buf + sb->len, data, len);
+ strbuf_setlen(sb, sb->len + len);
+}
+
+void strbuf_addf(struct strbuf *sb, const char *fmt, ...)
+{
+ int len;
+ va_list ap;
+
+ if (!strbuf_avail(sb))
+ strbuf_grow(sb, 64);
+ va_start(ap, fmt);
+ len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
+ va_end(ap);
+ if (len < 0)
+ die("your vsnprintf is broken");
+ if (len > strbuf_avail(sb)) {
+ strbuf_grow(sb, len);
+ va_start(ap, fmt);
+ len = vsnprintf(sb->buf + sb->len, sb->alloc - sb->len, fmt, ap);
+ va_end(ap);
+ if (len > strbuf_avail(sb)) {
+ die("this should not happen, your vsnprintf is broken");
+ }
+ }
+ strbuf_setlen(sb, sb->len + len);
+}
+
+ssize_t strbuf_read(struct strbuf *sb, int fd, ssize_t hint)
+{
+ size_t oldlen = sb->len;
+ size_t oldalloc = sb->alloc;
+
+ strbuf_grow(sb, hint ? hint : 8192);
+ for (;;) {
+ ssize_t cnt;
+
+ cnt = read(fd, sb->buf + sb->len, sb->alloc - sb->len - 1);
+ if (cnt < 0) {
+ if (oldalloc == 0)
+ strbuf_release(sb);
+ else
+ strbuf_setlen(sb, oldlen);
+ return -1;
+ }
+ if (!cnt)
+ break;
+ sb->len += cnt;
+ strbuf_grow(sb, 8192);
+ }
+
+ sb->buf[sb->len] = '\0';
+ return sb->len - oldlen;
+}
diff --git a/tools/perf/util/strbuf.h b/tools/perf/util/strbuf.h
new file mode 100644
index 00000000000..436ac319f6c
--- /dev/null
+++ b/tools/perf/util/strbuf.h
@@ -0,0 +1,92 @@
+#ifndef __PERF_STRBUF_H
+#define __PERF_STRBUF_H
+
+/*
+ * Strbuf's can be use in many ways: as a byte array, or to store arbitrary
+ * long, overflow safe strings.
+ *
+ * Strbufs has some invariants that are very important to keep in mind:
+ *
+ * 1. the ->buf member is always malloc-ed, hence strbuf's can be used to
+ * build complex strings/buffers whose final size isn't easily known.
+ *
+ * It is NOT legal to copy the ->buf pointer away.
+ * `strbuf_detach' is the operation that detachs a buffer from its shell
+ * while keeping the shell valid wrt its invariants.
+ *
+ * 2. the ->buf member is a byte array that has at least ->len + 1 bytes
+ * allocated. The extra byte is used to store a '\0', allowing the ->buf
+ * member to be a valid C-string. Every strbuf function ensure this
+ * invariant is preserved.
+ *
+ * Note that it is OK to "play" with the buffer directly if you work it
+ * that way:
+ *
+ * strbuf_grow(sb, SOME_SIZE);
+ * ... Here, the memory array starting at sb->buf, and of length
+ * ... strbuf_avail(sb) is all yours, and you are sure that
+ * ... strbuf_avail(sb) is at least SOME_SIZE.
+ * strbuf_setlen(sb, sb->len + SOME_OTHER_SIZE);
+ *
+ * Of course, SOME_OTHER_SIZE must be smaller or equal to strbuf_avail(sb).
+ *
+ * Doing so is safe, though if it has to be done in many places, adding the
+ * missing API to the strbuf module is the way to go.
+ *
+ * XXX: do _not_ assume that the area that is yours is of size ->alloc - 1
+ * even if it's true in the current implementation. Alloc is somehow a
+ * "private" member that should not be messed with.
+ */
+
+#include <assert.h>
+
+extern char strbuf_slopbuf[];
+struct strbuf {
+ size_t alloc;
+ size_t len;
+ char *buf;
+};
+
+#define STRBUF_INIT { 0, 0, strbuf_slopbuf }
+
+/*----- strbuf life cycle -----*/
+extern void strbuf_init(struct strbuf *buf, ssize_t hint);
+extern void strbuf_release(struct strbuf *);
+extern char *strbuf_detach(struct strbuf *, size_t *);
+
+/*----- strbuf size related -----*/
+static inline ssize_t strbuf_avail(const struct strbuf *sb) {
+ return sb->alloc ? sb->alloc - sb->len - 1 : 0;
+}
+
+extern void strbuf_grow(struct strbuf *, size_t);
+
+static inline void strbuf_setlen(struct strbuf *sb, size_t len) {
+ if (!sb->alloc)
+ strbuf_grow(sb, 0);
+ assert(len < sb->alloc);
+ sb->len = len;
+ sb->buf[len] = '\0';
+}
+
+/*----- add data in your buffer -----*/
+static inline void strbuf_addch(struct strbuf *sb, int c) {
+ strbuf_grow(sb, 1);
+ sb->buf[sb->len++] = c;
+ sb->buf[sb->len] = '\0';
+}
+
+extern void strbuf_remove(struct strbuf *, size_t pos, size_t len);
+
+extern void strbuf_add(struct strbuf *, const void *, size_t);
+static inline void strbuf_addstr(struct strbuf *sb, const char *s) {
+ strbuf_add(sb, s, strlen(s));
+}
+
+__attribute__((format(printf,2,3)))
+extern void strbuf_addf(struct strbuf *sb, const char *fmt, ...);
+
+/* XXX: if read fails, any partial read is undone */
+extern ssize_t strbuf_read(struct strbuf *, int fd, ssize_t hint);
+
+#endif /* __PERF_STRBUF_H */
diff --git a/tools/perf/util/strfilter.c b/tools/perf/util/strfilter.c
new file mode 100644
index 00000000000..79a757a2a15
--- /dev/null
+++ b/tools/perf/util/strfilter.c
@@ -0,0 +1,199 @@
+#include "util.h"
+#include "string.h"
+#include "strfilter.h"
+
+/* Operators */
+static const char *OP_and = "&"; /* Logical AND */
+static const char *OP_or = "|"; /* Logical OR */
+static const char *OP_not = "!"; /* Logical NOT */
+
+#define is_operator(c) ((c) == '|' || (c) == '&' || (c) == '!')
+#define is_separator(c) (is_operator(c) || (c) == '(' || (c) == ')')
+
+static void strfilter_node__delete(struct strfilter_node *node)
+{
+ if (node) {
+ if (node->p && !is_operator(*node->p))
+ zfree((char **)&node->p);
+ strfilter_node__delete(node->l);
+ strfilter_node__delete(node->r);
+ free(node);
+ }
+}
+
+void strfilter__delete(struct strfilter *filter)
+{
+ if (filter) {
+ strfilter_node__delete(filter->root);
+ free(filter);
+ }
+}
+
+static const char *get_token(const char *s, const char **e)
+{
+ const char *p;
+
+ while (isspace(*s)) /* Skip spaces */
+ s++;
+
+ if (*s == '\0') {
+ p = s;
+ goto end;
+ }
+
+ p = s + 1;
+ if (!is_separator(*s)) {
+ /* End search */
+retry:
+ while (*p && !is_separator(*p) && !isspace(*p))
+ p++;
+ /* Escape and special case: '!' is also used in glob pattern */
+ if (*(p - 1) == '\\' || (*p == '!' && *(p - 1) == '[')) {
+ p++;
+ goto retry;
+ }
+ }
+end:
+ *e = p;
+ return s;
+}
+
+static struct strfilter_node *strfilter_node__alloc(const char *op,
+ struct strfilter_node *l,
+ struct strfilter_node *r)
+{
+ struct strfilter_node *node = zalloc(sizeof(*node));
+
+ if (node) {
+ node->p = op;
+ node->l = l;
+ node->r = r;
+ }
+
+ return node;
+}
+
+static struct strfilter_node *strfilter_node__new(const char *s,
+ const char **ep)
+{
+ struct strfilter_node root, *cur, *last_op;
+ const char *e;
+
+ if (!s)
+ return NULL;
+
+ memset(&root, 0, sizeof(root));
+ last_op = cur = &root;
+
+ s = get_token(s, &e);
+ while (*s != '\0' && *s != ')') {
+ switch (*s) {
+ case '&': /* Exchg last OP->r with AND */
+ if (!cur->r || !last_op->r)
+ goto error;
+ cur = strfilter_node__alloc(OP_and, last_op->r, NULL);
+ if (!cur)
+ goto nomem;
+ last_op->r = cur;
+ last_op = cur;
+ break;
+ case '|': /* Exchg the root with OR */
+ if (!cur->r || !root.r)
+ goto error;
+ cur = strfilter_node__alloc(OP_or, root.r, NULL);
+ if (!cur)
+ goto nomem;
+ root.r = cur;
+ last_op = cur;
+ break;
+ case '!': /* Add NOT as a leaf node */
+ if (cur->r)
+ goto error;
+ cur->r = strfilter_node__alloc(OP_not, NULL, NULL);
+ if (!cur->r)
+ goto nomem;
+ cur = cur->r;
+ break;
+ case '(': /* Recursively parses inside the parenthesis */
+ if (cur->r)
+ goto error;
+ cur->r = strfilter_node__new(s + 1, &s);
+ if (!s)
+ goto nomem;
+ if (!cur->r || *s != ')')
+ goto error;
+ e = s + 1;
+ break;
+ default:
+ if (cur->r)
+ goto error;
+ cur->r = strfilter_node__alloc(NULL, NULL, NULL);
+ if (!cur->r)
+ goto nomem;
+ cur->r->p = strndup(s, e - s);
+ if (!cur->r->p)
+ goto nomem;
+ }
+ s = get_token(e, &e);
+ }
+ if (!cur->r)
+ goto error;
+ *ep = s;
+ return root.r;
+nomem:
+ s = NULL;
+error:
+ *ep = s;
+ strfilter_node__delete(root.r);
+ return NULL;
+}
+
+/*
+ * Parse filter rule and return new strfilter.
+ * Return NULL if fail, and *ep == NULL if memory allocation failed.
+ */
+struct strfilter *strfilter__new(const char *rules, const char **err)
+{
+ struct strfilter *filter = zalloc(sizeof(*filter));
+ const char *ep = NULL;
+
+ if (filter)
+ filter->root = strfilter_node__new(rules, &ep);
+
+ if (!filter || !filter->root || *ep != '\0') {
+ if (err)
+ *err = ep;
+ strfilter__delete(filter);
+ filter = NULL;
+ }
+
+ return filter;
+}
+
+static bool strfilter_node__compare(struct strfilter_node *node,
+ const char *str)
+{
+ if (!node || !node->p)
+ return false;
+
+ switch (*node->p) {
+ case '|': /* OR */
+ return strfilter_node__compare(node->l, str) ||
+ strfilter_node__compare(node->r, str);
+ case '&': /* AND */
+ return strfilter_node__compare(node->l, str) &&
+ strfilter_node__compare(node->r, str);
+ case '!': /* NOT */
+ return !strfilter_node__compare(node->r, str);
+ default:
+ return strglobmatch(str, node->p);
+ }
+}
+
+/* Return true if STR matches the filter rules */
+bool strfilter__compare(struct strfilter *filter, const char *str)
+{
+ if (!filter)
+ return false;
+ return strfilter_node__compare(filter->root, str);
+}
diff --git a/tools/perf/util/strfilter.h b/tools/perf/util/strfilter.h
new file mode 100644
index 00000000000..fe611f3c9e3
--- /dev/null
+++ b/tools/perf/util/strfilter.h
@@ -0,0 +1,48 @@
+#ifndef __PERF_STRFILTER_H
+#define __PERF_STRFILTER_H
+/* General purpose glob matching filter */
+
+#include <linux/list.h>
+#include <stdbool.h>
+
+/* A node of string filter */
+struct strfilter_node {
+ struct strfilter_node *l; /* Tree left branche (for &,|) */
+ struct strfilter_node *r; /* Tree right branche (for !,&,|) */
+ const char *p; /* Operator or rule */
+};
+
+/* String filter */
+struct strfilter {
+ struct strfilter_node *root;
+};
+
+/**
+ * strfilter__new - Create a new string filter
+ * @rules: Filter rule, which is a combination of glob expressions.
+ * @err: Pointer which points an error detected on @rules
+ *
+ * Parse @rules and return new strfilter. Return NULL if an error detected.
+ * In that case, *@err will indicate where it is detected, and *@err is NULL
+ * if a memory allocation is failed.
+ */
+struct strfilter *strfilter__new(const char *rules, const char **err);
+
+/**
+ * strfilter__compare - compare given string and a string filter
+ * @filter: String filter
+ * @str: target string
+ *
+ * Compare @str and @filter. Return true if the str match the rule
+ */
+bool strfilter__compare(struct strfilter *filter, const char *str);
+
+/**
+ * strfilter__delete - delete a string filter
+ * @filter: String filter to delete
+ *
+ * Delete @filter.
+ */
+void strfilter__delete(struct strfilter *filter);
+
+#endif
diff --git a/tools/perf/util/string.c b/tools/perf/util/string.c
new file mode 100644
index 00000000000..2553e5b55b8
--- /dev/null
+++ b/tools/perf/util/string.c
@@ -0,0 +1,413 @@
+#include "util.h"
+#include "linux/string.h"
+
+#define K 1024LL
+/*
+ * perf_atoll()
+ * Parse (\d+)(b|B|kb|KB|mb|MB|gb|GB|tb|TB) (e.g. "256MB")
+ * and return its numeric value
+ */
+s64 perf_atoll(const char *str)
+{
+ unsigned int i;
+ s64 length = -1, unit = 1;
+
+ if (!isdigit(str[0]))
+ goto out_err;
+
+ for (i = 1; i < strlen(str); i++) {
+ switch (str[i]) {
+ case 'B':
+ case 'b':
+ break;
+ case 'K':
+ if (str[i + 1] != 'B')
+ goto out_err;
+ else
+ goto kilo;
+ case 'k':
+ if (str[i + 1] != 'b')
+ goto out_err;
+kilo:
+ unit = K;
+ break;
+ case 'M':
+ if (str[i + 1] != 'B')
+ goto out_err;
+ else
+ goto mega;
+ case 'm':
+ if (str[i + 1] != 'b')
+ goto out_err;
+mega:
+ unit = K * K;
+ break;
+ case 'G':
+ if (str[i + 1] != 'B')
+ goto out_err;
+ else
+ goto giga;
+ case 'g':
+ if (str[i + 1] != 'b')
+ goto out_err;
+giga:
+ unit = K * K * K;
+ break;
+ case 'T':
+ if (str[i + 1] != 'B')
+ goto out_err;
+ else
+ goto tera;
+ case 't':
+ if (str[i + 1] != 'b')
+ goto out_err;
+tera:
+ unit = K * K * K * K;
+ break;
+ case '\0': /* only specified figures */
+ unit = 1;
+ break;
+ default:
+ if (!isdigit(str[i]))
+ goto out_err;
+ break;
+ }
+ }
+
+ length = atoll(str) * unit;
+ goto out;
+
+out_err:
+ length = -1;
+out:
+ return length;
+}
+
+/*
+ * Helper function for splitting a string into an argv-like array.
+ * originally copied from lib/argv_split.c
+ */
+static const char *skip_sep(const char *cp)
+{
+ while (*cp && isspace(*cp))
+ cp++;
+
+ return cp;
+}
+
+static const char *skip_arg(const char *cp)
+{
+ while (*cp && !isspace(*cp))
+ cp++;
+
+ return cp;
+}
+
+static int count_argc(const char *str)
+{
+ int count = 0;
+
+ while (*str) {
+ str = skip_sep(str);
+ if (*str) {
+ count++;
+ str = skip_arg(str);
+ }
+ }
+
+ return count;
+}
+
+/**
+ * argv_free - free an argv
+ * @argv - the argument vector to be freed
+ *
+ * Frees an argv and the strings it points to.
+ */
+void argv_free(char **argv)
+{
+ char **p;
+ for (p = argv; *p; p++)
+ zfree(p);
+
+ free(argv);
+}
+
+/**
+ * argv_split - split a string at whitespace, returning an argv
+ * @str: the string to be split
+ * @argcp: returned argument count
+ *
+ * Returns an array of pointers to strings which are split out from
+ * @str. This is performed by strictly splitting on white-space; no
+ * quote processing is performed. Multiple whitespace characters are
+ * considered to be a single argument separator. The returned array
+ * is always NULL-terminated. Returns NULL on memory allocation
+ * failure.
+ */
+char **argv_split(const char *str, int *argcp)
+{
+ int argc = count_argc(str);
+ char **argv = zalloc(sizeof(*argv) * (argc+1));
+ char **argvp;
+
+ if (argv == NULL)
+ goto out;
+
+ if (argcp)
+ *argcp = argc;
+
+ argvp = argv;
+
+ while (*str) {
+ str = skip_sep(str);
+
+ if (*str) {
+ const char *p = str;
+ char *t;
+
+ str = skip_arg(str);
+
+ t = strndup(p, str-p);
+ if (t == NULL)
+ goto fail;
+ *argvp++ = t;
+ }
+ }
+ *argvp = NULL;
+
+out:
+ return argv;
+
+fail:
+ argv_free(argv);
+ return NULL;
+}
+
+/* Character class matching */
+static bool __match_charclass(const char *pat, char c, const char **npat)
+{
+ bool complement = false, ret = true;
+
+ if (*pat == '!') {
+ complement = true;
+ pat++;
+ }
+ if (*pat++ == c) /* First character is special */
+ goto end;
+
+ while (*pat && *pat != ']') { /* Matching */
+ if (*pat == '-' && *(pat + 1) != ']') { /* Range */
+ if (*(pat - 1) <= c && c <= *(pat + 1))
+ goto end;
+ if (*(pat - 1) > *(pat + 1))
+ goto error;
+ pat += 2;
+ } else if (*pat++ == c)
+ goto end;
+ }
+ if (!*pat)
+ goto error;
+ ret = false;
+
+end:
+ while (*pat && *pat != ']') /* Searching closing */
+ pat++;
+ if (!*pat)
+ goto error;
+ *npat = pat + 1;
+ return complement ? !ret : ret;
+
+error:
+ return false;
+}
+
+/* Glob/lazy pattern matching */
+static bool __match_glob(const char *str, const char *pat, bool ignore_space)
+{
+ while (*str && *pat && *pat != '*') {
+ if (ignore_space) {
+ /* Ignore spaces for lazy matching */
+ if (isspace(*str)) {
+ str++;
+ continue;
+ }
+ if (isspace(*pat)) {
+ pat++;
+ continue;
+ }
+ }
+ if (*pat == '?') { /* Matches any single character */
+ str++;
+ pat++;
+ continue;
+ } else if (*pat == '[') /* Character classes/Ranges */
+ if (__match_charclass(pat + 1, *str, &pat)) {
+ str++;
+ continue;
+ } else
+ return false;
+ else if (*pat == '\\') /* Escaped char match as normal char */
+ pat++;
+ if (*str++ != *pat++)
+ return false;
+ }
+ /* Check wild card */
+ if (*pat == '*') {
+ while (*pat == '*')
+ pat++;
+ if (!*pat) /* Tail wild card matches all */
+ return true;
+ while (*str)
+ if (__match_glob(str++, pat, ignore_space))
+ return true;
+ }
+ return !*str && !*pat;
+}
+
+/**
+ * strglobmatch - glob expression pattern matching
+ * @str: the target string to match
+ * @pat: the pattern string to match
+ *
+ * This returns true if the @str matches @pat. @pat can includes wildcards
+ * ('*','?') and character classes ([CHARS], complementation and ranges are
+ * also supported). Also, this supports escape character ('\') to use special
+ * characters as normal character.
+ *
+ * Note: if @pat syntax is broken, this always returns false.
+ */
+bool strglobmatch(const char *str, const char *pat)
+{
+ return __match_glob(str, pat, false);
+}
+
+/**
+ * strlazymatch - matching pattern strings lazily with glob pattern
+ * @str: the target string to match
+ * @pat: the pattern string to match
+ *
+ * This is similar to strglobmatch, except this ignores spaces in
+ * the target string.
+ */
+bool strlazymatch(const char *str, const char *pat)
+{
+ return __match_glob(str, pat, true);
+}
+
+/**
+ * strtailcmp - Compare the tail of two strings
+ * @s1: 1st string to be compared
+ * @s2: 2nd string to be compared
+ *
+ * Return 0 if whole of either string is same as another's tail part.
+ */
+int strtailcmp(const char *s1, const char *s2)
+{
+ int i1 = strlen(s1);
+ int i2 = strlen(s2);
+ while (--i1 >= 0 && --i2 >= 0) {
+ if (s1[i1] != s2[i2])
+ return s1[i1] - s2[i2];
+ }
+ return 0;
+}
+
+/**
+ * strxfrchar - Locate and replace character in @s
+ * @s: The string to be searched/changed.
+ * @from: Source character to be replaced.
+ * @to: Destination character.
+ *
+ * Return pointer to the changed string.
+ */
+char *strxfrchar(char *s, char from, char to)
+{
+ char *p = s;
+
+ while ((p = strchr(p, from)) != NULL)
+ *p++ = to;
+
+ return s;
+}
+
+/**
+ * ltrim - Removes leading whitespace from @s.
+ * @s: The string to be stripped.
+ *
+ * Return pointer to the first non-whitespace character in @s.
+ */
+char *ltrim(char *s)
+{
+ int len = strlen(s);
+
+ while (len && isspace(*s)) {
+ len--;
+ s++;
+ }
+
+ return s;
+}
+
+/**
+ * rtrim - Removes trailing whitespace from @s.
+ * @s: The string to be stripped.
+ *
+ * Note that the first trailing whitespace is replaced with a %NUL-terminator
+ * in the given string @s. Returns @s.
+ */
+char *rtrim(char *s)
+{
+ size_t size = strlen(s);
+ char *end;
+
+ if (!size)
+ return s;
+
+ end = s + size - 1;
+ while (end >= s && isspace(*end))
+ end--;
+ *(end + 1) = '\0';
+
+ return s;
+}
+
+/**
+ * memdup - duplicate region of memory
+ * @src: memory region to duplicate
+ * @len: memory region length
+ */
+void *memdup(const void *src, size_t len)
+{
+ void *p;
+
+ p = malloc(len);
+ if (p)
+ memcpy(p, src, len);
+
+ return p;
+}
+
+/**
+ * str_append - reallocate string and append another
+ * @s: pointer to string pointer
+ * @len: pointer to len (initialized)
+ * @a: string to append.
+ */
+int str_append(char **s, int *len, const char *a)
+{
+ int olen = *s ? strlen(*s) : 0;
+ int nlen = olen + strlen(a) + 1;
+ if (*len < nlen) {
+ *len = *len * 2;
+ if (*len < nlen)
+ *len = nlen;
+ *s = realloc(*s, *len);
+ if (!*s)
+ return -ENOMEM;
+ if (olen == 0)
+ **s = 0;
+ }
+ strcat(*s, a);
+ return 0;
+}
diff --git a/tools/perf/util/strlist.c b/tools/perf/util/strlist.c
new file mode 100644
index 00000000000..71f9d102b96
--- /dev/null
+++ b/tools/perf/util/strlist.c
@@ -0,0 +1,173 @@
+/*
+ * (c) 2009 Arnaldo Carvalho de Melo <acme@redhat.com>
+ *
+ * Licensed under the GPLv2.
+ */
+
+#include "strlist.h"
+#include "util.h"
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+static
+struct rb_node *strlist__node_new(struct rblist *rblist, const void *entry)
+{
+ const char *s = entry;
+ struct rb_node *rc = NULL;
+ struct strlist *strlist = container_of(rblist, struct strlist, rblist);
+ struct str_node *snode = malloc(sizeof(*snode));
+
+ if (snode != NULL) {
+ if (strlist->dupstr) {
+ s = strdup(s);
+ if (s == NULL)
+ goto out_delete;
+ }
+ snode->s = s;
+ rc = &snode->rb_node;
+ }
+
+ return rc;
+
+out_delete:
+ free(snode);
+ return NULL;
+}
+
+static void str_node__delete(struct str_node *snode, bool dupstr)
+{
+ if (dupstr)
+ zfree((char **)&snode->s);
+ free(snode);
+}
+
+static
+void strlist__node_delete(struct rblist *rblist, struct rb_node *rb_node)
+{
+ struct strlist *slist = container_of(rblist, struct strlist, rblist);
+ struct str_node *snode = container_of(rb_node, struct str_node, rb_node);
+
+ str_node__delete(snode, slist->dupstr);
+}
+
+static int strlist__node_cmp(struct rb_node *rb_node, const void *entry)
+{
+ const char *str = entry;
+ struct str_node *snode = container_of(rb_node, struct str_node, rb_node);
+
+ return strcmp(snode->s, str);
+}
+
+int strlist__add(struct strlist *slist, const char *new_entry)
+{
+ return rblist__add_node(&slist->rblist, new_entry);
+}
+
+int strlist__load(struct strlist *slist, const char *filename)
+{
+ char entry[1024];
+ int err;
+ FILE *fp = fopen(filename, "r");
+
+ if (fp == NULL)
+ return errno;
+
+ while (fgets(entry, sizeof(entry), fp) != NULL) {
+ const size_t len = strlen(entry);
+
+ if (len == 0)
+ continue;
+ entry[len - 1] = '\0';
+
+ err = strlist__add(slist, entry);
+ if (err != 0)
+ goto out;
+ }
+
+ err = 0;
+out:
+ fclose(fp);
+ return err;
+}
+
+void strlist__remove(struct strlist *slist, struct str_node *snode)
+{
+ rblist__remove_node(&slist->rblist, &snode->rb_node);
+}
+
+struct str_node *strlist__find(struct strlist *slist, const char *entry)
+{
+ struct str_node *snode = NULL;
+ struct rb_node *rb_node = rblist__find(&slist->rblist, entry);
+
+ if (rb_node)
+ snode = container_of(rb_node, struct str_node, rb_node);
+
+ return snode;
+}
+
+static int strlist__parse_list_entry(struct strlist *slist, const char *s)
+{
+ if (strncmp(s, "file://", 7) == 0)
+ return strlist__load(slist, s + 7);
+
+ return strlist__add(slist, s);
+}
+
+int strlist__parse_list(struct strlist *slist, const char *s)
+{
+ char *sep;
+ int err;
+
+ while ((sep = strchr(s, ',')) != NULL) {
+ *sep = '\0';
+ err = strlist__parse_list_entry(slist, s);
+ *sep = ',';
+ if (err != 0)
+ return err;
+ s = sep + 1;
+ }
+
+ return *s ? strlist__parse_list_entry(slist, s) : 0;
+}
+
+struct strlist *strlist__new(bool dupstr, const char *list)
+{
+ struct strlist *slist = malloc(sizeof(*slist));
+
+ if (slist != NULL) {
+ rblist__init(&slist->rblist);
+ slist->rblist.node_cmp = strlist__node_cmp;
+ slist->rblist.node_new = strlist__node_new;
+ slist->rblist.node_delete = strlist__node_delete;
+
+ slist->dupstr = dupstr;
+ if (list && strlist__parse_list(slist, list) != 0)
+ goto out_error;
+ }
+
+ return slist;
+out_error:
+ free(slist);
+ return NULL;
+}
+
+void strlist__delete(struct strlist *slist)
+{
+ if (slist != NULL)
+ rblist__delete(&slist->rblist);
+}
+
+struct str_node *strlist__entry(const struct strlist *slist, unsigned int idx)
+{
+ struct str_node *snode = NULL;
+ struct rb_node *rb_node;
+
+ rb_node = rblist__entry(&slist->rblist, idx);
+ if (rb_node)
+ snode = container_of(rb_node, struct str_node, rb_node);
+
+ return snode;
+}
diff --git a/tools/perf/util/strlist.h b/tools/perf/util/strlist.h
new file mode 100644
index 00000000000..5c7f87069d9
--- /dev/null
+++ b/tools/perf/util/strlist.h
@@ -0,0 +1,79 @@
+#ifndef __PERF_STRLIST_H
+#define __PERF_STRLIST_H
+
+#include <linux/rbtree.h>
+#include <stdbool.h>
+
+#include "rblist.h"
+
+struct str_node {
+ struct rb_node rb_node;
+ const char *s;
+};
+
+struct strlist {
+ struct rblist rblist;
+ bool dupstr;
+};
+
+struct strlist *strlist__new(bool dupstr, const char *slist);
+void strlist__delete(struct strlist *slist);
+
+void strlist__remove(struct strlist *slist, struct str_node *sn);
+int strlist__load(struct strlist *slist, const char *filename);
+int strlist__add(struct strlist *slist, const char *str);
+
+struct str_node *strlist__entry(const struct strlist *slist, unsigned int idx);
+struct str_node *strlist__find(struct strlist *slist, const char *entry);
+
+static inline bool strlist__has_entry(struct strlist *slist, const char *entry)
+{
+ return strlist__find(slist, entry) != NULL;
+}
+
+static inline bool strlist__empty(const struct strlist *slist)
+{
+ return rblist__empty(&slist->rblist);
+}
+
+static inline unsigned int strlist__nr_entries(const struct strlist *slist)
+{
+ return rblist__nr_entries(&slist->rblist);
+}
+
+/* For strlist iteration */
+static inline struct str_node *strlist__first(struct strlist *slist)
+{
+ struct rb_node *rn = rb_first(&slist->rblist.entries);
+ return rn ? rb_entry(rn, struct str_node, rb_node) : NULL;
+}
+static inline struct str_node *strlist__next(struct str_node *sn)
+{
+ struct rb_node *rn;
+ if (!sn)
+ return NULL;
+ rn = rb_next(&sn->rb_node);
+ return rn ? rb_entry(rn, struct str_node, rb_node) : NULL;
+}
+
+/**
+ * strlist_for_each - iterate over a strlist
+ * @pos: the &struct str_node to use as a loop cursor.
+ * @slist: the &struct strlist for loop.
+ */
+#define strlist__for_each(pos, slist) \
+ for (pos = strlist__first(slist); pos; pos = strlist__next(pos))
+
+/**
+ * strlist_for_each_safe - iterate over a strlist safe against removal of
+ * str_node
+ * @pos: the &struct str_node to use as a loop cursor.
+ * @n: another &struct str_node to use as temporary storage.
+ * @slist: the &struct strlist for loop.
+ */
+#define strlist__for_each_safe(pos, n, slist) \
+ for (pos = strlist__first(slist), n = strlist__next(pos); pos;\
+ pos = n, n = strlist__next(n))
+
+int strlist__parse_list(struct strlist *slist, const char *s);
+#endif /* __PERF_STRLIST_H */
diff --git a/tools/perf/util/svghelper.c b/tools/perf/util/svghelper.c
new file mode 100644
index 00000000000..6a0a13d07a2
--- /dev/null
+++ b/tools/perf/util/svghelper.c
@@ -0,0 +1,716 @@
+/*
+ * svghelper.c - helper functions for outputting svg
+ *
+ * (C) Copyright 2009 Intel Corporation
+ *
+ * Authors:
+ * Arjan van de Ven <arjan@linux.intel.com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; version 2
+ * of the License.
+ */
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <linux/bitmap.h>
+
+#include "perf.h"
+#include "svghelper.h"
+#include "util.h"
+#include "cpumap.h"
+
+static u64 first_time, last_time;
+static u64 turbo_frequency, max_freq;
+
+
+#define SLOT_MULT 30.0
+#define SLOT_HEIGHT 25.0
+
+int svg_page_width = 1000;
+u64 svg_highlight;
+const char *svg_highlight_name;
+
+#define MIN_TEXT_SIZE 0.01
+
+static u64 total_height;
+static FILE *svgfile;
+
+static double cpu2slot(int cpu)
+{
+ return 2 * cpu + 1;
+}
+
+static int *topology_map;
+
+static double cpu2y(int cpu)
+{
+ if (topology_map)
+ return cpu2slot(topology_map[cpu]) * SLOT_MULT;
+ else
+ return cpu2slot(cpu) * SLOT_MULT;
+}
+
+static double time2pixels(u64 __time)
+{
+ double X;
+
+ X = 1.0 * svg_page_width * (__time - first_time) / (last_time - first_time);
+ return X;
+}
+
+/*
+ * Round text sizes so that the svg viewer only needs a discrete
+ * number of renderings of the font
+ */
+static double round_text_size(double size)
+{
+ int loop = 100;
+ double target = 10.0;
+
+ if (size >= 10.0)
+ return size;
+ while (loop--) {
+ if (size >= target)
+ return target;
+ target = target / 2.0;
+ }
+ return size;
+}
+
+void open_svg(const char *filename, int cpus, int rows, u64 start, u64 end)
+{
+ int new_width;
+
+ svgfile = fopen(filename, "w");
+ if (!svgfile) {
+ fprintf(stderr, "Cannot open %s for output\n", filename);
+ return;
+ }
+ first_time = start;
+ first_time = first_time / 100000000 * 100000000;
+ last_time = end;
+
+ /*
+ * if the recording is short, we default to a width of 1000, but
+ * for longer recordings we want at least 200 units of width per second
+ */
+ new_width = (last_time - first_time) / 5000000;
+
+ if (new_width > svg_page_width)
+ svg_page_width = new_width;
+
+ total_height = (1 + rows + cpu2slot(cpus)) * SLOT_MULT;
+ fprintf(svgfile, "<?xml version=\"1.0\" standalone=\"no\"?> \n");
+ fprintf(svgfile, "<!DOCTYPE svg SYSTEM \"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\">\n");
+ fprintf(svgfile, "<svg width=\"%i\" height=\"%" PRIu64 "\" version=\"1.1\" xmlns=\"http://www.w3.org/2000/svg\">\n", svg_page_width, total_height);
+
+ fprintf(svgfile, "<defs>\n <style type=\"text/css\">\n <![CDATA[\n");
+
+ fprintf(svgfile, " rect { stroke-width: 1; }\n");
+ fprintf(svgfile, " rect.process { fill:rgb(180,180,180); fill-opacity:0.9; stroke-width:1; stroke:rgb( 0, 0, 0); } \n");
+ fprintf(svgfile, " rect.process2 { fill:rgb(180,180,180); fill-opacity:0.9; stroke-width:0; stroke:rgb( 0, 0, 0); } \n");
+ fprintf(svgfile, " rect.sample { fill:rgb( 0, 0,255); fill-opacity:0.8; stroke-width:0; stroke:rgb( 0, 0, 0); } \n");
+ fprintf(svgfile, " rect.sample_hi{ fill:rgb(255,128, 0); fill-opacity:0.8; stroke-width:0; stroke:rgb( 0, 0, 0); } \n");
+ fprintf(svgfile, " rect.blocked { fill:rgb(255, 0, 0); fill-opacity:0.5; stroke-width:0; stroke:rgb( 0, 0, 0); } \n");
+ fprintf(svgfile, " rect.waiting { fill:rgb(224,214, 0); fill-opacity:0.8; stroke-width:0; stroke:rgb( 0, 0, 0); } \n");
+ fprintf(svgfile, " rect.WAITING { fill:rgb(255,214, 48); fill-opacity:0.6; stroke-width:0; stroke:rgb( 0, 0, 0); } \n");
+ fprintf(svgfile, " rect.cpu { fill:rgb(192,192,192); fill-opacity:0.2; stroke-width:0.5; stroke:rgb(128,128,128); } \n");
+ fprintf(svgfile, " rect.pstate { fill:rgb(128,128,128); fill-opacity:0.8; stroke-width:0; } \n");
+ fprintf(svgfile, " rect.c1 { fill:rgb(255,214,214); fill-opacity:0.5; stroke-width:0; } \n");
+ fprintf(svgfile, " rect.c2 { fill:rgb(255,172,172); fill-opacity:0.5; stroke-width:0; } \n");
+ fprintf(svgfile, " rect.c3 { fill:rgb(255,130,130); fill-opacity:0.5; stroke-width:0; } \n");
+ fprintf(svgfile, " rect.c4 { fill:rgb(255, 88, 88); fill-opacity:0.5; stroke-width:0; } \n");
+ fprintf(svgfile, " rect.c5 { fill:rgb(255, 44, 44); fill-opacity:0.5; stroke-width:0; } \n");
+ fprintf(svgfile, " rect.c6 { fill:rgb(255, 0, 0); fill-opacity:0.5; stroke-width:0; } \n");
+ fprintf(svgfile, " line.pstate { stroke:rgb(255,255, 0); stroke-opacity:0.8; stroke-width:2; } \n");
+
+ fprintf(svgfile, " ]]>\n </style>\n</defs>\n");
+}
+
+void svg_box(int Yslot, u64 start, u64 end, const char *type)
+{
+ if (!svgfile)
+ return;
+
+ fprintf(svgfile, "<rect x=\"%4.8f\" width=\"%4.8f\" y=\"%4.1f\" height=\"%4.1f\" class=\"%s\"/>\n",
+ time2pixels(start), time2pixels(end)-time2pixels(start), Yslot * SLOT_MULT, SLOT_HEIGHT, type);
+}
+
+static char *time_to_string(u64 duration);
+void svg_blocked(int Yslot, int cpu, u64 start, u64 end, const char *backtrace)
+{
+ if (!svgfile)
+ return;
+
+ fprintf(svgfile, "<g>\n");
+ fprintf(svgfile, "<title>#%d blocked %s</title>\n", cpu,
+ time_to_string(end - start));
+ if (backtrace)
+ fprintf(svgfile, "<desc>Blocked on:\n%s</desc>\n", backtrace);
+ svg_box(Yslot, start, end, "blocked");
+ fprintf(svgfile, "</g>\n");
+}
+
+void svg_running(int Yslot, int cpu, u64 start, u64 end, const char *backtrace)
+{
+ double text_size;
+ const char *type;
+
+ if (!svgfile)
+ return;
+
+ if (svg_highlight && end - start > svg_highlight)
+ type = "sample_hi";
+ else
+ type = "sample";
+ fprintf(svgfile, "<g>\n");
+
+ fprintf(svgfile, "<title>#%d running %s</title>\n",
+ cpu, time_to_string(end - start));
+ if (backtrace)
+ fprintf(svgfile, "<desc>Switched because:\n%s</desc>\n", backtrace);
+ fprintf(svgfile, "<rect x=\"%4.8f\" width=\"%4.8f\" y=\"%4.1f\" height=\"%4.1f\" class=\"%s\"/>\n",
+ time2pixels(start), time2pixels(end)-time2pixels(start), Yslot * SLOT_MULT, SLOT_HEIGHT,
+ type);
+
+ text_size = (time2pixels(end)-time2pixels(start));
+ if (cpu > 9)
+ text_size = text_size/2;
+ if (text_size > 1.25)
+ text_size = 1.25;
+ text_size = round_text_size(text_size);
+
+ if (text_size > MIN_TEXT_SIZE)
+ fprintf(svgfile, "<text x=\"%1.8f\" y=\"%1.8f\" font-size=\"%1.8fpt\">%i</text>\n",
+ time2pixels(start), Yslot * SLOT_MULT + SLOT_HEIGHT - 1, text_size, cpu + 1);
+
+ fprintf(svgfile, "</g>\n");
+}
+
+static char *time_to_string(u64 duration)
+{
+ static char text[80];
+
+ text[0] = 0;
+
+ if (duration < 1000) /* less than 1 usec */
+ return text;
+
+ if (duration < 1000 * 1000) { /* less than 1 msec */
+ sprintf(text, "%4.1f us", duration / 1000.0);
+ return text;
+ }
+ sprintf(text, "%4.1f ms", duration / 1000.0 / 1000);
+
+ return text;
+}
+
+void svg_waiting(int Yslot, int cpu, u64 start, u64 end, const char *backtrace)
+{
+ char *text;
+ const char *style;
+ double font_size;
+
+ if (!svgfile)
+ return;
+
+ style = "waiting";
+
+ if (end-start > 10 * 1000000) /* 10 msec */
+ style = "WAITING";
+
+ text = time_to_string(end-start);
+
+ font_size = 1.0 * (time2pixels(end)-time2pixels(start));
+
+ if (font_size > 3)
+ font_size = 3;
+
+ font_size = round_text_size(font_size);
+
+ fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\">\n", time2pixels(start), Yslot * SLOT_MULT);
+ fprintf(svgfile, "<title>#%d waiting %s</title>\n", cpu, time_to_string(end - start));
+ if (backtrace)
+ fprintf(svgfile, "<desc>Waiting on:\n%s</desc>\n", backtrace);
+ fprintf(svgfile, "<rect x=\"0\" width=\"%4.8f\" y=\"0\" height=\"%4.1f\" class=\"%s\"/>\n",
+ time2pixels(end)-time2pixels(start), SLOT_HEIGHT, style);
+ if (font_size > MIN_TEXT_SIZE)
+ fprintf(svgfile, "<text transform=\"rotate(90)\" font-size=\"%1.8fpt\"> %s</text>\n",
+ font_size, text);
+ fprintf(svgfile, "</g>\n");
+}
+
+static char *cpu_model(void)
+{
+ static char cpu_m[255];
+ char buf[256];
+ FILE *file;
+
+ cpu_m[0] = 0;
+ /* CPU type */
+ file = fopen("/proc/cpuinfo", "r");
+ if (file) {
+ while (fgets(buf, 255, file)) {
+ if (strstr(buf, "model name")) {
+ strncpy(cpu_m, &buf[13], 255);
+ break;
+ }
+ }
+ fclose(file);
+ }
+
+ /* CPU type */
+ file = fopen("/sys/devices/system/cpu/cpu0/cpufreq/scaling_available_frequencies", "r");
+ if (file) {
+ while (fgets(buf, 255, file)) {
+ unsigned int freq;
+ freq = strtoull(buf, NULL, 10);
+ if (freq > max_freq)
+ max_freq = freq;
+ }
+ fclose(file);
+ }
+ return cpu_m;
+}
+
+void svg_cpu_box(int cpu, u64 __max_freq, u64 __turbo_freq)
+{
+ char cpu_string[80];
+ if (!svgfile)
+ return;
+
+ max_freq = __max_freq;
+ turbo_frequency = __turbo_freq;
+
+ fprintf(svgfile, "<g>\n");
+
+ fprintf(svgfile, "<rect x=\"%4.8f\" width=\"%4.8f\" y=\"%4.1f\" height=\"%4.1f\" class=\"cpu\"/>\n",
+ time2pixels(first_time),
+ time2pixels(last_time)-time2pixels(first_time),
+ cpu2y(cpu), SLOT_MULT+SLOT_HEIGHT);
+
+ sprintf(cpu_string, "CPU %i", (int)cpu);
+ fprintf(svgfile, "<text x=\"%4.8f\" y=\"%4.8f\">%s</text>\n",
+ 10+time2pixels(first_time), cpu2y(cpu) + SLOT_HEIGHT/2, cpu_string);
+
+ fprintf(svgfile, "<text transform=\"translate(%4.8f,%4.8f)\" font-size=\"1.25pt\">%s</text>\n",
+ 10+time2pixels(first_time), cpu2y(cpu) + SLOT_MULT + SLOT_HEIGHT - 4, cpu_model());
+
+ fprintf(svgfile, "</g>\n");
+}
+
+void svg_process(int cpu, u64 start, u64 end, int pid, const char *name, const char *backtrace)
+{
+ double width;
+ const char *type;
+
+ if (!svgfile)
+ return;
+
+ if (svg_highlight && end - start >= svg_highlight)
+ type = "sample_hi";
+ else if (svg_highlight_name && strstr(name, svg_highlight_name))
+ type = "sample_hi";
+ else
+ type = "sample";
+
+ fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\">\n", time2pixels(start), cpu2y(cpu));
+ fprintf(svgfile, "<title>%d %s running %s</title>\n", pid, name, time_to_string(end - start));
+ if (backtrace)
+ fprintf(svgfile, "<desc>Switched because:\n%s</desc>\n", backtrace);
+ fprintf(svgfile, "<rect x=\"0\" width=\"%4.8f\" y=\"0\" height=\"%4.1f\" class=\"%s\"/>\n",
+ time2pixels(end)-time2pixels(start), SLOT_MULT+SLOT_HEIGHT, type);
+ width = time2pixels(end)-time2pixels(start);
+ if (width > 6)
+ width = 6;
+
+ width = round_text_size(width);
+
+ if (width > MIN_TEXT_SIZE)
+ fprintf(svgfile, "<text transform=\"rotate(90)\" font-size=\"%3.8fpt\">%s</text>\n",
+ width, name);
+
+ fprintf(svgfile, "</g>\n");
+}
+
+void svg_cstate(int cpu, u64 start, u64 end, int type)
+{
+ double width;
+ char style[128];
+
+ if (!svgfile)
+ return;
+
+
+ fprintf(svgfile, "<g>\n");
+
+ if (type > 6)
+ type = 6;
+ sprintf(style, "c%i", type);
+
+ fprintf(svgfile, "<rect class=\"%s\" x=\"%4.8f\" width=\"%4.8f\" y=\"%4.1f\" height=\"%4.1f\"/>\n",
+ style,
+ time2pixels(start), time2pixels(end)-time2pixels(start),
+ cpu2y(cpu), SLOT_MULT+SLOT_HEIGHT);
+
+ width = (time2pixels(end)-time2pixels(start))/2.0;
+ if (width > 6)
+ width = 6;
+
+ width = round_text_size(width);
+
+ if (width > MIN_TEXT_SIZE)
+ fprintf(svgfile, "<text x=\"%4.8f\" y=\"%4.8f\" font-size=\"%3.8fpt\">C%i</text>\n",
+ time2pixels(start), cpu2y(cpu)+width, width, type);
+
+ fprintf(svgfile, "</g>\n");
+}
+
+static char *HzToHuman(unsigned long hz)
+{
+ static char buffer[1024];
+ unsigned long long Hz;
+
+ memset(buffer, 0, 1024);
+
+ Hz = hz;
+
+ /* default: just put the Number in */
+ sprintf(buffer, "%9lli", Hz);
+
+ if (Hz > 1000)
+ sprintf(buffer, " %6lli Mhz", (Hz+500)/1000);
+
+ if (Hz > 1500000)
+ sprintf(buffer, " %6.2f Ghz", (Hz+5000.0)/1000000);
+
+ if (Hz == turbo_frequency)
+ sprintf(buffer, "Turbo");
+
+ return buffer;
+}
+
+void svg_pstate(int cpu, u64 start, u64 end, u64 freq)
+{
+ double height = 0;
+
+ if (!svgfile)
+ return;
+
+ fprintf(svgfile, "<g>\n");
+
+ if (max_freq)
+ height = freq * 1.0 / max_freq * (SLOT_HEIGHT + SLOT_MULT);
+ height = 1 + cpu2y(cpu) + SLOT_MULT + SLOT_HEIGHT - height;
+ fprintf(svgfile, "<line x1=\"%4.8f\" x2=\"%4.8f\" y1=\"%4.1f\" y2=\"%4.1f\" class=\"pstate\"/>\n",
+ time2pixels(start), time2pixels(end), height, height);
+ fprintf(svgfile, "<text x=\"%4.8f\" y=\"%4.8f\" font-size=\"0.25pt\">%s</text>\n",
+ time2pixels(start), height+0.9, HzToHuman(freq));
+
+ fprintf(svgfile, "</g>\n");
+}
+
+
+void svg_partial_wakeline(u64 start, int row1, char *desc1, int row2, char *desc2, const char *backtrace)
+{
+ double height;
+
+ if (!svgfile)
+ return;
+
+
+ fprintf(svgfile, "<g>\n");
+
+ fprintf(svgfile, "<title>%s wakes up %s</title>\n",
+ desc1 ? desc1 : "?",
+ desc2 ? desc2 : "?");
+
+ if (backtrace)
+ fprintf(svgfile, "<desc>%s</desc>\n", backtrace);
+
+ if (row1 < row2) {
+ if (row1) {
+ fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n",
+ time2pixels(start), row1 * SLOT_MULT + SLOT_HEIGHT, time2pixels(start), row1 * SLOT_MULT + SLOT_HEIGHT + SLOT_MULT/32);
+ if (desc2)
+ fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s &gt;</text></g>\n",
+ time2pixels(start), row1 * SLOT_MULT + SLOT_HEIGHT + SLOT_HEIGHT/48, desc2);
+ }
+ if (row2) {
+ fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n",
+ time2pixels(start), row2 * SLOT_MULT - SLOT_MULT/32, time2pixels(start), row2 * SLOT_MULT);
+ if (desc1)
+ fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s &gt;</text></g>\n",
+ time2pixels(start), row2 * SLOT_MULT - SLOT_MULT/32, desc1);
+ }
+ } else {
+ if (row2) {
+ fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n",
+ time2pixels(start), row2 * SLOT_MULT + SLOT_HEIGHT, time2pixels(start), row2 * SLOT_MULT + SLOT_HEIGHT + SLOT_MULT/32);
+ if (desc1)
+ fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s &lt;</text></g>\n",
+ time2pixels(start), row2 * SLOT_MULT + SLOT_HEIGHT + SLOT_MULT/48, desc1);
+ }
+ if (row1) {
+ fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n",
+ time2pixels(start), row1 * SLOT_MULT - SLOT_MULT/32, time2pixels(start), row1 * SLOT_MULT);
+ if (desc2)
+ fprintf(svgfile, "<g transform=\"translate(%4.8f,%4.8f)\"><text transform=\"rotate(90)\" font-size=\"0.02pt\">%s &lt;</text></g>\n",
+ time2pixels(start), row1 * SLOT_MULT - SLOT_HEIGHT/32, desc2);
+ }
+ }
+ height = row1 * SLOT_MULT;
+ if (row2 > row1)
+ height += SLOT_HEIGHT;
+ if (row1)
+ fprintf(svgfile, "<circle cx=\"%4.8f\" cy=\"%4.2f\" r = \"0.01\" style=\"fill:rgb(32,255,32)\"/>\n",
+ time2pixels(start), height);
+
+ fprintf(svgfile, "</g>\n");
+}
+
+void svg_wakeline(u64 start, int row1, int row2, const char *backtrace)
+{
+ double height;
+
+ if (!svgfile)
+ return;
+
+
+ fprintf(svgfile, "<g>\n");
+
+ if (backtrace)
+ fprintf(svgfile, "<desc>%s</desc>\n", backtrace);
+
+ if (row1 < row2)
+ fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n",
+ time2pixels(start), row1 * SLOT_MULT + SLOT_HEIGHT, time2pixels(start), row2 * SLOT_MULT);
+ else
+ fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%4.2f\" style=\"stroke:rgb(32,255,32);stroke-width:0.009\"/>\n",
+ time2pixels(start), row2 * SLOT_MULT + SLOT_HEIGHT, time2pixels(start), row1 * SLOT_MULT);
+
+ height = row1 * SLOT_MULT;
+ if (row2 > row1)
+ height += SLOT_HEIGHT;
+ fprintf(svgfile, "<circle cx=\"%4.8f\" cy=\"%4.2f\" r = \"0.01\" style=\"fill:rgb(32,255,32)\"/>\n",
+ time2pixels(start), height);
+
+ fprintf(svgfile, "</g>\n");
+}
+
+void svg_interrupt(u64 start, int row, const char *backtrace)
+{
+ if (!svgfile)
+ return;
+
+ fprintf(svgfile, "<g>\n");
+
+ fprintf(svgfile, "<title>Wakeup from interrupt</title>\n");
+
+ if (backtrace)
+ fprintf(svgfile, "<desc>%s</desc>\n", backtrace);
+
+ fprintf(svgfile, "<circle cx=\"%4.8f\" cy=\"%4.2f\" r = \"0.01\" style=\"fill:rgb(255,128,128)\"/>\n",
+ time2pixels(start), row * SLOT_MULT);
+ fprintf(svgfile, "<circle cx=\"%4.8f\" cy=\"%4.2f\" r = \"0.01\" style=\"fill:rgb(255,128,128)\"/>\n",
+ time2pixels(start), row * SLOT_MULT + SLOT_HEIGHT);
+
+ fprintf(svgfile, "</g>\n");
+}
+
+void svg_text(int Yslot, u64 start, const char *text)
+{
+ if (!svgfile)
+ return;
+
+ fprintf(svgfile, "<text x=\"%4.8f\" y=\"%4.8f\">%s</text>\n",
+ time2pixels(start), Yslot * SLOT_MULT+SLOT_HEIGHT/2, text);
+}
+
+static void svg_legenda_box(int X, const char *text, const char *style)
+{
+ double boxsize;
+ boxsize = SLOT_HEIGHT / 2;
+
+ fprintf(svgfile, "<rect x=\"%i\" width=\"%4.8f\" y=\"0\" height=\"%4.1f\" class=\"%s\"/>\n",
+ X, boxsize, boxsize, style);
+ fprintf(svgfile, "<text transform=\"translate(%4.8f, %4.8f)\" font-size=\"%4.8fpt\">%s</text>\n",
+ X + boxsize + 5, boxsize, 0.8 * boxsize, text);
+}
+
+void svg_legenda(void)
+{
+ if (!svgfile)
+ return;
+
+ fprintf(svgfile, "<g>\n");
+ svg_legenda_box(0, "Running", "sample");
+ svg_legenda_box(100, "Idle","c1");
+ svg_legenda_box(200, "Deeper Idle", "c3");
+ svg_legenda_box(350, "Deepest Idle", "c6");
+ svg_legenda_box(550, "Sleeping", "process2");
+ svg_legenda_box(650, "Waiting for cpu", "waiting");
+ svg_legenda_box(800, "Blocked on IO", "blocked");
+ fprintf(svgfile, "</g>\n");
+}
+
+void svg_time_grid(void)
+{
+ u64 i;
+
+ if (!svgfile)
+ return;
+
+ i = first_time;
+ while (i < last_time) {
+ int color = 220;
+ double thickness = 0.075;
+ if ((i % 100000000) == 0) {
+ thickness = 0.5;
+ color = 192;
+ }
+ if ((i % 1000000000) == 0) {
+ thickness = 2.0;
+ color = 128;
+ }
+
+ fprintf(svgfile, "<line x1=\"%4.8f\" y1=\"%4.2f\" x2=\"%4.8f\" y2=\"%" PRIu64 "\" style=\"stroke:rgb(%i,%i,%i);stroke-width:%1.3f\"/>\n",
+ time2pixels(i), SLOT_MULT/2, time2pixels(i), total_height, color, color, color, thickness);
+
+ i += 10000000;
+ }
+}
+
+void svg_close(void)
+{
+ if (svgfile) {
+ fprintf(svgfile, "</svg>\n");
+ fclose(svgfile);
+ svgfile = NULL;
+ }
+}
+
+#define cpumask_bits(maskp) ((maskp)->bits)
+typedef struct { DECLARE_BITMAP(bits, MAX_NR_CPUS); } cpumask_t;
+
+struct topology {
+ cpumask_t *sib_core;
+ int sib_core_nr;
+ cpumask_t *sib_thr;
+ int sib_thr_nr;
+};
+
+static void scan_thread_topology(int *map, struct topology *t, int cpu, int *pos)
+{
+ int i;
+ int thr;
+
+ for (i = 0; i < t->sib_thr_nr; i++) {
+ if (!test_bit(cpu, cpumask_bits(&t->sib_thr[i])))
+ continue;
+
+ for_each_set_bit(thr,
+ cpumask_bits(&t->sib_thr[i]),
+ MAX_NR_CPUS)
+ if (map[thr] == -1)
+ map[thr] = (*pos)++;
+ }
+}
+
+static void scan_core_topology(int *map, struct topology *t)
+{
+ int pos = 0;
+ int i;
+ int cpu;
+
+ for (i = 0; i < t->sib_core_nr; i++)
+ for_each_set_bit(cpu,
+ cpumask_bits(&t->sib_core[i]),
+ MAX_NR_CPUS)
+ scan_thread_topology(map, t, cpu, &pos);
+}
+
+static int str_to_bitmap(char *s, cpumask_t *b)
+{
+ int i;
+ int ret = 0;
+ struct cpu_map *m;
+ int c;
+
+ m = cpu_map__new(s);
+ if (!m)
+ return -1;
+
+ for (i = 0; i < m->nr; i++) {
+ c = m->map[i];
+ if (c >= MAX_NR_CPUS) {
+ ret = -1;
+ break;
+ }
+
+ set_bit(c, cpumask_bits(b));
+ }
+
+ cpu_map__delete(m);
+
+ return ret;
+}
+
+int svg_build_topology_map(char *sib_core, int sib_core_nr,
+ char *sib_thr, int sib_thr_nr)
+{
+ int i;
+ struct topology t;
+
+ t.sib_core_nr = sib_core_nr;
+ t.sib_thr_nr = sib_thr_nr;
+ t.sib_core = calloc(sib_core_nr, sizeof(cpumask_t));
+ t.sib_thr = calloc(sib_thr_nr, sizeof(cpumask_t));
+
+ if (!t.sib_core || !t.sib_thr) {
+ fprintf(stderr, "topology: no memory\n");
+ goto exit;
+ }
+
+ for (i = 0; i < sib_core_nr; i++) {
+ if (str_to_bitmap(sib_core, &t.sib_core[i])) {
+ fprintf(stderr, "topology: can't parse siblings map\n");
+ goto exit;
+ }
+
+ sib_core += strlen(sib_core) + 1;
+ }
+
+ for (i = 0; i < sib_thr_nr; i++) {
+ if (str_to_bitmap(sib_thr, &t.sib_thr[i])) {
+ fprintf(stderr, "topology: can't parse siblings map\n");
+ goto exit;
+ }
+
+ sib_thr += strlen(sib_thr) + 1;
+ }
+
+ topology_map = malloc(sizeof(int) * MAX_NR_CPUS);
+ if (!topology_map) {
+ fprintf(stderr, "topology: no memory\n");
+ goto exit;
+ }
+
+ for (i = 0; i < MAX_NR_CPUS; i++)
+ topology_map[i] = -1;
+
+ scan_core_topology(topology_map, &t);
+
+ return 0;
+
+exit:
+ zfree(&t.sib_core);
+ zfree(&t.sib_thr);
+
+ return -1;
+}
diff --git a/tools/perf/util/svghelper.h b/tools/perf/util/svghelper.h
new file mode 100644
index 00000000000..e3aff5332e3
--- /dev/null
+++ b/tools/perf/util/svghelper.h
@@ -0,0 +1,33 @@
+#ifndef __PERF_SVGHELPER_H
+#define __PERF_SVGHELPER_H
+
+#include <linux/types.h>
+
+extern void open_svg(const char *filename, int cpus, int rows, u64 start, u64 end);
+extern void svg_box(int Yslot, u64 start, u64 end, const char *type);
+extern void svg_blocked(int Yslot, int cpu, u64 start, u64 end, const char *backtrace);
+extern void svg_running(int Yslot, int cpu, u64 start, u64 end, const char *backtrace);
+extern void svg_waiting(int Yslot, int cpu, u64 start, u64 end, const char *backtrace);
+extern void svg_cpu_box(int cpu, u64 max_frequency, u64 turbo_frequency);
+
+
+extern void svg_process(int cpu, u64 start, u64 end, int pid, const char *name, const char *backtrace);
+extern void svg_cstate(int cpu, u64 start, u64 end, int type);
+extern void svg_pstate(int cpu, u64 start, u64 end, u64 freq);
+
+
+extern void svg_time_grid(void);
+extern void svg_legenda(void);
+extern void svg_wakeline(u64 start, int row1, int row2, const char *backtrace);
+extern void svg_partial_wakeline(u64 start, int row1, char *desc1, int row2, char *desc2, const char *backtrace);
+extern void svg_interrupt(u64 start, int row, const char *backtrace);
+extern void svg_text(int Yslot, u64 start, const char *text);
+extern void svg_close(void);
+extern int svg_build_topology_map(char *sib_core, int sib_core_nr,
+ char *sib_thr, int sib_thr_nr);
+
+extern int svg_page_width;
+extern u64 svg_highlight;
+extern const char *svg_highlight_name;
+
+#endif /* __PERF_SVGHELPER_H */
diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c
new file mode 100644
index 00000000000..6864661a79d
--- /dev/null
+++ b/tools/perf/util/symbol-elf.c
@@ -0,0 +1,1625 @@
+#include <fcntl.h>
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <inttypes.h>
+
+#include "symbol.h"
+#include "vdso.h"
+#include <symbol/kallsyms.h>
+#include "debug.h"
+
+#ifndef HAVE_ELF_GETPHDRNUM_SUPPORT
+static int elf_getphdrnum(Elf *elf, size_t *dst)
+{
+ GElf_Ehdr gehdr;
+ GElf_Ehdr *ehdr;
+
+ ehdr = gelf_getehdr(elf, &gehdr);
+ if (!ehdr)
+ return -1;
+
+ *dst = ehdr->e_phnum;
+
+ return 0;
+}
+#endif
+
+#ifndef NT_GNU_BUILD_ID
+#define NT_GNU_BUILD_ID 3
+#endif
+
+/**
+ * elf_symtab__for_each_symbol - iterate thru all the symbols
+ *
+ * @syms: struct elf_symtab instance to iterate
+ * @idx: uint32_t idx
+ * @sym: GElf_Sym iterator
+ */
+#define elf_symtab__for_each_symbol(syms, nr_syms, idx, sym) \
+ for (idx = 0, gelf_getsym(syms, idx, &sym);\
+ idx < nr_syms; \
+ idx++, gelf_getsym(syms, idx, &sym))
+
+static inline uint8_t elf_sym__type(const GElf_Sym *sym)
+{
+ return GELF_ST_TYPE(sym->st_info);
+}
+
+static inline int elf_sym__is_function(const GElf_Sym *sym)
+{
+ return elf_sym__type(sym) == STT_FUNC &&
+ sym->st_name != 0 &&
+ sym->st_shndx != SHN_UNDEF;
+}
+
+static inline bool elf_sym__is_object(const GElf_Sym *sym)
+{
+ return elf_sym__type(sym) == STT_OBJECT &&
+ sym->st_name != 0 &&
+ sym->st_shndx != SHN_UNDEF;
+}
+
+static inline int elf_sym__is_label(const GElf_Sym *sym)
+{
+ return elf_sym__type(sym) == STT_NOTYPE &&
+ sym->st_name != 0 &&
+ sym->st_shndx != SHN_UNDEF &&
+ sym->st_shndx != SHN_ABS;
+}
+
+static bool elf_sym__is_a(GElf_Sym *sym, enum map_type type)
+{
+ switch (type) {
+ case MAP__FUNCTION:
+ return elf_sym__is_function(sym);
+ case MAP__VARIABLE:
+ return elf_sym__is_object(sym);
+ default:
+ return false;
+ }
+}
+
+static inline const char *elf_sym__name(const GElf_Sym *sym,
+ const Elf_Data *symstrs)
+{
+ return symstrs->d_buf + sym->st_name;
+}
+
+static inline const char *elf_sec__name(const GElf_Shdr *shdr,
+ const Elf_Data *secstrs)
+{
+ return secstrs->d_buf + shdr->sh_name;
+}
+
+static inline int elf_sec__is_text(const GElf_Shdr *shdr,
+ const Elf_Data *secstrs)
+{
+ return strstr(elf_sec__name(shdr, secstrs), "text") != NULL;
+}
+
+static inline bool elf_sec__is_data(const GElf_Shdr *shdr,
+ const Elf_Data *secstrs)
+{
+ return strstr(elf_sec__name(shdr, secstrs), "data") != NULL;
+}
+
+static bool elf_sec__is_a(GElf_Shdr *shdr, Elf_Data *secstrs,
+ enum map_type type)
+{
+ switch (type) {
+ case MAP__FUNCTION:
+ return elf_sec__is_text(shdr, secstrs);
+ case MAP__VARIABLE:
+ return elf_sec__is_data(shdr, secstrs);
+ default:
+ return false;
+ }
+}
+
+static size_t elf_addr_to_index(Elf *elf, GElf_Addr addr)
+{
+ Elf_Scn *sec = NULL;
+ GElf_Shdr shdr;
+ size_t cnt = 1;
+
+ while ((sec = elf_nextscn(elf, sec)) != NULL) {
+ gelf_getshdr(sec, &shdr);
+
+ if ((addr >= shdr.sh_addr) &&
+ (addr < (shdr.sh_addr + shdr.sh_size)))
+ return cnt;
+
+ ++cnt;
+ }
+
+ return -1;
+}
+
+Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep,
+ GElf_Shdr *shp, const char *name, size_t *idx)
+{
+ Elf_Scn *sec = NULL;
+ size_t cnt = 1;
+
+ /* Elf is corrupted/truncated, avoid calling elf_strptr. */
+ if (!elf_rawdata(elf_getscn(elf, ep->e_shstrndx), NULL))
+ return NULL;
+
+ while ((sec = elf_nextscn(elf, sec)) != NULL) {
+ char *str;
+
+ gelf_getshdr(sec, shp);
+ str = elf_strptr(elf, ep->e_shstrndx, shp->sh_name);
+ if (str && !strcmp(name, str)) {
+ if (idx)
+ *idx = cnt;
+ return sec;
+ }
+ ++cnt;
+ }
+
+ return NULL;
+}
+
+#define elf_section__for_each_rel(reldata, pos, pos_mem, idx, nr_entries) \
+ for (idx = 0, pos = gelf_getrel(reldata, 0, &pos_mem); \
+ idx < nr_entries; \
+ ++idx, pos = gelf_getrel(reldata, idx, &pos_mem))
+
+#define elf_section__for_each_rela(reldata, pos, pos_mem, idx, nr_entries) \
+ for (idx = 0, pos = gelf_getrela(reldata, 0, &pos_mem); \
+ idx < nr_entries; \
+ ++idx, pos = gelf_getrela(reldata, idx, &pos_mem))
+
+/*
+ * We need to check if we have a .dynsym, so that we can handle the
+ * .plt, synthesizing its symbols, that aren't on the symtabs (be it
+ * .dynsym or .symtab).
+ * And always look at the original dso, not at debuginfo packages, that
+ * have the PLT data stripped out (shdr_rel_plt.sh_type == SHT_NOBITS).
+ */
+int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss, struct map *map,
+ symbol_filter_t filter)
+{
+ uint32_t nr_rel_entries, idx;
+ GElf_Sym sym;
+ u64 plt_offset;
+ GElf_Shdr shdr_plt;
+ struct symbol *f;
+ GElf_Shdr shdr_rel_plt, shdr_dynsym;
+ Elf_Data *reldata, *syms, *symstrs;
+ Elf_Scn *scn_plt_rel, *scn_symstrs, *scn_dynsym;
+ size_t dynsym_idx;
+ GElf_Ehdr ehdr;
+ char sympltname[1024];
+ Elf *elf;
+ int nr = 0, symidx, err = 0;
+
+ if (!ss->dynsym)
+ return 0;
+
+ elf = ss->elf;
+ ehdr = ss->ehdr;
+
+ scn_dynsym = ss->dynsym;
+ shdr_dynsym = ss->dynshdr;
+ dynsym_idx = ss->dynsym_idx;
+
+ if (scn_dynsym == NULL)
+ goto out_elf_end;
+
+ scn_plt_rel = elf_section_by_name(elf, &ehdr, &shdr_rel_plt,
+ ".rela.plt", NULL);
+ if (scn_plt_rel == NULL) {
+ scn_plt_rel = elf_section_by_name(elf, &ehdr, &shdr_rel_plt,
+ ".rel.plt", NULL);
+ if (scn_plt_rel == NULL)
+ goto out_elf_end;
+ }
+
+ err = -1;
+
+ if (shdr_rel_plt.sh_link != dynsym_idx)
+ goto out_elf_end;
+
+ if (elf_section_by_name(elf, &ehdr, &shdr_plt, ".plt", NULL) == NULL)
+ goto out_elf_end;
+
+ /*
+ * Fetch the relocation section to find the idxes to the GOT
+ * and the symbols in the .dynsym they refer to.
+ */
+ reldata = elf_getdata(scn_plt_rel, NULL);
+ if (reldata == NULL)
+ goto out_elf_end;
+
+ syms = elf_getdata(scn_dynsym, NULL);
+ if (syms == NULL)
+ goto out_elf_end;
+
+ scn_symstrs = elf_getscn(elf, shdr_dynsym.sh_link);
+ if (scn_symstrs == NULL)
+ goto out_elf_end;
+
+ symstrs = elf_getdata(scn_symstrs, NULL);
+ if (symstrs == NULL)
+ goto out_elf_end;
+
+ if (symstrs->d_size == 0)
+ goto out_elf_end;
+
+ nr_rel_entries = shdr_rel_plt.sh_size / shdr_rel_plt.sh_entsize;
+ plt_offset = shdr_plt.sh_offset;
+
+ if (shdr_rel_plt.sh_type == SHT_RELA) {
+ GElf_Rela pos_mem, *pos;
+
+ elf_section__for_each_rela(reldata, pos, pos_mem, idx,
+ nr_rel_entries) {
+ symidx = GELF_R_SYM(pos->r_info);
+ plt_offset += shdr_plt.sh_entsize;
+ gelf_getsym(syms, symidx, &sym);
+ snprintf(sympltname, sizeof(sympltname),
+ "%s@plt", elf_sym__name(&sym, symstrs));
+
+ f = symbol__new(plt_offset, shdr_plt.sh_entsize,
+ STB_GLOBAL, sympltname);
+ if (!f)
+ goto out_elf_end;
+
+ if (filter && filter(map, f))
+ symbol__delete(f);
+ else {
+ symbols__insert(&dso->symbols[map->type], f);
+ ++nr;
+ }
+ }
+ } else if (shdr_rel_plt.sh_type == SHT_REL) {
+ GElf_Rel pos_mem, *pos;
+ elf_section__for_each_rel(reldata, pos, pos_mem, idx,
+ nr_rel_entries) {
+ symidx = GELF_R_SYM(pos->r_info);
+ plt_offset += shdr_plt.sh_entsize;
+ gelf_getsym(syms, symidx, &sym);
+ snprintf(sympltname, sizeof(sympltname),
+ "%s@plt", elf_sym__name(&sym, symstrs));
+
+ f = symbol__new(plt_offset, shdr_plt.sh_entsize,
+ STB_GLOBAL, sympltname);
+ if (!f)
+ goto out_elf_end;
+
+ if (filter && filter(map, f))
+ symbol__delete(f);
+ else {
+ symbols__insert(&dso->symbols[map->type], f);
+ ++nr;
+ }
+ }
+ }
+
+ err = 0;
+out_elf_end:
+ if (err == 0)
+ return nr;
+ pr_debug("%s: problems reading %s PLT info.\n",
+ __func__, dso->long_name);
+ return 0;
+}
+
+/*
+ * Align offset to 4 bytes as needed for note name and descriptor data.
+ */
+#define NOTE_ALIGN(n) (((n) + 3) & -4U)
+
+static int elf_read_build_id(Elf *elf, void *bf, size_t size)
+{
+ int err = -1;
+ GElf_Ehdr ehdr;
+ GElf_Shdr shdr;
+ Elf_Data *data;
+ Elf_Scn *sec;
+ Elf_Kind ek;
+ void *ptr;
+
+ if (size < BUILD_ID_SIZE)
+ goto out;
+
+ ek = elf_kind(elf);
+ if (ek != ELF_K_ELF)
+ goto out;
+
+ if (gelf_getehdr(elf, &ehdr) == NULL) {
+ pr_err("%s: cannot get elf header.\n", __func__);
+ goto out;
+ }
+
+ /*
+ * Check following sections for notes:
+ * '.note.gnu.build-id'
+ * '.notes'
+ * '.note' (VDSO specific)
+ */
+ do {
+ sec = elf_section_by_name(elf, &ehdr, &shdr,
+ ".note.gnu.build-id", NULL);
+ if (sec)
+ break;
+
+ sec = elf_section_by_name(elf, &ehdr, &shdr,
+ ".notes", NULL);
+ if (sec)
+ break;
+
+ sec = elf_section_by_name(elf, &ehdr, &shdr,
+ ".note", NULL);
+ if (sec)
+ break;
+
+ return err;
+
+ } while (0);
+
+ data = elf_getdata(sec, NULL);
+ if (data == NULL)
+ goto out;
+
+ ptr = data->d_buf;
+ while (ptr < (data->d_buf + data->d_size)) {
+ GElf_Nhdr *nhdr = ptr;
+ size_t namesz = NOTE_ALIGN(nhdr->n_namesz),
+ descsz = NOTE_ALIGN(nhdr->n_descsz);
+ const char *name;
+
+ ptr += sizeof(*nhdr);
+ name = ptr;
+ ptr += namesz;
+ if (nhdr->n_type == NT_GNU_BUILD_ID &&
+ nhdr->n_namesz == sizeof("GNU")) {
+ if (memcmp(name, "GNU", sizeof("GNU")) == 0) {
+ size_t sz = min(size, descsz);
+ memcpy(bf, ptr, sz);
+ memset(bf + sz, 0, size - sz);
+ err = descsz;
+ break;
+ }
+ }
+ ptr += descsz;
+ }
+
+out:
+ return err;
+}
+
+int filename__read_build_id(const char *filename, void *bf, size_t size)
+{
+ int fd, err = -1;
+ Elf *elf;
+
+ if (size < BUILD_ID_SIZE)
+ goto out;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ goto out;
+
+ elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL);
+ if (elf == NULL) {
+ pr_debug2("%s: cannot read %s ELF file.\n", __func__, filename);
+ goto out_close;
+ }
+
+ err = elf_read_build_id(elf, bf, size);
+
+ elf_end(elf);
+out_close:
+ close(fd);
+out:
+ return err;
+}
+
+int sysfs__read_build_id(const char *filename, void *build_id, size_t size)
+{
+ int fd, err = -1;
+
+ if (size < BUILD_ID_SIZE)
+ goto out;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ goto out;
+
+ while (1) {
+ char bf[BUFSIZ];
+ GElf_Nhdr nhdr;
+ size_t namesz, descsz;
+
+ if (read(fd, &nhdr, sizeof(nhdr)) != sizeof(nhdr))
+ break;
+
+ namesz = NOTE_ALIGN(nhdr.n_namesz);
+ descsz = NOTE_ALIGN(nhdr.n_descsz);
+ if (nhdr.n_type == NT_GNU_BUILD_ID &&
+ nhdr.n_namesz == sizeof("GNU")) {
+ if (read(fd, bf, namesz) != (ssize_t)namesz)
+ break;
+ if (memcmp(bf, "GNU", sizeof("GNU")) == 0) {
+ size_t sz = min(descsz, size);
+ if (read(fd, build_id, sz) == (ssize_t)sz) {
+ memset(build_id + sz, 0, size - sz);
+ err = 0;
+ break;
+ }
+ } else if (read(fd, bf, descsz) != (ssize_t)descsz)
+ break;
+ } else {
+ int n = namesz + descsz;
+ if (read(fd, bf, n) != n)
+ break;
+ }
+ }
+ close(fd);
+out:
+ return err;
+}
+
+int filename__read_debuglink(const char *filename, char *debuglink,
+ size_t size)
+{
+ int fd, err = -1;
+ Elf *elf;
+ GElf_Ehdr ehdr;
+ GElf_Shdr shdr;
+ Elf_Data *data;
+ Elf_Scn *sec;
+ Elf_Kind ek;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ goto out;
+
+ elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL);
+ if (elf == NULL) {
+ pr_debug2("%s: cannot read %s ELF file.\n", __func__, filename);
+ goto out_close;
+ }
+
+ ek = elf_kind(elf);
+ if (ek != ELF_K_ELF)
+ goto out_elf_end;
+
+ if (gelf_getehdr(elf, &ehdr) == NULL) {
+ pr_err("%s: cannot get elf header.\n", __func__);
+ goto out_elf_end;
+ }
+
+ sec = elf_section_by_name(elf, &ehdr, &shdr,
+ ".gnu_debuglink", NULL);
+ if (sec == NULL)
+ goto out_elf_end;
+
+ data = elf_getdata(sec, NULL);
+ if (data == NULL)
+ goto out_elf_end;
+
+ /* the start of this section is a zero-terminated string */
+ strncpy(debuglink, data->d_buf, size);
+
+ err = 0;
+
+out_elf_end:
+ elf_end(elf);
+out_close:
+ close(fd);
+out:
+ return err;
+}
+
+static int dso__swap_init(struct dso *dso, unsigned char eidata)
+{
+ static unsigned int const endian = 1;
+
+ dso->needs_swap = DSO_SWAP__NO;
+
+ switch (eidata) {
+ case ELFDATA2LSB:
+ /* We are big endian, DSO is little endian. */
+ if (*(unsigned char const *)&endian != 1)
+ dso->needs_swap = DSO_SWAP__YES;
+ break;
+
+ case ELFDATA2MSB:
+ /* We are little endian, DSO is big endian. */
+ if (*(unsigned char const *)&endian != 0)
+ dso->needs_swap = DSO_SWAP__YES;
+ break;
+
+ default:
+ pr_err("unrecognized DSO data encoding %d\n", eidata);
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+bool symsrc__possibly_runtime(struct symsrc *ss)
+{
+ return ss->dynsym || ss->opdsec;
+}
+
+bool symsrc__has_symtab(struct symsrc *ss)
+{
+ return ss->symtab != NULL;
+}
+
+void symsrc__destroy(struct symsrc *ss)
+{
+ zfree(&ss->name);
+ elf_end(ss->elf);
+ close(ss->fd);
+}
+
+int symsrc__init(struct symsrc *ss, struct dso *dso, const char *name,
+ enum dso_binary_type type)
+{
+ int err = -1;
+ GElf_Ehdr ehdr;
+ Elf *elf;
+ int fd;
+
+ fd = open(name, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL);
+ if (elf == NULL) {
+ pr_debug("%s: cannot read %s ELF file.\n", __func__, name);
+ goto out_close;
+ }
+
+ if (gelf_getehdr(elf, &ehdr) == NULL) {
+ pr_debug("%s: cannot get elf header.\n", __func__);
+ goto out_elf_end;
+ }
+
+ if (dso__swap_init(dso, ehdr.e_ident[EI_DATA]))
+ goto out_elf_end;
+
+ /* Always reject images with a mismatched build-id: */
+ if (dso->has_build_id) {
+ u8 build_id[BUILD_ID_SIZE];
+
+ if (elf_read_build_id(elf, build_id, BUILD_ID_SIZE) < 0)
+ goto out_elf_end;
+
+ if (!dso__build_id_equal(dso, build_id))
+ goto out_elf_end;
+ }
+
+ ss->symtab = elf_section_by_name(elf, &ehdr, &ss->symshdr, ".symtab",
+ NULL);
+ if (ss->symshdr.sh_type != SHT_SYMTAB)
+ ss->symtab = NULL;
+
+ ss->dynsym_idx = 0;
+ ss->dynsym = elf_section_by_name(elf, &ehdr, &ss->dynshdr, ".dynsym",
+ &ss->dynsym_idx);
+ if (ss->dynshdr.sh_type != SHT_DYNSYM)
+ ss->dynsym = NULL;
+
+ ss->opdidx = 0;
+ ss->opdsec = elf_section_by_name(elf, &ehdr, &ss->opdshdr, ".opd",
+ &ss->opdidx);
+ if (ss->opdshdr.sh_type != SHT_PROGBITS)
+ ss->opdsec = NULL;
+
+ if (dso->kernel == DSO_TYPE_USER) {
+ GElf_Shdr shdr;
+ ss->adjust_symbols = (ehdr.e_type == ET_EXEC ||
+ ehdr.e_type == ET_REL ||
+ is_vdso_map(dso->short_name) ||
+ elf_section_by_name(elf, &ehdr, &shdr,
+ ".gnu.prelink_undo",
+ NULL) != NULL);
+ } else {
+ ss->adjust_symbols = ehdr.e_type == ET_EXEC ||
+ ehdr.e_type == ET_REL;
+ }
+
+ ss->name = strdup(name);
+ if (!ss->name)
+ goto out_elf_end;
+
+ ss->elf = elf;
+ ss->fd = fd;
+ ss->ehdr = ehdr;
+ ss->type = type;
+
+ return 0;
+
+out_elf_end:
+ elf_end(elf);
+out_close:
+ close(fd);
+ return err;
+}
+
+/**
+ * ref_reloc_sym_not_found - has kernel relocation symbol been found.
+ * @kmap: kernel maps and relocation reference symbol
+ *
+ * This function returns %true if we are dealing with the kernel maps and the
+ * relocation reference symbol has not yet been found. Otherwise %false is
+ * returned.
+ */
+static bool ref_reloc_sym_not_found(struct kmap *kmap)
+{
+ return kmap && kmap->ref_reloc_sym && kmap->ref_reloc_sym->name &&
+ !kmap->ref_reloc_sym->unrelocated_addr;
+}
+
+/**
+ * ref_reloc - kernel relocation offset.
+ * @kmap: kernel maps and relocation reference symbol
+ *
+ * This function returns the offset of kernel addresses as determined by using
+ * the relocation reference symbol i.e. if the kernel has not been relocated
+ * then the return value is zero.
+ */
+static u64 ref_reloc(struct kmap *kmap)
+{
+ if (kmap && kmap->ref_reloc_sym &&
+ kmap->ref_reloc_sym->unrelocated_addr)
+ return kmap->ref_reloc_sym->addr -
+ kmap->ref_reloc_sym->unrelocated_addr;
+ return 0;
+}
+
+int dso__load_sym(struct dso *dso, struct map *map,
+ struct symsrc *syms_ss, struct symsrc *runtime_ss,
+ symbol_filter_t filter, int kmodule)
+{
+ struct kmap *kmap = dso->kernel ? map__kmap(map) : NULL;
+ struct map *curr_map = map;
+ struct dso *curr_dso = dso;
+ Elf_Data *symstrs, *secstrs;
+ uint32_t nr_syms;
+ int err = -1;
+ uint32_t idx;
+ GElf_Ehdr ehdr;
+ GElf_Shdr shdr;
+ Elf_Data *syms, *opddata = NULL;
+ GElf_Sym sym;
+ Elf_Scn *sec, *sec_strndx;
+ Elf *elf;
+ int nr = 0;
+ bool remap_kernel = false, adjust_kernel_syms = false;
+
+ dso->symtab_type = syms_ss->type;
+ dso->rel = syms_ss->ehdr.e_type == ET_REL;
+
+ /*
+ * Modules may already have symbols from kallsyms, but those symbols
+ * have the wrong values for the dso maps, so remove them.
+ */
+ if (kmodule && syms_ss->symtab)
+ symbols__delete(&dso->symbols[map->type]);
+
+ if (!syms_ss->symtab) {
+ syms_ss->symtab = syms_ss->dynsym;
+ syms_ss->symshdr = syms_ss->dynshdr;
+ }
+
+ elf = syms_ss->elf;
+ ehdr = syms_ss->ehdr;
+ sec = syms_ss->symtab;
+ shdr = syms_ss->symshdr;
+
+ if (runtime_ss->opdsec)
+ opddata = elf_rawdata(runtime_ss->opdsec, NULL);
+
+ syms = elf_getdata(sec, NULL);
+ if (syms == NULL)
+ goto out_elf_end;
+
+ sec = elf_getscn(elf, shdr.sh_link);
+ if (sec == NULL)
+ goto out_elf_end;
+
+ symstrs = elf_getdata(sec, NULL);
+ if (symstrs == NULL)
+ goto out_elf_end;
+
+ sec_strndx = elf_getscn(elf, ehdr.e_shstrndx);
+ if (sec_strndx == NULL)
+ goto out_elf_end;
+
+ secstrs = elf_getdata(sec_strndx, NULL);
+ if (secstrs == NULL)
+ goto out_elf_end;
+
+ nr_syms = shdr.sh_size / shdr.sh_entsize;
+
+ memset(&sym, 0, sizeof(sym));
+
+ /*
+ * The kernel relocation symbol is needed in advance in order to adjust
+ * kernel maps correctly.
+ */
+ if (ref_reloc_sym_not_found(kmap)) {
+ elf_symtab__for_each_symbol(syms, nr_syms, idx, sym) {
+ const char *elf_name = elf_sym__name(&sym, symstrs);
+
+ if (strcmp(elf_name, kmap->ref_reloc_sym->name))
+ continue;
+ kmap->ref_reloc_sym->unrelocated_addr = sym.st_value;
+ map->reloc = kmap->ref_reloc_sym->addr -
+ kmap->ref_reloc_sym->unrelocated_addr;
+ break;
+ }
+ }
+
+ dso->adjust_symbols = runtime_ss->adjust_symbols || ref_reloc(kmap);
+ /*
+ * Initial kernel and module mappings do not map to the dso. For
+ * function mappings, flag the fixups.
+ */
+ if (map->type == MAP__FUNCTION && (dso->kernel || kmodule)) {
+ remap_kernel = true;
+ adjust_kernel_syms = dso->adjust_symbols;
+ }
+ elf_symtab__for_each_symbol(syms, nr_syms, idx, sym) {
+ struct symbol *f;
+ const char *elf_name = elf_sym__name(&sym, symstrs);
+ char *demangled = NULL;
+ int is_label = elf_sym__is_label(&sym);
+ const char *section_name;
+ bool used_opd = false;
+
+ if (!is_label && !elf_sym__is_a(&sym, map->type))
+ continue;
+
+ /* Reject ARM ELF "mapping symbols": these aren't unique and
+ * don't identify functions, so will confuse the profile
+ * output: */
+ if (ehdr.e_machine == EM_ARM) {
+ if (!strcmp(elf_name, "$a") ||
+ !strcmp(elf_name, "$d") ||
+ !strcmp(elf_name, "$t"))
+ continue;
+ }
+
+ if (runtime_ss->opdsec && sym.st_shndx == runtime_ss->opdidx) {
+ u32 offset = sym.st_value - syms_ss->opdshdr.sh_addr;
+ u64 *opd = opddata->d_buf + offset;
+ sym.st_value = DSO__SWAP(dso, u64, *opd);
+ sym.st_shndx = elf_addr_to_index(runtime_ss->elf,
+ sym.st_value);
+ used_opd = true;
+ }
+ /*
+ * When loading symbols in a data mapping, ABS symbols (which
+ * has a value of SHN_ABS in its st_shndx) failed at
+ * elf_getscn(). And it marks the loading as a failure so
+ * already loaded symbols cannot be fixed up.
+ *
+ * I'm not sure what should be done. Just ignore them for now.
+ * - Namhyung Kim
+ */
+ if (sym.st_shndx == SHN_ABS)
+ continue;
+
+ sec = elf_getscn(runtime_ss->elf, sym.st_shndx);
+ if (!sec)
+ goto out_elf_end;
+
+ gelf_getshdr(sec, &shdr);
+
+ if (is_label && !elf_sec__is_a(&shdr, secstrs, map->type))
+ continue;
+
+ section_name = elf_sec__name(&shdr, secstrs);
+
+ /* On ARM, symbols for thumb functions have 1 added to
+ * the symbol address as a flag - remove it */
+ if ((ehdr.e_machine == EM_ARM) &&
+ (map->type == MAP__FUNCTION) &&
+ (sym.st_value & 1))
+ --sym.st_value;
+
+ if (dso->kernel || kmodule) {
+ char dso_name[PATH_MAX];
+
+ /* Adjust symbol to map to file offset */
+ if (adjust_kernel_syms)
+ sym.st_value -= shdr.sh_addr - shdr.sh_offset;
+
+ if (strcmp(section_name,
+ (curr_dso->short_name +
+ dso->short_name_len)) == 0)
+ goto new_symbol;
+
+ if (strcmp(section_name, ".text") == 0) {
+ /*
+ * The initial kernel mapping is based on
+ * kallsyms and identity maps. Overwrite it to
+ * map to the kernel dso.
+ */
+ if (remap_kernel && dso->kernel) {
+ remap_kernel = false;
+ map->start = shdr.sh_addr +
+ ref_reloc(kmap);
+ map->end = map->start + shdr.sh_size;
+ map->pgoff = shdr.sh_offset;
+ map->map_ip = map__map_ip;
+ map->unmap_ip = map__unmap_ip;
+ /* Ensure maps are correctly ordered */
+ map_groups__remove(kmap->kmaps, map);
+ map_groups__insert(kmap->kmaps, map);
+ }
+
+ /*
+ * The initial module mapping is based on
+ * /proc/modules mapped to offset zero.
+ * Overwrite it to map to the module dso.
+ */
+ if (remap_kernel && kmodule) {
+ remap_kernel = false;
+ map->pgoff = shdr.sh_offset;
+ }
+
+ curr_map = map;
+ curr_dso = dso;
+ goto new_symbol;
+ }
+
+ if (!kmap)
+ goto new_symbol;
+
+ snprintf(dso_name, sizeof(dso_name),
+ "%s%s", dso->short_name, section_name);
+
+ curr_map = map_groups__find_by_name(kmap->kmaps, map->type, dso_name);
+ if (curr_map == NULL) {
+ u64 start = sym.st_value;
+
+ if (kmodule)
+ start += map->start + shdr.sh_offset;
+
+ curr_dso = dso__new(dso_name);
+ if (curr_dso == NULL)
+ goto out_elf_end;
+ curr_dso->kernel = dso->kernel;
+ curr_dso->long_name = dso->long_name;
+ curr_dso->long_name_len = dso->long_name_len;
+ curr_map = map__new2(start, curr_dso,
+ map->type);
+ if (curr_map == NULL) {
+ dso__delete(curr_dso);
+ goto out_elf_end;
+ }
+ if (adjust_kernel_syms) {
+ curr_map->start = shdr.sh_addr +
+ ref_reloc(kmap);
+ curr_map->end = curr_map->start +
+ shdr.sh_size;
+ curr_map->pgoff = shdr.sh_offset;
+ } else {
+ curr_map->map_ip = identity__map_ip;
+ curr_map->unmap_ip = identity__map_ip;
+ }
+ curr_dso->symtab_type = dso->symtab_type;
+ map_groups__insert(kmap->kmaps, curr_map);
+ dsos__add(&dso->node, curr_dso);
+ dso__set_loaded(curr_dso, map->type);
+ } else
+ curr_dso = curr_map->dso;
+
+ goto new_symbol;
+ }
+
+ if ((used_opd && runtime_ss->adjust_symbols)
+ || (!used_opd && syms_ss->adjust_symbols)) {
+ pr_debug4("%s: adjusting symbol: st_value: %#" PRIx64 " "
+ "sh_addr: %#" PRIx64 " sh_offset: %#" PRIx64 "\n", __func__,
+ (u64)sym.st_value, (u64)shdr.sh_addr,
+ (u64)shdr.sh_offset);
+ sym.st_value -= shdr.sh_addr - shdr.sh_offset;
+ }
+new_symbol:
+ /*
+ * We need to figure out if the object was created from C++ sources
+ * DWARF DW_compile_unit has this, but we don't always have access
+ * to it...
+ */
+ if (symbol_conf.demangle) {
+ demangled = bfd_demangle(NULL, elf_name,
+ DMGL_PARAMS | DMGL_ANSI);
+ if (demangled != NULL)
+ elf_name = demangled;
+ }
+ f = symbol__new(sym.st_value, sym.st_size,
+ GELF_ST_BIND(sym.st_info), elf_name);
+ free(demangled);
+ if (!f)
+ goto out_elf_end;
+
+ if (filter && filter(curr_map, f))
+ symbol__delete(f);
+ else {
+ symbols__insert(&curr_dso->symbols[curr_map->type], f);
+ nr++;
+ }
+ }
+
+ /*
+ * For misannotated, zeroed, ASM function sizes.
+ */
+ if (nr > 0) {
+ symbols__fixup_duplicate(&dso->symbols[map->type]);
+ symbols__fixup_end(&dso->symbols[map->type]);
+ if (kmap) {
+ /*
+ * We need to fixup this here too because we create new
+ * maps here, for things like vsyscall sections.
+ */
+ __map_groups__fixup_end(kmap->kmaps, map->type);
+ }
+ }
+ err = nr;
+out_elf_end:
+ return err;
+}
+
+static int elf_read_maps(Elf *elf, bool exe, mapfn_t mapfn, void *data)
+{
+ GElf_Phdr phdr;
+ size_t i, phdrnum;
+ int err;
+ u64 sz;
+
+ if (elf_getphdrnum(elf, &phdrnum))
+ return -1;
+
+ for (i = 0; i < phdrnum; i++) {
+ if (gelf_getphdr(elf, i, &phdr) == NULL)
+ return -1;
+ if (phdr.p_type != PT_LOAD)
+ continue;
+ if (exe) {
+ if (!(phdr.p_flags & PF_X))
+ continue;
+ } else {
+ if (!(phdr.p_flags & PF_R))
+ continue;
+ }
+ sz = min(phdr.p_memsz, phdr.p_filesz);
+ if (!sz)
+ continue;
+ err = mapfn(phdr.p_vaddr, sz, phdr.p_offset, data);
+ if (err)
+ return err;
+ }
+ return 0;
+}
+
+int file__read_maps(int fd, bool exe, mapfn_t mapfn, void *data,
+ bool *is_64_bit)
+{
+ int err;
+ Elf *elf;
+
+ elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL);
+ if (elf == NULL)
+ return -1;
+
+ if (is_64_bit)
+ *is_64_bit = (gelf_getclass(elf) == ELFCLASS64);
+
+ err = elf_read_maps(elf, exe, mapfn, data);
+
+ elf_end(elf);
+ return err;
+}
+
+static int copy_bytes(int from, off_t from_offs, int to, off_t to_offs, u64 len)
+{
+ ssize_t r;
+ size_t n;
+ int err = -1;
+ char *buf = malloc(page_size);
+
+ if (buf == NULL)
+ return -1;
+
+ if (lseek(to, to_offs, SEEK_SET) != to_offs)
+ goto out;
+
+ if (lseek(from, from_offs, SEEK_SET) != from_offs)
+ goto out;
+
+ while (len) {
+ n = page_size;
+ if (len < n)
+ n = len;
+ /* Use read because mmap won't work on proc files */
+ r = read(from, buf, n);
+ if (r < 0)
+ goto out;
+ if (!r)
+ break;
+ n = r;
+ r = write(to, buf, n);
+ if (r < 0)
+ goto out;
+ if ((size_t)r != n)
+ goto out;
+ len -= n;
+ }
+
+ err = 0;
+out:
+ free(buf);
+ return err;
+}
+
+struct kcore {
+ int fd;
+ int elfclass;
+ Elf *elf;
+ GElf_Ehdr ehdr;
+};
+
+static int kcore__open(struct kcore *kcore, const char *filename)
+{
+ GElf_Ehdr *ehdr;
+
+ kcore->fd = open(filename, O_RDONLY);
+ if (kcore->fd == -1)
+ return -1;
+
+ kcore->elf = elf_begin(kcore->fd, ELF_C_READ, NULL);
+ if (!kcore->elf)
+ goto out_close;
+
+ kcore->elfclass = gelf_getclass(kcore->elf);
+ if (kcore->elfclass == ELFCLASSNONE)
+ goto out_end;
+
+ ehdr = gelf_getehdr(kcore->elf, &kcore->ehdr);
+ if (!ehdr)
+ goto out_end;
+
+ return 0;
+
+out_end:
+ elf_end(kcore->elf);
+out_close:
+ close(kcore->fd);
+ return -1;
+}
+
+static int kcore__init(struct kcore *kcore, char *filename, int elfclass,
+ bool temp)
+{
+ GElf_Ehdr *ehdr;
+
+ kcore->elfclass = elfclass;
+
+ if (temp)
+ kcore->fd = mkstemp(filename);
+ else
+ kcore->fd = open(filename, O_WRONLY | O_CREAT | O_EXCL, 0400);
+ if (kcore->fd == -1)
+ return -1;
+
+ kcore->elf = elf_begin(kcore->fd, ELF_C_WRITE, NULL);
+ if (!kcore->elf)
+ goto out_close;
+
+ if (!gelf_newehdr(kcore->elf, elfclass))
+ goto out_end;
+
+ ehdr = gelf_getehdr(kcore->elf, &kcore->ehdr);
+ if (!ehdr)
+ goto out_end;
+
+ return 0;
+
+out_end:
+ elf_end(kcore->elf);
+out_close:
+ close(kcore->fd);
+ unlink(filename);
+ return -1;
+}
+
+static void kcore__close(struct kcore *kcore)
+{
+ elf_end(kcore->elf);
+ close(kcore->fd);
+}
+
+static int kcore__copy_hdr(struct kcore *from, struct kcore *to, size_t count)
+{
+ GElf_Ehdr *ehdr = &to->ehdr;
+ GElf_Ehdr *kehdr = &from->ehdr;
+
+ memcpy(ehdr->e_ident, kehdr->e_ident, EI_NIDENT);
+ ehdr->e_type = kehdr->e_type;
+ ehdr->e_machine = kehdr->e_machine;
+ ehdr->e_version = kehdr->e_version;
+ ehdr->e_entry = 0;
+ ehdr->e_shoff = 0;
+ ehdr->e_flags = kehdr->e_flags;
+ ehdr->e_phnum = count;
+ ehdr->e_shentsize = 0;
+ ehdr->e_shnum = 0;
+ ehdr->e_shstrndx = 0;
+
+ if (from->elfclass == ELFCLASS32) {
+ ehdr->e_phoff = sizeof(Elf32_Ehdr);
+ ehdr->e_ehsize = sizeof(Elf32_Ehdr);
+ ehdr->e_phentsize = sizeof(Elf32_Phdr);
+ } else {
+ ehdr->e_phoff = sizeof(Elf64_Ehdr);
+ ehdr->e_ehsize = sizeof(Elf64_Ehdr);
+ ehdr->e_phentsize = sizeof(Elf64_Phdr);
+ }
+
+ if (!gelf_update_ehdr(to->elf, ehdr))
+ return -1;
+
+ if (!gelf_newphdr(to->elf, count))
+ return -1;
+
+ return 0;
+}
+
+static int kcore__add_phdr(struct kcore *kcore, int idx, off_t offset,
+ u64 addr, u64 len)
+{
+ GElf_Phdr gphdr;
+ GElf_Phdr *phdr;
+
+ phdr = gelf_getphdr(kcore->elf, idx, &gphdr);
+ if (!phdr)
+ return -1;
+
+ phdr->p_type = PT_LOAD;
+ phdr->p_flags = PF_R | PF_W | PF_X;
+ phdr->p_offset = offset;
+ phdr->p_vaddr = addr;
+ phdr->p_paddr = 0;
+ phdr->p_filesz = len;
+ phdr->p_memsz = len;
+ phdr->p_align = page_size;
+
+ if (!gelf_update_phdr(kcore->elf, idx, phdr))
+ return -1;
+
+ return 0;
+}
+
+static off_t kcore__write(struct kcore *kcore)
+{
+ return elf_update(kcore->elf, ELF_C_WRITE);
+}
+
+struct phdr_data {
+ off_t offset;
+ u64 addr;
+ u64 len;
+};
+
+struct kcore_copy_info {
+ u64 stext;
+ u64 etext;
+ u64 first_symbol;
+ u64 last_symbol;
+ u64 first_module;
+ u64 last_module_symbol;
+ struct phdr_data kernel_map;
+ struct phdr_data modules_map;
+};
+
+static int kcore_copy__process_kallsyms(void *arg, const char *name, char type,
+ u64 start)
+{
+ struct kcore_copy_info *kci = arg;
+
+ if (!symbol_type__is_a(type, MAP__FUNCTION))
+ return 0;
+
+ if (strchr(name, '[')) {
+ if (start > kci->last_module_symbol)
+ kci->last_module_symbol = start;
+ return 0;
+ }
+
+ if (!kci->first_symbol || start < kci->first_symbol)
+ kci->first_symbol = start;
+
+ if (!kci->last_symbol || start > kci->last_symbol)
+ kci->last_symbol = start;
+
+ if (!strcmp(name, "_stext")) {
+ kci->stext = start;
+ return 0;
+ }
+
+ if (!strcmp(name, "_etext")) {
+ kci->etext = start;
+ return 0;
+ }
+
+ return 0;
+}
+
+static int kcore_copy__parse_kallsyms(struct kcore_copy_info *kci,
+ const char *dir)
+{
+ char kallsyms_filename[PATH_MAX];
+
+ scnprintf(kallsyms_filename, PATH_MAX, "%s/kallsyms", dir);
+
+ if (symbol__restricted_filename(kallsyms_filename, "/proc/kallsyms"))
+ return -1;
+
+ if (kallsyms__parse(kallsyms_filename, kci,
+ kcore_copy__process_kallsyms) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int kcore_copy__process_modules(void *arg,
+ const char *name __maybe_unused,
+ u64 start)
+{
+ struct kcore_copy_info *kci = arg;
+
+ if (!kci->first_module || start < kci->first_module)
+ kci->first_module = start;
+
+ return 0;
+}
+
+static int kcore_copy__parse_modules(struct kcore_copy_info *kci,
+ const char *dir)
+{
+ char modules_filename[PATH_MAX];
+
+ scnprintf(modules_filename, PATH_MAX, "%s/modules", dir);
+
+ if (symbol__restricted_filename(modules_filename, "/proc/modules"))
+ return -1;
+
+ if (modules__parse(modules_filename, kci,
+ kcore_copy__process_modules) < 0)
+ return -1;
+
+ return 0;
+}
+
+static void kcore_copy__map(struct phdr_data *p, u64 start, u64 end, u64 pgoff,
+ u64 s, u64 e)
+{
+ if (p->addr || s < start || s >= end)
+ return;
+
+ p->addr = s;
+ p->offset = (s - start) + pgoff;
+ p->len = e < end ? e - s : end - s;
+}
+
+static int kcore_copy__read_map(u64 start, u64 len, u64 pgoff, void *data)
+{
+ struct kcore_copy_info *kci = data;
+ u64 end = start + len;
+
+ kcore_copy__map(&kci->kernel_map, start, end, pgoff, kci->stext,
+ kci->etext);
+
+ kcore_copy__map(&kci->modules_map, start, end, pgoff, kci->first_module,
+ kci->last_module_symbol);
+
+ return 0;
+}
+
+static int kcore_copy__read_maps(struct kcore_copy_info *kci, Elf *elf)
+{
+ if (elf_read_maps(elf, true, kcore_copy__read_map, kci) < 0)
+ return -1;
+
+ return 0;
+}
+
+static int kcore_copy__calc_maps(struct kcore_copy_info *kci, const char *dir,
+ Elf *elf)
+{
+ if (kcore_copy__parse_kallsyms(kci, dir))
+ return -1;
+
+ if (kcore_copy__parse_modules(kci, dir))
+ return -1;
+
+ if (kci->stext)
+ kci->stext = round_down(kci->stext, page_size);
+ else
+ kci->stext = round_down(kci->first_symbol, page_size);
+
+ if (kci->etext) {
+ kci->etext = round_up(kci->etext, page_size);
+ } else if (kci->last_symbol) {
+ kci->etext = round_up(kci->last_symbol, page_size);
+ kci->etext += page_size;
+ }
+
+ kci->first_module = round_down(kci->first_module, page_size);
+
+ if (kci->last_module_symbol) {
+ kci->last_module_symbol = round_up(kci->last_module_symbol,
+ page_size);
+ kci->last_module_symbol += page_size;
+ }
+
+ if (!kci->stext || !kci->etext)
+ return -1;
+
+ if (kci->first_module && !kci->last_module_symbol)
+ return -1;
+
+ return kcore_copy__read_maps(kci, elf);
+}
+
+static int kcore_copy__copy_file(const char *from_dir, const char *to_dir,
+ const char *name)
+{
+ char from_filename[PATH_MAX];
+ char to_filename[PATH_MAX];
+
+ scnprintf(from_filename, PATH_MAX, "%s/%s", from_dir, name);
+ scnprintf(to_filename, PATH_MAX, "%s/%s", to_dir, name);
+
+ return copyfile_mode(from_filename, to_filename, 0400);
+}
+
+static int kcore_copy__unlink(const char *dir, const char *name)
+{
+ char filename[PATH_MAX];
+
+ scnprintf(filename, PATH_MAX, "%s/%s", dir, name);
+
+ return unlink(filename);
+}
+
+static int kcore_copy__compare_fds(int from, int to)
+{
+ char *buf_from;
+ char *buf_to;
+ ssize_t ret;
+ size_t len;
+ int err = -1;
+
+ buf_from = malloc(page_size);
+ buf_to = malloc(page_size);
+ if (!buf_from || !buf_to)
+ goto out;
+
+ while (1) {
+ /* Use read because mmap won't work on proc files */
+ ret = read(from, buf_from, page_size);
+ if (ret < 0)
+ goto out;
+
+ if (!ret)
+ break;
+
+ len = ret;
+
+ if (readn(to, buf_to, len) != (int)len)
+ goto out;
+
+ if (memcmp(buf_from, buf_to, len))
+ goto out;
+ }
+
+ err = 0;
+out:
+ free(buf_to);
+ free(buf_from);
+ return err;
+}
+
+static int kcore_copy__compare_files(const char *from_filename,
+ const char *to_filename)
+{
+ int from, to, err = -1;
+
+ from = open(from_filename, O_RDONLY);
+ if (from < 0)
+ return -1;
+
+ to = open(to_filename, O_RDONLY);
+ if (to < 0)
+ goto out_close_from;
+
+ err = kcore_copy__compare_fds(from, to);
+
+ close(to);
+out_close_from:
+ close(from);
+ return err;
+}
+
+static int kcore_copy__compare_file(const char *from_dir, const char *to_dir,
+ const char *name)
+{
+ char from_filename[PATH_MAX];
+ char to_filename[PATH_MAX];
+
+ scnprintf(from_filename, PATH_MAX, "%s/%s", from_dir, name);
+ scnprintf(to_filename, PATH_MAX, "%s/%s", to_dir, name);
+
+ return kcore_copy__compare_files(from_filename, to_filename);
+}
+
+/**
+ * kcore_copy - copy kallsyms, modules and kcore from one directory to another.
+ * @from_dir: from directory
+ * @to_dir: to directory
+ *
+ * This function copies kallsyms, modules and kcore files from one directory to
+ * another. kallsyms and modules are copied entirely. Only code segments are
+ * copied from kcore. It is assumed that two segments suffice: one for the
+ * kernel proper and one for all the modules. The code segments are determined
+ * from kallsyms and modules files. The kernel map starts at _stext or the
+ * lowest function symbol, and ends at _etext or the highest function symbol.
+ * The module map starts at the lowest module address and ends at the highest
+ * module symbol. Start addresses are rounded down to the nearest page. End
+ * addresses are rounded up to the nearest page. An extra page is added to the
+ * highest kernel symbol and highest module symbol to, hopefully, encompass that
+ * symbol too. Because it contains only code sections, the resulting kcore is
+ * unusual. One significant peculiarity is that the mapping (start -> pgoff)
+ * is not the same for the kernel map and the modules map. That happens because
+ * the data is copied adjacently whereas the original kcore has gaps. Finally,
+ * kallsyms and modules files are compared with their copies to check that
+ * modules have not been loaded or unloaded while the copies were taking place.
+ *
+ * Return: %0 on success, %-1 on failure.
+ */
+int kcore_copy(const char *from_dir, const char *to_dir)
+{
+ struct kcore kcore;
+ struct kcore extract;
+ size_t count = 2;
+ int idx = 0, err = -1;
+ off_t offset = page_size, sz, modules_offset = 0;
+ struct kcore_copy_info kci = { .stext = 0, };
+ char kcore_filename[PATH_MAX];
+ char extract_filename[PATH_MAX];
+
+ if (kcore_copy__copy_file(from_dir, to_dir, "kallsyms"))
+ return -1;
+
+ if (kcore_copy__copy_file(from_dir, to_dir, "modules"))
+ goto out_unlink_kallsyms;
+
+ scnprintf(kcore_filename, PATH_MAX, "%s/kcore", from_dir);
+ scnprintf(extract_filename, PATH_MAX, "%s/kcore", to_dir);
+
+ if (kcore__open(&kcore, kcore_filename))
+ goto out_unlink_modules;
+
+ if (kcore_copy__calc_maps(&kci, from_dir, kcore.elf))
+ goto out_kcore_close;
+
+ if (kcore__init(&extract, extract_filename, kcore.elfclass, false))
+ goto out_kcore_close;
+
+ if (!kci.modules_map.addr)
+ count -= 1;
+
+ if (kcore__copy_hdr(&kcore, &extract, count))
+ goto out_extract_close;
+
+ if (kcore__add_phdr(&extract, idx++, offset, kci.kernel_map.addr,
+ kci.kernel_map.len))
+ goto out_extract_close;
+
+ if (kci.modules_map.addr) {
+ modules_offset = offset + kci.kernel_map.len;
+ if (kcore__add_phdr(&extract, idx, modules_offset,
+ kci.modules_map.addr, kci.modules_map.len))
+ goto out_extract_close;
+ }
+
+ sz = kcore__write(&extract);
+ if (sz < 0 || sz > offset)
+ goto out_extract_close;
+
+ if (copy_bytes(kcore.fd, kci.kernel_map.offset, extract.fd, offset,
+ kci.kernel_map.len))
+ goto out_extract_close;
+
+ if (modules_offset && copy_bytes(kcore.fd, kci.modules_map.offset,
+ extract.fd, modules_offset,
+ kci.modules_map.len))
+ goto out_extract_close;
+
+ if (kcore_copy__compare_file(from_dir, to_dir, "modules"))
+ goto out_extract_close;
+
+ if (kcore_copy__compare_file(from_dir, to_dir, "kallsyms"))
+ goto out_extract_close;
+
+ err = 0;
+
+out_extract_close:
+ kcore__close(&extract);
+ if (err)
+ unlink(extract_filename);
+out_kcore_close:
+ kcore__close(&kcore);
+out_unlink_modules:
+ if (err)
+ kcore_copy__unlink(to_dir, "modules");
+out_unlink_kallsyms:
+ if (err)
+ kcore_copy__unlink(to_dir, "kallsyms");
+
+ return err;
+}
+
+int kcore_extract__create(struct kcore_extract *kce)
+{
+ struct kcore kcore;
+ struct kcore extract;
+ size_t count = 1;
+ int idx = 0, err = -1;
+ off_t offset = page_size, sz;
+
+ if (kcore__open(&kcore, kce->kcore_filename))
+ return -1;
+
+ strcpy(kce->extract_filename, PERF_KCORE_EXTRACT);
+ if (kcore__init(&extract, kce->extract_filename, kcore.elfclass, true))
+ goto out_kcore_close;
+
+ if (kcore__copy_hdr(&kcore, &extract, count))
+ goto out_extract_close;
+
+ if (kcore__add_phdr(&extract, idx, offset, kce->addr, kce->len))
+ goto out_extract_close;
+
+ sz = kcore__write(&extract);
+ if (sz < 0 || sz > offset)
+ goto out_extract_close;
+
+ if (copy_bytes(kcore.fd, kce->offs, extract.fd, offset, kce->len))
+ goto out_extract_close;
+
+ err = 0;
+
+out_extract_close:
+ kcore__close(&extract);
+ if (err)
+ unlink(kce->extract_filename);
+out_kcore_close:
+ kcore__close(&kcore);
+
+ return err;
+}
+
+void kcore_extract__delete(struct kcore_extract *kce)
+{
+ unlink(kce->extract_filename);
+}
+
+void symbol__elf_init(void)
+{
+ elf_version(EV_CURRENT);
+}
diff --git a/tools/perf/util/symbol-minimal.c b/tools/perf/util/symbol-minimal.c
new file mode 100644
index 00000000000..bd15f490d04
--- /dev/null
+++ b/tools/perf/util/symbol-minimal.c
@@ -0,0 +1,330 @@
+#include "symbol.h"
+#include "util.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <string.h>
+#include <byteswap.h>
+#include <sys/stat.h>
+
+
+static bool check_need_swap(int file_endian)
+{
+ const int data = 1;
+ u8 *check = (u8 *)&data;
+ int host_endian;
+
+ if (check[0] == 1)
+ host_endian = ELFDATA2LSB;
+ else
+ host_endian = ELFDATA2MSB;
+
+ return host_endian != file_endian;
+}
+
+#define NOTE_ALIGN(sz) (((sz) + 3) & ~3)
+
+#define NT_GNU_BUILD_ID 3
+
+static int read_build_id(void *note_data, size_t note_len, void *bf,
+ size_t size, bool need_swap)
+{
+ struct {
+ u32 n_namesz;
+ u32 n_descsz;
+ u32 n_type;
+ } *nhdr;
+ void *ptr;
+
+ ptr = note_data;
+ while (ptr < (note_data + note_len)) {
+ const char *name;
+ size_t namesz, descsz;
+
+ nhdr = ptr;
+ if (need_swap) {
+ nhdr->n_namesz = bswap_32(nhdr->n_namesz);
+ nhdr->n_descsz = bswap_32(nhdr->n_descsz);
+ nhdr->n_type = bswap_32(nhdr->n_type);
+ }
+
+ namesz = NOTE_ALIGN(nhdr->n_namesz);
+ descsz = NOTE_ALIGN(nhdr->n_descsz);
+
+ ptr += sizeof(*nhdr);
+ name = ptr;
+ ptr += namesz;
+ if (nhdr->n_type == NT_GNU_BUILD_ID &&
+ nhdr->n_namesz == sizeof("GNU")) {
+ if (memcmp(name, "GNU", sizeof("GNU")) == 0) {
+ size_t sz = min(size, descsz);
+ memcpy(bf, ptr, sz);
+ memset(bf + sz, 0, size - sz);
+ return 0;
+ }
+ }
+ ptr += descsz;
+ }
+
+ return -1;
+}
+
+int filename__read_debuglink(const char *filename __maybe_unused,
+ char *debuglink __maybe_unused,
+ size_t size __maybe_unused)
+{
+ return -1;
+}
+
+/*
+ * Just try PT_NOTE header otherwise fails
+ */
+int filename__read_build_id(const char *filename, void *bf, size_t size)
+{
+ FILE *fp;
+ int ret = -1;
+ bool need_swap = false;
+ u8 e_ident[EI_NIDENT];
+ size_t buf_size;
+ void *buf;
+ int i;
+
+ fp = fopen(filename, "r");
+ if (fp == NULL)
+ return -1;
+
+ if (fread(e_ident, sizeof(e_ident), 1, fp) != 1)
+ goto out;
+
+ if (memcmp(e_ident, ELFMAG, SELFMAG) ||
+ e_ident[EI_VERSION] != EV_CURRENT)
+ goto out;
+
+ need_swap = check_need_swap(e_ident[EI_DATA]);
+
+ /* for simplicity */
+ fseek(fp, 0, SEEK_SET);
+
+ if (e_ident[EI_CLASS] == ELFCLASS32) {
+ Elf32_Ehdr ehdr;
+ Elf32_Phdr *phdr;
+
+ if (fread(&ehdr, sizeof(ehdr), 1, fp) != 1)
+ goto out;
+
+ if (need_swap) {
+ ehdr.e_phoff = bswap_32(ehdr.e_phoff);
+ ehdr.e_phentsize = bswap_16(ehdr.e_phentsize);
+ ehdr.e_phnum = bswap_16(ehdr.e_phnum);
+ }
+
+ buf_size = ehdr.e_phentsize * ehdr.e_phnum;
+ buf = malloc(buf_size);
+ if (buf == NULL)
+ goto out;
+
+ fseek(fp, ehdr.e_phoff, SEEK_SET);
+ if (fread(buf, buf_size, 1, fp) != 1)
+ goto out_free;
+
+ for (i = 0, phdr = buf; i < ehdr.e_phnum; i++, phdr++) {
+ void *tmp;
+
+ if (need_swap) {
+ phdr->p_type = bswap_32(phdr->p_type);
+ phdr->p_offset = bswap_32(phdr->p_offset);
+ phdr->p_filesz = bswap_32(phdr->p_filesz);
+ }
+
+ if (phdr->p_type != PT_NOTE)
+ continue;
+
+ buf_size = phdr->p_filesz;
+ tmp = realloc(buf, buf_size);
+ if (tmp == NULL)
+ goto out_free;
+
+ buf = tmp;
+ fseek(fp, phdr->p_offset, SEEK_SET);
+ if (fread(buf, buf_size, 1, fp) != 1)
+ goto out_free;
+
+ ret = read_build_id(buf, buf_size, bf, size, need_swap);
+ if (ret == 0)
+ ret = size;
+ break;
+ }
+ } else {
+ Elf64_Ehdr ehdr;
+ Elf64_Phdr *phdr;
+
+ if (fread(&ehdr, sizeof(ehdr), 1, fp) != 1)
+ goto out;
+
+ if (need_swap) {
+ ehdr.e_phoff = bswap_64(ehdr.e_phoff);
+ ehdr.e_phentsize = bswap_16(ehdr.e_phentsize);
+ ehdr.e_phnum = bswap_16(ehdr.e_phnum);
+ }
+
+ buf_size = ehdr.e_phentsize * ehdr.e_phnum;
+ buf = malloc(buf_size);
+ if (buf == NULL)
+ goto out;
+
+ fseek(fp, ehdr.e_phoff, SEEK_SET);
+ if (fread(buf, buf_size, 1, fp) != 1)
+ goto out_free;
+
+ for (i = 0, phdr = buf; i < ehdr.e_phnum; i++, phdr++) {
+ void *tmp;
+
+ if (need_swap) {
+ phdr->p_type = bswap_32(phdr->p_type);
+ phdr->p_offset = bswap_64(phdr->p_offset);
+ phdr->p_filesz = bswap_64(phdr->p_filesz);
+ }
+
+ if (phdr->p_type != PT_NOTE)
+ continue;
+
+ buf_size = phdr->p_filesz;
+ tmp = realloc(buf, buf_size);
+ if (tmp == NULL)
+ goto out_free;
+
+ buf = tmp;
+ fseek(fp, phdr->p_offset, SEEK_SET);
+ if (fread(buf, buf_size, 1, fp) != 1)
+ goto out_free;
+
+ ret = read_build_id(buf, buf_size, bf, size, need_swap);
+ if (ret == 0)
+ ret = size;
+ break;
+ }
+ }
+out_free:
+ free(buf);
+out:
+ fclose(fp);
+ return ret;
+}
+
+int sysfs__read_build_id(const char *filename, void *build_id, size_t size)
+{
+ int fd;
+ int ret = -1;
+ struct stat stbuf;
+ size_t buf_size;
+ void *buf;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ if (fstat(fd, &stbuf) < 0)
+ goto out;
+
+ buf_size = stbuf.st_size;
+ buf = malloc(buf_size);
+ if (buf == NULL)
+ goto out;
+
+ if (read(fd, buf, buf_size) != (ssize_t) buf_size)
+ goto out_free;
+
+ ret = read_build_id(buf, buf_size, build_id, size, false);
+out_free:
+ free(buf);
+out:
+ close(fd);
+ return ret;
+}
+
+int symsrc__init(struct symsrc *ss, struct dso *dso __maybe_unused,
+ const char *name,
+ enum dso_binary_type type)
+{
+ int fd = open(name, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ ss->name = strdup(name);
+ if (!ss->name)
+ goto out_close;
+
+ ss->fd = fd;
+ ss->type = type;
+
+ return 0;
+out_close:
+ close(fd);
+ return -1;
+}
+
+bool symsrc__possibly_runtime(struct symsrc *ss __maybe_unused)
+{
+ /* Assume all sym sources could be a runtime image. */
+ return true;
+}
+
+bool symsrc__has_symtab(struct symsrc *ss __maybe_unused)
+{
+ return false;
+}
+
+void symsrc__destroy(struct symsrc *ss)
+{
+ zfree(&ss->name);
+ close(ss->fd);
+}
+
+int dso__synthesize_plt_symbols(struct dso *dso __maybe_unused,
+ struct symsrc *ss __maybe_unused,
+ struct map *map __maybe_unused,
+ symbol_filter_t filter __maybe_unused)
+{
+ return 0;
+}
+
+int dso__load_sym(struct dso *dso, struct map *map __maybe_unused,
+ struct symsrc *ss,
+ struct symsrc *runtime_ss __maybe_unused,
+ symbol_filter_t filter __maybe_unused,
+ int kmodule __maybe_unused)
+{
+ unsigned char *build_id[BUILD_ID_SIZE];
+
+ if (filename__read_build_id(ss->name, build_id, BUILD_ID_SIZE) > 0) {
+ dso__set_build_id(dso, build_id);
+ return 1;
+ }
+ return 0;
+}
+
+int file__read_maps(int fd __maybe_unused, bool exe __maybe_unused,
+ mapfn_t mapfn __maybe_unused, void *data __maybe_unused,
+ bool *is_64_bit __maybe_unused)
+{
+ return -1;
+}
+
+int kcore_extract__create(struct kcore_extract *kce __maybe_unused)
+{
+ return -1;
+}
+
+void kcore_extract__delete(struct kcore_extract *kce __maybe_unused)
+{
+}
+
+int kcore_copy(const char *from_dir __maybe_unused,
+ const char *to_dir __maybe_unused)
+{
+ return -1;
+}
+
+void symbol__elf_init(void)
+{
+}
diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c
new file mode 100644
index 00000000000..7b9096f29cd
--- /dev/null
+++ b/tools/perf/util/symbol.c
@@ -0,0 +1,1880 @@
+#include <dirent.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/param.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <inttypes.h>
+#include "build-id.h"
+#include "util.h"
+#include "debug.h"
+#include "machine.h"
+#include "symbol.h"
+#include "strlist.h"
+
+#include <elf.h>
+#include <limits.h>
+#include <symbol/kallsyms.h>
+#include <sys/utsname.h>
+
+static int dso__load_kernel_sym(struct dso *dso, struct map *map,
+ symbol_filter_t filter);
+static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map,
+ symbol_filter_t filter);
+int vmlinux_path__nr_entries;
+char **vmlinux_path;
+
+struct symbol_conf symbol_conf = {
+ .use_modules = true,
+ .try_vmlinux_path = true,
+ .annotate_src = true,
+ .demangle = true,
+ .cumulate_callchain = true,
+ .symfs = "",
+};
+
+static enum dso_binary_type binary_type_symtab[] = {
+ DSO_BINARY_TYPE__KALLSYMS,
+ DSO_BINARY_TYPE__GUEST_KALLSYMS,
+ DSO_BINARY_TYPE__JAVA_JIT,
+ DSO_BINARY_TYPE__DEBUGLINK,
+ DSO_BINARY_TYPE__BUILD_ID_CACHE,
+ DSO_BINARY_TYPE__FEDORA_DEBUGINFO,
+ DSO_BINARY_TYPE__UBUNTU_DEBUGINFO,
+ DSO_BINARY_TYPE__BUILDID_DEBUGINFO,
+ DSO_BINARY_TYPE__SYSTEM_PATH_DSO,
+ DSO_BINARY_TYPE__GUEST_KMODULE,
+ DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE,
+ DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO,
+ DSO_BINARY_TYPE__NOT_FOUND,
+};
+
+#define DSO_BINARY_TYPE__SYMTAB_CNT ARRAY_SIZE(binary_type_symtab)
+
+bool symbol_type__is_a(char symbol_type, enum map_type map_type)
+{
+ symbol_type = toupper(symbol_type);
+
+ switch (map_type) {
+ case MAP__FUNCTION:
+ return symbol_type == 'T' || symbol_type == 'W';
+ case MAP__VARIABLE:
+ return symbol_type == 'D';
+ default:
+ return false;
+ }
+}
+
+static int prefix_underscores_count(const char *str)
+{
+ const char *tail = str;
+
+ while (*tail == '_')
+ tail++;
+
+ return tail - str;
+}
+
+#define SYMBOL_A 0
+#define SYMBOL_B 1
+
+static int choose_best_symbol(struct symbol *syma, struct symbol *symb)
+{
+ s64 a;
+ s64 b;
+ size_t na, nb;
+
+ /* Prefer a symbol with non zero length */
+ a = syma->end - syma->start;
+ b = symb->end - symb->start;
+ if ((b == 0) && (a > 0))
+ return SYMBOL_A;
+ else if ((a == 0) && (b > 0))
+ return SYMBOL_B;
+
+ /* Prefer a non weak symbol over a weak one */
+ a = syma->binding == STB_WEAK;
+ b = symb->binding == STB_WEAK;
+ if (b && !a)
+ return SYMBOL_A;
+ if (a && !b)
+ return SYMBOL_B;
+
+ /* Prefer a global symbol over a non global one */
+ a = syma->binding == STB_GLOBAL;
+ b = symb->binding == STB_GLOBAL;
+ if (a && !b)
+ return SYMBOL_A;
+ if (b && !a)
+ return SYMBOL_B;
+
+ /* Prefer a symbol with less underscores */
+ a = prefix_underscores_count(syma->name);
+ b = prefix_underscores_count(symb->name);
+ if (b > a)
+ return SYMBOL_A;
+ else if (a > b)
+ return SYMBOL_B;
+
+ /* Choose the symbol with the longest name */
+ na = strlen(syma->name);
+ nb = strlen(symb->name);
+ if (na > nb)
+ return SYMBOL_A;
+ else if (na < nb)
+ return SYMBOL_B;
+
+ /* Avoid "SyS" kernel syscall aliases */
+ if (na >= 3 && !strncmp(syma->name, "SyS", 3))
+ return SYMBOL_B;
+ if (na >= 10 && !strncmp(syma->name, "compat_SyS", 10))
+ return SYMBOL_B;
+
+ return SYMBOL_A;
+}
+
+void symbols__fixup_duplicate(struct rb_root *symbols)
+{
+ struct rb_node *nd;
+ struct symbol *curr, *next;
+
+ nd = rb_first(symbols);
+
+ while (nd) {
+ curr = rb_entry(nd, struct symbol, rb_node);
+again:
+ nd = rb_next(&curr->rb_node);
+ next = rb_entry(nd, struct symbol, rb_node);
+
+ if (!nd)
+ break;
+
+ if (curr->start != next->start)
+ continue;
+
+ if (choose_best_symbol(curr, next) == SYMBOL_A) {
+ rb_erase(&next->rb_node, symbols);
+ symbol__delete(next);
+ goto again;
+ } else {
+ nd = rb_next(&curr->rb_node);
+ rb_erase(&curr->rb_node, symbols);
+ symbol__delete(curr);
+ }
+ }
+}
+
+void symbols__fixup_end(struct rb_root *symbols)
+{
+ struct rb_node *nd, *prevnd = rb_first(symbols);
+ struct symbol *curr, *prev;
+
+ if (prevnd == NULL)
+ return;
+
+ curr = rb_entry(prevnd, struct symbol, rb_node);
+
+ for (nd = rb_next(prevnd); nd; nd = rb_next(nd)) {
+ prev = curr;
+ curr = rb_entry(nd, struct symbol, rb_node);
+
+ if (prev->end == prev->start && prev->end != curr->start)
+ prev->end = curr->start - 1;
+ }
+
+ /* Last entry */
+ if (curr->end == curr->start)
+ curr->end = roundup(curr->start, 4096);
+}
+
+void __map_groups__fixup_end(struct map_groups *mg, enum map_type type)
+{
+ struct map *prev, *curr;
+ struct rb_node *nd, *prevnd = rb_first(&mg->maps[type]);
+
+ if (prevnd == NULL)
+ return;
+
+ curr = rb_entry(prevnd, struct map, rb_node);
+
+ for (nd = rb_next(prevnd); nd; nd = rb_next(nd)) {
+ prev = curr;
+ curr = rb_entry(nd, struct map, rb_node);
+ prev->end = curr->start - 1;
+ }
+
+ /*
+ * We still haven't the actual symbols, so guess the
+ * last map final address.
+ */
+ curr->end = ~0ULL;
+}
+
+struct symbol *symbol__new(u64 start, u64 len, u8 binding, const char *name)
+{
+ size_t namelen = strlen(name) + 1;
+ struct symbol *sym = calloc(1, (symbol_conf.priv_size +
+ sizeof(*sym) + namelen));
+ if (sym == NULL)
+ return NULL;
+
+ if (symbol_conf.priv_size)
+ sym = ((void *)sym) + symbol_conf.priv_size;
+
+ sym->start = start;
+ sym->end = len ? start + len - 1 : start;
+ sym->binding = binding;
+ sym->namelen = namelen - 1;
+
+ pr_debug4("%s: %s %#" PRIx64 "-%#" PRIx64 "\n",
+ __func__, name, start, sym->end);
+ memcpy(sym->name, name, namelen);
+
+ return sym;
+}
+
+void symbol__delete(struct symbol *sym)
+{
+ free(((void *)sym) - symbol_conf.priv_size);
+}
+
+size_t symbol__fprintf(struct symbol *sym, FILE *fp)
+{
+ return fprintf(fp, " %" PRIx64 "-%" PRIx64 " %c %s\n",
+ sym->start, sym->end,
+ sym->binding == STB_GLOBAL ? 'g' :
+ sym->binding == STB_LOCAL ? 'l' : 'w',
+ sym->name);
+}
+
+size_t symbol__fprintf_symname_offs(const struct symbol *sym,
+ const struct addr_location *al, FILE *fp)
+{
+ unsigned long offset;
+ size_t length;
+
+ if (sym && sym->name) {
+ length = fprintf(fp, "%s", sym->name);
+ if (al) {
+ if (al->addr < sym->end)
+ offset = al->addr - sym->start;
+ else
+ offset = al->addr - al->map->start - sym->start;
+ length += fprintf(fp, "+0x%lx", offset);
+ }
+ return length;
+ } else
+ return fprintf(fp, "[unknown]");
+}
+
+size_t symbol__fprintf_symname(const struct symbol *sym, FILE *fp)
+{
+ return symbol__fprintf_symname_offs(sym, NULL, fp);
+}
+
+void symbols__delete(struct rb_root *symbols)
+{
+ struct symbol *pos;
+ struct rb_node *next = rb_first(symbols);
+
+ while (next) {
+ pos = rb_entry(next, struct symbol, rb_node);
+ next = rb_next(&pos->rb_node);
+ rb_erase(&pos->rb_node, symbols);
+ symbol__delete(pos);
+ }
+}
+
+void symbols__insert(struct rb_root *symbols, struct symbol *sym)
+{
+ struct rb_node **p = &symbols->rb_node;
+ struct rb_node *parent = NULL;
+ const u64 ip = sym->start;
+ struct symbol *s;
+
+ while (*p != NULL) {
+ parent = *p;
+ s = rb_entry(parent, struct symbol, rb_node);
+ if (ip < s->start)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+ rb_link_node(&sym->rb_node, parent, p);
+ rb_insert_color(&sym->rb_node, symbols);
+}
+
+static struct symbol *symbols__find(struct rb_root *symbols, u64 ip)
+{
+ struct rb_node *n;
+
+ if (symbols == NULL)
+ return NULL;
+
+ n = symbols->rb_node;
+
+ while (n) {
+ struct symbol *s = rb_entry(n, struct symbol, rb_node);
+
+ if (ip < s->start)
+ n = n->rb_left;
+ else if (ip > s->end)
+ n = n->rb_right;
+ else
+ return s;
+ }
+
+ return NULL;
+}
+
+static struct symbol *symbols__first(struct rb_root *symbols)
+{
+ struct rb_node *n = rb_first(symbols);
+
+ if (n)
+ return rb_entry(n, struct symbol, rb_node);
+
+ return NULL;
+}
+
+struct symbol_name_rb_node {
+ struct rb_node rb_node;
+ struct symbol sym;
+};
+
+static void symbols__insert_by_name(struct rb_root *symbols, struct symbol *sym)
+{
+ struct rb_node **p = &symbols->rb_node;
+ struct rb_node *parent = NULL;
+ struct symbol_name_rb_node *symn, *s;
+
+ symn = container_of(sym, struct symbol_name_rb_node, sym);
+
+ while (*p != NULL) {
+ parent = *p;
+ s = rb_entry(parent, struct symbol_name_rb_node, rb_node);
+ if (strcmp(sym->name, s->sym.name) < 0)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+ rb_link_node(&symn->rb_node, parent, p);
+ rb_insert_color(&symn->rb_node, symbols);
+}
+
+static void symbols__sort_by_name(struct rb_root *symbols,
+ struct rb_root *source)
+{
+ struct rb_node *nd;
+
+ for (nd = rb_first(source); nd; nd = rb_next(nd)) {
+ struct symbol *pos = rb_entry(nd, struct symbol, rb_node);
+ symbols__insert_by_name(symbols, pos);
+ }
+}
+
+static struct symbol *symbols__find_by_name(struct rb_root *symbols,
+ const char *name)
+{
+ struct rb_node *n;
+
+ if (symbols == NULL)
+ return NULL;
+
+ n = symbols->rb_node;
+
+ while (n) {
+ struct symbol_name_rb_node *s;
+ int cmp;
+
+ s = rb_entry(n, struct symbol_name_rb_node, rb_node);
+ cmp = strcmp(name, s->sym.name);
+
+ if (cmp < 0)
+ n = n->rb_left;
+ else if (cmp > 0)
+ n = n->rb_right;
+ else
+ return &s->sym;
+ }
+
+ return NULL;
+}
+
+struct symbol *dso__find_symbol(struct dso *dso,
+ enum map_type type, u64 addr)
+{
+ return symbols__find(&dso->symbols[type], addr);
+}
+
+static struct symbol *dso__first_symbol(struct dso *dso, enum map_type type)
+{
+ return symbols__first(&dso->symbols[type]);
+}
+
+struct symbol *dso__find_symbol_by_name(struct dso *dso, enum map_type type,
+ const char *name)
+{
+ return symbols__find_by_name(&dso->symbol_names[type], name);
+}
+
+void dso__sort_by_name(struct dso *dso, enum map_type type)
+{
+ dso__set_sorted_by_name(dso, type);
+ return symbols__sort_by_name(&dso->symbol_names[type],
+ &dso->symbols[type]);
+}
+
+size_t dso__fprintf_symbols_by_name(struct dso *dso,
+ enum map_type type, FILE *fp)
+{
+ size_t ret = 0;
+ struct rb_node *nd;
+ struct symbol_name_rb_node *pos;
+
+ for (nd = rb_first(&dso->symbol_names[type]); nd; nd = rb_next(nd)) {
+ pos = rb_entry(nd, struct symbol_name_rb_node, rb_node);
+ fprintf(fp, "%s\n", pos->sym.name);
+ }
+
+ return ret;
+}
+
+int modules__parse(const char *filename, void *arg,
+ int (*process_module)(void *arg, const char *name,
+ u64 start))
+{
+ char *line = NULL;
+ size_t n;
+ FILE *file;
+ int err = 0;
+
+ file = fopen(filename, "r");
+ if (file == NULL)
+ return -1;
+
+ while (1) {
+ char name[PATH_MAX];
+ u64 start;
+ char *sep;
+ ssize_t line_len;
+
+ line_len = getline(&line, &n, file);
+ if (line_len < 0) {
+ if (feof(file))
+ break;
+ err = -1;
+ goto out;
+ }
+
+ if (!line) {
+ err = -1;
+ goto out;
+ }
+
+ line[--line_len] = '\0'; /* \n */
+
+ sep = strrchr(line, 'x');
+ if (sep == NULL)
+ continue;
+
+ hex2u64(sep + 1, &start);
+
+ sep = strchr(line, ' ');
+ if (sep == NULL)
+ continue;
+
+ *sep = '\0';
+
+ scnprintf(name, sizeof(name), "[%s]", line);
+
+ err = process_module(arg, name, start);
+ if (err)
+ break;
+ }
+out:
+ free(line);
+ fclose(file);
+ return err;
+}
+
+struct process_kallsyms_args {
+ struct map *map;
+ struct dso *dso;
+};
+
+bool symbol__is_idle(struct symbol *sym)
+{
+ const char * const idle_symbols[] = {
+ "cpu_idle",
+ "intel_idle",
+ "default_idle",
+ "native_safe_halt",
+ "enter_idle",
+ "exit_idle",
+ "mwait_idle",
+ "mwait_idle_with_hints",
+ "poll_idle",
+ "ppc64_runlatch_off",
+ "pseries_dedicated_idle_sleep",
+ NULL
+ };
+
+ int i;
+
+ if (!sym)
+ return false;
+
+ for (i = 0; idle_symbols[i]; i++) {
+ if (!strcmp(idle_symbols[i], sym->name))
+ return true;
+ }
+
+ return false;
+}
+
+static int map__process_kallsym_symbol(void *arg, const char *name,
+ char type, u64 start)
+{
+ struct symbol *sym;
+ struct process_kallsyms_args *a = arg;
+ struct rb_root *root = &a->dso->symbols[a->map->type];
+
+ if (!symbol_type__is_a(type, a->map->type))
+ return 0;
+
+ /*
+ * module symbols are not sorted so we add all
+ * symbols, setting length to 0, and rely on
+ * symbols__fixup_end() to fix it up.
+ */
+ sym = symbol__new(start, 0, kallsyms2elf_type(type), name);
+ if (sym == NULL)
+ return -ENOMEM;
+ /*
+ * We will pass the symbols to the filter later, in
+ * map__split_kallsyms, when we have split the maps per module
+ */
+ symbols__insert(root, sym);
+
+ return 0;
+}
+
+/*
+ * Loads the function entries in /proc/kallsyms into kernel_map->dso,
+ * so that we can in the next step set the symbol ->end address and then
+ * call kernel_maps__split_kallsyms.
+ */
+static int dso__load_all_kallsyms(struct dso *dso, const char *filename,
+ struct map *map)
+{
+ struct process_kallsyms_args args = { .map = map, .dso = dso, };
+ return kallsyms__parse(filename, &args, map__process_kallsym_symbol);
+}
+
+static int dso__split_kallsyms_for_kcore(struct dso *dso, struct map *map,
+ symbol_filter_t filter)
+{
+ struct map_groups *kmaps = map__kmap(map)->kmaps;
+ struct map *curr_map;
+ struct symbol *pos;
+ int count = 0, moved = 0;
+ struct rb_root *root = &dso->symbols[map->type];
+ struct rb_node *next = rb_first(root);
+
+ while (next) {
+ char *module;
+
+ pos = rb_entry(next, struct symbol, rb_node);
+ next = rb_next(&pos->rb_node);
+
+ module = strchr(pos->name, '\t');
+ if (module)
+ *module = '\0';
+
+ curr_map = map_groups__find(kmaps, map->type, pos->start);
+
+ if (!curr_map || (filter && filter(curr_map, pos))) {
+ rb_erase(&pos->rb_node, root);
+ symbol__delete(pos);
+ } else {
+ pos->start -= curr_map->start - curr_map->pgoff;
+ if (pos->end)
+ pos->end -= curr_map->start - curr_map->pgoff;
+ if (curr_map != map) {
+ rb_erase(&pos->rb_node, root);
+ symbols__insert(
+ &curr_map->dso->symbols[curr_map->type],
+ pos);
+ ++moved;
+ } else {
+ ++count;
+ }
+ }
+ }
+
+ /* Symbols have been adjusted */
+ dso->adjust_symbols = 1;
+
+ return count + moved;
+}
+
+/*
+ * Split the symbols into maps, making sure there are no overlaps, i.e. the
+ * kernel range is broken in several maps, named [kernel].N, as we don't have
+ * the original ELF section names vmlinux have.
+ */
+static int dso__split_kallsyms(struct dso *dso, struct map *map, u64 delta,
+ symbol_filter_t filter)
+{
+ struct map_groups *kmaps = map__kmap(map)->kmaps;
+ struct machine *machine = kmaps->machine;
+ struct map *curr_map = map;
+ struct symbol *pos;
+ int count = 0, moved = 0;
+ struct rb_root *root = &dso->symbols[map->type];
+ struct rb_node *next = rb_first(root);
+ int kernel_range = 0;
+
+ while (next) {
+ char *module;
+
+ pos = rb_entry(next, struct symbol, rb_node);
+ next = rb_next(&pos->rb_node);
+
+ module = strchr(pos->name, '\t');
+ if (module) {
+ if (!symbol_conf.use_modules)
+ goto discard_symbol;
+
+ *module++ = '\0';
+
+ if (strcmp(curr_map->dso->short_name, module)) {
+ if (curr_map != map &&
+ dso->kernel == DSO_TYPE_GUEST_KERNEL &&
+ machine__is_default_guest(machine)) {
+ /*
+ * We assume all symbols of a module are
+ * continuous in * kallsyms, so curr_map
+ * points to a module and all its
+ * symbols are in its kmap. Mark it as
+ * loaded.
+ */
+ dso__set_loaded(curr_map->dso,
+ curr_map->type);
+ }
+
+ curr_map = map_groups__find_by_name(kmaps,
+ map->type, module);
+ if (curr_map == NULL) {
+ pr_debug("%s/proc/{kallsyms,modules} "
+ "inconsistency while looking "
+ "for \"%s\" module!\n",
+ machine->root_dir, module);
+ curr_map = map;
+ goto discard_symbol;
+ }
+
+ if (curr_map->dso->loaded &&
+ !machine__is_default_guest(machine))
+ goto discard_symbol;
+ }
+ /*
+ * So that we look just like we get from .ko files,
+ * i.e. not prelinked, relative to map->start.
+ */
+ pos->start = curr_map->map_ip(curr_map, pos->start);
+ pos->end = curr_map->map_ip(curr_map, pos->end);
+ } else if (curr_map != map) {
+ char dso_name[PATH_MAX];
+ struct dso *ndso;
+
+ if (delta) {
+ /* Kernel was relocated at boot time */
+ pos->start -= delta;
+ pos->end -= delta;
+ }
+
+ if (count == 0) {
+ curr_map = map;
+ goto filter_symbol;
+ }
+
+ if (dso->kernel == DSO_TYPE_GUEST_KERNEL)
+ snprintf(dso_name, sizeof(dso_name),
+ "[guest.kernel].%d",
+ kernel_range++);
+ else
+ snprintf(dso_name, sizeof(dso_name),
+ "[kernel].%d",
+ kernel_range++);
+
+ ndso = dso__new(dso_name);
+ if (ndso == NULL)
+ return -1;
+
+ ndso->kernel = dso->kernel;
+
+ curr_map = map__new2(pos->start, ndso, map->type);
+ if (curr_map == NULL) {
+ dso__delete(ndso);
+ return -1;
+ }
+
+ curr_map->map_ip = curr_map->unmap_ip = identity__map_ip;
+ map_groups__insert(kmaps, curr_map);
+ ++kernel_range;
+ } else if (delta) {
+ /* Kernel was relocated at boot time */
+ pos->start -= delta;
+ pos->end -= delta;
+ }
+filter_symbol:
+ if (filter && filter(curr_map, pos)) {
+discard_symbol: rb_erase(&pos->rb_node, root);
+ symbol__delete(pos);
+ } else {
+ if (curr_map != map) {
+ rb_erase(&pos->rb_node, root);
+ symbols__insert(&curr_map->dso->symbols[curr_map->type], pos);
+ ++moved;
+ } else
+ ++count;
+ }
+ }
+
+ if (curr_map != map &&
+ dso->kernel == DSO_TYPE_GUEST_KERNEL &&
+ machine__is_default_guest(kmaps->machine)) {
+ dso__set_loaded(curr_map->dso, curr_map->type);
+ }
+
+ return count + moved;
+}
+
+bool symbol__restricted_filename(const char *filename,
+ const char *restricted_filename)
+{
+ bool restricted = false;
+
+ if (symbol_conf.kptr_restrict) {
+ char *r = realpath(filename, NULL);
+
+ if (r != NULL) {
+ restricted = strcmp(r, restricted_filename) == 0;
+ free(r);
+ return restricted;
+ }
+ }
+
+ return restricted;
+}
+
+struct module_info {
+ struct rb_node rb_node;
+ char *name;
+ u64 start;
+};
+
+static void add_module(struct module_info *mi, struct rb_root *modules)
+{
+ struct rb_node **p = &modules->rb_node;
+ struct rb_node *parent = NULL;
+ struct module_info *m;
+
+ while (*p != NULL) {
+ parent = *p;
+ m = rb_entry(parent, struct module_info, rb_node);
+ if (strcmp(mi->name, m->name) < 0)
+ p = &(*p)->rb_left;
+ else
+ p = &(*p)->rb_right;
+ }
+ rb_link_node(&mi->rb_node, parent, p);
+ rb_insert_color(&mi->rb_node, modules);
+}
+
+static void delete_modules(struct rb_root *modules)
+{
+ struct module_info *mi;
+ struct rb_node *next = rb_first(modules);
+
+ while (next) {
+ mi = rb_entry(next, struct module_info, rb_node);
+ next = rb_next(&mi->rb_node);
+ rb_erase(&mi->rb_node, modules);
+ zfree(&mi->name);
+ free(mi);
+ }
+}
+
+static struct module_info *find_module(const char *name,
+ struct rb_root *modules)
+{
+ struct rb_node *n = modules->rb_node;
+
+ while (n) {
+ struct module_info *m;
+ int cmp;
+
+ m = rb_entry(n, struct module_info, rb_node);
+ cmp = strcmp(name, m->name);
+ if (cmp < 0)
+ n = n->rb_left;
+ else if (cmp > 0)
+ n = n->rb_right;
+ else
+ return m;
+ }
+
+ return NULL;
+}
+
+static int __read_proc_modules(void *arg, const char *name, u64 start)
+{
+ struct rb_root *modules = arg;
+ struct module_info *mi;
+
+ mi = zalloc(sizeof(struct module_info));
+ if (!mi)
+ return -ENOMEM;
+
+ mi->name = strdup(name);
+ mi->start = start;
+
+ if (!mi->name) {
+ free(mi);
+ return -ENOMEM;
+ }
+
+ add_module(mi, modules);
+
+ return 0;
+}
+
+static int read_proc_modules(const char *filename, struct rb_root *modules)
+{
+ if (symbol__restricted_filename(filename, "/proc/modules"))
+ return -1;
+
+ if (modules__parse(filename, modules, __read_proc_modules)) {
+ delete_modules(modules);
+ return -1;
+ }
+
+ return 0;
+}
+
+int compare_proc_modules(const char *from, const char *to)
+{
+ struct rb_root from_modules = RB_ROOT;
+ struct rb_root to_modules = RB_ROOT;
+ struct rb_node *from_node, *to_node;
+ struct module_info *from_m, *to_m;
+ int ret = -1;
+
+ if (read_proc_modules(from, &from_modules))
+ return -1;
+
+ if (read_proc_modules(to, &to_modules))
+ goto out_delete_from;
+
+ from_node = rb_first(&from_modules);
+ to_node = rb_first(&to_modules);
+ while (from_node) {
+ if (!to_node)
+ break;
+
+ from_m = rb_entry(from_node, struct module_info, rb_node);
+ to_m = rb_entry(to_node, struct module_info, rb_node);
+
+ if (from_m->start != to_m->start ||
+ strcmp(from_m->name, to_m->name))
+ break;
+
+ from_node = rb_next(from_node);
+ to_node = rb_next(to_node);
+ }
+
+ if (!from_node && !to_node)
+ ret = 0;
+
+ delete_modules(&to_modules);
+out_delete_from:
+ delete_modules(&from_modules);
+
+ return ret;
+}
+
+static int do_validate_kcore_modules(const char *filename, struct map *map,
+ struct map_groups *kmaps)
+{
+ struct rb_root modules = RB_ROOT;
+ struct map *old_map;
+ int err;
+
+ err = read_proc_modules(filename, &modules);
+ if (err)
+ return err;
+
+ old_map = map_groups__first(kmaps, map->type);
+ while (old_map) {
+ struct map *next = map_groups__next(old_map);
+ struct module_info *mi;
+
+ if (old_map == map || old_map->start == map->start) {
+ /* The kernel map */
+ old_map = next;
+ continue;
+ }
+
+ /* Module must be in memory at the same address */
+ mi = find_module(old_map->dso->short_name, &modules);
+ if (!mi || mi->start != old_map->start) {
+ err = -EINVAL;
+ goto out;
+ }
+
+ old_map = next;
+ }
+out:
+ delete_modules(&modules);
+ return err;
+}
+
+/*
+ * If kallsyms is referenced by name then we look for filename in the same
+ * directory.
+ */
+static bool filename_from_kallsyms_filename(char *filename,
+ const char *base_name,
+ const char *kallsyms_filename)
+{
+ char *name;
+
+ strcpy(filename, kallsyms_filename);
+ name = strrchr(filename, '/');
+ if (!name)
+ return false;
+
+ name += 1;
+
+ if (!strcmp(name, "kallsyms")) {
+ strcpy(name, base_name);
+ return true;
+ }
+
+ return false;
+}
+
+static int validate_kcore_modules(const char *kallsyms_filename,
+ struct map *map)
+{
+ struct map_groups *kmaps = map__kmap(map)->kmaps;
+ char modules_filename[PATH_MAX];
+
+ if (!filename_from_kallsyms_filename(modules_filename, "modules",
+ kallsyms_filename))
+ return -EINVAL;
+
+ if (do_validate_kcore_modules(modules_filename, map, kmaps))
+ return -EINVAL;
+
+ return 0;
+}
+
+static int validate_kcore_addresses(const char *kallsyms_filename,
+ struct map *map)
+{
+ struct kmap *kmap = map__kmap(map);
+
+ if (kmap->ref_reloc_sym && kmap->ref_reloc_sym->name) {
+ u64 start;
+
+ start = kallsyms__get_function_start(kallsyms_filename,
+ kmap->ref_reloc_sym->name);
+ if (start != kmap->ref_reloc_sym->addr)
+ return -EINVAL;
+ }
+
+ return validate_kcore_modules(kallsyms_filename, map);
+}
+
+struct kcore_mapfn_data {
+ struct dso *dso;
+ enum map_type type;
+ struct list_head maps;
+};
+
+static int kcore_mapfn(u64 start, u64 len, u64 pgoff, void *data)
+{
+ struct kcore_mapfn_data *md = data;
+ struct map *map;
+
+ map = map__new2(start, md->dso, md->type);
+ if (map == NULL)
+ return -ENOMEM;
+
+ map->end = map->start + len;
+ map->pgoff = pgoff;
+
+ list_add(&map->node, &md->maps);
+
+ return 0;
+}
+
+static int dso__load_kcore(struct dso *dso, struct map *map,
+ const char *kallsyms_filename)
+{
+ struct map_groups *kmaps = map__kmap(map)->kmaps;
+ struct machine *machine = kmaps->machine;
+ struct kcore_mapfn_data md;
+ struct map *old_map, *new_map, *replacement_map = NULL;
+ bool is_64_bit;
+ int err, fd;
+ char kcore_filename[PATH_MAX];
+ struct symbol *sym;
+
+ /* This function requires that the map is the kernel map */
+ if (map != machine->vmlinux_maps[map->type])
+ return -EINVAL;
+
+ if (!filename_from_kallsyms_filename(kcore_filename, "kcore",
+ kallsyms_filename))
+ return -EINVAL;
+
+ /* Modules and kernel must be present at their original addresses */
+ if (validate_kcore_addresses(kallsyms_filename, map))
+ return -EINVAL;
+
+ md.dso = dso;
+ md.type = map->type;
+ INIT_LIST_HEAD(&md.maps);
+
+ fd = open(kcore_filename, O_RDONLY);
+ if (fd < 0)
+ return -EINVAL;
+
+ /* Read new maps into temporary lists */
+ err = file__read_maps(fd, md.type == MAP__FUNCTION, kcore_mapfn, &md,
+ &is_64_bit);
+ if (err)
+ goto out_err;
+
+ if (list_empty(&md.maps)) {
+ err = -EINVAL;
+ goto out_err;
+ }
+
+ /* Remove old maps */
+ old_map = map_groups__first(kmaps, map->type);
+ while (old_map) {
+ struct map *next = map_groups__next(old_map);
+
+ if (old_map != map)
+ map_groups__remove(kmaps, old_map);
+ old_map = next;
+ }
+
+ /* Find the kernel map using the first symbol */
+ sym = dso__first_symbol(dso, map->type);
+ list_for_each_entry(new_map, &md.maps, node) {
+ if (sym && sym->start >= new_map->start &&
+ sym->start < new_map->end) {
+ replacement_map = new_map;
+ break;
+ }
+ }
+
+ if (!replacement_map)
+ replacement_map = list_entry(md.maps.next, struct map, node);
+
+ /* Add new maps */
+ while (!list_empty(&md.maps)) {
+ new_map = list_entry(md.maps.next, struct map, node);
+ list_del(&new_map->node);
+ if (new_map == replacement_map) {
+ map->start = new_map->start;
+ map->end = new_map->end;
+ map->pgoff = new_map->pgoff;
+ map->map_ip = new_map->map_ip;
+ map->unmap_ip = new_map->unmap_ip;
+ map__delete(new_map);
+ /* Ensure maps are correctly ordered */
+ map_groups__remove(kmaps, map);
+ map_groups__insert(kmaps, map);
+ } else {
+ map_groups__insert(kmaps, new_map);
+ }
+ }
+
+ /*
+ * Set the data type and long name so that kcore can be read via
+ * dso__data_read_addr().
+ */
+ if (dso->kernel == DSO_TYPE_GUEST_KERNEL)
+ dso->binary_type = DSO_BINARY_TYPE__GUEST_KCORE;
+ else
+ dso->binary_type = DSO_BINARY_TYPE__KCORE;
+ dso__set_long_name(dso, strdup(kcore_filename), true);
+
+ close(fd);
+
+ if (map->type == MAP__FUNCTION)
+ pr_debug("Using %s for kernel object code\n", kcore_filename);
+ else
+ pr_debug("Using %s for kernel data\n", kcore_filename);
+
+ return 0;
+
+out_err:
+ while (!list_empty(&md.maps)) {
+ map = list_entry(md.maps.next, struct map, node);
+ list_del(&map->node);
+ map__delete(map);
+ }
+ close(fd);
+ return -EINVAL;
+}
+
+/*
+ * If the kernel is relocated at boot time, kallsyms won't match. Compute the
+ * delta based on the relocation reference symbol.
+ */
+static int kallsyms__delta(struct map *map, const char *filename, u64 *delta)
+{
+ struct kmap *kmap = map__kmap(map);
+ u64 addr;
+
+ if (!kmap->ref_reloc_sym || !kmap->ref_reloc_sym->name)
+ return 0;
+
+ addr = kallsyms__get_function_start(filename,
+ kmap->ref_reloc_sym->name);
+ if (!addr)
+ return -1;
+
+ *delta = addr - kmap->ref_reloc_sym->addr;
+ return 0;
+}
+
+int dso__load_kallsyms(struct dso *dso, const char *filename,
+ struct map *map, symbol_filter_t filter)
+{
+ u64 delta = 0;
+
+ if (symbol__restricted_filename(filename, "/proc/kallsyms"))
+ return -1;
+
+ if (dso__load_all_kallsyms(dso, filename, map) < 0)
+ return -1;
+
+ if (kallsyms__delta(map, filename, &delta))
+ return -1;
+
+ symbols__fixup_duplicate(&dso->symbols[map->type]);
+ symbols__fixup_end(&dso->symbols[map->type]);
+
+ if (dso->kernel == DSO_TYPE_GUEST_KERNEL)
+ dso->symtab_type = DSO_BINARY_TYPE__GUEST_KALLSYMS;
+ else
+ dso->symtab_type = DSO_BINARY_TYPE__KALLSYMS;
+
+ if (!dso__load_kcore(dso, map, filename))
+ return dso__split_kallsyms_for_kcore(dso, map, filter);
+ else
+ return dso__split_kallsyms(dso, map, delta, filter);
+}
+
+static int dso__load_perf_map(struct dso *dso, struct map *map,
+ symbol_filter_t filter)
+{
+ char *line = NULL;
+ size_t n;
+ FILE *file;
+ int nr_syms = 0;
+
+ file = fopen(dso->long_name, "r");
+ if (file == NULL)
+ goto out_failure;
+
+ while (!feof(file)) {
+ u64 start, size;
+ struct symbol *sym;
+ int line_len, len;
+
+ line_len = getline(&line, &n, file);
+ if (line_len < 0)
+ break;
+
+ if (!line)
+ goto out_failure;
+
+ line[--line_len] = '\0'; /* \n */
+
+ len = hex2u64(line, &start);
+
+ len++;
+ if (len + 2 >= line_len)
+ continue;
+
+ len += hex2u64(line + len, &size);
+
+ len++;
+ if (len + 2 >= line_len)
+ continue;
+
+ sym = symbol__new(start, size, STB_GLOBAL, line + len);
+
+ if (sym == NULL)
+ goto out_delete_line;
+
+ if (filter && filter(map, sym))
+ symbol__delete(sym);
+ else {
+ symbols__insert(&dso->symbols[map->type], sym);
+ nr_syms++;
+ }
+ }
+
+ free(line);
+ fclose(file);
+
+ return nr_syms;
+
+out_delete_line:
+ free(line);
+out_failure:
+ return -1;
+}
+
+static bool dso__is_compatible_symtab_type(struct dso *dso, bool kmod,
+ enum dso_binary_type type)
+{
+ switch (type) {
+ case DSO_BINARY_TYPE__JAVA_JIT:
+ case DSO_BINARY_TYPE__DEBUGLINK:
+ case DSO_BINARY_TYPE__SYSTEM_PATH_DSO:
+ case DSO_BINARY_TYPE__FEDORA_DEBUGINFO:
+ case DSO_BINARY_TYPE__UBUNTU_DEBUGINFO:
+ case DSO_BINARY_TYPE__BUILDID_DEBUGINFO:
+ case DSO_BINARY_TYPE__OPENEMBEDDED_DEBUGINFO:
+ return !kmod && dso->kernel == DSO_TYPE_USER;
+
+ case DSO_BINARY_TYPE__KALLSYMS:
+ case DSO_BINARY_TYPE__VMLINUX:
+ case DSO_BINARY_TYPE__KCORE:
+ return dso->kernel == DSO_TYPE_KERNEL;
+
+ case DSO_BINARY_TYPE__GUEST_KALLSYMS:
+ case DSO_BINARY_TYPE__GUEST_VMLINUX:
+ case DSO_BINARY_TYPE__GUEST_KCORE:
+ return dso->kernel == DSO_TYPE_GUEST_KERNEL;
+
+ case DSO_BINARY_TYPE__GUEST_KMODULE:
+ case DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE:
+ /*
+ * kernel modules know their symtab type - it's set when
+ * creating a module dso in machine__new_module().
+ */
+ return kmod && dso->symtab_type == type;
+
+ case DSO_BINARY_TYPE__BUILD_ID_CACHE:
+ return true;
+
+ case DSO_BINARY_TYPE__NOT_FOUND:
+ default:
+ return false;
+ }
+}
+
+int dso__load(struct dso *dso, struct map *map, symbol_filter_t filter)
+{
+ char *name;
+ int ret = -1;
+ u_int i;
+ struct machine *machine;
+ char *root_dir = (char *) "";
+ int ss_pos = 0;
+ struct symsrc ss_[2];
+ struct symsrc *syms_ss = NULL, *runtime_ss = NULL;
+ bool kmod;
+
+ dso__set_loaded(dso, map->type);
+
+ if (dso->kernel == DSO_TYPE_KERNEL)
+ return dso__load_kernel_sym(dso, map, filter);
+ else if (dso->kernel == DSO_TYPE_GUEST_KERNEL)
+ return dso__load_guest_kernel_sym(dso, map, filter);
+
+ if (map->groups && map->groups->machine)
+ machine = map->groups->machine;
+ else
+ machine = NULL;
+
+ dso->adjust_symbols = 0;
+
+ if (strncmp(dso->name, "/tmp/perf-", 10) == 0) {
+ struct stat st;
+
+ if (lstat(dso->name, &st) < 0)
+ return -1;
+
+ if (st.st_uid && (st.st_uid != geteuid())) {
+ pr_warning("File %s not owned by current user or root, "
+ "ignoring it.\n", dso->name);
+ return -1;
+ }
+
+ ret = dso__load_perf_map(dso, map, filter);
+ dso->symtab_type = ret > 0 ? DSO_BINARY_TYPE__JAVA_JIT :
+ DSO_BINARY_TYPE__NOT_FOUND;
+ return ret;
+ }
+
+ if (machine)
+ root_dir = machine->root_dir;
+
+ name = malloc(PATH_MAX);
+ if (!name)
+ return -1;
+
+ kmod = dso->symtab_type == DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE ||
+ dso->symtab_type == DSO_BINARY_TYPE__GUEST_KMODULE;
+
+ /*
+ * Iterate over candidate debug images.
+ * Keep track of "interesting" ones (those which have a symtab, dynsym,
+ * and/or opd section) for processing.
+ */
+ for (i = 0; i < DSO_BINARY_TYPE__SYMTAB_CNT; i++) {
+ struct symsrc *ss = &ss_[ss_pos];
+ bool next_slot = false;
+
+ enum dso_binary_type symtab_type = binary_type_symtab[i];
+
+ if (!dso__is_compatible_symtab_type(dso, kmod, symtab_type))
+ continue;
+
+ if (dso__read_binary_type_filename(dso, symtab_type,
+ root_dir, name, PATH_MAX))
+ continue;
+
+ /* Name is now the name of the next image to try */
+ if (symsrc__init(ss, dso, name, symtab_type) < 0)
+ continue;
+
+ if (!syms_ss && symsrc__has_symtab(ss)) {
+ syms_ss = ss;
+ next_slot = true;
+ if (!dso->symsrc_filename)
+ dso->symsrc_filename = strdup(name);
+ }
+
+ if (!runtime_ss && symsrc__possibly_runtime(ss)) {
+ runtime_ss = ss;
+ next_slot = true;
+ }
+
+ if (next_slot) {
+ ss_pos++;
+
+ if (syms_ss && runtime_ss)
+ break;
+ } else {
+ symsrc__destroy(ss);
+ }
+
+ }
+
+ if (!runtime_ss && !syms_ss)
+ goto out_free;
+
+ if (runtime_ss && !syms_ss) {
+ syms_ss = runtime_ss;
+ }
+
+ /* We'll have to hope for the best */
+ if (!runtime_ss && syms_ss)
+ runtime_ss = syms_ss;
+
+ if (syms_ss)
+ ret = dso__load_sym(dso, map, syms_ss, runtime_ss, filter, kmod);
+ else
+ ret = -1;
+
+ if (ret > 0) {
+ int nr_plt;
+
+ nr_plt = dso__synthesize_plt_symbols(dso, runtime_ss, map, filter);
+ if (nr_plt > 0)
+ ret += nr_plt;
+ }
+
+ for (; ss_pos > 0; ss_pos--)
+ symsrc__destroy(&ss_[ss_pos - 1]);
+out_free:
+ free(name);
+ if (ret < 0 && strstr(dso->name, " (deleted)") != NULL)
+ return 0;
+ return ret;
+}
+
+struct map *map_groups__find_by_name(struct map_groups *mg,
+ enum map_type type, const char *name)
+{
+ struct rb_node *nd;
+
+ for (nd = rb_first(&mg->maps[type]); nd; nd = rb_next(nd)) {
+ struct map *map = rb_entry(nd, struct map, rb_node);
+
+ if (map->dso && strcmp(map->dso->short_name, name) == 0)
+ return map;
+ }
+
+ return NULL;
+}
+
+int dso__load_vmlinux(struct dso *dso, struct map *map,
+ const char *vmlinux, bool vmlinux_allocated,
+ symbol_filter_t filter)
+{
+ int err = -1;
+ struct symsrc ss;
+ char symfs_vmlinux[PATH_MAX];
+ enum dso_binary_type symtab_type;
+
+ if (vmlinux[0] == '/')
+ snprintf(symfs_vmlinux, sizeof(symfs_vmlinux), "%s", vmlinux);
+ else
+ snprintf(symfs_vmlinux, sizeof(symfs_vmlinux), "%s%s",
+ symbol_conf.symfs, vmlinux);
+
+ if (dso->kernel == DSO_TYPE_GUEST_KERNEL)
+ symtab_type = DSO_BINARY_TYPE__GUEST_VMLINUX;
+ else
+ symtab_type = DSO_BINARY_TYPE__VMLINUX;
+
+ if (symsrc__init(&ss, dso, symfs_vmlinux, symtab_type))
+ return -1;
+
+ err = dso__load_sym(dso, map, &ss, &ss, filter, 0);
+ symsrc__destroy(&ss);
+
+ if (err > 0) {
+ if (dso->kernel == DSO_TYPE_GUEST_KERNEL)
+ dso->binary_type = DSO_BINARY_TYPE__GUEST_VMLINUX;
+ else
+ dso->binary_type = DSO_BINARY_TYPE__VMLINUX;
+ dso__set_long_name(dso, vmlinux, vmlinux_allocated);
+ dso__set_loaded(dso, map->type);
+ pr_debug("Using %s for symbols\n", symfs_vmlinux);
+ }
+
+ return err;
+}
+
+int dso__load_vmlinux_path(struct dso *dso, struct map *map,
+ symbol_filter_t filter)
+{
+ int i, err = 0;
+ char *filename;
+
+ pr_debug("Looking at the vmlinux_path (%d entries long)\n",
+ vmlinux_path__nr_entries + 1);
+
+ filename = dso__build_id_filename(dso, NULL, 0);
+ if (filename != NULL) {
+ err = dso__load_vmlinux(dso, map, filename, true, filter);
+ if (err > 0)
+ goto out;
+ free(filename);
+ }
+
+ for (i = 0; i < vmlinux_path__nr_entries; ++i) {
+ err = dso__load_vmlinux(dso, map, vmlinux_path[i], false, filter);
+ if (err > 0)
+ break;
+ }
+out:
+ return err;
+}
+
+static int find_matching_kcore(struct map *map, char *dir, size_t dir_sz)
+{
+ char kallsyms_filename[PATH_MAX];
+ struct dirent *dent;
+ int ret = -1;
+ DIR *d;
+
+ d = opendir(dir);
+ if (!d)
+ return -1;
+
+ while (1) {
+ dent = readdir(d);
+ if (!dent)
+ break;
+ if (dent->d_type != DT_DIR)
+ continue;
+ scnprintf(kallsyms_filename, sizeof(kallsyms_filename),
+ "%s/%s/kallsyms", dir, dent->d_name);
+ if (!validate_kcore_addresses(kallsyms_filename, map)) {
+ strlcpy(dir, kallsyms_filename, dir_sz);
+ ret = 0;
+ break;
+ }
+ }
+
+ closedir(d);
+
+ return ret;
+}
+
+static char *dso__find_kallsyms(struct dso *dso, struct map *map)
+{
+ u8 host_build_id[BUILD_ID_SIZE];
+ char sbuild_id[BUILD_ID_SIZE * 2 + 1];
+ bool is_host = false;
+ char path[PATH_MAX];
+
+ if (!dso->has_build_id) {
+ /*
+ * Last resort, if we don't have a build-id and couldn't find
+ * any vmlinux file, try the running kernel kallsyms table.
+ */
+ goto proc_kallsyms;
+ }
+
+ if (sysfs__read_build_id("/sys/kernel/notes", host_build_id,
+ sizeof(host_build_id)) == 0)
+ is_host = dso__build_id_equal(dso, host_build_id);
+
+ build_id__sprintf(dso->build_id, sizeof(dso->build_id), sbuild_id);
+
+ scnprintf(path, sizeof(path), "%s/[kernel.kcore]/%s", buildid_dir,
+ sbuild_id);
+
+ /* Use /proc/kallsyms if possible */
+ if (is_host) {
+ DIR *d;
+ int fd;
+
+ /* If no cached kcore go with /proc/kallsyms */
+ d = opendir(path);
+ if (!d)
+ goto proc_kallsyms;
+ closedir(d);
+
+ /*
+ * Do not check the build-id cache, until we know we cannot use
+ * /proc/kcore.
+ */
+ fd = open("/proc/kcore", O_RDONLY);
+ if (fd != -1) {
+ close(fd);
+ /* If module maps match go with /proc/kallsyms */
+ if (!validate_kcore_addresses("/proc/kallsyms", map))
+ goto proc_kallsyms;
+ }
+
+ /* Find kallsyms in build-id cache with kcore */
+ if (!find_matching_kcore(map, path, sizeof(path)))
+ return strdup(path);
+
+ goto proc_kallsyms;
+ }
+
+ /* Find kallsyms in build-id cache with kcore */
+ if (!find_matching_kcore(map, path, sizeof(path)))
+ return strdup(path);
+
+ scnprintf(path, sizeof(path), "%s/[kernel.kallsyms]/%s",
+ buildid_dir, sbuild_id);
+
+ if (access(path, F_OK)) {
+ pr_err("No kallsyms or vmlinux with build-id %s was found\n",
+ sbuild_id);
+ return NULL;
+ }
+
+ return strdup(path);
+
+proc_kallsyms:
+ return strdup("/proc/kallsyms");
+}
+
+static int dso__load_kernel_sym(struct dso *dso, struct map *map,
+ symbol_filter_t filter)
+{
+ int err;
+ const char *kallsyms_filename = NULL;
+ char *kallsyms_allocated_filename = NULL;
+ /*
+ * Step 1: if the user specified a kallsyms or vmlinux filename, use
+ * it and only it, reporting errors to the user if it cannot be used.
+ *
+ * For instance, try to analyse an ARM perf.data file _without_ a
+ * build-id, or if the user specifies the wrong path to the right
+ * vmlinux file, obviously we can't fallback to another vmlinux (a
+ * x86_86 one, on the machine where analysis is being performed, say),
+ * or worse, /proc/kallsyms.
+ *
+ * If the specified file _has_ a build-id and there is a build-id
+ * section in the perf.data file, we will still do the expected
+ * validation in dso__load_vmlinux and will bail out if they don't
+ * match.
+ */
+ if (symbol_conf.kallsyms_name != NULL) {
+ kallsyms_filename = symbol_conf.kallsyms_name;
+ goto do_kallsyms;
+ }
+
+ if (!symbol_conf.ignore_vmlinux && symbol_conf.vmlinux_name != NULL) {
+ return dso__load_vmlinux(dso, map, symbol_conf.vmlinux_name,
+ false, filter);
+ }
+
+ if (!symbol_conf.ignore_vmlinux && vmlinux_path != NULL) {
+ err = dso__load_vmlinux_path(dso, map, filter);
+ if (err > 0)
+ return err;
+ }
+
+ /* do not try local files if a symfs was given */
+ if (symbol_conf.symfs[0] != 0)
+ return -1;
+
+ kallsyms_allocated_filename = dso__find_kallsyms(dso, map);
+ if (!kallsyms_allocated_filename)
+ return -1;
+
+ kallsyms_filename = kallsyms_allocated_filename;
+
+do_kallsyms:
+ err = dso__load_kallsyms(dso, kallsyms_filename, map, filter);
+ if (err > 0)
+ pr_debug("Using %s for symbols\n", kallsyms_filename);
+ free(kallsyms_allocated_filename);
+
+ if (err > 0 && !dso__is_kcore(dso)) {
+ dso__set_long_name(dso, "[kernel.kallsyms]", false);
+ map__fixup_start(map);
+ map__fixup_end(map);
+ }
+
+ return err;
+}
+
+static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map,
+ symbol_filter_t filter)
+{
+ int err;
+ const char *kallsyms_filename = NULL;
+ struct machine *machine;
+ char path[PATH_MAX];
+
+ if (!map->groups) {
+ pr_debug("Guest kernel map hasn't the point to groups\n");
+ return -1;
+ }
+ machine = map->groups->machine;
+
+ if (machine__is_default_guest(machine)) {
+ /*
+ * if the user specified a vmlinux filename, use it and only
+ * it, reporting errors to the user if it cannot be used.
+ * Or use file guest_kallsyms inputted by user on commandline
+ */
+ if (symbol_conf.default_guest_vmlinux_name != NULL) {
+ err = dso__load_vmlinux(dso, map,
+ symbol_conf.default_guest_vmlinux_name,
+ false, filter);
+ return err;
+ }
+
+ kallsyms_filename = symbol_conf.default_guest_kallsyms;
+ if (!kallsyms_filename)
+ return -1;
+ } else {
+ sprintf(path, "%s/proc/kallsyms", machine->root_dir);
+ kallsyms_filename = path;
+ }
+
+ err = dso__load_kallsyms(dso, kallsyms_filename, map, filter);
+ if (err > 0)
+ pr_debug("Using %s for symbols\n", kallsyms_filename);
+ if (err > 0 && !dso__is_kcore(dso)) {
+ machine__mmap_name(machine, path, sizeof(path));
+ dso__set_long_name(dso, strdup(path), true);
+ map__fixup_start(map);
+ map__fixup_end(map);
+ }
+
+ return err;
+}
+
+static void vmlinux_path__exit(void)
+{
+ while (--vmlinux_path__nr_entries >= 0)
+ zfree(&vmlinux_path[vmlinux_path__nr_entries]);
+
+ zfree(&vmlinux_path);
+}
+
+static int vmlinux_path__init(void)
+{
+ struct utsname uts;
+ char bf[PATH_MAX];
+
+ vmlinux_path = malloc(sizeof(char *) * 5);
+ if (vmlinux_path == NULL)
+ return -1;
+
+ vmlinux_path[vmlinux_path__nr_entries] = strdup("vmlinux");
+ if (vmlinux_path[vmlinux_path__nr_entries] == NULL)
+ goto out_fail;
+ ++vmlinux_path__nr_entries;
+ vmlinux_path[vmlinux_path__nr_entries] = strdup("/boot/vmlinux");
+ if (vmlinux_path[vmlinux_path__nr_entries] == NULL)
+ goto out_fail;
+ ++vmlinux_path__nr_entries;
+
+ /* only try running kernel version if no symfs was given */
+ if (symbol_conf.symfs[0] != 0)
+ return 0;
+
+ if (uname(&uts) < 0)
+ return -1;
+
+ snprintf(bf, sizeof(bf), "/boot/vmlinux-%s", uts.release);
+ vmlinux_path[vmlinux_path__nr_entries] = strdup(bf);
+ if (vmlinux_path[vmlinux_path__nr_entries] == NULL)
+ goto out_fail;
+ ++vmlinux_path__nr_entries;
+ snprintf(bf, sizeof(bf), "/lib/modules/%s/build/vmlinux", uts.release);
+ vmlinux_path[vmlinux_path__nr_entries] = strdup(bf);
+ if (vmlinux_path[vmlinux_path__nr_entries] == NULL)
+ goto out_fail;
+ ++vmlinux_path__nr_entries;
+ snprintf(bf, sizeof(bf), "/usr/lib/debug/lib/modules/%s/vmlinux",
+ uts.release);
+ vmlinux_path[vmlinux_path__nr_entries] = strdup(bf);
+ if (vmlinux_path[vmlinux_path__nr_entries] == NULL)
+ goto out_fail;
+ ++vmlinux_path__nr_entries;
+
+ return 0;
+
+out_fail:
+ vmlinux_path__exit();
+ return -1;
+}
+
+int setup_list(struct strlist **list, const char *list_str,
+ const char *list_name)
+{
+ if (list_str == NULL)
+ return 0;
+
+ *list = strlist__new(true, list_str);
+ if (!*list) {
+ pr_err("problems parsing %s list\n", list_name);
+ return -1;
+ }
+ return 0;
+}
+
+static bool symbol__read_kptr_restrict(void)
+{
+ bool value = false;
+
+ if (geteuid() != 0) {
+ FILE *fp = fopen("/proc/sys/kernel/kptr_restrict", "r");
+ if (fp != NULL) {
+ char line[8];
+
+ if (fgets(line, sizeof(line), fp) != NULL)
+ value = atoi(line) != 0;
+
+ fclose(fp);
+ }
+ }
+
+ return value;
+}
+
+int symbol__init(void)
+{
+ const char *symfs;
+
+ if (symbol_conf.initialized)
+ return 0;
+
+ symbol_conf.priv_size = PERF_ALIGN(symbol_conf.priv_size, sizeof(u64));
+
+ symbol__elf_init();
+
+ if (symbol_conf.sort_by_name)
+ symbol_conf.priv_size += (sizeof(struct symbol_name_rb_node) -
+ sizeof(struct symbol));
+
+ if (symbol_conf.try_vmlinux_path && vmlinux_path__init() < 0)
+ return -1;
+
+ if (symbol_conf.field_sep && *symbol_conf.field_sep == '.') {
+ pr_err("'.' is the only non valid --field-separator argument\n");
+ return -1;
+ }
+
+ if (setup_list(&symbol_conf.dso_list,
+ symbol_conf.dso_list_str, "dso") < 0)
+ return -1;
+
+ if (setup_list(&symbol_conf.comm_list,
+ symbol_conf.comm_list_str, "comm") < 0)
+ goto out_free_dso_list;
+
+ if (setup_list(&symbol_conf.sym_list,
+ symbol_conf.sym_list_str, "symbol") < 0)
+ goto out_free_comm_list;
+
+ /*
+ * A path to symbols of "/" is identical to ""
+ * reset here for simplicity.
+ */
+ symfs = realpath(symbol_conf.symfs, NULL);
+ if (symfs == NULL)
+ symfs = symbol_conf.symfs;
+ if (strcmp(symfs, "/") == 0)
+ symbol_conf.symfs = "";
+ if (symfs != symbol_conf.symfs)
+ free((void *)symfs);
+
+ symbol_conf.kptr_restrict = symbol__read_kptr_restrict();
+
+ symbol_conf.initialized = true;
+ return 0;
+
+out_free_comm_list:
+ strlist__delete(symbol_conf.comm_list);
+out_free_dso_list:
+ strlist__delete(symbol_conf.dso_list);
+ return -1;
+}
+
+void symbol__exit(void)
+{
+ if (!symbol_conf.initialized)
+ return;
+ strlist__delete(symbol_conf.sym_list);
+ strlist__delete(symbol_conf.dso_list);
+ strlist__delete(symbol_conf.comm_list);
+ vmlinux_path__exit();
+ symbol_conf.sym_list = symbol_conf.dso_list = symbol_conf.comm_list = NULL;
+ symbol_conf.initialized = false;
+}
diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h
new file mode 100644
index 00000000000..615c752dd76
--- /dev/null
+++ b/tools/perf/util/symbol.h
@@ -0,0 +1,297 @@
+#ifndef __PERF_SYMBOL
+#define __PERF_SYMBOL 1
+
+#include <linux/types.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include "map.h"
+#include "../perf.h"
+#include <linux/list.h>
+#include <linux/rbtree.h>
+#include <stdio.h>
+#include <byteswap.h>
+#include <libgen.h>
+#include "build-id.h"
+#include "event.h"
+
+#ifdef HAVE_LIBELF_SUPPORT
+#include <libelf.h>
+#include <gelf.h>
+#endif
+#include <elf.h>
+
+#include "dso.h"
+
+#ifdef HAVE_CPLUS_DEMANGLE_SUPPORT
+extern char *cplus_demangle(const char *, int);
+
+static inline char *bfd_demangle(void __maybe_unused *v, const char *c, int i)
+{
+ return cplus_demangle(c, i);
+}
+#else
+#ifdef NO_DEMANGLE
+static inline char *bfd_demangle(void __maybe_unused *v,
+ const char __maybe_unused *c,
+ int __maybe_unused i)
+{
+ return NULL;
+}
+#else
+#define PACKAGE 'perf'
+#include <bfd.h>
+#endif
+#endif
+
+/*
+ * libelf 0.8.x and earlier do not support ELF_C_READ_MMAP;
+ * for newer versions we can use mmap to reduce memory usage:
+ */
+#ifdef HAVE_LIBELF_MMAP_SUPPORT
+# define PERF_ELF_C_READ_MMAP ELF_C_READ_MMAP
+#else
+# define PERF_ELF_C_READ_MMAP ELF_C_READ
+#endif
+
+#ifdef HAVE_LIBELF_SUPPORT
+extern Elf_Scn *elf_section_by_name(Elf *elf, GElf_Ehdr *ep,
+ GElf_Shdr *shp, const char *name, size_t *idx);
+#endif
+
+#ifndef DMGL_PARAMS
+#define DMGL_PARAMS (1 << 0) /* Include function args */
+#define DMGL_ANSI (1 << 1) /* Include const, volatile, etc */
+#endif
+
+/** struct symbol - symtab entry
+ *
+ * @ignore - resolvable but tools ignore it (e.g. idle routines)
+ */
+struct symbol {
+ struct rb_node rb_node;
+ u64 start;
+ u64 end;
+ u16 namelen;
+ u8 binding;
+ bool ignore;
+ char name[0];
+};
+
+void symbol__delete(struct symbol *sym);
+void symbols__delete(struct rb_root *symbols);
+
+/* symbols__for_each_entry - iterate over symbols (rb_root)
+ *
+ * @symbols: the rb_root of symbols
+ * @pos: the 'struct symbol *' to use as a loop cursor
+ * @nd: the 'struct rb_node *' to use as a temporary storage
+ */
+#define symbols__for_each_entry(symbols, pos, nd) \
+ for (nd = rb_first(symbols); \
+ nd && (pos = rb_entry(nd, struct symbol, rb_node)); \
+ nd = rb_next(nd))
+
+static inline size_t symbol__size(const struct symbol *sym)
+{
+ return sym->end - sym->start + 1;
+}
+
+struct strlist;
+
+struct symbol_conf {
+ unsigned short priv_size;
+ unsigned short nr_events;
+ bool try_vmlinux_path,
+ ignore_vmlinux,
+ show_kernel_path,
+ use_modules,
+ sort_by_name,
+ show_nr_samples,
+ show_total_period,
+ use_callchain,
+ cumulate_callchain,
+ exclude_other,
+ show_cpu_utilization,
+ initialized,
+ kptr_restrict,
+ annotate_asm_raw,
+ annotate_src,
+ event_group,
+ demangle,
+ filter_relative;
+ const char *vmlinux_name,
+ *kallsyms_name,
+ *source_prefix,
+ *field_sep;
+ const char *default_guest_vmlinux_name,
+ *default_guest_kallsyms,
+ *default_guest_modules;
+ const char *guestmount;
+ const char *dso_list_str,
+ *comm_list_str,
+ *sym_list_str,
+ *col_width_list_str;
+ struct strlist *dso_list,
+ *comm_list,
+ *sym_list,
+ *dso_from_list,
+ *dso_to_list,
+ *sym_from_list,
+ *sym_to_list;
+ const char *symfs;
+};
+
+extern struct symbol_conf symbol_conf;
+extern int vmlinux_path__nr_entries;
+extern char **vmlinux_path;
+
+static inline void *symbol__priv(struct symbol *sym)
+{
+ return ((void *)sym) - symbol_conf.priv_size;
+}
+
+struct ref_reloc_sym {
+ const char *name;
+ u64 addr;
+ u64 unrelocated_addr;
+};
+
+struct map_symbol {
+ struct map *map;
+ struct symbol *sym;
+ bool unfolded;
+ bool has_children;
+};
+
+struct addr_map_symbol {
+ struct map *map;
+ struct symbol *sym;
+ u64 addr;
+ u64 al_addr;
+};
+
+struct branch_info {
+ struct addr_map_symbol from;
+ struct addr_map_symbol to;
+ struct branch_flags flags;
+};
+
+struct mem_info {
+ struct addr_map_symbol iaddr;
+ struct addr_map_symbol daddr;
+ union perf_mem_data_src data_src;
+};
+
+struct addr_location {
+ struct machine *machine;
+ struct thread *thread;
+ struct map *map;
+ struct symbol *sym;
+ u64 addr;
+ char level;
+ u8 filtered;
+ u8 cpumode;
+ s32 cpu;
+};
+
+struct symsrc {
+ char *name;
+ int fd;
+ enum dso_binary_type type;
+
+#ifdef HAVE_LIBELF_SUPPORT
+ Elf *elf;
+ GElf_Ehdr ehdr;
+
+ Elf_Scn *opdsec;
+ size_t opdidx;
+ GElf_Shdr opdshdr;
+
+ Elf_Scn *symtab;
+ GElf_Shdr symshdr;
+
+ Elf_Scn *dynsym;
+ size_t dynsym_idx;
+ GElf_Shdr dynshdr;
+
+ bool adjust_symbols;
+#endif
+};
+
+void symsrc__destroy(struct symsrc *ss);
+int symsrc__init(struct symsrc *ss, struct dso *dso, const char *name,
+ enum dso_binary_type type);
+bool symsrc__has_symtab(struct symsrc *ss);
+bool symsrc__possibly_runtime(struct symsrc *ss);
+
+int dso__load(struct dso *dso, struct map *map, symbol_filter_t filter);
+int dso__load_vmlinux(struct dso *dso, struct map *map,
+ const char *vmlinux, bool vmlinux_allocated,
+ symbol_filter_t filter);
+int dso__load_vmlinux_path(struct dso *dso, struct map *map,
+ symbol_filter_t filter);
+int dso__load_kallsyms(struct dso *dso, const char *filename, struct map *map,
+ symbol_filter_t filter);
+
+struct symbol *dso__find_symbol(struct dso *dso, enum map_type type,
+ u64 addr);
+struct symbol *dso__find_symbol_by_name(struct dso *dso, enum map_type type,
+ const char *name);
+
+int filename__read_build_id(const char *filename, void *bf, size_t size);
+int sysfs__read_build_id(const char *filename, void *bf, size_t size);
+int modules__parse(const char *filename, void *arg,
+ int (*process_module)(void *arg, const char *name,
+ u64 start));
+int filename__read_debuglink(const char *filename, char *debuglink,
+ size_t size);
+
+int symbol__init(void);
+void symbol__exit(void);
+void symbol__elf_init(void);
+struct symbol *symbol__new(u64 start, u64 len, u8 binding, const char *name);
+size_t symbol__fprintf_symname_offs(const struct symbol *sym,
+ const struct addr_location *al, FILE *fp);
+size_t symbol__fprintf_symname(const struct symbol *sym, FILE *fp);
+size_t symbol__fprintf(struct symbol *sym, FILE *fp);
+bool symbol_type__is_a(char symbol_type, enum map_type map_type);
+bool symbol__restricted_filename(const char *filename,
+ const char *restricted_filename);
+bool symbol__is_idle(struct symbol *sym);
+
+int dso__load_sym(struct dso *dso, struct map *map, struct symsrc *syms_ss,
+ struct symsrc *runtime_ss, symbol_filter_t filter,
+ int kmodule);
+int dso__synthesize_plt_symbols(struct dso *dso, struct symsrc *ss,
+ struct map *map, symbol_filter_t filter);
+
+void symbols__insert(struct rb_root *symbols, struct symbol *sym);
+void symbols__fixup_duplicate(struct rb_root *symbols);
+void symbols__fixup_end(struct rb_root *symbols);
+void __map_groups__fixup_end(struct map_groups *mg, enum map_type type);
+
+typedef int (*mapfn_t)(u64 start, u64 len, u64 pgoff, void *data);
+int file__read_maps(int fd, bool exe, mapfn_t mapfn, void *data,
+ bool *is_64_bit);
+
+#define PERF_KCORE_EXTRACT "/tmp/perf-kcore-XXXXXX"
+
+struct kcore_extract {
+ char *kcore_filename;
+ u64 addr;
+ u64 offs;
+ u64 len;
+ char extract_filename[sizeof(PERF_KCORE_EXTRACT)];
+ int fd;
+};
+
+int kcore_extract__create(struct kcore_extract *kce);
+void kcore_extract__delete(struct kcore_extract *kce);
+
+int kcore_copy(const char *from_dir, const char *to_dir);
+int compare_proc_modules(const char *from, const char *to);
+
+int setup_list(struct strlist **list, const char *list_str,
+ const char *list_name);
+
+#endif /* __PERF_SYMBOL */
diff --git a/tools/perf/util/target.c b/tools/perf/util/target.c
new file mode 100644
index 00000000000..e74c5963dc7
--- /dev/null
+++ b/tools/perf/util/target.c
@@ -0,0 +1,158 @@
+/*
+ * Helper functions for handling target threads/cpus
+ *
+ * Copyright (C) 2012, LG Electronics, Namhyung Kim <namhyung.kim@lge.com>
+ *
+ * Released under the GPL v2.
+ */
+
+#include "target.h"
+#include "debug.h"
+
+#include <pwd.h>
+#include <string.h>
+
+
+enum target_errno target__validate(struct target *target)
+{
+ enum target_errno ret = TARGET_ERRNO__SUCCESS;
+
+ if (target->pid)
+ target->tid = target->pid;
+
+ /* CPU and PID are mutually exclusive */
+ if (target->tid && target->cpu_list) {
+ target->cpu_list = NULL;
+ if (ret == TARGET_ERRNO__SUCCESS)
+ ret = TARGET_ERRNO__PID_OVERRIDE_CPU;
+ }
+
+ /* UID and PID are mutually exclusive */
+ if (target->tid && target->uid_str) {
+ target->uid_str = NULL;
+ if (ret == TARGET_ERRNO__SUCCESS)
+ ret = TARGET_ERRNO__PID_OVERRIDE_UID;
+ }
+
+ /* UID and CPU are mutually exclusive */
+ if (target->uid_str && target->cpu_list) {
+ target->cpu_list = NULL;
+ if (ret == TARGET_ERRNO__SUCCESS)
+ ret = TARGET_ERRNO__UID_OVERRIDE_CPU;
+ }
+
+ /* PID and SYSTEM are mutually exclusive */
+ if (target->tid && target->system_wide) {
+ target->system_wide = false;
+ if (ret == TARGET_ERRNO__SUCCESS)
+ ret = TARGET_ERRNO__PID_OVERRIDE_SYSTEM;
+ }
+
+ /* UID and SYSTEM are mutually exclusive */
+ if (target->uid_str && target->system_wide) {
+ target->system_wide = false;
+ if (ret == TARGET_ERRNO__SUCCESS)
+ ret = TARGET_ERRNO__UID_OVERRIDE_SYSTEM;
+ }
+
+ /* THREAD and SYSTEM/CPU are mutually exclusive */
+ if (target->per_thread && (target->system_wide || target->cpu_list)) {
+ target->per_thread = false;
+ if (ret == TARGET_ERRNO__SUCCESS)
+ ret = TARGET_ERRNO__SYSTEM_OVERRIDE_THREAD;
+ }
+
+ return ret;
+}
+
+enum target_errno target__parse_uid(struct target *target)
+{
+ struct passwd pwd, *result;
+ char buf[1024];
+ const char *str = target->uid_str;
+
+ target->uid = UINT_MAX;
+ if (str == NULL)
+ return TARGET_ERRNO__SUCCESS;
+
+ /* Try user name first */
+ getpwnam_r(str, &pwd, buf, sizeof(buf), &result);
+
+ if (result == NULL) {
+ /*
+ * The user name not found. Maybe it's a UID number.
+ */
+ char *endptr;
+ int uid = strtol(str, &endptr, 10);
+
+ if (*endptr != '\0')
+ return TARGET_ERRNO__INVALID_UID;
+
+ getpwuid_r(uid, &pwd, buf, sizeof(buf), &result);
+
+ if (result == NULL)
+ return TARGET_ERRNO__USER_NOT_FOUND;
+ }
+
+ target->uid = result->pw_uid;
+ return TARGET_ERRNO__SUCCESS;
+}
+
+/*
+ * This must have a same ordering as the enum target_errno.
+ */
+static const char *target__error_str[] = {
+ "PID/TID switch overriding CPU",
+ "PID/TID switch overriding UID",
+ "UID switch overriding CPU",
+ "PID/TID switch overriding SYSTEM",
+ "UID switch overriding SYSTEM",
+ "SYSTEM/CPU switch overriding PER-THREAD",
+ "Invalid User: %s",
+ "Problems obtaining information for user %s",
+};
+
+int target__strerror(struct target *target, int errnum,
+ char *buf, size_t buflen)
+{
+ int idx;
+ const char *msg;
+
+ BUG_ON(buflen == 0);
+
+ if (errnum >= 0) {
+ const char *err = strerror_r(errnum, buf, buflen);
+
+ if (err != buf) {
+ size_t len = strlen(err);
+ memcpy(buf, err, min(buflen - 1, len));
+ *(buf + min(buflen - 1, len)) = '\0';
+ }
+
+ return 0;
+ }
+
+ if (errnum < __TARGET_ERRNO__START || errnum >= __TARGET_ERRNO__END)
+ return -1;
+
+ idx = errnum - __TARGET_ERRNO__START;
+ msg = target__error_str[idx];
+
+ switch (errnum) {
+ case TARGET_ERRNO__PID_OVERRIDE_CPU ...
+ TARGET_ERRNO__SYSTEM_OVERRIDE_THREAD:
+ snprintf(buf, buflen, "%s", msg);
+ break;
+
+ case TARGET_ERRNO__INVALID_UID:
+ case TARGET_ERRNO__USER_NOT_FOUND:
+ snprintf(buf, buflen, msg, target->uid_str);
+ break;
+
+ default:
+ /* cannot reach here */
+ break;
+ }
+
+ return 0;
+}
diff --git a/tools/perf/util/target.h b/tools/perf/util/target.h
new file mode 100644
index 00000000000..7381b1ca404
--- /dev/null
+++ b/tools/perf/util/target.h
@@ -0,0 +1,79 @@
+#ifndef _PERF_TARGET_H
+#define _PERF_TARGET_H
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+struct target {
+ const char *pid;
+ const char *tid;
+ const char *cpu_list;
+ const char *uid_str;
+ uid_t uid;
+ bool system_wide;
+ bool uses_mmap;
+ bool default_per_cpu;
+ bool per_thread;
+};
+
+enum target_errno {
+ TARGET_ERRNO__SUCCESS = 0,
+
+ /*
+ * Choose an arbitrary negative big number not to clash with standard
+ * errno since SUS requires the errno has distinct positive values.
+ * See 'Issue 6' in the link below.
+ *
+ * http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/errno.h.html
+ */
+ __TARGET_ERRNO__START = -10000,
+
+ /* for target__validate() */
+ TARGET_ERRNO__PID_OVERRIDE_CPU = __TARGET_ERRNO__START,
+ TARGET_ERRNO__PID_OVERRIDE_UID,
+ TARGET_ERRNO__UID_OVERRIDE_CPU,
+ TARGET_ERRNO__PID_OVERRIDE_SYSTEM,
+ TARGET_ERRNO__UID_OVERRIDE_SYSTEM,
+ TARGET_ERRNO__SYSTEM_OVERRIDE_THREAD,
+
+ /* for target__parse_uid() */
+ TARGET_ERRNO__INVALID_UID,
+ TARGET_ERRNO__USER_NOT_FOUND,
+
+ __TARGET_ERRNO__END,
+};
+
+enum target_errno target__validate(struct target *target);
+enum target_errno target__parse_uid(struct target *target);
+
+int target__strerror(struct target *target, int errnum, char *buf, size_t buflen);
+
+static inline bool target__has_task(struct target *target)
+{
+ return target->tid || target->pid || target->uid_str;
+}
+
+static inline bool target__has_cpu(struct target *target)
+{
+ return target->system_wide || target->cpu_list;
+}
+
+static inline bool target__none(struct target *target)
+{
+ return !target__has_task(target) && !target__has_cpu(target);
+}
+
+static inline bool target__uses_dummy_map(struct target *target)
+{
+ bool use_dummy = false;
+
+ if (target->default_per_cpu)
+ use_dummy = target->per_thread ? true : false;
+ else if (target__has_task(target) ||
+ (!target__has_cpu(target) && !target->uses_mmap))
+ use_dummy = true;
+
+ return use_dummy;
+}
+
+#endif /* _PERF_TARGET_H */
diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c
new file mode 100644
index 00000000000..2fde0d5e40b
--- /dev/null
+++ b/tools/perf/util/thread.c
@@ -0,0 +1,193 @@
+#include "../perf.h"
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include "session.h"
+#include "thread.h"
+#include "util.h"
+#include "debug.h"
+#include "comm.h"
+
+int thread__init_map_groups(struct thread *thread, struct machine *machine)
+{
+ struct thread *leader;
+ pid_t pid = thread->pid_;
+
+ if (pid == thread->tid) {
+ thread->mg = map_groups__new();
+ } else {
+ leader = machine__findnew_thread(machine, pid, pid);
+ if (leader)
+ thread->mg = map_groups__get(leader->mg);
+ }
+
+ return thread->mg ? 0 : -1;
+}
+
+struct thread *thread__new(pid_t pid, pid_t tid)
+{
+ char *comm_str;
+ struct comm *comm;
+ struct thread *thread = zalloc(sizeof(*thread));
+
+ if (thread != NULL) {
+ thread->pid_ = pid;
+ thread->tid = tid;
+ thread->ppid = -1;
+ INIT_LIST_HEAD(&thread->comm_list);
+
+ comm_str = malloc(32);
+ if (!comm_str)
+ goto err_thread;
+
+ snprintf(comm_str, 32, ":%d", tid);
+ comm = comm__new(comm_str, 0);
+ free(comm_str);
+ if (!comm)
+ goto err_thread;
+
+ list_add(&comm->list, &thread->comm_list);
+ }
+
+ return thread;
+
+err_thread:
+ free(thread);
+ return NULL;
+}
+
+void thread__delete(struct thread *thread)
+{
+ struct comm *comm, *tmp;
+
+ map_groups__put(thread->mg);
+ thread->mg = NULL;
+ list_for_each_entry_safe(comm, tmp, &thread->comm_list, list) {
+ list_del(&comm->list);
+ comm__free(comm);
+ }
+
+ free(thread);
+}
+
+struct comm *thread__comm(const struct thread *thread)
+{
+ if (list_empty(&thread->comm_list))
+ return NULL;
+
+ return list_first_entry(&thread->comm_list, struct comm, list);
+}
+
+/* CHECKME: time should always be 0 if event aren't ordered */
+int thread__set_comm(struct thread *thread, const char *str, u64 timestamp)
+{
+ struct comm *new, *curr = thread__comm(thread);
+ int err;
+
+ /* Override latest entry if it had no specific time coverage */
+ if (!curr->start) {
+ err = comm__override(curr, str, timestamp);
+ if (err)
+ return err;
+ } else {
+ new = comm__new(str, timestamp);
+ if (!new)
+ return -ENOMEM;
+ list_add(&new->list, &thread->comm_list);
+ }
+
+ thread->comm_set = true;
+
+ return 0;
+}
+
+const char *thread__comm_str(const struct thread *thread)
+{
+ const struct comm *comm = thread__comm(thread);
+
+ if (!comm)
+ return NULL;
+
+ return comm__str(comm);
+}
+
+/* CHECKME: it should probably better return the max comm len from its comm list */
+int thread__comm_len(struct thread *thread)
+{
+ if (!thread->comm_len) {
+ const char *comm = thread__comm_str(thread);
+ if (!comm)
+ return 0;
+ thread->comm_len = strlen(comm);
+ }
+
+ return thread->comm_len;
+}
+
+size_t thread__fprintf(struct thread *thread, FILE *fp)
+{
+ return fprintf(fp, "Thread %d %s\n", thread->tid, thread__comm_str(thread)) +
+ map_groups__fprintf(thread->mg, verbose, fp);
+}
+
+void thread__insert_map(struct thread *thread, struct map *map)
+{
+ map_groups__fixup_overlappings(thread->mg, map, verbose, stderr);
+ map_groups__insert(thread->mg, map);
+}
+
+static int thread__clone_map_groups(struct thread *thread,
+ struct thread *parent)
+{
+ int i;
+
+ /* This is new thread, we share map groups for process. */
+ if (thread->pid_ == parent->pid_)
+ return 0;
+
+ /* But this one is new process, copy maps. */
+ for (i = 0; i < MAP__NR_TYPES; ++i)
+ if (map_groups__clone(thread->mg, parent->mg, i) < 0)
+ return -ENOMEM;
+
+ return 0;
+}
+
+int thread__fork(struct thread *thread, struct thread *parent, u64 timestamp)
+{
+ int err;
+
+ if (parent->comm_set) {
+ const char *comm = thread__comm_str(parent);
+ if (!comm)
+ return -ENOMEM;
+ err = thread__set_comm(thread, comm, timestamp);
+ if (err)
+ return err;
+ thread->comm_set = true;
+ }
+
+ thread->ppid = parent->tid;
+ return thread__clone_map_groups(thread, parent);
+}
+
+void thread__find_cpumode_addr_location(struct thread *thread,
+ struct machine *machine,
+ enum map_type type, u64 addr,
+ struct addr_location *al)
+{
+ size_t i;
+ const u8 const cpumodes[] = {
+ PERF_RECORD_MISC_USER,
+ PERF_RECORD_MISC_KERNEL,
+ PERF_RECORD_MISC_GUEST_USER,
+ PERF_RECORD_MISC_GUEST_KERNEL
+ };
+
+ for (i = 0; i < ARRAY_SIZE(cpumodes); i++) {
+ thread__find_addr_location(thread, machine, cpumodes[i], type,
+ addr, al);
+ if (al->map)
+ break;
+ }
+}
diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h
new file mode 100644
index 00000000000..3c0c2724f82
--- /dev/null
+++ b/tools/perf/util/thread.h
@@ -0,0 +1,81 @@
+#ifndef __PERF_THREAD_H
+#define __PERF_THREAD_H
+
+#include <linux/rbtree.h>
+#include <linux/list.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include "symbol.h"
+#include <strlist.h>
+
+struct thread {
+ union {
+ struct rb_node rb_node;
+ struct list_head node;
+ };
+ struct map_groups *mg;
+ pid_t pid_; /* Not all tools update this */
+ pid_t tid;
+ pid_t ppid;
+ char shortname[3];
+ bool comm_set;
+ bool dead; /* if set thread has exited */
+ struct list_head comm_list;
+ int comm_len;
+
+ void *priv;
+};
+
+struct machine;
+struct comm;
+
+struct thread *thread__new(pid_t pid, pid_t tid);
+int thread__init_map_groups(struct thread *thread, struct machine *machine);
+void thread__delete(struct thread *thread);
+static inline void thread__exited(struct thread *thread)
+{
+ thread->dead = true;
+}
+
+int thread__set_comm(struct thread *thread, const char *comm, u64 timestamp);
+int thread__comm_len(struct thread *thread);
+struct comm *thread__comm(const struct thread *thread);
+const char *thread__comm_str(const struct thread *thread);
+void thread__insert_map(struct thread *thread, struct map *map);
+int thread__fork(struct thread *thread, struct thread *parent, u64 timestamp);
+size_t thread__fprintf(struct thread *thread, FILE *fp);
+
+void thread__find_addr_map(struct thread *thread, struct machine *machine,
+ u8 cpumode, enum map_type type, u64 addr,
+ struct addr_location *al);
+
+void thread__find_addr_location(struct thread *thread, struct machine *machine,
+ u8 cpumode, enum map_type type, u64 addr,
+ struct addr_location *al);
+
+void thread__find_cpumode_addr_location(struct thread *thread,
+ struct machine *machine,
+ enum map_type type, u64 addr,
+ struct addr_location *al);
+
+static inline void *thread__priv(struct thread *thread)
+{
+ return thread->priv;
+}
+
+static inline void thread__set_priv(struct thread *thread, void *p)
+{
+ thread->priv = p;
+}
+
+static inline bool thread__is_filtered(struct thread *thread)
+{
+ if (symbol_conf.comm_list &&
+ !strlist__has_entry(symbol_conf.comm_list, thread__comm_str(thread))) {
+ return true;
+ }
+
+ return false;
+}
+
+#endif /* __PERF_THREAD_H */
diff --git a/tools/perf/util/thread_map.c b/tools/perf/util/thread_map.c
new file mode 100644
index 00000000000..5d321591210
--- /dev/null
+++ b/tools/perf/util/thread_map.c
@@ -0,0 +1,294 @@
+#include <dirent.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include "strlist.h"
+#include <string.h>
+#include "thread_map.h"
+#include "util.h"
+
+/* Skip "." and ".." directories */
+static int filter(const struct dirent *dir)
+{
+ if (dir->d_name[0] == '.')
+ return 0;
+ else
+ return 1;
+}
+
+struct thread_map *thread_map__new_by_pid(pid_t pid)
+{
+ struct thread_map *threads;
+ char name[256];
+ int items;
+ struct dirent **namelist = NULL;
+ int i;
+
+ sprintf(name, "/proc/%d/task", pid);
+ items = scandir(name, &namelist, filter, NULL);
+ if (items <= 0)
+ return NULL;
+
+ threads = malloc(sizeof(*threads) + sizeof(pid_t) * items);
+ if (threads != NULL) {
+ for (i = 0; i < items; i++)
+ threads->map[i] = atoi(namelist[i]->d_name);
+ threads->nr = items;
+ }
+
+ for (i=0; i<items; i++)
+ zfree(&namelist[i]);
+ free(namelist);
+
+ return threads;
+}
+
+struct thread_map *thread_map__new_by_tid(pid_t tid)
+{
+ struct thread_map *threads = malloc(sizeof(*threads) + sizeof(pid_t));
+
+ if (threads != NULL) {
+ threads->map[0] = tid;
+ threads->nr = 1;
+ }
+
+ return threads;
+}
+
+struct thread_map *thread_map__new_by_uid(uid_t uid)
+{
+ DIR *proc;
+ int max_threads = 32, items, i;
+ char path[256];
+ struct dirent dirent, *next, **namelist = NULL;
+ struct thread_map *threads = malloc(sizeof(*threads) +
+ max_threads * sizeof(pid_t));
+ if (threads == NULL)
+ goto out;
+
+ proc = opendir("/proc");
+ if (proc == NULL)
+ goto out_free_threads;
+
+ threads->nr = 0;
+
+ while (!readdir_r(proc, &dirent, &next) && next) {
+ char *end;
+ bool grow = false;
+ struct stat st;
+ pid_t pid = strtol(dirent.d_name, &end, 10);
+
+ if (*end) /* only interested in proper numerical dirents */
+ continue;
+
+ snprintf(path, sizeof(path), "/proc/%s", dirent.d_name);
+
+ if (stat(path, &st) != 0)
+ continue;
+
+ if (st.st_uid != uid)
+ continue;
+
+ snprintf(path, sizeof(path), "/proc/%d/task", pid);
+ items = scandir(path, &namelist, filter, NULL);
+ if (items <= 0)
+ goto out_free_closedir;
+
+ while (threads->nr + items >= max_threads) {
+ max_threads *= 2;
+ grow = true;
+ }
+
+ if (grow) {
+ struct thread_map *tmp;
+
+ tmp = realloc(threads, (sizeof(*threads) +
+ max_threads * sizeof(pid_t)));
+ if (tmp == NULL)
+ goto out_free_namelist;
+
+ threads = tmp;
+ }
+
+ for (i = 0; i < items; i++)
+ threads->map[threads->nr + i] = atoi(namelist[i]->d_name);
+
+ for (i = 0; i < items; i++)
+ zfree(&namelist[i]);
+ free(namelist);
+
+ threads->nr += items;
+ }
+
+out_closedir:
+ closedir(proc);
+out:
+ return threads;
+
+out_free_threads:
+ free(threads);
+ return NULL;
+
+out_free_namelist:
+ for (i = 0; i < items; i++)
+ zfree(&namelist[i]);
+ free(namelist);
+
+out_free_closedir:
+ zfree(&threads);
+ goto out_closedir;
+}
+
+struct thread_map *thread_map__new(pid_t pid, pid_t tid, uid_t uid)
+{
+ if (pid != -1)
+ return thread_map__new_by_pid(pid);
+
+ if (tid == -1 && uid != UINT_MAX)
+ return thread_map__new_by_uid(uid);
+
+ return thread_map__new_by_tid(tid);
+}
+
+static struct thread_map *thread_map__new_by_pid_str(const char *pid_str)
+{
+ struct thread_map *threads = NULL, *nt;
+ char name[256];
+ int items, total_tasks = 0;
+ struct dirent **namelist = NULL;
+ int i, j = 0;
+ pid_t pid, prev_pid = INT_MAX;
+ char *end_ptr;
+ struct str_node *pos;
+ struct strlist *slist = strlist__new(false, pid_str);
+
+ if (!slist)
+ return NULL;
+
+ strlist__for_each(pos, slist) {
+ pid = strtol(pos->s, &end_ptr, 10);
+
+ if (pid == INT_MIN || pid == INT_MAX ||
+ (*end_ptr != '\0' && *end_ptr != ','))
+ goto out_free_threads;
+
+ if (pid == prev_pid)
+ continue;
+
+ sprintf(name, "/proc/%d/task", pid);
+ items = scandir(name, &namelist, filter, NULL);
+ if (items <= 0)
+ goto out_free_threads;
+
+ total_tasks += items;
+ nt = realloc(threads, (sizeof(*threads) +
+ sizeof(pid_t) * total_tasks));
+ if (nt == NULL)
+ goto out_free_namelist;
+
+ threads = nt;
+
+ for (i = 0; i < items; i++) {
+ threads->map[j++] = atoi(namelist[i]->d_name);
+ zfree(&namelist[i]);
+ }
+ threads->nr = total_tasks;
+ free(namelist);
+ }
+
+out:
+ strlist__delete(slist);
+ return threads;
+
+out_free_namelist:
+ for (i = 0; i < items; i++)
+ zfree(&namelist[i]);
+ free(namelist);
+
+out_free_threads:
+ zfree(&threads);
+ goto out;
+}
+
+static struct thread_map *thread_map__new_by_tid_str(const char *tid_str)
+{
+ struct thread_map *threads = NULL, *nt;
+ int ntasks = 0;
+ pid_t tid, prev_tid = INT_MAX;
+ char *end_ptr;
+ struct str_node *pos;
+ struct strlist *slist;
+
+ /* perf-stat expects threads to be generated even if tid not given */
+ if (!tid_str) {
+ threads = malloc(sizeof(*threads) + sizeof(pid_t));
+ if (threads != NULL) {
+ threads->map[0] = -1;
+ threads->nr = 1;
+ }
+ return threads;
+ }
+
+ slist = strlist__new(false, tid_str);
+ if (!slist)
+ return NULL;
+
+ strlist__for_each(pos, slist) {
+ tid = strtol(pos->s, &end_ptr, 10);
+
+ if (tid == INT_MIN || tid == INT_MAX ||
+ (*end_ptr != '\0' && *end_ptr != ','))
+ goto out_free_threads;
+
+ if (tid == prev_tid)
+ continue;
+
+ ntasks++;
+ nt = realloc(threads, sizeof(*threads) + sizeof(pid_t) * ntasks);
+
+ if (nt == NULL)
+ goto out_free_threads;
+
+ threads = nt;
+ threads->map[ntasks - 1] = tid;
+ threads->nr = ntasks;
+ }
+out:
+ return threads;
+
+out_free_threads:
+ zfree(&threads);
+ goto out;
+}
+
+struct thread_map *thread_map__new_str(const char *pid, const char *tid,
+ uid_t uid)
+{
+ if (pid)
+ return thread_map__new_by_pid_str(pid);
+
+ if (!tid && uid != UINT_MAX)
+ return thread_map__new_by_uid(uid);
+
+ return thread_map__new_by_tid_str(tid);
+}
+
+void thread_map__delete(struct thread_map *threads)
+{
+ free(threads);
+}
+
+size_t thread_map__fprintf(struct thread_map *threads, FILE *fp)
+{
+ int i;
+ size_t printed = fprintf(fp, "%d thread%s: ",
+ threads->nr, threads->nr > 1 ? "s" : "");
+ for (i = 0; i < threads->nr; ++i)
+ printed += fprintf(fp, "%s%d", i ? ", " : "", threads->map[i]);
+
+ return printed + fprintf(fp, "\n");
+}
diff --git a/tools/perf/util/thread_map.h b/tools/perf/util/thread_map.h
new file mode 100644
index 00000000000..0cd8b310808
--- /dev/null
+++ b/tools/perf/util/thread_map.h
@@ -0,0 +1,29 @@
+#ifndef __PERF_THREAD_MAP_H
+#define __PERF_THREAD_MAP_H
+
+#include <sys/types.h>
+#include <stdio.h>
+
+struct thread_map {
+ int nr;
+ pid_t map[];
+};
+
+struct thread_map *thread_map__new_by_pid(pid_t pid);
+struct thread_map *thread_map__new_by_tid(pid_t tid);
+struct thread_map *thread_map__new_by_uid(uid_t uid);
+struct thread_map *thread_map__new(pid_t pid, pid_t tid, uid_t uid);
+
+struct thread_map *thread_map__new_str(const char *pid,
+ const char *tid, uid_t uid);
+
+void thread_map__delete(struct thread_map *threads);
+
+size_t thread_map__fprintf(struct thread_map *threads, FILE *fp);
+
+static inline int thread_map__nr(struct thread_map *threads)
+{
+ return threads ? threads->nr : 1;
+}
+
+#endif /* __PERF_THREAD_MAP_H */
diff --git a/tools/perf/util/tool.h b/tools/perf/util/tool.h
new file mode 100644
index 00000000000..4385816d3d4
--- /dev/null
+++ b/tools/perf/util/tool.h
@@ -0,0 +1,47 @@
+#ifndef __PERF_TOOL_H
+#define __PERF_TOOL_H
+
+#include <stdbool.h>
+
+struct perf_session;
+union perf_event;
+struct perf_evlist;
+struct perf_evsel;
+struct perf_sample;
+struct perf_tool;
+struct machine;
+
+typedef int (*event_sample)(struct perf_tool *tool, union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel, struct machine *machine);
+
+typedef int (*event_op)(struct perf_tool *tool, union perf_event *event,
+ struct perf_sample *sample, struct machine *machine);
+
+typedef int (*event_attr_op)(struct perf_tool *tool,
+ union perf_event *event,
+ struct perf_evlist **pevlist);
+
+typedef int (*event_op2)(struct perf_tool *tool, union perf_event *event,
+ struct perf_session *session);
+
+struct perf_tool {
+ event_sample sample,
+ read;
+ event_op mmap,
+ mmap2,
+ comm,
+ fork,
+ exit,
+ lost,
+ throttle,
+ unthrottle;
+ event_attr_op attr;
+ event_op2 tracing_data;
+ event_op2 finished_round,
+ build_id;
+ bool ordered_samples;
+ bool ordering_requires_timestamps;
+};
+
+#endif /* __PERF_TOOL_H */
diff --git a/tools/perf/util/top.c b/tools/perf/util/top.c
new file mode 100644
index 00000000000..8e517def925
--- /dev/null
+++ b/tools/perf/util/top.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2011, Red Hat Inc, Arnaldo Carvalho de Melo <acme@redhat.com>
+ *
+ * Refactored from builtin-top.c, see that files for further copyright notes.
+ *
+ * Released under the GPL v2. (and only v2, not any later version)
+ */
+
+#include "cpumap.h"
+#include "event.h"
+#include "evlist.h"
+#include "evsel.h"
+#include "parse-events.h"
+#include "symbol.h"
+#include "top.h"
+#include <inttypes.h>
+
+#define SNPRINTF(buf, size, fmt, args...) \
+({ \
+ size_t r = snprintf(buf, size, fmt, ## args); \
+ r > size ? size : r; \
+})
+
+size_t perf_top__header_snprintf(struct perf_top *top, char *bf, size_t size)
+{
+ float samples_per_sec;
+ float ksamples_per_sec;
+ float esamples_percent;
+ struct record_opts *opts = &top->record_opts;
+ struct target *target = &opts->target;
+ size_t ret = 0;
+
+ if (top->samples) {
+ samples_per_sec = top->samples / top->delay_secs;
+ ksamples_per_sec = top->kernel_samples / top->delay_secs;
+ esamples_percent = (100.0 * top->exact_samples) / top->samples;
+ } else {
+ samples_per_sec = ksamples_per_sec = esamples_percent = 0.0;
+ }
+
+ if (!perf_guest) {
+ float ksamples_percent = 0.0;
+
+ if (samples_per_sec)
+ ksamples_percent = (100.0 * ksamples_per_sec) /
+ samples_per_sec;
+ ret = SNPRINTF(bf, size,
+ " PerfTop:%8.0f irqs/sec kernel:%4.1f%%"
+ " exact: %4.1f%% [", samples_per_sec,
+ ksamples_percent, esamples_percent);
+ } else {
+ float us_samples_per_sec = top->us_samples / top->delay_secs;
+ float guest_kernel_samples_per_sec = top->guest_kernel_samples / top->delay_secs;
+ float guest_us_samples_per_sec = top->guest_us_samples / top->delay_secs;
+
+ ret = SNPRINTF(bf, size,
+ " PerfTop:%8.0f irqs/sec kernel:%4.1f%% us:%4.1f%%"
+ " guest kernel:%4.1f%% guest us:%4.1f%%"
+ " exact: %4.1f%% [", samples_per_sec,
+ 100.0 - (100.0 * ((samples_per_sec - ksamples_per_sec) /
+ samples_per_sec)),
+ 100.0 - (100.0 * ((samples_per_sec - us_samples_per_sec) /
+ samples_per_sec)),
+ 100.0 - (100.0 * ((samples_per_sec -
+ guest_kernel_samples_per_sec) /
+ samples_per_sec)),
+ 100.0 - (100.0 * ((samples_per_sec -
+ guest_us_samples_per_sec) /
+ samples_per_sec)),
+ esamples_percent);
+ }
+
+ if (top->evlist->nr_entries == 1) {
+ struct perf_evsel *first = perf_evlist__first(top->evlist);
+ ret += SNPRINTF(bf + ret, size - ret, "%" PRIu64 "%s ",
+ (uint64_t)first->attr.sample_period,
+ opts->freq ? "Hz" : "");
+ }
+
+ ret += SNPRINTF(bf + ret, size - ret, "%s", perf_evsel__name(top->sym_evsel));
+
+ ret += SNPRINTF(bf + ret, size - ret, "], ");
+
+ if (target->pid)
+ ret += SNPRINTF(bf + ret, size - ret, " (target_pid: %s",
+ target->pid);
+ else if (target->tid)
+ ret += SNPRINTF(bf + ret, size - ret, " (target_tid: %s",
+ target->tid);
+ else if (target->uid_str != NULL)
+ ret += SNPRINTF(bf + ret, size - ret, " (uid: %s",
+ target->uid_str);
+ else
+ ret += SNPRINTF(bf + ret, size - ret, " (all");
+
+ if (target->cpu_list)
+ ret += SNPRINTF(bf + ret, size - ret, ", CPU%s: %s)",
+ top->evlist->cpus->nr > 1 ? "s" : "",
+ target->cpu_list);
+ else {
+ if (target->tid)
+ ret += SNPRINTF(bf + ret, size - ret, ")");
+ else
+ ret += SNPRINTF(bf + ret, size - ret, ", %d CPU%s)",
+ top->evlist->cpus->nr,
+ top->evlist->cpus->nr > 1 ? "s" : "");
+ }
+
+ return ret;
+}
+
+void perf_top__reset_sample_counters(struct perf_top *top)
+{
+ top->samples = top->us_samples = top->kernel_samples =
+ top->exact_samples = top->guest_kernel_samples =
+ top->guest_us_samples = 0;
+}
diff --git a/tools/perf/util/top.h b/tools/perf/util/top.h
new file mode 100644
index 00000000000..f92c37abb0a
--- /dev/null
+++ b/tools/perf/util/top.h
@@ -0,0 +1,47 @@
+#ifndef __PERF_TOP_H
+#define __PERF_TOP_H 1
+
+#include "tool.h"
+#include <linux/types.h>
+#include <stddef.h>
+#include <stdbool.h>
+#include <termios.h>
+
+struct perf_evlist;
+struct perf_evsel;
+struct perf_session;
+
+struct perf_top {
+ struct perf_tool tool;
+ struct perf_evlist *evlist;
+ struct record_opts record_opts;
+ /*
+ * Symbols will be added here in perf_event__process_sample and will
+ * get out after decayed.
+ */
+ u64 samples;
+ u64 kernel_samples, us_samples;
+ u64 exact_samples;
+ u64 guest_us_samples, guest_kernel_samples;
+ int print_entries, count_filter, delay_secs;
+ int max_stack;
+ bool hide_kernel_symbols, hide_user_symbols, zero;
+ bool use_tui, use_stdio;
+ bool kptr_restrict_warned;
+ bool vmlinux_warned;
+ bool dump_symtab;
+ struct hist_entry *sym_filter_entry;
+ struct perf_evsel *sym_evsel;
+ struct perf_session *session;
+ struct winsize winsize;
+ int realtime_prio;
+ int sym_pcnt_filter;
+ const char *sym_filter;
+ float min_percent;
+};
+
+#define CONSOLE_CLEAR ""
+
+size_t perf_top__header_snprintf(struct perf_top *top, char *bf, size_t size);
+void perf_top__reset_sample_counters(struct perf_top *top);
+#endif /* __PERF_TOP_H */
diff --git a/tools/perf/util/trace-event-info.c b/tools/perf/util/trace-event-info.c
new file mode 100644
index 00000000000..7e6fcfe8b43
--- /dev/null
+++ b/tools/perf/util/trace-event-info.c
@@ -0,0 +1,600 @@
+/*
+ * Copyright (C) 2008,2009, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include "util.h"
+#include <dirent.h>
+#include <mntent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <linux/list.h>
+#include <linux/kernel.h>
+
+#include "../perf.h"
+#include "trace-event.h"
+#include <api/fs/debugfs.h>
+#include "evsel.h"
+
+#define VERSION "0.5"
+
+static int output_fd;
+
+
+int bigendian(void)
+{
+ unsigned char str[] = { 0x1, 0x2, 0x3, 0x4, 0x0, 0x0, 0x0, 0x0};
+ unsigned int *ptr;
+
+ ptr = (unsigned int *)(void *)str;
+ return *ptr == 0x01020304;
+}
+
+/* unfortunately, you can not stat debugfs or proc files for size */
+static int record_file(const char *file, ssize_t hdr_sz)
+{
+ unsigned long long size = 0;
+ char buf[BUFSIZ], *sizep;
+ off_t hdr_pos = lseek(output_fd, 0, SEEK_CUR);
+ int r, fd;
+ int err = -EIO;
+
+ fd = open(file, O_RDONLY);
+ if (fd < 0) {
+ pr_debug("Can't read '%s'", file);
+ return -errno;
+ }
+
+ /* put in zeros for file size, then fill true size later */
+ if (hdr_sz) {
+ if (write(output_fd, &size, hdr_sz) != hdr_sz)
+ goto out;
+ }
+
+ do {
+ r = read(fd, buf, BUFSIZ);
+ if (r > 0) {
+ size += r;
+ if (write(output_fd, buf, r) != r)
+ goto out;
+ }
+ } while (r > 0);
+
+ /* ugh, handle big-endian hdr_size == 4 */
+ sizep = (char*)&size;
+ if (bigendian())
+ sizep += sizeof(u64) - hdr_sz;
+
+ if (hdr_sz && pwrite(output_fd, sizep, hdr_sz, hdr_pos) < 0) {
+ pr_debug("writing file size failed\n");
+ goto out;
+ }
+
+ err = 0;
+out:
+ close(fd);
+ return err;
+}
+
+static int record_header_files(void)
+{
+ char *path;
+ struct stat st;
+ int err = -EIO;
+
+ path = get_tracing_file("events/header_page");
+ if (!path) {
+ pr_debug("can't get tracing/events/header_page");
+ return -ENOMEM;
+ }
+
+ if (stat(path, &st) < 0) {
+ pr_debug("can't read '%s'", path);
+ goto out;
+ }
+
+ if (write(output_fd, "header_page", 12) != 12) {
+ pr_debug("can't write header_page\n");
+ goto out;
+ }
+
+ if (record_file(path, 8) < 0) {
+ pr_debug("can't record header_page file\n");
+ goto out;
+ }
+
+ put_tracing_file(path);
+
+ path = get_tracing_file("events/header_event");
+ if (!path) {
+ pr_debug("can't get tracing/events/header_event");
+ err = -ENOMEM;
+ goto out;
+ }
+
+ if (stat(path, &st) < 0) {
+ pr_debug("can't read '%s'", path);
+ goto out;
+ }
+
+ if (write(output_fd, "header_event", 13) != 13) {
+ pr_debug("can't write header_event\n");
+ goto out;
+ }
+
+ if (record_file(path, 8) < 0) {
+ pr_debug("can't record header_event file\n");
+ goto out;
+ }
+
+ err = 0;
+out:
+ put_tracing_file(path);
+ return err;
+}
+
+static bool name_in_tp_list(char *sys, struct tracepoint_path *tps)
+{
+ while (tps) {
+ if (!strcmp(sys, tps->name))
+ return true;
+ tps = tps->next;
+ }
+
+ return false;
+}
+
+static int copy_event_system(const char *sys, struct tracepoint_path *tps)
+{
+ struct dirent *dent;
+ struct stat st;
+ char *format;
+ DIR *dir;
+ int count = 0;
+ int ret;
+ int err;
+
+ dir = opendir(sys);
+ if (!dir) {
+ pr_debug("can't read directory '%s'", sys);
+ return -errno;
+ }
+
+ while ((dent = readdir(dir))) {
+ if (dent->d_type != DT_DIR ||
+ strcmp(dent->d_name, ".") == 0 ||
+ strcmp(dent->d_name, "..") == 0 ||
+ !name_in_tp_list(dent->d_name, tps))
+ continue;
+ format = malloc(strlen(sys) + strlen(dent->d_name) + 10);
+ if (!format) {
+ err = -ENOMEM;
+ goto out;
+ }
+ sprintf(format, "%s/%s/format", sys, dent->d_name);
+ ret = stat(format, &st);
+ free(format);
+ if (ret < 0)
+ continue;
+ count++;
+ }
+
+ if (write(output_fd, &count, 4) != 4) {
+ err = -EIO;
+ pr_debug("can't write count\n");
+ goto out;
+ }
+
+ rewinddir(dir);
+ while ((dent = readdir(dir))) {
+ if (dent->d_type != DT_DIR ||
+ strcmp(dent->d_name, ".") == 0 ||
+ strcmp(dent->d_name, "..") == 0 ||
+ !name_in_tp_list(dent->d_name, tps))
+ continue;
+ format = malloc(strlen(sys) + strlen(dent->d_name) + 10);
+ if (!format) {
+ err = -ENOMEM;
+ goto out;
+ }
+ sprintf(format, "%s/%s/format", sys, dent->d_name);
+ ret = stat(format, &st);
+
+ if (ret >= 0) {
+ err = record_file(format, 8);
+ if (err) {
+ free(format);
+ goto out;
+ }
+ }
+ free(format);
+ }
+ err = 0;
+out:
+ closedir(dir);
+ return err;
+}
+
+static int record_ftrace_files(struct tracepoint_path *tps)
+{
+ char *path;
+ int ret;
+
+ path = get_tracing_file("events/ftrace");
+ if (!path) {
+ pr_debug("can't get tracing/events/ftrace");
+ return -ENOMEM;
+ }
+
+ ret = copy_event_system(path, tps);
+
+ put_tracing_file(path);
+
+ return ret;
+}
+
+static bool system_in_tp_list(char *sys, struct tracepoint_path *tps)
+{
+ while (tps) {
+ if (!strcmp(sys, tps->system))
+ return true;
+ tps = tps->next;
+ }
+
+ return false;
+}
+
+static int record_event_files(struct tracepoint_path *tps)
+{
+ struct dirent *dent;
+ struct stat st;
+ char *path;
+ char *sys;
+ DIR *dir;
+ int count = 0;
+ int ret;
+ int err;
+
+ path = get_tracing_file("events");
+ if (!path) {
+ pr_debug("can't get tracing/events");
+ return -ENOMEM;
+ }
+
+ dir = opendir(path);
+ if (!dir) {
+ err = -errno;
+ pr_debug("can't read directory '%s'", path);
+ goto out;
+ }
+
+ while ((dent = readdir(dir))) {
+ if (dent->d_type != DT_DIR ||
+ strcmp(dent->d_name, ".") == 0 ||
+ strcmp(dent->d_name, "..") == 0 ||
+ strcmp(dent->d_name, "ftrace") == 0 ||
+ !system_in_tp_list(dent->d_name, tps))
+ continue;
+ count++;
+ }
+
+ if (write(output_fd, &count, 4) != 4) {
+ err = -EIO;
+ pr_debug("can't write count\n");
+ goto out;
+ }
+
+ rewinddir(dir);
+ while ((dent = readdir(dir))) {
+ if (dent->d_type != DT_DIR ||
+ strcmp(dent->d_name, ".") == 0 ||
+ strcmp(dent->d_name, "..") == 0 ||
+ strcmp(dent->d_name, "ftrace") == 0 ||
+ !system_in_tp_list(dent->d_name, tps))
+ continue;
+ sys = malloc(strlen(path) + strlen(dent->d_name) + 2);
+ if (!sys) {
+ err = -ENOMEM;
+ goto out;
+ }
+ sprintf(sys, "%s/%s", path, dent->d_name);
+ ret = stat(sys, &st);
+ if (ret >= 0) {
+ ssize_t size = strlen(dent->d_name) + 1;
+
+ if (write(output_fd, dent->d_name, size) != size ||
+ copy_event_system(sys, tps) < 0) {
+ err = -EIO;
+ free(sys);
+ goto out;
+ }
+ }
+ free(sys);
+ }
+ err = 0;
+out:
+ closedir(dir);
+ put_tracing_file(path);
+
+ return err;
+}
+
+static int record_proc_kallsyms(void)
+{
+ unsigned int size;
+ const char *path = "/proc/kallsyms";
+ struct stat st;
+ int ret, err = 0;
+
+ ret = stat(path, &st);
+ if (ret < 0) {
+ /* not found */
+ size = 0;
+ if (write(output_fd, &size, 4) != 4)
+ err = -EIO;
+ return err;
+ }
+ return record_file(path, 4);
+}
+
+static int record_ftrace_printk(void)
+{
+ unsigned int size;
+ char *path;
+ struct stat st;
+ int ret, err = 0;
+
+ path = get_tracing_file("printk_formats");
+ if (!path) {
+ pr_debug("can't get tracing/printk_formats");
+ return -ENOMEM;
+ }
+
+ ret = stat(path, &st);
+ if (ret < 0) {
+ /* not found */
+ size = 0;
+ if (write(output_fd, &size, 4) != 4)
+ err = -EIO;
+ goto out;
+ }
+ err = record_file(path, 4);
+
+out:
+ put_tracing_file(path);
+ return err;
+}
+
+static void
+put_tracepoints_path(struct tracepoint_path *tps)
+{
+ while (tps) {
+ struct tracepoint_path *t = tps;
+
+ tps = tps->next;
+ zfree(&t->name);
+ zfree(&t->system);
+ free(t);
+ }
+}
+
+static struct tracepoint_path *
+get_tracepoints_path(struct list_head *pattrs)
+{
+ struct tracepoint_path path, *ppath = &path;
+ struct perf_evsel *pos;
+ int nr_tracepoints = 0;
+
+ list_for_each_entry(pos, pattrs, node) {
+ if (pos->attr.type != PERF_TYPE_TRACEPOINT)
+ continue;
+ ++nr_tracepoints;
+
+ if (pos->name) {
+ ppath->next = tracepoint_name_to_path(pos->name);
+ if (ppath->next)
+ goto next;
+
+ if (strchr(pos->name, ':') == NULL)
+ goto try_id;
+
+ goto error;
+ }
+
+try_id:
+ ppath->next = tracepoint_id_to_path(pos->attr.config);
+ if (!ppath->next) {
+error:
+ pr_debug("No memory to alloc tracepoints list\n");
+ put_tracepoints_path(&path);
+ return NULL;
+ }
+next:
+ ppath = ppath->next;
+ }
+
+ return nr_tracepoints > 0 ? path.next : NULL;
+}
+
+bool have_tracepoints(struct list_head *pattrs)
+{
+ struct perf_evsel *pos;
+
+ list_for_each_entry(pos, pattrs, node)
+ if (pos->attr.type == PERF_TYPE_TRACEPOINT)
+ return true;
+
+ return false;
+}
+
+static int tracing_data_header(void)
+{
+ char buf[20];
+ ssize_t size;
+
+ /* just guessing this is someone's birthday.. ;) */
+ buf[0] = 23;
+ buf[1] = 8;
+ buf[2] = 68;
+ memcpy(buf + 3, "tracing", 7);
+
+ if (write(output_fd, buf, 10) != 10)
+ return -1;
+
+ size = strlen(VERSION) + 1;
+ if (write(output_fd, VERSION, size) != size)
+ return -1;
+
+ /* save endian */
+ if (bigendian())
+ buf[0] = 1;
+ else
+ buf[0] = 0;
+
+ if (write(output_fd, buf, 1) != 1)
+ return -1;
+
+ /* save size of long */
+ buf[0] = sizeof(long);
+ if (write(output_fd, buf, 1) != 1)
+ return -1;
+
+ /* save page_size */
+ if (write(output_fd, &page_size, 4) != 4)
+ return -1;
+
+ return 0;
+}
+
+struct tracing_data *tracing_data_get(struct list_head *pattrs,
+ int fd, bool temp)
+{
+ struct tracepoint_path *tps;
+ struct tracing_data *tdata;
+ int err;
+
+ output_fd = fd;
+
+ tps = get_tracepoints_path(pattrs);
+ if (!tps)
+ return NULL;
+
+ tdata = malloc(sizeof(*tdata));
+ if (!tdata)
+ return NULL;
+
+ tdata->temp = temp;
+ tdata->size = 0;
+
+ if (temp) {
+ int temp_fd;
+
+ snprintf(tdata->temp_file, sizeof(tdata->temp_file),
+ "/tmp/perf-XXXXXX");
+ if (!mkstemp(tdata->temp_file)) {
+ pr_debug("Can't make temp file");
+ return NULL;
+ }
+
+ temp_fd = open(tdata->temp_file, O_RDWR);
+ if (temp_fd < 0) {
+ pr_debug("Can't read '%s'", tdata->temp_file);
+ return NULL;
+ }
+
+ /*
+ * Set the temp file the default output, so all the
+ * tracing data are stored into it.
+ */
+ output_fd = temp_fd;
+ }
+
+ err = tracing_data_header();
+ if (err)
+ goto out;
+ err = record_header_files();
+ if (err)
+ goto out;
+ err = record_ftrace_files(tps);
+ if (err)
+ goto out;
+ err = record_event_files(tps);
+ if (err)
+ goto out;
+ err = record_proc_kallsyms();
+ if (err)
+ goto out;
+ err = record_ftrace_printk();
+
+out:
+ /*
+ * All tracing data are stored by now, we can restore
+ * the default output file in case we used temp file.
+ */
+ if (temp) {
+ tdata->size = lseek(output_fd, 0, SEEK_CUR);
+ close(output_fd);
+ output_fd = fd;
+ }
+
+ if (err)
+ zfree(&tdata);
+
+ put_tracepoints_path(tps);
+ return tdata;
+}
+
+int tracing_data_put(struct tracing_data *tdata)
+{
+ int err = 0;
+
+ if (tdata->temp) {
+ err = record_file(tdata->temp_file, 0);
+ unlink(tdata->temp_file);
+ }
+
+ free(tdata);
+ return err;
+}
+
+int read_tracing_data(int fd, struct list_head *pattrs)
+{
+ int err;
+ struct tracing_data *tdata;
+
+ /*
+ * We work over the real file, so we can write data
+ * directly, no temp file is needed.
+ */
+ tdata = tracing_data_get(pattrs, fd, false);
+ if (!tdata)
+ return -ENOMEM;
+
+ err = tracing_data_put(tdata);
+ return err;
+}
diff --git a/tools/perf/util/trace-event-parse.c b/tools/perf/util/trace-event-parse.c
new file mode 100644
index 00000000000..c36636fd825
--- /dev/null
+++ b/tools/perf/util/trace-event-parse.c
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2009, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include <errno.h>
+
+#include "../perf.h"
+#include "util.h"
+#include "trace-event.h"
+
+static int get_common_field(struct scripting_context *context,
+ int *offset, int *size, const char *type)
+{
+ struct pevent *pevent = context->pevent;
+ struct event_format *event;
+ struct format_field *field;
+
+ if (!*size) {
+ if (!pevent->events)
+ return 0;
+
+ event = pevent->events[0];
+ field = pevent_find_common_field(event, type);
+ if (!field)
+ return 0;
+ *offset = field->offset;
+ *size = field->size;
+ }
+
+ return pevent_read_number(pevent, context->event_data + *offset, *size);
+}
+
+int common_lock_depth(struct scripting_context *context)
+{
+ static int offset;
+ static int size;
+ int ret;
+
+ ret = get_common_field(context, &size, &offset,
+ "common_lock_depth");
+ if (ret < 0)
+ return -1;
+
+ return ret;
+}
+
+int common_flags(struct scripting_context *context)
+{
+ static int offset;
+ static int size;
+ int ret;
+
+ ret = get_common_field(context, &size, &offset,
+ "common_flags");
+ if (ret < 0)
+ return -1;
+
+ return ret;
+}
+
+int common_pc(struct scripting_context *context)
+{
+ static int offset;
+ static int size;
+ int ret;
+
+ ret = get_common_field(context, &size, &offset,
+ "common_preempt_count");
+ if (ret < 0)
+ return -1;
+
+ return ret;
+}
+
+unsigned long long
+raw_field_value(struct event_format *event, const char *name, void *data)
+{
+ struct format_field *field;
+ unsigned long long val;
+
+ field = pevent_find_any_field(event, name);
+ if (!field)
+ return 0ULL;
+
+ pevent_read_number_field(field, data, &val);
+
+ return val;
+}
+
+unsigned long long read_size(struct event_format *event, void *ptr, int size)
+{
+ return pevent_read_number(event->pevent, ptr, size);
+}
+
+void event_format__print(struct event_format *event,
+ int cpu, void *data, int size)
+{
+ struct pevent_record record;
+ struct trace_seq s;
+
+ memset(&record, 0, sizeof(record));
+ record.cpu = cpu;
+ record.size = size;
+ record.data = data;
+
+ trace_seq_init(&s);
+ pevent_event_info(&s, event, &record);
+ trace_seq_do_printf(&s);
+ trace_seq_destroy(&s);
+}
+
+void parse_proc_kallsyms(struct pevent *pevent,
+ char *file, unsigned int size __maybe_unused)
+{
+ unsigned long long addr;
+ char *func;
+ char *line;
+ char *next = NULL;
+ char *addr_str;
+ char *mod;
+ char *fmt = NULL;
+
+ line = strtok_r(file, "\n", &next);
+ while (line) {
+ mod = NULL;
+ addr_str = strtok_r(line, " ", &fmt);
+ addr = strtoull(addr_str, NULL, 16);
+ /* skip character */
+ strtok_r(NULL, " ", &fmt);
+ func = strtok_r(NULL, "\t", &fmt);
+ mod = strtok_r(NULL, "]", &fmt);
+ /* truncate the extra '[' */
+ if (mod)
+ mod = mod + 1;
+
+ pevent_register_function(pevent, func, addr, mod);
+
+ line = strtok_r(NULL, "\n", &next);
+ }
+}
+
+void parse_ftrace_printk(struct pevent *pevent,
+ char *file, unsigned int size __maybe_unused)
+{
+ unsigned long long addr;
+ char *printk;
+ char *line;
+ char *next = NULL;
+ char *addr_str;
+ char *fmt;
+
+ line = strtok_r(file, "\n", &next);
+ while (line) {
+ addr_str = strtok_r(line, ":", &fmt);
+ if (!addr_str) {
+ warning("printk format with empty entry");
+ break;
+ }
+ addr = strtoull(addr_str, NULL, 16);
+ /* fmt still has a space, skip it */
+ printk = strdup(fmt+1);
+ line = strtok_r(NULL, "\n", &next);
+ pevent_register_print_string(pevent, printk, addr);
+ }
+}
+
+int parse_ftrace_file(struct pevent *pevent, char *buf, unsigned long size)
+{
+ return pevent_parse_event(pevent, buf, size, "ftrace");
+}
+
+int parse_event_file(struct pevent *pevent,
+ char *buf, unsigned long size, char *sys)
+{
+ return pevent_parse_event(pevent, buf, size, sys);
+}
+
+struct event_format *trace_find_next_event(struct pevent *pevent,
+ struct event_format *event)
+{
+ static int idx;
+
+ if (!pevent || !pevent->events)
+ return NULL;
+
+ if (!event) {
+ idx = 0;
+ return pevent->events[0];
+ }
+
+ if (idx < pevent->nr_events && event == pevent->events[idx]) {
+ idx++;
+ if (idx == pevent->nr_events)
+ return NULL;
+ return pevent->events[idx];
+ }
+
+ for (idx = 1; idx < pevent->nr_events; idx++) {
+ if (event == pevent->events[idx - 1])
+ return pevent->events[idx];
+ }
+ return NULL;
+}
+
+struct flag {
+ const char *name;
+ unsigned long long value;
+};
+
+static const struct flag flags[] = {
+ { "HI_SOFTIRQ", 0 },
+ { "TIMER_SOFTIRQ", 1 },
+ { "NET_TX_SOFTIRQ", 2 },
+ { "NET_RX_SOFTIRQ", 3 },
+ { "BLOCK_SOFTIRQ", 4 },
+ { "BLOCK_IOPOLL_SOFTIRQ", 5 },
+ { "TASKLET_SOFTIRQ", 6 },
+ { "SCHED_SOFTIRQ", 7 },
+ { "HRTIMER_SOFTIRQ", 8 },
+ { "RCU_SOFTIRQ", 9 },
+
+ { "HRTIMER_NORESTART", 0 },
+ { "HRTIMER_RESTART", 1 },
+};
+
+unsigned long long eval_flag(const char *flag)
+{
+ int i;
+
+ /*
+ * Some flags in the format files do not get converted.
+ * If the flag is not numeric, see if it is something that
+ * we already know about.
+ */
+ if (isdigit(flag[0]))
+ return strtoull(flag, NULL, 0);
+
+ for (i = 0; i < (int)(sizeof(flags)/sizeof(flags[0])); i++)
+ if (strcmp(flags[i].name, flag) == 0)
+ return flags[i].value;
+
+ return 0;
+}
diff --git a/tools/perf/util/trace-event-read.c b/tools/perf/util/trace-event-read.c
new file mode 100644
index 00000000000..e113e180c48
--- /dev/null
+++ b/tools/perf/util/trace-event-read.c
@@ -0,0 +1,444 @@
+/*
+ * Copyright (C) 2009, Steven Rostedt <srostedt@redhat.com>
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; version 2 of the License (not later!)
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ */
+#include <dirent.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <stdarg.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/mman.h>
+#include <pthread.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <errno.h>
+
+#include "../perf.h"
+#include "util.h"
+#include "trace-event.h"
+
+static int input_fd;
+
+static ssize_t trace_data_size;
+static bool repipe;
+
+static int __do_read(int fd, void *buf, int size)
+{
+ int rsize = size;
+
+ while (size) {
+ int ret = read(fd, buf, size);
+
+ if (ret <= 0)
+ return -1;
+
+ if (repipe) {
+ int retw = write(STDOUT_FILENO, buf, ret);
+
+ if (retw <= 0 || retw != ret) {
+ pr_debug("repiping input file");
+ return -1;
+ }
+ }
+
+ size -= ret;
+ buf += ret;
+ }
+
+ return rsize;
+}
+
+static int do_read(void *data, int size)
+{
+ int r;
+
+ r = __do_read(input_fd, data, size);
+ if (r <= 0) {
+ pr_debug("reading input file (size expected=%d received=%d)",
+ size, r);
+ return -1;
+ }
+
+ trace_data_size += r;
+
+ return r;
+}
+
+/* If it fails, the next read will report it */
+static void skip(int size)
+{
+ char buf[BUFSIZ];
+ int r;
+
+ while (size) {
+ r = size > BUFSIZ ? BUFSIZ : size;
+ do_read(buf, r);
+ size -= r;
+ };
+}
+
+static unsigned int read4(struct pevent *pevent)
+{
+ unsigned int data;
+
+ if (do_read(&data, 4) < 0)
+ return 0;
+ return __data2host4(pevent, data);
+}
+
+static unsigned long long read8(struct pevent *pevent)
+{
+ unsigned long long data;
+
+ if (do_read(&data, 8) < 0)
+ return 0;
+ return __data2host8(pevent, data);
+}
+
+static char *read_string(void)
+{
+ char buf[BUFSIZ];
+ char *str = NULL;
+ int size = 0;
+ off_t r;
+ char c;
+
+ for (;;) {
+ r = read(input_fd, &c, 1);
+ if (r < 0) {
+ pr_debug("reading input file");
+ goto out;
+ }
+
+ if (!r) {
+ pr_debug("no data");
+ goto out;
+ }
+
+ if (repipe) {
+ int retw = write(STDOUT_FILENO, &c, 1);
+
+ if (retw <= 0 || retw != r) {
+ pr_debug("repiping input file string");
+ goto out;
+ }
+ }
+
+ buf[size++] = c;
+
+ if (!c)
+ break;
+ }
+
+ trace_data_size += size;
+
+ str = malloc(size);
+ if (str)
+ memcpy(str, buf, size);
+out:
+ return str;
+}
+
+static int read_proc_kallsyms(struct pevent *pevent)
+{
+ unsigned int size;
+ char *buf;
+
+ size = read4(pevent);
+ if (!size)
+ return 0;
+
+ buf = malloc(size + 1);
+ if (buf == NULL)
+ return -1;
+
+ if (do_read(buf, size) < 0) {
+ free(buf);
+ return -1;
+ }
+ buf[size] = '\0';
+
+ parse_proc_kallsyms(pevent, buf, size);
+
+ free(buf);
+ return 0;
+}
+
+static int read_ftrace_printk(struct pevent *pevent)
+{
+ unsigned int size;
+ char *buf;
+
+ /* it can have 0 size */
+ size = read4(pevent);
+ if (!size)
+ return 0;
+
+ buf = malloc(size);
+ if (buf == NULL)
+ return -1;
+
+ if (do_read(buf, size) < 0) {
+ free(buf);
+ return -1;
+ }
+
+ parse_ftrace_printk(pevent, buf, size);
+
+ free(buf);
+ return 0;
+}
+
+static int read_header_files(struct pevent *pevent)
+{
+ unsigned long long size;
+ char *header_page;
+ char buf[BUFSIZ];
+ int ret = 0;
+
+ if (do_read(buf, 12) < 0)
+ return -1;
+
+ if (memcmp(buf, "header_page", 12) != 0) {
+ pr_debug("did not read header page");
+ return -1;
+ }
+
+ size = read8(pevent);
+
+ header_page = malloc(size);
+ if (header_page == NULL)
+ return -1;
+
+ if (do_read(header_page, size) < 0) {
+ pr_debug("did not read header page");
+ free(header_page);
+ return -1;
+ }
+
+ if (!pevent_parse_header_page(pevent, header_page, size,
+ pevent_get_long_size(pevent))) {
+ /*
+ * The commit field in the page is of type long,
+ * use that instead, since it represents the kernel.
+ */
+ pevent_set_long_size(pevent, pevent->header_page_size_size);
+ }
+ free(header_page);
+
+ if (do_read(buf, 13) < 0)
+ return -1;
+
+ if (memcmp(buf, "header_event", 13) != 0) {
+ pr_debug("did not read header event");
+ return -1;
+ }
+
+ size = read8(pevent);
+ skip(size);
+
+ return ret;
+}
+
+static int read_ftrace_file(struct pevent *pevent, unsigned long long size)
+{
+ char *buf;
+
+ buf = malloc(size);
+ if (buf == NULL)
+ return -1;
+
+ if (do_read(buf, size) < 0) {
+ free(buf);
+ return -1;
+ }
+
+ parse_ftrace_file(pevent, buf, size);
+ free(buf);
+ return 0;
+}
+
+static int read_event_file(struct pevent *pevent, char *sys,
+ unsigned long long size)
+{
+ char *buf;
+
+ buf = malloc(size);
+ if (buf == NULL)
+ return -1;
+
+ if (do_read(buf, size) < 0) {
+ free(buf);
+ return -1;
+ }
+
+ parse_event_file(pevent, buf, size, sys);
+ free(buf);
+ return 0;
+}
+
+static int read_ftrace_files(struct pevent *pevent)
+{
+ unsigned long long size;
+ int count;
+ int i;
+ int ret;
+
+ count = read4(pevent);
+
+ for (i = 0; i < count; i++) {
+ size = read8(pevent);
+ ret = read_ftrace_file(pevent, size);
+ if (ret)
+ return ret;
+ }
+ return 0;
+}
+
+static int read_event_files(struct pevent *pevent)
+{
+ unsigned long long size;
+ char *sys;
+ int systems;
+ int count;
+ int i,x;
+ int ret;
+
+ systems = read4(pevent);
+
+ for (i = 0; i < systems; i++) {
+ sys = read_string();
+ if (sys == NULL)
+ return -1;
+
+ count = read4(pevent);
+
+ for (x=0; x < count; x++) {
+ size = read8(pevent);
+ ret = read_event_file(pevent, sys, size);
+ if (ret)
+ return ret;
+ }
+ }
+ return 0;
+}
+
+ssize_t trace_report(int fd, struct trace_event *tevent, bool __repipe)
+{
+ char buf[BUFSIZ];
+ char test[] = { 23, 8, 68 };
+ char *version;
+ int show_version = 0;
+ int show_funcs = 0;
+ int show_printk = 0;
+ ssize_t size = -1;
+ int file_bigendian;
+ int host_bigendian;
+ int file_long_size;
+ int file_page_size;
+ struct pevent *pevent = NULL;
+ int err;
+
+ repipe = __repipe;
+ input_fd = fd;
+
+ if (do_read(buf, 3) < 0)
+ return -1;
+ if (memcmp(buf, test, 3) != 0) {
+ pr_debug("no trace data in the file");
+ return -1;
+ }
+
+ if (do_read(buf, 7) < 0)
+ return -1;
+ if (memcmp(buf, "tracing", 7) != 0) {
+ pr_debug("not a trace file (missing 'tracing' tag)");
+ return -1;
+ }
+
+ version = read_string();
+ if (version == NULL)
+ return -1;
+ if (show_version)
+ printf("version = %s\n", version);
+ free(version);
+
+ if (do_read(buf, 1) < 0)
+ return -1;
+ file_bigendian = buf[0];
+ host_bigendian = bigendian();
+
+ if (trace_event__init(tevent)) {
+ pr_debug("trace_event__init failed");
+ goto out;
+ }
+
+ pevent = tevent->pevent;
+
+ pevent_set_flag(pevent, PEVENT_NSEC_OUTPUT);
+ pevent_set_file_bigendian(pevent, file_bigendian);
+ pevent_set_host_bigendian(pevent, host_bigendian);
+
+ if (do_read(buf, 1) < 0)
+ goto out;
+ file_long_size = buf[0];
+
+ file_page_size = read4(pevent);
+ if (!file_page_size)
+ goto out;
+
+ pevent_set_long_size(pevent, file_long_size);
+ pevent_set_page_size(pevent, file_page_size);
+
+ err = read_header_files(pevent);
+ if (err)
+ goto out;
+ err = read_ftrace_files(pevent);
+ if (err)
+ goto out;
+ err = read_event_files(pevent);
+ if (err)
+ goto out;
+ err = read_proc_kallsyms(pevent);
+ if (err)
+ goto out;
+ err = read_ftrace_printk(pevent);
+ if (err)
+ goto out;
+
+ size = trace_data_size;
+ repipe = false;
+
+ if (show_funcs) {
+ pevent_print_funcs(pevent);
+ } else if (show_printk) {
+ pevent_print_printk(pevent);
+ }
+
+ pevent = NULL;
+
+out:
+ if (pevent)
+ trace_event__cleanup(tevent);
+ return size;
+}
diff --git a/tools/perf/util/trace-event-scripting.c b/tools/perf/util/trace-event-scripting.c
new file mode 100644
index 00000000000..57aaccc1692
--- /dev/null
+++ b/tools/perf/util/trace-event-scripting.c
@@ -0,0 +1,171 @@
+/*
+ * trace-event-scripting. Scripting engine common and initialization code.
+ *
+ * Copyright (C) 2009-2010 Tom Zanussi <tzanussi@gmail.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include "../perf.h"
+#include "util.h"
+#include "trace-event.h"
+
+struct scripting_context *scripting_context;
+
+static int stop_script_unsupported(void)
+{
+ return 0;
+}
+
+static void process_event_unsupported(union perf_event *event __maybe_unused,
+ struct perf_sample *sample __maybe_unused,
+ struct perf_evsel *evsel __maybe_unused,
+ struct thread *thread __maybe_unused,
+ struct addr_location *al __maybe_unused)
+{
+}
+
+static void print_python_unsupported_msg(void)
+{
+ fprintf(stderr, "Python scripting not supported."
+ " Install libpython and rebuild perf to enable it.\n"
+ "For example:\n # apt-get install python-dev (ubuntu)"
+ "\n # yum install python-devel (Fedora)"
+ "\n etc.\n");
+}
+
+static int python_start_script_unsupported(const char *script __maybe_unused,
+ int argc __maybe_unused,
+ const char **argv __maybe_unused)
+{
+ print_python_unsupported_msg();
+
+ return -1;
+}
+
+static int python_generate_script_unsupported(struct pevent *pevent
+ __maybe_unused,
+ const char *outfile
+ __maybe_unused)
+{
+ print_python_unsupported_msg();
+
+ return -1;
+}
+
+struct scripting_ops python_scripting_unsupported_ops = {
+ .name = "Python",
+ .start_script = python_start_script_unsupported,
+ .stop_script = stop_script_unsupported,
+ .process_event = process_event_unsupported,
+ .generate_script = python_generate_script_unsupported,
+};
+
+static void register_python_scripting(struct scripting_ops *scripting_ops)
+{
+ int err;
+ err = script_spec_register("Python", scripting_ops);
+ if (err)
+ die("error registering Python script extension");
+
+ err = script_spec_register("py", scripting_ops);
+ if (err)
+ die("error registering py script extension");
+
+ scripting_context = malloc(sizeof(struct scripting_context));
+}
+
+#ifdef NO_LIBPYTHON
+void setup_python_scripting(void)
+{
+ register_python_scripting(&python_scripting_unsupported_ops);
+}
+#else
+extern struct scripting_ops python_scripting_ops;
+
+void setup_python_scripting(void)
+{
+ register_python_scripting(&python_scripting_ops);
+}
+#endif
+
+static void print_perl_unsupported_msg(void)
+{
+ fprintf(stderr, "Perl scripting not supported."
+ " Install libperl and rebuild perf to enable it.\n"
+ "For example:\n # apt-get install libperl-dev (ubuntu)"
+ "\n # yum install 'perl(ExtUtils::Embed)' (Fedora)"
+ "\n etc.\n");
+}
+
+static int perl_start_script_unsupported(const char *script __maybe_unused,
+ int argc __maybe_unused,
+ const char **argv __maybe_unused)
+{
+ print_perl_unsupported_msg();
+
+ return -1;
+}
+
+static int perl_generate_script_unsupported(struct pevent *pevent
+ __maybe_unused,
+ const char *outfile __maybe_unused)
+{
+ print_perl_unsupported_msg();
+
+ return -1;
+}
+
+struct scripting_ops perl_scripting_unsupported_ops = {
+ .name = "Perl",
+ .start_script = perl_start_script_unsupported,
+ .stop_script = stop_script_unsupported,
+ .process_event = process_event_unsupported,
+ .generate_script = perl_generate_script_unsupported,
+};
+
+static void register_perl_scripting(struct scripting_ops *scripting_ops)
+{
+ int err;
+ err = script_spec_register("Perl", scripting_ops);
+ if (err)
+ die("error registering Perl script extension");
+
+ err = script_spec_register("pl", scripting_ops);
+ if (err)
+ die("error registering pl script extension");
+
+ scripting_context = malloc(sizeof(struct scripting_context));
+}
+
+#ifdef NO_LIBPERL
+void setup_perl_scripting(void)
+{
+ register_perl_scripting(&perl_scripting_unsupported_ops);
+}
+#else
+extern struct scripting_ops perl_scripting_ops;
+
+void setup_perl_scripting(void)
+{
+ register_perl_scripting(&perl_scripting_ops);
+}
+#endif
diff --git a/tools/perf/util/trace-event.c b/tools/perf/util/trace-event.c
new file mode 100644
index 00000000000..6322d37164c
--- /dev/null
+++ b/tools/perf/util/trace-event.c
@@ -0,0 +1,82 @@
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <linux/kernel.h>
+#include <traceevent/event-parse.h>
+#include "trace-event.h"
+#include "util.h"
+
+/*
+ * global trace_event object used by trace_event__tp_format
+ *
+ * TODO There's no cleanup call for this. Add some sort of
+ * __exit function support and call trace_event__cleanup
+ * there.
+ */
+static struct trace_event tevent;
+
+int trace_event__init(struct trace_event *t)
+{
+ struct pevent *pevent = pevent_alloc();
+
+ if (pevent) {
+ t->plugin_list = traceevent_load_plugins(pevent);
+ t->pevent = pevent;
+ }
+
+ return pevent ? 0 : -1;
+}
+
+void trace_event__cleanup(struct trace_event *t)
+{
+ traceevent_unload_plugins(t->plugin_list, t->pevent);
+ pevent_free(t->pevent);
+}
+
+static struct event_format*
+tp_format(const char *sys, const char *name)
+{
+ struct pevent *pevent = tevent.pevent;
+ struct event_format *event = NULL;
+ char path[PATH_MAX];
+ size_t size;
+ char *data;
+
+ scnprintf(path, PATH_MAX, "%s/%s/%s/format",
+ tracing_events_path, sys, name);
+
+ if (filename__read_str(path, &data, &size))
+ return NULL;
+
+ pevent_parse_format(pevent, &event, data, size, sys);
+
+ free(data);
+ return event;
+}
+
+struct event_format*
+trace_event__tp_format(const char *sys, const char *name)
+{
+ static bool initialized;
+
+ if (!initialized) {
+ int be = traceevent_host_bigendian();
+ struct pevent *pevent;
+
+ if (trace_event__init(&tevent))
+ return NULL;
+
+ pevent = tevent.pevent;
+ pevent_set_flag(pevent, PEVENT_NSEC_OUTPUT);
+ pevent_set_file_bigendian(pevent, be);
+ pevent_set_host_bigendian(pevent, be);
+ initialized = true;
+ }
+
+ return tp_format(sys, name);
+}
diff --git a/tools/perf/util/trace-event.h b/tools/perf/util/trace-event.h
new file mode 100644
index 00000000000..7b6d6868832
--- /dev/null
+++ b/tools/perf/util/trace-event.h
@@ -0,0 +1,90 @@
+#ifndef _PERF_UTIL_TRACE_EVENT_H
+#define _PERF_UTIL_TRACE_EVENT_H
+
+#include <traceevent/event-parse.h>
+#include "parse-events.h"
+
+struct machine;
+struct perf_sample;
+union perf_event;
+struct perf_tool;
+struct thread;
+struct plugin_list;
+
+struct trace_event {
+ struct pevent *pevent;
+ struct plugin_list *plugin_list;
+};
+
+int trace_event__init(struct trace_event *t);
+void trace_event__cleanup(struct trace_event *t);
+struct event_format*
+trace_event__tp_format(const char *sys, const char *name);
+
+int bigendian(void);
+
+void event_format__print(struct event_format *event,
+ int cpu, void *data, int size);
+
+int parse_ftrace_file(struct pevent *pevent, char *buf, unsigned long size);
+int parse_event_file(struct pevent *pevent,
+ char *buf, unsigned long size, char *sys);
+
+unsigned long long
+raw_field_value(struct event_format *event, const char *name, void *data);
+
+void parse_proc_kallsyms(struct pevent *pevent, char *file, unsigned int size);
+void parse_ftrace_printk(struct pevent *pevent, char *file, unsigned int size);
+
+ssize_t trace_report(int fd, struct trace_event *tevent, bool repipe);
+
+struct event_format *trace_find_next_event(struct pevent *pevent,
+ struct event_format *event);
+unsigned long long read_size(struct event_format *event, void *ptr, int size);
+unsigned long long eval_flag(const char *flag);
+
+int read_tracing_data(int fd, struct list_head *pattrs);
+
+struct tracing_data {
+ /* size is only valid if temp is 'true' */
+ ssize_t size;
+ bool temp;
+ char temp_file[50];
+};
+
+struct tracing_data *tracing_data_get(struct list_head *pattrs,
+ int fd, bool temp);
+int tracing_data_put(struct tracing_data *tdata);
+
+
+struct addr_location;
+
+struct perf_session;
+
+struct scripting_ops {
+ const char *name;
+ int (*start_script) (const char *script, int argc, const char **argv);
+ int (*stop_script) (void);
+ void (*process_event) (union perf_event *event,
+ struct perf_sample *sample,
+ struct perf_evsel *evsel,
+ struct thread *thread,
+ struct addr_location *al);
+ int (*generate_script) (struct pevent *pevent, const char *outfile);
+};
+
+int script_spec_register(const char *spec, struct scripting_ops *ops);
+
+void setup_perl_scripting(void);
+void setup_python_scripting(void);
+
+struct scripting_context {
+ struct pevent *pevent;
+ void *event_data;
+};
+
+int common_pc(struct scripting_context *context);
+int common_flags(struct scripting_context *context);
+int common_lock_depth(struct scripting_context *context);
+
+#endif /* _PERF_UTIL_TRACE_EVENT_H */
diff --git a/tools/perf/util/unwind-libdw.c b/tools/perf/util/unwind-libdw.c
new file mode 100644
index 00000000000..5ec80a575b5
--- /dev/null
+++ b/tools/perf/util/unwind-libdw.c
@@ -0,0 +1,210 @@
+#include <linux/compiler.h>
+#include <elfutils/libdw.h>
+#include <elfutils/libdwfl.h>
+#include <inttypes.h>
+#include <errno.h>
+#include "unwind.h"
+#include "unwind-libdw.h"
+#include "machine.h"
+#include "thread.h"
+#include <linux/types.h>
+#include "event.h"
+#include "perf_regs.h"
+
+static char *debuginfo_path;
+
+static const Dwfl_Callbacks offline_callbacks = {
+ .find_debuginfo = dwfl_standard_find_debuginfo,
+ .debuginfo_path = &debuginfo_path,
+ .section_address = dwfl_offline_section_address,
+};
+
+static int __report_module(struct addr_location *al, u64 ip,
+ struct unwind_info *ui)
+{
+ Dwfl_Module *mod;
+ struct dso *dso = NULL;
+
+ thread__find_addr_location(ui->thread, ui->machine,
+ PERF_RECORD_MISC_USER,
+ MAP__FUNCTION, ip, al);
+
+ if (al->map)
+ dso = al->map->dso;
+
+ if (!dso)
+ return 0;
+
+ mod = dwfl_addrmodule(ui->dwfl, ip);
+ if (!mod)
+ mod = dwfl_report_elf(ui->dwfl, dso->short_name,
+ dso->long_name, -1, al->map->start,
+ false);
+
+ return mod && dwfl_addrmodule(ui->dwfl, ip) == mod ? 0 : -1;
+}
+
+static int report_module(u64 ip, struct unwind_info *ui)
+{
+ struct addr_location al;
+
+ return __report_module(&al, ip, ui);
+}
+
+static int entry(u64 ip, struct unwind_info *ui)
+
+{
+ struct unwind_entry e;
+ struct addr_location al;
+
+ if (__report_module(&al, ip, ui))
+ return -1;
+
+ e.ip = ip;
+ e.map = al.map;
+ e.sym = al.sym;
+
+ pr_debug("unwind: %s:ip = 0x%" PRIx64 " (0x%" PRIx64 ")\n",
+ al.sym ? al.sym->name : "''",
+ ip,
+ al.map ? al.map->map_ip(al.map, ip) : (u64) 0);
+
+ return ui->cb(&e, ui->arg);
+}
+
+static pid_t next_thread(Dwfl *dwfl, void *arg, void **thread_argp)
+{
+ /* We want only single thread to be processed. */
+ if (*thread_argp != NULL)
+ return 0;
+
+ *thread_argp = arg;
+ return dwfl_pid(dwfl);
+}
+
+static int access_dso_mem(struct unwind_info *ui, Dwarf_Addr addr,
+ Dwarf_Word *data)
+{
+ struct addr_location al;
+ ssize_t size;
+
+ thread__find_addr_map(ui->thread, ui->machine, PERF_RECORD_MISC_USER,
+ MAP__FUNCTION, addr, &al);
+ if (!al.map) {
+ pr_debug("unwind: no map for %lx\n", (unsigned long)addr);
+ return -1;
+ }
+
+ if (!al.map->dso)
+ return -1;
+
+ size = dso__data_read_addr(al.map->dso, al.map, ui->machine,
+ addr, (u8 *) data, sizeof(*data));
+
+ return !(size == sizeof(*data));
+}
+
+static bool memory_read(Dwfl *dwfl __maybe_unused, Dwarf_Addr addr, Dwarf_Word *result,
+ void *arg)
+{
+ struct unwind_info *ui = arg;
+ struct stack_dump *stack = &ui->sample->user_stack;
+ u64 start, end;
+ int offset;
+ int ret;
+
+ ret = perf_reg_value(&start, &ui->sample->user_regs, PERF_REG_SP);
+ if (ret)
+ return false;
+
+ end = start + stack->size;
+
+ /* Check overflow. */
+ if (addr + sizeof(Dwarf_Word) < addr)
+ return false;
+
+ if (addr < start || addr + sizeof(Dwarf_Word) > end) {
+ ret = access_dso_mem(ui, addr, result);
+ if (ret) {
+ pr_debug("unwind: access_mem 0x%" PRIx64 " not inside range"
+ " 0x%" PRIx64 "-0x%" PRIx64 "\n",
+ addr, start, end);
+ return false;
+ }
+ return true;
+ }
+
+ offset = addr - start;
+ *result = *(Dwarf_Word *)&stack->data[offset];
+ pr_debug("unwind: access_mem addr 0x%" PRIx64 ", val %lx, offset %d\n",
+ addr, (unsigned long)*result, offset);
+ return true;
+}
+
+static const Dwfl_Thread_Callbacks callbacks = {
+ .next_thread = next_thread,
+ .memory_read = memory_read,
+ .set_initial_registers = libdw__arch_set_initial_registers,
+};
+
+static int
+frame_callback(Dwfl_Frame *state, void *arg)
+{
+ struct unwind_info *ui = arg;
+ Dwarf_Addr pc;
+
+ if (!dwfl_frame_pc(state, &pc, NULL)) {
+ pr_err("%s", dwfl_errmsg(-1));
+ return DWARF_CB_ABORT;
+ }
+
+ return entry(pc, ui) || !(--ui->max_stack) ?
+ DWARF_CB_ABORT : DWARF_CB_OK;
+}
+
+int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
+ struct machine *machine, struct thread *thread,
+ struct perf_sample *data,
+ int max_stack)
+{
+ struct unwind_info ui = {
+ .sample = data,
+ .thread = thread,
+ .machine = machine,
+ .cb = cb,
+ .arg = arg,
+ .max_stack = max_stack,
+ };
+ Dwarf_Word ip;
+ int err = -EINVAL;
+
+ if (!data->user_regs.regs)
+ return -EINVAL;
+
+ ui.dwfl = dwfl_begin(&offline_callbacks);
+ if (!ui.dwfl)
+ goto out;
+
+ err = perf_reg_value(&ip, &data->user_regs, PERF_REG_IP);
+ if (err)
+ goto out;
+
+ err = report_module(ip, &ui);
+ if (err)
+ goto out;
+
+ if (!dwfl_attach_state(ui.dwfl, EM_NONE, thread->tid, &callbacks, &ui))
+ goto out;
+
+ err = dwfl_getthread_frames(ui.dwfl, thread->tid, frame_callback, &ui);
+
+ if (err && !ui.max_stack)
+ err = 0;
+
+ out:
+ if (err)
+ pr_debug("unwind: failed with '%s'\n", dwfl_errmsg(-1));
+
+ dwfl_end(ui.dwfl);
+ return 0;
+}
diff --git a/tools/perf/util/unwind-libdw.h b/tools/perf/util/unwind-libdw.h
new file mode 100644
index 00000000000..417a1426f3a
--- /dev/null
+++ b/tools/perf/util/unwind-libdw.h
@@ -0,0 +1,21 @@
+#ifndef __PERF_UNWIND_LIBDW_H
+#define __PERF_UNWIND_LIBDW_H
+
+#include <elfutils/libdwfl.h>
+#include "event.h"
+#include "thread.h"
+#include "unwind.h"
+
+bool libdw__arch_set_initial_registers(Dwfl_Thread *thread, void *arg);
+
+struct unwind_info {
+ Dwfl *dwfl;
+ struct perf_sample *sample;
+ struct machine *machine;
+ struct thread *thread;
+ unwind_entry_cb_t cb;
+ void *arg;
+ int max_stack;
+};
+
+#endif /* __PERF_UNWIND_LIBDW_H */
diff --git a/tools/perf/util/unwind-libunwind.c b/tools/perf/util/unwind-libunwind.c
new file mode 100644
index 00000000000..25578b98f5c
--- /dev/null
+++ b/tools/perf/util/unwind-libunwind.c
@@ -0,0 +1,579 @@
+/*
+ * Post mortem Dwarf CFI based unwinding on top of regs and stack dumps.
+ *
+ * Lots of this code have been borrowed or heavily inspired from parts of
+ * the libunwind 0.99 code which are (amongst other contributors I may have
+ * forgotten):
+ *
+ * Copyright (C) 2002-2007 Hewlett-Packard Co
+ * Contributed by David Mosberger-Tang <davidm@hpl.hp.com>
+ *
+ * And the bugs have been added by:
+ *
+ * Copyright (C) 2010, Frederic Weisbecker <fweisbec@gmail.com>
+ * Copyright (C) 2012, Jiri Olsa <jolsa@redhat.com>
+ *
+ */
+
+#include <elf.h>
+#include <gelf.h>
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <linux/list.h>
+#include <libunwind.h>
+#include <libunwind-ptrace.h>
+#include "thread.h"
+#include "session.h"
+#include "perf_regs.h"
+#include "unwind.h"
+#include "symbol.h"
+#include "util.h"
+
+extern int
+UNW_OBJ(dwarf_search_unwind_table) (unw_addr_space_t as,
+ unw_word_t ip,
+ unw_dyn_info_t *di,
+ unw_proc_info_t *pi,
+ int need_unwind_info, void *arg);
+
+#define dwarf_search_unwind_table UNW_OBJ(dwarf_search_unwind_table)
+
+extern int
+UNW_OBJ(dwarf_find_debug_frame) (int found, unw_dyn_info_t *di_debug,
+ unw_word_t ip,
+ unw_word_t segbase,
+ const char *obj_name, unw_word_t start,
+ unw_word_t end);
+
+#define dwarf_find_debug_frame UNW_OBJ(dwarf_find_debug_frame)
+
+#define DW_EH_PE_FORMAT_MASK 0x0f /* format of the encoded value */
+#define DW_EH_PE_APPL_MASK 0x70 /* how the value is to be applied */
+
+/* Pointer-encoding formats: */
+#define DW_EH_PE_omit 0xff
+#define DW_EH_PE_ptr 0x00 /* pointer-sized unsigned value */
+#define DW_EH_PE_udata4 0x03 /* unsigned 32-bit value */
+#define DW_EH_PE_udata8 0x04 /* unsigned 64-bit value */
+#define DW_EH_PE_sdata4 0x0b /* signed 32-bit value */
+#define DW_EH_PE_sdata8 0x0c /* signed 64-bit value */
+
+/* Pointer-encoding application: */
+#define DW_EH_PE_absptr 0x00 /* absolute value */
+#define DW_EH_PE_pcrel 0x10 /* rel. to addr. of encoded value */
+
+/*
+ * The following are not documented by LSB v1.3, yet they are used by
+ * GCC, presumably they aren't documented by LSB since they aren't
+ * used on Linux:
+ */
+#define DW_EH_PE_funcrel 0x40 /* start-of-procedure-relative */
+#define DW_EH_PE_aligned 0x50 /* aligned pointer */
+
+/* Flags intentionaly not handled, since they're not needed:
+ * #define DW_EH_PE_indirect 0x80
+ * #define DW_EH_PE_uleb128 0x01
+ * #define DW_EH_PE_udata2 0x02
+ * #define DW_EH_PE_sleb128 0x09
+ * #define DW_EH_PE_sdata2 0x0a
+ * #define DW_EH_PE_textrel 0x20
+ * #define DW_EH_PE_datarel 0x30
+ */
+
+struct unwind_info {
+ struct perf_sample *sample;
+ struct machine *machine;
+ struct thread *thread;
+};
+
+#define dw_read(ptr, type, end) ({ \
+ type *__p = (type *) ptr; \
+ type __v; \
+ if ((__p + 1) > (type *) end) \
+ return -EINVAL; \
+ __v = *__p++; \
+ ptr = (typeof(ptr)) __p; \
+ __v; \
+ })
+
+static int __dw_read_encoded_value(u8 **p, u8 *end, u64 *val,
+ u8 encoding)
+{
+ u8 *cur = *p;
+ *val = 0;
+
+ switch (encoding) {
+ case DW_EH_PE_omit:
+ *val = 0;
+ goto out;
+ case DW_EH_PE_ptr:
+ *val = dw_read(cur, unsigned long, end);
+ goto out;
+ default:
+ break;
+ }
+
+ switch (encoding & DW_EH_PE_APPL_MASK) {
+ case DW_EH_PE_absptr:
+ break;
+ case DW_EH_PE_pcrel:
+ *val = (unsigned long) cur;
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ if ((encoding & 0x07) == 0x00)
+ encoding |= DW_EH_PE_udata4;
+
+ switch (encoding & DW_EH_PE_FORMAT_MASK) {
+ case DW_EH_PE_sdata4:
+ *val += dw_read(cur, s32, end);
+ break;
+ case DW_EH_PE_udata4:
+ *val += dw_read(cur, u32, end);
+ break;
+ case DW_EH_PE_sdata8:
+ *val += dw_read(cur, s64, end);
+ break;
+ case DW_EH_PE_udata8:
+ *val += dw_read(cur, u64, end);
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ out:
+ *p = cur;
+ return 0;
+}
+
+#define dw_read_encoded_value(ptr, end, enc) ({ \
+ u64 __v; \
+ if (__dw_read_encoded_value(&ptr, end, &__v, enc)) { \
+ return -EINVAL; \
+ } \
+ __v; \
+ })
+
+static u64 elf_section_offset(int fd, const char *name)
+{
+ Elf *elf;
+ GElf_Ehdr ehdr;
+ GElf_Shdr shdr;
+ u64 offset = 0;
+
+ elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL);
+ if (elf == NULL)
+ return 0;
+
+ do {
+ if (gelf_getehdr(elf, &ehdr) == NULL)
+ break;
+
+ if (!elf_section_by_name(elf, &ehdr, &shdr, name, NULL))
+ break;
+
+ offset = shdr.sh_offset;
+ } while (0);
+
+ elf_end(elf);
+ return offset;
+}
+
+struct table_entry {
+ u32 start_ip_offset;
+ u32 fde_offset;
+};
+
+struct eh_frame_hdr {
+ unsigned char version;
+ unsigned char eh_frame_ptr_enc;
+ unsigned char fde_count_enc;
+ unsigned char table_enc;
+
+ /*
+ * The rest of the header is variable-length and consists of the
+ * following members:
+ *
+ * encoded_t eh_frame_ptr;
+ * encoded_t fde_count;
+ */
+
+ /* A single encoded pointer should not be more than 8 bytes. */
+ u64 enc[2];
+
+ /*
+ * struct {
+ * encoded_t start_ip;
+ * encoded_t fde_addr;
+ * } binary_search_table[fde_count];
+ */
+ char data[0];
+} __packed;
+
+static int unwind_spec_ehframe(struct dso *dso, struct machine *machine,
+ u64 offset, u64 *table_data, u64 *segbase,
+ u64 *fde_count)
+{
+ struct eh_frame_hdr hdr;
+ u8 *enc = (u8 *) &hdr.enc;
+ u8 *end = (u8 *) &hdr.data;
+ ssize_t r;
+
+ r = dso__data_read_offset(dso, machine, offset,
+ (u8 *) &hdr, sizeof(hdr));
+ if (r != sizeof(hdr))
+ return -EINVAL;
+
+ /* We dont need eh_frame_ptr, just skip it. */
+ dw_read_encoded_value(enc, end, hdr.eh_frame_ptr_enc);
+
+ *fde_count = dw_read_encoded_value(enc, end, hdr.fde_count_enc);
+ *segbase = offset;
+ *table_data = (enc - (u8 *) &hdr) + offset;
+ return 0;
+}
+
+static int read_unwind_spec_eh_frame(struct dso *dso, struct machine *machine,
+ u64 *table_data, u64 *segbase,
+ u64 *fde_count)
+{
+ int ret = -EINVAL, fd;
+ u64 offset;
+
+ fd = dso__data_fd(dso, machine);
+ if (fd < 0)
+ return -EINVAL;
+
+ /* Check the .eh_frame section for unwinding info */
+ offset = elf_section_offset(fd, ".eh_frame_hdr");
+
+ if (offset)
+ ret = unwind_spec_ehframe(dso, machine, offset,
+ table_data, segbase,
+ fde_count);
+
+ return ret;
+}
+
+#ifndef NO_LIBUNWIND_DEBUG_FRAME
+static int read_unwind_spec_debug_frame(struct dso *dso,
+ struct machine *machine, u64 *offset)
+{
+ int fd = dso__data_fd(dso, machine);
+
+ if (fd < 0)
+ return -EINVAL;
+
+ /* Check the .debug_frame section for unwinding info */
+ *offset = elf_section_offset(fd, ".debug_frame");
+
+ if (*offset)
+ return 0;
+
+ return -EINVAL;
+}
+#endif
+
+static struct map *find_map(unw_word_t ip, struct unwind_info *ui)
+{
+ struct addr_location al;
+
+ thread__find_addr_map(ui->thread, ui->machine, PERF_RECORD_MISC_USER,
+ MAP__FUNCTION, ip, &al);
+ return al.map;
+}
+
+static int
+find_proc_info(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi,
+ int need_unwind_info, void *arg)
+{
+ struct unwind_info *ui = arg;
+ struct map *map;
+ unw_dyn_info_t di;
+ u64 table_data, segbase, fde_count;
+
+ map = find_map(ip, ui);
+ if (!map || !map->dso)
+ return -EINVAL;
+
+ pr_debug("unwind: find_proc_info dso %s\n", map->dso->name);
+
+ /* Check the .eh_frame section for unwinding info */
+ if (!read_unwind_spec_eh_frame(map->dso, ui->machine,
+ &table_data, &segbase, &fde_count)) {
+ memset(&di, 0, sizeof(di));
+ di.format = UNW_INFO_FORMAT_REMOTE_TABLE;
+ di.start_ip = map->start;
+ di.end_ip = map->end;
+ di.u.rti.segbase = map->start + segbase;
+ di.u.rti.table_data = map->start + table_data;
+ di.u.rti.table_len = fde_count * sizeof(struct table_entry)
+ / sizeof(unw_word_t);
+ return dwarf_search_unwind_table(as, ip, &di, pi,
+ need_unwind_info, arg);
+ }
+
+#ifndef NO_LIBUNWIND_DEBUG_FRAME
+ /* Check the .debug_frame section for unwinding info */
+ if (!read_unwind_spec_debug_frame(map->dso, ui->machine, &segbase)) {
+ memset(&di, 0, sizeof(di));
+ if (dwarf_find_debug_frame(0, &di, ip, 0, map->dso->name,
+ map->start, map->end))
+ return dwarf_search_unwind_table(as, ip, &di, pi,
+ need_unwind_info, arg);
+ }
+#endif
+
+ return -EINVAL;
+}
+
+static int access_fpreg(unw_addr_space_t __maybe_unused as,
+ unw_regnum_t __maybe_unused num,
+ unw_fpreg_t __maybe_unused *val,
+ int __maybe_unused __write,
+ void __maybe_unused *arg)
+{
+ pr_err("unwind: access_fpreg unsupported\n");
+ return -UNW_EINVAL;
+}
+
+static int get_dyn_info_list_addr(unw_addr_space_t __maybe_unused as,
+ unw_word_t __maybe_unused *dil_addr,
+ void __maybe_unused *arg)
+{
+ return -UNW_ENOINFO;
+}
+
+static int resume(unw_addr_space_t __maybe_unused as,
+ unw_cursor_t __maybe_unused *cu,
+ void __maybe_unused *arg)
+{
+ pr_err("unwind: resume unsupported\n");
+ return -UNW_EINVAL;
+}
+
+static int
+get_proc_name(unw_addr_space_t __maybe_unused as,
+ unw_word_t __maybe_unused addr,
+ char __maybe_unused *bufp, size_t __maybe_unused buf_len,
+ unw_word_t __maybe_unused *offp, void __maybe_unused *arg)
+{
+ pr_err("unwind: get_proc_name unsupported\n");
+ return -UNW_EINVAL;
+}
+
+static int access_dso_mem(struct unwind_info *ui, unw_word_t addr,
+ unw_word_t *data)
+{
+ struct addr_location al;
+ ssize_t size;
+
+ thread__find_addr_map(ui->thread, ui->machine, PERF_RECORD_MISC_USER,
+ MAP__FUNCTION, addr, &al);
+ if (!al.map) {
+ pr_debug("unwind: no map for %lx\n", (unsigned long)addr);
+ return -1;
+ }
+
+ if (!al.map->dso)
+ return -1;
+
+ size = dso__data_read_addr(al.map->dso, al.map, ui->machine,
+ addr, (u8 *) data, sizeof(*data));
+
+ return !(size == sizeof(*data));
+}
+
+static int access_mem(unw_addr_space_t __maybe_unused as,
+ unw_word_t addr, unw_word_t *valp,
+ int __write, void *arg)
+{
+ struct unwind_info *ui = arg;
+ struct stack_dump *stack = &ui->sample->user_stack;
+ u64 start, end;
+ int offset;
+ int ret;
+
+ /* Don't support write, probably not needed. */
+ if (__write || !stack || !ui->sample->user_regs.regs) {
+ *valp = 0;
+ return 0;
+ }
+
+ ret = perf_reg_value(&start, &ui->sample->user_regs, PERF_REG_SP);
+ if (ret)
+ return ret;
+
+ end = start + stack->size;
+
+ /* Check overflow. */
+ if (addr + sizeof(unw_word_t) < addr)
+ return -EINVAL;
+
+ if (addr < start || addr + sizeof(unw_word_t) >= end) {
+ ret = access_dso_mem(ui, addr, valp);
+ if (ret) {
+ pr_debug("unwind: access_mem %p not inside range"
+ " 0x%" PRIx64 "-0x%" PRIx64 "\n",
+ (void *) addr, start, end);
+ *valp = 0;
+ return ret;
+ }
+ return 0;
+ }
+
+ offset = addr - start;
+ *valp = *(unw_word_t *)&stack->data[offset];
+ pr_debug("unwind: access_mem addr %p val %lx, offset %d\n",
+ (void *) addr, (unsigned long)*valp, offset);
+ return 0;
+}
+
+static int access_reg(unw_addr_space_t __maybe_unused as,
+ unw_regnum_t regnum, unw_word_t *valp,
+ int __write, void *arg)
+{
+ struct unwind_info *ui = arg;
+ int id, ret;
+ u64 val;
+
+ /* Don't support write, I suspect we don't need it. */
+ if (__write) {
+ pr_err("unwind: access_reg w %d\n", regnum);
+ return 0;
+ }
+
+ if (!ui->sample->user_regs.regs) {
+ *valp = 0;
+ return 0;
+ }
+
+ id = libunwind__arch_reg_id(regnum);
+ if (id < 0)
+ return -EINVAL;
+
+ ret = perf_reg_value(&val, &ui->sample->user_regs, id);
+ if (ret) {
+ pr_err("unwind: can't read reg %d\n", regnum);
+ return ret;
+ }
+
+ *valp = (unw_word_t) val;
+ pr_debug("unwind: reg %d, val %lx\n", regnum, (unsigned long)*valp);
+ return 0;
+}
+
+static void put_unwind_info(unw_addr_space_t __maybe_unused as,
+ unw_proc_info_t *pi __maybe_unused,
+ void *arg __maybe_unused)
+{
+ pr_debug("unwind: put_unwind_info called\n");
+}
+
+static int entry(u64 ip, struct thread *thread, struct machine *machine,
+ unwind_entry_cb_t cb, void *arg)
+{
+ struct unwind_entry e;
+ struct addr_location al;
+
+ thread__find_addr_location(thread, machine,
+ PERF_RECORD_MISC_USER,
+ MAP__FUNCTION, ip, &al);
+
+ e.ip = ip;
+ e.map = al.map;
+ e.sym = al.sym;
+
+ pr_debug("unwind: %s:ip = 0x%" PRIx64 " (0x%" PRIx64 ")\n",
+ al.sym ? al.sym->name : "''",
+ ip,
+ al.map ? al.map->map_ip(al.map, ip) : (u64) 0);
+
+ return cb(&e, arg);
+}
+
+static void display_error(int err)
+{
+ switch (err) {
+ case UNW_EINVAL:
+ pr_err("unwind: Only supports local.\n");
+ break;
+ case UNW_EUNSPEC:
+ pr_err("unwind: Unspecified error.\n");
+ break;
+ case UNW_EBADREG:
+ pr_err("unwind: Register unavailable.\n");
+ break;
+ default:
+ break;
+ }
+}
+
+static unw_accessors_t accessors = {
+ .find_proc_info = find_proc_info,
+ .put_unwind_info = put_unwind_info,
+ .get_dyn_info_list_addr = get_dyn_info_list_addr,
+ .access_mem = access_mem,
+ .access_reg = access_reg,
+ .access_fpreg = access_fpreg,
+ .resume = resume,
+ .get_proc_name = get_proc_name,
+};
+
+static int get_entries(struct unwind_info *ui, unwind_entry_cb_t cb,
+ void *arg, int max_stack)
+{
+ unw_addr_space_t addr_space;
+ unw_cursor_t c;
+ int ret;
+
+ addr_space = unw_create_addr_space(&accessors, 0);
+ if (!addr_space) {
+ pr_err("unwind: Can't create unwind address space.\n");
+ return -ENOMEM;
+ }
+
+ ret = unw_init_remote(&c, addr_space, ui);
+ if (ret)
+ display_error(ret);
+
+ while (!ret && (unw_step(&c) > 0) && max_stack--) {
+ unw_word_t ip;
+
+ unw_get_reg(&c, UNW_REG_IP, &ip);
+ ret = ip ? entry(ip, ui->thread, ui->machine, cb, arg) : 0;
+ }
+
+ unw_destroy_addr_space(addr_space);
+ return ret;
+}
+
+int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
+ struct machine *machine, struct thread *thread,
+ struct perf_sample *data, int max_stack)
+{
+ u64 ip;
+ struct unwind_info ui = {
+ .sample = data,
+ .thread = thread,
+ .machine = machine,
+ };
+ int ret;
+
+ if (!data->user_regs.regs)
+ return -EINVAL;
+
+ ret = perf_reg_value(&ip, &data->user_regs, PERF_REG_IP);
+ if (ret)
+ return ret;
+
+ ret = entry(ip, thread, machine, cb, arg);
+ if (ret)
+ return -ENOMEM;
+
+ return --max_stack > 0 ? get_entries(&ui, cb, arg, max_stack) : 0;
+}
diff --git a/tools/perf/util/unwind.h b/tools/perf/util/unwind.h
new file mode 100644
index 00000000000..f03061260b4
--- /dev/null
+++ b/tools/perf/util/unwind.h
@@ -0,0 +1,37 @@
+#ifndef __UNWIND_H
+#define __UNWIND_H
+
+#include <linux/types.h>
+#include "event.h"
+#include "symbol.h"
+
+struct unwind_entry {
+ struct map *map;
+ struct symbol *sym;
+ u64 ip;
+};
+
+typedef int (*unwind_entry_cb_t)(struct unwind_entry *entry, void *arg);
+
+#ifdef HAVE_DWARF_UNWIND_SUPPORT
+int unwind__get_entries(unwind_entry_cb_t cb, void *arg,
+ struct machine *machine,
+ struct thread *thread,
+ struct perf_sample *data, int max_stack);
+/* libunwind specific */
+#ifdef HAVE_LIBUNWIND_SUPPORT
+int libunwind__arch_reg_id(int regnum);
+#endif
+#else
+static inline int
+unwind__get_entries(unwind_entry_cb_t cb __maybe_unused,
+ void *arg __maybe_unused,
+ struct machine *machine __maybe_unused,
+ struct thread *thread __maybe_unused,
+ struct perf_sample *data __maybe_unused,
+ int max_stack __maybe_unused)
+{
+ return 0;
+}
+#endif /* HAVE_DWARF_UNWIND_SUPPORT */
+#endif /* __UNWIND_H */
diff --git a/tools/perf/util/usage.c b/tools/perf/util/usage.c
new file mode 100644
index 00000000000..4007aca8e0c
--- /dev/null
+++ b/tools/perf/util/usage.c
@@ -0,0 +1,84 @@
+/*
+ * usage.c
+ *
+ * Various reporting routines.
+ * Originally copied from GIT source.
+ *
+ * Copyright (C) Linus Torvalds, 2005
+ */
+#include "util.h"
+#include "debug.h"
+
+static void report(const char *prefix, const char *err, va_list params)
+{
+ char msg[1024];
+ vsnprintf(msg, sizeof(msg), err, params);
+ fprintf(stderr, " %s%s\n", prefix, msg);
+}
+
+static NORETURN void usage_builtin(const char *err)
+{
+ fprintf(stderr, "\n Usage: %s\n", err);
+ exit(129);
+}
+
+static NORETURN void die_builtin(const char *err, va_list params)
+{
+ report(" Fatal: ", err, params);
+ exit(128);
+}
+
+static void error_builtin(const char *err, va_list params)
+{
+ report(" Error: ", err, params);
+}
+
+static void warn_builtin(const char *warn, va_list params)
+{
+ report(" Warning: ", warn, params);
+}
+
+/* If we are in a dlopen()ed .so write to a global variable would segfault
+ * (ugh), so keep things static. */
+static void (*usage_routine)(const char *err) NORETURN = usage_builtin;
+static void (*die_routine)(const char *err, va_list params) NORETURN = die_builtin;
+static void (*error_routine)(const char *err, va_list params) = error_builtin;
+static void (*warn_routine)(const char *err, va_list params) = warn_builtin;
+
+void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN)
+{
+ die_routine = routine;
+}
+
+void usage(const char *err)
+{
+ usage_routine(err);
+}
+
+void die(const char *err, ...)
+{
+ va_list params;
+
+ va_start(params, err);
+ die_routine(err, params);
+ va_end(params);
+}
+
+int error(const char *err, ...)
+{
+ va_list params;
+
+ va_start(params, err);
+ error_routine(err, params);
+ va_end(params);
+ return -1;
+}
+
+void warning(const char *warn, ...)
+{
+ va_list params;
+
+ va_start(params, warn);
+ warn_routine(warn, params);
+ va_end(params);
+}
diff --git a/tools/perf/util/util.c b/tools/perf/util/util.c
new file mode 100644
index 00000000000..95aefa78bb0
--- /dev/null
+++ b/tools/perf/util/util.c
@@ -0,0 +1,542 @@
+#include "../perf.h"
+#include "util.h"
+#include <api/fs/fs.h>
+#include <sys/mman.h>
+#ifdef HAVE_BACKTRACE_SUPPORT
+#include <execinfo.h>
+#endif
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <byteswap.h>
+#include <linux/kernel.h>
+
+/*
+ * XXX We need to find a better place for these things...
+ */
+unsigned int page_size;
+int cacheline_size;
+
+bool test_attr__enabled;
+
+bool perf_host = true;
+bool perf_guest = false;
+
+char tracing_events_path[PATH_MAX + 1] = "/sys/kernel/debug/tracing/events";
+
+void event_attr_init(struct perf_event_attr *attr)
+{
+ if (!perf_host)
+ attr->exclude_host = 1;
+ if (!perf_guest)
+ attr->exclude_guest = 1;
+ /* to capture ABI version */
+ attr->size = sizeof(*attr);
+}
+
+int mkdir_p(char *path, mode_t mode)
+{
+ struct stat st;
+ int err;
+ char *d = path;
+
+ if (*d != '/')
+ return -1;
+
+ if (stat(path, &st) == 0)
+ return 0;
+
+ while (*++d == '/');
+
+ while ((d = strchr(d, '/'))) {
+ *d = '\0';
+ err = stat(path, &st) && mkdir(path, mode);
+ *d++ = '/';
+ if (err)
+ return -1;
+ while (*d == '/')
+ ++d;
+ }
+ return (stat(path, &st) && mkdir(path, mode)) ? -1 : 0;
+}
+
+static int slow_copyfile(const char *from, const char *to, mode_t mode)
+{
+ int err = -1;
+ char *line = NULL;
+ size_t n;
+ FILE *from_fp = fopen(from, "r"), *to_fp;
+ mode_t old_umask;
+
+ if (from_fp == NULL)
+ goto out;
+
+ old_umask = umask(mode ^ 0777);
+ to_fp = fopen(to, "w");
+ umask(old_umask);
+ if (to_fp == NULL)
+ goto out_fclose_from;
+
+ while (getline(&line, &n, from_fp) > 0)
+ if (fputs(line, to_fp) == EOF)
+ goto out_fclose_to;
+ err = 0;
+out_fclose_to:
+ fclose(to_fp);
+ free(line);
+out_fclose_from:
+ fclose(from_fp);
+out:
+ return err;
+}
+
+int copyfile_mode(const char *from, const char *to, mode_t mode)
+{
+ int fromfd, tofd;
+ struct stat st;
+ void *addr;
+ int err = -1;
+
+ if (stat(from, &st))
+ goto out;
+
+ if (st.st_size == 0) /* /proc? do it slowly... */
+ return slow_copyfile(from, to, mode);
+
+ fromfd = open(from, O_RDONLY);
+ if (fromfd < 0)
+ goto out;
+
+ tofd = creat(to, mode);
+ if (tofd < 0)
+ goto out_close_from;
+
+ addr = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fromfd, 0);
+ if (addr == MAP_FAILED)
+ goto out_close_to;
+
+ if (write(tofd, addr, st.st_size) == st.st_size)
+ err = 0;
+
+ munmap(addr, st.st_size);
+out_close_to:
+ close(tofd);
+ if (err)
+ unlink(to);
+out_close_from:
+ close(fromfd);
+out:
+ return err;
+}
+
+int copyfile(const char *from, const char *to)
+{
+ return copyfile_mode(from, to, 0755);
+}
+
+unsigned long convert_unit(unsigned long value, char *unit)
+{
+ *unit = ' ';
+
+ if (value > 1000) {
+ value /= 1000;
+ *unit = 'K';
+ }
+
+ if (value > 1000) {
+ value /= 1000;
+ *unit = 'M';
+ }
+
+ if (value > 1000) {
+ value /= 1000;
+ *unit = 'G';
+ }
+
+ return value;
+}
+
+static ssize_t ion(bool is_read, int fd, void *buf, size_t n)
+{
+ void *buf_start = buf;
+ size_t left = n;
+
+ while (left) {
+ ssize_t ret = is_read ? read(fd, buf, left) :
+ write(fd, buf, left);
+
+ if (ret < 0 && errno == EINTR)
+ continue;
+ if (ret <= 0)
+ return ret;
+
+ left -= ret;
+ buf += ret;
+ }
+
+ BUG_ON((size_t)(buf - buf_start) != n);
+ return n;
+}
+
+/*
+ * Read exactly 'n' bytes or return an error.
+ */
+ssize_t readn(int fd, void *buf, size_t n)
+{
+ return ion(true, fd, buf, n);
+}
+
+/*
+ * Write exactly 'n' bytes or return an error.
+ */
+ssize_t writen(int fd, void *buf, size_t n)
+{
+ return ion(false, fd, buf, n);
+}
+
+size_t hex_width(u64 v)
+{
+ size_t n = 1;
+
+ while ((v >>= 4))
+ ++n;
+
+ return n;
+}
+
+static int hex(char ch)
+{
+ if ((ch >= '0') && (ch <= '9'))
+ return ch - '0';
+ if ((ch >= 'a') && (ch <= 'f'))
+ return ch - 'a' + 10;
+ if ((ch >= 'A') && (ch <= 'F'))
+ return ch - 'A' + 10;
+ return -1;
+}
+
+/*
+ * While we find nice hex chars, build a long_val.
+ * Return number of chars processed.
+ */
+int hex2u64(const char *ptr, u64 *long_val)
+{
+ const char *p = ptr;
+ *long_val = 0;
+
+ while (*p) {
+ const int hex_val = hex(*p);
+
+ if (hex_val < 0)
+ break;
+
+ *long_val = (*long_val << 4) | hex_val;
+ p++;
+ }
+
+ return p - ptr;
+}
+
+/* Obtain a backtrace and print it to stdout. */
+#ifdef HAVE_BACKTRACE_SUPPORT
+void dump_stack(void)
+{
+ void *array[16];
+ size_t size = backtrace(array, ARRAY_SIZE(array));
+ char **strings = backtrace_symbols(array, size);
+ size_t i;
+
+ printf("Obtained %zd stack frames.\n", size);
+
+ for (i = 0; i < size; i++)
+ printf("%s\n", strings[i]);
+
+ free(strings);
+}
+#else
+void dump_stack(void) {}
+#endif
+
+void get_term_dimensions(struct winsize *ws)
+{
+ char *s = getenv("LINES");
+
+ if (s != NULL) {
+ ws->ws_row = atoi(s);
+ s = getenv("COLUMNS");
+ if (s != NULL) {
+ ws->ws_col = atoi(s);
+ if (ws->ws_row && ws->ws_col)
+ return;
+ }
+ }
+#ifdef TIOCGWINSZ
+ if (ioctl(1, TIOCGWINSZ, ws) == 0 &&
+ ws->ws_row && ws->ws_col)
+ return;
+#endif
+ ws->ws_row = 25;
+ ws->ws_col = 80;
+}
+
+static void set_tracing_events_path(const char *mountpoint)
+{
+ snprintf(tracing_events_path, sizeof(tracing_events_path), "%s/%s",
+ mountpoint, "tracing/events");
+}
+
+const char *perf_debugfs_mount(const char *mountpoint)
+{
+ const char *mnt;
+
+ mnt = debugfs_mount(mountpoint);
+ if (!mnt)
+ return NULL;
+
+ set_tracing_events_path(mnt);
+
+ return mnt;
+}
+
+void perf_debugfs_set_path(const char *mntpt)
+{
+ snprintf(debugfs_mountpoint, strlen(debugfs_mountpoint), "%s", mntpt);
+ set_tracing_events_path(mntpt);
+}
+
+static const char *find_debugfs(void)
+{
+ const char *path = perf_debugfs_mount(NULL);
+
+ if (!path)
+ fprintf(stderr, "Your kernel does not support the debugfs filesystem");
+
+ return path;
+}
+
+/*
+ * Finds the path to the debugfs/tracing
+ * Allocates the string and stores it.
+ */
+const char *find_tracing_dir(void)
+{
+ static char *tracing;
+ static int tracing_found;
+ const char *debugfs;
+
+ if (tracing_found)
+ return tracing;
+
+ debugfs = find_debugfs();
+ if (!debugfs)
+ return NULL;
+
+ tracing = malloc(strlen(debugfs) + 9);
+ if (!tracing)
+ return NULL;
+
+ sprintf(tracing, "%s/tracing", debugfs);
+
+ tracing_found = 1;
+ return tracing;
+}
+
+char *get_tracing_file(const char *name)
+{
+ const char *tracing;
+ char *file;
+
+ tracing = find_tracing_dir();
+ if (!tracing)
+ return NULL;
+
+ file = malloc(strlen(tracing) + strlen(name) + 2);
+ if (!file)
+ return NULL;
+
+ sprintf(file, "%s/%s", tracing, name);
+ return file;
+}
+
+void put_tracing_file(char *file)
+{
+ free(file);
+}
+
+int parse_nsec_time(const char *str, u64 *ptime)
+{
+ u64 time_sec, time_nsec;
+ char *end;
+
+ time_sec = strtoul(str, &end, 10);
+ if (*end != '.' && *end != '\0')
+ return -1;
+
+ if (*end == '.') {
+ int i;
+ char nsec_buf[10];
+
+ if (strlen(++end) > 9)
+ return -1;
+
+ strncpy(nsec_buf, end, 9);
+ nsec_buf[9] = '\0';
+
+ /* make it nsec precision */
+ for (i = strlen(nsec_buf); i < 9; i++)
+ nsec_buf[i] = '0';
+
+ time_nsec = strtoul(nsec_buf, &end, 10);
+ if (*end != '\0')
+ return -1;
+ } else
+ time_nsec = 0;
+
+ *ptime = time_sec * NSEC_PER_SEC + time_nsec;
+ return 0;
+}
+
+unsigned long parse_tag_value(const char *str, struct parse_tag *tags)
+{
+ struct parse_tag *i = tags;
+
+ while (i->tag) {
+ char *s;
+
+ s = strchr(str, i->tag);
+ if (s) {
+ unsigned long int value;
+ char *endptr;
+
+ value = strtoul(str, &endptr, 10);
+ if (s != endptr)
+ break;
+
+ if (value > ULONG_MAX / i->mult)
+ break;
+ value *= i->mult;
+ return value;
+ }
+ i++;
+ }
+
+ return (unsigned long) -1;
+}
+
+int filename__read_int(const char *filename, int *value)
+{
+ char line[64];
+ int fd = open(filename, O_RDONLY), err = -1;
+
+ if (fd < 0)
+ return -1;
+
+ if (read(fd, line, sizeof(line)) > 0) {
+ *value = atoi(line);
+ err = 0;
+ }
+
+ close(fd);
+ return err;
+}
+
+int filename__read_str(const char *filename, char **buf, size_t *sizep)
+{
+ size_t size = 0, alloc_size = 0;
+ void *bf = NULL, *nbf;
+ int fd, n, err = 0;
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0)
+ return -errno;
+
+ do {
+ if (size == alloc_size) {
+ alloc_size += BUFSIZ;
+ nbf = realloc(bf, alloc_size);
+ if (!nbf) {
+ err = -ENOMEM;
+ break;
+ }
+
+ bf = nbf;
+ }
+
+ n = read(fd, bf + size, alloc_size - size);
+ if (n < 0) {
+ if (size) {
+ pr_warning("read failed %d: %s\n",
+ errno, strerror(errno));
+ err = 0;
+ } else
+ err = -errno;
+
+ break;
+ }
+
+ size += n;
+ } while (n > 0);
+
+ if (!err) {
+ *sizep = size;
+ *buf = bf;
+ } else
+ free(bf);
+
+ close(fd);
+ return err;
+}
+
+const char *get_filename_for_perf_kvm(void)
+{
+ const char *filename;
+
+ if (perf_host && !perf_guest)
+ filename = strdup("perf.data.host");
+ else if (!perf_host && perf_guest)
+ filename = strdup("perf.data.guest");
+ else
+ filename = strdup("perf.data.kvm");
+
+ return filename;
+}
+
+int perf_event_paranoid(void)
+{
+ char path[PATH_MAX];
+ const char *procfs = procfs__mountpoint();
+ int value;
+
+ if (!procfs)
+ return INT_MAX;
+
+ scnprintf(path, PATH_MAX, "%s/sys/kernel/perf_event_paranoid", procfs);
+
+ if (filename__read_int(path, &value))
+ return INT_MAX;
+
+ return value;
+}
+
+void mem_bswap_32(void *src, int byte_size)
+{
+ u32 *m = src;
+ while (byte_size > 0) {
+ *m = bswap_32(*m);
+ byte_size -= sizeof(u32);
+ ++m;
+ }
+}
+
+void mem_bswap_64(void *src, int byte_size)
+{
+ u64 *m = src;
+
+ while (byte_size > 0) {
+ *m = bswap_64(*m);
+ byte_size -= sizeof(u64);
+ ++m;
+ }
+}
diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h
new file mode 100644
index 00000000000..66864364ccb
--- /dev/null
+++ b/tools/perf/util/util.h
@@ -0,0 +1,333 @@
+#ifndef GIT_COMPAT_UTIL_H
+#define GIT_COMPAT_UTIL_H
+
+#ifndef FLEX_ARRAY
+/*
+ * See if our compiler is known to support flexible array members.
+ */
+#if defined(__STDC_VERSION__) && (__STDC_VERSION__ >= 199901L)
+# define FLEX_ARRAY /* empty */
+#elif defined(__GNUC__)
+# if (__GNUC__ >= 3)
+# define FLEX_ARRAY /* empty */
+# else
+# define FLEX_ARRAY 0 /* older GNU extension */
+# endif
+#endif
+
+/*
+ * Otherwise, default to safer but a bit wasteful traditional style
+ */
+#ifndef FLEX_ARRAY
+# define FLEX_ARRAY 1
+#endif
+#endif
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+#ifdef __GNUC__
+#define TYPEOF(x) (__typeof__(x))
+#else
+#define TYPEOF(x)
+#endif
+
+#define MSB(x, bits) ((x) & TYPEOF(x)(~0ULL << (sizeof(x) * 8 - (bits))))
+#define HAS_MULTI_BITS(i) ((i) & ((i) - 1)) /* checks if an integer has more than 1 bit set */
+
+/* Approximation of the length of the decimal representation of this type. */
+#define decimal_length(x) ((int)(sizeof(x) * 2.56 + 0.5) + 1)
+
+#define _ALL_SOURCE 1
+#define _BSD_SOURCE 1
+#define HAS_BOOL
+
+#include <unistd.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/statfs.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <limits.h>
+#include <sys/param.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <sys/time.h>
+#include <time.h>
+#include <signal.h>
+#include <fnmatch.h>
+#include <assert.h>
+#include <regex.h>
+#include <utime.h>
+#include <sys/wait.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include <sys/ioctl.h>
+#include <inttypes.h>
+#include <linux/magic.h>
+#include <linux/types.h>
+#include <sys/ttydefaults.h>
+#include <api/fs/debugfs.h>
+#include <termios.h>
+#include <linux/bitops.h>
+
+extern const char *graph_line;
+extern const char *graph_dotted_line;
+extern char buildid_dir[];
+extern char tracing_events_path[];
+extern void perf_debugfs_set_path(const char *mountpoint);
+const char *perf_debugfs_mount(const char *mountpoint);
+const char *find_tracing_dir(void);
+char *get_tracing_file(const char *name);
+void put_tracing_file(char *file);
+
+/* On most systems <limits.h> would have given us this, but
+ * not on some systems (e.g. GNU/Hurd).
+ */
+#ifndef PATH_MAX
+#define PATH_MAX 4096
+#endif
+
+#ifndef PRIuMAX
+#define PRIuMAX "llu"
+#endif
+
+#ifndef PRIu32
+#define PRIu32 "u"
+#endif
+
+#ifndef PRIx32
+#define PRIx32 "x"
+#endif
+
+#ifndef PATH_SEP
+#define PATH_SEP ':'
+#endif
+
+#ifndef STRIP_EXTENSION
+#define STRIP_EXTENSION ""
+#endif
+
+#ifndef has_dos_drive_prefix
+#define has_dos_drive_prefix(path) 0
+#endif
+
+#ifndef is_dir_sep
+#define is_dir_sep(c) ((c) == '/')
+#endif
+
+#ifdef __GNUC__
+#define NORETURN __attribute__((__noreturn__))
+#else
+#define NORETURN
+#ifndef __attribute__
+#define __attribute__(x)
+#endif
+#endif
+
+#define PERF_GTK_DSO "libperf-gtk.so"
+
+/* General helper functions */
+extern void usage(const char *err) NORETURN;
+extern void die(const char *err, ...) NORETURN __attribute__((format (printf, 1, 2)));
+extern int error(const char *err, ...) __attribute__((format (printf, 1, 2)));
+extern void warning(const char *err, ...) __attribute__((format (printf, 1, 2)));
+
+#include "../../../include/linux/stringify.h"
+
+#define DIE_IF(cnd) \
+ do { if (cnd) \
+ die(" at (" __FILE__ ":" __stringify(__LINE__) "): " \
+ __stringify(cnd) "\n"); \
+ } while (0)
+
+
+extern void set_die_routine(void (*routine)(const char *err, va_list params) NORETURN);
+
+extern int prefixcmp(const char *str, const char *prefix);
+extern void set_buildid_dir(void);
+extern void disable_buildid_cache(void);
+
+static inline const char *skip_prefix(const char *str, const char *prefix)
+{
+ size_t len = strlen(prefix);
+ return strncmp(str, prefix, len) ? NULL : str + len;
+}
+
+#ifdef __GLIBC_PREREQ
+#if __GLIBC_PREREQ(2, 1)
+#define HAVE_STRCHRNUL
+#endif
+#endif
+
+#ifndef HAVE_STRCHRNUL
+#define strchrnul gitstrchrnul
+static inline char *gitstrchrnul(const char *s, int c)
+{
+ while (*s && *s != c)
+ s++;
+ return (char *)s;
+}
+#endif
+
+/*
+ * Wrappers:
+ */
+extern char *xstrdup(const char *str);
+extern void *xrealloc(void *ptr, size_t size) __attribute__((weak));
+
+
+static inline void *zalloc(size_t size)
+{
+ return calloc(1, size);
+}
+
+#define zfree(ptr) ({ free(*ptr); *ptr = NULL; })
+
+static inline int has_extension(const char *filename, const char *ext)
+{
+ size_t len = strlen(filename);
+ size_t extlen = strlen(ext);
+
+ return len > extlen && !memcmp(filename + len - extlen, ext, extlen);
+}
+
+/* Sane ctype - no locale, and works with signed chars */
+#undef isascii
+#undef isspace
+#undef isdigit
+#undef isxdigit
+#undef isalpha
+#undef isprint
+#undef isalnum
+#undef islower
+#undef isupper
+#undef tolower
+#undef toupper
+
+#ifndef NSEC_PER_MSEC
+#define NSEC_PER_MSEC 1000000L
+#endif
+
+int parse_nsec_time(const char *str, u64 *ptime);
+
+extern unsigned char sane_ctype[256];
+#define GIT_SPACE 0x01
+#define GIT_DIGIT 0x02
+#define GIT_ALPHA 0x04
+#define GIT_GLOB_SPECIAL 0x08
+#define GIT_REGEX_SPECIAL 0x10
+#define GIT_PRINT_EXTRA 0x20
+#define GIT_PRINT 0x3E
+#define sane_istest(x,mask) ((sane_ctype[(unsigned char)(x)] & (mask)) != 0)
+#define isascii(x) (((x) & ~0x7f) == 0)
+#define isspace(x) sane_istest(x,GIT_SPACE)
+#define isdigit(x) sane_istest(x,GIT_DIGIT)
+#define isxdigit(x) \
+ (sane_istest(toupper(x), GIT_ALPHA | GIT_DIGIT) && toupper(x) < 'G')
+#define isalpha(x) sane_istest(x,GIT_ALPHA)
+#define isalnum(x) sane_istest(x,GIT_ALPHA | GIT_DIGIT)
+#define isprint(x) sane_istest(x,GIT_PRINT)
+#define islower(x) (sane_istest(x,GIT_ALPHA) && (x & 0x20))
+#define isupper(x) (sane_istest(x,GIT_ALPHA) && !(x & 0x20))
+#define tolower(x) sane_case((unsigned char)(x), 0x20)
+#define toupper(x) sane_case((unsigned char)(x), 0)
+
+static inline int sane_case(int x, int high)
+{
+ if (sane_istest(x, GIT_ALPHA))
+ x = (x & ~0x20) | high;
+ return x;
+}
+
+int mkdir_p(char *path, mode_t mode);
+int copyfile(const char *from, const char *to);
+int copyfile_mode(const char *from, const char *to, mode_t mode);
+
+s64 perf_atoll(const char *str);
+char **argv_split(const char *str, int *argcp);
+void argv_free(char **argv);
+bool strglobmatch(const char *str, const char *pat);
+bool strlazymatch(const char *str, const char *pat);
+int strtailcmp(const char *s1, const char *s2);
+char *strxfrchar(char *s, char from, char to);
+unsigned long convert_unit(unsigned long value, char *unit);
+ssize_t readn(int fd, void *buf, size_t n);
+ssize_t writen(int fd, void *buf, size_t n);
+
+struct perf_event_attr;
+
+void event_attr_init(struct perf_event_attr *attr);
+
+#define _STR(x) #x
+#define STR(x) _STR(x)
+
+/*
+ * Determine whether some value is a power of two, where zero is
+ * *not* considered a power of two.
+ */
+
+static inline __attribute__((const))
+bool is_power_of_2(unsigned long n)
+{
+ return (n != 0 && ((n & (n - 1)) == 0));
+}
+
+static inline unsigned next_pow2(unsigned x)
+{
+ if (!x)
+ return 1;
+ return 1ULL << (32 - __builtin_clz(x - 1));
+}
+
+static inline unsigned long next_pow2_l(unsigned long x)
+{
+#if BITS_PER_LONG == 64
+ if (x <= (1UL << 31))
+ return next_pow2(x);
+ return (unsigned long)next_pow2(x >> 32) << 32;
+#else
+ return next_pow2(x);
+#endif
+}
+
+size_t hex_width(u64 v);
+int hex2u64(const char *ptr, u64 *val);
+
+char *ltrim(char *s);
+char *rtrim(char *s);
+
+void dump_stack(void);
+
+extern unsigned int page_size;
+extern int cacheline_size;
+
+void get_term_dimensions(struct winsize *ws);
+
+struct parse_tag {
+ char tag;
+ int mult;
+};
+
+unsigned long parse_tag_value(const char *str, struct parse_tag *tags);
+
+#define SRCLINE_UNKNOWN ((char *) "??:0")
+
+struct dso;
+
+char *get_srcline(struct dso *dso, unsigned long addr);
+void free_srcline(char *srcline);
+
+int filename__read_int(const char *filename, int *value);
+int filename__read_str(const char *filename, char **buf, size_t *sizep);
+int perf_event_paranoid(void);
+
+void mem_bswap_64(void *src, int byte_size);
+void mem_bswap_32(void *src, int byte_size);
+
+const char *get_filename_for_perf_kvm(void);
+#endif /* GIT_COMPAT_UTIL_H */
diff --git a/tools/perf/util/values.c b/tools/perf/util/values.c
new file mode 100644
index 00000000000..0fb3c1fcd3e
--- /dev/null
+++ b/tools/perf/util/values.c
@@ -0,0 +1,232 @@
+#include <stdlib.h>
+
+#include "util.h"
+#include "values.h"
+
+void perf_read_values_init(struct perf_read_values *values)
+{
+ values->threads_max = 16;
+ values->pid = malloc(values->threads_max * sizeof(*values->pid));
+ values->tid = malloc(values->threads_max * sizeof(*values->tid));
+ values->value = malloc(values->threads_max * sizeof(*values->value));
+ if (!values->pid || !values->tid || !values->value)
+ die("failed to allocate read_values threads arrays");
+ values->threads = 0;
+
+ values->counters_max = 16;
+ values->counterrawid = malloc(values->counters_max
+ * sizeof(*values->counterrawid));
+ values->countername = malloc(values->counters_max
+ * sizeof(*values->countername));
+ if (!values->counterrawid || !values->countername)
+ die("failed to allocate read_values counters arrays");
+ values->counters = 0;
+}
+
+void perf_read_values_destroy(struct perf_read_values *values)
+{
+ int i;
+
+ if (!values->threads_max || !values->counters_max)
+ return;
+
+ for (i = 0; i < values->threads; i++)
+ zfree(&values->value[i]);
+ zfree(&values->value);
+ zfree(&values->pid);
+ zfree(&values->tid);
+ zfree(&values->counterrawid);
+ for (i = 0; i < values->counters; i++)
+ zfree(&values->countername[i]);
+ zfree(&values->countername);
+}
+
+static void perf_read_values__enlarge_threads(struct perf_read_values *values)
+{
+ values->threads_max *= 2;
+ values->pid = realloc(values->pid,
+ values->threads_max * sizeof(*values->pid));
+ values->tid = realloc(values->tid,
+ values->threads_max * sizeof(*values->tid));
+ values->value = realloc(values->value,
+ values->threads_max * sizeof(*values->value));
+ if (!values->pid || !values->tid || !values->value)
+ die("failed to enlarge read_values threads arrays");
+}
+
+static int perf_read_values__findnew_thread(struct perf_read_values *values,
+ u32 pid, u32 tid)
+{
+ int i;
+
+ for (i = 0; i < values->threads; i++)
+ if (values->pid[i] == pid && values->tid[i] == tid)
+ return i;
+
+ if (values->threads == values->threads_max)
+ perf_read_values__enlarge_threads(values);
+
+ i = values->threads++;
+ values->pid[i] = pid;
+ values->tid[i] = tid;
+ values->value[i] = malloc(values->counters_max * sizeof(**values->value));
+ if (!values->value[i])
+ die("failed to allocate read_values counters array");
+
+ return i;
+}
+
+static void perf_read_values__enlarge_counters(struct perf_read_values *values)
+{
+ int i;
+
+ values->counters_max *= 2;
+ values->counterrawid = realloc(values->counterrawid,
+ values->counters_max * sizeof(*values->counterrawid));
+ values->countername = realloc(values->countername,
+ values->counters_max * sizeof(*values->countername));
+ if (!values->counterrawid || !values->countername)
+ die("failed to enlarge read_values counters arrays");
+
+ for (i = 0; i < values->threads; i++) {
+ values->value[i] = realloc(values->value[i],
+ values->counters_max * sizeof(**values->value));
+ if (!values->value[i])
+ die("failed to enlarge read_values counters arrays");
+ }
+}
+
+static int perf_read_values__findnew_counter(struct perf_read_values *values,
+ u64 rawid, const char *name)
+{
+ int i;
+
+ for (i = 0; i < values->counters; i++)
+ if (values->counterrawid[i] == rawid)
+ return i;
+
+ if (values->counters == values->counters_max)
+ perf_read_values__enlarge_counters(values);
+
+ i = values->counters++;
+ values->counterrawid[i] = rawid;
+ values->countername[i] = strdup(name);
+
+ return i;
+}
+
+void perf_read_values_add_value(struct perf_read_values *values,
+ u32 pid, u32 tid,
+ u64 rawid, const char *name, u64 value)
+{
+ int tindex, cindex;
+
+ tindex = perf_read_values__findnew_thread(values, pid, tid);
+ cindex = perf_read_values__findnew_counter(values, rawid, name);
+
+ values->value[tindex][cindex] = value;
+}
+
+static void perf_read_values__display_pretty(FILE *fp,
+ struct perf_read_values *values)
+{
+ int i, j;
+ int pidwidth, tidwidth;
+ int *counterwidth;
+
+ counterwidth = malloc(values->counters * sizeof(*counterwidth));
+ if (!counterwidth)
+ die("failed to allocate counterwidth array");
+ tidwidth = 3;
+ pidwidth = 3;
+ for (j = 0; j < values->counters; j++)
+ counterwidth[j] = strlen(values->countername[j]);
+ for (i = 0; i < values->threads; i++) {
+ int width;
+
+ width = snprintf(NULL, 0, "%d", values->pid[i]);
+ if (width > pidwidth)
+ pidwidth = width;
+ width = snprintf(NULL, 0, "%d", values->tid[i]);
+ if (width > tidwidth)
+ tidwidth = width;
+ for (j = 0; j < values->counters; j++) {
+ width = snprintf(NULL, 0, "%" PRIu64, values->value[i][j]);
+ if (width > counterwidth[j])
+ counterwidth[j] = width;
+ }
+ }
+
+ fprintf(fp, "# %*s %*s", pidwidth, "PID", tidwidth, "TID");
+ for (j = 0; j < values->counters; j++)
+ fprintf(fp, " %*s", counterwidth[j], values->countername[j]);
+ fprintf(fp, "\n");
+
+ for (i = 0; i < values->threads; i++) {
+ fprintf(fp, " %*d %*d", pidwidth, values->pid[i],
+ tidwidth, values->tid[i]);
+ for (j = 0; j < values->counters; j++)
+ fprintf(fp, " %*" PRIu64,
+ counterwidth[j], values->value[i][j]);
+ fprintf(fp, "\n");
+ }
+ free(counterwidth);
+}
+
+static void perf_read_values__display_raw(FILE *fp,
+ struct perf_read_values *values)
+{
+ int width, pidwidth, tidwidth, namewidth, rawwidth, countwidth;
+ int i, j;
+
+ tidwidth = 3; /* TID */
+ pidwidth = 3; /* PID */
+ namewidth = 4; /* "Name" */
+ rawwidth = 3; /* "Raw" */
+ countwidth = 5; /* "Count" */
+
+ for (i = 0; i < values->threads; i++) {
+ width = snprintf(NULL, 0, "%d", values->pid[i]);
+ if (width > pidwidth)
+ pidwidth = width;
+ width = snprintf(NULL, 0, "%d", values->tid[i]);
+ if (width > tidwidth)
+ tidwidth = width;
+ }
+ for (j = 0; j < values->counters; j++) {
+ width = strlen(values->countername[j]);
+ if (width > namewidth)
+ namewidth = width;
+ width = snprintf(NULL, 0, "%" PRIx64, values->counterrawid[j]);
+ if (width > rawwidth)
+ rawwidth = width;
+ }
+ for (i = 0; i < values->threads; i++) {
+ for (j = 0; j < values->counters; j++) {
+ width = snprintf(NULL, 0, "%" PRIu64, values->value[i][j]);
+ if (width > countwidth)
+ countwidth = width;
+ }
+ }
+
+ fprintf(fp, "# %*s %*s %*s %*s %*s\n",
+ pidwidth, "PID", tidwidth, "TID",
+ namewidth, "Name", rawwidth, "Raw",
+ countwidth, "Count");
+ for (i = 0; i < values->threads; i++)
+ for (j = 0; j < values->counters; j++)
+ fprintf(fp, " %*d %*d %*s %*" PRIx64 " %*" PRIu64,
+ pidwidth, values->pid[i],
+ tidwidth, values->tid[i],
+ namewidth, values->countername[j],
+ rawwidth, values->counterrawid[j],
+ countwidth, values->value[i][j]);
+}
+
+void perf_read_values_display(FILE *fp, struct perf_read_values *values, int raw)
+{
+ if (raw)
+ perf_read_values__display_raw(fp, values);
+ else
+ perf_read_values__display_pretty(fp, values);
+}
diff --git a/tools/perf/util/values.h b/tools/perf/util/values.h
new file mode 100644
index 00000000000..b21a80c6cf8
--- /dev/null
+++ b/tools/perf/util/values.h
@@ -0,0 +1,27 @@
+#ifndef __PERF_VALUES_H
+#define __PERF_VALUES_H
+
+#include <linux/types.h>
+
+struct perf_read_values {
+ int threads;
+ int threads_max;
+ u32 *pid, *tid;
+ int counters;
+ int counters_max;
+ u64 *counterrawid;
+ char **countername;
+ u64 **value;
+};
+
+void perf_read_values_init(struct perf_read_values *values);
+void perf_read_values_destroy(struct perf_read_values *values);
+
+void perf_read_values_add_value(struct perf_read_values *values,
+ u32 pid, u32 tid,
+ u64 rawid, const char *name, u64 value);
+
+void perf_read_values_display(FILE *fp, struct perf_read_values *values,
+ int raw);
+
+#endif /* __PERF_VALUES_H */
diff --git a/tools/perf/util/vdso.c b/tools/perf/util/vdso.c
new file mode 100644
index 00000000000..0ddb3b8a89e
--- /dev/null
+++ b/tools/perf/util/vdso.c
@@ -0,0 +1,111 @@
+
+#include <unistd.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <linux/kernel.h>
+
+#include "vdso.h"
+#include "util.h"
+#include "symbol.h"
+#include "linux/string.h"
+
+static bool vdso_found;
+static char vdso_file[] = "/tmp/perf-vdso.so-XXXXXX";
+
+static int find_vdso_map(void **start, void **end)
+{
+ FILE *maps;
+ char line[128];
+ int found = 0;
+
+ maps = fopen("/proc/self/maps", "r");
+ if (!maps) {
+ pr_err("vdso: cannot open maps\n");
+ return -1;
+ }
+
+ while (!found && fgets(line, sizeof(line), maps)) {
+ int m = -1;
+
+ /* We care only about private r-x mappings. */
+ if (2 != sscanf(line, "%p-%p r-xp %*x %*x:%*x %*u %n",
+ start, end, &m))
+ continue;
+ if (m < 0)
+ continue;
+
+ if (!strncmp(&line[m], VDSO__MAP_NAME,
+ sizeof(VDSO__MAP_NAME) - 1))
+ found = 1;
+ }
+
+ fclose(maps);
+ return !found;
+}
+
+static char *get_file(void)
+{
+ char *vdso = NULL;
+ char *buf = NULL;
+ void *start, *end;
+ size_t size;
+ int fd;
+
+ if (vdso_found)
+ return vdso_file;
+
+ if (find_vdso_map(&start, &end))
+ return NULL;
+
+ size = end - start;
+
+ buf = memdup(start, size);
+ if (!buf)
+ return NULL;
+
+ fd = mkstemp(vdso_file);
+ if (fd < 0)
+ goto out;
+
+ if (size == (size_t) write(fd, buf, size))
+ vdso = vdso_file;
+
+ close(fd);
+
+ out:
+ free(buf);
+
+ vdso_found = (vdso != NULL);
+ return vdso;
+}
+
+void vdso__exit(void)
+{
+ if (vdso_found)
+ unlink(vdso_file);
+}
+
+struct dso *vdso__dso_findnew(struct list_head *head)
+{
+ struct dso *dso = dsos__find(head, VDSO__MAP_NAME, true);
+
+ if (!dso) {
+ char *file;
+
+ file = get_file();
+ if (!file)
+ return NULL;
+
+ dso = dso__new(VDSO__MAP_NAME);
+ if (dso != NULL) {
+ dsos__add(head, dso);
+ dso__set_long_name(dso, file, false);
+ }
+ }
+
+ return dso;
+}
diff --git a/tools/perf/util/vdso.h b/tools/perf/util/vdso.h
new file mode 100644
index 00000000000..0f76e7caf6f
--- /dev/null
+++ b/tools/perf/util/vdso.h
@@ -0,0 +1,18 @@
+#ifndef __PERF_VDSO__
+#define __PERF_VDSO__
+
+#include <linux/types.h>
+#include <string.h>
+#include <stdbool.h>
+
+#define VDSO__MAP_NAME "[vdso]"
+
+static inline bool is_vdso_map(const char *filename)
+{
+ return !strcmp(filename, VDSO__MAP_NAME);
+}
+
+struct dso *vdso__dso_findnew(struct list_head *head);
+void vdso__exit(void);
+
+#endif /* __PERF_VDSO__ */
diff --git a/tools/perf/util/wrapper.c b/tools/perf/util/wrapper.c
new file mode 100644
index 00000000000..19f15b65070
--- /dev/null
+++ b/tools/perf/util/wrapper.c
@@ -0,0 +1,41 @@
+/*
+ * Various trivial helper wrappers around standard functions
+ */
+#include "cache.h"
+
+/*
+ * There's no pack memory to release - but stay close to the Git
+ * version so wrap this away:
+ */
+static inline void release_pack_memory(size_t size __maybe_unused,
+ int flag __maybe_unused)
+{
+}
+
+char *xstrdup(const char *str)
+{
+ char *ret = strdup(str);
+ if (!ret) {
+ release_pack_memory(strlen(str) + 1, -1);
+ ret = strdup(str);
+ if (!ret)
+ die("Out of memory, strdup failed");
+ }
+ return ret;
+}
+
+void *xrealloc(void *ptr, size_t size)
+{
+ void *ret = realloc(ptr, size);
+ if (!ret && !size)
+ ret = realloc(ptr, 1);
+ if (!ret) {
+ release_pack_memory(size, -1);
+ ret = realloc(ptr, size);
+ if (!ret && !size)
+ ret = realloc(ptr, 1);
+ if (!ret)
+ die("Out of memory, realloc failed");
+ }
+ return ret;
+}
diff --git a/tools/perf/util/xyarray.c b/tools/perf/util/xyarray.c
new file mode 100644
index 00000000000..22afbf6c536
--- /dev/null
+++ b/tools/perf/util/xyarray.c
@@ -0,0 +1,20 @@
+#include "xyarray.h"
+#include "util.h"
+
+struct xyarray *xyarray__new(int xlen, int ylen, size_t entry_size)
+{
+ size_t row_size = ylen * entry_size;
+ struct xyarray *xy = zalloc(sizeof(*xy) + xlen * row_size);
+
+ if (xy != NULL) {
+ xy->entry_size = entry_size;
+ xy->row_size = row_size;
+ }
+
+ return xy;
+}
+
+void xyarray__delete(struct xyarray *xy)
+{
+ free(xy);
+}
diff --git a/tools/perf/util/xyarray.h b/tools/perf/util/xyarray.h
new file mode 100644
index 00000000000..c488a07275d
--- /dev/null
+++ b/tools/perf/util/xyarray.h
@@ -0,0 +1,20 @@
+#ifndef _PERF_XYARRAY_H_
+#define _PERF_XYARRAY_H_ 1
+
+#include <sys/types.h>
+
+struct xyarray {
+ size_t row_size;
+ size_t entry_size;
+ char contents[];
+};
+
+struct xyarray *xyarray__new(int xlen, int ylen, size_t entry_size);
+void xyarray__delete(struct xyarray *xy);
+
+static inline void *xyarray__entry(struct xyarray *xy, int x, int y)
+{
+ return &xy->contents[x * xy->row_size + y * xy->entry_size];
+}
+
+#endif /* _PERF_XYARRAY_H_ */
diff --git a/tools/power/acpi/Makefile b/tools/power/acpi/Makefile
new file mode 100644
index 00000000000..e5a3c4be2a1
--- /dev/null
+++ b/tools/power/acpi/Makefile
@@ -0,0 +1,156 @@
+# tools/power/acpi/Makefile - ACPI tool Makefile
+#
+# Copyright (c) 2013, Intel Corporation
+# Author: Lv Zheng <lv.zheng@intel.com>
+#
+# This program is free software; you can redistribute it and/or
+# modify it under the terms of the GNU General Public License
+# as published by the Free Software Foundation; version 2
+# of the License.
+
+OUTPUT=./
+ifeq ("$(origin O)", "command line")
+ OUTPUT := $(O)/
+endif
+
+ifneq ($(OUTPUT),)
+# check that the output directory actually exists
+OUTDIR := $(shell cd $(OUTPUT) && /bin/pwd)
+$(if $(OUTDIR),, $(error output directory "$(OUTPUT)" does not exist))
+endif
+
+SUBDIRS = tools/ec
+
+# --- CONFIGURATION BEGIN ---
+
+# Set the following to `true' to make a unstripped, unoptimized
+# binary. Leave this set to `false' for production use.
+DEBUG ?= true
+
+# make the build silent. Set this to something else to make it noisy again.
+V ?= false
+
+# Prefix to the directories we're installing to
+DESTDIR ?=
+
+# --- CONFIGURATION END ---
+
+# Directory definitions. These are default and most probably
+# do not need to be changed. Please note that DESTDIR is
+# added in front of any of them
+
+bindir ?= /usr/bin
+sbindir ?= /usr/sbin
+mandir ?= /usr/man
+
+# Toolchain: what tools do we use, and what options do they need:
+
+INSTALL = /usr/bin/install -c
+INSTALL_PROGRAM = ${INSTALL}
+INSTALL_DATA = ${INSTALL} -m 644
+INSTALL_SCRIPT = ${INSTALL_PROGRAM}
+
+# If you are running a cross compiler, you may want to set this
+# to something more interesting, like "arm-linux-". If you want
+# to compile vs uClibc, that can be done here as well.
+CROSS = #/usr/i386-linux-uclibc/usr/bin/i386-uclibc-
+CC = $(CROSS)gcc
+LD = $(CROSS)gcc
+STRIP = $(CROSS)strip
+HOSTCC = gcc
+
+# check if compiler option is supported
+cc-supports = ${shell if $(CC) ${1} -S -o /dev/null -x c /dev/null > /dev/null 2>&1; then echo "$(1)"; fi;}
+
+# use '-Os' optimization if available, else use -O2
+OPTIMIZATION := $(call cc-supports,-Os,-O2)
+
+WARNINGS := -Wall
+WARNINGS += $(call cc-supports,-Wstrict-prototypes)
+WARNINGS += $(call cc-supports,-Wdeclaration-after-statement)
+
+KERNEL_INCLUDE := ../../../include
+ACPICA_INCLUDE := ../../../drivers/acpi/acpica
+CFLAGS += -D_LINUX -I$(KERNEL_INCLUDE) -I$(ACPICA_INCLUDE)
+CFLAGS += $(WARNINGS)
+
+ifeq ($(strip $(V)),false)
+ QUIET=@
+ ECHO=@echo
+else
+ QUIET=
+ ECHO=@\#
+endif
+export QUIET ECHO
+
+# if DEBUG is enabled, then we do not strip or optimize
+ifeq ($(strip $(DEBUG)),true)
+ CFLAGS += -O1 -g -DDEBUG
+ STRIPCMD = /bin/true -Since_we_are_debugging
+else
+ CFLAGS += $(OPTIMIZATION) -fomit-frame-pointer
+ STRIPCMD = $(STRIP) -s --remove-section=.note --remove-section=.comment
+endif
+
+# --- ACPIDUMP BEGIN ---
+
+vpath %.c \
+ ../../../drivers/acpi/acpica\
+ tools/acpidump\
+ common\
+ os_specific/service_layers
+
+CFLAGS += -DACPI_DUMP_APP -Itools/acpidump
+
+DUMP_OBJS = \
+ apdump.o\
+ apfiles.o\
+ apmain.o\
+ osunixdir.o\
+ osunixmap.o\
+ tbprint.o\
+ tbxfroot.o\
+ utbuffer.o\
+ utexcep.o\
+ utmath.o\
+ utstring.o\
+ utxferror.o\
+ oslinuxtbl.o\
+ cmfsize.o\
+ getopt.o
+
+DUMP_OBJS := $(addprefix $(OUTPUT)tools/acpidump/,$(DUMP_OBJS))
+
+$(OUTPUT)acpidump: $(DUMP_OBJS)
+ $(ECHO) " LD " $@
+ $(QUIET) $(LD) $(CFLAGS) $(LDFLAGS) $(DUMP_OBJS) -L$(OUTPUT) -o $@
+ $(QUIET) $(STRIPCMD) $@
+
+$(OUTPUT)tools/acpidump/%.o: %.c
+ $(ECHO) " CC " $@
+ $(QUIET) $(CC) -c $(CFLAGS) -o $@ $<
+
+# --- ACPIDUMP END ---
+
+all: $(OUTPUT)acpidump
+ echo $(OUTPUT)
+
+clean:
+ -find $(OUTPUT) \( -not -type d \) -and \( -name '*~' -o -name '*.[oas]' \) -type f -print \
+ | xargs rm -f
+ -rm -f $(OUTPUT)acpidump
+
+install-tools:
+ $(INSTALL) -d $(DESTDIR)${sbindir}
+ $(INSTALL_PROGRAM) $(OUTPUT)acpidump $(DESTDIR)${sbindir}
+
+install-man:
+ $(INSTALL_DATA) -D man/acpidump.8 $(DESTDIR)${mandir}/man8/acpidump.8
+
+install: all install-tools install-man
+
+uninstall:
+ - rm -f $(DESTDIR)${sbindir}/acpidump
+ - rm -f $(DESTDIR)${mandir}/man8/acpidump.8
+
+.PHONY: all utils install-tools install-man install uninstall clean
diff --git a/tools/power/acpi/common/cmfsize.c b/tools/power/acpi/common/cmfsize.c
new file mode 100644
index 00000000000..5140e5edae1
--- /dev/null
+++ b/tools/power/acpi/common/cmfsize.c
@@ -0,0 +1,101 @@
+/******************************************************************************
+ *
+ * Module Name: cfsize - Common get file size function
+ *
+ *****************************************************************************/
+
+/*
+ * Copyright (C) 2000 - 2014, Intel Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ * 3. Neither the names of the above-listed copyright holders nor the names
+ * of any contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ */
+
+#include <acpi/acpi.h>
+#include "accommon.h"
+#include "acapps.h"
+#include <stdio.h>
+
+#define _COMPONENT ACPI_TOOLS
+ACPI_MODULE_NAME("cmfsize")
+
+/*******************************************************************************
+ *
+ * FUNCTION: cm_get_file_size
+ *
+ * PARAMETERS: file - Open file descriptor
+ *
+ * RETURN: File Size. On error, -1 (ACPI_UINT32_MAX)
+ *
+ * DESCRIPTION: Get the size of a file. Uses seek-to-EOF. File must be open.
+ * Does not disturb the current file pointer. Uses perror for
+ * error messages.
+ *
+ ******************************************************************************/
+u32 cm_get_file_size(FILE * file)
+{
+ long file_size;
+ long current_offset;
+
+ /* Save the current file pointer, seek to EOF to obtain file size */
+
+ current_offset = ftell(file);
+ if (current_offset < 0) {
+ goto offset_error;
+ }
+
+ if (fseek(file, 0, SEEK_END)) {
+ goto seek_error;
+ }
+
+ file_size = ftell(file);
+ if (file_size < 0) {
+ goto offset_error;
+ }
+
+ /* Restore original file pointer */
+
+ if (fseek(file, current_offset, SEEK_SET)) {
+ goto seek_error;
+ }
+
+ return ((u32)file_size);
+
+offset_error:
+ perror("Could not get file offset");
+ return (ACPI_UINT32_MAX);
+
+seek_error:
+ perror("Could not seek file");
+ return (ACPI_UINT32_MAX);
+}
diff --git a/tools/power/acpi/common/getopt.c b/tools/power/acpi/common/getopt.c
new file mode 100644
index 00000000000..a302f52e4fd
--- /dev/null
+++ b/tools/power/acpi/common/getopt.c
@@ -0,0 +1,239 @@
+/******************************************************************************
+ *
+ * Module Name: getopt
+ *
+ *****************************************************************************/
+
+/*
+ * Copyright (C) 2000 - 2014, Intel Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ * 3. Neither the names of the above-listed copyright holders nor the names
+ * of any contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ */
+
+/*
+ * ACPICA getopt() implementation
+ *
+ * Option strings:
+ * "f" - Option has no arguments
+ * "f:" - Option requires an argument
+ * "f^" - Option has optional single-char sub-options
+ * "f|" - Option has required single-char sub-options
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <acpi/acpi.h>
+#include "accommon.h"
+#include "acapps.h"
+
+#define ACPI_OPTION_ERROR(msg, badchar) \
+ if (acpi_gbl_opterr) {fprintf (stderr, "%s%c\n", msg, badchar);}
+
+int acpi_gbl_opterr = 1;
+int acpi_gbl_optind = 1;
+int acpi_gbl_sub_opt_char = 0;
+char *acpi_gbl_optarg;
+
+static int current_char_ptr = 1;
+
+/*******************************************************************************
+ *
+ * FUNCTION: acpi_getopt_argument
+ *
+ * PARAMETERS: argc, argv - from main
+ *
+ * RETURN: 0 if an argument was found, -1 otherwise. Sets acpi_gbl_Optarg
+ * to point to the next argument.
+ *
+ * DESCRIPTION: Get the next argument. Used to obtain arguments for the
+ * two-character options after the original call to acpi_getopt.
+ * Note: Either the argument starts at the next character after
+ * the option, or it is pointed to by the next argv entry.
+ * (After call to acpi_getopt, we need to backup to the previous
+ * argv entry).
+ *
+ ******************************************************************************/
+
+int acpi_getopt_argument(int argc, char **argv)
+{
+ acpi_gbl_optind--;
+ current_char_ptr++;
+
+ if (argv[acpi_gbl_optind][(int)(current_char_ptr + 1)] != '\0') {
+ acpi_gbl_optarg =
+ &argv[acpi_gbl_optind++][(int)(current_char_ptr + 1)];
+ } else if (++acpi_gbl_optind >= argc) {
+ ACPI_OPTION_ERROR("Option requires an argument: -", 'v');
+
+ current_char_ptr = 1;
+ return (-1);
+ } else {
+ acpi_gbl_optarg = argv[acpi_gbl_optind++];
+ }
+
+ current_char_ptr = 1;
+ return (0);
+}
+
+/*******************************************************************************
+ *
+ * FUNCTION: acpi_getopt
+ *
+ * PARAMETERS: argc, argv - from main
+ * opts - options info list
+ *
+ * RETURN: Option character or EOF
+ *
+ * DESCRIPTION: Get the next option
+ *
+ ******************************************************************************/
+
+int acpi_getopt(int argc, char **argv, char *opts)
+{
+ int current_char;
+ char *opts_ptr;
+
+ if (current_char_ptr == 1) {
+ if (acpi_gbl_optind >= argc ||
+ argv[acpi_gbl_optind][0] != '-' ||
+ argv[acpi_gbl_optind][1] == '\0') {
+ return (EOF);
+ } else if (strcmp(argv[acpi_gbl_optind], "--") == 0) {
+ acpi_gbl_optind++;
+ return (EOF);
+ }
+ }
+
+ /* Get the option */
+
+ current_char = argv[acpi_gbl_optind][current_char_ptr];
+
+ /* Make sure that the option is legal */
+
+ if (current_char == ':' ||
+ (opts_ptr = strchr(opts, current_char)) == NULL) {
+ ACPI_OPTION_ERROR("Illegal option: -", current_char);
+
+ if (argv[acpi_gbl_optind][++current_char_ptr] == '\0') {
+ acpi_gbl_optind++;
+ current_char_ptr = 1;
+ }
+
+ return ('?');
+ }
+
+ /* Option requires an argument? */
+
+ if (*++opts_ptr == ':') {
+ if (argv[acpi_gbl_optind][(int)(current_char_ptr + 1)] != '\0') {
+ acpi_gbl_optarg =
+ &argv[acpi_gbl_optind++][(int)
+ (current_char_ptr + 1)];
+ } else if (++acpi_gbl_optind >= argc) {
+ ACPI_OPTION_ERROR("Option requires an argument: -",
+ current_char);
+
+ current_char_ptr = 1;
+ return ('?');
+ } else {
+ acpi_gbl_optarg = argv[acpi_gbl_optind++];
+ }
+
+ current_char_ptr = 1;
+ }
+
+ /* Option has an optional argument? */
+
+ else if (*opts_ptr == '+') {
+ if (argv[acpi_gbl_optind][(int)(current_char_ptr + 1)] != '\0') {
+ acpi_gbl_optarg =
+ &argv[acpi_gbl_optind++][(int)
+ (current_char_ptr + 1)];
+ } else if (++acpi_gbl_optind >= argc) {
+ acpi_gbl_optarg = NULL;
+ } else {
+ acpi_gbl_optarg = argv[acpi_gbl_optind++];
+ }
+
+ current_char_ptr = 1;
+ }
+
+ /* Option has optional single-char arguments? */
+
+ else if (*opts_ptr == '^') {
+ if (argv[acpi_gbl_optind][(int)(current_char_ptr + 1)] != '\0') {
+ acpi_gbl_optarg =
+ &argv[acpi_gbl_optind][(int)(current_char_ptr + 1)];
+ } else {
+ acpi_gbl_optarg = "^";
+ }
+
+ acpi_gbl_sub_opt_char = acpi_gbl_optarg[0];
+ acpi_gbl_optind++;
+ current_char_ptr = 1;
+ }
+
+ /* Option has a required single-char argument? */
+
+ else if (*opts_ptr == '|') {
+ if (argv[acpi_gbl_optind][(int)(current_char_ptr + 1)] != '\0') {
+ acpi_gbl_optarg =
+ &argv[acpi_gbl_optind][(int)(current_char_ptr + 1)];
+ } else {
+ ACPI_OPTION_ERROR
+ ("Option requires a single-character suboption: -",
+ current_char);
+
+ current_char_ptr = 1;
+ return ('?');
+ }
+
+ acpi_gbl_sub_opt_char = acpi_gbl_optarg[0];
+ acpi_gbl_optind++;
+ current_char_ptr = 1;
+ }
+
+ /* Option with no arguments */
+
+ else {
+ if (argv[acpi_gbl_optind][++current_char_ptr] == '\0') {
+ current_char_ptr = 1;
+ acpi_gbl_optind++;
+ }
+
+ acpi_gbl_optarg = NULL;
+ }
+
+ return (current_char);
+}
diff --git a/tools/power/acpi/man/acpidump.8 b/tools/power/acpi/man/acpidump.8
new file mode 100644
index 00000000000..38f095d86b5
--- /dev/null
+++ b/tools/power/acpi/man/acpidump.8
@@ -0,0 +1,120 @@
+.TH ACPIDUMP 8
+.SH NAME
+acpidump \- dump a system's ACPI tables to an ASCII file
+
+.SH SYNOPSIS
+.B acpidump
+.RI [ options ]
+.br
+
+.SH DESCRIPTION
+.B acpidump
+dumps the systems ACPI tables to an ASCII file appropriate for
+attaching to a bug report.
+
+Subsequently, they can be processed by utilities in the ACPICA package.
+
+.SH OPTIONS
+acpidump options are as follow:
+.TP
+.B Options
+.TP
+.B \-b
+Dump tables to binary files
+.TP
+.B \-c
+Dump customized tables
+.TP
+.B \-h \-?
+This help message
+.TP
+.B \-o <File>
+Redirect output to file
+.TP
+.B \-r <Address>
+Dump tables from specified RSDP
+.TP
+.B \-s
+Print table summaries only
+.TP
+.B \-v
+Display version information
+.TP
+.B \-z
+Verbose mode
+.TP
+.B Table Options
+.TP
+.B \-a <Address>
+Get table via a physical address
+.TP
+.B \-f <BinaryFile>
+Get table via a binary file
+.TP
+.B \-n <Signature>
+Get table via a name/signature
+.TP
+Invocation without parameters dumps all available tables
+.TP
+Multiple mixed instances of -a, -f, and -n are supported
+
+.SH EXAMPLES
+
+.nf
+# acpidump > acpidump.out
+
+$ acpixtract -a acpidump.out
+ Acpi table [DSDT] - 15974 bytes written to DSDT.dat
+ Acpi table [FACS] - 64 bytes written to FACS.dat
+ Acpi table [FACP] - 116 bytes written to FACP.dat
+ Acpi table [APIC] - 120 bytes written to APIC.dat
+ Acpi table [MCFG] - 60 bytes written to MCFG.dat
+ Acpi table [SSDT] - 444 bytes written to SSDT1.dat
+ Acpi table [SSDT] - 439 bytes written to SSDT2.dat
+ Acpi table [SSDT] - 439 bytes written to SSDT3.dat
+ Acpi table [SSDT] - 439 bytes written to SSDT4.dat
+ Acpi table [SSDT] - 439 bytes written to SSDT5.dat
+ Acpi table [RSDT] - 76 bytes written to RSDT.dat
+ Acpi table [RSDP] - 20 bytes written to RSDP.dat
+
+$ iasl -d *.dat
+...
+.fi
+creates *.dsl, a human readable form which can be edited
+and compiled using iasl.
+
+
+.SH NOTES
+
+.B "acpidump "
+must be run as root.
+
+.SH REFERENCES
+ACPICA: https://acpica.org/
+
+.SH FILES
+.ta
+.nf
+/dev/mem
+/sys/firmware/acpi/tables/*
+/sys/firmware/acpi/tables/dynamic/*
+/sys/firmware/efi/systab
+.fi
+
+.SH AUTHOR
+.TP
+Original by:
+ Len Brown <len.brown@intel.com>
+.TP
+Written by:
+ Chao Guan <chao.guan@intel.com>
+.TP
+Updated by:
+ Bob Moore <robert.moore@intel.com>
+ Lv Zheng <lv.zheng@intel.com>
+
+.SH SEE ALSO
+\&\fIacpixtract\fR\|(8), \fIiasl\fR\|(8).
+
+.SH COPYRIGHT
+COPYRIGHT (c) 2013, Intel Corporation.
diff --git a/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c b/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c
new file mode 100644
index 00000000000..28c52008e85
--- /dev/null
+++ b/tools/power/acpi/os_specific/service_layers/oslinuxtbl.c
@@ -0,0 +1,1329 @@
+/******************************************************************************
+ *
+ * Module Name: oslinuxtbl - Linux OSL for obtaining ACPI tables
+ *
+ *****************************************************************************/
+
+/*
+ * Copyright (C) 2000 - 2014, Intel Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ * 3. Neither the names of the above-listed copyright holders nor the names
+ * of any contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ */
+
+#include "acpidump.h"
+
+#define _COMPONENT ACPI_OS_SERVICES
+ACPI_MODULE_NAME("oslinuxtbl")
+
+#ifndef PATH_MAX
+#define PATH_MAX 256
+#endif
+/* List of information about obtained ACPI tables */
+typedef struct osl_table_info {
+ struct osl_table_info *next;
+ u32 instance;
+ char signature[ACPI_NAME_SIZE];
+
+} osl_table_info;
+
+/* Local prototypes */
+
+static acpi_status osl_table_initialize(void);
+
+static acpi_status
+osl_table_name_from_file(char *filename, char *signature, u32 *instance);
+
+static acpi_status osl_add_table_to_list(char *signature, u32 instance);
+
+static acpi_status
+osl_read_table_from_file(char *filename,
+ acpi_size file_offset,
+ char *signature, struct acpi_table_header **table);
+
+static acpi_status
+osl_map_table(acpi_size address,
+ char *signature, struct acpi_table_header **table);
+
+static void osl_unmap_table(struct acpi_table_header *table);
+
+static acpi_physical_address osl_find_rsdp_via_efi(void);
+
+static acpi_status osl_load_rsdp(void);
+
+static acpi_status osl_list_customized_tables(char *directory);
+
+static acpi_status
+osl_get_customized_table(char *pathname,
+ char *signature,
+ u32 instance,
+ struct acpi_table_header **table,
+ acpi_physical_address * address);
+
+static acpi_status osl_list_bios_tables(void);
+
+static acpi_status
+osl_get_bios_table(char *signature,
+ u32 instance,
+ struct acpi_table_header **table,
+ acpi_physical_address * address);
+
+static acpi_status osl_get_last_status(acpi_status default_status);
+
+/* File locations */
+
+#define DYNAMIC_TABLE_DIR "/sys/firmware/acpi/tables/dynamic"
+#define STATIC_TABLE_DIR "/sys/firmware/acpi/tables"
+#define EFI_SYSTAB "/sys/firmware/efi/systab"
+
+/* Should we get dynamically loaded SSDTs from DYNAMIC_TABLE_DIR? */
+
+u8 gbl_dump_dynamic_tables = TRUE;
+
+/* Initialization flags */
+
+u8 gbl_table_list_initialized = FALSE;
+
+/* Local copies of main ACPI tables */
+
+struct acpi_table_rsdp gbl_rsdp;
+struct acpi_table_fadt *gbl_fadt = NULL;
+struct acpi_table_rsdt *gbl_rsdt = NULL;
+struct acpi_table_xsdt *gbl_xsdt = NULL;
+
+/* Table addresses */
+
+acpi_physical_address gbl_fadt_address = 0;
+acpi_physical_address gbl_rsdp_address = 0;
+
+/* Revision of RSD PTR */
+
+u8 gbl_revision = 0;
+
+struct osl_table_info *gbl_table_list_head = NULL;
+u32 gbl_table_count = 0;
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_get_last_status
+ *
+ * PARAMETERS: default_status - Default error status to return
+ *
+ * RETURN: Status; Converted from errno.
+ *
+ * DESCRIPTION: Get last errno and conver it to acpi_status.
+ *
+ *****************************************************************************/
+
+static acpi_status osl_get_last_status(acpi_status default_status)
+{
+
+ switch (errno) {
+ case EACCES:
+ case EPERM:
+
+ return (AE_ACCESS);
+
+ case ENOENT:
+
+ return (AE_NOT_FOUND);
+
+ case ENOMEM:
+
+ return (AE_NO_MEMORY);
+
+ default:
+
+ return (default_status);
+ }
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: acpi_os_get_table_by_address
+ *
+ * PARAMETERS: address - Physical address of the ACPI table
+ * table - Where a pointer to the table is returned
+ *
+ * RETURN: Status; Table buffer is returned if AE_OK.
+ * AE_NOT_FOUND: A valid table was not found at the address
+ *
+ * DESCRIPTION: Get an ACPI table via a physical memory address.
+ *
+ *****************************************************************************/
+
+acpi_status
+acpi_os_get_table_by_address(acpi_physical_address address,
+ struct acpi_table_header ** table)
+{
+ u32 table_length;
+ struct acpi_table_header *mapped_table;
+ struct acpi_table_header *local_table = NULL;
+ acpi_status status = AE_OK;
+
+ /* Get main ACPI tables from memory on first invocation of this function */
+
+ status = osl_table_initialize();
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+
+ /* Map the table and validate it */
+
+ status = osl_map_table(address, NULL, &mapped_table);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+
+ /* Copy table to local buffer and return it */
+
+ table_length = ap_get_table_length(mapped_table);
+ if (table_length == 0) {
+ status = AE_BAD_HEADER;
+ goto exit;
+ }
+
+ local_table = calloc(1, table_length);
+ if (!local_table) {
+ status = AE_NO_MEMORY;
+ goto exit;
+ }
+
+ ACPI_MEMCPY(local_table, mapped_table, table_length);
+
+exit:
+ osl_unmap_table(mapped_table);
+ *table = local_table;
+ return (status);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: acpi_os_get_table_by_name
+ *
+ * PARAMETERS: signature - ACPI Signature for desired table. Must be
+ * a null terminated 4-character string.
+ * instance - Multiple table support for SSDT/UEFI (0...n)
+ * Must be 0 for other tables.
+ * table - Where a pointer to the table is returned
+ * address - Where the table physical address is returned
+ *
+ * RETURN: Status; Table buffer and physical address returned if AE_OK.
+ * AE_LIMIT: Instance is beyond valid limit
+ * AE_NOT_FOUND: A table with the signature was not found
+ *
+ * NOTE: Assumes the input signature is uppercase.
+ *
+ *****************************************************************************/
+
+acpi_status
+acpi_os_get_table_by_name(char *signature,
+ u32 instance,
+ struct acpi_table_header ** table,
+ acpi_physical_address * address)
+{
+ acpi_status status;
+
+ /* Get main ACPI tables from memory on first invocation of this function */
+
+ status = osl_table_initialize();
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+
+ /* Not a main ACPI table, attempt to extract it from the RSDT/XSDT */
+
+ if (!gbl_dump_customized_tables) {
+
+ /* Attempt to get the table from the memory */
+
+ status =
+ osl_get_bios_table(signature, instance, table, address);
+ } else {
+ /* Attempt to get the table from the static directory */
+
+ status = osl_get_customized_table(STATIC_TABLE_DIR, signature,
+ instance, table, address);
+ }
+
+ if (ACPI_FAILURE(status) && status == AE_LIMIT) {
+ if (gbl_dump_dynamic_tables) {
+
+ /* Attempt to get a dynamic table */
+
+ status =
+ osl_get_customized_table(DYNAMIC_TABLE_DIR,
+ signature, instance, table,
+ address);
+ }
+ }
+
+ return (status);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_add_table_to_list
+ *
+ * PARAMETERS: signature - Table signature
+ * instance - Table instance
+ *
+ * RETURN: Status; Successfully added if AE_OK.
+ * AE_NO_MEMORY: Memory allocation error
+ *
+ * DESCRIPTION: Insert a table structure into OSL table list.
+ *
+ *****************************************************************************/
+
+static acpi_status osl_add_table_to_list(char *signature, u32 instance)
+{
+ struct osl_table_info *new_info;
+ struct osl_table_info *next;
+ u32 next_instance = 0;
+ u8 found = FALSE;
+
+ new_info = calloc(1, sizeof(struct osl_table_info));
+ if (!new_info) {
+ return (AE_NO_MEMORY);
+ }
+
+ ACPI_MOVE_NAME(new_info->signature, signature);
+
+ if (!gbl_table_list_head) {
+ gbl_table_list_head = new_info;
+ } else {
+ next = gbl_table_list_head;
+ while (1) {
+ if (ACPI_COMPARE_NAME(next->signature, signature)) {
+ if (next->instance == instance) {
+ found = TRUE;
+ }
+ if (next->instance >= next_instance) {
+ next_instance = next->instance + 1;
+ }
+ }
+
+ if (!next->next) {
+ break;
+ }
+ next = next->next;
+ }
+ next->next = new_info;
+ }
+
+ if (found) {
+ if (instance) {
+ fprintf(stderr,
+ "%4.4s: Warning unmatched table instance %d, expected %d\n",
+ signature, instance, next_instance);
+ }
+ instance = next_instance;
+ }
+
+ new_info->instance = instance;
+ gbl_table_count++;
+
+ return (AE_OK);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: acpi_os_get_table_by_index
+ *
+ * PARAMETERS: index - Which table to get
+ * table - Where a pointer to the table is returned
+ * instance - Where a pointer to the table instance no. is
+ * returned
+ * address - Where the table physical address is returned
+ *
+ * RETURN: Status; Table buffer and physical address returned if AE_OK.
+ * AE_LIMIT: Index is beyond valid limit
+ *
+ * DESCRIPTION: Get an ACPI table via an index value (0 through n). Returns
+ * AE_LIMIT when an invalid index is reached. Index is not
+ * necessarily an index into the RSDT/XSDT.
+ *
+ *****************************************************************************/
+
+acpi_status
+acpi_os_get_table_by_index(u32 index,
+ struct acpi_table_header ** table,
+ u32 *instance, acpi_physical_address * address)
+{
+ struct osl_table_info *info;
+ acpi_status status;
+ u32 i;
+
+ /* Get main ACPI tables from memory on first invocation of this function */
+
+ status = osl_table_initialize();
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+
+ /* Validate Index */
+
+ if (index >= gbl_table_count) {
+ return (AE_LIMIT);
+ }
+
+ /* Point to the table list entry specified by the Index argument */
+
+ info = gbl_table_list_head;
+ for (i = 0; i < index; i++) {
+ info = info->next;
+ }
+
+ /* Now we can just get the table via the signature */
+
+ status = acpi_os_get_table_by_name(info->signature, info->instance,
+ table, address);
+
+ if (ACPI_SUCCESS(status)) {
+ *instance = info->instance;
+ }
+ return (status);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_find_rsdp_via_efi
+ *
+ * PARAMETERS: None
+ *
+ * RETURN: RSDP address if found
+ *
+ * DESCRIPTION: Find RSDP address via EFI.
+ *
+ *****************************************************************************/
+
+static acpi_physical_address osl_find_rsdp_via_efi(void)
+{
+ FILE *file;
+ char buffer[80];
+ unsigned long address = 0;
+
+ file = fopen(EFI_SYSTAB, "r");
+ if (file) {
+ while (fgets(buffer, 80, file)) {
+ if (sscanf(buffer, "ACPI20=0x%lx", &address) == 1) {
+ break;
+ }
+ }
+ fclose(file);
+ }
+
+ return ((acpi_physical_address) (address));
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_load_rsdp
+ *
+ * PARAMETERS: None
+ *
+ * RETURN: Status
+ *
+ * DESCRIPTION: Scan and load RSDP.
+ *
+ *****************************************************************************/
+
+static acpi_status osl_load_rsdp(void)
+{
+ struct acpi_table_header *mapped_table;
+ u8 *rsdp_address;
+ acpi_physical_address rsdp_base;
+ acpi_size rsdp_size;
+
+ /* Get RSDP from memory */
+
+ rsdp_size = sizeof(struct acpi_table_rsdp);
+ if (gbl_rsdp_base) {
+ rsdp_base = gbl_rsdp_base;
+ } else {
+ rsdp_base = osl_find_rsdp_via_efi();
+ }
+
+ if (!rsdp_base) {
+ rsdp_base = ACPI_HI_RSDP_WINDOW_BASE;
+ rsdp_size = ACPI_HI_RSDP_WINDOW_SIZE;
+ }
+
+ rsdp_address = acpi_os_map_memory(rsdp_base, rsdp_size);
+ if (!rsdp_address) {
+ return (osl_get_last_status(AE_BAD_ADDRESS));
+ }
+
+ /* Search low memory for the RSDP */
+
+ mapped_table = ACPI_CAST_PTR(struct acpi_table_header,
+ acpi_tb_scan_memory_for_rsdp(rsdp_address,
+ rsdp_size));
+ if (!mapped_table) {
+ acpi_os_unmap_memory(rsdp_address, rsdp_size);
+ return (AE_NOT_FOUND);
+ }
+
+ gbl_rsdp_address =
+ rsdp_base + (ACPI_CAST8(mapped_table) - rsdp_address);
+
+ ACPI_MEMCPY(&gbl_rsdp, mapped_table, sizeof(struct acpi_table_rsdp));
+ acpi_os_unmap_memory(rsdp_address, rsdp_size);
+
+ return (AE_OK);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_can_use_xsdt
+ *
+ * PARAMETERS: None
+ *
+ * RETURN: TRUE if XSDT is allowed to be used.
+ *
+ * DESCRIPTION: This function collects logic that can be used to determine if
+ * XSDT should be used instead of RSDT.
+ *
+ *****************************************************************************/
+
+static u8 osl_can_use_xsdt(void)
+{
+ if (gbl_revision && !acpi_gbl_do_not_use_xsdt) {
+ return (TRUE);
+ } else {
+ return (FALSE);
+ }
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_table_initialize
+ *
+ * PARAMETERS: None
+ *
+ * RETURN: Status
+ *
+ * DESCRIPTION: Initialize ACPI table data. Get and store main ACPI tables to
+ * local variables. Main ACPI tables include RSDT, FADT, RSDT,
+ * and/or XSDT.
+ *
+ *****************************************************************************/
+
+static acpi_status osl_table_initialize(void)
+{
+ acpi_status status;
+ acpi_physical_address address;
+
+ if (gbl_table_list_initialized) {
+ return (AE_OK);
+ }
+
+ /* Get RSDP from memory */
+
+ status = osl_load_rsdp();
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+
+ /* Get XSDT from memory */
+
+ if (gbl_rsdp.revision && !gbl_do_not_dump_xsdt) {
+ if (gbl_xsdt) {
+ free(gbl_xsdt);
+ gbl_xsdt = NULL;
+ }
+
+ gbl_revision = 2;
+ status = osl_get_bios_table(ACPI_SIG_XSDT, 0,
+ ACPI_CAST_PTR(struct
+ acpi_table_header *,
+ &gbl_xsdt), &address);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+ }
+
+ /* Get RSDT from memory */
+
+ if (gbl_rsdp.rsdt_physical_address) {
+ if (gbl_rsdt) {
+ free(gbl_rsdt);
+ gbl_rsdt = NULL;
+ }
+
+ status = osl_get_bios_table(ACPI_SIG_RSDT, 0,
+ ACPI_CAST_PTR(struct
+ acpi_table_header *,
+ &gbl_rsdt), &address);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+ }
+
+ /* Get FADT from memory */
+
+ if (gbl_fadt) {
+ free(gbl_fadt);
+ gbl_fadt = NULL;
+ }
+
+ status = osl_get_bios_table(ACPI_SIG_FADT, 0,
+ ACPI_CAST_PTR(struct acpi_table_header *,
+ &gbl_fadt),
+ &gbl_fadt_address);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+
+ if (!gbl_dump_customized_tables) {
+
+ /* Add mandatory tables to global table list first */
+
+ status = osl_add_table_to_list(ACPI_RSDP_NAME, 0);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+
+ status = osl_add_table_to_list(ACPI_SIG_RSDT, 0);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+
+ if (gbl_revision == 2) {
+ status = osl_add_table_to_list(ACPI_SIG_XSDT, 0);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+ }
+
+ status = osl_add_table_to_list(ACPI_SIG_DSDT, 0);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+
+ status = osl_add_table_to_list(ACPI_SIG_FACS, 0);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+
+ /* Add all tables found in the memory */
+
+ status = osl_list_bios_tables();
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+ } else {
+ /* Add all tables found in the static directory */
+
+ status = osl_list_customized_tables(STATIC_TABLE_DIR);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+ }
+
+ if (gbl_dump_dynamic_tables) {
+
+ /* Add all dynamically loaded tables in the dynamic directory */
+
+ status = osl_list_customized_tables(DYNAMIC_TABLE_DIR);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+ }
+
+ gbl_table_list_initialized = TRUE;
+ return (AE_OK);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_list_bios_tables
+ *
+ * PARAMETERS: None
+ *
+ * RETURN: Status; Table list is initialized if AE_OK.
+ *
+ * DESCRIPTION: Add ACPI tables to the table list from memory.
+ *
+ * NOTE: This works on Linux as table customization does not modify the
+ * addresses stored in RSDP/RSDT/XSDT/FADT.
+ *
+ *****************************************************************************/
+
+static acpi_status osl_list_bios_tables(void)
+{
+ struct acpi_table_header *mapped_table = NULL;
+ u8 *table_data;
+ u8 number_of_tables;
+ u8 item_size;
+ acpi_physical_address table_address = 0;
+ acpi_status status = AE_OK;
+ u32 i;
+
+ if (osl_can_use_xsdt()) {
+ item_size = sizeof(u64);
+ table_data =
+ ACPI_CAST8(gbl_xsdt) + sizeof(struct acpi_table_header);
+ number_of_tables =
+ (u8)((gbl_xsdt->header.length -
+ sizeof(struct acpi_table_header))
+ / item_size);
+ } else { /* Use RSDT if XSDT is not available */
+
+ item_size = sizeof(u32);
+ table_data =
+ ACPI_CAST8(gbl_rsdt) + sizeof(struct acpi_table_header);
+ number_of_tables =
+ (u8)((gbl_rsdt->header.length -
+ sizeof(struct acpi_table_header))
+ / item_size);
+ }
+
+ /* Search RSDT/XSDT for the requested table */
+
+ for (i = 0; i < number_of_tables; ++i, table_data += item_size) {
+ if (osl_can_use_xsdt()) {
+ table_address =
+ (acpi_physical_address) (*ACPI_CAST64(table_data));
+ } else {
+ table_address =
+ (acpi_physical_address) (*ACPI_CAST32(table_data));
+ }
+
+ /* Skip NULL entries in RSDT/XSDT */
+
+ if (!table_address) {
+ continue;
+ }
+
+ status = osl_map_table(table_address, NULL, &mapped_table);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+
+ osl_add_table_to_list(mapped_table->signature, 0);
+ osl_unmap_table(mapped_table);
+ }
+
+ return (AE_OK);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_get_bios_table
+ *
+ * PARAMETERS: signature - ACPI Signature for common table. Must be
+ * a null terminated 4-character string.
+ * instance - Multiple table support for SSDT/UEFI (0...n)
+ * Must be 0 for other tables.
+ * table - Where a pointer to the table is returned
+ * address - Where the table physical address is returned
+ *
+ * RETURN: Status; Table buffer and physical address returned if AE_OK.
+ * AE_LIMIT: Instance is beyond valid limit
+ * AE_NOT_FOUND: A table with the signature was not found
+ *
+ * DESCRIPTION: Get a BIOS provided ACPI table
+ *
+ * NOTE: Assumes the input signature is uppercase.
+ *
+ *****************************************************************************/
+
+static acpi_status
+osl_get_bios_table(char *signature,
+ u32 instance,
+ struct acpi_table_header **table,
+ acpi_physical_address * address)
+{
+ struct acpi_table_header *local_table = NULL;
+ struct acpi_table_header *mapped_table = NULL;
+ u8 *table_data;
+ u8 number_of_tables;
+ u8 item_size;
+ u32 current_instance = 0;
+ acpi_physical_address table_address = 0;
+ u32 table_length = 0;
+ acpi_status status = AE_OK;
+ u32 i;
+
+ /* Handle special tables whose addresses are not in RSDT/XSDT */
+
+ if (ACPI_COMPARE_NAME(signature, ACPI_RSDP_NAME) ||
+ ACPI_COMPARE_NAME(signature, ACPI_SIG_RSDT) ||
+ ACPI_COMPARE_NAME(signature, ACPI_SIG_XSDT) ||
+ ACPI_COMPARE_NAME(signature, ACPI_SIG_DSDT) ||
+ ACPI_COMPARE_NAME(signature, ACPI_SIG_FACS)) {
+ if (instance > 0) {
+ return (AE_LIMIT);
+ }
+
+ /*
+ * Get the appropriate address, either 32-bit or 64-bit. Be very
+ * careful about the FADT length and validate table addresses.
+ * Note: The 64-bit addresses have priority.
+ */
+ if (ACPI_COMPARE_NAME(signature, ACPI_SIG_DSDT)) {
+ if ((gbl_fadt->header.length >= MIN_FADT_FOR_XDSDT) &&
+ gbl_fadt->Xdsdt) {
+ table_address =
+ (acpi_physical_address) gbl_fadt->Xdsdt;
+ } else
+ if ((gbl_fadt->header.length >= MIN_FADT_FOR_DSDT)
+ && gbl_fadt->dsdt) {
+ table_address =
+ (acpi_physical_address) gbl_fadt->dsdt;
+ }
+ } else if (ACPI_COMPARE_NAME(signature, ACPI_SIG_FACS)) {
+ if ((gbl_fadt->header.length >= MIN_FADT_FOR_XFACS) &&
+ gbl_fadt->Xfacs) {
+ table_address =
+ (acpi_physical_address) gbl_fadt->Xfacs;
+ } else
+ if ((gbl_fadt->header.length >= MIN_FADT_FOR_FACS)
+ && gbl_fadt->facs) {
+ table_address =
+ (acpi_physical_address) gbl_fadt->facs;
+ }
+ } else if (ACPI_COMPARE_NAME(signature, ACPI_SIG_XSDT)) {
+ if (!gbl_revision) {
+ return (AE_BAD_SIGNATURE);
+ }
+ table_address =
+ (acpi_physical_address) gbl_rsdp.
+ xsdt_physical_address;
+ } else if (ACPI_COMPARE_NAME(signature, ACPI_SIG_RSDT)) {
+ table_address =
+ (acpi_physical_address) gbl_rsdp.
+ rsdt_physical_address;
+ } else {
+ table_address =
+ (acpi_physical_address) gbl_rsdp_address;
+ signature = ACPI_SIG_RSDP;
+ }
+
+ /* Now we can get the requested special table */
+
+ status = osl_map_table(table_address, signature, &mapped_table);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+
+ table_length = ap_get_table_length(mapped_table);
+ } else { /* Case for a normal ACPI table */
+
+ if (osl_can_use_xsdt()) {
+ item_size = sizeof(u64);
+ table_data =
+ ACPI_CAST8(gbl_xsdt) +
+ sizeof(struct acpi_table_header);
+ number_of_tables =
+ (u8)((gbl_xsdt->header.length -
+ sizeof(struct acpi_table_header))
+ / item_size);
+ } else { /* Use RSDT if XSDT is not available */
+
+ item_size = sizeof(u32);
+ table_data =
+ ACPI_CAST8(gbl_rsdt) +
+ sizeof(struct acpi_table_header);
+ number_of_tables =
+ (u8)((gbl_rsdt->header.length -
+ sizeof(struct acpi_table_header))
+ / item_size);
+ }
+
+ /* Search RSDT/XSDT for the requested table */
+
+ for (i = 0; i < number_of_tables; ++i, table_data += item_size) {
+ if (osl_can_use_xsdt()) {
+ table_address =
+ (acpi_physical_address) (*ACPI_CAST64
+ (table_data));
+ } else {
+ table_address =
+ (acpi_physical_address) (*ACPI_CAST32
+ (table_data));
+ }
+
+ /* Skip NULL entries in RSDT/XSDT */
+
+ if (!table_address) {
+ continue;
+ }
+
+ status =
+ osl_map_table(table_address, NULL, &mapped_table);
+ if (ACPI_FAILURE(status)) {
+ return (status);
+ }
+ table_length = mapped_table->length;
+
+ /* Does this table match the requested signature? */
+
+ if (!ACPI_COMPARE_NAME
+ (mapped_table->signature, signature)) {
+ osl_unmap_table(mapped_table);
+ mapped_table = NULL;
+ continue;
+ }
+
+ /* Match table instance (for SSDT/UEFI tables) */
+
+ if (current_instance != instance) {
+ osl_unmap_table(mapped_table);
+ mapped_table = NULL;
+ current_instance++;
+ continue;
+ }
+
+ break;
+ }
+ }
+
+ if (!mapped_table) {
+ return (AE_LIMIT);
+ }
+
+ if (table_length == 0) {
+ status = AE_BAD_HEADER;
+ goto exit;
+ }
+
+ /* Copy table to local buffer and return it */
+
+ local_table = calloc(1, table_length);
+ if (!local_table) {
+ status = AE_NO_MEMORY;
+ goto exit;
+ }
+
+ ACPI_MEMCPY(local_table, mapped_table, table_length);
+ *address = table_address;
+ *table = local_table;
+
+exit:
+ osl_unmap_table(mapped_table);
+ return (status);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_list_customized_tables
+ *
+ * PARAMETERS: directory - Directory that contains the tables
+ *
+ * RETURN: Status; Table list is initialized if AE_OK.
+ *
+ * DESCRIPTION: Add ACPI tables to the table list from a directory.
+ *
+ *****************************************************************************/
+
+static acpi_status osl_list_customized_tables(char *directory)
+{
+ void *table_dir;
+ u32 instance;
+ char temp_name[ACPI_NAME_SIZE];
+ char *filename;
+ acpi_status status = AE_OK;
+
+ /* Open the requested directory */
+
+ table_dir = acpi_os_open_directory(directory, "*", REQUEST_FILE_ONLY);
+ if (!table_dir) {
+ return (osl_get_last_status(AE_NOT_FOUND));
+ }
+
+ /* Examine all entries in this directory */
+
+ while ((filename = acpi_os_get_next_filename(table_dir))) {
+
+ /* Extract table name and instance number */
+
+ status =
+ osl_table_name_from_file(filename, temp_name, &instance);
+
+ /* Ignore meaningless files */
+
+ if (ACPI_FAILURE(status)) {
+ continue;
+ }
+
+ /* Add new info node to global table list */
+
+ status = osl_add_table_to_list(temp_name, instance);
+ if (ACPI_FAILURE(status)) {
+ break;
+ }
+ }
+
+ acpi_os_close_directory(table_dir);
+ return (status);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_map_table
+ *
+ * PARAMETERS: address - Address of the table in memory
+ * signature - Optional ACPI Signature for desired table.
+ * Null terminated 4-character string.
+ * table - Where a pointer to the mapped table is
+ * returned
+ *
+ * RETURN: Status; Mapped table is returned if AE_OK.
+ * AE_NOT_FOUND: A valid table was not found at the address
+ *
+ * DESCRIPTION: Map entire ACPI table into caller's address space.
+ *
+ *****************************************************************************/
+
+static acpi_status
+osl_map_table(acpi_size address,
+ char *signature, struct acpi_table_header **table)
+{
+ struct acpi_table_header *mapped_table;
+ u32 length;
+
+ if (!address) {
+ return (AE_BAD_ADDRESS);
+ }
+
+ /*
+ * Map the header so we can get the table length.
+ * Use sizeof (struct acpi_table_header) as:
+ * 1. it is bigger than 24 to include RSDP->Length
+ * 2. it is smaller than sizeof (struct acpi_table_rsdp)
+ */
+ mapped_table =
+ acpi_os_map_memory(address, sizeof(struct acpi_table_header));
+ if (!mapped_table) {
+ fprintf(stderr, "Could not map table header at 0x%8.8X%8.8X\n",
+ ACPI_FORMAT_UINT64(address));
+ return (osl_get_last_status(AE_BAD_ADDRESS));
+ }
+
+ /* If specified, signature must match */
+
+ if (signature) {
+ if (ACPI_VALIDATE_RSDP_SIG(signature)) {
+ if (!ACPI_VALIDATE_RSDP_SIG(mapped_table->signature)) {
+ acpi_os_unmap_memory(mapped_table,
+ sizeof(struct
+ acpi_table_header));
+ return (AE_BAD_SIGNATURE);
+ }
+ } else
+ if (!ACPI_COMPARE_NAME(signature, mapped_table->signature))
+ {
+ acpi_os_unmap_memory(mapped_table,
+ sizeof(struct acpi_table_header));
+ return (AE_BAD_SIGNATURE);
+ }
+ }
+
+ /* Map the entire table */
+
+ length = ap_get_table_length(mapped_table);
+ acpi_os_unmap_memory(mapped_table, sizeof(struct acpi_table_header));
+ if (length == 0) {
+ return (AE_BAD_HEADER);
+ }
+
+ mapped_table = acpi_os_map_memory(address, length);
+ if (!mapped_table) {
+ fprintf(stderr,
+ "Could not map table at 0x%8.8X%8.8X length %8.8X\n",
+ ACPI_FORMAT_UINT64(address), length);
+ return (osl_get_last_status(AE_INVALID_TABLE_LENGTH));
+ }
+
+ (void)ap_is_valid_checksum(mapped_table);
+
+ *table = mapped_table;
+ return (AE_OK);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_unmap_table
+ *
+ * PARAMETERS: table - A pointer to the mapped table
+ *
+ * RETURN: None
+ *
+ * DESCRIPTION: Unmap entire ACPI table.
+ *
+ *****************************************************************************/
+
+static void osl_unmap_table(struct acpi_table_header *table)
+{
+ if (table) {
+ acpi_os_unmap_memory(table, ap_get_table_length(table));
+ }
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_table_name_from_file
+ *
+ * PARAMETERS: filename - File that contains the desired table
+ * signature - Pointer to 4-character buffer to store
+ * extracted table signature.
+ * instance - Pointer to integer to store extracted
+ * table instance number.
+ *
+ * RETURN: Status; Table name is extracted if AE_OK.
+ *
+ * DESCRIPTION: Extract table signature and instance number from a table file
+ * name.
+ *
+ *****************************************************************************/
+
+static acpi_status
+osl_table_name_from_file(char *filename, char *signature, u32 *instance)
+{
+
+ /* Ignore meaningless files */
+
+ if (strlen(filename) < ACPI_NAME_SIZE) {
+ return (AE_BAD_SIGNATURE);
+ }
+
+ /* Extract instance number */
+
+ if (isdigit((int)filename[ACPI_NAME_SIZE])) {
+ sscanf(&filename[ACPI_NAME_SIZE], "%d", instance);
+ } else if (strlen(filename) != ACPI_NAME_SIZE) {
+ return (AE_BAD_SIGNATURE);
+ } else {
+ *instance = 0;
+ }
+
+ /* Extract signature */
+
+ ACPI_MOVE_NAME(signature, filename);
+ return (AE_OK);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_read_table_from_file
+ *
+ * PARAMETERS: filename - File that contains the desired table
+ * file_offset - Offset of the table in file
+ * signature - Optional ACPI Signature for desired table.
+ * A null terminated 4-character string.
+ * table - Where a pointer to the table is returned
+ *
+ * RETURN: Status; Table buffer is returned if AE_OK.
+ *
+ * DESCRIPTION: Read a ACPI table from a file.
+ *
+ *****************************************************************************/
+
+static acpi_status
+osl_read_table_from_file(char *filename,
+ acpi_size file_offset,
+ char *signature, struct acpi_table_header **table)
+{
+ FILE *table_file;
+ struct acpi_table_header header;
+ struct acpi_table_header *local_table = NULL;
+ u32 table_length;
+ s32 count;
+ acpi_status status = AE_OK;
+
+ /* Open the file */
+
+ table_file = fopen(filename, "rb");
+ if (table_file == NULL) {
+ fprintf(stderr, "Could not open table file: %s\n", filename);
+ return (osl_get_last_status(AE_NOT_FOUND));
+ }
+
+ fseek(table_file, file_offset, SEEK_SET);
+
+ /* Read the Table header to get the table length */
+
+ count = fread(&header, 1, sizeof(struct acpi_table_header), table_file);
+ if (count != sizeof(struct acpi_table_header)) {
+ fprintf(stderr, "Could not read table header: %s\n", filename);
+ status = AE_BAD_HEADER;
+ goto exit;
+ }
+
+ /* If signature is specified, it must match the table */
+
+ if (signature) {
+ if (ACPI_VALIDATE_RSDP_SIG(signature)) {
+ if (!ACPI_VALIDATE_RSDP_SIG(header.signature)) {
+ fprintf(stderr,
+ "Incorrect RSDP signature: found %8.8s\n",
+ header.signature);
+ status = AE_BAD_SIGNATURE;
+ goto exit;
+ }
+ } else if (!ACPI_COMPARE_NAME(signature, header.signature)) {
+ fprintf(stderr,
+ "Incorrect signature: Expecting %4.4s, found %4.4s\n",
+ signature, header.signature);
+ status = AE_BAD_SIGNATURE;
+ goto exit;
+ }
+ }
+
+ table_length = ap_get_table_length(&header);
+ if (table_length == 0) {
+ status = AE_BAD_HEADER;
+ goto exit;
+ }
+
+ /* Read the entire table into a local buffer */
+
+ local_table = calloc(1, table_length);
+ if (!local_table) {
+ fprintf(stderr,
+ "%4.4s: Could not allocate buffer for table of length %X\n",
+ header.signature, table_length);
+ status = AE_NO_MEMORY;
+ goto exit;
+ }
+
+ fseek(table_file, file_offset, SEEK_SET);
+
+ count = fread(local_table, 1, table_length, table_file);
+ if (count != table_length) {
+ fprintf(stderr, "%4.4s: Could not read table content\n",
+ header.signature);
+ status = AE_INVALID_TABLE_LENGTH;
+ goto exit;
+ }
+
+ /* Validate checksum */
+
+ (void)ap_is_valid_checksum(local_table);
+
+exit:
+ fclose(table_file);
+ *table = local_table;
+ return (status);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: osl_get_customized_table
+ *
+ * PARAMETERS: pathname - Directory to find Linux customized table
+ * signature - ACPI Signature for desired table. Must be
+ * a null terminated 4-character string.
+ * instance - Multiple table support for SSDT/UEFI (0...n)
+ * Must be 0 for other tables.
+ * table - Where a pointer to the table is returned
+ * address - Where the table physical address is returned
+ *
+ * RETURN: Status; Table buffer is returned if AE_OK.
+ * AE_LIMIT: Instance is beyond valid limit
+ * AE_NOT_FOUND: A table with the signature was not found
+ *
+ * DESCRIPTION: Get an OS customized table.
+ *
+ *****************************************************************************/
+
+static acpi_status
+osl_get_customized_table(char *pathname,
+ char *signature,
+ u32 instance,
+ struct acpi_table_header **table,
+ acpi_physical_address * address)
+{
+ void *table_dir;
+ u32 current_instance = 0;
+ char temp_name[ACPI_NAME_SIZE];
+ char table_filename[PATH_MAX];
+ char *filename;
+ acpi_status status;
+
+ /* Open the directory for customized tables */
+
+ table_dir = acpi_os_open_directory(pathname, "*", REQUEST_FILE_ONLY);
+ if (!table_dir) {
+ return (osl_get_last_status(AE_NOT_FOUND));
+ }
+
+ /* Attempt to find the table in the directory */
+
+ while ((filename = acpi_os_get_next_filename(table_dir))) {
+
+ /* Ignore meaningless files */
+
+ if (!ACPI_COMPARE_NAME(filename, signature)) {
+ continue;
+ }
+
+ /* Extract table name and instance number */
+
+ status =
+ osl_table_name_from_file(filename, temp_name,
+ &current_instance);
+
+ /* Ignore meaningless files */
+
+ if (ACPI_FAILURE(status) || current_instance != instance) {
+ continue;
+ }
+
+ /* Create the table pathname */
+
+ if (instance != 0) {
+ sprintf(table_filename, "%s/%4.4s%d", pathname,
+ temp_name, instance);
+ } else {
+ sprintf(table_filename, "%s/%4.4s", pathname,
+ temp_name);
+ }
+ break;
+ }
+
+ acpi_os_close_directory(table_dir);
+
+ if (!filename) {
+ return (AE_LIMIT);
+ }
+
+ /* There is no physical address saved for customized tables, use zero */
+
+ *address = 0;
+ status = osl_read_table_from_file(table_filename, 0, NULL, table);
+
+ return (status);
+}
diff --git a/tools/power/acpi/os_specific/service_layers/osunixdir.c b/tools/power/acpi/os_specific/service_layers/osunixdir.c
new file mode 100644
index 00000000000..733f9e490fc
--- /dev/null
+++ b/tools/power/acpi/os_specific/service_layers/osunixdir.c
@@ -0,0 +1,204 @@
+/******************************************************************************
+ *
+ * Module Name: osunixdir - Unix directory access interfaces
+ *
+ *****************************************************************************/
+
+/*
+ * Copyright (C) 2000 - 2014, Intel Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ * 3. Neither the names of the above-listed copyright holders nor the names
+ * of any contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ */
+
+#include <acpi/acpi.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <dirent.h>
+#include <fnmatch.h>
+#include <ctype.h>
+#include <sys/stat.h>
+
+/*
+ * Allocated structure returned from os_open_directory
+ */
+typedef struct external_find_info {
+ char *dir_pathname;
+ DIR *dir_ptr;
+ char temp_buffer[256];
+ char *wildcard_spec;
+ char requested_file_type;
+
+} external_find_info;
+
+/*******************************************************************************
+ *
+ * FUNCTION: acpi_os_open_directory
+ *
+ * PARAMETERS: dir_pathname - Full pathname to the directory
+ * wildcard_spec - string of the form "*.c", etc.
+ *
+ * RETURN: A directory "handle" to be used in subsequent search operations.
+ * NULL returned on failure.
+ *
+ * DESCRIPTION: Open a directory in preparation for a wildcard search
+ *
+ ******************************************************************************/
+
+void *acpi_os_open_directory(char *dir_pathname,
+ char *wildcard_spec, char requested_file_type)
+{
+ struct external_find_info *external_info;
+ DIR *dir;
+
+ /* Allocate the info struct that will be returned to the caller */
+
+ external_info = calloc(1, sizeof(struct external_find_info));
+ if (!external_info) {
+ return (NULL);
+ }
+
+ /* Get the directory stream */
+
+ dir = opendir(dir_pathname);
+ if (!dir) {
+ fprintf(stderr, "Cannot open directory - %s\n", dir_pathname);
+ free(external_info);
+ return (NULL);
+ }
+
+ /* Save the info in the return structure */
+
+ external_info->wildcard_spec = wildcard_spec;
+ external_info->requested_file_type = requested_file_type;
+ external_info->dir_pathname = dir_pathname;
+ external_info->dir_ptr = dir;
+ return (external_info);
+}
+
+/*******************************************************************************
+ *
+ * FUNCTION: acpi_os_get_next_filename
+ *
+ * PARAMETERS: dir_handle - Created via acpi_os_open_directory
+ *
+ * RETURN: Next filename matched. NULL if no more matches.
+ *
+ * DESCRIPTION: Get the next file in the directory that matches the wildcard
+ * specification.
+ *
+ ******************************************************************************/
+
+char *acpi_os_get_next_filename(void *dir_handle)
+{
+ struct external_find_info *external_info = dir_handle;
+ struct dirent *dir_entry;
+ char *temp_str;
+ int str_len;
+ struct stat temp_stat;
+ int err;
+
+ while ((dir_entry = readdir(external_info->dir_ptr))) {
+ if (!fnmatch
+ (external_info->wildcard_spec, dir_entry->d_name, 0)) {
+ if (dir_entry->d_name[0] == '.') {
+ continue;
+ }
+
+ str_len = strlen(dir_entry->d_name) +
+ strlen(external_info->dir_pathname) + 2;
+
+ temp_str = calloc(str_len, 1);
+ if (!temp_str) {
+ fprintf(stderr,
+ "Could not allocate buffer for temporary string\n");
+ return (NULL);
+ }
+
+ strcpy(temp_str, external_info->dir_pathname);
+ strcat(temp_str, "/");
+ strcat(temp_str, dir_entry->d_name);
+
+ err = stat(temp_str, &temp_stat);
+ if (err == -1) {
+ fprintf(stderr,
+ "Cannot stat file (should not happen) - %s\n",
+ temp_str);
+ free(temp_str);
+ return (NULL);
+ }
+
+ free(temp_str);
+
+ if ((S_ISDIR(temp_stat.st_mode)
+ && (external_info->requested_file_type ==
+ REQUEST_DIR_ONLY))
+ || ((!S_ISDIR(temp_stat.st_mode)
+ && external_info->requested_file_type ==
+ REQUEST_FILE_ONLY))) {
+
+ /* copy to a temp buffer because dir_entry struct is on the stack */
+
+ strcpy(external_info->temp_buffer,
+ dir_entry->d_name);
+ return (external_info->temp_buffer);
+ }
+ }
+ }
+
+ return (NULL);
+}
+
+/*******************************************************************************
+ *
+ * FUNCTION: acpi_os_close_directory
+ *
+ * PARAMETERS: dir_handle - Created via acpi_os_open_directory
+ *
+ * RETURN: None.
+ *
+ * DESCRIPTION: Close the open directory and cleanup.
+ *
+ ******************************************************************************/
+
+void acpi_os_close_directory(void *dir_handle)
+{
+ struct external_find_info *external_info = dir_handle;
+
+ /* Close the directory and free allocations */
+
+ closedir(external_info->dir_ptr);
+ free(dir_handle);
+}
diff --git a/tools/power/acpi/os_specific/service_layers/osunixmap.c b/tools/power/acpi/os_specific/service_layers/osunixmap.c
new file mode 100644
index 00000000000..99b47b6194a
--- /dev/null
+++ b/tools/power/acpi/os_specific/service_layers/osunixmap.c
@@ -0,0 +1,151 @@
+/******************************************************************************
+ *
+ * Module Name: osunixmap - Unix OSL for file mappings
+ *
+ *****************************************************************************/
+
+/*
+ * Copyright (C) 2000 - 2014, Intel Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ * 3. Neither the names of the above-listed copyright holders nor the names
+ * of any contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ */
+
+#include "acpidump.h"
+#include <unistd.h>
+#include <sys/mman.h>
+#ifdef _free_BSD
+#include <sys/param.h>
+#endif
+
+#define _COMPONENT ACPI_OS_SERVICES
+ACPI_MODULE_NAME("osunixmap")
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
+#ifdef _free_BSD
+#define MMAP_FLAGS MAP_SHARED
+#else
+#define MMAP_FLAGS MAP_PRIVATE
+#endif
+#define SYSTEM_MEMORY "/dev/mem"
+/*******************************************************************************
+ *
+ * FUNCTION: acpi_os_get_page_size
+ *
+ * PARAMETERS: None
+ *
+ * RETURN: Page size of the platform.
+ *
+ * DESCRIPTION: Obtain page size of the platform.
+ *
+ ******************************************************************************/
+static acpi_size acpi_os_get_page_size(void)
+{
+
+#ifdef PAGE_SIZE
+ return PAGE_SIZE;
+#else
+ return sysconf(_SC_PAGESIZE);
+#endif
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: acpi_os_map_memory
+ *
+ * PARAMETERS: where - Physical address of memory to be mapped
+ * length - How much memory to map
+ *
+ * RETURN: Pointer to mapped memory. Null on error.
+ *
+ * DESCRIPTION: Map physical memory into local address space.
+ *
+ *****************************************************************************/
+
+void *acpi_os_map_memory(acpi_physical_address where, acpi_size length)
+{
+ u8 *mapped_memory;
+ acpi_physical_address offset;
+ acpi_size page_size;
+ int fd;
+
+ fd = open(SYSTEM_MEMORY, O_RDONLY | O_BINARY);
+ if (fd < 0) {
+ fprintf(stderr, "Cannot open %s\n", SYSTEM_MEMORY);
+ return (NULL);
+ }
+
+ /* Align the offset to use mmap */
+
+ page_size = acpi_os_get_page_size();
+ offset = where % page_size;
+
+ /* Map the table header to get the length of the full table */
+
+ mapped_memory = mmap(NULL, (length + offset), PROT_READ, MMAP_FLAGS,
+ fd, (where - offset));
+ if (mapped_memory == MAP_FAILED) {
+ fprintf(stderr, "Cannot map %s\n", SYSTEM_MEMORY);
+ close(fd);
+ return (NULL);
+ }
+
+ close(fd);
+ return (ACPI_CAST8(mapped_memory + offset));
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: acpi_os_unmap_memory
+ *
+ * PARAMETERS: where - Logical address of memory to be unmapped
+ * length - How much memory to unmap
+ *
+ * RETURN: None.
+ *
+ * DESCRIPTION: Delete a previously created mapping. Where and Length must
+ * correspond to a previous mapping exactly.
+ *
+ *****************************************************************************/
+
+void acpi_os_unmap_memory(void *where, acpi_size length)
+{
+ acpi_physical_address offset;
+ acpi_size page_size;
+
+ page_size = acpi_os_get_page_size();
+ offset = (acpi_physical_address) where % page_size;
+ munmap((u8 *)where - offset, (length + offset));
+}
diff --git a/tools/power/acpi/tools/acpidump/acpidump.h b/tools/power/acpi/tools/acpidump/acpidump.h
new file mode 100644
index 00000000000..46f519597fe
--- /dev/null
+++ b/tools/power/acpi/tools/acpidump/acpidump.h
@@ -0,0 +1,130 @@
+/******************************************************************************
+ *
+ * Module Name: acpidump.h - Include file for acpi_dump utility
+ *
+ *****************************************************************************/
+
+/*
+ * Copyright (C) 2000 - 2014, Intel Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ * 3. Neither the names of the above-listed copyright holders nor the names
+ * of any contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ */
+
+/*
+ * Global variables. Defined in main.c only, externed in all other files
+ */
+#ifdef _DECLARE_GLOBALS
+#define EXTERN
+#define INIT_GLOBAL(a,b) a=b
+#define DEFINE_ACPI_GLOBALS 1
+#else
+#define EXTERN extern
+#define INIT_GLOBAL(a,b) a
+#endif
+
+#include <acpi/acpi.h>
+#include "accommon.h"
+#include "actables.h"
+
+#include <stdio.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/stat.h>
+
+/* Globals */
+
+EXTERN u8 INIT_GLOBAL(gbl_summary_mode, FALSE);
+EXTERN u8 INIT_GLOBAL(gbl_verbose_mode, FALSE);
+EXTERN u8 INIT_GLOBAL(gbl_binary_mode, FALSE);
+EXTERN u8 INIT_GLOBAL(gbl_dump_customized_tables, FALSE);
+EXTERN u8 INIT_GLOBAL(gbl_do_not_dump_xsdt, FALSE);
+EXTERN FILE INIT_GLOBAL(*gbl_output_file, NULL);
+EXTERN char INIT_GLOBAL(*gbl_output_filename, NULL);
+EXTERN u64 INIT_GLOBAL(gbl_rsdp_base, 0);
+
+/* Globals required for use with ACPICA modules */
+
+#ifdef _DECLARE_GLOBALS
+u8 acpi_gbl_integer_byte_width = 8;
+#endif
+
+/* Action table used to defer requested options */
+
+struct ap_dump_action {
+ char *argument;
+ u32 to_be_done;
+};
+
+#define AP_MAX_ACTIONS 32
+
+#define AP_DUMP_ALL_TABLES 0
+#define AP_DUMP_TABLE_BY_ADDRESS 1
+#define AP_DUMP_TABLE_BY_NAME 2
+#define AP_DUMP_TABLE_BY_FILE 3
+
+#define AP_MAX_ACPI_FILES 256 /* Prevent infinite loops */
+
+/* Minimum FADT sizes for various table addresses */
+
+#define MIN_FADT_FOR_DSDT (ACPI_FADT_OFFSET (dsdt) + sizeof (u32))
+#define MIN_FADT_FOR_FACS (ACPI_FADT_OFFSET (facs) + sizeof (u32))
+#define MIN_FADT_FOR_XDSDT (ACPI_FADT_OFFSET (Xdsdt) + sizeof (u64))
+#define MIN_FADT_FOR_XFACS (ACPI_FADT_OFFSET (Xfacs) + sizeof (u64))
+
+/*
+ * apdump - Table get/dump routines
+ */
+int ap_dump_table_from_file(char *pathname);
+
+int ap_dump_table_by_name(char *signature);
+
+int ap_dump_table_by_address(char *ascii_address);
+
+int ap_dump_all_tables(void);
+
+u8 ap_is_valid_header(struct acpi_table_header *table);
+
+u8 ap_is_valid_checksum(struct acpi_table_header *table);
+
+u32 ap_get_table_length(struct acpi_table_header *table);
+
+/*
+ * apfiles - File I/O utilities
+ */
+int ap_open_output_file(char *pathname);
+
+int ap_write_to_binary_file(struct acpi_table_header *table, u32 instance);
+
+struct acpi_table_header *ap_get_table_from_file(char *pathname,
+ u32 *file_size);
diff --git a/tools/power/acpi/tools/acpidump/apdump.c b/tools/power/acpi/tools/acpidump/apdump.c
new file mode 100644
index 00000000000..3cac1237836
--- /dev/null
+++ b/tools/power/acpi/tools/acpidump/apdump.c
@@ -0,0 +1,451 @@
+/******************************************************************************
+ *
+ * Module Name: apdump - Dump routines for ACPI tables (acpidump)
+ *
+ *****************************************************************************/
+
+/*
+ * Copyright (C) 2000 - 2014, Intel Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ * 3. Neither the names of the above-listed copyright holders nor the names
+ * of any contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ */
+
+#include "acpidump.h"
+
+/* Local prototypes */
+
+static int
+ap_dump_table_buffer(struct acpi_table_header *table,
+ u32 instance, acpi_physical_address address);
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_is_valid_header
+ *
+ * PARAMETERS: table - Pointer to table to be validated
+ *
+ * RETURN: TRUE if the header appears to be valid. FALSE otherwise
+ *
+ * DESCRIPTION: Check for a valid ACPI table header
+ *
+ ******************************************************************************/
+
+u8 ap_is_valid_header(struct acpi_table_header *table)
+{
+
+ if (!ACPI_VALIDATE_RSDP_SIG(table->signature)) {
+
+ /* Make sure signature is all ASCII and a valid ACPI name */
+
+ if (!acpi_ut_valid_acpi_name(table->signature)) {
+ fprintf(stderr,
+ "Table signature (0x%8.8X) is invalid\n",
+ *(u32 *)table->signature);
+ return (FALSE);
+ }
+
+ /* Check for minimum table length */
+
+ if (table->length < sizeof(struct acpi_table_header)) {
+ fprintf(stderr, "Table length (0x%8.8X) is invalid\n",
+ table->length);
+ return (FALSE);
+ }
+ }
+
+ return (TRUE);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_is_valid_checksum
+ *
+ * PARAMETERS: table - Pointer to table to be validated
+ *
+ * RETURN: TRUE if the checksum appears to be valid. FALSE otherwise.
+ *
+ * DESCRIPTION: Check for a valid ACPI table checksum.
+ *
+ ******************************************************************************/
+
+u8 ap_is_valid_checksum(struct acpi_table_header *table)
+{
+ acpi_status status;
+ struct acpi_table_rsdp *rsdp;
+
+ if (ACPI_VALIDATE_RSDP_SIG(table->signature)) {
+ /*
+ * Checksum for RSDP.
+ * Note: Other checksums are computed during the table dump.
+ */
+ rsdp = ACPI_CAST_PTR(struct acpi_table_rsdp, table);
+ status = acpi_tb_validate_rsdp(rsdp);
+ } else {
+ status = acpi_tb_verify_checksum(table, table->length);
+ }
+
+ if (ACPI_FAILURE(status)) {
+ fprintf(stderr, "%4.4s: Warning: wrong checksum in table\n",
+ table->signature);
+ }
+
+ return (AE_OK);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_get_table_length
+ *
+ * PARAMETERS: table - Pointer to the table
+ *
+ * RETURN: Table length
+ *
+ * DESCRIPTION: Obtain table length according to table signature.
+ *
+ ******************************************************************************/
+
+u32 ap_get_table_length(struct acpi_table_header *table)
+{
+ struct acpi_table_rsdp *rsdp;
+
+ /* Check if table is valid */
+
+ if (!ap_is_valid_header(table)) {
+ return (0);
+ }
+
+ if (ACPI_VALIDATE_RSDP_SIG(table->signature)) {
+ rsdp = ACPI_CAST_PTR(struct acpi_table_rsdp, table);
+ return (rsdp->length);
+ }
+
+ /* Normal ACPI table */
+
+ return (table->length);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_dump_table_buffer
+ *
+ * PARAMETERS: table - ACPI table to be dumped
+ * instance - ACPI table instance no. to be dumped
+ * address - Physical address of the table
+ *
+ * RETURN: None
+ *
+ * DESCRIPTION: Dump an ACPI table in standard ASCII hex format, with a
+ * header that is compatible with the acpi_xtract utility.
+ *
+ ******************************************************************************/
+
+static int
+ap_dump_table_buffer(struct acpi_table_header *table,
+ u32 instance, acpi_physical_address address)
+{
+ u32 table_length;
+
+ table_length = ap_get_table_length(table);
+
+ /* Print only the header if requested */
+
+ if (gbl_summary_mode) {
+ acpi_tb_print_table_header(address, table);
+ return (0);
+ }
+
+ /* Dump to binary file if requested */
+
+ if (gbl_binary_mode) {
+ return (ap_write_to_binary_file(table, instance));
+ }
+
+ /*
+ * Dump the table with header for use with acpixtract utility.
+ * Note: simplest to just always emit a 64-bit address. acpi_xtract
+ * utility can handle this.
+ */
+ printf("%4.4s @ 0x%8.8X%8.8X\n", table->signature,
+ ACPI_FORMAT_UINT64(address));
+
+ acpi_ut_dump_buffer(ACPI_CAST_PTR(u8, table), table_length,
+ DB_BYTE_DISPLAY, 0);
+ printf("\n");
+ return (0);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_dump_all_tables
+ *
+ * PARAMETERS: None
+ *
+ * RETURN: Status
+ *
+ * DESCRIPTION: Get all tables from the RSDT/XSDT (or at least all of the
+ * tables that we can possibly get).
+ *
+ ******************************************************************************/
+
+int ap_dump_all_tables(void)
+{
+ struct acpi_table_header *table;
+ u32 instance = 0;
+ acpi_physical_address address;
+ acpi_status status;
+ int table_status;
+ u32 i;
+
+ /* Get and dump all available ACPI tables */
+
+ for (i = 0; i < AP_MAX_ACPI_FILES; i++) {
+ status =
+ acpi_os_get_table_by_index(i, &table, &instance, &address);
+ if (ACPI_FAILURE(status)) {
+
+ /* AE_LIMIT means that no more tables are available */
+
+ if (status == AE_LIMIT) {
+ return (0);
+ } else if (i == 0) {
+ fprintf(stderr,
+ "Could not get ACPI tables, %s\n",
+ acpi_format_exception(status));
+ return (-1);
+ } else {
+ fprintf(stderr,
+ "Could not get ACPI table at index %u, %s\n",
+ i, acpi_format_exception(status));
+ continue;
+ }
+ }
+
+ table_status = ap_dump_table_buffer(table, instance, address);
+ free(table);
+
+ if (table_status) {
+ break;
+ }
+ }
+
+ /* Something seriously bad happened if the loop terminates here */
+
+ return (-1);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_dump_table_by_address
+ *
+ * PARAMETERS: ascii_address - Address for requested ACPI table
+ *
+ * RETURN: Status
+ *
+ * DESCRIPTION: Get an ACPI table via a physical address and dump it.
+ *
+ ******************************************************************************/
+
+int ap_dump_table_by_address(char *ascii_address)
+{
+ acpi_physical_address address;
+ struct acpi_table_header *table;
+ acpi_status status;
+ int table_status;
+ u64 long_address;
+
+ /* Convert argument to an integer physical address */
+
+ status = acpi_ut_strtoul64(ascii_address, 0, &long_address);
+ if (ACPI_FAILURE(status)) {
+ fprintf(stderr, "%s: Could not convert to a physical address\n",
+ ascii_address);
+ return (-1);
+ }
+
+ address = (acpi_physical_address) long_address;
+ status = acpi_os_get_table_by_address(address, &table);
+ if (ACPI_FAILURE(status)) {
+ fprintf(stderr, "Could not get table at 0x%8.8X%8.8X, %s\n",
+ ACPI_FORMAT_UINT64(address),
+ acpi_format_exception(status));
+ return (-1);
+ }
+
+ table_status = ap_dump_table_buffer(table, 0, address);
+ free(table);
+ return (table_status);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_dump_table_by_name
+ *
+ * PARAMETERS: signature - Requested ACPI table signature
+ *
+ * RETURN: Status
+ *
+ * DESCRIPTION: Get an ACPI table via a signature and dump it. Handles
+ * multiple tables with the same signature (SSDTs).
+ *
+ ******************************************************************************/
+
+int ap_dump_table_by_name(char *signature)
+{
+ char local_signature[ACPI_NAME_SIZE + 1];
+ u32 instance;
+ struct acpi_table_header *table;
+ acpi_physical_address address;
+ acpi_status status;
+ int table_status;
+
+ if (strlen(signature) != ACPI_NAME_SIZE) {
+ fprintf(stderr,
+ "Invalid table signature [%s]: must be exactly 4 characters\n",
+ signature);
+ return (-1);
+ }
+
+ /* Table signatures are expected to be uppercase */
+
+ strcpy(local_signature, signature);
+ acpi_ut_strupr(local_signature);
+
+ /* To be friendly, handle tables whose signatures do not match the name */
+
+ if (ACPI_COMPARE_NAME(local_signature, "FADT")) {
+ strcpy(local_signature, ACPI_SIG_FADT);
+ } else if (ACPI_COMPARE_NAME(local_signature, "MADT")) {
+ strcpy(local_signature, ACPI_SIG_MADT);
+ }
+
+ /* Dump all instances of this signature (to handle multiple SSDTs) */
+
+ for (instance = 0; instance < AP_MAX_ACPI_FILES; instance++) {
+ status = acpi_os_get_table_by_name(local_signature, instance,
+ &table, &address);
+ if (ACPI_FAILURE(status)) {
+
+ /* AE_LIMIT means that no more tables are available */
+
+ if (status == AE_LIMIT) {
+ return (0);
+ }
+
+ fprintf(stderr,
+ "Could not get ACPI table with signature [%s], %s\n",
+ local_signature, acpi_format_exception(status));
+ return (-1);
+ }
+
+ table_status = ap_dump_table_buffer(table, instance, address);
+ free(table);
+
+ if (table_status) {
+ break;
+ }
+ }
+
+ /* Something seriously bad happened if the loop terminates here */
+
+ return (-1);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_dump_table_from_file
+ *
+ * PARAMETERS: pathname - File containing the binary ACPI table
+ *
+ * RETURN: Status
+ *
+ * DESCRIPTION: Dump an ACPI table from a binary file
+ *
+ ******************************************************************************/
+
+int ap_dump_table_from_file(char *pathname)
+{
+ struct acpi_table_header *table;
+ u32 file_size = 0;
+ int table_status = -1;
+
+ /* Get the entire ACPI table from the file */
+
+ table = ap_get_table_from_file(pathname, &file_size);
+ if (!table) {
+ return (-1);
+ }
+
+ /* File must be at least as long as the table length */
+
+ if (table->length > file_size) {
+ fprintf(stderr,
+ "Table length (0x%X) is too large for input file (0x%X) %s\n",
+ table->length, file_size, pathname);
+ goto exit;
+ }
+
+ if (gbl_verbose_mode) {
+ fprintf(stderr,
+ "Input file: %s contains table [%4.4s], 0x%X (%u) bytes\n",
+ pathname, table->signature, file_size, file_size);
+ }
+
+ table_status = ap_dump_table_buffer(table, 0, 0);
+
+exit:
+ free(table);
+ return (table_status);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: acpi_os* print functions
+ *
+ * DESCRIPTION: Used for linkage with ACPICA modules
+ *
+ ******************************************************************************/
+
+void ACPI_INTERNAL_VAR_XFACE acpi_os_printf(const char *fmt, ...)
+{
+ va_list args;
+
+ va_start(args, fmt);
+ vfprintf(stdout, fmt, args);
+ va_end(args);
+}
+
+void acpi_os_vprintf(const char *fmt, va_list args)
+{
+ vfprintf(stdout, fmt, args);
+}
diff --git a/tools/power/acpi/tools/acpidump/apfiles.c b/tools/power/acpi/tools/acpidump/apfiles.c
new file mode 100644
index 00000000000..4488accc010
--- /dev/null
+++ b/tools/power/acpi/tools/acpidump/apfiles.c
@@ -0,0 +1,228 @@
+/******************************************************************************
+ *
+ * Module Name: apfiles - File-related functions for acpidump utility
+ *
+ *****************************************************************************/
+
+/*
+ * Copyright (C) 2000 - 2014, Intel Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ * 3. Neither the names of the above-listed copyright holders nor the names
+ * of any contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ */
+
+#include "acpidump.h"
+#include "acapps.h"
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_open_output_file
+ *
+ * PARAMETERS: pathname - Output filename
+ *
+ * RETURN: Open file handle
+ *
+ * DESCRIPTION: Open a text output file for acpidump. Checks if file already
+ * exists.
+ *
+ ******************************************************************************/
+
+int ap_open_output_file(char *pathname)
+{
+ struct stat stat_info;
+ FILE *file;
+
+ /* If file exists, prompt for overwrite */
+
+ if (!stat(pathname, &stat_info)) {
+ fprintf(stderr,
+ "Target path already exists, overwrite? [y|n] ");
+
+ if (getchar() != 'y') {
+ return (-1);
+ }
+ }
+
+ /* Point stdout to the file */
+
+ file = freopen(pathname, "w", stdout);
+ if (!file) {
+ perror("Could not open output file");
+ return (-1);
+ }
+
+ /* Save the file and path */
+
+ gbl_output_file = file;
+ gbl_output_filename = pathname;
+ return (0);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_write_to_binary_file
+ *
+ * PARAMETERS: table - ACPI table to be written
+ * instance - ACPI table instance no. to be written
+ *
+ * RETURN: Status
+ *
+ * DESCRIPTION: Write an ACPI table to a binary file. Builds the output
+ * filename from the table signature.
+ *
+ ******************************************************************************/
+
+int ap_write_to_binary_file(struct acpi_table_header *table, u32 instance)
+{
+ char filename[ACPI_NAME_SIZE + 16];
+ char instance_str[16];
+ FILE *file;
+ size_t actual;
+ u32 table_length;
+
+ /* Obtain table length */
+
+ table_length = ap_get_table_length(table);
+
+ /* Construct lower-case filename from the table local signature */
+
+ if (ACPI_VALIDATE_RSDP_SIG(table->signature)) {
+ ACPI_MOVE_NAME(filename, ACPI_RSDP_NAME);
+ } else {
+ ACPI_MOVE_NAME(filename, table->signature);
+ }
+ filename[0] = (char)ACPI_TOLOWER(filename[0]);
+ filename[1] = (char)ACPI_TOLOWER(filename[1]);
+ filename[2] = (char)ACPI_TOLOWER(filename[2]);
+ filename[3] = (char)ACPI_TOLOWER(filename[3]);
+ filename[ACPI_NAME_SIZE] = 0;
+
+ /* Handle multiple SSDts - create different filenames for each */
+
+ if (instance > 0) {
+ sprintf(instance_str, "%u", instance);
+ strcat(filename, instance_str);
+ }
+
+ strcat(filename, ACPI_TABLE_FILE_SUFFIX);
+
+ if (gbl_verbose_mode) {
+ fprintf(stderr,
+ "Writing [%4.4s] to binary file: %s 0x%X (%u) bytes\n",
+ table->signature, filename, table->length,
+ table->length);
+ }
+
+ /* Open the file and dump the entire table in binary mode */
+
+ file = fopen(filename, "wb");
+ if (!file) {
+ perror("Could not open output file");
+ return (-1);
+ }
+
+ actual = fwrite(table, 1, table_length, file);
+ if (actual != table_length) {
+ perror("Error writing binary output file");
+ fclose(file);
+ return (-1);
+ }
+
+ fclose(file);
+ return (0);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_get_table_from_file
+ *
+ * PARAMETERS: pathname - File containing the binary ACPI table
+ * out_file_size - Where the file size is returned
+ *
+ * RETURN: Buffer containing the ACPI table. NULL on error.
+ *
+ * DESCRIPTION: Open a file and read it entirely into a new buffer
+ *
+ ******************************************************************************/
+
+struct acpi_table_header *ap_get_table_from_file(char *pathname,
+ u32 *out_file_size)
+{
+ struct acpi_table_header *buffer = NULL;
+ FILE *file;
+ u32 file_size;
+ size_t actual;
+
+ /* Must use binary mode */
+
+ file = fopen(pathname, "rb");
+ if (!file) {
+ perror("Could not open input file");
+ return (NULL);
+ }
+
+ /* Need file size to allocate a buffer */
+
+ file_size = cm_get_file_size(file);
+ if (file_size == ACPI_UINT32_MAX) {
+ fprintf(stderr,
+ "Could not get input file size: %s\n", pathname);
+ goto cleanup;
+ }
+
+ /* Allocate a buffer for the entire file */
+
+ buffer = calloc(1, file_size);
+ if (!buffer) {
+ fprintf(stderr,
+ "Could not allocate file buffer of size: %u\n",
+ file_size);
+ goto cleanup;
+ }
+
+ /* Read the entire file */
+
+ actual = fread(buffer, 1, file_size, file);
+ if (actual != file_size) {
+ fprintf(stderr, "Could not read input file: %s\n", pathname);
+ free(buffer);
+ buffer = NULL;
+ goto cleanup;
+ }
+
+ *out_file_size = file_size;
+
+cleanup:
+ fclose(file);
+ return (buffer);
+}
diff --git a/tools/power/acpi/tools/acpidump/apmain.c b/tools/power/acpi/tools/acpidump/apmain.c
new file mode 100644
index 00000000000..51e8d638db1
--- /dev/null
+++ b/tools/power/acpi/tools/acpidump/apmain.c
@@ -0,0 +1,351 @@
+/******************************************************************************
+ *
+ * Module Name: apmain - Main module for the acpidump utility
+ *
+ *****************************************************************************/
+
+/*
+ * Copyright (C) 2000 - 2014, Intel Corp.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions, and the following disclaimer,
+ * without modification.
+ * 2. Redistributions in binary form must reproduce at minimum a disclaimer
+ * substantially similar to the "NO WARRANTY" disclaimer below
+ * ("Disclaimer") and any redistribution must be conditioned upon
+ * including a substantially similar Disclaimer requirement for further
+ * binary redistribution.
+ * 3. Neither the names of the above-listed copyright holders nor the names
+ * of any contributors may be used to endorse or promote products derived
+ * from this software without specific prior written permission.
+ *
+ * Alternatively, this software may be distributed under the terms of the
+ * GNU General Public License ("GPL") version 2 as published by the Free
+ * Software Foundation.
+ *
+ * NO WARRANTY
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
+ * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGES.
+ */
+
+#define _DECLARE_GLOBALS
+#include "acpidump.h"
+#include "acapps.h"
+
+/*
+ * acpidump - A portable utility for obtaining system ACPI tables and dumping
+ * them in an ASCII hex format suitable for binary extraction via acpixtract.
+ *
+ * Obtaining the system ACPI tables is an OS-specific operation.
+ *
+ * This utility can be ported to any host operating system by providing a
+ * module containing system-specific versions of these interfaces:
+ *
+ * acpi_os_get_table_by_address
+ * acpi_os_get_table_by_index
+ * acpi_os_get_table_by_name
+ *
+ * See the ACPICA Reference Guide for the exact definitions of these
+ * interfaces. Also, see these ACPICA source code modules for example
+ * implementations:
+ *
+ * source/os_specific/service_layers/oswintbl.c
+ * source/os_specific/service_layers/oslinuxtbl.c
+ */
+
+/* Local prototypes */
+
+static void ap_display_usage(void);
+
+static int ap_do_options(int argc, char **argv);
+
+static void ap_insert_action(char *argument, u32 to_be_done);
+
+/* Table for deferred actions from command line options */
+
+struct ap_dump_action action_table[AP_MAX_ACTIONS];
+u32 current_action = 0;
+
+#define AP_UTILITY_NAME "ACPI Binary Table Dump Utility"
+#define AP_SUPPORTED_OPTIONS "?a:bcf:hn:o:r:svxz"
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_display_usage
+ *
+ * DESCRIPTION: Usage message for the acpi_dump utility
+ *
+ ******************************************************************************/
+
+static void ap_display_usage(void)
+{
+
+ ACPI_USAGE_HEADER("acpidump [options]");
+
+ ACPI_OPTION("-b", "Dump tables to binary files");
+ ACPI_OPTION("-c", "Dump customized tables");
+ ACPI_OPTION("-h -?", "This help message");
+ ACPI_OPTION("-o <File>", "Redirect output to file");
+ ACPI_OPTION("-r <Address>", "Dump tables from specified RSDP");
+ ACPI_OPTION("-s", "Print table summaries only");
+ ACPI_OPTION("-v", "Display version information");
+ ACPI_OPTION("-z", "Verbose mode");
+
+ printf("\nTable Options:\n");
+
+ ACPI_OPTION("-a <Address>", "Get table via a physical address");
+ ACPI_OPTION("-f <BinaryFile>", "Get table via a binary file");
+ ACPI_OPTION("-n <Signature>", "Get table via a name/signature");
+ ACPI_OPTION("-x", "Do not use but dump XSDT");
+ ACPI_OPTION("-x -x", "Do not use or dump XSDT");
+
+ printf("\n"
+ "Invocation without parameters dumps all available tables\n"
+ "Multiple mixed instances of -a, -f, and -n are supported\n\n");
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_insert_action
+ *
+ * PARAMETERS: argument - Pointer to the argument for this action
+ * to_be_done - What to do to process this action
+ *
+ * RETURN: None. Exits program if action table becomes full.
+ *
+ * DESCRIPTION: Add an action item to the action table
+ *
+ ******************************************************************************/
+
+static void ap_insert_action(char *argument, u32 to_be_done)
+{
+
+ /* Insert action and check for table overflow */
+
+ action_table[current_action].argument = argument;
+ action_table[current_action].to_be_done = to_be_done;
+
+ current_action++;
+ if (current_action > AP_MAX_ACTIONS) {
+ fprintf(stderr, "Too many table options (max %u)\n",
+ AP_MAX_ACTIONS);
+ exit(-1);
+ }
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: ap_do_options
+ *
+ * PARAMETERS: argc/argv - Standard argc/argv
+ *
+ * RETURN: Status
+ *
+ * DESCRIPTION: Command line option processing. The main actions for getting
+ * and dumping tables are deferred via the action table.
+ *
+ *****************************************************************************/
+
+static int ap_do_options(int argc, char **argv)
+{
+ int j;
+ acpi_status status;
+
+ /* Command line options */
+
+ while ((j = acpi_getopt(argc, argv, AP_SUPPORTED_OPTIONS)) != EOF)
+ switch (j) {
+ /*
+ * Global options
+ */
+ case 'b': /* Dump all input tables to binary files */
+
+ gbl_binary_mode = TRUE;
+ continue;
+
+ case 'c': /* Dump customized tables */
+
+ gbl_dump_customized_tables = TRUE;
+ continue;
+
+ case 'h':
+ case '?':
+
+ ap_display_usage();
+ exit(0);
+
+ case 'o': /* Redirect output to a single file */
+
+ if (ap_open_output_file(acpi_gbl_optarg)) {
+ exit(-1);
+ }
+ continue;
+
+ case 'r': /* Dump tables from specified RSDP */
+
+ status =
+ acpi_ut_strtoul64(acpi_gbl_optarg, 0,
+ &gbl_rsdp_base);
+ if (ACPI_FAILURE(status)) {
+ fprintf(stderr,
+ "%s: Could not convert to a physical address\n",
+ acpi_gbl_optarg);
+ exit(-1);
+ }
+ continue;
+
+ case 's': /* Print table summaries only */
+
+ gbl_summary_mode = TRUE;
+ continue;
+
+ case 'x': /* Do not use XSDT */
+
+ if (!acpi_gbl_do_not_use_xsdt) {
+ acpi_gbl_do_not_use_xsdt = TRUE;
+ } else {
+ gbl_do_not_dump_xsdt = TRUE;
+ }
+ continue;
+
+ case 'v': /* Revision/version */
+
+ printf(ACPI_COMMON_SIGNON(AP_UTILITY_NAME));
+ exit(0);
+
+ case 'z': /* Verbose mode */
+
+ gbl_verbose_mode = TRUE;
+ fprintf(stderr, ACPI_COMMON_SIGNON(AP_UTILITY_NAME));
+ continue;
+
+ /*
+ * Table options
+ */
+ case 'a': /* Get table by physical address */
+
+ ap_insert_action(acpi_gbl_optarg,
+ AP_DUMP_TABLE_BY_ADDRESS);
+ break;
+
+ case 'f': /* Get table from a file */
+
+ ap_insert_action(acpi_gbl_optarg,
+ AP_DUMP_TABLE_BY_FILE);
+ break;
+
+ case 'n': /* Get table by input name (signature) */
+
+ ap_insert_action(acpi_gbl_optarg,
+ AP_DUMP_TABLE_BY_NAME);
+ break;
+
+ default:
+
+ ap_display_usage();
+ exit(-1);
+ }
+
+ /* If there are no actions, this means "get/dump all tables" */
+
+ if (current_action == 0) {
+ ap_insert_action(NULL, AP_DUMP_ALL_TABLES);
+ }
+
+ return (0);
+}
+
+/******************************************************************************
+ *
+ * FUNCTION: main
+ *
+ * PARAMETERS: argc/argv - Standard argc/argv
+ *
+ * RETURN: Status
+ *
+ * DESCRIPTION: C main function for acpidump utility
+ *
+ ******************************************************************************/
+
+int ACPI_SYSTEM_XFACE main(int argc, char *argv[])
+{
+ int status = 0;
+ struct ap_dump_action *action;
+ u32 file_size;
+ u32 i;
+
+ ACPI_DEBUG_INITIALIZE(); /* For debug version only */
+
+ /* Process command line options */
+
+ if (ap_do_options(argc, argv)) {
+ return (-1);
+ }
+
+ /* Get/dump ACPI table(s) as requested */
+
+ for (i = 0; i < current_action; i++) {
+ action = &action_table[i];
+ switch (action->to_be_done) {
+ case AP_DUMP_ALL_TABLES:
+
+ status = ap_dump_all_tables();
+ break;
+
+ case AP_DUMP_TABLE_BY_ADDRESS:
+
+ status = ap_dump_table_by_address(action->argument);
+ break;
+
+ case AP_DUMP_TABLE_BY_NAME:
+
+ status = ap_dump_table_by_name(action->argument);
+ break;
+
+ case AP_DUMP_TABLE_BY_FILE:
+
+ status = ap_dump_table_from_file(action->argument);
+ break;
+
+ default:
+
+ fprintf(stderr,
+ "Internal error, invalid action: 0x%X\n",
+ action->to_be_done);
+ return (-1);
+ }
+
+ if (status) {
+ return (status);
+ }
+ }
+
+ if (gbl_output_file) {
+ if (gbl_verbose_mode) {
+
+ /* Summary for the output file */
+
+ file_size = cm_get_file_size(gbl_output_file);
+ fprintf(stderr,
+ "Output file %s contains 0x%X (%u) bytes\n\n",
+ gbl_output_filename, file_size, file_size);
+ }
+
+ fclose(gbl_output_file);
+ }
+
+ return (status);
+}
diff --git a/tools/power/acpi/tools/ec/Makefile b/tools/power/acpi/tools/ec/Makefile
new file mode 100644
index 00000000000..b7b0b929bd3
--- /dev/null
+++ b/tools/power/acpi/tools/ec/Makefile
@@ -0,0 +1,22 @@
+ec_access: ec_access.o
+ $(ECHO) " LD " $@
+ $(QUIET) $(LD) $(CFLAGS) $(LDFLAGS) $< -o $@
+ $(QUIET) $(STRIPCMD) $@
+
+%.o: %.c
+ $(ECHO) " CC " $@
+ $(QUIET) $(CC) -c $(CFLAGS) -o $@ $<
+
+all: ec_access
+
+install:
+ $(INSTALL) -d $(DESTDIR)${sbindir}
+ $(INSTALL_PROGRAM) ec_access $(DESTDIR)${sbindir}
+
+uninstall:
+ - rm -f $(DESTDIR)${sbindir}/ec_access
+
+clean:
+ -rm -f $(OUTPUT)ec_access
+
+.PHONY: all install uninstall
diff --git a/tools/power/acpi/tools/ec/ec_access.c b/tools/power/acpi/tools/ec/ec_access.c
new file mode 100644
index 00000000000..6b8aaed44f2
--- /dev/null
+++ b/tools/power/acpi/tools/ec/ec_access.c
@@ -0,0 +1,238 @@
+/*
+ * ec_access.c
+ *
+ * Copyright (C) 2010 SUSE Linux Products GmbH
+ * Author:
+ * Thomas Renninger <trenn@suse.de>
+ *
+ * This work is licensed under the terms of the GNU GPL, version 2.
+ */
+
+#include <fcntl.h>
+#include <err.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+
+#define EC_SPACE_SIZE 256
+#define SYSFS_PATH "/sys/kernel/debug/ec/ec0/io"
+
+/* TBD/Enhancements:
+ - Provide param for accessing different ECs (not supported by kernel yet)
+*/
+
+static int read_mode = -1;
+static int sleep_time;
+static int write_byte_offset = -1;
+static int read_byte_offset = -1;
+static uint8_t write_value = -1;
+
+void usage(char progname[], int exit_status)
+{
+ printf("Usage:\n");
+ printf("1) %s -r [-s sleep]\n", basename(progname));
+ printf("2) %s -b byte_offset\n", basename(progname));
+ printf("3) %s -w byte_offset -v value\n\n", basename(progname));
+
+ puts("\t-r [-s sleep] : Dump EC registers");
+ puts("\t If sleep is given, sleep x seconds,");
+ puts("\t re-read EC registers and show changes");
+ puts("\t-b offset : Read value at byte_offset (in hex)");
+ puts("\t-w offset -v value : Write value at byte_offset");
+ puts("\t-h : Print this help\n\n");
+ puts("Offsets and values are in hexadecimal number sytem.");
+ puts("The offset and value must be between 0 and 0xff.");
+ exit(exit_status);
+}
+
+void parse_opts(int argc, char *argv[])
+{
+ int c;
+
+ while ((c = getopt(argc, argv, "rs:b:w:v:h")) != -1) {
+
+ switch (c) {
+ case 'r':
+ if (read_mode != -1)
+ usage(argv[0], EXIT_FAILURE);
+ read_mode = 1;
+ break;
+ case 's':
+ if (read_mode != -1 && read_mode != 1)
+ usage(argv[0], EXIT_FAILURE);
+
+ sleep_time = atoi(optarg);
+ if (sleep_time <= 0) {
+ sleep_time = 0;
+ usage(argv[0], EXIT_FAILURE);
+ printf("Bad sleep time: %s\n", optarg);
+ }
+ break;
+ case 'b':
+ if (read_mode != -1)
+ usage(argv[0], EXIT_FAILURE);
+ read_mode = 1;
+ read_byte_offset = strtoul(optarg, NULL, 16);
+ break;
+ case 'w':
+ if (read_mode != -1)
+ usage(argv[0], EXIT_FAILURE);
+ read_mode = 0;
+ write_byte_offset = strtoul(optarg, NULL, 16);
+ break;
+ case 'v':
+ write_value = strtoul(optarg, NULL, 16);
+ break;
+ case 'h':
+ usage(argv[0], EXIT_SUCCESS);
+ default:
+ fprintf(stderr, "Unknown option!\n");
+ usage(argv[0], EXIT_FAILURE);
+ }
+ }
+ if (read_mode == 0) {
+ if (write_byte_offset < 0 ||
+ write_byte_offset >= EC_SPACE_SIZE) {
+ fprintf(stderr, "Wrong byte offset 0x%.2x, valid: "
+ "[0-0x%.2x]\n",
+ write_byte_offset, EC_SPACE_SIZE - 1);
+ usage(argv[0], EXIT_FAILURE);
+ }
+ if (write_value < 0 ||
+ write_value >= 255) {
+ fprintf(stderr, "Wrong byte offset 0x%.2x, valid:"
+ "[0-0xff]\n", write_byte_offset);
+ usage(argv[0], EXIT_FAILURE);
+ }
+ }
+ if (read_mode == 1 && read_byte_offset != -1) {
+ if (read_byte_offset < -1 ||
+ read_byte_offset >= EC_SPACE_SIZE) {
+ fprintf(stderr, "Wrong byte offset 0x%.2x, valid: "
+ "[0-0x%.2x]\n",
+ read_byte_offset, EC_SPACE_SIZE - 1);
+ usage(argv[0], EXIT_FAILURE);
+ }
+ }
+ /* Add additional parameter checks here */
+}
+
+void dump_ec(int fd)
+{
+ char buf[EC_SPACE_SIZE];
+ char buf2[EC_SPACE_SIZE];
+ int byte_off, bytes_read;
+
+ bytes_read = read(fd, buf, EC_SPACE_SIZE);
+
+ if (bytes_read == -1)
+ err(EXIT_FAILURE, "Could not read from %s\n", SYSFS_PATH);
+
+ if (bytes_read != EC_SPACE_SIZE)
+ fprintf(stderr, "Could only read %d bytes\n", bytes_read);
+
+ printf(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
+ for (byte_off = 0; byte_off < bytes_read; byte_off++) {
+ if ((byte_off % 16) == 0)
+ printf("\n%.2X: ", byte_off);
+ printf(" %.2x ", (uint8_t)buf[byte_off]);
+ }
+ printf("\n");
+
+ if (!sleep_time)
+ return;
+
+ printf("\n");
+ lseek(fd, 0, SEEK_SET);
+ sleep(sleep_time);
+
+ bytes_read = read(fd, buf2, EC_SPACE_SIZE);
+
+ if (bytes_read == -1)
+ err(EXIT_FAILURE, "Could not read from %s\n", SYSFS_PATH);
+
+ if (bytes_read != EC_SPACE_SIZE)
+ fprintf(stderr, "Could only read %d bytes\n", bytes_read);
+
+ printf(" 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F");
+ for (byte_off = 0; byte_off < bytes_read; byte_off++) {
+ if ((byte_off % 16) == 0)
+ printf("\n%.2X: ", byte_off);
+
+ if (buf[byte_off] == buf2[byte_off])
+ printf(" %.2x ", (uint8_t)buf2[byte_off]);
+ else
+ printf("*%.2x ", (uint8_t)buf2[byte_off]);
+ }
+ printf("\n");
+}
+
+void read_ec_val(int fd, int byte_offset)
+{
+ uint8_t buf;
+ int error;
+
+ error = lseek(fd, byte_offset, SEEK_SET);
+ if (error != byte_offset)
+ err(EXIT_FAILURE, "Cannot set offset to 0x%.2x", byte_offset);
+
+ error = read(fd, &buf, 1);
+ if (error != 1)
+ err(EXIT_FAILURE, "Could not read byte 0x%.2x from %s\n",
+ byte_offset, SYSFS_PATH);
+ printf("0x%.2x\n", buf);
+ return;
+}
+
+void write_ec_val(int fd, int byte_offset, uint8_t value)
+{
+ int error;
+
+ error = lseek(fd, byte_offset, SEEK_SET);
+ if (error != byte_offset)
+ err(EXIT_FAILURE, "Cannot set offset to 0x%.2x", byte_offset);
+
+ error = write(fd, &value, 1);
+ if (error != 1)
+ err(EXIT_FAILURE, "Cannot write value 0x%.2x to offset 0x%.2x",
+ value, byte_offset);
+}
+
+int main(int argc, char *argv[])
+{
+ int file_mode = O_RDONLY;
+ int fd;
+
+ parse_opts(argc, argv);
+
+ if (read_mode == 0)
+ file_mode = O_WRONLY;
+ else if (read_mode == 1)
+ file_mode = O_RDONLY;
+ else
+ usage(argv[0], EXIT_FAILURE);
+
+ fd = open(SYSFS_PATH, file_mode);
+ if (fd == -1)
+ err(EXIT_FAILURE, "%s", SYSFS_PATH);
+
+ if (read_mode)
+ if (read_byte_offset == -1)
+ dump_ec(fd);
+ else if (read_byte_offset < 0 ||
+ read_byte_offset >= EC_SPACE_SIZE)
+ usage(argv[0], EXIT_FAILURE);
+ else
+ read_ec_val(fd, read_byte_offset);
+ else
+ write_ec_val(fd, write_byte_offset, write_value);
+ close(fd);
+
+ exit(EXIT_SUCCESS);
+}
diff --git a/tools/power/cpupower/.gitignore b/tools/power/cpupower/.gitignore
new file mode 100644
index 00000000000..d42073f1260
--- /dev/null
+++ b/tools/power/cpupower/.gitignore
@@ -0,0 +1,29 @@
+.libs
+libcpupower.so
+libcpupower.so.0
+libcpupower.so.0.0.0
+build/ccdv
+cpufreq-info
+cpufreq-set
+cpufreq-aperf
+lib/.libs
+lib/cpufreq.lo
+lib/cpufreq.o
+lib/proc.lo
+lib/proc.o
+lib/sysfs.lo
+lib/sysfs.o
+po/cpupowerutils.pot
+po/*.gmo
+utils/cpufreq-info.o
+utils/cpufreq-set.o
+utils/cpufreq-aperf.o
+cpupower
+bench/cpufreq-bench
+debug/kernel/Module.symvers
+debug/i386/centrino-decode
+debug/i386/dump_psb
+debug/i386/intel_gsic
+debug/i386/powernow-k8-decode
+debug/x86_64/centrino-decode
+debug/x86_64/powernow-k8-decode
diff --git a/tools/power/cpupower/Makefile b/tools/power/cpupower/Makefile
new file mode 100644
index 00000000000..2e2ba2efa0d
--- /dev/null
+++ b/tools/power/cpupower/Makefile
@@ -0,0 +1,310 @@
+# Makefile for cpupower
+#
+# Copyright (C) 2005,2006 Dominik Brodowski <linux@dominikbrodowski.net>
+#
+# Based largely on the Makefile for udev by:
+#
+# Copyright (C) 2003,2004 Greg Kroah-Hartman <greg@kroah.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; version 2 of the License.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+#
+OUTPUT=./
+ifeq ("$(origin O)", "command line")
+ OUTPUT := $(O)/
+endif
+
+ifneq ($(OUTPUT),)
+# check that the output directory actually exists
+OUTDIR := $(shell cd $(OUTPUT) && /bin/pwd)
+$(if $(OUTDIR),, $(error output directory "$(OUTPUT)" does not exist))
+endif
+
+# --- CONFIGURATION BEGIN ---
+
+# Set the following to `true' to make a unstripped, unoptimized
+# binary. Leave this set to `false' for production use.
+DEBUG ?= true
+
+# make the build silent. Set this to something else to make it noisy again.
+V ?= false
+
+# Internationalization support (output in different languages).
+# Requires gettext.
+NLS ?= true
+
+# Set the following to 'true' to build/install the
+# cpufreq-bench benchmarking tool
+CPUFREQ_BENCH ?= true
+
+# Prefix to the directories we're installing to
+DESTDIR ?=
+
+# --- CONFIGURATION END ---
+
+
+
+# Package-related definitions. Distributions can modify the version
+# and _should_ modify the PACKAGE_BUGREPORT definition
+
+VERSION= $(shell ./utils/version-gen.sh)
+LIB_MAJ= 0.0.0
+LIB_MIN= 0
+
+PACKAGE = cpupower
+PACKAGE_BUGREPORT = linux-pm@vger.kernel.org
+LANGUAGES = de fr it cs pt
+
+
+# Directory definitions. These are default and most probably
+# do not need to be changed. Please note that DESTDIR is
+# added in front of any of them
+
+bindir ?= /usr/bin
+sbindir ?= /usr/sbin
+mandir ?= /usr/man
+includedir ?= /usr/include
+libdir ?= /usr/lib
+localedir ?= /usr/share/locale
+docdir ?= /usr/share/doc/packages/cpupower
+confdir ?= /etc/
+
+# Toolchain: what tools do we use, and what options do they need:
+
+CP = cp -fpR
+INSTALL = /usr/bin/install -c
+INSTALL_PROGRAM = ${INSTALL}
+INSTALL_DATA = ${INSTALL} -m 644
+INSTALL_SCRIPT = ${INSTALL_PROGRAM}
+
+# If you are running a cross compiler, you may want to set this
+# to something more interesting, like "arm-linux-". If you want
+# to compile vs uClibc, that can be done here as well.
+CROSS = #/usr/i386-linux-uclibc/usr/bin/i386-uclibc-
+CC = $(CROSS)gcc
+LD = $(CROSS)gcc
+AR = $(CROSS)ar
+STRIP = $(CROSS)strip
+RANLIB = $(CROSS)ranlib
+HOSTCC = gcc
+MKDIR = mkdir
+
+
+# Now we set up the build system
+#
+
+# set up PWD so that older versions of make will work with our build.
+PWD = $(shell pwd)
+
+GMO_FILES = ${shell for HLANG in ${LANGUAGES}; do echo $(OUTPUT)po/$$HLANG.gmo; done;}
+
+export CROSS CC AR STRIP RANLIB CFLAGS LDFLAGS LIB_OBJS
+
+# check if compiler option is supported
+cc-supports = ${shell if $(CC) ${1} -S -o /dev/null -x c /dev/null > /dev/null 2>&1; then echo "$(1)"; fi;}
+
+# use '-Os' optimization if available, else use -O2
+OPTIMIZATION := $(call cc-supports,-Os,-O2)
+
+WARNINGS := -Wall -Wchar-subscripts -Wpointer-arith -Wsign-compare
+WARNINGS += $(call cc-supports,-Wno-pointer-sign)
+WARNINGS += $(call cc-supports,-Wdeclaration-after-statement)
+WARNINGS += -Wshadow
+
+CFLAGS += -DVERSION=\"$(VERSION)\" -DPACKAGE=\"$(PACKAGE)\" \
+ -DPACKAGE_BUGREPORT=\"$(PACKAGE_BUGREPORT)\" -D_GNU_SOURCE
+
+UTIL_OBJS = utils/helpers/amd.o utils/helpers/topology.o utils/helpers/msr.o \
+ utils/helpers/sysfs.o utils/helpers/misc.o utils/helpers/cpuid.o \
+ utils/helpers/pci.o utils/helpers/bitmask.o \
+ utils/idle_monitor/nhm_idle.o utils/idle_monitor/snb_idle.o \
+ utils/idle_monitor/hsw_ext_idle.o \
+ utils/idle_monitor/amd_fam14h_idle.o utils/idle_monitor/cpuidle_sysfs.o \
+ utils/idle_monitor/mperf_monitor.o utils/idle_monitor/cpupower-monitor.o \
+ utils/cpupower.o utils/cpufreq-info.o utils/cpufreq-set.o \
+ utils/cpupower-set.o utils/cpupower-info.o utils/cpuidle-info.o \
+ utils/cpuidle-set.o
+
+UTIL_SRC := $(UTIL_OBJS:.o=.c)
+
+UTIL_OBJS := $(addprefix $(OUTPUT),$(UTIL_OBJS))
+
+UTIL_HEADERS = utils/helpers/helpers.h utils/idle_monitor/cpupower-monitor.h \
+ utils/helpers/bitmask.h \
+ utils/idle_monitor/idle_monitors.h utils/idle_monitor/idle_monitors.def
+
+LIB_HEADERS = lib/cpufreq.h lib/sysfs.h
+LIB_SRC = lib/cpufreq.c lib/sysfs.c
+LIB_OBJS = lib/cpufreq.o lib/sysfs.o
+LIB_OBJS := $(addprefix $(OUTPUT),$(LIB_OBJS))
+
+CFLAGS += -pipe
+
+ifeq ($(strip $(NLS)),true)
+ INSTALL_NLS += install-gmo
+ COMPILE_NLS += create-gmo
+ CFLAGS += -DNLS
+endif
+
+ifeq ($(strip $(CPUFREQ_BENCH)),true)
+ INSTALL_BENCH += install-bench
+ COMPILE_BENCH += compile-bench
+endif
+
+CFLAGS += $(WARNINGS)
+
+ifeq ($(strip $(V)),false)
+ QUIET=@
+ ECHO=@echo
+else
+ QUIET=
+ ECHO=@\#
+endif
+export QUIET ECHO
+
+# if DEBUG is enabled, then we do not strip or optimize
+ifeq ($(strip $(DEBUG)),true)
+ CFLAGS += -O1 -g -DDEBUG
+ STRIPCMD = /bin/true -Since_we_are_debugging
+else
+ CFLAGS += $(OPTIMIZATION) -fomit-frame-pointer
+ STRIPCMD = $(STRIP) -s --remove-section=.note --remove-section=.comment
+endif
+
+
+# the actual make rules
+
+all: libcpupower $(OUTPUT)cpupower $(COMPILE_NLS) $(COMPILE_BENCH)
+
+$(OUTPUT)lib/%.o: $(LIB_SRC) $(LIB_HEADERS)
+ $(ECHO) " CC " $@
+ $(QUIET) $(CC) $(CFLAGS) -fPIC -o $@ -c lib/$*.c
+
+$(OUTPUT)libcpupower.so.$(LIB_MAJ): $(LIB_OBJS)
+ $(ECHO) " LD " $@
+ $(QUIET) $(CC) -shared $(CFLAGS) $(LDFLAGS) -o $@ \
+ -Wl,-soname,libcpupower.so.$(LIB_MIN) $(LIB_OBJS)
+ @ln -sf $(@F) $(OUTPUT)libcpupower.so
+ @ln -sf $(@F) $(OUTPUT)libcpupower.so.$(LIB_MIN)
+
+libcpupower: $(OUTPUT)libcpupower.so.$(LIB_MAJ)
+
+# Let all .o files depend on its .c file and all headers
+# Might be worth to put this into utils/Makefile at some point of time
+$(UTIL_OBJS): $(UTIL_HEADERS)
+
+$(OUTPUT)%.o: %.c
+ $(ECHO) " CC " $@
+ $(QUIET) $(CC) $(CFLAGS) -I./lib -I ./utils -o $@ -c $*.c
+
+$(OUTPUT)cpupower: $(UTIL_OBJS) $(OUTPUT)libcpupower.so.$(LIB_MAJ)
+ $(ECHO) " CC " $@
+ $(QUIET) $(CC) $(CFLAGS) $(LDFLAGS) $(UTIL_OBJS) -lcpupower -lrt -lpci -L$(OUTPUT) -o $@
+ $(QUIET) $(STRIPCMD) $@
+
+$(OUTPUT)po/$(PACKAGE).pot: $(UTIL_SRC)
+ $(ECHO) " GETTEXT " $@
+ $(QUIET) xgettext --default-domain=$(PACKAGE) --add-comments \
+ --keyword=_ --keyword=N_ $(UTIL_SRC) -p $(@D) -o $(@F)
+
+$(OUTPUT)po/%.gmo: po/%.po
+ $(ECHO) " MSGFMT " $@
+ $(QUIET) msgfmt -o $@ po/$*.po
+
+create-gmo: ${GMO_FILES}
+
+update-po: $(OUTPUT)po/$(PACKAGE).pot
+ $(ECHO) " MSGMRG " $@
+ $(QUIET) @for HLANG in $(LANGUAGES); do \
+ echo -n "Updating $$HLANG "; \
+ if msgmerge po/$$HLANG.po $< -o \
+ $(OUTPUT)po/$$HLANG.new.po; then \
+ mv -f $(OUTPUT)po/$$HLANG.new.po $(OUTPUT)po/$$HLANG.po; \
+ else \
+ echo "msgmerge for $$HLANG failed!"; \
+ rm -f $(OUTPUT)po/$$HLANG.new.po; \
+ fi; \
+ done;
+
+compile-bench: $(OUTPUT)libcpupower.so.$(LIB_MAJ)
+ @V=$(V) confdir=$(confdir) $(MAKE) -C bench O=$(OUTPUT)
+
+# we compile into subdirectories. if the target directory is not the
+# source directory, they might not exists. So we depend the various
+# files onto their directories.
+DIRECTORY_DEPS = $(LIB_OBJS) $(UTIL_OBJS) $(GMO_FILES)
+$(DIRECTORY_DEPS): | $(sort $(dir $(DIRECTORY_DEPS)))
+
+# In the second step, we make a rule to actually create these directories
+$(sort $(dir $(DIRECTORY_DEPS))):
+ $(ECHO) " MKDIR " $@
+ $(QUIET) $(MKDIR) -p $@ 2>/dev/null
+
+clean:
+ -find $(OUTPUT) \( -not -type d \) -and \( -name '*~' -o -name '*.[oas]' \) -type f -print \
+ | xargs rm -f
+ -rm -f $(OUTPUT)cpupower
+ -rm -f $(OUTPUT)libcpupower.so*
+ -rm -rf $(OUTPUT)po/*.gmo
+ -rm -rf $(OUTPUT)po/*.pot
+ $(MAKE) -C bench O=$(OUTPUT) clean
+
+
+install-lib:
+ $(INSTALL) -d $(DESTDIR)${libdir}
+ $(CP) $(OUTPUT)libcpupower.so* $(DESTDIR)${libdir}/
+ $(INSTALL) -d $(DESTDIR)${includedir}
+ $(INSTALL_DATA) lib/cpufreq.h $(DESTDIR)${includedir}/cpufreq.h
+
+install-tools:
+ $(INSTALL) -d $(DESTDIR)${bindir}
+ $(INSTALL_PROGRAM) $(OUTPUT)cpupower $(DESTDIR)${bindir}
+
+install-man:
+ $(INSTALL_DATA) -D man/cpupower.1 $(DESTDIR)${mandir}/man1/cpupower.1
+ $(INSTALL_DATA) -D man/cpupower-frequency-set.1 $(DESTDIR)${mandir}/man1/cpupower-frequency-set.1
+ $(INSTALL_DATA) -D man/cpupower-frequency-info.1 $(DESTDIR)${mandir}/man1/cpupower-frequency-info.1
+ $(INSTALL_DATA) -D man/cpupower-idle-set.1 $(DESTDIR)${mandir}/man1/cpupower-idle-set.1
+ $(INSTALL_DATA) -D man/cpupower-idle-info.1 $(DESTDIR)${mandir}/man1/cpupower-idle-info.1
+ $(INSTALL_DATA) -D man/cpupower-set.1 $(DESTDIR)${mandir}/man1/cpupower-set.1
+ $(INSTALL_DATA) -D man/cpupower-info.1 $(DESTDIR)${mandir}/man1/cpupower-info.1
+ $(INSTALL_DATA) -D man/cpupower-monitor.1 $(DESTDIR)${mandir}/man1/cpupower-monitor.1
+
+install-gmo:
+ $(INSTALL) -d $(DESTDIR)${localedir}
+ for HLANG in $(LANGUAGES); do \
+ echo '$(INSTALL_DATA) -D $(OUTPUT)po/$$HLANG.gmo $(DESTDIR)${localedir}/$$HLANG/LC_MESSAGES/cpupower.mo'; \
+ $(INSTALL_DATA) -D $(OUTPUT)po/$$HLANG.gmo $(DESTDIR)${localedir}/$$HLANG/LC_MESSAGES/cpupower.mo; \
+ done;
+
+install-bench:
+ @#DESTDIR must be set from outside to survive
+ @sbindir=$(sbindir) bindir=$(bindir) docdir=$(docdir) confdir=$(confdir) $(MAKE) -C bench O=$(OUTPUT) install
+
+install: all install-lib install-tools install-man $(INSTALL_NLS) $(INSTALL_BENCH)
+
+uninstall:
+ - rm -f $(DESTDIR)${libdir}/libcpupower.*
+ - rm -f $(DESTDIR)${includedir}/cpufreq.h
+ - rm -f $(DESTDIR)${bindir}/utils/cpupower
+ - rm -f $(DESTDIR)${mandir}/man1/cpupower.1
+ - rm -f $(DESTDIR)${mandir}/man1/cpupower-frequency-set.1
+ - rm -f $(DESTDIR)${mandir}/man1/cpupower-frequency-info.1
+ - rm -f $(DESTDIR)${mandir}/man1/cpupower-set.1
+ - rm -f $(DESTDIR)${mandir}/man1/cpupower-info.1
+ - rm -f $(DESTDIR)${mandir}/man1/cpupower-monitor.1
+ - for HLANG in $(LANGUAGES); do \
+ rm -f $(DESTDIR)${localedir}/$$HLANG/LC_MESSAGES/cpupower.mo; \
+ done;
+
+.PHONY: all utils libcpupower update-po create-gmo install-lib install-tools install-man install-gmo install uninstall clean
diff --git a/tools/power/cpupower/README b/tools/power/cpupower/README
new file mode 100644
index 00000000000..1c68f47663b
--- /dev/null
+++ b/tools/power/cpupower/README
@@ -0,0 +1,47 @@
+The cpupower package consists of the following elements:
+
+requirements
+------------
+
+On x86 pciutils is needed at runtime (-lpci).
+For compilation pciutils-devel (pci/pci.h) and a gcc version
+providing cpuid.h is needed.
+For both it's not explicitly checked for (yet).
+
+
+libcpupower
+----------
+
+"libcpupower" is a library which offers a unified access method for userspace
+tools and programs to the cpufreq core and drivers in the Linux kernel. This
+allows for code reduction in userspace tools, a clean implementation of
+the interaction to the cpufreq core, and support for both the sysfs and proc
+interfaces [depending on configuration, see below].
+
+
+compilation and installation
+----------------------------
+
+make
+su
+make install
+
+should suffice on most systems. It builds libcpupower to put in
+/usr/lib; cpupower, cpufreq-bench_plot.sh to put in /usr/bin; and
+cpufreq-bench to put in /usr/sbin. If you want to set up the paths
+differently and/or want to configure the package to your specific
+needs, you need to open "Makefile" with an editor of your choice and
+edit the block marked CONFIGURATION.
+
+
+THANKS
+------
+Many thanks to Mattia Dongili who wrote the autotoolization and
+libtoolization, the manpages and the italian language file for cpupower;
+to Dave Jones for his feedback and his dump_psb tool; to Bruno Ducrot for his
+powernow-k8-decode and intel_gsic tools as well as the french language file;
+and to various others commenting on the previous (pre-)releases of
+cpupower.
+
+
+ Dominik Brodowski
diff --git a/tools/power/cpupower/ToDo b/tools/power/cpupower/ToDo
new file mode 100644
index 00000000000..6e8b89f282e
--- /dev/null
+++ b/tools/power/cpupower/ToDo
@@ -0,0 +1,10 @@
+ToDos sorted by priority:
+
+- Use bitmask functions to parse CPU topology more robust
+ (current implementation has issues on AMD)
+- Try to read out boost states and frequencies on Intel
+- Somewhere saw the ability to read power consumption of
+ RAM from HW on Intel SandyBridge -> another monitor?
+- Add another c1e debug idle monitor
+ -> Is by design racy with BIOS, but could be added
+ with a --force option and some "be careful" messages
diff --git a/tools/power/cpupower/bench/Makefile b/tools/power/cpupower/bench/Makefile
new file mode 100644
index 00000000000..7ec7021a29c
--- /dev/null
+++ b/tools/power/cpupower/bench/Makefile
@@ -0,0 +1,36 @@
+OUTPUT := ./
+ifeq ("$(origin O)", "command line")
+ifneq ($(O),)
+ OUTPUT := $(O)/
+endif
+endif
+
+LIBS = -L../ -L$(OUTPUT) -lm -lcpupower
+
+OBJS = $(OUTPUT)main.o $(OUTPUT)parse.o $(OUTPUT)system.o $(OUTPUT)benchmark.o
+CFLAGS += -D_GNU_SOURCE -I../lib -DDEFAULT_CONFIG_FILE=\"$(confdir)/cpufreq-bench.conf\"
+
+$(OUTPUT)%.o : %.c
+ $(ECHO) " CC " $@
+ $(QUIET) $(CC) -c $(CFLAGS) $< -o $@
+
+$(OUTPUT)cpufreq-bench: $(OBJS)
+ $(ECHO) " CC " $@
+ $(QUIET) $(CC) -o $@ $(CFLAGS) $(OBJS) $(LIBS)
+
+all: $(OUTPUT)cpufreq-bench
+
+install:
+ mkdir -p $(DESTDIR)/$(sbindir)
+ mkdir -p $(DESTDIR)/$(bindir)
+ mkdir -p $(DESTDIR)/$(docdir)
+ mkdir -p $(DESTDIR)/$(confdir)
+ install -m 755 $(OUTPUT)cpufreq-bench $(DESTDIR)/$(sbindir)/cpufreq-bench
+ install -m 755 cpufreq-bench_plot.sh $(DESTDIR)/$(bindir)/cpufreq-bench_plot.sh
+ install -m 644 README-BENCH $(DESTDIR)/$(docdir)/README-BENCH
+ install -m 755 cpufreq-bench_script.sh $(DESTDIR)/$(docdir)/cpufreq-bench_script.sh
+ install -m 644 example.cfg $(DESTDIR)/$(confdir)/cpufreq-bench.conf
+
+clean:
+ rm -f $(OUTPUT)*.o
+ rm -f $(OUTPUT)cpufreq-bench
diff --git a/tools/power/cpupower/bench/README-BENCH b/tools/power/cpupower/bench/README-BENCH
new file mode 100644
index 00000000000..8093ec73817
--- /dev/null
+++ b/tools/power/cpupower/bench/README-BENCH
@@ -0,0 +1,124 @@
+This is cpufreq-bench, a microbenchmark for the cpufreq framework.
+
+Purpose
+=======
+
+What is this benchmark for:
+ - Identify worst case performance loss when doing dynamic frequency
+ scaling using Linux kernel governors
+ - Identify average reaction time of a governor to CPU load changes
+ - (Stress) Testing whether a cpufreq low level driver or governor works
+ as expected
+ - Identify cpufreq related performance regressions between kernels
+ - Possibly Real time priority testing? -> what happens if there are
+ processes with a higher prio than the governor's kernel thread
+ - ...
+
+What this benchmark does *not* cover:
+ - Power saving related regressions (In fact as better the performance
+ throughput is, the worse the power savings will be, but the first should
+ mostly count more...)
+ - Real world (workloads)
+
+
+Description
+===========
+
+cpufreq-bench helps to test the condition of a given cpufreq governor.
+For that purpose, it compares the performance governor to a configured
+powersave module.
+
+
+How it works
+============
+You can specify load (100% CPU load) and sleep (0% CPU load) times in us which
+will be run X time in a row (cycles):
+
+ sleep=25000
+ load=25000
+ cycles=20
+
+This part of the configuration file will create 25ms load/sleep turns,
+repeated 20 times.
+
+Adding this:
+ sleep_step=25000
+ load_step=25000
+ rounds=5
+Will increase load and sleep time by 25ms 5 times.
+Together you get following test:
+25ms load/sleep time repeated 20 times (cycles).
+50ms load/sleep time repeated 20 times (cycles).
+..
+100ms load/sleep time repeated 20 times (cycles).
+
+First it is calibrated how long a specific CPU intensive calculation
+takes on this machine and needs to be run in a loop using the performance
+governor.
+Then the above test runs are processed using the performance governor
+and the governor to test. The time the calculation really needed
+with the dynamic freq scaling governor is compared with the time needed
+on full performance and you get the overall performance loss.
+
+
+Example of expected results with ondemand governor:
+
+This shows expected results of the first two test run rounds from
+above config, you there have:
+
+100% CPU load (load) | 0 % CPU load (sleep) | round
+ 25 ms | 25 ms | 1
+ 50 ms | 50 ms | 2
+
+For example if ondemand governor is configured to have a 50ms
+sampling rate you get:
+
+In round 1, ondemand should have rather static 50% load and probably
+won't ever switch up (as long as up_threshold is above).
+
+In round 2, if the ondemand sampling times exactly match the load/sleep
+trigger of the cpufreq-bench, you will see no performance loss (compare with
+below possible ondemand sample kick ins (1)):
+
+But if ondemand always kicks in in the middle of the load sleep cycles, it
+will always see 50% loads and you get worst performance impact never
+switching up (compare with below possible ondemand sample kick ins (2))::
+
+ 50 50 50 50ms ->time
+load -----| |-----| |-----| |-----|
+ | | | | | | |
+sleep |-----| |-----| |-----| |----
+ |-----|-----|-----|-----|-----|-----|-----|---- ondemand sampling (1)
+ 100 0 100 0 100 0 100 load seen by ondemand(%)
+ |-----|-----|-----|-----|-----|-----|-----|-- ondemand sampling (2)
+ 50 50 50 50 50 50 50 load seen by ondemand(%)
+
+You can easily test all kind of load/sleep times and check whether your
+governor in average behaves as expected.
+
+
+ToDo
+====
+
+Provide a gnuplot utility script for easy generation of plots to present
+the outcome nicely.
+
+
+cpufreq-bench Command Usage
+===========================
+-l, --load=<long int> initial load time in us
+-s, --sleep=<long int> initial sleep time in us
+-x, --load-step=<long int> time to be added to load time, in us
+-y, --sleep-step=<long int> time to be added to sleep time, in us
+-c, --cpu=<unsigned int> CPU Number to use, starting at 0
+-p, --prio=<priority> scheduler priority, HIGH, LOW or DEFAULT
+-g, --governor=<governor> cpufreq governor to test
+-n, --cycles=<int> load/sleep cycles to get an avarage value to compare
+-r, --rounds<int> load/sleep rounds
+-f, --file=<configfile> config file to use
+-o, --output=<dir> output dir, must exist
+-v, --verbose verbose output on/off
+
+Due to the high priority, the application may not be responsible for some time.
+After the benchmark, the logfile is saved in OUTPUTDIR/benchmark_TIMESTAMP.log
+
diff --git a/tools/power/cpupower/bench/benchmark.c b/tools/power/cpupower/bench/benchmark.c
new file mode 100644
index 00000000000..81b1c48607d
--- /dev/null
+++ b/tools/power/cpupower/bench/benchmark.c
@@ -0,0 +1,194 @@
+/* cpufreq-bench CPUFreq microbenchmark
+ *
+ * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <math.h>
+
+#include "config.h"
+#include "system.h"
+#include "benchmark.h"
+
+/* Print out progress if we log into a file */
+#define show_progress(total_time, progress_time) \
+if (config->output != stdout) { \
+ fprintf(stdout, "Progress: %02lu %%\r", \
+ (progress_time * 100) / total_time); \
+ fflush(stdout); \
+}
+
+/**
+ * compute how many rounds of calculation we should do
+ * to get the given load time
+ *
+ * @param load aimed load time in µs
+ *
+ * @retval rounds of calculation
+ **/
+
+unsigned int calculate_timespace(long load, struct config *config)
+{
+ int i;
+ long long now, then;
+ unsigned int estimated = GAUGECOUNT;
+ unsigned int rounds = 0;
+ unsigned int timed = 0;
+
+ if (config->verbose)
+ printf("calibrating load of %lius, please wait...\n", load);
+
+ /* get the initial calculation time for a specific number of rounds */
+ now = get_time();
+ ROUNDS(estimated);
+ then = get_time();
+
+ timed = (unsigned int)(then - now);
+
+ /* approximation of the wanted load time by comparing with the
+ * initial calculation time */
+ for (i = 0; i < 4; i++) {
+ rounds = (unsigned int)(load * estimated / timed);
+ dprintf("calibrating with %u rounds\n", rounds);
+ now = get_time();
+ ROUNDS(rounds);
+ then = get_time();
+
+ timed = (unsigned int)(then - now);
+ estimated = rounds;
+ }
+ if (config->verbose)
+ printf("calibration done\n");
+
+ return estimated;
+}
+
+/**
+ * benchmark
+ * generates a specific sleep an load time with the performance
+ * governor and compares the used time for same calculations done
+ * with the configured powersave governor
+ *
+ * @param config config values for the benchmark
+ *
+ **/
+
+void start_benchmark(struct config *config)
+{
+ unsigned int _round, cycle;
+ long long now, then;
+ long sleep_time = 0, load_time = 0;
+ long performance_time = 0, powersave_time = 0;
+ unsigned int calculations;
+ unsigned long total_time = 0, progress_time = 0;
+
+ sleep_time = config->sleep;
+ load_time = config->load;
+
+ /* For the progress bar */
+ for (_round = 1; _round <= config->rounds; _round++)
+ total_time += _round * (config->sleep + config->load);
+ total_time *= 2; /* powersave and performance cycles */
+
+ for (_round = 0; _round < config->rounds; _round++) {
+ performance_time = 0LL;
+ powersave_time = 0LL;
+
+ show_progress(total_time, progress_time);
+
+ /* set the cpufreq governor to "performance" which disables
+ * P-State switching. */
+ if (set_cpufreq_governor("performance", config->cpu) != 0)
+ return;
+
+ /* calibrate the calculation time. the resulting calculation
+ * _rounds should produce a load which matches the configured
+ * load time */
+ calculations = calculate_timespace(load_time, config);
+
+ if (config->verbose)
+ printf("_round %i: doing %u cycles with %u calculations"
+ " for %lius\n", _round + 1, config->cycles,
+ calculations, load_time);
+
+ fprintf(config->output, "%u %li %li ",
+ _round, load_time, sleep_time);
+
+ if (config->verbose)
+ printf("avarage: %lius, rps:%li\n",
+ load_time / calculations,
+ 1000000 * calculations / load_time);
+
+ /* do some sleep/load cycles with the performance governor */
+ for (cycle = 0; cycle < config->cycles; cycle++) {
+ now = get_time();
+ usleep(sleep_time);
+ ROUNDS(calculations);
+ then = get_time();
+ performance_time += then - now - sleep_time;
+ if (config->verbose)
+ printf("performance cycle took %lius, "
+ "sleep: %lius, "
+ "load: %lius, rounds: %u\n",
+ (long)(then - now), sleep_time,
+ load_time, calculations);
+ }
+ fprintf(config->output, "%li ",
+ performance_time / config->cycles);
+
+ progress_time += sleep_time + load_time;
+ show_progress(total_time, progress_time);
+
+ /* set the powersave governor which activates P-State switching
+ * again */
+ if (set_cpufreq_governor(config->governor, config->cpu) != 0)
+ return;
+
+ /* again, do some sleep/load cycles with the
+ * powersave governor */
+ for (cycle = 0; cycle < config->cycles; cycle++) {
+ now = get_time();
+ usleep(sleep_time);
+ ROUNDS(calculations);
+ then = get_time();
+ powersave_time += then - now - sleep_time;
+ if (config->verbose)
+ printf("powersave cycle took %lius, "
+ "sleep: %lius, "
+ "load: %lius, rounds: %u\n",
+ (long)(then - now), sleep_time,
+ load_time, calculations);
+ }
+
+ progress_time += sleep_time + load_time;
+
+ /* compare the avarage sleep/load cycles */
+ fprintf(config->output, "%li ",
+ powersave_time / config->cycles);
+ fprintf(config->output, "%.3f\n",
+ performance_time * 100.0 / powersave_time);
+ fflush(config->output);
+
+ if (config->verbose)
+ printf("performance is at %.2f%%\n",
+ performance_time * 100.0 / powersave_time);
+
+ sleep_time += config->sleep_step;
+ load_time += config->load_step;
+ }
+}
diff --git a/tools/power/cpupower/bench/benchmark.h b/tools/power/cpupower/bench/benchmark.h
new file mode 100644
index 00000000000..51d7f50ac2b
--- /dev/null
+++ b/tools/power/cpupower/bench/benchmark.h
@@ -0,0 +1,29 @@
+/* cpufreq-bench CPUFreq microbenchmark
+ *
+ * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* load loop, this schould take about 1 to 2ms to complete */
+#define ROUNDS(x) {unsigned int rcnt; \
+ for (rcnt = 0; rcnt < x*1000; rcnt++) { \
+ (void)(((int)(pow(rcnt, rcnt) * \
+ sqrt(rcnt*7230970)) ^ 7230716) ^ \
+ (int)atan2(rcnt, rcnt)); \
+ } } \
+
+
+void start_benchmark(struct config *config);
diff --git a/tools/power/cpupower/bench/config.h b/tools/power/cpupower/bench/config.h
new file mode 100644
index 00000000000..ee6f258e533
--- /dev/null
+++ b/tools/power/cpupower/bench/config.h
@@ -0,0 +1,36 @@
+/* cpufreq-bench CPUFreq microbenchmark
+ *
+ * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* initial loop count for the load calibration */
+#define GAUGECOUNT 1500
+
+/* default scheduling policy SCHED_OTHER */
+#define SCHEDULER SCHED_OTHER
+
+#define PRIORITY_DEFAULT 0
+#define PRIORITY_HIGH sched_get_priority_max(SCHEDULER)
+#define PRIORITY_LOW sched_get_priority_min(SCHEDULER)
+
+/* enable further debug messages */
+#ifdef DEBUG
+#define dprintf printf
+#else
+#define dprintf(...) do { } while (0)
+#endif
+
diff --git a/tools/power/cpupower/bench/cpufreq-bench_plot.sh b/tools/power/cpupower/bench/cpufreq-bench_plot.sh
new file mode 100644
index 00000000000..410021a12f4
--- /dev/null
+++ b/tools/power/cpupower/bench/cpufreq-bench_plot.sh
@@ -0,0 +1,104 @@
+#!/bin/bash
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+# Author/Copyright(c): 2009, Thomas Renninger <trenn@suse.de>, Novell Inc.
+
+# Helper script to easily create nice plots of your cpufreq-bench results
+
+dir=`mktemp -d`
+output_file="cpufreq-bench.png"
+global_title="cpufreq-bench plot"
+picture_type="jpeg"
+file[0]=""
+
+function usage()
+{
+ echo "cpufreq-bench_plot.sh [OPTIONS] logfile [measure_title] [logfile [measure_title]] ...]"
+ echo
+ echo "Options"
+ echo " -o output_file"
+ echo " -t global_title"
+ echo " -p picture_type [jpeg|gif|png|postscript|...]"
+ exit 1
+}
+
+if [ $# -eq 0 ];then
+ echo "No benchmark results file provided"
+ echo
+ usage
+fi
+
+while getopts o:t:p: name ; do
+ case $name in
+ o)
+ output_file="$OPTARG".$picture_type
+ ;;
+ t)
+ global_title="$OPTARG"
+ ;;
+ p)
+ picture_type="$OPTARG"
+ ;;
+ ?)
+ usage
+ ;;
+ esac
+done
+shift $(($OPTIND -1))
+
+plots=0
+while [ "$1" ];do
+ if [ ! -f "$1" ];then
+ echo "File $1 does not exist"
+ usage
+ fi
+ file[$plots]="$1"
+ title[$plots]="$2"
+ # echo "File: ${file[$plots]} - ${title[plots]}"
+ shift;shift
+ plots=$((plots + 1))
+done
+
+echo "set terminal $picture_type" >> $dir/plot_script.gpl
+echo "set output \"$output_file\"" >> $dir/plot_script.gpl
+echo "set title \"$global_title\"" >> $dir/plot_script.gpl
+echo "set xlabel \"sleep/load time\"" >> $dir/plot_script.gpl
+echo "set ylabel \"Performance (%)\"" >> $dir/plot_script.gpl
+
+for((plot=0;plot<$plots;plot++));do
+
+ # Sanity check
+ ###### I am to dump to get this redirected to stderr/stdout in one awk call... #####
+ cat ${file[$plot]} |grep -v "^#" |awk '{if ($2 != $3) printf("Error in measure %d:Load time %s does not equal sleep time %s, plot will not be correct\n", $1, $2, $3); ERR=1}'
+ ###### I am to dump to get this redirected in one awk call... #####
+
+ # Parse out load time (which must be equal to sleep time for a plot), divide it by 1000
+ # to get ms and parse out the performance in percentage and write it to a temp file for plotting
+ cat ${file[$plot]} |grep -v "^#" |awk '{printf "%lu %.1f\n",$2/1000, $6}' >$dir/data_$plot
+
+ if [ $plot -eq 0 ];then
+ echo -n "plot " >> $dir/plot_script.gpl
+ fi
+ echo -n "\"$dir/data_$plot\" title \"${title[$plot]}\" with lines" >> $dir/plot_script.gpl
+ if [ $(($plot + 1)) -ne $plots ];then
+ echo -n ", " >> $dir/plot_script.gpl
+ fi
+done
+echo >> $dir/plot_script.gpl
+
+gnuplot $dir/plot_script.gpl
+rm -r $dir \ No newline at end of file
diff --git a/tools/power/cpupower/bench/cpufreq-bench_script.sh b/tools/power/cpupower/bench/cpufreq-bench_script.sh
new file mode 100644
index 00000000000..de20d2a0687
--- /dev/null
+++ b/tools/power/cpupower/bench/cpufreq-bench_script.sh
@@ -0,0 +1,101 @@
+#!/bin/bash
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2, or (at your option)
+# any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
+# 02110-1301, USA.
+
+# Author/Copyright(c): 2009, Thomas Renninger <trenn@suse.de>, Novell Inc.
+
+# Ondemand up_threshold and sampling rate test script for cpufreq-bench
+# mircobenchmark.
+# Modify the general variables at the top or extend or copy out parts
+# if you want to test other things
+#
+
+# Default with latest kernels is 95, before micro account patches
+# it was 80, cmp. with git commit 808009131046b62ac434dbc796
+UP_THRESHOLD="60 80 95"
+# Depending on the kernel and the HW sampling rate could be restricted
+# and cannot be set that low...
+# E.g. before git commit cef9615a853ebc4972084f7 one could only set
+# min sampling rate of 80000 if CONFIG_HZ=250
+SAMPLING_RATE="20000 80000"
+
+function measure()
+{
+ local -i up_threshold_set
+ local -i sampling_rate_set
+
+ for up_threshold in $UP_THRESHOLD;do
+ for sampling_rate in $SAMPLING_RATE;do
+ # Set values in sysfs
+ echo $up_threshold >/sys/devices/system/cpu/cpu0/cpufreq/ondemand/up_threshold
+ echo $sampling_rate >/sys/devices/system/cpu/cpu0/cpufreq/ondemand/sampling_rate
+ up_threshold_set=$(cat /sys/devices/system/cpu/cpu0/cpufreq/ondemand/up_threshold)
+ sampling_rate_set=$(cat /sys/devices/system/cpu/cpu0/cpufreq/ondemand/sampling_rate)
+
+ # Verify set values in sysfs
+ if [ ${up_threshold_set} -eq ${up_threshold} ];then
+ echo "up_threshold: $up_threshold, set in sysfs: ${up_threshold_set}"
+ else
+ echo "WARNING: Tried to set up_threshold: $up_threshold, set in sysfs: ${up_threshold_set}"
+ fi
+ if [ ${sampling_rate_set} -eq ${sampling_rate} ];then
+ echo "sampling_rate: $sampling_rate, set in sysfs: ${sampling_rate_set}"
+ else
+ echo "WARNING: Tried to set sampling_rate: $sampling_rate, set in sysfs: ${sampling_rate_set}"
+ fi
+
+ # Benchmark
+ cpufreq-bench -o /var/log/cpufreq-bench/up_threshold_${up_threshold}_sampling_rate_${sampling_rate}
+ done
+ done
+}
+
+function create_plots()
+{
+ local command
+
+ for up_threshold in $UP_THRESHOLD;do
+ command="cpufreq-bench_plot.sh -o \"sampling_rate_${SAMPLING_RATE}_up_threshold_${up_threshold}\" -t \"Ondemand sampling_rate: ${SAMPLING_RATE} comparison - Up_threshold: $up_threshold %\""
+ for sampling_rate in $SAMPLING_RATE;do
+ command="${command} /var/log/cpufreq-bench/up_threshold_${up_threshold}_sampling_rate_${sampling_rate}/* \"sampling_rate = $sampling_rate\""
+ done
+ echo $command
+ eval "$command"
+ echo
+ done
+
+ for sampling_rate in $SAMPLING_RATE;do
+ command="cpufreq-bench_plot.sh -o \"up_threshold_${UP_THRESHOLD}_sampling_rate_${sampling_rate}\" -t \"Ondemand up_threshold: ${UP_THRESHOLD} % comparison - sampling_rate: $sampling_rate\""
+ for up_threshold in $UP_THRESHOLD;do
+ command="${command} /var/log/cpufreq-bench/up_threshold_${up_threshold}_sampling_rate_${sampling_rate}/* \"up_threshold = $up_threshold\""
+ done
+ echo $command
+ eval "$command"
+ echo
+ done
+
+ command="cpufreq-bench_plot.sh -o \"up_threshold_${UP_THRESHOLD}_sampling_rate_${SAMPLING_RATE}\" -t \"Ondemand up_threshold: ${UP_THRESHOLD} and sampling_rate ${SAMPLING_RATE} comparison\""
+ for sampling_rate in $SAMPLING_RATE;do
+ for up_threshold in $UP_THRESHOLD;do
+ command="${command} /var/log/cpufreq-bench/up_threshold_${up_threshold}_sampling_rate_${sampling_rate}/* \"up_threshold = $up_threshold - sampling_rate = $sampling_rate\""
+ done
+ done
+ echo "$command"
+ eval "$command"
+}
+
+measure
+create_plots \ No newline at end of file
diff --git a/tools/power/cpupower/bench/example.cfg b/tools/power/cpupower/bench/example.cfg
new file mode 100644
index 00000000000..f91f6436068
--- /dev/null
+++ b/tools/power/cpupower/bench/example.cfg
@@ -0,0 +1,11 @@
+sleep = 50000
+load = 50000
+cpu = 0
+priority = LOW
+output = /var/log/cpufreq-bench
+sleep_step = 50000
+load_step = 50000
+cycles = 20
+rounds = 40
+verbose = 0
+governor = ondemand
diff --git a/tools/power/cpupower/bench/main.c b/tools/power/cpupower/bench/main.c
new file mode 100644
index 00000000000..24910313a52
--- /dev/null
+++ b/tools/power/cpupower/bench/main.c
@@ -0,0 +1,202 @@
+/* cpufreq-bench CPUFreq microbenchmark
+ *
+ * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <errno.h>
+
+#include "config.h"
+#include "system.h"
+#include "benchmark.h"
+
+static struct option long_options[] = {
+ {"output", 1, 0, 'o'},
+ {"sleep", 1, 0, 's'},
+ {"load", 1, 0, 'l'},
+ {"verbose", 0, 0, 'v'},
+ {"cpu", 1, 0, 'c'},
+ {"governor", 1, 0, 'g'},
+ {"prio", 1, 0, 'p'},
+ {"file", 1, 0, 'f'},
+ {"cycles", 1, 0, 'n'},
+ {"rounds", 1, 0, 'r'},
+ {"load-step", 1, 0, 'x'},
+ {"sleep-step", 1, 0, 'y'},
+ {"help", 0, 0, 'h'},
+ {0, 0, 0, 0}
+};
+
+/*******************************************************************
+ usage
+*******************************************************************/
+
+void usage()
+{
+ printf("usage: ./bench\n");
+ printf("Options:\n");
+ printf(" -l, --load=<long int>\t\tinitial load time in us\n");
+ printf(" -s, --sleep=<long int>\t\tinitial sleep time in us\n");
+ printf(" -x, --load-step=<long int>\ttime to be added to load time, in us\n");
+ printf(" -y, --sleep-step=<long int>\ttime to be added to sleep time, in us\n");
+ printf(" -c, --cpu=<cpu #>\t\t\tCPU Nr. to use, starting at 0\n");
+ printf(" -p, --prio=<priority>\t\t\tscheduler priority, HIGH, LOW or DEFAULT\n");
+ printf(" -g, --governor=<governor>\t\tcpufreq governor to test\n");
+ printf(" -n, --cycles=<int>\t\t\tload/sleep cycles\n");
+ printf(" -r, --rounds<int>\t\t\tload/sleep rounds\n");
+ printf(" -f, --file=<configfile>\t\tconfig file to use\n");
+ printf(" -o, --output=<dir>\t\t\toutput path. Filename will be OUTPUTPATH/benchmark_TIMESTAMP.log\n");
+ printf(" -v, --verbose\t\t\t\tverbose output on/off\n");
+ printf(" -h, --help\t\t\t\tPrint this help screen\n");
+ exit(1);
+}
+
+/*******************************************************************
+ main
+*******************************************************************/
+
+int main(int argc, char **argv)
+{
+ int c;
+ int option_index = 0;
+ struct config *config = NULL;
+
+ config = prepare_default_config();
+
+ if (config == NULL)
+ return EXIT_FAILURE;
+
+ while (1) {
+ c = getopt_long (argc, argv, "hg:o:s:l:vc:p:f:n:r:x:y:",
+ long_options, &option_index);
+ if (c == -1)
+ break;
+
+ switch (c) {
+ case 'o':
+ if (config->output != NULL)
+ fclose(config->output);
+
+ config->output = prepare_output(optarg);
+
+ if (config->output == NULL)
+ return EXIT_FAILURE;
+
+ dprintf("user output path -> %s\n", optarg);
+ break;
+ case 's':
+ sscanf(optarg, "%li", &config->sleep);
+ dprintf("user sleep time -> %s\n", optarg);
+ break;
+ case 'l':
+ sscanf(optarg, "%li", &config->load);
+ dprintf("user load time -> %s\n", optarg);
+ break;
+ case 'c':
+ sscanf(optarg, "%u", &config->cpu);
+ dprintf("user cpu -> %s\n", optarg);
+ break;
+ case 'g':
+ strncpy(config->governor, optarg, 14);
+ dprintf("user governor -> %s\n", optarg);
+ break;
+ case 'p':
+ if (string_to_prio(optarg) != SCHED_ERR) {
+ config->prio = string_to_prio(optarg);
+ dprintf("user prio -> %s\n", optarg);
+ } else {
+ if (config != NULL) {
+ if (config->output != NULL)
+ fclose(config->output);
+ free(config);
+ }
+ usage();
+ }
+ break;
+ case 'n':
+ sscanf(optarg, "%u", &config->cycles);
+ dprintf("user cycles -> %s\n", optarg);
+ break;
+ case 'r':
+ sscanf(optarg, "%u", &config->rounds);
+ dprintf("user rounds -> %s\n", optarg);
+ break;
+ case 'x':
+ sscanf(optarg, "%li", &config->load_step);
+ dprintf("user load_step -> %s\n", optarg);
+ break;
+ case 'y':
+ sscanf(optarg, "%li", &config->sleep_step);
+ dprintf("user sleep_step -> %s\n", optarg);
+ break;
+ case 'f':
+ if (prepare_config(optarg, config))
+ return EXIT_FAILURE;
+ break;
+ case 'v':
+ config->verbose = 1;
+ dprintf("verbose output enabled\n");
+ break;
+ case 'h':
+ case '?':
+ default:
+ if (config != NULL) {
+ if (config->output != NULL)
+ fclose(config->output);
+ free(config);
+ }
+ usage();
+ }
+ }
+
+ if (config->verbose) {
+ printf("starting benchmark with parameters:\n");
+ printf("config:\n\t"
+ "sleep=%li\n\t"
+ "load=%li\n\t"
+ "sleep_step=%li\n\t"
+ "load_step=%li\n\t"
+ "cpu=%u\n\t"
+ "cycles=%u\n\t"
+ "rounds=%u\n\t"
+ "governor=%s\n\n",
+ config->sleep,
+ config->load,
+ config->sleep_step,
+ config->load_step,
+ config->cpu,
+ config->cycles,
+ config->rounds,
+ config->governor);
+ }
+
+ prepare_user(config);
+ prepare_system(config);
+ start_benchmark(config);
+
+ if (config->output != stdout)
+ fclose(config->output);
+
+ free(config);
+
+ return EXIT_SUCCESS;
+}
+
diff --git a/tools/power/cpupower/bench/parse.c b/tools/power/cpupower/bench/parse.c
new file mode 100644
index 00000000000..543bba14ae2
--- /dev/null
+++ b/tools/power/cpupower/bench/parse.c
@@ -0,0 +1,225 @@
+/* cpufreq-bench CPUFreq microbenchmark
+ *
+ * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+#include <dirent.h>
+
+#include <sys/utsname.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#include "parse.h"
+#include "config.h"
+
+/**
+ * converts priority string to priority
+ *
+ * @param str string that represents a scheduler priority
+ *
+ * @retval priority
+ * @retval SCHED_ERR when the priority doesn't exit
+ **/
+
+enum sched_prio string_to_prio(const char *str)
+{
+ if (strncasecmp("high", str, strlen(str)) == 0)
+ return SCHED_HIGH;
+ else if (strncasecmp("default", str, strlen(str)) == 0)
+ return SCHED_DEFAULT;
+ else if (strncasecmp("low", str, strlen(str)) == 0)
+ return SCHED_LOW;
+ else
+ return SCHED_ERR;
+}
+
+/**
+ * create and open logfile
+ *
+ * @param dir directory in which the logfile should be created
+ *
+ * @retval logfile on success
+ * @retval NULL when the file can't be created
+ **/
+
+FILE *prepare_output(const char *dirname)
+{
+ FILE *output = NULL;
+ int len;
+ char *filename;
+ struct utsname sysdata;
+ DIR *dir;
+
+ dir = opendir(dirname);
+ if (dir == NULL) {
+ if (mkdir(dirname, 0755)) {
+ perror("mkdir");
+ fprintf(stderr, "error: Cannot create dir %s\n",
+ dirname);
+ return NULL;
+ }
+ }
+
+ len = strlen(dirname) + 30;
+ filename = malloc(sizeof(char) * len);
+
+ if (uname(&sysdata) == 0) {
+ len += strlen(sysdata.nodename) + strlen(sysdata.release);
+ filename = realloc(filename, sizeof(char) * len);
+
+ if (filename == NULL) {
+ perror("realloc");
+ return NULL;
+ }
+
+ snprintf(filename, len - 1, "%s/benchmark_%s_%s_%li.log",
+ dirname, sysdata.nodename, sysdata.release, time(NULL));
+ } else {
+ snprintf(filename, len - 1, "%s/benchmark_%li.log",
+ dirname, time(NULL));
+ }
+
+ dprintf("logilename: %s\n", filename);
+
+ output = fopen(filename, "w+");
+ if (output == NULL) {
+ perror("fopen");
+ fprintf(stderr, "error: unable to open logfile\n");
+ }
+
+ fprintf(stdout, "Logfile: %s\n", filename);
+
+ free(filename);
+ fprintf(output, "#round load sleep performance powersave percentage\n");
+ return output;
+}
+
+/**
+ * returns the default config
+ *
+ * @retval default config on success
+ * @retval NULL when the output file can't be created
+ **/
+
+struct config *prepare_default_config()
+{
+ struct config *config = malloc(sizeof(struct config));
+
+ dprintf("loading defaults\n");
+
+ config->sleep = 500000;
+ config->load = 500000;
+ config->sleep_step = 500000;
+ config->load_step = 500000;
+ config->cycles = 5;
+ config->rounds = 50;
+ config->cpu = 0;
+ config->prio = SCHED_HIGH;
+ config->verbose = 0;
+ strncpy(config->governor, "ondemand", 8);
+
+ config->output = stdout;
+
+#ifdef DEFAULT_CONFIG_FILE
+ if (prepare_config(DEFAULT_CONFIG_FILE, config))
+ return NULL;
+#endif
+ return config;
+}
+
+/**
+ * parses config file and returns the config to the caller
+ *
+ * @param path config file name
+ *
+ * @retval 1 on error
+ * @retval 0 on success
+ **/
+
+int prepare_config(const char *path, struct config *config)
+{
+ size_t len = 0;
+ char *opt, *val, *line = NULL;
+ FILE *configfile = fopen(path, "r");
+
+ if (config == NULL) {
+ fprintf(stderr, "error: config is NULL\n");
+ return 1;
+ }
+
+ if (configfile == NULL) {
+ perror("fopen");
+ fprintf(stderr, "error: unable to read configfile\n");
+ free(config);
+ return 1;
+ }
+
+ while (getline(&line, &len, configfile) != -1) {
+ if (line[0] == '#' || line[0] == ' ')
+ continue;
+
+ sscanf(line, "%as = %as", &opt, &val);
+
+ dprintf("parsing: %s -> %s\n", opt, val);
+
+ if (strncmp("sleep", opt, strlen(opt)) == 0)
+ sscanf(val, "%li", &config->sleep);
+
+ else if (strncmp("load", opt, strlen(opt)) == 0)
+ sscanf(val, "%li", &config->load);
+
+ else if (strncmp("load_step", opt, strlen(opt)) == 0)
+ sscanf(val, "%li", &config->load_step);
+
+ else if (strncmp("sleep_step", opt, strlen(opt)) == 0)
+ sscanf(val, "%li", &config->sleep_step);
+
+ else if (strncmp("cycles", opt, strlen(opt)) == 0)
+ sscanf(val, "%u", &config->cycles);
+
+ else if (strncmp("rounds", opt, strlen(opt)) == 0)
+ sscanf(val, "%u", &config->rounds);
+
+ else if (strncmp("verbose", opt, strlen(opt)) == 0)
+ sscanf(val, "%u", &config->verbose);
+
+ else if (strncmp("output", opt, strlen(opt)) == 0)
+ config->output = prepare_output(val);
+
+ else if (strncmp("cpu", opt, strlen(opt)) == 0)
+ sscanf(val, "%u", &config->cpu);
+
+ else if (strncmp("governor", opt, 14) == 0)
+ strncpy(config->governor, val, 14);
+
+ else if (strncmp("priority", opt, strlen(opt)) == 0) {
+ if (string_to_prio(val) != SCHED_ERR)
+ config->prio = string_to_prio(val);
+ }
+ }
+
+ free(line);
+ free(opt);
+ free(val);
+
+ return 0;
+}
diff --git a/tools/power/cpupower/bench/parse.h b/tools/power/cpupower/bench/parse.h
new file mode 100644
index 00000000000..a8dc632d9ee
--- /dev/null
+++ b/tools/power/cpupower/bench/parse.h
@@ -0,0 +1,53 @@
+/* cpufreq-bench CPUFreq microbenchmark
+ *
+ * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* struct that holds the required config parameters */
+struct config
+{
+ long sleep; /* sleep time in µs */
+ long load; /* load time in µs */
+ long sleep_step; /* time value which changes the
+ * sleep time after every round in µs */
+ long load_step; /* time value which changes the
+ * load time after every round in µs */
+ unsigned int cycles; /* calculation cycles with the same sleep/load time */
+ unsigned int rounds; /* calculation rounds with iterated sleep/load time */
+ unsigned int cpu; /* cpu for which the affinity is set */
+ char governor[15]; /* cpufreq governor */
+ enum sched_prio /* possible scheduler priorities */
+ {
+ SCHED_ERR = -1,
+ SCHED_HIGH,
+ SCHED_DEFAULT,
+ SCHED_LOW
+ } prio;
+
+ unsigned int verbose; /* verbose output */
+ FILE *output; /* logfile */
+ char *output_filename; /* logfile name, must be freed at the end
+ if output != NULL and output != stdout*/
+};
+
+enum sched_prio string_to_prio(const char *str);
+
+FILE *prepare_output(const char *dir);
+
+int prepare_config(const char *path, struct config *config);
+struct config *prepare_default_config();
+
diff --git a/tools/power/cpupower/bench/system.c b/tools/power/cpupower/bench/system.c
new file mode 100644
index 00000000000..f01e3f4be84
--- /dev/null
+++ b/tools/power/cpupower/bench/system.c
@@ -0,0 +1,191 @@
+/* cpufreq-bench CPUFreq microbenchmark
+ *
+ * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include <stdio.h>
+#include <time.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <sched.h>
+
+#include <cpufreq.h>
+
+#include "config.h"
+#include "system.h"
+
+/**
+ * returns time since epoch in µs
+ *
+ * @retval time
+ **/
+
+long long int get_time()
+{
+ struct timeval now;
+
+ gettimeofday(&now, NULL);
+
+ return (long long int)(now.tv_sec * 1000000LL + now.tv_usec);
+}
+
+/**
+ * sets the cpufreq governor
+ *
+ * @param governor cpufreq governor name
+ * @param cpu cpu for which the governor should be set
+ *
+ * @retval 0 on success
+ * @retval -1 when failed
+ **/
+
+int set_cpufreq_governor(char *governor, unsigned int cpu)
+{
+
+ dprintf("set %s as cpufreq governor\n", governor);
+
+ if (cpufreq_cpu_exists(cpu) != 0) {
+ perror("cpufreq_cpu_exists");
+ fprintf(stderr, "error: cpu %u does not exist\n", cpu);
+ return -1;
+ }
+
+ if (cpufreq_modify_policy_governor(cpu, governor) != 0) {
+ perror("cpufreq_modify_policy_governor");
+ fprintf(stderr, "error: unable to set %s governor\n", governor);
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * sets cpu affinity for the process
+ *
+ * @param cpu cpu# to which the affinity should be set
+ *
+ * @retval 0 on success
+ * @retval -1 when setting the affinity failed
+ **/
+
+int set_cpu_affinity(unsigned int cpu)
+{
+ cpu_set_t cpuset;
+
+ CPU_ZERO(&cpuset);
+ CPU_SET(cpu, &cpuset);
+
+ dprintf("set affinity to cpu #%u\n", cpu);
+
+ if (sched_setaffinity(getpid(), sizeof(cpu_set_t), &cpuset) < 0) {
+ perror("sched_setaffinity");
+ fprintf(stderr, "warning: unable to set cpu affinity\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * sets the process priority parameter
+ *
+ * @param priority priority value
+ *
+ * @retval 0 on success
+ * @retval -1 when setting the priority failed
+ **/
+
+int set_process_priority(int priority)
+{
+ struct sched_param param;
+
+ dprintf("set scheduler priority to %i\n", priority);
+
+ param.sched_priority = priority;
+
+ if (sched_setscheduler(0, SCHEDULER, &param) < 0) {
+ perror("sched_setscheduler");
+ fprintf(stderr, "warning: unable to set scheduler priority\n");
+ return -1;
+ }
+
+ return 0;
+}
+
+/**
+ * notifies the user that the benchmark may run some time
+ *
+ * @param config benchmark config values
+ *
+ **/
+
+void prepare_user(const struct config *config)
+{
+ unsigned long sleep_time = 0;
+ unsigned long load_time = 0;
+ unsigned int round;
+
+ for (round = 0; round < config->rounds; round++) {
+ sleep_time += 2 * config->cycles *
+ (config->sleep + config->sleep_step * round);
+ load_time += 2 * config->cycles *
+ (config->load + config->load_step * round) +
+ (config->load + config->load_step * round * 4);
+ }
+
+ if (config->verbose || config->output != stdout)
+ printf("approx. test duration: %im\n",
+ (int)((sleep_time + load_time) / 60000000));
+}
+
+/**
+ * sets up the cpu affinity and scheduler priority
+ *
+ * @param config benchmark config values
+ *
+ **/
+
+void prepare_system(const struct config *config)
+{
+ if (config->verbose)
+ printf("set cpu affinity to cpu #%u\n", config->cpu);
+
+ set_cpu_affinity(config->cpu);
+
+ switch (config->prio) {
+ case SCHED_HIGH:
+ if (config->verbose)
+ printf("high priority condition requested\n");
+
+ set_process_priority(PRIORITY_HIGH);
+ break;
+ case SCHED_LOW:
+ if (config->verbose)
+ printf("low priority condition requested\n");
+
+ set_process_priority(PRIORITY_LOW);
+ break;
+ default:
+ if (config->verbose)
+ printf("default priority condition requested\n");
+
+ set_process_priority(PRIORITY_DEFAULT);
+ }
+}
+
diff --git a/tools/power/cpupower/bench/system.h b/tools/power/cpupower/bench/system.h
new file mode 100644
index 00000000000..3a8c858b78f
--- /dev/null
+++ b/tools/power/cpupower/bench/system.h
@@ -0,0 +1,29 @@
+/* cpufreq-bench CPUFreq microbenchmark
+ *
+ * Copyright (C) 2008 Christian Kornacker <ckornacker@suse.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+#include "parse.h"
+
+long long get_time();
+
+int set_cpufreq_governor(char *governor, unsigned int cpu);
+int set_cpu_affinity(unsigned int cpu);
+int set_process_priority(int priority);
+
+void prepare_user(const struct config *config);
+void prepare_system(const struct config *config);
diff --git a/tools/power/cpupower/debug/i386/Makefile b/tools/power/cpupower/debug/i386/Makefile
new file mode 100644
index 00000000000..c05cc0ac80c
--- /dev/null
+++ b/tools/power/cpupower/debug/i386/Makefile
@@ -0,0 +1,41 @@
+OUTPUT=./
+ifeq ("$(origin O)", "command line")
+ OUTPUT := $(O)/
+endif
+
+DESTDIR =
+bindir = /usr/bin
+
+INSTALL = /usr/bin/install
+
+
+default: all
+
+$(OUTPUT)centrino-decode: centrino-decode.c
+ $(CC) $(CFLAGS) -o $@ centrino-decode.c
+
+$(OUTPUT)dump_psb: dump_psb.c
+ $(CC) $(CFLAGS) -o $@ dump_psb.c
+
+$(OUTPUT)intel_gsic: intel_gsic.c
+ $(CC) $(CFLAGS) -o $@ -llrmi intel_gsic.c
+
+$(OUTPUT)powernow-k8-decode: powernow-k8-decode.c
+ $(CC) $(CFLAGS) -o $@ powernow-k8-decode.c
+
+all: $(OUTPUT)centrino-decode $(OUTPUT)dump_psb $(OUTPUT)intel_gsic $(OUTPUT)powernow-k8-decode
+
+clean:
+ rm -rf $(OUTPUT)centrino-decode
+ rm -rf $(OUTPUT)dump_psb
+ rm -rf $(OUTPUT)intel_gsic
+ rm -rf $(OUTPUT)powernow-k8-decode
+
+install:
+ $(INSTALL) -d $(DESTDIR)${bindir}
+ $(INSTALL) $(OUTPUT)centrino-decode $(DESTDIR)${bindir}
+ $(INSTALL) $(OUTPUT)powernow-k8-decode $(DESTDIR)${bindir}
+ $(INSTALL) $(OUTPUT)dump_psb $(DESTDIR)${bindir}
+ $(INSTALL) $(OUTPUT)intel_gsic $(DESTDIR)${bindir}
+
+.PHONY: all default clean install
diff --git a/tools/power/cpupower/debug/i386/centrino-decode.c b/tools/power/cpupower/debug/i386/centrino-decode.c
new file mode 100644
index 00000000000..7ef24cce492
--- /dev/null
+++ b/tools/power/cpupower/debug/i386/centrino-decode.c
@@ -0,0 +1,113 @@
+/*
+ * (C) 2003 - 2004 Dominik Brodowski <linux@dominikbrodowski.de>
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ * Based on code found in
+ * linux/arch/i386/kernel/cpu/cpufreq/speedstep-centrino.c
+ * and originally developed by Jeremy Fitzhardinge.
+ *
+ * USAGE: simply run it to decode the current settings on CPU 0,
+ * or pass the CPU number as argument, or pass the MSR content
+ * as argument.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define MCPU 32
+
+#define MSR_IA32_PERF_STATUS 0x198
+
+static int rdmsr(unsigned int cpu, unsigned int msr,
+ unsigned int *lo, unsigned int *hi)
+{
+ int fd;
+ char file[20];
+ unsigned long long val;
+ int retval = -1;
+
+ *lo = *hi = 0;
+
+ if (cpu > MCPU)
+ goto err1;
+
+ sprintf(file, "/dev/cpu/%d/msr", cpu);
+ fd = open(file, O_RDONLY);
+
+ if (fd < 0)
+ goto err1;
+
+ if (lseek(fd, msr, SEEK_CUR) == -1)
+ goto err2;
+
+ if (read(fd, &val, 8) != 8)
+ goto err2;
+
+ *lo = (uint32_t )(val & 0xffffffffull);
+ *hi = (uint32_t )(val>>32 & 0xffffffffull);
+
+ retval = 0;
+err2:
+ close(fd);
+err1:
+ return retval;
+}
+
+static void decode (unsigned int msr)
+{
+ unsigned int multiplier;
+ unsigned int mv;
+
+ multiplier = ((msr >> 8) & 0xFF);
+
+ mv = (((msr & 0xFF) * 16) + 700);
+
+ printf("0x%x means multiplier %d @ %d mV\n", msr, multiplier, mv);
+}
+
+static int decode_live(unsigned int cpu)
+{
+ unsigned int lo, hi;
+ int err;
+
+ err = rdmsr(cpu, MSR_IA32_PERF_STATUS, &lo, &hi);
+
+ if (err) {
+ printf("can't get MSR_IA32_PERF_STATUS for cpu %d\n", cpu);
+ printf("Possible trouble: you don't run an Enhanced SpeedStep capable cpu\n");
+ printf("or you are not root, or the msr driver is not present\n");
+ return 1;
+ }
+
+ decode(lo);
+
+ return 0;
+}
+
+int main (int argc, char **argv)
+{
+ unsigned int cpu, mode = 0;
+
+ if (argc < 2)
+ cpu = 0;
+ else {
+ cpu = strtoul(argv[1], NULL, 0);
+ if (cpu >= MCPU)
+ mode = 1;
+ }
+
+ if (mode)
+ decode(cpu);
+ else
+ decode_live(cpu);
+
+ return 0;
+}
diff --git a/tools/power/cpupower/debug/i386/dump_psb.c b/tools/power/cpupower/debug/i386/dump_psb.c
new file mode 100644
index 00000000000..8d6a4751425
--- /dev/null
+++ b/tools/power/cpupower/debug/i386/dump_psb.c
@@ -0,0 +1,196 @@
+/*
+ * dump_psb. (c) 2004, Dave Jones, Red Hat Inc.
+ * Licensed under the GPL v2.
+ */
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#define _GNU_SOURCE
+#include <getopt.h>
+
+#include <sys/mman.h>
+
+#define LEN (0x100000 - 0xc0000)
+#define OFFSET (0xc0000)
+
+#ifndef __packed
+#define __packed __attribute((packed))
+#endif
+
+static long relevant;
+
+static const int fid_to_mult[32] = {
+ 110, 115, 120, 125, 50, 55, 60, 65,
+ 70, 75, 80, 85, 90, 95, 100, 105,
+ 30, 190, 40, 200, 130, 135, 140, 210,
+ 150, 225, 160, 165, 170, 180, -1, -1,
+};
+
+static const int vid_to_voltage[32] = {
+ 2000, 1950, 1900, 1850, 1800, 1750, 1700, 1650,
+ 1600, 1550, 1500, 1450, 1400, 1350, 1300, 0,
+ 1275, 1250, 1225, 1200, 1175, 1150, 1125, 1100,
+ 1075, 1050, 1024, 1000, 975, 950, 925, 0,
+};
+
+struct psb_header {
+ char signature[10];
+ u_char version;
+ u_char flags;
+ u_short settlingtime;
+ u_char res1;
+ u_char numpst;
+} __packed;
+
+struct pst_header {
+ u_int32_t cpuid;
+ u_char fsb;
+ u_char maxfid;
+ u_char startvid;
+ u_char numpstates;
+} __packed;
+
+static u_int fsb;
+static u_int sgtc;
+
+static int
+decode_pst(char *p, int npstates)
+{
+ int i;
+ int freq, fid, vid;
+
+ for (i = 0; i < npstates; ++i) {
+ fid = *p++;
+ vid = *p++;
+ freq = 100 * fid_to_mult[fid] * fsb;
+
+ printf(" %2d %8dkHz FID %02x (%2d.%01d) VID %02x (%4dmV)\n",
+ i,
+ freq,
+ fid, fid_to_mult[fid]/10, fid_to_mult[fid]%10,
+ vid, vid_to_voltage[vid]);
+ }
+
+ return 0;
+}
+
+static
+void decode_psb(char *p, int numpst)
+{
+ int i;
+ struct psb_header *psb;
+ struct pst_header *pst;
+
+ psb = (struct psb_header*) p;
+
+ if (psb->version != 0x12)
+ return;
+
+ printf("PSB version: %hhx flags: %hhx settling time %hhuus res1 %hhx num pst %hhu\n",
+ psb->version,
+ psb->flags,
+ psb->settlingtime,
+ psb->res1,
+ psb->numpst);
+ sgtc = psb->settlingtime * 100;
+
+ if (sgtc < 10000)
+ sgtc = 10000;
+
+ p = ((char *) psb) + sizeof(struct psb_header);
+
+ if (numpst < 0)
+ numpst = psb->numpst;
+ else
+ printf("Overriding number of pst :%d\n", numpst);
+
+ for (i = 0; i < numpst; i++) {
+ pst = (struct pst_header*) p;
+
+ if (relevant != 0) {
+ if (relevant!= pst->cpuid)
+ goto next_one;
+ }
+
+ printf(" PST %d cpuid %.3x fsb %hhu mfid %hhx svid %hhx numberstates %hhu\n",
+ i+1,
+ pst->cpuid,
+ pst->fsb,
+ pst->maxfid,
+ pst->startvid,
+ pst->numpstates);
+
+ fsb = pst->fsb;
+ decode_pst(p + sizeof(struct pst_header), pst->numpstates);
+
+next_one:
+ p += sizeof(struct pst_header) + 2*pst->numpstates;
+ }
+
+}
+
+static struct option info_opts[] = {
+ {.name = "numpst", .has_arg=no_argument, .flag=NULL, .val='n'},
+};
+
+void print_help(void)
+{
+ printf ("Usage: dump_psb [options]\n");
+ printf ("Options:\n");
+ printf (" -n, --numpst Set number of PST tables to scan\n");
+ printf (" -r, --relevant Only display PSTs relevant to cpuid N\n");
+}
+
+int
+main(int argc, char *argv[])
+{
+ int fd;
+ int numpst=-1;
+ int ret=0, cont=1;
+ char *mem = NULL;
+ char *p;
+
+ do {
+ ret = getopt_long(argc, argv, "hr:n:", info_opts, NULL);
+ switch (ret){
+ case '?':
+ case 'h':
+ print_help();
+ cont = 0;
+ break;
+ case 'r':
+ relevant = strtol(optarg, NULL, 16);
+ break;
+ case 'n':
+ numpst = strtol(optarg, NULL, 10);
+ break;
+ case -1:
+ cont = 0;
+ break;
+ }
+
+ } while(cont);
+
+ fd = open("/dev/mem", O_RDONLY);
+ if (fd < 0) {
+ printf ("Couldn't open /dev/mem. Are you root?\n");
+ exit(1);
+ }
+
+ mem = mmap(mem, 0x100000 - 0xc0000, PROT_READ, MAP_SHARED, fd, 0xc0000);
+ close(fd);
+
+ for (p = mem; p - mem < LEN; p+=16) {
+ if (memcmp(p, "AMDK7PNOW!", 10) == 0) {
+ decode_psb(p, numpst);
+ break;
+ }
+ }
+
+ munmap(mem, LEN);
+ return 0;
+}
diff --git a/tools/power/cpupower/debug/i386/intel_gsic.c b/tools/power/cpupower/debug/i386/intel_gsic.c
new file mode 100644
index 00000000000..d032c826d42
--- /dev/null
+++ b/tools/power/cpupower/debug/i386/intel_gsic.c
@@ -0,0 +1,78 @@
+/*
+ * (C) 2003 Bruno Ducrot
+ * (C) 2004 Dominik Brodowski <linux@dominikbrodowski.de>
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ * Based on code found in
+ * linux/include/asm-i386/ist.h and linux/arch/i386/kernel/setup.c
+ * and originally developed by Andy Grover <andrew.grover@intel.com>
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <lrmi.h>
+
+int main (void)
+{
+ struct LRMI_regs r;
+ int retval;
+
+ if (!LRMI_init())
+ return 0;
+
+ memset(&r, 0, sizeof(r));
+
+ r.eax = 0x0000E980;
+ r.edx = 0x47534943;
+
+ retval = LRMI_int(0x15, &r);
+
+ if (!retval) {
+ printf("Failed!\n");
+ return 0;
+ }
+ if (r.eax == 0x47534943) {
+ printf("BIOS supports GSIC call:\n");
+ printf("\tsignature: %c%c%c%c\n",
+ (r.eax >> 24) & 0xff,
+ (r.eax >> 16) & 0xff,
+ (r.eax >> 8) & 0xff,
+ (r.eax) & 0xff);
+ printf("\tcommand port = 0x%.4x\n",
+ r.ebx & 0xffff);
+ printf("\tcommand = 0x%.4x\n",
+ (r.ebx >> 16) & 0xffff);
+ printf("\tevent port = 0x%.8x\n", r.ecx);
+ printf("\tflags = 0x%.8x\n", r.edx);
+ if (((r.ebx >> 16) & 0xffff) != 0x82) {
+ printf("non-default command value. If speedstep-smi "
+ "doesn't work out of the box,\nyou may want to "
+ "try out the default value by passing "
+ "smi_cmd=0x82 to the module\n ON YOUR OWN "
+ "RISK.\n");
+ }
+ if ((r.ebx & 0xffff) != 0xb2) {
+ printf("non-default command port. If speedstep-smi "
+ "doesn't work out of the box,\nyou may want to "
+ "try out the default value by passing "
+ "smi_port=0x82 to the module\n ON YOUR OWN "
+ "RISK.\n");
+ }
+ } else {
+ printf("BIOS DOES NOT support GSIC call. Dumping registers anyway:\n");
+ printf("eax = 0x%.8x\n", r.eax);
+ printf("ebx = 0x%.8x\n", r.ebx);
+ printf("ecx = 0x%.8x\n", r.ecx);
+ printf("edx = 0x%.8x\n", r.edx);
+ printf("Note also that some BIOS do not support the initial "
+ "GSIC call, but the newer\nspeedstep-smi driver may "
+ "work.\nFor this, you need to pass some arguments to "
+ "the speedstep-smi driver:\n");
+ printf("\tsmi_cmd=0x?? smi_port=0x?? smi_sig=1\n");
+ printf("\nUnfortunately, you have to know what exactly are "
+ "smi_cmd and smi_port, and this\nis system "
+ "dependant.\n");
+ }
+ return 1;
+}
diff --git a/tools/power/cpupower/debug/i386/powernow-k8-decode.c b/tools/power/cpupower/debug/i386/powernow-k8-decode.c
new file mode 100644
index 00000000000..638a6b3bfd9
--- /dev/null
+++ b/tools/power/cpupower/debug/i386/powernow-k8-decode.c
@@ -0,0 +1,96 @@
+/*
+ * (C) 2004 Bruno Ducrot <ducrot@poupinou.org>
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ * Based on code found in
+ * linux/arch/i386/kernel/cpu/cpufreq/powernow-k8.c
+ * and originally developed by Paul Devriendt
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#define MCPU 32
+
+#define MSR_FIDVID_STATUS 0xc0010042
+
+#define MSR_S_HI_CURRENT_VID 0x0000001f
+#define MSR_S_LO_CURRENT_FID 0x0000003f
+
+static int get_fidvid(uint32_t cpu, uint32_t *fid, uint32_t *vid)
+{
+ int err = 1;
+ uint64_t msr = 0;
+ int fd;
+ char file[20];
+
+ if (cpu > MCPU)
+ goto out;
+
+ sprintf(file, "/dev/cpu/%d/msr", cpu);
+
+ fd = open(file, O_RDONLY);
+ if (fd < 0)
+ goto out;
+ lseek(fd, MSR_FIDVID_STATUS, SEEK_CUR);
+ if (read(fd, &msr, 8) != 8)
+ goto err1;
+
+ *fid = ((uint32_t )(msr & 0xffffffffull)) & MSR_S_LO_CURRENT_FID;
+ *vid = ((uint32_t )(msr>>32 & 0xffffffffull)) & MSR_S_HI_CURRENT_VID;
+ err = 0;
+err1:
+ close(fd);
+out:
+ return err;
+}
+
+
+/* Return a frequency in MHz, given an input fid */
+static uint32_t find_freq_from_fid(uint32_t fid)
+{
+ return 800 + (fid * 100);
+}
+
+/* Return a voltage in miliVolts, given an input vid */
+static uint32_t find_millivolts_from_vid(uint32_t vid)
+{
+ return 1550-vid*25;
+}
+
+int main (int argc, char *argv[])
+{
+ int err;
+ int cpu;
+ uint32_t fid, vid;
+
+ if (argc < 2)
+ cpu = 0;
+ else
+ cpu = strtoul(argv[1], NULL, 0);
+
+ err = get_fidvid(cpu, &fid, &vid);
+
+ if (err) {
+ printf("can't get fid, vid from MSR\n");
+ printf("Possible trouble: you don't run a powernow-k8 capable cpu\n");
+ printf("or you are not root, or the msr driver is not present\n");
+ exit(1);
+ }
+
+
+ printf("cpu %d currently at %d MHz and %d mV\n",
+ cpu,
+ find_freq_from_fid(fid),
+ find_millivolts_from_vid(vid));
+
+ return 0;
+}
diff --git a/tools/power/cpupower/debug/kernel/Makefile b/tools/power/cpupower/debug/kernel/Makefile
new file mode 100644
index 00000000000..96b146fe6f8
--- /dev/null
+++ b/tools/power/cpupower/debug/kernel/Makefile
@@ -0,0 +1,23 @@
+obj-m :=
+
+KDIR := /lib/modules/$(shell uname -r)/build
+PWD := $(shell pwd)
+KMISC := /lib/modules/$(shell uname -r)/cpufrequtils/
+
+ifeq ("$(CONFIG_X86_TSC)", "y")
+ obj-m += cpufreq-test_tsc.o
+endif
+
+default:
+ $(MAKE) -C $(KDIR) M=$(PWD)
+
+clean:
+ - rm -rf *.o *.ko .tmp-versions .*.cmd .*.mod.* *.mod.c
+ - rm -rf .tmp_versions* Module.symvers modules.order
+
+install: default
+ install -d $(KMISC)
+ install -m 644 -c *.ko $(KMISC)
+ /sbin/depmod -a
+
+all: default
diff --git a/tools/power/cpupower/debug/kernel/cpufreq-test_tsc.c b/tools/power/cpupower/debug/kernel/cpufreq-test_tsc.c
new file mode 100644
index 00000000000..5224ee5b392
--- /dev/null
+++ b/tools/power/cpupower/debug/kernel/cpufreq-test_tsc.c
@@ -0,0 +1,110 @@
+/*
+ * test module to check whether the TSC-based delay routine continues
+ * to work properly after cpufreq transitions. Needs ACPI to work
+ * properly.
+ *
+ * Based partly on the Power Management Timer (PMTMR) code to be found
+ * in arch/i386/kernel/timers/timer_pm.c on recent 2.6. kernels, especially
+ * code written by John Stultz. The read_pmtmr function was copied verbatim
+ * from that file.
+ *
+ * (C) 2004 Dominik Brodowski
+ *
+ * To use:
+ * 1.) pass clock=tsc to the kernel on your bootloader
+ * 2.) modprobe this module (it'll fail)
+ * 3.) change CPU frequency
+ * 4.) modprobe this module again
+ * 5.) if the third value, "diff_pmtmr", changes between 2. and 4., the
+ * TSC-based delay routine on the Linux kernel does not correctly
+ * handle the cpufreq transition. Please report this to
+ * linux-pm@vger.kernel.org
+ */
+
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/acpi.h>
+#include <asm/io.h>
+
+static int pm_tmr_ioport = 0;
+
+/*helper function to safely read acpi pm timesource*/
+static u32 read_pmtmr(void)
+{
+ u32 v1=0,v2=0,v3=0;
+ /* It has been reported that because of various broken
+ * chipsets (ICH4, PIIX4 and PIIX4E) where the ACPI PM time
+ * source is not latched, so you must read it multiple
+ * times to insure a safe value is read.
+ */
+ do {
+ v1 = inl(pm_tmr_ioport);
+ v2 = inl(pm_tmr_ioport);
+ v3 = inl(pm_tmr_ioport);
+ } while ((v1 > v2 && v1 < v3) || (v2 > v3 && v2 < v1)
+ || (v3 > v1 && v3 < v2));
+
+ /* mask the output to 24 bits */
+ return (v2 & 0xFFFFFF);
+}
+
+static int __init cpufreq_test_tsc(void)
+{
+ u32 now, then, diff;
+ u64 now_tsc, then_tsc, diff_tsc;
+ int i;
+
+ /* the following code snipped is copied from arch/x86/kernel/acpi/boot.c
+ of Linux v2.6.25. */
+
+ /* detect the location of the ACPI PM Timer */
+ if (acpi_gbl_FADT.header.revision >= FADT2_REVISION_ID) {
+ /* FADT rev. 2 */
+ if (acpi_gbl_FADT.xpm_timer_block.space_id !=
+ ACPI_ADR_SPACE_SYSTEM_IO)
+ return 0;
+
+ pm_tmr_ioport = acpi_gbl_FADT.xpm_timer_block.address;
+ /*
+ * "X" fields are optional extensions to the original V1.0
+ * fields, so we must selectively expand V1.0 fields if the
+ * corresponding X field is zero.
+ */
+ if (!pm_tmr_ioport)
+ pm_tmr_ioport = acpi_gbl_FADT.pm_timer_block;
+ } else {
+ /* FADT rev. 1 */
+ pm_tmr_ioport = acpi_gbl_FADT.pm_timer_block;
+ }
+
+ printk(KERN_DEBUG "start--> \n");
+ then = read_pmtmr();
+ rdtscll(then_tsc);
+ for (i=0;i<20;i++) {
+ mdelay(100);
+ now = read_pmtmr();
+ rdtscll(now_tsc);
+ diff = (now - then) & 0xFFFFFF;
+ diff_tsc = now_tsc - then_tsc;
+ printk(KERN_DEBUG "t1: %08u t2: %08u diff_pmtmr: %08u diff_tsc: %016llu\n", then, now, diff, diff_tsc);
+ then = now;
+ then_tsc = now_tsc;
+ }
+ printk(KERN_DEBUG "<-- end \n");
+ return -ENODEV;
+}
+
+static void __exit cpufreq_none(void)
+{
+ return;
+}
+
+module_init(cpufreq_test_tsc)
+module_exit(cpufreq_none)
+
+
+MODULE_AUTHOR("Dominik Brodowski");
+MODULE_DESCRIPTION("Verify the TSC cpufreq notifier working correctly -- needs ACPI-enabled system");
+MODULE_LICENSE ("GPL");
diff --git a/tools/power/cpupower/debug/x86_64/Makefile b/tools/power/cpupower/debug/x86_64/Makefile
new file mode 100644
index 00000000000..1c521452671
--- /dev/null
+++ b/tools/power/cpupower/debug/x86_64/Makefile
@@ -0,0 +1,30 @@
+OUTPUT=./
+ifeq ("$(origin O)", "command line")
+ OUTPUT := $(O)/
+endif
+
+DESTDIR =
+bindir = /usr/bin
+
+INSTALL = /usr/bin/install
+
+
+default: all
+
+$(OUTPUT)centrino-decode: ../i386/centrino-decode.c
+ $(CC) $(CFLAGS) -o $@ $<
+
+$(OUTPUT)powernow-k8-decode: ../i386/powernow-k8-decode.c
+ $(CC) $(CFLAGS) -o $@ $<
+
+all: $(OUTPUT)centrino-decode $(OUTPUT)powernow-k8-decode
+
+clean:
+ rm -rf $(OUTPUT)centrino-decode $(OUTPUT)powernow-k8-decode
+
+install:
+ $(INSTALL) -d $(DESTDIR)${bindir}
+ $(INSTALL) $(OUTPUT)centrino-decode $(DESTDIR)${bindir}
+ $(INSTALL) $(OUTPUT)powernow-k8-decode $(DESTDIR)${bindir}
+
+.PHONY: all default clean install
diff --git a/tools/power/cpupower/lib/cpufreq.c b/tools/power/cpupower/lib/cpufreq.c
new file mode 100644
index 00000000000..d961101d1ce
--- /dev/null
+++ b/tools/power/cpupower/lib/cpufreq.c
@@ -0,0 +1,208 @@
+/*
+ * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de>
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ */
+
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "cpufreq.h"
+#include "sysfs.h"
+
+int cpufreq_cpu_exists(unsigned int cpu)
+{
+ return sysfs_cpu_exists(cpu);
+}
+
+unsigned long cpufreq_get_freq_kernel(unsigned int cpu)
+{
+ return sysfs_get_freq_kernel(cpu);
+}
+
+unsigned long cpufreq_get_freq_hardware(unsigned int cpu)
+{
+ return sysfs_get_freq_hardware(cpu);
+}
+
+unsigned long cpufreq_get_transition_latency(unsigned int cpu)
+{
+ return sysfs_get_freq_transition_latency(cpu);
+}
+
+int cpufreq_get_hardware_limits(unsigned int cpu,
+ unsigned long *min,
+ unsigned long *max)
+{
+ if ((!min) || (!max))
+ return -EINVAL;
+ return sysfs_get_freq_hardware_limits(cpu, min, max);
+}
+
+char *cpufreq_get_driver(unsigned int cpu)
+{
+ return sysfs_get_freq_driver(cpu);
+}
+
+void cpufreq_put_driver(char *ptr)
+{
+ if (!ptr)
+ return;
+ free(ptr);
+}
+
+struct cpufreq_policy *cpufreq_get_policy(unsigned int cpu)
+{
+ return sysfs_get_freq_policy(cpu);
+}
+
+void cpufreq_put_policy(struct cpufreq_policy *policy)
+{
+ if ((!policy) || (!policy->governor))
+ return;
+
+ free(policy->governor);
+ policy->governor = NULL;
+ free(policy);
+}
+
+struct cpufreq_available_governors *cpufreq_get_available_governors(unsigned
+ int cpu)
+{
+ return sysfs_get_freq_available_governors(cpu);
+}
+
+void cpufreq_put_available_governors(struct cpufreq_available_governors *any)
+{
+ struct cpufreq_available_governors *tmp, *next;
+
+ if (!any)
+ return;
+
+ tmp = any->first;
+ while (tmp) {
+ next = tmp->next;
+ if (tmp->governor)
+ free(tmp->governor);
+ free(tmp);
+ tmp = next;
+ }
+}
+
+
+struct cpufreq_available_frequencies
+*cpufreq_get_available_frequencies(unsigned int cpu)
+{
+ return sysfs_get_available_frequencies(cpu);
+}
+
+void cpufreq_put_available_frequencies(struct cpufreq_available_frequencies
+ *any) {
+ struct cpufreq_available_frequencies *tmp, *next;
+
+ if (!any)
+ return;
+
+ tmp = any->first;
+ while (tmp) {
+ next = tmp->next;
+ free(tmp);
+ tmp = next;
+ }
+}
+
+
+struct cpufreq_affected_cpus *cpufreq_get_affected_cpus(unsigned int cpu)
+{
+ return sysfs_get_freq_affected_cpus(cpu);
+}
+
+void cpufreq_put_affected_cpus(struct cpufreq_affected_cpus *any)
+{
+ struct cpufreq_affected_cpus *tmp, *next;
+
+ if (!any)
+ return;
+
+ tmp = any->first;
+ while (tmp) {
+ next = tmp->next;
+ free(tmp);
+ tmp = next;
+ }
+}
+
+
+struct cpufreq_affected_cpus *cpufreq_get_related_cpus(unsigned int cpu)
+{
+ return sysfs_get_freq_related_cpus(cpu);
+}
+
+void cpufreq_put_related_cpus(struct cpufreq_affected_cpus *any)
+{
+ cpufreq_put_affected_cpus(any);
+}
+
+
+int cpufreq_set_policy(unsigned int cpu, struct cpufreq_policy *policy)
+{
+ if (!policy || !(policy->governor))
+ return -EINVAL;
+
+ return sysfs_set_freq_policy(cpu, policy);
+}
+
+
+int cpufreq_modify_policy_min(unsigned int cpu, unsigned long min_freq)
+{
+ return sysfs_modify_freq_policy_min(cpu, min_freq);
+}
+
+
+int cpufreq_modify_policy_max(unsigned int cpu, unsigned long max_freq)
+{
+ return sysfs_modify_freq_policy_max(cpu, max_freq);
+}
+
+
+int cpufreq_modify_policy_governor(unsigned int cpu, char *governor)
+{
+ if ((!governor) || (strlen(governor) > 19))
+ return -EINVAL;
+
+ return sysfs_modify_freq_policy_governor(cpu, governor);
+}
+
+int cpufreq_set_frequency(unsigned int cpu, unsigned long target_frequency)
+{
+ return sysfs_set_frequency(cpu, target_frequency);
+}
+
+struct cpufreq_stats *cpufreq_get_stats(unsigned int cpu,
+ unsigned long long *total_time)
+{
+ return sysfs_get_freq_stats(cpu, total_time);
+}
+
+void cpufreq_put_stats(struct cpufreq_stats *any)
+{
+ struct cpufreq_stats *tmp, *next;
+
+ if (!any)
+ return;
+
+ tmp = any->first;
+ while (tmp) {
+ next = tmp->next;
+ free(tmp);
+ tmp = next;
+ }
+}
+
+unsigned long cpufreq_get_transitions(unsigned int cpu)
+{
+ return sysfs_get_freq_transitions(cpu);
+}
diff --git a/tools/power/cpupower/lib/cpufreq.h b/tools/power/cpupower/lib/cpufreq.h
new file mode 100644
index 00000000000..3aae8e7a083
--- /dev/null
+++ b/tools/power/cpupower/lib/cpufreq.h
@@ -0,0 +1,223 @@
+/*
+ * cpufreq.h - definitions for libcpufreq
+ *
+ * Copyright (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 2 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+#ifndef _CPUFREQ_H
+#define _CPUFREQ_H 1
+
+struct cpufreq_policy {
+ unsigned long min;
+ unsigned long max;
+ char *governor;
+};
+
+struct cpufreq_available_governors {
+ char *governor;
+ struct cpufreq_available_governors *next;
+ struct cpufreq_available_governors *first;
+};
+
+struct cpufreq_available_frequencies {
+ unsigned long frequency;
+ struct cpufreq_available_frequencies *next;
+ struct cpufreq_available_frequencies *first;
+};
+
+
+struct cpufreq_affected_cpus {
+ unsigned int cpu;
+ struct cpufreq_affected_cpus *next;
+ struct cpufreq_affected_cpus *first;
+};
+
+struct cpufreq_stats {
+ unsigned long frequency;
+ unsigned long long time_in_state;
+ struct cpufreq_stats *next;
+ struct cpufreq_stats *first;
+};
+
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*
+ * returns 0 if the specified CPU is present (it doesn't say
+ * whether it is online!), and an error value if not.
+ */
+
+extern int cpufreq_cpu_exists(unsigned int cpu);
+
+/* determine current CPU frequency
+ * - _kernel variant means kernel's opinion of CPU frequency
+ * - _hardware variant means actual hardware CPU frequency,
+ * which is only available to root.
+ *
+ * returns 0 on failure, else frequency in kHz.
+ */
+
+extern unsigned long cpufreq_get_freq_kernel(unsigned int cpu);
+
+extern unsigned long cpufreq_get_freq_hardware(unsigned int cpu);
+
+#define cpufreq_get(cpu) cpufreq_get_freq_kernel(cpu);
+
+
+/* determine CPU transition latency
+ *
+ * returns 0 on failure, else transition latency in 10^(-9) s = nanoseconds
+ */
+extern unsigned long cpufreq_get_transition_latency(unsigned int cpu);
+
+
+/* determine hardware CPU frequency limits
+ *
+ * These may be limited further by thermal, energy or other
+ * considerations by cpufreq policy notifiers in the kernel.
+ */
+
+extern int cpufreq_get_hardware_limits(unsigned int cpu,
+ unsigned long *min,
+ unsigned long *max);
+
+
+/* determine CPUfreq driver used
+ *
+ * Remember to call cpufreq_put_driver when no longer needed
+ * to avoid memory leakage, please.
+ */
+
+extern char *cpufreq_get_driver(unsigned int cpu);
+
+extern void cpufreq_put_driver(char *ptr);
+
+
+/* determine CPUfreq policy currently used
+ *
+ * Remember to call cpufreq_put_policy when no longer needed
+ * to avoid memory leakage, please.
+ */
+
+
+extern struct cpufreq_policy *cpufreq_get_policy(unsigned int cpu);
+
+extern void cpufreq_put_policy(struct cpufreq_policy *policy);
+
+
+/* determine CPUfreq governors currently available
+ *
+ * may be modified by modprobe'ing or rmmod'ing other governors. Please
+ * free allocated memory by calling cpufreq_put_available_governors
+ * after use.
+ */
+
+
+extern struct cpufreq_available_governors
+*cpufreq_get_available_governors(unsigned int cpu);
+
+extern void cpufreq_put_available_governors(
+ struct cpufreq_available_governors *first);
+
+
+/* determine CPU frequency states available
+ *
+ * Only present on _some_ ->target() cpufreq drivers. For information purposes
+ * only. Please free allocated memory by calling
+ * cpufreq_put_available_frequencies after use.
+ */
+
+extern struct cpufreq_available_frequencies
+*cpufreq_get_available_frequencies(unsigned int cpu);
+
+extern void cpufreq_put_available_frequencies(
+ struct cpufreq_available_frequencies *first);
+
+
+/* determine affected CPUs
+ *
+ * Remember to call cpufreq_put_affected_cpus when no longer needed
+ * to avoid memory leakage, please.
+ */
+
+extern struct cpufreq_affected_cpus *cpufreq_get_affected_cpus(unsigned
+ int cpu);
+
+extern void cpufreq_put_affected_cpus(struct cpufreq_affected_cpus *first);
+
+
+/* determine related CPUs
+ *
+ * Remember to call cpufreq_put_related_cpus when no longer needed
+ * to avoid memory leakage, please.
+ */
+
+extern struct cpufreq_affected_cpus *cpufreq_get_related_cpus(unsigned
+ int cpu);
+
+extern void cpufreq_put_related_cpus(struct cpufreq_affected_cpus *first);
+
+
+/* determine stats for cpufreq subsystem
+ *
+ * This is not available in all kernel versions or configurations.
+ */
+
+extern struct cpufreq_stats *cpufreq_get_stats(unsigned int cpu,
+ unsigned long long *total_time);
+
+extern void cpufreq_put_stats(struct cpufreq_stats *stats);
+
+extern unsigned long cpufreq_get_transitions(unsigned int cpu);
+
+
+/* set new cpufreq policy
+ *
+ * Tries to set the passed policy as new policy as close as possible,
+ * but results may differ depending e.g. on governors being available.
+ */
+
+extern int cpufreq_set_policy(unsigned int cpu, struct cpufreq_policy *policy);
+
+
+/* modify a policy by only changing min/max freq or governor
+ *
+ * Does not check whether result is what was intended.
+ */
+
+extern int cpufreq_modify_policy_min(unsigned int cpu, unsigned long min_freq);
+extern int cpufreq_modify_policy_max(unsigned int cpu, unsigned long max_freq);
+extern int cpufreq_modify_policy_governor(unsigned int cpu, char *governor);
+
+
+/* set a specific frequency
+ *
+ * Does only work if userspace governor can be used and no external
+ * interference (other calls to this function or to set/modify_policy)
+ * occurs. Also does not work on ->range() cpufreq drivers.
+ */
+
+extern int cpufreq_set_frequency(unsigned int cpu,
+ unsigned long target_frequency);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _CPUFREQ_H */
diff --git a/tools/power/cpupower/lib/sysfs.c b/tools/power/cpupower/lib/sysfs.c
new file mode 100644
index 00000000000..870713a75a8
--- /dev/null
+++ b/tools/power/cpupower/lib/sysfs.c
@@ -0,0 +1,672 @@
+/*
+ * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de>
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "cpufreq.h"
+
+#define PATH_TO_CPU "/sys/devices/system/cpu/"
+#define MAX_LINE_LEN 4096
+#define SYSFS_PATH_MAX 255
+
+
+static unsigned int sysfs_read_file(const char *path, char *buf, size_t buflen)
+{
+ int fd;
+ ssize_t numread;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return 0;
+
+ numread = read(fd, buf, buflen - 1);
+ if (numread < 1) {
+ close(fd);
+ return 0;
+ }
+
+ buf[numread] = '\0';
+ close(fd);
+
+ return (unsigned int) numread;
+}
+
+
+/* CPUFREQ sysfs access **************************************************/
+
+/* helper function to read file from /sys into given buffer */
+/* fname is a relative path under "cpuX/cpufreq" dir */
+static unsigned int sysfs_cpufreq_read_file(unsigned int cpu, const char *fname,
+ char *buf, size_t buflen)
+{
+ char path[SYSFS_PATH_MAX];
+
+ snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpufreq/%s",
+ cpu, fname);
+ return sysfs_read_file(path, buf, buflen);
+}
+
+/* helper function to write a new value to a /sys file */
+/* fname is a relative path under "cpuX/cpufreq" dir */
+static unsigned int sysfs_cpufreq_write_file(unsigned int cpu,
+ const char *fname,
+ const char *value, size_t len)
+{
+ char path[SYSFS_PATH_MAX];
+ int fd;
+ ssize_t numwrite;
+
+ snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpufreq/%s",
+ cpu, fname);
+
+ fd = open(path, O_WRONLY);
+ if (fd == -1)
+ return 0;
+
+ numwrite = write(fd, value, len);
+ if (numwrite < 1) {
+ close(fd);
+ return 0;
+ }
+
+ close(fd);
+
+ return (unsigned int) numwrite;
+}
+
+/* read access to files which contain one numeric value */
+
+enum cpufreq_value {
+ CPUINFO_CUR_FREQ,
+ CPUINFO_MIN_FREQ,
+ CPUINFO_MAX_FREQ,
+ CPUINFO_LATENCY,
+ SCALING_CUR_FREQ,
+ SCALING_MIN_FREQ,
+ SCALING_MAX_FREQ,
+ STATS_NUM_TRANSITIONS,
+ MAX_CPUFREQ_VALUE_READ_FILES
+};
+
+static const char *cpufreq_value_files[MAX_CPUFREQ_VALUE_READ_FILES] = {
+ [CPUINFO_CUR_FREQ] = "cpuinfo_cur_freq",
+ [CPUINFO_MIN_FREQ] = "cpuinfo_min_freq",
+ [CPUINFO_MAX_FREQ] = "cpuinfo_max_freq",
+ [CPUINFO_LATENCY] = "cpuinfo_transition_latency",
+ [SCALING_CUR_FREQ] = "scaling_cur_freq",
+ [SCALING_MIN_FREQ] = "scaling_min_freq",
+ [SCALING_MAX_FREQ] = "scaling_max_freq",
+ [STATS_NUM_TRANSITIONS] = "stats/total_trans"
+};
+
+
+static unsigned long sysfs_cpufreq_get_one_value(unsigned int cpu,
+ enum cpufreq_value which)
+{
+ unsigned long value;
+ unsigned int len;
+ char linebuf[MAX_LINE_LEN];
+ char *endp;
+
+ if (which >= MAX_CPUFREQ_VALUE_READ_FILES)
+ return 0;
+
+ len = sysfs_cpufreq_read_file(cpu, cpufreq_value_files[which],
+ linebuf, sizeof(linebuf));
+
+ if (len == 0)
+ return 0;
+
+ value = strtoul(linebuf, &endp, 0);
+
+ if (endp == linebuf || errno == ERANGE)
+ return 0;
+
+ return value;
+}
+
+/* read access to files which contain one string */
+
+enum cpufreq_string {
+ SCALING_DRIVER,
+ SCALING_GOVERNOR,
+ MAX_CPUFREQ_STRING_FILES
+};
+
+static const char *cpufreq_string_files[MAX_CPUFREQ_STRING_FILES] = {
+ [SCALING_DRIVER] = "scaling_driver",
+ [SCALING_GOVERNOR] = "scaling_governor",
+};
+
+
+static char *sysfs_cpufreq_get_one_string(unsigned int cpu,
+ enum cpufreq_string which)
+{
+ char linebuf[MAX_LINE_LEN];
+ char *result;
+ unsigned int len;
+
+ if (which >= MAX_CPUFREQ_STRING_FILES)
+ return NULL;
+
+ len = sysfs_cpufreq_read_file(cpu, cpufreq_string_files[which],
+ linebuf, sizeof(linebuf));
+ if (len == 0)
+ return NULL;
+
+ result = strdup(linebuf);
+ if (result == NULL)
+ return NULL;
+
+ if (result[strlen(result) - 1] == '\n')
+ result[strlen(result) - 1] = '\0';
+
+ return result;
+}
+
+/* write access */
+
+enum cpufreq_write {
+ WRITE_SCALING_MIN_FREQ,
+ WRITE_SCALING_MAX_FREQ,
+ WRITE_SCALING_GOVERNOR,
+ WRITE_SCALING_SET_SPEED,
+ MAX_CPUFREQ_WRITE_FILES
+};
+
+static const char *cpufreq_write_files[MAX_CPUFREQ_WRITE_FILES] = {
+ [WRITE_SCALING_MIN_FREQ] = "scaling_min_freq",
+ [WRITE_SCALING_MAX_FREQ] = "scaling_max_freq",
+ [WRITE_SCALING_GOVERNOR] = "scaling_governor",
+ [WRITE_SCALING_SET_SPEED] = "scaling_setspeed",
+};
+
+static int sysfs_cpufreq_write_one_value(unsigned int cpu,
+ enum cpufreq_write which,
+ const char *new_value, size_t len)
+{
+ if (which >= MAX_CPUFREQ_WRITE_FILES)
+ return 0;
+
+ if (sysfs_cpufreq_write_file(cpu, cpufreq_write_files[which],
+ new_value, len) != len)
+ return -ENODEV;
+
+ return 0;
+};
+
+unsigned long sysfs_get_freq_kernel(unsigned int cpu)
+{
+ return sysfs_cpufreq_get_one_value(cpu, SCALING_CUR_FREQ);
+}
+
+unsigned long sysfs_get_freq_hardware(unsigned int cpu)
+{
+ return sysfs_cpufreq_get_one_value(cpu, CPUINFO_CUR_FREQ);
+}
+
+unsigned long sysfs_get_freq_transition_latency(unsigned int cpu)
+{
+ return sysfs_cpufreq_get_one_value(cpu, CPUINFO_LATENCY);
+}
+
+int sysfs_get_freq_hardware_limits(unsigned int cpu,
+ unsigned long *min,
+ unsigned long *max)
+{
+ if ((!min) || (!max))
+ return -EINVAL;
+
+ *min = sysfs_cpufreq_get_one_value(cpu, CPUINFO_MIN_FREQ);
+ if (!*min)
+ return -ENODEV;
+
+ *max = sysfs_cpufreq_get_one_value(cpu, CPUINFO_MAX_FREQ);
+ if (!*max)
+ return -ENODEV;
+
+ return 0;
+}
+
+char *sysfs_get_freq_driver(unsigned int cpu)
+{
+ return sysfs_cpufreq_get_one_string(cpu, SCALING_DRIVER);
+}
+
+struct cpufreq_policy *sysfs_get_freq_policy(unsigned int cpu)
+{
+ struct cpufreq_policy *policy;
+
+ policy = malloc(sizeof(struct cpufreq_policy));
+ if (!policy)
+ return NULL;
+
+ policy->governor = sysfs_cpufreq_get_one_string(cpu, SCALING_GOVERNOR);
+ if (!policy->governor) {
+ free(policy);
+ return NULL;
+ }
+ policy->min = sysfs_cpufreq_get_one_value(cpu, SCALING_MIN_FREQ);
+ policy->max = sysfs_cpufreq_get_one_value(cpu, SCALING_MAX_FREQ);
+ if ((!policy->min) || (!policy->max)) {
+ free(policy->governor);
+ free(policy);
+ return NULL;
+ }
+
+ return policy;
+}
+
+struct cpufreq_available_governors *
+sysfs_get_freq_available_governors(unsigned int cpu) {
+ struct cpufreq_available_governors *first = NULL;
+ struct cpufreq_available_governors *current = NULL;
+ char linebuf[MAX_LINE_LEN];
+ unsigned int pos, i;
+ unsigned int len;
+
+ len = sysfs_cpufreq_read_file(cpu, "scaling_available_governors",
+ linebuf, sizeof(linebuf));
+ if (len == 0)
+ return NULL;
+
+ pos = 0;
+ for (i = 0; i < len; i++) {
+ if (linebuf[i] == ' ' || linebuf[i] == '\n') {
+ if (i - pos < 2)
+ continue;
+ if (current) {
+ current->next = malloc(sizeof(*current));
+ if (!current->next)
+ goto error_out;
+ current = current->next;
+ } else {
+ first = malloc(sizeof(*first));
+ if (!first)
+ goto error_out;
+ current = first;
+ }
+ current->first = first;
+ current->next = NULL;
+
+ current->governor = malloc(i - pos + 1);
+ if (!current->governor)
+ goto error_out;
+
+ memcpy(current->governor, linebuf + pos, i - pos);
+ current->governor[i - pos] = '\0';
+ pos = i + 1;
+ }
+ }
+
+ return first;
+
+ error_out:
+ while (first) {
+ current = first->next;
+ if (first->governor)
+ free(first->governor);
+ free(first);
+ first = current;
+ }
+ return NULL;
+}
+
+
+struct cpufreq_available_frequencies *
+sysfs_get_available_frequencies(unsigned int cpu) {
+ struct cpufreq_available_frequencies *first = NULL;
+ struct cpufreq_available_frequencies *current = NULL;
+ char one_value[SYSFS_PATH_MAX];
+ char linebuf[MAX_LINE_LEN];
+ unsigned int pos, i;
+ unsigned int len;
+
+ len = sysfs_cpufreq_read_file(cpu, "scaling_available_frequencies",
+ linebuf, sizeof(linebuf));
+ if (len == 0)
+ return NULL;
+
+ pos = 0;
+ for (i = 0; i < len; i++) {
+ if (linebuf[i] == ' ' || linebuf[i] == '\n') {
+ if (i - pos < 2)
+ continue;
+ if (i - pos >= SYSFS_PATH_MAX)
+ goto error_out;
+ if (current) {
+ current->next = malloc(sizeof(*current));
+ if (!current->next)
+ goto error_out;
+ current = current->next;
+ } else {
+ first = malloc(sizeof(*first));
+ if (!first)
+ goto error_out;
+ current = first;
+ }
+ current->first = first;
+ current->next = NULL;
+
+ memcpy(one_value, linebuf + pos, i - pos);
+ one_value[i - pos] = '\0';
+ if (sscanf(one_value, "%lu", &current->frequency) != 1)
+ goto error_out;
+
+ pos = i + 1;
+ }
+ }
+
+ return first;
+
+ error_out:
+ while (first) {
+ current = first->next;
+ free(first);
+ first = current;
+ }
+ return NULL;
+}
+
+static struct cpufreq_affected_cpus *sysfs_get_cpu_list(unsigned int cpu,
+ const char *file)
+{
+ struct cpufreq_affected_cpus *first = NULL;
+ struct cpufreq_affected_cpus *current = NULL;
+ char one_value[SYSFS_PATH_MAX];
+ char linebuf[MAX_LINE_LEN];
+ unsigned int pos, i;
+ unsigned int len;
+
+ len = sysfs_cpufreq_read_file(cpu, file, linebuf, sizeof(linebuf));
+ if (len == 0)
+ return NULL;
+
+ pos = 0;
+ for (i = 0; i < len; i++) {
+ if (i == len || linebuf[i] == ' ' || linebuf[i] == '\n') {
+ if (i - pos < 1)
+ continue;
+ if (i - pos >= SYSFS_PATH_MAX)
+ goto error_out;
+ if (current) {
+ current->next = malloc(sizeof(*current));
+ if (!current->next)
+ goto error_out;
+ current = current->next;
+ } else {
+ first = malloc(sizeof(*first));
+ if (!first)
+ goto error_out;
+ current = first;
+ }
+ current->first = first;
+ current->next = NULL;
+
+ memcpy(one_value, linebuf + pos, i - pos);
+ one_value[i - pos] = '\0';
+
+ if (sscanf(one_value, "%u", &current->cpu) != 1)
+ goto error_out;
+
+ pos = i + 1;
+ }
+ }
+
+ return first;
+
+ error_out:
+ while (first) {
+ current = first->next;
+ free(first);
+ first = current;
+ }
+ return NULL;
+}
+
+struct cpufreq_affected_cpus *sysfs_get_freq_affected_cpus(unsigned int cpu)
+{
+ return sysfs_get_cpu_list(cpu, "affected_cpus");
+}
+
+struct cpufreq_affected_cpus *sysfs_get_freq_related_cpus(unsigned int cpu)
+{
+ return sysfs_get_cpu_list(cpu, "related_cpus");
+}
+
+struct cpufreq_stats *sysfs_get_freq_stats(unsigned int cpu,
+ unsigned long long *total_time) {
+ struct cpufreq_stats *first = NULL;
+ struct cpufreq_stats *current = NULL;
+ char one_value[SYSFS_PATH_MAX];
+ char linebuf[MAX_LINE_LEN];
+ unsigned int pos, i;
+ unsigned int len;
+
+ len = sysfs_cpufreq_read_file(cpu, "stats/time_in_state",
+ linebuf, sizeof(linebuf));
+ if (len == 0)
+ return NULL;
+
+ *total_time = 0;
+ pos = 0;
+ for (i = 0; i < len; i++) {
+ if (i == strlen(linebuf) || linebuf[i] == '\n') {
+ if (i - pos < 2)
+ continue;
+ if ((i - pos) >= SYSFS_PATH_MAX)
+ goto error_out;
+ if (current) {
+ current->next = malloc(sizeof(*current));
+ if (!current->next)
+ goto error_out;
+ current = current->next;
+ } else {
+ first = malloc(sizeof(*first));
+ if (!first)
+ goto error_out;
+ current = first;
+ }
+ current->first = first;
+ current->next = NULL;
+
+ memcpy(one_value, linebuf + pos, i - pos);
+ one_value[i - pos] = '\0';
+ if (sscanf(one_value, "%lu %llu",
+ &current->frequency,
+ &current->time_in_state) != 2)
+ goto error_out;
+
+ *total_time = *total_time + current->time_in_state;
+ pos = i + 1;
+ }
+ }
+
+ return first;
+
+ error_out:
+ while (first) {
+ current = first->next;
+ free(first);
+ first = current;
+ }
+ return NULL;
+}
+
+unsigned long sysfs_get_freq_transitions(unsigned int cpu)
+{
+ return sysfs_cpufreq_get_one_value(cpu, STATS_NUM_TRANSITIONS);
+}
+
+static int verify_gov(char *new_gov, char *passed_gov)
+{
+ unsigned int i, j = 0;
+
+ if (!passed_gov || (strlen(passed_gov) > 19))
+ return -EINVAL;
+
+ strncpy(new_gov, passed_gov, 20);
+ for (i = 0; i < 20; i++) {
+ if (j) {
+ new_gov[i] = '\0';
+ continue;
+ }
+ if ((new_gov[i] >= 'a') && (new_gov[i] <= 'z'))
+ continue;
+
+ if ((new_gov[i] >= 'A') && (new_gov[i] <= 'Z'))
+ continue;
+
+ if (new_gov[i] == '-')
+ continue;
+
+ if (new_gov[i] == '_')
+ continue;
+
+ if (new_gov[i] == '\0') {
+ j = 1;
+ continue;
+ }
+ return -EINVAL;
+ }
+ new_gov[19] = '\0';
+ return 0;
+}
+
+int sysfs_modify_freq_policy_governor(unsigned int cpu, char *governor)
+{
+ char new_gov[SYSFS_PATH_MAX];
+
+ if (!governor)
+ return -EINVAL;
+
+ if (verify_gov(new_gov, governor))
+ return -EINVAL;
+
+ return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_GOVERNOR,
+ new_gov, strlen(new_gov));
+};
+
+int sysfs_modify_freq_policy_max(unsigned int cpu, unsigned long max_freq)
+{
+ char value[SYSFS_PATH_MAX];
+
+ snprintf(value, SYSFS_PATH_MAX, "%lu", max_freq);
+
+ return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MAX_FREQ,
+ value, strlen(value));
+};
+
+
+int sysfs_modify_freq_policy_min(unsigned int cpu, unsigned long min_freq)
+{
+ char value[SYSFS_PATH_MAX];
+
+ snprintf(value, SYSFS_PATH_MAX, "%lu", min_freq);
+
+ return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MIN_FREQ,
+ value, strlen(value));
+};
+
+
+int sysfs_set_freq_policy(unsigned int cpu, struct cpufreq_policy *policy)
+{
+ char min[SYSFS_PATH_MAX];
+ char max[SYSFS_PATH_MAX];
+ char gov[SYSFS_PATH_MAX];
+ int ret;
+ unsigned long old_min;
+ int write_max_first;
+
+ if (!policy || !(policy->governor))
+ return -EINVAL;
+
+ if (policy->max < policy->min)
+ return -EINVAL;
+
+ if (verify_gov(gov, policy->governor))
+ return -EINVAL;
+
+ snprintf(min, SYSFS_PATH_MAX, "%lu", policy->min);
+ snprintf(max, SYSFS_PATH_MAX, "%lu", policy->max);
+
+ old_min = sysfs_cpufreq_get_one_value(cpu, SCALING_MIN_FREQ);
+ write_max_first = (old_min && (policy->max < old_min) ? 0 : 1);
+
+ if (write_max_first) {
+ ret = sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MAX_FREQ,
+ max, strlen(max));
+ if (ret)
+ return ret;
+ }
+
+ ret = sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MIN_FREQ, min,
+ strlen(min));
+ if (ret)
+ return ret;
+
+ if (!write_max_first) {
+ ret = sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_MAX_FREQ,
+ max, strlen(max));
+ if (ret)
+ return ret;
+ }
+
+ return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_GOVERNOR,
+ gov, strlen(gov));
+}
+
+int sysfs_set_frequency(unsigned int cpu, unsigned long target_frequency)
+{
+ struct cpufreq_policy *pol = sysfs_get_freq_policy(cpu);
+ char userspace_gov[] = "userspace";
+ char freq[SYSFS_PATH_MAX];
+ int ret;
+
+ if (!pol)
+ return -ENODEV;
+
+ if (strncmp(pol->governor, userspace_gov, 9) != 0) {
+ ret = sysfs_modify_freq_policy_governor(cpu, userspace_gov);
+ if (ret) {
+ cpufreq_put_policy(pol);
+ return ret;
+ }
+ }
+
+ cpufreq_put_policy(pol);
+
+ snprintf(freq, SYSFS_PATH_MAX, "%lu", target_frequency);
+
+ return sysfs_cpufreq_write_one_value(cpu, WRITE_SCALING_SET_SPEED,
+ freq, strlen(freq));
+}
+
+/* CPUFREQ sysfs access **************************************************/
+
+/* General sysfs access **************************************************/
+int sysfs_cpu_exists(unsigned int cpu)
+{
+ char file[SYSFS_PATH_MAX];
+ struct stat statbuf;
+
+ snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU "cpu%u/", cpu);
+
+ if (stat(file, &statbuf) != 0)
+ return -ENOSYS;
+
+ return S_ISDIR(statbuf.st_mode) ? 0 : -ENOSYS;
+}
+
+/* General sysfs access **************************************************/
diff --git a/tools/power/cpupower/lib/sysfs.h b/tools/power/cpupower/lib/sysfs.h
new file mode 100644
index 00000000000..c76a5e0af50
--- /dev/null
+++ b/tools/power/cpupower/lib/sysfs.h
@@ -0,0 +1,31 @@
+/* General */
+extern unsigned int sysfs_cpu_exists(unsigned int cpu);
+
+/* CPUfreq */
+extern unsigned long sysfs_get_freq_kernel(unsigned int cpu);
+extern unsigned long sysfs_get_freq_hardware(unsigned int cpu);
+extern unsigned long sysfs_get_freq_transition_latency(unsigned int cpu);
+extern int sysfs_get_freq_hardware_limits(unsigned int cpu,
+ unsigned long *min, unsigned long *max);
+extern char *sysfs_get_freq_driver(unsigned int cpu);
+extern struct cpufreq_policy *sysfs_get_freq_policy(unsigned int cpu);
+extern struct cpufreq_available_governors *sysfs_get_freq_available_governors(
+ unsigned int cpu);
+extern struct cpufreq_available_frequencies *sysfs_get_available_frequencies(
+ unsigned int cpu);
+extern struct cpufreq_affected_cpus *sysfs_get_freq_affected_cpus(
+ unsigned int cpu);
+extern struct cpufreq_affected_cpus *sysfs_get_freq_related_cpus(
+ unsigned int cpu);
+extern struct cpufreq_stats *sysfs_get_freq_stats(unsigned int cpu,
+ unsigned long long *total_time);
+extern unsigned long sysfs_get_freq_transitions(unsigned int cpu);
+extern int sysfs_set_freq_policy(unsigned int cpu,
+ struct cpufreq_policy *policy);
+extern int sysfs_modify_freq_policy_min(unsigned int cpu,
+ unsigned long min_freq);
+extern int sysfs_modify_freq_policy_max(unsigned int cpu,
+ unsigned long max_freq);
+extern int sysfs_modify_freq_policy_governor(unsigned int cpu, char *governor);
+extern int sysfs_set_frequency(unsigned int cpu,
+ unsigned long target_frequency);
diff --git a/tools/power/cpupower/man/cpupower-frequency-info.1 b/tools/power/cpupower/man/cpupower-frequency-info.1
new file mode 100644
index 00000000000..9c85a382e35
--- /dev/null
+++ b/tools/power/cpupower/man/cpupower-frequency-info.1
@@ -0,0 +1,77 @@
+.TH "CPUPOWER\-FREQUENCY\-INFO" "1" "0.1" "" "cpupower Manual"
+.SH "NAME"
+.LP
+cpupower frequency\-info \- Utility to retrieve cpufreq kernel information
+.SH "SYNTAX"
+.LP
+cpupower [ \-c cpulist ] frequency\-info [\fIoptions\fP]
+.SH "DESCRIPTION"
+.LP
+A small tool which prints out cpufreq information helpful to developers and interested users.
+.SH "OPTIONS"
+.LP
+.TP
+\fB\-e\fR \fB\-\-debug\fR
+Prints out debug information.
+.TP
+\fB\-f\fR \fB\-\-freq\fR
+Get frequency the CPU currently runs at, according to the cpufreq core.
+.TP
+\fB\-w\fR \fB\-\-hwfreq\fR
+Get frequency the CPU currently runs at, by reading it from hardware (only available to root).
+.TP
+\fB\-l\fR \fB\-\-hwlimits\fR
+Determine the minimum and maximum CPU frequency allowed.
+.TP
+\fB\-d\fR \fB\-\-driver\fR
+Determines the used cpufreq kernel driver.
+.TP
+\fB\-p\fR \fB\-\-policy\fR
+Gets the currently used cpufreq policy.
+.TP
+\fB\-g\fR \fB\-\-governors\fR
+Determines available cpufreq governors.
+.TP
+\fB\-a\fR \fB\-\-related\-cpus\fR
+Determines which CPUs run at the same hardware frequency.
+.TP
+\fB\-a\fR \fB\-\-affected\-cpus\fR
+Determines which CPUs need to have their frequency coordinated by software.
+.TP
+\fB\-s\fR \fB\-\-stats\fR
+Shows cpufreq statistics if available.
+.TP
+\fB\-y\fR \fB\-\-latency\fR
+Determines the maximum latency on CPU frequency changes.
+.TP
+\fB\-o\fR \fB\-\-proc\fR
+Prints out information like provided by the /proc/cpufreq interface in 2.4. and early 2.6. kernels.
+.TP
+\fB\-m\fR \fB\-\-human\fR
+human\-readable output for the \-f, \-w, \-s and \-y parameters.
+.TP
+\fB\-n\fR \fB\-\-no-rounding\fR
+Output frequencies and latencies without rounding off values.
+.TP
+.SH "REMARKS"
+.LP
+By default only values of core zero are displayed. How to display settings of
+other cores is described in the cpupower(1) manpage in the \-\-cpu option section.
+.LP
+You can't specify more than one of the output specific options \-o \-e \-a \-g \-p \-d \-l \-w \-f \-y.
+.LP
+You also can't specify the \-o option combined with the \-c option.
+.SH "FILES"
+.nf
+\fI/sys/devices/system/cpu/cpu*/cpufreq/\fP
+\fI/proc/cpufreq\fP (deprecated)
+\fI/proc/sys/cpu/\fP (deprecated)
+.fi
+.SH "AUTHORS"
+.nf
+Dominik Brodowski <linux@brodo.de> \- author
+Mattia Dongili<malattia@gmail.com> \- first autolibtoolization
+.fi
+.SH "SEE ALSO"
+.LP
+cpupower\-frequency\-set(1), cpupower(1)
diff --git a/tools/power/cpupower/man/cpupower-frequency-set.1 b/tools/power/cpupower/man/cpupower-frequency-set.1
new file mode 100644
index 00000000000..3eacc8d03d1
--- /dev/null
+++ b/tools/power/cpupower/man/cpupower-frequency-set.1
@@ -0,0 +1,52 @@
+.TH "CPUPOWER\-FREQUENCY\-SET" "1" "0.1" "" "cpupower Manual"
+.SH "NAME"
+.LP
+cpupower frequency\-set \- A small tool which allows to modify cpufreq settings.
+.SH "SYNTAX"
+.LP
+cpupower [ \-c cpu ] frequency\-set [\fIoptions\fP]
+.SH "DESCRIPTION"
+.LP
+cpupower frequency\-set allows you to modify cpufreq settings without having to type e.g. "/sys/devices/system/cpu/cpu0/cpufreq/scaling_set_speed" all the time.
+.SH "OPTIONS"
+.LP
+.TP
+\fB\-d\fR \fB\-\-min\fR <FREQ>
+new minimum CPU frequency the governor may select.
+.TP
+\fB\-u\fR \fB\-\-max\fR <FREQ>
+new maximum CPU frequency the governor may select.
+.TP
+\fB\-g\fR \fB\-\-governor\fR <GOV>
+new cpufreq governor.
+.TP
+\fB\-f\fR \fB\-\-freq\fR <FREQ>
+specific frequency to be set. Requires userspace governor to be available and loaded.
+.TP
+\fB\-r\fR \fB\-\-related\fR
+modify all hardware-related CPUs at the same time
+.TP
+.SH "REMARKS"
+.LP
+By default values are applied on all cores. How to modify single core
+configurations is described in the cpupower(1) manpage in the \-\-cpu option section.
+.LP
+The \-f FREQ, \-\-freq FREQ parameter cannot be combined with any other parameter.
+.LP
+FREQuencies can be passed in Hz, kHz (default), MHz, GHz, or THz by postfixing the value with the wanted unit name, without any space (frequency in kHz =^ Hz * 0.001 =^ MHz * 1000 =^ GHz * 1000000).
+.LP
+On Linux kernels up to 2.6.29, the \-r or \-\-related parameter is ignored.
+.SH "FILES"
+.nf
+\fI/sys/devices/system/cpu/cpu*/cpufreq/\fP
+\fI/proc/cpufreq\fP (deprecated)
+\fI/proc/sys/cpu/\fP (deprecated)
+.fi
+.SH "AUTHORS"
+.nf
+Dominik Brodowski <linux@brodo.de> \- author
+Mattia Dongili<malattia@gmail.com> \- first autolibtoolization
+.fi
+.SH "SEE ALSO"
+.LP
+cpupower\-frequency\-info(1), cpupower(1)
diff --git a/tools/power/cpupower/man/cpupower-idle-info.1 b/tools/power/cpupower/man/cpupower-idle-info.1
new file mode 100644
index 00000000000..7b3646adb92
--- /dev/null
+++ b/tools/power/cpupower/man/cpupower-idle-info.1
@@ -0,0 +1,91 @@
+.TH "CPUPOWER-IDLE-INFO" "1" "0.1" "" "cpupower Manual"
+.SH "NAME"
+.LP
+cpupower idle\-info \- Utility to retrieve cpu idle kernel information
+.SH "SYNTAX"
+.LP
+cpupower [ \-c cpulist ] idle\-info [\fIoptions\fP]
+.SH "DESCRIPTION"
+.LP
+A tool which prints out per cpu idle information helpful to developers and interested users.
+.SH "OPTIONS"
+.LP
+.TP
+\fB\-f\fR \fB\-\-silent\fR
+Only print a summary of all available C-states in the system.
+.TP
+\fB\-e\fR \fB\-\-proc\fR
+deprecated.
+Prints out idle information in old /proc/acpi/processor/*/power format. This
+interface has been removed from the kernel for quite some time, do not let
+further code depend on this option, best do not use it.
+
+.SH IDLE\-INFO DESCRIPTIONS
+CPU sleep state statistics and descriptions are retrieved from sysfs files,
+exported by the cpuidle kernel subsystem. The kernel only updates these
+statistics when it enters or leaves an idle state, therefore on a very idle or
+a very busy system, these statistics may not be accurate. They still provide a
+good overview about the usage and availability of processor sleep states on
+the platform.
+
+Be aware that the sleep states as exported by the hardware or BIOS and used by
+the Linux kernel may not exactly reflect the capabilities of the
+processor. This often is the case on the X86 architecture when the acpi_idle
+driver is used. It is also possible that the hardware overrules the kernel
+requests, due to internal activity monitors or other reasons.
+On recent X86 platforms it is often possible to read out hardware registers
+which monitor the duration of sleep states the processor resided in. The
+cpupower monitor tool (cpupower\-monitor(1)) can be used to show real sleep
+state residencies. Please refer to the architecture specific description
+section below.
+
+.SH IDLE\-INFO ARCHITECTURE SPECIFIC DESCRIPTIONS
+.SS "X86"
+POLL idle state
+
+If cpuidle is active, X86 platforms have one special idle state.
+The POLL idle state is not a real idle state, it does not save any
+power. Instead, a busy\-loop is executed doing nothing for a short period of
+time. This state is used if the kernel knows that work has to be processed
+very soon and entering any real hardware idle state may result in a slight
+performance penalty.
+
+There exist two different cpuidle drivers on the X86 architecture platform:
+
+"acpi_idle" cpuidle driver
+
+The acpi_idle cpuidle driver retrieves available sleep states (C\-states) from
+the ACPI BIOS tables (from the _CST ACPI function on recent platforms or from
+the FADT BIOS table on older ones).
+The C1 state is not retrieved from ACPI tables. If the C1 state is entered,
+the kernel will call the hlt instruction (or mwait on Intel).
+
+"intel_idle" cpuidle driver
+
+In kernel 2.6.36 the intel_idle driver was introduced.
+It only serves recent Intel CPUs (Nehalem, Westmere, Sandybridge, Atoms or
+newer). On older Intel CPUs the acpi_idle driver is still used (if the BIOS
+provides C\-state ACPI tables).
+The intel_idle driver knows the sleep state capabilities of the processor and
+ignores ACPI BIOS exported processor sleep states tables.
+
+.SH "REMARKS"
+.LP
+By default only values of core zero are displayed. How to display settings of
+other cores is described in the cpupower(1) manpage in the \-\-cpu option
+section.
+.SH REFERENCES
+http://www.acpi.info/spec.htm
+.SH "FILES"
+.nf
+\fI/sys/devices/system/cpu/cpu*/cpuidle/state*\fP
+\fI/sys/devices/system/cpu/cpuidle/*\fP
+.fi
+.SH "AUTHORS"
+.nf
+Thomas Renninger <trenn@suse.de>
+.fi
+.SH "SEE ALSO"
+.LP
+cpupower(1), cpupower\-monitor(1), cpupower\-info(1), cpupower\-set(1),
+cpupower\-idle\-set(1)
diff --git a/tools/power/cpupower/man/cpupower-idle-set.1 b/tools/power/cpupower/man/cpupower-idle-set.1
new file mode 100644
index 00000000000..3e6799d7a79
--- /dev/null
+++ b/tools/power/cpupower/man/cpupower-idle-set.1
@@ -0,0 +1,77 @@
+.TH "CPUPOWER-IDLE-SET" "1" "0.1" "" "cpupower Manual"
+.SH "NAME"
+.LP
+cpupower idle\-set \- Utility to set cpu idle state specific kernel options
+.SH "SYNTAX"
+.LP
+cpupower [ \-c cpulist ] idle\-info [\fIoptions\fP]
+.SH "DESCRIPTION"
+.LP
+The cpupower idle\-set subcommand allows to set cpu idle, also called cpu
+sleep state, specific options offered by the kernel. One example is disabling
+sleep states. This can be handy for power vs performance tuning.
+.SH "OPTIONS"
+.LP
+.TP
+\fB\-d\fR \fB\-\-disable\fR <STATE_NO>
+Disable a specific processor sleep state.
+.TP
+\fB\-e\fR \fB\-\-enable\fR <STATE_NO>
+Enable a specific processor sleep state.
+.TP
+\fB\-D\fR \fB\-\-disable-by-latency\fR <LATENCY>
+Disable all idle states with a equal or higher latency than <LATENCY>
+.TP
+\fB\-E\fR \fB\-\-enable-all\fR
+Enable all idle states if not enabled already.
+
+.SH "REMARKS"
+.LP
+Cpuidle Governors Policy on Disabling Sleep States
+
+.RS 4
+Depending on the used cpuidle governor, implementing the kernel policy
+how to choose sleep states, subsequent sleep states on this core, might get
+disabled as well.
+
+There are two cpuidle governors ladder and menu. While the ladder
+governor is always available, if CONFIG_CPU_IDLE is selected, the
+menu governor additionally requires CONFIG_NO_HZ.
+
+The behavior and the effect of the disable variable depends on the
+implementation of a particular governor. In the ladder governor, for
+example, it is not coherent, i.e. if one is disabling a light state,
+then all deeper states are disabled as well. Likewise, if one enables a
+deep state but a lighter state still is disabled, then this has no effect.
+.RE
+.LP
+Disabling the Lightest Sleep State may not have any Affect
+
+.RS 4
+If criteria are not met to enter deeper sleep states and the lightest sleep
+state is chosen when idle, the kernel may still enter this sleep state,
+irrespective of whether it is disabled or not. This is also reflected in
+the usage count of the disabled sleep state when using the cpupower idle-info
+command.
+.RE
+.LP
+Selecting specific CPU Cores
+
+.RS 4
+By default processor sleep states of all CPU cores are set. Please refer
+to the cpupower(1) manpage in the \-\-cpu option section how to disable
+C-states of specific cores.
+.RE
+.SH "FILES"
+.nf
+\fI/sys/devices/system/cpu/cpu*/cpuidle/state*\fP
+\fI/sys/devices/system/cpu/cpuidle/*\fP
+.fi
+.SH "AUTHORS"
+.nf
+Thomas Renninger <trenn@suse.de>
+.fi
+.SH "SEE ALSO"
+.LP
+cpupower(1), cpupower\-monitor(1), cpupower\-info(1), cpupower\-set(1),
+cpupower\-idle\-info(1)
diff --git a/tools/power/cpupower/man/cpupower-info.1 b/tools/power/cpupower/man/cpupower-info.1
new file mode 100644
index 00000000000..340bcd0be7d
--- /dev/null
+++ b/tools/power/cpupower/man/cpupower-info.1
@@ -0,0 +1,19 @@
+.TH CPUPOWER\-INFO "1" "22/02/2011" "" "cpupower Manual"
+.SH NAME
+cpupower\-info \- Shows processor power related kernel or hardware configurations
+.SH SYNOPSIS
+.ft B
+.B cpupower info [ \-b ]
+
+.SH DESCRIPTION
+\fBcpupower info \fP shows kernel configurations or processor hardware
+registers affecting processor power saving policies.
+
+Some options are platform wide, some affect single cores. By default values
+of core zero are displayed only. cpupower --cpu all cpuinfo will show the
+settings of all cores, see cpupower(1) how to choose specific cores.
+
+.SH "SEE ALSO"
+Options are described in detail in:
+
+cpupower(1), cpupower-set(1)
diff --git a/tools/power/cpupower/man/cpupower-monitor.1 b/tools/power/cpupower/man/cpupower-monitor.1
new file mode 100644
index 00000000000..914cbb9d9cd
--- /dev/null
+++ b/tools/power/cpupower/man/cpupower-monitor.1
@@ -0,0 +1,198 @@
+.TH CPUPOWER\-MONITOR "1" "22/02/2011" "" "cpupower Manual"
+.SH NAME
+cpupower\-monitor \- Report processor frequency and idle statistics
+.SH SYNOPSIS
+.ft B
+.B cpupower monitor
+.RB "\-l"
+
+.B cpupower monitor
+.RB [ -c ] [ "\-m <mon1>," [ "<mon2>,..." ] ]
+.RB [ "\-i seconds" ]
+.br
+.B cpupower monitor
+.RB [ -c ][ "\-m <mon1>," [ "<mon2>,..." ] ]
+.RB command
+.br
+.SH DESCRIPTION
+\fBcpupower-monitor \fP reports processor topology, frequency and idle power
+state statistics. Either \fBcommand\fP is forked and
+statistics are printed upon its completion, or statistics are printed periodically.
+
+\fBcpupower-monitor \fP implements independent processor sleep state and
+frequency counters. Some are retrieved from kernel statistics, some are
+directly reading out hardware registers. Use \-l to get an overview which are
+supported on your system.
+
+.SH Options
+.PP
+\-l
+.RS 4
+List available monitors on your system. Additional details about each monitor
+are shown:
+.RS 2
+.IP \(bu
+The name in quotation marks which can be passed to the \-m parameter.
+.IP \(bu
+The number of different counters the monitor supports in brackets.
+.IP \(bu
+The amount of time in seconds the counters might overflow, due to
+implementation constraints.
+.IP \(bu
+The name and a description of each counter and its processor hierarchy level
+coverage in square brackets:
+.RS 4
+.IP \(bu
+[T] \-> Thread
+.IP \(bu
+[C] \-> Core
+.IP \(bu
+[P] \-> Processor Package (Socket)
+.IP \(bu
+[M] \-> Machine/Platform wide counter
+.RE
+.RE
+.RE
+.PP
+\-m <mon1>,<mon2>,...
+.RS 4
+Only display specific monitors. Use the monitor string(s) provided by \-l option.
+.RE
+.PP
+\-i seconds
+.RS 4
+Measure intervall.
+.RE
+.PP
+\-c
+.RS 4
+Schedule the process on every core before starting and ending measuring.
+This could be needed for the Idle_Stats monitor when no other MSR based
+monitor (has to be run on the core that is measured) is run in parallel.
+This is to wake up the processors from deeper sleep states and let the
+kernel re
+-account its cpuidle (C-state) information before reading the
+cpuidle timings from sysfs.
+.RE
+.PP
+command
+.RS 4
+Measure idle and frequency characteristics of an arbitrary command/workload.
+The executable \fBcommand\fP is forked and upon its exit, statistics gathered since it was
+forked are displayed.
+.RE
+.PP
+\-v
+.RS 4
+Increase verbosity if the binary was compiled with the DEBUG option set.
+.RE
+
+.SH MONITOR DESCRIPTIONS
+.SS "Idle_Stats"
+Shows statistics of the cpuidle kernel subsystem. Values are retrieved from
+/sys/devices/system/cpu/cpu*/cpuidle/state*/.
+The kernel updates these values every time an idle state is entered or
+left. Therefore there can be some inaccuracy when cores are in an idle
+state for some time when the measure starts or ends. In worst case it can happen
+that one core stayed in an idle state for the whole measure time and the idle
+state usage time as exported by the kernel did not get updated. In this case
+a state residency of 0 percent is shown while it was 100.
+
+.SS "Mperf"
+The name comes from the aperf/mperf (average and maximum) MSR registers used
+which are available on recent X86 processors. It shows the average frequency
+(including boost frequencies).
+The fact that on all recent hardware the mperf timer stops ticking in any idle
+state it is also used to show C0 (processor is active) and Cx (processor is in
+any sleep state) times. These counters do not have the inaccuracy restrictions
+the "Idle_Stats" counters may show.
+May work poorly on Linux-2.6.20 through 2.6.29, as the \fBacpi-cpufreq \fP
+kernel frequency driver periodically cleared aperf/mperf registers in those
+kernels.
+
+.SS "Nehalem" "SandyBridge" "HaswellExtended"
+Intel Core and Package sleep state counters.
+Threads (hyperthreaded cores) may not be able to enter deeper core states if
+its sibling is utilized.
+Deepest package sleep states may in reality show up as machine/platform wide
+sleep states and can only be entered if all cores are idle. Look up Intel
+manuals (some are provided in the References section) for further details.
+The monitors are named after the CPU family where the sleep state capabilities
+got introduced and may not match exactly the CPU name of the platform.
+For example an IvyBridge processor has sleep state capabilities which got
+introduced in Nehalem and SandyBridge processor families.
+Thus on an IvyBridge processor one will get Nehalem and SandyBridge sleep
+state monitors.
+HaswellExtended extra package sleep state capabilities are available only in a
+specific Haswell (family 0x45) and probably also other future processors.
+
+.SS "Fam_12h" "Fam_14h"
+AMD laptop and desktop processor (family 12h and 14h) sleep state counters.
+The registers are accessed via PCI and therefore can still be read out while
+cores have been offlined.
+
+There is one special counter: NBP1 (North Bridge P1).
+This one always returns 0 or 1, depending on whether the North Bridge P1
+power state got entered at least once during measure time.
+Being able to enter NBP1 state also depends on graphics power management.
+Therefore this counter can be used to verify whether the graphics' driver
+power management is working as expected.
+
+.SH EXAMPLES
+
+cpupower monitor -l" may show:
+.RS 4
+Monitor "Mperf" (3 states) \- Might overflow after 922000000 s
+
+ ...
+
+Monitor "Idle_Stats" (3 states) \- Might overflow after 4294967295 s
+
+ ...
+
+.RE
+cpupower monitor \-m "Idle_Stats,Mperf" scp /tmp/test /nfs/tmp
+
+Monitor the scp command, show both Mperf and Idle_Stats states counter
+statistics, but in exchanged order.
+
+
+
+.RE
+Be careful that the typical command to fully utilize one CPU by doing:
+
+cpupower monitor cat /dev/zero >/dev/null
+
+Does not work as expected, because the measured output is redirected to
+/dev/null. This could get workarounded by putting the line into an own, tiny
+shell script. Hit CTRL\-c to terminate the command and get the measure output
+displayed.
+
+.SH REFERENCES
+"BIOS and Kernel Developer’s Guide (BKDG) for AMD Family 14h Processors"
+http://support.amd.com/us/Processor_TechDocs/43170.pdf
+
+"Intel® Turbo Boost Technology
+in Intel® Core™ Microarchitecture (Nehalem) Based Processors"
+http://download.intel.com/design/processor/applnots/320354.pdf
+
+"Intel® 64 and IA-32 Architectures Software Developer's Manual
+Volume 3B: System Programming Guide"
+http://www.intel.com/products/processor/manuals
+
+.SH FILES
+.ta
+.nf
+/dev/cpu/*/msr
+/sys/devices/system/cpu/cpu*/cpuidle/state*/.
+.fi
+
+.SH "SEE ALSO"
+powertop(8), msr(4), vmstat(8)
+.PP
+.SH AUTHORS
+.nf
+Written by Thomas Renninger <trenn@suse.de>
+
+Nehalem, SandyBridge monitors and command passing
+based on turbostat.8 from Len Brown <len.brown@intel.com>
diff --git a/tools/power/cpupower/man/cpupower-set.1 b/tools/power/cpupower/man/cpupower-set.1
new file mode 100644
index 00000000000..2bcc696f449
--- /dev/null
+++ b/tools/power/cpupower/man/cpupower-set.1
@@ -0,0 +1,65 @@
+.TH CPUPOWER\-SET "1" "22/02/2011" "" "cpupower Manual"
+.SH NAME
+cpupower\-set \- Set processor power related kernel or hardware configurations
+.SH SYNOPSIS
+.ft B
+.B cpupower set [ \-b VAL ]
+
+
+.SH DESCRIPTION
+\fBcpupower set \fP sets kernel configurations or directly accesses hardware
+registers affecting processor power saving policies.
+
+Some options are platform wide, some affect single cores. By default values
+are applied on all cores. How to modify single core configurations is
+described in the cpupower(1) manpage in the \-\-cpu option section. Whether an
+option affects the whole system or can be applied to individual cores is
+described in the Options sections.
+
+Use \fBcpupower info \fP to read out current settings and whether they are
+supported on the system at all.
+
+.SH Options
+.PP
+\-\-perf-bias, \-b
+.RS 4
+Sets a register on supported Intel processore which allows software to convey
+its policy for the relative importance of performance versus energy savings to
+the processor.
+
+The range of valid numbers is 0-15, where 0 is maximum
+performance and 15 is maximum energy efficiency.
+
+The processor uses this information in model-specific ways
+when it must select trade-offs between performance and
+energy efficiency.
+
+This policy hint does not supersede Processor Performance states
+(P-states) or CPU Idle power states (C-states), but allows
+software to have influence where it would otherwise be unable
+to express a preference.
+
+For example, this setting may tell the hardware how
+aggressively or conservatively to control frequency
+in the "turbo range" above the explicitly OS-controlled
+P-state frequency range. It may also tell the hardware
+how aggressively it should enter the OS requested C-states.
+
+This option can be applied to individual cores only via the \-\-cpu option,
+cpupower(1).
+
+Setting the performance bias value on one CPU can modify the setting on
+related CPUs as well (for example all CPUs on one socket), because of
+hardware restrictions.
+Use \fBcpupower -c all info -b\fP to verify.
+
+This options needs the msr kernel driver (CONFIG_X86_MSR) loaded.
+.RE
+
+.SH "SEE ALSO"
+cpupower-info(1), cpupower-monitor(1), powertop(1)
+.PP
+.SH AUTHORS
+.nf
+\-\-perf\-bias parts written by Len Brown <len.brown@intel.com>
+Thomas Renninger <trenn@suse.de>
diff --git a/tools/power/cpupower/man/cpupower.1 b/tools/power/cpupower/man/cpupower.1
new file mode 100644
index 00000000000..baf741d06e8
--- /dev/null
+++ b/tools/power/cpupower/man/cpupower.1
@@ -0,0 +1,72 @@
+.TH CPUPOWER "1" "07/03/2011" "" "cpupower Manual"
+.SH NAME
+cpupower \- Shows and sets processor power related values
+.SH SYNOPSIS
+.ft B
+.B cpupower [ \-c cpulist ] <command> [ARGS]
+
+.B cpupower \-v|\-\-version
+
+.B cpupower \-h|\-\-help
+
+.SH DESCRIPTION
+\fBcpupower \fP is a collection of tools to examine and tune power saving
+related features of your processor.
+
+The manpages of the commands (cpupower\-<command>(1)) provide detailed
+descriptions of supported features. Run \fBcpupower help\fP to get an overview
+of supported commands.
+
+.SH Options
+.PP
+\-\-help, \-h
+.RS 4
+Shows supported commands and general usage.
+.RE
+.PP
+\-\-cpu cpulist, \-c cpulist
+.RS 4
+Only show or set values for specific cores.
+This option is not supported by all commands, details can be found in the
+manpages of the commands.
+
+Some commands access all cores (typically the *\-set commands), some only
+the first core (typically the *\-info commands) by default.
+
+The syntax for <cpulist> is based on how the kernel exports CPU bitmasks via
+sysfs files. Some examples:
+.RS 4
+.TP 16
+Input
+Equivalent to
+.TP
+all
+all cores
+.TP
+0\-3
+0,1,2,3
+.TP
+0\-7:2
+0,2,4,6
+.TP
+1,3,5-7
+1,3,5,6,7
+.TP
+0\-3:2,8\-15:4
+0,2,8,12
+.RE
+.RE
+.PP
+\-\-version, \-v
+.RS 4
+Print the package name and version number.
+
+.SH "SEE ALSO"
+cpupower-set(1), cpupower-info(1), cpupower-idle(1),
+cpupower-frequency-set(1), cpupower-frequency-info(1), cpupower-monitor(1),
+powertop(1)
+.PP
+.SH AUTHORS
+.nf
+\-\-perf\-bias parts written by Len Brown <len.brown@intel.com>
+Thomas Renninger <trenn@suse.de>
diff --git a/tools/power/cpupower/po/cs.po b/tools/power/cpupower/po/cs.po
new file mode 100644
index 00000000000..cb22c45c506
--- /dev/null
+++ b/tools/power/cpupower/po/cs.po
@@ -0,0 +1,944 @@
+# translation of cs.po to Czech
+# Czech translation for cpufrequtils package
+# Czech messages for cpufrequtils.
+# Copyright (C) 2007 kavol
+# This file is distributed under the same license as the cpufrequtils package.
+#
+# Karel Volný <kavol@seznam.cz>, 2007, 2008.
+msgid ""
+msgstr ""
+"Project-Id-Version: cs\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-03-08 17:03+0100\n"
+"PO-Revision-Date: 2008-06-11 16:26+0200\n"
+"Last-Translator: Karel Volný <kavol@seznam.cz>\n"
+"Language-Team: Czech <diskuze@lists.l10n.cz>\n"
+"Language: cs\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=3; plural=(n==1) ? 0 : (n>=2 && n<=4) ? 1 : 2;\n"
+"X-Generator: KBabel 1.11.4\n"
+
+#: utils/idle_monitor/nhm_idle.c:36
+msgid "Processor Core C3"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:43
+msgid "Processor Core C6"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:51
+msgid "Processor Package C3"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:58 utils/idle_monitor/amd_fam14h_idle.c:70
+msgid "Processor Package C6"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:33
+msgid "Processor Core C7"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:40
+msgid "Processor Package C2"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:47
+msgid "Processor Package C7"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:56
+msgid "Package in sleep state (PC1 or deeper)"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:63
+msgid "Processor Package C1"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:77
+msgid "North Bridge P1 boolean counter (returns 0 or 1)"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:35
+msgid "Processor Core not idle"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:42
+msgid "Processor Core in an idle state"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:50
+msgid "Average Frequency (including boost) in MHz"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:66
+#, c-format
+msgid ""
+"cpupower monitor: [-h] [ [-t] | [-l] | [-m <mon1>,[<mon2>] ] ] [-i "
+"interval_sec | -c command ...]\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:69
+#, c-format
+msgid ""
+"cpupower monitor: [-v] [-h] [ [-t] | [-l] | [-m <mon1>,[<mon2>] ] ] [-i "
+"interval_sec | -c command ...]\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:71
+#, c-format
+msgid "\t -v: be more verbose\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:73
+#, c-format
+msgid "\t -h: print this help\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:74
+#, c-format
+msgid "\t -i: time intervall to measure for in seconds (default 1)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:75
+#, c-format
+msgid "\t -t: show CPU topology/hierarchy\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:76
+#, c-format
+msgid "\t -l: list available CPU sleep monitors (for use with -m)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:77
+#, c-format
+msgid "\t -m: show specific CPU sleep monitors only (in same order)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:79
+#, c-format
+msgid ""
+"only one of: -t, -l, -m are allowed\n"
+"If none of them is passed,"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:80
+#, c-format
+msgid " all supported monitors are shown\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:197
+#, c-format
+msgid "Monitor %s, Counter %s has no count function. Implementation error\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:207
+#, c-format
+msgid " *is offline\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:236
+#, c-format
+msgid "%s: max monitor name length (%d) exceeded\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:250
+#, c-format
+msgid "No matching monitor found in %s, try -l option\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:266
+#, c-format
+msgid "Monitor \"%s\" (%d states) - Might overflow after %u s\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:319
+#, c-format
+msgid "%s took %.5f seconds and exited with status %d\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:406
+#, c-format
+msgid "Cannot read number of available processors\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:417
+#, c-format
+msgid "Available monitor %s needs root access\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:428
+#, c-format
+msgid "No HW Cstate monitors found\n"
+msgstr ""
+
+#: utils/cpupower.c:78
+#, c-format
+msgid "cpupower [ -c cpulist ] subcommand [ARGS]\n"
+msgstr ""
+
+#: utils/cpupower.c:79
+#, c-format
+msgid "cpupower --version\n"
+msgstr ""
+
+#: utils/cpupower.c:80
+#, c-format
+msgid "Supported subcommands are:\n"
+msgstr ""
+
+#: utils/cpupower.c:83
+#, c-format
+msgid ""
+"\n"
+"Some subcommands can make use of the -c cpulist option.\n"
+msgstr ""
+
+#: utils/cpupower.c:84
+#, c-format
+msgid "Look at the general cpupower manpage how to use it\n"
+msgstr ""
+
+#: utils/cpupower.c:85
+#, c-format
+msgid "and read up the subcommand's manpage whether it is supported.\n"
+msgstr ""
+
+#: utils/cpupower.c:86
+#, c-format
+msgid ""
+"\n"
+"Use cpupower help subcommand for getting help for above subcommands.\n"
+msgstr ""
+
+#: utils/cpupower.c:91
+#, c-format
+msgid "Report errors and bugs to %s, please.\n"
+msgstr ""
+"Chyby v programu prosím hlaste na %s (anglicky).\n"
+"Chyby v pÅ™ekladu prosím hlaste na kavol@seznam.cz (Äesky ;-)\n"
+
+#: utils/cpupower.c:114
+#, c-format
+msgid "Error parsing cpu list\n"
+msgstr ""
+
+#: utils/cpupower.c:172
+#, c-format
+msgid "Subcommand %s needs root privileges\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:31
+#, c-format
+msgid "Couldn't count the number of CPUs (%s: %s), assuming 1\n"
+msgstr "Nelze zjistit poÄet CPU (%s: %s), pÅ™edpokládá se 1.\n"
+
+#: utils/cpufreq-info.c:63
+#, c-format
+msgid ""
+" minimum CPU frequency - maximum CPU frequency - governor\n"
+msgstr ""
+" minimální frekvence CPU - maximální frekvence CPU - regulátor\n"
+
+#: utils/cpufreq-info.c:151
+#, c-format
+msgid "Error while evaluating Boost Capabilities on CPU %d -- are you root?\n"
+msgstr ""
+
+#. P state changes via MSR are identified via cpuid 80000007
+#. on Intel and AMD, but we assume boost capable machines can do that
+#. if (cpuid_eax(0x80000000) >= 0x80000007
+#. && (cpuid_edx(0x80000007) & (1 << 7)))
+#.
+#: utils/cpufreq-info.c:161
+#, c-format
+msgid " boost state support: \n"
+msgstr ""
+
+#: utils/cpufreq-info.c:163
+#, c-format
+msgid " Supported: %s\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:163 utils/cpufreq-info.c:164
+msgid "yes"
+msgstr ""
+
+#: utils/cpufreq-info.c:163 utils/cpufreq-info.c:164
+msgid "no"
+msgstr ""
+
+#: utils/cpufreq-info.c:164
+#, fuzzy, c-format
+msgid " Active: %s\n"
+msgstr " ovladaÄ: %s\n"
+
+#: utils/cpufreq-info.c:177
+#, c-format
+msgid " Boost States: %d\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:178
+#, c-format
+msgid " Total States: %d\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:181
+#, c-format
+msgid " Pstate-Pb%d: %luMHz (boost state)\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:184
+#, c-format
+msgid " Pstate-P%d: %luMHz\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:211
+#, c-format
+msgid " no or unknown cpufreq driver is active on this CPU\n"
+msgstr " pro tento CPU není aktivní žádný známý ovladaÄ cpufreq\n"
+
+#: utils/cpufreq-info.c:213
+#, c-format
+msgid " driver: %s\n"
+msgstr " ovladaÄ: %s\n"
+
+#: utils/cpufreq-info.c:219
+#, fuzzy, c-format
+msgid " CPUs which run at the same hardware frequency: "
+msgstr " CPU, které musí měnit frekvenci zároveň: "
+
+#: utils/cpufreq-info.c:230
+#, fuzzy, c-format
+msgid " CPUs which need to have their frequency coordinated by software: "
+msgstr " CPU, které musí měnit frekvenci zároveň: "
+
+#: utils/cpufreq-info.c:241
+#, c-format
+msgid " maximum transition latency: "
+msgstr ""
+
+#: utils/cpufreq-info.c:247
+#, c-format
+msgid " hardware limits: "
+msgstr " hardwarové meze: "
+
+#: utils/cpufreq-info.c:256
+#, c-format
+msgid " available frequency steps: "
+msgstr " dostupné frekvence: "
+
+#: utils/cpufreq-info.c:269
+#, c-format
+msgid " available cpufreq governors: "
+msgstr " dostupné regulátory: "
+
+#: utils/cpufreq-info.c:280
+#, c-format
+msgid " current policy: frequency should be within "
+msgstr " souÄasná taktika: frekvence by mÄ›la být mezi "
+
+#: utils/cpufreq-info.c:282
+#, c-format
+msgid " and "
+msgstr " a "
+
+#: utils/cpufreq-info.c:286
+#, c-format
+msgid ""
+"The governor \"%s\" may decide which speed to use\n"
+" within this range.\n"
+msgstr ""
+" Regulátor \"%s\" může rozhodnout jakou frekvenci použít\n"
+" v těchto mezích.\n"
+
+#: utils/cpufreq-info.c:293
+#, c-format
+msgid " current CPU frequency is "
+msgstr " souÄasná frekvence CPU je "
+
+#: utils/cpufreq-info.c:296
+#, c-format
+msgid " (asserted by call to hardware)"
+msgstr " (zjištěno hardwarovým voláním)"
+
+#: utils/cpufreq-info.c:304
+#, c-format
+msgid " cpufreq stats: "
+msgstr " statistika cpufreq: "
+
+#: utils/cpufreq-info.c:472
+#, fuzzy, c-format
+msgid "Usage: cpupower freqinfo [options]\n"
+msgstr "Užití: cpufreq-info [pÅ™epínaÄe]\n"
+
+#: utils/cpufreq-info.c:473 utils/cpufreq-set.c:26 utils/cpupower-set.c:23
+#: utils/cpupower-info.c:22 utils/cpuidle-info.c:148
+#, c-format
+msgid "Options:\n"
+msgstr "PÅ™epínaÄe:\n"
+
+#: utils/cpufreq-info.c:474
+#, fuzzy, c-format
+msgid " -e, --debug Prints out debug information [default]\n"
+msgstr " -e, --debug Vypíše ladicí informace\n"
+
+#: utils/cpufreq-info.c:475
+#, c-format
+msgid ""
+" -f, --freq Get frequency the CPU currently runs at, according\n"
+" to the cpufreq core *\n"
+msgstr ""
+" -f, --freq Zjistí aktuální frekvenci, na které CPU běží\n"
+" podle cpufreq *\n"
+
+#: utils/cpufreq-info.c:477
+#, c-format
+msgid ""
+" -w, --hwfreq Get frequency the CPU currently runs at, by reading\n"
+" it from hardware (only available to root) *\n"
+msgstr ""
+" -w, --hwfreq Zjistí aktuální frekvenci, na které CPU běží\n"
+" z hardware (dostupné jen uživateli root) *\n"
+
+#: utils/cpufreq-info.c:479
+#, c-format
+msgid ""
+" -l, --hwlimits Determine the minimum and maximum CPU frequency "
+"allowed *\n"
+msgstr ""
+" -l, --hwlimits Zjistí minimální a maximální dostupnou frekvenci CPU "
+"*\n"
+
+#: utils/cpufreq-info.c:480
+#, c-format
+msgid " -d, --driver Determines the used cpufreq kernel driver *\n"
+msgstr " -d, --driver Zjistí aktivní ovladaÄ cpufreq *\n"
+
+#: utils/cpufreq-info.c:481
+#, c-format
+msgid " -p, --policy Gets the currently used cpufreq policy *\n"
+msgstr " -p, --policy Zjistí aktuální taktiku cpufreq *\n"
+
+#: utils/cpufreq-info.c:482
+#, c-format
+msgid " -g, --governors Determines available cpufreq governors *\n"
+msgstr " -g, --governors Zjistí dostupné regulátory cpufreq *\n"
+
+#: utils/cpufreq-info.c:483
+#, fuzzy, c-format
+msgid ""
+" -r, --related-cpus Determines which CPUs run at the same hardware "
+"frequency *\n"
+msgstr ""
+" -a, --affected-cpus Zjistí, které CPU musí měnit frekvenci zároveň *\n"
+
+#: utils/cpufreq-info.c:484
+#, fuzzy, c-format
+msgid ""
+" -a, --affected-cpus Determines which CPUs need to have their frequency\n"
+" coordinated by software *\n"
+msgstr ""
+" -a, --affected-cpus Zjistí, které CPU musí měnit frekvenci zároveň *\n"
+
+#: utils/cpufreq-info.c:486
+#, c-format
+msgid " -s, --stats Shows cpufreq statistics if available\n"
+msgstr " -s, --stats Zobrazí statistiku cpufreq, je-li dostupná\n"
+
+#: utils/cpufreq-info.c:487
+#, fuzzy, c-format
+msgid ""
+" -y, --latency Determines the maximum latency on CPU frequency "
+"changes *\n"
+msgstr ""
+" -l, --hwlimits Zjistí minimální a maximální dostupnou frekvenci CPU "
+"*\n"
+
+#: utils/cpufreq-info.c:488
+#, c-format
+msgid " -b, --boost Checks for turbo or boost modes *\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:489
+#, c-format
+msgid ""
+" -o, --proc Prints out information like provided by the /proc/"
+"cpufreq\n"
+" interface in 2.4. and early 2.6. kernels\n"
+msgstr ""
+" -o, --proc Vypíše informace ve formátu, jaký používalo rozhraní\n"
+" /proc/cpufreq v kernelech Å™ady 2.4 a Äasné 2.6\n"
+
+#: utils/cpufreq-info.c:491
+#, fuzzy, c-format
+msgid ""
+" -m, --human human-readable output for the -f, -w, -s and -y "
+"parameters\n"
+msgstr ""
+" -m, --human Výstup parametrů -f, -w a -s v „lidmi Äitelném“ "
+"formátu\n"
+
+#: utils/cpufreq-info.c:492 utils/cpuidle-info.c:152
+#, c-format
+msgid " -h, --help Prints out this screen\n"
+msgstr " -h, --help Vypíše tuto nápovědu\n"
+
+#: utils/cpufreq-info.c:495
+#, c-format
+msgid ""
+"If no argument or only the -c, --cpu parameter is given, debug output about\n"
+"cpufreq is printed which is useful e.g. for reporting bugs.\n"
+msgstr ""
+"Není-li zadán žádný parametr nebo je-li zadán pouze pÅ™epínaÄ -c, --cpu, "
+"jsou\n"
+"vypsány ladicí informace, což může být užiteÄné například pÅ™i hlášení chyb.\n"
+
+#: utils/cpufreq-info.c:497
+#, c-format
+msgid ""
+"For the arguments marked with *, omitting the -c or --cpu argument is\n"
+"equivalent to setting it to zero\n"
+msgstr ""
+"Není-li pÅ™i použití pÅ™epínaÄů oznaÄených * zadán parametr -c nebo --cpu,\n"
+"předpokládá se jeho hodnota 0.\n"
+
+#: utils/cpufreq-info.c:580
+#, c-format
+msgid ""
+"The argument passed to this tool can't be combined with passing a --cpu "
+"argument\n"
+msgstr "Zadaný parametr nemůže být použit zároveň s pÅ™epínaÄem -c nebo --cpu\n"
+
+#: utils/cpufreq-info.c:596
+#, c-format
+msgid ""
+"You can't specify more than one --cpu parameter and/or\n"
+"more than one output-specific argument\n"
+msgstr ""
+"Nelze zadat více než jeden parametr -c nebo --cpu\n"
+"anebo více než jeden parametr urÄující výstup\n"
+
+#: utils/cpufreq-info.c:600 utils/cpufreq-set.c:82 utils/cpupower-set.c:42
+#: utils/cpupower-info.c:42 utils/cpuidle-info.c:213
+#, c-format
+msgid "invalid or unknown argument\n"
+msgstr "neplatný nebo neznámý parametr\n"
+
+#: utils/cpufreq-info.c:617
+#, c-format
+msgid "couldn't analyze CPU %d as it doesn't seem to be present\n"
+msgstr "nelze analyzovat CPU %d, vypadá to, že není přítomen\n"
+
+#: utils/cpufreq-info.c:620 utils/cpupower-info.c:142
+#, c-format
+msgid "analyzing CPU %d:\n"
+msgstr "analyzuji CPU %d:\n"
+
+#: utils/cpufreq-set.c:25
+#, fuzzy, c-format
+msgid "Usage: cpupower frequency-set [options]\n"
+msgstr "Užití: cpufreq-set [pÅ™epínaÄe]\n"
+
+#: utils/cpufreq-set.c:27
+#, c-format
+msgid ""
+" -d FREQ, --min FREQ new minimum CPU frequency the governor may "
+"select\n"
+msgstr ""
+" -d FREQ, --min FREQ Nová nejnižší frekvence, kterou může regulátor "
+"vybrat\n"
+
+#: utils/cpufreq-set.c:28
+#, c-format
+msgid ""
+" -u FREQ, --max FREQ new maximum CPU frequency the governor may "
+"select\n"
+msgstr ""
+" -u FREQ, --max FREQ Nová nejvyšší frekvence, kterou může regulátor "
+"zvolit\n"
+
+#: utils/cpufreq-set.c:29
+#, c-format
+msgid " -g GOV, --governor GOV new cpufreq governor\n"
+msgstr " -g GOV, --governors GOV Nový regulátor cpufreq\n"
+
+#: utils/cpufreq-set.c:30
+#, c-format
+msgid ""
+" -f FREQ, --freq FREQ specific frequency to be set. Requires userspace\n"
+" governor to be available and loaded\n"
+msgstr ""
+" -f FREQ, --freq FREQ Frekvence, která má být nastavena. Vyžaduje, aby "
+"byl\n"
+" v jádře nahrán regulátor ‚userspace‘.\n"
+
+#: utils/cpufreq-set.c:32
+#, c-format
+msgid " -r, --related Switches all hardware-related CPUs\n"
+msgstr ""
+
+#: utils/cpufreq-set.c:33 utils/cpupower-set.c:28 utils/cpupower-info.c:27
+#, fuzzy, c-format
+msgid " -h, --help Prints out this screen\n"
+msgstr " -h, --help Vypíše tuto nápovědu\n"
+
+#: utils/cpufreq-set.c:35
+#, fuzzy, c-format
+msgid ""
+"Notes:\n"
+"1. Omitting the -c or --cpu argument is equivalent to setting it to \"all\"\n"
+msgstr ""
+"Není-li pÅ™i použití pÅ™epínaÄů oznaÄených * zadán parametr -c nebo --cpu,\n"
+"předpokládá se jeho hodnota 0.\n"
+
+#: utils/cpufreq-set.c:37
+#, fuzzy, c-format
+msgid ""
+"2. The -f FREQ, --freq FREQ parameter cannot be combined with any other "
+"parameter\n"
+" except the -c CPU, --cpu CPU parameter\n"
+"3. FREQuencies can be passed in Hz, kHz (default), MHz, GHz, or THz\n"
+" by postfixing the value with the wanted unit name, without any space\n"
+" (FREQuency in kHz =^ Hz * 0.001 =^ MHz * 1000 =^ GHz * 1000000).\n"
+msgstr ""
+"Poznámky:\n"
+"1. Vynechání parametru -c nebo --cpu je ekvivalentní jeho nastavení na 0\n"
+"2. PÅ™epínaÄ -f nebo --freq nemůže být použit zároveň s žádným jiným vyjma -"
+"c\n"
+" nebo --cpu\n"
+"3. Frekvence (FREQ) mohou být zadány v Hz, kHz (výchozí), MHz, GHz nebo THz\n"
+" pÅ™ipojením názvu jednotky bez mezery mezi Äíslem a jednotkou\n"
+" (FREQ v kHz =^ Hz * 0,001 = ^ MHz * 1000 =^ GHz * 1000000)\n"
+
+#: utils/cpufreq-set.c:57
+#, c-format
+msgid ""
+"Error setting new values. Common errors:\n"
+"- Do you have proper administration rights? (super-user?)\n"
+"- Is the governor you requested available and modprobed?\n"
+"- Trying to set an invalid policy?\n"
+"- Trying to set a specific frequency, but userspace governor is not "
+"available,\n"
+" for example because of hardware which cannot be set to a specific "
+"frequency\n"
+" or because the userspace governor isn't loaded?\n"
+msgstr ""
+"Chyba při nastavování nových hodnot. Obvyklé problémy:\n"
+"- Máte patÅ™iÄná administrátorská práva? (root?)\n"
+"- Je požadovaný regulátor dostupný v jádře? (modprobe?)\n"
+"- Snažíte se nastavit neplatnou taktiku?\n"
+"- Snažíte se nastavit urÄitou frekvenci, ale není dostupný\n"
+" regulátor ‚userspace‘, například protože není nahrán v jádře,\n"
+" nebo nelze na tomto hardware nastavit urÄitou frekvenci?\n"
+
+#: utils/cpufreq-set.c:170
+#, c-format
+msgid "wrong, unknown or unhandled CPU?\n"
+msgstr "neznámý nebo nepodporovaný CPU?\n"
+
+#: utils/cpufreq-set.c:302
+#, c-format
+msgid ""
+"the -f/--freq parameter cannot be combined with -d/--min, -u/--max or\n"
+"-g/--governor parameters\n"
+msgstr ""
+"pÅ™epínaÄ -f/--freq nemůže být použit zároveň\n"
+"s pÅ™epínaÄem -d/--min, -u/--max nebo -g/--governor\n"
+
+#: utils/cpufreq-set.c:308
+#, c-format
+msgid ""
+"At least one parameter out of -f/--freq, -d/--min, -u/--max, and\n"
+"-g/--governor must be passed\n"
+msgstr ""
+"Musí být zadán alespoň jeden pÅ™epínaÄ\n"
+"-f/--freq, -d/--min, -u/--max nebo -g/--governor\n"
+
+#: utils/cpufreq-set.c:347
+#, c-format
+msgid "Setting cpu: %d\n"
+msgstr ""
+
+#: utils/cpupower-set.c:22
+#, c-format
+msgid "Usage: cpupower set [ -b val ] [ -m val ] [ -s val ]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:24
+#, c-format
+msgid ""
+" -b, --perf-bias [VAL] Sets CPU's power vs performance policy on some\n"
+" Intel models [0-15], see manpage for details\n"
+msgstr ""
+
+#: utils/cpupower-set.c:26
+#, c-format
+msgid ""
+" -m, --sched-mc [VAL] Sets the kernel's multi core scheduler policy.\n"
+msgstr ""
+
+#: utils/cpupower-set.c:27
+#, c-format
+msgid ""
+" -s, --sched-smt [VAL] Sets the kernel's thread sibling scheduler "
+"policy.\n"
+msgstr ""
+
+#: utils/cpupower-set.c:80
+#, c-format
+msgid "--perf-bias param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:91
+#, c-format
+msgid "--sched-mc param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:102
+#, c-format
+msgid "--sched-smt param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:121
+#, c-format
+msgid "Error setting sched-mc %s\n"
+msgstr ""
+
+#: utils/cpupower-set.c:127
+#, c-format
+msgid "Error setting sched-smt %s\n"
+msgstr ""
+
+#: utils/cpupower-set.c:146
+#, c-format
+msgid "Error setting perf-bias value on CPU %d\n"
+msgstr ""
+
+#: utils/cpupower-info.c:21
+#, c-format
+msgid "Usage: cpupower info [ -b ] [ -m ] [ -s ]\n"
+msgstr ""
+
+#: utils/cpupower-info.c:23
+#, c-format
+msgid ""
+" -b, --perf-bias Gets CPU's power vs performance policy on some\n"
+" Intel models [0-15], see manpage for details\n"
+msgstr ""
+
+#: utils/cpupower-info.c:25
+#, fuzzy, c-format
+msgid " -m, --sched-mc Gets the kernel's multi core scheduler policy.\n"
+msgstr " -p, --policy Zjistí aktuální taktiku cpufreq *\n"
+
+#: utils/cpupower-info.c:26
+#, c-format
+msgid ""
+" -s, --sched-smt Gets the kernel's thread sibling scheduler policy.\n"
+msgstr ""
+
+#: utils/cpupower-info.c:28
+#, c-format
+msgid ""
+"\n"
+"Passing no option will show all info, by default only on core 0\n"
+msgstr ""
+
+#: utils/cpupower-info.c:102
+#, c-format
+msgid "System's multi core scheduler setting: "
+msgstr ""
+
+#. if sysfs file is missing it's: errno == ENOENT
+#: utils/cpupower-info.c:105 utils/cpupower-info.c:114
+#, c-format
+msgid "not supported\n"
+msgstr ""
+
+#: utils/cpupower-info.c:111
+#, c-format
+msgid "System's thread sibling scheduler setting: "
+msgstr ""
+
+#: utils/cpupower-info.c:126
+#, c-format
+msgid "Intel's performance bias setting needs root privileges\n"
+msgstr ""
+
+#: utils/cpupower-info.c:128
+#, c-format
+msgid "System does not support Intel's performance bias setting\n"
+msgstr ""
+
+#: utils/cpupower-info.c:147
+#, c-format
+msgid "Could not read perf-bias value\n"
+msgstr ""
+
+#: utils/cpupower-info.c:150
+#, c-format
+msgid "perf-bias: %d\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:28
+#, fuzzy, c-format
+msgid "Analyzing CPU %d:\n"
+msgstr "analyzuji CPU %d:\n"
+
+#: utils/cpuidle-info.c:32
+#, c-format
+msgid "CPU %u: No idle states\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:36
+#, c-format
+msgid "CPU %u: Can't read idle state info\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:41
+#, c-format
+msgid "Could not determine max idle state %u\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:46
+#, c-format
+msgid "Number of idle states: %d\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:48
+#, fuzzy, c-format
+msgid "Available idle states:"
+msgstr " dostupné frekvence: "
+
+#: utils/cpuidle-info.c:71
+#, c-format
+msgid "Flags/Description: %s\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:74
+#, c-format
+msgid "Latency: %lu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:76
+#, c-format
+msgid "Usage: %lu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:78
+#, c-format
+msgid "Duration: %llu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:90
+#, c-format
+msgid "Could not determine cpuidle driver\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:94
+#, fuzzy, c-format
+msgid "CPUidle driver: %s\n"
+msgstr " ovladaÄ: %s\n"
+
+#: utils/cpuidle-info.c:99
+#, c-format
+msgid "Could not determine cpuidle governor\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:103
+#, c-format
+msgid "CPUidle governor: %s\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:122
+#, c-format
+msgid "CPU %u: Can't read C-state info\n"
+msgstr ""
+
+#. printf("Cstates: %d\n", cstates);
+#: utils/cpuidle-info.c:127
+#, c-format
+msgid "active state: C0\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:128
+#, c-format
+msgid "max_cstate: C%u\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:129
+#, c-format
+msgid "maximum allowed latency: %lu usec\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:130
+#, c-format
+msgid "states:\t\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:132
+#, c-format
+msgid " C%d: type[C%d] "
+msgstr ""
+
+#: utils/cpuidle-info.c:134
+#, c-format
+msgid "promotion[--] demotion[--] "
+msgstr ""
+
+#: utils/cpuidle-info.c:135
+#, c-format
+msgid "latency[%03lu] "
+msgstr ""
+
+#: utils/cpuidle-info.c:137
+#, c-format
+msgid "usage[%08lu] "
+msgstr ""
+
+#: utils/cpuidle-info.c:139
+#, c-format
+msgid "duration[%020Lu] \n"
+msgstr ""
+
+#: utils/cpuidle-info.c:147
+#, fuzzy, c-format
+msgid "Usage: cpupower idleinfo [options]\n"
+msgstr "Užití: cpufreq-info [pÅ™epínaÄe]\n"
+
+#: utils/cpuidle-info.c:149
+#, fuzzy, c-format
+msgid " -s, --silent Only show general C-state information\n"
+msgstr " -e, --debug Vypíše ladicí informace\n"
+
+#: utils/cpuidle-info.c:150
+#, fuzzy, c-format
+msgid ""
+" -o, --proc Prints out information like provided by the /proc/"
+"acpi/processor/*/power\n"
+" interface in older kernels\n"
+msgstr ""
+" -o, --proc Vypíše informace ve formátu, jaký používalo rozhraní\n"
+" /proc/cpufreq v kernelech Å™ady 2.4 a Äasné 2.6\n"
+
+#: utils/cpuidle-info.c:209
+#, fuzzy, c-format
+msgid "You can't specify more than one output-specific argument\n"
+msgstr ""
+"Nelze zadat více než jeden parametr -c nebo --cpu\n"
+"anebo více než jeden parametr urÄující výstup\n"
+
+#~ msgid ""
+#~ " -c CPU, --cpu CPU CPU number which information shall be determined "
+#~ "about\n"
+#~ msgstr ""
+#~ " -c CPU, --cpu CPU Číslo CPU, o kterém se mají zjistit informace\n"
+
+#~ msgid ""
+#~ " -c CPU, --cpu CPU number of CPU where cpufreq settings shall be "
+#~ "modified\n"
+#~ msgstr ""
+#~ " -c CPU, --cpu CPU Číslo CPU pro který se má provést nastavení "
+#~ "cpufreq\n"
diff --git a/tools/power/cpupower/po/de.po b/tools/power/cpupower/po/de.po
new file mode 100644
index 00000000000..78c09e51663
--- /dev/null
+++ b/tools/power/cpupower/po/de.po
@@ -0,0 +1,961 @@
+# German translations for cpufrequtils package
+# German messages for cpufrequtils.
+# Copyright (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.net>
+# This file is distributed under the same license as the cpufrequtils package.
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: cpufrequtils 006\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-03-08 17:03+0100\n"
+"PO-Revision-Date: 2009-08-08 17:18+0100\n"
+"Last-Translator: <linux@dominikbrodowski.net>\n"
+"Language-Team: NONE\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO-8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Plural-Forms: nplurals=2; plural=(n != 1);\n"
+
+#: utils/idle_monitor/nhm_idle.c:36
+msgid "Processor Core C3"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:43
+msgid "Processor Core C6"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:51
+msgid "Processor Package C3"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:58 utils/idle_monitor/amd_fam14h_idle.c:70
+msgid "Processor Package C6"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:33
+msgid "Processor Core C7"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:40
+msgid "Processor Package C2"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:47
+msgid "Processor Package C7"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:56
+msgid "Package in sleep state (PC1 or deeper)"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:63
+msgid "Processor Package C1"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:77
+msgid "North Bridge P1 boolean counter (returns 0 or 1)"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:35
+msgid "Processor Core not idle"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:42
+msgid "Processor Core in an idle state"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:50
+msgid "Average Frequency (including boost) in MHz"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:66
+#, c-format
+msgid ""
+"cpupower monitor: [-h] [ [-t] | [-l] | [-m <mon1>,[<mon2>] ] ] [-i "
+"interval_sec | -c command ...]\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:69
+#, c-format
+msgid ""
+"cpupower monitor: [-v] [-h] [ [-t] | [-l] | [-m <mon1>,[<mon2>] ] ] [-i "
+"interval_sec | -c command ...]\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:71
+#, c-format
+msgid "\t -v: be more verbose\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:73
+#, c-format
+msgid "\t -h: print this help\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:74
+#, c-format
+msgid "\t -i: time intervall to measure for in seconds (default 1)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:75
+#, c-format
+msgid "\t -t: show CPU topology/hierarchy\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:76
+#, c-format
+msgid "\t -l: list available CPU sleep monitors (for use with -m)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:77
+#, c-format
+msgid "\t -m: show specific CPU sleep monitors only (in same order)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:79
+#, c-format
+msgid ""
+"only one of: -t, -l, -m are allowed\n"
+"If none of them is passed,"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:80
+#, c-format
+msgid " all supported monitors are shown\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:197
+#, c-format
+msgid "Monitor %s, Counter %s has no count function. Implementation error\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:207
+#, c-format
+msgid " *is offline\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:236
+#, c-format
+msgid "%s: max monitor name length (%d) exceeded\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:250
+#, c-format
+msgid "No matching monitor found in %s, try -l option\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:266
+#, c-format
+msgid "Monitor \"%s\" (%d states) - Might overflow after %u s\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:319
+#, c-format
+msgid "%s took %.5f seconds and exited with status %d\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:406
+#, c-format
+msgid "Cannot read number of available processors\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:417
+#, c-format
+msgid "Available monitor %s needs root access\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:428
+#, c-format
+msgid "No HW Cstate monitors found\n"
+msgstr ""
+
+#: utils/cpupower.c:78
+#, c-format
+msgid "cpupower [ -c cpulist ] subcommand [ARGS]\n"
+msgstr ""
+
+#: utils/cpupower.c:79
+#, c-format
+msgid "cpupower --version\n"
+msgstr ""
+
+#: utils/cpupower.c:80
+#, c-format
+msgid "Supported subcommands are:\n"
+msgstr ""
+
+#: utils/cpupower.c:83
+#, c-format
+msgid ""
+"\n"
+"Some subcommands can make use of the -c cpulist option.\n"
+msgstr ""
+
+#: utils/cpupower.c:84
+#, c-format
+msgid "Look at the general cpupower manpage how to use it\n"
+msgstr ""
+
+#: utils/cpupower.c:85
+#, c-format
+msgid "and read up the subcommand's manpage whether it is supported.\n"
+msgstr ""
+
+#: utils/cpupower.c:86
+#, c-format
+msgid ""
+"\n"
+"Use cpupower help subcommand for getting help for above subcommands.\n"
+msgstr ""
+
+#: utils/cpupower.c:91
+#, c-format
+msgid "Report errors and bugs to %s, please.\n"
+msgstr "Bitte melden Sie Fehler an %s.\n"
+
+#: utils/cpupower.c:114
+#, c-format
+msgid "Error parsing cpu list\n"
+msgstr ""
+
+#: utils/cpupower.c:172
+#, c-format
+msgid "Subcommand %s needs root privileges\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:31
+#, c-format
+msgid "Couldn't count the number of CPUs (%s: %s), assuming 1\n"
+msgstr ""
+"Konnte nicht die Anzahl der CPUs herausfinden (%s : %s), nehme daher 1 an.\n"
+
+#: utils/cpufreq-info.c:63
+#, c-format
+msgid ""
+" minimum CPU frequency - maximum CPU frequency - governor\n"
+msgstr ""
+" minimale CPU-Taktfreq. - maximale CPU-Taktfreq. - Regler \n"
+
+#: utils/cpufreq-info.c:151
+#, c-format
+msgid "Error while evaluating Boost Capabilities on CPU %d -- are you root?\n"
+msgstr ""
+
+#. P state changes via MSR are identified via cpuid 80000007
+#. on Intel and AMD, but we assume boost capable machines can do that
+#. if (cpuid_eax(0x80000000) >= 0x80000007
+#. && (cpuid_edx(0x80000007) & (1 << 7)))
+#.
+#: utils/cpufreq-info.c:161
+#, c-format
+msgid " boost state support: \n"
+msgstr ""
+
+#: utils/cpufreq-info.c:163
+#, c-format
+msgid " Supported: %s\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:163 utils/cpufreq-info.c:164
+msgid "yes"
+msgstr ""
+
+#: utils/cpufreq-info.c:163 utils/cpufreq-info.c:164
+msgid "no"
+msgstr ""
+
+#: utils/cpufreq-info.c:164
+#, fuzzy, c-format
+msgid " Active: %s\n"
+msgstr " Treiber: %s\n"
+
+#: utils/cpufreq-info.c:177
+#, c-format
+msgid " Boost States: %d\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:178
+#, c-format
+msgid " Total States: %d\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:181
+#, c-format
+msgid " Pstate-Pb%d: %luMHz (boost state)\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:184
+#, c-format
+msgid " Pstate-P%d: %luMHz\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:211
+#, c-format
+msgid " no or unknown cpufreq driver is active on this CPU\n"
+msgstr " kein oder nicht bestimmbarer cpufreq-Treiber aktiv\n"
+
+#: utils/cpufreq-info.c:213
+#, c-format
+msgid " driver: %s\n"
+msgstr " Treiber: %s\n"
+
+#: utils/cpufreq-info.c:219
+#, c-format
+msgid " CPUs which run at the same hardware frequency: "
+msgstr " Folgende CPUs laufen mit der gleichen Hardware-Taktfrequenz: "
+
+#: utils/cpufreq-info.c:230
+#, c-format
+msgid " CPUs which need to have their frequency coordinated by software: "
+msgstr " Die Taktfrequenz folgender CPUs werden per Software koordiniert: "
+
+#: utils/cpufreq-info.c:241
+#, c-format
+msgid " maximum transition latency: "
+msgstr " Maximale Dauer eines Taktfrequenzwechsels: "
+
+#: utils/cpufreq-info.c:247
+#, c-format
+msgid " hardware limits: "
+msgstr " Hardwarebedingte Grenzen der Taktfrequenz: "
+
+#: utils/cpufreq-info.c:256
+#, c-format
+msgid " available frequency steps: "
+msgstr " mögliche Taktfrequenzen: "
+
+#: utils/cpufreq-info.c:269
+#, c-format
+msgid " available cpufreq governors: "
+msgstr " mögliche Regler: "
+
+#: utils/cpufreq-info.c:280
+#, c-format
+msgid " current policy: frequency should be within "
+msgstr " momentane Taktik: die Frequenz soll innerhalb "
+
+#: utils/cpufreq-info.c:282
+#, c-format
+msgid " and "
+msgstr " und "
+
+#: utils/cpufreq-info.c:286
+#, c-format
+msgid ""
+"The governor \"%s\" may decide which speed to use\n"
+" within this range.\n"
+msgstr ""
+" liegen. Der Regler \"%s\" kann frei entscheiden,\n"
+" welche Taktfrequenz innerhalb dieser Grenze verwendet "
+"wird.\n"
+
+#: utils/cpufreq-info.c:293
+#, c-format
+msgid " current CPU frequency is "
+msgstr " momentane Taktfrequenz ist "
+
+#: utils/cpufreq-info.c:296
+#, c-format
+msgid " (asserted by call to hardware)"
+msgstr " (verifiziert durch Nachfrage bei der Hardware)"
+
+#: utils/cpufreq-info.c:304
+#, c-format
+msgid " cpufreq stats: "
+msgstr " Statistik:"
+
+#: utils/cpufreq-info.c:472
+#, fuzzy, c-format
+msgid "Usage: cpupower freqinfo [options]\n"
+msgstr "Aufruf: cpufreq-info [Optionen]\n"
+
+#: utils/cpufreq-info.c:473 utils/cpufreq-set.c:26 utils/cpupower-set.c:23
+#: utils/cpupower-info.c:22 utils/cpuidle-info.c:148
+#, c-format
+msgid "Options:\n"
+msgstr "Optionen:\n"
+
+#: utils/cpufreq-info.c:474
+#, fuzzy, c-format
+msgid " -e, --debug Prints out debug information [default]\n"
+msgstr ""
+" -e, --debug Erzeugt detaillierte Informationen, hilfreich\n"
+" zum Aufspüren von Fehlern\n"
+
+#: utils/cpufreq-info.c:475
+#, c-format
+msgid ""
+" -f, --freq Get frequency the CPU currently runs at, according\n"
+" to the cpufreq core *\n"
+msgstr ""
+" -f, --freq Findet die momentane CPU-Taktfrquenz heraus (nach\n"
+" Meinung des Betriebssystems) *\n"
+
+#: utils/cpufreq-info.c:477
+#, c-format
+msgid ""
+" -w, --hwfreq Get frequency the CPU currently runs at, by reading\n"
+" it from hardware (only available to root) *\n"
+msgstr ""
+" -w, --hwfreq Findet die momentane CPU-Taktfrequenz heraus\n"
+" (verifiziert durch Nachfrage bei der Hardware)\n"
+" [nur der Administrator kann dies tun] *\n"
+
+#: utils/cpufreq-info.c:479
+#, c-format
+msgid ""
+" -l, --hwlimits Determine the minimum and maximum CPU frequency "
+"allowed *\n"
+msgstr ""
+" -l, --hwlimits Findet die minimale und maximale Taktfrequenz heraus "
+"*\n"
+
+#: utils/cpufreq-info.c:480
+#, c-format
+msgid " -d, --driver Determines the used cpufreq kernel driver *\n"
+msgstr " -d, --driver Findet den momentanen Treiber heraus *\n"
+
+#: utils/cpufreq-info.c:481
+#, c-format
+msgid " -p, --policy Gets the currently used cpufreq policy *\n"
+msgstr " -p, --policy Findet die momentane Taktik heraus *\n"
+
+#: utils/cpufreq-info.c:482
+#, c-format
+msgid " -g, --governors Determines available cpufreq governors *\n"
+msgstr " -g, --governors Erzeugt eine Liste mit verfügbaren Reglern *\n"
+
+#: utils/cpufreq-info.c:483
+#, c-format
+msgid ""
+" -r, --related-cpus Determines which CPUs run at the same hardware "
+"frequency *\n"
+msgstr ""
+" -r, --related-cpus Findet heraus, welche CPUs mit derselben "
+"physikalischen\n"
+" Taktfrequenz laufen *\n"
+
+#: utils/cpufreq-info.c:484
+#, c-format
+msgid ""
+" -a, --affected-cpus Determines which CPUs need to have their frequency\n"
+" coordinated by software *\n"
+msgstr ""
+" -a, --affected-cpus Findet heraus, von welchen CPUs die Taktfrequenz "
+"durch\n"
+" Software koordiniert werden muss *\n"
+
+#: utils/cpufreq-info.c:486
+#, c-format
+msgid " -s, --stats Shows cpufreq statistics if available\n"
+msgstr ""
+" -s, --stats Zeigt, sofern möglich, Statistiken über cpufreq an.\n"
+
+#: utils/cpufreq-info.c:487
+#, c-format
+msgid ""
+" -y, --latency Determines the maximum latency on CPU frequency "
+"changes *\n"
+msgstr ""
+" -y, --latency Findet die maximale Dauer eines Taktfrequenzwechsels "
+"heraus *\n"
+
+#: utils/cpufreq-info.c:488
+#, c-format
+msgid " -b, --boost Checks for turbo or boost modes *\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:489
+#, c-format
+msgid ""
+" -o, --proc Prints out information like provided by the /proc/"
+"cpufreq\n"
+" interface in 2.4. and early 2.6. kernels\n"
+msgstr ""
+" -o, --proc Erzeugt Informationen in einem ähnlichem Format zu "
+"dem\n"
+" der /proc/cpufreq-Datei in 2.4. und frühen 2.6.\n"
+" Kernel-Versionen\n"
+
+#: utils/cpufreq-info.c:491
+#, c-format
+msgid ""
+" -m, --human human-readable output for the -f, -w, -s and -y "
+"parameters\n"
+msgstr ""
+" -m, --human Formatiert Taktfrequenz- und Zeitdauerangaben in "
+"besser\n"
+" lesbarer Form (MHz, GHz; us, ms)\n"
+
+#: utils/cpufreq-info.c:492 utils/cpuidle-info.c:152
+#, c-format
+msgid " -h, --help Prints out this screen\n"
+msgstr " -h, --help Gibt diese Kurzübersicht aus\n"
+
+#: utils/cpufreq-info.c:495
+#, c-format
+msgid ""
+"If no argument or only the -c, --cpu parameter is given, debug output about\n"
+"cpufreq is printed which is useful e.g. for reporting bugs.\n"
+msgstr ""
+"Sofern kein anderer Parameter als '-c, --cpu' angegeben wird, liefert "
+"dieses\n"
+"Programm Informationen, die z.B. zum Berichten von Fehlern nützlich sind.\n"
+
+#: utils/cpufreq-info.c:497
+#, c-format
+msgid ""
+"For the arguments marked with *, omitting the -c or --cpu argument is\n"
+"equivalent to setting it to zero\n"
+msgstr ""
+"Bei den mit * markierten Parametern wird '--cpu 0' angenommen, soweit nicht\n"
+"mittels -c oder --cpu etwas anderes angegeben wird\n"
+
+#: utils/cpufreq-info.c:580
+#, c-format
+msgid ""
+"The argument passed to this tool can't be combined with passing a --cpu "
+"argument\n"
+msgstr "Diese Option kann nicht mit der --cpu-Option kombiniert werden\n"
+
+#: utils/cpufreq-info.c:596
+#, c-format
+msgid ""
+"You can't specify more than one --cpu parameter and/or\n"
+"more than one output-specific argument\n"
+msgstr ""
+"Man kann nicht mehr als einen --cpu-Parameter und/oder mehr als einen\n"
+"informationsspezifischen Parameter gleichzeitig angeben\n"
+
+#: utils/cpufreq-info.c:600 utils/cpufreq-set.c:82 utils/cpupower-set.c:42
+#: utils/cpupower-info.c:42 utils/cpuidle-info.c:213
+#, c-format
+msgid "invalid or unknown argument\n"
+msgstr "unbekannter oder falscher Parameter\n"
+
+#: utils/cpufreq-info.c:617
+#, c-format
+msgid "couldn't analyze CPU %d as it doesn't seem to be present\n"
+msgstr ""
+"Konnte nicht die CPU %d analysieren, da sie (scheinbar?) nicht existiert.\n"
+
+#: utils/cpufreq-info.c:620 utils/cpupower-info.c:142
+#, c-format
+msgid "analyzing CPU %d:\n"
+msgstr "analysiere CPU %d:\n"
+
+#: utils/cpufreq-set.c:25
+#, fuzzy, c-format
+msgid "Usage: cpupower frequency-set [options]\n"
+msgstr "Aufruf: cpufreq-set [Optionen]\n"
+
+#: utils/cpufreq-set.c:27
+#, c-format
+msgid ""
+" -d FREQ, --min FREQ new minimum CPU frequency the governor may "
+"select\n"
+msgstr ""
+" -d FREQ, --min FREQ neue minimale Taktfrequenz, die der Regler\n"
+" auswählen darf\n"
+
+#: utils/cpufreq-set.c:28
+#, c-format
+msgid ""
+" -u FREQ, --max FREQ new maximum CPU frequency the governor may "
+"select\n"
+msgstr ""
+" -u FREQ, --max FREQ neue maximale Taktfrequenz, die der Regler\n"
+" auswählen darf\n"
+
+#: utils/cpufreq-set.c:29
+#, c-format
+msgid " -g GOV, --governor GOV new cpufreq governor\n"
+msgstr " -g GOV, --governors GOV wechsle zu Regler GOV\n"
+
+#: utils/cpufreq-set.c:30
+#, c-format
+msgid ""
+" -f FREQ, --freq FREQ specific frequency to be set. Requires userspace\n"
+" governor to be available and loaded\n"
+msgstr ""
+" -f FREQ, --freq FREQ setze exakte Taktfrequenz. Benötigt den Regler\n"
+" 'userspace'.\n"
+
+#: utils/cpufreq-set.c:32
+#, c-format
+msgid " -r, --related Switches all hardware-related CPUs\n"
+msgstr ""
+" -r, --related Setze Werte für alle CPUs, deren Taktfrequenz\n"
+" hardwarebedingt identisch ist.\n"
+
+#: utils/cpufreq-set.c:33 utils/cpupower-set.c:28 utils/cpupower-info.c:27
+#, c-format
+msgid " -h, --help Prints out this screen\n"
+msgstr " -h, --help Gibt diese Kurzübersicht aus\n"
+
+#: utils/cpufreq-set.c:35
+#, fuzzy, c-format
+msgid ""
+"Notes:\n"
+"1. Omitting the -c or --cpu argument is equivalent to setting it to \"all\"\n"
+msgstr ""
+"Bei den mit * markierten Parametern wird '--cpu 0' angenommen, soweit nicht\n"
+"mittels -c oder --cpu etwas anderes angegeben wird\n"
+
+#: utils/cpufreq-set.c:37
+#, fuzzy, c-format
+msgid ""
+"2. The -f FREQ, --freq FREQ parameter cannot be combined with any other "
+"parameter\n"
+" except the -c CPU, --cpu CPU parameter\n"
+"3. FREQuencies can be passed in Hz, kHz (default), MHz, GHz, or THz\n"
+" by postfixing the value with the wanted unit name, without any space\n"
+" (FREQuency in kHz =^ Hz * 0.001 =^ MHz * 1000 =^ GHz * 1000000).\n"
+msgstr ""
+"Hinweise:\n"
+"1. Sofern kein -c oder --cpu-Parameter angegeben ist, wird '--cpu 0'\n"
+" angenommen\n"
+"2. Der Parameter -f bzw. --freq kann mit keinem anderen als dem Parameter\n"
+" -c bzw. --cpu kombiniert werden\n"
+"3. FREQuenzen können in Hz, kHz (Standard), MHz, GHz oder THz eingegeben\n"
+" werden, indem der Wert und unmittelbar anschließend (ohne Leerzeichen!)\n"
+" die Einheit angegeben werden. (Bsp: 1GHz )\n"
+" (FREQuenz in kHz =^ MHz * 1000 =^ GHz * 1000000).\n"
+
+#: utils/cpufreq-set.c:57
+#, c-format
+msgid ""
+"Error setting new values. Common errors:\n"
+"- Do you have proper administration rights? (super-user?)\n"
+"- Is the governor you requested available and modprobed?\n"
+"- Trying to set an invalid policy?\n"
+"- Trying to set a specific frequency, but userspace governor is not "
+"available,\n"
+" for example because of hardware which cannot be set to a specific "
+"frequency\n"
+" or because the userspace governor isn't loaded?\n"
+msgstr ""
+"Beim Einstellen ist ein Fehler aufgetreten. Typische Fehlerquellen sind:\n"
+"- nicht ausreichende Rechte (Administrator)\n"
+"- der Regler ist nicht verfügbar bzw. nicht geladen\n"
+"- die angegebene Taktik ist inkorrekt\n"
+"- eine spezifische Frequenz wurde angegeben, aber der Regler 'userspace'\n"
+" kann entweder hardwarebedingt nicht genutzt werden oder ist nicht geladen\n"
+
+#: utils/cpufreq-set.c:170
+#, c-format
+msgid "wrong, unknown or unhandled CPU?\n"
+msgstr "unbekannte oder nicht regelbare CPU\n"
+
+#: utils/cpufreq-set.c:302
+#, c-format
+msgid ""
+"the -f/--freq parameter cannot be combined with -d/--min, -u/--max or\n"
+"-g/--governor parameters\n"
+msgstr ""
+"Der -f bzw. --freq-Parameter kann nicht mit den Parametern -d/--min, -u/--"
+"max\n"
+"oder -g/--governor kombiniert werden\n"
+
+#: utils/cpufreq-set.c:308
+#, c-format
+msgid ""
+"At least one parameter out of -f/--freq, -d/--min, -u/--max, and\n"
+"-g/--governor must be passed\n"
+msgstr ""
+"Es muss mindestens ein Parameter aus -f/--freq, -d/--min, -u/--max oder\n"
+"-g/--governor angegeben werden.\n"
+
+#: utils/cpufreq-set.c:347
+#, c-format
+msgid "Setting cpu: %d\n"
+msgstr ""
+
+#: utils/cpupower-set.c:22
+#, c-format
+msgid "Usage: cpupower set [ -b val ] [ -m val ] [ -s val ]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:24
+#, c-format
+msgid ""
+" -b, --perf-bias [VAL] Sets CPU's power vs performance policy on some\n"
+" Intel models [0-15], see manpage for details\n"
+msgstr ""
+
+#: utils/cpupower-set.c:26
+#, c-format
+msgid ""
+" -m, --sched-mc [VAL] Sets the kernel's multi core scheduler policy.\n"
+msgstr ""
+
+#: utils/cpupower-set.c:27
+#, c-format
+msgid ""
+" -s, --sched-smt [VAL] Sets the kernel's thread sibling scheduler "
+"policy.\n"
+msgstr ""
+
+#: utils/cpupower-set.c:80
+#, c-format
+msgid "--perf-bias param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:91
+#, c-format
+msgid "--sched-mc param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:102
+#, c-format
+msgid "--sched-smt param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:121
+#, c-format
+msgid "Error setting sched-mc %s\n"
+msgstr ""
+
+#: utils/cpupower-set.c:127
+#, c-format
+msgid "Error setting sched-smt %s\n"
+msgstr ""
+
+#: utils/cpupower-set.c:146
+#, c-format
+msgid "Error setting perf-bias value on CPU %d\n"
+msgstr ""
+
+#: utils/cpupower-info.c:21
+#, c-format
+msgid "Usage: cpupower info [ -b ] [ -m ] [ -s ]\n"
+msgstr ""
+
+#: utils/cpupower-info.c:23
+#, c-format
+msgid ""
+" -b, --perf-bias Gets CPU's power vs performance policy on some\n"
+" Intel models [0-15], see manpage for details\n"
+msgstr ""
+
+#: utils/cpupower-info.c:25
+#, fuzzy, c-format
+msgid " -m, --sched-mc Gets the kernel's multi core scheduler policy.\n"
+msgstr " -p, --policy Findet die momentane Taktik heraus *\n"
+
+#: utils/cpupower-info.c:26
+#, c-format
+msgid ""
+" -s, --sched-smt Gets the kernel's thread sibling scheduler policy.\n"
+msgstr ""
+
+#: utils/cpupower-info.c:28
+#, c-format
+msgid ""
+"\n"
+"Passing no option will show all info, by default only on core 0\n"
+msgstr ""
+
+#: utils/cpupower-info.c:102
+#, c-format
+msgid "System's multi core scheduler setting: "
+msgstr ""
+
+#. if sysfs file is missing it's: errno == ENOENT
+#: utils/cpupower-info.c:105 utils/cpupower-info.c:114
+#, c-format
+msgid "not supported\n"
+msgstr ""
+
+#: utils/cpupower-info.c:111
+#, c-format
+msgid "System's thread sibling scheduler setting: "
+msgstr ""
+
+#: utils/cpupower-info.c:126
+#, c-format
+msgid "Intel's performance bias setting needs root privileges\n"
+msgstr ""
+
+#: utils/cpupower-info.c:128
+#, c-format
+msgid "System does not support Intel's performance bias setting\n"
+msgstr ""
+
+#: utils/cpupower-info.c:147
+#, c-format
+msgid "Could not read perf-bias value\n"
+msgstr ""
+
+#: utils/cpupower-info.c:150
+#, c-format
+msgid "perf-bias: %d\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:28
+#, fuzzy, c-format
+msgid "Analyzing CPU %d:\n"
+msgstr "analysiere CPU %d:\n"
+
+#: utils/cpuidle-info.c:32
+#, c-format
+msgid "CPU %u: No idle states\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:36
+#, c-format
+msgid "CPU %u: Can't read idle state info\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:41
+#, c-format
+msgid "Could not determine max idle state %u\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:46
+#, c-format
+msgid "Number of idle states: %d\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:48
+#, fuzzy, c-format
+msgid "Available idle states:"
+msgstr " mögliche Taktfrequenzen: "
+
+#: utils/cpuidle-info.c:71
+#, c-format
+msgid "Flags/Description: %s\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:74
+#, c-format
+msgid "Latency: %lu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:76
+#, c-format
+msgid "Usage: %lu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:78
+#, c-format
+msgid "Duration: %llu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:90
+#, c-format
+msgid "Could not determine cpuidle driver\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:94
+#, fuzzy, c-format
+msgid "CPUidle driver: %s\n"
+msgstr " Treiber: %s\n"
+
+#: utils/cpuidle-info.c:99
+#, c-format
+msgid "Could not determine cpuidle governor\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:103
+#, c-format
+msgid "CPUidle governor: %s\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:122
+#, c-format
+msgid "CPU %u: Can't read C-state info\n"
+msgstr ""
+
+#. printf("Cstates: %d\n", cstates);
+#: utils/cpuidle-info.c:127
+#, c-format
+msgid "active state: C0\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:128
+#, c-format
+msgid "max_cstate: C%u\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:129
+#, fuzzy, c-format
+msgid "maximum allowed latency: %lu usec\n"
+msgstr " Maximale Dauer eines Taktfrequenzwechsels: "
+
+#: utils/cpuidle-info.c:130
+#, c-format
+msgid "states:\t\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:132
+#, c-format
+msgid " C%d: type[C%d] "
+msgstr ""
+
+#: utils/cpuidle-info.c:134
+#, c-format
+msgid "promotion[--] demotion[--] "
+msgstr ""
+
+#: utils/cpuidle-info.c:135
+#, c-format
+msgid "latency[%03lu] "
+msgstr ""
+
+#: utils/cpuidle-info.c:137
+#, c-format
+msgid "usage[%08lu] "
+msgstr ""
+
+#: utils/cpuidle-info.c:139
+#, c-format
+msgid "duration[%020Lu] \n"
+msgstr ""
+
+#: utils/cpuidle-info.c:147
+#, fuzzy, c-format
+msgid "Usage: cpupower idleinfo [options]\n"
+msgstr "Aufruf: cpufreq-info [Optionen]\n"
+
+#: utils/cpuidle-info.c:149
+#, fuzzy, c-format
+msgid " -s, --silent Only show general C-state information\n"
+msgstr ""
+" -e, --debug Erzeugt detaillierte Informationen, hilfreich\n"
+" zum Aufspüren von Fehlern\n"
+
+#: utils/cpuidle-info.c:150
+#, fuzzy, c-format
+msgid ""
+" -o, --proc Prints out information like provided by the /proc/"
+"acpi/processor/*/power\n"
+" interface in older kernels\n"
+msgstr ""
+" -o, --proc Erzeugt Informationen in einem ähnlichem Format zu "
+"dem\n"
+" der /proc/cpufreq-Datei in 2.4. und frühen 2.6.\n"
+" Kernel-Versionen\n"
+
+#: utils/cpuidle-info.c:209
+#, fuzzy, c-format
+msgid "You can't specify more than one output-specific argument\n"
+msgstr ""
+"Man kann nicht mehr als einen --cpu-Parameter und/oder mehr als einen\n"
+"informationsspezifischen Parameter gleichzeitig angeben\n"
+
+#~ msgid ""
+#~ " -c CPU, --cpu CPU CPU number which information shall be determined "
+#~ "about\n"
+#~ msgstr ""
+#~ " -c CPU, --cpu CPU Nummer der CPU, über die Informationen "
+#~ "herausgefunden werden sollen\n"
+
+#~ msgid ""
+#~ " -c CPU, --cpu CPU number of CPU where cpufreq settings shall be "
+#~ "modified\n"
+#~ msgstr ""
+#~ " -c CPU, --cpu CPU Nummer der CPU, deren Taktfrequenz-"
+#~ "Einstellung\n"
+#~ " werden soll\n"
diff --git a/tools/power/cpupower/po/fr.po b/tools/power/cpupower/po/fr.po
new file mode 100644
index 00000000000..245ad20a9bf
--- /dev/null
+++ b/tools/power/cpupower/po/fr.po
@@ -0,0 +1,947 @@
+# French translations for cpufrequtils package
+# Copyright (C) 2004 THE PACKAGE'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the cpufrequtils package.
+# Ducrot Bruno <ducrot@poupinou.org>, 2004.
+#
+#, fuzzy
+msgid ""
+msgstr ""
+"Project-Id-Version: cpufrequtils 0.1-pre2\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-03-08 17:03+0100\n"
+"PO-Revision-Date: 2004-11-17 15:53+1000\n"
+"Last-Translator: Bruno Ducrot <ducrot@poupinou.org>\n"
+"Language-Team: NONE\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=ISO-8859-1\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: utils/idle_monitor/nhm_idle.c:36
+msgid "Processor Core C3"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:43
+msgid "Processor Core C6"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:51
+msgid "Processor Package C3"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:58 utils/idle_monitor/amd_fam14h_idle.c:70
+msgid "Processor Package C6"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:33
+msgid "Processor Core C7"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:40
+msgid "Processor Package C2"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:47
+msgid "Processor Package C7"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:56
+msgid "Package in sleep state (PC1 or deeper)"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:63
+msgid "Processor Package C1"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:77
+msgid "North Bridge P1 boolean counter (returns 0 or 1)"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:35
+msgid "Processor Core not idle"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:42
+msgid "Processor Core in an idle state"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:50
+msgid "Average Frequency (including boost) in MHz"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:66
+#, c-format
+msgid ""
+"cpupower monitor: [-h] [ [-t] | [-l] | [-m <mon1>,[<mon2>] ] ] [-i "
+"interval_sec | -c command ...]\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:69
+#, c-format
+msgid ""
+"cpupower monitor: [-v] [-h] [ [-t] | [-l] | [-m <mon1>,[<mon2>] ] ] [-i "
+"interval_sec | -c command ...]\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:71
+#, c-format
+msgid "\t -v: be more verbose\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:73
+#, c-format
+msgid "\t -h: print this help\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:74
+#, c-format
+msgid "\t -i: time intervall to measure for in seconds (default 1)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:75
+#, c-format
+msgid "\t -t: show CPU topology/hierarchy\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:76
+#, c-format
+msgid "\t -l: list available CPU sleep monitors (for use with -m)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:77
+#, c-format
+msgid "\t -m: show specific CPU sleep monitors only (in same order)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:79
+#, c-format
+msgid ""
+"only one of: -t, -l, -m are allowed\n"
+"If none of them is passed,"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:80
+#, c-format
+msgid " all supported monitors are shown\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:197
+#, c-format
+msgid "Monitor %s, Counter %s has no count function. Implementation error\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:207
+#, c-format
+msgid " *is offline\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:236
+#, c-format
+msgid "%s: max monitor name length (%d) exceeded\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:250
+#, c-format
+msgid "No matching monitor found in %s, try -l option\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:266
+#, c-format
+msgid "Monitor \"%s\" (%d states) - Might overflow after %u s\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:319
+#, c-format
+msgid "%s took %.5f seconds and exited with status %d\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:406
+#, c-format
+msgid "Cannot read number of available processors\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:417
+#, c-format
+msgid "Available monitor %s needs root access\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:428
+#, c-format
+msgid "No HW Cstate monitors found\n"
+msgstr ""
+
+#: utils/cpupower.c:78
+#, c-format
+msgid "cpupower [ -c cpulist ] subcommand [ARGS]\n"
+msgstr ""
+
+#: utils/cpupower.c:79
+#, c-format
+msgid "cpupower --version\n"
+msgstr ""
+
+#: utils/cpupower.c:80
+#, c-format
+msgid "Supported subcommands are:\n"
+msgstr ""
+
+#: utils/cpupower.c:83
+#, c-format
+msgid ""
+"\n"
+"Some subcommands can make use of the -c cpulist option.\n"
+msgstr ""
+
+#: utils/cpupower.c:84
+#, c-format
+msgid "Look at the general cpupower manpage how to use it\n"
+msgstr ""
+
+#: utils/cpupower.c:85
+#, c-format
+msgid "and read up the subcommand's manpage whether it is supported.\n"
+msgstr ""
+
+#: utils/cpupower.c:86
+#, c-format
+msgid ""
+"\n"
+"Use cpupower help subcommand for getting help for above subcommands.\n"
+msgstr ""
+
+#: utils/cpupower.c:91
+#, c-format
+msgid "Report errors and bugs to %s, please.\n"
+msgstr "Veuillez rapportez les erreurs et les bogues à %s, s'il vous plait.\n"
+
+#: utils/cpupower.c:114
+#, c-format
+msgid "Error parsing cpu list\n"
+msgstr ""
+
+#: utils/cpupower.c:172
+#, c-format
+msgid "Subcommand %s needs root privileges\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:31
+#, c-format
+msgid "Couldn't count the number of CPUs (%s: %s), assuming 1\n"
+msgstr "Détermination du nombre de CPUs (%s : %s) impossible. Assume 1\n"
+
+#: utils/cpufreq-info.c:63
+#, c-format
+msgid ""
+" minimum CPU frequency - maximum CPU frequency - governor\n"
+msgstr ""
+" Fréquence CPU minimale - Fréquence CPU maximale - régulateur\n"
+
+#: utils/cpufreq-info.c:151
+#, c-format
+msgid "Error while evaluating Boost Capabilities on CPU %d -- are you root?\n"
+msgstr ""
+
+#. P state changes via MSR are identified via cpuid 80000007
+#. on Intel and AMD, but we assume boost capable machines can do that
+#. if (cpuid_eax(0x80000000) >= 0x80000007
+#. && (cpuid_edx(0x80000007) & (1 << 7)))
+#.
+#: utils/cpufreq-info.c:161
+#, c-format
+msgid " boost state support: \n"
+msgstr ""
+
+#: utils/cpufreq-info.c:163
+#, c-format
+msgid " Supported: %s\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:163 utils/cpufreq-info.c:164
+msgid "yes"
+msgstr ""
+
+#: utils/cpufreq-info.c:163 utils/cpufreq-info.c:164
+msgid "no"
+msgstr ""
+
+#: utils/cpufreq-info.c:164
+#, fuzzy, c-format
+msgid " Active: %s\n"
+msgstr " pilote : %s\n"
+
+#: utils/cpufreq-info.c:177
+#, c-format
+msgid " Boost States: %d\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:178
+#, c-format
+msgid " Total States: %d\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:181
+#, c-format
+msgid " Pstate-Pb%d: %luMHz (boost state)\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:184
+#, c-format
+msgid " Pstate-P%d: %luMHz\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:211
+#, c-format
+msgid " no or unknown cpufreq driver is active on this CPU\n"
+msgstr " pas de pilotes cpufreq reconnu pour ce CPU\n"
+
+#: utils/cpufreq-info.c:213
+#, c-format
+msgid " driver: %s\n"
+msgstr " pilote : %s\n"
+
+#: utils/cpufreq-info.c:219
+#, fuzzy, c-format
+msgid " CPUs which run at the same hardware frequency: "
+msgstr " CPUs qui doivent changer de fréquences en même temps : "
+
+#: utils/cpufreq-info.c:230
+#, fuzzy, c-format
+msgid " CPUs which need to have their frequency coordinated by software: "
+msgstr " CPUs qui doivent changer de fréquences en même temps : "
+
+#: utils/cpufreq-info.c:241
+#, c-format
+msgid " maximum transition latency: "
+msgstr ""
+
+#: utils/cpufreq-info.c:247
+#, c-format
+msgid " hardware limits: "
+msgstr " limitation matérielle : "
+
+#: utils/cpufreq-info.c:256
+#, c-format
+msgid " available frequency steps: "
+msgstr " plage de fréquence : "
+
+#: utils/cpufreq-info.c:269
+#, c-format
+msgid " available cpufreq governors: "
+msgstr " régulateurs disponibles : "
+
+#: utils/cpufreq-info.c:280
+#, c-format
+msgid " current policy: frequency should be within "
+msgstr " tactique actuelle : la fréquence doit être comprise entre "
+
+#: utils/cpufreq-info.c:282
+#, c-format
+msgid " and "
+msgstr " et "
+
+#: utils/cpufreq-info.c:286
+#, c-format
+msgid ""
+"The governor \"%s\" may decide which speed to use\n"
+" within this range.\n"
+msgstr ""
+"Le régulateur \"%s\" est libre de choisir la vitesse\n"
+" dans cette plage de fréquences.\n"
+
+#: utils/cpufreq-info.c:293
+#, c-format
+msgid " current CPU frequency is "
+msgstr " la fréquence actuelle de ce CPU est "
+
+#: utils/cpufreq-info.c:296
+#, c-format
+msgid " (asserted by call to hardware)"
+msgstr " (vérifié par un appel direct du matériel)"
+
+#: utils/cpufreq-info.c:304
+#, c-format
+msgid " cpufreq stats: "
+msgstr " des statistique concernant cpufreq:"
+
+#: utils/cpufreq-info.c:472
+#, fuzzy, c-format
+msgid "Usage: cpupower freqinfo [options]\n"
+msgstr "Usage : cpufreq-info [options]\n"
+
+#: utils/cpufreq-info.c:473 utils/cpufreq-set.c:26 utils/cpupower-set.c:23
+#: utils/cpupower-info.c:22 utils/cpuidle-info.c:148
+#, c-format
+msgid "Options:\n"
+msgstr "Options :\n"
+
+#: utils/cpufreq-info.c:474
+#, fuzzy, c-format
+msgid " -e, --debug Prints out debug information [default]\n"
+msgstr " -e, --debug Afficher les informations de déboguage\n"
+
+#: utils/cpufreq-info.c:475
+#, c-format
+msgid ""
+" -f, --freq Get frequency the CPU currently runs at, according\n"
+" to the cpufreq core *\n"
+msgstr ""
+" -f, --freq Obtenir la fréquence actuelle du CPU selon le point\n"
+" de vue du coeur du système de cpufreq *\n"
+
+#: utils/cpufreq-info.c:477
+#, c-format
+msgid ""
+" -w, --hwfreq Get frequency the CPU currently runs at, by reading\n"
+" it from hardware (only available to root) *\n"
+msgstr ""
+" -w, --hwfreq Obtenir la fréquence actuelle du CPU directement par\n"
+" le matériel (doit être root) *\n"
+
+#: utils/cpufreq-info.c:479
+#, c-format
+msgid ""
+" -l, --hwlimits Determine the minimum and maximum CPU frequency "
+"allowed *\n"
+msgstr ""
+" -l, --hwlimits Affiche les fréquences minimales et maximales du CPU "
+"*\n"
+
+#: utils/cpufreq-info.c:480
+#, c-format
+msgid " -d, --driver Determines the used cpufreq kernel driver *\n"
+msgstr " -d, --driver Affiche le pilote cpufreq utilisé *\n"
+
+#: utils/cpufreq-info.c:481
+#, c-format
+msgid " -p, --policy Gets the currently used cpufreq policy *\n"
+msgstr " -p, --policy Affiche la tactique actuelle de cpufreq *\n"
+
+#: utils/cpufreq-info.c:482
+#, c-format
+msgid " -g, --governors Determines available cpufreq governors *\n"
+msgstr ""
+" -g, --governors Affiche les régulateurs disponibles de cpufreq *\n"
+
+#: utils/cpufreq-info.c:483
+#, fuzzy, c-format
+msgid ""
+" -r, --related-cpus Determines which CPUs run at the same hardware "
+"frequency *\n"
+msgstr ""
+" -a, --affected-cpus Affiche quels sont les CPUs qui doivent changer de\n"
+" fréquences en même temps *\n"
+
+#: utils/cpufreq-info.c:484
+#, fuzzy, c-format
+msgid ""
+" -a, --affected-cpus Determines which CPUs need to have their frequency\n"
+" coordinated by software *\n"
+msgstr ""
+" -a, --affected-cpus Affiche quels sont les CPUs qui doivent changer de\n"
+" fréquences en même temps *\n"
+
+#: utils/cpufreq-info.c:486
+#, c-format
+msgid " -s, --stats Shows cpufreq statistics if available\n"
+msgstr ""
+" -s, --stats Indique des statistiques concernant cpufreq, si\n"
+" disponibles\n"
+
+#: utils/cpufreq-info.c:487
+#, fuzzy, c-format
+msgid ""
+" -y, --latency Determines the maximum latency on CPU frequency "
+"changes *\n"
+msgstr ""
+" -l, --hwlimits Affiche les fréquences minimales et maximales du CPU "
+"*\n"
+
+#: utils/cpufreq-info.c:488
+#, c-format
+msgid " -b, --boost Checks for turbo or boost modes *\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:489
+#, c-format
+msgid ""
+" -o, --proc Prints out information like provided by the /proc/"
+"cpufreq\n"
+" interface in 2.4. and early 2.6. kernels\n"
+msgstr ""
+" -o, --proc Affiche les informations en utilisant l'interface\n"
+" fournie par /proc/cpufreq, présente dans les "
+"versions\n"
+" 2.4 et les anciennes versions 2.6 du noyau\n"
+
+#: utils/cpufreq-info.c:491
+#, fuzzy, c-format
+msgid ""
+" -m, --human human-readable output for the -f, -w, -s and -y "
+"parameters\n"
+msgstr ""
+" -m, --human affiche dans un format lisible pour un humain\n"
+" pour les options -f, -w et -s (MHz, GHz)\n"
+
+#: utils/cpufreq-info.c:492 utils/cpuidle-info.c:152
+#, c-format
+msgid " -h, --help Prints out this screen\n"
+msgstr " -h, --help affiche l'aide-mémoire\n"
+
+#: utils/cpufreq-info.c:495
+#, c-format
+msgid ""
+"If no argument or only the -c, --cpu parameter is given, debug output about\n"
+"cpufreq is printed which is useful e.g. for reporting bugs.\n"
+msgstr ""
+"Par défaut, les informations de déboguage seront affichées si aucun\n"
+"argument, ou bien si seulement l'argument -c (--cpu) est donné, afin de\n"
+"faciliter les rapports de bogues par exemple\n"
+
+#: utils/cpufreq-info.c:497
+#, c-format
+msgid ""
+"For the arguments marked with *, omitting the -c or --cpu argument is\n"
+"equivalent to setting it to zero\n"
+msgstr "Les arguments avec un * utiliseront le CPU 0 si -c (--cpu) est omis\n"
+
+#: utils/cpufreq-info.c:580
+#, c-format
+msgid ""
+"The argument passed to this tool can't be combined with passing a --cpu "
+"argument\n"
+msgstr "Cette option est incompatible avec --cpu\n"
+
+#: utils/cpufreq-info.c:596
+#, c-format
+msgid ""
+"You can't specify more than one --cpu parameter and/or\n"
+"more than one output-specific argument\n"
+msgstr ""
+"On ne peut indiquer plus d'un paramètre --cpu, tout comme l'on ne peut\n"
+"spécifier plus d'un argument de formatage\n"
+
+#: utils/cpufreq-info.c:600 utils/cpufreq-set.c:82 utils/cpupower-set.c:42
+#: utils/cpupower-info.c:42 utils/cpuidle-info.c:213
+#, c-format
+msgid "invalid or unknown argument\n"
+msgstr "option invalide\n"
+
+#: utils/cpufreq-info.c:617
+#, c-format
+msgid "couldn't analyze CPU %d as it doesn't seem to be present\n"
+msgstr "analyse du CPU %d impossible puisqu'il ne semble pas être présent\n"
+
+#: utils/cpufreq-info.c:620 utils/cpupower-info.c:142
+#, c-format
+msgid "analyzing CPU %d:\n"
+msgstr "analyse du CPU %d :\n"
+
+#: utils/cpufreq-set.c:25
+#, fuzzy, c-format
+msgid "Usage: cpupower frequency-set [options]\n"
+msgstr "Usage : cpufreq-set [options]\n"
+
+#: utils/cpufreq-set.c:27
+#, c-format
+msgid ""
+" -d FREQ, --min FREQ new minimum CPU frequency the governor may "
+"select\n"
+msgstr ""
+" -d FREQ, --min FREQ nouvelle fréquence minimale du CPU à utiliser\n"
+" par le régulateur\n"
+
+#: utils/cpufreq-set.c:28
+#, c-format
+msgid ""
+" -u FREQ, --max FREQ new maximum CPU frequency the governor may "
+"select\n"
+msgstr ""
+" -u FREQ, --max FREQ nouvelle fréquence maximale du CPU à utiliser\n"
+" par le régulateur\n"
+
+#: utils/cpufreq-set.c:29
+#, c-format
+msgid " -g GOV, --governor GOV new cpufreq governor\n"
+msgstr " -g GOV, --governor GOV active le régulateur GOV\n"
+
+#: utils/cpufreq-set.c:30
+#, c-format
+msgid ""
+" -f FREQ, --freq FREQ specific frequency to be set. Requires userspace\n"
+" governor to be available and loaded\n"
+msgstr ""
+" -f FREQ, --freq FREQ fixe la fréquence du processeur à FREQ. Il faut\n"
+" que le régulateur « userspace » soit disponible \n"
+" et activé.\n"
+
+#: utils/cpufreq-set.c:32
+#, c-format
+msgid " -r, --related Switches all hardware-related CPUs\n"
+msgstr ""
+
+#: utils/cpufreq-set.c:33 utils/cpupower-set.c:28 utils/cpupower-info.c:27
+#, fuzzy, c-format
+msgid " -h, --help Prints out this screen\n"
+msgstr " -h, --help affiche l'aide-mémoire\n"
+
+#: utils/cpufreq-set.c:35
+#, fuzzy, c-format
+msgid ""
+"Notes:\n"
+"1. Omitting the -c or --cpu argument is equivalent to setting it to \"all\"\n"
+msgstr "Les arguments avec un * utiliseront le CPU 0 si -c (--cpu) est omis\n"
+
+#: utils/cpufreq-set.c:37
+#, fuzzy, c-format
+msgid ""
+"2. The -f FREQ, --freq FREQ parameter cannot be combined with any other "
+"parameter\n"
+" except the -c CPU, --cpu CPU parameter\n"
+"3. FREQuencies can be passed in Hz, kHz (default), MHz, GHz, or THz\n"
+" by postfixing the value with the wanted unit name, without any space\n"
+" (FREQuency in kHz =^ Hz * 0.001 =^ MHz * 1000 =^ GHz * 1000000).\n"
+msgstr ""
+"Remarque :\n"
+"1. Le CPU numéro 0 sera utilisé par défaut si -c (ou --cpu) est omis ;\n"
+"2. l'argument -f FREQ (ou --freq FREQ) ne peut être utilisé qu'avec --cpu ;\n"
+"3. on pourra préciser l'unité des fréquences en postfixant sans aucune "
+"espace\n"
+" les valeurs par hz, kHz (par défaut), MHz, GHz ou THz\n"
+" (kHz =^ Hz * 0.001 =^ MHz * 1000 =^ GHz * 1000000).\n"
+
+#: utils/cpufreq-set.c:57
+#, c-format
+msgid ""
+"Error setting new values. Common errors:\n"
+"- Do you have proper administration rights? (super-user?)\n"
+"- Is the governor you requested available and modprobed?\n"
+"- Trying to set an invalid policy?\n"
+"- Trying to set a specific frequency, but userspace governor is not "
+"available,\n"
+" for example because of hardware which cannot be set to a specific "
+"frequency\n"
+" or because the userspace governor isn't loaded?\n"
+msgstr ""
+"En ajustant les nouveaux paramètres, une erreur est apparue. Les sources\n"
+"d'erreur typique sont :\n"
+"- droit d'administration insuffisant (êtes-vous root ?) ;\n"
+"- le régulateur choisi n'est pas disponible, ou bien n'est pas disponible "
+"en\n"
+" tant que module noyau ;\n"
+"- la tactique n'est pas disponible ;\n"
+"- vous voulez utiliser l'option -f/--freq, mais le régulateur « userspace »\n"
+" n'est pas disponible, par exemple parce que le matériel ne le supporte\n"
+" pas, ou bien n'est tout simplement pas chargé.\n"
+
+#: utils/cpufreq-set.c:170
+#, c-format
+msgid "wrong, unknown or unhandled CPU?\n"
+msgstr "CPU inconnu ou non supporté ?\n"
+
+#: utils/cpufreq-set.c:302
+#, c-format
+msgid ""
+"the -f/--freq parameter cannot be combined with -d/--min, -u/--max or\n"
+"-g/--governor parameters\n"
+msgstr ""
+"l'option -f/--freq est incompatible avec les options -d/--min, -u/--max et\n"
+"-g/--governor\n"
+
+#: utils/cpufreq-set.c:308
+#, c-format
+msgid ""
+"At least one parameter out of -f/--freq, -d/--min, -u/--max, and\n"
+"-g/--governor must be passed\n"
+msgstr ""
+"L'un de ces paramètres est obligatoire : -f/--freq, -d/--min, -u/--max et\n"
+"-g/--governor\n"
+
+#: utils/cpufreq-set.c:347
+#, c-format
+msgid "Setting cpu: %d\n"
+msgstr ""
+
+#: utils/cpupower-set.c:22
+#, c-format
+msgid "Usage: cpupower set [ -b val ] [ -m val ] [ -s val ]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:24
+#, c-format
+msgid ""
+" -b, --perf-bias [VAL] Sets CPU's power vs performance policy on some\n"
+" Intel models [0-15], see manpage for details\n"
+msgstr ""
+
+#: utils/cpupower-set.c:26
+#, c-format
+msgid ""
+" -m, --sched-mc [VAL] Sets the kernel's multi core scheduler policy.\n"
+msgstr ""
+
+#: utils/cpupower-set.c:27
+#, c-format
+msgid ""
+" -s, --sched-smt [VAL] Sets the kernel's thread sibling scheduler "
+"policy.\n"
+msgstr ""
+
+#: utils/cpupower-set.c:80
+#, c-format
+msgid "--perf-bias param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:91
+#, c-format
+msgid "--sched-mc param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:102
+#, c-format
+msgid "--sched-smt param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:121
+#, c-format
+msgid "Error setting sched-mc %s\n"
+msgstr ""
+
+#: utils/cpupower-set.c:127
+#, c-format
+msgid "Error setting sched-smt %s\n"
+msgstr ""
+
+#: utils/cpupower-set.c:146
+#, c-format
+msgid "Error setting perf-bias value on CPU %d\n"
+msgstr ""
+
+#: utils/cpupower-info.c:21
+#, c-format
+msgid "Usage: cpupower info [ -b ] [ -m ] [ -s ]\n"
+msgstr ""
+
+#: utils/cpupower-info.c:23
+#, c-format
+msgid ""
+" -b, --perf-bias Gets CPU's power vs performance policy on some\n"
+" Intel models [0-15], see manpage for details\n"
+msgstr ""
+
+#: utils/cpupower-info.c:25
+#, fuzzy, c-format
+msgid " -m, --sched-mc Gets the kernel's multi core scheduler policy.\n"
+msgstr " -p, --policy Affiche la tactique actuelle de cpufreq *\n"
+
+#: utils/cpupower-info.c:26
+#, c-format
+msgid ""
+" -s, --sched-smt Gets the kernel's thread sibling scheduler policy.\n"
+msgstr ""
+
+#: utils/cpupower-info.c:28
+#, c-format
+msgid ""
+"\n"
+"Passing no option will show all info, by default only on core 0\n"
+msgstr ""
+
+#: utils/cpupower-info.c:102
+#, c-format
+msgid "System's multi core scheduler setting: "
+msgstr ""
+
+#. if sysfs file is missing it's: errno == ENOENT
+#: utils/cpupower-info.c:105 utils/cpupower-info.c:114
+#, c-format
+msgid "not supported\n"
+msgstr ""
+
+#: utils/cpupower-info.c:111
+#, c-format
+msgid "System's thread sibling scheduler setting: "
+msgstr ""
+
+#: utils/cpupower-info.c:126
+#, c-format
+msgid "Intel's performance bias setting needs root privileges\n"
+msgstr ""
+
+#: utils/cpupower-info.c:128
+#, c-format
+msgid "System does not support Intel's performance bias setting\n"
+msgstr ""
+
+#: utils/cpupower-info.c:147
+#, c-format
+msgid "Could not read perf-bias value\n"
+msgstr ""
+
+#: utils/cpupower-info.c:150
+#, c-format
+msgid "perf-bias: %d\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:28
+#, fuzzy, c-format
+msgid "Analyzing CPU %d:\n"
+msgstr "analyse du CPU %d :\n"
+
+#: utils/cpuidle-info.c:32
+#, c-format
+msgid "CPU %u: No idle states\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:36
+#, c-format
+msgid "CPU %u: Can't read idle state info\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:41
+#, c-format
+msgid "Could not determine max idle state %u\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:46
+#, c-format
+msgid "Number of idle states: %d\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:48
+#, fuzzy, c-format
+msgid "Available idle states:"
+msgstr " plage de fréquence : "
+
+#: utils/cpuidle-info.c:71
+#, c-format
+msgid "Flags/Description: %s\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:74
+#, c-format
+msgid "Latency: %lu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:76
+#, c-format
+msgid "Usage: %lu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:78
+#, c-format
+msgid "Duration: %llu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:90
+#, c-format
+msgid "Could not determine cpuidle driver\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:94
+#, fuzzy, c-format
+msgid "CPUidle driver: %s\n"
+msgstr " pilote : %s\n"
+
+#: utils/cpuidle-info.c:99
+#, c-format
+msgid "Could not determine cpuidle governor\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:103
+#, c-format
+msgid "CPUidle governor: %s\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:122
+#, c-format
+msgid "CPU %u: Can't read C-state info\n"
+msgstr ""
+
+#. printf("Cstates: %d\n", cstates);
+#: utils/cpuidle-info.c:127
+#, c-format
+msgid "active state: C0\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:128
+#, c-format
+msgid "max_cstate: C%u\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:129
+#, c-format
+msgid "maximum allowed latency: %lu usec\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:130
+#, c-format
+msgid "states:\t\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:132
+#, c-format
+msgid " C%d: type[C%d] "
+msgstr ""
+
+#: utils/cpuidle-info.c:134
+#, c-format
+msgid "promotion[--] demotion[--] "
+msgstr ""
+
+#: utils/cpuidle-info.c:135
+#, c-format
+msgid "latency[%03lu] "
+msgstr ""
+
+#: utils/cpuidle-info.c:137
+#, c-format
+msgid "usage[%08lu] "
+msgstr ""
+
+#: utils/cpuidle-info.c:139
+#, c-format
+msgid "duration[%020Lu] \n"
+msgstr ""
+
+#: utils/cpuidle-info.c:147
+#, fuzzy, c-format
+msgid "Usage: cpupower idleinfo [options]\n"
+msgstr "Usage : cpufreq-info [options]\n"
+
+#: utils/cpuidle-info.c:149
+#, fuzzy, c-format
+msgid " -s, --silent Only show general C-state information\n"
+msgstr " -e, --debug Afficher les informations de déboguage\n"
+
+#: utils/cpuidle-info.c:150
+#, fuzzy, c-format
+msgid ""
+" -o, --proc Prints out information like provided by the /proc/"
+"acpi/processor/*/power\n"
+" interface in older kernels\n"
+msgstr ""
+" -o, --proc Affiche les informations en utilisant l'interface\n"
+" fournie par /proc/cpufreq, présente dans les "
+"versions\n"
+" 2.4 et les anciennes versions 2.6 du noyau\n"
+
+#: utils/cpuidle-info.c:209
+#, fuzzy, c-format
+msgid "You can't specify more than one output-specific argument\n"
+msgstr ""
+"On ne peut indiquer plus d'un paramètre --cpu, tout comme l'on ne peut\n"
+"spécifier plus d'un argument de formatage\n"
+
+#~ msgid ""
+#~ " -c CPU, --cpu CPU CPU number which information shall be determined "
+#~ "about\n"
+#~ msgstr ""
+#~ " -c CPU, --cpu CPU Numéro du CPU pour lequel l'information sera "
+#~ "affichée\n"
+
+#~ msgid ""
+#~ " -c CPU, --cpu CPU number of CPU where cpufreq settings shall be "
+#~ "modified\n"
+#~ msgstr ""
+#~ " -c CPU, --cpu CPU numéro du CPU à prendre en compte pour les\n"
+#~ " changements\n"
diff --git a/tools/power/cpupower/po/it.po b/tools/power/cpupower/po/it.po
new file mode 100644
index 00000000000..f80c4ddb9bd
--- /dev/null
+++ b/tools/power/cpupower/po/it.po
@@ -0,0 +1,961 @@
+# Italian translations for cpufrequtils package
+# Copyright (C) 2004-2009
+# This file is distributed under the same license as the cpufrequtils package.
+# Mattia Dongili <malattia@gmail.com>.
+#
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: cpufrequtils 0.3\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-03-08 17:03+0100\n"
+"PO-Revision-Date: 2009-08-15 12:00+0900\n"
+"Last-Translator: Mattia Dongili <malattia@gmail.com>\n"
+"Language-Team: NONE\n"
+"Language: \n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: utils/idle_monitor/nhm_idle.c:36
+msgid "Processor Core C3"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:43
+msgid "Processor Core C6"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:51
+msgid "Processor Package C3"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:58 utils/idle_monitor/amd_fam14h_idle.c:70
+msgid "Processor Package C6"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:33
+msgid "Processor Core C7"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:40
+msgid "Processor Package C2"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:47
+msgid "Processor Package C7"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:56
+msgid "Package in sleep state (PC1 or deeper)"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:63
+msgid "Processor Package C1"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:77
+msgid "North Bridge P1 boolean counter (returns 0 or 1)"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:35
+msgid "Processor Core not idle"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:42
+msgid "Processor Core in an idle state"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:50
+msgid "Average Frequency (including boost) in MHz"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:66
+#, c-format
+msgid ""
+"cpupower monitor: [-h] [ [-t] | [-l] | [-m <mon1>,[<mon2>] ] ] [-i "
+"interval_sec | -c command ...]\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:69
+#, c-format
+msgid ""
+"cpupower monitor: [-v] [-h] [ [-t] | [-l] | [-m <mon1>,[<mon2>] ] ] [-i "
+"interval_sec | -c command ...]\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:71
+#, c-format
+msgid "\t -v: be more verbose\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:73
+#, c-format
+msgid "\t -h: print this help\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:74
+#, c-format
+msgid "\t -i: time intervall to measure for in seconds (default 1)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:75
+#, c-format
+msgid "\t -t: show CPU topology/hierarchy\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:76
+#, c-format
+msgid "\t -l: list available CPU sleep monitors (for use with -m)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:77
+#, c-format
+msgid "\t -m: show specific CPU sleep monitors only (in same order)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:79
+#, c-format
+msgid ""
+"only one of: -t, -l, -m are allowed\n"
+"If none of them is passed,"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:80
+#, c-format
+msgid " all supported monitors are shown\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:197
+#, c-format
+msgid "Monitor %s, Counter %s has no count function. Implementation error\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:207
+#, c-format
+msgid " *is offline\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:236
+#, c-format
+msgid "%s: max monitor name length (%d) exceeded\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:250
+#, c-format
+msgid "No matching monitor found in %s, try -l option\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:266
+#, c-format
+msgid "Monitor \"%s\" (%d states) - Might overflow after %u s\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:319
+#, c-format
+msgid "%s took %.5f seconds and exited with status %d\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:406
+#, c-format
+msgid "Cannot read number of available processors\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:417
+#, c-format
+msgid "Available monitor %s needs root access\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:428
+#, c-format
+msgid "No HW Cstate monitors found\n"
+msgstr ""
+
+#: utils/cpupower.c:78
+#, c-format
+msgid "cpupower [ -c cpulist ] subcommand [ARGS]\n"
+msgstr ""
+
+#: utils/cpupower.c:79
+#, c-format
+msgid "cpupower --version\n"
+msgstr ""
+
+#: utils/cpupower.c:80
+#, c-format
+msgid "Supported subcommands are:\n"
+msgstr ""
+
+#: utils/cpupower.c:83
+#, c-format
+msgid ""
+"\n"
+"Some subcommands can make use of the -c cpulist option.\n"
+msgstr ""
+
+#: utils/cpupower.c:84
+#, c-format
+msgid "Look at the general cpupower manpage how to use it\n"
+msgstr ""
+
+#: utils/cpupower.c:85
+#, c-format
+msgid "and read up the subcommand's manpage whether it is supported.\n"
+msgstr ""
+
+#: utils/cpupower.c:86
+#, c-format
+msgid ""
+"\n"
+"Use cpupower help subcommand for getting help for above subcommands.\n"
+msgstr ""
+
+#: utils/cpupower.c:91
+#, c-format
+msgid "Report errors and bugs to %s, please.\n"
+msgstr "Per favore, comunicare errori e malfunzionamenti a %s.\n"
+
+#: utils/cpupower.c:114
+#, c-format
+msgid "Error parsing cpu list\n"
+msgstr ""
+
+#: utils/cpupower.c:172
+#, c-format
+msgid "Subcommand %s needs root privileges\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:31
+#, c-format
+msgid "Couldn't count the number of CPUs (%s: %s), assuming 1\n"
+msgstr "Impossibile determinare il numero di CPU (%s: %s), assumo sia 1\n"
+
+#: utils/cpufreq-info.c:63
+#, c-format
+msgid ""
+" minimum CPU frequency - maximum CPU frequency - governor\n"
+msgstr ""
+" frequenza minima CPU - frequenza massima CPU - gestore\n"
+
+#: utils/cpufreq-info.c:151
+#, c-format
+msgid "Error while evaluating Boost Capabilities on CPU %d -- are you root?\n"
+msgstr ""
+
+#. P state changes via MSR are identified via cpuid 80000007
+#. on Intel and AMD, but we assume boost capable machines can do that
+#. if (cpuid_eax(0x80000000) >= 0x80000007
+#. && (cpuid_edx(0x80000007) & (1 << 7)))
+#.
+#: utils/cpufreq-info.c:161
+#, c-format
+msgid " boost state support: \n"
+msgstr ""
+
+#: utils/cpufreq-info.c:163
+#, c-format
+msgid " Supported: %s\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:163 utils/cpufreq-info.c:164
+msgid "yes"
+msgstr ""
+
+#: utils/cpufreq-info.c:163 utils/cpufreq-info.c:164
+msgid "no"
+msgstr ""
+
+#: utils/cpufreq-info.c:164
+#, fuzzy, c-format
+msgid " Active: %s\n"
+msgstr " modulo %s\n"
+
+#: utils/cpufreq-info.c:177
+#, c-format
+msgid " Boost States: %d\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:178
+#, c-format
+msgid " Total States: %d\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:181
+#, c-format
+msgid " Pstate-Pb%d: %luMHz (boost state)\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:184
+#, c-format
+msgid " Pstate-P%d: %luMHz\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:211
+#, c-format
+msgid " no or unknown cpufreq driver is active on this CPU\n"
+msgstr " nessun modulo o modulo cpufreq sconosciuto per questa CPU\n"
+
+#: utils/cpufreq-info.c:213
+#, c-format
+msgid " driver: %s\n"
+msgstr " modulo %s\n"
+
+#: utils/cpufreq-info.c:219
+#, c-format
+msgid " CPUs which run at the same hardware frequency: "
+msgstr " CPU che operano alla stessa frequenza hardware: "
+
+#: utils/cpufreq-info.c:230
+#, c-format
+msgid " CPUs which need to have their frequency coordinated by software: "
+msgstr " CPU che è necessario siano coordinate dal software: "
+
+#: utils/cpufreq-info.c:241
+#, c-format
+msgid " maximum transition latency: "
+msgstr " latenza massima durante la transizione: "
+
+#: utils/cpufreq-info.c:247
+#, c-format
+msgid " hardware limits: "
+msgstr " limiti hardware: "
+
+#: utils/cpufreq-info.c:256
+#, c-format
+msgid " available frequency steps: "
+msgstr " frequenze disponibili: "
+
+#: utils/cpufreq-info.c:269
+#, c-format
+msgid " available cpufreq governors: "
+msgstr " gestori disponibili: "
+
+#: utils/cpufreq-info.c:280
+#, c-format
+msgid " current policy: frequency should be within "
+msgstr " gestore attuale: la frequenza deve mantenersi tra "
+
+#: utils/cpufreq-info.c:282
+#, c-format
+msgid " and "
+msgstr " e "
+
+#: utils/cpufreq-info.c:286
+#, c-format
+msgid ""
+"The governor \"%s\" may decide which speed to use\n"
+" within this range.\n"
+msgstr ""
+" Il gestore \"%s\" può decidere quale velocità usare\n"
+" in questo intervallo.\n"
+
+#: utils/cpufreq-info.c:293
+#, c-format
+msgid " current CPU frequency is "
+msgstr " la frequenza attuale della CPU è "
+
+#: utils/cpufreq-info.c:296
+#, c-format
+msgid " (asserted by call to hardware)"
+msgstr " (ottenuta da una chiamata diretta all'hardware)"
+
+#: utils/cpufreq-info.c:304
+#, c-format
+msgid " cpufreq stats: "
+msgstr " statistiche cpufreq:"
+
+#: utils/cpufreq-info.c:472
+#, fuzzy, c-format
+msgid "Usage: cpupower freqinfo [options]\n"
+msgstr "Uso: cpufreq-info [opzioni]\n"
+
+#: utils/cpufreq-info.c:473 utils/cpufreq-set.c:26 utils/cpupower-set.c:23
+#: utils/cpupower-info.c:22 utils/cpuidle-info.c:148
+#, c-format
+msgid "Options:\n"
+msgstr "Opzioni:\n"
+
+#: utils/cpufreq-info.c:474
+#, fuzzy, c-format
+msgid " -e, --debug Prints out debug information [default]\n"
+msgstr " -e, --debug Mostra informazioni di debug\n"
+
+#: utils/cpufreq-info.c:475
+#, c-format
+msgid ""
+" -f, --freq Get frequency the CPU currently runs at, according\n"
+" to the cpufreq core *\n"
+msgstr ""
+" -f, --freq Mostra la frequenza attuale della CPU secondo\n"
+" il modulo cpufreq *\n"
+
+#: utils/cpufreq-info.c:477
+#, c-format
+msgid ""
+" -w, --hwfreq Get frequency the CPU currently runs at, by reading\n"
+" it from hardware (only available to root) *\n"
+msgstr ""
+" -w, --hwfreq Mostra la frequenza attuale della CPU leggendola\n"
+" dall'hardware (disponibile solo per l'utente root) *\n"
+
+#: utils/cpufreq-info.c:479
+#, c-format
+msgid ""
+" -l, --hwlimits Determine the minimum and maximum CPU frequency "
+"allowed *\n"
+msgstr ""
+" -l, --hwlimits Determina le frequenze minima e massima possibili per "
+"la CPU *\n"
+
+#: utils/cpufreq-info.c:480
+#, c-format
+msgid " -d, --driver Determines the used cpufreq kernel driver *\n"
+msgstr ""
+" -d, --driver Determina il modulo cpufreq del kernel in uso *\n"
+
+#: utils/cpufreq-info.c:481
+#, c-format
+msgid " -p, --policy Gets the currently used cpufreq policy *\n"
+msgstr ""
+" -p, --policy Mostra il gestore cpufreq attualmente in uso *\n"
+
+#: utils/cpufreq-info.c:482
+#, c-format
+msgid " -g, --governors Determines available cpufreq governors *\n"
+msgstr " -g, --governors Determina i gestori cpufreq disponibili *\n"
+
+#: utils/cpufreq-info.c:483
+#, c-format
+msgid ""
+" -r, --related-cpus Determines which CPUs run at the same hardware "
+"frequency *\n"
+msgstr ""
+" -r, --related-cpus Determina quali CPU operano alla stessa frequenza *\n"
+
+#: utils/cpufreq-info.c:484
+#, c-format
+msgid ""
+" -a, --affected-cpus Determines which CPUs need to have their frequency\n"
+" coordinated by software *\n"
+msgstr ""
+" -a, --affected-cpus Determina quali CPU devono avere la frequenza\n"
+" coordinata dal software *\n"
+
+#: utils/cpufreq-info.c:486
+#, c-format
+msgid " -s, --stats Shows cpufreq statistics if available\n"
+msgstr " -s, --stats Mostra le statistiche se disponibili\n"
+
+#: utils/cpufreq-info.c:487
+#, c-format
+msgid ""
+" -y, --latency Determines the maximum latency on CPU frequency "
+"changes *\n"
+msgstr ""
+" -y, --latency Determina la latenza massima durante i cambi di "
+"frequenza *\n"
+
+#: utils/cpufreq-info.c:488
+#, c-format
+msgid " -b, --boost Checks for turbo or boost modes *\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:489
+#, c-format
+msgid ""
+" -o, --proc Prints out information like provided by the /proc/"
+"cpufreq\n"
+" interface in 2.4. and early 2.6. kernels\n"
+msgstr ""
+" -o, --proc Stampa le informazioni come se provenissero dalla\n"
+" interfaccia cpufreq /proc/ presente nei kernel\n"
+" 2.4 ed i primi 2.6\n"
+
+#: utils/cpufreq-info.c:491
+#, c-format
+msgid ""
+" -m, --human human-readable output for the -f, -w, -s and -y "
+"parameters\n"
+msgstr ""
+" -m, --human formatta l'output delle opzioni -f, -w, -s e -y in "
+"maniera\n"
+" leggibile da un essere umano\n"
+
+#: utils/cpufreq-info.c:492 utils/cpuidle-info.c:152
+#, c-format
+msgid " -h, --help Prints out this screen\n"
+msgstr " -h, --help Stampa questa schermata\n"
+
+#: utils/cpufreq-info.c:495
+#, c-format
+msgid ""
+"If no argument or only the -c, --cpu parameter is given, debug output about\n"
+"cpufreq is printed which is useful e.g. for reporting bugs.\n"
+msgstr ""
+"Se non viene specificata nessuna opzione o viene specificata solo l'opzione -"
+"c, --cpu,\n"
+"le informazioni di debug per cpufreq saranno utili ad esempio a riportare i "
+"bug.\n"
+
+#: utils/cpufreq-info.c:497
+#, c-format
+msgid ""
+"For the arguments marked with *, omitting the -c or --cpu argument is\n"
+"equivalent to setting it to zero\n"
+msgstr ""
+"Per le opzioni segnalate con *, omettere l'opzione -c o --cpu è come "
+"specificarla\n"
+"con il valore 0\n"
+
+#: utils/cpufreq-info.c:580
+#, c-format
+msgid ""
+"The argument passed to this tool can't be combined with passing a --cpu "
+"argument\n"
+msgstr ""
+"L'opzione specificata a questo programma non può essere combinata con --cpu\n"
+
+#: utils/cpufreq-info.c:596
+#, c-format
+msgid ""
+"You can't specify more than one --cpu parameter and/or\n"
+"more than one output-specific argument\n"
+msgstr ""
+"Non è possibile specificare più di una volta l'opzione --cpu e/o\n"
+"specificare più di un parametro di output specifico\n"
+
+#: utils/cpufreq-info.c:600 utils/cpufreq-set.c:82 utils/cpupower-set.c:42
+#: utils/cpupower-info.c:42 utils/cpuidle-info.c:213
+#, c-format
+msgid "invalid or unknown argument\n"
+msgstr "opzione sconosciuta o non valida\n"
+
+#: utils/cpufreq-info.c:617
+#, c-format
+msgid "couldn't analyze CPU %d as it doesn't seem to be present\n"
+msgstr "impossibile analizzare la CPU %d poiché non sembra essere presente\n"
+
+#: utils/cpufreq-info.c:620 utils/cpupower-info.c:142
+#, c-format
+msgid "analyzing CPU %d:\n"
+msgstr "analisi della CPU %d:\n"
+
+#: utils/cpufreq-set.c:25
+#, fuzzy, c-format
+msgid "Usage: cpupower frequency-set [options]\n"
+msgstr "Uso: cpufreq-set [opzioni]\n"
+
+#: utils/cpufreq-set.c:27
+#, c-format
+msgid ""
+" -d FREQ, --min FREQ new minimum CPU frequency the governor may "
+"select\n"
+msgstr ""
+" -d FREQ, --min FREQ la nuova frequenza minima che il gestore cpufreq "
+"può scegliere\n"
+
+#: utils/cpufreq-set.c:28
+#, c-format
+msgid ""
+" -u FREQ, --max FREQ new maximum CPU frequency the governor may "
+"select\n"
+msgstr ""
+" -u FREQ, --max FREQ la nuova frequenza massima che il gestore cpufreq "
+"può scegliere\n"
+
+#: utils/cpufreq-set.c:29
+#, c-format
+msgid " -g GOV, --governor GOV new cpufreq governor\n"
+msgstr " -g GOV, --governor GOV nuovo gestore cpufreq\n"
+
+#: utils/cpufreq-set.c:30
+#, c-format
+msgid ""
+" -f FREQ, --freq FREQ specific frequency to be set. Requires userspace\n"
+" governor to be available and loaded\n"
+msgstr ""
+" -f FREQ, --freq FREQ specifica la frequenza a cui impostare la CPU.\n"
+" È necessario che il gestore userspace sia "
+"disponibile e caricato\n"
+
+#: utils/cpufreq-set.c:32
+#, c-format
+msgid " -r, --related Switches all hardware-related CPUs\n"
+msgstr ""
+" -r, --related Modifica tutte le CPU coordinate dall'hardware\n"
+
+#: utils/cpufreq-set.c:33 utils/cpupower-set.c:28 utils/cpupower-info.c:27
+#, c-format
+msgid " -h, --help Prints out this screen\n"
+msgstr " -h, --help Stampa questa schermata\n"
+
+#: utils/cpufreq-set.c:35
+#, fuzzy, c-format
+msgid ""
+"Notes:\n"
+"1. Omitting the -c or --cpu argument is equivalent to setting it to \"all\"\n"
+msgstr ""
+"Per le opzioni segnalate con *, omettere l'opzione -c o --cpu è come "
+"specificarla\n"
+"con il valore 0\n"
+
+#: utils/cpufreq-set.c:37
+#, fuzzy, c-format
+msgid ""
+"2. The -f FREQ, --freq FREQ parameter cannot be combined with any other "
+"parameter\n"
+" except the -c CPU, --cpu CPU parameter\n"
+"3. FREQuencies can be passed in Hz, kHz (default), MHz, GHz, or THz\n"
+" by postfixing the value with the wanted unit name, without any space\n"
+" (FREQuency in kHz =^ Hz * 0.001 =^ MHz * 1000 =^ GHz * 1000000).\n"
+msgstr ""
+"Note:\n"
+"1. Omettere l'opzione -c o --cpu è equivalente a impostarlo a 0\n"
+"2. l'opzione -f FREQ, --freq FREQ non può essere specificata con altre "
+"opzioni\n"
+" ad eccezione dell'opzione -c CPU o --cpu CPU\n"
+"3. le FREQuenze possono essere specuficate in Hz, kHz (default), MHz, GHz, "
+"or THz\n"
+" postponendo l'unità di misura al valore senza nessuno spazio fra loro\n"
+" (FREQuenza in kHz =^ Hz * 0.001 =^ MHz * 1000 =^ GHz * 1000000).\n"
+
+#: utils/cpufreq-set.c:57
+#, c-format
+msgid ""
+"Error setting new values. Common errors:\n"
+"- Do you have proper administration rights? (super-user?)\n"
+"- Is the governor you requested available and modprobed?\n"
+"- Trying to set an invalid policy?\n"
+"- Trying to set a specific frequency, but userspace governor is not "
+"available,\n"
+" for example because of hardware which cannot be set to a specific "
+"frequency\n"
+" or because the userspace governor isn't loaded?\n"
+msgstr ""
+"Si sono verificati degli errori impostando i nuovi valori.\n"
+"Alcuni errori comuni possono essere:\n"
+"- Hai i necessari diritti di amministrazione? (super-user?)\n"
+"- Il gestore che hai richiesto è disponibile e caricato?\n"
+"- Stai provando ad impostare una politica di gestione non valida?\n"
+"- Stai provando a impostare una specifica frequenza ma il gestore\n"
+" userspace non è disponibile, per esempio a causa dell'hardware\n"
+" che non supporta frequenze fisse o a causa del fatto che\n"
+" il gestore userspace non è caricato?\n"
+
+#: utils/cpufreq-set.c:170
+#, c-format
+msgid "wrong, unknown or unhandled CPU?\n"
+msgstr "CPU errata, sconosciuta o non gestita?\n"
+
+#: utils/cpufreq-set.c:302
+#, c-format
+msgid ""
+"the -f/--freq parameter cannot be combined with -d/--min, -u/--max or\n"
+"-g/--governor parameters\n"
+msgstr ""
+"l'opzione -f/--freq non può venire combinata con i parametri\n"
+" -d/--min, -u/--max o -g/--governor\n"
+
+#: utils/cpufreq-set.c:308
+#, c-format
+msgid ""
+"At least one parameter out of -f/--freq, -d/--min, -u/--max, and\n"
+"-g/--governor must be passed\n"
+msgstr ""
+"Almeno una delle opzioni -f/--freq, -d/--min, -u/--max, e -g/--governor\n"
+"deve essere specificata\n"
+
+#: utils/cpufreq-set.c:347
+#, c-format
+msgid "Setting cpu: %d\n"
+msgstr ""
+
+#: utils/cpupower-set.c:22
+#, c-format
+msgid "Usage: cpupower set [ -b val ] [ -m val ] [ -s val ]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:24
+#, c-format
+msgid ""
+" -b, --perf-bias [VAL] Sets CPU's power vs performance policy on some\n"
+" Intel models [0-15], see manpage for details\n"
+msgstr ""
+
+#: utils/cpupower-set.c:26
+#, c-format
+msgid ""
+" -m, --sched-mc [VAL] Sets the kernel's multi core scheduler policy.\n"
+msgstr ""
+
+#: utils/cpupower-set.c:27
+#, c-format
+msgid ""
+" -s, --sched-smt [VAL] Sets the kernel's thread sibling scheduler "
+"policy.\n"
+msgstr ""
+
+#: utils/cpupower-set.c:80
+#, c-format
+msgid "--perf-bias param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:91
+#, c-format
+msgid "--sched-mc param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:102
+#, c-format
+msgid "--sched-smt param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:121
+#, c-format
+msgid "Error setting sched-mc %s\n"
+msgstr ""
+
+#: utils/cpupower-set.c:127
+#, c-format
+msgid "Error setting sched-smt %s\n"
+msgstr ""
+
+#: utils/cpupower-set.c:146
+#, c-format
+msgid "Error setting perf-bias value on CPU %d\n"
+msgstr ""
+
+#: utils/cpupower-info.c:21
+#, c-format
+msgid "Usage: cpupower info [ -b ] [ -m ] [ -s ]\n"
+msgstr ""
+
+#: utils/cpupower-info.c:23
+#, c-format
+msgid ""
+" -b, --perf-bias Gets CPU's power vs performance policy on some\n"
+" Intel models [0-15], see manpage for details\n"
+msgstr ""
+
+#: utils/cpupower-info.c:25
+#, fuzzy, c-format
+msgid " -m, --sched-mc Gets the kernel's multi core scheduler policy.\n"
+msgstr ""
+" -p, --policy Mostra il gestore cpufreq attualmente in uso *\n"
+
+#: utils/cpupower-info.c:26
+#, c-format
+msgid ""
+" -s, --sched-smt Gets the kernel's thread sibling scheduler policy.\n"
+msgstr ""
+
+#: utils/cpupower-info.c:28
+#, c-format
+msgid ""
+"\n"
+"Passing no option will show all info, by default only on core 0\n"
+msgstr ""
+
+#: utils/cpupower-info.c:102
+#, c-format
+msgid "System's multi core scheduler setting: "
+msgstr ""
+
+#. if sysfs file is missing it's: errno == ENOENT
+#: utils/cpupower-info.c:105 utils/cpupower-info.c:114
+#, c-format
+msgid "not supported\n"
+msgstr ""
+
+#: utils/cpupower-info.c:111
+#, c-format
+msgid "System's thread sibling scheduler setting: "
+msgstr ""
+
+#: utils/cpupower-info.c:126
+#, c-format
+msgid "Intel's performance bias setting needs root privileges\n"
+msgstr ""
+
+#: utils/cpupower-info.c:128
+#, c-format
+msgid "System does not support Intel's performance bias setting\n"
+msgstr ""
+
+#: utils/cpupower-info.c:147
+#, c-format
+msgid "Could not read perf-bias value\n"
+msgstr ""
+
+#: utils/cpupower-info.c:150
+#, c-format
+msgid "perf-bias: %d\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:28
+#, fuzzy, c-format
+msgid "Analyzing CPU %d:\n"
+msgstr "analisi della CPU %d:\n"
+
+#: utils/cpuidle-info.c:32
+#, c-format
+msgid "CPU %u: No idle states\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:36
+#, c-format
+msgid "CPU %u: Can't read idle state info\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:41
+#, c-format
+msgid "Could not determine max idle state %u\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:46
+#, c-format
+msgid "Number of idle states: %d\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:48
+#, fuzzy, c-format
+msgid "Available idle states:"
+msgstr " frequenze disponibili: "
+
+#: utils/cpuidle-info.c:71
+#, c-format
+msgid "Flags/Description: %s\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:74
+#, c-format
+msgid "Latency: %lu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:76
+#, c-format
+msgid "Usage: %lu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:78
+#, c-format
+msgid "Duration: %llu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:90
+#, c-format
+msgid "Could not determine cpuidle driver\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:94
+#, fuzzy, c-format
+msgid "CPUidle driver: %s\n"
+msgstr " modulo %s\n"
+
+#: utils/cpuidle-info.c:99
+#, c-format
+msgid "Could not determine cpuidle governor\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:103
+#, c-format
+msgid "CPUidle governor: %s\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:122
+#, c-format
+msgid "CPU %u: Can't read C-state info\n"
+msgstr ""
+
+#. printf("Cstates: %d\n", cstates);
+#: utils/cpuidle-info.c:127
+#, c-format
+msgid "active state: C0\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:128
+#, c-format
+msgid "max_cstate: C%u\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:129
+#, fuzzy, c-format
+msgid "maximum allowed latency: %lu usec\n"
+msgstr " latenza massima durante la transizione: "
+
+#: utils/cpuidle-info.c:130
+#, c-format
+msgid "states:\t\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:132
+#, c-format
+msgid " C%d: type[C%d] "
+msgstr ""
+
+#: utils/cpuidle-info.c:134
+#, c-format
+msgid "promotion[--] demotion[--] "
+msgstr ""
+
+#: utils/cpuidle-info.c:135
+#, c-format
+msgid "latency[%03lu] "
+msgstr ""
+
+#: utils/cpuidle-info.c:137
+#, c-format
+msgid "usage[%08lu] "
+msgstr ""
+
+#: utils/cpuidle-info.c:139
+#, c-format
+msgid "duration[%020Lu] \n"
+msgstr ""
+
+#: utils/cpuidle-info.c:147
+#, fuzzy, c-format
+msgid "Usage: cpupower idleinfo [options]\n"
+msgstr "Uso: cpufreq-info [opzioni]\n"
+
+#: utils/cpuidle-info.c:149
+#, fuzzy, c-format
+msgid " -s, --silent Only show general C-state information\n"
+msgstr " -e, --debug Mostra informazioni di debug\n"
+
+#: utils/cpuidle-info.c:150
+#, fuzzy, c-format
+msgid ""
+" -o, --proc Prints out information like provided by the /proc/"
+"acpi/processor/*/power\n"
+" interface in older kernels\n"
+msgstr ""
+" -o, --proc Stampa le informazioni come se provenissero dalla\n"
+" interfaccia cpufreq /proc/ presente nei kernel\n"
+" 2.4 ed i primi 2.6\n"
+
+#: utils/cpuidle-info.c:209
+#, fuzzy, c-format
+msgid "You can't specify more than one output-specific argument\n"
+msgstr ""
+"Non è possibile specificare più di una volta l'opzione --cpu e/o\n"
+"specificare più di un parametro di output specifico\n"
+
+#~ msgid ""
+#~ " -c CPU, --cpu CPU CPU number which information shall be determined "
+#~ "about\n"
+#~ msgstr ""
+#~ " -c CPU, --cpu CPU Numero di CPU per la quale ottenere le "
+#~ "informazioni\n"
+
+#~ msgid ""
+#~ " -c CPU, --cpu CPU number of CPU where cpufreq settings shall be "
+#~ "modified\n"
+#~ msgstr ""
+#~ " -c CPU, --cpu CPU numero di CPU per la quale modificare le "
+#~ "impostazioni\n"
+
+#, fuzzy
+#~ msgid " CPUs which coordinate software frequency requirements: "
+#~ msgstr ""
+#~ " CPU per le quali e` necessario cambiare la frequenza "
+#~ "contemporaneamente: "
diff --git a/tools/power/cpupower/po/pt.po b/tools/power/cpupower/po/pt.po
new file mode 100644
index 00000000000..990f5267ffe
--- /dev/null
+++ b/tools/power/cpupower/po/pt.po
@@ -0,0 +1,957 @@
+# Brazilian Portuguese translations for cpufrequtils package
+# Copyright (C) 2008 THE cpufrequtils'S COPYRIGHT HOLDER
+# This file is distributed under the same license as the cpufrequtils package.
+# Claudio Eduardo <claudioeddy@gmail.com>, 2009.
+#
+#
+msgid ""
+msgstr ""
+"Project-Id-Version: cpufrequtils 004\n"
+"Report-Msgid-Bugs-To: \n"
+"POT-Creation-Date: 2011-03-08 17:03+0100\n"
+"PO-Revision-Date: 2008-06-14 22:16-0400\n"
+"Last-Translator: Claudio Eduardo <claudioeddy@gmail.com>\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+
+#: utils/idle_monitor/nhm_idle.c:36
+msgid "Processor Core C3"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:43
+msgid "Processor Core C6"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:51
+msgid "Processor Package C3"
+msgstr ""
+
+#: utils/idle_monitor/nhm_idle.c:58 utils/idle_monitor/amd_fam14h_idle.c:70
+msgid "Processor Package C6"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:33
+msgid "Processor Core C7"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:40
+msgid "Processor Package C2"
+msgstr ""
+
+#: utils/idle_monitor/snb_idle.c:47
+msgid "Processor Package C7"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:56
+msgid "Package in sleep state (PC1 or deeper)"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:63
+msgid "Processor Package C1"
+msgstr ""
+
+#: utils/idle_monitor/amd_fam14h_idle.c:77
+msgid "North Bridge P1 boolean counter (returns 0 or 1)"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:35
+msgid "Processor Core not idle"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:42
+msgid "Processor Core in an idle state"
+msgstr ""
+
+#: utils/idle_monitor/mperf_monitor.c:50
+msgid "Average Frequency (including boost) in MHz"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:66
+#, c-format
+msgid ""
+"cpupower monitor: [-h] [ [-t] | [-l] | [-m <mon1>,[<mon2>] ] ] [-i "
+"interval_sec | -c command ...]\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:69
+#, c-format
+msgid ""
+"cpupower monitor: [-v] [-h] [ [-t] | [-l] | [-m <mon1>,[<mon2>] ] ] [-i "
+"interval_sec | -c command ...]\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:71
+#, c-format
+msgid "\t -v: be more verbose\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:73
+#, c-format
+msgid "\t -h: print this help\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:74
+#, c-format
+msgid "\t -i: time intervall to measure for in seconds (default 1)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:75
+#, c-format
+msgid "\t -t: show CPU topology/hierarchy\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:76
+#, c-format
+msgid "\t -l: list available CPU sleep monitors (for use with -m)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:77
+#, c-format
+msgid "\t -m: show specific CPU sleep monitors only (in same order)\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:79
+#, c-format
+msgid ""
+"only one of: -t, -l, -m are allowed\n"
+"If none of them is passed,"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:80
+#, c-format
+msgid " all supported monitors are shown\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:197
+#, c-format
+msgid "Monitor %s, Counter %s has no count function. Implementation error\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:207
+#, c-format
+msgid " *is offline\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:236
+#, c-format
+msgid "%s: max monitor name length (%d) exceeded\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:250
+#, c-format
+msgid "No matching monitor found in %s, try -l option\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:266
+#, c-format
+msgid "Monitor \"%s\" (%d states) - Might overflow after %u s\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:319
+#, c-format
+msgid "%s took %.5f seconds and exited with status %d\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:406
+#, c-format
+msgid "Cannot read number of available processors\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:417
+#, c-format
+msgid "Available monitor %s needs root access\n"
+msgstr ""
+
+#: utils/idle_monitor/cpupower-monitor.c:428
+#, c-format
+msgid "No HW Cstate monitors found\n"
+msgstr ""
+
+#: utils/cpupower.c:78
+#, c-format
+msgid "cpupower [ -c cpulist ] subcommand [ARGS]\n"
+msgstr ""
+
+#: utils/cpupower.c:79
+#, c-format
+msgid "cpupower --version\n"
+msgstr ""
+
+#: utils/cpupower.c:80
+#, c-format
+msgid "Supported subcommands are:\n"
+msgstr ""
+
+#: utils/cpupower.c:83
+#, c-format
+msgid ""
+"\n"
+"Some subcommands can make use of the -c cpulist option.\n"
+msgstr ""
+
+#: utils/cpupower.c:84
+#, c-format
+msgid "Look at the general cpupower manpage how to use it\n"
+msgstr ""
+
+#: utils/cpupower.c:85
+#, c-format
+msgid "and read up the subcommand's manpage whether it is supported.\n"
+msgstr ""
+
+#: utils/cpupower.c:86
+#, c-format
+msgid ""
+"\n"
+"Use cpupower help subcommand for getting help for above subcommands.\n"
+msgstr ""
+
+#: utils/cpupower.c:91
+#, c-format
+msgid "Report errors and bugs to %s, please.\n"
+msgstr "Reporte erros e bugs para %s, por favor.\n"
+
+#: utils/cpupower.c:114
+#, c-format
+msgid "Error parsing cpu list\n"
+msgstr ""
+
+#: utils/cpupower.c:172
+#, c-format
+msgid "Subcommand %s needs root privileges\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:31
+#, c-format
+msgid "Couldn't count the number of CPUs (%s: %s), assuming 1\n"
+msgstr "Não foi possível contar o número de CPUs (%s: %s), assumindo 1\n"
+
+#: utils/cpufreq-info.c:63
+#, c-format
+msgid ""
+" minimum CPU frequency - maximum CPU frequency - governor\n"
+msgstr ""
+" frequência mínina do CPU - frequência máxima do CPU - "
+"regulador\n"
+
+#: utils/cpufreq-info.c:151
+#, c-format
+msgid "Error while evaluating Boost Capabilities on CPU %d -- are you root?\n"
+msgstr ""
+
+#. P state changes via MSR are identified via cpuid 80000007
+#. on Intel and AMD, but we assume boost capable machines can do that
+#. if (cpuid_eax(0x80000000) >= 0x80000007
+#. && (cpuid_edx(0x80000007) & (1 << 7)))
+#.
+#: utils/cpufreq-info.c:161
+#, c-format
+msgid " boost state support: \n"
+msgstr ""
+
+#: utils/cpufreq-info.c:163
+#, c-format
+msgid " Supported: %s\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:163 utils/cpufreq-info.c:164
+msgid "yes"
+msgstr ""
+
+#: utils/cpufreq-info.c:163 utils/cpufreq-info.c:164
+msgid "no"
+msgstr ""
+
+#: utils/cpufreq-info.c:164
+#, fuzzy, c-format
+msgid " Active: %s\n"
+msgstr " driver: %s\n"
+
+#: utils/cpufreq-info.c:177
+#, c-format
+msgid " Boost States: %d\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:178
+#, c-format
+msgid " Total States: %d\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:181
+#, c-format
+msgid " Pstate-Pb%d: %luMHz (boost state)\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:184
+#, c-format
+msgid " Pstate-P%d: %luMHz\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:211
+#, c-format
+msgid " no or unknown cpufreq driver is active on this CPU\n"
+msgstr " nenhum ou driver do cpufreq deconhecido está ativo nesse CPU\n"
+
+#: utils/cpufreq-info.c:213
+#, c-format
+msgid " driver: %s\n"
+msgstr " driver: %s\n"
+
+#: utils/cpufreq-info.c:219
+#, c-format
+msgid " CPUs which run at the same hardware frequency: "
+msgstr " CPUs que rodam na mesma frequência de hardware: "
+
+#: utils/cpufreq-info.c:230
+#, c-format
+msgid " CPUs which need to have their frequency coordinated by software: "
+msgstr " CPUs que precisam ter suas frequências coordenadas por software: "
+
+#: utils/cpufreq-info.c:241
+#, c-format
+msgid " maximum transition latency: "
+msgstr " maior latência de transição: "
+
+#: utils/cpufreq-info.c:247
+#, c-format
+msgid " hardware limits: "
+msgstr " limites do hardware: "
+
+#: utils/cpufreq-info.c:256
+#, c-format
+msgid " available frequency steps: "
+msgstr " níveis de frequência disponíveis: "
+
+#: utils/cpufreq-info.c:269
+#, c-format
+msgid " available cpufreq governors: "
+msgstr " reguladores do cpufreq disponíveis: "
+
+#: utils/cpufreq-info.c:280
+#, c-format
+msgid " current policy: frequency should be within "
+msgstr " política de frequência atual deve estar entre "
+
+#: utils/cpufreq-info.c:282
+#, c-format
+msgid " and "
+msgstr " e "
+
+#: utils/cpufreq-info.c:286
+#, c-format
+msgid ""
+"The governor \"%s\" may decide which speed to use\n"
+" within this range.\n"
+msgstr ""
+"O regulador \"%s\" deve decidir qual velocidade usar\n"
+" dentro desse limite.\n"
+
+#: utils/cpufreq-info.c:293
+#, c-format
+msgid " current CPU frequency is "
+msgstr " frequência atual do CPU é "
+
+#: utils/cpufreq-info.c:296
+#, c-format
+msgid " (asserted by call to hardware)"
+msgstr " (declarado por chamada ao hardware)"
+
+#: utils/cpufreq-info.c:304
+#, c-format
+msgid " cpufreq stats: "
+msgstr " status do cpufreq: "
+
+#: utils/cpufreq-info.c:472
+#, fuzzy, c-format
+msgid "Usage: cpupower freqinfo [options]\n"
+msgstr "Uso: cpufreq-info [opções]\n"
+
+#: utils/cpufreq-info.c:473 utils/cpufreq-set.c:26 utils/cpupower-set.c:23
+#: utils/cpupower-info.c:22 utils/cpuidle-info.c:148
+#, c-format
+msgid "Options:\n"
+msgstr "Opções:\n"
+
+#: utils/cpufreq-info.c:474
+#, fuzzy, c-format
+msgid " -e, --debug Prints out debug information [default]\n"
+msgstr " -e, --debug Mostra informação de debug\n"
+
+#: utils/cpufreq-info.c:475
+#, c-format
+msgid ""
+" -f, --freq Get frequency the CPU currently runs at, according\n"
+" to the cpufreq core *\n"
+msgstr ""
+" -f, --freq Obtem a frequência na qual o CPU roda no momento, de "
+"acordo\n"
+" com o núcleo do cpufreq *\n"
+
+#: utils/cpufreq-info.c:477
+#, c-format
+msgid ""
+" -w, --hwfreq Get frequency the CPU currently runs at, by reading\n"
+" it from hardware (only available to root) *\n"
+msgstr ""
+" -w, --hwfreq Obtem a frequência na qual o CPU está operando no "
+"momento,\n"
+" através de leitura no hardware (disponível somente "
+"para root) *\n"
+
+#: utils/cpufreq-info.c:479
+#, c-format
+msgid ""
+" -l, --hwlimits Determine the minimum and maximum CPU frequency "
+"allowed *\n"
+msgstr ""
+" -l, --hwlimits Determina a frequência mínima e máxima do CPU "
+"permitida *\n"
+
+#: utils/cpufreq-info.c:480
+#, c-format
+msgid " -d, --driver Determines the used cpufreq kernel driver *\n"
+msgstr ""
+" -d, --driver Determina o driver do kernel do cpufreq usado *\n"
+
+#: utils/cpufreq-info.c:481
+#, c-format
+msgid " -p, --policy Gets the currently used cpufreq policy *\n"
+msgstr ""
+"--p, --policy Obtem a política do cpufreq em uso no momento *\n"
+
+#: utils/cpufreq-info.c:482
+#, c-format
+msgid " -g, --governors Determines available cpufreq governors *\n"
+msgstr ""
+" -g, --governors Determina reguladores do cpufreq disponíveis *\n"
+
+#: utils/cpufreq-info.c:483
+#, c-format
+msgid ""
+" -r, --related-cpus Determines which CPUs run at the same hardware "
+"frequency *\n"
+msgstr ""
+" -r, --related-cpus Determina quais CPUs rodam na mesma frequência de "
+"hardware *\n"
+
+#: utils/cpufreq-info.c:484
+#, c-format
+msgid ""
+" -a, --affected-cpus Determines which CPUs need to have their frequency\n"
+" coordinated by software *\n"
+msgstr ""
+" -a, --affected-cpus Determina quais CPUs precisam ter suas frequências\n"
+" coordenadas por software *\n"
+
+#: utils/cpufreq-info.c:486
+#, c-format
+msgid " -s, --stats Shows cpufreq statistics if available\n"
+msgstr " -s, --stats Mostra estatísticas do cpufreq se disponíveis\n"
+
+#: utils/cpufreq-info.c:487
+#, c-format
+msgid ""
+" -y, --latency Determines the maximum latency on CPU frequency "
+"changes *\n"
+msgstr ""
+" -y, --latency Determina a latência máxima nas trocas de frequência "
+"do CPU *\n"
+
+#: utils/cpufreq-info.c:488
+#, c-format
+msgid " -b, --boost Checks for turbo or boost modes *\n"
+msgstr ""
+
+#: utils/cpufreq-info.c:489
+#, c-format
+msgid ""
+" -o, --proc Prints out information like provided by the /proc/"
+"cpufreq\n"
+" interface in 2.4. and early 2.6. kernels\n"
+msgstr ""
+" -o, --proc Mostra informação do tipo provida pela interface /"
+"proc/cpufreq\n"
+" em kernels 2.4. e mais recentes 2.6\n"
+
+#: utils/cpufreq-info.c:491
+#, c-format
+msgid ""
+" -m, --human human-readable output for the -f, -w, -s and -y "
+"parameters\n"
+msgstr ""
+" -m, --human saída legível para humanos para os parâmetros -f, -w, "
+"-s e -y\n"
+
+#: utils/cpufreq-info.c:492 utils/cpuidle-info.c:152
+#, c-format
+msgid " -h, --help Prints out this screen\n"
+msgstr " -h, --help Imprime essa tela\n"
+
+#: utils/cpufreq-info.c:495
+#, c-format
+msgid ""
+"If no argument or only the -c, --cpu parameter is given, debug output about\n"
+"cpufreq is printed which is useful e.g. for reporting bugs.\n"
+msgstr ""
+"Se nenhum argumento ou somente o parâmetro -c, --cpu é dado, informação de "
+"debug sobre\n"
+"o cpufreq é mostrada, o que é útil por exemplo para reportar bugs.\n"
+
+#: utils/cpufreq-info.c:497
+#, c-format
+msgid ""
+"For the arguments marked with *, omitting the -c or --cpu argument is\n"
+"equivalent to setting it to zero\n"
+msgstr ""
+"Para os argumentos marcados com *, omitir o argumento -c ou --cpu é\n"
+"equivalente a setá-lo como zero\n"
+
+#: utils/cpufreq-info.c:580
+#, c-format
+msgid ""
+"The argument passed to this tool can't be combined with passing a --cpu "
+"argument\n"
+msgstr ""
+"O argumento usado pra essa ferramenta não pode ser combinado com um "
+"argumento --cpu\n"
+
+#: utils/cpufreq-info.c:596
+#, c-format
+msgid ""
+"You can't specify more than one --cpu parameter and/or\n"
+"more than one output-specific argument\n"
+msgstr ""
+"Você não pode especificar mais do que um parâmetro --cpu e/ou\n"
+"mais do que um argumento de saída específico\n"
+
+#: utils/cpufreq-info.c:600 utils/cpufreq-set.c:82 utils/cpupower-set.c:42
+#: utils/cpupower-info.c:42 utils/cpuidle-info.c:213
+#, c-format
+msgid "invalid or unknown argument\n"
+msgstr "argumento inválido ou desconhecido\n"
+
+#: utils/cpufreq-info.c:617
+#, c-format
+msgid "couldn't analyze CPU %d as it doesn't seem to be present\n"
+msgstr ""
+"não foi possível analisar o CPU % já que o mesmo parece não estar presente\n"
+
+#: utils/cpufreq-info.c:620 utils/cpupower-info.c:142
+#, c-format
+msgid "analyzing CPU %d:\n"
+msgstr "analisando o CPU %d:\n"
+
+#: utils/cpufreq-set.c:25
+#, fuzzy, c-format
+msgid "Usage: cpupower frequency-set [options]\n"
+msgstr "Uso: cpufreq-set [opções]\n"
+
+#: utils/cpufreq-set.c:27
+#, c-format
+msgid ""
+" -d FREQ, --min FREQ new minimum CPU frequency the governor may "
+"select\n"
+msgstr ""
+" -d FREQ, --min FREQ nova frequência mínima do CPU que o regulador "
+"deve selecionar\n"
+
+#: utils/cpufreq-set.c:28
+#, c-format
+msgid ""
+" -u FREQ, --max FREQ new maximum CPU frequency the governor may "
+"select\n"
+msgstr ""
+" -u FREQ, --max FREQ nova frequência máxima do CPU que o regulador "
+"deve escolher\n"
+
+#: utils/cpufreq-set.c:29
+#, c-format
+msgid " -g GOV, --governor GOV new cpufreq governor\n"
+msgstr " -g GOV, --governor GOV novo regulador do cpufreq\n"
+
+#: utils/cpufreq-set.c:30
+#, c-format
+msgid ""
+" -f FREQ, --freq FREQ specific frequency to be set. Requires userspace\n"
+" governor to be available and loaded\n"
+msgstr ""
+" -f FREQ, --freq FREQ frequência específica para ser setada. Necessita "
+"que o regulador em\n"
+" nível de usuário esteja disponível e carregado\n"
+
+#: utils/cpufreq-set.c:32
+#, c-format
+msgid " -r, --related Switches all hardware-related CPUs\n"
+msgstr ""
+" -r, --related Modifica todos os CPUs relacionados ao hardware\n"
+
+#: utils/cpufreq-set.c:33 utils/cpupower-set.c:28 utils/cpupower-info.c:27
+#, c-format
+msgid " -h, --help Prints out this screen\n"
+msgstr " -h, --help Mostra essa tela\n"
+
+#: utils/cpufreq-set.c:35
+#, fuzzy, c-format
+msgid ""
+"Notes:\n"
+"1. Omitting the -c or --cpu argument is equivalent to setting it to \"all\"\n"
+msgstr ""
+"Para os argumentos marcados com *, omitir o argumento -c ou --cpu é\n"
+"equivalente a setá-lo como zero\n"
+
+#: utils/cpufreq-set.c:37
+#, fuzzy, c-format
+msgid ""
+"2. The -f FREQ, --freq FREQ parameter cannot be combined with any other "
+"parameter\n"
+" except the -c CPU, --cpu CPU parameter\n"
+"3. FREQuencies can be passed in Hz, kHz (default), MHz, GHz, or THz\n"
+" by postfixing the value with the wanted unit name, without any space\n"
+" (FREQuency in kHz =^ Hz * 0.001 =^ MHz * 1000 =^ GHz * 1000000).\n"
+msgstr ""
+"Notas:\n"
+"1. Omitir o argumento -c or --cpu é equivalente a setá-lo como zero\n"
+"2. O parâmetro -f FREQ, --freq FREQ não pode ser combinado com qualquer "
+"outro parâmetro\n"
+" exceto com o parâmetro -c CPU, --cpu CPU\n"
+"3. FREQuências podem ser usadas em Hz, kHz (padrão), MHz, GHz, o THz\n"
+" colocando o nome desejado da unidade após o valor, sem qualquer espaço\n"
+" (FREQuência em kHz =^ Hz * 0.001 =^ MHz * 1000 =^ GHz * 1000000).\n"
+
+#: utils/cpufreq-set.c:57
+#, c-format
+msgid ""
+"Error setting new values. Common errors:\n"
+"- Do you have proper administration rights? (super-user?)\n"
+"- Is the governor you requested available and modprobed?\n"
+"- Trying to set an invalid policy?\n"
+"- Trying to set a specific frequency, but userspace governor is not "
+"available,\n"
+" for example because of hardware which cannot be set to a specific "
+"frequency\n"
+" or because the userspace governor isn't loaded?\n"
+msgstr ""
+"Erro ao setar novos valores. Erros comuns:\n"
+"- Você tem direitos administrativos necessários? (super-usuário?)\n"
+"- O regulador que você requesitou está disponível e foi \"modprobed\"?\n"
+"- Tentando setar uma política inválida?\n"
+"- Tentando setar uma frequência específica, mas o regulador em nível de "
+"usuário não está disponível,\n"
+" por exemplo devido ao hardware que não pode ser setado pra uma frequência "
+"específica\n"
+" ou porque o regulador em nível de usuário não foi carregado?\n"
+
+#: utils/cpufreq-set.c:170
+#, c-format
+msgid "wrong, unknown or unhandled CPU?\n"
+msgstr "CPU errado, desconhecido ou inesperado?\n"
+
+#: utils/cpufreq-set.c:302
+#, c-format
+msgid ""
+"the -f/--freq parameter cannot be combined with -d/--min, -u/--max or\n"
+"-g/--governor parameters\n"
+msgstr ""
+"o parâmetro -f/--freq não pode ser combinado com os parâmetros -d/--min, -"
+"u/--max ou\n"
+"-g/--governor\n"
+
+#: utils/cpufreq-set.c:308
+#, c-format
+msgid ""
+"At least one parameter out of -f/--freq, -d/--min, -u/--max, and\n"
+"-g/--governor must be passed\n"
+msgstr ""
+"Pelo menos um parâmetro entre -f/--freq, -d/--min, -u/--max, e\n"
+"-g/--governor deve ser usado\n"
+
+#: utils/cpufreq-set.c:347
+#, c-format
+msgid "Setting cpu: %d\n"
+msgstr ""
+
+#: utils/cpupower-set.c:22
+#, c-format
+msgid "Usage: cpupower set [ -b val ] [ -m val ] [ -s val ]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:24
+#, c-format
+msgid ""
+" -b, --perf-bias [VAL] Sets CPU's power vs performance policy on some\n"
+" Intel models [0-15], see manpage for details\n"
+msgstr ""
+
+#: utils/cpupower-set.c:26
+#, c-format
+msgid ""
+" -m, --sched-mc [VAL] Sets the kernel's multi core scheduler policy.\n"
+msgstr ""
+
+#: utils/cpupower-set.c:27
+#, c-format
+msgid ""
+" -s, --sched-smt [VAL] Sets the kernel's thread sibling scheduler "
+"policy.\n"
+msgstr ""
+
+#: utils/cpupower-set.c:80
+#, c-format
+msgid "--perf-bias param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:91
+#, c-format
+msgid "--sched-mc param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:102
+#, c-format
+msgid "--sched-smt param out of range [0-%d]\n"
+msgstr ""
+
+#: utils/cpupower-set.c:121
+#, c-format
+msgid "Error setting sched-mc %s\n"
+msgstr ""
+
+#: utils/cpupower-set.c:127
+#, c-format
+msgid "Error setting sched-smt %s\n"
+msgstr ""
+
+#: utils/cpupower-set.c:146
+#, c-format
+msgid "Error setting perf-bias value on CPU %d\n"
+msgstr ""
+
+#: utils/cpupower-info.c:21
+#, c-format
+msgid "Usage: cpupower info [ -b ] [ -m ] [ -s ]\n"
+msgstr ""
+
+#: utils/cpupower-info.c:23
+#, c-format
+msgid ""
+" -b, --perf-bias Gets CPU's power vs performance policy on some\n"
+" Intel models [0-15], see manpage for details\n"
+msgstr ""
+
+#: utils/cpupower-info.c:25
+#, fuzzy, c-format
+msgid " -m, --sched-mc Gets the kernel's multi core scheduler policy.\n"
+msgstr ""
+"--p, --policy Obtem a política do cpufreq em uso no momento *\n"
+
+#: utils/cpupower-info.c:26
+#, c-format
+msgid ""
+" -s, --sched-smt Gets the kernel's thread sibling scheduler policy.\n"
+msgstr ""
+
+#: utils/cpupower-info.c:28
+#, c-format
+msgid ""
+"\n"
+"Passing no option will show all info, by default only on core 0\n"
+msgstr ""
+
+#: utils/cpupower-info.c:102
+#, c-format
+msgid "System's multi core scheduler setting: "
+msgstr ""
+
+#. if sysfs file is missing it's: errno == ENOENT
+#: utils/cpupower-info.c:105 utils/cpupower-info.c:114
+#, c-format
+msgid "not supported\n"
+msgstr ""
+
+#: utils/cpupower-info.c:111
+#, c-format
+msgid "System's thread sibling scheduler setting: "
+msgstr ""
+
+#: utils/cpupower-info.c:126
+#, c-format
+msgid "Intel's performance bias setting needs root privileges\n"
+msgstr ""
+
+#: utils/cpupower-info.c:128
+#, c-format
+msgid "System does not support Intel's performance bias setting\n"
+msgstr ""
+
+#: utils/cpupower-info.c:147
+#, c-format
+msgid "Could not read perf-bias value\n"
+msgstr ""
+
+#: utils/cpupower-info.c:150
+#, c-format
+msgid "perf-bias: %d\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:28
+#, fuzzy, c-format
+msgid "Analyzing CPU %d:\n"
+msgstr "analisando o CPU %d:\n"
+
+#: utils/cpuidle-info.c:32
+#, c-format
+msgid "CPU %u: No idle states\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:36
+#, c-format
+msgid "CPU %u: Can't read idle state info\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:41
+#, c-format
+msgid "Could not determine max idle state %u\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:46
+#, c-format
+msgid "Number of idle states: %d\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:48
+#, fuzzy, c-format
+msgid "Available idle states:"
+msgstr " níveis de frequência disponíveis: "
+
+#: utils/cpuidle-info.c:71
+#, c-format
+msgid "Flags/Description: %s\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:74
+#, c-format
+msgid "Latency: %lu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:76
+#, c-format
+msgid "Usage: %lu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:78
+#, c-format
+msgid "Duration: %llu\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:90
+#, c-format
+msgid "Could not determine cpuidle driver\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:94
+#, fuzzy, c-format
+msgid "CPUidle driver: %s\n"
+msgstr " driver: %s\n"
+
+#: utils/cpuidle-info.c:99
+#, c-format
+msgid "Could not determine cpuidle governor\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:103
+#, c-format
+msgid "CPUidle governor: %s\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:122
+#, c-format
+msgid "CPU %u: Can't read C-state info\n"
+msgstr ""
+
+#. printf("Cstates: %d\n", cstates);
+#: utils/cpuidle-info.c:127
+#, c-format
+msgid "active state: C0\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:128
+#, c-format
+msgid "max_cstate: C%u\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:129
+#, fuzzy, c-format
+msgid "maximum allowed latency: %lu usec\n"
+msgstr " maior latência de transição: "
+
+#: utils/cpuidle-info.c:130
+#, c-format
+msgid "states:\t\n"
+msgstr ""
+
+#: utils/cpuidle-info.c:132
+#, c-format
+msgid " C%d: type[C%d] "
+msgstr ""
+
+#: utils/cpuidle-info.c:134
+#, c-format
+msgid "promotion[--] demotion[--] "
+msgstr ""
+
+#: utils/cpuidle-info.c:135
+#, c-format
+msgid "latency[%03lu] "
+msgstr ""
+
+#: utils/cpuidle-info.c:137
+#, c-format
+msgid "usage[%08lu] "
+msgstr ""
+
+#: utils/cpuidle-info.c:139
+#, c-format
+msgid "duration[%020Lu] \n"
+msgstr ""
+
+#: utils/cpuidle-info.c:147
+#, fuzzy, c-format
+msgid "Usage: cpupower idleinfo [options]\n"
+msgstr "Uso: cpufreq-info [opções]\n"
+
+#: utils/cpuidle-info.c:149
+#, fuzzy, c-format
+msgid " -s, --silent Only show general C-state information\n"
+msgstr " -e, --debug Mostra informação de debug\n"
+
+#: utils/cpuidle-info.c:150
+#, fuzzy, c-format
+msgid ""
+" -o, --proc Prints out information like provided by the /proc/"
+"acpi/processor/*/power\n"
+" interface in older kernels\n"
+msgstr ""
+" -o, --proc Mostra informação do tipo provida pela interface /"
+"proc/cpufreq\n"
+" em kernels 2.4. e mais recentes 2.6\n"
+
+#: utils/cpuidle-info.c:209
+#, fuzzy, c-format
+msgid "You can't specify more than one output-specific argument\n"
+msgstr ""
+"Você não pode especificar mais do que um parâmetro --cpu e/ou\n"
+"mais do que um argumento de saída específico\n"
+
+#~ msgid ""
+#~ " -c CPU, --cpu CPU CPU number which information shall be determined "
+#~ "about\n"
+#~ msgstr ""
+#~ " -c CPU, --cpu CPU número do CPU sobre o qual as inforções devem ser "
+#~ "determinadas\n"
+
+#~ msgid ""
+#~ " -c CPU, --cpu CPU number of CPU where cpufreq settings shall be "
+#~ "modified\n"
+#~ msgstr ""
+#~ " -c CPU, --cpu CPU número do CPU onde as configurações do cpufreq "
+#~ "vão ser modificadas\n"
diff --git a/tools/power/cpupower/utils/builtin.h b/tools/power/cpupower/utils/builtin.h
new file mode 100644
index 00000000000..2284c8ea4e2
--- /dev/null
+++ b/tools/power/cpupower/utils/builtin.h
@@ -0,0 +1,12 @@
+#ifndef BUILTIN_H
+#define BUILTIN_H
+
+extern int cmd_set(int argc, const char **argv);
+extern int cmd_info(int argc, const char **argv);
+extern int cmd_freq_set(int argc, const char **argv);
+extern int cmd_freq_info(int argc, const char **argv);
+extern int cmd_idle_set(int argc, const char **argv);
+extern int cmd_idle_info(int argc, const char **argv);
+extern int cmd_monitor(int argc, const char **argv);
+
+#endif
diff --git a/tools/power/cpupower/utils/cpufreq-info.c b/tools/power/cpupower/utils/cpufreq-info.c
new file mode 100644
index 00000000000..b4b90a97662
--- /dev/null
+++ b/tools/power/cpupower/utils/cpufreq-info.c
@@ -0,0 +1,698 @@
+/*
+ * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de>
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ */
+
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <getopt.h>
+
+#include "cpufreq.h"
+#include "helpers/helpers.h"
+#include "helpers/bitmask.h"
+
+#define LINE_LEN 10
+
+static unsigned int count_cpus(void)
+{
+ FILE *fp;
+ char value[LINE_LEN];
+ unsigned int ret = 0;
+ unsigned int cpunr = 0;
+
+ fp = fopen("/proc/stat", "r");
+ if (!fp) {
+ printf(_("Couldn't count the number of CPUs (%s: %s), assuming 1\n"), "/proc/stat", strerror(errno));
+ return 1;
+ }
+
+ while (!feof(fp)) {
+ if (!fgets(value, LINE_LEN, fp))
+ continue;
+ value[LINE_LEN - 1] = '\0';
+ if (strlen(value) < (LINE_LEN - 2))
+ continue;
+ if (strstr(value, "cpu "))
+ continue;
+ if (sscanf(value, "cpu%d ", &cpunr) != 1)
+ continue;
+ if (cpunr > ret)
+ ret = cpunr;
+ }
+ fclose(fp);
+
+ /* cpu count starts from 0, on error return 1 (UP) */
+ return ret + 1;
+}
+
+
+static void proc_cpufreq_output(void)
+{
+ unsigned int cpu, nr_cpus;
+ struct cpufreq_policy *policy;
+ unsigned int min_pctg = 0;
+ unsigned int max_pctg = 0;
+ unsigned long min, max;
+
+ printf(_(" minimum CPU frequency - maximum CPU frequency - governor\n"));
+
+ nr_cpus = count_cpus();
+ for (cpu = 0; cpu < nr_cpus; cpu++) {
+ policy = cpufreq_get_policy(cpu);
+ if (!policy)
+ continue;
+
+ if (cpufreq_get_hardware_limits(cpu, &min, &max)) {
+ max = 0;
+ } else {
+ min_pctg = (policy->min * 100) / max;
+ max_pctg = (policy->max * 100) / max;
+ }
+ printf("CPU%3d %9lu kHz (%3d %%) - %9lu kHz (%3d %%) - %s\n",
+ cpu , policy->min, max ? min_pctg : 0, policy->max,
+ max ? max_pctg : 0, policy->governor);
+
+ cpufreq_put_policy(policy);
+ }
+}
+
+static int no_rounding;
+static void print_speed(unsigned long speed)
+{
+ unsigned long tmp;
+
+ if (no_rounding) {
+ if (speed > 1000000)
+ printf("%u.%06u GHz", ((unsigned int) speed/1000000),
+ ((unsigned int) speed%1000000));
+ else if (speed > 100000)
+ printf("%u MHz", (unsigned int) speed);
+ else if (speed > 1000)
+ printf("%u.%03u MHz", ((unsigned int) speed/1000),
+ (unsigned int) (speed%1000));
+ else
+ printf("%lu kHz", speed);
+ } else {
+ if (speed > 1000000) {
+ tmp = speed%10000;
+ if (tmp >= 5000)
+ speed += 10000;
+ printf("%u.%02u GHz", ((unsigned int) speed/1000000),
+ ((unsigned int) (speed%1000000)/10000));
+ } else if (speed > 100000) {
+ tmp = speed%1000;
+ if (tmp >= 500)
+ speed += 1000;
+ printf("%u MHz", ((unsigned int) speed/1000));
+ } else if (speed > 1000) {
+ tmp = speed%100;
+ if (tmp >= 50)
+ speed += 100;
+ printf("%u.%01u MHz", ((unsigned int) speed/1000),
+ ((unsigned int) (speed%1000)/100));
+ }
+ }
+
+ return;
+}
+
+static void print_duration(unsigned long duration)
+{
+ unsigned long tmp;
+
+ if (no_rounding) {
+ if (duration > 1000000)
+ printf("%u.%06u ms", ((unsigned int) duration/1000000),
+ ((unsigned int) duration%1000000));
+ else if (duration > 100000)
+ printf("%u us", ((unsigned int) duration/1000));
+ else if (duration > 1000)
+ printf("%u.%03u us", ((unsigned int) duration/1000),
+ ((unsigned int) duration%1000));
+ else
+ printf("%lu ns", duration);
+ } else {
+ if (duration > 1000000) {
+ tmp = duration%10000;
+ if (tmp >= 5000)
+ duration += 10000;
+ printf("%u.%02u ms", ((unsigned int) duration/1000000),
+ ((unsigned int) (duration%1000000)/10000));
+ } else if (duration > 100000) {
+ tmp = duration%1000;
+ if (tmp >= 500)
+ duration += 1000;
+ printf("%u us", ((unsigned int) duration / 1000));
+ } else if (duration > 1000) {
+ tmp = duration%100;
+ if (tmp >= 50)
+ duration += 100;
+ printf("%u.%01u us", ((unsigned int) duration/1000),
+ ((unsigned int) (duration%1000)/100));
+ } else
+ printf("%lu ns", duration);
+ }
+ return;
+}
+
+/* --boost / -b */
+
+static int get_boost_mode(unsigned int cpu)
+{
+ int support, active, b_states = 0, ret, pstate_no, i;
+ /* ToDo: Make this more global */
+ unsigned long pstates[MAX_HW_PSTATES] = {0,};
+
+ if (cpupower_cpu_info.vendor != X86_VENDOR_AMD &&
+ cpupower_cpu_info.vendor != X86_VENDOR_INTEL)
+ return 0;
+
+ ret = cpufreq_has_boost_support(cpu, &support, &active, &b_states);
+ if (ret) {
+ printf(_("Error while evaluating Boost Capabilities"
+ " on CPU %d -- are you root?\n"), cpu);
+ return ret;
+ }
+ /* P state changes via MSR are identified via cpuid 80000007
+ on Intel and AMD, but we assume boost capable machines can do that
+ if (cpuid_eax(0x80000000) >= 0x80000007
+ && (cpuid_edx(0x80000007) & (1 << 7)))
+ */
+
+ printf(_(" boost state support:\n"));
+
+ printf(_(" Supported: %s\n"), support ? _("yes") : _("no"));
+ printf(_(" Active: %s\n"), active ? _("yes") : _("no"));
+
+ if (cpupower_cpu_info.vendor == X86_VENDOR_AMD &&
+ cpupower_cpu_info.family >= 0x10) {
+ ret = decode_pstates(cpu, cpupower_cpu_info.family, b_states,
+ pstates, &pstate_no);
+ if (ret)
+ return ret;
+
+ printf(_(" Boost States: %d\n"), b_states);
+ printf(_(" Total States: %d\n"), pstate_no);
+ for (i = 0; i < pstate_no; i++) {
+ if (i < b_states)
+ printf(_(" Pstate-Pb%d: %luMHz (boost state)"
+ "\n"), i, pstates[i]);
+ else
+ printf(_(" Pstate-P%d: %luMHz\n"),
+ i - b_states, pstates[i]);
+ }
+ } else if (cpupower_cpu_info.caps & CPUPOWER_CAP_HAS_TURBO_RATIO) {
+ double bclk;
+ unsigned long long intel_turbo_ratio = 0;
+ unsigned int ratio;
+
+ /* Any way to autodetect this ? */
+ if (cpupower_cpu_info.caps & CPUPOWER_CAP_IS_SNB)
+ bclk = 100.00;
+ else
+ bclk = 133.33;
+ intel_turbo_ratio = msr_intel_get_turbo_ratio(cpu);
+ dprint (" Ratio: 0x%llx - bclk: %f\n",
+ intel_turbo_ratio, bclk);
+
+ ratio = (intel_turbo_ratio >> 24) & 0xFF;
+ if (ratio)
+ printf(_(" %.0f MHz max turbo 4 active cores\n"),
+ ratio * bclk);
+
+ ratio = (intel_turbo_ratio >> 16) & 0xFF;
+ if (ratio)
+ printf(_(" %.0f MHz max turbo 3 active cores\n"),
+ ratio * bclk);
+
+ ratio = (intel_turbo_ratio >> 8) & 0xFF;
+ if (ratio)
+ printf(_(" %.0f MHz max turbo 2 active cores\n"),
+ ratio * bclk);
+
+ ratio = (intel_turbo_ratio >> 0) & 0xFF;
+ if (ratio)
+ printf(_(" %.0f MHz max turbo 1 active cores\n"),
+ ratio * bclk);
+ }
+ return 0;
+}
+
+static void debug_output_one(unsigned int cpu)
+{
+ char *driver;
+ struct cpufreq_affected_cpus *cpus;
+ struct cpufreq_available_frequencies *freqs;
+ unsigned long min, max, freq_kernel, freq_hardware;
+ unsigned long total_trans, latency;
+ unsigned long long total_time;
+ struct cpufreq_policy *policy;
+ struct cpufreq_available_governors *governors;
+ struct cpufreq_stats *stats;
+
+ if (cpufreq_cpu_exists(cpu))
+ return;
+
+ freq_kernel = cpufreq_get_freq_kernel(cpu);
+ freq_hardware = cpufreq_get_freq_hardware(cpu);
+
+ driver = cpufreq_get_driver(cpu);
+ if (!driver) {
+ printf(_(" no or unknown cpufreq driver is active on this CPU\n"));
+ } else {
+ printf(_(" driver: %s\n"), driver);
+ cpufreq_put_driver(driver);
+ }
+
+ cpus = cpufreq_get_related_cpus(cpu);
+ if (cpus) {
+ printf(_(" CPUs which run at the same hardware frequency: "));
+ while (cpus->next) {
+ printf("%d ", cpus->cpu);
+ cpus = cpus->next;
+ }
+ printf("%d\n", cpus->cpu);
+ cpufreq_put_related_cpus(cpus);
+ }
+
+ cpus = cpufreq_get_affected_cpus(cpu);
+ if (cpus) {
+ printf(_(" CPUs which need to have their frequency coordinated by software: "));
+ while (cpus->next) {
+ printf("%d ", cpus->cpu);
+ cpus = cpus->next;
+ }
+ printf("%d\n", cpus->cpu);
+ cpufreq_put_affected_cpus(cpus);
+ }
+
+ latency = cpufreq_get_transition_latency(cpu);
+ if (latency) {
+ printf(_(" maximum transition latency: "));
+ print_duration(latency);
+ printf(".\n");
+ }
+
+ if (!(cpufreq_get_hardware_limits(cpu, &min, &max))) {
+ printf(_(" hardware limits: "));
+ print_speed(min);
+ printf(" - ");
+ print_speed(max);
+ printf("\n");
+ }
+
+ freqs = cpufreq_get_available_frequencies(cpu);
+ if (freqs) {
+ printf(_(" available frequency steps: "));
+ while (freqs->next) {
+ print_speed(freqs->frequency);
+ printf(", ");
+ freqs = freqs->next;
+ }
+ print_speed(freqs->frequency);
+ printf("\n");
+ cpufreq_put_available_frequencies(freqs);
+ }
+
+ governors = cpufreq_get_available_governors(cpu);
+ if (governors) {
+ printf(_(" available cpufreq governors: "));
+ while (governors->next) {
+ printf("%s, ", governors->governor);
+ governors = governors->next;
+ }
+ printf("%s\n", governors->governor);
+ cpufreq_put_available_governors(governors);
+ }
+
+ policy = cpufreq_get_policy(cpu);
+ if (policy) {
+ printf(_(" current policy: frequency should be within "));
+ print_speed(policy->min);
+ printf(_(" and "));
+ print_speed(policy->max);
+
+ printf(".\n ");
+ printf(_("The governor \"%s\" may"
+ " decide which speed to use\n within this range.\n"),
+ policy->governor);
+ cpufreq_put_policy(policy);
+ }
+
+ if (freq_kernel || freq_hardware) {
+ printf(_(" current CPU frequency is "));
+ if (freq_hardware) {
+ print_speed(freq_hardware);
+ printf(_(" (asserted by call to hardware)"));
+ } else
+ print_speed(freq_kernel);
+ printf(".\n");
+ }
+ stats = cpufreq_get_stats(cpu, &total_time);
+ if (stats) {
+ printf(_(" cpufreq stats: "));
+ while (stats) {
+ print_speed(stats->frequency);
+ printf(":%.2f%%", (100.0 * stats->time_in_state) / total_time);
+ stats = stats->next;
+ if (stats)
+ printf(", ");
+ }
+ cpufreq_put_stats(stats);
+ total_trans = cpufreq_get_transitions(cpu);
+ if (total_trans)
+ printf(" (%lu)\n", total_trans);
+ else
+ printf("\n");
+ }
+ get_boost_mode(cpu);
+
+}
+
+/* --freq / -f */
+
+static int get_freq_kernel(unsigned int cpu, unsigned int human)
+{
+ unsigned long freq = cpufreq_get_freq_kernel(cpu);
+ if (!freq)
+ return -EINVAL;
+ if (human) {
+ print_speed(freq);
+ printf("\n");
+ } else
+ printf("%lu\n", freq);
+ return 0;
+}
+
+
+/* --hwfreq / -w */
+
+static int get_freq_hardware(unsigned int cpu, unsigned int human)
+{
+ unsigned long freq = cpufreq_get_freq_hardware(cpu);
+ if (!freq)
+ return -EINVAL;
+ if (human) {
+ print_speed(freq);
+ printf("\n");
+ } else
+ printf("%lu\n", freq);
+ return 0;
+}
+
+/* --hwlimits / -l */
+
+static int get_hardware_limits(unsigned int cpu)
+{
+ unsigned long min, max;
+ if (cpufreq_get_hardware_limits(cpu, &min, &max))
+ return -EINVAL;
+ printf("%lu %lu\n", min, max);
+ return 0;
+}
+
+/* --driver / -d */
+
+static int get_driver(unsigned int cpu)
+{
+ char *driver = cpufreq_get_driver(cpu);
+ if (!driver)
+ return -EINVAL;
+ printf("%s\n", driver);
+ cpufreq_put_driver(driver);
+ return 0;
+}
+
+/* --policy / -p */
+
+static int get_policy(unsigned int cpu)
+{
+ struct cpufreq_policy *policy = cpufreq_get_policy(cpu);
+ if (!policy)
+ return -EINVAL;
+ printf("%lu %lu %s\n", policy->min, policy->max, policy->governor);
+ cpufreq_put_policy(policy);
+ return 0;
+}
+
+/* --governors / -g */
+
+static int get_available_governors(unsigned int cpu)
+{
+ struct cpufreq_available_governors *governors =
+ cpufreq_get_available_governors(cpu);
+ if (!governors)
+ return -EINVAL;
+
+ while (governors->next) {
+ printf("%s ", governors->governor);
+ governors = governors->next;
+ }
+ printf("%s\n", governors->governor);
+ cpufreq_put_available_governors(governors);
+ return 0;
+}
+
+
+/* --affected-cpus / -a */
+
+static int get_affected_cpus(unsigned int cpu)
+{
+ struct cpufreq_affected_cpus *cpus = cpufreq_get_affected_cpus(cpu);
+ if (!cpus)
+ return -EINVAL;
+
+ while (cpus->next) {
+ printf("%d ", cpus->cpu);
+ cpus = cpus->next;
+ }
+ printf("%d\n", cpus->cpu);
+ cpufreq_put_affected_cpus(cpus);
+ return 0;
+}
+
+/* --related-cpus / -r */
+
+static int get_related_cpus(unsigned int cpu)
+{
+ struct cpufreq_affected_cpus *cpus = cpufreq_get_related_cpus(cpu);
+ if (!cpus)
+ return -EINVAL;
+
+ while (cpus->next) {
+ printf("%d ", cpus->cpu);
+ cpus = cpus->next;
+ }
+ printf("%d\n", cpus->cpu);
+ cpufreq_put_related_cpus(cpus);
+ return 0;
+}
+
+/* --stats / -s */
+
+static int get_freq_stats(unsigned int cpu, unsigned int human)
+{
+ unsigned long total_trans = cpufreq_get_transitions(cpu);
+ unsigned long long total_time;
+ struct cpufreq_stats *stats = cpufreq_get_stats(cpu, &total_time);
+ while (stats) {
+ if (human) {
+ print_speed(stats->frequency);
+ printf(":%.2f%%",
+ (100.0 * stats->time_in_state) / total_time);
+ } else
+ printf("%lu:%llu",
+ stats->frequency, stats->time_in_state);
+ stats = stats->next;
+ if (stats)
+ printf(", ");
+ }
+ cpufreq_put_stats(stats);
+ if (total_trans)
+ printf(" (%lu)\n", total_trans);
+ return 0;
+}
+
+/* --latency / -y */
+
+static int get_latency(unsigned int cpu, unsigned int human)
+{
+ unsigned long latency = cpufreq_get_transition_latency(cpu);
+ if (!latency)
+ return -EINVAL;
+
+ if (human) {
+ print_duration(latency);
+ printf("\n");
+ } else
+ printf("%lu\n", latency);
+ return 0;
+}
+
+static struct option info_opts[] = {
+ { .name = "debug", .has_arg = no_argument, .flag = NULL, .val = 'e'},
+ { .name = "boost", .has_arg = no_argument, .flag = NULL, .val = 'b'},
+ { .name = "freq", .has_arg = no_argument, .flag = NULL, .val = 'f'},
+ { .name = "hwfreq", .has_arg = no_argument, .flag = NULL, .val = 'w'},
+ { .name = "hwlimits", .has_arg = no_argument, .flag = NULL, .val = 'l'},
+ { .name = "driver", .has_arg = no_argument, .flag = NULL, .val = 'd'},
+ { .name = "policy", .has_arg = no_argument, .flag = NULL, .val = 'p'},
+ { .name = "governors", .has_arg = no_argument, .flag = NULL, .val = 'g'},
+ { .name = "related-cpus", .has_arg = no_argument, .flag = NULL, .val = 'r'},
+ { .name = "affected-cpus",.has_arg = no_argument, .flag = NULL, .val = 'a'},
+ { .name = "stats", .has_arg = no_argument, .flag = NULL, .val = 's'},
+ { .name = "latency", .has_arg = no_argument, .flag = NULL, .val = 'y'},
+ { .name = "proc", .has_arg = no_argument, .flag = NULL, .val = 'o'},
+ { .name = "human", .has_arg = no_argument, .flag = NULL, .val = 'm'},
+ { .name = "no-rounding", .has_arg = no_argument, .flag = NULL, .val = 'n'},
+ { },
+};
+
+int cmd_freq_info(int argc, char **argv)
+{
+ extern char *optarg;
+ extern int optind, opterr, optopt;
+ int ret = 0, cont = 1;
+ unsigned int cpu = 0;
+ unsigned int human = 0;
+ int output_param = 0;
+
+ do {
+ ret = getopt_long(argc, argv, "oefwldpgrasmybn", info_opts,
+ NULL);
+ switch (ret) {
+ case '?':
+ output_param = '?';
+ cont = 0;
+ break;
+ case -1:
+ cont = 0;
+ break;
+ case 'b':
+ case 'o':
+ case 'a':
+ case 'r':
+ case 'g':
+ case 'p':
+ case 'd':
+ case 'l':
+ case 'w':
+ case 'f':
+ case 'e':
+ case 's':
+ case 'y':
+ if (output_param) {
+ output_param = -1;
+ cont = 0;
+ break;
+ }
+ output_param = ret;
+ break;
+ case 'm':
+ if (human) {
+ output_param = -1;
+ cont = 0;
+ break;
+ }
+ human = 1;
+ break;
+ case 'n':
+ no_rounding = 1;
+ break;
+ default:
+ fprintf(stderr, "invalid or unknown argument\n");
+ return EXIT_FAILURE;
+ }
+ } while (cont);
+
+ switch (output_param) {
+ case 'o':
+ if (!bitmask_isallclear(cpus_chosen)) {
+ printf(_("The argument passed to this tool can't be "
+ "combined with passing a --cpu argument\n"));
+ return -EINVAL;
+ }
+ break;
+ case 0:
+ output_param = 'e';
+ }
+
+ ret = 0;
+
+ /* Default is: show output of CPU 0 only */
+ if (bitmask_isallclear(cpus_chosen))
+ bitmask_setbit(cpus_chosen, 0);
+
+ switch (output_param) {
+ case -1:
+ printf(_("You can't specify more than one --cpu parameter and/or\n"
+ "more than one output-specific argument\n"));
+ return -EINVAL;
+ case '?':
+ printf(_("invalid or unknown argument\n"));
+ return -EINVAL;
+ case 'o':
+ proc_cpufreq_output();
+ return EXIT_SUCCESS;
+ }
+
+ for (cpu = bitmask_first(cpus_chosen);
+ cpu <= bitmask_last(cpus_chosen); cpu++) {
+
+ if (!bitmask_isbitset(cpus_chosen, cpu))
+ continue;
+ if (cpufreq_cpu_exists(cpu)) {
+ printf(_("couldn't analyze CPU %d as it doesn't seem to be present\n"), cpu);
+ continue;
+ }
+ printf(_("analyzing CPU %d:\n"), cpu);
+
+ switch (output_param) {
+ case 'b':
+ get_boost_mode(cpu);
+ break;
+ case 'e':
+ debug_output_one(cpu);
+ break;
+ case 'a':
+ ret = get_affected_cpus(cpu);
+ break;
+ case 'r':
+ ret = get_related_cpus(cpu);
+ break;
+ case 'g':
+ ret = get_available_governors(cpu);
+ break;
+ case 'p':
+ ret = get_policy(cpu);
+ break;
+ case 'd':
+ ret = get_driver(cpu);
+ break;
+ case 'l':
+ ret = get_hardware_limits(cpu);
+ break;
+ case 'w':
+ ret = get_freq_hardware(cpu, human);
+ break;
+ case 'f':
+ ret = get_freq_kernel(cpu, human);
+ break;
+ case 's':
+ ret = get_freq_stats(cpu, human);
+ break;
+ case 'y':
+ ret = get_latency(cpu, human);
+ break;
+ }
+ if (ret)
+ return ret;
+ }
+ return ret;
+}
diff --git a/tools/power/cpupower/utils/cpufreq-set.c b/tools/power/cpupower/utils/cpufreq-set.c
new file mode 100644
index 00000000000..a416de80c55
--- /dev/null
+++ b/tools/power/cpupower/utils/cpufreq-set.c
@@ -0,0 +1,331 @@
+/*
+ * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de>
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ */
+
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <getopt.h>
+
+#include "cpufreq.h"
+#include "helpers/helpers.h"
+
+#define NORM_FREQ_LEN 32
+
+static struct option set_opts[] = {
+ { .name = "min", .has_arg = required_argument, .flag = NULL, .val = 'd'},
+ { .name = "max", .has_arg = required_argument, .flag = NULL, .val = 'u'},
+ { .name = "governor", .has_arg = required_argument, .flag = NULL, .val = 'g'},
+ { .name = "freq", .has_arg = required_argument, .flag = NULL, .val = 'f'},
+ { .name = "related", .has_arg = no_argument, .flag = NULL, .val='r'},
+ { },
+};
+
+static void print_error(void)
+{
+ printf(_("Error setting new values. Common errors:\n"
+ "- Do you have proper administration rights? (super-user?)\n"
+ "- Is the governor you requested available and modprobed?\n"
+ "- Trying to set an invalid policy?\n"
+ "- Trying to set a specific frequency, but userspace governor is not available,\n"
+ " for example because of hardware which cannot be set to a specific frequency\n"
+ " or because the userspace governor isn't loaded?\n"));
+};
+
+struct freq_units {
+ char *str_unit;
+ int power_of_ten;
+};
+
+const struct freq_units def_units[] = {
+ {"hz", -3},
+ {"khz", 0}, /* default */
+ {"mhz", 3},
+ {"ghz", 6},
+ {"thz", 9},
+ {NULL, 0}
+};
+
+static void print_unknown_arg(void)
+{
+ printf(_("invalid or unknown argument\n"));
+}
+
+static unsigned long string_to_frequency(const char *str)
+{
+ char normalized[NORM_FREQ_LEN];
+ const struct freq_units *unit;
+ const char *scan;
+ char *end;
+ unsigned long freq;
+ int power = 0, match_count = 0, i, cp, pad;
+
+ while (*str == '0')
+ str++;
+
+ for (scan = str; isdigit(*scan) || *scan == '.'; scan++) {
+ if (*scan == '.' && match_count == 0)
+ match_count = 1;
+ else if (*scan == '.' && match_count == 1)
+ return 0;
+ }
+
+ if (*scan) {
+ match_count = 0;
+ for (unit = def_units; unit->str_unit; unit++) {
+ for (i = 0;
+ scan[i] && tolower(scan[i]) == unit->str_unit[i];
+ ++i)
+ continue;
+ if (scan[i])
+ continue;
+ match_count++;
+ power = unit->power_of_ten;
+ }
+ if (match_count != 1)
+ return 0;
+ }
+
+ /* count the number of digits to be copied */
+ for (cp = 0; isdigit(str[cp]); cp++)
+ continue;
+
+ if (str[cp] == '.') {
+ while (power > -1 && isdigit(str[cp+1]))
+ cp++, power--;
+ }
+ if (power >= -1) /* not enough => pad */
+ pad = power + 1;
+ else /* to much => strip */
+ pad = 0, cp += power + 1;
+ /* check bounds */
+ if (cp <= 0 || cp + pad > NORM_FREQ_LEN - 1)
+ return 0;
+
+ /* copy digits */
+ for (i = 0; i < cp; i++, str++) {
+ if (*str == '.')
+ str++;
+ normalized[i] = *str;
+ }
+ /* and pad */
+ for (; i < cp + pad; i++)
+ normalized[i] = '0';
+
+ /* round up, down ? */
+ match_count = (normalized[i-1] >= '5');
+ /* and drop the decimal part */
+ normalized[i-1] = 0; /* cp > 0 && pad >= 0 ==> i > 0 */
+
+ /* final conversion (and applying rounding) */
+ errno = 0;
+ freq = strtoul(normalized, &end, 10);
+ if (errno)
+ return 0;
+ else {
+ if (match_count && freq != ULONG_MAX)
+ freq++;
+ return freq;
+ }
+}
+
+static int do_new_policy(unsigned int cpu, struct cpufreq_policy *new_pol)
+{
+ struct cpufreq_policy *cur_pol = cpufreq_get_policy(cpu);
+ int ret;
+
+ if (!cur_pol) {
+ printf(_("wrong, unknown or unhandled CPU?\n"));
+ return -EINVAL;
+ }
+
+ if (!new_pol->min)
+ new_pol->min = cur_pol->min;
+
+ if (!new_pol->max)
+ new_pol->max = cur_pol->max;
+
+ if (!new_pol->governor)
+ new_pol->governor = cur_pol->governor;
+
+ ret = cpufreq_set_policy(cpu, new_pol);
+
+ cpufreq_put_policy(cur_pol);
+
+ return ret;
+}
+
+
+static int do_one_cpu(unsigned int cpu, struct cpufreq_policy *new_pol,
+ unsigned long freq, unsigned int pc)
+{
+ switch (pc) {
+ case 0:
+ return cpufreq_set_frequency(cpu, freq);
+
+ case 1:
+ /* if only one value of a policy is to be changed, we can
+ * use a "fast path".
+ */
+ if (new_pol->min)
+ return cpufreq_modify_policy_min(cpu, new_pol->min);
+ else if (new_pol->max)
+ return cpufreq_modify_policy_max(cpu, new_pol->max);
+ else if (new_pol->governor)
+ return cpufreq_modify_policy_governor(cpu,
+ new_pol->governor);
+
+ default:
+ /* slow path */
+ return do_new_policy(cpu, new_pol);
+ }
+}
+
+int cmd_freq_set(int argc, char **argv)
+{
+ extern char *optarg;
+ extern int optind, opterr, optopt;
+ int ret = 0, cont = 1;
+ int double_parm = 0, related = 0, policychange = 0;
+ unsigned long freq = 0;
+ char gov[20];
+ unsigned int cpu;
+
+ struct cpufreq_policy new_pol = {
+ .min = 0,
+ .max = 0,
+ .governor = NULL,
+ };
+
+ /* parameter parsing */
+ do {
+ ret = getopt_long(argc, argv, "d:u:g:f:r", set_opts, NULL);
+ switch (ret) {
+ case '?':
+ print_unknown_arg();
+ return -EINVAL;
+ case -1:
+ cont = 0;
+ break;
+ case 'r':
+ if (related)
+ double_parm++;
+ related++;
+ break;
+ case 'd':
+ if (new_pol.min)
+ double_parm++;
+ policychange++;
+ new_pol.min = string_to_frequency(optarg);
+ if (new_pol.min == 0) {
+ print_unknown_arg();
+ return -EINVAL;
+ }
+ break;
+ case 'u':
+ if (new_pol.max)
+ double_parm++;
+ policychange++;
+ new_pol.max = string_to_frequency(optarg);
+ if (new_pol.max == 0) {
+ print_unknown_arg();
+ return -EINVAL;
+ }
+ break;
+ case 'f':
+ if (freq)
+ double_parm++;
+ freq = string_to_frequency(optarg);
+ if (freq == 0) {
+ print_unknown_arg();
+ return -EINVAL;
+ }
+ break;
+ case 'g':
+ if (new_pol.governor)
+ double_parm++;
+ policychange++;
+ if ((strlen(optarg) < 3) || (strlen(optarg) > 18)) {
+ print_unknown_arg();
+ return -EINVAL;
+ }
+ if ((sscanf(optarg, "%19s", gov)) != 1) {
+ print_unknown_arg();
+ return -EINVAL;
+ }
+ new_pol.governor = gov;
+ break;
+ }
+ } while (cont);
+
+ /* parameter checking */
+ if (double_parm) {
+ printf("the same parameter was passed more than once\n");
+ return -EINVAL;
+ }
+
+ if (freq && policychange) {
+ printf(_("the -f/--freq parameter cannot be combined with -d/--min, -u/--max or\n"
+ "-g/--governor parameters\n"));
+ return -EINVAL;
+ }
+
+ if (!freq && !policychange) {
+ printf(_("At least one parameter out of -f/--freq, -d/--min, -u/--max, and\n"
+ "-g/--governor must be passed\n"));
+ return -EINVAL;
+ }
+
+ /* Default is: set all CPUs */
+ if (bitmask_isallclear(cpus_chosen))
+ bitmask_setall(cpus_chosen);
+
+ /* Also set frequency settings for related CPUs if -r is passed */
+ if (related) {
+ for (cpu = bitmask_first(cpus_chosen);
+ cpu <= bitmask_last(cpus_chosen); cpu++) {
+ struct cpufreq_affected_cpus *cpus;
+
+ if (!bitmask_isbitset(cpus_chosen, cpu) ||
+ cpufreq_cpu_exists(cpu))
+ continue;
+
+ cpus = cpufreq_get_related_cpus(cpu);
+ if (!cpus)
+ break;
+ while (cpus->next) {
+ bitmask_setbit(cpus_chosen, cpus->cpu);
+ cpus = cpus->next;
+ }
+ cpufreq_put_related_cpus(cpus);
+ }
+ }
+
+
+ /* loop over CPUs */
+ for (cpu = bitmask_first(cpus_chosen);
+ cpu <= bitmask_last(cpus_chosen); cpu++) {
+
+ if (!bitmask_isbitset(cpus_chosen, cpu) ||
+ cpufreq_cpu_exists(cpu))
+ continue;
+
+ printf(_("Setting cpu: %d\n"), cpu);
+ ret = do_one_cpu(cpu, &new_pol, freq, policychange);
+ if (ret)
+ break;
+ }
+
+ if (ret)
+ print_error();
+
+ return ret;
+}
diff --git a/tools/power/cpupower/utils/cpuidle-info.c b/tools/power/cpupower/utils/cpuidle-info.c
new file mode 100644
index 00000000000..75e66de7e7a
--- /dev/null
+++ b/tools/power/cpupower/utils/cpuidle-info.c
@@ -0,0 +1,208 @@
+/*
+ * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de>
+ * (C) 2010 Thomas Renninger <trenn@suse.de>
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ */
+
+
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <getopt.h>
+#include <cpufreq.h>
+
+#include "helpers/helpers.h"
+#include "helpers/sysfs.h"
+#include "helpers/bitmask.h"
+
+#define LINE_LEN 10
+
+static void cpuidle_cpu_output(unsigned int cpu, int verbose)
+{
+ unsigned int idlestates, idlestate;
+ char *tmp;
+
+ printf(_ ("Analyzing CPU %d:\n"), cpu);
+
+ idlestates = sysfs_get_idlestate_count(cpu);
+ if (idlestates == 0) {
+ printf(_("CPU %u: No idle states\n"), cpu);
+ return;
+ }
+
+ printf(_("Number of idle states: %d\n"), idlestates);
+ printf(_("Available idle states:"));
+ for (idlestate = 0; idlestate < idlestates; idlestate++) {
+ tmp = sysfs_get_idlestate_name(cpu, idlestate);
+ if (!tmp)
+ continue;
+ printf(" %s", tmp);
+ free(tmp);
+ }
+ printf("\n");
+
+ if (!verbose)
+ return;
+
+ for (idlestate = 0; idlestate < idlestates; idlestate++) {
+ int disabled = sysfs_is_idlestate_disabled(cpu, idlestate);
+ /* Disabled interface not supported on older kernels */
+ if (disabled < 0)
+ disabled = 0;
+ tmp = sysfs_get_idlestate_name(cpu, idlestate);
+ if (!tmp)
+ continue;
+ printf("%s%s:\n", tmp, (disabled) ? " (DISABLED) " : "");
+ free(tmp);
+
+ tmp = sysfs_get_idlestate_desc(cpu, idlestate);
+ if (!tmp)
+ continue;
+ printf(_("Flags/Description: %s\n"), tmp);
+ free(tmp);
+
+ printf(_("Latency: %lu\n"),
+ sysfs_get_idlestate_latency(cpu, idlestate));
+ printf(_("Usage: %lu\n"),
+ sysfs_get_idlestate_usage(cpu, idlestate));
+ printf(_("Duration: %llu\n"),
+ sysfs_get_idlestate_time(cpu, idlestate));
+ }
+ printf("\n");
+}
+
+static void cpuidle_general_output(void)
+{
+ char *tmp;
+
+ tmp = sysfs_get_cpuidle_driver();
+ if (!tmp) {
+ printf(_("Could not determine cpuidle driver\n"));
+ return;
+ }
+
+ printf(_("CPUidle driver: %s\n"), tmp);
+ free(tmp);
+
+ tmp = sysfs_get_cpuidle_governor();
+ if (!tmp) {
+ printf(_("Could not determine cpuidle governor\n"));
+ return;
+ }
+
+ printf(_("CPUidle governor: %s\n"), tmp);
+ free(tmp);
+}
+
+static void proc_cpuidle_cpu_output(unsigned int cpu)
+{
+ long max_allowed_cstate = 2000000000;
+ unsigned int cstate, cstates;
+
+ cstates = sysfs_get_idlestate_count(cpu);
+ if (cstates == 0) {
+ printf(_("CPU %u: No C-states info\n"), cpu);
+ return;
+ }
+
+ printf(_("active state: C0\n"));
+ printf(_("max_cstate: C%u\n"), cstates-1);
+ printf(_("maximum allowed latency: %lu usec\n"), max_allowed_cstate);
+ printf(_("states:\t\n"));
+ for (cstate = 1; cstate < cstates; cstate++) {
+ printf(_(" C%d: "
+ "type[C%d] "), cstate, cstate);
+ printf(_("promotion[--] demotion[--] "));
+ printf(_("latency[%03lu] "),
+ sysfs_get_idlestate_latency(cpu, cstate));
+ printf(_("usage[%08lu] "),
+ sysfs_get_idlestate_usage(cpu, cstate));
+ printf(_("duration[%020Lu] \n"),
+ sysfs_get_idlestate_time(cpu, cstate));
+ }
+}
+
+static struct option info_opts[] = {
+ { .name = "silent", .has_arg = no_argument, .flag = NULL, .val = 's'},
+ { .name = "proc", .has_arg = no_argument, .flag = NULL, .val = 'o'},
+ { },
+};
+
+static inline void cpuidle_exit(int fail)
+{
+ exit(EXIT_FAILURE);
+}
+
+int cmd_idle_info(int argc, char **argv)
+{
+ extern char *optarg;
+ extern int optind, opterr, optopt;
+ int ret = 0, cont = 1, output_param = 0, verbose = 1;
+ unsigned int cpu = 0;
+
+ do {
+ ret = getopt_long(argc, argv, "os", info_opts, NULL);
+ if (ret == -1)
+ break;
+ switch (ret) {
+ case '?':
+ output_param = '?';
+ cont = 0;
+ break;
+ case 's':
+ verbose = 0;
+ break;
+ case -1:
+ cont = 0;
+ break;
+ case 'o':
+ if (output_param) {
+ output_param = -1;
+ cont = 0;
+ break;
+ }
+ output_param = ret;
+ break;
+ }
+ } while (cont);
+
+ switch (output_param) {
+ case -1:
+ printf(_("You can't specify more than one "
+ "output-specific argument\n"));
+ cpuidle_exit(EXIT_FAILURE);
+ case '?':
+ printf(_("invalid or unknown argument\n"));
+ cpuidle_exit(EXIT_FAILURE);
+ }
+
+ /* Default is: show output of CPU 0 only */
+ if (bitmask_isallclear(cpus_chosen))
+ bitmask_setbit(cpus_chosen, 0);
+
+ if (output_param == 0)
+ cpuidle_general_output();
+
+ for (cpu = bitmask_first(cpus_chosen);
+ cpu <= bitmask_last(cpus_chosen); cpu++) {
+
+ if (!bitmask_isbitset(cpus_chosen, cpu) ||
+ cpufreq_cpu_exists(cpu))
+ continue;
+
+ switch (output_param) {
+
+ case 'o':
+ proc_cpuidle_cpu_output(cpu);
+ break;
+ case 0:
+ printf("\n");
+ cpuidle_cpu_output(cpu, verbose);
+ break;
+ }
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/tools/power/cpupower/utils/cpuidle-set.c b/tools/power/cpupower/utils/cpuidle-set.c
new file mode 100644
index 00000000000..d45d8d775c0
--- /dev/null
+++ b/tools/power/cpupower/utils/cpuidle-set.c
@@ -0,0 +1,181 @@
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <string.h>
+#include <ctype.h>
+
+#include <getopt.h>
+
+#include "cpufreq.h"
+#include "helpers/helpers.h"
+#include "helpers/sysfs.h"
+
+static struct option info_opts[] = {
+ { .name = "disable",
+ .has_arg = required_argument, .flag = NULL, .val = 'd'},
+ { .name = "enable",
+ .has_arg = required_argument, .flag = NULL, .val = 'e'},
+ { .name = "disable-by-latency",
+ .has_arg = required_argument, .flag = NULL, .val = 'D'},
+ { .name = "enable-all",
+ .has_arg = no_argument, .flag = NULL, .val = 'E'},
+ { },
+};
+
+
+int cmd_idle_set(int argc, char **argv)
+{
+ extern char *optarg;
+ extern int optind, opterr, optopt;
+ int ret = 0, cont = 1, param = 0, disabled;
+ unsigned long long latency = 0, state_latency;
+ unsigned int cpu = 0, idlestate = 0, idlestates = 0;
+ char *endptr;
+
+ do {
+ ret = getopt_long(argc, argv, "d:e:ED:", info_opts, NULL);
+ if (ret == -1)
+ break;
+ switch (ret) {
+ case '?':
+ param = '?';
+ cont = 0;
+ break;
+ case 'd':
+ if (param) {
+ param = -1;
+ cont = 0;
+ break;
+ }
+ param = ret;
+ idlestate = atoi(optarg);
+ break;
+ case 'e':
+ if (param) {
+ param = -1;
+ cont = 0;
+ break;
+ }
+ param = ret;
+ idlestate = atoi(optarg);
+ break;
+ case 'D':
+ if (param) {
+ param = -1;
+ cont = 0;
+ break;
+ }
+ param = ret;
+ latency = strtoull(optarg, &endptr, 10);
+ if (*endptr != '\0') {
+ printf(_("Bad latency value: %s\n"), optarg);
+ exit(EXIT_FAILURE);
+ }
+ break;
+ case 'E':
+ if (param) {
+ param = -1;
+ cont = 0;
+ break;
+ }
+ param = ret;
+ break;
+ case -1:
+ cont = 0;
+ break;
+ }
+ } while (cont);
+
+ switch (param) {
+ case -1:
+ printf(_("You can't specify more than one "
+ "output-specific argument\n"));
+ exit(EXIT_FAILURE);
+ case '?':
+ printf(_("invalid or unknown argument\n"));
+ exit(EXIT_FAILURE);
+ }
+
+ /* Default is: set all CPUs */
+ if (bitmask_isallclear(cpus_chosen))
+ bitmask_setall(cpus_chosen);
+
+ for (cpu = bitmask_first(cpus_chosen);
+ cpu <= bitmask_last(cpus_chosen); cpu++) {
+
+ if (!bitmask_isbitset(cpus_chosen, cpu))
+ continue;
+
+ if (sysfs_is_cpu_online(cpu) != 1)
+ continue;
+
+ idlestates = sysfs_get_idlestate_count(cpu);
+ if (idlestates <= 0)
+ continue;
+
+ switch (param) {
+ case 'd':
+ ret = sysfs_idlestate_disable(cpu, idlestate, 1);
+ if (ret == 0)
+ printf(_("Idlestate %u disabled on CPU %u\n"), idlestate, cpu);
+ else if (ret == -1)
+ printf(_("Idlestate %u not available on CPU %u\n"),
+ idlestate, cpu);
+ else if (ret == -2)
+ printf(_("Idlestate disabling not supported by kernel\n"));
+ else
+ printf(_("Idlestate %u not disabled on CPU %u\n"),
+ idlestate, cpu);
+ break;
+ case 'e':
+ ret = sysfs_idlestate_disable(cpu, idlestate, 0);
+ if (ret == 0)
+ printf(_("Idlestate %u enabled on CPU %u\n"), idlestate, cpu);
+ else if (ret == -1)
+ printf(_("Idlestate %u not available on CPU %u\n"),
+ idlestate, cpu);
+ else if (ret == -2)
+ printf(_("Idlestate enabling not supported by kernel\n"));
+ else
+ printf(_("Idlestate %u not enabled on CPU %u\n"),
+ idlestate, cpu);
+ break;
+ case 'D':
+ for (idlestate = 0; idlestate < idlestates; idlestate++) {
+ disabled = sysfs_is_idlestate_disabled
+ (cpu, idlestate);
+ state_latency = sysfs_get_idlestate_latency
+ (cpu, idlestate);
+ printf("CPU: %u - idlestate %u - state_latency: %llu - latency: %llu\n",
+ cpu, idlestate, state_latency, latency);
+ if (disabled == 1 || latency > state_latency)
+ continue;
+ ret = sysfs_idlestate_disable
+ (cpu, idlestate, 1);
+ if (ret == 0)
+ printf(_("Idlestate %u disabled on CPU %u\n"), idlestate, cpu);
+ }
+ break;
+ case 'E':
+ for (idlestate = 0; idlestate < idlestates; idlestate++) {
+ disabled = sysfs_is_idlestate_disabled
+ (cpu, idlestate);
+ if (disabled == 1) {
+ ret = sysfs_idlestate_disable
+ (cpu, idlestate, 0);
+ if (ret == 0)
+ printf(_("Idlestate %u enabled on CPU %u\n"), idlestate, cpu);
+ }
+ }
+ break;
+ default:
+ /* Not reachable with proper args checking */
+ printf(_("Invalid or unknown argument\n"));
+ exit(EXIT_FAILURE);
+ break;
+ }
+ }
+ return EXIT_SUCCESS;
+}
diff --git a/tools/power/cpupower/utils/cpupower-info.c b/tools/power/cpupower/utils/cpupower-info.c
new file mode 100644
index 00000000000..136d979e958
--- /dev/null
+++ b/tools/power/cpupower/utils/cpupower-info.c
@@ -0,0 +1,103 @@
+/*
+ * (C) 2011 Thomas Renninger <trenn@suse.de>, Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ */
+
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+
+#include <cpufreq.h>
+#include "helpers/helpers.h"
+#include "helpers/sysfs.h"
+
+static struct option set_opts[] = {
+ { .name = "perf-bias", .has_arg = optional_argument, .flag = NULL, .val = 'b'},
+ { },
+};
+
+static void print_wrong_arg_exit(void)
+{
+ printf(_("invalid or unknown argument\n"));
+ exit(EXIT_FAILURE);
+}
+
+int cmd_info(int argc, char **argv)
+{
+ extern char *optarg;
+ extern int optind, opterr, optopt;
+ unsigned int cpu;
+
+ union {
+ struct {
+ int perf_bias:1;
+ };
+ int params;
+ } params = {};
+ int ret = 0;
+
+ setlocale(LC_ALL, "");
+ textdomain(PACKAGE);
+
+ /* parameter parsing */
+ while ((ret = getopt_long(argc, argv, "b", set_opts, NULL)) != -1) {
+ switch (ret) {
+ case 'b':
+ if (params.perf_bias)
+ print_wrong_arg_exit();
+ params.perf_bias = 1;
+ break;
+ default:
+ print_wrong_arg_exit();
+ }
+ };
+
+ if (!params.params)
+ params.params = 0x7;
+
+ /* Default is: show output of CPU 0 only */
+ if (bitmask_isallclear(cpus_chosen))
+ bitmask_setbit(cpus_chosen, 0);
+
+ /* Add more per cpu options here */
+ if (!params.perf_bias)
+ return ret;
+
+ if (params.perf_bias) {
+ if (!run_as_root) {
+ params.perf_bias = 0;
+ printf(_("Intel's performance bias setting needs root privileges\n"));
+ } else if (!(cpupower_cpu_info.caps & CPUPOWER_CAP_PERF_BIAS)) {
+ printf(_("System does not support Intel's performance"
+ " bias setting\n"));
+ params.perf_bias = 0;
+ }
+ }
+
+ /* loop over CPUs */
+ for (cpu = bitmask_first(cpus_chosen);
+ cpu <= bitmask_last(cpus_chosen); cpu++) {
+
+ if (!bitmask_isbitset(cpus_chosen, cpu) ||
+ cpufreq_cpu_exists(cpu))
+ continue;
+
+ printf(_("analyzing CPU %d:\n"), cpu);
+
+ if (params.perf_bias) {
+ ret = msr_intel_get_perf_bias(cpu);
+ if (ret < 0) {
+ fprintf(stderr,
+ _("Could not read perf-bias value[%d]\n"), ret);
+ exit(EXIT_FAILURE);
+ } else
+ printf(_("perf-bias: %d\n"), ret);
+ }
+ }
+ return 0;
+}
diff --git a/tools/power/cpupower/utils/cpupower-set.c b/tools/power/cpupower/utils/cpupower-set.c
new file mode 100644
index 00000000000..573c75f8e3f
--- /dev/null
+++ b/tools/power/cpupower/utils/cpupower-set.c
@@ -0,0 +1,95 @@
+/*
+ * (C) 2011 Thomas Renninger <trenn@suse.de>, Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ */
+
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <string.h>
+#include <getopt.h>
+
+#include <cpufreq.h>
+#include "helpers/helpers.h"
+#include "helpers/sysfs.h"
+#include "helpers/bitmask.h"
+
+static struct option set_opts[] = {
+ { .name = "perf-bias", .has_arg = required_argument, .flag = NULL, .val = 'b'},
+ { },
+};
+
+static void print_wrong_arg_exit(void)
+{
+ printf(_("invalid or unknown argument\n"));
+ exit(EXIT_FAILURE);
+}
+
+int cmd_set(int argc, char **argv)
+{
+ extern char *optarg;
+ extern int optind, opterr, optopt;
+ unsigned int cpu;
+
+ union {
+ struct {
+ int perf_bias:1;
+ };
+ int params;
+ } params;
+ int perf_bias = 0;
+ int ret = 0;
+
+ setlocale(LC_ALL, "");
+ textdomain(PACKAGE);
+
+ params.params = 0;
+ /* parameter parsing */
+ while ((ret = getopt_long(argc, argv, "b:",
+ set_opts, NULL)) != -1) {
+ switch (ret) {
+ case 'b':
+ if (params.perf_bias)
+ print_wrong_arg_exit();
+ perf_bias = atoi(optarg);
+ if (perf_bias < 0 || perf_bias > 15) {
+ printf(_("--perf-bias param out "
+ "of range [0-%d]\n"), 15);
+ print_wrong_arg_exit();
+ }
+ params.perf_bias = 1;
+ break;
+ default:
+ print_wrong_arg_exit();
+ }
+ };
+
+ if (!params.params)
+ print_wrong_arg_exit();
+
+ /* Default is: set all CPUs */
+ if (bitmask_isallclear(cpus_chosen))
+ bitmask_setall(cpus_chosen);
+
+ /* loop over CPUs */
+ for (cpu = bitmask_first(cpus_chosen);
+ cpu <= bitmask_last(cpus_chosen); cpu++) {
+
+ if (!bitmask_isbitset(cpus_chosen, cpu) ||
+ cpufreq_cpu_exists(cpu))
+ continue;
+
+ if (params.perf_bias) {
+ ret = msr_intel_set_perf_bias(cpu, perf_bias);
+ if (ret) {
+ fprintf(stderr, _("Error setting perf-bias "
+ "value on CPU %d\n"), cpu);
+ break;
+ }
+ }
+ }
+ return ret;
+}
diff --git a/tools/power/cpupower/utils/cpupower.c b/tools/power/cpupower/utils/cpupower.c
new file mode 100644
index 00000000000..7cdcf88659c
--- /dev/null
+++ b/tools/power/cpupower/utils/cpupower.c
@@ -0,0 +1,229 @@
+/*
+ * (C) 2010,2011 Thomas Renninger <trenn@suse.de>, Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ * Ideas taken over from the perf userspace tool (included in the Linus
+ * kernel git repo): subcommand builtins and param parsing.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/utsname.h>
+
+#include "builtin.h"
+#include "helpers/helpers.h"
+#include "helpers/bitmask.h"
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+static int cmd_help(int argc, const char **argv);
+
+/* Global cpu_info object available for all binaries
+ * Info only retrieved from CPU 0
+ *
+ * Values will be zero/unknown on non X86 archs
+ */
+struct cpupower_cpu_info cpupower_cpu_info;
+int run_as_root;
+/* Affected cpus chosen by -c/--cpu param */
+struct bitmask *cpus_chosen;
+
+#ifdef DEBUG
+int be_verbose;
+#endif
+
+static void print_help(void);
+
+struct cmd_struct {
+ const char *cmd;
+ int (*main)(int, const char **);
+ int needs_root;
+};
+
+static struct cmd_struct commands[] = {
+ { "frequency-info", cmd_freq_info, 0 },
+ { "frequency-set", cmd_freq_set, 1 },
+ { "idle-info", cmd_idle_info, 0 },
+ { "idle-set", cmd_idle_set, 1 },
+ { "set", cmd_set, 1 },
+ { "info", cmd_info, 0 },
+ { "monitor", cmd_monitor, 0 },
+ { "help", cmd_help, 0 },
+ /* { "bench", cmd_bench, 1 }, */
+};
+
+static void print_help(void)
+{
+ unsigned int i;
+
+#ifdef DEBUG
+ printf(_("Usage:\tcpupower [-d|--debug] [-c|--cpu cpulist ] <command> [<args>]\n"));
+#else
+ printf(_("Usage:\tcpupower [-c|--cpu cpulist ] <command> [<args>]\n"));
+#endif
+ printf(_("Supported commands are:\n"));
+ for (i = 0; i < ARRAY_SIZE(commands); i++)
+ printf("\t%s\n", commands[i].cmd);
+ printf(_("\nNot all commands can make use of the -c cpulist option.\n"));
+ printf(_("\nUse 'cpupower help <command>' for getting help for above commands.\n"));
+}
+
+static int print_man_page(const char *subpage)
+{
+ int len;
+ char *page;
+
+ len = 10; /* enough for "cpupower-" */
+ if (subpage != NULL)
+ len += strlen(subpage);
+
+ page = malloc(len);
+ if (!page)
+ return -ENOMEM;
+
+ sprintf(page, "cpupower");
+ if ((subpage != NULL) && strcmp(subpage, "help")) {
+ strcat(page, "-");
+ strcat(page, subpage);
+ }
+
+ execlp("man", "man", page, NULL);
+
+ /* should not be reached */
+ return -EINVAL;
+}
+
+static int cmd_help(int argc, const char **argv)
+{
+ if (argc > 1) {
+ print_man_page(argv[1]); /* exits within execlp() */
+ return EXIT_FAILURE;
+ }
+
+ print_help();
+ return EXIT_SUCCESS;
+}
+
+static void print_version(void)
+{
+ printf(PACKAGE " " VERSION "\n");
+ printf(_("Report errors and bugs to %s, please.\n"), PACKAGE_BUGREPORT);
+}
+
+static void handle_options(int *argc, const char ***argv)
+{
+ int ret, x, new_argc = 0;
+
+ if (*argc < 1)
+ return;
+
+ for (x = 0; x < *argc && ((*argv)[x])[0] == '-'; x++) {
+ const char *param = (*argv)[x];
+ if (!strcmp(param, "-h") || !strcmp(param, "--help")) {
+ print_help();
+ exit(EXIT_SUCCESS);
+ } else if (!strcmp(param, "-c") || !strcmp(param, "--cpu")) {
+ if (*argc < 2) {
+ print_help();
+ exit(EXIT_FAILURE);
+ }
+ if (!strcmp((*argv)[x+1], "all"))
+ bitmask_setall(cpus_chosen);
+ else {
+ ret = bitmask_parselist(
+ (*argv)[x+1], cpus_chosen);
+ if (ret < 0) {
+ fprintf(stderr, _("Error parsing cpu "
+ "list\n"));
+ exit(EXIT_FAILURE);
+ }
+ }
+ x += 1;
+ /* Cut out param: cpupower -c 1 info -> cpupower info */
+ new_argc += 2;
+ continue;
+ } else if (!strcmp(param, "-v") ||
+ !strcmp(param, "--version")) {
+ print_version();
+ exit(EXIT_SUCCESS);
+#ifdef DEBUG
+ } else if (!strcmp(param, "-d") || !strcmp(param, "--debug")) {
+ be_verbose = 1;
+ new_argc++;
+ continue;
+#endif
+ } else {
+ fprintf(stderr, "Unknown option: %s\n", param);
+ print_help();
+ exit(EXIT_FAILURE);
+ }
+ }
+ *argc -= new_argc;
+ *argv += new_argc;
+}
+
+int main(int argc, const char *argv[])
+{
+ const char *cmd;
+ unsigned int i, ret;
+ struct stat statbuf;
+ struct utsname uts;
+
+ cpus_chosen = bitmask_alloc(sysconf(_SC_NPROCESSORS_CONF));
+
+ argc--;
+ argv += 1;
+
+ handle_options(&argc, &argv);
+
+ cmd = argv[0];
+
+ if (argc < 1) {
+ print_help();
+ return EXIT_FAILURE;
+ }
+
+ setlocale(LC_ALL, "");
+ textdomain(PACKAGE);
+
+ /* Turn "perf cmd --help" into "perf help cmd" */
+ if (argc > 1 && !strcmp(argv[1], "--help")) {
+ argv[1] = argv[0];
+ argv[0] = cmd = "help";
+ }
+
+ get_cpu_info(0, &cpupower_cpu_info);
+ run_as_root = !getuid();
+ if (run_as_root) {
+ ret = uname(&uts);
+ if (!ret && !strcmp(uts.machine, "x86_64") &&
+ stat("/dev/cpu/0/msr", &statbuf) != 0) {
+ if (system("modprobe msr") == -1)
+ fprintf(stderr, _("MSR access not available.\n"));
+ }
+ }
+
+
+ for (i = 0; i < ARRAY_SIZE(commands); i++) {
+ struct cmd_struct *p = commands + i;
+ if (strcmp(p->cmd, cmd))
+ continue;
+ if (!run_as_root && p->needs_root) {
+ fprintf(stderr, _("Subcommand %s needs root "
+ "privileges\n"), cmd);
+ return EXIT_FAILURE;
+ }
+ ret = p->main(argc, argv);
+ if (cpus_chosen)
+ bitmask_free(cpus_chosen);
+ return ret;
+ }
+ print_help();
+ return EXIT_FAILURE;
+}
diff --git a/tools/power/cpupower/utils/helpers/amd.c b/tools/power/cpupower/utils/helpers/amd.c
new file mode 100644
index 00000000000..6437ef39aee
--- /dev/null
+++ b/tools/power/cpupower/utils/helpers/amd.c
@@ -0,0 +1,135 @@
+#if defined(__i386__) || defined(__x86_64__)
+#include <unistd.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#include <pci/pci.h>
+
+#include "helpers/helpers.h"
+
+#define MSR_AMD_PSTATE_STATUS 0xc0010063
+#define MSR_AMD_PSTATE 0xc0010064
+#define MSR_AMD_PSTATE_LIMIT 0xc0010061
+
+union msr_pstate {
+ struct {
+ unsigned fid:6;
+ unsigned did:3;
+ unsigned vid:7;
+ unsigned res1:6;
+ unsigned nbdid:1;
+ unsigned res2:2;
+ unsigned nbvid:7;
+ unsigned iddval:8;
+ unsigned idddiv:2;
+ unsigned res3:21;
+ unsigned en:1;
+ } bits;
+ unsigned long long val;
+};
+
+static int get_did(int family, union msr_pstate pstate)
+{
+ int t;
+
+ if (family == 0x12)
+ t = pstate.val & 0xf;
+ else
+ t = pstate.bits.did;
+
+ return t;
+}
+
+static int get_cof(int family, union msr_pstate pstate)
+{
+ int t;
+ int fid, did;
+
+ did = get_did(family, pstate);
+
+ t = 0x10;
+ fid = pstate.bits.fid;
+ if (family == 0x11)
+ t = 0x8;
+
+ return (100 * (fid + t)) >> did;
+}
+
+/* Needs:
+ * cpu -> the cpu that gets evaluated
+ * cpu_family -> The cpu's family (0x10, 0x12,...)
+ * boots_states -> how much boost states the machines support
+ *
+ * Fills up:
+ * pstates -> a pointer to an array of size MAX_HW_PSTATES
+ * must be initialized with zeros.
+ * All available HW pstates (including boost states)
+ * no -> amount of pstates above array got filled up with
+ *
+ * returns zero on success, -1 on failure
+ */
+int decode_pstates(unsigned int cpu, unsigned int cpu_family,
+ int boost_states, unsigned long *pstates, int *no)
+{
+ int i, psmax, pscur;
+ union msr_pstate pstate;
+ unsigned long long val;
+
+ /* Only read out frequencies from HW when CPU might be boostable
+ to keep the code as short and clean as possible.
+ Otherwise frequencies are exported via ACPI tables.
+ */
+ if (cpu_family < 0x10 || cpu_family == 0x14)
+ return -1;
+
+ if (read_msr(cpu, MSR_AMD_PSTATE_LIMIT, &val))
+ return -1;
+
+ psmax = (val >> 4) & 0x7;
+
+ if (read_msr(cpu, MSR_AMD_PSTATE_STATUS, &val))
+ return -1;
+
+ pscur = val & 0x7;
+
+ pscur += boost_states;
+ psmax += boost_states;
+ for (i = 0; i <= psmax; i++) {
+ if (i >= MAX_HW_PSTATES) {
+ fprintf(stderr, "HW pstates [%d] exceeding max [%d]\n",
+ psmax, MAX_HW_PSTATES);
+ return -1;
+ }
+ if (read_msr(cpu, MSR_AMD_PSTATE + i, &pstate.val))
+ return -1;
+ pstates[i] = get_cof(cpu_family, pstate);
+ }
+ *no = i;
+ return 0;
+}
+
+int amd_pci_get_num_boost_states(int *active, int *states)
+{
+ struct pci_access *pci_acc;
+ struct pci_dev *device;
+ uint8_t val = 0;
+
+ *active = *states = 0;
+
+ device = pci_slot_func_init(&pci_acc, 0x18, 4);
+
+ if (device == NULL)
+ return -ENODEV;
+
+ val = pci_read_byte(device, 0x15c);
+ if (val & 3)
+ *active = 1;
+ else
+ *active = 0;
+ *states = (val >> 2) & 7;
+
+ pci_cleanup(pci_acc);
+ return 0;
+}
+#endif /* defined(__i386__) || defined(__x86_64__) */
diff --git a/tools/power/cpupower/utils/helpers/bitmask.c b/tools/power/cpupower/utils/helpers/bitmask.c
new file mode 100644
index 00000000000..5c074c60f90
--- /dev/null
+++ b/tools/power/cpupower/utils/helpers/bitmask.c
@@ -0,0 +1,292 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <helpers/bitmask.h>
+
+/* How many bits in an unsigned long */
+#define bitsperlong (8 * sizeof(unsigned long))
+
+/* howmany(a,b) : how many elements of size b needed to hold all of a */
+#define howmany(x, y) (((x)+((y)-1))/(y))
+
+/* How many longs in mask of n bits */
+#define longsperbits(n) howmany(n, bitsperlong)
+
+#define max(a, b) ((a) > (b) ? (a) : (b))
+
+/*
+ * Allocate and free `struct bitmask *`
+ */
+
+/* Allocate a new `struct bitmask` with a size of n bits */
+struct bitmask *bitmask_alloc(unsigned int n)
+{
+ struct bitmask *bmp;
+
+ bmp = malloc(sizeof(*bmp));
+ if (bmp == 0)
+ return 0;
+ bmp->size = n;
+ bmp->maskp = calloc(longsperbits(n), sizeof(unsigned long));
+ if (bmp->maskp == 0) {
+ free(bmp);
+ return 0;
+ }
+ return bmp;
+}
+
+/* Free `struct bitmask` */
+void bitmask_free(struct bitmask *bmp)
+{
+ if (bmp == 0)
+ return;
+ free(bmp->maskp);
+ bmp->maskp = (unsigned long *)0xdeadcdef; /* double free tripwire */
+ free(bmp);
+}
+
+/*
+ * The routines _getbit() and _setbit() are the only
+ * routines that actually understand the layout of bmp->maskp[].
+ *
+ * On little endian architectures, this could simply be an array of
+ * bytes. But the kernel layout of bitmasks _is_ visible to userspace
+ * via the sched_(set/get)affinity calls in Linux 2.6, and on big
+ * endian architectures, it is painfully obvious that this is an
+ * array of unsigned longs.
+ */
+
+/* Return the value (0 or 1) of bit n in bitmask bmp */
+static unsigned int _getbit(const struct bitmask *bmp, unsigned int n)
+{
+ if (n < bmp->size)
+ return (bmp->maskp[n/bitsperlong] >> (n % bitsperlong)) & 1;
+ else
+ return 0;
+}
+
+/* Set bit n in bitmask bmp to value v (0 or 1) */
+static void _setbit(struct bitmask *bmp, unsigned int n, unsigned int v)
+{
+ if (n < bmp->size) {
+ if (v)
+ bmp->maskp[n/bitsperlong] |= 1UL << (n % bitsperlong);
+ else
+ bmp->maskp[n/bitsperlong] &=
+ ~(1UL << (n % bitsperlong));
+ }
+}
+
+/*
+ * When parsing bitmask lists, only allow numbers, separated by one
+ * of the allowed next characters.
+ *
+ * The parameter 'sret' is the return from a sscanf "%u%c". It is
+ * -1 if the sscanf input string was empty. It is 0 if the first
+ * character in the sscanf input string was not a decimal number.
+ * It is 1 if the unsigned number matching the "%u" was the end of the
+ * input string. It is 2 if one or more additional characters followed
+ * the matched unsigned number. If it is 2, then 'nextc' is the first
+ * character following the number. The parameter 'ok_next_chars'
+ * is the nul-terminated list of allowed next characters.
+ *
+ * The mask term just scanned was ok if and only if either the numbers
+ * matching the %u were all of the input or if the next character in
+ * the input past the numbers was one of the allowed next characters.
+ */
+static int scan_was_ok(int sret, char nextc, const char *ok_next_chars)
+{
+ return sret == 1 ||
+ (sret == 2 && strchr(ok_next_chars, nextc) != NULL);
+}
+
+static const char *nexttoken(const char *q, int sep)
+{
+ if (q)
+ q = strchr(q, sep);
+ if (q)
+ q++;
+ return q;
+}
+
+/* Set a single bit i in bitmask */
+struct bitmask *bitmask_setbit(struct bitmask *bmp, unsigned int i)
+{
+ _setbit(bmp, i, 1);
+ return bmp;
+}
+
+/* Set all bits in bitmask: bmp = ~0 */
+struct bitmask *bitmask_setall(struct bitmask *bmp)
+{
+ unsigned int i;
+ for (i = 0; i < bmp->size; i++)
+ _setbit(bmp, i, 1);
+ return bmp;
+}
+
+/* Clear all bits in bitmask: bmp = 0 */
+struct bitmask *bitmask_clearall(struct bitmask *bmp)
+{
+ unsigned int i;
+ for (i = 0; i < bmp->size; i++)
+ _setbit(bmp, i, 0);
+ return bmp;
+}
+
+/* True if all bits are clear */
+int bitmask_isallclear(const struct bitmask *bmp)
+{
+ unsigned int i;
+ for (i = 0; i < bmp->size; i++)
+ if (_getbit(bmp, i))
+ return 0;
+ return 1;
+}
+
+/* True if specified bit i is set */
+int bitmask_isbitset(const struct bitmask *bmp, unsigned int i)
+{
+ return _getbit(bmp, i);
+}
+
+/* Number of lowest set bit (min) */
+unsigned int bitmask_first(const struct bitmask *bmp)
+{
+ return bitmask_next(bmp, 0);
+}
+
+/* Number of highest set bit (max) */
+unsigned int bitmask_last(const struct bitmask *bmp)
+{
+ unsigned int i;
+ unsigned int m = bmp->size;
+ for (i = 0; i < bmp->size; i++)
+ if (_getbit(bmp, i))
+ m = i;
+ return m;
+}
+
+/* Number of next set bit at or above given bit i */
+unsigned int bitmask_next(const struct bitmask *bmp, unsigned int i)
+{
+ unsigned int n;
+ for (n = i; n < bmp->size; n++)
+ if (_getbit(bmp, n))
+ break;
+ return n;
+}
+
+/*
+ * Parses a comma-separated list of numbers and ranges of numbers,
+ * with optional ':%u' strides modifying ranges, into provided bitmask.
+ * Some examples of input lists and their equivalent simple list:
+ * Input Equivalent to
+ * 0-3 0,1,2,3
+ * 0-7:2 0,2,4,6
+ * 1,3,5-7 1,3,5,6,7
+ * 0-3:2,8-15:4 0,2,8,12
+ */
+int bitmask_parselist(const char *buf, struct bitmask *bmp)
+{
+ const char *p, *q;
+
+ bitmask_clearall(bmp);
+
+ q = buf;
+ while (p = q, q = nexttoken(q, ','), p) {
+ unsigned int a; /* begin of range */
+ unsigned int b; /* end of range */
+ unsigned int s; /* stride */
+ const char *c1, *c2; /* next tokens after '-' or ',' */
+ char nextc; /* char after sscanf %u match */
+ int sret; /* sscanf return (number of matches) */
+
+ sret = sscanf(p, "%u%c", &a, &nextc);
+ if (!scan_was_ok(sret, nextc, ",-"))
+ goto err;
+ b = a;
+ s = 1;
+ c1 = nexttoken(p, '-');
+ c2 = nexttoken(p, ',');
+ if (c1 != NULL && (c2 == NULL || c1 < c2)) {
+ sret = sscanf(c1, "%u%c", &b, &nextc);
+ if (!scan_was_ok(sret, nextc, ",:"))
+ goto err;
+ c1 = nexttoken(c1, ':');
+ if (c1 != NULL && (c2 == NULL || c1 < c2)) {
+ sret = sscanf(c1, "%u%c", &s, &nextc);
+ if (!scan_was_ok(sret, nextc, ","))
+ goto err;
+ }
+ }
+ if (!(a <= b))
+ goto err;
+ if (b >= bmp->size)
+ goto err;
+ while (a <= b) {
+ _setbit(bmp, a, 1);
+ a += s;
+ }
+ }
+ return 0;
+err:
+ bitmask_clearall(bmp);
+ return -1;
+}
+
+/*
+ * emit(buf, buflen, rbot, rtop, len)
+ *
+ * Helper routine for bitmask_displaylist(). Write decimal number
+ * or range to buf+len, suppressing output past buf+buflen, with optional
+ * comma-prefix. Return len of what would be written to buf, if it
+ * all fit.
+ */
+
+static inline int emit(char *buf, int buflen, int rbot, int rtop, int len)
+{
+ if (len > 0)
+ len += snprintf(buf + len, max(buflen - len, 0), ",");
+ if (rbot == rtop)
+ len += snprintf(buf + len, max(buflen - len, 0), "%d", rbot);
+ else
+ len += snprintf(buf + len, max(buflen - len, 0), "%d-%d",
+ rbot, rtop);
+ return len;
+}
+
+/*
+ * Write decimal list representation of bmp to buf.
+ *
+ * Output format is a comma-separated list of decimal numbers and
+ * ranges. Consecutively set bits are shown as two hyphen-separated
+ * decimal numbers, the smallest and largest bit numbers set in
+ * the range. Output format is compatible with the format
+ * accepted as input by bitmap_parselist().
+ *
+ * The return value is the number of characters which would be
+ * generated for the given input, excluding the trailing '\0', as
+ * per ISO C99.
+ */
+
+int bitmask_displaylist(char *buf, int buflen, const struct bitmask *bmp)
+{
+ int len = 0;
+ /* current bit is 'cur', most recently seen range is [rbot, rtop] */
+ unsigned int cur, rbot, rtop;
+
+ if (buflen > 0)
+ *buf = 0;
+ rbot = cur = bitmask_first(bmp);
+ while (cur < bmp->size) {
+ rtop = cur;
+ cur = bitmask_next(bmp, cur+1);
+ if (cur >= bmp->size || cur > rtop + 1) {
+ len = emit(buf, buflen, rbot, rtop, len);
+ rbot = cur;
+ }
+ }
+ return len;
+}
diff --git a/tools/power/cpupower/utils/helpers/bitmask.h b/tools/power/cpupower/utils/helpers/bitmask.h
new file mode 100644
index 00000000000..eb289df4105
--- /dev/null
+++ b/tools/power/cpupower/utils/helpers/bitmask.h
@@ -0,0 +1,33 @@
+#ifndef __CPUPOWER_BITMASK__
+#define __CPUPOWER_BITMASK__
+
+/* Taken over from libbitmask, a project initiated from sgi:
+ * Url: http://oss.sgi.com/projects/cpusets/
+ * Unfortunately it's not very widespread, therefore relevant parts are
+ * pasted here.
+ */
+
+struct bitmask {
+ unsigned int size;
+ unsigned long *maskp;
+};
+
+struct bitmask *bitmask_alloc(unsigned int n);
+void bitmask_free(struct bitmask *bmp);
+
+struct bitmask *bitmask_setbit(struct bitmask *bmp, unsigned int i);
+struct bitmask *bitmask_setall(struct bitmask *bmp);
+struct bitmask *bitmask_clearall(struct bitmask *bmp);
+
+unsigned int bitmask_first(const struct bitmask *bmp);
+unsigned int bitmask_next(const struct bitmask *bmp, unsigned int i);
+unsigned int bitmask_last(const struct bitmask *bmp);
+int bitmask_isallclear(const struct bitmask *bmp);
+int bitmask_isbitset(const struct bitmask *bmp, unsigned int i);
+
+int bitmask_parselist(const char *buf, struct bitmask *bmp);
+int bitmask_displaylist(char *buf, int len, const struct bitmask *bmp);
+
+
+
+#endif /*__CPUPOWER_BITMASK__ */
diff --git a/tools/power/cpupower/utils/helpers/cpuid.c b/tools/power/cpupower/utils/helpers/cpuid.c
new file mode 100644
index 00000000000..93b0aa74ca0
--- /dev/null
+++ b/tools/power/cpupower/utils/helpers/cpuid.c
@@ -0,0 +1,178 @@
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+
+#include "helpers/helpers.h"
+
+static const char *cpu_vendor_table[X86_VENDOR_MAX] = {
+ "Unknown", "GenuineIntel", "AuthenticAMD",
+};
+
+#if defined(__i386__) || defined(__x86_64__)
+
+/* from gcc */
+#include <cpuid.h>
+
+/*
+ * CPUID functions returning a single datum
+ *
+ * Define unsigned int cpuid_e[abcd]x(unsigned int op)
+ */
+#define cpuid_func(reg) \
+ unsigned int cpuid_##reg(unsigned int op) \
+ { \
+ unsigned int eax, ebx, ecx, edx; \
+ __cpuid(op, eax, ebx, ecx, edx); \
+ return reg; \
+ }
+cpuid_func(eax);
+cpuid_func(ebx);
+cpuid_func(ecx);
+cpuid_func(edx);
+
+#endif /* defined(__i386__) || defined(__x86_64__) */
+
+/* get_cpu_info
+ *
+ * Extract CPU vendor, family, model, stepping info from /proc/cpuinfo
+ *
+ * Returns 0 on success or a negativ error code
+ *
+ * TBD: Should there be a cpuid alternative for this if /proc is not mounted?
+ */
+int get_cpu_info(unsigned int cpu, struct cpupower_cpu_info *cpu_info)
+{
+ FILE *fp;
+ char value[64];
+ unsigned int proc, x;
+ unsigned int unknown = 0xffffff;
+ unsigned int cpuid_level, ext_cpuid_level;
+
+ int ret = -EINVAL;
+
+ cpu_info->vendor = X86_VENDOR_UNKNOWN;
+ cpu_info->family = unknown;
+ cpu_info->model = unknown;
+ cpu_info->stepping = unknown;
+ cpu_info->caps = 0;
+
+ fp = fopen("/proc/cpuinfo", "r");
+ if (!fp)
+ return -EIO;
+
+ while (!feof(fp)) {
+ if (!fgets(value, 64, fp))
+ continue;
+ value[63 - 1] = '\0';
+
+ if (!strncmp(value, "processor\t: ", 12))
+ sscanf(value, "processor\t: %u", &proc);
+
+ if (proc != cpu)
+ continue;
+
+ /* Get CPU vendor */
+ if (!strncmp(value, "vendor_id", 9)) {
+ for (x = 1; x < X86_VENDOR_MAX; x++) {
+ if (strstr(value, cpu_vendor_table[x]))
+ cpu_info->vendor = x;
+ }
+ /* Get CPU family, etc. */
+ } else if (!strncmp(value, "cpu family\t: ", 13)) {
+ sscanf(value, "cpu family\t: %u",
+ &cpu_info->family);
+ } else if (!strncmp(value, "model\t\t: ", 9)) {
+ sscanf(value, "model\t\t: %u",
+ &cpu_info->model);
+ } else if (!strncmp(value, "stepping\t: ", 10)) {
+ sscanf(value, "stepping\t: %u",
+ &cpu_info->stepping);
+
+ /* Exit -> all values must have been set */
+ if (cpu_info->vendor == X86_VENDOR_UNKNOWN ||
+ cpu_info->family == unknown ||
+ cpu_info->model == unknown ||
+ cpu_info->stepping == unknown) {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = 0;
+ goto out;
+ }
+ }
+ ret = -ENODEV;
+out:
+ fclose(fp);
+ /* Get some useful CPU capabilities from cpuid */
+ if (cpu_info->vendor != X86_VENDOR_AMD &&
+ cpu_info->vendor != X86_VENDOR_INTEL)
+ return ret;
+
+ cpuid_level = cpuid_eax(0);
+ ext_cpuid_level = cpuid_eax(0x80000000);
+
+ /* Invariant TSC */
+ if (ext_cpuid_level >= 0x80000007 &&
+ (cpuid_edx(0x80000007) & (1 << 8)))
+ cpu_info->caps |= CPUPOWER_CAP_INV_TSC;
+
+ /* Aperf/Mperf registers support */
+ if (cpuid_level >= 6 && (cpuid_ecx(6) & 0x1))
+ cpu_info->caps |= CPUPOWER_CAP_APERF;
+
+ /* AMD Boost state enable/disable register */
+ if (cpu_info->vendor == X86_VENDOR_AMD) {
+ if (ext_cpuid_level >= 0x80000007 &&
+ (cpuid_edx(0x80000007) & (1 << 9)))
+ cpu_info->caps |= CPUPOWER_CAP_AMD_CBP;
+ }
+
+ if (cpu_info->vendor == X86_VENDOR_INTEL) {
+ if (cpuid_level >= 6 &&
+ (cpuid_eax(6) & (1 << 1)))
+ cpu_info->caps |= CPUPOWER_CAP_INTEL_IDA;
+ }
+
+ if (cpu_info->vendor == X86_VENDOR_INTEL) {
+ /* Intel's perf-bias MSR support */
+ if (cpuid_level >= 6 && (cpuid_ecx(6) & (1 << 3)))
+ cpu_info->caps |= CPUPOWER_CAP_PERF_BIAS;
+
+ /* Intel's Turbo Ratio Limit support */
+ if (cpu_info->family == 6) {
+ switch (cpu_info->model) {
+ case 0x1A: /* Core i7, Xeon 5500 series
+ * Bloomfield, Gainstown NHM-EP
+ */
+ case 0x1E: /* Core i7 and i5 Processor
+ * Clarksfield, Lynnfield, Jasper Forest
+ */
+ case 0x1F: /* Core i7 and i5 Processor - Nehalem */
+ case 0x25: /* Westmere Client
+ * Clarkdale, Arrandale
+ */
+ case 0x2C: /* Westmere EP - Gulftown */
+ cpu_info->caps |= CPUPOWER_CAP_HAS_TURBO_RATIO;
+ case 0x2A: /* SNB */
+ case 0x2D: /* SNB Xeon */
+ case 0x3A: /* IVB */
+ case 0x3E: /* IVB Xeon */
+ cpu_info->caps |= CPUPOWER_CAP_HAS_TURBO_RATIO;
+ cpu_info->caps |= CPUPOWER_CAP_IS_SNB;
+ break;
+ case 0x2E: /* Nehalem-EX Xeon - Beckton */
+ case 0x2F: /* Westmere-EX Xeon - Eagleton */
+ default:
+ break;
+ }
+ }
+ }
+
+ /* printf("ID: %u - Extid: 0x%x - Caps: 0x%llx\n",
+ cpuid_level, ext_cpuid_level, cpu_info->caps);
+ */
+ return ret;
+}
diff --git a/tools/power/cpupower/utils/helpers/helpers.h b/tools/power/cpupower/utils/helpers/helpers.h
new file mode 100644
index 00000000000..aa9e95486a2
--- /dev/null
+++ b/tools/power/cpupower/utils/helpers/helpers.h
@@ -0,0 +1,195 @@
+/*
+ * (C) 2010,2011 Thomas Renninger <trenn@suse.de>, Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ * Miscellaneous helpers which do not fit or are worth
+ * to put into separate headers
+ */
+
+#ifndef __CPUPOWERUTILS_HELPERS__
+#define __CPUPOWERUTILS_HELPERS__
+
+#include <libintl.h>
+#include <locale.h>
+
+#include "helpers/bitmask.h"
+
+/* Internationalization ****************************/
+#ifdef NLS
+
+#define _(String) gettext(String)
+#ifndef gettext_noop
+#define gettext_noop(String) String
+#endif
+#define N_(String) gettext_noop(String)
+
+#else /* !NLS */
+
+#define _(String) String
+#define N_(String) String
+
+#endif
+/* Internationalization ****************************/
+
+extern int run_as_root;
+extern struct bitmask *cpus_chosen;
+
+/* Global verbose (-d) stuff *********************************/
+/*
+ * define DEBUG via global Makefile variable
+ * Debug output is sent to stderr, do:
+ * cpupower monitor 2>/tmp/debug
+ * to split debug output away from normal output
+*/
+#ifdef DEBUG
+extern int be_verbose;
+
+#define dprint(fmt, ...) { \
+ if (be_verbose) { \
+ fprintf(stderr, "%s: " fmt, \
+ __func__, ##__VA_ARGS__); \
+ } \
+ }
+#else
+static inline void dprint(const char *fmt, ...) { }
+#endif
+extern int be_verbose;
+/* Global verbose (-v) stuff *********************************/
+
+/* cpuid and cpuinfo helpers **************************/
+enum cpupower_cpu_vendor {X86_VENDOR_UNKNOWN = 0, X86_VENDOR_INTEL,
+ X86_VENDOR_AMD, X86_VENDOR_MAX};
+
+#define CPUPOWER_CAP_INV_TSC 0x00000001
+#define CPUPOWER_CAP_APERF 0x00000002
+#define CPUPOWER_CAP_AMD_CBP 0x00000004
+#define CPUPOWER_CAP_PERF_BIAS 0x00000008
+#define CPUPOWER_CAP_HAS_TURBO_RATIO 0x00000010
+#define CPUPOWER_CAP_IS_SNB 0x00000020
+#define CPUPOWER_CAP_INTEL_IDA 0x00000040
+
+#define MAX_HW_PSTATES 10
+
+struct cpupower_cpu_info {
+ enum cpupower_cpu_vendor vendor;
+ unsigned int family;
+ unsigned int model;
+ unsigned int stepping;
+ /* CPU capabilities read out from cpuid */
+ unsigned long long caps;
+};
+
+/* get_cpu_info
+ *
+ * Extract CPU vendor, family, model, stepping info from /proc/cpuinfo
+ *
+ * Returns 0 on success or a negativ error code
+ * Only used on x86, below global's struct values are zero/unknown on
+ * other archs
+ */
+extern int get_cpu_info(unsigned int cpu, struct cpupower_cpu_info *cpu_info);
+extern struct cpupower_cpu_info cpupower_cpu_info;
+/* cpuid and cpuinfo helpers **************************/
+
+struct cpuid_core_info {
+ int pkg;
+ int core;
+ int cpu;
+
+ /* flags */
+ unsigned int is_online:1;
+};
+
+/* CPU topology/hierarchy parsing ******************/
+struct cpupower_topology {
+ /* Amount of CPU cores, packages and threads per core in the system */
+ unsigned int cores;
+ unsigned int pkgs;
+ unsigned int threads; /* per core */
+
+ /* Array gets mallocated with cores entries, holding per core info */
+ struct cpuid_core_info *core_info;
+};
+
+extern int get_cpu_topology(struct cpupower_topology *cpu_top);
+extern void cpu_topology_release(struct cpupower_topology cpu_top);
+
+/* CPU topology/hierarchy parsing ******************/
+
+/* X86 ONLY ****************************************/
+#if defined(__i386__) || defined(__x86_64__)
+
+#include <pci/pci.h>
+
+/* Read/Write msr ****************************/
+extern int read_msr(int cpu, unsigned int idx, unsigned long long *val);
+extern int write_msr(int cpu, unsigned int idx, unsigned long long val);
+
+extern int msr_intel_set_perf_bias(unsigned int cpu, unsigned int val);
+extern int msr_intel_get_perf_bias(unsigned int cpu);
+extern unsigned long long msr_intel_get_turbo_ratio(unsigned int cpu);
+
+/* Read/Write msr ****************************/
+
+/* PCI stuff ****************************/
+extern int amd_pci_get_num_boost_states(int *active, int *states);
+extern struct pci_dev *pci_acc_init(struct pci_access **pacc, int domain,
+ int bus, int slot, int func, int vendor,
+ int dev);
+extern struct pci_dev *pci_slot_func_init(struct pci_access **pacc,
+ int slot, int func);
+
+/* PCI stuff ****************************/
+
+/* AMD HW pstate decoding **************************/
+
+extern int decode_pstates(unsigned int cpu, unsigned int cpu_family,
+ int boost_states, unsigned long *pstates, int *no);
+
+/* AMD HW pstate decoding **************************/
+
+extern int cpufreq_has_boost_support(unsigned int cpu, int *support,
+ int *active, int * states);
+/*
+ * CPUID functions returning a single datum
+ */
+unsigned int cpuid_eax(unsigned int op);
+unsigned int cpuid_ebx(unsigned int op);
+unsigned int cpuid_ecx(unsigned int op);
+unsigned int cpuid_edx(unsigned int op);
+
+/* cpuid and cpuinfo helpers **************************/
+/* X86 ONLY ********************************************/
+#else
+static inline int decode_pstates(unsigned int cpu, unsigned int cpu_family,
+ int boost_states, unsigned long *pstates,
+ int *no)
+{ return -1; };
+
+static inline int read_msr(int cpu, unsigned int idx, unsigned long long *val)
+{ return -1; };
+static inline int write_msr(int cpu, unsigned int idx, unsigned long long val)
+{ return -1; };
+static inline int msr_intel_set_perf_bias(unsigned int cpu, unsigned int val)
+{ return -1; };
+static inline int msr_intel_get_perf_bias(unsigned int cpu)
+{ return -1; };
+static inline unsigned long long msr_intel_get_turbo_ratio(unsigned int cpu)
+{ return 0; };
+
+/* Read/Write msr ****************************/
+
+static inline int cpufreq_has_boost_support(unsigned int cpu, int *support,
+ int *active, int * states)
+{ return -1; }
+
+/* cpuid and cpuinfo helpers **************************/
+
+static inline unsigned int cpuid_eax(unsigned int op) { return 0; };
+static inline unsigned int cpuid_ebx(unsigned int op) { return 0; };
+static inline unsigned int cpuid_ecx(unsigned int op) { return 0; };
+static inline unsigned int cpuid_edx(unsigned int op) { return 0; };
+#endif /* defined(__i386__) || defined(__x86_64__) */
+
+#endif /* __CPUPOWERUTILS_HELPERS__ */
diff --git a/tools/power/cpupower/utils/helpers/misc.c b/tools/power/cpupower/utils/helpers/misc.c
new file mode 100644
index 00000000000..1609243f5c6
--- /dev/null
+++ b/tools/power/cpupower/utils/helpers/misc.c
@@ -0,0 +1,27 @@
+#if defined(__i386__) || defined(__x86_64__)
+
+#include "helpers/helpers.h"
+
+int cpufreq_has_boost_support(unsigned int cpu, int *support, int *active,
+ int *states)
+{
+ struct cpupower_cpu_info cpu_info;
+ int ret;
+
+ *support = *active = *states = 0;
+
+ ret = get_cpu_info(0, &cpu_info);
+ if (ret)
+ return ret;
+
+ if (cpupower_cpu_info.caps & CPUPOWER_CAP_AMD_CBP) {
+ *support = 1;
+ amd_pci_get_num_boost_states(active, states);
+ if (ret <= 0)
+ return ret;
+ *support = 1;
+ } else if (cpupower_cpu_info.caps & CPUPOWER_CAP_INTEL_IDA)
+ *support = *active = 1;
+ return 0;
+}
+#endif /* #if defined(__i386__) || defined(__x86_64__) */
diff --git a/tools/power/cpupower/utils/helpers/msr.c b/tools/power/cpupower/utils/helpers/msr.c
new file mode 100644
index 00000000000..31a4b24a8bc
--- /dev/null
+++ b/tools/power/cpupower/utils/helpers/msr.c
@@ -0,0 +1,115 @@
+#if defined(__i386__) || defined(__x86_64__)
+
+#include <fcntl.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <stdint.h>
+
+#include "helpers/helpers.h"
+
+/* Intel specific MSRs */
+#define MSR_IA32_PERF_STATUS 0x198
+#define MSR_IA32_MISC_ENABLES 0x1a0
+#define MSR_IA32_ENERGY_PERF_BIAS 0x1b0
+#define MSR_NEHALEM_TURBO_RATIO_LIMIT 0x1ad
+
+/*
+ * read_msr
+ *
+ * Will return 0 on success and -1 on failure.
+ * Possible errno values could be:
+ * EFAULT -If the read/write did not fully complete
+ * EIO -If the CPU does not support MSRs
+ * ENXIO -If the CPU does not exist
+ */
+
+int read_msr(int cpu, unsigned int idx, unsigned long long *val)
+{
+ int fd;
+ char msr_file_name[64];
+
+ sprintf(msr_file_name, "/dev/cpu/%d/msr", cpu);
+ fd = open(msr_file_name, O_RDONLY);
+ if (fd < 0)
+ return -1;
+ if (lseek(fd, idx, SEEK_CUR) == -1)
+ goto err;
+ if (read(fd, val, sizeof *val) != sizeof *val)
+ goto err;
+ close(fd);
+ return 0;
+ err:
+ close(fd);
+ return -1;
+}
+
+/*
+ * write_msr
+ *
+ * Will return 0 on success and -1 on failure.
+ * Possible errno values could be:
+ * EFAULT -If the read/write did not fully complete
+ * EIO -If the CPU does not support MSRs
+ * ENXIO -If the CPU does not exist
+ */
+int write_msr(int cpu, unsigned int idx, unsigned long long val)
+{
+ int fd;
+ char msr_file_name[64];
+
+ sprintf(msr_file_name, "/dev/cpu/%d/msr", cpu);
+ fd = open(msr_file_name, O_WRONLY);
+ if (fd < 0)
+ return -1;
+ if (lseek(fd, idx, SEEK_CUR) == -1)
+ goto err;
+ if (write(fd, &val, sizeof val) != sizeof val)
+ goto err;
+ close(fd);
+ return 0;
+ err:
+ close(fd);
+ return -1;
+}
+
+int msr_intel_get_perf_bias(unsigned int cpu)
+{
+ unsigned long long val;
+ int ret;
+
+ if (!(cpupower_cpu_info.caps & CPUPOWER_CAP_PERF_BIAS))
+ return -1;
+
+ ret = read_msr(cpu, MSR_IA32_ENERGY_PERF_BIAS, &val);
+ if (ret)
+ return ret;
+ return val;
+}
+
+int msr_intel_set_perf_bias(unsigned int cpu, unsigned int val)
+{
+ int ret;
+
+ if (!(cpupower_cpu_info.caps & CPUPOWER_CAP_PERF_BIAS))
+ return -1;
+
+ ret = write_msr(cpu, MSR_IA32_ENERGY_PERF_BIAS, val);
+ if (ret)
+ return ret;
+ return 0;
+}
+
+unsigned long long msr_intel_get_turbo_ratio(unsigned int cpu)
+{
+ unsigned long long val;
+ int ret;
+
+ if (!(cpupower_cpu_info.caps & CPUPOWER_CAP_HAS_TURBO_RATIO))
+ return -1;
+
+ ret = read_msr(cpu, MSR_NEHALEM_TURBO_RATIO_LIMIT, &val);
+ if (ret)
+ return ret;
+ return val;
+}
+#endif
diff --git a/tools/power/cpupower/utils/helpers/pci.c b/tools/power/cpupower/utils/helpers/pci.c
new file mode 100644
index 00000000000..9690798e644
--- /dev/null
+++ b/tools/power/cpupower/utils/helpers/pci.c
@@ -0,0 +1,55 @@
+#if defined(__i386__) || defined(__x86_64__)
+
+#include <helpers/helpers.h>
+
+/*
+ * pci_acc_init
+ *
+ * PCI access helper function depending on libpci
+ *
+ * **pacc : if a valid pci_dev is returned
+ * *pacc must be passed to pci_acc_cleanup to free it
+ *
+ * domain: domain
+ * bus: bus
+ * slot: slot
+ * func: func
+ * vendor: vendor
+ * device: device
+ * Pass -1 for one of the six above to match any
+ *
+ * Returns :
+ * struct pci_dev which can be used with pci_{read,write}_* functions
+ * to access the PCI config space of matching pci devices
+ */
+struct pci_dev *pci_acc_init(struct pci_access **pacc, int domain, int bus,
+ int slot, int func, int vendor, int dev)
+{
+ struct pci_filter filter_nb_link = { domain, bus, slot, func,
+ vendor, dev };
+ struct pci_dev *device;
+
+ *pacc = pci_alloc();
+ if (*pacc == NULL)
+ return NULL;
+
+ pci_init(*pacc);
+ pci_scan_bus(*pacc);
+
+ for (device = (*pacc)->devices; device; device = device->next) {
+ if (pci_filter_match(&filter_nb_link, device))
+ return device;
+ }
+ pci_cleanup(*pacc);
+ return NULL;
+}
+
+/* Typically one wants to get a specific slot(device)/func of the root domain
+ and bus */
+struct pci_dev *pci_slot_func_init(struct pci_access **pacc, int slot,
+ int func)
+{
+ return pci_acc_init(pacc, 0, 0, slot, func, -1, -1);
+}
+
+#endif /* defined(__i386__) || defined(__x86_64__) */
diff --git a/tools/power/cpupower/utils/helpers/sysfs.c b/tools/power/cpupower/utils/helpers/sysfs.c
new file mode 100644
index 00000000000..851c7a16ca4
--- /dev/null
+++ b/tools/power/cpupower/utils/helpers/sysfs.c
@@ -0,0 +1,472 @@
+/*
+ * (C) 2004-2009 Dominik Brodowski <linux@dominikbrodowski.de>
+ * (C) 2011 Thomas Renninger <trenn@novell.com> Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ */
+
+#include <stdio.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+
+#include "helpers/sysfs.h"
+
+unsigned int sysfs_read_file(const char *path, char *buf, size_t buflen)
+{
+ int fd;
+ ssize_t numread;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return 0;
+
+ numread = read(fd, buf, buflen - 1);
+ if (numread < 1) {
+ close(fd);
+ return 0;
+ }
+
+ buf[numread] = '\0';
+ close(fd);
+
+ return (unsigned int) numread;
+}
+
+/*
+ * Detect whether a CPU is online
+ *
+ * Returns:
+ * 1 -> if CPU is online
+ * 0 -> if CPU is offline
+ * negative errno values in error case
+ */
+int sysfs_is_cpu_online(unsigned int cpu)
+{
+ char path[SYSFS_PATH_MAX];
+ int fd;
+ ssize_t numread;
+ unsigned long long value;
+ char linebuf[MAX_LINE_LEN];
+ char *endp;
+ struct stat statbuf;
+
+ snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u", cpu);
+
+ if (stat(path, &statbuf) != 0)
+ return 0;
+
+ /*
+ * kernel without CONFIG_HOTPLUG_CPU
+ * -> cpuX directory exists, but not cpuX/online file
+ */
+ snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/online", cpu);
+ if (stat(path, &statbuf) != 0)
+ return 1;
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return -errno;
+
+ numread = read(fd, linebuf, MAX_LINE_LEN - 1);
+ if (numread < 1) {
+ close(fd);
+ return -EIO;
+ }
+ linebuf[numread] = '\0';
+ close(fd);
+
+ value = strtoull(linebuf, &endp, 0);
+ if (value > 1 || value < 0)
+ return -EINVAL;
+
+ return value;
+}
+
+/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */
+
+
+/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */
+
+/*
+ * helper function to check whether a file under "../cpuX/cpuidle/stateX/" dir
+ * exists.
+ * For example the functionality to disable c-states was introduced in later
+ * kernel versions, this function can be used to explicitly check for this
+ * feature.
+ *
+ * returns 1 if the file exists, 0 otherwise.
+ */
+unsigned int sysfs_idlestate_file_exists(unsigned int cpu,
+ unsigned int idlestate,
+ const char *fname)
+{
+ char path[SYSFS_PATH_MAX];
+ struct stat statbuf;
+
+
+ snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s",
+ cpu, idlestate, fname);
+ if (stat(path, &statbuf) != 0)
+ return 0;
+ return 1;
+}
+
+/*
+ * helper function to read file from /sys into given buffer
+ * fname is a relative path under "cpuX/cpuidle/stateX/" dir
+ * cstates starting with 0, C0 is not counted as cstate.
+ * This means if you want C1 info, pass 0 as idlestate param
+ */
+unsigned int sysfs_idlestate_read_file(unsigned int cpu, unsigned int idlestate,
+ const char *fname, char *buf, size_t buflen)
+{
+ char path[SYSFS_PATH_MAX];
+ int fd;
+ ssize_t numread;
+
+ snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s",
+ cpu, idlestate, fname);
+
+ fd = open(path, O_RDONLY);
+ if (fd == -1)
+ return 0;
+
+ numread = read(fd, buf, buflen - 1);
+ if (numread < 1) {
+ close(fd);
+ return 0;
+ }
+
+ buf[numread] = '\0';
+ close(fd);
+
+ return (unsigned int) numread;
+}
+
+/*
+ * helper function to write a new value to a /sys file
+ * fname is a relative path under "../cpuX/cpuidle/cstateY/" dir
+ *
+ * Returns the number of bytes written or 0 on error
+ */
+static
+unsigned int sysfs_idlestate_write_file(unsigned int cpu,
+ unsigned int idlestate,
+ const char *fname,
+ const char *value, size_t len)
+{
+ char path[SYSFS_PATH_MAX];
+ int fd;
+ ssize_t numwrite;
+
+ snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/cpuidle/state%u/%s",
+ cpu, idlestate, fname);
+
+ fd = open(path, O_WRONLY);
+ if (fd == -1)
+ return 0;
+
+ numwrite = write(fd, value, len);
+ if (numwrite < 1) {
+ close(fd);
+ return 0;
+ }
+
+ close(fd);
+
+ return (unsigned int) numwrite;
+}
+
+/* read access to files which contain one numeric value */
+
+enum idlestate_value {
+ IDLESTATE_USAGE,
+ IDLESTATE_POWER,
+ IDLESTATE_LATENCY,
+ IDLESTATE_TIME,
+ IDLESTATE_DISABLE,
+ MAX_IDLESTATE_VALUE_FILES
+};
+
+static const char *idlestate_value_files[MAX_IDLESTATE_VALUE_FILES] = {
+ [IDLESTATE_USAGE] = "usage",
+ [IDLESTATE_POWER] = "power",
+ [IDLESTATE_LATENCY] = "latency",
+ [IDLESTATE_TIME] = "time",
+ [IDLESTATE_DISABLE] = "disable",
+};
+
+static unsigned long long sysfs_idlestate_get_one_value(unsigned int cpu,
+ unsigned int idlestate,
+ enum idlestate_value which)
+{
+ unsigned long long value;
+ unsigned int len;
+ char linebuf[MAX_LINE_LEN];
+ char *endp;
+
+ if (which >= MAX_IDLESTATE_VALUE_FILES)
+ return 0;
+
+ len = sysfs_idlestate_read_file(cpu, idlestate,
+ idlestate_value_files[which],
+ linebuf, sizeof(linebuf));
+ if (len == 0)
+ return 0;
+
+ value = strtoull(linebuf, &endp, 0);
+
+ if (endp == linebuf || errno == ERANGE)
+ return 0;
+
+ return value;
+}
+
+/* read access to files which contain one string */
+
+enum idlestate_string {
+ IDLESTATE_DESC,
+ IDLESTATE_NAME,
+ MAX_IDLESTATE_STRING_FILES
+};
+
+static const char *idlestate_string_files[MAX_IDLESTATE_STRING_FILES] = {
+ [IDLESTATE_DESC] = "desc",
+ [IDLESTATE_NAME] = "name",
+};
+
+
+static char *sysfs_idlestate_get_one_string(unsigned int cpu,
+ unsigned int idlestate,
+ enum idlestate_string which)
+{
+ char linebuf[MAX_LINE_LEN];
+ char *result;
+ unsigned int len;
+
+ if (which >= MAX_IDLESTATE_STRING_FILES)
+ return NULL;
+
+ len = sysfs_idlestate_read_file(cpu, idlestate,
+ idlestate_string_files[which],
+ linebuf, sizeof(linebuf));
+ if (len == 0)
+ return NULL;
+
+ result = strdup(linebuf);
+ if (result == NULL)
+ return NULL;
+
+ if (result[strlen(result) - 1] == '\n')
+ result[strlen(result) - 1] = '\0';
+
+ return result;
+}
+
+/*
+ * Returns:
+ * 1 if disabled
+ * 0 if enabled
+ * -1 if idlestate is not available
+ * -2 if disabling is not supported by the kernel
+ */
+int sysfs_is_idlestate_disabled(unsigned int cpu,
+ unsigned int idlestate)
+{
+ if (sysfs_get_idlestate_count(cpu) <= idlestate)
+ return -1;
+
+ if (!sysfs_idlestate_file_exists(cpu, idlestate,
+ idlestate_value_files[IDLESTATE_DISABLE]))
+ return -2;
+ return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_DISABLE);
+}
+
+/*
+ * Pass 1 as last argument to disable or 0 to enable the state
+ * Returns:
+ * 0 on success
+ * negative values on error, for example:
+ * -1 if idlestate is not available
+ * -2 if disabling is not supported by the kernel
+ * -3 No write access to disable/enable C-states
+ */
+int sysfs_idlestate_disable(unsigned int cpu,
+ unsigned int idlestate,
+ unsigned int disable)
+{
+ char value[SYSFS_PATH_MAX];
+ int bytes_written;
+
+ if (sysfs_get_idlestate_count(cpu) <= idlestate)
+ return -1;
+
+ if (!sysfs_idlestate_file_exists(cpu, idlestate,
+ idlestate_value_files[IDLESTATE_DISABLE]))
+ return -2;
+
+ snprintf(value, SYSFS_PATH_MAX, "%u", disable);
+
+ bytes_written = sysfs_idlestate_write_file(cpu, idlestate, "disable",
+ value, sizeof(disable));
+ if (bytes_written)
+ return 0;
+ return -3;
+}
+
+unsigned long sysfs_get_idlestate_latency(unsigned int cpu,
+ unsigned int idlestate)
+{
+ return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_LATENCY);
+}
+
+unsigned long sysfs_get_idlestate_usage(unsigned int cpu,
+ unsigned int idlestate)
+{
+ return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_USAGE);
+}
+
+unsigned long long sysfs_get_idlestate_time(unsigned int cpu,
+ unsigned int idlestate)
+{
+ return sysfs_idlestate_get_one_value(cpu, idlestate, IDLESTATE_TIME);
+}
+
+char *sysfs_get_idlestate_name(unsigned int cpu, unsigned int idlestate)
+{
+ return sysfs_idlestate_get_one_string(cpu, idlestate, IDLESTATE_NAME);
+}
+
+char *sysfs_get_idlestate_desc(unsigned int cpu, unsigned int idlestate)
+{
+ return sysfs_idlestate_get_one_string(cpu, idlestate, IDLESTATE_DESC);
+}
+
+/*
+ * Returns number of supported C-states of CPU core cpu
+ * Negativ in error case
+ * Zero if cpuidle does not export any C-states
+ */
+unsigned int sysfs_get_idlestate_count(unsigned int cpu)
+{
+ char file[SYSFS_PATH_MAX];
+ struct stat statbuf;
+ int idlestates = 1;
+
+
+ snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU "cpuidle");
+ if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
+ return -ENODEV;
+
+ snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU "cpu%u/cpuidle/state0", cpu);
+ if (stat(file, &statbuf) != 0 || !S_ISDIR(statbuf.st_mode))
+ return 0;
+
+ while (stat(file, &statbuf) == 0 && S_ISDIR(statbuf.st_mode)) {
+ snprintf(file, SYSFS_PATH_MAX, PATH_TO_CPU
+ "cpu%u/cpuidle/state%d", cpu, idlestates);
+ idlestates++;
+ }
+ idlestates--;
+ return idlestates;
+}
+
+/* CPUidle general /sys/devices/system/cpu/cpuidle/ sysfs access ********/
+
+/*
+ * helper function to read file from /sys into given buffer
+ * fname is a relative path under "cpu/cpuidle/" dir
+ */
+static unsigned int sysfs_cpuidle_read_file(const char *fname, char *buf,
+ size_t buflen)
+{
+ char path[SYSFS_PATH_MAX];
+
+ snprintf(path, sizeof(path), PATH_TO_CPU "cpuidle/%s", fname);
+
+ return sysfs_read_file(path, buf, buflen);
+}
+
+
+
+/* read access to files which contain one string */
+
+enum cpuidle_string {
+ CPUIDLE_GOVERNOR,
+ CPUIDLE_GOVERNOR_RO,
+ CPUIDLE_DRIVER,
+ MAX_CPUIDLE_STRING_FILES
+};
+
+static const char *cpuidle_string_files[MAX_CPUIDLE_STRING_FILES] = {
+ [CPUIDLE_GOVERNOR] = "current_governor",
+ [CPUIDLE_GOVERNOR_RO] = "current_governor_ro",
+ [CPUIDLE_DRIVER] = "current_driver",
+};
+
+
+static char *sysfs_cpuidle_get_one_string(enum cpuidle_string which)
+{
+ char linebuf[MAX_LINE_LEN];
+ char *result;
+ unsigned int len;
+
+ if (which >= MAX_CPUIDLE_STRING_FILES)
+ return NULL;
+
+ len = sysfs_cpuidle_read_file(cpuidle_string_files[which],
+ linebuf, sizeof(linebuf));
+ if (len == 0)
+ return NULL;
+
+ result = strdup(linebuf);
+ if (result == NULL)
+ return NULL;
+
+ if (result[strlen(result) - 1] == '\n')
+ result[strlen(result) - 1] = '\0';
+
+ return result;
+}
+
+char *sysfs_get_cpuidle_governor(void)
+{
+ char *tmp = sysfs_cpuidle_get_one_string(CPUIDLE_GOVERNOR_RO);
+ if (!tmp)
+ return sysfs_cpuidle_get_one_string(CPUIDLE_GOVERNOR);
+ else
+ return tmp;
+}
+
+char *sysfs_get_cpuidle_driver(void)
+{
+ return sysfs_cpuidle_get_one_string(CPUIDLE_DRIVER);
+}
+/* CPUidle idlestate specific /sys/devices/system/cpu/cpuX/cpuidle/ access */
+
+/*
+ * Get sched_mc or sched_smt settings
+ * Pass "mc" or "smt" as argument
+ *
+ * Returns negative value on failure
+ */
+int sysfs_get_sched(const char *smt_mc)
+{
+ return -ENODEV;
+}
+
+/*
+ * Get sched_mc or sched_smt settings
+ * Pass "mc" or "smt" as argument
+ *
+ * Returns negative value on failure
+ */
+int sysfs_set_sched(const char *smt_mc, int val)
+{
+ return -ENODEV;
+}
diff --git a/tools/power/cpupower/utils/helpers/sysfs.h b/tools/power/cpupower/utils/helpers/sysfs.h
new file mode 100644
index 00000000000..d28f11fedbd
--- /dev/null
+++ b/tools/power/cpupower/utils/helpers/sysfs.h
@@ -0,0 +1,38 @@
+#ifndef __CPUPOWER_HELPERS_SYSFS_H__
+#define __CPUPOWER_HELPERS_SYSFS_H__
+
+#define PATH_TO_CPU "/sys/devices/system/cpu/"
+#define MAX_LINE_LEN 255
+#define SYSFS_PATH_MAX 255
+
+extern unsigned int sysfs_read_file(const char *path, char *buf, size_t buflen);
+
+extern unsigned int sysfs_idlestate_file_exists(unsigned int cpu,
+ unsigned int idlestate,
+ const char *fname);
+
+extern int sysfs_is_cpu_online(unsigned int cpu);
+
+extern int sysfs_is_idlestate_disabled(unsigned int cpu,
+ unsigned int idlestate);
+extern int sysfs_idlestate_disable(unsigned int cpu, unsigned int idlestate,
+ unsigned int disable);
+extern unsigned long sysfs_get_idlestate_latency(unsigned int cpu,
+ unsigned int idlestate);
+extern unsigned long sysfs_get_idlestate_usage(unsigned int cpu,
+ unsigned int idlestate);
+extern unsigned long long sysfs_get_idlestate_time(unsigned int cpu,
+ unsigned int idlestate);
+extern char *sysfs_get_idlestate_name(unsigned int cpu,
+ unsigned int idlestate);
+extern char *sysfs_get_idlestate_desc(unsigned int cpu,
+ unsigned int idlestate);
+extern unsigned int sysfs_get_idlestate_count(unsigned int cpu);
+
+extern char *sysfs_get_cpuidle_governor(void);
+extern char *sysfs_get_cpuidle_driver(void);
+
+extern int sysfs_get_sched(const char *smt_mc);
+extern int sysfs_set_sched(const char *smt_mc, int val);
+
+#endif /* __CPUPOWER_HELPERS_SYSFS_H__ */
diff --git a/tools/power/cpupower/utils/helpers/topology.c b/tools/power/cpupower/utils/helpers/topology.c
new file mode 100644
index 00000000000..c13120af519
--- /dev/null
+++ b/tools/power/cpupower/utils/helpers/topology.c
@@ -0,0 +1,116 @@
+/*
+ * (C) 2010,2011 Thomas Renninger <trenn@suse.de>, Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ * ToDo: Needs to be done more properly for AMD/Intel specifics
+ */
+
+/* Helper struct for qsort, must be in sync with cpupower_topology.cpu_info */
+/* Be careful: Need to pass unsigned to the sort, so that offlined cores are
+ in the end, but double check for -1 for offlined cpus at other places */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fcntl.h>
+
+#include <helpers/helpers.h>
+#include <helpers/sysfs.h>
+
+/* returns -1 on failure, 0 on success */
+static int sysfs_topology_read_file(unsigned int cpu, const char *fname, int *result)
+{
+ char linebuf[MAX_LINE_LEN];
+ char *endp;
+ char path[SYSFS_PATH_MAX];
+
+ snprintf(path, sizeof(path), PATH_TO_CPU "cpu%u/topology/%s",
+ cpu, fname);
+ if (sysfs_read_file(path, linebuf, MAX_LINE_LEN) == 0)
+ return -1;
+ *result = strtol(linebuf, &endp, 0);
+ if (endp == linebuf || errno == ERANGE)
+ return -1;
+ return 0;
+}
+
+static int __compare(const void *t1, const void *t2)
+{
+ struct cpuid_core_info *top1 = (struct cpuid_core_info *)t1;
+ struct cpuid_core_info *top2 = (struct cpuid_core_info *)t2;
+ if (top1->pkg < top2->pkg)
+ return -1;
+ else if (top1->pkg > top2->pkg)
+ return 1;
+ else if (top1->core < top2->core)
+ return -1;
+ else if (top1->core > top2->core)
+ return 1;
+ else if (top1->cpu < top2->cpu)
+ return -1;
+ else if (top1->cpu > top2->cpu)
+ return 1;
+ else
+ return 0;
+}
+
+/*
+ * Returns amount of cpus, negative on error, cpu_top must be
+ * passed to cpu_topology_release to free resources
+ *
+ * Array is sorted after ->pkg, ->core, then ->cpu
+ */
+int get_cpu_topology(struct cpupower_topology *cpu_top)
+{
+ int cpu, last_pkg, cpus = sysconf(_SC_NPROCESSORS_CONF);
+
+ cpu_top->core_info = malloc(sizeof(struct cpuid_core_info) * cpus);
+ if (cpu_top->core_info == NULL)
+ return -ENOMEM;
+ cpu_top->pkgs = cpu_top->cores = 0;
+ for (cpu = 0; cpu < cpus; cpu++) {
+ cpu_top->core_info[cpu].cpu = cpu;
+ cpu_top->core_info[cpu].is_online = sysfs_is_cpu_online(cpu);
+ if(sysfs_topology_read_file(
+ cpu,
+ "physical_package_id",
+ &(cpu_top->core_info[cpu].pkg)) < 0)
+ return -1;
+ if(sysfs_topology_read_file(
+ cpu,
+ "core_id",
+ &(cpu_top->core_info[cpu].core)) < 0)
+ return -1;
+ }
+
+ qsort(cpu_top->core_info, cpus, sizeof(struct cpuid_core_info),
+ __compare);
+
+ /* Count the number of distinct pkgs values. This works
+ because the primary sort of the core_info struct was just
+ done by pkg value. */
+ last_pkg = cpu_top->core_info[0].pkg;
+ for(cpu = 1; cpu < cpus; cpu++) {
+ if(cpu_top->core_info[cpu].pkg != last_pkg) {
+ last_pkg = cpu_top->core_info[cpu].pkg;
+ cpu_top->pkgs++;
+ }
+ }
+ cpu_top->pkgs++;
+
+ /* Intel's cores count is not consecutively numbered, there may
+ * be a core_id of 3, but none of 2. Assume there always is 0
+ * Get amount of cores by counting duplicates in a package
+ for (cpu = 0; cpu_top->core_info[cpu].pkg = 0 && cpu < cpus; cpu++) {
+ if (cpu_top->core_info[cpu].core == 0)
+ cpu_top->cores++;
+ */
+ return cpus;
+}
+
+void cpu_topology_release(struct cpupower_topology cpu_top)
+{
+ free(cpu_top.core_info);
+}
diff --git a/tools/power/cpupower/utils/idle_monitor/amd_fam14h_idle.c b/tools/power/cpupower/utils/idle_monitor/amd_fam14h_idle.c
new file mode 100644
index 00000000000..2116df9ad83
--- /dev/null
+++ b/tools/power/cpupower/utils/idle_monitor/amd_fam14h_idle.c
@@ -0,0 +1,335 @@
+/*
+ * (C) 2010,2011 Thomas Renninger <trenn@suse.de>, Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ * PCI initialization based on example code from:
+ * Andreas Herrmann <andreas.herrmann3@amd.com>
+ */
+
+#if defined(__i386__) || defined(__x86_64__)
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <time.h>
+#include <string.h>
+
+#include <pci/pci.h>
+
+#include "idle_monitor/cpupower-monitor.h"
+#include "helpers/helpers.h"
+
+#define PCI_NON_PC0_OFFSET 0xb0
+#define PCI_PC1_OFFSET 0xb4
+#define PCI_PC6_OFFSET 0xb8
+
+#define PCI_MONITOR_ENABLE_REG 0xe0
+
+#define PCI_NON_PC0_ENABLE_BIT 0
+#define PCI_PC1_ENABLE_BIT 1
+#define PCI_PC6_ENABLE_BIT 2
+
+#define PCI_NBP1_STAT_OFFSET 0x98
+#define PCI_NBP1_ACTIVE_BIT 2
+#define PCI_NBP1_ENTERED_BIT 1
+
+#define PCI_NBP1_CAP_OFFSET 0x90
+#define PCI_NBP1_CAPABLE_BIT 31
+
+#define OVERFLOW_MS 343597 /* 32 bit register filled at 12500 HZ
+ (1 tick per 80ns) */
+
+enum amd_fam14h_states {NON_PC0 = 0, PC1, PC6, NBP1,
+ AMD_FAM14H_STATE_NUM};
+
+static int fam14h_get_count_percent(unsigned int self_id, double *percent,
+ unsigned int cpu);
+static int fam14h_nbp1_count(unsigned int id, unsigned long long *count,
+ unsigned int cpu);
+
+static cstate_t amd_fam14h_cstates[AMD_FAM14H_STATE_NUM] = {
+ {
+ .name = "!PC0",
+ .desc = N_("Package in sleep state (PC1 or deeper)"),
+ .id = NON_PC0,
+ .range = RANGE_PACKAGE,
+ .get_count_percent = fam14h_get_count_percent,
+ },
+ {
+ .name = "PC1",
+ .desc = N_("Processor Package C1"),
+ .id = PC1,
+ .range = RANGE_PACKAGE,
+ .get_count_percent = fam14h_get_count_percent,
+ },
+ {
+ .name = "PC6",
+ .desc = N_("Processor Package C6"),
+ .id = PC6,
+ .range = RANGE_PACKAGE,
+ .get_count_percent = fam14h_get_count_percent,
+ },
+ {
+ .name = "NBP1",
+ .desc = N_("North Bridge P1 boolean counter (returns 0 or 1)"),
+ .id = NBP1,
+ .range = RANGE_PACKAGE,
+ .get_count = fam14h_nbp1_count,
+ },
+};
+
+static struct pci_access *pci_acc;
+static struct pci_dev *amd_fam14h_pci_dev;
+static int nbp1_entered;
+
+struct timespec start_time;
+static unsigned long long timediff;
+
+#ifdef DEBUG
+struct timespec dbg_time;
+long dbg_timediff;
+#endif
+
+static unsigned long long *previous_count[AMD_FAM14H_STATE_NUM];
+static unsigned long long *current_count[AMD_FAM14H_STATE_NUM];
+
+static int amd_fam14h_get_pci_info(struct cstate *state,
+ unsigned int *pci_offset,
+ unsigned int *enable_bit,
+ unsigned int cpu)
+{
+ switch (state->id) {
+ case NON_PC0:
+ *enable_bit = PCI_NON_PC0_ENABLE_BIT;
+ *pci_offset = PCI_NON_PC0_OFFSET;
+ break;
+ case PC1:
+ *enable_bit = PCI_PC1_ENABLE_BIT;
+ *pci_offset = PCI_PC1_OFFSET;
+ break;
+ case PC6:
+ *enable_bit = PCI_PC6_ENABLE_BIT;
+ *pci_offset = PCI_PC6_OFFSET;
+ break;
+ case NBP1:
+ *enable_bit = PCI_NBP1_ENTERED_BIT;
+ *pci_offset = PCI_NBP1_STAT_OFFSET;
+ break;
+ default:
+ return -1;
+ };
+ return 0;
+}
+
+static int amd_fam14h_init(cstate_t *state, unsigned int cpu)
+{
+ int enable_bit, pci_offset, ret;
+ uint32_t val;
+
+ ret = amd_fam14h_get_pci_info(state, &pci_offset, &enable_bit, cpu);
+ if (ret)
+ return ret;
+
+ /* NBP1 needs extra treating -> write 1 to D18F6x98 bit 1 for init */
+ if (state->id == NBP1) {
+ val = pci_read_long(amd_fam14h_pci_dev, pci_offset);
+ val |= 1 << enable_bit;
+ val = pci_write_long(amd_fam14h_pci_dev, pci_offset, val);
+ return ret;
+ }
+
+ /* Enable monitor */
+ val = pci_read_long(amd_fam14h_pci_dev, PCI_MONITOR_ENABLE_REG);
+ dprint("Init %s: read at offset: 0x%x val: %u\n", state->name,
+ PCI_MONITOR_ENABLE_REG, (unsigned int) val);
+ val |= 1 << enable_bit;
+ pci_write_long(amd_fam14h_pci_dev, PCI_MONITOR_ENABLE_REG, val);
+
+ dprint("Init %s: offset: 0x%x enable_bit: %d - val: %u (%u)\n",
+ state->name, PCI_MONITOR_ENABLE_REG, enable_bit,
+ (unsigned int) val, cpu);
+
+ /* Set counter to zero */
+ pci_write_long(amd_fam14h_pci_dev, pci_offset, 0);
+ previous_count[state->id][cpu] = 0;
+
+ return 0;
+}
+
+static int amd_fam14h_disable(cstate_t *state, unsigned int cpu)
+{
+ int enable_bit, pci_offset, ret;
+ uint32_t val;
+
+ ret = amd_fam14h_get_pci_info(state, &pci_offset, &enable_bit, cpu);
+ if (ret)
+ return ret;
+
+ val = pci_read_long(amd_fam14h_pci_dev, pci_offset);
+ dprint("%s: offset: 0x%x %u\n", state->name, pci_offset, val);
+ if (state->id == NBP1) {
+ /* was the bit whether NBP1 got entered set? */
+ nbp1_entered = (val & (1 << PCI_NBP1_ACTIVE_BIT)) |
+ (val & (1 << PCI_NBP1_ENTERED_BIT));
+
+ dprint("NBP1 was %sentered - 0x%x - enable_bit: "
+ "%d - pci_offset: 0x%x\n",
+ nbp1_entered ? "" : "not ",
+ val, enable_bit, pci_offset);
+ return ret;
+ }
+ current_count[state->id][cpu] = val;
+
+ dprint("%s: Current - %llu (%u)\n", state->name,
+ current_count[state->id][cpu], cpu);
+ dprint("%s: Previous - %llu (%u)\n", state->name,
+ previous_count[state->id][cpu], cpu);
+
+ val = pci_read_long(amd_fam14h_pci_dev, PCI_MONITOR_ENABLE_REG);
+ val &= ~(1 << enable_bit);
+ pci_write_long(amd_fam14h_pci_dev, PCI_MONITOR_ENABLE_REG, val);
+
+ return 0;
+}
+
+static int fam14h_nbp1_count(unsigned int id, unsigned long long *count,
+ unsigned int cpu)
+{
+ if (id == NBP1) {
+ if (nbp1_entered)
+ *count = 1;
+ else
+ *count = 0;
+ return 0;
+ }
+ return -1;
+}
+static int fam14h_get_count_percent(unsigned int id, double *percent,
+ unsigned int cpu)
+{
+ unsigned long diff;
+
+ if (id >= AMD_FAM14H_STATE_NUM)
+ return -1;
+ /* residency count in 80ns -> divide through 12.5 to get us residency */
+ diff = current_count[id][cpu] - previous_count[id][cpu];
+
+ if (timediff == 0)
+ *percent = 0.0;
+ else
+ *percent = 100.0 * diff / timediff / 12.5;
+
+ dprint("Timediff: %llu - res~: %lu us - percent: %.2f %%\n",
+ timediff, diff * 10 / 125, *percent);
+
+ return 0;
+}
+
+static int amd_fam14h_start(void)
+{
+ int num, cpu;
+ clock_gettime(CLOCK_REALTIME, &start_time);
+ for (num = 0; num < AMD_FAM14H_STATE_NUM; num++) {
+ for (cpu = 0; cpu < cpu_count; cpu++)
+ amd_fam14h_init(&amd_fam14h_cstates[num], cpu);
+ }
+#ifdef DEBUG
+ clock_gettime(CLOCK_REALTIME, &dbg_time);
+ dbg_timediff = timespec_diff_us(start_time, dbg_time);
+ dprint("Enabling counters took: %lu us\n",
+ dbg_timediff);
+#endif
+ return 0;
+}
+
+static int amd_fam14h_stop(void)
+{
+ int num, cpu;
+ struct timespec end_time;
+
+ clock_gettime(CLOCK_REALTIME, &end_time);
+
+ for (num = 0; num < AMD_FAM14H_STATE_NUM; num++) {
+ for (cpu = 0; cpu < cpu_count; cpu++)
+ amd_fam14h_disable(&amd_fam14h_cstates[num], cpu);
+ }
+#ifdef DEBUG
+ clock_gettime(CLOCK_REALTIME, &dbg_time);
+ dbg_timediff = timespec_diff_us(end_time, dbg_time);
+ dprint("Disabling counters took: %lu ns\n", dbg_timediff);
+#endif
+ timediff = timespec_diff_us(start_time, end_time);
+ if (timediff / 1000 > OVERFLOW_MS)
+ print_overflow_err((unsigned int)timediff / 1000000,
+ OVERFLOW_MS / 1000);
+
+ return 0;
+}
+
+static int is_nbp1_capable(void)
+{
+ uint32_t val;
+ val = pci_read_long(amd_fam14h_pci_dev, PCI_NBP1_CAP_OFFSET);
+ return val & (1 << 31);
+}
+
+struct cpuidle_monitor *amd_fam14h_register(void)
+{
+ int num;
+
+ if (cpupower_cpu_info.vendor != X86_VENDOR_AMD)
+ return NULL;
+
+ if (cpupower_cpu_info.family == 0x14)
+ strncpy(amd_fam14h_monitor.name, "Fam_14h",
+ MONITOR_NAME_LEN - 1);
+ else if (cpupower_cpu_info.family == 0x12)
+ strncpy(amd_fam14h_monitor.name, "Fam_12h",
+ MONITOR_NAME_LEN - 1);
+ else
+ return NULL;
+
+ /* We do not alloc for nbp1 machine wide counter */
+ for (num = 0; num < AMD_FAM14H_STATE_NUM - 1; num++) {
+ previous_count[num] = calloc(cpu_count,
+ sizeof(unsigned long long));
+ current_count[num] = calloc(cpu_count,
+ sizeof(unsigned long long));
+ }
+
+ /* We need PCI device: Slot 18, Func 6, compare with BKDG
+ for fam 12h/14h */
+ amd_fam14h_pci_dev = pci_slot_func_init(&pci_acc, 0x18, 6);
+ if (amd_fam14h_pci_dev == NULL || pci_acc == NULL)
+ return NULL;
+
+ if (!is_nbp1_capable())
+ amd_fam14h_monitor.hw_states_num = AMD_FAM14H_STATE_NUM - 1;
+
+ amd_fam14h_monitor.name_len = strlen(amd_fam14h_monitor.name);
+ return &amd_fam14h_monitor;
+}
+
+static void amd_fam14h_unregister(void)
+{
+ int num;
+ for (num = 0; num < AMD_FAM14H_STATE_NUM - 1; num++) {
+ free(previous_count[num]);
+ free(current_count[num]);
+ }
+ pci_cleanup(pci_acc);
+}
+
+struct cpuidle_monitor amd_fam14h_monitor = {
+ .name = "",
+ .hw_states = amd_fam14h_cstates,
+ .hw_states_num = AMD_FAM14H_STATE_NUM,
+ .start = amd_fam14h_start,
+ .stop = amd_fam14h_stop,
+ .do_register = amd_fam14h_register,
+ .unregister = amd_fam14h_unregister,
+ .needs_root = 1,
+ .overflow_s = OVERFLOW_MS / 1000,
+};
+#endif /* #if defined(__i386__) || defined(__x86_64__) */
diff --git a/tools/power/cpupower/utils/idle_monitor/cpuidle_sysfs.c b/tools/power/cpupower/utils/idle_monitor/cpuidle_sysfs.c
new file mode 100644
index 00000000000..bcd22a1a397
--- /dev/null
+++ b/tools/power/cpupower/utils/idle_monitor/cpuidle_sysfs.c
@@ -0,0 +1,196 @@
+/*
+ * (C) 2010,2011 Thomas Renninger <trenn@suse.de>, Novell Inc
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <limits.h>
+
+#include "helpers/sysfs.h"
+#include "helpers/helpers.h"
+#include "idle_monitor/cpupower-monitor.h"
+
+#define CPUIDLE_STATES_MAX 10
+static cstate_t cpuidle_cstates[CPUIDLE_STATES_MAX];
+struct cpuidle_monitor cpuidle_sysfs_monitor;
+
+static unsigned long long **previous_count;
+static unsigned long long **current_count;
+struct timespec start_time;
+static unsigned long long timediff;
+
+static int cpuidle_get_count_percent(unsigned int id, double *percent,
+ unsigned int cpu)
+{
+ unsigned long long statediff = current_count[cpu][id]
+ - previous_count[cpu][id];
+ dprint("%s: - diff: %llu - percent: %f (%u)\n",
+ cpuidle_cstates[id].name, timediff, *percent, cpu);
+
+ if (timediff == 0)
+ *percent = 0.0;
+ else
+ *percent = ((100.0 * statediff) / timediff);
+
+ dprint("%s: - timediff: %llu - statediff: %llu - percent: %f (%u)\n",
+ cpuidle_cstates[id].name, timediff, statediff, *percent, cpu);
+
+ return 0;
+}
+
+static int cpuidle_start(void)
+{
+ int cpu, state;
+ clock_gettime(CLOCK_REALTIME, &start_time);
+ for (cpu = 0; cpu < cpu_count; cpu++) {
+ for (state = 0; state < cpuidle_sysfs_monitor.hw_states_num;
+ state++) {
+ previous_count[cpu][state] =
+ sysfs_get_idlestate_time(cpu, state);
+ dprint("CPU %d - State: %d - Val: %llu\n",
+ cpu, state, previous_count[cpu][state]);
+ }
+ };
+ return 0;
+}
+
+static int cpuidle_stop(void)
+{
+ int cpu, state;
+ struct timespec end_time;
+ clock_gettime(CLOCK_REALTIME, &end_time);
+ timediff = timespec_diff_us(start_time, end_time);
+
+ for (cpu = 0; cpu < cpu_count; cpu++) {
+ for (state = 0; state < cpuidle_sysfs_monitor.hw_states_num;
+ state++) {
+ current_count[cpu][state] =
+ sysfs_get_idlestate_time(cpu, state);
+ dprint("CPU %d - State: %d - Val: %llu\n",
+ cpu, state, previous_count[cpu][state]);
+ }
+ };
+ return 0;
+}
+
+void fix_up_intel_idle_driver_name(char *tmp, int num)
+{
+ /* fix up cpuidle name for intel idle driver */
+ if (!strncmp(tmp, "NHM-", 4)) {
+ switch (num) {
+ case 1:
+ strcpy(tmp, "C1");
+ break;
+ case 2:
+ strcpy(tmp, "C3");
+ break;
+ case 3:
+ strcpy(tmp, "C6");
+ break;
+ }
+ } else if (!strncmp(tmp, "SNB-", 4)) {
+ switch (num) {
+ case 1:
+ strcpy(tmp, "C1");
+ break;
+ case 2:
+ strcpy(tmp, "C3");
+ break;
+ case 3:
+ strcpy(tmp, "C6");
+ break;
+ case 4:
+ strcpy(tmp, "C7");
+ break;
+ }
+ } else if (!strncmp(tmp, "ATM-", 4)) {
+ switch (num) {
+ case 1:
+ strcpy(tmp, "C1");
+ break;
+ case 2:
+ strcpy(tmp, "C2");
+ break;
+ case 3:
+ strcpy(tmp, "C4");
+ break;
+ case 4:
+ strcpy(tmp, "C6");
+ break;
+ }
+ }
+}
+
+static struct cpuidle_monitor *cpuidle_register(void)
+{
+ int num;
+ char *tmp;
+
+ /* Assume idle state count is the same for all CPUs */
+ cpuidle_sysfs_monitor.hw_states_num = sysfs_get_idlestate_count(0);
+
+ if (cpuidle_sysfs_monitor.hw_states_num <= 0)
+ return NULL;
+
+ for (num = 0; num < cpuidle_sysfs_monitor.hw_states_num; num++) {
+ tmp = sysfs_get_idlestate_name(0, num);
+ if (tmp == NULL)
+ continue;
+
+ fix_up_intel_idle_driver_name(tmp, num);
+ strncpy(cpuidle_cstates[num].name, tmp, CSTATE_NAME_LEN - 1);
+ free(tmp);
+
+ tmp = sysfs_get_idlestate_desc(0, num);
+ if (tmp == NULL)
+ continue;
+ strncpy(cpuidle_cstates[num].desc, tmp, CSTATE_DESC_LEN - 1);
+ free(tmp);
+
+ cpuidle_cstates[num].range = RANGE_THREAD;
+ cpuidle_cstates[num].id = num;
+ cpuidle_cstates[num].get_count_percent =
+ cpuidle_get_count_percent;
+ };
+
+ /* Free this at program termination */
+ previous_count = malloc(sizeof(long long *) * cpu_count);
+ current_count = malloc(sizeof(long long *) * cpu_count);
+ for (num = 0; num < cpu_count; num++) {
+ previous_count[num] = malloc(sizeof(long long) *
+ cpuidle_sysfs_monitor.hw_states_num);
+ current_count[num] = malloc(sizeof(long long) *
+ cpuidle_sysfs_monitor.hw_states_num);
+ }
+
+ cpuidle_sysfs_monitor.name_len = strlen(cpuidle_sysfs_monitor.name);
+ return &cpuidle_sysfs_monitor;
+}
+
+void cpuidle_unregister(void)
+{
+ int num;
+
+ for (num = 0; num < cpu_count; num++) {
+ free(previous_count[num]);
+ free(current_count[num]);
+ }
+ free(previous_count);
+ free(current_count);
+}
+
+struct cpuidle_monitor cpuidle_sysfs_monitor = {
+ .name = "Idle_Stats",
+ .hw_states = cpuidle_cstates,
+ .start = cpuidle_start,
+ .stop = cpuidle_stop,
+ .do_register = cpuidle_register,
+ .unregister = cpuidle_unregister,
+ .needs_root = 0,
+ .overflow_s = UINT_MAX,
+};
diff --git a/tools/power/cpupower/utils/idle_monitor/cpupower-monitor.c b/tools/power/cpupower/utils/idle_monitor/cpupower-monitor.c
new file mode 100644
index 00000000000..c4bae9203a6
--- /dev/null
+++ b/tools/power/cpupower/utils/idle_monitor/cpupower-monitor.c
@@ -0,0 +1,455 @@
+/*
+ * (C) 2010,2011 Thomas Renninger <trenn@suse.de>, Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ * Output format inspired by Len Brown's <lenb@kernel.org> turbostat tool.
+ *
+ */
+
+
+#include <stdio.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <string.h>
+#include <time.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <libgen.h>
+
+#include "idle_monitor/cpupower-monitor.h"
+#include "idle_monitor/idle_monitors.h"
+#include "helpers/helpers.h"
+
+/* Define pointers to all monitors. */
+#define DEF(x) & x ## _monitor ,
+struct cpuidle_monitor *all_monitors[] = {
+#include "idle_monitors.def"
+0
+};
+
+static struct cpuidle_monitor *monitors[MONITORS_MAX];
+static unsigned int avail_monitors;
+
+static char *progname;
+
+enum operation_mode_e { list = 1, show, show_all };
+static int mode;
+static int interval = 1;
+static char *show_monitors_param;
+static struct cpupower_topology cpu_top;
+static unsigned int wake_cpus;
+
+/* ToDo: Document this in the manpage */
+static char range_abbr[RANGE_MAX] = { 'T', 'C', 'P', 'M', };
+
+static void print_wrong_arg_exit(void)
+{
+ printf(_("invalid or unknown argument\n"));
+ exit(EXIT_FAILURE);
+}
+
+long long timespec_diff_us(struct timespec start, struct timespec end)
+{
+ struct timespec temp;
+ if ((end.tv_nsec - start.tv_nsec) < 0) {
+ temp.tv_sec = end.tv_sec - start.tv_sec - 1;
+ temp.tv_nsec = 1000000000 + end.tv_nsec - start.tv_nsec;
+ } else {
+ temp.tv_sec = end.tv_sec - start.tv_sec;
+ temp.tv_nsec = end.tv_nsec - start.tv_nsec;
+ }
+ return (temp.tv_sec * 1000000) + (temp.tv_nsec / 1000);
+}
+
+void print_n_spaces(int n)
+{
+ int x;
+ for (x = 0; x < n; x++)
+ printf(" ");
+}
+
+/* size of s must be at least n + 1 */
+int fill_string_with_spaces(char *s, int n)
+{
+ int len = strlen(s);
+ if (len > n)
+ return -1;
+ for (; len < n; len++)
+ s[len] = ' ';
+ s[len] = '\0';
+ return 0;
+}
+
+void print_header(int topology_depth)
+{
+ int unsigned mon;
+ int state, need_len;
+ cstate_t s;
+ char buf[128] = "";
+ int percent_width = 4;
+
+ fill_string_with_spaces(buf, topology_depth * 5 - 1);
+ printf("%s|", buf);
+
+ for (mon = 0; mon < avail_monitors; mon++) {
+ need_len = monitors[mon]->hw_states_num * (percent_width + 3)
+ - 1;
+ if (mon != 0) {
+ printf("|| ");
+ need_len--;
+ }
+ sprintf(buf, "%s", monitors[mon]->name);
+ fill_string_with_spaces(buf, need_len);
+ printf("%s", buf);
+ }
+ printf("\n");
+
+ if (topology_depth > 2)
+ printf("PKG |");
+ if (topology_depth > 1)
+ printf("CORE|");
+ if (topology_depth > 0)
+ printf("CPU |");
+
+ for (mon = 0; mon < avail_monitors; mon++) {
+ if (mon != 0)
+ printf("|| ");
+ else
+ printf(" ");
+ for (state = 0; state < monitors[mon]->hw_states_num; state++) {
+ if (state != 0)
+ printf(" | ");
+ s = monitors[mon]->hw_states[state];
+ sprintf(buf, "%s", s.name);
+ fill_string_with_spaces(buf, percent_width);
+ printf("%s", buf);
+ }
+ printf(" ");
+ }
+ printf("\n");
+}
+
+
+void print_results(int topology_depth, int cpu)
+{
+ unsigned int mon;
+ int state, ret;
+ double percent;
+ unsigned long long result;
+ cstate_t s;
+
+ /* Be careful CPUs may got resorted for pkg value do not just use cpu */
+ if (!bitmask_isbitset(cpus_chosen, cpu_top.core_info[cpu].cpu))
+ return;
+
+ if (topology_depth > 2)
+ printf("%4d|", cpu_top.core_info[cpu].pkg);
+ if (topology_depth > 1)
+ printf("%4d|", cpu_top.core_info[cpu].core);
+ if (topology_depth > 0)
+ printf("%4d|", cpu_top.core_info[cpu].cpu);
+
+ for (mon = 0; mon < avail_monitors; mon++) {
+ if (mon != 0)
+ printf("||");
+
+ for (state = 0; state < monitors[mon]->hw_states_num; state++) {
+ if (state != 0)
+ printf("|");
+
+ s = monitors[mon]->hw_states[state];
+
+ if (s.get_count_percent) {
+ ret = s.get_count_percent(s.id, &percent,
+ cpu_top.core_info[cpu].cpu);
+ if (ret)
+ printf("******");
+ else if (percent >= 100.0)
+ printf("%6.1f", percent);
+ else
+ printf("%6.2f", percent);
+ } else if (s.get_count) {
+ ret = s.get_count(s.id, &result,
+ cpu_top.core_info[cpu].cpu);
+ if (ret)
+ printf("******");
+ else
+ printf("%6llu", result);
+ } else {
+ printf(_("Monitor %s, Counter %s has no count "
+ "function. Implementation error\n"),
+ monitors[mon]->name, s.name);
+ exit(EXIT_FAILURE);
+ }
+ }
+ }
+ /*
+ * The monitor could still provide useful data, for example
+ * AMD HW counters partly sit in PCI config space.
+ * It's up to the monitor plug-in to check .is_online, this one
+ * is just for additional info.
+ */
+ if (!cpu_top.core_info[cpu].is_online) {
+ printf(_(" *is offline\n"));
+ return;
+ } else
+ printf("\n");
+}
+
+
+/* param: string passed by -m param (The list of monitors to show)
+ *
+ * Monitors must have been registered already, matching monitors
+ * are picked out and available monitors array is overridden
+ * with matching ones
+ *
+ * Monitors get sorted in the same order the user passes them
+*/
+
+static void parse_monitor_param(char *param)
+{
+ unsigned int num;
+ int mon, hits = 0;
+ char *tmp = param, *token;
+ struct cpuidle_monitor *tmp_mons[MONITORS_MAX];
+
+
+ for (mon = 0; mon < MONITORS_MAX; mon++, tmp = NULL) {
+ token = strtok(tmp, ",");
+ if (token == NULL)
+ break;
+ if (strlen(token) >= MONITOR_NAME_LEN) {
+ printf(_("%s: max monitor name length"
+ " (%d) exceeded\n"), token, MONITOR_NAME_LEN);
+ continue;
+ }
+
+ for (num = 0; num < avail_monitors; num++) {
+ if (!strcmp(monitors[num]->name, token)) {
+ dprint("Found requested monitor: %s\n", token);
+ tmp_mons[hits] = monitors[num];
+ hits++;
+ }
+ }
+ }
+ if (hits == 0) {
+ printf(_("No matching monitor found in %s, "
+ "try -l option\n"), param);
+ exit(EXIT_FAILURE);
+ }
+ /* Override detected/registerd monitors array with requested one */
+ memcpy(monitors, tmp_mons,
+ sizeof(struct cpuidle_monitor *) * MONITORS_MAX);
+ avail_monitors = hits;
+}
+
+void list_monitors(void)
+{
+ unsigned int mon;
+ int state;
+ cstate_t s;
+
+ for (mon = 0; mon < avail_monitors; mon++) {
+ printf(_("Monitor \"%s\" (%d states) - Might overflow after %u "
+ "s\n"),
+ monitors[mon]->name, monitors[mon]->hw_states_num,
+ monitors[mon]->overflow_s);
+
+ for (state = 0; state < monitors[mon]->hw_states_num; state++) {
+ s = monitors[mon]->hw_states[state];
+ /*
+ * ToDo show more state capabilities:
+ * percent, time (granlarity)
+ */
+ printf("%s\t[%c] -> %s\n", s.name, range_abbr[s.range],
+ gettext(s.desc));
+ }
+ }
+}
+
+int fork_it(char **argv)
+{
+ int status;
+ unsigned int num;
+ unsigned long long timediff;
+ pid_t child_pid;
+ struct timespec start, end;
+
+ child_pid = fork();
+ clock_gettime(CLOCK_REALTIME, &start);
+
+ for (num = 0; num < avail_monitors; num++)
+ monitors[num]->start();
+
+ if (!child_pid) {
+ /* child */
+ execvp(argv[0], argv);
+ } else {
+ /* parent */
+ if (child_pid == -1) {
+ perror("fork");
+ exit(1);
+ }
+
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ if (waitpid(child_pid, &status, 0) == -1) {
+ perror("wait");
+ exit(1);
+ }
+ }
+ clock_gettime(CLOCK_REALTIME, &end);
+ for (num = 0; num < avail_monitors; num++)
+ monitors[num]->stop();
+
+ timediff = timespec_diff_us(start, end);
+ if (WIFEXITED(status))
+ printf(_("%s took %.5f seconds and exited with status %d\n"),
+ argv[0], timediff / (1000.0 * 1000),
+ WEXITSTATUS(status));
+ return 0;
+}
+
+int do_interval_measure(int i)
+{
+ unsigned int num;
+ int cpu;
+
+ if (wake_cpus)
+ for (cpu = 0; cpu < cpu_count; cpu++)
+ bind_cpu(cpu);
+
+ for (num = 0; num < avail_monitors; num++) {
+ dprint("HW C-state residency monitor: %s - States: %d\n",
+ monitors[num]->name, monitors[num]->hw_states_num);
+ monitors[num]->start();
+ }
+
+ sleep(i);
+
+ if (wake_cpus)
+ for (cpu = 0; cpu < cpu_count; cpu++)
+ bind_cpu(cpu);
+
+ for (num = 0; num < avail_monitors; num++)
+ monitors[num]->stop();
+
+
+ return 0;
+}
+
+static void cmdline(int argc, char *argv[])
+{
+ int opt;
+ progname = basename(argv[0]);
+
+ while ((opt = getopt(argc, argv, "+lci:m:")) != -1) {
+ switch (opt) {
+ case 'l':
+ if (mode)
+ print_wrong_arg_exit();
+ mode = list;
+ break;
+ case 'i':
+ /* only allow -i with -m or no option */
+ if (mode && mode != show)
+ print_wrong_arg_exit();
+ interval = atoi(optarg);
+ break;
+ case 'm':
+ if (mode)
+ print_wrong_arg_exit();
+ mode = show;
+ show_monitors_param = optarg;
+ break;
+ case 'c':
+ wake_cpus = 1;
+ break;
+ default:
+ print_wrong_arg_exit();
+ }
+ }
+ if (!mode)
+ mode = show_all;
+}
+
+int cmd_monitor(int argc, char **argv)
+{
+ unsigned int num;
+ struct cpuidle_monitor *test_mon;
+ int cpu;
+
+ cmdline(argc, argv);
+ cpu_count = get_cpu_topology(&cpu_top);
+ if (cpu_count < 0) {
+ printf(_("Cannot read number of available processors\n"));
+ return EXIT_FAILURE;
+ }
+
+ /* Default is: monitor all CPUs */
+ if (bitmask_isallclear(cpus_chosen))
+ bitmask_setall(cpus_chosen);
+
+ dprint("System has up to %d CPU cores\n", cpu_count);
+
+ for (num = 0; all_monitors[num]; num++) {
+ dprint("Try to register: %s\n", all_monitors[num]->name);
+ test_mon = all_monitors[num]->do_register();
+ if (test_mon) {
+ if (test_mon->needs_root && !run_as_root) {
+ fprintf(stderr, _("Available monitor %s needs "
+ "root access\n"), test_mon->name);
+ continue;
+ }
+ monitors[avail_monitors] = test_mon;
+ dprint("%s registered\n", all_monitors[num]->name);
+ avail_monitors++;
+ }
+ }
+
+ if (avail_monitors == 0) {
+ printf(_("No HW Cstate monitors found\n"));
+ return 1;
+ }
+
+ if (mode == list) {
+ list_monitors();
+ exit(EXIT_SUCCESS);
+ }
+
+ if (mode == show)
+ parse_monitor_param(show_monitors_param);
+
+ dprint("Packages: %d - Cores: %d - CPUs: %d\n",
+ cpu_top.pkgs, cpu_top.cores, cpu_count);
+
+ /*
+ * if any params left, it must be a command to fork
+ */
+ if (argc - optind)
+ fork_it(argv + optind);
+ else
+ do_interval_measure(interval);
+
+ /* ToDo: Topology parsing needs fixing first to do
+ this more generically */
+ if (cpu_top.pkgs > 1)
+ print_header(3);
+ else
+ print_header(1);
+
+ for (cpu = 0; cpu < cpu_count; cpu++) {
+ if (cpu_top.pkgs > 1)
+ print_results(3, cpu);
+ else
+ print_results(1, cpu);
+ }
+
+ for (num = 0; num < avail_monitors; num++)
+ monitors[num]->unregister();
+
+ cpu_topology_release(cpu_top);
+ return 0;
+}
diff --git a/tools/power/cpupower/utils/idle_monitor/cpupower-monitor.h b/tools/power/cpupower/utils/idle_monitor/cpupower-monitor.h
new file mode 100644
index 00000000000..9e43f3371fb
--- /dev/null
+++ b/tools/power/cpupower/utils/idle_monitor/cpupower-monitor.h
@@ -0,0 +1,85 @@
+/*
+ * (C) 2010,2011 Thomas Renninger <trenn@suse.de>, Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ */
+
+#ifndef __CPUIDLE_INFO_HW__
+#define __CPUIDLE_INFO_HW__
+
+#include <stdarg.h>
+#include <time.h>
+
+#include "idle_monitor/idle_monitors.h"
+
+#define MONITORS_MAX 20
+#define MONITOR_NAME_LEN 20
+#define CSTATE_NAME_LEN 5
+#define CSTATE_DESC_LEN 60
+
+int cpu_count;
+
+/* Hard to define the right names ...: */
+enum power_range_e {
+ RANGE_THREAD, /* Lowest in topology hierarcy, AMD: core, Intel: thread
+ kernel sysfs: cpu */
+ RANGE_CORE, /* AMD: unit, Intel: core, kernel_sysfs: core_id */
+ RANGE_PACKAGE, /* Package, processor socket */
+ RANGE_MACHINE, /* Machine, platform wide */
+ RANGE_MAX };
+
+typedef struct cstate {
+ int id;
+ enum power_range_e range;
+ char name[CSTATE_NAME_LEN];
+ char desc[CSTATE_DESC_LEN];
+
+ /* either provide a percentage or a general count */
+ int (*get_count_percent)(unsigned int self_id, double *percent,
+ unsigned int cpu);
+ int (*get_count)(unsigned int self_id, unsigned long long *count,
+ unsigned int cpu);
+} cstate_t;
+
+struct cpuidle_monitor {
+ /* Name must not contain whitespaces */
+ char name[MONITOR_NAME_LEN];
+ int name_len;
+ int hw_states_num;
+ cstate_t *hw_states;
+ int (*start) (void);
+ int (*stop) (void);
+ struct cpuidle_monitor* (*do_register) (void);
+ void (*unregister)(void);
+ unsigned int overflow_s;
+ int needs_root;
+};
+
+extern long long timespec_diff_us(struct timespec start, struct timespec end);
+
+#define print_overflow_err(mes, ov) \
+{ \
+ fprintf(stderr, gettext("Measure took %u seconds, but registers could " \
+ "overflow at %u seconds, results " \
+ "could be inaccurate\n"), mes, ov); \
+}
+
+
+/* Taken over from x86info project sources -> return 0 on success */
+#include <sched.h>
+#include <sys/types.h>
+#include <unistd.h>
+static inline int bind_cpu(int cpu)
+{
+ cpu_set_t set;
+
+ if (sched_getaffinity(getpid(), sizeof(set), &set) == 0) {
+ CPU_ZERO(&set);
+ CPU_SET(cpu, &set);
+ return sched_setaffinity(getpid(), sizeof(set), &set);
+ }
+ return 1;
+}
+
+#endif /* __CPUIDLE_INFO_HW__ */
diff --git a/tools/power/cpupower/utils/idle_monitor/hsw_ext_idle.c b/tools/power/cpupower/utils/idle_monitor/hsw_ext_idle.c
new file mode 100644
index 00000000000..ebeaba6571a
--- /dev/null
+++ b/tools/power/cpupower/utils/idle_monitor/hsw_ext_idle.c
@@ -0,0 +1,196 @@
+/*
+ * (C) 2010,2011 Thomas Renninger <trenn@suse.de>, Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ * Based on SandyBridge monitor. Implements the new package C-states
+ * (PC8, PC9, PC10) coming with a specific Haswell (family 0x45) CPU.
+ */
+
+#if defined(__i386__) || defined(__x86_64__)
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "helpers/helpers.h"
+#include "idle_monitor/cpupower-monitor.h"
+
+#define MSR_PKG_C8_RESIDENCY 0x00000630
+#define MSR_PKG_C9_RESIDENCY 0x00000631
+#define MSR_PKG_C10_RESIDENCY 0x00000632
+
+#define MSR_TSC 0x10
+
+enum intel_hsw_ext_id { PC8 = 0, PC9, PC10, HSW_EXT_CSTATE_COUNT,
+ TSC = 0xFFFF };
+
+static int hsw_ext_get_count_percent(unsigned int self_id, double *percent,
+ unsigned int cpu);
+
+static cstate_t hsw_ext_cstates[HSW_EXT_CSTATE_COUNT] = {
+ {
+ .name = "PC8",
+ .desc = N_("Processor Package C8"),
+ .id = PC8,
+ .range = RANGE_PACKAGE,
+ .get_count_percent = hsw_ext_get_count_percent,
+ },
+ {
+ .name = "PC9",
+ .desc = N_("Processor Package C9"),
+ .desc = N_("Processor Package C2"),
+ .id = PC9,
+ .range = RANGE_PACKAGE,
+ .get_count_percent = hsw_ext_get_count_percent,
+ },
+ {
+ .name = "PC10",
+ .desc = N_("Processor Package C10"),
+ .id = PC10,
+ .range = RANGE_PACKAGE,
+ .get_count_percent = hsw_ext_get_count_percent,
+ },
+};
+
+static unsigned long long tsc_at_measure_start;
+static unsigned long long tsc_at_measure_end;
+static unsigned long long *previous_count[HSW_EXT_CSTATE_COUNT];
+static unsigned long long *current_count[HSW_EXT_CSTATE_COUNT];
+/* valid flag for all CPUs. If a MSR read failed it will be zero */
+static int *is_valid;
+
+static int hsw_ext_get_count(enum intel_hsw_ext_id id, unsigned long long *val,
+ unsigned int cpu)
+{
+ int msr;
+
+ switch (id) {
+ case PC8:
+ msr = MSR_PKG_C8_RESIDENCY;
+ break;
+ case PC9:
+ msr = MSR_PKG_C9_RESIDENCY;
+ break;
+ case PC10:
+ msr = MSR_PKG_C10_RESIDENCY;
+ break;
+ case TSC:
+ msr = MSR_TSC;
+ break;
+ default:
+ return -1;
+ };
+ if (read_msr(cpu, msr, val))
+ return -1;
+ return 0;
+}
+
+static int hsw_ext_get_count_percent(unsigned int id, double *percent,
+ unsigned int cpu)
+{
+ *percent = 0.0;
+
+ if (!is_valid[cpu])
+ return -1;
+
+ *percent = (100.0 *
+ (current_count[id][cpu] - previous_count[id][cpu])) /
+ (tsc_at_measure_end - tsc_at_measure_start);
+
+ dprint("%s: previous: %llu - current: %llu - (%u)\n",
+ hsw_ext_cstates[id].name, previous_count[id][cpu],
+ current_count[id][cpu], cpu);
+
+ dprint("%s: tsc_diff: %llu - count_diff: %llu - percent: %2.f (%u)\n",
+ hsw_ext_cstates[id].name,
+ (unsigned long long) tsc_at_measure_end - tsc_at_measure_start,
+ current_count[id][cpu] - previous_count[id][cpu],
+ *percent, cpu);
+
+ return 0;
+}
+
+static int hsw_ext_start(void)
+{
+ int num, cpu;
+ unsigned long long val;
+
+ for (num = 0; num < HSW_EXT_CSTATE_COUNT; num++) {
+ for (cpu = 0; cpu < cpu_count; cpu++) {
+ hsw_ext_get_count(num, &val, cpu);
+ previous_count[num][cpu] = val;
+ }
+ }
+ hsw_ext_get_count(TSC, &tsc_at_measure_start, 0);
+ return 0;
+}
+
+static int hsw_ext_stop(void)
+{
+ unsigned long long val;
+ int num, cpu;
+
+ hsw_ext_get_count(TSC, &tsc_at_measure_end, 0);
+
+ for (num = 0; num < HSW_EXT_CSTATE_COUNT; num++) {
+ for (cpu = 0; cpu < cpu_count; cpu++) {
+ is_valid[cpu] = !hsw_ext_get_count(num, &val, cpu);
+ current_count[num][cpu] = val;
+ }
+ }
+ return 0;
+}
+
+struct cpuidle_monitor intel_hsw_ext_monitor;
+
+static struct cpuidle_monitor *hsw_ext_register(void)
+{
+ int num;
+
+ if (cpupower_cpu_info.vendor != X86_VENDOR_INTEL
+ || cpupower_cpu_info.family != 6)
+ return NULL;
+
+ switch (cpupower_cpu_info.model) {
+ case 0x45: /* HSW */
+ break;
+ default:
+ return NULL;
+ }
+
+ is_valid = calloc(cpu_count, sizeof(int));
+ for (num = 0; num < HSW_EXT_CSTATE_COUNT; num++) {
+ previous_count[num] = calloc(cpu_count,
+ sizeof(unsigned long long));
+ current_count[num] = calloc(cpu_count,
+ sizeof(unsigned long long));
+ }
+ intel_hsw_ext_monitor.name_len = strlen(intel_hsw_ext_monitor.name);
+ return &intel_hsw_ext_monitor;
+}
+
+void hsw_ext_unregister(void)
+{
+ int num;
+ free(is_valid);
+ for (num = 0; num < HSW_EXT_CSTATE_COUNT; num++) {
+ free(previous_count[num]);
+ free(current_count[num]);
+ }
+}
+
+struct cpuidle_monitor intel_hsw_ext_monitor = {
+ .name = "HaswellExtended",
+ .hw_states = hsw_ext_cstates,
+ .hw_states_num = HSW_EXT_CSTATE_COUNT,
+ .start = hsw_ext_start,
+ .stop = hsw_ext_stop,
+ .do_register = hsw_ext_register,
+ .unregister = hsw_ext_unregister,
+ .needs_root = 1,
+ .overflow_s = 922000000 /* 922337203 seconds TSC overflow
+ at 20GHz */
+};
+#endif /* defined(__i386__) || defined(__x86_64__) */
diff --git a/tools/power/cpupower/utils/idle_monitor/idle_monitors.def b/tools/power/cpupower/utils/idle_monitor/idle_monitors.def
new file mode 100644
index 00000000000..0d6ba4dbb9c
--- /dev/null
+++ b/tools/power/cpupower/utils/idle_monitor/idle_monitors.def
@@ -0,0 +1,8 @@
+#if defined(__i386__) || defined(__x86_64__)
+DEF(amd_fam14h)
+DEF(intel_nhm)
+DEF(intel_snb)
+DEF(intel_hsw_ext)
+DEF(mperf)
+#endif
+DEF(cpuidle_sysfs)
diff --git a/tools/power/cpupower/utils/idle_monitor/idle_monitors.h b/tools/power/cpupower/utils/idle_monitor/idle_monitors.h
new file mode 100644
index 00000000000..4fcdeb1e07e
--- /dev/null
+++ b/tools/power/cpupower/utils/idle_monitor/idle_monitors.h
@@ -0,0 +1,18 @@
+/*
+ * (C) 2010,2011 Thomas Renninger <trenn@suse.de>, Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ * Based on the idea from Michael Matz <matz@suse.de>
+ *
+ */
+
+#ifndef _CPUIDLE_IDLE_MONITORS_H_
+#define _CPUIDLE_IDLE_MONITORS_H_
+
+#define DEF(x) extern struct cpuidle_monitor x ##_monitor;
+#include "idle_monitors.def"
+#undef DEF
+extern struct cpuidle_monitor *all_monitors[];
+
+#endif /* _CPUIDLE_IDLE_MONITORS_H_ */
diff --git a/tools/power/cpupower/utils/idle_monitor/mperf_monitor.c b/tools/power/cpupower/utils/idle_monitor/mperf_monitor.c
new file mode 100644
index 00000000000..5650ab5a2c2
--- /dev/null
+++ b/tools/power/cpupower/utils/idle_monitor/mperf_monitor.c
@@ -0,0 +1,338 @@
+/*
+ * (C) 2010,2011 Thomas Renninger <trenn@suse.de>, Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ */
+
+#if defined(__i386__) || defined(__x86_64__)
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <limits.h>
+
+#include <cpufreq.h>
+
+#include "helpers/helpers.h"
+#include "idle_monitor/cpupower-monitor.h"
+
+#define MSR_APERF 0xE8
+#define MSR_MPERF 0xE7
+
+#define MSR_TSC 0x10
+
+#define MSR_AMD_HWCR 0xc0010015
+
+enum mperf_id { C0 = 0, Cx, AVG_FREQ, MPERF_CSTATE_COUNT };
+
+static int mperf_get_count_percent(unsigned int self_id, double *percent,
+ unsigned int cpu);
+static int mperf_get_count_freq(unsigned int id, unsigned long long *count,
+ unsigned int cpu);
+static struct timespec time_start, time_end;
+
+static cstate_t mperf_cstates[MPERF_CSTATE_COUNT] = {
+ {
+ .name = "C0",
+ .desc = N_("Processor Core not idle"),
+ .id = C0,
+ .range = RANGE_THREAD,
+ .get_count_percent = mperf_get_count_percent,
+ },
+ {
+ .name = "Cx",
+ .desc = N_("Processor Core in an idle state"),
+ .id = Cx,
+ .range = RANGE_THREAD,
+ .get_count_percent = mperf_get_count_percent,
+ },
+
+ {
+ .name = "Freq",
+ .desc = N_("Average Frequency (including boost) in MHz"),
+ .id = AVG_FREQ,
+ .range = RANGE_THREAD,
+ .get_count = mperf_get_count_freq,
+ },
+};
+
+enum MAX_FREQ_MODE { MAX_FREQ_SYSFS, MAX_FREQ_TSC_REF };
+static int max_freq_mode;
+/*
+ * The max frequency mperf is ticking at (in C0), either retrieved via:
+ * 1) calculated after measurements if we know TSC ticks at mperf/P0 frequency
+ * 2) cpufreq /sys/devices/.../cpu0/cpufreq/cpuinfo_max_freq at init time
+ * 1. Is preferred as it also works without cpufreq subsystem (e.g. on Xen)
+ */
+static unsigned long max_frequency;
+
+static unsigned long long tsc_at_measure_start;
+static unsigned long long tsc_at_measure_end;
+static unsigned long long *mperf_previous_count;
+static unsigned long long *aperf_previous_count;
+static unsigned long long *mperf_current_count;
+static unsigned long long *aperf_current_count;
+
+/* valid flag for all CPUs. If a MSR read failed it will be zero */
+static int *is_valid;
+
+static int mperf_get_tsc(unsigned long long *tsc)
+{
+ int ret;
+ ret = read_msr(0, MSR_TSC, tsc);
+ if (ret)
+ dprint("Reading TSC MSR failed, returning %llu\n", *tsc);
+ return ret;
+}
+
+static int mperf_init_stats(unsigned int cpu)
+{
+ unsigned long long val;
+ int ret;
+
+ ret = read_msr(cpu, MSR_APERF, &val);
+ aperf_previous_count[cpu] = val;
+ ret |= read_msr(cpu, MSR_MPERF, &val);
+ mperf_previous_count[cpu] = val;
+ is_valid[cpu] = !ret;
+
+ return 0;
+}
+
+static int mperf_measure_stats(unsigned int cpu)
+{
+ unsigned long long val;
+ int ret;
+
+ ret = read_msr(cpu, MSR_APERF, &val);
+ aperf_current_count[cpu] = val;
+ ret |= read_msr(cpu, MSR_MPERF, &val);
+ mperf_current_count[cpu] = val;
+ is_valid[cpu] = !ret;
+
+ return 0;
+}
+
+static int mperf_get_count_percent(unsigned int id, double *percent,
+ unsigned int cpu)
+{
+ unsigned long long aperf_diff, mperf_diff, tsc_diff;
+ unsigned long long timediff;
+
+ if (!is_valid[cpu])
+ return -1;
+
+ if (id != C0 && id != Cx)
+ return -1;
+
+ mperf_diff = mperf_current_count[cpu] - mperf_previous_count[cpu];
+ aperf_diff = aperf_current_count[cpu] - aperf_previous_count[cpu];
+
+ if (max_freq_mode == MAX_FREQ_TSC_REF) {
+ tsc_diff = tsc_at_measure_end - tsc_at_measure_start;
+ *percent = 100.0 * mperf_diff / tsc_diff;
+ dprint("%s: TSC Ref - mperf_diff: %llu, tsc_diff: %llu\n",
+ mperf_cstates[id].name, mperf_diff, tsc_diff);
+ } else if (max_freq_mode == MAX_FREQ_SYSFS) {
+ timediff = timespec_diff_us(time_start, time_end);
+ *percent = 100.0 * mperf_diff / timediff;
+ dprint("%s: MAXFREQ - mperf_diff: %llu, time_diff: %llu\n",
+ mperf_cstates[id].name, mperf_diff, timediff);
+ } else
+ return -1;
+
+ if (id == Cx)
+ *percent = 100.0 - *percent;
+
+ dprint("%s: previous: %llu - current: %llu - (%u)\n",
+ mperf_cstates[id].name, mperf_diff, aperf_diff, cpu);
+ dprint("%s: %f\n", mperf_cstates[id].name, *percent);
+ return 0;
+}
+
+static int mperf_get_count_freq(unsigned int id, unsigned long long *count,
+ unsigned int cpu)
+{
+ unsigned long long aperf_diff, mperf_diff, time_diff, tsc_diff;
+
+ if (id != AVG_FREQ)
+ return 1;
+
+ if (!is_valid[cpu])
+ return -1;
+
+ mperf_diff = mperf_current_count[cpu] - mperf_previous_count[cpu];
+ aperf_diff = aperf_current_count[cpu] - aperf_previous_count[cpu];
+
+ if (max_freq_mode == MAX_FREQ_TSC_REF) {
+ /* Calculate max_freq from TSC count */
+ tsc_diff = tsc_at_measure_end - tsc_at_measure_start;
+ time_diff = timespec_diff_us(time_start, time_end);
+ max_frequency = tsc_diff / time_diff;
+ }
+
+ *count = max_frequency * ((double)aperf_diff / mperf_diff);
+ dprint("%s: Average freq based on %s maximum frequency:\n",
+ mperf_cstates[id].name,
+ (max_freq_mode == MAX_FREQ_TSC_REF) ? "TSC calculated" : "sysfs read");
+ dprint("%max_frequency: %lu", max_frequency);
+ dprint("aperf_diff: %llu\n", aperf_diff);
+ dprint("mperf_diff: %llu\n", mperf_diff);
+ dprint("avg freq: %llu\n", *count);
+ return 0;
+}
+
+static int mperf_start(void)
+{
+ int cpu;
+ unsigned long long dbg;
+
+ clock_gettime(CLOCK_REALTIME, &time_start);
+ mperf_get_tsc(&tsc_at_measure_start);
+
+ for (cpu = 0; cpu < cpu_count; cpu++)
+ mperf_init_stats(cpu);
+
+ mperf_get_tsc(&dbg);
+ dprint("TSC diff: %llu\n", dbg - tsc_at_measure_start);
+ return 0;
+}
+
+static int mperf_stop(void)
+{
+ unsigned long long dbg;
+ int cpu;
+
+ for (cpu = 0; cpu < cpu_count; cpu++)
+ mperf_measure_stats(cpu);
+
+ mperf_get_tsc(&tsc_at_measure_end);
+ clock_gettime(CLOCK_REALTIME, &time_end);
+
+ mperf_get_tsc(&dbg);
+ dprint("TSC diff: %llu\n", dbg - tsc_at_measure_end);
+
+ return 0;
+}
+
+/*
+ * Mperf register is defined to tick at P0 (maximum) frequency
+ *
+ * Instead of reading out P0 which can be tricky to read out from HW,
+ * we use TSC counter if it reliably ticks at P0/mperf frequency.
+ *
+ * Still try to fall back to:
+ * /sys/devices/system/cpu/cpu0/cpufreq/cpuinfo_max_freq
+ * on older Intel HW without invariant TSC feature.
+ * Or on AMD machines where TSC does not tick at P0 (do not exist yet, but
+ * it's still double checked (MSR_AMD_HWCR)).
+ *
+ * On these machines the user would still get useful mperf
+ * stats when acpi-cpufreq driver is loaded.
+ */
+static int init_maxfreq_mode(void)
+{
+ int ret;
+ unsigned long long hwcr;
+ unsigned long min;
+
+ if (!cpupower_cpu_info.caps & CPUPOWER_CAP_INV_TSC)
+ goto use_sysfs;
+
+ if (cpupower_cpu_info.vendor == X86_VENDOR_AMD) {
+ /* MSR_AMD_HWCR tells us whether TSC runs at P0/mperf
+ * freq.
+ * A test whether hwcr is accessable/available would be:
+ * (cpupower_cpu_info.family > 0x10 ||
+ * cpupower_cpu_info.family == 0x10 &&
+ * cpupower_cpu_info.model >= 0x2))
+ * This should be the case for all aperf/mperf
+ * capable AMD machines and is therefore safe to test here.
+ * Compare with Linus kernel git commit: acf01734b1747b1ec4
+ */
+ ret = read_msr(0, MSR_AMD_HWCR, &hwcr);
+ /*
+ * If the MSR read failed, assume a Xen system that did
+ * not explicitly provide access to it and assume TSC works
+ */
+ if (ret != 0) {
+ dprint("TSC read 0x%x failed - assume TSC working\n",
+ MSR_AMD_HWCR);
+ return 0;
+ } else if (1 & (hwcr >> 24)) {
+ max_freq_mode = MAX_FREQ_TSC_REF;
+ return 0;
+ } else { /* Use sysfs max frequency if available */ }
+ } else if (cpupower_cpu_info.vendor == X86_VENDOR_INTEL) {
+ /*
+ * On Intel we assume mperf (in C0) is ticking at same
+ * rate than TSC
+ */
+ max_freq_mode = MAX_FREQ_TSC_REF;
+ return 0;
+ }
+use_sysfs:
+ if (cpufreq_get_hardware_limits(0, &min, &max_frequency)) {
+ dprint("Cannot retrieve max freq from cpufreq kernel "
+ "subsystem\n");
+ return -1;
+ }
+ max_freq_mode = MAX_FREQ_SYSFS;
+ return 0;
+}
+
+/*
+ * This monitor provides:
+ *
+ * 1) Average frequency a CPU resided in
+ * This always works if the CPU has aperf/mperf capabilities
+ *
+ * 2) C0 and Cx (any sleep state) time a CPU resided in
+ * Works if mperf timer stops ticking in sleep states which
+ * seem to be the case on all current HW.
+ * Both is directly retrieved from HW registers and is independent
+ * from kernel statistics.
+ */
+struct cpuidle_monitor mperf_monitor;
+struct cpuidle_monitor *mperf_register(void)
+{
+ if (!(cpupower_cpu_info.caps & CPUPOWER_CAP_APERF))
+ return NULL;
+
+ if (init_maxfreq_mode())
+ return NULL;
+
+ /* Free this at program termination */
+ is_valid = calloc(cpu_count, sizeof(int));
+ mperf_previous_count = calloc(cpu_count, sizeof(unsigned long long));
+ aperf_previous_count = calloc(cpu_count, sizeof(unsigned long long));
+ mperf_current_count = calloc(cpu_count, sizeof(unsigned long long));
+ aperf_current_count = calloc(cpu_count, sizeof(unsigned long long));
+
+ mperf_monitor.name_len = strlen(mperf_monitor.name);
+ return &mperf_monitor;
+}
+
+void mperf_unregister(void)
+{
+ free(mperf_previous_count);
+ free(aperf_previous_count);
+ free(mperf_current_count);
+ free(aperf_current_count);
+ free(is_valid);
+}
+
+struct cpuidle_monitor mperf_monitor = {
+ .name = "Mperf",
+ .hw_states_num = MPERF_CSTATE_COUNT,
+ .hw_states = mperf_cstates,
+ .start = mperf_start,
+ .stop = mperf_stop,
+ .do_register = mperf_register,
+ .unregister = mperf_unregister,
+ .needs_root = 1,
+ .overflow_s = 922000000 /* 922337203 seconds TSC overflow
+ at 20GHz */
+};
+#endif /* #if defined(__i386__) || defined(__x86_64__) */
diff --git a/tools/power/cpupower/utils/idle_monitor/nhm_idle.c b/tools/power/cpupower/utils/idle_monitor/nhm_idle.c
new file mode 100644
index 00000000000..d2a91dd0d56
--- /dev/null
+++ b/tools/power/cpupower/utils/idle_monitor/nhm_idle.c
@@ -0,0 +1,216 @@
+/*
+ * (C) 2010,2011 Thomas Renninger <trenn@suse.de>, Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ * Based on Len Brown's <lenb@kernel.org> turbostat tool.
+ */
+
+#if defined(__i386__) || defined(__x86_64__)
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "helpers/helpers.h"
+#include "idle_monitor/cpupower-monitor.h"
+
+#define MSR_PKG_C3_RESIDENCY 0x3F8
+#define MSR_PKG_C6_RESIDENCY 0x3F9
+#define MSR_CORE_C3_RESIDENCY 0x3FC
+#define MSR_CORE_C6_RESIDENCY 0x3FD
+
+#define MSR_TSC 0x10
+
+#define NHM_CSTATE_COUNT 4
+
+enum intel_nhm_id { C3 = 0, C6, PC3, PC6, TSC = 0xFFFF };
+
+static int nhm_get_count_percent(unsigned int self_id, double *percent,
+ unsigned int cpu);
+
+static cstate_t nhm_cstates[NHM_CSTATE_COUNT] = {
+ {
+ .name = "C3",
+ .desc = N_("Processor Core C3"),
+ .id = C3,
+ .range = RANGE_CORE,
+ .get_count_percent = nhm_get_count_percent,
+ },
+ {
+ .name = "C6",
+ .desc = N_("Processor Core C6"),
+ .id = C6,
+ .range = RANGE_CORE,
+ .get_count_percent = nhm_get_count_percent,
+ },
+
+ {
+ .name = "PC3",
+ .desc = N_("Processor Package C3"),
+ .id = PC3,
+ .range = RANGE_PACKAGE,
+ .get_count_percent = nhm_get_count_percent,
+ },
+ {
+ .name = "PC6",
+ .desc = N_("Processor Package C6"),
+ .id = PC6,
+ .range = RANGE_PACKAGE,
+ .get_count_percent = nhm_get_count_percent,
+ },
+};
+
+static unsigned long long tsc_at_measure_start;
+static unsigned long long tsc_at_measure_end;
+static unsigned long long *previous_count[NHM_CSTATE_COUNT];
+static unsigned long long *current_count[NHM_CSTATE_COUNT];
+/* valid flag for all CPUs. If a MSR read failed it will be zero */
+static int *is_valid;
+
+static int nhm_get_count(enum intel_nhm_id id, unsigned long long *val,
+ unsigned int cpu)
+{
+ int msr;
+
+ switch (id) {
+ case C3:
+ msr = MSR_CORE_C3_RESIDENCY;
+ break;
+ case C6:
+ msr = MSR_CORE_C6_RESIDENCY;
+ break;
+ case PC3:
+ msr = MSR_PKG_C3_RESIDENCY;
+ break;
+ case PC6:
+ msr = MSR_PKG_C6_RESIDENCY;
+ break;
+ case TSC:
+ msr = MSR_TSC;
+ break;
+ default:
+ return -1;
+ };
+ if (read_msr(cpu, msr, val))
+ return -1;
+
+ return 0;
+}
+
+static int nhm_get_count_percent(unsigned int id, double *percent,
+ unsigned int cpu)
+{
+ *percent = 0.0;
+
+ if (!is_valid[cpu])
+ return -1;
+
+ *percent = (100.0 *
+ (current_count[id][cpu] - previous_count[id][cpu])) /
+ (tsc_at_measure_end - tsc_at_measure_start);
+
+ dprint("%s: previous: %llu - current: %llu - (%u)\n",
+ nhm_cstates[id].name, previous_count[id][cpu],
+ current_count[id][cpu], cpu);
+
+ dprint("%s: tsc_diff: %llu - count_diff: %llu - percent: %2.f (%u)\n",
+ nhm_cstates[id].name,
+ (unsigned long long) tsc_at_measure_end - tsc_at_measure_start,
+ current_count[id][cpu] - previous_count[id][cpu],
+ *percent, cpu);
+
+ return 0;
+}
+
+static int nhm_start(void)
+{
+ int num, cpu;
+ unsigned long long dbg, val;
+
+ nhm_get_count(TSC, &tsc_at_measure_start, 0);
+
+ for (num = 0; num < NHM_CSTATE_COUNT; num++) {
+ for (cpu = 0; cpu < cpu_count; cpu++) {
+ is_valid[cpu] = !nhm_get_count(num, &val, cpu);
+ previous_count[num][cpu] = val;
+ }
+ }
+ nhm_get_count(TSC, &dbg, 0);
+ dprint("TSC diff: %llu\n", dbg - tsc_at_measure_start);
+ return 0;
+}
+
+static int nhm_stop(void)
+{
+ unsigned long long val;
+ unsigned long long dbg;
+ int num, cpu;
+
+ nhm_get_count(TSC, &tsc_at_measure_end, 0);
+
+ for (num = 0; num < NHM_CSTATE_COUNT; num++) {
+ for (cpu = 0; cpu < cpu_count; cpu++) {
+ is_valid[cpu] = !nhm_get_count(num, &val, cpu);
+ current_count[num][cpu] = val;
+ }
+ }
+ nhm_get_count(TSC, &dbg, 0);
+ dprint("TSC diff: %llu\n", dbg - tsc_at_measure_end);
+
+ return 0;
+}
+
+struct cpuidle_monitor intel_nhm_monitor;
+
+struct cpuidle_monitor *intel_nhm_register(void)
+{
+ int num;
+
+ if (cpupower_cpu_info.vendor != X86_VENDOR_INTEL)
+ return NULL;
+
+ if (!(cpupower_cpu_info.caps & CPUPOWER_CAP_INV_TSC))
+ return NULL;
+
+ if (!(cpupower_cpu_info.caps & CPUPOWER_CAP_APERF))
+ return NULL;
+
+ /* Free this at program termination */
+ is_valid = calloc(cpu_count, sizeof(int));
+ for (num = 0; num < NHM_CSTATE_COUNT; num++) {
+ previous_count[num] = calloc(cpu_count,
+ sizeof(unsigned long long));
+ current_count[num] = calloc(cpu_count,
+ sizeof(unsigned long long));
+ }
+
+ intel_nhm_monitor.name_len = strlen(intel_nhm_monitor.name);
+ return &intel_nhm_monitor;
+}
+
+void intel_nhm_unregister(void)
+{
+ int num;
+
+ for (num = 0; num < NHM_CSTATE_COUNT; num++) {
+ free(previous_count[num]);
+ free(current_count[num]);
+ }
+ free(is_valid);
+}
+
+struct cpuidle_monitor intel_nhm_monitor = {
+ .name = "Nehalem",
+ .hw_states_num = NHM_CSTATE_COUNT,
+ .hw_states = nhm_cstates,
+ .start = nhm_start,
+ .stop = nhm_stop,
+ .do_register = intel_nhm_register,
+ .unregister = intel_nhm_unregister,
+ .needs_root = 1,
+ .overflow_s = 922000000 /* 922337203 seconds TSC overflow
+ at 20GHz */
+};
+#endif
diff --git a/tools/power/cpupower/utils/idle_monitor/snb_idle.c b/tools/power/cpupower/utils/idle_monitor/snb_idle.c
new file mode 100644
index 00000000000..efc8a69c9ab
--- /dev/null
+++ b/tools/power/cpupower/utils/idle_monitor/snb_idle.c
@@ -0,0 +1,200 @@
+/*
+ * (C) 2010,2011 Thomas Renninger <trenn@suse.de>, Novell Inc.
+ *
+ * Licensed under the terms of the GNU GPL License version 2.
+ *
+ * Based on Len Brown's <lenb@kernel.org> turbostat tool.
+ */
+
+#if defined(__i386__) || defined(__x86_64__)
+
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "helpers/helpers.h"
+#include "idle_monitor/cpupower-monitor.h"
+
+#define MSR_PKG_C2_RESIDENCY 0x60D
+#define MSR_PKG_C7_RESIDENCY 0x3FA
+#define MSR_CORE_C7_RESIDENCY 0x3FE
+
+#define MSR_TSC 0x10
+
+enum intel_snb_id { C7 = 0, PC2, PC7, SNB_CSTATE_COUNT, TSC = 0xFFFF };
+
+static int snb_get_count_percent(unsigned int self_id, double *percent,
+ unsigned int cpu);
+
+static cstate_t snb_cstates[SNB_CSTATE_COUNT] = {
+ {
+ .name = "C7",
+ .desc = N_("Processor Core C7"),
+ .id = C7,
+ .range = RANGE_CORE,
+ .get_count_percent = snb_get_count_percent,
+ },
+ {
+ .name = "PC2",
+ .desc = N_("Processor Package C2"),
+ .id = PC2,
+ .range = RANGE_PACKAGE,
+ .get_count_percent = snb_get_count_percent,
+ },
+ {
+ .name = "PC7",
+ .desc = N_("Processor Package C7"),
+ .id = PC7,
+ .range = RANGE_PACKAGE,
+ .get_count_percent = snb_get_count_percent,
+ },
+};
+
+static unsigned long long tsc_at_measure_start;
+static unsigned long long tsc_at_measure_end;
+static unsigned long long *previous_count[SNB_CSTATE_COUNT];
+static unsigned long long *current_count[SNB_CSTATE_COUNT];
+/* valid flag for all CPUs. If a MSR read failed it will be zero */
+static int *is_valid;
+
+static int snb_get_count(enum intel_snb_id id, unsigned long long *val,
+ unsigned int cpu)
+{
+ int msr;
+
+ switch (id) {
+ case C7:
+ msr = MSR_CORE_C7_RESIDENCY;
+ break;
+ case PC2:
+ msr = MSR_PKG_C2_RESIDENCY;
+ break;
+ case PC7:
+ msr = MSR_PKG_C7_RESIDENCY;
+ break;
+ case TSC:
+ msr = MSR_TSC;
+ break;
+ default:
+ return -1;
+ };
+ if (read_msr(cpu, msr, val))
+ return -1;
+ return 0;
+}
+
+static int snb_get_count_percent(unsigned int id, double *percent,
+ unsigned int cpu)
+{
+ *percent = 0.0;
+
+ if (!is_valid[cpu])
+ return -1;
+
+ *percent = (100.0 *
+ (current_count[id][cpu] - previous_count[id][cpu])) /
+ (tsc_at_measure_end - tsc_at_measure_start);
+
+ dprint("%s: previous: %llu - current: %llu - (%u)\n",
+ snb_cstates[id].name, previous_count[id][cpu],
+ current_count[id][cpu], cpu);
+
+ dprint("%s: tsc_diff: %llu - count_diff: %llu - percent: %2.f (%u)\n",
+ snb_cstates[id].name,
+ (unsigned long long) tsc_at_measure_end - tsc_at_measure_start,
+ current_count[id][cpu] - previous_count[id][cpu],
+ *percent, cpu);
+
+ return 0;
+}
+
+static int snb_start(void)
+{
+ int num, cpu;
+ unsigned long long val;
+
+ for (num = 0; num < SNB_CSTATE_COUNT; num++) {
+ for (cpu = 0; cpu < cpu_count; cpu++) {
+ snb_get_count(num, &val, cpu);
+ previous_count[num][cpu] = val;
+ }
+ }
+ snb_get_count(TSC, &tsc_at_measure_start, 0);
+ return 0;
+}
+
+static int snb_stop(void)
+{
+ unsigned long long val;
+ int num, cpu;
+
+ snb_get_count(TSC, &tsc_at_measure_end, 0);
+
+ for (num = 0; num < SNB_CSTATE_COUNT; num++) {
+ for (cpu = 0; cpu < cpu_count; cpu++) {
+ is_valid[cpu] = !snb_get_count(num, &val, cpu);
+ current_count[num][cpu] = val;
+ }
+ }
+ return 0;
+}
+
+struct cpuidle_monitor intel_snb_monitor;
+
+static struct cpuidle_monitor *snb_register(void)
+{
+ int num;
+
+ if (cpupower_cpu_info.vendor != X86_VENDOR_INTEL
+ || cpupower_cpu_info.family != 6)
+ return NULL;
+
+ switch (cpupower_cpu_info.model) {
+ case 0x2A: /* SNB */
+ case 0x2D: /* SNB Xeon */
+ case 0x3A: /* IVB */
+ case 0x3E: /* IVB Xeon */
+ case 0x3C: /* HSW */
+ case 0x3F: /* HSW */
+ case 0x45: /* HSW */
+ case 0x46: /* HSW */
+ break;
+ default:
+ return NULL;
+ }
+
+ is_valid = calloc(cpu_count, sizeof(int));
+ for (num = 0; num < SNB_CSTATE_COUNT; num++) {
+ previous_count[num] = calloc(cpu_count,
+ sizeof(unsigned long long));
+ current_count[num] = calloc(cpu_count,
+ sizeof(unsigned long long));
+ }
+ intel_snb_monitor.name_len = strlen(intel_snb_monitor.name);
+ return &intel_snb_monitor;
+}
+
+void snb_unregister(void)
+{
+ int num;
+ free(is_valid);
+ for (num = 0; num < SNB_CSTATE_COUNT; num++) {
+ free(previous_count[num]);
+ free(current_count[num]);
+ }
+}
+
+struct cpuidle_monitor intel_snb_monitor = {
+ .name = "SandyBridge",
+ .hw_states = snb_cstates,
+ .hw_states_num = SNB_CSTATE_COUNT,
+ .start = snb_start,
+ .stop = snb_stop,
+ .do_register = snb_register,
+ .unregister = snb_unregister,
+ .needs_root = 1,
+ .overflow_s = 922000000 /* 922337203 seconds TSC overflow
+ at 20GHz */
+};
+#endif /* defined(__i386__) || defined(__x86_64__) */
diff --git a/tools/power/cpupower/utils/version-gen.sh b/tools/power/cpupower/utils/version-gen.sh
new file mode 100755
index 00000000000..5ec41c55699
--- /dev/null
+++ b/tools/power/cpupower/utils/version-gen.sh
@@ -0,0 +1,35 @@
+#!/bin/sh
+#
+# Script which prints out the version to use for building cpupowerutils.
+# Must be called from tools/power/cpupower/
+#
+# Heavily based on tools/perf/util/PERF-VERSION-GEN .
+
+LF='
+'
+
+# First check if there is a .git to get the version from git describe
+# otherwise try to get the version from the kernel makefile
+if test -d ../../../.git -o -f ../../../.git &&
+ VN=$(git describe --abbrev=4 HEAD 2>/dev/null) &&
+ case "$VN" in
+ *$LF*) (exit 1) ;;
+ v[0-9]*)
+ git update-index -q --refresh
+ test -z "$(git diff-index --name-only HEAD --)" ||
+ VN="$VN-dirty" ;;
+ esac
+then
+ VN=$(echo "$VN" | sed -e 's/-/./g');
+else
+ eval $(grep '^VERSION[[:space:]]*=' ../../../Makefile|tr -d ' ')
+ eval $(grep '^PATCHLEVEL[[:space:]]*=' ../../../Makefile|tr -d ' ')
+ eval $(grep '^SUBLEVEL[[:space:]]*=' ../../../Makefile|tr -d ' ')
+ eval $(grep '^EXTRAVERSION[[:space:]]*=' ../../../Makefile|tr -d ' ')
+
+ VN="${VERSION}.${PATCHLEVEL}.${SUBLEVEL}${EXTRAVERSION}"
+fi
+
+VN=$(expr "$VN" : v*'\(.*\)')
+
+echo $VN
diff --git a/tools/power/x86/turbostat/.gitignore b/tools/power/x86/turbostat/.gitignore
new file mode 100644
index 00000000000..7521370d356
--- /dev/null
+++ b/tools/power/x86/turbostat/.gitignore
@@ -0,0 +1 @@
+turbostat
diff --git a/tools/power/x86/turbostat/Makefile b/tools/power/x86/turbostat/Makefile
new file mode 100644
index 00000000000..d1b3a361e52
--- /dev/null
+++ b/tools/power/x86/turbostat/Makefile
@@ -0,0 +1,22 @@
+CC = $(CROSS_COMPILE)gcc
+BUILD_OUTPUT := $(PWD)
+PREFIX := /usr
+DESTDIR :=
+
+turbostat : turbostat.c
+CFLAGS += -Wall
+CFLAGS += -DMSRHEADER='"../../../../arch/x86/include/uapi/asm/msr-index.h"'
+
+%: %.c
+ @mkdir -p $(BUILD_OUTPUT)
+ $(CC) $(CFLAGS) $< -o $(BUILD_OUTPUT)/$@
+
+.PHONY : clean
+clean :
+ @rm -f $(BUILD_OUTPUT)/turbostat
+
+install : turbostat
+ install -d $(DESTDIR)$(PREFIX)/bin
+ install $(BUILD_OUTPUT)/turbostat $(DESTDIR)$(PREFIX)/bin/turbostat
+ install -d $(DESTDIR)$(PREFIX)/share/man/man8
+ install turbostat.8 $(DESTDIR)$(PREFIX)/share/man/man8
diff --git a/tools/power/x86/turbostat/turbostat.8 b/tools/power/x86/turbostat/turbostat.8
new file mode 100644
index 00000000000..56bfb523c5b
--- /dev/null
+++ b/tools/power/x86/turbostat/turbostat.8
@@ -0,0 +1,221 @@
+.TH TURBOSTAT 8
+.SH NAME
+turbostat \- Report processor frequency and idle statistics
+.SH SYNOPSIS
+.ft B
+.B turbostat
+.RB [ Options ]
+.RB command
+.br
+.B turbostat
+.RB [ Options ]
+.RB [ "\-i interval_sec" ]
+.SH DESCRIPTION
+\fBturbostat \fP reports processor topology, frequency,
+idle power-state statistics, temperature and power on modern X86 processors.
+Either \fBcommand\fP is forked and statistics are printed
+upon its completion, or statistics are printed periodically.
+
+\fBturbostat \fP
+must be run on root, and
+minimally requires that the processor
+supports an "invariant" TSC, plus the APERF and MPERF MSRs.
+Additional information is reported depending on hardware counter support.
+
+.SS Options
+The \fB-p\fP option limits output to the 1st thread in 1st core of each package.
+.PP
+The \fB-P\fP option limits output to the 1st thread in each Package.
+.PP
+The \fB-S\fP option limits output to a 1-line System Summary for each interval.
+.PP
+The \fB-v\fP option increases verbosity.
+.PP
+The \fB-c MSR#\fP option includes the delta of the specified 32-bit MSR counter.
+.PP
+The \fB-C MSR#\fP option includes the delta of the specified 64-bit MSR counter.
+.PP
+The \fB-m MSR#\fP option includes the the specified 32-bit MSR value.
+.PP
+The \fB-M MSR#\fP option includes the the specified 64-bit MSR value.
+.PP
+The \fB-i interval_sec\fP option prints statistics every \fiinterval_sec\fP seconds.
+The default is 5 seconds.
+.PP
+The \fBcommand\fP parameter forks \fBcommand\fP and upon its exit,
+displays the statistics gathered since it was forked.
+.PP
+.SH FIELD DESCRIPTIONS
+.nf
+\fBPackage\fP processor package number.
+\fBCore\fP processor core number.
+\fBCPU\fP Linux CPU (logical processor) number.
+Note that multiple CPUs per core indicate support for Intel(R) Hyper-Threading Technology.
+\fBAVG_MHz\fP number of cycles executed divided by time elapsed.
+\fB%Buzy\fP percent of the interval that the CPU retired instructions, aka. % of time in "C0" state.
+\fBBzy_MHz\fP average clock rate while the CPU was busy (in "c0" state).
+\fBTSC_MHz\fP average MHz that the TSC ran during the entire interval.
+\fBCPU%c1, CPU%c3, CPU%c6, CPU%c7\fP show the percentage residency in hardware core idle states.
+\fBCoreTmp\fP Degrees Celsius reported by the per-core Digital Thermal Sensor.
+\fBPkgTtmp\fP Degrees Celsius reported by the per-package Package Thermal Monitor.
+\fBPkg%pc2, Pkg%pc3, Pkg%pc6, Pkg%pc7\fP percentage residency in hardware package idle states.
+\fBPkgWatt\fP Watts consumed by the whole package.
+\fBCorWatt\fP Watts consumed by the core part of the package.
+\fBGFXWatt\fP Watts consumed by the Graphics part of the package -- available only on client processors.
+\fBRAMWatt\fP Watts consumed by the DRAM DIMMS -- available only on server processors.
+\fBPKG_%\fP percent of the interval that RAPL throttling was active on the Package.
+\fBRAM_%\fP percent of the interval that RAPL throttling was active on DRAM.
+.fi
+.PP
+.SH EXAMPLE
+Without any parameters, turbostat prints out counters ever 5 seconds.
+(override interval with "-i sec" option, or specify a command
+for turbostat to fork).
+
+The first row of statistics is a summary for the entire system.
+For residency % columns, the summary is a weighted average.
+For Temperature columns, the summary is the column maximum.
+For Watts columns, the summary is a system total.
+Subsequent rows show per-CPU statistics.
+
+.nf
+[root@ivy]# ./turbostat
+ Core CPU Avg_MHz %Busy Bzy_MHz TSC_MHz SMI CPU%c1 CPU%c3 CPU%c6 CPU%c7 CoreTmp PkgTmp Pkg%pc2 Pkg%pc3 Pkg%pc6 Pkg%pc7 PkgWatt CorWatt GFXWatt
+ - - 6 0.36 1596 3492 0 0.59 0.01 99.04 0.00 23 24 23.82 0.01 72.47 0.00 6.40 1.01 0.00
+ 0 0 9 0.58 1596 3492 0 0.28 0.01 99.13 0.00 23 24 23.82 0.01 72.47 0.00 6.40 1.01 0.00
+ 0 4 1 0.07 1596 3492 0 0.79
+ 1 1 10 0.65 1596 3492 0 0.59 0.00 98.76 0.00 23
+ 1 5 5 0.28 1596 3492 0 0.95
+ 2 2 10 0.66 1596 3492 0 0.41 0.01 98.92 0.00 23
+ 2 6 2 0.10 1597 3492 0 0.97
+ 3 3 3 0.20 1596 3492 0 0.44 0.00 99.37 0.00 23
+ 3 7 5 0.31 1596 3492 0 0.33
+.fi
+.SH VERBOSE EXAMPLE
+The "-v" option adds verbosity to the output:
+
+.nf
+[root@ivy]# turbostat -v
+turbostat v3.0 November 23, 2012 - Len Brown <lenb@kernel.org>
+CPUID(0): GenuineIntel 13 CPUID levels; family:model:stepping 0x6:3a:9 (6:58:9)
+CPUID(6): APERF, DTS, PTM, EPB
+RAPL: 851 sec. Joule Counter Range
+cpu0: MSR_NHM_PLATFORM_INFO: 0x81010f0012300
+16 * 100 = 1600 MHz max efficiency
+35 * 100 = 3500 MHz TSC frequency
+cpu0: MSR_NHM_SNB_PKG_CST_CFG_CTL: 0x1e008402 (UNdemote-C3, UNdemote-C1, demote-C3, demote-C1, locked: pkg-cstate-limit=2: pc6-noret)
+cpu0: MSR_NHM_TURBO_RATIO_LIMIT: 0x25262727
+37 * 100 = 3700 MHz max turbo 4 active cores
+38 * 100 = 3800 MHz max turbo 3 active cores
+39 * 100 = 3900 MHz max turbo 2 active cores
+39 * 100 = 3900 MHz max turbo 1 active cores
+cpu0: MSR_IA32_ENERGY_PERF_BIAS: 0x00000006 (balanced)
+cpu0: MSR_RAPL_POWER_UNIT: 0x000a1003 (0.125000 Watts, 0.000015 Joules, 0.000977 sec.)
+cpu0: MSR_PKG_POWER_INFO: 0x01e00268 (77 W TDP, RAPL 60 - 0 W, 0.000000 sec.)
+cpu0: MSR_PKG_POWER_LIMIT: 0x830000148268 (UNlocked)
+cpu0: PKG Limit #1: ENabled (77.000000 Watts, 1.000000 sec, clamp DISabled)
+cpu0: PKG Limit #2: ENabled (96.000000 Watts, 0.000977* sec, clamp DISabled)
+cpu0: MSR_PP0_POLICY: 0
+cpu0: MSR_PP0_POWER_LIMIT: 0x00000000 (UNlocked)
+cpu0: Cores Limit: DISabled (0.000000 Watts, 0.000977 sec, clamp DISabled)
+cpu0: MSR_PP1_POLICY: 0
+cpu0: MSR_PP1_POWER_LIMIT: 0x00000000 (UNlocked)
+cpu0: GFX Limit: DISabled (0.000000 Watts, 0.000977 sec, clamp DISabled)
+cpu0: MSR_IA32_TEMPERATURE_TARGET: 0x00691400 (105 C)
+cpu0: MSR_IA32_PACKAGE_THERM_STATUS: 0x884e0000 (27 C)
+cpu0: MSR_IA32_THERM_STATUS: 0x88560000 (19 C +/- 1)
+cpu1: MSR_IA32_THERM_STATUS: 0x88560000 (19 C +/- 1)
+cpu2: MSR_IA32_THERM_STATUS: 0x88540000 (21 C +/- 1)
+cpu3: MSR_IA32_THERM_STATUS: 0x884e0000 (27 C +/- 1)
+ ...
+.fi
+The \fBmax efficiency\fP frequency, a.k.a. Low Frequency Mode, is the frequency
+available at the minimum package voltage. The \fBTSC frequency\fP is the nominal
+maximum frequency of the processor if turbo-mode were not available. This frequency
+should be sustainable on all CPUs indefinitely, given nominal power and cooling.
+The remaining rows show what maximum turbo frequency is possible
+depending on the number of idle cores. Note that this information is
+not available on all processors.
+.SH FORK EXAMPLE
+If turbostat is invoked with a command, it will fork that command
+and output the statistics gathered when the command exits.
+eg. Here a cycle soaker is run on 1 CPU (see %c0) for a few seconds
+until ^C while the other CPUs are mostly idle:
+
+.nf
+root@ivy: turbostat cat /dev/zero > /dev/null
+^C
+ Core CPU Avg_MHz %Busy Bzy_MHz TSC_MHz SMI CPU%c1 CPU%c3 CPU%c6 CPU%c7 CoreTmp PkgTmp Pkg%pc2 Pkg%pc3 Pkg%pc6 Pkg%pc7 PkgWatt CorWatt GFXWatt
+ - - 496 12.75 3886 3492 0 13.16 0.04 74.04 0.00 36 36 0.00 0.00 0.00 0.00 23.15 17.65 0.00
+ 0 0 22 0.57 3830 3492 0 0.83 0.02 98.59 0.00 27 36 0.00 0.00 0.00 0.00 23.15 17.65 0.00
+ 0 4 9 0.24 3829 3492 0 1.15
+ 1 1 4 0.09 3783 3492 0 99.91 0.00 0.00 0.00 36
+ 1 5 3880 99.82 3888 3492 0 0.18
+ 2 2 17 0.44 3813 3492 0 0.77 0.04 98.75 0.00 28
+ 2 6 12 0.32 3823 3492 0 0.89
+ 3 3 16 0.43 3844 3492 0 0.63 0.11 98.84 0.00 30
+ 3 7 4 0.11 3827 3492 0 0.94
+30.372243 sec
+
+.fi
+Above the cycle soaker drives cpu5 up its 3.8 GHz turbo limit
+while the other processors are generally in various states of idle.
+
+Note that cpu1 and cpu5 are HT siblings within core1.
+As cpu5 is very busy, it prevents its sibling, cpu1,
+from entering a c-state deeper than c1.
+
+Note that the Avg_MHz column reflects the total number of cycles executed
+divided by the measurement interval. If the %Busy column is 100%,
+then the processor was running at that speed the entire interval.
+The Avg_MHz multiplied by the %Busy results in the Bzy_MHz --
+which is the average frequency while the processor was executing --
+not including any non-busy idle time.
+
+.SH NOTES
+
+.B "turbostat "
+must be run as root.
+
+.B "turbostat "
+reads hardware counters, but doesn't write them.
+So it will not interfere with the OS or other programs, including
+multiple invocations of itself.
+
+\fBturbostat \fP
+may work poorly on Linux-2.6.20 through 2.6.29,
+as \fBacpi-cpufreq \fPperiodically cleared the APERF and MPERF
+in those kernels.
+
+If the TSC column does not make sense, then
+the other numbers will also make no sense.
+Turbostat is lightweight, and its data collection is not atomic.
+These issues are usually caused by an extremely short measurement
+interval (much less than 1 second), or system activity that prevents
+turbostat from being able to run on all CPUS to quickly collect data.
+
+The APERF, MPERF MSRs are defined to count non-halted cycles.
+Although it is not guaranteed by the architecture, turbostat assumes
+that they count at TSC rate, which is true on all processors tested to date.
+
+.SH REFERENCES
+"Intel® Turbo Boost Technology
+in Intel® Core™ Microarchitecture (Nehalem) Based Processors"
+http://download.intel.com/design/processor/applnots/320354.pdf
+
+"Intel® 64 and IA-32 Architectures Software Developer's Manual
+Volume 3B: System Programming Guide"
+http://www.intel.com/products/processor/manuals/
+
+.SH FILES
+.ta
+.nf
+/dev/cpu/*/msr
+.fi
+
+.SH "SEE ALSO"
+msr(4), vmstat(8)
+.PP
+.SH AUTHOR
+.nf
+Written by Len Brown <len.brown@intel.com>
diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c
new file mode 100644
index 00000000000..d0396af99fa
--- /dev/null
+++ b/tools/power/x86/turbostat/turbostat.c
@@ -0,0 +1,2462 @@
+/*
+ * turbostat -- show CPU frequency and C-state residency
+ * on modern Intel turbo-capable processors.
+ *
+ * Copyright (c) 2013 Intel Corporation.
+ * Len Brown <len.brown@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define _GNU_SOURCE
+#include MSRHEADER
+#include <stdarg.h>
+#include <stdio.h>
+#include <err.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/resource.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <string.h>
+#include <ctype.h>
+#include <sched.h>
+#include <cpuid.h>
+
+char *proc_stat = "/proc/stat";
+unsigned int interval_sec = 5; /* set with -i interval_sec */
+unsigned int verbose; /* set with -v */
+unsigned int rapl_verbose; /* set with -R */
+unsigned int rapl_joules; /* set with -J */
+unsigned int thermal_verbose; /* set with -T */
+unsigned int summary_only; /* set with -S */
+unsigned int dump_only; /* set with -s */
+unsigned int skip_c0;
+unsigned int skip_c1;
+unsigned int do_nhm_cstates;
+unsigned int do_snb_cstates;
+unsigned int do_c8_c9_c10;
+unsigned int do_slm_cstates;
+unsigned int use_c1_residency_msr;
+unsigned int has_aperf;
+unsigned int has_epb;
+unsigned int units = 1000000; /* MHz etc */
+unsigned int genuine_intel;
+unsigned int has_invariant_tsc;
+unsigned int do_nehalem_platform_info;
+unsigned int do_nehalem_turbo_ratio_limit;
+unsigned int do_ivt_turbo_ratio_limit;
+unsigned int extra_msr_offset32;
+unsigned int extra_msr_offset64;
+unsigned int extra_delta_offset32;
+unsigned int extra_delta_offset64;
+int do_smi;
+double bclk;
+unsigned int show_pkg;
+unsigned int show_core;
+unsigned int show_cpu;
+unsigned int show_pkg_only;
+unsigned int show_core_only;
+char *output_buffer, *outp;
+unsigned int do_rapl;
+unsigned int do_dts;
+unsigned int do_ptm;
+unsigned int tcc_activation_temp;
+unsigned int tcc_activation_temp_override;
+double rapl_power_units, rapl_energy_units, rapl_time_units;
+double rapl_joule_counter_range;
+
+#define RAPL_PKG (1 << 0)
+ /* 0x610 MSR_PKG_POWER_LIMIT */
+ /* 0x611 MSR_PKG_ENERGY_STATUS */
+#define RAPL_PKG_PERF_STATUS (1 << 1)
+ /* 0x613 MSR_PKG_PERF_STATUS */
+#define RAPL_PKG_POWER_INFO (1 << 2)
+ /* 0x614 MSR_PKG_POWER_INFO */
+
+#define RAPL_DRAM (1 << 3)
+ /* 0x618 MSR_DRAM_POWER_LIMIT */
+ /* 0x619 MSR_DRAM_ENERGY_STATUS */
+ /* 0x61c MSR_DRAM_POWER_INFO */
+#define RAPL_DRAM_PERF_STATUS (1 << 4)
+ /* 0x61b MSR_DRAM_PERF_STATUS */
+
+#define RAPL_CORES (1 << 5)
+ /* 0x638 MSR_PP0_POWER_LIMIT */
+ /* 0x639 MSR_PP0_ENERGY_STATUS */
+#define RAPL_CORE_POLICY (1 << 6)
+ /* 0x63a MSR_PP0_POLICY */
+
+
+#define RAPL_GFX (1 << 7)
+ /* 0x640 MSR_PP1_POWER_LIMIT */
+ /* 0x641 MSR_PP1_ENERGY_STATUS */
+ /* 0x642 MSR_PP1_POLICY */
+#define TJMAX_DEFAULT 100
+
+#define MAX(a, b) ((a) > (b) ? (a) : (b))
+
+int aperf_mperf_unstable;
+int backwards_count;
+char *progname;
+
+cpu_set_t *cpu_present_set, *cpu_affinity_set;
+size_t cpu_present_setsize, cpu_affinity_setsize;
+
+struct thread_data {
+ unsigned long long tsc;
+ unsigned long long aperf;
+ unsigned long long mperf;
+ unsigned long long c1;
+ unsigned long long extra_msr64;
+ unsigned long long extra_delta64;
+ unsigned long long extra_msr32;
+ unsigned long long extra_delta32;
+ unsigned int smi_count;
+ unsigned int cpu_id;
+ unsigned int flags;
+#define CPU_IS_FIRST_THREAD_IN_CORE 0x2
+#define CPU_IS_FIRST_CORE_IN_PACKAGE 0x4
+} *thread_even, *thread_odd;
+
+struct core_data {
+ unsigned long long c3;
+ unsigned long long c6;
+ unsigned long long c7;
+ unsigned int core_temp_c;
+ unsigned int core_id;
+} *core_even, *core_odd;
+
+struct pkg_data {
+ unsigned long long pc2;
+ unsigned long long pc3;
+ unsigned long long pc6;
+ unsigned long long pc7;
+ unsigned long long pc8;
+ unsigned long long pc9;
+ unsigned long long pc10;
+ unsigned int package_id;
+ unsigned int energy_pkg; /* MSR_PKG_ENERGY_STATUS */
+ unsigned int energy_dram; /* MSR_DRAM_ENERGY_STATUS */
+ unsigned int energy_cores; /* MSR_PP0_ENERGY_STATUS */
+ unsigned int energy_gfx; /* MSR_PP1_ENERGY_STATUS */
+ unsigned int rapl_pkg_perf_status; /* MSR_PKG_PERF_STATUS */
+ unsigned int rapl_dram_perf_status; /* MSR_DRAM_PERF_STATUS */
+ unsigned int pkg_temp_c;
+
+} *package_even, *package_odd;
+
+#define ODD_COUNTERS thread_odd, core_odd, package_odd
+#define EVEN_COUNTERS thread_even, core_even, package_even
+
+#define GET_THREAD(thread_base, thread_no, core_no, pkg_no) \
+ (thread_base + (pkg_no) * topo.num_cores_per_pkg * \
+ topo.num_threads_per_core + \
+ (core_no) * topo.num_threads_per_core + (thread_no))
+#define GET_CORE(core_base, core_no, pkg_no) \
+ (core_base + (pkg_no) * topo.num_cores_per_pkg + (core_no))
+#define GET_PKG(pkg_base, pkg_no) (pkg_base + pkg_no)
+
+struct system_summary {
+ struct thread_data threads;
+ struct core_data cores;
+ struct pkg_data packages;
+} sum, average;
+
+
+struct topo_params {
+ int num_packages;
+ int num_cpus;
+ int num_cores;
+ int max_cpu_num;
+ int num_cores_per_pkg;
+ int num_threads_per_core;
+} topo;
+
+struct timeval tv_even, tv_odd, tv_delta;
+
+void setup_all_buffers(void);
+
+int cpu_is_not_present(int cpu)
+{
+ return !CPU_ISSET_S(cpu, cpu_present_setsize, cpu_present_set);
+}
+/*
+ * run func(thread, core, package) in topology order
+ * skip non-present cpus
+ */
+
+int for_all_cpus(int (func)(struct thread_data *, struct core_data *, struct pkg_data *),
+ struct thread_data *thread_base, struct core_data *core_base, struct pkg_data *pkg_base)
+{
+ int retval, pkg_no, core_no, thread_no;
+
+ for (pkg_no = 0; pkg_no < topo.num_packages; ++pkg_no) {
+ for (core_no = 0; core_no < topo.num_cores_per_pkg; ++core_no) {
+ for (thread_no = 0; thread_no <
+ topo.num_threads_per_core; ++thread_no) {
+ struct thread_data *t;
+ struct core_data *c;
+ struct pkg_data *p;
+
+ t = GET_THREAD(thread_base, thread_no, core_no, pkg_no);
+
+ if (cpu_is_not_present(t->cpu_id))
+ continue;
+
+ c = GET_CORE(core_base, core_no, pkg_no);
+ p = GET_PKG(pkg_base, pkg_no);
+
+ retval = func(t, c, p);
+ if (retval)
+ return retval;
+ }
+ }
+ }
+ return 0;
+}
+
+int cpu_migrate(int cpu)
+{
+ CPU_ZERO_S(cpu_affinity_setsize, cpu_affinity_set);
+ CPU_SET_S(cpu, cpu_affinity_setsize, cpu_affinity_set);
+ if (sched_setaffinity(0, cpu_affinity_setsize, cpu_affinity_set) == -1)
+ return -1;
+ else
+ return 0;
+}
+
+int get_msr(int cpu, off_t offset, unsigned long long *msr)
+{
+ ssize_t retval;
+ char pathname[32];
+ int fd;
+
+ sprintf(pathname, "/dev/cpu/%d/msr", cpu);
+ fd = open(pathname, O_RDONLY);
+ if (fd < 0)
+ return -1;
+
+ retval = pread(fd, msr, sizeof *msr, offset);
+ close(fd);
+
+ if (retval != sizeof *msr) {
+ fprintf(stderr, "%s offset 0x%llx read failed\n", pathname, (unsigned long long)offset);
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * Example Format w/ field column widths:
+ *
+ * Package Core CPU Avg_MHz Bzy_MHz TSC_MHz SMI %Busy CPU_%c1 CPU_%c3 CPU_%c6 CPU_%c7 CoreTmp PkgTmp Pkg%pc2 Pkg%pc3 Pkg%pc6 Pkg%pc7 PkgWatt CorWatt GFXWatt
+ * 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567 1234567
+ */
+
+void print_header(void)
+{
+ if (show_pkg)
+ outp += sprintf(outp, "Package ");
+ if (show_core)
+ outp += sprintf(outp, " Core ");
+ if (show_cpu)
+ outp += sprintf(outp, " CPU ");
+ if (has_aperf)
+ outp += sprintf(outp, "Avg_MHz ");
+ if (do_nhm_cstates)
+ outp += sprintf(outp, " %%Busy ");
+ if (has_aperf)
+ outp += sprintf(outp, "Bzy_MHz ");
+ outp += sprintf(outp, "TSC_MHz ");
+ if (do_smi)
+ outp += sprintf(outp, " SMI ");
+ if (extra_delta_offset32)
+ outp += sprintf(outp, " count 0x%03X ", extra_delta_offset32);
+ if (extra_delta_offset64)
+ outp += sprintf(outp, " COUNT 0x%03X ", extra_delta_offset64);
+ if (extra_msr_offset32)
+ outp += sprintf(outp, " MSR 0x%03X ", extra_msr_offset32);
+ if (extra_msr_offset64)
+ outp += sprintf(outp, " MSR 0x%03X ", extra_msr_offset64);
+ if (do_nhm_cstates)
+ outp += sprintf(outp, " CPU%%c1 ");
+ if (do_nhm_cstates && !do_slm_cstates)
+ outp += sprintf(outp, " CPU%%c3 ");
+ if (do_nhm_cstates)
+ outp += sprintf(outp, " CPU%%c6 ");
+ if (do_snb_cstates)
+ outp += sprintf(outp, " CPU%%c7 ");
+
+ if (do_dts)
+ outp += sprintf(outp, "CoreTmp ");
+ if (do_ptm)
+ outp += sprintf(outp, " PkgTmp ");
+
+ if (do_snb_cstates)
+ outp += sprintf(outp, "Pkg%%pc2 ");
+ if (do_nhm_cstates && !do_slm_cstates)
+ outp += sprintf(outp, "Pkg%%pc3 ");
+ if (do_nhm_cstates && !do_slm_cstates)
+ outp += sprintf(outp, "Pkg%%pc6 ");
+ if (do_snb_cstates)
+ outp += sprintf(outp, "Pkg%%pc7 ");
+ if (do_c8_c9_c10) {
+ outp += sprintf(outp, "Pkg%%pc8 ");
+ outp += sprintf(outp, "Pkg%%pc9 ");
+ outp += sprintf(outp, "Pk%%pc10 ");
+ }
+
+ if (do_rapl && !rapl_joules) {
+ if (do_rapl & RAPL_PKG)
+ outp += sprintf(outp, "PkgWatt ");
+ if (do_rapl & RAPL_CORES)
+ outp += sprintf(outp, "CorWatt ");
+ if (do_rapl & RAPL_GFX)
+ outp += sprintf(outp, "GFXWatt ");
+ if (do_rapl & RAPL_DRAM)
+ outp += sprintf(outp, "RAMWatt ");
+ if (do_rapl & RAPL_PKG_PERF_STATUS)
+ outp += sprintf(outp, " PKG_%% ");
+ if (do_rapl & RAPL_DRAM_PERF_STATUS)
+ outp += sprintf(outp, " RAM_%% ");
+ } else {
+ if (do_rapl & RAPL_PKG)
+ outp += sprintf(outp, " Pkg_J ");
+ if (do_rapl & RAPL_CORES)
+ outp += sprintf(outp, " Cor_J ");
+ if (do_rapl & RAPL_GFX)
+ outp += sprintf(outp, " GFX_J ");
+ if (do_rapl & RAPL_DRAM)
+ outp += sprintf(outp, " RAM_W ");
+ if (do_rapl & RAPL_PKG_PERF_STATUS)
+ outp += sprintf(outp, " PKG_%% ");
+ if (do_rapl & RAPL_DRAM_PERF_STATUS)
+ outp += sprintf(outp, " RAM_%% ");
+ outp += sprintf(outp, " time ");
+
+ }
+ outp += sprintf(outp, "\n");
+}
+
+int dump_counters(struct thread_data *t, struct core_data *c,
+ struct pkg_data *p)
+{
+ outp += sprintf(outp, "t %p, c %p, p %p\n", t, c, p);
+
+ if (t) {
+ outp += sprintf(outp, "CPU: %d flags 0x%x\n",
+ t->cpu_id, t->flags);
+ outp += sprintf(outp, "TSC: %016llX\n", t->tsc);
+ outp += sprintf(outp, "aperf: %016llX\n", t->aperf);
+ outp += sprintf(outp, "mperf: %016llX\n", t->mperf);
+ outp += sprintf(outp, "c1: %016llX\n", t->c1);
+ outp += sprintf(outp, "msr0x%x: %08llX\n",
+ extra_delta_offset32, t->extra_delta32);
+ outp += sprintf(outp, "msr0x%x: %016llX\n",
+ extra_delta_offset64, t->extra_delta64);
+ outp += sprintf(outp, "msr0x%x: %08llX\n",
+ extra_msr_offset32, t->extra_msr32);
+ outp += sprintf(outp, "msr0x%x: %016llX\n",
+ extra_msr_offset64, t->extra_msr64);
+ if (do_smi)
+ outp += sprintf(outp, "SMI: %08X\n", t->smi_count);
+ }
+
+ if (c) {
+ outp += sprintf(outp, "core: %d\n", c->core_id);
+ outp += sprintf(outp, "c3: %016llX\n", c->c3);
+ outp += sprintf(outp, "c6: %016llX\n", c->c6);
+ outp += sprintf(outp, "c7: %016llX\n", c->c7);
+ outp += sprintf(outp, "DTS: %dC\n", c->core_temp_c);
+ }
+
+ if (p) {
+ outp += sprintf(outp, "package: %d\n", p->package_id);
+ outp += sprintf(outp, "pc2: %016llX\n", p->pc2);
+ outp += sprintf(outp, "pc3: %016llX\n", p->pc3);
+ outp += sprintf(outp, "pc6: %016llX\n", p->pc6);
+ outp += sprintf(outp, "pc7: %016llX\n", p->pc7);
+ outp += sprintf(outp, "pc8: %016llX\n", p->pc8);
+ outp += sprintf(outp, "pc9: %016llX\n", p->pc9);
+ outp += sprintf(outp, "pc10: %016llX\n", p->pc10);
+ outp += sprintf(outp, "Joules PKG: %0X\n", p->energy_pkg);
+ outp += sprintf(outp, "Joules COR: %0X\n", p->energy_cores);
+ outp += sprintf(outp, "Joules GFX: %0X\n", p->energy_gfx);
+ outp += sprintf(outp, "Joules RAM: %0X\n", p->energy_dram);
+ outp += sprintf(outp, "Throttle PKG: %0X\n",
+ p->rapl_pkg_perf_status);
+ outp += sprintf(outp, "Throttle RAM: %0X\n",
+ p->rapl_dram_perf_status);
+ outp += sprintf(outp, "PTM: %dC\n", p->pkg_temp_c);
+ }
+
+ outp += sprintf(outp, "\n");
+
+ return 0;
+}
+
+/*
+ * column formatting convention & formats
+ */
+int format_counters(struct thread_data *t, struct core_data *c,
+ struct pkg_data *p)
+{
+ double interval_float;
+ char *fmt8;
+
+ /* if showing only 1st thread in core and this isn't one, bail out */
+ if (show_core_only && !(t->flags & CPU_IS_FIRST_THREAD_IN_CORE))
+ return 0;
+
+ /* if showing only 1st thread in pkg and this isn't one, bail out */
+ if (show_pkg_only && !(t->flags & CPU_IS_FIRST_CORE_IN_PACKAGE))
+ return 0;
+
+ interval_float = tv_delta.tv_sec + tv_delta.tv_usec/1000000.0;
+
+ /* topo columns, print blanks on 1st (average) line */
+ if (t == &average.threads) {
+ if (show_pkg)
+ outp += sprintf(outp, " -");
+ if (show_core)
+ outp += sprintf(outp, " -");
+ if (show_cpu)
+ outp += sprintf(outp, " -");
+ } else {
+ if (show_pkg) {
+ if (p)
+ outp += sprintf(outp, "%8d", p->package_id);
+ else
+ outp += sprintf(outp, " -");
+ }
+ if (show_core) {
+ if (c)
+ outp += sprintf(outp, "%8d", c->core_id);
+ else
+ outp += sprintf(outp, " -");
+ }
+ if (show_cpu)
+ outp += sprintf(outp, "%8d", t->cpu_id);
+ }
+
+ /* AvgMHz */
+ if (has_aperf)
+ outp += sprintf(outp, "%8.0f",
+ 1.0 / units * t->aperf / interval_float);
+
+ /* %c0 */
+ if (do_nhm_cstates) {
+ if (!skip_c0)
+ outp += sprintf(outp, "%8.2f", 100.0 * t->mperf/t->tsc);
+ else
+ outp += sprintf(outp, "********");
+ }
+
+ /* BzyMHz */
+ if (has_aperf)
+ outp += sprintf(outp, "%8.0f",
+ 1.0 * t->tsc / units * t->aperf / t->mperf / interval_float);
+
+ /* TSC */
+ outp += sprintf(outp, "%8.0f", 1.0 * t->tsc/units/interval_float);
+
+ /* SMI */
+ if (do_smi)
+ outp += sprintf(outp, "%8d", t->smi_count);
+
+ /* delta */
+ if (extra_delta_offset32)
+ outp += sprintf(outp, " %11llu", t->extra_delta32);
+
+ /* DELTA */
+ if (extra_delta_offset64)
+ outp += sprintf(outp, " %11llu", t->extra_delta64);
+ /* msr */
+ if (extra_msr_offset32)
+ outp += sprintf(outp, " 0x%08llx", t->extra_msr32);
+
+ /* MSR */
+ if (extra_msr_offset64)
+ outp += sprintf(outp, " 0x%016llx", t->extra_msr64);
+
+ if (do_nhm_cstates) {
+ if (!skip_c1)
+ outp += sprintf(outp, "%8.2f", 100.0 * t->c1/t->tsc);
+ else
+ outp += sprintf(outp, "********");
+ }
+
+ /* print per-core data only for 1st thread in core */
+ if (!(t->flags & CPU_IS_FIRST_THREAD_IN_CORE))
+ goto done;
+
+ if (do_nhm_cstates && !do_slm_cstates)
+ outp += sprintf(outp, "%8.2f", 100.0 * c->c3/t->tsc);
+ if (do_nhm_cstates)
+ outp += sprintf(outp, "%8.2f", 100.0 * c->c6/t->tsc);
+ if (do_snb_cstates)
+ outp += sprintf(outp, "%8.2f", 100.0 * c->c7/t->tsc);
+
+ if (do_dts)
+ outp += sprintf(outp, "%8d", c->core_temp_c);
+
+ /* print per-package data only for 1st core in package */
+ if (!(t->flags & CPU_IS_FIRST_CORE_IN_PACKAGE))
+ goto done;
+
+ if (do_ptm)
+ outp += sprintf(outp, "%8d", p->pkg_temp_c);
+
+ if (do_snb_cstates)
+ outp += sprintf(outp, "%8.2f", 100.0 * p->pc2/t->tsc);
+ if (do_nhm_cstates && !do_slm_cstates)
+ outp += sprintf(outp, "%8.2f", 100.0 * p->pc3/t->tsc);
+ if (do_nhm_cstates && !do_slm_cstates)
+ outp += sprintf(outp, "%8.2f", 100.0 * p->pc6/t->tsc);
+ if (do_snb_cstates)
+ outp += sprintf(outp, "%8.2f", 100.0 * p->pc7/t->tsc);
+ if (do_c8_c9_c10) {
+ outp += sprintf(outp, "%8.2f", 100.0 * p->pc8/t->tsc);
+ outp += sprintf(outp, "%8.2f", 100.0 * p->pc9/t->tsc);
+ outp += sprintf(outp, "%8.2f", 100.0 * p->pc10/t->tsc);
+ }
+
+ /*
+ * If measurement interval exceeds minimum RAPL Joule Counter range,
+ * indicate that results are suspect by printing "**" in fraction place.
+ */
+ if (interval_float < rapl_joule_counter_range)
+ fmt8 = "%8.2f";
+ else
+ fmt8 = " %6.0f**";
+
+ if (do_rapl && !rapl_joules) {
+ if (do_rapl & RAPL_PKG)
+ outp += sprintf(outp, fmt8, p->energy_pkg * rapl_energy_units / interval_float);
+ if (do_rapl & RAPL_CORES)
+ outp += sprintf(outp, fmt8, p->energy_cores * rapl_energy_units / interval_float);
+ if (do_rapl & RAPL_GFX)
+ outp += sprintf(outp, fmt8, p->energy_gfx * rapl_energy_units / interval_float);
+ if (do_rapl & RAPL_DRAM)
+ outp += sprintf(outp, fmt8, p->energy_dram * rapl_energy_units / interval_float);
+ if (do_rapl & RAPL_PKG_PERF_STATUS)
+ outp += sprintf(outp, fmt8, 100.0 * p->rapl_pkg_perf_status * rapl_time_units / interval_float);
+ if (do_rapl & RAPL_DRAM_PERF_STATUS)
+ outp += sprintf(outp, fmt8, 100.0 * p->rapl_dram_perf_status * rapl_time_units / interval_float);
+ } else {
+ if (do_rapl & RAPL_PKG)
+ outp += sprintf(outp, fmt8,
+ p->energy_pkg * rapl_energy_units);
+ if (do_rapl & RAPL_CORES)
+ outp += sprintf(outp, fmt8,
+ p->energy_cores * rapl_energy_units);
+ if (do_rapl & RAPL_GFX)
+ outp += sprintf(outp, fmt8,
+ p->energy_gfx * rapl_energy_units);
+ if (do_rapl & RAPL_DRAM)
+ outp += sprintf(outp, fmt8,
+ p->energy_dram * rapl_energy_units);
+ if (do_rapl & RAPL_PKG_PERF_STATUS)
+ outp += sprintf(outp, fmt8, 100.0 * p->rapl_pkg_perf_status * rapl_time_units / interval_float);
+ if (do_rapl & RAPL_DRAM_PERF_STATUS)
+ outp += sprintf(outp, fmt8, 100.0 * p->rapl_dram_perf_status * rapl_time_units / interval_float);
+ outp += sprintf(outp, fmt8, interval_float);
+
+ }
+done:
+ outp += sprintf(outp, "\n");
+
+ return 0;
+}
+
+void flush_stdout()
+{
+ fputs(output_buffer, stdout);
+ fflush(stdout);
+ outp = output_buffer;
+}
+void flush_stderr()
+{
+ fputs(output_buffer, stderr);
+ outp = output_buffer;
+}
+void format_all_counters(struct thread_data *t, struct core_data *c, struct pkg_data *p)
+{
+ static int printed;
+
+ if (!printed || !summary_only)
+ print_header();
+
+ if (topo.num_cpus > 1)
+ format_counters(&average.threads, &average.cores,
+ &average.packages);
+
+ printed = 1;
+
+ if (summary_only)
+ return;
+
+ for_all_cpus(format_counters, t, c, p);
+}
+
+#define DELTA_WRAP32(new, old) \
+ if (new > old) { \
+ old = new - old; \
+ } else { \
+ old = 0x100000000 + new - old; \
+ }
+
+void
+delta_package(struct pkg_data *new, struct pkg_data *old)
+{
+ old->pc2 = new->pc2 - old->pc2;
+ old->pc3 = new->pc3 - old->pc3;
+ old->pc6 = new->pc6 - old->pc6;
+ old->pc7 = new->pc7 - old->pc7;
+ old->pc8 = new->pc8 - old->pc8;
+ old->pc9 = new->pc9 - old->pc9;
+ old->pc10 = new->pc10 - old->pc10;
+ old->pkg_temp_c = new->pkg_temp_c;
+
+ DELTA_WRAP32(new->energy_pkg, old->energy_pkg);
+ DELTA_WRAP32(new->energy_cores, old->energy_cores);
+ DELTA_WRAP32(new->energy_gfx, old->energy_gfx);
+ DELTA_WRAP32(new->energy_dram, old->energy_dram);
+ DELTA_WRAP32(new->rapl_pkg_perf_status, old->rapl_pkg_perf_status);
+ DELTA_WRAP32(new->rapl_dram_perf_status, old->rapl_dram_perf_status);
+}
+
+void
+delta_core(struct core_data *new, struct core_data *old)
+{
+ old->c3 = new->c3 - old->c3;
+ old->c6 = new->c6 - old->c6;
+ old->c7 = new->c7 - old->c7;
+ old->core_temp_c = new->core_temp_c;
+}
+
+/*
+ * old = new - old
+ */
+void
+delta_thread(struct thread_data *new, struct thread_data *old,
+ struct core_data *core_delta)
+{
+ old->tsc = new->tsc - old->tsc;
+
+ /* check for TSC < 1 Mcycles over interval */
+ if (old->tsc < (1000 * 1000))
+ errx(-3, "Insanely slow TSC rate, TSC stops in idle?\n"
+ "You can disable all c-states by booting with \"idle=poll\"\n"
+ "or just the deep ones with \"processor.max_cstate=1\"");
+
+ old->c1 = new->c1 - old->c1;
+
+ if ((new->aperf > old->aperf) && (new->mperf > old->mperf)) {
+ old->aperf = new->aperf - old->aperf;
+ old->mperf = new->mperf - old->mperf;
+ } else {
+
+ if (!aperf_mperf_unstable) {
+ fprintf(stderr, "%s: APERF or MPERF went backwards *\n", progname);
+ fprintf(stderr, "* Frequency results do not cover entire interval *\n");
+ fprintf(stderr, "* fix this by running Linux-2.6.30 or later *\n");
+
+ aperf_mperf_unstable = 1;
+ }
+ /*
+ * mperf delta is likely a huge "positive" number
+ * can not use it for calculating c0 time
+ */
+ skip_c0 = 1;
+ skip_c1 = 1;
+ }
+
+
+ if (use_c1_residency_msr) {
+ /*
+ * Some models have a dedicated C1 residency MSR,
+ * which should be more accurate than the derivation below.
+ */
+ } else {
+ /*
+ * As counter collection is not atomic,
+ * it is possible for mperf's non-halted cycles + idle states
+ * to exceed TSC's all cycles: show c1 = 0% in that case.
+ */
+ if ((old->mperf + core_delta->c3 + core_delta->c6 + core_delta->c7) > old->tsc)
+ old->c1 = 0;
+ else {
+ /* normal case, derive c1 */
+ old->c1 = old->tsc - old->mperf - core_delta->c3
+ - core_delta->c6 - core_delta->c7;
+ }
+ }
+
+ if (old->mperf == 0) {
+ if (verbose > 1) fprintf(stderr, "cpu%d MPERF 0!\n", old->cpu_id);
+ old->mperf = 1; /* divide by 0 protection */
+ }
+
+ old->extra_delta32 = new->extra_delta32 - old->extra_delta32;
+ old->extra_delta32 &= 0xFFFFFFFF;
+
+ old->extra_delta64 = new->extra_delta64 - old->extra_delta64;
+
+ /*
+ * Extra MSR is just a snapshot, simply copy latest w/o subtracting
+ */
+ old->extra_msr32 = new->extra_msr32;
+ old->extra_msr64 = new->extra_msr64;
+
+ if (do_smi)
+ old->smi_count = new->smi_count - old->smi_count;
+}
+
+int delta_cpu(struct thread_data *t, struct core_data *c,
+ struct pkg_data *p, struct thread_data *t2,
+ struct core_data *c2, struct pkg_data *p2)
+{
+ /* calculate core delta only for 1st thread in core */
+ if (t->flags & CPU_IS_FIRST_THREAD_IN_CORE)
+ delta_core(c, c2);
+
+ /* always calculate thread delta */
+ delta_thread(t, t2, c2); /* c2 is core delta */
+
+ /* calculate package delta only for 1st core in package */
+ if (t->flags & CPU_IS_FIRST_CORE_IN_PACKAGE)
+ delta_package(p, p2);
+
+ return 0;
+}
+
+void clear_counters(struct thread_data *t, struct core_data *c, struct pkg_data *p)
+{
+ t->tsc = 0;
+ t->aperf = 0;
+ t->mperf = 0;
+ t->c1 = 0;
+
+ t->smi_count = 0;
+ t->extra_delta32 = 0;
+ t->extra_delta64 = 0;
+
+ /* tells format_counters to dump all fields from this set */
+ t->flags = CPU_IS_FIRST_THREAD_IN_CORE | CPU_IS_FIRST_CORE_IN_PACKAGE;
+
+ c->c3 = 0;
+ c->c6 = 0;
+ c->c7 = 0;
+ c->core_temp_c = 0;
+
+ p->pc2 = 0;
+ p->pc3 = 0;
+ p->pc6 = 0;
+ p->pc7 = 0;
+ p->pc8 = 0;
+ p->pc9 = 0;
+ p->pc10 = 0;
+
+ p->energy_pkg = 0;
+ p->energy_dram = 0;
+ p->energy_cores = 0;
+ p->energy_gfx = 0;
+ p->rapl_pkg_perf_status = 0;
+ p->rapl_dram_perf_status = 0;
+ p->pkg_temp_c = 0;
+}
+int sum_counters(struct thread_data *t, struct core_data *c,
+ struct pkg_data *p)
+{
+ average.threads.tsc += t->tsc;
+ average.threads.aperf += t->aperf;
+ average.threads.mperf += t->mperf;
+ average.threads.c1 += t->c1;
+
+ average.threads.extra_delta32 += t->extra_delta32;
+ average.threads.extra_delta64 += t->extra_delta64;
+
+ /* sum per-core values only for 1st thread in core */
+ if (!(t->flags & CPU_IS_FIRST_THREAD_IN_CORE))
+ return 0;
+
+ average.cores.c3 += c->c3;
+ average.cores.c6 += c->c6;
+ average.cores.c7 += c->c7;
+
+ average.cores.core_temp_c = MAX(average.cores.core_temp_c, c->core_temp_c);
+
+ /* sum per-pkg values only for 1st core in pkg */
+ if (!(t->flags & CPU_IS_FIRST_CORE_IN_PACKAGE))
+ return 0;
+
+ average.packages.pc2 += p->pc2;
+ average.packages.pc3 += p->pc3;
+ average.packages.pc6 += p->pc6;
+ average.packages.pc7 += p->pc7;
+ average.packages.pc8 += p->pc8;
+ average.packages.pc9 += p->pc9;
+ average.packages.pc10 += p->pc10;
+
+ average.packages.energy_pkg += p->energy_pkg;
+ average.packages.energy_dram += p->energy_dram;
+ average.packages.energy_cores += p->energy_cores;
+ average.packages.energy_gfx += p->energy_gfx;
+
+ average.packages.pkg_temp_c = MAX(average.packages.pkg_temp_c, p->pkg_temp_c);
+
+ average.packages.rapl_pkg_perf_status += p->rapl_pkg_perf_status;
+ average.packages.rapl_dram_perf_status += p->rapl_dram_perf_status;
+ return 0;
+}
+/*
+ * sum the counters for all cpus in the system
+ * compute the weighted average
+ */
+void compute_average(struct thread_data *t, struct core_data *c,
+ struct pkg_data *p)
+{
+ clear_counters(&average.threads, &average.cores, &average.packages);
+
+ for_all_cpus(sum_counters, t, c, p);
+
+ average.threads.tsc /= topo.num_cpus;
+ average.threads.aperf /= topo.num_cpus;
+ average.threads.mperf /= topo.num_cpus;
+ average.threads.c1 /= topo.num_cpus;
+
+ average.threads.extra_delta32 /= topo.num_cpus;
+ average.threads.extra_delta32 &= 0xFFFFFFFF;
+
+ average.threads.extra_delta64 /= topo.num_cpus;
+
+ average.cores.c3 /= topo.num_cores;
+ average.cores.c6 /= topo.num_cores;
+ average.cores.c7 /= topo.num_cores;
+
+ average.packages.pc2 /= topo.num_packages;
+ average.packages.pc3 /= topo.num_packages;
+ average.packages.pc6 /= topo.num_packages;
+ average.packages.pc7 /= topo.num_packages;
+
+ average.packages.pc8 /= topo.num_packages;
+ average.packages.pc9 /= topo.num_packages;
+ average.packages.pc10 /= topo.num_packages;
+}
+
+static unsigned long long rdtsc(void)
+{
+ unsigned int low, high;
+
+ asm volatile("rdtsc" : "=a" (low), "=d" (high));
+
+ return low | ((unsigned long long)high) << 32;
+}
+
+
+/*
+ * get_counters(...)
+ * migrate to cpu
+ * acquire and record local counters for that cpu
+ */
+int get_counters(struct thread_data *t, struct core_data *c, struct pkg_data *p)
+{
+ int cpu = t->cpu_id;
+ unsigned long long msr;
+
+ if (cpu_migrate(cpu)) {
+ fprintf(stderr, "Could not migrate to CPU %d\n", cpu);
+ return -1;
+ }
+
+ t->tsc = rdtsc(); /* we are running on local CPU of interest */
+
+ if (has_aperf) {
+ if (get_msr(cpu, MSR_IA32_APERF, &t->aperf))
+ return -3;
+ if (get_msr(cpu, MSR_IA32_MPERF, &t->mperf))
+ return -4;
+ }
+
+ if (do_smi) {
+ if (get_msr(cpu, MSR_SMI_COUNT, &msr))
+ return -5;
+ t->smi_count = msr & 0xFFFFFFFF;
+ }
+ if (extra_delta_offset32) {
+ if (get_msr(cpu, extra_delta_offset32, &msr))
+ return -5;
+ t->extra_delta32 = msr & 0xFFFFFFFF;
+ }
+
+ if (extra_delta_offset64)
+ if (get_msr(cpu, extra_delta_offset64, &t->extra_delta64))
+ return -5;
+
+ if (extra_msr_offset32) {
+ if (get_msr(cpu, extra_msr_offset32, &msr))
+ return -5;
+ t->extra_msr32 = msr & 0xFFFFFFFF;
+ }
+
+ if (extra_msr_offset64)
+ if (get_msr(cpu, extra_msr_offset64, &t->extra_msr64))
+ return -5;
+
+ if (use_c1_residency_msr) {
+ if (get_msr(cpu, MSR_CORE_C1_RES, &t->c1))
+ return -6;
+ }
+
+ /* collect core counters only for 1st thread in core */
+ if (!(t->flags & CPU_IS_FIRST_THREAD_IN_CORE))
+ return 0;
+
+ if (do_nhm_cstates && !do_slm_cstates) {
+ if (get_msr(cpu, MSR_CORE_C3_RESIDENCY, &c->c3))
+ return -6;
+ }
+
+ if (do_nhm_cstates) {
+ if (get_msr(cpu, MSR_CORE_C6_RESIDENCY, &c->c6))
+ return -7;
+ }
+
+ if (do_snb_cstates)
+ if (get_msr(cpu, MSR_CORE_C7_RESIDENCY, &c->c7))
+ return -8;
+
+ if (do_dts) {
+ if (get_msr(cpu, MSR_IA32_THERM_STATUS, &msr))
+ return -9;
+ c->core_temp_c = tcc_activation_temp - ((msr >> 16) & 0x7F);
+ }
+
+
+ /* collect package counters only for 1st core in package */
+ if (!(t->flags & CPU_IS_FIRST_CORE_IN_PACKAGE))
+ return 0;
+
+ if (do_nhm_cstates && !do_slm_cstates) {
+ if (get_msr(cpu, MSR_PKG_C3_RESIDENCY, &p->pc3))
+ return -9;
+ if (get_msr(cpu, MSR_PKG_C6_RESIDENCY, &p->pc6))
+ return -10;
+ }
+ if (do_snb_cstates) {
+ if (get_msr(cpu, MSR_PKG_C2_RESIDENCY, &p->pc2))
+ return -11;
+ if (get_msr(cpu, MSR_PKG_C7_RESIDENCY, &p->pc7))
+ return -12;
+ }
+ if (do_c8_c9_c10) {
+ if (get_msr(cpu, MSR_PKG_C8_RESIDENCY, &p->pc8))
+ return -13;
+ if (get_msr(cpu, MSR_PKG_C9_RESIDENCY, &p->pc9))
+ return -13;
+ if (get_msr(cpu, MSR_PKG_C10_RESIDENCY, &p->pc10))
+ return -13;
+ }
+ if (do_rapl & RAPL_PKG) {
+ if (get_msr(cpu, MSR_PKG_ENERGY_STATUS, &msr))
+ return -13;
+ p->energy_pkg = msr & 0xFFFFFFFF;
+ }
+ if (do_rapl & RAPL_CORES) {
+ if (get_msr(cpu, MSR_PP0_ENERGY_STATUS, &msr))
+ return -14;
+ p->energy_cores = msr & 0xFFFFFFFF;
+ }
+ if (do_rapl & RAPL_DRAM) {
+ if (get_msr(cpu, MSR_DRAM_ENERGY_STATUS, &msr))
+ return -15;
+ p->energy_dram = msr & 0xFFFFFFFF;
+ }
+ if (do_rapl & RAPL_GFX) {
+ if (get_msr(cpu, MSR_PP1_ENERGY_STATUS, &msr))
+ return -16;
+ p->energy_gfx = msr & 0xFFFFFFFF;
+ }
+ if (do_rapl & RAPL_PKG_PERF_STATUS) {
+ if (get_msr(cpu, MSR_PKG_PERF_STATUS, &msr))
+ return -16;
+ p->rapl_pkg_perf_status = msr & 0xFFFFFFFF;
+ }
+ if (do_rapl & RAPL_DRAM_PERF_STATUS) {
+ if (get_msr(cpu, MSR_DRAM_PERF_STATUS, &msr))
+ return -16;
+ p->rapl_dram_perf_status = msr & 0xFFFFFFFF;
+ }
+ if (do_ptm) {
+ if (get_msr(cpu, MSR_IA32_PACKAGE_THERM_STATUS, &msr))
+ return -17;
+ p->pkg_temp_c = tcc_activation_temp - ((msr >> 16) & 0x7F);
+ }
+ return 0;
+}
+
+void print_verbose_header(void)
+{
+ unsigned long long msr;
+ unsigned int ratio;
+
+ if (!do_nehalem_platform_info)
+ return;
+
+ get_msr(0, MSR_NHM_PLATFORM_INFO, &msr);
+
+ fprintf(stderr, "cpu0: MSR_NHM_PLATFORM_INFO: 0x%08llx\n", msr);
+
+ ratio = (msr >> 40) & 0xFF;
+ fprintf(stderr, "%d * %.0f = %.0f MHz max efficiency\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 8) & 0xFF;
+ fprintf(stderr, "%d * %.0f = %.0f MHz TSC frequency\n",
+ ratio, bclk, ratio * bclk);
+
+ get_msr(0, MSR_IA32_POWER_CTL, &msr);
+ fprintf(stderr, "cpu0: MSR_IA32_POWER_CTL: 0x%08llx (C1E auto-promotion: %sabled)\n",
+ msr, msr & 0x2 ? "EN" : "DIS");
+
+ if (!do_ivt_turbo_ratio_limit)
+ goto print_nhm_turbo_ratio_limits;
+
+ get_msr(0, MSR_IVT_TURBO_RATIO_LIMIT, &msr);
+
+ fprintf(stderr, "cpu0: MSR_IVT_TURBO_RATIO_LIMIT: 0x%08llx\n", msr);
+
+ ratio = (msr >> 56) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 16 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 48) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 15 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 40) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 14 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 32) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 13 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 24) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 12 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 16) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 11 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 8) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 10 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 0) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 9 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+print_nhm_turbo_ratio_limits:
+ get_msr(0, MSR_NHM_SNB_PKG_CST_CFG_CTL, &msr);
+
+#define SNB_C1_AUTO_UNDEMOTE (1UL << 27)
+#define SNB_C3_AUTO_UNDEMOTE (1UL << 28)
+
+ fprintf(stderr, "cpu0: MSR_NHM_SNB_PKG_CST_CFG_CTL: 0x%08llx", msr);
+
+ fprintf(stderr, " (%s%s%s%s%slocked: pkg-cstate-limit=%d: ",
+ (msr & SNB_C3_AUTO_UNDEMOTE) ? "UNdemote-C3, " : "",
+ (msr & SNB_C1_AUTO_UNDEMOTE) ? "UNdemote-C1, " : "",
+ (msr & NHM_C3_AUTO_DEMOTE) ? "demote-C3, " : "",
+ (msr & NHM_C1_AUTO_DEMOTE) ? "demote-C1, " : "",
+ (msr & (1 << 15)) ? "" : "UN",
+ (unsigned int)msr & 7);
+
+
+ switch(msr & 0x7) {
+ case 0:
+ fprintf(stderr, do_slm_cstates ? "no pkg states" : "pc0");
+ break;
+ case 1:
+ fprintf(stderr, do_slm_cstates ? "no pkg states" : do_snb_cstates ? "pc2" : "pc0");
+ break;
+ case 2:
+ fprintf(stderr, do_slm_cstates ? "invalid" : do_snb_cstates ? "pc6-noret" : "pc3");
+ break;
+ case 3:
+ fprintf(stderr, do_slm_cstates ? "invalid" : "pc6");
+ break;
+ case 4:
+ fprintf(stderr, do_slm_cstates ? "pc4" : "pc7");
+ break;
+ case 5:
+ fprintf(stderr, do_slm_cstates ? "invalid" : do_snb_cstates ? "pc7s" : "invalid");
+ break;
+ case 6:
+ fprintf(stderr, do_slm_cstates ? "pc6" : "invalid");
+ break;
+ case 7:
+ fprintf(stderr, do_slm_cstates ? "pc7" : "unlimited");
+ break;
+ default:
+ fprintf(stderr, "invalid");
+ }
+ fprintf(stderr, ")\n");
+
+ if (!do_nehalem_turbo_ratio_limit)
+ return;
+
+ get_msr(0, MSR_NHM_TURBO_RATIO_LIMIT, &msr);
+
+ fprintf(stderr, "cpu0: MSR_NHM_TURBO_RATIO_LIMIT: 0x%08llx\n", msr);
+
+ ratio = (msr >> 56) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 8 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 48) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 7 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 40) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 6 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 32) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 5 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 24) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 4 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 16) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 3 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 8) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 2 active cores\n",
+ ratio, bclk, ratio * bclk);
+
+ ratio = (msr >> 0) & 0xFF;
+ if (ratio)
+ fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 1 active cores\n",
+ ratio, bclk, ratio * bclk);
+}
+
+void free_all_buffers(void)
+{
+ CPU_FREE(cpu_present_set);
+ cpu_present_set = NULL;
+ cpu_present_set = 0;
+
+ CPU_FREE(cpu_affinity_set);
+ cpu_affinity_set = NULL;
+ cpu_affinity_setsize = 0;
+
+ free(thread_even);
+ free(core_even);
+ free(package_even);
+
+ thread_even = NULL;
+ core_even = NULL;
+ package_even = NULL;
+
+ free(thread_odd);
+ free(core_odd);
+ free(package_odd);
+
+ thread_odd = NULL;
+ core_odd = NULL;
+ package_odd = NULL;
+
+ free(output_buffer);
+ output_buffer = NULL;
+ outp = NULL;
+}
+
+/*
+ * Open a file, and exit on failure
+ */
+FILE *fopen_or_die(const char *path, const char *mode)
+{
+ FILE *filep = fopen(path, "r");
+ if (!filep)
+ err(1, "%s: open failed", path);
+ return filep;
+}
+
+/*
+ * Parse a file containing a single int.
+ */
+int parse_int_file(const char *fmt, ...)
+{
+ va_list args;
+ char path[PATH_MAX];
+ FILE *filep;
+ int value;
+
+ va_start(args, fmt);
+ vsnprintf(path, sizeof(path), fmt, args);
+ va_end(args);
+ filep = fopen_or_die(path, "r");
+ if (fscanf(filep, "%d", &value) != 1)
+ err(1, "%s: failed to parse number from file", path);
+ fclose(filep);
+ return value;
+}
+
+/*
+ * cpu_is_first_sibling_in_core(cpu)
+ * return 1 if given CPU is 1st HT sibling in the core
+ */
+int cpu_is_first_sibling_in_core(int cpu)
+{
+ return cpu == parse_int_file("/sys/devices/system/cpu/cpu%d/topology/thread_siblings_list", cpu);
+}
+
+/*
+ * cpu_is_first_core_in_package(cpu)
+ * return 1 if given CPU is 1st core in package
+ */
+int cpu_is_first_core_in_package(int cpu)
+{
+ return cpu == parse_int_file("/sys/devices/system/cpu/cpu%d/topology/core_siblings_list", cpu);
+}
+
+int get_physical_package_id(int cpu)
+{
+ return parse_int_file("/sys/devices/system/cpu/cpu%d/topology/physical_package_id", cpu);
+}
+
+int get_core_id(int cpu)
+{
+ return parse_int_file("/sys/devices/system/cpu/cpu%d/topology/core_id", cpu);
+}
+
+int get_num_ht_siblings(int cpu)
+{
+ char path[80];
+ FILE *filep;
+ int sib1, sib2;
+ int matches;
+ char character;
+
+ sprintf(path, "/sys/devices/system/cpu/cpu%d/topology/thread_siblings_list", cpu);
+ filep = fopen_or_die(path, "r");
+ /*
+ * file format:
+ * if a pair of number with a character between: 2 siblings (eg. 1-2, or 1,4)
+ * otherwinse 1 sibling (self).
+ */
+ matches = fscanf(filep, "%d%c%d\n", &sib1, &character, &sib2);
+
+ fclose(filep);
+
+ if (matches == 3)
+ return 2;
+ else
+ return 1;
+}
+
+/*
+ * run func(thread, core, package) in topology order
+ * skip non-present cpus
+ */
+
+int for_all_cpus_2(int (func)(struct thread_data *, struct core_data *,
+ struct pkg_data *, struct thread_data *, struct core_data *,
+ struct pkg_data *), struct thread_data *thread_base,
+ struct core_data *core_base, struct pkg_data *pkg_base,
+ struct thread_data *thread_base2, struct core_data *core_base2,
+ struct pkg_data *pkg_base2)
+{
+ int retval, pkg_no, core_no, thread_no;
+
+ for (pkg_no = 0; pkg_no < topo.num_packages; ++pkg_no) {
+ for (core_no = 0; core_no < topo.num_cores_per_pkg; ++core_no) {
+ for (thread_no = 0; thread_no <
+ topo.num_threads_per_core; ++thread_no) {
+ struct thread_data *t, *t2;
+ struct core_data *c, *c2;
+ struct pkg_data *p, *p2;
+
+ t = GET_THREAD(thread_base, thread_no, core_no, pkg_no);
+
+ if (cpu_is_not_present(t->cpu_id))
+ continue;
+
+ t2 = GET_THREAD(thread_base2, thread_no, core_no, pkg_no);
+
+ c = GET_CORE(core_base, core_no, pkg_no);
+ c2 = GET_CORE(core_base2, core_no, pkg_no);
+
+ p = GET_PKG(pkg_base, pkg_no);
+ p2 = GET_PKG(pkg_base2, pkg_no);
+
+ retval = func(t, c, p, t2, c2, p2);
+ if (retval)
+ return retval;
+ }
+ }
+ }
+ return 0;
+}
+
+/*
+ * run func(cpu) on every cpu in /proc/stat
+ * return max_cpu number
+ */
+int for_all_proc_cpus(int (func)(int))
+{
+ FILE *fp;
+ int cpu_num;
+ int retval;
+
+ fp = fopen_or_die(proc_stat, "r");
+
+ retval = fscanf(fp, "cpu %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d\n");
+ if (retval != 0)
+ err(1, "%s: failed to parse format", proc_stat);
+
+ while (1) {
+ retval = fscanf(fp, "cpu%u %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d\n", &cpu_num);
+ if (retval != 1)
+ break;
+
+ retval = func(cpu_num);
+ if (retval) {
+ fclose(fp);
+ return(retval);
+ }
+ }
+ fclose(fp);
+ return 0;
+}
+
+void re_initialize(void)
+{
+ free_all_buffers();
+ setup_all_buffers();
+ printf("turbostat: re-initialized with num_cpus %d\n", topo.num_cpus);
+}
+
+
+/*
+ * count_cpus()
+ * remember the last one seen, it will be the max
+ */
+int count_cpus(int cpu)
+{
+ if (topo.max_cpu_num < cpu)
+ topo.max_cpu_num = cpu;
+
+ topo.num_cpus += 1;
+ return 0;
+}
+int mark_cpu_present(int cpu)
+{
+ CPU_SET_S(cpu, cpu_present_setsize, cpu_present_set);
+ return 0;
+}
+
+void turbostat_loop()
+{
+ int retval;
+ int restarted = 0;
+
+restart:
+ restarted++;
+
+ retval = for_all_cpus(get_counters, EVEN_COUNTERS);
+ if (retval < -1) {
+ exit(retval);
+ } else if (retval == -1) {
+ if (restarted > 1) {
+ exit(retval);
+ }
+ re_initialize();
+ goto restart;
+ }
+ restarted = 0;
+ gettimeofday(&tv_even, (struct timezone *)NULL);
+
+ while (1) {
+ if (for_all_proc_cpus(cpu_is_not_present)) {
+ re_initialize();
+ goto restart;
+ }
+ sleep(interval_sec);
+ retval = for_all_cpus(get_counters, ODD_COUNTERS);
+ if (retval < -1) {
+ exit(retval);
+ } else if (retval == -1) {
+ re_initialize();
+ goto restart;
+ }
+ gettimeofday(&tv_odd, (struct timezone *)NULL);
+ timersub(&tv_odd, &tv_even, &tv_delta);
+ for_all_cpus_2(delta_cpu, ODD_COUNTERS, EVEN_COUNTERS);
+ compute_average(EVEN_COUNTERS);
+ format_all_counters(EVEN_COUNTERS);
+ flush_stdout();
+ sleep(interval_sec);
+ retval = for_all_cpus(get_counters, EVEN_COUNTERS);
+ if (retval < -1) {
+ exit(retval);
+ } else if (retval == -1) {
+ re_initialize();
+ goto restart;
+ }
+ gettimeofday(&tv_even, (struct timezone *)NULL);
+ timersub(&tv_even, &tv_odd, &tv_delta);
+ for_all_cpus_2(delta_cpu, EVEN_COUNTERS, ODD_COUNTERS);
+ compute_average(ODD_COUNTERS);
+ format_all_counters(ODD_COUNTERS);
+ flush_stdout();
+ }
+}
+
+void check_dev_msr()
+{
+ struct stat sb;
+
+ if (stat("/dev/cpu/0/msr", &sb))
+ err(-5, "no /dev/cpu/0/msr\n"
+ "Try \"# modprobe msr\"");
+}
+
+void check_super_user()
+{
+ if (getuid() != 0)
+ errx(-6, "must be root");
+}
+
+int has_nehalem_turbo_ratio_limit(unsigned int family, unsigned int model)
+{
+ if (!genuine_intel)
+ return 0;
+
+ if (family != 6)
+ return 0;
+
+ switch (model) {
+ case 0x1A: /* Core i7, Xeon 5500 series - Bloomfield, Gainstown NHM-EP */
+ case 0x1E: /* Core i7 and i5 Processor - Clarksfield, Lynnfield, Jasper Forest */
+ case 0x1F: /* Core i7 and i5 Processor - Nehalem */
+ case 0x25: /* Westmere Client - Clarkdale, Arrandale */
+ case 0x2C: /* Westmere EP - Gulftown */
+ case 0x2A: /* SNB */
+ case 0x2D: /* SNB Xeon */
+ case 0x3A: /* IVB */
+ case 0x3E: /* IVB Xeon */
+ case 0x3C: /* HSW */
+ case 0x3F: /* HSX */
+ case 0x45: /* HSW */
+ case 0x46: /* HSW */
+ case 0x37: /* BYT */
+ case 0x4D: /* AVN */
+ case 0x3D: /* BDW */
+ case 0x4F: /* BDX */
+ case 0x56: /* BDX-DE */
+ return 1;
+ case 0x2E: /* Nehalem-EX Xeon - Beckton */
+ case 0x2F: /* Westmere-EX Xeon - Eagleton */
+ default:
+ return 0;
+ }
+}
+int has_ivt_turbo_ratio_limit(unsigned int family, unsigned int model)
+{
+ if (!genuine_intel)
+ return 0;
+
+ if (family != 6)
+ return 0;
+
+ switch (model) {
+ case 0x3E: /* IVB Xeon */
+ return 1;
+ default:
+ return 0;
+ }
+}
+
+/*
+ * print_epb()
+ * Decode the ENERGY_PERF_BIAS MSR
+ */
+int print_epb(struct thread_data *t, struct core_data *c, struct pkg_data *p)
+{
+ unsigned long long msr;
+ char *epb_string;
+ int cpu;
+
+ if (!has_epb)
+ return 0;
+
+ cpu = t->cpu_id;
+
+ /* EPB is per-package */
+ if (!(t->flags & CPU_IS_FIRST_THREAD_IN_CORE) || !(t->flags & CPU_IS_FIRST_CORE_IN_PACKAGE))
+ return 0;
+
+ if (cpu_migrate(cpu)) {
+ fprintf(stderr, "Could not migrate to CPU %d\n", cpu);
+ return -1;
+ }
+
+ if (get_msr(cpu, MSR_IA32_ENERGY_PERF_BIAS, &msr))
+ return 0;
+
+ switch (msr & 0x7) {
+ case ENERGY_PERF_BIAS_PERFORMANCE:
+ epb_string = "performance";
+ break;
+ case ENERGY_PERF_BIAS_NORMAL:
+ epb_string = "balanced";
+ break;
+ case ENERGY_PERF_BIAS_POWERSAVE:
+ epb_string = "powersave";
+ break;
+ default:
+ epb_string = "custom";
+ break;
+ }
+ fprintf(stderr, "cpu%d: MSR_IA32_ENERGY_PERF_BIAS: 0x%08llx (%s)\n", cpu, msr, epb_string);
+
+ return 0;
+}
+
+#define RAPL_POWER_GRANULARITY 0x7FFF /* 15 bit power granularity */
+#define RAPL_TIME_GRANULARITY 0x3F /* 6 bit time granularity */
+
+double get_tdp(model)
+{
+ unsigned long long msr;
+
+ if (do_rapl & RAPL_PKG_POWER_INFO)
+ if (!get_msr(0, MSR_PKG_POWER_INFO, &msr))
+ return ((msr >> 0) & RAPL_POWER_GRANULARITY) * rapl_power_units;
+
+ switch (model) {
+ case 0x37:
+ case 0x4D:
+ return 30.0;
+ default:
+ return 135.0;
+ }
+}
+
+
+/*
+ * rapl_probe()
+ *
+ * sets do_rapl, rapl_power_units, rapl_energy_units, rapl_time_units
+ */
+void rapl_probe(unsigned int family, unsigned int model)
+{
+ unsigned long long msr;
+ unsigned int time_unit;
+ double tdp;
+
+ if (!genuine_intel)
+ return;
+
+ if (family != 6)
+ return;
+
+ switch (model) {
+ case 0x2A:
+ case 0x3A:
+ case 0x3C: /* HSW */
+ case 0x45: /* HSW */
+ case 0x46: /* HSW */
+ case 0x3D: /* BDW */
+ do_rapl = RAPL_PKG | RAPL_CORES | RAPL_CORE_POLICY | RAPL_GFX | RAPL_PKG_POWER_INFO;
+ break;
+ case 0x3F: /* HSX */
+ case 0x4F: /* BDX */
+ case 0x56: /* BDX-DE */
+ do_rapl = RAPL_PKG | RAPL_DRAM | RAPL_DRAM_PERF_STATUS | RAPL_PKG_PERF_STATUS | RAPL_PKG_POWER_INFO;
+ break;
+ case 0x2D:
+ case 0x3E:
+ do_rapl = RAPL_PKG | RAPL_CORES | RAPL_CORE_POLICY | RAPL_DRAM | RAPL_PKG_PERF_STATUS | RAPL_DRAM_PERF_STATUS | RAPL_PKG_POWER_INFO;
+ break;
+ case 0x37: /* BYT */
+ case 0x4D: /* AVN */
+ do_rapl = RAPL_PKG | RAPL_CORES ;
+ break;
+ default:
+ return;
+ }
+
+ /* units on package 0, verify later other packages match */
+ if (get_msr(0, MSR_RAPL_POWER_UNIT, &msr))
+ return;
+
+ rapl_power_units = 1.0 / (1 << (msr & 0xF));
+ if (model == 0x37)
+ rapl_energy_units = 1.0 * (1 << (msr >> 8 & 0x1F)) / 1000000;
+ else
+ rapl_energy_units = 1.0 / (1 << (msr >> 8 & 0x1F));
+
+ time_unit = msr >> 16 & 0xF;
+ if (time_unit == 0)
+ time_unit = 0xA;
+
+ rapl_time_units = 1.0 / (1 << (time_unit));
+
+ tdp = get_tdp(model);
+
+ rapl_joule_counter_range = 0xFFFFFFFF * rapl_energy_units / tdp;
+ if (verbose)
+ fprintf(stderr, "RAPL: %.0f sec. Joule Counter Range, at %.0f Watts\n", rapl_joule_counter_range, tdp);
+
+ return;
+}
+
+int print_thermal(struct thread_data *t, struct core_data *c, struct pkg_data *p)
+{
+ unsigned long long msr;
+ unsigned int dts;
+ int cpu;
+
+ if (!(do_dts || do_ptm))
+ return 0;
+
+ cpu = t->cpu_id;
+
+ /* DTS is per-core, no need to print for each thread */
+ if (!(t->flags & CPU_IS_FIRST_THREAD_IN_CORE))
+ return 0;
+
+ if (cpu_migrate(cpu)) {
+ fprintf(stderr, "Could not migrate to CPU %d\n", cpu);
+ return -1;
+ }
+
+ if (do_ptm && (t->flags & CPU_IS_FIRST_CORE_IN_PACKAGE)) {
+ if (get_msr(cpu, MSR_IA32_PACKAGE_THERM_STATUS, &msr))
+ return 0;
+
+ dts = (msr >> 16) & 0x7F;
+ fprintf(stderr, "cpu%d: MSR_IA32_PACKAGE_THERM_STATUS: 0x%08llx (%d C)\n",
+ cpu, msr, tcc_activation_temp - dts);
+
+#ifdef THERM_DEBUG
+ if (get_msr(cpu, MSR_IA32_PACKAGE_THERM_INTERRUPT, &msr))
+ return 0;
+
+ dts = (msr >> 16) & 0x7F;
+ dts2 = (msr >> 8) & 0x7F;
+ fprintf(stderr, "cpu%d: MSR_IA32_PACKAGE_THERM_INTERRUPT: 0x%08llx (%d C, %d C)\n",
+ cpu, msr, tcc_activation_temp - dts, tcc_activation_temp - dts2);
+#endif
+ }
+
+
+ if (do_dts) {
+ unsigned int resolution;
+
+ if (get_msr(cpu, MSR_IA32_THERM_STATUS, &msr))
+ return 0;
+
+ dts = (msr >> 16) & 0x7F;
+ resolution = (msr >> 27) & 0xF;
+ fprintf(stderr, "cpu%d: MSR_IA32_THERM_STATUS: 0x%08llx (%d C +/- %d)\n",
+ cpu, msr, tcc_activation_temp - dts, resolution);
+
+#ifdef THERM_DEBUG
+ if (get_msr(cpu, MSR_IA32_THERM_INTERRUPT, &msr))
+ return 0;
+
+ dts = (msr >> 16) & 0x7F;
+ dts2 = (msr >> 8) & 0x7F;
+ fprintf(stderr, "cpu%d: MSR_IA32_THERM_INTERRUPT: 0x%08llx (%d C, %d C)\n",
+ cpu, msr, tcc_activation_temp - dts, tcc_activation_temp - dts2);
+#endif
+ }
+
+ return 0;
+}
+
+void print_power_limit_msr(int cpu, unsigned long long msr, char *label)
+{
+ fprintf(stderr, "cpu%d: %s: %sabled (%f Watts, %f sec, clamp %sabled)\n",
+ cpu, label,
+ ((msr >> 15) & 1) ? "EN" : "DIS",
+ ((msr >> 0) & 0x7FFF) * rapl_power_units,
+ (1.0 + (((msr >> 22) & 0x3)/4.0)) * (1 << ((msr >> 17) & 0x1F)) * rapl_time_units,
+ (((msr >> 16) & 1) ? "EN" : "DIS"));
+
+ return;
+}
+
+int print_rapl(struct thread_data *t, struct core_data *c, struct pkg_data *p)
+{
+ unsigned long long msr;
+ int cpu;
+
+ if (!do_rapl)
+ return 0;
+
+ /* RAPL counters are per package, so print only for 1st thread/package */
+ if (!(t->flags & CPU_IS_FIRST_THREAD_IN_CORE) || !(t->flags & CPU_IS_FIRST_CORE_IN_PACKAGE))
+ return 0;
+
+ cpu = t->cpu_id;
+ if (cpu_migrate(cpu)) {
+ fprintf(stderr, "Could not migrate to CPU %d\n", cpu);
+ return -1;
+ }
+
+ if (get_msr(cpu, MSR_RAPL_POWER_UNIT, &msr))
+ return -1;
+
+ if (verbose) {
+ fprintf(stderr, "cpu%d: MSR_RAPL_POWER_UNIT: 0x%08llx "
+ "(%f Watts, %f Joules, %f sec.)\n", cpu, msr,
+ rapl_power_units, rapl_energy_units, rapl_time_units);
+ }
+ if (do_rapl & RAPL_PKG_POWER_INFO) {
+
+ if (get_msr(cpu, MSR_PKG_POWER_INFO, &msr))
+ return -5;
+
+
+ fprintf(stderr, "cpu%d: MSR_PKG_POWER_INFO: 0x%08llx (%.0f W TDP, RAPL %.0f - %.0f W, %f sec.)\n",
+ cpu, msr,
+ ((msr >> 0) & RAPL_POWER_GRANULARITY) * rapl_power_units,
+ ((msr >> 16) & RAPL_POWER_GRANULARITY) * rapl_power_units,
+ ((msr >> 32) & RAPL_POWER_GRANULARITY) * rapl_power_units,
+ ((msr >> 48) & RAPL_TIME_GRANULARITY) * rapl_time_units);
+
+ }
+ if (do_rapl & RAPL_PKG) {
+
+ if (get_msr(cpu, MSR_PKG_POWER_LIMIT, &msr))
+ return -9;
+
+ fprintf(stderr, "cpu%d: MSR_PKG_POWER_LIMIT: 0x%08llx (%slocked)\n",
+ cpu, msr, (msr >> 63) & 1 ? "": "UN");
+
+ print_power_limit_msr(cpu, msr, "PKG Limit #1");
+ fprintf(stderr, "cpu%d: PKG Limit #2: %sabled (%f Watts, %f* sec, clamp %sabled)\n",
+ cpu,
+ ((msr >> 47) & 1) ? "EN" : "DIS",
+ ((msr >> 32) & 0x7FFF) * rapl_power_units,
+ (1.0 + (((msr >> 54) & 0x3)/4.0)) * (1 << ((msr >> 49) & 0x1F)) * rapl_time_units,
+ ((msr >> 48) & 1) ? "EN" : "DIS");
+ }
+
+ if (do_rapl & RAPL_DRAM) {
+ if (get_msr(cpu, MSR_DRAM_POWER_INFO, &msr))
+ return -6;
+
+
+ fprintf(stderr, "cpu%d: MSR_DRAM_POWER_INFO,: 0x%08llx (%.0f W TDP, RAPL %.0f - %.0f W, %f sec.)\n",
+ cpu, msr,
+ ((msr >> 0) & RAPL_POWER_GRANULARITY) * rapl_power_units,
+ ((msr >> 16) & RAPL_POWER_GRANULARITY) * rapl_power_units,
+ ((msr >> 32) & RAPL_POWER_GRANULARITY) * rapl_power_units,
+ ((msr >> 48) & RAPL_TIME_GRANULARITY) * rapl_time_units);
+
+
+ if (get_msr(cpu, MSR_DRAM_POWER_LIMIT, &msr))
+ return -9;
+ fprintf(stderr, "cpu%d: MSR_DRAM_POWER_LIMIT: 0x%08llx (%slocked)\n",
+ cpu, msr, (msr >> 31) & 1 ? "": "UN");
+
+ print_power_limit_msr(cpu, msr, "DRAM Limit");
+ }
+ if (do_rapl & RAPL_CORE_POLICY) {
+ if (verbose) {
+ if (get_msr(cpu, MSR_PP0_POLICY, &msr))
+ return -7;
+
+ fprintf(stderr, "cpu%d: MSR_PP0_POLICY: %lld\n", cpu, msr & 0xF);
+ }
+ }
+ if (do_rapl & RAPL_CORES) {
+ if (verbose) {
+
+ if (get_msr(cpu, MSR_PP0_POWER_LIMIT, &msr))
+ return -9;
+ fprintf(stderr, "cpu%d: MSR_PP0_POWER_LIMIT: 0x%08llx (%slocked)\n",
+ cpu, msr, (msr >> 31) & 1 ? "": "UN");
+ print_power_limit_msr(cpu, msr, "Cores Limit");
+ }
+ }
+ if (do_rapl & RAPL_GFX) {
+ if (verbose) {
+ if (get_msr(cpu, MSR_PP1_POLICY, &msr))
+ return -8;
+
+ fprintf(stderr, "cpu%d: MSR_PP1_POLICY: %lld\n", cpu, msr & 0xF);
+
+ if (get_msr(cpu, MSR_PP1_POWER_LIMIT, &msr))
+ return -9;
+ fprintf(stderr, "cpu%d: MSR_PP1_POWER_LIMIT: 0x%08llx (%slocked)\n",
+ cpu, msr, (msr >> 31) & 1 ? "": "UN");
+ print_power_limit_msr(cpu, msr, "GFX Limit");
+ }
+ }
+ return 0;
+}
+
+
+int is_snb(unsigned int family, unsigned int model)
+{
+ if (!genuine_intel)
+ return 0;
+
+ switch (model) {
+ case 0x2A:
+ case 0x2D:
+ case 0x3A: /* IVB */
+ case 0x3E: /* IVB Xeon */
+ case 0x3C: /* HSW */
+ case 0x3F: /* HSW */
+ case 0x45: /* HSW */
+ case 0x46: /* HSW */
+ case 0x3D: /* BDW */
+ case 0x4F: /* BDX */
+ case 0x56: /* BDX-DE */
+ return 1;
+ }
+ return 0;
+}
+
+int has_c8_c9_c10(unsigned int family, unsigned int model)
+{
+ if (!genuine_intel)
+ return 0;
+
+ switch (model) {
+ case 0x45: /* HSW */
+ case 0x3D: /* BDW */
+ return 1;
+ }
+ return 0;
+}
+
+
+int is_slm(unsigned int family, unsigned int model)
+{
+ if (!genuine_intel)
+ return 0;
+ switch (model) {
+ case 0x37: /* BYT */
+ case 0x4D: /* AVN */
+ return 1;
+ }
+ return 0;
+}
+
+#define SLM_BCLK_FREQS 5
+double slm_freq_table[SLM_BCLK_FREQS] = { 83.3, 100.0, 133.3, 116.7, 80.0};
+
+double slm_bclk(void)
+{
+ unsigned long long msr = 3;
+ unsigned int i;
+ double freq;
+
+ if (get_msr(0, MSR_FSB_FREQ, &msr))
+ fprintf(stderr, "SLM BCLK: unknown\n");
+
+ i = msr & 0xf;
+ if (i >= SLM_BCLK_FREQS) {
+ fprintf(stderr, "SLM BCLK[%d] invalid\n", i);
+ msr = 3;
+ }
+ freq = slm_freq_table[i];
+
+ fprintf(stderr, "SLM BCLK: %.1f Mhz\n", freq);
+
+ return freq;
+}
+
+double discover_bclk(unsigned int family, unsigned int model)
+{
+ if (is_snb(family, model))
+ return 100.00;
+ else if (is_slm(family, model))
+ return slm_bclk();
+ else
+ return 133.33;
+}
+
+/*
+ * MSR_IA32_TEMPERATURE_TARGET indicates the temperature where
+ * the Thermal Control Circuit (TCC) activates.
+ * This is usually equal to tjMax.
+ *
+ * Older processors do not have this MSR, so there we guess,
+ * but also allow cmdline over-ride with -T.
+ *
+ * Several MSR temperature values are in units of degrees-C
+ * below this value, including the Digital Thermal Sensor (DTS),
+ * Package Thermal Management Sensor (PTM), and thermal event thresholds.
+ */
+int set_temperature_target(struct thread_data *t, struct core_data *c, struct pkg_data *p)
+{
+ unsigned long long msr;
+ unsigned int target_c_local;
+ int cpu;
+
+ /* tcc_activation_temp is used only for dts or ptm */
+ if (!(do_dts || do_ptm))
+ return 0;
+
+ /* this is a per-package concept */
+ if (!(t->flags & CPU_IS_FIRST_THREAD_IN_CORE) || !(t->flags & CPU_IS_FIRST_CORE_IN_PACKAGE))
+ return 0;
+
+ cpu = t->cpu_id;
+ if (cpu_migrate(cpu)) {
+ fprintf(stderr, "Could not migrate to CPU %d\n", cpu);
+ return -1;
+ }
+
+ if (tcc_activation_temp_override != 0) {
+ tcc_activation_temp = tcc_activation_temp_override;
+ fprintf(stderr, "cpu%d: Using cmdline TCC Target (%d C)\n",
+ cpu, tcc_activation_temp);
+ return 0;
+ }
+
+ /* Temperature Target MSR is Nehalem and newer only */
+ if (!do_nehalem_platform_info)
+ goto guess;
+
+ if (get_msr(0, MSR_IA32_TEMPERATURE_TARGET, &msr))
+ goto guess;
+
+ target_c_local = (msr >> 16) & 0xFF;
+
+ if (verbose)
+ fprintf(stderr, "cpu%d: MSR_IA32_TEMPERATURE_TARGET: 0x%08llx (%d C)\n",
+ cpu, msr, target_c_local);
+
+ if (!target_c_local)
+ goto guess;
+
+ tcc_activation_temp = target_c_local;
+
+ return 0;
+
+guess:
+ tcc_activation_temp = TJMAX_DEFAULT;
+ fprintf(stderr, "cpu%d: Guessing tjMax %d C, Please use -T to specify\n",
+ cpu, tcc_activation_temp);
+
+ return 0;
+}
+void check_cpuid()
+{
+ unsigned int eax, ebx, ecx, edx, max_level;
+ unsigned int fms, family, model, stepping;
+
+ eax = ebx = ecx = edx = 0;
+
+ __get_cpuid(0, &max_level, &ebx, &ecx, &edx);
+
+ if (ebx == 0x756e6547 && edx == 0x49656e69 && ecx == 0x6c65746e)
+ genuine_intel = 1;
+
+ if (verbose)
+ fprintf(stderr, "CPUID(0): %.4s%.4s%.4s ",
+ (char *)&ebx, (char *)&edx, (char *)&ecx);
+
+ __get_cpuid(1, &fms, &ebx, &ecx, &edx);
+ family = (fms >> 8) & 0xf;
+ model = (fms >> 4) & 0xf;
+ stepping = fms & 0xf;
+ if (family == 6 || family == 0xf)
+ model += ((fms >> 16) & 0xf) << 4;
+
+ if (verbose)
+ fprintf(stderr, "%d CPUID levels; family:model:stepping 0x%x:%x:%x (%d:%d:%d)\n",
+ max_level, family, model, stepping, family, model, stepping);
+
+ if (!(edx & (1 << 5)))
+ errx(1, "CPUID: no MSR");
+
+ /*
+ * check max extended function levels of CPUID.
+ * This is needed to check for invariant TSC.
+ * This check is valid for both Intel and AMD.
+ */
+ ebx = ecx = edx = 0;
+ __get_cpuid(0x80000000, &max_level, &ebx, &ecx, &edx);
+
+ if (max_level < 0x80000007)
+ errx(1, "CPUID: no invariant TSC (max_level 0x%x)", max_level);
+
+ /*
+ * Non-Stop TSC is advertised by CPUID.EAX=0x80000007: EDX.bit8
+ * this check is valid for both Intel and AMD
+ */
+ __get_cpuid(0x80000007, &eax, &ebx, &ecx, &edx);
+ has_invariant_tsc = edx & (1 << 8);
+
+ if (!has_invariant_tsc)
+ errx(1, "No invariant TSC");
+
+ /*
+ * APERF/MPERF is advertised by CPUID.EAX=0x6: ECX.bit0
+ * this check is valid for both Intel and AMD
+ */
+
+ __get_cpuid(0x6, &eax, &ebx, &ecx, &edx);
+ has_aperf = ecx & (1 << 0);
+ do_dts = eax & (1 << 0);
+ do_ptm = eax & (1 << 6);
+ has_epb = ecx & (1 << 3);
+
+ if (verbose)
+ fprintf(stderr, "CPUID(6): %s%s%s%s\n",
+ has_aperf ? "APERF" : "No APERF!",
+ do_dts ? ", DTS" : "",
+ do_ptm ? ", PTM": "",
+ has_epb ? ", EPB": "");
+
+ if (!has_aperf)
+ errx(-1, "No APERF");
+
+ do_nehalem_platform_info = genuine_intel && has_invariant_tsc;
+ do_nhm_cstates = genuine_intel; /* all Intel w/ non-stop TSC have NHM counters */
+ do_smi = do_nhm_cstates;
+ do_snb_cstates = is_snb(family, model);
+ do_c8_c9_c10 = has_c8_c9_c10(family, model);
+ do_slm_cstates = is_slm(family, model);
+ bclk = discover_bclk(family, model);
+
+ do_nehalem_turbo_ratio_limit = has_nehalem_turbo_ratio_limit(family, model);
+ do_ivt_turbo_ratio_limit = has_ivt_turbo_ratio_limit(family, model);
+ rapl_probe(family, model);
+
+ return;
+}
+
+
+void usage()
+{
+ errx(1, "%s: [-v][-R][-T][-p|-P|-S][-c MSR#][-C MSR#][-m MSR#][-M MSR#][-i interval_sec | command ...]\n",
+ progname);
+}
+
+
+/*
+ * in /dev/cpu/ return success for names that are numbers
+ * ie. filter out ".", "..", "microcode".
+ */
+int dir_filter(const struct dirent *dirp)
+{
+ if (isdigit(dirp->d_name[0]))
+ return 1;
+ else
+ return 0;
+}
+
+int open_dev_cpu_msr(int dummy1)
+{
+ return 0;
+}
+
+void topology_probe()
+{
+ int i;
+ int max_core_id = 0;
+ int max_package_id = 0;
+ int max_siblings = 0;
+ struct cpu_topology {
+ int core_id;
+ int physical_package_id;
+ } *cpus;
+
+ /* Initialize num_cpus, max_cpu_num */
+ topo.num_cpus = 0;
+ topo.max_cpu_num = 0;
+ for_all_proc_cpus(count_cpus);
+ if (!summary_only && topo.num_cpus > 1)
+ show_cpu = 1;
+
+ if (verbose > 1)
+ fprintf(stderr, "num_cpus %d max_cpu_num %d\n", topo.num_cpus, topo.max_cpu_num);
+
+ cpus = calloc(1, (topo.max_cpu_num + 1) * sizeof(struct cpu_topology));
+ if (cpus == NULL)
+ err(1, "calloc cpus");
+
+ /*
+ * Allocate and initialize cpu_present_set
+ */
+ cpu_present_set = CPU_ALLOC((topo.max_cpu_num + 1));
+ if (cpu_present_set == NULL)
+ err(3, "CPU_ALLOC");
+ cpu_present_setsize = CPU_ALLOC_SIZE((topo.max_cpu_num + 1));
+ CPU_ZERO_S(cpu_present_setsize, cpu_present_set);
+ for_all_proc_cpus(mark_cpu_present);
+
+ /*
+ * Allocate and initialize cpu_affinity_set
+ */
+ cpu_affinity_set = CPU_ALLOC((topo.max_cpu_num + 1));
+ if (cpu_affinity_set == NULL)
+ err(3, "CPU_ALLOC");
+ cpu_affinity_setsize = CPU_ALLOC_SIZE((topo.max_cpu_num + 1));
+ CPU_ZERO_S(cpu_affinity_setsize, cpu_affinity_set);
+
+
+ /*
+ * For online cpus
+ * find max_core_id, max_package_id
+ */
+ for (i = 0; i <= topo.max_cpu_num; ++i) {
+ int siblings;
+
+ if (cpu_is_not_present(i)) {
+ if (verbose > 1)
+ fprintf(stderr, "cpu%d NOT PRESENT\n", i);
+ continue;
+ }
+ cpus[i].core_id = get_core_id(i);
+ if (cpus[i].core_id > max_core_id)
+ max_core_id = cpus[i].core_id;
+
+ cpus[i].physical_package_id = get_physical_package_id(i);
+ if (cpus[i].physical_package_id > max_package_id)
+ max_package_id = cpus[i].physical_package_id;
+
+ siblings = get_num_ht_siblings(i);
+ if (siblings > max_siblings)
+ max_siblings = siblings;
+ if (verbose > 1)
+ fprintf(stderr, "cpu %d pkg %d core %d\n",
+ i, cpus[i].physical_package_id, cpus[i].core_id);
+ }
+ topo.num_cores_per_pkg = max_core_id + 1;
+ if (verbose > 1)
+ fprintf(stderr, "max_core_id %d, sizing for %d cores per package\n",
+ max_core_id, topo.num_cores_per_pkg);
+ if (!summary_only && topo.num_cores_per_pkg > 1)
+ show_core = 1;
+
+ topo.num_packages = max_package_id + 1;
+ if (verbose > 1)
+ fprintf(stderr, "max_package_id %d, sizing for %d packages\n",
+ max_package_id, topo.num_packages);
+ if (!summary_only && topo.num_packages > 1)
+ show_pkg = 1;
+
+ topo.num_threads_per_core = max_siblings;
+ if (verbose > 1)
+ fprintf(stderr, "max_siblings %d\n", max_siblings);
+
+ free(cpus);
+}
+
+void
+allocate_counters(struct thread_data **t, struct core_data **c, struct pkg_data **p)
+{
+ int i;
+
+ *t = calloc(topo.num_threads_per_core * topo.num_cores_per_pkg *
+ topo.num_packages, sizeof(struct thread_data));
+ if (*t == NULL)
+ goto error;
+
+ for (i = 0; i < topo.num_threads_per_core *
+ topo.num_cores_per_pkg * topo.num_packages; i++)
+ (*t)[i].cpu_id = -1;
+
+ *c = calloc(topo.num_cores_per_pkg * topo.num_packages,
+ sizeof(struct core_data));
+ if (*c == NULL)
+ goto error;
+
+ for (i = 0; i < topo.num_cores_per_pkg * topo.num_packages; i++)
+ (*c)[i].core_id = -1;
+
+ *p = calloc(topo.num_packages, sizeof(struct pkg_data));
+ if (*p == NULL)
+ goto error;
+
+ for (i = 0; i < topo.num_packages; i++)
+ (*p)[i].package_id = i;
+
+ return;
+error:
+ err(1, "calloc counters");
+}
+/*
+ * init_counter()
+ *
+ * set cpu_id, core_num, pkg_num
+ * set FIRST_THREAD_IN_CORE and FIRST_CORE_IN_PACKAGE
+ *
+ * increment topo.num_cores when 1st core in pkg seen
+ */
+void init_counter(struct thread_data *thread_base, struct core_data *core_base,
+ struct pkg_data *pkg_base, int thread_num, int core_num,
+ int pkg_num, int cpu_id)
+{
+ struct thread_data *t;
+ struct core_data *c;
+ struct pkg_data *p;
+
+ t = GET_THREAD(thread_base, thread_num, core_num, pkg_num);
+ c = GET_CORE(core_base, core_num, pkg_num);
+ p = GET_PKG(pkg_base, pkg_num);
+
+ t->cpu_id = cpu_id;
+ if (thread_num == 0) {
+ t->flags |= CPU_IS_FIRST_THREAD_IN_CORE;
+ if (cpu_is_first_core_in_package(cpu_id))
+ t->flags |= CPU_IS_FIRST_CORE_IN_PACKAGE;
+ }
+
+ c->core_id = core_num;
+ p->package_id = pkg_num;
+}
+
+
+int initialize_counters(int cpu_id)
+{
+ int my_thread_id, my_core_id, my_package_id;
+
+ my_package_id = get_physical_package_id(cpu_id);
+ my_core_id = get_core_id(cpu_id);
+
+ if (cpu_is_first_sibling_in_core(cpu_id)) {
+ my_thread_id = 0;
+ topo.num_cores++;
+ } else {
+ my_thread_id = 1;
+ }
+
+ init_counter(EVEN_COUNTERS, my_thread_id, my_core_id, my_package_id, cpu_id);
+ init_counter(ODD_COUNTERS, my_thread_id, my_core_id, my_package_id, cpu_id);
+ return 0;
+}
+
+void allocate_output_buffer()
+{
+ output_buffer = calloc(1, (1 + topo.num_cpus) * 1024);
+ outp = output_buffer;
+ if (outp == NULL)
+ err(-1, "calloc output buffer");
+}
+
+void setup_all_buffers(void)
+{
+ topology_probe();
+ allocate_counters(&thread_even, &core_even, &package_even);
+ allocate_counters(&thread_odd, &core_odd, &package_odd);
+ allocate_output_buffer();
+ for_all_proc_cpus(initialize_counters);
+}
+
+void turbostat_init()
+{
+ check_cpuid();
+
+ check_dev_msr();
+ check_super_user();
+
+ setup_all_buffers();
+
+ if (verbose)
+ print_verbose_header();
+
+ if (verbose)
+ for_all_cpus(print_epb, ODD_COUNTERS);
+
+ if (verbose)
+ for_all_cpus(print_rapl, ODD_COUNTERS);
+
+ for_all_cpus(set_temperature_target, ODD_COUNTERS);
+
+ if (verbose)
+ for_all_cpus(print_thermal, ODD_COUNTERS);
+}
+
+int fork_it(char **argv)
+{
+ pid_t child_pid;
+ int status;
+
+ status = for_all_cpus(get_counters, EVEN_COUNTERS);
+ if (status)
+ exit(status);
+ /* clear affinity side-effect of get_counters() */
+ sched_setaffinity(0, cpu_present_setsize, cpu_present_set);
+ gettimeofday(&tv_even, (struct timezone *)NULL);
+
+ child_pid = fork();
+ if (!child_pid) {
+ /* child */
+ execvp(argv[0], argv);
+ } else {
+
+ /* parent */
+ if (child_pid == -1)
+ err(1, "fork");
+
+ signal(SIGINT, SIG_IGN);
+ signal(SIGQUIT, SIG_IGN);
+ if (waitpid(child_pid, &status, 0) == -1)
+ err(status, "waitpid");
+ }
+ /*
+ * n.b. fork_it() does not check for errors from for_all_cpus()
+ * because re-starting is problematic when forking
+ */
+ for_all_cpus(get_counters, ODD_COUNTERS);
+ gettimeofday(&tv_odd, (struct timezone *)NULL);
+ timersub(&tv_odd, &tv_even, &tv_delta);
+ for_all_cpus_2(delta_cpu, ODD_COUNTERS, EVEN_COUNTERS);
+ compute_average(EVEN_COUNTERS);
+ format_all_counters(EVEN_COUNTERS);
+ flush_stderr();
+
+ fprintf(stderr, "%.6f sec\n", tv_delta.tv_sec + tv_delta.tv_usec/1000000.0);
+
+ return status;
+}
+
+int get_and_dump_counters(void)
+{
+ int status;
+
+ status = for_all_cpus(get_counters, ODD_COUNTERS);
+ if (status)
+ return status;
+
+ status = for_all_cpus(dump_counters, ODD_COUNTERS);
+ if (status)
+ return status;
+
+ flush_stdout();
+
+ return status;
+}
+
+void cmdline(int argc, char **argv)
+{
+ int opt;
+
+ progname = argv[0];
+
+ while ((opt = getopt(argc, argv, "+pPsSvi:c:C:m:M:RJT:")) != -1) {
+ switch (opt) {
+ case 'p':
+ show_core_only++;
+ break;
+ case 'P':
+ show_pkg_only++;
+ break;
+ case 's':
+ dump_only++;
+ break;
+ case 'S':
+ summary_only++;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ case 'i':
+ interval_sec = atoi(optarg);
+ break;
+ case 'c':
+ sscanf(optarg, "%x", &extra_delta_offset32);
+ break;
+ case 'C':
+ sscanf(optarg, "%x", &extra_delta_offset64);
+ break;
+ case 'm':
+ sscanf(optarg, "%x", &extra_msr_offset32);
+ break;
+ case 'M':
+ sscanf(optarg, "%x", &extra_msr_offset64);
+ break;
+ case 'R':
+ rapl_verbose++;
+ break;
+ case 'T':
+ tcc_activation_temp_override = atoi(optarg);
+ break;
+ case 'J':
+ rapl_joules++;
+ break;
+
+ default:
+ usage();
+ }
+ }
+}
+
+int main(int argc, char **argv)
+{
+ cmdline(argc, argv);
+
+ if (verbose)
+ fprintf(stderr, "turbostat v3.7 Feb 6, 2014"
+ " - Len Brown <lenb@kernel.org>\n");
+
+ turbostat_init();
+
+ /* dump counters and exit */
+ if (dump_only)
+ return get_and_dump_counters();
+
+ /*
+ * if any params left, it must be a command to fork
+ */
+ if (argc - optind)
+ return fork_it(argv + optind);
+ else
+ turbostat_loop();
+
+ return 0;
+}
diff --git a/tools/power/x86/x86_energy_perf_policy/Makefile b/tools/power/x86/x86_energy_perf_policy/Makefile
new file mode 100644
index 00000000000..971c9ffdcb5
--- /dev/null
+++ b/tools/power/x86/x86_energy_perf_policy/Makefile
@@ -0,0 +1,10 @@
+DESTDIR ?=
+
+x86_energy_perf_policy : x86_energy_perf_policy.c
+
+clean :
+ rm -f x86_energy_perf_policy
+
+install :
+ install x86_energy_perf_policy ${DESTDIR}/usr/bin/
+ install x86_energy_perf_policy.8 ${DESTDIR}/usr/share/man/man8/
diff --git a/tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.8 b/tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.8
new file mode 100644
index 00000000000..8eaaad648cd
--- /dev/null
+++ b/tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.8
@@ -0,0 +1,104 @@
+.\" This page Copyright (C) 2010 Len Brown <len.brown@intel.com>
+.\" Distributed under the GPL, Copyleft 1994.
+.TH X86_ENERGY_PERF_POLICY 8
+.SH NAME
+x86_energy_perf_policy \- read or write MSR_IA32_ENERGY_PERF_BIAS
+.SH SYNOPSIS
+.ft B
+.B x86_energy_perf_policy
+.RB [ "\-c cpu" ]
+.RB [ "\-v" ]
+.RB "\-r"
+.br
+.B x86_energy_perf_policy
+.RB [ "\-c cpu" ]
+.RB [ "\-v" ]
+.RB 'performance'
+.br
+.B x86_energy_perf_policy
+.RB [ "\-c cpu" ]
+.RB [ "\-v" ]
+.RB 'normal'
+.br
+.B x86_energy_perf_policy
+.RB [ "\-c cpu" ]
+.RB [ "\-v" ]
+.RB 'powersave'
+.br
+.B x86_energy_perf_policy
+.RB [ "\-c cpu" ]
+.RB [ "\-v" ]
+.RB n
+.br
+.SH DESCRIPTION
+\fBx86_energy_perf_policy\fP
+allows software to convey
+its policy for the relative importance of performance
+versus energy savings to the processor.
+
+The processor uses this information in model-specific ways
+when it must select trade-offs between performance and
+energy efficiency.
+
+This policy hint does not supersede Processor Performance states
+(P-states) or CPU Idle power states (C-states), but allows
+software to have influence where it would otherwise be unable
+to express a preference.
+
+For example, this setting may tell the hardware how
+aggressively or conservatively to control frequency
+in the "turbo range" above the explicitly OS-controlled
+P-state frequency range. It may also tell the hardware
+how aggressively is should enter the OS requested C-states.
+
+Support for this feature is indicated by CPUID.06H.ECX.bit3
+per the Intel Architectures Software Developer's Manual.
+
+.SS Options
+\fB-c\fP limits operation to a single CPU.
+The default is to operate on all CPUs.
+Note that MSR_IA32_ENERGY_PERF_BIAS is defined per
+logical processor, but that the initial implementations
+of the MSR were shared among all processors in each package.
+.PP
+\fB-v\fP increases verbosity. By default
+x86_energy_perf_policy is silent.
+.PP
+\fB-r\fP is for "read-only" mode - the unchanged state
+is read and displayed.
+.PP
+.I performance
+Set a policy where performance is paramount.
+The processor will be unwilling to sacrifice any performance
+for the sake of energy saving. This is the hardware default.
+.PP
+.I normal
+Set a policy with a normal balance between performance and energy efficiency.
+The processor will tolerate minor performance compromise
+for potentially significant energy savings.
+This reasonable default for most desktops and servers.
+.PP
+.I powersave
+Set a policy where the processor can accept
+a measurable performance hit to maximize energy efficiency.
+.PP
+.I n
+Set MSR_IA32_ENERGY_PERF_BIAS to the specified number.
+The range of valid numbers is 0-15, where 0 is maximum
+performance and 15 is maximum energy efficiency.
+
+.SH NOTES
+.B "x86_energy_perf_policy "
+runs only as root.
+.SH FILES
+.ta
+.nf
+/dev/cpu/*/msr
+.fi
+
+.SH "SEE ALSO"
+msr(4)
+.PP
+.SH AUTHORS
+.nf
+Written by Len Brown <len.brown@intel.com>
diff --git a/tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.c b/tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.c
new file mode 100644
index 00000000000..40b3e5482f8
--- /dev/null
+++ b/tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.c
@@ -0,0 +1,324 @@
+/*
+ * x86_energy_perf_policy -- set the energy versus performance
+ * policy preference bias on recent X86 processors.
+ */
+/*
+ * Copyright (c) 2010, Intel Corporation.
+ * Len Brown <len.brown@intel.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/resource.h>
+#include <fcntl.h>
+#include <signal.h>
+#include <sys/time.h>
+#include <stdlib.h>
+#include <string.h>
+
+unsigned int verbose; /* set with -v */
+unsigned int read_only; /* set with -r */
+char *progname;
+unsigned long long new_bias;
+int cpu = -1;
+
+/*
+ * Usage:
+ *
+ * -c cpu: limit action to a single CPU (default is all CPUs)
+ * -v: verbose output (can invoke more than once)
+ * -r: read-only, don't change any settings
+ *
+ * performance
+ * Performance is paramount.
+ * Unwilling to sacrifice any performance
+ * for the sake of energy saving. (hardware default)
+ *
+ * normal
+ * Can tolerate minor performance compromise
+ * for potentially significant energy savings.
+ * (reasonable default for most desktops and servers)
+ *
+ * powersave
+ * Can tolerate significant performance hit
+ * to maximize energy savings.
+ *
+ * n
+ * a numerical value to write to the underlying MSR.
+ */
+void usage(void)
+{
+ printf("%s: [-c cpu] [-v] "
+ "(-r | 'performance' | 'normal' | 'powersave' | n)\n",
+ progname);
+ exit(1);
+}
+
+#define MSR_IA32_ENERGY_PERF_BIAS 0x000001b0
+
+#define BIAS_PERFORMANCE 0
+#define BIAS_BALANCE 6
+#define BIAS_POWERSAVE 15
+
+void cmdline(int argc, char **argv)
+{
+ int opt;
+
+ progname = argv[0];
+
+ while ((opt = getopt(argc, argv, "+rvc:")) != -1) {
+ switch (opt) {
+ case 'c':
+ cpu = atoi(optarg);
+ break;
+ case 'r':
+ read_only = 1;
+ break;
+ case 'v':
+ verbose++;
+ break;
+ default:
+ usage();
+ }
+ }
+ /* if -r, then should be no additional optind */
+ if (read_only && (argc > optind))
+ usage();
+
+ /*
+ * if no -r , then must be one additional optind
+ */
+ if (!read_only) {
+
+ if (argc != optind + 1) {
+ printf("must supply -r or policy param\n");
+ usage();
+ }
+
+ if (!strcmp("performance", argv[optind])) {
+ new_bias = BIAS_PERFORMANCE;
+ } else if (!strcmp("normal", argv[optind])) {
+ new_bias = BIAS_BALANCE;
+ } else if (!strcmp("powersave", argv[optind])) {
+ new_bias = BIAS_POWERSAVE;
+ } else {
+ char *endptr;
+
+ new_bias = strtoull(argv[optind], &endptr, 0);
+ if (endptr == argv[optind] ||
+ new_bias > BIAS_POWERSAVE) {
+ fprintf(stderr, "invalid value: %s\n",
+ argv[optind]);
+ usage();
+ }
+ }
+ }
+}
+
+/*
+ * validate_cpuid()
+ * returns on success, quietly exits on failure (make verbose with -v)
+ */
+void validate_cpuid(void)
+{
+ unsigned int eax, ebx, ecx, edx, max_level;
+ unsigned int fms, family, model, stepping;
+
+ eax = ebx = ecx = edx = 0;
+
+ asm("cpuid" : "=a" (max_level), "=b" (ebx), "=c" (ecx),
+ "=d" (edx) : "a" (0));
+
+ if (ebx != 0x756e6547 || edx != 0x49656e69 || ecx != 0x6c65746e) {
+ if (verbose)
+ fprintf(stderr, "%.4s%.4s%.4s != GenuineIntel",
+ (char *)&ebx, (char *)&edx, (char *)&ecx);
+ exit(1);
+ }
+
+ asm("cpuid" : "=a" (fms), "=c" (ecx), "=d" (edx) : "a" (1) : "ebx");
+ family = (fms >> 8) & 0xf;
+ model = (fms >> 4) & 0xf;
+ stepping = fms & 0xf;
+ if (family == 6 || family == 0xf)
+ model += ((fms >> 16) & 0xf) << 4;
+
+ if (verbose > 1)
+ printf("CPUID %d levels family:model:stepping "
+ "0x%x:%x:%x (%d:%d:%d)\n", max_level,
+ family, model, stepping, family, model, stepping);
+
+ if (!(edx & (1 << 5))) {
+ if (verbose)
+ printf("CPUID: no MSR\n");
+ exit(1);
+ }
+
+ /*
+ * Support for MSR_IA32_ENERGY_PERF_BIAS
+ * is indicated by CPUID.06H.ECX.bit3
+ */
+ asm("cpuid" : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) : "a" (6));
+ if (verbose)
+ printf("CPUID.06H.ECX: 0x%x\n", ecx);
+ if (!(ecx & (1 << 3))) {
+ if (verbose)
+ printf("CPUID: No MSR_IA32_ENERGY_PERF_BIAS\n");
+ exit(1);
+ }
+ return; /* success */
+}
+
+unsigned long long get_msr(int cpu, int offset)
+{
+ unsigned long long msr;
+ char msr_path[32];
+ int retval;
+ int fd;
+
+ sprintf(msr_path, "/dev/cpu/%d/msr", cpu);
+ fd = open(msr_path, O_RDONLY);
+ if (fd < 0) {
+ printf("Try \"# modprobe msr\"\n");
+ perror(msr_path);
+ exit(1);
+ }
+
+ retval = pread(fd, &msr, sizeof msr, offset);
+
+ if (retval != sizeof msr) {
+ printf("pread cpu%d 0x%x = %d\n", cpu, offset, retval);
+ exit(-2);
+ }
+ close(fd);
+ return msr;
+}
+
+unsigned long long put_msr(int cpu, unsigned long long new_msr, int offset)
+{
+ unsigned long long old_msr;
+ char msr_path[32];
+ int retval;
+ int fd;
+
+ sprintf(msr_path, "/dev/cpu/%d/msr", cpu);
+ fd = open(msr_path, O_RDWR);
+ if (fd < 0) {
+ perror(msr_path);
+ exit(1);
+ }
+
+ retval = pread(fd, &old_msr, sizeof old_msr, offset);
+ if (retval != sizeof old_msr) {
+ perror("pwrite");
+ printf("pread cpu%d 0x%x = %d\n", cpu, offset, retval);
+ exit(-2);
+ }
+
+ retval = pwrite(fd, &new_msr, sizeof new_msr, offset);
+ if (retval != sizeof new_msr) {
+ perror("pwrite");
+ printf("pwrite cpu%d 0x%x = %d\n", cpu, offset, retval);
+ exit(-2);
+ }
+
+ close(fd);
+
+ return old_msr;
+}
+
+void print_msr(int cpu)
+{
+ printf("cpu%d: 0x%016llx\n",
+ cpu, get_msr(cpu, MSR_IA32_ENERGY_PERF_BIAS));
+}
+
+void update_msr(int cpu)
+{
+ unsigned long long previous_msr;
+
+ previous_msr = put_msr(cpu, new_bias, MSR_IA32_ENERGY_PERF_BIAS);
+
+ if (verbose)
+ printf("cpu%d msr0x%x 0x%016llx -> 0x%016llx\n",
+ cpu, MSR_IA32_ENERGY_PERF_BIAS, previous_msr, new_bias);
+
+ return;
+}
+
+char *proc_stat = "/proc/stat";
+/*
+ * run func() on every cpu in /dev/cpu
+ */
+void for_every_cpu(void (func)(int))
+{
+ FILE *fp;
+ int retval;
+
+ fp = fopen(proc_stat, "r");
+ if (fp == NULL) {
+ perror(proc_stat);
+ exit(1);
+ }
+
+ retval = fscanf(fp, "cpu %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d\n");
+ if (retval != 0) {
+ perror("/proc/stat format");
+ exit(1);
+ }
+
+ while (1) {
+ int cpu;
+
+ retval = fscanf(fp,
+ "cpu%u %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d\n",
+ &cpu);
+ if (retval != 1)
+ break;
+
+ func(cpu);
+ }
+ fclose(fp);
+}
+
+int main(int argc, char **argv)
+{
+ cmdline(argc, argv);
+
+ if (verbose > 1)
+ printf("x86_energy_perf_policy Nov 24, 2010"
+ " - Len Brown <lenb@kernel.org>\n");
+ if (verbose > 1 && !read_only)
+ printf("new_bias %lld\n", new_bias);
+
+ validate_cpuid();
+
+ if (cpu != -1) {
+ if (read_only)
+ print_msr(cpu);
+ else
+ update_msr(cpu);
+ } else {
+ if (read_only)
+ for_every_cpu(print_msr);
+ else
+ for_every_cpu(update_msr);
+ }
+
+ return 0;
+}
diff --git a/tools/scripts/Makefile.include b/tools/scripts/Makefile.include
new file mode 100644
index 00000000000..8abbef164b4
--- /dev/null
+++ b/tools/scripts/Makefile.include
@@ -0,0 +1,84 @@
+ifneq ($(O),)
+ifeq ($(origin O), command line)
+ dummy := $(if $(shell test -d $(O) || echo $(O)),$(error O=$(O) does not exist),)
+ ABSOLUTE_O := $(shell cd $(O) ; pwd)
+ OUTPUT := $(ABSOLUTE_O)/$(if $(subdir),$(subdir)/)
+ COMMAND_O := O=$(ABSOLUTE_O)
+ifeq ($(objtree),)
+ objtree := $(O)
+endif
+endif
+endif
+
+# check that the output directory actually exists
+ifneq ($(OUTPUT),)
+OUTDIR := $(shell cd $(OUTPUT) && /bin/pwd)
+$(if $(OUTDIR),, $(error output directory "$(OUTPUT)" does not exist))
+endif
+
+#
+# Include saner warnings here, which can catch bugs:
+#
+EXTRA_WARNINGS := -Wbad-function-cast
+EXTRA_WARNINGS += -Wdeclaration-after-statement
+EXTRA_WARNINGS += -Wformat-security
+EXTRA_WARNINGS += -Wformat-y2k
+EXTRA_WARNINGS += -Winit-self
+EXTRA_WARNINGS += -Wmissing-declarations
+EXTRA_WARNINGS += -Wmissing-prototypes
+EXTRA_WARNINGS += -Wnested-externs
+EXTRA_WARNINGS += -Wno-system-headers
+EXTRA_WARNINGS += -Wold-style-definition
+EXTRA_WARNINGS += -Wpacked
+EXTRA_WARNINGS += -Wredundant-decls
+EXTRA_WARNINGS += -Wshadow
+EXTRA_WARNINGS += -Wstrict-aliasing=3
+EXTRA_WARNINGS += -Wstrict-prototypes
+EXTRA_WARNINGS += -Wswitch-default
+EXTRA_WARNINGS += -Wswitch-enum
+EXTRA_WARNINGS += -Wundef
+EXTRA_WARNINGS += -Wwrite-strings
+EXTRA_WARNINGS += -Wformat
+
+ifneq ($(findstring $(MAKEFLAGS), w),w)
+PRINT_DIR = --no-print-directory
+else
+NO_SUBDIR = :
+endif
+
+#
+# Define a callable command for descending to a new directory
+#
+# Call by doing: $(call descend,directory[,target])
+#
+descend = \
+ +mkdir -p $(OUTPUT)$(1) && \
+ $(MAKE) $(COMMAND_O) subdir=$(if $(subdir),$(subdir)/$(1),$(1)) $(PRINT_DIR) -C $(1) $(2)
+
+QUIET_SUBDIR0 = +$(MAKE) $(COMMAND_O) -C # space to separate -C and subdir
+QUIET_SUBDIR1 =
+
+ifneq ($(findstring $(MAKEFLAGS),s),s)
+ ifneq ($(V),1)
+ QUIET_CC = @echo ' CC '$@;
+ QUIET_CC_FPIC = @echo ' CC FPIC '$@;
+ QUIET_AR = @echo ' AR '$@;
+ QUIET_LINK = @echo ' LINK '$@;
+ QUIET_MKDIR = @echo ' MKDIR '$@;
+ QUIET_GEN = @echo ' GEN '$@;
+ QUIET_SUBDIR0 = +@subdir=
+ QUIET_SUBDIR1 = ;$(NO_SUBDIR) \
+ echo ' SUBDIR '$$subdir; \
+ $(MAKE) $(PRINT_DIR) -C $$subdir
+ QUIET_FLEX = @echo ' FLEX '$@;
+ QUIET_BISON = @echo ' BISON '$@;
+
+ descend = \
+ +@echo ' DESCEND '$(1); \
+ mkdir -p $(OUTPUT)$(1) && \
+ $(MAKE) $(COMMAND_O) subdir=$(if $(subdir),$(subdir)/$(1),$(1)) $(PRINT_DIR) -C $(1) $(2)
+
+ QUIET_CLEAN = @printf ' CLEAN %s\n' $1;
+ QUIET_INSTALL = @printf ' INSTALL %s\n' $1;
+ endif
+endif
diff --git a/tools/testing/fault-injection/failcmd.sh b/tools/testing/fault-injection/failcmd.sh
new file mode 100644
index 00000000000..78a9ed7fecd
--- /dev/null
+++ b/tools/testing/fault-injection/failcmd.sh
@@ -0,0 +1,219 @@
+#!/bin/bash
+#
+# NAME
+# failcmd.sh - run a command with injecting slab/page allocation failures
+#
+# SYNOPSIS
+# failcmd.sh --help
+# failcmd.sh [<options>] command [arguments]
+#
+# DESCRIPTION
+# Run command with injecting slab/page allocation failures by fault
+# injection.
+#
+# NOTE: you need to run this script as root.
+#
+
+usage()
+{
+ cat >&2 <<EOF
+Usage: $0 [options] command [arguments]
+
+OPTIONS
+ -p percent
+ --probability=percent
+ likelihood of failure injection, in percent.
+ Default value is 1
+
+ -t value
+ --times=value
+ specifies how many times failures may happen at most.
+ Default value is 1
+
+ --oom-kill-allocating-task=value
+ set /proc/sys/vm/oom_kill_allocating_task to specified value
+ before running the command.
+ Default value is 1
+
+ -h, --help
+ Display a usage message and exit
+
+ --interval=value, --space=value, --verbose=value, --task-filter=value,
+ --stacktrace-depth=value, --require-start=value, --require-end=value,
+ --reject-start=value, --reject-end=value, --ignore-gfp-wait=value
+ See Documentation/fault-injection/fault-injection.txt for more
+ information
+
+ failslab options:
+ --cache-filter=value
+
+ fail_page_alloc options:
+ --ignore-gfp-highmem=value, --min-order=value
+
+ENVIRONMENT
+ FAILCMD_TYPE
+ The following values for FAILCMD_TYPE are recognized:
+
+ failslab
+ inject slab allocation failures
+ fail_page_alloc
+ inject page allocation failures
+
+ If FAILCMD_TYPE is not defined, then failslab is used.
+EOF
+}
+
+if [ $UID != 0 ]; then
+ echo must be run as root >&2
+ exit 1
+fi
+
+DEBUGFS=`mount -t debugfs | head -1 | awk '{ print $3}'`
+
+if [ ! -d "$DEBUGFS" ]; then
+ echo debugfs is not mounted >&2
+ exit 1
+fi
+
+FAILCMD_TYPE=${FAILCMD_TYPE:-failslab}
+FAULTATTR=$DEBUGFS/$FAILCMD_TYPE
+
+if [ ! -d $FAULTATTR ]; then
+ echo $FAILCMD_TYPE is not available >&2
+ exit 1
+fi
+
+LONGOPTS=probability:,interval:,times:,space:,verbose:,task-filter:
+LONGOPTS=$LONGOPTS,stacktrace-depth:,require-start:,require-end:
+LONGOPTS=$LONGOPTS,reject-start:,reject-end:,oom-kill-allocating-task:,help
+
+if [ $FAILCMD_TYPE = failslab ]; then
+ LONGOPTS=$LONGOPTS,ignore-gfp-wait:,cache-filter:
+elif [ $FAILCMD_TYPE = fail_page_alloc ]; then
+ LONGOPTS=$LONGOPTS,ignore-gfp-wait:,ignore-gfp-highmem:,min-order:
+fi
+
+TEMP=`getopt -o p:i:t:s:v:h --long $LONGOPTS -n 'failcmd.sh' -- "$@"`
+
+if [ $? != 0 ]; then
+ usage
+ exit 1
+fi
+
+eval set -- "$TEMP"
+
+fault_attr_default()
+{
+ echo N > $FAULTATTR/task-filter
+ echo 0 > $FAULTATTR/probability
+ echo 1 > $FAULTATTR/times
+}
+
+fault_attr_default
+
+oom_kill_allocating_task_saved=`cat /proc/sys/vm/oom_kill_allocating_task`
+
+restore_values()
+{
+ fault_attr_default
+ echo $oom_kill_allocating_task_saved \
+ > /proc/sys/vm/oom_kill_allocating_task
+}
+
+#
+# Default options
+#
+declare -i oom_kill_allocating_task=1
+declare task_filter=Y
+declare -i probability=1
+declare -i times=1
+
+while true; do
+ case "$1" in
+ -p|--probability)
+ probability=$2
+ shift 2
+ ;;
+ -i|--interval)
+ echo $2 > $FAULTATTR/interval
+ shift 2
+ ;;
+ -t|--times)
+ times=$2
+ shift 2
+ ;;
+ -s|--space)
+ echo $2 > $FAULTATTR/space
+ shift 2
+ ;;
+ -v|--verbose)
+ echo $2 > $FAULTATTR/verbose
+ shift 2
+ ;;
+ --task-filter)
+ task_filter=$2
+ shift 2
+ ;;
+ --stacktrace-depth)
+ echo $2 > $FAULTATTR/stacktrace-depth
+ shift 2
+ ;;
+ --require-start)
+ echo $2 > $FAULTATTR/require-start
+ shift 2
+ ;;
+ --require-end)
+ echo $2 > $FAULTATTR/require-end
+ shift 2
+ ;;
+ --reject-start)
+ echo $2 > $FAULTATTR/reject-start
+ shift 2
+ ;;
+ --reject-end)
+ echo $2 > $FAULTATTR/reject-end
+ shift 2
+ ;;
+ --oom-kill-allocating-task)
+ oom_kill_allocating_task=$2
+ shift 2
+ ;;
+ --ignore-gfp-wait)
+ echo $2 > $FAULTATTR/ignore-gfp-wait
+ shift 2
+ ;;
+ --cache-filter)
+ echo $2 > $FAULTATTR/cache_filter
+ shift 2
+ ;;
+ --ignore-gfp-highmem)
+ echo $2 > $FAULTATTR/ignore-gfp-highmem
+ shift 2
+ ;;
+ --min-order)
+ echo $2 > $FAULTATTR/min-order
+ shift 2
+ ;;
+ -h|--help)
+ usage
+ exit 0
+ shift
+ ;;
+ --)
+ shift
+ break
+ ;;
+ esac
+done
+
+[ -z "$1" ] && exit 0
+
+echo $oom_kill_allocating_task > /proc/sys/vm/oom_kill_allocating_task
+echo $task_filter > $FAULTATTR/task-filter
+echo $probability > $FAULTATTR/probability
+echo $times > $FAULTATTR/times
+
+trap "restore_values" SIGINT SIGTERM EXIT
+
+cmd="echo 1 > /proc/self/make-it-fail && exec $@"
+bash -c "$cmd"
diff --git a/tools/testing/ktest/compare-ktest-sample.pl b/tools/testing/ktest/compare-ktest-sample.pl
new file mode 100755
index 00000000000..a373a5bfff6
--- /dev/null
+++ b/tools/testing/ktest/compare-ktest-sample.pl
@@ -0,0 +1,32 @@
+#!/usr/bin/perl
+
+open (IN,"ktest.pl");
+while (<IN>) {
+ # hashes are now used
+ if (/\$opt\{"?([A-Z].*?)(\[.*\])?"?\}/ ||
+ /^\s*"?([A-Z].*?)"?\s*=>\s*/ ||
+ /set_test_option\("(.*?)"/) {
+ $opt{$1} = 1;
+ }
+}
+close IN;
+
+open (IN, "sample.conf");
+while (<IN>) {
+ if (/^\s*#?\s*([A-Z]\S*)\s*=/) {
+ $samp{$1} = 1;
+ }
+}
+close IN;
+
+foreach $opt (keys %opt) {
+ if (!defined($samp{$opt})) {
+ print "opt = $opt\n";
+ }
+}
+
+foreach $samp (keys %samp) {
+ if (!defined($opt{$samp})) {
+ print "samp = $samp\n";
+ }
+}
diff --git a/tools/testing/ktest/examples/README b/tools/testing/ktest/examples/README
new file mode 100644
index 00000000000..a12d295a09d
--- /dev/null
+++ b/tools/testing/ktest/examples/README
@@ -0,0 +1,32 @@
+This directory contains example configs to use ktest for various tasks.
+The configs still need to be customized for your environment, but it
+is broken up by task which makes it easier to understand how to set up
+ktest.
+
+The configs are based off of real working configs but have been modified
+and commented to show more generic use cases that are more helpful for
+developers.
+
+crosstests.conf - this config shows an example of testing a git repo against
+ lots of different architectures. It only does build tests, but makes
+ it easy to compile test different archs. You can download the arch
+ cross compilers from:
+ http://kernel.org/pub/tools/crosstool/files/bin/x86_64/
+
+test.conf - A generic example of a config. This is based on an actual config
+ used to perform real testing.
+
+kvm.conf - A example of a config that is used to test a virtual guest running
+ on a host.
+
+snowball.conf - An example config that was used to demo ktest.pl against
+ a snowball ARM board.
+
+include/ - The include directory holds default configs that can be
+ included into other configs. This is a real use example that shows how
+ to reuse configs for various machines or set ups. The files here
+ are included by other config files, where the other config files define
+ options and variables that will make the included config work for the
+ given environment.
+
+
diff --git a/tools/testing/ktest/examples/crosstests.conf b/tools/testing/ktest/examples/crosstests.conf
new file mode 100644
index 00000000000..a1203148dfa
--- /dev/null
+++ b/tools/testing/ktest/examples/crosstests.conf
@@ -0,0 +1,254 @@
+#
+# Example config for cross compiling
+#
+# In this config, it is expected that the tool chains from:
+#
+# http://kernel.org/pub/tools/crosstool/files/bin/x86_64/
+#
+# running on a x86_64 system have been downloaded and installed into:
+#
+# /usr/local/
+#
+# such that the compiler binaries are something like:
+#
+# /usr/local/gcc-4.5.2-nolibc/mips-linux/bin/mips-linux-gcc
+#
+# Some of the archs will use gcc-4.5.1 instead of gcc-4.5.2
+# this config uses variables to differentiate them.
+#
+# Comments describe some of the options, but full descriptions of
+# options are described in the samples.conf file.
+
+# ${PWD} is defined by ktest.pl to be the directory that the user
+# was in when they executed ktest.pl. It may be better to hardcode the
+# path name here. THIS_DIR is the variable used through out the config file
+# in case you want to change it.
+
+THIS_DIR := ${PWD}
+
+# Update the BUILD_DIR option to the location of your git repo you want to test.
+BUILD_DIR = ${THIS_DIR}/linux.git
+
+# The build will go into this directory. It will be created when you run the test.
+OUTPUT_DIR = ${THIS_DIR}/cross-compile
+
+# The build will be compiled with -j8
+BUILD_OPTIONS = -j8
+
+# The test will not stop when it hits a failure.
+DIE_ON_FAILURE = 0
+
+# If you want to have ktest.pl store the failure somewhere, uncomment this option
+# and change the directory where ktest should store the failures.
+#STORE_FAILURES = ${THIS_DIR}/failures
+
+# The log file is stored in the OUTPUT_DIR called cross.log
+# If you enable this, you need to create the OUTPUT_DIR. It wont be created for you.
+LOG_FILE = ${OUTPUT_DIR}/cross.log
+
+# The log file will be cleared each time you run ktest.
+CLEAR_LOG = 1
+
+# As some archs do not build with the defconfig, they have been marked
+# to be ignored. If you want to test them anyway, change DO_FAILED to 1.
+# If a test that has been marked as DO_FAILED passes, then you should change
+# that test to be DO_DEFAULT
+
+DO_FAILED := 0
+DO_DEFAULT := 1
+
+# By setting both DO_FAILED and DO_DEFAULT to zero, you can pick a single
+# arch that you want to test. (uncomment RUN and chose your arch)
+#RUN := m32r
+
+# At the bottom of the config file exists a bisect test. You can update that
+# test and set DO_FAILED and DO_DEFAULT to zero, and uncomment this variable
+# to run the bisect on the arch.
+#RUN := bisect
+
+# By default all tests will be running gcc 4.5.2. Some tests are using 4.5.1
+# and they select that in the test.
+# Note: GCC_VER is declared as on option and not a variable ('=' instead of ':=')
+# This is important. A variable is used only in the config file and if it is set
+# it stays that way for the rest of the config file until it is change again.
+# Here we want GCC_VER to remain persistent and change for each test, as it is used in
+# the MAKE_CMD. By using '=' instead of ':=' we achieve our goal.
+
+GCC_VER = 4.5.2
+MAKE_CMD = PATH=/usr/local/gcc-${GCC_VER}-nolibc/${CROSS}/bin:$PATH CROSS_COMPILE=${CROSS}- make ARCH=${ARCH}
+
+# all tests are only doing builds.
+TEST_TYPE = build
+
+# If you want to add configs on top of the defconfig, you can add those configs into
+# the add-config file and uncomment this option. This is useful if you want to test
+# all cross compiles with PREEMPT set, or TRACING on, etc.
+#ADD_CONFIG = ${THIS_DIR}/add-config
+
+# All tests are using defconfig
+BUILD_TYPE = defconfig
+
+# The test names will have the arch and cross compiler used. This will be shown in
+# the results.
+TEST_NAME = ${ARCH} ${CROSS}
+
+# alpha
+TEST_START IF ${RUN} == alpha || ${DO_DEFAULT}
+# Notice that CROSS and ARCH are also options and not variables (again '=' instead
+# of ':='). This is because TEST_NAME and MAKE_CMD wil use them for each test.
+# Only options are available during runs. Variables are only present in parsing the
+# config file.
+CROSS = alpha-linux
+ARCH = alpha
+
+# arm
+TEST_START IF ${RUN} == arm || ${DO_DEFAULT}
+CROSS = arm-unknown-linux-gnueabi
+ARCH = arm
+
+# black fin
+TEST_START IF ${RUN} == bfin || ${DO_DEFAULT}
+CROSS = bfin-uclinux
+ARCH = blackfin
+BUILD_OPTIONS = -j8 vmlinux
+
+# cris - FAILS?
+TEST_START IF ${RUN} == cris || ${RUN} == cris64 || ${DO_FAILED}
+CROSS = cris-linux
+ARCH = cris
+
+# cris32 - not right arch?
+TEST_START IF ${RUN} == cris || ${RUN} == cris32 || ${DO_FAILED}
+CROSS = crisv32-linux
+ARCH = cris
+
+# ia64
+TEST_START IF ${RUN} == ia64 || ${DO_DEFAULT}
+CROSS = ia64-linux
+ARCH = ia64
+
+# frv
+TEST_START IF ${RUN} == frv || ${DO_FAILED}
+CROSS = frv-linux
+ARCH = frv
+GCC_VER = 4.5.1
+
+# m68k fails with error?
+TEST_START IF ${RUN} == m68k || ${DO_DEFAULT}
+CROSS = m68k-linux
+ARCH = m68k
+
+# mips64
+TEST_START IF ${RUN} == mips || ${RUN} == mips64 || ${DO_DEFAULT}
+CROSS = mips64-linux
+ARCH = mips
+
+# mips32
+TEST_START IF ${RUN} == mips || ${RUN} == mips32 || ${DO_DEFAULT}
+CROSS = mips-linux
+ARCH = mips
+
+# m32r
+TEST_START IF ${RUN} == m32r || ${DO_FAILED}
+CROSS = m32r-linux
+ARCH = m32r
+GCC_VER = 4.5.1
+BUILD_OPTIONS = -j8 vmlinux
+
+# parisc64 failed?
+TEST_START IF ${RUN} == hppa || ${RUN} == hppa64 || ${DO_FAILED}
+CROSS = hppa64-linux
+ARCH = parisc
+
+# parisc
+TEST_START IF ${RUN} == hppa || ${RUN} == hppa32 || ${DO_FAILED}
+CROSS = hppa-linux
+ARCH = parisc
+
+# ppc
+TEST_START IF ${RUN} == ppc || ${RUN} == ppc32 || ${DO_DEFAULT}
+CROSS = powerpc-linux
+ARCH = powerpc
+
+# ppc64
+TEST_START IF ${RUN} == ppc || ${RUN} == ppc64 || ${DO_DEFAULT}
+CROSS = powerpc64-linux
+ARCH = powerpc
+
+# s390
+TEST_START IF ${RUN} == s390 || ${DO_DEFAULT}
+CROSS = s390x-linux
+ARCH = s390
+
+# sh
+TEST_START IF ${RUN} == sh || ${DO_DEFAULT}
+CROSS = sh4-linux
+ARCH = sh
+
+# sparc64
+TEST_START IF ${RUN} == sparc || ${RUN} == sparc64 || ${DO_DEFAULT}
+CROSS = sparc64-linux
+ARCH = sparc64
+
+# sparc
+TEST_START IF ${RUN} == sparc || ${RUN} == sparc32 || ${DO_DEFAULT}
+CROSS = sparc-linux
+ARCH = sparc
+
+# xtensa failed
+TEST_START IF ${RUN} == xtensa || ${DO_FAILED}
+CROSS = xtensa-linux
+ARCH = xtensa
+
+# UML
+TEST_START IF ${RUN} == uml || ${DO_DEFAULT}
+MAKE_CMD = make ARCH=um SUBARCH=x86_64
+ARCH = uml
+CROSS =
+
+TEST_START IF ${RUN} == x86 || ${RUN} == i386 || ${DO_DEFAULT}
+MAKE_CMD = make ARCH=i386
+ARCH = i386
+CROSS =
+
+TEST_START IF ${RUN} == x86 || ${RUN} == x86_64 || ${DO_DEFAULT}
+MAKE_CMD = make ARCH=x86_64
+ARCH = x86_64
+CROSS =
+
+#################################
+
+# This is a bisect if needed. You need to give it a MIN_CONFIG that
+# will be the config file it uses. Basically, just copy the created defconfig
+# for the arch someplace and point MIN_CONFIG to it.
+TEST_START IF ${RUN} == bisect
+MIN_CONFIG = ${THIS_DIR}/min-config
+CROSS = s390x-linux
+ARCH = s390
+TEST_TYPE = bisect
+BISECT_TYPE = build
+BISECT_GOOD = v3.1
+BISECT_BAD = v3.2
+CHECKOUT = v3.2
+
+#################################
+
+# These defaults are needed to keep ktest.pl from complaining. They are
+# ignored because the test does not go pass the build. No install or
+# booting of the target images.
+
+DEFAULTS
+MACHINE = crosstest
+SSH_USER = root
+BUILD_TARGET = cross
+TARGET_IMAGE = image
+POWER_CYCLE = cycle
+CONSOLE = console
+LOCALVERSION = version
+GRUB_MENU = grub
+
+REBOOT_ON_ERROR = 0
+POWEROFF_ON_ERROR = 0
+POWEROFF_ON_SUCCESS = 0
+REBOOT_ON_SUCCESS = 0
+
diff --git a/tools/testing/ktest/examples/include/bisect.conf b/tools/testing/ktest/examples/include/bisect.conf
new file mode 100644
index 00000000000..009bea65bfb
--- /dev/null
+++ b/tools/testing/ktest/examples/include/bisect.conf
@@ -0,0 +1,90 @@
+#
+# This example shows the bisect tests (git bisect and config bisect)
+#
+
+
+# The config that includes this file may define a RUN_TEST
+# variable that will tell this config what test to run.
+# (what to set the TEST option to).
+#
+DEFAULTS IF NOT DEFINED RUN_TEST
+# Requires that hackbench is in the PATH
+RUN_TEST := ${SSH} hackbench 50
+
+
+# Set TEST to 'bisect' to do a normal git bisect. You need
+# to modify the options below to make it bisect the exact
+# commits you are interested in.
+#
+TEST_START IF ${TEST} == bisect
+TEST_TYPE = bisect
+# You must set the commit that was considered good (git bisect good)
+BISECT_GOOD = v3.3
+# You must set the commit that was considered bad (git bisect bad)
+BISECT_BAD = HEAD
+# It's best to specify the branch to checkout before starting the bisect.
+CHECKOUT = origin/master
+# This can be build, boot, or test. Here we are doing a bisect
+# that requires to run a test to know if the bisect was good or bad.
+# The test should exit with 0 on good, non-zero for bad. But see
+# the BISECT_RET_* options in samples.conf to override this.
+BISECT_TYPE = test
+TEST = ${RUN_TEST}
+# It is usually a good idea to confirm that the GOOD and the BAD
+# commits are truly good and bad respectively. Having BISECT_CHECK
+# set to 1 will check both that the good commit works and the bad
+# commit fails. If you only want to check one or the other,
+# set BISECT_CHECK to 'good' or to 'bad'.
+BISECT_CHECK = 1
+#BISECT_CHECK = good
+#BISECT_CHECK = bad
+
+# Usually it's a good idea to specify the exact config you
+# want to use throughout the entire bisect. Here we placed
+# it in the directory we called ktest.pl from and named it
+# 'config-bisect'.
+MIN_CONFIG = ${THIS_DIR}/config-bisect
+# By default, if we are doing a BISECT_TYPE = test run but the
+# build or boot fails, ktest.pl will do a 'git bisect skip'.
+# Uncomment the below option to make ktest stop testing on such
+# an error.
+#BISECT_SKIP = 0
+# Now if you had BISECT_SKIP = 0 and the test fails, you can
+# examine what happened and then do 'git bisect log > /tmp/replay'
+# Set BISECT_REPLAY to /tmp/replay and ktest.pl will run the
+# 'git bisect replay /tmp/replay' before continuing the bisect test.
+#BISECT_REPLAY = /tmp/replay
+# If you used BISECT_REPLAY after the bisect test failed, you may
+# not want to continue the bisect on that commit that failed.
+# By setting BISECT_START to a new commit. ktest.pl will checkout
+# that commit after it has performed the 'git bisect replay' but
+# before it continues running the bisect test.
+#BISECT_START = 2545eb6198e7e1ec50daa0cfc64a4cdfecf24ec9
+
+# Now if you don't trust ktest.pl to make the decisions for you, then
+# set BISECT_MANUAL to 1. This will cause ktest.pl not to decide
+# if the commit was good or bad. Instead, it will ask you to tell
+# it if the current commit was good. In the mean time, you could
+# take the result, load it on any machine you want. Run several tests,
+# or whatever you feel like. Then, when you are happy, you can tell
+# ktest if you think it was good or not and ktest.pl will continue
+# the git bisect. You can even change what commit it is currently at.
+#BISECT_MANUAL = 1
+
+
+# One of the unique tests that ktest does is the config bisect.
+# Currently (which hopefully will be fixed soon), the bad config
+# must be a superset of the good config. This is because it only
+# searches for a config that causes the target to fail. If the
+# good config is not a subset of the bad config, or if the target
+# fails because of a lack of a config, then it will not find
+# the config for you.
+TEST_START IF ${TEST} == config-bisect
+TEST_TYPE = config_bisect
+# set to build, boot, test
+CONFIG_BISECT_TYPE = boot
+# Set the config that is considered bad.
+CONFIG_BISECT = ${THIS_DIR}/config-bad
+# This config is optional. By default it uses the
+# MIN_CONFIG as the good config.
+CONFIG_BISECT_GOOD = ${THIS_DIR}/config-good
diff --git a/tools/testing/ktest/examples/include/defaults.conf b/tools/testing/ktest/examples/include/defaults.conf
new file mode 100644
index 00000000000..63a1a83f4f0
--- /dev/null
+++ b/tools/testing/ktest/examples/include/defaults.conf
@@ -0,0 +1,157 @@
+# This file holds defaults for most the tests. It defines the options that
+# are most common to tests that are likely to be shared.
+#
+# Note, after including this file, a config file may override any option
+# with a DEFAULTS OVERRIDE section.
+#
+
+# For those cases that use the same machine to boot a 64 bit
+# and a 32 bit version. The MACHINE is the DNS name to get to the
+# box (usually different if it was 64 bit or 32 bit) but the
+# BOX here is defined as a variable that will be the name of the box
+# itself. It is useful for calling scripts that will power cycle
+# the box, as only one script needs to be created to power cycle
+# even though the box itself has multiple operating systems on it.
+# By default, BOX and MACHINE are the same.
+
+DEFAULTS IF NOT DEFINED BOX
+BOX := ${MACHINE}
+
+
+# Consider each box as 64 bit box, unless the config including this file
+# has defined BITS = 32
+
+DEFAULTS IF NOT DEFINED BITS
+BITS := 64
+
+
+DEFAULTS
+
+# THIS_DIR is used through out the configs and defaults to ${PWD} which
+# is the directory that ktest.pl was called from.
+
+THIS_DIR := ${PWD}
+
+
+# to organize your configs, having each machine save their configs
+# into a separate directly is useful.
+CONFIG_DIR := ${THIS_DIR}/configs/${MACHINE}
+
+# Reset the log before running each test.
+CLEAR_LOG = 1
+
+# As installing kernels usually requires root privilege, default the
+# user on the target as root. It is also required that the target
+# allows ssh to root from the host without asking for a password.
+
+SSH_USER = root
+
+# For accesing the machine, we will ssh to root@machine.
+SSH := ssh ${SSH_USER}@${MACHINE}
+
+# Update this. The default here is ktest will ssh to the target box
+# and run a script called 'run-test' located on that box.
+TEST = ${SSH} run-test
+
+# Point build dir to the git repo you use
+BUILD_DIR = ${THIS_DIR}/linux.git
+
+# Each machine will have its own output build directory.
+OUTPUT_DIR = ${THIS_DIR}/build/${MACHINE}
+
+# Yes this config is focused on x86 (but ktest works for other archs too)
+BUILD_TARGET = arch/x86/boot/bzImage
+TARGET_IMAGE = /boot/vmlinuz-test
+
+# have directory for the scripts to reboot and power cycle the boxes
+SCRIPTS_DIR := ${THIS_DIR}/scripts
+
+# You can have each box/machine have a script to power cycle it.
+# Name your script <box>-cycle.
+POWER_CYCLE = ${SCRIPTS_DIR}/${BOX}-cycle
+
+# This script is used to power off the box.
+POWER_OFF = ${SCRIPTS_DIR}/${BOX}-poweroff
+
+# Keep your test kernels separate from your other kernels.
+LOCALVERSION = -test
+
+# The /boot/grub/menu.lst is searched for the line:
+# title Test Kernel
+# and ktest will use that kernel to reboot into.
+# For grub2 or other boot loaders, you need to set BOOT_TYPE
+# to 'script' and define other ways to load the kernel.
+# See snowball.conf example.
+#
+GRUB_MENU = Test Kernel
+
+# The kernel build will use this option.
+BUILD_OPTIONS = -j8
+
+# Keeping the log file with the output dir is convenient.
+LOG_FILE = ${OUTPUT_DIR}/${MACHINE}.log
+
+# Each box should have their own minum configuration
+# See min-config.conf
+MIN_CONFIG = ${CONFIG_DIR}/config-min
+
+# For things like randconfigs, there may be configs you find that
+# are already broken, or there may be some configs that you always
+# want set. Uncomment ADD_CONFIG and point it to the make config files
+# that set the configs you want to keep on (or off) in your build.
+# ADD_CONFIG is usually something to add configs to all machines,
+# where as, MIN_CONFIG is specific per machine.
+#ADD_CONFIG = ${THIS_DIR}/config-broken ${THIS_DIR}/config-general
+
+# To speed up reboots for bisects and patchcheck, instead of
+# waiting 60 seconds for the console to be idle, if this line is
+# seen in the console output, ktest will know the good kernel has
+# finished rebooting and it will be able to continue the tests.
+REBOOT_SUCCESS_LINE = ${MACHINE} login:
+
+# The following is different ways to end the test.
+# by setting the variable REBOOT to: none, error, fail or
+# something else, ktest will power cycle or reboot the target box
+# at the end of the tests.
+#
+# REBOOT := none
+# Don't do anything at the end of the test.
+#
+# REBOOT := error
+# Reboot the box if ktest detects an error
+#
+# REBOOT := fail
+# Do not stop on failure, and after all tests are complete
+# power off the box (for both success and error)
+# This is good to run over a weekend and you don't want to waste
+# electricity.
+#
+
+DEFAULTS IF ${REBOOT} == none
+REBOOT_ON_SUCCESS = 0
+REBOOT_ON_ERROR = 0
+POWEROFF_ON_ERROR = 0
+POWEROFF_ON_SUCCESS = 0
+
+DEFAULTS ELSE IF ${REBOOT} == error
+REBOOT_ON_SUCCESS = 0
+REBOOT_ON_ERROR = 1
+POWEROFF_ON_ERROR = 0
+POWEROFF_ON_SUCCESS = 0
+
+DEFAULTS ELSE IF ${REBOOT} == fail
+REBOOT_ON_SUCCESS = 0
+POWEROFF_ON_ERROR = 1
+POWEROFF_ON_SUCCESS = 1
+POWEROFF_AFTER_HALT = 120
+DIE_ON_FAILURE = 0
+
+# Store the failure information into this directory
+# such as the .config, dmesg, and build log.
+STORE_FAILURES = ${THIS_DIR}/failures
+
+DEFAULTS ELSE
+REBOOT_ON_SUCCESS = 1
+REBOOT_ON_ERROR = 1
+POWEROFF_ON_ERROR = 0
+POWEROFF_ON_SUCCESS = 0
diff --git a/tools/testing/ktest/examples/include/min-config.conf b/tools/testing/ktest/examples/include/min-config.conf
new file mode 100644
index 00000000000..c703cc46d15
--- /dev/null
+++ b/tools/testing/ktest/examples/include/min-config.conf
@@ -0,0 +1,60 @@
+#
+# This file has some examples for creating a MIN_CONFIG.
+# (A .config file that is the minimum for a machine to boot, or
+# to boot and make a network connection.)
+#
+# A MIN_CONFIG is very useful as it is the minimum configuration
+# needed to boot a given machine. You can debug someone else's
+# .config by only setting the configs in your MIN_CONFIG. The closer
+# your MIN_CONFIG is to the true minimum set of configs needed to
+# boot your machine, the closer the config you test with will be
+# to the users config that had the failure.
+#
+# The make_min_config test allows you to create a MIN_CONFIG that
+# is truly the minimum set of configs needed to boot a box.
+#
+# In this example, the final config will reside in
+# ${CONFIG_DIR}/config-new-min and ${CONFIG_DIR}/config-new-min-net.
+# Just move one to the location you have set for MIN_CONFIG.
+#
+# The first test creates a MIN_CONFIG that will be the minimum
+# configuration to boot ${MACHINE} and be able to ssh to it.
+#
+# The second test creates a MIN_CONFIG that will only boot
+# the target and most likely will not let you ssh to it. (Notice
+# how the second test uses the first test's result to continue with.
+# This is because the second test config is a subset of the first).
+#
+# The ${CONFIG_DIR}/config-skip (and -net) will hold the configs
+# that ktest.pl found would not boot the target without them set.
+# The config-new-min holds configs that ktest.pl could not test
+# directly because another config that was needed to boot the box
+# selected them. Sometimes it is possible that this file will hold
+# the true minimum configuration. You can test to see if this is
+# the case by running the boot test with BOOT_TYPE = allnoconfig and
+# setting setting the MIN_CONFIG to ${CONFIG_DIR}/config-skip. If the
+# machine still boots, then you can use the config-skip as your MIN_CONFIG.
+#
+# These tests can run for several hours (and perhaps days).
+# It's OK to kill the test with a Ctrl^C. By restarting without
+# modifying this config, ktest.pl will notice that the config-new-min(-net)
+# exists, and will use that instead as the starting point.
+# The USE_OUTPUT_MIN_CONFIG is set to 1 to keep ktest.pl from asking
+# you if you want to use the OUTPUT_MIN_CONFIG as the starting point.
+# By using the OUTPUT_MIN_CONFIG as the starting point will allow ktest.pl to
+# start almost where it left off.
+#
+TEST_START IF ${TEST} == min-config
+TEST_TYPE = make_min_config
+OUTPUT_MIN_CONFIG = ${CONFIG_DIR}/config-new-min-net
+IGNORE_CONFIG = ${CONFIG_DIR}/config-skip-net
+MIN_CONFIG_TYPE = test
+TEST = ${SSH} echo hi
+USE_OUTPUT_MIN_CONFIG = 1
+
+TEST_START IF ${TEST} == min-config && ${MULTI}
+TEST_TYPE = make_min_config
+OUTPUT_MIN_CONFIG = ${CONFIG_DIR}/config-new-min
+IGNORE_CONFIG = ${CONFIG_DIR}/config-skip
+MIN_CONFIG = ${CONFIG_DIR}/config-new-min-net
+USE_OUTPUT_MIN_CONFIG = 1
diff --git a/tools/testing/ktest/examples/include/patchcheck.conf b/tools/testing/ktest/examples/include/patchcheck.conf
new file mode 100644
index 00000000000..0eb0a5ac77d
--- /dev/null
+++ b/tools/testing/ktest/examples/include/patchcheck.conf
@@ -0,0 +1,111 @@
+# patchcheck.conf
+#
+# This contains a test that takes two git commits and will test each
+# commit between the two. The build test will look at what files the
+# commit has touched, and if any of those files produce a warning, then
+# the build will fail.
+
+
+# PATCH_START is the commit to begin with and PATCH_END is the commit
+# to end with (inclusive). This is similar to doing a git rebase -i PATCH_START~1
+# and then testing each commit and doing a git rebase --continue.
+# You can use a SHA1, a git tag, or anything that git will accept for a checkout
+
+PATCH_START := HEAD~3
+PATCH_END := HEAD
+
+# Use the oldconfig if build_type wasn't defined
+DEFAULTS IF NOT DEFINED BUILD_TYPE
+DO_BUILD_TYPE := oldconfig
+
+DEFAULTS ELSE
+DO_BUILD_TYPE := ${BUILD_TYPE}
+
+DEFAULTS
+
+
+# Change PATCH_CHECKOUT to be the branch you want to test. The test will
+# do a git checkout of this branch before starting. Obviously both
+# PATCH_START and PATCH_END must be in this branch (and PATCH_START must
+# be contained by PATCH_END).
+
+PATCH_CHECKOUT := test/branch
+
+# Usually it's a good idea to have a set config to use for testing individual
+# patches.
+PATCH_CONFIG := ${CONFIG_DIR}/config-patchcheck
+
+# Change PATCH_TEST to run some test for each patch. Each commit that is
+# tested, after it is built and installed on the test machine, this command
+# will be executed. Usually what is done is to ssh to the target box and
+# run some test scripts. If you just want to boot test your patches
+# comment PATCH_TEST out.
+PATCH_TEST := ${SSH} "/usr/local/bin/ktest-test-script"
+
+DEFAULTS IF DEFINED PATCH_TEST
+PATCH_TEST_TYPE := test
+
+DEFAULTS ELSE
+PATCH_TEST_TYPE := boot
+
+# If for some reason a file has a warning that one of your patches touch
+# but you do not care about it, set IGNORE_WARNINGS to that commit(s)
+# (space delimited)
+#IGNORE_WARNINGS = 39eaf7ef884dcc44f7ff1bac803ca2a1dcf43544 6edb2a8a385f0cdef51dae37ff23e74d76d8a6ce
+
+# Instead of just checking for warnings to files that are changed
+# it can be advantageous to check for any new warnings. If a
+# header file is changed, it could cause a warning in a file not
+# touched by the commit. To detect these kinds of warnings, you
+# can use the WARNINGS_FILE option.
+#
+# If the variable CREATE_WARNINGS_FILE is set, this config will
+# enable the WARNINGS_FILE during the patchcheck test. Also,
+# before running the patchcheck test, it will create the
+# warnings file.
+#
+DEFAULTS IF DEFINED CREATE_WARNINGS_FILE
+WARNINGS_FILE = ${OUTPUT_DIR}/warnings_file
+
+TEST_START IF DEFINED CREATE_WARNINGS_FILE
+# WARNINGS_FILE is already set by the DEFAULTS above
+TEST_TYPE = make_warnings_file
+# Checkout the commit before the patches to test,
+# and record all the warnings that exist before the patches
+# to test are added
+CHECKOUT = ${PATCHCHECK_START}~1
+# Force a full build
+BUILD_NOCLEAN = 0
+BUILD_TYPE = ${DO_BUILD_TYPE}
+
+# If you are running a multi test, and the test failed on the first
+# test but on, say the 5th patch. If you want to restart on the
+# fifth patch, set PATCH_START1. This will make the first test start
+# from this commit instead of the PATCH_START commit.
+# Note, do not change this option. Just define PATCH_START1 in the
+# top config (the one you pass to ktest.pl), and this will use it,
+# otherwise it will just use PATCH_START if PATCH_START1 is not defined.
+DEFAULTS IF NOT DEFINED PATCH_START1
+PATCH_START1 := ${PATCH_START}
+
+TEST_START IF ${TEST} == patchcheck
+TEST_TYPE = patchcheck
+MIN_CONFIG = ${PATCH_CONFIG}
+TEST = ${PATCH_TEST}
+PATCHCHECK_TYPE = ${PATCH_TEST_TYPE}
+PATCHCHECK_START = ${PATCH_START1}
+PATCHCHECK_END = ${PATCH_END}
+CHECKOUT = ${PATCH_CHECKOUT}
+BUILD_TYPE = ${DO_BUILD_TYPE}
+
+TEST_START IF ${TEST} == patchcheck && ${MULTI}
+TEST_TYPE = patchcheck
+MIN_CONFIG = ${PATCH_CONFIG}
+TEST = ${PATCH_TEST}
+PATCHCHECK_TYPE = ${PATCH_TEST_TYPE}
+PATCHCHECK_START = ${PATCH_START}
+PATCHCHECK_END = ${PATCH_END}
+CHECKOUT = ${PATCH_CHECKOUT}
+# Use multi to test different compilers?
+MAKE_CMD = CC=gcc-4.5.1 make
+BUILD_TYPE = ${DO_BUILD_TYPE}
diff --git a/tools/testing/ktest/examples/include/tests.conf b/tools/testing/ktest/examples/include/tests.conf
new file mode 100644
index 00000000000..60cedb1a115
--- /dev/null
+++ b/tools/testing/ktest/examples/include/tests.conf
@@ -0,0 +1,74 @@
+#
+# This is an example of various tests that you can run
+#
+# The variable TEST can be of boot, build, randconfig, or test.
+#
+# Note that TEST is a variable created with ':=' and only exists
+# throughout the config processing (not during the tests itself).
+#
+# The TEST option (defined with '=') is used to tell ktest.pl
+# what test to run after a successful boot. The TEST option is
+# persistent into the test runs.
+#
+
+# The config that includes this file may define a BOOT_TYPE
+# variable that tells this config what type of boot test to run.
+# If it's not defined, the below DEFAULTS will set the default
+# to 'oldconfig'.
+#
+DEFAULTS IF NOT DEFINED BOOT_TYPE
+BOOT_TYPE := oldconfig
+
+# The config that includes this file may define a RUN_TEST
+# variable that will tell this config what test to run.
+# (what to set the TEST option to).
+#
+DEFAULTS IF NOT DEFINED RUN_TEST
+# Requires that hackbench is in the PATH
+RUN_TEST := ${SSH} hackbench 50
+
+
+# If TEST is set to 'boot' then just build a kernel and boot
+# the target.
+TEST_START IF ${TEST} == boot
+TEST_TYPE = boot
+# Notice how we set the BUILD_TYPE option to the BOOT_TYPE variable.
+BUILD_TYPE = ${BOOT_TYPE}
+# Do not do a make mrproper.
+BUILD_NOCLEAN = 1
+
+# If you only want to build the kernel, and perhaps install
+# and test it yourself, then just set TEST to build.
+TEST_START IF ${TEST} == build
+TEST_TYPE = build
+BUILD_TYPE = ${BOOT_TYPE}
+BUILD_NOCLEAN = 1
+
+# Build, install, boot and test with a randconfg 10 times.
+# It is important that you have set MIN_CONFIG in the config
+# that includes this file otherwise it is likely that the
+# randconfig will not have the necessary configs needed to
+# boot your box. This version of the test requires a min
+# config that has enough to make sure the target has network
+# working.
+TEST_START ITERATE 10 IF ${TEST} == randconfig
+MIN_CONFIG = ${CONFIG_DIR}/config-min-net
+TEST_TYPE = test
+BUILD_TYPE = randconfig
+TEST = ${RUN_TEST}
+
+# This is the same as above, but only tests to a boot prompt.
+# The MIN_CONFIG used here does not need to have networking
+# working.
+TEST_START ITERATE 10 IF ${TEST} == randconfig && ${MULTI}
+TEST_TYPE = boot
+BUILD_TYPE = randconfig
+MIN_CONFIG = ${CONFIG_DIR}/config-min
+MAKE_CMD = make
+
+# This builds, installs, boots and tests the target.
+TEST_START IF ${TEST} == test
+TEST_TYPE = test
+BUILD_TYPE = ${BOOT_TYPE}
+TEST = ${RUN_TEST}
+BUILD_NOCLEAN = 1
diff --git a/tools/testing/ktest/examples/kvm.conf b/tools/testing/ktest/examples/kvm.conf
new file mode 100644
index 00000000000..fbc134f9ac6
--- /dev/null
+++ b/tools/testing/ktest/examples/kvm.conf
@@ -0,0 +1,92 @@
+#
+# This config is an example usage of ktest.pl with a kvm guest
+#
+# The guest is called 'Guest' and this would be something that
+# could be run on the host to test a virtual machine target.
+
+MACHINE = Guest
+
+
+# Use virsh to read the serial console of the guest
+CONSOLE = virsh console ${MACHINE}
+
+# Use SIGKILL to terminate virsh console. We can't kill virsh console
+# by the default signal, SIGINT.
+CLOSE_CONSOLE_SIGNAL = KILL
+
+#*************************************#
+# This part is the same as test.conf #
+#*************************************#
+
+# The include files will set up the type of test to run. Just set TEST to
+# which test you want to run.
+#
+# TESTS = patchcheck, randconfig, boot, test, config-bisect, bisect, min-config
+#
+# See the include/*.conf files that define these tests
+#
+TEST := patchcheck
+
+# Some tests may have more than one test to run. Define MULTI := 1 to run
+# the extra tests.
+MULTI := 0
+
+# In case you want to differentiate which type of system you are testing
+BITS := 64
+
+# REBOOT = none, error, fail, empty
+# See include/defaults.conf
+REBOOT := empty
+
+
+# The defaults file will set up various settings that can be used by all
+# machine configs.
+INCLUDE include/defaults.conf
+
+
+#*************************************#
+# Now we are different from test.conf #
+#*************************************#
+
+
+# The example here assumes that Guest is running a Fedora release
+# that uses dracut for its initfs. The POST_INSTALL will be executed
+# after the install of the kernel and modules are complete.
+#
+POST_INSTALL = ${SSH} /sbin/dracut -f /boot/initramfs-test.img $KERNEL_VERSION
+
+# Guests sometimes get stuck on reboot. We wait 3 seconds after running
+# the reboot command and then do a full power-cycle of the guest.
+# This forces the guest to restart.
+#
+POWERCYCLE_AFTER_REBOOT = 3
+
+# We do the same after the halt command, but this time we wait 20 seconds.
+POWEROFF_AFTER_HALT = 20
+
+
+# As the defaults.conf file has a POWER_CYCLE option already defined,
+# and options can not be defined in the same section more than once
+# (all DEFAULTS sections are considered the same). We use the
+# DEFAULTS OVERRIDE to tell ktest.pl to ignore the previous defined
+# options, for the options set in the OVERRIDE section.
+#
+DEFAULTS OVERRIDE
+
+# Instead of using the default POWER_CYCLE option defined in
+# defaults.conf, we use virsh to cycle it. To do so, we destroy
+# the guest, wait 5 seconds, and then start it up again.
+# Crude, but effective.
+#
+POWER_CYCLE = virsh destroy ${MACHINE}; sleep 5; virsh start ${MACHINE}
+
+
+DEFAULTS
+
+# The following files each handle a different test case.
+# Having them included allows you to set up more than one machine and share
+# the same tests.
+INCLUDE include/patchcheck.conf
+INCLUDE include/tests.conf
+INCLUDE include/bisect.conf
+INCLUDE include/min-config.conf
diff --git a/tools/testing/ktest/examples/snowball.conf b/tools/testing/ktest/examples/snowball.conf
new file mode 100644
index 00000000000..a82a3c5bc2b
--- /dev/null
+++ b/tools/testing/ktest/examples/snowball.conf
@@ -0,0 +1,53 @@
+# This example was used to boot the snowball ARM board.
+# See http://people.redhat.com/srostedt/ktest-embedded-2012/
+
+# PWD is a ktest.pl variable that will result in the process working
+# directory that ktest.pl is executed in.
+
+# THIS_DIR is automatically assigned the PWD of the path that generated
+# the config file. It is best to use this variable when assigning other
+# directory paths within this directory. This allows you to easily
+# move the test cases to other locations or to other machines.
+#
+THIS_DIR := /home/rostedt/work/demo/ktest-embed
+LOG_FILE = ${OUTPUT_DIR}/snowball.log
+CLEAR_LOG = 1
+MAKE_CMD = PATH=/usr/local/gcc-4.5.2-nolibc/arm-unknown-linux-gnueabi/bin:$PATH CROSS_COMPILE=arm-unknown-linux-gnueabi- make ARCH=arm
+ADD_CONFIG = ${THIS_DIR}/addconfig
+
+SCP_TO_TARGET = echo "don't do scp"
+
+TFTPBOOT := /var/lib/tftpboot
+TFTPDEF := ${TFTPBOOT}/snowball-default
+TFTPTEST := ${OUTPUT_DIR}/${BUILD_TARGET}
+
+SWITCH_TO_GOOD = cp ${TFTPDEF} ${TARGET_IMAGE}
+SWITCH_TO_TEST = cp ${TFTPTEST} ${TARGET_IMAGE}
+
+# Define each test with TEST_START
+# The config options below it will override the defaults
+TEST_START SKIP
+TEST_TYPE = boot
+BUILD_TYPE = u8500_defconfig
+BUILD_NOCLEAN = 1
+
+TEST_START
+TEST_TYPE = make_min_config
+OUTPUT_MIN_CONFIG = ${THIS_DIR}/config.newmin
+START_MIN_CONFIG = ${THIS_DIR}/config.orig
+IGNORE_CONFIG = ${THIS_DIR}/config.ignore
+BUILD_NOCLEAN = 1
+
+
+DEFAULTS
+LOCALVERSION = -test
+POWER_CYCLE = echo use the thumb luke; read a
+CONSOLE = cat ${THIS_DIR}/snowball-cat
+REBOOT_TYPE = script
+SSH_USER = root
+BUILD_OPTIONS = -j8 uImage
+BUILD_DIR = ${THIS_DIR}/linux.git
+OUTPUT_DIR = ${THIS_DIR}/snowball-build
+MACHINE = snowball
+TARGET_IMAGE = /var/lib/tftpboot/snowball-image
+BUILD_TARGET = arch/arm/boot/uImage
diff --git a/tools/testing/ktest/examples/test.conf b/tools/testing/ktest/examples/test.conf
new file mode 100644
index 00000000000..b725210efb7
--- /dev/null
+++ b/tools/testing/ktest/examples/test.conf
@@ -0,0 +1,62 @@
+#
+# Generic config for a machine
+#
+
+# Name your machine (the DNS name, what you ssh to)
+MACHINE = foo
+
+# BOX can be different than foo, if the machine BOX has
+# multiple partitions with different systems installed. For example,
+# you may have a i386 and x86_64 installation on a test box.
+# If this is the case, MACHINE defines the way to connect to the
+# machine, which may be different between which system the machine
+# is booting into. BOX is used for the scripts to reboot and power cycle
+# the machine, where it does not matter which system the machine boots into.
+#
+#BOX := bar
+
+# Define a way to read the console
+CONSOLE = stty -F /dev/ttyS0 115200 parodd; cat /dev/ttyS0
+
+# The include files will set up the type of test to run. Just set TEST to
+# which test you want to run.
+#
+# TESTS = patchcheck, randconfig, boot, test, config-bisect, bisect, min-config
+#
+# See the include/*.conf files that define these tests
+#
+TEST := patchcheck
+
+# Some tests may have more than one test to run. Define MULTI := 1 to run
+# the extra tests.
+MULTI := 0
+
+# In case you want to differentiate which type of system you are testing
+BITS := 64
+
+# REBOOT = none, error, fail, empty
+# See include/defaults.conf
+REBOOT := empty
+
+# The defaults file will set up various settings that can be used by all
+# machine configs.
+INCLUDE include/defaults.conf
+
+# In case you need to add a patch for a bisect or something
+#PRE_BUILD = patch -p1 < ${THIS_DIR}/fix.patch
+
+# Reset the repo after the build and remove all 'test' modules from the target
+# Notice that DO_POST_BUILD is a variable (defined by ':=') and POST_BUILD
+# is the option (defined by '=')
+
+DO_POST_BUILD := git reset --hard
+POST_BUILD = ${SSH} 'rm -rf /lib/modules/*-test*'; ${DO_POST_BUILD}
+
+# The following files each handle a different test case.
+# Having them included allows you to set up more than one machine and share
+# the same tests.
+INCLUDE include/patchcheck.conf
+INCLUDE include/tests.conf
+INCLUDE include/bisect.conf
+INCLUDE include/min-config.conf
+
diff --git a/tools/testing/ktest/ktest.pl b/tools/testing/ktest/ktest.pl
new file mode 100755
index 00000000000..40631569a0f
--- /dev/null
+++ b/tools/testing/ktest/ktest.pl
@@ -0,0 +1,4181 @@
+#!/usr/bin/perl -w
+#
+# Copyright 2010 - Steven Rostedt <srostedt@redhat.com>, Red Hat Inc.
+# Licensed under the terms of the GNU GPL License version 2
+#
+
+use strict;
+use IPC::Open2;
+use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK);
+use File::Path qw(mkpath);
+use File::Copy qw(cp);
+use FileHandle;
+
+my $VERSION = "0.2";
+
+$| = 1;
+
+my %opt;
+my %repeat_tests;
+my %repeats;
+my %evals;
+
+#default opts
+my %default = (
+ "NUM_TESTS" => 1,
+ "TEST_TYPE" => "build",
+ "BUILD_TYPE" => "randconfig",
+ "MAKE_CMD" => "make",
+ "CLOSE_CONSOLE_SIGNAL" => "INT",
+ "TIMEOUT" => 120,
+ "TMP_DIR" => "/tmp/ktest/\${MACHINE}",
+ "SLEEP_TIME" => 60, # sleep time between tests
+ "BUILD_NOCLEAN" => 0,
+ "REBOOT_ON_ERROR" => 0,
+ "POWEROFF_ON_ERROR" => 0,
+ "REBOOT_ON_SUCCESS" => 1,
+ "POWEROFF_ON_SUCCESS" => 0,
+ "BUILD_OPTIONS" => "",
+ "BISECT_SLEEP_TIME" => 60, # sleep time between bisects
+ "PATCHCHECK_SLEEP_TIME" => 60, # sleep time between patch checks
+ "CLEAR_LOG" => 0,
+ "BISECT_MANUAL" => 0,
+ "BISECT_SKIP" => 1,
+ "BISECT_TRIES" => 1,
+ "MIN_CONFIG_TYPE" => "boot",
+ "SUCCESS_LINE" => "login:",
+ "DETECT_TRIPLE_FAULT" => 1,
+ "NO_INSTALL" => 0,
+ "BOOTED_TIMEOUT" => 1,
+ "DIE_ON_FAILURE" => 1,
+ "SSH_EXEC" => "ssh \$SSH_USER\@\$MACHINE \$SSH_COMMAND",
+ "SCP_TO_TARGET" => "scp \$SRC_FILE \$SSH_USER\@\$MACHINE:\$DST_FILE",
+ "SCP_TO_TARGET_INSTALL" => "\${SCP_TO_TARGET}",
+ "REBOOT" => "ssh \$SSH_USER\@\$MACHINE reboot",
+ "STOP_AFTER_SUCCESS" => 10,
+ "STOP_AFTER_FAILURE" => 60,
+ "STOP_TEST_AFTER" => 600,
+ "MAX_MONITOR_WAIT" => 1800,
+ "GRUB_REBOOT" => "grub2-reboot",
+ "SYSLINUX" => "extlinux",
+ "SYSLINUX_PATH" => "/boot/extlinux",
+
+# required, and we will ask users if they don't have them but we keep the default
+# value something that is common.
+ "REBOOT_TYPE" => "grub",
+ "LOCALVERSION" => "-test",
+ "SSH_USER" => "root",
+ "BUILD_TARGET" => "arch/x86/boot/bzImage",
+ "TARGET_IMAGE" => "/boot/vmlinuz-test",
+
+ "LOG_FILE" => undef,
+ "IGNORE_UNUSED" => 0,
+);
+
+my $ktest_config;
+my $version;
+my $have_version = 0;
+my $machine;
+my $last_machine;
+my $ssh_user;
+my $tmpdir;
+my $builddir;
+my $outputdir;
+my $output_config;
+my $test_type;
+my $build_type;
+my $build_options;
+my $final_post_ktest;
+my $pre_ktest;
+my $post_ktest;
+my $pre_test;
+my $post_test;
+my $pre_build;
+my $post_build;
+my $pre_build_die;
+my $post_build_die;
+my $reboot_type;
+my $reboot_script;
+my $power_cycle;
+my $reboot;
+my $reboot_on_error;
+my $switch_to_good;
+my $switch_to_test;
+my $poweroff_on_error;
+my $reboot_on_success;
+my $die_on_failure;
+my $powercycle_after_reboot;
+my $poweroff_after_halt;
+my $max_monitor_wait;
+my $ssh_exec;
+my $scp_to_target;
+my $scp_to_target_install;
+my $power_off;
+my $grub_menu;
+my $last_grub_menu;
+my $grub_file;
+my $grub_number;
+my $grub_reboot;
+my $syslinux;
+my $syslinux_path;
+my $syslinux_label;
+my $target;
+my $make;
+my $pre_install;
+my $post_install;
+my $no_install;
+my $noclean;
+my $minconfig;
+my $start_minconfig;
+my $start_minconfig_defined;
+my $output_minconfig;
+my $minconfig_type;
+my $use_output_minconfig;
+my $warnings_file;
+my $ignore_config;
+my $ignore_errors;
+my $addconfig;
+my $in_bisect = 0;
+my $bisect_bad_commit = "";
+my $reverse_bisect;
+my $bisect_manual;
+my $bisect_skip;
+my $bisect_tries;
+my $config_bisect_good;
+my $bisect_ret_good;
+my $bisect_ret_bad;
+my $bisect_ret_skip;
+my $bisect_ret_abort;
+my $bisect_ret_default;
+my $in_patchcheck = 0;
+my $run_test;
+my $redirect;
+my $buildlog;
+my $testlog;
+my $dmesg;
+my $monitor_fp;
+my $monitor_pid;
+my $monitor_cnt = 0;
+my $sleep_time;
+my $bisect_sleep_time;
+my $patchcheck_sleep_time;
+my $ignore_warnings;
+my $store_failures;
+my $store_successes;
+my $test_name;
+my $timeout;
+my $booted_timeout;
+my $detect_triplefault;
+my $console;
+my $close_console_signal;
+my $reboot_success_line;
+my $success_line;
+my $stop_after_success;
+my $stop_after_failure;
+my $stop_test_after;
+my $build_target;
+my $target_image;
+my $checkout;
+my $localversion;
+my $iteration = 0;
+my $successes = 0;
+
+my $bisect_good;
+my $bisect_bad;
+my $bisect_type;
+my $bisect_start;
+my $bisect_replay;
+my $bisect_files;
+my $bisect_reverse;
+my $bisect_check;
+
+my $config_bisect;
+my $config_bisect_type;
+my $config_bisect_check;
+
+my $patchcheck_type;
+my $patchcheck_start;
+my $patchcheck_end;
+
+# set when a test is something other that just building or install
+# which would require more options.
+my $buildonly = 1;
+
+# tell build not to worry about warnings, even when WARNINGS_FILE is set
+my $warnings_ok = 0;
+
+# set when creating a new config
+my $newconfig = 0;
+
+my %entered_configs;
+my %config_help;
+my %variable;
+
+# force_config is the list of configs that we force enabled (or disabled)
+# in a .config file. The MIN_CONFIG and ADD_CONFIG configs.
+my %force_config;
+
+# do not force reboots on config problems
+my $no_reboot = 1;
+
+# reboot on success
+my $reboot_success = 0;
+
+my %option_map = (
+ "MACHINE" => \$machine,
+ "SSH_USER" => \$ssh_user,
+ "TMP_DIR" => \$tmpdir,
+ "OUTPUT_DIR" => \$outputdir,
+ "BUILD_DIR" => \$builddir,
+ "TEST_TYPE" => \$test_type,
+ "PRE_KTEST" => \$pre_ktest,
+ "POST_KTEST" => \$post_ktest,
+ "PRE_TEST" => \$pre_test,
+ "POST_TEST" => \$post_test,
+ "BUILD_TYPE" => \$build_type,
+ "BUILD_OPTIONS" => \$build_options,
+ "PRE_BUILD" => \$pre_build,
+ "POST_BUILD" => \$post_build,
+ "PRE_BUILD_DIE" => \$pre_build_die,
+ "POST_BUILD_DIE" => \$post_build_die,
+ "POWER_CYCLE" => \$power_cycle,
+ "REBOOT" => \$reboot,
+ "BUILD_NOCLEAN" => \$noclean,
+ "MIN_CONFIG" => \$minconfig,
+ "OUTPUT_MIN_CONFIG" => \$output_minconfig,
+ "START_MIN_CONFIG" => \$start_minconfig,
+ "MIN_CONFIG_TYPE" => \$minconfig_type,
+ "USE_OUTPUT_MIN_CONFIG" => \$use_output_minconfig,
+ "WARNINGS_FILE" => \$warnings_file,
+ "IGNORE_CONFIG" => \$ignore_config,
+ "TEST" => \$run_test,
+ "ADD_CONFIG" => \$addconfig,
+ "REBOOT_TYPE" => \$reboot_type,
+ "GRUB_MENU" => \$grub_menu,
+ "GRUB_FILE" => \$grub_file,
+ "GRUB_REBOOT" => \$grub_reboot,
+ "SYSLINUX" => \$syslinux,
+ "SYSLINUX_PATH" => \$syslinux_path,
+ "SYSLINUX_LABEL" => \$syslinux_label,
+ "PRE_INSTALL" => \$pre_install,
+ "POST_INSTALL" => \$post_install,
+ "NO_INSTALL" => \$no_install,
+ "REBOOT_SCRIPT" => \$reboot_script,
+ "REBOOT_ON_ERROR" => \$reboot_on_error,
+ "SWITCH_TO_GOOD" => \$switch_to_good,
+ "SWITCH_TO_TEST" => \$switch_to_test,
+ "POWEROFF_ON_ERROR" => \$poweroff_on_error,
+ "REBOOT_ON_SUCCESS" => \$reboot_on_success,
+ "DIE_ON_FAILURE" => \$die_on_failure,
+ "POWER_OFF" => \$power_off,
+ "POWERCYCLE_AFTER_REBOOT" => \$powercycle_after_reboot,
+ "POWEROFF_AFTER_HALT" => \$poweroff_after_halt,
+ "MAX_MONITOR_WAIT" => \$max_monitor_wait,
+ "SLEEP_TIME" => \$sleep_time,
+ "BISECT_SLEEP_TIME" => \$bisect_sleep_time,
+ "PATCHCHECK_SLEEP_TIME" => \$patchcheck_sleep_time,
+ "IGNORE_WARNINGS" => \$ignore_warnings,
+ "IGNORE_ERRORS" => \$ignore_errors,
+ "BISECT_MANUAL" => \$bisect_manual,
+ "BISECT_SKIP" => \$bisect_skip,
+ "BISECT_TRIES" => \$bisect_tries,
+ "CONFIG_BISECT_GOOD" => \$config_bisect_good,
+ "BISECT_RET_GOOD" => \$bisect_ret_good,
+ "BISECT_RET_BAD" => \$bisect_ret_bad,
+ "BISECT_RET_SKIP" => \$bisect_ret_skip,
+ "BISECT_RET_ABORT" => \$bisect_ret_abort,
+ "BISECT_RET_DEFAULT" => \$bisect_ret_default,
+ "STORE_FAILURES" => \$store_failures,
+ "STORE_SUCCESSES" => \$store_successes,
+ "TEST_NAME" => \$test_name,
+ "TIMEOUT" => \$timeout,
+ "BOOTED_TIMEOUT" => \$booted_timeout,
+ "CONSOLE" => \$console,
+ "CLOSE_CONSOLE_SIGNAL" => \$close_console_signal,
+ "DETECT_TRIPLE_FAULT" => \$detect_triplefault,
+ "SUCCESS_LINE" => \$success_line,
+ "REBOOT_SUCCESS_LINE" => \$reboot_success_line,
+ "STOP_AFTER_SUCCESS" => \$stop_after_success,
+ "STOP_AFTER_FAILURE" => \$stop_after_failure,
+ "STOP_TEST_AFTER" => \$stop_test_after,
+ "BUILD_TARGET" => \$build_target,
+ "SSH_EXEC" => \$ssh_exec,
+ "SCP_TO_TARGET" => \$scp_to_target,
+ "SCP_TO_TARGET_INSTALL" => \$scp_to_target_install,
+ "CHECKOUT" => \$checkout,
+ "TARGET_IMAGE" => \$target_image,
+ "LOCALVERSION" => \$localversion,
+
+ "BISECT_GOOD" => \$bisect_good,
+ "BISECT_BAD" => \$bisect_bad,
+ "BISECT_TYPE" => \$bisect_type,
+ "BISECT_START" => \$bisect_start,
+ "BISECT_REPLAY" => \$bisect_replay,
+ "BISECT_FILES" => \$bisect_files,
+ "BISECT_REVERSE" => \$bisect_reverse,
+ "BISECT_CHECK" => \$bisect_check,
+
+ "CONFIG_BISECT" => \$config_bisect,
+ "CONFIG_BISECT_TYPE" => \$config_bisect_type,
+ "CONFIG_BISECT_CHECK" => \$config_bisect_check,
+
+ "PATCHCHECK_TYPE" => \$patchcheck_type,
+ "PATCHCHECK_START" => \$patchcheck_start,
+ "PATCHCHECK_END" => \$patchcheck_end,
+);
+
+# Options may be used by other options, record them.
+my %used_options;
+
+# default variables that can be used
+chomp ($variable{"PWD"} = `pwd`);
+
+$config_help{"MACHINE"} = << "EOF"
+ The machine hostname that you will test.
+ For build only tests, it is still needed to differentiate log files.
+EOF
+ ;
+$config_help{"SSH_USER"} = << "EOF"
+ The box is expected to have ssh on normal bootup, provide the user
+ (most likely root, since you need privileged operations)
+EOF
+ ;
+$config_help{"BUILD_DIR"} = << "EOF"
+ The directory that contains the Linux source code (full path).
+ You can use \${PWD} that will be the path where ktest.pl is run, or use
+ \${THIS_DIR} which is assigned \${PWD} but may be changed later.
+EOF
+ ;
+$config_help{"OUTPUT_DIR"} = << "EOF"
+ The directory that the objects will be built (full path).
+ (can not be same as BUILD_DIR)
+ You can use \${PWD} that will be the path where ktest.pl is run, or use
+ \${THIS_DIR} which is assigned \${PWD} but may be changed later.
+EOF
+ ;
+$config_help{"BUILD_TARGET"} = << "EOF"
+ The location of the compiled file to copy to the target.
+ (relative to OUTPUT_DIR)
+EOF
+ ;
+$config_help{"BUILD_OPTIONS"} = << "EOF"
+ Options to add to \"make\" when building.
+ i.e. -j20
+EOF
+ ;
+$config_help{"TARGET_IMAGE"} = << "EOF"
+ The place to put your image on the test machine.
+EOF
+ ;
+$config_help{"POWER_CYCLE"} = << "EOF"
+ A script or command to reboot the box.
+
+ Here is a digital loggers power switch example
+ POWER_CYCLE = wget --no-proxy -O /dev/null -q --auth-no-challenge 'http://admin:admin\@power/outlet?5=CCL'
+
+ Here is an example to reboot a virtual box on the current host
+ with the name "Guest".
+ POWER_CYCLE = virsh destroy Guest; sleep 5; virsh start Guest
+EOF
+ ;
+$config_help{"CONSOLE"} = << "EOF"
+ The script or command that reads the console
+
+ If you use ttywatch server, something like the following would work.
+CONSOLE = nc -d localhost 3001
+
+ For a virtual machine with guest name "Guest".
+CONSOLE = virsh console Guest
+EOF
+ ;
+$config_help{"LOCALVERSION"} = << "EOF"
+ Required version ending to differentiate the test
+ from other linux builds on the system.
+EOF
+ ;
+$config_help{"REBOOT_TYPE"} = << "EOF"
+ Way to reboot the box to the test kernel.
+ Only valid options so far are "grub", "grub2", "syslinux", and "script".
+
+ If you specify grub, it will assume grub version 1
+ and will search in /boot/grub/menu.lst for the title \$GRUB_MENU
+ and select that target to reboot to the kernel. If this is not
+ your setup, then specify "script" and have a command or script
+ specified in REBOOT_SCRIPT to boot to the target.
+
+ The entry in /boot/grub/menu.lst must be entered in manually.
+ The test will not modify that file.
+
+ If you specify grub2, then you also need to specify both \$GRUB_MENU
+ and \$GRUB_FILE.
+
+ If you specify syslinux, then you may use SYSLINUX to define the syslinux
+ command (defaults to extlinux), and SYSLINUX_PATH to specify the path to
+ the syslinux install (defaults to /boot/extlinux). But you have to specify
+ SYSLINUX_LABEL to define the label to boot to for the test kernel.
+EOF
+ ;
+$config_help{"GRUB_MENU"} = << "EOF"
+ The grub title name for the test kernel to boot
+ (Only mandatory if REBOOT_TYPE = grub or grub2)
+
+ Note, ktest.pl will not update the grub menu.lst, you need to
+ manually add an option for the test. ktest.pl will search
+ the grub menu.lst for this option to find what kernel to
+ reboot into.
+
+ For example, if in the /boot/grub/menu.lst the test kernel title has:
+ title Test Kernel
+ kernel vmlinuz-test
+ GRUB_MENU = Test Kernel
+
+ For grub2, a search of \$GRUB_FILE is performed for the lines
+ that begin with "menuentry". It will not detect submenus. The
+ menu must be a non-nested menu. Add the quotes used in the menu
+ to guarantee your selection, as the first menuentry with the content
+ of \$GRUB_MENU that is found will be used.
+EOF
+ ;
+$config_help{"GRUB_FILE"} = << "EOF"
+ If grub2 is used, the full path for the grub.cfg file is placed
+ here. Use something like /boot/grub2/grub.cfg to search.
+EOF
+ ;
+$config_help{"SYSLINUX_LABEL"} = << "EOF"
+ If syslinux is used, the label that boots the target kernel must
+ be specified with SYSLINUX_LABEL.
+EOF
+ ;
+$config_help{"REBOOT_SCRIPT"} = << "EOF"
+ A script to reboot the target into the test kernel
+ (Only mandatory if REBOOT_TYPE = script)
+EOF
+ ;
+
+sub _logit {
+ if (defined($opt{"LOG_FILE"})) {
+ open(OUT, ">> $opt{LOG_FILE}") or die "Can't write to $opt{LOG_FILE}";
+ print OUT @_;
+ close(OUT);
+ }
+}
+
+sub logit {
+ if (defined($opt{"LOG_FILE"})) {
+ _logit @_;
+ } else {
+ print @_;
+ }
+}
+
+sub doprint {
+ print @_;
+ _logit @_;
+}
+
+sub read_prompt {
+ my ($cancel, $prompt) = @_;
+
+ my $ans;
+
+ for (;;) {
+ if ($cancel) {
+ print "$prompt [y/n/C] ";
+ } else {
+ print "$prompt [Y/n] ";
+ }
+ $ans = <STDIN>;
+ chomp $ans;
+ if ($ans =~ /^\s*$/) {
+ if ($cancel) {
+ $ans = "c";
+ } else {
+ $ans = "y";
+ }
+ }
+ last if ($ans =~ /^y$/i || $ans =~ /^n$/i);
+ if ($cancel) {
+ last if ($ans =~ /^c$/i);
+ print "Please answer either 'y', 'n' or 'c'.\n";
+ } else {
+ print "Please answer either 'y' or 'n'.\n";
+ }
+ }
+ if ($ans =~ /^c/i) {
+ exit;
+ }
+ if ($ans !~ /^y$/i) {
+ return 0;
+ }
+ return 1;
+}
+
+sub read_yn {
+ my ($prompt) = @_;
+
+ return read_prompt 0, $prompt;
+}
+
+sub read_ync {
+ my ($prompt) = @_;
+
+ return read_prompt 1, $prompt;
+}
+
+sub get_ktest_config {
+ my ($config) = @_;
+ my $ans;
+
+ return if (defined($opt{$config}));
+
+ if (defined($config_help{$config})) {
+ print "\n";
+ print $config_help{$config};
+ }
+
+ for (;;) {
+ print "$config = ";
+ if (defined($default{$config}) && length($default{$config})) {
+ print "\[$default{$config}\] ";
+ }
+ $ans = <STDIN>;
+ $ans =~ s/^\s*(.*\S)\s*$/$1/;
+ if ($ans =~ /^\s*$/) {
+ if ($default{$config}) {
+ $ans = $default{$config};
+ } else {
+ print "Your answer can not be blank\n";
+ next;
+ }
+ }
+ $entered_configs{$config} = ${ans};
+ last;
+ }
+}
+
+sub get_ktest_configs {
+ get_ktest_config("MACHINE");
+ get_ktest_config("BUILD_DIR");
+ get_ktest_config("OUTPUT_DIR");
+
+ if ($newconfig) {
+ get_ktest_config("BUILD_OPTIONS");
+ }
+
+ # options required for other than just building a kernel
+ if (!$buildonly) {
+ get_ktest_config("POWER_CYCLE");
+ get_ktest_config("CONSOLE");
+ }
+
+ # options required for install and more
+ if ($buildonly != 1) {
+ get_ktest_config("SSH_USER");
+ get_ktest_config("BUILD_TARGET");
+ get_ktest_config("TARGET_IMAGE");
+ }
+
+ get_ktest_config("LOCALVERSION");
+
+ return if ($buildonly);
+
+ my $rtype = $opt{"REBOOT_TYPE"};
+
+ if (!defined($rtype)) {
+ if (!defined($opt{"GRUB_MENU"})) {
+ get_ktest_config("REBOOT_TYPE");
+ $rtype = $entered_configs{"REBOOT_TYPE"};
+ } else {
+ $rtype = "grub";
+ }
+ }
+
+ if ($rtype eq "grub") {
+ get_ktest_config("GRUB_MENU");
+ }
+
+ if ($rtype eq "grub2") {
+ get_ktest_config("GRUB_MENU");
+ get_ktest_config("GRUB_FILE");
+ }
+
+ if ($rtype eq "syslinux") {
+ get_ktest_config("SYSLINUX_LABEL");
+ }
+}
+
+sub process_variables {
+ my ($value, $remove_undef) = @_;
+ my $retval = "";
+
+ # We want to check for '\', and it is just easier
+ # to check the previous characet of '$' and not need
+ # to worry if '$' is the first character. By adding
+ # a space to $value, we can just check [^\\]\$ and
+ # it will still work.
+ $value = " $value";
+
+ while ($value =~ /(.*?[^\\])\$\{(.*?)\}(.*)/) {
+ my $begin = $1;
+ my $var = $2;
+ my $end = $3;
+ # append beginning of value to retval
+ $retval = "$retval$begin";
+ if (defined($variable{$var})) {
+ $retval = "$retval$variable{$var}";
+ } elsif (defined($remove_undef) && $remove_undef) {
+ # for if statements, any variable that is not defined,
+ # we simple convert to 0
+ $retval = "${retval}0";
+ } else {
+ # put back the origin piece.
+ $retval = "$retval\$\{$var\}";
+ # This could be an option that is used later, save
+ # it so we don't warn if this option is not one of
+ # ktests options.
+ $used_options{$var} = 1;
+ }
+ $value = $end;
+ }
+ $retval = "$retval$value";
+
+ # remove the space added in the beginning
+ $retval =~ s/ //;
+
+ return "$retval"
+}
+
+sub set_value {
+ my ($lvalue, $rvalue, $override, $overrides, $name) = @_;
+
+ my $prvalue = process_variables($rvalue);
+
+ if ($buildonly && $lvalue =~ /^TEST_TYPE(\[.*\])?$/ && $prvalue ne "build") {
+ # Note if a test is something other than build, then we
+ # will need other manditory options.
+ if ($prvalue ne "install") {
+ # for bisect, we need to check BISECT_TYPE
+ if ($prvalue ne "bisect") {
+ $buildonly = 0;
+ }
+ } else {
+ # install still limits some manditory options.
+ $buildonly = 2;
+ }
+ }
+
+ if ($buildonly && $lvalue =~ /^BISECT_TYPE(\[.*\])?$/ && $prvalue ne "build") {
+ if ($prvalue ne "install") {
+ $buildonly = 0;
+ } else {
+ # install still limits some manditory options.
+ $buildonly = 2;
+ }
+ }
+
+ if (defined($opt{$lvalue})) {
+ if (!$override || defined(${$overrides}{$lvalue})) {
+ my $extra = "";
+ if ($override) {
+ $extra = "In the same override section!\n";
+ }
+ die "$name: $.: Option $lvalue defined more than once!\n$extra";
+ }
+ ${$overrides}{$lvalue} = $prvalue;
+ }
+ if ($rvalue =~ /^\s*$/) {
+ delete $opt{$lvalue};
+ } else {
+ $opt{$lvalue} = $prvalue;
+ }
+}
+
+sub set_eval {
+ my ($lvalue, $rvalue, $name) = @_;
+
+ my $prvalue = process_variables($rvalue);
+ my $arr;
+
+ if (defined($evals{$lvalue})) {
+ $arr = $evals{$lvalue};
+ } else {
+ $arr = [];
+ $evals{$lvalue} = $arr;
+ }
+
+ push @{$arr}, $rvalue;
+}
+
+sub set_variable {
+ my ($lvalue, $rvalue) = @_;
+
+ if ($rvalue =~ /^\s*$/) {
+ delete $variable{$lvalue};
+ } else {
+ $rvalue = process_variables($rvalue);
+ $variable{$lvalue} = $rvalue;
+ }
+}
+
+sub process_compare {
+ my ($lval, $cmp, $rval) = @_;
+
+ # remove whitespace
+
+ $lval =~ s/^\s*//;
+ $lval =~ s/\s*$//;
+
+ $rval =~ s/^\s*//;
+ $rval =~ s/\s*$//;
+
+ if ($cmp eq "==") {
+ return $lval eq $rval;
+ } elsif ($cmp eq "!=") {
+ return $lval ne $rval;
+ } elsif ($cmp eq "=~") {
+ return $lval =~ m/$rval/;
+ } elsif ($cmp eq "!~") {
+ return $lval !~ m/$rval/;
+ }
+
+ my $statement = "$lval $cmp $rval";
+ my $ret = eval $statement;
+
+ # $@ stores error of eval
+ if ($@) {
+ return -1;
+ }
+
+ return $ret;
+}
+
+sub value_defined {
+ my ($val) = @_;
+
+ return defined($variable{$2}) ||
+ defined($opt{$2});
+}
+
+my $d = 0;
+sub process_expression {
+ my ($name, $val) = @_;
+
+ my $c = $d++;
+
+ while ($val =~ s/\(([^\(]*?)\)/\&\&\&\&VAL\&\&\&\&/) {
+ my $express = $1;
+
+ if (process_expression($name, $express)) {
+ $val =~ s/\&\&\&\&VAL\&\&\&\&/ 1 /;
+ } else {
+ $val =~ s/\&\&\&\&VAL\&\&\&\&/ 0 /;
+ }
+ }
+
+ $d--;
+ my $OR = "\\|\\|";
+ my $AND = "\\&\\&";
+
+ while ($val =~ s/^(.*?)($OR|$AND)//) {
+ my $express = $1;
+ my $op = $2;
+
+ if (process_expression($name, $express)) {
+ if ($op eq "||") {
+ return 1;
+ }
+ } else {
+ if ($op eq "&&") {
+ return 0;
+ }
+ }
+ }
+
+ if ($val =~ /(.*)(==|\!=|>=|<=|>|<|=~|\!~)(.*)/) {
+ my $ret = process_compare($1, $2, $3);
+ if ($ret < 0) {
+ die "$name: $.: Unable to process comparison\n";
+ }
+ return $ret;
+ }
+
+ if ($val =~ /^\s*(NOT\s*)?DEFINED\s+(\S+)\s*$/) {
+ if (defined $1) {
+ return !value_defined($2);
+ } else {
+ return value_defined($2);
+ }
+ }
+
+ if ($val =~ /^\s*0\s*$/) {
+ return 0;
+ } elsif ($val =~ /^\s*\d+\s*$/) {
+ return 1;
+ }
+
+ die ("$name: $.: Undefined content $val in if statement\n");
+}
+
+sub process_if {
+ my ($name, $value) = @_;
+
+ # Convert variables and replace undefined ones with 0
+ my $val = process_variables($value, 1);
+ my $ret = process_expression $name, $val;
+
+ return $ret;
+}
+
+sub __read_config {
+ my ($config, $current_test_num) = @_;
+
+ my $in;
+ open($in, $config) || die "can't read file $config";
+
+ my $name = $config;
+ $name =~ s,.*/(.*),$1,;
+
+ my $test_num = $$current_test_num;
+ my $default = 1;
+ my $repeat = 1;
+ my $num_tests_set = 0;
+ my $skip = 0;
+ my $rest;
+ my $line;
+ my $test_case = 0;
+ my $if = 0;
+ my $if_set = 0;
+ my $override = 0;
+
+ my %overrides;
+
+ while (<$in>) {
+
+ # ignore blank lines and comments
+ next if (/^\s*$/ || /\s*\#/);
+
+ if (/^\s*(TEST_START|DEFAULTS)\b(.*)/) {
+
+ my $type = $1;
+ $rest = $2;
+ $line = $2;
+
+ my $old_test_num;
+ my $old_repeat;
+ $override = 0;
+
+ if ($type eq "TEST_START") {
+
+ if ($num_tests_set) {
+ die "$name: $.: Can not specify both NUM_TESTS and TEST_START\n";
+ }
+
+ $old_test_num = $test_num;
+ $old_repeat = $repeat;
+
+ $test_num += $repeat;
+ $default = 0;
+ $repeat = 1;
+ } else {
+ $default = 1;
+ }
+
+ # If SKIP is anywhere in the line, the command will be skipped
+ if ($rest =~ s/\s+SKIP\b//) {
+ $skip = 1;
+ } else {
+ $test_case = 1;
+ $skip = 0;
+ }
+
+ if ($rest =~ s/\sELSE\b//) {
+ if (!$if) {
+ die "$name: $.: ELSE found with out matching IF section\n$_";
+ }
+ $if = 0;
+
+ if ($if_set) {
+ $skip = 1;
+ } else {
+ $skip = 0;
+ }
+ }
+
+ if ($rest =~ s/\sIF\s+(.*)//) {
+ if (process_if($name, $1)) {
+ $if_set = 1;
+ } else {
+ $skip = 1;
+ }
+ $if = 1;
+ } else {
+ $if = 0;
+ $if_set = 0;
+ }
+
+ if (!$skip) {
+ if ($type eq "TEST_START") {
+ if ($rest =~ s/\s+ITERATE\s+(\d+)//) {
+ $repeat = $1;
+ $repeat_tests{"$test_num"} = $repeat;
+ }
+ } elsif ($rest =~ s/\sOVERRIDE\b//) {
+ # DEFAULT only
+ $override = 1;
+ # Clear previous overrides
+ %overrides = ();
+ }
+ }
+
+ if (!$skip && $rest !~ /^\s*$/) {
+ die "$name: $.: Gargbage found after $type\n$_";
+ }
+
+ if ($skip && $type eq "TEST_START") {
+ $test_num = $old_test_num;
+ $repeat = $old_repeat;
+ }
+
+ } elsif (/^\s*ELSE\b(.*)$/) {
+ if (!$if) {
+ die "$name: $.: ELSE found with out matching IF section\n$_";
+ }
+ $rest = $1;
+ if ($if_set) {
+ $skip = 1;
+ $rest = "";
+ } else {
+ $skip = 0;
+
+ if ($rest =~ /\sIF\s+(.*)/) {
+ # May be a ELSE IF section.
+ if (process_if($name, $1)) {
+ $if_set = 1;
+ } else {
+ $skip = 1;
+ }
+ $rest = "";
+ } else {
+ $if = 0;
+ }
+ }
+
+ if ($rest !~ /^\s*$/) {
+ die "$name: $.: Gargbage found after DEFAULTS\n$_";
+ }
+
+ } elsif (/^\s*INCLUDE\s+(\S+)/) {
+
+ next if ($skip);
+
+ if (!$default) {
+ die "$name: $.: INCLUDE can only be done in default sections\n$_";
+ }
+
+ my $file = process_variables($1);
+
+ if ($file !~ m,^/,) {
+ # check the path of the config file first
+ if ($config =~ m,(.*)/,) {
+ if (-f "$1/$file") {
+ $file = "$1/$file";
+ }
+ }
+ }
+
+ if ( ! -r $file ) {
+ die "$name: $.: Can't read file $file\n$_";
+ }
+
+ if (__read_config($file, \$test_num)) {
+ $test_case = 1;
+ }
+
+ } elsif (/^\s*([A-Z_\[\]\d]+)\s*=~\s*(.*?)\s*$/) {
+
+ next if ($skip);
+
+ my $lvalue = $1;
+ my $rvalue = $2;
+
+ if ($default || $lvalue =~ /\[\d+\]$/) {
+ set_eval($lvalue, $rvalue, $name);
+ } else {
+ my $val = "$lvalue\[$test_num\]";
+ set_eval($val, $rvalue, $name);
+ }
+
+ } elsif (/^\s*([A-Z_\[\]\d]+)\s*=\s*(.*?)\s*$/) {
+
+ next if ($skip);
+
+ my $lvalue = $1;
+ my $rvalue = $2;
+
+ if (!$default &&
+ ($lvalue eq "NUM_TESTS" ||
+ $lvalue eq "LOG_FILE" ||
+ $lvalue eq "CLEAR_LOG")) {
+ die "$name: $.: $lvalue must be set in DEFAULTS section\n";
+ }
+
+ if ($lvalue eq "NUM_TESTS") {
+ if ($test_num) {
+ die "$name: $.: Can not specify both NUM_TESTS and TEST_START\n";
+ }
+ if (!$default) {
+ die "$name: $.: NUM_TESTS must be set in default section\n";
+ }
+ $num_tests_set = 1;
+ }
+
+ if ($default || $lvalue =~ /\[\d+\]$/) {
+ set_value($lvalue, $rvalue, $override, \%overrides, $name);
+ } else {
+ my $val = "$lvalue\[$test_num\]";
+ set_value($val, $rvalue, $override, \%overrides, $name);
+
+ if ($repeat > 1) {
+ $repeats{$val} = $repeat;
+ }
+ }
+ } elsif (/^\s*([A-Z_\[\]\d]+)\s*:=\s*(.*?)\s*$/) {
+ next if ($skip);
+
+ my $lvalue = $1;
+ my $rvalue = $2;
+
+ # process config variables.
+ # Config variables are only active while reading the
+ # config and can be defined anywhere. They also ignore
+ # TEST_START and DEFAULTS, but are skipped if they are in
+ # on of these sections that have SKIP defined.
+ # The save variable can be
+ # defined multiple times and the new one simply overrides
+ # the prevous one.
+ set_variable($lvalue, $rvalue);
+
+ } else {
+ die "$name: $.: Garbage found in config\n$_";
+ }
+ }
+
+ if ($test_num) {
+ $test_num += $repeat - 1;
+ $opt{"NUM_TESTS"} = $test_num;
+ }
+
+ close($in);
+
+ $$current_test_num = $test_num;
+
+ return $test_case;
+}
+
+sub get_test_case {
+ print "What test case would you like to run?\n";
+ print " (build, install or boot)\n";
+ print " Other tests are available but require editing the config file\n";
+ my $ans = <STDIN>;
+ chomp $ans;
+ $default{"TEST_TYPE"} = $ans;
+}
+
+sub read_config {
+ my ($config) = @_;
+
+ my $test_case;
+ my $test_num = 0;
+
+ $test_case = __read_config $config, \$test_num;
+
+ # make sure we have all mandatory configs
+ get_ktest_configs;
+
+ # was a test specified?
+ if (!$test_case) {
+ print "No test case specified.\n";
+ get_test_case;
+ }
+
+ # set any defaults
+
+ foreach my $default (keys %default) {
+ if (!defined($opt{$default})) {
+ $opt{$default} = $default{$default};
+ }
+ }
+
+ if ($opt{"IGNORE_UNUSED"} == 1) {
+ return;
+ }
+
+ my %not_used;
+
+ # check if there are any stragglers (typos?)
+ foreach my $option (keys %opt) {
+ my $op = $option;
+ # remove per test labels.
+ $op =~ s/\[.*\]//;
+ if (!exists($option_map{$op}) &&
+ !exists($default{$op}) &&
+ !exists($used_options{$op})) {
+ $not_used{$op} = 1;
+ }
+ }
+
+ if (%not_used) {
+ my $s = "s are";
+ $s = " is" if (keys %not_used == 1);
+ print "The following option$s not used; could be a typo:\n";
+ foreach my $option (keys %not_used) {
+ print "$option\n";
+ }
+ print "Set IGRNORE_UNUSED = 1 to have ktest ignore unused variables\n";
+ if (!read_yn "Do you want to continue?") {
+ exit -1;
+ }
+ }
+}
+
+sub __eval_option {
+ my ($name, $option, $i) = @_;
+
+ # Add space to evaluate the character before $
+ $option = " $option";
+ my $retval = "";
+ my $repeated = 0;
+ my $parent = 0;
+
+ foreach my $test (keys %repeat_tests) {
+ if ($i >= $test &&
+ $i < $test + $repeat_tests{$test}) {
+
+ $repeated = 1;
+ $parent = $test;
+ last;
+ }
+ }
+
+ while ($option =~ /(.*?[^\\])\$\{(.*?)\}(.*)/) {
+ my $start = $1;
+ my $var = $2;
+ my $end = $3;
+
+ # Append beginning of line
+ $retval = "$retval$start";
+
+ # If the iteration option OPT[$i] exists, then use that.
+ # otherwise see if the default OPT (without [$i]) exists.
+
+ my $o = "$var\[$i\]";
+ my $parento = "$var\[$parent\]";
+
+ # If a variable contains itself, use the default var
+ if (($var eq $name) && defined($opt{$var})) {
+ $o = $opt{$var};
+ $retval = "$retval$o";
+ } elsif (defined($opt{$o})) {
+ $o = $opt{$o};
+ $retval = "$retval$o";
+ } elsif ($repeated && defined($opt{$parento})) {
+ $o = $opt{$parento};
+ $retval = "$retval$o";
+ } elsif (defined($opt{$var})) {
+ $o = $opt{$var};
+ $retval = "$retval$o";
+ } elsif ($var eq "KERNEL_VERSION" && defined($make)) {
+ # special option KERNEL_VERSION uses kernel version
+ get_version();
+ $retval = "$retval$version";
+ } else {
+ $retval = "$retval\$\{$var\}";
+ }
+
+ $option = $end;
+ }
+
+ $retval = "$retval$option";
+
+ $retval =~ s/^ //;
+
+ return $retval;
+}
+
+sub process_evals {
+ my ($name, $option, $i) = @_;
+
+ my $option_name = "$name\[$i\]";
+ my $ev;
+
+ my $old_option = $option;
+
+ if (defined($evals{$option_name})) {
+ $ev = $evals{$option_name};
+ } elsif (defined($evals{$name})) {
+ $ev = $evals{$name};
+ } else {
+ return $option;
+ }
+
+ for my $e (@{$ev}) {
+ eval "\$option =~ $e";
+ }
+
+ if ($option ne $old_option) {
+ doprint("$name changed from '$old_option' to '$option'\n");
+ }
+
+ return $option;
+}
+
+sub eval_option {
+ my ($name, $option, $i) = @_;
+
+ my $prev = "";
+
+ # Since an option can evaluate to another option,
+ # keep iterating until we do not evaluate any more
+ # options.
+ my $r = 0;
+ while ($prev ne $option) {
+ # Check for recursive evaluations.
+ # 100 deep should be more than enough.
+ if ($r++ > 100) {
+ die "Over 100 evaluations accurred with $option\n" .
+ "Check for recursive variables\n";
+ }
+ $prev = $option;
+ $option = __eval_option($name, $option, $i);
+ }
+
+ $option = process_evals($name, $option, $i);
+
+ return $option;
+}
+
+sub run_command;
+sub start_monitor;
+sub end_monitor;
+sub wait_for_monitor;
+
+sub reboot {
+ my ($time) = @_;
+
+ # Make sure everything has been written to disk
+ run_ssh("sync");
+
+ if (defined($time)) {
+ start_monitor;
+ # flush out current monitor
+ # May contain the reboot success line
+ wait_for_monitor 1;
+ }
+
+ # try to reboot normally
+ if (run_command $reboot) {
+ if (defined($powercycle_after_reboot)) {
+ sleep $powercycle_after_reboot;
+ run_command "$power_cycle";
+ }
+ } else {
+ # nope? power cycle it.
+ run_command "$power_cycle";
+ }
+
+ if (defined($time)) {
+
+ # We only want to get to the new kernel, don't fail
+ # if we stumble over a call trace.
+ my $save_ignore_errors = $ignore_errors;
+ $ignore_errors = 1;
+
+ # Look for the good kernel to boot
+ if (wait_for_monitor($time, "Linux version")) {
+ # reboot got stuck?
+ doprint "Reboot did not finish. Forcing power cycle\n";
+ run_command "$power_cycle";
+ }
+
+ $ignore_errors = $save_ignore_errors;
+
+ # Still need to wait for the reboot to finish
+ wait_for_monitor($time, $reboot_success_line);
+
+ end_monitor;
+ }
+}
+
+sub reboot_to_good {
+ my ($time) = @_;
+
+ if (defined($switch_to_good)) {
+ run_command $switch_to_good;
+ }
+
+ reboot $time;
+}
+
+sub do_not_reboot {
+ my $i = $iteration;
+
+ return $test_type eq "build" || $no_reboot ||
+ ($test_type eq "patchcheck" && $opt{"PATCHCHECK_TYPE[$i]"} eq "build") ||
+ ($test_type eq "bisect" && $opt{"BISECT_TYPE[$i]"} eq "build");
+}
+
+sub dodie {
+ doprint "CRITICAL FAILURE... ", @_, "\n";
+
+ my $i = $iteration;
+
+ if ($reboot_on_error && !do_not_reboot) {
+
+ doprint "REBOOTING\n";
+ reboot_to_good;
+
+ } elsif ($poweroff_on_error && defined($power_off)) {
+ doprint "POWERING OFF\n";
+ `$power_off`;
+ }
+
+ if (defined($opt{"LOG_FILE"})) {
+ print " See $opt{LOG_FILE} for more info.\n";
+ }
+
+ die @_, "\n";
+}
+
+sub open_console {
+ my ($fp) = @_;
+
+ my $flags;
+
+ my $pid = open($fp, "$console|") or
+ dodie "Can't open console $console";
+
+ $flags = fcntl($fp, F_GETFL, 0) or
+ dodie "Can't get flags for the socket: $!";
+ $flags = fcntl($fp, F_SETFL, $flags | O_NONBLOCK) or
+ dodie "Can't set flags for the socket: $!";
+
+ return $pid;
+}
+
+sub close_console {
+ my ($fp, $pid) = @_;
+
+ doprint "kill child process $pid\n";
+ kill $close_console_signal, $pid;
+
+ print "closing!\n";
+ close($fp);
+}
+
+sub start_monitor {
+ if ($monitor_cnt++) {
+ return;
+ }
+ $monitor_fp = \*MONFD;
+ $monitor_pid = open_console $monitor_fp;
+
+ return;
+
+ open(MONFD, "Stop perl from warning about single use of MONFD");
+}
+
+sub end_monitor {
+ return if (!defined $console);
+ if (--$monitor_cnt) {
+ return;
+ }
+ close_console($monitor_fp, $monitor_pid);
+}
+
+sub wait_for_monitor {
+ my ($time, $stop) = @_;
+ my $full_line = "";
+ my $line;
+ my $booted = 0;
+ my $start_time = time;
+ my $skip_call_trace = 0;
+ my $bug = 0;
+ my $bug_ignored = 0;
+ my $now;
+
+ doprint "** Wait for monitor to settle down **\n";
+
+ # read the monitor and wait for the system to calm down
+ while (!$booted) {
+ $line = wait_for_input($monitor_fp, $time);
+ last if (!defined($line));
+ print "$line";
+ $full_line .= $line;
+
+ if (defined($stop) && $full_line =~ /$stop/) {
+ doprint "wait for monitor detected $stop\n";
+ $booted = 1;
+ }
+
+ if ($full_line =~ /\[ backtrace testing \]/) {
+ $skip_call_trace = 1;
+ }
+
+ if ($full_line =~ /call trace:/i) {
+ if (!$bug && !$skip_call_trace) {
+ if ($ignore_errors) {
+ $bug_ignored = 1;
+ } else {
+ $bug = 1;
+ }
+ }
+ }
+
+ if ($full_line =~ /\[ end of backtrace testing \]/) {
+ $skip_call_trace = 0;
+ }
+
+ if ($full_line =~ /Kernel panic -/) {
+ $bug = 1;
+ }
+
+ if ($line =~ /\n/) {
+ $full_line = "";
+ }
+ $now = time;
+ if ($now - $start_time >= $max_monitor_wait) {
+ doprint "Exiting monitor flush due to hitting MAX_MONITOR_WAIT\n";
+ return 1;
+ }
+ }
+ print "** Monitor flushed **\n";
+ return $bug;
+}
+
+sub save_logs {
+ my ($result, $basedir) = @_;
+ my @t = localtime;
+ my $date = sprintf "%04d%02d%02d%02d%02d%02d",
+ 1900+$t[5],$t[4],$t[3],$t[2],$t[1],$t[0];
+
+ my $type = $build_type;
+ if ($type =~ /useconfig/) {
+ $type = "useconfig";
+ }
+
+ my $dir = "$machine-$test_type-$type-$result-$date";
+
+ $dir = "$basedir/$dir";
+
+ if (!-d $dir) {
+ mkpath($dir) or
+ die "can't create $dir";
+ }
+
+ my %files = (
+ "config" => $output_config,
+ "buildlog" => $buildlog,
+ "dmesg" => $dmesg,
+ "testlog" => $testlog,
+ );
+
+ while (my ($name, $source) = each(%files)) {
+ if (-f "$source") {
+ cp "$source", "$dir/$name" or
+ die "failed to copy $source";
+ }
+ }
+
+ doprint "*** Saved info to $dir ***\n";
+}
+
+sub fail {
+
+ if (defined($post_test)) {
+ run_command $post_test;
+ }
+
+ if ($die_on_failure) {
+ dodie @_;
+ }
+
+ doprint "FAILED\n";
+
+ my $i = $iteration;
+
+ # no need to reboot for just building.
+ if (!do_not_reboot) {
+ doprint "REBOOTING\n";
+ reboot_to_good $sleep_time;
+ }
+
+ my $name = "";
+
+ if (defined($test_name)) {
+ $name = " ($test_name)";
+ }
+
+ doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
+ doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
+ doprint "KTEST RESULT: TEST $i$name Failed: ", @_, "\n";
+ doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
+ doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n";
+
+ if (defined($store_failures)) {
+ save_logs "fail", $store_failures;
+ }
+
+ return 1;
+}
+
+sub run_command {
+ my ($command) = @_;
+ my $dolog = 0;
+ my $dord = 0;
+ my $pid;
+
+ $command =~ s/\$SSH_USER/$ssh_user/g;
+ $command =~ s/\$MACHINE/$machine/g;
+
+ doprint("$command ... ");
+
+ $pid = open(CMD, "$command 2>&1 |") or
+ (fail "unable to exec $command" and return 0);
+
+ if (defined($opt{"LOG_FILE"})) {
+ open(LOG, ">>$opt{LOG_FILE}") or
+ dodie "failed to write to log";
+ $dolog = 1;
+ }
+
+ if (defined($redirect)) {
+ open (RD, ">$redirect") or
+ dodie "failed to write to redirect $redirect";
+ $dord = 1;
+ }
+
+ while (<CMD>) {
+ print LOG if ($dolog);
+ print RD if ($dord);
+ }
+
+ waitpid($pid, 0);
+ my $failed = $?;
+
+ close(CMD);
+ close(LOG) if ($dolog);
+ close(RD) if ($dord);
+
+ if ($failed) {
+ doprint "FAILED!\n";
+ } else {
+ doprint "SUCCESS\n";
+ }
+
+ return !$failed;
+}
+
+sub run_ssh {
+ my ($cmd) = @_;
+ my $cp_exec = $ssh_exec;
+
+ $cp_exec =~ s/\$SSH_COMMAND/$cmd/g;
+ return run_command "$cp_exec";
+}
+
+sub run_scp {
+ my ($src, $dst, $cp_scp) = @_;
+
+ $cp_scp =~ s/\$SRC_FILE/$src/g;
+ $cp_scp =~ s/\$DST_FILE/$dst/g;
+
+ return run_command "$cp_scp";
+}
+
+sub run_scp_install {
+ my ($src, $dst) = @_;
+
+ my $cp_scp = $scp_to_target_install;
+
+ return run_scp($src, $dst, $cp_scp);
+}
+
+sub run_scp_mod {
+ my ($src, $dst) = @_;
+
+ my $cp_scp = $scp_to_target;
+
+ return run_scp($src, $dst, $cp_scp);
+}
+
+sub get_grub2_index {
+
+ return if (defined($grub_number) && defined($last_grub_menu) &&
+ $last_grub_menu eq $grub_menu && defined($last_machine) &&
+ $last_machine eq $machine);
+
+ doprint "Find grub2 menu ... ";
+ $grub_number = -1;
+
+ my $ssh_grub = $ssh_exec;
+ $ssh_grub =~ s,\$SSH_COMMAND,cat $grub_file,g;
+
+ open(IN, "$ssh_grub |")
+ or die "unable to get $grub_file";
+
+ my $found = 0;
+
+ while (<IN>) {
+ if (/^menuentry.*$grub_menu/) {
+ $grub_number++;
+ $found = 1;
+ last;
+ } elsif (/^menuentry\s/) {
+ $grub_number++;
+ }
+ }
+ close(IN);
+
+ die "Could not find '$grub_menu' in $grub_file on $machine"
+ if (!$found);
+ doprint "$grub_number\n";
+ $last_grub_menu = $grub_menu;
+ $last_machine = $machine;
+}
+
+sub get_grub_index {
+
+ if ($reboot_type eq "grub2") {
+ get_grub2_index;
+ return;
+ }
+
+ if ($reboot_type ne "grub") {
+ return;
+ }
+ return if (defined($grub_number) && defined($last_grub_menu) &&
+ $last_grub_menu eq $grub_menu && defined($last_machine) &&
+ $last_machine eq $machine);
+
+ doprint "Find grub menu ... ";
+ $grub_number = -1;
+
+ my $ssh_grub = $ssh_exec;
+ $ssh_grub =~ s,\$SSH_COMMAND,cat /boot/grub/menu.lst,g;
+
+ open(IN, "$ssh_grub |")
+ or die "unable to get menu.lst";
+
+ my $found = 0;
+
+ while (<IN>) {
+ if (/^\s*title\s+$grub_menu\s*$/) {
+ $grub_number++;
+ $found = 1;
+ last;
+ } elsif (/^\s*title\s/) {
+ $grub_number++;
+ }
+ }
+ close(IN);
+
+ die "Could not find '$grub_menu' in /boot/grub/menu on $machine"
+ if (!$found);
+ doprint "$grub_number\n";
+ $last_grub_menu = $grub_menu;
+ $last_machine = $machine;
+}
+
+sub wait_for_input
+{
+ my ($fp, $time) = @_;
+ my $rin;
+ my $ready;
+ my $line;
+ my $ch;
+
+ if (!defined($time)) {
+ $time = $timeout;
+ }
+
+ $rin = '';
+ vec($rin, fileno($fp), 1) = 1;
+ ($ready, $time) = select($rin, undef, undef, $time);
+
+ $line = "";
+
+ # try to read one char at a time
+ while (sysread $fp, $ch, 1) {
+ $line .= $ch;
+ last if ($ch eq "\n");
+ }
+
+ if (!length($line)) {
+ return undef;
+ }
+
+ return $line;
+}
+
+sub reboot_to {
+ if (defined($switch_to_test)) {
+ run_command $switch_to_test;
+ }
+
+ if ($reboot_type eq "grub") {
+ run_ssh "'(echo \"savedefault --default=$grub_number --once\" | grub --batch)'";
+ } elsif ($reboot_type eq "grub2") {
+ run_ssh "$grub_reboot $grub_number";
+ } elsif ($reboot_type eq "syslinux") {
+ run_ssh "$syslinux --once \\\"$syslinux_label\\\" $syslinux_path";
+ } elsif (defined $reboot_script) {
+ run_command "$reboot_script";
+ }
+ reboot;
+}
+
+sub get_sha1 {
+ my ($commit) = @_;
+
+ doprint "git rev-list --max-count=1 $commit ... ";
+ my $sha1 = `git rev-list --max-count=1 $commit`;
+ my $ret = $?;
+
+ logit $sha1;
+
+ if ($ret) {
+ doprint "FAILED\n";
+ dodie "Failed to get git $commit";
+ }
+
+ print "SUCCESS\n";
+
+ chomp $sha1;
+
+ return $sha1;
+}
+
+sub monitor {
+ my $booted = 0;
+ my $bug = 0;
+ my $bug_ignored = 0;
+ my $skip_call_trace = 0;
+ my $loops;
+
+ wait_for_monitor 5;
+
+ my $line;
+ my $full_line = "";
+
+ open(DMESG, "> $dmesg") or
+ die "unable to write to $dmesg";
+
+ reboot_to;
+
+ my $success_start;
+ my $failure_start;
+ my $monitor_start = time;
+ my $done = 0;
+ my $version_found = 0;
+
+ while (!$done) {
+
+ if ($bug && defined($stop_after_failure) &&
+ $stop_after_failure >= 0) {
+ my $time = $stop_after_failure - (time - $failure_start);
+ $line = wait_for_input($monitor_fp, $time);
+ if (!defined($line)) {
+ doprint "bug timed out after $booted_timeout seconds\n";
+ doprint "Test forced to stop after $stop_after_failure seconds after failure\n";
+ last;
+ }
+ } elsif ($booted) {
+ $line = wait_for_input($monitor_fp, $booted_timeout);
+ if (!defined($line)) {
+ my $s = $booted_timeout == 1 ? "" : "s";
+ doprint "Successful boot found: break after $booted_timeout second$s\n";
+ last;
+ }
+ } else {
+ $line = wait_for_input($monitor_fp);
+ if (!defined($line)) {
+ my $s = $timeout == 1 ? "" : "s";
+ doprint "Timed out after $timeout second$s\n";
+ last;
+ }
+ }
+
+ doprint $line;
+ print DMESG $line;
+
+ # we are not guaranteed to get a full line
+ $full_line .= $line;
+
+ if ($full_line =~ /$success_line/) {
+ $booted = 1;
+ $success_start = time;
+ }
+
+ if ($booted && defined($stop_after_success) &&
+ $stop_after_success >= 0) {
+ my $now = time;
+ if ($now - $success_start >= $stop_after_success) {
+ doprint "Test forced to stop after $stop_after_success seconds after success\n";
+ last;
+ }
+ }
+
+ if ($full_line =~ /\[ backtrace testing \]/) {
+ $skip_call_trace = 1;
+ }
+
+ if ($full_line =~ /call trace:/i) {
+ if (!$bug && !$skip_call_trace) {
+ if ($ignore_errors) {
+ $bug_ignored = 1;
+ } else {
+ $bug = 1;
+ $failure_start = time;
+ }
+ }
+ }
+
+ if ($bug && defined($stop_after_failure) &&
+ $stop_after_failure >= 0) {
+ my $now = time;
+ if ($now - $failure_start >= $stop_after_failure) {
+ doprint "Test forced to stop after $stop_after_failure seconds after failure\n";
+ last;
+ }
+ }
+
+ if ($full_line =~ /\[ end of backtrace testing \]/) {
+ $skip_call_trace = 0;
+ }
+
+ if ($full_line =~ /Kernel panic -/) {
+ $failure_start = time;
+ $bug = 1;
+ }
+
+ # Detect triple faults by testing the banner
+ if ($full_line =~ /\bLinux version (\S+).*\n/) {
+ if ($1 eq $version) {
+ $version_found = 1;
+ } elsif ($version_found && $detect_triplefault) {
+ # We already booted into the kernel we are testing,
+ # but now we booted into another kernel?
+ # Consider this a triple fault.
+ doprint "Already booted in Linux kernel $version, but now\n";
+ doprint "we booted into Linux kernel $1.\n";
+ doprint "Assuming that this is a triple fault.\n";
+ doprint "To disable this: set DETECT_TRIPLE_FAULT to 0\n";
+ last;
+ }
+ }
+
+ if ($line =~ /\n/) {
+ $full_line = "";
+ }
+
+ if ($stop_test_after > 0 && !$booted && !$bug) {
+ if (time - $monitor_start > $stop_test_after) {
+ doprint "STOP_TEST_AFTER ($stop_test_after seconds) timed out\n";
+ $done = 1;
+ }
+ }
+ }
+
+ close(DMESG);
+
+ if ($bug) {
+ return 0 if ($in_bisect);
+ fail "failed - got a bug report" and return 0;
+ }
+
+ if (!$booted) {
+ return 0 if ($in_bisect);
+ fail "failed - never got a boot prompt." and return 0;
+ }
+
+ if ($bug_ignored) {
+ doprint "WARNING: Call Trace detected but ignored due to IGNORE_ERRORS=1\n";
+ }
+
+ return 1;
+}
+
+sub eval_kernel_version {
+ my ($option) = @_;
+
+ $option =~ s/\$KERNEL_VERSION/$version/g;
+
+ return $option;
+}
+
+sub do_post_install {
+
+ return if (!defined($post_install));
+
+ my $cp_post_install = eval_kernel_version $post_install;
+ run_command "$cp_post_install" or
+ dodie "Failed to run post install";
+}
+
+# Sometimes the reboot fails, and will hang. We try to ssh to the box
+# and if we fail, we force another reboot, that should powercycle it.
+sub test_booted {
+ if (!run_ssh "echo testing connection") {
+ reboot $sleep_time;
+ }
+}
+
+sub install {
+
+ return if ($no_install);
+
+ if (defined($pre_install)) {
+ my $cp_pre_install = eval_kernel_version $pre_install;
+ run_command "$cp_pre_install" or
+ dodie "Failed to run pre install";
+ }
+
+ my $cp_target = eval_kernel_version $target_image;
+
+ test_booted;
+
+ run_scp_install "$outputdir/$build_target", "$cp_target" or
+ dodie "failed to copy image";
+
+ my $install_mods = 0;
+
+ # should we process modules?
+ $install_mods = 0;
+ open(IN, "$output_config") or dodie("Can't read config file");
+ while (<IN>) {
+ if (/CONFIG_MODULES(=y)?/) {
+ if (defined($1)) {
+ $install_mods = 1;
+ last;
+ }
+ }
+ }
+ close(IN);
+
+ if (!$install_mods) {
+ do_post_install;
+ doprint "No modules needed\n";
+ return;
+ }
+
+ run_command "$make INSTALL_MOD_STRIP=1 INSTALL_MOD_PATH=$tmpdir modules_install" or
+ dodie "Failed to install modules";
+
+ my $modlib = "/lib/modules/$version";
+ my $modtar = "ktest-mods.tar.bz2";
+
+ run_ssh "rm -rf $modlib" or
+ dodie "failed to remove old mods: $modlib";
+
+ # would be nice if scp -r did not follow symbolic links
+ run_command "cd $tmpdir && tar -cjf $modtar lib/modules/$version" or
+ dodie "making tarball";
+
+ run_scp_mod "$tmpdir/$modtar", "/tmp" or
+ dodie "failed to copy modules";
+
+ unlink "$tmpdir/$modtar";
+
+ run_ssh "'(cd / && tar xjf /tmp/$modtar)'" or
+ dodie "failed to tar modules";
+
+ run_ssh "rm -f /tmp/$modtar";
+
+ do_post_install;
+}
+
+sub get_version {
+ # get the release name
+ return if ($have_version);
+ doprint "$make kernelrelease ... ";
+ $version = `$make kernelrelease | tail -1`;
+ chomp($version);
+ doprint "$version\n";
+ $have_version = 1;
+}
+
+sub start_monitor_and_boot {
+ # Make sure the stable kernel has finished booting
+
+ # Install bisects, don't need console
+ if (defined $console) {
+ start_monitor;
+ wait_for_monitor 5;
+ end_monitor;
+ }
+
+ get_grub_index;
+ get_version;
+ install;
+
+ start_monitor if (defined $console);
+ return monitor;
+}
+
+my $check_build_re = ".*:.*(warning|error|Error):.*";
+my $utf8_quote = "\\x{e2}\\x{80}(\\x{98}|\\x{99})";
+
+sub process_warning_line {
+ my ($line) = @_;
+
+ chomp $line;
+
+ # for distcc heterogeneous systems, some compilers
+ # do things differently causing warning lines
+ # to be slightly different. This makes an attempt
+ # to fixe those issues.
+
+ # chop off the index into the line
+ # using distcc, some compilers give different indexes
+ # depending on white space
+ $line =~ s/^(\s*\S+:\d+:)\d+/$1/;
+
+ # Some compilers use UTF-8 extended for quotes and some don't.
+ $line =~ s/$utf8_quote/'/g;
+
+ return $line;
+}
+
+# Read buildlog and check against warnings file for any
+# new warnings.
+#
+# Returns 1 if OK
+# 0 otherwise
+sub check_buildlog {
+ return 1 if (!defined $warnings_file);
+
+ my %warnings_list;
+
+ # Failed builds should not reboot the target
+ my $save_no_reboot = $no_reboot;
+ $no_reboot = 1;
+
+ if (-f $warnings_file) {
+ open(IN, $warnings_file) or
+ dodie "Error opening $warnings_file";
+
+ while (<IN>) {
+ if (/$check_build_re/) {
+ my $warning = process_warning_line $_;
+
+ $warnings_list{$warning} = 1;
+ }
+ }
+ close(IN);
+ }
+
+ # If warnings file didn't exist, and WARNINGS_FILE exist,
+ # then we fail on any warning!
+
+ open(IN, $buildlog) or dodie "Can't open $buildlog";
+ while (<IN>) {
+ if (/$check_build_re/) {
+ my $warning = process_warning_line $_;
+
+ if (!defined $warnings_list{$warning}) {
+ fail "New warning found (not in $warnings_file)\n$_\n";
+ $no_reboot = $save_no_reboot;
+ return 0;
+ }
+ }
+ }
+ $no_reboot = $save_no_reboot;
+ close(IN);
+}
+
+sub check_patch_buildlog {
+ my ($patch) = @_;
+
+ my @files = `git show $patch | diffstat -l`;
+
+ foreach my $file (@files) {
+ chomp $file;
+ }
+
+ open(IN, "git show $patch |") or
+ dodie "failed to show $patch";
+ while (<IN>) {
+ if (m,^--- a/(.*),) {
+ chomp $1;
+ $files[$#files] = $1;
+ }
+ }
+ close(IN);
+
+ open(IN, $buildlog) or dodie "Can't open $buildlog";
+ while (<IN>) {
+ if (/^\s*(.*?):.*(warning|error)/) {
+ my $err = $1;
+ foreach my $file (@files) {
+ my $fullpath = "$builddir/$file";
+ if ($file eq $err || $fullpath eq $err) {
+ fail "$file built with warnings" and return 0;
+ }
+ }
+ }
+ }
+ close(IN);
+
+ return 1;
+}
+
+sub apply_min_config {
+ my $outconfig = "$output_config.new";
+
+ # Read the config file and remove anything that
+ # is in the force_config hash (from minconfig and others)
+ # then add the force config back.
+
+ doprint "Applying minimum configurations into $output_config.new\n";
+
+ open (OUT, ">$outconfig") or
+ dodie "Can't create $outconfig";
+
+ if (-f $output_config) {
+ open (IN, $output_config) or
+ dodie "Failed to open $output_config";
+ while (<IN>) {
+ if (/^(# )?(CONFIG_[^\s=]*)/) {
+ next if (defined($force_config{$2}));
+ }
+ print OUT;
+ }
+ close IN;
+ }
+ foreach my $config (keys %force_config) {
+ print OUT "$force_config{$config}\n";
+ }
+ close OUT;
+
+ run_command "mv $outconfig $output_config";
+}
+
+sub make_oldconfig {
+
+ my @force_list = keys %force_config;
+
+ if ($#force_list >= 0) {
+ apply_min_config;
+ }
+
+ if (!run_command "$make olddefconfig") {
+ # Perhaps olddefconfig doesn't exist in this version of the kernel
+ # try oldnoconfig
+ doprint "olddefconfig failed, trying make oldnoconfig\n";
+ if (!run_command "$make oldnoconfig") {
+ doprint "oldnoconfig failed, trying yes '' | make oldconfig\n";
+ # try a yes '' | oldconfig
+ run_command "yes '' | $make oldconfig" or
+ dodie "failed make config oldconfig";
+ }
+ }
+}
+
+# read a config file and use this to force new configs.
+sub load_force_config {
+ my ($config) = @_;
+
+ doprint "Loading force configs from $config\n";
+ open(IN, $config) or
+ dodie "failed to read $config";
+ while (<IN>) {
+ chomp;
+ if (/^(CONFIG[^\s=]*)(\s*=.*)/) {
+ $force_config{$1} = $_;
+ } elsif (/^# (CONFIG_\S*) is not set/) {
+ $force_config{$1} = $_;
+ }
+ }
+ close IN;
+}
+
+sub build {
+ my ($type) = @_;
+
+ unlink $buildlog;
+
+ # Failed builds should not reboot the target
+ my $save_no_reboot = $no_reboot;
+ $no_reboot = 1;
+
+ # Calculate a new version from here.
+ $have_version = 0;
+
+ if (defined($pre_build)) {
+ my $ret = run_command $pre_build;
+ if (!$ret && defined($pre_build_die) &&
+ $pre_build_die) {
+ dodie "failed to pre_build\n";
+ }
+ }
+
+ if ($type =~ /^useconfig:(.*)/) {
+ run_command "cp $1 $output_config" or
+ dodie "could not copy $1 to .config";
+
+ $type = "oldconfig";
+ }
+
+ # old config can ask questions
+ if ($type eq "oldconfig") {
+ $type = "olddefconfig";
+
+ # allow for empty configs
+ run_command "touch $output_config";
+
+ if (!$noclean) {
+ run_command "mv $output_config $outputdir/config_temp" or
+ dodie "moving .config";
+
+ run_command "$make mrproper" or dodie "make mrproper";
+
+ run_command "mv $outputdir/config_temp $output_config" or
+ dodie "moving config_temp";
+ }
+
+ } elsif (!$noclean) {
+ unlink "$output_config";
+ run_command "$make mrproper" or
+ dodie "make mrproper";
+ }
+
+ # add something to distinguish this build
+ open(OUT, "> $outputdir/localversion") or dodie("Can't make localversion file");
+ print OUT "$localversion\n";
+ close(OUT);
+
+ if (defined($minconfig)) {
+ load_force_config($minconfig);
+ }
+
+ if ($type ne "olddefconfig") {
+ run_command "$make $type" or
+ dodie "failed make config";
+ }
+ # Run old config regardless, to enforce min configurations
+ make_oldconfig;
+
+ $redirect = "$buildlog";
+ my $build_ret = run_command "$make $build_options";
+ undef $redirect;
+
+ if (defined($post_build)) {
+ # Because a post build may change the kernel version
+ # do it now.
+ get_version;
+ my $ret = run_command $post_build;
+ if (!$ret && defined($post_build_die) &&
+ $post_build_die) {
+ dodie "failed to post_build\n";
+ }
+ }
+
+ if (!$build_ret) {
+ # bisect may need this to pass
+ if ($in_bisect) {
+ $no_reboot = $save_no_reboot;
+ return 0;
+ }
+ fail "failed build" and return 0;
+ }
+
+ $no_reboot = $save_no_reboot;
+
+ return 1;
+}
+
+sub halt {
+ if (!run_ssh "halt" or defined($power_off)) {
+ if (defined($poweroff_after_halt)) {
+ sleep $poweroff_after_halt;
+ run_command "$power_off";
+ }
+ } else {
+ # nope? the zap it!
+ run_command "$power_off";
+ }
+}
+
+sub success {
+ my ($i) = @_;
+
+ if (defined($post_test)) {
+ run_command $post_test;
+ }
+
+ $successes++;
+
+ my $name = "";
+
+ if (defined($test_name)) {
+ $name = " ($test_name)";
+ }
+
+ doprint "\n\n*******************************************\n";
+ doprint "*******************************************\n";
+ doprint "KTEST RESULT: TEST $i$name SUCCESS!!!! **\n";
+ doprint "*******************************************\n";
+ doprint "*******************************************\n";
+
+ if (defined($store_successes)) {
+ save_logs "success", $store_successes;
+ }
+
+ if ($i != $opt{"NUM_TESTS"} && !do_not_reboot) {
+ doprint "Reboot and wait $sleep_time seconds\n";
+ reboot_to_good $sleep_time;
+ }
+}
+
+sub answer_bisect {
+ for (;;) {
+ doprint "Pass or fail? [p/f]";
+ my $ans = <STDIN>;
+ chomp $ans;
+ if ($ans eq "p" || $ans eq "P") {
+ return 1;
+ } elsif ($ans eq "f" || $ans eq "F") {
+ return 0;
+ } else {
+ print "Please answer 'P' or 'F'\n";
+ }
+ }
+}
+
+sub child_run_test {
+ my $failed = 0;
+
+ # child should have no power
+ $reboot_on_error = 0;
+ $poweroff_on_error = 0;
+ $die_on_failure = 1;
+
+ $redirect = "$testlog";
+ run_command $run_test or $failed = 1;
+ undef $redirect;
+
+ exit $failed;
+}
+
+my $child_done;
+
+sub child_finished {
+ $child_done = 1;
+}
+
+sub do_run_test {
+ my $child_pid;
+ my $child_exit;
+ my $line;
+ my $full_line;
+ my $bug = 0;
+ my $bug_ignored = 0;
+
+ wait_for_monitor 1;
+
+ doprint "run test $run_test\n";
+
+ $child_done = 0;
+
+ $SIG{CHLD} = qw(child_finished);
+
+ $child_pid = fork;
+
+ child_run_test if (!$child_pid);
+
+ $full_line = "";
+
+ do {
+ $line = wait_for_input($monitor_fp, 1);
+ if (defined($line)) {
+
+ # we are not guaranteed to get a full line
+ $full_line .= $line;
+ doprint $line;
+
+ if ($full_line =~ /call trace:/i) {
+ if ($ignore_errors) {
+ $bug_ignored = 1;
+ } else {
+ $bug = 1;
+ }
+ }
+
+ if ($full_line =~ /Kernel panic -/) {
+ $bug = 1;
+ }
+
+ if ($line =~ /\n/) {
+ $full_line = "";
+ }
+ }
+ } while (!$child_done && !$bug);
+
+ if (!$bug && $bug_ignored) {
+ doprint "WARNING: Call Trace detected but ignored due to IGNORE_ERRORS=1\n";
+ }
+
+ if ($bug) {
+ my $failure_start = time;
+ my $now;
+ do {
+ $line = wait_for_input($monitor_fp, 1);
+ if (defined($line)) {
+ doprint $line;
+ }
+ $now = time;
+ if ($now - $failure_start >= $stop_after_failure) {
+ last;
+ }
+ } while (defined($line));
+
+ doprint "Detected kernel crash!\n";
+ # kill the child with extreme prejudice
+ kill 9, $child_pid;
+ }
+
+ waitpid $child_pid, 0;
+ $child_exit = $?;
+
+ if (!$bug && $in_bisect) {
+ if (defined($bisect_ret_good)) {
+ if ($child_exit == $bisect_ret_good) {
+ return 1;
+ }
+ }
+ if (defined($bisect_ret_skip)) {
+ if ($child_exit == $bisect_ret_skip) {
+ return -1;
+ }
+ }
+ if (defined($bisect_ret_abort)) {
+ if ($child_exit == $bisect_ret_abort) {
+ fail "test abort" and return -2;
+ }
+ }
+ if (defined($bisect_ret_bad)) {
+ if ($child_exit == $bisect_ret_skip) {
+ return 0;
+ }
+ }
+ if (defined($bisect_ret_default)) {
+ if ($bisect_ret_default eq "good") {
+ return 1;
+ } elsif ($bisect_ret_default eq "bad") {
+ return 0;
+ } elsif ($bisect_ret_default eq "skip") {
+ return -1;
+ } elsif ($bisect_ret_default eq "abort") {
+ return -2;
+ } else {
+ fail "unknown default action: $bisect_ret_default"
+ and return -2;
+ }
+ }
+ }
+
+ if ($bug || $child_exit) {
+ return 0 if $in_bisect;
+ fail "test failed" and return 0;
+ }
+ return 1;
+}
+
+sub run_git_bisect {
+ my ($command) = @_;
+
+ doprint "$command ... ";
+
+ my $output = `$command 2>&1`;
+ my $ret = $?;
+
+ logit $output;
+
+ if ($ret) {
+ doprint "FAILED\n";
+ dodie "Failed to git bisect";
+ }
+
+ doprint "SUCCESS\n";
+ if ($output =~ m/^(Bisecting: .*\(roughly \d+ steps?\))\s+\[([[:xdigit:]]+)\]/) {
+ doprint "$1 [$2]\n";
+ } elsif ($output =~ m/^([[:xdigit:]]+) is the first bad commit/) {
+ $bisect_bad_commit = $1;
+ doprint "Found bad commit... $1\n";
+ return 0;
+ } else {
+ # we already logged it, just print it now.
+ print $output;
+ }
+
+ return 1;
+}
+
+sub bisect_reboot {
+ doprint "Reboot and sleep $bisect_sleep_time seconds\n";
+ reboot_to_good $bisect_sleep_time;
+}
+
+# returns 1 on success, 0 on failure, -1 on skip
+sub run_bisect_test {
+ my ($type, $buildtype) = @_;
+
+ my $failed = 0;
+ my $result;
+ my $output;
+ my $ret;
+
+ $in_bisect = 1;
+
+ build $buildtype or $failed = 1;
+
+ if ($type ne "build") {
+ if ($failed && $bisect_skip) {
+ $in_bisect = 0;
+ return -1;
+ }
+ dodie "Failed on build" if $failed;
+
+ # Now boot the box
+ start_monitor_and_boot or $failed = 1;
+
+ if ($type ne "boot") {
+ if ($failed && $bisect_skip) {
+ end_monitor;
+ bisect_reboot;
+ $in_bisect = 0;
+ return -1;
+ }
+ dodie "Failed on boot" if $failed;
+
+ do_run_test or $failed = 1;
+ }
+ end_monitor;
+ }
+
+ if ($failed) {
+ $result = 0;
+ } else {
+ $result = 1;
+ }
+
+ # reboot the box to a kernel we can ssh to
+ if ($type ne "build") {
+ bisect_reboot;
+ }
+ $in_bisect = 0;
+
+ return $result;
+}
+
+sub run_bisect {
+ my ($type) = @_;
+ my $buildtype = "oldconfig";
+
+ # We should have a minconfig to use?
+ if (defined($minconfig)) {
+ $buildtype = "useconfig:$minconfig";
+ }
+
+ # If the user sets bisect_tries to less than 1, then no tries
+ # is a success.
+ my $ret = 1;
+
+ # Still let the user manually decide that though.
+ if ($bisect_tries < 1 && $bisect_manual) {
+ $ret = answer_bisect;
+ }
+
+ for (my $i = 0; $i < $bisect_tries; $i++) {
+ if ($bisect_tries > 1) {
+ my $t = $i + 1;
+ doprint("Running bisect trial $t of $bisect_tries:\n");
+ }
+ $ret = run_bisect_test $type, $buildtype;
+
+ if ($bisect_manual) {
+ $ret = answer_bisect;
+ }
+
+ last if (!$ret);
+ }
+
+ # Are we looking for where it worked, not failed?
+ if ($reverse_bisect && $ret >= 0) {
+ $ret = !$ret;
+ }
+
+ if ($ret > 0) {
+ return "good";
+ } elsif ($ret == 0) {
+ return "bad";
+ } elsif ($bisect_skip) {
+ doprint "HIT A BAD COMMIT ... SKIPPING\n";
+ return "skip";
+ }
+}
+
+sub update_bisect_replay {
+ my $tmp_log = "$tmpdir/ktest_bisect_log";
+ run_command "git bisect log > $tmp_log" or
+ die "can't create bisect log";
+ return $tmp_log;
+}
+
+sub bisect {
+ my ($i) = @_;
+
+ my $result;
+
+ die "BISECT_GOOD[$i] not defined\n" if (!defined($bisect_good));
+ die "BISECT_BAD[$i] not defined\n" if (!defined($bisect_bad));
+ die "BISECT_TYPE[$i] not defined\n" if (!defined($bisect_type));
+
+ my $good = $bisect_good;
+ my $bad = $bisect_bad;
+ my $type = $bisect_type;
+ my $start = $bisect_start;
+ my $replay = $bisect_replay;
+ my $start_files = $bisect_files;
+
+ if (defined($start_files)) {
+ $start_files = " -- " . $start_files;
+ } else {
+ $start_files = "";
+ }
+
+ # convert to true sha1's
+ $good = get_sha1($good);
+ $bad = get_sha1($bad);
+
+ if (defined($bisect_reverse) && $bisect_reverse == 1) {
+ doprint "Performing a reverse bisect (bad is good, good is bad!)\n";
+ $reverse_bisect = 1;
+ } else {
+ $reverse_bisect = 0;
+ }
+
+ # Can't have a test without having a test to run
+ if ($type eq "test" && !defined($run_test)) {
+ $type = "boot";
+ }
+
+ # Check if a bisect was running
+ my $bisect_start_file = "$builddir/.git/BISECT_START";
+
+ my $check = $bisect_check;
+ my $do_check = defined($check) && $check ne "0";
+
+ if ( -f $bisect_start_file ) {
+ print "Bisect in progress found\n";
+ if ($do_check) {
+ print " If you say yes, then no checks of good or bad will be done\n";
+ }
+ if (defined($replay)) {
+ print "** BISECT_REPLAY is defined in config file **";
+ print " Ignore config option and perform new git bisect log?\n";
+ if (read_ync " (yes, no, or cancel) ") {
+ $replay = update_bisect_replay;
+ $do_check = 0;
+ }
+ } elsif (read_yn "read git log and continue?") {
+ $replay = update_bisect_replay;
+ $do_check = 0;
+ }
+ }
+
+ if ($do_check) {
+
+ # get current HEAD
+ my $head = get_sha1("HEAD");
+
+ if ($check ne "good") {
+ doprint "TESTING BISECT BAD [$bad]\n";
+ run_command "git checkout $bad" or
+ die "Failed to checkout $bad";
+
+ $result = run_bisect $type;
+
+ if ($result ne "bad") {
+ fail "Tested BISECT_BAD [$bad] and it succeeded" and return 0;
+ }
+ }
+
+ if ($check ne "bad") {
+ doprint "TESTING BISECT GOOD [$good]\n";
+ run_command "git checkout $good" or
+ die "Failed to checkout $good";
+
+ $result = run_bisect $type;
+
+ if ($result ne "good") {
+ fail "Tested BISECT_GOOD [$good] and it failed" and return 0;
+ }
+ }
+
+ # checkout where we started
+ run_command "git checkout $head" or
+ die "Failed to checkout $head";
+ }
+
+ run_command "git bisect start$start_files" or
+ dodie "could not start bisect";
+
+ run_command "git bisect good $good" or
+ dodie "could not set bisect good to $good";
+
+ run_git_bisect "git bisect bad $bad" or
+ dodie "could not set bisect bad to $bad";
+
+ if (defined($replay)) {
+ run_command "git bisect replay $replay" or
+ dodie "failed to run replay";
+ }
+
+ if (defined($start)) {
+ run_command "git checkout $start" or
+ dodie "failed to checkout $start";
+ }
+
+ my $test;
+ do {
+ $result = run_bisect $type;
+ $test = run_git_bisect "git bisect $result";
+ } while ($test);
+
+ run_command "git bisect log" or
+ dodie "could not capture git bisect log";
+
+ run_command "git bisect reset" or
+ dodie "could not reset git bisect";
+
+ doprint "Bad commit was [$bisect_bad_commit]\n";
+
+ success $i;
+}
+
+# config_ignore holds the configs that were set (or unset) for
+# a good config and we will ignore these configs for the rest
+# of a config bisect. These configs stay as they were.
+my %config_ignore;
+
+# config_set holds what all configs were set as.
+my %config_set;
+
+# config_off holds the set of configs that the bad config had disabled.
+# We need to record them and set them in the .config when running
+# olddefconfig, because olddefconfig keeps the defaults.
+my %config_off;
+
+# config_off_tmp holds a set of configs to turn off for now
+my @config_off_tmp;
+
+# config_list is the set of configs that are being tested
+my %config_list;
+my %null_config;
+
+my %dependency;
+
+sub assign_configs {
+ my ($hash, $config) = @_;
+
+ open (IN, $config)
+ or dodie "Failed to read $config";
+
+ while (<IN>) {
+ if (/^((CONFIG\S*)=.*)/) {
+ ${$hash}{$2} = $1;
+ }
+ }
+
+ close(IN);
+}
+
+sub process_config_ignore {
+ my ($config) = @_;
+
+ assign_configs \%config_ignore, $config;
+}
+
+sub read_current_config {
+ my ($config_ref) = @_;
+
+ %{$config_ref} = ();
+ undef %{$config_ref};
+
+ my @key = keys %{$config_ref};
+ if ($#key >= 0) {
+ print "did not delete!\n";
+ exit;
+ }
+ open (IN, "$output_config");
+
+ while (<IN>) {
+ if (/^(CONFIG\S+)=(.*)/) {
+ ${$config_ref}{$1} = $2;
+ }
+ }
+ close(IN);
+}
+
+sub get_dependencies {
+ my ($config) = @_;
+
+ my $arr = $dependency{$config};
+ if (!defined($arr)) {
+ return ();
+ }
+
+ my @deps = @{$arr};
+
+ foreach my $dep (@{$arr}) {
+ print "ADD DEP $dep\n";
+ @deps = (@deps, get_dependencies $dep);
+ }
+
+ return @deps;
+}
+
+sub create_config {
+ my @configs = @_;
+
+ open(OUT, ">$output_config") or dodie "Can not write to $output_config";
+
+ foreach my $config (@configs) {
+ print OUT "$config_set{$config}\n";
+ my @deps = get_dependencies $config;
+ foreach my $dep (@deps) {
+ print OUT "$config_set{$dep}\n";
+ }
+ }
+
+ # turn off configs to keep off
+ foreach my $config (keys %config_off) {
+ print OUT "# $config is not set\n";
+ }
+
+ # turn off configs that should be off for now
+ foreach my $config (@config_off_tmp) {
+ print OUT "# $config is not set\n";
+ }
+
+ foreach my $config (keys %config_ignore) {
+ print OUT "$config_ignore{$config}\n";
+ }
+ close(OUT);
+
+ make_oldconfig;
+}
+
+sub compare_configs {
+ my (%a, %b) = @_;
+
+ foreach my $item (keys %a) {
+ if (!defined($b{$item})) {
+ print "diff $item\n";
+ return 1;
+ }
+ delete $b{$item};
+ }
+
+ my @keys = keys %b;
+ if ($#keys) {
+ print "diff2 $keys[0]\n";
+ }
+ return -1 if ($#keys >= 0);
+
+ return 0;
+}
+
+sub run_config_bisect_test {
+ my ($type) = @_;
+
+ return run_bisect_test $type, "oldconfig";
+}
+
+sub process_passed {
+ my (%configs) = @_;
+
+ doprint "These configs had no failure: (Enabling them for further compiles)\n";
+ # Passed! All these configs are part of a good compile.
+ # Add them to the min options.
+ foreach my $config (keys %configs) {
+ if (defined($config_list{$config})) {
+ doprint " removing $config\n";
+ $config_ignore{$config} = $config_list{$config};
+ delete $config_list{$config};
+ }
+ }
+ doprint "config copied to $outputdir/config_good\n";
+ run_command "cp -f $output_config $outputdir/config_good";
+}
+
+sub process_failed {
+ my ($config) = @_;
+
+ doprint "\n\n***************************************\n";
+ doprint "Found bad config: $config\n";
+ doprint "***************************************\n\n";
+}
+
+sub run_config_bisect {
+
+ my @start_list = keys %config_list;
+
+ if ($#start_list < 0) {
+ doprint "No more configs to test!!!\n";
+ return -1;
+ }
+
+ doprint "***** RUN TEST ***\n";
+ my $type = $config_bisect_type;
+ my $ret;
+ my %current_config;
+
+ my $count = $#start_list + 1;
+ doprint " $count configs to test\n";
+
+ my $half = int($#start_list / 2);
+
+ do {
+ my @tophalf = @start_list[0 .. $half];
+
+ # keep the bottom half off
+ if ($half < $#start_list) {
+ @config_off_tmp = @start_list[$half + 1 .. $#start_list];
+ } else {
+ @config_off_tmp = ();
+ }
+
+ create_config @tophalf;
+ read_current_config \%current_config;
+
+ $count = $#tophalf + 1;
+ doprint "Testing $count configs\n";
+ my $found = 0;
+ # make sure we test something
+ foreach my $config (@tophalf) {
+ if (defined($current_config{$config})) {
+ logit " $config\n";
+ $found = 1;
+ }
+ }
+ if (!$found) {
+ # try the other half
+ doprint "Top half produced no set configs, trying bottom half\n";
+
+ # keep the top half off
+ @config_off_tmp = @tophalf;
+ @tophalf = @start_list[$half + 1 .. $#start_list];
+
+ create_config @tophalf;
+ read_current_config \%current_config;
+ foreach my $config (@tophalf) {
+ if (defined($current_config{$config})) {
+ logit " $config\n";
+ $found = 1;
+ }
+ }
+ if (!$found) {
+ doprint "Failed: Can't make new config with current configs\n";
+ foreach my $config (@start_list) {
+ doprint " CONFIG: $config\n";
+ }
+ return -1;
+ }
+ $count = $#tophalf + 1;
+ doprint "Testing $count configs\n";
+ }
+
+ $ret = run_config_bisect_test $type;
+ if ($bisect_manual) {
+ $ret = answer_bisect;
+ }
+ if ($ret) {
+ process_passed %current_config;
+ return 0;
+ }
+
+ doprint "This config had a failure.\n";
+ doprint "Removing these configs that were not set in this config:\n";
+ doprint "config copied to $outputdir/config_bad\n";
+ run_command "cp -f $output_config $outputdir/config_bad";
+
+ # A config exists in this group that was bad.
+ foreach my $config (keys %config_list) {
+ if (!defined($current_config{$config})) {
+ doprint " removing $config\n";
+ delete $config_list{$config};
+ }
+ }
+
+ @start_list = @tophalf;
+
+ if ($#start_list == 0) {
+ process_failed $start_list[0];
+ return 1;
+ }
+
+ # remove half the configs we are looking at and see if
+ # they are good.
+ $half = int($#start_list / 2);
+ } while ($#start_list > 0);
+
+ # we found a single config, try it again unless we are running manually
+
+ if ($bisect_manual) {
+ process_failed $start_list[0];
+ return 1;
+ }
+
+ my @tophalf = @start_list[0 .. 0];
+
+ $ret = run_config_bisect_test $type;
+ if ($ret) {
+ process_passed %current_config;
+ return 0;
+ }
+
+ process_failed $start_list[0];
+ return 1;
+}
+
+sub config_bisect {
+ my ($i) = @_;
+
+ my $start_config = $config_bisect;
+
+ my $tmpconfig = "$tmpdir/use_config";
+
+ if (defined($config_bisect_good)) {
+ process_config_ignore $config_bisect_good;
+ }
+
+ # Make the file with the bad config and the min config
+ if (defined($minconfig)) {
+ # read the min config for things to ignore
+ run_command "cp $minconfig $tmpconfig" or
+ dodie "failed to copy $minconfig to $tmpconfig";
+ } else {
+ unlink $tmpconfig;
+ }
+
+ if (-f $tmpconfig) {
+ load_force_config($tmpconfig);
+ process_config_ignore $tmpconfig;
+ }
+
+ # now process the start config
+ run_command "cp $start_config $output_config" or
+ dodie "failed to copy $start_config to $output_config";
+
+ # read directly what we want to check
+ my %config_check;
+ open (IN, $output_config)
+ or dodie "failed to open $output_config";
+
+ while (<IN>) {
+ if (/^((CONFIG\S*)=.*)/) {
+ $config_check{$2} = $1;
+ }
+ }
+ close(IN);
+
+ # Now run oldconfig with the minconfig
+ make_oldconfig;
+
+ # check to see what we lost (or gained)
+ open (IN, $output_config)
+ or dodie "Failed to read $start_config";
+
+ my %removed_configs;
+ my %added_configs;
+
+ while (<IN>) {
+ if (/^((CONFIG\S*)=.*)/) {
+ # save off all options
+ $config_set{$2} = $1;
+ if (defined($config_check{$2})) {
+ if (defined($config_ignore{$2})) {
+ $removed_configs{$2} = $1;
+ } else {
+ $config_list{$2} = $1;
+ }
+ } elsif (!defined($config_ignore{$2})) {
+ $added_configs{$2} = $1;
+ $config_list{$2} = $1;
+ }
+ } elsif (/^# ((CONFIG\S*).*)/) {
+ # Keep these configs disabled
+ $config_set{$2} = $1;
+ $config_off{$2} = $1;
+ }
+ }
+ close(IN);
+
+ my @confs = keys %removed_configs;
+ if ($#confs >= 0) {
+ doprint "Configs overridden by default configs and removed from check:\n";
+ foreach my $config (@confs) {
+ doprint " $config\n";
+ }
+ }
+ @confs = keys %added_configs;
+ if ($#confs >= 0) {
+ doprint "Configs appearing in make oldconfig and added:\n";
+ foreach my $config (@confs) {
+ doprint " $config\n";
+ }
+ }
+
+ my %config_test;
+ my $once = 0;
+
+ @config_off_tmp = ();
+
+ # Sometimes kconfig does weird things. We must make sure
+ # that the config we autocreate has everything we need
+ # to test, otherwise we may miss testing configs, or
+ # may not be able to create a new config.
+ # Here we create a config with everything set.
+ create_config (keys %config_list);
+ read_current_config \%config_test;
+ foreach my $config (keys %config_list) {
+ if (!defined($config_test{$config})) {
+ if (!$once) {
+ $once = 1;
+ doprint "Configs not produced by kconfig (will not be checked):\n";
+ }
+ doprint " $config\n";
+ delete $config_list{$config};
+ }
+ }
+ my $ret;
+
+ if (defined($config_bisect_check) && $config_bisect_check) {
+ doprint " Checking to make sure bad config with min config fails\n";
+ create_config keys %config_list;
+ $ret = run_config_bisect_test $config_bisect_type;
+ if ($ret) {
+ doprint " FAILED! Bad config with min config boots fine\n";
+ return -1;
+ }
+ doprint " Bad config with min config fails as expected\n";
+ }
+
+ do {
+ $ret = run_config_bisect;
+ } while (!$ret);
+
+ return $ret if ($ret < 0);
+
+ success $i;
+}
+
+sub patchcheck_reboot {
+ doprint "Reboot and sleep $patchcheck_sleep_time seconds\n";
+ reboot_to_good $patchcheck_sleep_time;
+}
+
+sub patchcheck {
+ my ($i) = @_;
+
+ die "PATCHCHECK_START[$i] not defined\n"
+ if (!defined($patchcheck_start));
+ die "PATCHCHECK_TYPE[$i] not defined\n"
+ if (!defined($patchcheck_type));
+
+ my $start = $patchcheck_start;
+
+ my $end = "HEAD";
+ if (defined($patchcheck_end)) {
+ $end = $patchcheck_end;
+ }
+
+ # Get the true sha1's since we can use things like HEAD~3
+ $start = get_sha1($start);
+ $end = get_sha1($end);
+
+ my $type = $patchcheck_type;
+
+ # Can't have a test without having a test to run
+ if ($type eq "test" && !defined($run_test)) {
+ $type = "boot";
+ }
+
+ open (IN, "git log --pretty=oneline $end|") or
+ dodie "could not get git list";
+
+ my @list;
+
+ while (<IN>) {
+ chomp;
+ $list[$#list+1] = $_;
+ last if (/^$start/);
+ }
+ close(IN);
+
+ if ($list[$#list] !~ /^$start/) {
+ fail "SHA1 $start not found";
+ }
+
+ # go backwards in the list
+ @list = reverse @list;
+
+ my $save_clean = $noclean;
+ my %ignored_warnings;
+
+ if (defined($ignore_warnings)) {
+ foreach my $sha1 (split /\s+/, $ignore_warnings) {
+ $ignored_warnings{$sha1} = 1;
+ }
+ }
+
+ $in_patchcheck = 1;
+ foreach my $item (@list) {
+ my $sha1 = $item;
+ $sha1 =~ s/^([[:xdigit:]]+).*/$1/;
+
+ doprint "\nProcessing commit $item\n\n";
+
+ run_command "git checkout $sha1" or
+ die "Failed to checkout $sha1";
+
+ # only clean on the first and last patch
+ if ($item eq $list[0] ||
+ $item eq $list[$#list]) {
+ $noclean = $save_clean;
+ } else {
+ $noclean = 1;
+ }
+
+ if (defined($minconfig)) {
+ build "useconfig:$minconfig" or return 0;
+ } else {
+ # ?? no config to use?
+ build "oldconfig" or return 0;
+ }
+
+ # No need to do per patch checking if warnings file exists
+ if (!defined($warnings_file) && !defined($ignored_warnings{$sha1})) {
+ check_patch_buildlog $sha1 or return 0;
+ }
+
+ check_buildlog or return 0;
+
+ next if ($type eq "build");
+
+ my $failed = 0;
+
+ start_monitor_and_boot or $failed = 1;
+
+ if (!$failed && $type ne "boot"){
+ do_run_test or $failed = 1;
+ }
+ end_monitor;
+ return 0 if ($failed);
+
+ patchcheck_reboot;
+
+ }
+ $in_patchcheck = 0;
+ success $i;
+
+ return 1;
+}
+
+my %depends;
+my %depcount;
+my $iflevel = 0;
+my @ifdeps;
+
+# prevent recursion
+my %read_kconfigs;
+
+sub add_dep {
+ # $config depends on $dep
+ my ($config, $dep) = @_;
+
+ if (defined($depends{$config})) {
+ $depends{$config} .= " " . $dep;
+ } else {
+ $depends{$config} = $dep;
+ }
+
+ # record the number of configs depending on $dep
+ if (defined $depcount{$dep}) {
+ $depcount{$dep}++;
+ } else {
+ $depcount{$dep} = 1;
+ }
+}
+
+# taken from streamline_config.pl
+sub read_kconfig {
+ my ($kconfig) = @_;
+
+ my $state = "NONE";
+ my $config;
+ my @kconfigs;
+
+ my $cont = 0;
+ my $line;
+
+
+ if (! -f $kconfig) {
+ doprint "file $kconfig does not exist, skipping\n";
+ return;
+ }
+
+ open(KIN, "$kconfig")
+ or die "Can't open $kconfig";
+ while (<KIN>) {
+ chomp;
+
+ # Make sure that lines ending with \ continue
+ if ($cont) {
+ $_ = $line . " " . $_;
+ }
+
+ if (s/\\$//) {
+ $cont = 1;
+ $line = $_;
+ next;
+ }
+
+ $cont = 0;
+
+ # collect any Kconfig sources
+ if (/^source\s*"(.*)"/) {
+ $kconfigs[$#kconfigs+1] = $1;
+ }
+
+ # configs found
+ if (/^\s*(menu)?config\s+(\S+)\s*$/) {
+ $state = "NEW";
+ $config = $2;
+
+ for (my $i = 0; $i < $iflevel; $i++) {
+ add_dep $config, $ifdeps[$i];
+ }
+
+ # collect the depends for the config
+ } elsif ($state eq "NEW" && /^\s*depends\s+on\s+(.*)$/) {
+
+ add_dep $config, $1;
+
+ # Get the configs that select this config
+ } elsif ($state eq "NEW" && /^\s*select\s+(\S+)/) {
+
+ # selected by depends on config
+ add_dep $1, $config;
+
+ # Check for if statements
+ } elsif (/^if\s+(.*\S)\s*$/) {
+ my $deps = $1;
+ # remove beginning and ending non text
+ $deps =~ s/^[^a-zA-Z0-9_]*//;
+ $deps =~ s/[^a-zA-Z0-9_]*$//;
+
+ my @deps = split /[^a-zA-Z0-9_]+/, $deps;
+
+ $ifdeps[$iflevel++] = join ':', @deps;
+
+ } elsif (/^endif/) {
+
+ $iflevel-- if ($iflevel);
+
+ # stop on "help"
+ } elsif (/^\s*help\s*$/) {
+ $state = "NONE";
+ }
+ }
+ close(KIN);
+
+ # read in any configs that were found.
+ foreach $kconfig (@kconfigs) {
+ if (!defined($read_kconfigs{$kconfig})) {
+ $read_kconfigs{$kconfig} = 1;
+ read_kconfig("$builddir/$kconfig");
+ }
+ }
+}
+
+sub read_depends {
+ # find out which arch this is by the kconfig file
+ open (IN, $output_config)
+ or dodie "Failed to read $output_config";
+ my $arch;
+ while (<IN>) {
+ if (m,Linux/(\S+)\s+\S+\s+Kernel Configuration,) {
+ $arch = $1;
+ last;
+ }
+ }
+ close IN;
+
+ if (!defined($arch)) {
+ doprint "Could not find arch from config file\n";
+ doprint "no dependencies used\n";
+ return;
+ }
+
+ # arch is really the subarch, we need to know
+ # what directory to look at.
+ if ($arch eq "i386" || $arch eq "x86_64") {
+ $arch = "x86";
+ } elsif ($arch =~ /^tile/) {
+ $arch = "tile";
+ }
+
+ my $kconfig = "$builddir/arch/$arch/Kconfig";
+
+ if (! -f $kconfig && $arch =~ /\d$/) {
+ my $orig = $arch;
+ # some subarchs have numbers, truncate them
+ $arch =~ s/\d*$//;
+ $kconfig = "$builddir/arch/$arch/Kconfig";
+ if (! -f $kconfig) {
+ doprint "No idea what arch dir $orig is for\n";
+ doprint "no dependencies used\n";
+ return;
+ }
+ }
+
+ read_kconfig($kconfig);
+}
+
+sub read_config_list {
+ my ($config) = @_;
+
+ open (IN, $config)
+ or dodie "Failed to read $config";
+
+ while (<IN>) {
+ if (/^((CONFIG\S*)=.*)/) {
+ if (!defined($config_ignore{$2})) {
+ $config_list{$2} = $1;
+ }
+ }
+ }
+
+ close(IN);
+}
+
+sub read_output_config {
+ my ($config) = @_;
+
+ assign_configs \%config_ignore, $config;
+}
+
+sub make_new_config {
+ my @configs = @_;
+
+ open (OUT, ">$output_config")
+ or dodie "Failed to write $output_config";
+
+ foreach my $config (@configs) {
+ print OUT "$config\n";
+ }
+ close OUT;
+}
+
+sub chomp_config {
+ my ($config) = @_;
+
+ $config =~ s/CONFIG_//;
+
+ return $config;
+}
+
+sub get_depends {
+ my ($dep) = @_;
+
+ my $kconfig = chomp_config $dep;
+
+ $dep = $depends{"$kconfig"};
+
+ # the dep string we have saves the dependencies as they
+ # were found, including expressions like ! && ||. We
+ # want to split this out into just an array of configs.
+
+ my $valid = "A-Za-z_0-9";
+
+ my @configs;
+
+ while ($dep =~ /[$valid]/) {
+
+ if ($dep =~ /^[^$valid]*([$valid]+)/) {
+ my $conf = "CONFIG_" . $1;
+
+ $configs[$#configs + 1] = $conf;
+
+ $dep =~ s/^[^$valid]*[$valid]+//;
+ } else {
+ die "this should never happen";
+ }
+ }
+
+ return @configs;
+}
+
+my %min_configs;
+my %keep_configs;
+my %save_configs;
+my %processed_configs;
+my %nochange_config;
+
+sub test_this_config {
+ my ($config) = @_;
+
+ my $found;
+
+ # if we already processed this config, skip it
+ if (defined($processed_configs{$config})) {
+ return undef;
+ }
+ $processed_configs{$config} = 1;
+
+ # if this config failed during this round, skip it
+ if (defined($nochange_config{$config})) {
+ return undef;
+ }
+
+ my $kconfig = chomp_config $config;
+
+ # Test dependencies first
+ if (defined($depends{"$kconfig"})) {
+ my @parents = get_depends $config;
+ foreach my $parent (@parents) {
+ # if the parent is in the min config, check it first
+ next if (!defined($min_configs{$parent}));
+ $found = test_this_config($parent);
+ if (defined($found)) {
+ return $found;
+ }
+ }
+ }
+
+ # Remove this config from the list of configs
+ # do a make olddefconfig and then read the resulting
+ # .config to make sure it is missing the config that
+ # we had before
+ my %configs = %min_configs;
+ delete $configs{$config};
+ make_new_config ((values %configs), (values %keep_configs));
+ make_oldconfig;
+ undef %configs;
+ assign_configs \%configs, $output_config;
+
+ return $config if (!defined($configs{$config}));
+
+ doprint "disabling config $config did not change .config\n";
+
+ $nochange_config{$config} = 1;
+
+ return undef;
+}
+
+sub make_min_config {
+ my ($i) = @_;
+
+ my $type = $minconfig_type;
+ if ($type ne "boot" && $type ne "test") {
+ fail "Invalid MIN_CONFIG_TYPE '$minconfig_type'\n" .
+ " make_min_config works only with 'boot' and 'test'\n" and return;
+ }
+
+ if (!defined($output_minconfig)) {
+ fail "OUTPUT_MIN_CONFIG not defined" and return;
+ }
+
+ # If output_minconfig exists, and the start_minconfig
+ # came from min_config, than ask if we should use
+ # that instead.
+ if (-f $output_minconfig && !$start_minconfig_defined) {
+ print "$output_minconfig exists\n";
+ if (!defined($use_output_minconfig)) {
+ if (read_yn " Use it as minconfig?") {
+ $start_minconfig = $output_minconfig;
+ }
+ } elsif ($use_output_minconfig > 0) {
+ doprint "Using $output_minconfig as MIN_CONFIG\n";
+ $start_minconfig = $output_minconfig;
+ } else {
+ doprint "Set to still use MIN_CONFIG as starting point\n";
+ }
+ }
+
+ if (!defined($start_minconfig)) {
+ fail "START_MIN_CONFIG or MIN_CONFIG not defined" and return;
+ }
+
+ my $temp_config = "$tmpdir/temp_config";
+
+ # First things first. We build an allnoconfig to find
+ # out what the defaults are that we can't touch.
+ # Some are selections, but we really can't handle selections.
+
+ my $save_minconfig = $minconfig;
+ undef $minconfig;
+
+ run_command "$make allnoconfig" or return 0;
+
+ read_depends;
+
+ process_config_ignore $output_config;
+
+ undef %save_configs;
+ undef %min_configs;
+
+ if (defined($ignore_config)) {
+ # make sure the file exists
+ `touch $ignore_config`;
+ assign_configs \%save_configs, $ignore_config;
+ }
+
+ %keep_configs = %save_configs;
+
+ doprint "Load initial configs from $start_minconfig\n";
+
+ # Look at the current min configs, and save off all the
+ # ones that were set via the allnoconfig
+ assign_configs \%min_configs, $start_minconfig;
+
+ my @config_keys = keys %min_configs;
+
+ # All configs need a depcount
+ foreach my $config (@config_keys) {
+ my $kconfig = chomp_config $config;
+ if (!defined $depcount{$kconfig}) {
+ $depcount{$kconfig} = 0;
+ }
+ }
+
+ # Remove anything that was set by the make allnoconfig
+ # we shouldn't need them as they get set for us anyway.
+ foreach my $config (@config_keys) {
+ # Remove anything in the ignore_config
+ if (defined($keep_configs{$config})) {
+ my $file = $ignore_config;
+ $file =~ s,.*/(.*?)$,$1,;
+ doprint "$config set by $file ... ignored\n";
+ delete $min_configs{$config};
+ next;
+ }
+ # But make sure the settings are the same. If a min config
+ # sets a selection, we do not want to get rid of it if
+ # it is not the same as what we have. Just move it into
+ # the keep configs.
+ if (defined($config_ignore{$config})) {
+ if ($config_ignore{$config} ne $min_configs{$config}) {
+ doprint "$config is in allnoconfig as '$config_ignore{$config}'";
+ doprint " but it is '$min_configs{$config}' in minconfig .. keeping\n";
+ $keep_configs{$config} = $min_configs{$config};
+ } else {
+ doprint "$config set by allnoconfig ... ignored\n";
+ }
+ delete $min_configs{$config};
+ }
+ }
+
+ my $done = 0;
+ my $take_two = 0;
+
+ while (!$done) {
+
+ my $config;
+ my $found;
+
+ # Now disable each config one by one and do a make oldconfig
+ # till we find a config that changes our list.
+
+ my @test_configs = keys %min_configs;
+
+ # Sort keys by who is most dependent on
+ @test_configs = sort { $depcount{chomp_config($b)} <=> $depcount{chomp_config($a)} }
+ @test_configs ;
+
+ # Put configs that did not modify the config at the end.
+ my $reset = 1;
+ for (my $i = 0; $i < $#test_configs; $i++) {
+ if (!defined($nochange_config{$test_configs[0]})) {
+ $reset = 0;
+ last;
+ }
+ # This config didn't change the .config last time.
+ # Place it at the end
+ my $config = shift @test_configs;
+ push @test_configs, $config;
+ }
+
+ # if every test config has failed to modify the .config file
+ # in the past, then reset and start over.
+ if ($reset) {
+ undef %nochange_config;
+ }
+
+ undef %processed_configs;
+
+ foreach my $config (@test_configs) {
+
+ $found = test_this_config $config;
+
+ last if (defined($found));
+
+ # oh well, try another config
+ }
+
+ if (!defined($found)) {
+ # we could have failed due to the nochange_config hash
+ # reset and try again
+ if (!$take_two) {
+ undef %nochange_config;
+ $take_two = 1;
+ next;
+ }
+ doprint "No more configs found that we can disable\n";
+ $done = 1;
+ last;
+ }
+ $take_two = 0;
+
+ $config = $found;
+
+ doprint "Test with $config disabled\n";
+
+ # set in_bisect to keep build and monitor from dieing
+ $in_bisect = 1;
+
+ my $failed = 0;
+ build "oldconfig" or $failed = 1;
+ if (!$failed) {
+ start_monitor_and_boot or $failed = 1;
+
+ if ($type eq "test" && !$failed) {
+ do_run_test or $failed = 1;
+ }
+
+ end_monitor;
+ }
+
+ $in_bisect = 0;
+
+ if ($failed) {
+ doprint "$min_configs{$config} is needed to boot the box... keeping\n";
+ # this config is needed, add it to the ignore list.
+ $keep_configs{$config} = $min_configs{$config};
+ $save_configs{$config} = $min_configs{$config};
+ delete $min_configs{$config};
+
+ # update new ignore configs
+ if (defined($ignore_config)) {
+ open (OUT, ">$temp_config")
+ or die "Can't write to $temp_config";
+ foreach my $config (keys %save_configs) {
+ print OUT "$save_configs{$config}\n";
+ }
+ close OUT;
+ run_command "mv $temp_config $ignore_config" or
+ dodie "failed to copy update to $ignore_config";
+ }
+
+ } else {
+ # We booted without this config, remove it from the minconfigs.
+ doprint "$config is not needed, disabling\n";
+
+ delete $min_configs{$config};
+
+ # Also disable anything that is not enabled in this config
+ my %configs;
+ assign_configs \%configs, $output_config;
+ my @config_keys = keys %min_configs;
+ foreach my $config (@config_keys) {
+ if (!defined($configs{$config})) {
+ doprint "$config is not set, disabling\n";
+ delete $min_configs{$config};
+ }
+ }
+
+ # Save off all the current mandidory configs
+ open (OUT, ">$temp_config")
+ or die "Can't write to $temp_config";
+ foreach my $config (keys %keep_configs) {
+ print OUT "$keep_configs{$config}\n";
+ }
+ foreach my $config (keys %min_configs) {
+ print OUT "$min_configs{$config}\n";
+ }
+ close OUT;
+
+ run_command "mv $temp_config $output_minconfig" or
+ dodie "failed to copy update to $output_minconfig";
+ }
+
+ doprint "Reboot and wait $sleep_time seconds\n";
+ reboot_to_good $sleep_time;
+ }
+
+ success $i;
+ return 1;
+}
+
+sub make_warnings_file {
+ my ($i) = @_;
+
+ if (!defined($warnings_file)) {
+ dodie "Must define WARNINGS_FILE for make_warnings_file test";
+ }
+
+ if ($build_type eq "nobuild") {
+ dodie "BUILD_TYPE can not be 'nobuild' for make_warnings_file test";
+ }
+
+ build $build_type or dodie "Failed to build";
+
+ open(OUT, ">$warnings_file") or dodie "Can't create $warnings_file";
+
+ open(IN, $buildlog) or dodie "Can't open $buildlog";
+ while (<IN>) {
+
+ # Some compilers use UTF-8 extended for quotes
+ # for distcc heterogeneous systems, this causes issues
+ s/$utf8_quote/'/g;
+
+ if (/$check_build_re/) {
+ print OUT;
+ }
+ }
+ close(IN);
+
+ close(OUT);
+
+ success $i;
+}
+
+$#ARGV < 1 or die "ktest.pl version: $VERSION\n usage: ktest.pl config-file\n";
+
+if ($#ARGV == 0) {
+ $ktest_config = $ARGV[0];
+ if (! -f $ktest_config) {
+ print "$ktest_config does not exist.\n";
+ if (!read_yn "Create it?") {
+ exit 0;
+ }
+ }
+} else {
+ $ktest_config = "ktest.conf";
+}
+
+if (! -f $ktest_config) {
+ $newconfig = 1;
+ get_test_case;
+ open(OUT, ">$ktest_config") or die "Can not create $ktest_config";
+ print OUT << "EOF"
+# Generated by ktest.pl
+#
+
+# PWD is a ktest.pl variable that will result in the process working
+# directory that ktest.pl is executed in.
+
+# THIS_DIR is automatically assigned the PWD of the path that generated
+# the config file. It is best to use this variable when assigning other
+# directory paths within this directory. This allows you to easily
+# move the test cases to other locations or to other machines.
+#
+THIS_DIR := $variable{"PWD"}
+
+# Define each test with TEST_START
+# The config options below it will override the defaults
+TEST_START
+TEST_TYPE = $default{"TEST_TYPE"}
+
+DEFAULTS
+EOF
+;
+ close(OUT);
+}
+read_config $ktest_config;
+
+if (defined($opt{"LOG_FILE"})) {
+ $opt{"LOG_FILE"} = eval_option("LOG_FILE", $opt{"LOG_FILE"}, -1);
+}
+
+# Append any configs entered in manually to the config file.
+my @new_configs = keys %entered_configs;
+if ($#new_configs >= 0) {
+ print "\nAppending entered in configs to $ktest_config\n";
+ open(OUT, ">>$ktest_config") or die "Can not append to $ktest_config";
+ foreach my $config (@new_configs) {
+ print OUT "$config = $entered_configs{$config}\n";
+ $opt{$config} = process_variables($entered_configs{$config});
+ }
+}
+
+if ($opt{"CLEAR_LOG"} && defined($opt{"LOG_FILE"})) {
+ unlink $opt{"LOG_FILE"};
+}
+
+doprint "\n\nSTARTING AUTOMATED TESTS\n\n";
+
+for (my $i = 0, my $repeat = 1; $i <= $opt{"NUM_TESTS"}; $i += $repeat) {
+
+ if (!$i) {
+ doprint "DEFAULT OPTIONS:\n";
+ } else {
+ doprint "\nTEST $i OPTIONS";
+ if (defined($repeat_tests{$i})) {
+ $repeat = $repeat_tests{$i};
+ doprint " ITERATE $repeat";
+ }
+ doprint "\n";
+ }
+
+ foreach my $option (sort keys %opt) {
+
+ if ($option =~ /\[(\d+)\]$/) {
+ next if ($i != $1);
+ } else {
+ next if ($i);
+ }
+
+ doprint "$option = $opt{$option}\n";
+ }
+}
+
+sub __set_test_option {
+ my ($name, $i) = @_;
+
+ my $option = "$name\[$i\]";
+
+ if (defined($opt{$option})) {
+ return $opt{$option};
+ }
+
+ foreach my $test (keys %repeat_tests) {
+ if ($i >= $test &&
+ $i < $test + $repeat_tests{$test}) {
+ $option = "$name\[$test\]";
+ if (defined($opt{$option})) {
+ return $opt{$option};
+ }
+ }
+ }
+
+ if (defined($opt{$name})) {
+ return $opt{$name};
+ }
+
+ return undef;
+}
+
+sub set_test_option {
+ my ($name, $i) = @_;
+
+ my $option = __set_test_option($name, $i);
+ return $option if (!defined($option));
+
+ return eval_option($name, $option, $i);
+}
+
+# First we need to do is the builds
+for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) {
+
+ # Do not reboot on failing test options
+ $no_reboot = 1;
+ $reboot_success = 0;
+
+ $have_version = 0;
+
+ $iteration = $i;
+
+ undef %force_config;
+
+ my $makecmd = set_test_option("MAKE_CMD", $i);
+
+ $outputdir = set_test_option("OUTPUT_DIR", $i);
+ $builddir = set_test_option("BUILD_DIR", $i);
+
+ chdir $builddir || die "can't change directory to $builddir";
+
+ if (!-d $outputdir) {
+ mkpath($outputdir) or
+ die "can't create $outputdir";
+ }
+
+ $make = "$makecmd O=$outputdir";
+
+ # Load all the options into their mapped variable names
+ foreach my $opt (keys %option_map) {
+ ${$option_map{$opt}} = set_test_option($opt, $i);
+ }
+
+ $start_minconfig_defined = 1;
+
+ # The first test may override the PRE_KTEST option
+ if (defined($pre_ktest) && $i == 1) {
+ doprint "\n";
+ run_command $pre_ktest;
+ }
+
+ # Any test can override the POST_KTEST option
+ # The last test takes precedence.
+ if (defined($post_ktest)) {
+ $final_post_ktest = $post_ktest;
+ }
+
+ if (!defined($start_minconfig)) {
+ $start_minconfig_defined = 0;
+ $start_minconfig = $minconfig;
+ }
+
+ if (!-d $tmpdir) {
+ mkpath($tmpdir) or
+ die "can't create $tmpdir";
+ }
+
+ $ENV{"SSH_USER"} = $ssh_user;
+ $ENV{"MACHINE"} = $machine;
+
+ $buildlog = "$tmpdir/buildlog-$machine";
+ $testlog = "$tmpdir/testlog-$machine";
+ $dmesg = "$tmpdir/dmesg-$machine";
+ $output_config = "$outputdir/.config";
+
+ if (!$buildonly) {
+ $target = "$ssh_user\@$machine";
+ if ($reboot_type eq "grub") {
+ dodie "GRUB_MENU not defined" if (!defined($grub_menu));
+ } elsif ($reboot_type eq "grub2") {
+ dodie "GRUB_MENU not defined" if (!defined($grub_menu));
+ dodie "GRUB_FILE not defined" if (!defined($grub_file));
+ } elsif ($reboot_type eq "syslinux") {
+ dodie "SYSLINUX_LABEL not defined" if (!defined($syslinux_label));
+ }
+ }
+
+ my $run_type = $build_type;
+ if ($test_type eq "patchcheck") {
+ $run_type = $patchcheck_type;
+ } elsif ($test_type eq "bisect") {
+ $run_type = $bisect_type;
+ } elsif ($test_type eq "config_bisect") {
+ $run_type = $config_bisect_type;
+ } elsif ($test_type eq "make_min_config") {
+ $run_type = "";
+ } elsif ($test_type eq "make_warnings_file") {
+ $run_type = "";
+ }
+
+ # mistake in config file?
+ if (!defined($run_type)) {
+ $run_type = "ERROR";
+ }
+
+ my $installme = "";
+ $installme = " no_install" if ($no_install);
+
+ doprint "\n\n";
+ doprint "RUNNING TEST $i of $opt{NUM_TESTS} with option $test_type $run_type$installme\n\n";
+
+ if (defined($pre_test)) {
+ run_command $pre_test;
+ }
+
+ unlink $dmesg;
+ unlink $buildlog;
+ unlink $testlog;
+
+ if (defined($addconfig)) {
+ my $min = $minconfig;
+ if (!defined($minconfig)) {
+ $min = "";
+ }
+ run_command "cat $addconfig $min > $tmpdir/add_config" or
+ dodie "Failed to create temp config";
+ $minconfig = "$tmpdir/add_config";
+ }
+
+ if (defined($checkout)) {
+ run_command "git checkout $checkout" or
+ die "failed to checkout $checkout";
+ }
+
+ $no_reboot = 0;
+
+ # A test may opt to not reboot the box
+ if ($reboot_on_success) {
+ $reboot_success = 1;
+ }
+
+ if ($test_type eq "bisect") {
+ bisect $i;
+ next;
+ } elsif ($test_type eq "config_bisect") {
+ config_bisect $i;
+ next;
+ } elsif ($test_type eq "patchcheck") {
+ patchcheck $i;
+ next;
+ } elsif ($test_type eq "make_min_config") {
+ make_min_config $i;
+ next;
+ } elsif ($test_type eq "make_warnings_file") {
+ $no_reboot = 1;
+ make_warnings_file $i;
+ next;
+ }
+
+ if ($build_type ne "nobuild") {
+ build $build_type or next;
+ check_buildlog or next;
+ }
+
+ if ($test_type eq "install") {
+ get_version;
+ install;
+ success $i;
+ next;
+ }
+
+ if ($test_type ne "build") {
+ my $failed = 0;
+ start_monitor_and_boot or $failed = 1;
+
+ if (!$failed && $test_type ne "boot" && defined($run_test)) {
+ do_run_test or $failed = 1;
+ }
+ end_monitor;
+ next if ($failed);
+ }
+
+ success $i;
+}
+
+if (defined($final_post_ktest)) {
+ run_command $final_post_ktest;
+}
+
+if ($opt{"POWEROFF_ON_SUCCESS"}) {
+ halt;
+} elsif ($opt{"REBOOT_ON_SUCCESS"} && !do_not_reboot && $reboot_success) {
+ reboot_to_good;
+} elsif (defined($switch_to_good)) {
+ # still need to get to the good kernel
+ run_command $switch_to_good;
+}
+
+
+doprint "\n $successes of $opt{NUM_TESTS} tests were successful\n\n";
+
+exit 0;
diff --git a/tools/testing/ktest/sample.conf b/tools/testing/ktest/sample.conf
new file mode 100644
index 00000000000..172eec4517f
--- /dev/null
+++ b/tools/testing/ktest/sample.conf
@@ -0,0 +1,1289 @@
+#
+# Config file for ktest.pl
+#
+# Note, all paths must be absolute
+#
+
+# Options set in the beginning of the file are considered to be
+# default options. These options can be overriden by test specific
+# options, with the following exceptions:
+#
+# LOG_FILE
+# CLEAR_LOG
+# POWEROFF_ON_SUCCESS
+# REBOOT_ON_SUCCESS
+#
+# Test specific options are set after the label:
+#
+# TEST_START
+#
+# The options after a TEST_START label are specific to that test.
+# Each TEST_START label will set up a new test. If you want to
+# perform a test more than once, you can add the ITERATE label
+# to it followed by the number of times you want that test
+# to iterate. If the ITERATE is left off, the test will only
+# be performed once.
+#
+# TEST_START ITERATE 10
+#
+# You can skip a test by adding SKIP (before or after the ITERATE
+# and number)
+#
+# TEST_START SKIP
+#
+# TEST_START SKIP ITERATE 10
+#
+# TEST_START ITERATE 10 SKIP
+#
+# The SKIP label causes the options and the test itself to be ignored.
+# This is useful to set up several different tests in one config file, and
+# only enabling the ones you want to use for a current test run.
+#
+# You can add default options anywhere in the file as well
+# with the DEFAULTS tag. This allows you to have default options
+# after the test options to keep the test options at the top
+# of the file. You can even place the DEFAULTS tag between
+# test cases (but not in the middle of a single test case)
+#
+# TEST_START
+# MIN_CONFIG = /home/test/config-test1
+#
+# DEFAULTS
+# MIN_CONFIG = /home/test/config-default
+#
+# TEST_START ITERATE 10
+#
+# The above will run the first test with MIN_CONFIG set to
+# /home/test/config-test-1. Then 10 tests will be executed
+# with MIN_CONFIG with /home/test/config-default.
+#
+# You can also disable defaults with the SKIP option
+#
+# DEFAULTS SKIP
+# MIN_CONFIG = /home/test/config-use-sometimes
+#
+# DEFAULTS
+# MIN_CONFIG = /home/test/config-most-times
+#
+# The above will ignore the first MIN_CONFIG. If you want to
+# use the first MIN_CONFIG, remove the SKIP from the first
+# DEFAULTS tag and add it to the second. Be careful, options
+# may only be declared once per test or default. If you have
+# the same option name under the same test or as default
+# ktest will fail to execute, and no tests will run.
+#
+# DEFAULTS OVERRIDE
+#
+# Options defined in the DEFAULTS section can not be duplicated
+# even if they are defined in two different DEFAULT sections.
+# This is done to catch mistakes where an option is added but
+# the previous option was forgotten about and not commented.
+#
+# The OVERRIDE keyword can be added to a section to allow this
+# section to override other DEFAULT sections values that have
+# been defined previously. It will only override options that
+# have been defined before its use. Options defined later
+# in a non override section will still error. The same option
+# can not be defined in the same section even if that section
+# is marked OVERRIDE.
+#
+#
+#
+# Both TEST_START and DEFAULTS sections can also have the IF keyword
+# The value after the IF must evaluate into a 0 or non 0 positive
+# integer, and can use the config variables (explained below).
+#
+# DEFAULTS IF ${IS_X86_32}
+#
+# The above will process the DEFAULTS section if the config
+# variable IS_X86_32 evaluates to a non zero positive integer
+# otherwise if it evaluates to zero, it will act the same
+# as if the SKIP keyword was used.
+#
+# The ELSE keyword can be used directly after a section with
+# a IF statement.
+#
+# TEST_START IF ${RUN_NET_TESTS}
+# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-network
+#
+# ELSE
+#
+# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-normal
+#
+#
+# The ELSE keyword can also contain an IF statement to allow multiple
+# if then else sections. But all the sections must be either
+# DEFAULT or TEST_START, they can not be a mixture.
+#
+# TEST_START IF ${RUN_NET_TESTS}
+# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-network
+#
+# ELSE IF ${RUN_DISK_TESTS}
+# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-tests
+#
+# ELSE IF ${RUN_CPU_TESTS}
+# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-cpu
+#
+# ELSE
+# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-network
+#
+# The if statement may also have comparisons that will and for
+# == and !=, strings may be used for both sides.
+#
+# BOX_TYPE := x86_32
+#
+# DEFAULTS IF ${BOX_TYPE} == x86_32
+# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-32
+# ELSE
+# BUILD_TYPE = useconfig:${CONFIG_DIR}/config-64
+#
+# The DEFINED keyword can be used by the IF statements too.
+# It returns true if the given config variable or option has been defined
+# or false otherwise.
+#
+#
+# DEFAULTS IF DEFINED USE_CC
+# CC := ${USE_CC}
+# ELSE
+# CC := gcc
+#
+#
+# As well as NOT DEFINED.
+#
+# DEFAULTS IF NOT DEFINED MAKE_CMD
+# MAKE_CMD := make ARCH=x86
+#
+#
+# And/or ops (&&,||) may also be used to make complex conditionals.
+#
+# TEST_START IF (DEFINED ALL_TESTS || ${MYTEST} == boottest) && ${MACHINE} == gandalf
+#
+# Notice the use of parentheses. Without any parentheses the above would be
+# processed the same as:
+#
+# TEST_START IF DEFINED ALL_TESTS || (${MYTEST} == boottest && ${MACHINE} == gandalf)
+#
+#
+#
+# INCLUDE file
+#
+# The INCLUDE keyword may be used in DEFAULT sections. This will
+# read another config file and process that file as well. The included
+# file can include other files, add new test cases or default
+# statements. Config variables will be passed to these files and changes
+# to config variables will be seen by top level config files. Including
+# a file is processed just like the contents of the file was cut and pasted
+# into the top level file, except, that include files that end with
+# TEST_START sections will have that section ended at the end of
+# the include file. That is, an included file is included followed
+# by another DEFAULT keyword.
+#
+# Unlike other files referenced in this config, the file path does not need
+# to be absolute. If the file does not start with '/', then the directory
+# that the current config file was located in is used. If no config by the
+# given name is found there, then the current directory is searched.
+#
+# INCLUDE myfile
+# DEFAULT
+#
+# is the same as:
+#
+# INCLUDE myfile
+#
+# Note, if the include file does not contain a full path, the file is
+# searched first by the location of the original include file, and then
+# by the location that ktest.pl was executed in.
+#
+
+#### Config variables ####
+#
+# This config file can also contain "config variables".
+# These are assigned with ":=" instead of the ktest option
+# assigment "=".
+#
+# The difference between ktest options and config variables
+# is that config variables can be used multiple times,
+# where each instance will override the previous instance.
+# And that they only live at time of processing this config.
+#
+# The advantage to config variables are that they can be used
+# by any option or any other config variables to define thing
+# that you may use over and over again in the options.
+#
+# For example:
+#
+# USER := root
+# TARGET := mybox
+# TEST_CASE := ssh ${USER}@${TARGET} /path/to/my/test
+#
+# TEST_START
+# MIN_CONFIG = config1
+# TEST = ${TEST_CASE}
+#
+# TEST_START
+# MIN_CONFIG = config2
+# TEST = ${TEST_CASE}
+#
+# TEST_CASE := ssh ${USER}@${TARGET} /path/to/my/test2
+#
+# TEST_START
+# MIN_CONFIG = config1
+# TEST = ${TEST_CASE}
+#
+# TEST_START
+# MIN_CONFIG = config2
+# TEST = ${TEST_CASE}
+#
+# TEST_DIR := /home/me/test
+#
+# BUILD_DIR = ${TEST_DIR}/linux.git
+# OUTPUT_DIR = ${TEST_DIR}/test
+#
+# Note, the config variables are evaluated immediately, thus
+# updating TARGET after TEST_CASE has been assigned does nothing
+# to TEST_CASE.
+#
+# As shown in the example, to evaluate a config variable, you
+# use the ${X} convention. Simple $X will not work.
+#
+# If the config variable does not exist, the ${X} will not
+# be evaluated. Thus:
+#
+# MAKE_CMD = PATH=/mypath:${PATH} make
+#
+# If PATH is not a config variable, then the ${PATH} in
+# the MAKE_CMD option will be evaluated by the shell when
+# the MAKE_CMD option is passed into shell processing.
+
+#### Using options in other options ####
+#
+# Options that are defined in the config file may also be used
+# by other options. All options are evaulated at time of
+# use (except that config variables are evaluated at config
+# processing time).
+#
+# If an ktest option is used within another option, instead of
+# typing it again in that option you can simply use the option
+# just like you can config variables.
+#
+# MACHINE = mybox
+#
+# TEST = ssh root@${MACHINE} /path/to/test
+#
+# The option will be used per test case. Thus:
+#
+# TEST_TYPE = test
+# TEST = ssh root@{MACHINE}
+#
+# TEST_START
+# MACHINE = box1
+#
+# TEST_START
+# MACHINE = box2
+#
+# For both test cases, MACHINE will be evaluated at the time
+# of the test case. The first test will run ssh root@box1
+# and the second will run ssh root@box2.
+
+#### Mandatory Default Options ####
+
+# These options must be in the default section, although most
+# may be overridden by test options.
+
+# The machine hostname that you will test
+#MACHINE = target
+
+# The box is expected to have ssh on normal bootup, provide the user
+# (most likely root, since you need privileged operations)
+#SSH_USER = root
+
+# The directory that contains the Linux source code
+#BUILD_DIR = /home/test/linux.git
+
+# The directory that the objects will be built
+# (can not be same as BUILD_DIR)
+#OUTPUT_DIR = /home/test/build/target
+
+# The location of the compiled file to copy to the target
+# (relative to OUTPUT_DIR)
+#BUILD_TARGET = arch/x86/boot/bzImage
+
+# The place to put your image on the test machine
+#TARGET_IMAGE = /boot/vmlinuz-test
+
+# A script or command to reboot the box
+#
+# Here is a digital loggers power switch example
+#POWER_CYCLE = wget --no-proxy -O /dev/null -q --auth-no-challenge 'http://admin:admin@power/outlet?5=CCL'
+#
+# Here is an example to reboot a virtual box on the current host
+# with the name "Guest".
+#POWER_CYCLE = virsh destroy Guest; sleep 5; virsh start Guest
+
+# The script or command that reads the console
+#
+# If you use ttywatch server, something like the following would work.
+#CONSOLE = nc -d localhost 3001
+#
+# For a virtual machine with guest name "Guest".
+#CONSOLE = virsh console Guest
+
+# Signal to send to kill console.
+# ktest.pl will create a child process to monitor the console.
+# When the console is finished, ktest will kill the child process
+# with this signal.
+# (default INT)
+#CLOSE_CONSOLE_SIGNAL = HUP
+
+# Required version ending to differentiate the test
+# from other linux builds on the system.
+#LOCALVERSION = -test
+
+# For REBOOT_TYPE = grub2, you must specify where the grub.cfg
+# file is. This is the file that is searched to find the menu
+# option to boot to with GRUB_REBOOT
+#GRUB_FILE = /boot/grub2/grub.cfg
+
+# The tool for REBOOT_TYPE = grub2 to set the next reboot kernel
+# to boot into (one shot mode).
+# (default grub2_reboot)
+#GRUB_REBOOT = grub2_reboot
+
+# The grub title name for the test kernel to boot
+# (Only mandatory if REBOOT_TYPE = grub or grub2)
+#
+# Note, ktest.pl will not update the grub menu.lst, you need to
+# manually add an option for the test. ktest.pl will search
+# the grub menu.lst for this option to find what kernel to
+# reboot into.
+#
+# For example, if in the /boot/grub/menu.lst the test kernel title has:
+# title Test Kernel
+# kernel vmlinuz-test
+#
+# For grub2, a search of top level "menuentry"s are done. No
+# submenu is searched. The menu is found by searching for the
+# contents of GRUB_MENU in the line that starts with "menuentry".
+# You may want to include the quotes around the option. For example:
+# for: menuentry 'Test Kernel'
+# do a: GRUB_MENU = 'Test Kernel'
+# For customizing, add your entry in /etc/grub.d/40_custom.
+#
+#GRUB_MENU = Test Kernel
+
+# For REBOOT_TYPE = syslinux, the name of the syslinux executable
+# (on the target) to use to set up the next reboot to boot the
+# test kernel.
+# (default extlinux)
+#SYSLINUX = syslinux
+
+# For REBOOT_TYPE = syslinux, the path that is passed to to the
+# syslinux command where syslinux is installed.
+# (default /boot/extlinux)
+#SYSLINUX_PATH = /boot/syslinux
+
+# For REBOOT_TYPE = syslinux, the syslinux label that references the
+# test kernel in the syslinux config file.
+# (default undefined)
+#SYSLINUX_LABEL = "test-kernel"
+
+# A script to reboot the target into the test kernel
+# This and SWITCH_TO_TEST are about the same, except
+# SWITCH_TO_TEST is run even for REBOOT_TYPE = grub.
+# This may be left undefined.
+# (default undefined)
+#REBOOT_SCRIPT =
+
+#### Optional Config Options (all have defaults) ####
+
+# Start a test setup. If you leave this off, all options
+# will be default and the test will run once.
+# This is a label and not really an option (it takes no value).
+# You can append ITERATE and a number after it to iterate the
+# test a number of times, or SKIP to ignore this test.
+#
+#TEST_START
+#TEST_START ITERATE 5
+#TEST_START SKIP
+
+# Have the following options as default again. Used after tests
+# have already been defined by TEST_START. Optionally, you can
+# just define all default options before the first TEST_START
+# and you do not need this option.
+#
+# This is a label and not really an option (it takes no value).
+# You can append SKIP to this label and the options within this
+# section will be ignored.
+#
+# DEFAULTS
+# DEFAULTS SKIP
+
+# If you want to execute some command before the first test runs
+# you can set this option. Note, it can be set as a default option
+# or an option in the first test case. All other test cases will
+# ignore it. If both the default and first test have this option
+# set, then the first test will take precedence.
+#
+# default (undefined)
+#PRE_KTEST = ${SSH} ~/set_up_test
+
+# If you want to execute some command after all the tests have
+# completed, you can set this option. Note, it can be set as a
+# default or any test case can override it. If multiple test cases
+# set this option, then the last test case that set it will take
+# precedence
+#
+# default (undefined)
+#POST_KTEST = ${SSH} ~/dismantle_test
+
+# The default test type (default test)
+# The test types may be:
+# build - only build the kernel, do nothing else
+# install - build and install, but do nothing else (does not reboot)
+# boot - build, install, and boot the kernel
+# test - build, boot and if TEST is set, run the test script
+# (If TEST is not set, it defaults back to boot)
+# bisect - Perform a bisect on the kernel (see BISECT_TYPE below)
+# patchcheck - Do a test on a series of commits in git (see PATCHCHECK below)
+#TEST_TYPE = test
+
+# Test to run if there is a successful boot and TEST_TYPE is test.
+# Must exit with 0 on success and non zero on error
+# default (undefined)
+#TEST = ssh user@machine /root/run_test
+
+# The build type is any make config type or special command
+# (default randconfig)
+# nobuild - skip the clean and build step
+# useconfig:/path/to/config - use the given config and run
+# oldconfig on it.
+# This option is ignored if TEST_TYPE is patchcheck or bisect
+#BUILD_TYPE = randconfig
+
+# The make command (default make)
+# If you are building a 32bit x86 on a 64 bit host
+#MAKE_CMD = CC=i386-gcc AS=i386-as make ARCH=i386
+
+# Any build options for the make of the kernel (not for other makes, like configs)
+# (default "")
+#BUILD_OPTIONS = -j20
+
+# If you need to do some special handling before installing
+# you can add a script with this option.
+# The environment variable KERNEL_VERSION will be set to the
+# kernel version that is used.
+#
+# default (undefined)
+#PRE_INSTALL = ssh user@target rm -rf '/lib/modules/*-test*'
+
+# If you need an initrd, you can add a script or code here to install
+# it. The environment variable KERNEL_VERSION will be set to the
+# kernel version that is used. Remember to add the initrd line
+# to your grub menu.lst file.
+#
+# Here's a couple of examples to use:
+#POST_INSTALL = ssh user@target /sbin/mkinitrd --allow-missing -f /boot/initramfs-test.img $KERNEL_VERSION
+#
+# or on some systems:
+#POST_INSTALL = ssh user@target /sbin/dracut -f /boot/initramfs-test.img $KERNEL_VERSION
+
+# If for some reason you just want to boot the kernel and you do not
+# want the test to install anything new. For example, you may just want
+# to boot test the same kernel over and over and do not want to go through
+# the hassle of installing anything, you can set this option to 1
+# (default 0)
+#NO_INSTALL = 1
+
+# If there is a command that you want to run before the individual test
+# case executes, then you can set this option
+#
+# default (undefined)
+#PRE_TEST = ${SSH} reboot_to_special_kernel
+
+# If there is a command you want to run after the individual test case
+# completes, then you can set this option.
+#
+# default (undefined)
+#POST_TEST = cd ${BUILD_DIR}; git reset --hard
+
+# If there is a script that you require to run before the build is done
+# you can specify it with PRE_BUILD.
+#
+# One example may be if you must add a temporary patch to the build to
+# fix a unrelated bug to perform a patchcheck test. This will apply the
+# patch before each build that is made. Use the POST_BUILD to do a git reset --hard
+# to remove the patch.
+#
+# (default undef)
+#PRE_BUILD = cd ${BUILD_DIR} && patch -p1 < /tmp/temp.patch
+
+# To specify if the test should fail if the PRE_BUILD fails,
+# PRE_BUILD_DIE needs to be set to 1. Otherwise the PRE_BUILD
+# result is ignored.
+# (default 0)
+# PRE_BUILD_DIE = 1
+
+# If there is a script that should run after the build is done
+# you can specify it with POST_BUILD.
+#
+# As the example in PRE_BUILD, POST_BUILD can be used to reset modifications
+# made by the PRE_BUILD.
+#
+# (default undef)
+#POST_BUILD = cd ${BUILD_DIR} && git reset --hard
+
+# To specify if the test should fail if the POST_BUILD fails,
+# POST_BUILD_DIE needs to be set to 1. Otherwise the POST_BUILD
+# result is ignored.
+# (default 0)
+#POST_BUILD_DIE = 1
+
+# Way to reboot the box to the test kernel.
+# Only valid options so far are "grub", "grub2", "syslinux" and "script"
+# (default grub)
+# If you specify grub, it will assume grub version 1
+# and will search in /boot/grub/menu.lst for the title $GRUB_MENU
+# and select that target to reboot to the kernel. If this is not
+# your setup, then specify "script" and have a command or script
+# specified in REBOOT_SCRIPT to boot to the target.
+#
+# For REBOOT_TYPE = grub2, you must define both GRUB_MENU and
+# GRUB_FILE.
+#
+# For REBOOT_TYPE = syslinux, you must define SYSLINUX_LABEL, and
+# perhaps modify SYSLINUX (default extlinux) and SYSLINUX_PATH
+# (default /boot/extlinux)
+#
+# The entry in /boot/grub/menu.lst must be entered in manually.
+# The test will not modify that file.
+#REBOOT_TYPE = grub
+
+# If you are using a machine that doesn't boot with grub, and
+# perhaps gets its kernel from a remote server (tftp), then
+# you can use this option to update the target image with the
+# test image.
+#
+# You could also do the same with POST_INSTALL, but the difference
+# between that option and this option is that POST_INSTALL runs
+# after the install, where this one runs just before a reboot.
+# (default undefined)
+#SWITCH_TO_TEST = cp ${OUTPUT_DIR}/${BUILD_TARGET} ${TARGET_IMAGE}
+
+# If you are using a machine that doesn't boot with grub, and
+# perhaps gets its kernel from a remote server (tftp), then
+# you can use this option to update the target image with the
+# the known good image to reboot safely back into.
+#
+# This option holds a command that will execute before needing
+# to reboot to a good known image.
+# (default undefined)
+#SWITCH_TO_GOOD = ssh ${SSH_USER}/${MACHINE} cp good_image ${TARGET_IMAGE}
+
+# The min config that is needed to build for the machine
+# A nice way to create this is with the following:
+#
+# $ ssh target
+# $ lsmod > mymods
+# $ scp mymods host:/tmp
+# $ exit
+# $ cd linux.git
+# $ rm .config
+# $ make LSMOD=mymods localyesconfig
+# $ grep '^CONFIG' .config > /home/test/config-min
+#
+# If you want even less configs:
+#
+# log in directly to target (do not ssh)
+#
+# $ su
+# # lsmod | cut -d' ' -f1 | xargs rmmod
+#
+# repeat the above several times
+#
+# # lsmod > mymods
+# # reboot
+#
+# May need to reboot to get your network back to copy the mymods
+# to the host, and then remove the previous .config and run the
+# localyesconfig again. The CONFIG_MIN generated like this will
+# not guarantee network activity to the box so the TEST_TYPE of
+# test may fail.
+#
+# You might also want to set:
+# CONFIG_CMDLINE="<your options here>"
+# randconfig may set the above and override your real command
+# line options.
+# (default undefined)
+#MIN_CONFIG = /home/test/config-min
+
+# Sometimes there's options that just break the boot and
+# you do not care about. Here are a few:
+# # CONFIG_STAGING is not set
+# Staging drivers are horrible, and can break the build.
+# # CONFIG_SCSI_DEBUG is not set
+# SCSI_DEBUG may change your root partition
+# # CONFIG_KGDB_SERIAL_CONSOLE is not set
+# KGDB may cause oops waiting for a connection that's not there.
+# This option points to the file containing config options that will be prepended
+# to the MIN_CONFIG (or be the MIN_CONFIG if it is not set)
+#
+# Note, config options in MIN_CONFIG will override these options.
+#
+# (default undefined)
+#ADD_CONFIG = /home/test/config-broken
+
+# The location on the host where to write temp files
+# (default /tmp/ktest/${MACHINE})
+#TMP_DIR = /tmp/ktest/${MACHINE}
+
+# Optional log file to write the status (recommended)
+# Note, this is a DEFAULT section only option.
+# (default undefined)
+#LOG_FILE = /home/test/logfiles/target.log
+
+# Remove old logfile if it exists before starting all tests.
+# Note, this is a DEFAULT section only option.
+# (default 0)
+#CLEAR_LOG = 0
+
+# Line to define a successful boot up in console output.
+# This is what the line contains, not the entire line. If you need
+# the entire line to match, then use regural expression syntax like:
+# (do not add any quotes around it)
+#
+# SUCCESS_LINE = ^MyBox Login:$
+#
+# (default "login:")
+#SUCCESS_LINE = login:
+
+# To speed up between reboots, defining a line that the
+# default kernel produces that represents that the default
+# kernel has successfully booted and can be used to pass
+# a new test kernel to it. Otherwise ktest.pl will wait till
+# SLEEP_TIME to continue.
+# (default undefined)
+#REBOOT_SUCCESS_LINE = login:
+
+# In case the console constantly fills the screen, having
+# a specified time to stop the test after success is recommended.
+# (in seconds)
+# (default 10)
+#STOP_AFTER_SUCCESS = 10
+
+# In case the console constantly fills the screen, having
+# a specified time to stop the test after failure is recommended.
+# (in seconds)
+# (default 60)
+#STOP_AFTER_FAILURE = 60
+
+# In case the console constantly fills the screen, having
+# a specified time to stop the test if it never succeeds nor fails
+# is recommended.
+# Note: this is ignored if a success or failure is detected.
+# (in seconds)
+# (default 600, -1 is to never stop)
+#STOP_TEST_AFTER = 600
+
+# Stop testing if a build fails. If set, the script will end if
+# a failure is detected, otherwise it will save off the .config,
+# dmesg and bootlog in a directory called
+# MACHINE-TEST_TYPE_BUILD_TYPE-fail-yyyymmddhhmmss
+# if the STORE_FAILURES directory is set.
+# (default 1)
+# Note, even if this is set to zero, there are some errors that still
+# stop the tests.
+#DIE_ON_FAILURE = 1
+
+# Directory to store failure directories on failure. If this is not
+# set, DIE_ON_FAILURE=0 will not save off the .config, dmesg and
+# bootlog. This option is ignored if DIE_ON_FAILURE is not set.
+# (default undefined)
+#STORE_FAILURES = /home/test/failures
+
+# Directory to store success directories on success. If this is not
+# set, the .config, dmesg and bootlog will not be saved if a
+# test succeeds.
+# (default undefined)
+#STORE_SUCCESSES = /home/test/successes
+
+# Build without doing a make mrproper, or removing .config
+# (default 0)
+#BUILD_NOCLEAN = 0
+
+# As the test reads the console, after it hits the SUCCESS_LINE
+# the time it waits for the monitor to settle down between reads
+# can usually be lowered.
+# (in seconds) (default 1)
+#BOOTED_TIMEOUT = 1
+
+# The timeout in seconds when we consider the box hung after
+# the console stop producing output. Be sure to leave enough
+# time here to get pass a reboot. Some machines may not produce
+# any console output for a long time during a reboot. You do
+# not want the test to fail just because the system was in
+# the process of rebooting to the test kernel.
+# (default 120)
+#TIMEOUT = 120
+
+# In between tests, a reboot of the box may occur, and this
+# is the time to wait for the console after it stops producing
+# output. Some machines may not produce a large lag on reboot
+# so this should accommodate it.
+# The difference between this and TIMEOUT, is that TIMEOUT happens
+# when rebooting to the test kernel. This sleep time happens
+# after a test has completed and we are about to start running
+# another test. If a reboot to the reliable kernel happens,
+# we wait SLEEP_TIME for the console to stop producing output
+# before starting the next test.
+#
+# You can speed up reboot times even more by setting REBOOT_SUCCESS_LINE.
+# (default 60)
+#SLEEP_TIME = 60
+
+# The time in between bisects to sleep (in seconds)
+# (default 60)
+#BISECT_SLEEP_TIME = 60
+
+# The max wait time (in seconds) for waiting for the console to finish.
+# If for some reason, the console is outputting content without
+# ever finishing, this will cause ktest to get stuck. This
+# option is the max time ktest will wait for the monitor (console)
+# to settle down before continuing.
+# (default 1800)
+#MAX_MONITOR_WAIT
+
+# The time in between patch checks to sleep (in seconds)
+# (default 60)
+#PATCHCHECK_SLEEP_TIME = 60
+
+# Reboot the target box on error (default 0)
+#REBOOT_ON_ERROR = 0
+
+# Power off the target on error (ignored if REBOOT_ON_ERROR is set)
+# Note, this is a DEFAULT section only option.
+# (default 0)
+#POWEROFF_ON_ERROR = 0
+
+# Power off the target after all tests have completed successfully
+# Note, this is a DEFAULT section only option.
+# (default 0)
+#POWEROFF_ON_SUCCESS = 0
+
+# Reboot the target after all test completed successfully (default 1)
+# (ignored if POWEROFF_ON_SUCCESS is set)
+#REBOOT_ON_SUCCESS = 1
+
+# In case there are isses with rebooting, you can specify this
+# to always powercycle after this amount of time after calling
+# reboot.
+# Note, POWERCYCLE_AFTER_REBOOT = 0 does NOT disable it. It just
+# makes it powercycle immediately after rebooting. Do not define
+# it if you do not want it.
+# (default undefined)
+#POWERCYCLE_AFTER_REBOOT = 5
+
+# In case there's isses with halting, you can specify this
+# to always poweroff after this amount of time after calling
+# halt.
+# Note, POWEROFF_AFTER_HALT = 0 does NOT disable it. It just
+# makes it poweroff immediately after halting. Do not define
+# it if you do not want it.
+# (default undefined)
+#POWEROFF_AFTER_HALT = 20
+
+# A script or command to power off the box (default undefined)
+# Needed for POWEROFF_ON_ERROR and SUCCESS
+#
+# Example for digital loggers power switch:
+#POWER_OFF = wget --no-proxy -O /dev/null -q --auth-no-challenge 'http://admin:admin@power/outlet?5=OFF'
+#
+# Example for a virtual guest call "Guest".
+#POWER_OFF = virsh destroy Guest
+
+# To have the build fail on "new" warnings, create a file that
+# contains a list of all known warnings (they must match exactly
+# to the line with 'warning:', 'error:' or 'Error:'. If the option
+# WARNINGS_FILE is set, then that file will be read, and if the
+# build detects a warning, it will examine this file and if the
+# warning does not exist in it, it will fail the build.
+#
+# Note, if this option is defined to a file that does not exist
+# then any warning will fail the build.
+# (see make_warnings_file below)
+#
+# (optional, default undefined)
+#WARNINGS_FILE = ${OUTPUT_DIR}/warnings_file
+
+# The way to execute a command on the target
+# (default ssh $SSH_USER@$MACHINE $SSH_COMMAND";)
+# The variables SSH_USER, MACHINE and SSH_COMMAND are defined
+#SSH_EXEC = ssh $SSH_USER@$MACHINE $SSH_COMMAND";
+
+# The way to copy a file to the target (install and modules)
+# (default scp $SRC_FILE $SSH_USER@$MACHINE:$DST_FILE)
+# The variables SSH_USER, MACHINE are defined by the config
+# SRC_FILE and DST_FILE are ktest internal variables and
+# should only have '$' and not the '${}' notation.
+# (default scp $SRC_FILE ${SSH_USER}@${MACHINE}:$DST_FILE)
+#SCP_TO_TARGET = echo skip scp for $SRC_FILE $DST_FILE
+
+# If install needs to be different than modules, then this
+# option will override the SCP_TO_TARGET for installation.
+# (default ${SCP_TO_TARGET} )
+#SCP_TO_TARGET_INSTALL = scp $SRC_FILE tftp@tftpserver:$DST_FILE
+
+# The nice way to reboot the target
+# (default ssh $SSH_USER@$MACHINE reboot)
+# The variables SSH_USER and MACHINE are defined.
+#REBOOT = ssh $SSH_USER@$MACHINE reboot
+
+# The way triple faults are detected is by testing the kernel
+# banner. If the kernel banner for the kernel we are testing is
+# found, and then later a kernel banner for another kernel version
+# is found, it is considered that we encountered a triple fault,
+# and there is no panic or callback, but simply a reboot.
+# To disable this (because it did a false positive) set the following
+# to 0.
+# (default 1)
+#DETECT_TRIPLE_FAULT = 0
+
+# All options in the config file should be either used by ktest
+# or could be used within a value of another option. If an option
+# in the config file is not used, ktest will warn about it and ask
+# if you want to continue.
+#
+# If you don't care if there are non-used options, enable this
+# option. Be careful though, a non-used option is usually a sign
+# of an option name being typed incorrectly.
+# (default 0)
+#IGNORE_UNUSED = 1
+
+# When testing a kernel that happens to have WARNINGs, and call
+# traces, ktest.pl will detect these and fail a boot or test run
+# due to warnings. By setting this option, ktest will ignore
+# call traces, and will not fail a test if the kernel produces
+# an oops. Use this option with care.
+# (default 0)
+#IGNORE_ERRORS = 1
+
+#### Per test run options ####
+# The following options are only allowed in TEST_START sections.
+# They are ignored in the DEFAULTS sections.
+#
+# All of these are optional and undefined by default, although
+# some of these options are required for TEST_TYPE of patchcheck
+# and bisect.
+#
+#
+# CHECKOUT = branch
+#
+# If the BUILD_DIR is a git repository, then you can set this option
+# to checkout the given branch before running the TEST. If you
+# specify this for the first run, that branch will be used for
+# all preceding tests until a new CHECKOUT is set.
+#
+#
+# TEST_NAME = name
+#
+# If you want the test to have a name that is displayed in
+# the test result banner at the end of the test, then use this
+# option. This is useful to search for the RESULT keyword and
+# not have to translate a test number to a test in the config.
+#
+# For TEST_TYPE = patchcheck
+#
+# This expects the BUILD_DIR to be a git repository, and
+# will checkout the PATCHCHECK_START commit.
+#
+# The option BUILD_TYPE will be ignored.
+#
+# The MIN_CONFIG will be used for all builds of the patchcheck. The build type
+# used for patchcheck is oldconfig.
+#
+# PATCHCHECK_START is required and is the first patch to
+# test (the SHA1 of the commit). You may also specify anything
+# that git checkout allows (branch name, tage, HEAD~3).
+#
+# PATCHCHECK_END is the last patch to check (default HEAD)
+#
+# PATCHCHECK_TYPE is required and is the type of test to run:
+# build, boot, test.
+#
+# Note, the build test will look for warnings, if a warning occurred
+# in a file that a commit touches, the build will fail, unless
+# IGNORE_WARNINGS is set for the given commit's sha1
+#
+# IGNORE_WARNINGS can be used to disable the failure of patchcheck
+# on a particuler commit (SHA1). You can add more than one commit
+# by adding a list of SHA1s that are space delimited.
+#
+# If BUILD_NOCLEAN is set, then make mrproper will not be run on
+# any of the builds, just like all other TEST_TYPE tests. But
+# what makes patchcheck different from the other tests, is if
+# BUILD_NOCLEAN is not set, only the first and last patch run
+# make mrproper. This helps speed up the test.
+#
+# Example:
+# TEST_START
+# TEST_TYPE = patchcheck
+# CHECKOUT = mybranch
+# PATCHCHECK_TYPE = boot
+# PATCHCHECK_START = 747e94ae3d1b4c9bf5380e569f614eb9040b79e7
+# PATCHCHECK_END = HEAD~2
+# IGNORE_WARNINGS = 42f9c6b69b54946ffc0515f57d01dc7f5c0e4712 0c17ca2c7187f431d8ffc79e81addc730f33d128
+#
+#
+#
+# For TEST_TYPE = bisect
+#
+# You can specify a git bisect if the BUILD_DIR is a git repository.
+# The MIN_CONFIG will be used for all builds of the bisect. The build type
+# used for bisecting is oldconfig.
+#
+# The option BUILD_TYPE will be ignored.
+#
+# BISECT_TYPE is the type of test to perform:
+# build - bad fails to build
+# boot - bad builds but fails to boot
+# test - bad boots but fails a test
+#
+# BISECT_GOOD is the commit (SHA1) to label as good (accepts all git good commit types)
+# BISECT_BAD is the commit to label as bad (accepts all git bad commit types)
+#
+# The above three options are required for a bisect operation.
+#
+# BISECT_REPLAY = /path/to/replay/file (optional, default undefined)
+#
+# If an operation failed in the bisect that was not expected to
+# fail. Then the test ends. The state of the BUILD_DIR will be
+# left off at where the failure occurred. You can examine the
+# reason for the failure, and perhaps even find a git commit
+# that would work to continue with. You can run:
+#
+# git bisect log > /path/to/replay/file
+#
+# The adding:
+#
+# BISECT_REPLAY= /path/to/replay/file
+#
+# And running the test again. The test will perform the initial
+# git bisect start, git bisect good, and git bisect bad, and
+# then it will run git bisect replay on this file, before
+# continuing with the bisect.
+#
+# BISECT_START = commit (optional, default undefined)
+#
+# As with BISECT_REPLAY, if the test failed on a commit that
+# just happen to have a bad commit in the middle of the bisect,
+# and you need to skip it. If BISECT_START is defined, it
+# will checkout that commit after doing the initial git bisect start,
+# git bisect good, git bisect bad, and running the git bisect replay
+# if the BISECT_REPLAY is set.
+#
+# BISECT_SKIP = 1 (optional, default 0)
+#
+# If BISECT_TYPE is set to test but the build fails, ktest will
+# simply fail the test and end their. You could use BISECT_REPLAY
+# and BISECT_START to resume after you found a new starting point,
+# or you could set BISECT_SKIP to 1. If BISECT_SKIP is set to 1,
+# when something other than the BISECT_TYPE fails, ktest.pl will
+# run "git bisect skip" and try again.
+#
+# BISECT_FILES = <path> (optional, default undefined)
+#
+# To just run the git bisect on a specific path, set BISECT_FILES.
+# For example:
+#
+# BISECT_FILES = arch/x86 kernel/time
+#
+# Will run the bisect with "git bisect start -- arch/x86 kernel/time"
+#
+# BISECT_REVERSE = 1 (optional, default 0)
+#
+# In those strange instances where it was broken forever
+# and you are trying to find where it started to work!
+# Set BISECT_GOOD to the commit that was last known to fail
+# Set BISECT_BAD to the commit that is known to start working.
+# With BISECT_REVERSE = 1, The test will consider failures as
+# good, and success as bad.
+#
+# BISECT_MANUAL = 1 (optional, default 0)
+#
+# In case there's a problem with automating the bisect for
+# whatever reason. (Can't reboot, want to inspect each iteration)
+# Doing a BISECT_MANUAL will have the test wait for you to
+# tell it if the test passed or failed after each iteration.
+# This is basicall the same as running git bisect yourself
+# but ktest will rebuild and install the kernel for you.
+#
+# BISECT_CHECK = 1 (optional, default 0)
+#
+# Just to be sure the good is good and bad is bad, setting
+# BISECT_CHECK to 1 will start the bisect by first checking
+# out BISECT_BAD and makes sure it fails, then it will check
+# out BISECT_GOOD and makes sure it succeeds before starting
+# the bisect (it works for BISECT_REVERSE too).
+#
+# You can limit the test to just check BISECT_GOOD or
+# BISECT_BAD with BISECT_CHECK = good or
+# BISECT_CHECK = bad, respectively.
+#
+# BISECT_TRIES = 5 (optional, default 1)
+#
+# For those cases that it takes several tries to hit a bug,
+# the BISECT_TRIES is useful. It is the number of times the
+# test is ran before it says the kernel is good. The first failure
+# will stop trying and mark the current SHA1 as bad.
+#
+# Note, as with all race bugs, there's no guarantee that if
+# it succeeds, it is really a good bisect. But it helps in case
+# the bug is some what reliable.
+#
+# You can set BISECT_TRIES to zero, and all tests will be considered
+# good, unless you also set BISECT_MANUAL.
+#
+# BISECT_RET_GOOD = 0 (optional, default undefined)
+#
+# In case the specificed test returns something other than just
+# 0 for good, and non-zero for bad, you can override 0 being
+# good by defining BISECT_RET_GOOD.
+#
+# BISECT_RET_BAD = 1 (optional, default undefined)
+#
+# In case the specificed test returns something other than just
+# 0 for good, and non-zero for bad, you can override non-zero being
+# bad by defining BISECT_RET_BAD.
+#
+# BISECT_RET_ABORT = 255 (optional, default undefined)
+#
+# If you need to abort the bisect if the test discovers something
+# that was wrong, you can define BISECT_RET_ABORT to be the error
+# code returned by the test in order to abort the bisect.
+#
+# BISECT_RET_SKIP = 2 (optional, default undefined)
+#
+# If the test detects that the current commit is neither good
+# nor bad, but something else happened (another bug detected)
+# you can specify BISECT_RET_SKIP to an error code that the
+# test returns when it should skip the current commit.
+#
+# BISECT_RET_DEFAULT = good (optional, default undefined)
+#
+# You can override the default of what to do when the above
+# options are not hit. This may be one of, "good", "bad",
+# "abort" or "skip" (without the quotes).
+#
+# Note, if you do not define any of the previous BISECT_RET_*
+# and define BISECT_RET_DEFAULT, all bisects results will do
+# what the BISECT_RET_DEFAULT has.
+#
+#
+# Example:
+# TEST_START
+# TEST_TYPE = bisect
+# BISECT_GOOD = v2.6.36
+# BISECT_BAD = b5153163ed580e00c67bdfecb02b2e3843817b3e
+# BISECT_TYPE = build
+# MIN_CONFIG = /home/test/config-bisect
+#
+#
+#
+# For TEST_TYPE = config_bisect
+#
+# In those cases that you have two different configs. One of them
+# work, the other does not, and you do not know what config causes
+# the problem.
+# The TEST_TYPE config_bisect will bisect the bad config looking for
+# what config causes the failure.
+#
+# The way it works is this:
+#
+# First it finds a config to work with. Since a different version, or
+# MIN_CONFIG may cause different dependecies, it must run through this
+# preparation.
+#
+# Overwrites any config set in the bad config with a config set in
+# either the MIN_CONFIG or ADD_CONFIG. Thus, make sure these configs
+# are minimal and do not disable configs you want to test:
+# (ie. # CONFIG_FOO is not set).
+#
+# An oldconfig is run on the bad config and any new config that
+# appears will be added to the configs to test.
+#
+# Finally, it generates a config with the above result and runs it
+# again through make oldconfig to produce a config that should be
+# satisfied by kconfig.
+#
+# Then it starts the bisect.
+#
+# The configs to test are cut in half. If all the configs in this
+# half depend on a config in the other half, then the other half
+# is tested instead. If no configs are enabled by either half, then
+# this means a circular dependency exists and the test fails.
+#
+# A config is created with the test half, and the bisect test is run.
+#
+# If the bisect succeeds, then all configs in the generated config
+# are removed from the configs to test and added to the configs that
+# will be enabled for all builds (they will be enabled, but not be part
+# of the configs to examine).
+#
+# If the bisect fails, then all test configs that were not enabled by
+# the config file are removed from the test. These configs will not
+# be enabled in future tests. Since current config failed, we consider
+# this to be a subset of the config that we started with.
+#
+# When we are down to one config, it is considered the bad config.
+#
+# Note, the config chosen may not be the true bad config. Due to
+# dependencies and selections of the kbuild system, mulitple
+# configs may be needed to cause a failure. If you disable the
+# config that was found and restart the test, if the test fails
+# again, it is recommended to rerun the config_bisect with a new
+# bad config without the found config enabled.
+#
+# The option BUILD_TYPE will be ignored.
+#
+# CONFIG_BISECT_TYPE is the type of test to perform:
+# build - bad fails to build
+# boot - bad builds but fails to boot
+# test - bad boots but fails a test
+#
+# CONFIG_BISECT is the config that failed to boot
+#
+# If BISECT_MANUAL is set, it will pause between iterations.
+# This is useful to use just ktest.pl just for the config bisect.
+# If you set it to build, it will run the bisect and you can
+# control what happens in between iterations. It will ask you if
+# the test succeeded or not and continue the config bisect.
+#
+# CONFIG_BISECT_GOOD (optional)
+# If you have a good config to start with, then you
+# can specify it with CONFIG_BISECT_GOOD. Otherwise
+# the MIN_CONFIG is the base.
+#
+# CONFIG_BISECT_CHECK (optional)
+# Set this to 1 if you want to confirm that the config ktest
+# generates (the bad config with the min config) is still bad.
+# It may be that the min config fixes what broke the bad config
+# and the test will not return a result.
+#
+# Example:
+# TEST_START
+# TEST_TYPE = config_bisect
+# CONFIG_BISECT_TYPE = build
+# CONFIG_BISECT = /home/test/config-bad
+# MIN_CONFIG = /home/test/config-min
+# BISECT_MANUAL = 1
+#
+#
+#
+# For TEST_TYPE = make_min_config
+#
+# After doing a make localyesconfig, your kernel configuration may
+# not be the most useful minimum configuration. Having a true minimum
+# config that you can use against other configs is very useful if
+# someone else has a config that breaks on your code. By only forcing
+# those configurations that are truly required to boot your machine
+# will give you less of a chance that one of your set configurations
+# will make the bug go away. This will give you a better chance to
+# be able to reproduce the reported bug matching the broken config.
+#
+# Note, this does take some time, and may require you to run the
+# test over night, or perhaps over the weekend. But it also allows
+# you to interrupt it, and gives you the current minimum config
+# that was found till that time.
+#
+# Note, this test automatically assumes a BUILD_TYPE of oldconfig
+# and its test type acts like boot.
+# TODO: add a test version that makes the config do more than just
+# boot, like having network access.
+#
+# To save time, the test does not just grab any option and test
+# it. The Kconfig files are examined to determine the dependencies
+# of the configs. If a config is chosen that depends on another
+# config, that config will be checked first. By checking the
+# parents first, we can eliminate whole groups of configs that
+# may have been enabled.
+#
+# For example, if a USB device config is chosen and depends on CONFIG_USB,
+# the CONFIG_USB will be tested before the device. If CONFIG_USB is
+# found not to be needed, it, as well as all configs that depend on
+# it, will be disabled and removed from the current min_config.
+#
+# OUTPUT_MIN_CONFIG is the path and filename of the file that will
+# be created from the MIN_CONFIG. If you interrupt the test, set
+# this file as your new min config, and use it to continue the test.
+# This file does not need to exist on start of test.
+# This file is not created until a config is found that can be removed.
+# If this file exists, you will be prompted if you want to use it
+# as the min_config (overriding MIN_CONFIG) if START_MIN_CONFIG
+# is not defined.
+# (required field)
+#
+# START_MIN_CONFIG is the config to use to start the test with.
+# you can set this as the same OUTPUT_MIN_CONFIG, but if you do
+# the OUTPUT_MIN_CONFIG file must exist.
+# (default MIN_CONFIG)
+#
+# IGNORE_CONFIG is used to specify a config file that has configs that
+# you already know must be set. Configs are written here that have
+# been tested and proved to be required. It is best to define this
+# file if you intend on interrupting the test and running it where
+# it left off. New configs that it finds will be written to this file
+# and will not be tested again in later runs.
+# (optional)
+#
+# MIN_CONFIG_TYPE can be either 'boot' or 'test'. With 'boot' it will
+# test if the created config can just boot the machine. If this is
+# set to 'test', then the TEST option must be defined and the created
+# config will not only boot the target, but also make sure that the
+# config lets the test succeed. This is useful to make sure the final
+# config that is generated allows network activity (ssh).
+# (optional)
+#
+# USE_OUTPUT_MIN_CONFIG set this to 1 if you do not want to be prompted
+# about using the OUTPUT_MIN_CONFIG as the MIN_CONFIG as the starting
+# point. Set it to 0 if you want to always just use the given MIN_CONFIG.
+# If it is not defined, it will prompt you to pick which config
+# to start with (MIN_CONFIG or OUTPUT_MIN_CONFIG).
+#
+# Example:
+#
+# TEST_TYPE = make_min_config
+# OUTPUT_MIN_CONFIG = /path/to/config-new-min
+# START_MIN_CONFIG = /path/to/config-min
+# IGNORE_CONFIG = /path/to/config-tested
+# MIN_CONFIG_TYPE = test
+# TEST = ssh ${USER}@${MACHINE} echo hi
+#
+#
+#
+#
+# For TEST_TYPE = make_warnings_file
+#
+# If you want the build to fail when a new warning is discovered
+# you set the WARNINGS_FILE to point to a file of known warnings.
+#
+# The test "make_warnings_file" will let you create a new warnings
+# file before you run other tests, like patchcheck.
+#
+# What this test does is to run just a build, you still need to
+# specify BUILD_TYPE to tell the test what type of config to use.
+# A BUILD_TYPE of nobuild will fail this test.
+#
+# The test will do the build and scan for all warnings. Any warning
+# it discovers will be saved in the WARNINGS_FILE (required) option.
+#
+# It is recommended (but not necessary) to make sure BUILD_NOCLEAN is
+# off, so that a full build is done (make mrproper is performed).
+# That way, all warnings will be captured.
+#
+# Example:
+#
+# TEST_TYPE = make_warnings_file
+# WARNINGS_FILE = ${OUTPUT_DIR}
+# BUILD_TYPE = useconfig:oldconfig
+# CHECKOUT = v3.8
+# BUILD_NOCLEAN = 0
+#
diff --git a/tools/testing/selftests/Makefile b/tools/testing/selftests/Makefile
new file mode 100644
index 00000000000..e66e710cc59
--- /dev/null
+++ b/tools/testing/selftests/Makefile
@@ -0,0 +1,28 @@
+TARGETS = breakpoints
+TARGETS += cpu-hotplug
+TARGETS += efivarfs
+TARGETS += kcmp
+TARGETS += memory-hotplug
+TARGETS += mqueue
+TARGETS += net
+TARGETS += ptrace
+TARGETS += timers
+TARGETS += vm
+TARGETS += powerpc
+TARGETS += user
+TARGETS += sysctl
+
+all:
+ for TARGET in $(TARGETS); do \
+ make -C $$TARGET; \
+ done;
+
+run_tests: all
+ for TARGET in $(TARGETS); do \
+ make -C $$TARGET run_tests; \
+ done;
+
+clean:
+ for TARGET in $(TARGETS); do \
+ make -C $$TARGET clean; \
+ done;
diff --git a/tools/testing/selftests/README.txt b/tools/testing/selftests/README.txt
new file mode 100644
index 00000000000..5e2faf9c55d
--- /dev/null
+++ b/tools/testing/selftests/README.txt
@@ -0,0 +1,42 @@
+Linux Kernel Selftests
+
+The kernel contains a set of "self tests" under the tools/testing/selftests/
+directory. These are intended to be small unit tests to exercise individual
+code paths in the kernel.
+
+Running the selftests
+=====================
+
+To build the tests:
+
+ $ make -C tools/testing/selftests
+
+
+To run the tests:
+
+ $ make -C tools/testing/selftests run_tests
+
+- note that some tests will require root privileges.
+
+
+To run only tests targetted for a single subsystem:
+
+ $ make -C tools/testing/selftests TARGETS=cpu-hotplug run_tests
+
+See the top-level tools/testing/selftests/Makefile for the list of all possible
+targets.
+
+
+Contributing new tests
+======================
+
+In general, the rules for for selftests are
+
+ * Do as much as you can if you're not root;
+
+ * Don't take too long;
+
+ * Don't break the build on any architecture, and
+
+ * Don't cause the top-level "make run_tests" to fail if your feature is
+ unconfigured.
diff --git a/tools/testing/selftests/breakpoints/Makefile b/tools/testing/selftests/breakpoints/Makefile
new file mode 100644
index 00000000000..e18b42b254a
--- /dev/null
+++ b/tools/testing/selftests/breakpoints/Makefile
@@ -0,0 +1,23 @@
+# Taken from perf makefile
+uname_M := $(shell uname -m 2>/dev/null || echo not)
+ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/)
+ifeq ($(ARCH),i386)
+ ARCH := x86
+endif
+ifeq ($(ARCH),x86_64)
+ ARCH := x86
+endif
+
+
+all:
+ifeq ($(ARCH),x86)
+ gcc breakpoint_test.c -o breakpoint_test
+else
+ echo "Not an x86 target, can't build breakpoints selftests"
+endif
+
+run_tests:
+ @./breakpoint_test || echo "breakpoints selftests: [FAIL]"
+
+clean:
+ rm -fr breakpoint_test
diff --git a/tools/testing/selftests/breakpoints/breakpoint_test.c b/tools/testing/selftests/breakpoints/breakpoint_test.c
new file mode 100644
index 00000000000..a0743f3b2b5
--- /dev/null
+++ b/tools/testing/selftests/breakpoints/breakpoint_test.c
@@ -0,0 +1,394 @@
+/*
+ * Copyright (C) 2011 Red Hat, Inc., Frederic Weisbecker <fweisbec@redhat.com>
+ *
+ * Licensed under the terms of the GNU GPL License version 2
+ *
+ * Selftests for breakpoints (and more generally the do_debug() path) in x86.
+ */
+
+
+#include <sys/ptrace.h>
+#include <unistd.h>
+#include <stddef.h>
+#include <sys/user.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+
+
+/* Breakpoint access modes */
+enum {
+ BP_X = 1,
+ BP_RW = 2,
+ BP_W = 4,
+};
+
+static pid_t child_pid;
+
+/*
+ * Ensures the child and parent are always "talking" about
+ * the same test sequence. (ie: that we haven't forgotten
+ * to call check_trapped() somewhere).
+ */
+static int nr_tests;
+
+static void set_breakpoint_addr(void *addr, int n)
+{
+ int ret;
+
+ ret = ptrace(PTRACE_POKEUSER, child_pid,
+ offsetof(struct user, u_debugreg[n]), addr);
+ if (ret) {
+ perror("Can't set breakpoint addr\n");
+ exit(-1);
+ }
+}
+
+static void toggle_breakpoint(int n, int type, int len,
+ int local, int global, int set)
+{
+ int ret;
+
+ int xtype, xlen;
+ unsigned long vdr7, dr7;
+
+ switch (type) {
+ case BP_X:
+ xtype = 0;
+ break;
+ case BP_W:
+ xtype = 1;
+ break;
+ case BP_RW:
+ xtype = 3;
+ break;
+ }
+
+ switch (len) {
+ case 1:
+ xlen = 0;
+ break;
+ case 2:
+ xlen = 4;
+ break;
+ case 4:
+ xlen = 0xc;
+ break;
+ case 8:
+ xlen = 8;
+ break;
+ }
+
+ dr7 = ptrace(PTRACE_PEEKUSER, child_pid,
+ offsetof(struct user, u_debugreg[7]), 0);
+
+ vdr7 = (xlen | xtype) << 16;
+ vdr7 <<= 4 * n;
+
+ if (local) {
+ vdr7 |= 1 << (2 * n);
+ vdr7 |= 1 << 8;
+ }
+ if (global) {
+ vdr7 |= 2 << (2 * n);
+ vdr7 |= 1 << 9;
+ }
+
+ if (set)
+ dr7 |= vdr7;
+ else
+ dr7 &= ~vdr7;
+
+ ret = ptrace(PTRACE_POKEUSER, child_pid,
+ offsetof(struct user, u_debugreg[7]), dr7);
+ if (ret) {
+ perror("Can't set dr7");
+ exit(-1);
+ }
+}
+
+/* Dummy variables to test read/write accesses */
+static unsigned long long dummy_var[4];
+
+/* Dummy functions to test execution accesses */
+static void dummy_func(void) { }
+static void dummy_func1(void) { }
+static void dummy_func2(void) { }
+static void dummy_func3(void) { }
+
+static void (*dummy_funcs[])(void) = {
+ dummy_func,
+ dummy_func1,
+ dummy_func2,
+ dummy_func3,
+};
+
+static int trapped;
+
+static void check_trapped(void)
+{
+ /*
+ * If we haven't trapped, wake up the parent
+ * so that it notices the failure.
+ */
+ if (!trapped)
+ kill(getpid(), SIGUSR1);
+ trapped = 0;
+
+ nr_tests++;
+}
+
+static void write_var(int len)
+{
+ char *pcval; short *psval; int *pival; long long *plval;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ switch (len) {
+ case 1:
+ pcval = (char *)&dummy_var[i];
+ *pcval = 0xff;
+ break;
+ case 2:
+ psval = (short *)&dummy_var[i];
+ *psval = 0xffff;
+ break;
+ case 4:
+ pival = (int *)&dummy_var[i];
+ *pival = 0xffffffff;
+ break;
+ case 8:
+ plval = (long long *)&dummy_var[i];
+ *plval = 0xffffffffffffffffLL;
+ break;
+ }
+ check_trapped();
+ }
+}
+
+static void read_var(int len)
+{
+ char cval; short sval; int ival; long long lval;
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ switch (len) {
+ case 1:
+ cval = *(char *)&dummy_var[i];
+ break;
+ case 2:
+ sval = *(short *)&dummy_var[i];
+ break;
+ case 4:
+ ival = *(int *)&dummy_var[i];
+ break;
+ case 8:
+ lval = *(long long *)&dummy_var[i];
+ break;
+ }
+ check_trapped();
+ }
+}
+
+/*
+ * Do the r/w/x accesses to trigger the breakpoints. And run
+ * the usual traps.
+ */
+static void trigger_tests(void)
+{
+ int len, local, global, i;
+ char val;
+ int ret;
+
+ ret = ptrace(PTRACE_TRACEME, 0, NULL, 0);
+ if (ret) {
+ perror("Can't be traced?\n");
+ return;
+ }
+
+ /* Wake up father so that it sets up the first test */
+ kill(getpid(), SIGUSR1);
+
+ /* Test instruction breakpoints */
+ for (local = 0; local < 2; local++) {
+ for (global = 0; global < 2; global++) {
+ if (!local && !global)
+ continue;
+
+ for (i = 0; i < 4; i++) {
+ dummy_funcs[i]();
+ check_trapped();
+ }
+ }
+ }
+
+ /* Test write watchpoints */
+ for (len = 1; len <= sizeof(long); len <<= 1) {
+ for (local = 0; local < 2; local++) {
+ for (global = 0; global < 2; global++) {
+ if (!local && !global)
+ continue;
+ write_var(len);
+ }
+ }
+ }
+
+ /* Test read/write watchpoints (on read accesses) */
+ for (len = 1; len <= sizeof(long); len <<= 1) {
+ for (local = 0; local < 2; local++) {
+ for (global = 0; global < 2; global++) {
+ if (!local && !global)
+ continue;
+ read_var(len);
+ }
+ }
+ }
+
+ /* Icebp trap */
+ asm(".byte 0xf1\n");
+ check_trapped();
+
+ /* Int 3 trap */
+ asm("int $3\n");
+ check_trapped();
+
+ kill(getpid(), SIGUSR1);
+}
+
+static void check_success(const char *msg)
+{
+ const char *msg2;
+ int child_nr_tests;
+ int status;
+
+ /* Wait for the child to SIGTRAP */
+ wait(&status);
+
+ msg2 = "Failed";
+
+ if (WSTOPSIG(status) == SIGTRAP) {
+ child_nr_tests = ptrace(PTRACE_PEEKDATA, child_pid,
+ &nr_tests, 0);
+ if (child_nr_tests == nr_tests)
+ msg2 = "Ok";
+ if (ptrace(PTRACE_POKEDATA, child_pid, &trapped, 1)) {
+ perror("Can't poke\n");
+ exit(-1);
+ }
+ }
+
+ nr_tests++;
+
+ printf("%s [%s]\n", msg, msg2);
+}
+
+static void launch_instruction_breakpoints(char *buf, int local, int global)
+{
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ set_breakpoint_addr(dummy_funcs[i], i);
+ toggle_breakpoint(i, BP_X, 1, local, global, 1);
+ ptrace(PTRACE_CONT, child_pid, NULL, 0);
+ sprintf(buf, "Test breakpoint %d with local: %d global: %d",
+ i, local, global);
+ check_success(buf);
+ toggle_breakpoint(i, BP_X, 1, local, global, 0);
+ }
+}
+
+static void launch_watchpoints(char *buf, int mode, int len,
+ int local, int global)
+{
+ const char *mode_str;
+ int i;
+
+ if (mode == BP_W)
+ mode_str = "write";
+ else
+ mode_str = "read";
+
+ for (i = 0; i < 4; i++) {
+ set_breakpoint_addr(&dummy_var[i], i);
+ toggle_breakpoint(i, mode, len, local, global, 1);
+ ptrace(PTRACE_CONT, child_pid, NULL, 0);
+ sprintf(buf, "Test %s watchpoint %d with len: %d local: "
+ "%d global: %d", mode_str, i, len, local, global);
+ check_success(buf);
+ toggle_breakpoint(i, mode, len, local, global, 0);
+ }
+}
+
+/* Set the breakpoints and check the child successfully trigger them */
+static void launch_tests(void)
+{
+ char buf[1024];
+ int len, local, global, i;
+
+ /* Instruction breakpoints */
+ for (local = 0; local < 2; local++) {
+ for (global = 0; global < 2; global++) {
+ if (!local && !global)
+ continue;
+ launch_instruction_breakpoints(buf, local, global);
+ }
+ }
+
+ /* Write watchpoint */
+ for (len = 1; len <= sizeof(long); len <<= 1) {
+ for (local = 0; local < 2; local++) {
+ for (global = 0; global < 2; global++) {
+ if (!local && !global)
+ continue;
+ launch_watchpoints(buf, BP_W, len,
+ local, global);
+ }
+ }
+ }
+
+ /* Read-Write watchpoint */
+ for (len = 1; len <= sizeof(long); len <<= 1) {
+ for (local = 0; local < 2; local++) {
+ for (global = 0; global < 2; global++) {
+ if (!local && !global)
+ continue;
+ launch_watchpoints(buf, BP_RW, len,
+ local, global);
+ }
+ }
+ }
+
+ /* Icebp traps */
+ ptrace(PTRACE_CONT, child_pid, NULL, 0);
+ check_success("Test icebp");
+
+ /* Int 3 traps */
+ ptrace(PTRACE_CONT, child_pid, NULL, 0);
+ check_success("Test int 3 trap");
+
+ ptrace(PTRACE_CONT, child_pid, NULL, 0);
+}
+
+int main(int argc, char **argv)
+{
+ pid_t pid;
+ int ret;
+
+ pid = fork();
+ if (!pid) {
+ trigger_tests();
+ return 0;
+ }
+
+ child_pid = pid;
+
+ wait(NULL);
+
+ launch_tests();
+
+ wait(NULL);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/cpu-hotplug/Makefile b/tools/testing/selftests/cpu-hotplug/Makefile
new file mode 100644
index 00000000000..790c23a9db4
--- /dev/null
+++ b/tools/testing/selftests/cpu-hotplug/Makefile
@@ -0,0 +1,6 @@
+all:
+
+run_tests:
+ @/bin/bash ./on-off-test.sh || echo "cpu-hotplug selftests: [FAIL]"
+
+clean:
diff --git a/tools/testing/selftests/cpu-hotplug/on-off-test.sh b/tools/testing/selftests/cpu-hotplug/on-off-test.sh
new file mode 100644
index 00000000000..bdde7cf428b
--- /dev/null
+++ b/tools/testing/selftests/cpu-hotplug/on-off-test.sh
@@ -0,0 +1,221 @@
+#!/bin/bash
+
+SYSFS=
+
+prerequisite()
+{
+ msg="skip all tests:"
+
+ if [ $UID != 0 ]; then
+ echo $msg must be run as root >&2
+ exit 0
+ fi
+
+ SYSFS=`mount -t sysfs | head -1 | awk '{ print $3 }'`
+
+ if [ ! -d "$SYSFS" ]; then
+ echo $msg sysfs is not mounted >&2
+ exit 0
+ fi
+
+ if ! ls $SYSFS/devices/system/cpu/cpu* > /dev/null 2>&1; then
+ echo $msg cpu hotplug is not supported >&2
+ exit 0
+ fi
+}
+
+#
+# list all hot-pluggable CPUs
+#
+hotpluggable_cpus()
+{
+ local state=${1:-.\*}
+
+ for cpu in $SYSFS/devices/system/cpu/cpu*; do
+ if [ -f $cpu/online ] && grep -q $state $cpu/online; then
+ echo ${cpu##/*/cpu}
+ fi
+ done
+}
+
+hotplaggable_offline_cpus()
+{
+ hotpluggable_cpus 0
+}
+
+hotpluggable_online_cpus()
+{
+ hotpluggable_cpus 1
+}
+
+cpu_is_online()
+{
+ grep -q 1 $SYSFS/devices/system/cpu/cpu$1/online
+}
+
+cpu_is_offline()
+{
+ grep -q 0 $SYSFS/devices/system/cpu/cpu$1/online
+}
+
+online_cpu()
+{
+ echo 1 > $SYSFS/devices/system/cpu/cpu$1/online
+}
+
+offline_cpu()
+{
+ echo 0 > $SYSFS/devices/system/cpu/cpu$1/online
+}
+
+online_cpu_expect_success()
+{
+ local cpu=$1
+
+ if ! online_cpu $cpu; then
+ echo $FUNCNAME $cpu: unexpected fail >&2
+ elif ! cpu_is_online $cpu; then
+ echo $FUNCNAME $cpu: unexpected offline >&2
+ fi
+}
+
+online_cpu_expect_fail()
+{
+ local cpu=$1
+
+ if online_cpu $cpu 2> /dev/null; then
+ echo $FUNCNAME $cpu: unexpected success >&2
+ elif ! cpu_is_offline $cpu; then
+ echo $FUNCNAME $cpu: unexpected online >&2
+ fi
+}
+
+offline_cpu_expect_success()
+{
+ local cpu=$1
+
+ if ! offline_cpu $cpu; then
+ echo $FUNCNAME $cpu: unexpected fail >&2
+ elif ! cpu_is_offline $cpu; then
+ echo $FUNCNAME $cpu: unexpected offline >&2
+ fi
+}
+
+offline_cpu_expect_fail()
+{
+ local cpu=$1
+
+ if offline_cpu $cpu 2> /dev/null; then
+ echo $FUNCNAME $cpu: unexpected success >&2
+ elif ! cpu_is_online $cpu; then
+ echo $FUNCNAME $cpu: unexpected offline >&2
+ fi
+}
+
+error=-12
+priority=0
+
+while getopts e:hp: opt; do
+ case $opt in
+ e)
+ error=$OPTARG
+ ;;
+ h)
+ echo "Usage $0 [ -e errno ] [ -p notifier-priority ]"
+ exit
+ ;;
+ p)
+ priority=$OPTARG
+ ;;
+ esac
+done
+
+if ! [ "$error" -ge -4095 -a "$error" -lt 0 ]; then
+ echo "error code must be -4095 <= errno < 0" >&2
+ exit 1
+fi
+
+prerequisite
+
+#
+# Online all hot-pluggable CPUs
+#
+for cpu in `hotplaggable_offline_cpus`; do
+ online_cpu_expect_success $cpu
+done
+
+#
+# Offline all hot-pluggable CPUs
+#
+for cpu in `hotpluggable_online_cpus`; do
+ offline_cpu_expect_success $cpu
+done
+
+#
+# Online all hot-pluggable CPUs again
+#
+for cpu in `hotplaggable_offline_cpus`; do
+ online_cpu_expect_success $cpu
+done
+
+#
+# Test with cpu notifier error injection
+#
+
+DEBUGFS=`mount -t debugfs | head -1 | awk '{ print $3 }'`
+NOTIFIER_ERR_INJECT_DIR=$DEBUGFS/notifier-error-inject/cpu
+
+prerequisite_extra()
+{
+ msg="skip extra tests:"
+
+ /sbin/modprobe -q -r cpu-notifier-error-inject
+ /sbin/modprobe -q cpu-notifier-error-inject priority=$priority
+
+ if [ ! -d "$DEBUGFS" ]; then
+ echo $msg debugfs is not mounted >&2
+ exit 0
+ fi
+
+ if [ ! -d $NOTIFIER_ERR_INJECT_DIR ]; then
+ echo $msg cpu-notifier-error-inject module is not available >&2
+ exit 0
+ fi
+}
+
+prerequisite_extra
+
+#
+# Offline all hot-pluggable CPUs
+#
+echo 0 > $NOTIFIER_ERR_INJECT_DIR/actions/CPU_DOWN_PREPARE/error
+for cpu in `hotpluggable_online_cpus`; do
+ offline_cpu_expect_success $cpu
+done
+
+#
+# Test CPU hot-add error handling (offline => online)
+#
+echo $error > $NOTIFIER_ERR_INJECT_DIR/actions/CPU_UP_PREPARE/error
+for cpu in `hotplaggable_offline_cpus`; do
+ online_cpu_expect_fail $cpu
+done
+
+#
+# Online all hot-pluggable CPUs
+#
+echo 0 > $NOTIFIER_ERR_INJECT_DIR/actions/CPU_UP_PREPARE/error
+for cpu in `hotplaggable_offline_cpus`; do
+ online_cpu_expect_success $cpu
+done
+
+#
+# Test CPU hot-remove error handling (online => offline)
+#
+echo $error > $NOTIFIER_ERR_INJECT_DIR/actions/CPU_DOWN_PREPARE/error
+for cpu in `hotpluggable_online_cpus`; do
+ offline_cpu_expect_fail $cpu
+done
+
+echo 0 > $NOTIFIER_ERR_INJECT_DIR/actions/CPU_DOWN_PREPARE/error
+/sbin/modprobe -q -r cpu-notifier-error-inject
diff --git a/tools/testing/selftests/efivarfs/Makefile b/tools/testing/selftests/efivarfs/Makefile
new file mode 100644
index 00000000000..29e8c6bc81b
--- /dev/null
+++ b/tools/testing/selftests/efivarfs/Makefile
@@ -0,0 +1,12 @@
+CC = $(CROSS_COMPILE)gcc
+CFLAGS = -Wall
+
+test_objs = open-unlink create-read
+
+all: $(test_objs)
+
+run_tests: all
+ @/bin/bash ./efivarfs.sh || echo "efivarfs selftests: [FAIL]"
+
+clean:
+ rm -f $(test_objs)
diff --git a/tools/testing/selftests/efivarfs/create-read.c b/tools/testing/selftests/efivarfs/create-read.c
new file mode 100644
index 00000000000..7feef188096
--- /dev/null
+++ b/tools/testing/selftests/efivarfs/create-read.c
@@ -0,0 +1,38 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+
+int main(int argc, char **argv)
+{
+ const char *path;
+ char buf[4];
+ int fd, rc;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <path>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ path = argv[1];
+
+ /* create a test variable */
+ fd = open(path, O_RDWR | O_CREAT, 0600);
+ if (fd < 0) {
+ perror("open(O_WRONLY)");
+ return EXIT_FAILURE;
+ }
+
+ rc = read(fd, buf, sizeof(buf));
+ if (rc != 0) {
+ fprintf(stderr, "Reading a new var should return EOF\n");
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/tools/testing/selftests/efivarfs/efivarfs.sh b/tools/testing/selftests/efivarfs/efivarfs.sh
new file mode 100644
index 00000000000..77edcdcc016
--- /dev/null
+++ b/tools/testing/selftests/efivarfs/efivarfs.sh
@@ -0,0 +1,198 @@
+#!/bin/bash
+
+efivarfs_mount=/sys/firmware/efi/efivars
+test_guid=210be57c-9849-4fc7-a635-e6382d1aec27
+
+check_prereqs()
+{
+ local msg="skip all tests:"
+
+ if [ $UID != 0 ]; then
+ echo $msg must be run as root >&2
+ exit 0
+ fi
+
+ if ! grep -q "^\S\+ $efivarfs_mount efivarfs" /proc/mounts; then
+ echo $msg efivarfs is not mounted on $efivarfs_mount >&2
+ exit 0
+ fi
+}
+
+run_test()
+{
+ local test="$1"
+
+ echo "--------------------"
+ echo "running $test"
+ echo "--------------------"
+
+ if [ "$(type -t $test)" = 'function' ]; then
+ ( $test )
+ else
+ ( ./$test )
+ fi
+
+ if [ $? -ne 0 ]; then
+ echo " [FAIL]"
+ rc=1
+ else
+ echo " [PASS]"
+ fi
+}
+
+test_create()
+{
+ local attrs='\x07\x00\x00\x00'
+ local file=$efivarfs_mount/$FUNCNAME-$test_guid
+
+ printf "$attrs\x00" > $file
+
+ if [ ! -e $file ]; then
+ echo "$file couldn't be created" >&2
+ exit 1
+ fi
+
+ if [ $(stat -c %s $file) -ne 5 ]; then
+ echo "$file has invalid size" >&2
+ exit 1
+ fi
+}
+
+test_create_empty()
+{
+ local file=$efivarfs_mount/$FUNCNAME-$test_guid
+
+ : > $file
+
+ if [ ! -e $file ]; then
+ echo "$file can not be created without writing" >&2
+ exit 1
+ fi
+}
+
+test_create_read()
+{
+ local file=$efivarfs_mount/$FUNCNAME-$test_guid
+ ./create-read $file
+}
+
+test_delete()
+{
+ local attrs='\x07\x00\x00\x00'
+ local file=$efivarfs_mount/$FUNCNAME-$test_guid
+
+ printf "$attrs\x00" > $file
+
+ if [ ! -e $file ]; then
+ echo "$file couldn't be created" >&2
+ exit 1
+ fi
+
+ rm $file
+
+ if [ -e $file ]; then
+ echo "$file couldn't be deleted" >&2
+ exit 1
+ fi
+
+}
+
+# test that we can remove a variable by issuing a write with only
+# attributes specified
+test_zero_size_delete()
+{
+ local attrs='\x07\x00\x00\x00'
+ local file=$efivarfs_mount/$FUNCNAME-$test_guid
+
+ printf "$attrs\x00" > $file
+
+ if [ ! -e $file ]; then
+ echo "$file does not exist" >&2
+ exit 1
+ fi
+
+ printf "$attrs" > $file
+
+ if [ -e $file ]; then
+ echo "$file should have been deleted" >&2
+ exit 1
+ fi
+}
+
+test_open_unlink()
+{
+ local file=$efivarfs_mount/$FUNCNAME-$test_guid
+ ./open-unlink $file
+}
+
+# test that we can create a range of filenames
+test_valid_filenames()
+{
+ local attrs='\x07\x00\x00\x00'
+ local ret=0
+
+ local file_list="abc dump-type0-11-1-1362436005 1234 -"
+ for f in $file_list; do
+ local file=$efivarfs_mount/$f-$test_guid
+
+ printf "$attrs\x00" > $file
+
+ if [ ! -e $file ]; then
+ echo "$file could not be created" >&2
+ ret=1
+ else
+ rm $file
+ fi
+ done
+
+ exit $ret
+}
+
+test_invalid_filenames()
+{
+ local attrs='\x07\x00\x00\x00'
+ local ret=0
+
+ local file_list="
+ -1234-1234-1234-123456789abc
+ foo
+ foo-bar
+ -foo-
+ foo-barbazba-foob-foob-foob-foobarbazfoo
+ foo-------------------------------------
+ -12345678-1234-1234-1234-123456789abc
+ a-12345678=1234-1234-1234-123456789abc
+ a-12345678-1234=1234-1234-123456789abc
+ a-12345678-1234-1234=1234-123456789abc
+ a-12345678-1234-1234-1234=123456789abc
+ 1112345678-1234-1234-1234-123456789abc"
+
+ for f in $file_list; do
+ local file=$efivarfs_mount/$f
+
+ printf "$attrs\x00" 2>/dev/null > $file
+
+ if [ -e $file ]; then
+ echo "Creating $file should have failed" >&2
+ rm $file
+ ret=1
+ fi
+ done
+
+ exit $ret
+}
+
+check_prereqs
+
+rc=0
+
+run_test test_create
+run_test test_create_empty
+run_test test_create_read
+run_test test_delete
+run_test test_zero_size_delete
+run_test test_open_unlink
+run_test test_valid_filenames
+run_test test_invalid_filenames
+
+exit $rc
diff --git a/tools/testing/selftests/efivarfs/open-unlink.c b/tools/testing/selftests/efivarfs/open-unlink.c
new file mode 100644
index 00000000000..8c0764407b3
--- /dev/null
+++ b/tools/testing/selftests/efivarfs/open-unlink.c
@@ -0,0 +1,63 @@
+#include <stdio.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+int main(int argc, char **argv)
+{
+ const char *path;
+ char buf[5];
+ int fd, rc;
+
+ if (argc < 2) {
+ fprintf(stderr, "usage: %s <path>\n", argv[0]);
+ return EXIT_FAILURE;
+ }
+
+ path = argv[1];
+
+ /* attributes: EFI_VARIABLE_NON_VOLATILE |
+ * EFI_VARIABLE_BOOTSERVICE_ACCESS |
+ * EFI_VARIABLE_RUNTIME_ACCESS
+ */
+ *(uint32_t *)buf = 0x7;
+ buf[4] = 0;
+
+ /* create a test variable */
+ fd = open(path, O_WRONLY | O_CREAT);
+ if (fd < 0) {
+ perror("open(O_WRONLY)");
+ return EXIT_FAILURE;
+ }
+
+ rc = write(fd, buf, sizeof(buf));
+ if (rc != sizeof(buf)) {
+ perror("write");
+ return EXIT_FAILURE;
+ }
+
+ close(fd);
+
+ fd = open(path, O_RDONLY);
+ if (fd < 0) {
+ perror("open");
+ return EXIT_FAILURE;
+ }
+
+ if (unlink(path) < 0) {
+ perror("unlink");
+ return EXIT_FAILURE;
+ }
+
+ rc = read(fd, buf, sizeof(buf));
+ if (rc > 0) {
+ fprintf(stderr, "reading from an unlinked variable "
+ "shouldn't be possible\n");
+ return EXIT_FAILURE;
+ }
+
+ return EXIT_SUCCESS;
+}
diff --git a/tools/testing/selftests/ipc/Makefile b/tools/testing/selftests/ipc/Makefile
new file mode 100644
index 00000000000..5386fd7c43a
--- /dev/null
+++ b/tools/testing/selftests/ipc/Makefile
@@ -0,0 +1,25 @@
+uname_M := $(shell uname -m 2>/dev/null || echo not)
+ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/)
+ifeq ($(ARCH),i386)
+ ARCH := X86
+ CFLAGS := -DCONFIG_X86_32 -D__i386__
+endif
+ifeq ($(ARCH),x86_64)
+ ARCH := X86
+ CFLAGS := -DCONFIG_X86_64 -D__x86_64__
+endif
+
+CFLAGS += -I../../../../usr/include/
+
+all:
+ifeq ($(ARCH),X86)
+ gcc $(CFLAGS) msgque.c -o msgque_test
+else
+ echo "Not an x86 target, can't build msgque selftest"
+endif
+
+run_tests: all
+ ./msgque_test
+
+clean:
+ rm -fr ./msgque_test
diff --git a/tools/testing/selftests/ipc/msgque.c b/tools/testing/selftests/ipc/msgque.c
new file mode 100644
index 00000000000..552f0810bff
--- /dev/null
+++ b/tools/testing/selftests/ipc/msgque.c
@@ -0,0 +1,252 @@
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include <linux/msg.h>
+#include <fcntl.h>
+
+#define MAX_MSG_SIZE 32
+
+struct msg1 {
+ int msize;
+ long mtype;
+ char mtext[MAX_MSG_SIZE];
+};
+
+#define TEST_STRING "Test sysv5 msg"
+#define MSG_TYPE 1
+
+#define ANOTHER_TEST_STRING "Yet another test sysv5 msg"
+#define ANOTHER_MSG_TYPE 26538
+
+struct msgque_data {
+ key_t key;
+ int msq_id;
+ int qbytes;
+ int qnum;
+ int mode;
+ struct msg1 *messages;
+};
+
+int restore_queue(struct msgque_data *msgque)
+{
+ int fd, ret, id, i;
+ char buf[32];
+
+ fd = open("/proc/sys/kernel/msg_next_id", O_WRONLY);
+ if (fd == -1) {
+ printf("Failed to open /proc/sys/kernel/msg_next_id\n");
+ return -errno;
+ }
+ sprintf(buf, "%d", msgque->msq_id);
+
+ ret = write(fd, buf, strlen(buf));
+ if (ret != strlen(buf)) {
+ printf("Failed to write to /proc/sys/kernel/msg_next_id\n");
+ return -errno;
+ }
+
+ id = msgget(msgque->key, msgque->mode | IPC_CREAT | IPC_EXCL);
+ if (id == -1) {
+ printf("Failed to create queue\n");
+ return -errno;
+ }
+
+ if (id != msgque->msq_id) {
+ printf("Restored queue has wrong id (%d instead of %d)\n",
+ id, msgque->msq_id);
+ ret = -EFAULT;
+ goto destroy;
+ }
+
+ for (i = 0; i < msgque->qnum; i++) {
+ if (msgsnd(msgque->msq_id, &msgque->messages[i].mtype,
+ msgque->messages[i].msize, IPC_NOWAIT) != 0) {
+ printf("msgsnd failed (%m)\n");
+ ret = -errno;
+ goto destroy;
+ };
+ }
+ return 0;
+
+destroy:
+ if (msgctl(id, IPC_RMID, 0))
+ printf("Failed to destroy queue: %d\n", -errno);
+ return ret;
+}
+
+int check_and_destroy_queue(struct msgque_data *msgque)
+{
+ struct msg1 message;
+ int cnt = 0, ret;
+
+ while (1) {
+ ret = msgrcv(msgque->msq_id, &message.mtype, MAX_MSG_SIZE,
+ 0, IPC_NOWAIT);
+ if (ret < 0) {
+ if (errno == ENOMSG)
+ break;
+ printf("Failed to read IPC message: %m\n");
+ ret = -errno;
+ goto err;
+ }
+ if (ret != msgque->messages[cnt].msize) {
+ printf("Wrong message size: %d (expected %d)\n", ret,
+ msgque->messages[cnt].msize);
+ ret = -EINVAL;
+ goto err;
+ }
+ if (message.mtype != msgque->messages[cnt].mtype) {
+ printf("Wrong message type\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ if (memcmp(message.mtext, msgque->messages[cnt].mtext, ret)) {
+ printf("Wrong message content\n");
+ ret = -EINVAL;
+ goto err;
+ }
+ cnt++;
+ }
+
+ if (cnt != msgque->qnum) {
+ printf("Wrong message number\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = 0;
+err:
+ if (msgctl(msgque->msq_id, IPC_RMID, 0)) {
+ printf("Failed to destroy queue: %d\n", -errno);
+ return -errno;
+ }
+ return ret;
+}
+
+int dump_queue(struct msgque_data *msgque)
+{
+ struct msqid64_ds ds;
+ int kern_id;
+ int i, ret;
+
+ for (kern_id = 0; kern_id < 256; kern_id++) {
+ ret = msgctl(kern_id, MSG_STAT, &ds);
+ if (ret < 0) {
+ if (errno == -EINVAL)
+ continue;
+ printf("Failed to get stats for IPC queue with id %d\n",
+ kern_id);
+ return -errno;
+ }
+
+ if (ret == msgque->msq_id)
+ break;
+ }
+
+ msgque->messages = malloc(sizeof(struct msg1) * ds.msg_qnum);
+ if (msgque->messages == NULL) {
+ printf("Failed to get stats for IPC queue\n");
+ return -ENOMEM;
+ }
+
+ msgque->qnum = ds.msg_qnum;
+ msgque->mode = ds.msg_perm.mode;
+ msgque->qbytes = ds.msg_qbytes;
+
+ for (i = 0; i < msgque->qnum; i++) {
+ ret = msgrcv(msgque->msq_id, &msgque->messages[i].mtype,
+ MAX_MSG_SIZE, i, IPC_NOWAIT | MSG_COPY);
+ if (ret < 0) {
+ printf("Failed to copy IPC message: %m (%d)\n", errno);
+ return -errno;
+ }
+ msgque->messages[i].msize = ret;
+ }
+ return 0;
+}
+
+int fill_msgque(struct msgque_data *msgque)
+{
+ struct msg1 msgbuf;
+
+ msgbuf.mtype = MSG_TYPE;
+ memcpy(msgbuf.mtext, TEST_STRING, sizeof(TEST_STRING));
+ if (msgsnd(msgque->msq_id, &msgbuf.mtype, sizeof(TEST_STRING),
+ IPC_NOWAIT) != 0) {
+ printf("First message send failed (%m)\n");
+ return -errno;
+ };
+
+ msgbuf.mtype = ANOTHER_MSG_TYPE;
+ memcpy(msgbuf.mtext, ANOTHER_TEST_STRING, sizeof(ANOTHER_TEST_STRING));
+ if (msgsnd(msgque->msq_id, &msgbuf.mtype, sizeof(ANOTHER_TEST_STRING),
+ IPC_NOWAIT) != 0) {
+ printf("Second message send failed (%m)\n");
+ return -errno;
+ };
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int msg, pid, err;
+ struct msgque_data msgque;
+
+ if (getuid() != 0) {
+ printf("Please run the test as root - Exiting.\n");
+ exit(1);
+ }
+
+ msgque.key = ftok(argv[0], 822155650);
+ if (msgque.key == -1) {
+ printf("Can't make key\n");
+ return -errno;
+ }
+
+ msgque.msq_id = msgget(msgque.key, IPC_CREAT | IPC_EXCL | 0666);
+ if (msgque.msq_id == -1) {
+ err = -errno;
+ printf("Can't create queue\n");
+ goto err_out;
+ }
+
+ err = fill_msgque(&msgque);
+ if (err) {
+ printf("Failed to fill queue\n");
+ goto err_destroy;
+ }
+
+ err = dump_queue(&msgque);
+ if (err) {
+ printf("Failed to dump queue\n");
+ goto err_destroy;
+ }
+
+ err = check_and_destroy_queue(&msgque);
+ if (err) {
+ printf("Failed to check and destroy queue\n");
+ goto err_out;
+ }
+
+ err = restore_queue(&msgque);
+ if (err) {
+ printf("Failed to restore queue\n");
+ goto err_destroy;
+ }
+
+ err = check_and_destroy_queue(&msgque);
+ if (err) {
+ printf("Failed to test queue\n");
+ goto err_out;
+ }
+ return 0;
+
+err_destroy:
+ if (msgctl(msgque.msq_id, IPC_RMID, 0)) {
+ printf("Failed to destroy queue: %d\n", -errno);
+ return -errno;
+ }
+err_out:
+ return err;
+}
diff --git a/tools/testing/selftests/kcmp/.gitignore b/tools/testing/selftests/kcmp/.gitignore
new file mode 100644
index 00000000000..5a9b3732b2d
--- /dev/null
+++ b/tools/testing/selftests/kcmp/.gitignore
@@ -0,0 +1,2 @@
+kcmp_test
+kcmp-test-file
diff --git a/tools/testing/selftests/kcmp/Makefile b/tools/testing/selftests/kcmp/Makefile
new file mode 100644
index 00000000000..d7d6bbeeff2
--- /dev/null
+++ b/tools/testing/selftests/kcmp/Makefile
@@ -0,0 +1,28 @@
+uname_M := $(shell uname -m 2>/dev/null || echo not)
+ARCH ?= $(shell echo $(uname_M) | sed -e s/i.86/i386/)
+ifeq ($(ARCH),i386)
+ ARCH := X86
+ CFLAGS := -DCONFIG_X86_32 -D__i386__
+endif
+ifeq ($(ARCH),x86_64)
+ ARCH := X86
+ CFLAGS := -DCONFIG_X86_64 -D__x86_64__
+endif
+
+CFLAGS += -I../../../../arch/x86/include/generated/
+CFLAGS += -I../../../../include/
+CFLAGS += -I../../../../usr/include/
+CFLAGS += -I../../../../arch/x86/include/
+
+all:
+ifeq ($(ARCH),X86)
+ gcc $(CFLAGS) kcmp_test.c -o kcmp_test
+else
+ echo "Not an x86 target, can't build kcmp selftest"
+endif
+
+run_tests: all
+ @./kcmp_test || echo "kcmp_test: [FAIL]"
+
+clean:
+ $(RM) kcmp_test kcmp-test-file
diff --git a/tools/testing/selftests/kcmp/kcmp_test.c b/tools/testing/selftests/kcmp/kcmp_test.c
new file mode 100644
index 00000000000..fa4f1b37e04
--- /dev/null
+++ b/tools/testing/selftests/kcmp/kcmp_test.c
@@ -0,0 +1,96 @@
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+#include <limits.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include <linux/unistd.h>
+#include <linux/kcmp.h>
+
+#include <sys/syscall.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+
+static long sys_kcmp(int pid1, int pid2, int type, int fd1, int fd2)
+{
+ return syscall(__NR_kcmp, pid1, pid2, type, fd1, fd2);
+}
+
+int main(int argc, char **argv)
+{
+ const char kpath[] = "kcmp-test-file";
+ int pid1, pid2;
+ int fd1, fd2;
+ int status;
+
+ fd1 = open(kpath, O_RDWR | O_CREAT | O_TRUNC, 0644);
+ pid1 = getpid();
+
+ if (fd1 < 0) {
+ perror("Can't create file");
+ exit(1);
+ }
+
+ pid2 = fork();
+ if (pid2 < 0) {
+ perror("fork failed");
+ exit(1);
+ }
+
+ if (!pid2) {
+ int pid2 = getpid();
+ int ret;
+
+ fd2 = open(kpath, O_RDWR, 0644);
+ if (fd2 < 0) {
+ perror("Can't open file");
+ exit(1);
+ }
+
+ /* An example of output and arguments */
+ printf("pid1: %6d pid2: %6d FD: %2ld FILES: %2ld VM: %2ld "
+ "FS: %2ld SIGHAND: %2ld IO: %2ld SYSVSEM: %2ld "
+ "INV: %2ld\n",
+ pid1, pid2,
+ sys_kcmp(pid1, pid2, KCMP_FILE, fd1, fd2),
+ sys_kcmp(pid1, pid2, KCMP_FILES, 0, 0),
+ sys_kcmp(pid1, pid2, KCMP_VM, 0, 0),
+ sys_kcmp(pid1, pid2, KCMP_FS, 0, 0),
+ sys_kcmp(pid1, pid2, KCMP_SIGHAND, 0, 0),
+ sys_kcmp(pid1, pid2, KCMP_IO, 0, 0),
+ sys_kcmp(pid1, pid2, KCMP_SYSVSEM, 0, 0),
+
+ /* This one should fail */
+ sys_kcmp(pid1, pid2, KCMP_TYPES + 1, 0, 0));
+
+ /* This one should return same fd */
+ ret = sys_kcmp(pid1, pid2, KCMP_FILE, fd1, fd1);
+ if (ret) {
+ printf("FAIL: 0 expected but %d returned (%s)\n",
+ ret, strerror(errno));
+ ret = -1;
+ } else
+ printf("PASS: 0 returned as expected\n");
+
+ /* Compare with self */
+ ret = sys_kcmp(pid1, pid1, KCMP_VM, 0, 0);
+ if (ret) {
+ printf("FAIL: 0 expected but %li returned (%s)\n",
+ ret, strerror(errno));
+ ret = -1;
+ } else
+ printf("PASS: 0 returned as expected\n");
+
+ exit(ret);
+ }
+
+ waitpid(pid2, &status, P_ALL);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/memory-hotplug/Makefile b/tools/testing/selftests/memory-hotplug/Makefile
new file mode 100644
index 00000000000..058c76f5d10
--- /dev/null
+++ b/tools/testing/selftests/memory-hotplug/Makefile
@@ -0,0 +1,6 @@
+all:
+
+run_tests:
+ @/bin/bash ./on-off-test.sh || echo "memory-hotplug selftests: [FAIL]"
+
+clean:
diff --git a/tools/testing/selftests/memory-hotplug/on-off-test.sh b/tools/testing/selftests/memory-hotplug/on-off-test.sh
new file mode 100644
index 00000000000..a2816f63154
--- /dev/null
+++ b/tools/testing/selftests/memory-hotplug/on-off-test.sh
@@ -0,0 +1,230 @@
+#!/bin/bash
+
+SYSFS=
+
+prerequisite()
+{
+ msg="skip all tests:"
+
+ if [ $UID != 0 ]; then
+ echo $msg must be run as root >&2
+ exit 0
+ fi
+
+ SYSFS=`mount -t sysfs | head -1 | awk '{ print $3 }'`
+
+ if [ ! -d "$SYSFS" ]; then
+ echo $msg sysfs is not mounted >&2
+ exit 0
+ fi
+
+ if ! ls $SYSFS/devices/system/memory/memory* > /dev/null 2>&1; then
+ echo $msg memory hotplug is not supported >&2
+ exit 0
+ fi
+}
+
+#
+# list all hot-pluggable memory
+#
+hotpluggable_memory()
+{
+ local state=${1:-.\*}
+
+ for memory in $SYSFS/devices/system/memory/memory*; do
+ if grep -q 1 $memory/removable &&
+ grep -q $state $memory/state; then
+ echo ${memory##/*/memory}
+ fi
+ done
+}
+
+hotplaggable_offline_memory()
+{
+ hotpluggable_memory offline
+}
+
+hotpluggable_online_memory()
+{
+ hotpluggable_memory online
+}
+
+memory_is_online()
+{
+ grep -q online $SYSFS/devices/system/memory/memory$1/state
+}
+
+memory_is_offline()
+{
+ grep -q offline $SYSFS/devices/system/memory/memory$1/state
+}
+
+online_memory()
+{
+ echo online > $SYSFS/devices/system/memory/memory$1/state
+}
+
+offline_memory()
+{
+ echo offline > $SYSFS/devices/system/memory/memory$1/state
+}
+
+online_memory_expect_success()
+{
+ local memory=$1
+
+ if ! online_memory $memory; then
+ echo $FUNCNAME $memory: unexpected fail >&2
+ elif ! memory_is_online $memory; then
+ echo $FUNCNAME $memory: unexpected offline >&2
+ fi
+}
+
+online_memory_expect_fail()
+{
+ local memory=$1
+
+ if online_memory $memory 2> /dev/null; then
+ echo $FUNCNAME $memory: unexpected success >&2
+ elif ! memory_is_offline $memory; then
+ echo $FUNCNAME $memory: unexpected online >&2
+ fi
+}
+
+offline_memory_expect_success()
+{
+ local memory=$1
+
+ if ! offline_memory $memory; then
+ echo $FUNCNAME $memory: unexpected fail >&2
+ elif ! memory_is_offline $memory; then
+ echo $FUNCNAME $memory: unexpected offline >&2
+ fi
+}
+
+offline_memory_expect_fail()
+{
+ local memory=$1
+
+ if offline_memory $memory 2> /dev/null; then
+ echo $FUNCNAME $memory: unexpected success >&2
+ elif ! memory_is_online $memory; then
+ echo $FUNCNAME $memory: unexpected offline >&2
+ fi
+}
+
+error=-12
+priority=0
+ratio=10
+
+while getopts e:hp:r: opt; do
+ case $opt in
+ e)
+ error=$OPTARG
+ ;;
+ h)
+ echo "Usage $0 [ -e errno ] [ -p notifier-priority ] [ -r percent-of-memory-to-offline ]"
+ exit
+ ;;
+ p)
+ priority=$OPTARG
+ ;;
+ r)
+ ratio=$OPTARG
+ ;;
+ esac
+done
+
+if ! [ "$error" -ge -4095 -a "$error" -lt 0 ]; then
+ echo "error code must be -4095 <= errno < 0" >&2
+ exit 1
+fi
+
+prerequisite
+
+#
+# Online all hot-pluggable memory
+#
+for memory in `hotplaggable_offline_memory`; do
+ online_memory_expect_success $memory
+done
+
+#
+# Offline $ratio percent of hot-pluggable memory
+#
+for memory in `hotpluggable_online_memory`; do
+ if [ $((RANDOM % 100)) -lt $ratio ]; then
+ offline_memory_expect_success $memory
+ fi
+done
+
+#
+# Online all hot-pluggable memory again
+#
+for memory in `hotplaggable_offline_memory`; do
+ online_memory_expect_success $memory
+done
+
+#
+# Test with memory notifier error injection
+#
+
+DEBUGFS=`mount -t debugfs | head -1 | awk '{ print $3 }'`
+NOTIFIER_ERR_INJECT_DIR=$DEBUGFS/notifier-error-inject/memory
+
+prerequisite_extra()
+{
+ msg="skip extra tests:"
+
+ /sbin/modprobe -q -r memory-notifier-error-inject
+ /sbin/modprobe -q memory-notifier-error-inject priority=$priority
+
+ if [ ! -d "$DEBUGFS" ]; then
+ echo $msg debugfs is not mounted >&2
+ exit 0
+ fi
+
+ if [ ! -d $NOTIFIER_ERR_INJECT_DIR ]; then
+ echo $msg memory-notifier-error-inject module is not available >&2
+ exit 0
+ fi
+}
+
+prerequisite_extra
+
+#
+# Offline $ratio percent of hot-pluggable memory
+#
+echo 0 > $NOTIFIER_ERR_INJECT_DIR/actions/MEM_GOING_OFFLINE/error
+for memory in `hotpluggable_online_memory`; do
+ if [ $((RANDOM % 100)) -lt $ratio ]; then
+ offline_memory_expect_success $memory
+ fi
+done
+
+#
+# Test memory hot-add error handling (offline => online)
+#
+echo $error > $NOTIFIER_ERR_INJECT_DIR/actions/MEM_GOING_ONLINE/error
+for memory in `hotplaggable_offline_memory`; do
+ online_memory_expect_fail $memory
+done
+
+#
+# Online all hot-pluggable memory
+#
+echo 0 > $NOTIFIER_ERR_INJECT_DIR/actions/MEM_GOING_ONLINE/error
+for memory in `hotplaggable_offline_memory`; do
+ online_memory_expect_success $memory
+done
+
+#
+# Test memory hot-remove error handling (online => offline)
+#
+echo $error > $NOTIFIER_ERR_INJECT_DIR/actions/MEM_GOING_OFFLINE/error
+for memory in `hotpluggable_online_memory`; do
+ offline_memory_expect_fail $memory
+done
+
+echo 0 > $NOTIFIER_ERR_INJECT_DIR/actions/MEM_GOING_OFFLINE/error
+/sbin/modprobe -q -r memory-notifier-error-inject
diff --git a/tools/testing/selftests/mqueue/.gitignore b/tools/testing/selftests/mqueue/.gitignore
new file mode 100644
index 00000000000..d8d42377205
--- /dev/null
+++ b/tools/testing/selftests/mqueue/.gitignore
@@ -0,0 +1,2 @@
+mq_open_tests
+mq_perf_tests
diff --git a/tools/testing/selftests/mqueue/Makefile b/tools/testing/selftests/mqueue/Makefile
new file mode 100644
index 00000000000..218a122c795
--- /dev/null
+++ b/tools/testing/selftests/mqueue/Makefile
@@ -0,0 +1,10 @@
+all:
+ gcc -O2 -lrt mq_open_tests.c -o mq_open_tests
+ gcc -O2 -lrt -lpthread -lpopt -o mq_perf_tests mq_perf_tests.c
+
+run_tests:
+ @./mq_open_tests /test1 || echo "mq_open_tests: [FAIL]"
+ @./mq_perf_tests || echo "mq_perf_tests: [FAIL]"
+
+clean:
+ rm -f mq_open_tests mq_perf_tests
diff --git a/tools/testing/selftests/mqueue/mq_open_tests.c b/tools/testing/selftests/mqueue/mq_open_tests.c
new file mode 100644
index 00000000000..711cc292304
--- /dev/null
+++ b/tools/testing/selftests/mqueue/mq_open_tests.c
@@ -0,0 +1,492 @@
+/*
+ * This application is Copyright 2012 Red Hat, Inc.
+ * Doug Ledford <dledford@redhat.com>
+ *
+ * mq_open_tests is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * mq_open_tests is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * For the full text of the license, see <http://www.gnu.org/licenses/>.
+ *
+ * mq_open_tests.c
+ * Tests the various situations that should either succeed or fail to
+ * open a posix message queue and then reports whether or not they
+ * did as they were supposed to.
+ *
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <mqueue.h>
+
+static char *usage =
+"Usage:\n"
+" %s path\n"
+"\n"
+" path Path name of the message queue to create\n"
+"\n"
+" Note: this program must be run as root in order to enable all tests\n"
+"\n";
+
+char *DEF_MSGS = "/proc/sys/fs/mqueue/msg_default";
+char *DEF_MSGSIZE = "/proc/sys/fs/mqueue/msgsize_default";
+char *MAX_MSGS = "/proc/sys/fs/mqueue/msg_max";
+char *MAX_MSGSIZE = "/proc/sys/fs/mqueue/msgsize_max";
+
+int default_settings;
+struct rlimit saved_limits, cur_limits;
+int saved_def_msgs, saved_def_msgsize, saved_max_msgs, saved_max_msgsize;
+int cur_def_msgs, cur_def_msgsize, cur_max_msgs, cur_max_msgsize;
+FILE *def_msgs, *def_msgsize, *max_msgs, *max_msgsize;
+char *queue_path;
+mqd_t queue = -1;
+
+static inline void __set(FILE *stream, int value, char *err_msg);
+void shutdown(int exit_val, char *err_cause, int line_no);
+static inline int get(FILE *stream);
+static inline void set(FILE *stream, int value);
+static inline void getr(int type, struct rlimit *rlim);
+static inline void setr(int type, struct rlimit *rlim);
+void validate_current_settings();
+static inline void test_queue(struct mq_attr *attr, struct mq_attr *result);
+static inline int test_queue_fail(struct mq_attr *attr, struct mq_attr *result);
+
+static inline void __set(FILE *stream, int value, char *err_msg)
+{
+ rewind(stream);
+ if (fprintf(stream, "%d", value) < 0)
+ perror(err_msg);
+}
+
+
+void shutdown(int exit_val, char *err_cause, int line_no)
+{
+ static int in_shutdown = 0;
+
+ /* In case we get called recursively by a set() call below */
+ if (in_shutdown++)
+ return;
+
+ seteuid(0);
+
+ if (queue != -1)
+ if (mq_close(queue))
+ perror("mq_close() during shutdown");
+ if (queue_path)
+ /*
+ * Be silent if this fails, if we cleaned up already it's
+ * expected to fail
+ */
+ mq_unlink(queue_path);
+ if (default_settings) {
+ if (saved_def_msgs)
+ __set(def_msgs, saved_def_msgs,
+ "failed to restore saved_def_msgs");
+ if (saved_def_msgsize)
+ __set(def_msgsize, saved_def_msgsize,
+ "failed to restore saved_def_msgsize");
+ }
+ if (saved_max_msgs)
+ __set(max_msgs, saved_max_msgs,
+ "failed to restore saved_max_msgs");
+ if (saved_max_msgsize)
+ __set(max_msgsize, saved_max_msgsize,
+ "failed to restore saved_max_msgsize");
+ if (exit_val)
+ error(exit_val, errno, "%s at %d", err_cause, line_no);
+ exit(0);
+}
+
+static inline int get(FILE *stream)
+{
+ int value;
+ rewind(stream);
+ if (fscanf(stream, "%d", &value) != 1)
+ shutdown(4, "Error reading /proc entry", __LINE__ - 1);
+ return value;
+}
+
+static inline void set(FILE *stream, int value)
+{
+ int new_value;
+
+ rewind(stream);
+ if (fprintf(stream, "%d", value) < 0)
+ return shutdown(5, "Failed writing to /proc file",
+ __LINE__ - 1);
+ new_value = get(stream);
+ if (new_value != value)
+ return shutdown(5, "We didn't get what we wrote to /proc back",
+ __LINE__ - 1);
+}
+
+static inline void getr(int type, struct rlimit *rlim)
+{
+ if (getrlimit(type, rlim))
+ shutdown(6, "getrlimit()", __LINE__ - 1);
+}
+
+static inline void setr(int type, struct rlimit *rlim)
+{
+ if (setrlimit(type, rlim))
+ shutdown(7, "setrlimit()", __LINE__ - 1);
+}
+
+void validate_current_settings()
+{
+ int rlim_needed;
+
+ if (cur_limits.rlim_cur < 4096) {
+ printf("Current rlimit value for POSIX message queue bytes is "
+ "unreasonably low,\nincreasing.\n\n");
+ cur_limits.rlim_cur = 8192;
+ cur_limits.rlim_max = 16384;
+ setr(RLIMIT_MSGQUEUE, &cur_limits);
+ }
+
+ if (default_settings) {
+ rlim_needed = (cur_def_msgs + 1) * (cur_def_msgsize + 1 +
+ 2 * sizeof(void *));
+ if (rlim_needed > cur_limits.rlim_cur) {
+ printf("Temporarily lowering default queue parameters "
+ "to something that will work\n"
+ "with the current rlimit values.\n\n");
+ set(def_msgs, 10);
+ cur_def_msgs = 10;
+ set(def_msgsize, 128);
+ cur_def_msgsize = 128;
+ }
+ } else {
+ rlim_needed = (cur_max_msgs + 1) * (cur_max_msgsize + 1 +
+ 2 * sizeof(void *));
+ if (rlim_needed > cur_limits.rlim_cur) {
+ printf("Temporarily lowering maximum queue parameters "
+ "to something that will work\n"
+ "with the current rlimit values in case this is "
+ "a kernel that ties the default\n"
+ "queue parameters to the maximum queue "
+ "parameters.\n\n");
+ set(max_msgs, 10);
+ cur_max_msgs = 10;
+ set(max_msgsize, 128);
+ cur_max_msgsize = 128;
+ }
+ }
+}
+
+/*
+ * test_queue - Test opening a queue, shutdown if we fail. This should
+ * only be called in situations that should never fail. We clean up
+ * after ourselves and return the queue attributes in *result.
+ */
+static inline void test_queue(struct mq_attr *attr, struct mq_attr *result)
+{
+ int flags = O_RDWR | O_EXCL | O_CREAT;
+ int perms = DEFFILEMODE;
+
+ if ((queue = mq_open(queue_path, flags, perms, attr)) == -1)
+ shutdown(1, "mq_open()", __LINE__);
+ if (mq_getattr(queue, result))
+ shutdown(1, "mq_getattr()", __LINE__);
+ if (mq_close(queue))
+ shutdown(1, "mq_close()", __LINE__);
+ queue = -1;
+ if (mq_unlink(queue_path))
+ shutdown(1, "mq_unlink()", __LINE__);
+}
+
+/*
+ * Same as test_queue above, but failure is not fatal.
+ * Returns:
+ * 0 - Failed to create a queue
+ * 1 - Created a queue, attributes in *result
+ */
+static inline int test_queue_fail(struct mq_attr *attr, struct mq_attr *result)
+{
+ int flags = O_RDWR | O_EXCL | O_CREAT;
+ int perms = DEFFILEMODE;
+
+ if ((queue = mq_open(queue_path, flags, perms, attr)) == -1)
+ return 0;
+ if (mq_getattr(queue, result))
+ shutdown(1, "mq_getattr()", __LINE__);
+ if (mq_close(queue))
+ shutdown(1, "mq_close()", __LINE__);
+ queue = -1;
+ if (mq_unlink(queue_path))
+ shutdown(1, "mq_unlink()", __LINE__);
+ return 1;
+}
+
+int main(int argc, char *argv[])
+{
+ struct mq_attr attr, result;
+
+ if (argc != 2) {
+ fprintf(stderr, "Must pass a valid queue name\n\n");
+ fprintf(stderr, usage, argv[0]);
+ exit(1);
+ }
+
+ /*
+ * Although we can create a msg queue with a non-absolute path name,
+ * unlink will fail. So, if the name doesn't start with a /, add one
+ * when we save it.
+ */
+ if (*argv[1] == '/')
+ queue_path = strdup(argv[1]);
+ else {
+ queue_path = malloc(strlen(argv[1]) + 2);
+ if (!queue_path) {
+ perror("malloc()");
+ exit(1);
+ }
+ queue_path[0] = '/';
+ queue_path[1] = 0;
+ strcat(queue_path, argv[1]);
+ }
+
+ if (getuid() != 0) {
+ fprintf(stderr, "Not running as root, but almost all tests "
+ "require root in order to modify\nsystem settings. "
+ "Exiting.\n");
+ exit(1);
+ }
+
+ /* Find out what files there are for us to make tweaks in */
+ def_msgs = fopen(DEF_MSGS, "r+");
+ def_msgsize = fopen(DEF_MSGSIZE, "r+");
+ max_msgs = fopen(MAX_MSGS, "r+");
+ max_msgsize = fopen(MAX_MSGSIZE, "r+");
+
+ if (!max_msgs)
+ shutdown(2, "Failed to open msg_max", __LINE__);
+ if (!max_msgsize)
+ shutdown(2, "Failed to open msgsize_max", __LINE__);
+ if (def_msgs || def_msgsize)
+ default_settings = 1;
+
+ /* Load up the current system values for everything we can */
+ getr(RLIMIT_MSGQUEUE, &saved_limits);
+ cur_limits = saved_limits;
+ if (default_settings) {
+ saved_def_msgs = cur_def_msgs = get(def_msgs);
+ saved_def_msgsize = cur_def_msgsize = get(def_msgsize);
+ }
+ saved_max_msgs = cur_max_msgs = get(max_msgs);
+ saved_max_msgsize = cur_max_msgsize = get(max_msgsize);
+
+ /* Tell the user our initial state */
+ printf("\nInitial system state:\n");
+ printf("\tUsing queue path:\t\t%s\n", queue_path);
+ printf("\tRLIMIT_MSGQUEUE(soft):\t\t%d\n", saved_limits.rlim_cur);
+ printf("\tRLIMIT_MSGQUEUE(hard):\t\t%d\n", saved_limits.rlim_max);
+ printf("\tMaximum Message Size:\t\t%d\n", saved_max_msgsize);
+ printf("\tMaximum Queue Size:\t\t%d\n", saved_max_msgs);
+ if (default_settings) {
+ printf("\tDefault Message Size:\t\t%d\n", saved_def_msgsize);
+ printf("\tDefault Queue Size:\t\t%d\n", saved_def_msgs);
+ } else {
+ printf("\tDefault Message Size:\t\tNot Supported\n");
+ printf("\tDefault Queue Size:\t\tNot Supported\n");
+ }
+ printf("\n");
+
+ validate_current_settings();
+
+ printf("Adjusted system state for testing:\n");
+ printf("\tRLIMIT_MSGQUEUE(soft):\t\t%d\n", cur_limits.rlim_cur);
+ printf("\tRLIMIT_MSGQUEUE(hard):\t\t%d\n", cur_limits.rlim_max);
+ printf("\tMaximum Message Size:\t\t%d\n", cur_max_msgsize);
+ printf("\tMaximum Queue Size:\t\t%d\n", cur_max_msgs);
+ if (default_settings) {
+ printf("\tDefault Message Size:\t\t%d\n", cur_def_msgsize);
+ printf("\tDefault Queue Size:\t\t%d\n", cur_def_msgs);
+ }
+
+ printf("\n\nTest series 1, behavior when no attr struct "
+ "passed to mq_open:\n");
+ if (!default_settings) {
+ test_queue(NULL, &result);
+ printf("Given sane system settings, mq_open without an attr "
+ "struct succeeds:\tPASS\n");
+ if (result.mq_maxmsg != cur_max_msgs ||
+ result.mq_msgsize != cur_max_msgsize) {
+ printf("Kernel does not support setting the default "
+ "mq attributes,\nbut also doesn't tie the "
+ "defaults to the maximums:\t\t\tPASS\n");
+ } else {
+ set(max_msgs, ++cur_max_msgs);
+ set(max_msgsize, ++cur_max_msgsize);
+ test_queue(NULL, &result);
+ if (result.mq_maxmsg == cur_max_msgs &&
+ result.mq_msgsize == cur_max_msgsize)
+ printf("Kernel does not support setting the "
+ "default mq attributes and\n"
+ "also ties system wide defaults to "
+ "the system wide maximums:\t\t"
+ "FAIL\n");
+ else
+ printf("Kernel does not support setting the "
+ "default mq attributes,\n"
+ "but also doesn't tie the defaults to "
+ "the maximums:\t\t\tPASS\n");
+ }
+ } else {
+ printf("Kernel supports setting defaults separately from "
+ "maximums:\t\tPASS\n");
+ /*
+ * While we are here, go ahead and test that the kernel
+ * properly follows the default settings
+ */
+ test_queue(NULL, &result);
+ printf("Given sane values, mq_open without an attr struct "
+ "succeeds:\t\tPASS\n");
+ if (result.mq_maxmsg != cur_def_msgs ||
+ result.mq_msgsize != cur_def_msgsize)
+ printf("Kernel supports setting defaults, but does "
+ "not actually honor them:\tFAIL\n\n");
+ else {
+ set(def_msgs, ++cur_def_msgs);
+ set(def_msgsize, ++cur_def_msgsize);
+ /* In case max was the same as the default */
+ set(max_msgs, ++cur_max_msgs);
+ set(max_msgsize, ++cur_max_msgsize);
+ test_queue(NULL, &result);
+ if (result.mq_maxmsg != cur_def_msgs ||
+ result.mq_msgsize != cur_def_msgsize)
+ printf("Kernel supports setting defaults, but "
+ "does not actually honor them:\t"
+ "FAIL\n");
+ else
+ printf("Kernel properly honors default setting "
+ "knobs:\t\t\t\tPASS\n");
+ }
+ set(def_msgs, cur_max_msgs + 1);
+ cur_def_msgs = cur_max_msgs + 1;
+ set(def_msgsize, cur_max_msgsize + 1);
+ cur_def_msgsize = cur_max_msgsize + 1;
+ if (cur_def_msgs * (cur_def_msgsize + 2 * sizeof(void *)) >=
+ cur_limits.rlim_cur) {
+ cur_limits.rlim_cur = (cur_def_msgs + 2) *
+ (cur_def_msgsize + 2 * sizeof(void *));
+ cur_limits.rlim_max = 2 * cur_limits.rlim_cur;
+ setr(RLIMIT_MSGQUEUE, &cur_limits);
+ }
+ if (test_queue_fail(NULL, &result)) {
+ if (result.mq_maxmsg == cur_max_msgs &&
+ result.mq_msgsize == cur_max_msgsize)
+ printf("Kernel properly limits default values "
+ "to lesser of default/max:\t\tPASS\n");
+ else
+ printf("Kernel does not properly set default "
+ "queue parameters when\ndefaults > "
+ "max:\t\t\t\t\t\t\t\tFAIL\n");
+ } else
+ printf("Kernel fails to open mq because defaults are "
+ "greater than maximums:\tFAIL\n");
+ set(def_msgs, --cur_def_msgs);
+ set(def_msgsize, --cur_def_msgsize);
+ cur_limits.rlim_cur = cur_limits.rlim_max = cur_def_msgs *
+ cur_def_msgsize;
+ setr(RLIMIT_MSGQUEUE, &cur_limits);
+ if (test_queue_fail(NULL, &result))
+ printf("Kernel creates queue even though defaults "
+ "would exceed\nrlimit setting:"
+ "\t\t\t\t\t\t\t\tFAIL\n");
+ else
+ printf("Kernel properly fails to create queue when "
+ "defaults would\nexceed rlimit:"
+ "\t\t\t\t\t\t\t\tPASS\n");
+ }
+
+ /*
+ * Test #2 - open with an attr struct that exceeds rlimit
+ */
+ printf("\n\nTest series 2, behavior when attr struct is "
+ "passed to mq_open:\n");
+ cur_max_msgs = 32;
+ cur_max_msgsize = cur_limits.rlim_max >> 4;
+ set(max_msgs, cur_max_msgs);
+ set(max_msgsize, cur_max_msgsize);
+ attr.mq_maxmsg = cur_max_msgs;
+ attr.mq_msgsize = cur_max_msgsize;
+ if (test_queue_fail(&attr, &result))
+ printf("Queue open in excess of rlimit max when euid = 0 "
+ "succeeded:\t\tFAIL\n");
+ else
+ printf("Queue open in excess of rlimit max when euid = 0 "
+ "failed:\t\tPASS\n");
+ attr.mq_maxmsg = cur_max_msgs + 1;
+ attr.mq_msgsize = 10;
+ if (test_queue_fail(&attr, &result))
+ printf("Queue open with mq_maxmsg > limit when euid = 0 "
+ "succeeded:\t\tPASS\n");
+ else
+ printf("Queue open with mq_maxmsg > limit when euid = 0 "
+ "failed:\t\tFAIL\n");
+ attr.mq_maxmsg = 1;
+ attr.mq_msgsize = cur_max_msgsize + 1;
+ if (test_queue_fail(&attr, &result))
+ printf("Queue open with mq_msgsize > limit when euid = 0 "
+ "succeeded:\t\tPASS\n");
+ else
+ printf("Queue open with mq_msgsize > limit when euid = 0 "
+ "failed:\t\tFAIL\n");
+ attr.mq_maxmsg = 65536;
+ attr.mq_msgsize = 65536;
+ if (test_queue_fail(&attr, &result))
+ printf("Queue open with total size > 2GB when euid = 0 "
+ "succeeded:\t\tFAIL\n");
+ else
+ printf("Queue open with total size > 2GB when euid = 0 "
+ "failed:\t\t\tPASS\n");
+ seteuid(99);
+ attr.mq_maxmsg = cur_max_msgs;
+ attr.mq_msgsize = cur_max_msgsize;
+ if (test_queue_fail(&attr, &result))
+ printf("Queue open in excess of rlimit max when euid = 99 "
+ "succeeded:\t\tFAIL\n");
+ else
+ printf("Queue open in excess of rlimit max when euid = 99 "
+ "failed:\t\tPASS\n");
+ attr.mq_maxmsg = cur_max_msgs + 1;
+ attr.mq_msgsize = 10;
+ if (test_queue_fail(&attr, &result))
+ printf("Queue open with mq_maxmsg > limit when euid = 99 "
+ "succeeded:\t\tFAIL\n");
+ else
+ printf("Queue open with mq_maxmsg > limit when euid = 99 "
+ "failed:\t\tPASS\n");
+ attr.mq_maxmsg = 1;
+ attr.mq_msgsize = cur_max_msgsize + 1;
+ if (test_queue_fail(&attr, &result))
+ printf("Queue open with mq_msgsize > limit when euid = 99 "
+ "succeeded:\t\tFAIL\n");
+ else
+ printf("Queue open with mq_msgsize > limit when euid = 99 "
+ "failed:\t\tPASS\n");
+ attr.mq_maxmsg = 65536;
+ attr.mq_msgsize = 65536;
+ if (test_queue_fail(&attr, &result))
+ printf("Queue open with total size > 2GB when euid = 99 "
+ "succeeded:\t\tFAIL\n");
+ else
+ printf("Queue open with total size > 2GB when euid = 99 "
+ "failed:\t\t\tPASS\n");
+
+ shutdown(0,"",0);
+}
diff --git a/tools/testing/selftests/mqueue/mq_perf_tests.c b/tools/testing/selftests/mqueue/mq_perf_tests.c
new file mode 100644
index 00000000000..2fadd4b9704
--- /dev/null
+++ b/tools/testing/selftests/mqueue/mq_perf_tests.c
@@ -0,0 +1,741 @@
+/*
+ * This application is Copyright 2012 Red Hat, Inc.
+ * Doug Ledford <dledford@redhat.com>
+ *
+ * mq_perf_tests is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3.
+ *
+ * mq_perf_tests is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * For the full text of the license, see <http://www.gnu.org/licenses/>.
+ *
+ * mq_perf_tests.c
+ * Tests various types of message queue workloads, concentrating on those
+ * situations that invole large message sizes, large message queue depths,
+ * or both, and reports back useful metrics about kernel message queue
+ * performance.
+ *
+ */
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <limits.h>
+#include <errno.h>
+#include <signal.h>
+#include <pthread.h>
+#include <sched.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <sys/resource.h>
+#include <sys/stat.h>
+#include <mqueue.h>
+#include <popt.h>
+
+static char *usage =
+"Usage:\n"
+" %s [-c #[,#..] -f] path\n"
+"\n"
+" -c # Skip most tests and go straight to a high queue depth test\n"
+" and then run that test continuously (useful for running at\n"
+" the same time as some other workload to see how much the\n"
+" cache thrashing caused by adding messages to a very deep\n"
+" queue impacts the performance of other programs). The number\n"
+" indicates which CPU core we should bind the process to during\n"
+" the run. If you have more than one physical CPU, then you\n"
+" will need one copy per physical CPU package, and you should\n"
+" specify the CPU cores to pin ourself to via a comma separated\n"
+" list of CPU values.\n"
+" -f Only usable with continuous mode. Pin ourself to the CPUs\n"
+" as requested, then instead of looping doing a high mq\n"
+" workload, just busy loop. This will allow us to lock up a\n"
+" single CPU just like we normally would, but without actually\n"
+" thrashing the CPU cache. This is to make it easier to get\n"
+" comparable numbers from some other workload running on the\n"
+" other CPUs. One set of numbers with # CPUs locked up running\n"
+" an mq workload, and another set of numbers with those same\n"
+" CPUs locked away from the test workload, but not doing\n"
+" anything to trash the cache like the mq workload might.\n"
+" path Path name of the message queue to create\n"
+"\n"
+" Note: this program must be run as root in order to enable all tests\n"
+"\n";
+
+char *MAX_MSGS = "/proc/sys/fs/mqueue/msg_max";
+char *MAX_MSGSIZE = "/proc/sys/fs/mqueue/msgsize_max";
+
+#define min(a, b) ((a) < (b) ? (a) : (b))
+#define MAX_CPUS 64
+char *cpu_option_string;
+int cpus_to_pin[MAX_CPUS];
+int num_cpus_to_pin;
+pthread_t cpu_threads[MAX_CPUS];
+pthread_t main_thread;
+cpu_set_t *cpu_set;
+int cpu_set_size;
+int cpus_online;
+
+#define MSG_SIZE 16
+#define TEST1_LOOPS 10000000
+#define TEST2_LOOPS 100000
+int continuous_mode;
+int continuous_mode_fake;
+
+struct rlimit saved_limits, cur_limits;
+int saved_max_msgs, saved_max_msgsize;
+int cur_max_msgs, cur_max_msgsize;
+FILE *max_msgs, *max_msgsize;
+int cur_nice;
+char *queue_path = "/mq_perf_tests";
+mqd_t queue = -1;
+struct mq_attr result;
+int mq_prio_max;
+
+const struct poptOption options[] = {
+ {
+ .longName = "continuous",
+ .shortName = 'c',
+ .argInfo = POPT_ARG_STRING,
+ .arg = &cpu_option_string,
+ .val = 'c',
+ .descrip = "Run continuous tests at a high queue depth in "
+ "order to test the effects of cache thrashing on "
+ "other tasks on the system. This test is intended "
+ "to be run on one core of each physical CPU while "
+ "some other CPU intensive task is run on all the other "
+ "cores of that same physical CPU and the other task "
+ "is timed. It is assumed that the process of adding "
+ "messages to the message queue in a tight loop will "
+ "impact that other task to some degree. Once the "
+ "tests are performed in this way, you should then "
+ "re-run the tests using fake mode in order to check "
+ "the difference in time required to perform the CPU "
+ "intensive task",
+ .argDescrip = "cpu[,cpu]",
+ },
+ {
+ .longName = "fake",
+ .shortName = 'f',
+ .argInfo = POPT_ARG_NONE,
+ .arg = &continuous_mode_fake,
+ .val = 0,
+ .descrip = "Tie up the CPUs that we would normally tie up in"
+ "continuous mode, but don't actually do any mq stuff, "
+ "just keep the CPU busy so it can't be used to process "
+ "system level tasks as this would free up resources on "
+ "the other CPU cores and skew the comparison between "
+ "the no-mqueue work and mqueue work tests",
+ .argDescrip = NULL,
+ },
+ {
+ .longName = "path",
+ .shortName = 'p',
+ .argInfo = POPT_ARG_STRING | POPT_ARGFLAG_SHOW_DEFAULT,
+ .arg = &queue_path,
+ .val = 'p',
+ .descrip = "The name of the path to use in the mqueue "
+ "filesystem for our tests",
+ .argDescrip = "pathname",
+ },
+ POPT_AUTOHELP
+ POPT_TABLEEND
+};
+
+static inline void __set(FILE *stream, int value, char *err_msg);
+void shutdown(int exit_val, char *err_cause, int line_no);
+void sig_action_SIGUSR1(int signum, siginfo_t *info, void *context);
+void sig_action(int signum, siginfo_t *info, void *context);
+static inline int get(FILE *stream);
+static inline void set(FILE *stream, int value);
+static inline int try_set(FILE *stream, int value);
+static inline void getr(int type, struct rlimit *rlim);
+static inline void setr(int type, struct rlimit *rlim);
+static inline void open_queue(struct mq_attr *attr);
+void increase_limits(void);
+
+static inline void __set(FILE *stream, int value, char *err_msg)
+{
+ rewind(stream);
+ if (fprintf(stream, "%d", value) < 0)
+ perror(err_msg);
+}
+
+
+void shutdown(int exit_val, char *err_cause, int line_no)
+{
+ static int in_shutdown = 0;
+ int errno_at_shutdown = errno;
+ int i;
+
+ /* In case we get called by multiple threads or from an sighandler */
+ if (in_shutdown++)
+ return;
+
+ for (i = 0; i < num_cpus_to_pin; i++)
+ if (cpu_threads[i]) {
+ pthread_kill(cpu_threads[i], SIGUSR1);
+ pthread_join(cpu_threads[i], NULL);
+ }
+
+ if (queue != -1)
+ if (mq_close(queue))
+ perror("mq_close() during shutdown");
+ if (queue_path)
+ /*
+ * Be silent if this fails, if we cleaned up already it's
+ * expected to fail
+ */
+ mq_unlink(queue_path);
+ if (saved_max_msgs)
+ __set(max_msgs, saved_max_msgs,
+ "failed to restore saved_max_msgs");
+ if (saved_max_msgsize)
+ __set(max_msgsize, saved_max_msgsize,
+ "failed to restore saved_max_msgsize");
+ if (exit_val)
+ error(exit_val, errno_at_shutdown, "%s at %d",
+ err_cause, line_no);
+ exit(0);
+}
+
+void sig_action_SIGUSR1(int signum, siginfo_t *info, void *context)
+{
+ if (pthread_self() != main_thread)
+ pthread_exit(0);
+ else {
+ fprintf(stderr, "Caught signal %d in SIGUSR1 handler, "
+ "exiting\n", signum);
+ shutdown(0, "", 0);
+ fprintf(stderr, "\n\nReturned from shutdown?!?!\n\n");
+ exit(0);
+ }
+}
+
+void sig_action(int signum, siginfo_t *info, void *context)
+{
+ if (pthread_self() != main_thread)
+ pthread_kill(main_thread, signum);
+ else {
+ fprintf(stderr, "Caught signal %d, exiting\n", signum);
+ shutdown(0, "", 0);
+ fprintf(stderr, "\n\nReturned from shutdown?!?!\n\n");
+ exit(0);
+ }
+}
+
+static inline int get(FILE *stream)
+{
+ int value;
+ rewind(stream);
+ if (fscanf(stream, "%d", &value) != 1)
+ shutdown(4, "Error reading /proc entry", __LINE__);
+ return value;
+}
+
+static inline void set(FILE *stream, int value)
+{
+ int new_value;
+
+ rewind(stream);
+ if (fprintf(stream, "%d", value) < 0)
+ return shutdown(5, "Failed writing to /proc file", __LINE__);
+ new_value = get(stream);
+ if (new_value != value)
+ return shutdown(5, "We didn't get what we wrote to /proc back",
+ __LINE__);
+}
+
+static inline int try_set(FILE *stream, int value)
+{
+ int new_value;
+
+ rewind(stream);
+ fprintf(stream, "%d", value);
+ new_value = get(stream);
+ return new_value == value;
+}
+
+static inline void getr(int type, struct rlimit *rlim)
+{
+ if (getrlimit(type, rlim))
+ shutdown(6, "getrlimit()", __LINE__);
+}
+
+static inline void setr(int type, struct rlimit *rlim)
+{
+ if (setrlimit(type, rlim))
+ shutdown(7, "setrlimit()", __LINE__);
+}
+
+/**
+ * open_queue - open the global queue for testing
+ * @attr - An attr struct specifying the desired queue traits
+ * @result - An attr struct that lists the actual traits the queue has
+ *
+ * This open is not allowed to fail, failure will result in an orderly
+ * shutdown of the program. The global queue_path is used to set what
+ * queue to open, the queue descriptor is saved in the global queue
+ * variable.
+ */
+static inline void open_queue(struct mq_attr *attr)
+{
+ int flags = O_RDWR | O_EXCL | O_CREAT | O_NONBLOCK;
+ int perms = DEFFILEMODE;
+
+ queue = mq_open(queue_path, flags, perms, attr);
+ if (queue == -1)
+ shutdown(1, "mq_open()", __LINE__);
+ if (mq_getattr(queue, &result))
+ shutdown(1, "mq_getattr()", __LINE__);
+ printf("\n\tQueue %s created:\n", queue_path);
+ printf("\t\tmq_flags:\t\t\t%s\n", result.mq_flags & O_NONBLOCK ?
+ "O_NONBLOCK" : "(null)");
+ printf("\t\tmq_maxmsg:\t\t\t%d\n", result.mq_maxmsg);
+ printf("\t\tmq_msgsize:\t\t\t%d\n", result.mq_msgsize);
+ printf("\t\tmq_curmsgs:\t\t\t%d\n", result.mq_curmsgs);
+}
+
+void *fake_cont_thread(void *arg)
+{
+ int i;
+
+ for (i = 0; i < num_cpus_to_pin; i++)
+ if (cpu_threads[i] == pthread_self())
+ break;
+ printf("\tStarted fake continuous mode thread %d on CPU %d\n", i,
+ cpus_to_pin[i]);
+ while (1)
+ ;
+}
+
+void *cont_thread(void *arg)
+{
+ char buff[MSG_SIZE];
+ int i, priority;
+
+ for (i = 0; i < num_cpus_to_pin; i++)
+ if (cpu_threads[i] == pthread_self())
+ break;
+ printf("\tStarted continuous mode thread %d on CPU %d\n", i,
+ cpus_to_pin[i]);
+ while (1) {
+ while (mq_send(queue, buff, sizeof(buff), 0) == 0)
+ ;
+ mq_receive(queue, buff, sizeof(buff), &priority);
+ }
+}
+
+#define drain_queue() \
+ while (mq_receive(queue, buff, MSG_SIZE, &prio_in) == MSG_SIZE)
+
+#define do_untimed_send() \
+ do { \
+ if (mq_send(queue, buff, MSG_SIZE, prio_out)) \
+ shutdown(3, "Test send failure", __LINE__); \
+ } while (0)
+
+#define do_send_recv() \
+ do { \
+ clock_gettime(clock, &start); \
+ if (mq_send(queue, buff, MSG_SIZE, prio_out)) \
+ shutdown(3, "Test send failure", __LINE__); \
+ clock_gettime(clock, &middle); \
+ if (mq_receive(queue, buff, MSG_SIZE, &prio_in) != MSG_SIZE) \
+ shutdown(3, "Test receive failure", __LINE__); \
+ clock_gettime(clock, &end); \
+ nsec = ((middle.tv_sec - start.tv_sec) * 1000000000) + \
+ (middle.tv_nsec - start.tv_nsec); \
+ send_total.tv_nsec += nsec; \
+ if (send_total.tv_nsec >= 1000000000) { \
+ send_total.tv_sec++; \
+ send_total.tv_nsec -= 1000000000; \
+ } \
+ nsec = ((end.tv_sec - middle.tv_sec) * 1000000000) + \
+ (end.tv_nsec - middle.tv_nsec); \
+ recv_total.tv_nsec += nsec; \
+ if (recv_total.tv_nsec >= 1000000000) { \
+ recv_total.tv_sec++; \
+ recv_total.tv_nsec -= 1000000000; \
+ } \
+ } while (0)
+
+struct test {
+ char *desc;
+ void (*func)(int *);
+};
+
+void const_prio(int *prio)
+{
+ return;
+}
+
+void inc_prio(int *prio)
+{
+ if (++*prio == mq_prio_max)
+ *prio = 0;
+}
+
+void dec_prio(int *prio)
+{
+ if (--*prio < 0)
+ *prio = mq_prio_max - 1;
+}
+
+void random_prio(int *prio)
+{
+ *prio = random() % mq_prio_max;
+}
+
+struct test test2[] = {
+ {"\n\tTest #2a: Time send/recv message, queue full, constant prio\n",
+ const_prio},
+ {"\n\tTest #2b: Time send/recv message, queue full, increasing prio\n",
+ inc_prio},
+ {"\n\tTest #2c: Time send/recv message, queue full, decreasing prio\n",
+ dec_prio},
+ {"\n\tTest #2d: Time send/recv message, queue full, random prio\n",
+ random_prio},
+ {NULL, NULL}
+};
+
+/**
+ * Tests to perform (all done with MSG_SIZE messages):
+ *
+ * 1) Time to add/remove message with 0 messages on queue
+ * 1a) with constant prio
+ * 2) Time to add/remove message when queue close to capacity:
+ * 2a) with constant prio
+ * 2b) with increasing prio
+ * 2c) with decreasing prio
+ * 2d) with random prio
+ * 3) Test limits of priorities honored (double check _SC_MQ_PRIO_MAX)
+ */
+void *perf_test_thread(void *arg)
+{
+ char buff[MSG_SIZE];
+ int prio_out, prio_in;
+ int i;
+ clockid_t clock;
+ pthread_t *t;
+ struct timespec res, start, middle, end, send_total, recv_total;
+ unsigned long long nsec;
+ struct test *cur_test;
+
+ t = &cpu_threads[0];
+ printf("\n\tStarted mqueue performance test thread on CPU %d\n",
+ cpus_to_pin[0]);
+ mq_prio_max = sysconf(_SC_MQ_PRIO_MAX);
+ if (mq_prio_max == -1)
+ shutdown(2, "sysconf(_SC_MQ_PRIO_MAX)", __LINE__);
+ if (pthread_getcpuclockid(cpu_threads[0], &clock) != 0)
+ shutdown(2, "pthread_getcpuclockid", __LINE__);
+
+ if (clock_getres(clock, &res))
+ shutdown(2, "clock_getres()", __LINE__);
+
+ printf("\t\tMax priorities:\t\t\t%d\n", mq_prio_max);
+ printf("\t\tClock resolution:\t\t%d nsec%s\n", res.tv_nsec,
+ res.tv_nsec > 1 ? "s" : "");
+
+
+
+ printf("\n\tTest #1: Time send/recv message, queue empty\n");
+ printf("\t\t(%d iterations)\n", TEST1_LOOPS);
+ prio_out = 0;
+ send_total.tv_sec = 0;
+ send_total.tv_nsec = 0;
+ recv_total.tv_sec = 0;
+ recv_total.tv_nsec = 0;
+ for (i = 0; i < TEST1_LOOPS; i++)
+ do_send_recv();
+ printf("\t\tSend msg:\t\t\t%d.%ds total time\n",
+ send_total.tv_sec, send_total.tv_nsec);
+ nsec = ((unsigned long long)send_total.tv_sec * 1000000000 +
+ send_total.tv_nsec) / TEST1_LOOPS;
+ printf("\t\t\t\t\t\t%d nsec/msg\n", nsec);
+ printf("\t\tRecv msg:\t\t\t%d.%ds total time\n",
+ recv_total.tv_sec, recv_total.tv_nsec);
+ nsec = ((unsigned long long)recv_total.tv_sec * 1000000000 +
+ recv_total.tv_nsec) / TEST1_LOOPS;
+ printf("\t\t\t\t\t\t%d nsec/msg\n", nsec);
+
+
+ for (cur_test = test2; cur_test->desc != NULL; cur_test++) {
+ printf(cur_test->desc);
+ printf("\t\t(%d iterations)\n", TEST2_LOOPS);
+ prio_out = 0;
+ send_total.tv_sec = 0;
+ send_total.tv_nsec = 0;
+ recv_total.tv_sec = 0;
+ recv_total.tv_nsec = 0;
+ printf("\t\tFilling queue...");
+ fflush(stdout);
+ clock_gettime(clock, &start);
+ for (i = 0; i < result.mq_maxmsg - 1; i++) {
+ do_untimed_send();
+ cur_test->func(&prio_out);
+ }
+ clock_gettime(clock, &end);
+ nsec = ((unsigned long long)(end.tv_sec - start.tv_sec) *
+ 1000000000) + (end.tv_nsec - start.tv_nsec);
+ printf("done.\t\t%lld.%llds\n", nsec / 1000000000,
+ nsec % 1000000000);
+ printf("\t\tTesting...");
+ fflush(stdout);
+ for (i = 0; i < TEST2_LOOPS; i++) {
+ do_send_recv();
+ cur_test->func(&prio_out);
+ }
+ printf("done.\n");
+ printf("\t\tSend msg:\t\t\t%d.%ds total time\n",
+ send_total.tv_sec, send_total.tv_nsec);
+ nsec = ((unsigned long long)send_total.tv_sec * 1000000000 +
+ send_total.tv_nsec) / TEST2_LOOPS;
+ printf("\t\t\t\t\t\t%d nsec/msg\n", nsec);
+ printf("\t\tRecv msg:\t\t\t%d.%ds total time\n",
+ recv_total.tv_sec, recv_total.tv_nsec);
+ nsec = ((unsigned long long)recv_total.tv_sec * 1000000000 +
+ recv_total.tv_nsec) / TEST2_LOOPS;
+ printf("\t\t\t\t\t\t%d nsec/msg\n", nsec);
+ printf("\t\tDraining queue...");
+ fflush(stdout);
+ clock_gettime(clock, &start);
+ drain_queue();
+ clock_gettime(clock, &end);
+ nsec = ((unsigned long long)(end.tv_sec - start.tv_sec) *
+ 1000000000) + (end.tv_nsec - start.tv_nsec);
+ printf("done.\t\t%lld.%llds\n", nsec / 1000000000,
+ nsec % 1000000000);
+ }
+ return 0;
+}
+
+void increase_limits(void)
+{
+ cur_limits.rlim_cur = RLIM_INFINITY;
+ cur_limits.rlim_max = RLIM_INFINITY;
+ setr(RLIMIT_MSGQUEUE, &cur_limits);
+ while (try_set(max_msgs, cur_max_msgs += 10))
+ ;
+ cur_max_msgs = get(max_msgs);
+ while (try_set(max_msgsize, cur_max_msgsize += 1024))
+ ;
+ cur_max_msgsize = get(max_msgsize);
+ if (setpriority(PRIO_PROCESS, 0, -20) != 0)
+ shutdown(2, "setpriority()", __LINE__);
+ cur_nice = -20;
+}
+
+int main(int argc, char *argv[])
+{
+ struct mq_attr attr;
+ char *option, *next_option;
+ int i, cpu;
+ struct sigaction sa;
+ poptContext popt_context;
+ char rc;
+ void *retval;
+
+ main_thread = pthread_self();
+ num_cpus_to_pin = 0;
+
+ if (sysconf(_SC_NPROCESSORS_ONLN) == -1) {
+ perror("sysconf(_SC_NPROCESSORS_ONLN)");
+ exit(1);
+ }
+ cpus_online = min(MAX_CPUS, sysconf(_SC_NPROCESSORS_ONLN));
+ cpu_set = CPU_ALLOC(cpus_online);
+ if (cpu_set == NULL) {
+ perror("CPU_ALLOC()");
+ exit(1);
+ }
+ cpu_set_size = CPU_ALLOC_SIZE(cpus_online);
+ CPU_ZERO_S(cpu_set_size, cpu_set);
+
+ popt_context = poptGetContext(NULL, argc, (const char **)argv,
+ options, 0);
+
+ while ((rc = poptGetNextOpt(popt_context)) > 0) {
+ switch (rc) {
+ case 'c':
+ continuous_mode = 1;
+ option = cpu_option_string;
+ do {
+ next_option = strchr(option, ',');
+ if (next_option)
+ *next_option = '\0';
+ cpu = atoi(option);
+ if (cpu >= cpus_online)
+ fprintf(stderr, "CPU %d exceeds "
+ "cpus online, ignoring.\n",
+ cpu);
+ else
+ cpus_to_pin[num_cpus_to_pin++] = cpu;
+ if (next_option)
+ option = ++next_option;
+ } while (next_option && num_cpus_to_pin < MAX_CPUS);
+ /* Double check that they didn't give us the same CPU
+ * more than once */
+ for (cpu = 0; cpu < num_cpus_to_pin; cpu++) {
+ if (CPU_ISSET_S(cpus_to_pin[cpu], cpu_set_size,
+ cpu_set)) {
+ fprintf(stderr, "Any given CPU may "
+ "only be given once.\n");
+ exit(1);
+ } else
+ CPU_SET_S(cpus_to_pin[cpu],
+ cpu_set_size, cpu_set);
+ }
+ break;
+ case 'p':
+ /*
+ * Although we can create a msg queue with a
+ * non-absolute path name, unlink will fail. So,
+ * if the name doesn't start with a /, add one
+ * when we save it.
+ */
+ option = queue_path;
+ if (*option != '/') {
+ queue_path = malloc(strlen(option) + 2);
+ if (!queue_path) {
+ perror("malloc()");
+ exit(1);
+ }
+ queue_path[0] = '/';
+ queue_path[1] = 0;
+ strcat(queue_path, option);
+ free(option);
+ }
+ break;
+ }
+ }
+
+ if (continuous_mode && num_cpus_to_pin == 0) {
+ fprintf(stderr, "Must pass at least one CPU to continuous "
+ "mode.\n");
+ poptPrintUsage(popt_context, stderr, 0);
+ exit(1);
+ } else if (!continuous_mode) {
+ num_cpus_to_pin = 1;
+ cpus_to_pin[0] = cpus_online - 1;
+ }
+
+ if (getuid() != 0) {
+ fprintf(stderr, "Not running as root, but almost all tests "
+ "require root in order to modify\nsystem settings. "
+ "Exiting.\n");
+ exit(1);
+ }
+
+ max_msgs = fopen(MAX_MSGS, "r+");
+ max_msgsize = fopen(MAX_MSGSIZE, "r+");
+ if (!max_msgs)
+ shutdown(2, "Failed to open msg_max", __LINE__);
+ if (!max_msgsize)
+ shutdown(2, "Failed to open msgsize_max", __LINE__);
+
+ /* Load up the current system values for everything we can */
+ getr(RLIMIT_MSGQUEUE, &saved_limits);
+ cur_limits = saved_limits;
+ saved_max_msgs = cur_max_msgs = get(max_msgs);
+ saved_max_msgsize = cur_max_msgsize = get(max_msgsize);
+ errno = 0;
+ cur_nice = getpriority(PRIO_PROCESS, 0);
+ if (errno)
+ shutdown(2, "getpriority()", __LINE__);
+
+ /* Tell the user our initial state */
+ printf("\nInitial system state:\n");
+ printf("\tUsing queue path:\t\t\t%s\n", queue_path);
+ printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t%d\n", saved_limits.rlim_cur);
+ printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t%d\n", saved_limits.rlim_max);
+ printf("\tMaximum Message Size:\t\t\t%d\n", saved_max_msgsize);
+ printf("\tMaximum Queue Size:\t\t\t%d\n", saved_max_msgs);
+ printf("\tNice value:\t\t\t\t%d\n", cur_nice);
+ printf("\n");
+
+ increase_limits();
+
+ printf("Adjusted system state for testing:\n");
+ if (cur_limits.rlim_cur == RLIM_INFINITY) {
+ printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t(unlimited)\n");
+ printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t(unlimited)\n");
+ } else {
+ printf("\tRLIMIT_MSGQUEUE(soft):\t\t\t%d\n",
+ cur_limits.rlim_cur);
+ printf("\tRLIMIT_MSGQUEUE(hard):\t\t\t%d\n",
+ cur_limits.rlim_max);
+ }
+ printf("\tMaximum Message Size:\t\t\t%d\n", cur_max_msgsize);
+ printf("\tMaximum Queue Size:\t\t\t%d\n", cur_max_msgs);
+ printf("\tNice value:\t\t\t\t%d\n", cur_nice);
+ printf("\tContinuous mode:\t\t\t(%s)\n", continuous_mode ?
+ (continuous_mode_fake ? "fake mode" : "enabled") :
+ "disabled");
+ printf("\tCPUs to pin:\t\t\t\t%d", cpus_to_pin[0]);
+ for (cpu = 1; cpu < num_cpus_to_pin; cpu++)
+ printf(",%d", cpus_to_pin[cpu]);
+ printf("\n");
+
+ sa.sa_sigaction = sig_action_SIGUSR1;
+ sigemptyset(&sa.sa_mask);
+ sigaddset(&sa.sa_mask, SIGHUP);
+ sigaddset(&sa.sa_mask, SIGINT);
+ sigaddset(&sa.sa_mask, SIGQUIT);
+ sigaddset(&sa.sa_mask, SIGTERM);
+ sa.sa_flags = SA_SIGINFO;
+ if (sigaction(SIGUSR1, &sa, NULL) == -1)
+ shutdown(1, "sigaction(SIGUSR1)", __LINE__);
+ sa.sa_sigaction = sig_action;
+ if (sigaction(SIGHUP, &sa, NULL) == -1)
+ shutdown(1, "sigaction(SIGHUP)", __LINE__);
+ if (sigaction(SIGINT, &sa, NULL) == -1)
+ shutdown(1, "sigaction(SIGINT)", __LINE__);
+ if (sigaction(SIGQUIT, &sa, NULL) == -1)
+ shutdown(1, "sigaction(SIGQUIT)", __LINE__);
+ if (sigaction(SIGTERM, &sa, NULL) == -1)
+ shutdown(1, "sigaction(SIGTERM)", __LINE__);
+
+ if (!continuous_mode_fake) {
+ attr.mq_flags = O_NONBLOCK;
+ attr.mq_maxmsg = cur_max_msgs;
+ attr.mq_msgsize = MSG_SIZE;
+ open_queue(&attr);
+ }
+ for (i = 0; i < num_cpus_to_pin; i++) {
+ pthread_attr_t thread_attr;
+ void *thread_func;
+
+ if (continuous_mode_fake)
+ thread_func = &fake_cont_thread;
+ else if (continuous_mode)
+ thread_func = &cont_thread;
+ else
+ thread_func = &perf_test_thread;
+
+ CPU_ZERO_S(cpu_set_size, cpu_set);
+ CPU_SET_S(cpus_to_pin[i], cpu_set_size, cpu_set);
+ pthread_attr_init(&thread_attr);
+ pthread_attr_setaffinity_np(&thread_attr, cpu_set_size,
+ cpu_set);
+ if (pthread_create(&cpu_threads[i], &thread_attr, thread_func,
+ NULL))
+ shutdown(1, "pthread_create()", __LINE__);
+ pthread_attr_destroy(&thread_attr);
+ }
+
+ if (!continuous_mode) {
+ pthread_join(cpu_threads[0], &retval);
+ shutdown((long)retval, "perf_test_thread()", __LINE__);
+ } else {
+ while (1)
+ sleep(1);
+ }
+ shutdown(0, "", 0);
+}
diff --git a/tools/testing/selftests/net/.gitignore b/tools/testing/selftests/net/.gitignore
new file mode 100644
index 00000000000..00326629d4a
--- /dev/null
+++ b/tools/testing/selftests/net/.gitignore
@@ -0,0 +1,3 @@
+socket
+psock_fanout
+psock_tpacket
diff --git a/tools/testing/selftests/net/Makefile b/tools/testing/selftests/net/Makefile
new file mode 100644
index 00000000000..c7493b8f9b0
--- /dev/null
+++ b/tools/testing/selftests/net/Makefile
@@ -0,0 +1,25 @@
+# Makefile for net selftests
+
+CC = $(CROSS_COMPILE)gcc
+CFLAGS = -Wall -O2 -g
+
+CFLAGS += -I../../../../usr/include/
+
+NET_PROGS = socket psock_fanout psock_tpacket
+
+all: $(NET_PROGS)
+%: %.c
+ $(CC) $(CFLAGS) -o $@ $^
+
+run_tests: all
+ @/bin/sh ./run_netsocktests || echo "sockettests: [FAIL]"
+ @/bin/sh ./run_afpackettests || echo "afpackettests: [FAIL]"
+ @if /sbin/modprobe test_bpf ; then \
+ /sbin/rmmod test_bpf; \
+ echo "test_bpf: ok"; \
+ else \
+ echo "test_bpf: [FAIL]"; \
+ exit 1; \
+ fi
+clean:
+ $(RM) $(NET_PROGS)
diff --git a/tools/testing/selftests/net/psock_fanout.c b/tools/testing/selftests/net/psock_fanout.c
new file mode 100644
index 00000000000..57b9c2b7c4f
--- /dev/null
+++ b/tools/testing/selftests/net/psock_fanout.c
@@ -0,0 +1,312 @@
+/*
+ * Copyright 2013 Google Inc.
+ * Author: Willem de Bruijn (willemb@google.com)
+ *
+ * A basic test of packet socket fanout behavior.
+ *
+ * Control:
+ * - create fanout fails as expected with illegal flag combinations
+ * - join fanout fails as expected with diverging types or flags
+ *
+ * Datapath:
+ * Open a pair of packet sockets and a pair of INET sockets, send a known
+ * number of packets across the two INET sockets and count the number of
+ * packets enqueued onto the two packet sockets.
+ *
+ * The test currently runs for
+ * - PACKET_FANOUT_HASH
+ * - PACKET_FANOUT_HASH with PACKET_FANOUT_FLAG_ROLLOVER
+ * - PACKET_FANOUT_LB
+ * - PACKET_FANOUT_CPU
+ * - PACKET_FANOUT_ROLLOVER
+ *
+ * Todo:
+ * - functionality: PACKET_FANOUT_FLAG_DEFRAG
+ *
+ * License (GPLv2):
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#define _GNU_SOURCE /* for sched_setaffinity */
+
+#include <arpa/inet.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/filter.h>
+#include <linux/if_packet.h>
+#include <net/ethernet.h>
+#include <netinet/ip.h>
+#include <netinet/udp.h>
+#include <poll.h>
+#include <sched.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "psock_lib.h"
+
+#define RING_NUM_FRAMES 20
+
+/* Open a socket in a given fanout mode.
+ * @return -1 if mode is bad, a valid socket otherwise */
+static int sock_fanout_open(uint16_t typeflags, int num_packets)
+{
+ int fd, val;
+
+ fd = socket(PF_PACKET, SOCK_DGRAM, htons(ETH_P_IP));
+ if (fd < 0) {
+ perror("socket packet");
+ exit(1);
+ }
+
+ /* fanout group ID is always 0: tests whether old groups are deleted */
+ val = ((int) typeflags) << 16;
+ if (setsockopt(fd, SOL_PACKET, PACKET_FANOUT, &val, sizeof(val))) {
+ if (close(fd)) {
+ perror("close packet");
+ exit(1);
+ }
+ return -1;
+ }
+
+ pair_udp_setfilter(fd);
+ return fd;
+}
+
+static char *sock_fanout_open_ring(int fd)
+{
+ struct tpacket_req req = {
+ .tp_block_size = getpagesize(),
+ .tp_frame_size = getpagesize(),
+ .tp_block_nr = RING_NUM_FRAMES,
+ .tp_frame_nr = RING_NUM_FRAMES,
+ };
+ char *ring;
+ int val = TPACKET_V2;
+
+ if (setsockopt(fd, SOL_PACKET, PACKET_VERSION, (void *) &val,
+ sizeof(val))) {
+ perror("packetsock ring setsockopt version");
+ exit(1);
+ }
+ if (setsockopt(fd, SOL_PACKET, PACKET_RX_RING, (void *) &req,
+ sizeof(req))) {
+ perror("packetsock ring setsockopt");
+ exit(1);
+ }
+
+ ring = mmap(0, req.tp_block_size * req.tp_block_nr,
+ PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
+ if (!ring) {
+ fprintf(stderr, "packetsock ring mmap\n");
+ exit(1);
+ }
+
+ return ring;
+}
+
+static int sock_fanout_read_ring(int fd, void *ring)
+{
+ struct tpacket2_hdr *header = ring;
+ int count = 0;
+
+ while (header->tp_status & TP_STATUS_USER && count < RING_NUM_FRAMES) {
+ count++;
+ header = ring + (count * getpagesize());
+ }
+
+ return count;
+}
+
+static int sock_fanout_read(int fds[], char *rings[], const int expect[])
+{
+ int ret[2];
+
+ ret[0] = sock_fanout_read_ring(fds[0], rings[0]);
+ ret[1] = sock_fanout_read_ring(fds[1], rings[1]);
+
+ fprintf(stderr, "info: count=%d,%d, expect=%d,%d\n",
+ ret[0], ret[1], expect[0], expect[1]);
+
+ if ((!(ret[0] == expect[0] && ret[1] == expect[1])) &&
+ (!(ret[0] == expect[1] && ret[1] == expect[0]))) {
+ fprintf(stderr, "ERROR: incorrect queue lengths\n");
+ return 1;
+ }
+
+ return 0;
+}
+
+/* Test illegal mode + flag combination */
+static void test_control_single(void)
+{
+ fprintf(stderr, "test: control single socket\n");
+
+ if (sock_fanout_open(PACKET_FANOUT_ROLLOVER |
+ PACKET_FANOUT_FLAG_ROLLOVER, 0) != -1) {
+ fprintf(stderr, "ERROR: opened socket with dual rollover\n");
+ exit(1);
+ }
+}
+
+/* Test illegal group with different modes or flags */
+static void test_control_group(void)
+{
+ int fds[2];
+
+ fprintf(stderr, "test: control multiple sockets\n");
+
+ fds[0] = sock_fanout_open(PACKET_FANOUT_HASH, 20);
+ if (fds[0] == -1) {
+ fprintf(stderr, "ERROR: failed to open HASH socket\n");
+ exit(1);
+ }
+ if (sock_fanout_open(PACKET_FANOUT_HASH |
+ PACKET_FANOUT_FLAG_DEFRAG, 10) != -1) {
+ fprintf(stderr, "ERROR: joined group with wrong flag defrag\n");
+ exit(1);
+ }
+ if (sock_fanout_open(PACKET_FANOUT_HASH |
+ PACKET_FANOUT_FLAG_ROLLOVER, 10) != -1) {
+ fprintf(stderr, "ERROR: joined group with wrong flag ro\n");
+ exit(1);
+ }
+ if (sock_fanout_open(PACKET_FANOUT_CPU, 10) != -1) {
+ fprintf(stderr, "ERROR: joined group with wrong mode\n");
+ exit(1);
+ }
+ fds[1] = sock_fanout_open(PACKET_FANOUT_HASH, 20);
+ if (fds[1] == -1) {
+ fprintf(stderr, "ERROR: failed to join group\n");
+ exit(1);
+ }
+ if (close(fds[1]) || close(fds[0])) {
+ fprintf(stderr, "ERROR: closing sockets\n");
+ exit(1);
+ }
+}
+
+static int test_datapath(uint16_t typeflags, int port_off,
+ const int expect1[], const int expect2[])
+{
+ const int expect0[] = { 0, 0 };
+ char *rings[2];
+ int fds[2], fds_udp[2][2], ret;
+
+ fprintf(stderr, "test: datapath 0x%hx\n", typeflags);
+
+ fds[0] = sock_fanout_open(typeflags, 20);
+ fds[1] = sock_fanout_open(typeflags, 20);
+ if (fds[0] == -1 || fds[1] == -1) {
+ fprintf(stderr, "ERROR: failed open\n");
+ exit(1);
+ }
+ rings[0] = sock_fanout_open_ring(fds[0]);
+ rings[1] = sock_fanout_open_ring(fds[1]);
+ pair_udp_open(fds_udp[0], PORT_BASE);
+ pair_udp_open(fds_udp[1], PORT_BASE + port_off);
+ sock_fanout_read(fds, rings, expect0);
+
+ /* Send data, but not enough to overflow a queue */
+ pair_udp_send(fds_udp[0], 15);
+ pair_udp_send(fds_udp[1], 5);
+ ret = sock_fanout_read(fds, rings, expect1);
+
+ /* Send more data, overflow the queue */
+ pair_udp_send(fds_udp[0], 15);
+ /* TODO: ensure consistent order between expect1 and expect2 */
+ ret |= sock_fanout_read(fds, rings, expect2);
+
+ if (munmap(rings[1], RING_NUM_FRAMES * getpagesize()) ||
+ munmap(rings[0], RING_NUM_FRAMES * getpagesize())) {
+ fprintf(stderr, "close rings\n");
+ exit(1);
+ }
+ if (close(fds_udp[1][1]) || close(fds_udp[1][0]) ||
+ close(fds_udp[0][1]) || close(fds_udp[0][0]) ||
+ close(fds[1]) || close(fds[0])) {
+ fprintf(stderr, "close datapath\n");
+ exit(1);
+ }
+
+ return ret;
+}
+
+static int set_cpuaffinity(int cpuid)
+{
+ cpu_set_t mask;
+
+ CPU_ZERO(&mask);
+ CPU_SET(cpuid, &mask);
+ if (sched_setaffinity(0, sizeof(mask), &mask)) {
+ if (errno != EINVAL) {
+ fprintf(stderr, "setaffinity %d\n", cpuid);
+ exit(1);
+ }
+ return 1;
+ }
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ const int expect_hash[2][2] = { { 15, 5 }, { 20, 5 } };
+ const int expect_hash_rb[2][2] = { { 15, 5 }, { 20, 15 } };
+ const int expect_lb[2][2] = { { 10, 10 }, { 18, 17 } };
+ const int expect_rb[2][2] = { { 20, 0 }, { 20, 15 } };
+ const int expect_cpu0[2][2] = { { 20, 0 }, { 20, 0 } };
+ const int expect_cpu1[2][2] = { { 0, 20 }, { 0, 20 } };
+ int port_off = 2, tries = 5, ret;
+
+ test_control_single();
+ test_control_group();
+
+ /* find a set of ports that do not collide onto the same socket */
+ ret = test_datapath(PACKET_FANOUT_HASH, port_off,
+ expect_hash[0], expect_hash[1]);
+ while (ret && tries--) {
+ fprintf(stderr, "info: trying alternate ports (%d)\n", tries);
+ ret = test_datapath(PACKET_FANOUT_HASH, ++port_off,
+ expect_hash[0], expect_hash[1]);
+ }
+
+ ret |= test_datapath(PACKET_FANOUT_HASH | PACKET_FANOUT_FLAG_ROLLOVER,
+ port_off, expect_hash_rb[0], expect_hash_rb[1]);
+ ret |= test_datapath(PACKET_FANOUT_LB,
+ port_off, expect_lb[0], expect_lb[1]);
+ ret |= test_datapath(PACKET_FANOUT_ROLLOVER,
+ port_off, expect_rb[0], expect_rb[1]);
+
+ set_cpuaffinity(0);
+ ret |= test_datapath(PACKET_FANOUT_CPU, port_off,
+ expect_cpu0[0], expect_cpu0[1]);
+ if (!set_cpuaffinity(1))
+ /* TODO: test that choice alternates with previous */
+ ret |= test_datapath(PACKET_FANOUT_CPU, port_off,
+ expect_cpu1[0], expect_cpu1[1]);
+
+ if (ret)
+ return 1;
+
+ printf("OK. All tests passed\n");
+ return 0;
+}
diff --git a/tools/testing/selftests/net/psock_lib.h b/tools/testing/selftests/net/psock_lib.h
new file mode 100644
index 00000000000..37da54ac85a
--- /dev/null
+++ b/tools/testing/selftests/net/psock_lib.h
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2013 Google Inc.
+ * Author: Willem de Bruijn <willemb@google.com>
+ * Daniel Borkmann <dborkman@redhat.com>
+ *
+ * License (GPLv2):
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#ifndef PSOCK_LIB_H
+#define PSOCK_LIB_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <string.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+
+#define DATA_LEN 100
+#define DATA_CHAR 'a'
+
+#define PORT_BASE 8000
+
+#ifndef __maybe_unused
+# define __maybe_unused __attribute__ ((__unused__))
+#endif
+
+static __maybe_unused void pair_udp_setfilter(int fd)
+{
+ struct sock_filter bpf_filter[] = {
+ { 0x80, 0, 0, 0x00000000 }, /* LD pktlen */
+ { 0x35, 0, 5, DATA_LEN }, /* JGE DATA_LEN [f goto nomatch]*/
+ { 0x30, 0, 0, 0x00000050 }, /* LD ip[80] */
+ { 0x15, 0, 3, DATA_CHAR }, /* JEQ DATA_CHAR [f goto nomatch]*/
+ { 0x30, 0, 0, 0x00000051 }, /* LD ip[81] */
+ { 0x15, 0, 1, DATA_CHAR }, /* JEQ DATA_CHAR [f goto nomatch]*/
+ { 0x06, 0, 0, 0x00000060 }, /* RET match */
+ { 0x06, 0, 0, 0x00000000 }, /* RET no match */
+ };
+ struct sock_fprog bpf_prog;
+
+ bpf_prog.filter = bpf_filter;
+ bpf_prog.len = sizeof(bpf_filter) / sizeof(struct sock_filter);
+ if (setsockopt(fd, SOL_SOCKET, SO_ATTACH_FILTER, &bpf_prog,
+ sizeof(bpf_prog))) {
+ perror("setsockopt SO_ATTACH_FILTER");
+ exit(1);
+ }
+}
+
+static __maybe_unused void pair_udp_open(int fds[], uint16_t port)
+{
+ struct sockaddr_in saddr, daddr;
+
+ fds[0] = socket(PF_INET, SOCK_DGRAM, 0);
+ fds[1] = socket(PF_INET, SOCK_DGRAM, 0);
+ if (fds[0] == -1 || fds[1] == -1) {
+ fprintf(stderr, "ERROR: socket dgram\n");
+ exit(1);
+ }
+
+ memset(&saddr, 0, sizeof(saddr));
+ saddr.sin_family = AF_INET;
+ saddr.sin_port = htons(port);
+ saddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ memset(&daddr, 0, sizeof(daddr));
+ daddr.sin_family = AF_INET;
+ daddr.sin_port = htons(port + 1);
+ daddr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
+
+ /* must bind both to get consistent hash result */
+ if (bind(fds[1], (void *) &daddr, sizeof(daddr))) {
+ perror("bind");
+ exit(1);
+ }
+ if (bind(fds[0], (void *) &saddr, sizeof(saddr))) {
+ perror("bind");
+ exit(1);
+ }
+ if (connect(fds[0], (void *) &daddr, sizeof(daddr))) {
+ perror("connect");
+ exit(1);
+ }
+}
+
+static __maybe_unused void pair_udp_send(int fds[], int num)
+{
+ char buf[DATA_LEN], rbuf[DATA_LEN];
+
+ memset(buf, DATA_CHAR, sizeof(buf));
+ while (num--) {
+ /* Should really handle EINTR and EAGAIN */
+ if (write(fds[0], buf, sizeof(buf)) != sizeof(buf)) {
+ fprintf(stderr, "ERROR: send failed left=%d\n", num);
+ exit(1);
+ }
+ if (read(fds[1], rbuf, sizeof(rbuf)) != sizeof(rbuf)) {
+ fprintf(stderr, "ERROR: recv failed left=%d\n", num);
+ exit(1);
+ }
+ if (memcmp(buf, rbuf, sizeof(buf))) {
+ fprintf(stderr, "ERROR: data failed left=%d\n", num);
+ exit(1);
+ }
+ }
+}
+
+static __maybe_unused void pair_udp_close(int fds[])
+{
+ close(fds[0]);
+ close(fds[1]);
+}
+
+#endif /* PSOCK_LIB_H */
diff --git a/tools/testing/selftests/net/psock_tpacket.c b/tools/testing/selftests/net/psock_tpacket.c
new file mode 100644
index 00000000000..24adf709bd9
--- /dev/null
+++ b/tools/testing/selftests/net/psock_tpacket.c
@@ -0,0 +1,805 @@
+/*
+ * Copyright 2013 Red Hat, Inc.
+ * Author: Daniel Borkmann <dborkman@redhat.com>
+ * Chetan Loke <loke.chetan@gmail.com> (TPACKET_V3 usage example)
+ *
+ * A basic test of packet socket's TPACKET_V1/TPACKET_V2/TPACKET_V3 behavior.
+ *
+ * Control:
+ * Test the setup of the TPACKET socket with different patterns that are
+ * known to fail (TODO) resp. succeed (OK).
+ *
+ * Datapath:
+ * Open a pair of packet sockets and send resp. receive an a priori known
+ * packet pattern accross the sockets and check if it was received resp.
+ * sent correctly. Fanout in combination with RX_RING is currently not
+ * tested here.
+ *
+ * The test currently runs for
+ * - TPACKET_V1: RX_RING, TX_RING
+ * - TPACKET_V2: RX_RING, TX_RING
+ * - TPACKET_V3: RX_RING
+ *
+ * License (GPLv2):
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms and conditions of the GNU General Public License,
+ * version 2, as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. * See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/mman.h>
+#include <linux/if_packet.h>
+#include <linux/filter.h>
+#include <ctype.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <bits/wordsize.h>
+#include <net/ethernet.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <stdint.h>
+#include <string.h>
+#include <assert.h>
+#include <net/if.h>
+#include <inttypes.h>
+#include <poll.h>
+
+#include "psock_lib.h"
+
+#ifndef bug_on
+# define bug_on(cond) assert(!(cond))
+#endif
+
+#ifndef __aligned_tpacket
+# define __aligned_tpacket __attribute__((aligned(TPACKET_ALIGNMENT)))
+#endif
+
+#ifndef __align_tpacket
+# define __align_tpacket(x) __attribute__((aligned(TPACKET_ALIGN(x))))
+#endif
+
+#define NUM_PACKETS 100
+#define ALIGN_8(x) (((x) + 8 - 1) & ~(8 - 1))
+
+struct ring {
+ struct iovec *rd;
+ uint8_t *mm_space;
+ size_t mm_len, rd_len;
+ struct sockaddr_ll ll;
+ void (*walk)(int sock, struct ring *ring);
+ int type, rd_num, flen, version;
+ union {
+ struct tpacket_req req;
+ struct tpacket_req3 req3;
+ };
+};
+
+struct block_desc {
+ uint32_t version;
+ uint32_t offset_to_priv;
+ struct tpacket_hdr_v1 h1;
+};
+
+union frame_map {
+ struct {
+ struct tpacket_hdr tp_h __aligned_tpacket;
+ struct sockaddr_ll s_ll __align_tpacket(sizeof(struct tpacket_hdr));
+ } *v1;
+ struct {
+ struct tpacket2_hdr tp_h __aligned_tpacket;
+ struct sockaddr_ll s_ll __align_tpacket(sizeof(struct tpacket2_hdr));
+ } *v2;
+ void *raw;
+};
+
+static unsigned int total_packets, total_bytes;
+
+static int pfsocket(int ver)
+{
+ int ret, sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
+ if (sock == -1) {
+ perror("socket");
+ exit(1);
+ }
+
+ ret = setsockopt(sock, SOL_PACKET, PACKET_VERSION, &ver, sizeof(ver));
+ if (ret == -1) {
+ perror("setsockopt");
+ exit(1);
+ }
+
+ return sock;
+}
+
+static void status_bar_update(void)
+{
+ if (total_packets % 10 == 0) {
+ fprintf(stderr, ".");
+ fflush(stderr);
+ }
+}
+
+static void test_payload(void *pay, size_t len)
+{
+ struct ethhdr *eth = pay;
+
+ if (len < sizeof(struct ethhdr)) {
+ fprintf(stderr, "test_payload: packet too "
+ "small: %zu bytes!\n", len);
+ exit(1);
+ }
+
+ if (eth->h_proto != htons(ETH_P_IP)) {
+ fprintf(stderr, "test_payload: wrong ethernet "
+ "type: 0x%x!\n", ntohs(eth->h_proto));
+ exit(1);
+ }
+}
+
+static void create_payload(void *pay, size_t *len)
+{
+ int i;
+ struct ethhdr *eth = pay;
+ struct iphdr *ip = pay + sizeof(*eth);
+
+ /* Lets create some broken crap, that still passes
+ * our BPF filter.
+ */
+
+ *len = DATA_LEN + 42;
+
+ memset(pay, 0xff, ETH_ALEN * 2);
+ eth->h_proto = htons(ETH_P_IP);
+
+ for (i = 0; i < sizeof(*ip); ++i)
+ ((uint8_t *) pay)[i + sizeof(*eth)] = (uint8_t) rand();
+
+ ip->ihl = 5;
+ ip->version = 4;
+ ip->protocol = 0x11;
+ ip->frag_off = 0;
+ ip->ttl = 64;
+ ip->tot_len = htons((uint16_t) *len - sizeof(*eth));
+
+ ip->saddr = htonl(INADDR_LOOPBACK);
+ ip->daddr = htonl(INADDR_LOOPBACK);
+
+ memset(pay + sizeof(*eth) + sizeof(*ip),
+ DATA_CHAR, DATA_LEN);
+}
+
+static inline int __v1_rx_kernel_ready(struct tpacket_hdr *hdr)
+{
+ return ((hdr->tp_status & TP_STATUS_USER) == TP_STATUS_USER);
+}
+
+static inline void __v1_rx_user_ready(struct tpacket_hdr *hdr)
+{
+ hdr->tp_status = TP_STATUS_KERNEL;
+ __sync_synchronize();
+}
+
+static inline int __v2_rx_kernel_ready(struct tpacket2_hdr *hdr)
+{
+ return ((hdr->tp_status & TP_STATUS_USER) == TP_STATUS_USER);
+}
+
+static inline void __v2_rx_user_ready(struct tpacket2_hdr *hdr)
+{
+ hdr->tp_status = TP_STATUS_KERNEL;
+ __sync_synchronize();
+}
+
+static inline int __v1_v2_rx_kernel_ready(void *base, int version)
+{
+ switch (version) {
+ case TPACKET_V1:
+ return __v1_rx_kernel_ready(base);
+ case TPACKET_V2:
+ return __v2_rx_kernel_ready(base);
+ default:
+ bug_on(1);
+ return 0;
+ }
+}
+
+static inline void __v1_v2_rx_user_ready(void *base, int version)
+{
+ switch (version) {
+ case TPACKET_V1:
+ __v1_rx_user_ready(base);
+ break;
+ case TPACKET_V2:
+ __v2_rx_user_ready(base);
+ break;
+ }
+}
+
+static void walk_v1_v2_rx(int sock, struct ring *ring)
+{
+ struct pollfd pfd;
+ int udp_sock[2];
+ union frame_map ppd;
+ unsigned int frame_num = 0;
+
+ bug_on(ring->type != PACKET_RX_RING);
+
+ pair_udp_open(udp_sock, PORT_BASE);
+ pair_udp_setfilter(sock);
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = sock;
+ pfd.events = POLLIN | POLLERR;
+ pfd.revents = 0;
+
+ pair_udp_send(udp_sock, NUM_PACKETS);
+
+ while (total_packets < NUM_PACKETS * 2) {
+ while (__v1_v2_rx_kernel_ready(ring->rd[frame_num].iov_base,
+ ring->version)) {
+ ppd.raw = ring->rd[frame_num].iov_base;
+
+ switch (ring->version) {
+ case TPACKET_V1:
+ test_payload((uint8_t *) ppd.raw + ppd.v1->tp_h.tp_mac,
+ ppd.v1->tp_h.tp_snaplen);
+ total_bytes += ppd.v1->tp_h.tp_snaplen;
+ break;
+
+ case TPACKET_V2:
+ test_payload((uint8_t *) ppd.raw + ppd.v2->tp_h.tp_mac,
+ ppd.v2->tp_h.tp_snaplen);
+ total_bytes += ppd.v2->tp_h.tp_snaplen;
+ break;
+ }
+
+ status_bar_update();
+ total_packets++;
+
+ __v1_v2_rx_user_ready(ppd.raw, ring->version);
+
+ frame_num = (frame_num + 1) % ring->rd_num;
+ }
+
+ poll(&pfd, 1, 1);
+ }
+
+ pair_udp_close(udp_sock);
+
+ if (total_packets != 2 * NUM_PACKETS) {
+ fprintf(stderr, "walk_v%d_rx: received %u out of %u pkts\n",
+ ring->version, total_packets, NUM_PACKETS);
+ exit(1);
+ }
+
+ fprintf(stderr, " %u pkts (%u bytes)", NUM_PACKETS, total_bytes >> 1);
+}
+
+static inline int __v1_tx_kernel_ready(struct tpacket_hdr *hdr)
+{
+ return !(hdr->tp_status & (TP_STATUS_SEND_REQUEST | TP_STATUS_SENDING));
+}
+
+static inline void __v1_tx_user_ready(struct tpacket_hdr *hdr)
+{
+ hdr->tp_status = TP_STATUS_SEND_REQUEST;
+ __sync_synchronize();
+}
+
+static inline int __v2_tx_kernel_ready(struct tpacket2_hdr *hdr)
+{
+ return !(hdr->tp_status & (TP_STATUS_SEND_REQUEST | TP_STATUS_SENDING));
+}
+
+static inline void __v2_tx_user_ready(struct tpacket2_hdr *hdr)
+{
+ hdr->tp_status = TP_STATUS_SEND_REQUEST;
+ __sync_synchronize();
+}
+
+static inline int __v1_v2_tx_kernel_ready(void *base, int version)
+{
+ switch (version) {
+ case TPACKET_V1:
+ return __v1_tx_kernel_ready(base);
+ case TPACKET_V2:
+ return __v2_tx_kernel_ready(base);
+ default:
+ bug_on(1);
+ return 0;
+ }
+}
+
+static inline void __v1_v2_tx_user_ready(void *base, int version)
+{
+ switch (version) {
+ case TPACKET_V1:
+ __v1_tx_user_ready(base);
+ break;
+ case TPACKET_V2:
+ __v2_tx_user_ready(base);
+ break;
+ }
+}
+
+static void __v1_v2_set_packet_loss_discard(int sock)
+{
+ int ret, discard = 1;
+
+ ret = setsockopt(sock, SOL_PACKET, PACKET_LOSS, (void *) &discard,
+ sizeof(discard));
+ if (ret == -1) {
+ perror("setsockopt");
+ exit(1);
+ }
+}
+
+static void walk_v1_v2_tx(int sock, struct ring *ring)
+{
+ struct pollfd pfd;
+ int rcv_sock, ret;
+ size_t packet_len;
+ union frame_map ppd;
+ char packet[1024];
+ unsigned int frame_num = 0, got = 0;
+ struct sockaddr_ll ll = {
+ .sll_family = PF_PACKET,
+ .sll_halen = ETH_ALEN,
+ };
+
+ bug_on(ring->type != PACKET_TX_RING);
+ bug_on(ring->rd_num < NUM_PACKETS);
+
+ rcv_sock = socket(PF_PACKET, SOCK_RAW, htons(ETH_P_ALL));
+ if (rcv_sock == -1) {
+ perror("socket");
+ exit(1);
+ }
+
+ pair_udp_setfilter(rcv_sock);
+
+ ll.sll_ifindex = if_nametoindex("lo");
+ ret = bind(rcv_sock, (struct sockaddr *) &ll, sizeof(ll));
+ if (ret == -1) {
+ perror("bind");
+ exit(1);
+ }
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = sock;
+ pfd.events = POLLOUT | POLLERR;
+ pfd.revents = 0;
+
+ total_packets = NUM_PACKETS;
+ create_payload(packet, &packet_len);
+
+ while (total_packets > 0) {
+ while (__v1_v2_tx_kernel_ready(ring->rd[frame_num].iov_base,
+ ring->version) &&
+ total_packets > 0) {
+ ppd.raw = ring->rd[frame_num].iov_base;
+
+ switch (ring->version) {
+ case TPACKET_V1:
+ ppd.v1->tp_h.tp_snaplen = packet_len;
+ ppd.v1->tp_h.tp_len = packet_len;
+
+ memcpy((uint8_t *) ppd.raw + TPACKET_HDRLEN -
+ sizeof(struct sockaddr_ll), packet,
+ packet_len);
+ total_bytes += ppd.v1->tp_h.tp_snaplen;
+ break;
+
+ case TPACKET_V2:
+ ppd.v2->tp_h.tp_snaplen = packet_len;
+ ppd.v2->tp_h.tp_len = packet_len;
+
+ memcpy((uint8_t *) ppd.raw + TPACKET2_HDRLEN -
+ sizeof(struct sockaddr_ll), packet,
+ packet_len);
+ total_bytes += ppd.v2->tp_h.tp_snaplen;
+ break;
+ }
+
+ status_bar_update();
+ total_packets--;
+
+ __v1_v2_tx_user_ready(ppd.raw, ring->version);
+
+ frame_num = (frame_num + 1) % ring->rd_num;
+ }
+
+ poll(&pfd, 1, 1);
+ }
+
+ bug_on(total_packets != 0);
+
+ ret = sendto(sock, NULL, 0, 0, NULL, 0);
+ if (ret == -1) {
+ perror("sendto");
+ exit(1);
+ }
+
+ while ((ret = recvfrom(rcv_sock, packet, sizeof(packet),
+ 0, NULL, NULL)) > 0 &&
+ total_packets < NUM_PACKETS) {
+ got += ret;
+ test_payload(packet, ret);
+
+ status_bar_update();
+ total_packets++;
+ }
+
+ close(rcv_sock);
+
+ if (total_packets != NUM_PACKETS) {
+ fprintf(stderr, "walk_v%d_rx: received %u out of %u pkts\n",
+ ring->version, total_packets, NUM_PACKETS);
+ exit(1);
+ }
+
+ fprintf(stderr, " %u pkts (%u bytes)", NUM_PACKETS, got);
+}
+
+static void walk_v1_v2(int sock, struct ring *ring)
+{
+ if (ring->type == PACKET_RX_RING)
+ walk_v1_v2_rx(sock, ring);
+ else
+ walk_v1_v2_tx(sock, ring);
+}
+
+static uint64_t __v3_prev_block_seq_num = 0;
+
+void __v3_test_block_seq_num(struct block_desc *pbd)
+{
+ if (__v3_prev_block_seq_num + 1 != pbd->h1.seq_num) {
+ fprintf(stderr, "\nprev_block_seq_num:%"PRIu64", expected "
+ "seq:%"PRIu64" != actual seq:%"PRIu64"\n",
+ __v3_prev_block_seq_num, __v3_prev_block_seq_num + 1,
+ (uint64_t) pbd->h1.seq_num);
+ exit(1);
+ }
+
+ __v3_prev_block_seq_num = pbd->h1.seq_num;
+}
+
+static void __v3_test_block_len(struct block_desc *pbd, uint32_t bytes, int block_num)
+{
+ if (pbd->h1.num_pkts && bytes != pbd->h1.blk_len) {
+ fprintf(stderr, "\nblock:%u with %upackets, expected "
+ "len:%u != actual len:%u\n", block_num,
+ pbd->h1.num_pkts, bytes, pbd->h1.blk_len);
+ exit(1);
+ }
+}
+
+static void __v3_test_block_header(struct block_desc *pbd, const int block_num)
+{
+ if ((pbd->h1.block_status & TP_STATUS_USER) == 0) {
+ fprintf(stderr, "\nblock %u: not in TP_STATUS_USER\n", block_num);
+ exit(1);
+ }
+
+ __v3_test_block_seq_num(pbd);
+}
+
+static void __v3_walk_block(struct block_desc *pbd, const int block_num)
+{
+ int num_pkts = pbd->h1.num_pkts, i;
+ unsigned long bytes = 0, bytes_with_padding = ALIGN_8(sizeof(*pbd));
+ struct tpacket3_hdr *ppd;
+
+ __v3_test_block_header(pbd, block_num);
+
+ ppd = (struct tpacket3_hdr *) ((uint8_t *) pbd +
+ pbd->h1.offset_to_first_pkt);
+
+ for (i = 0; i < num_pkts; ++i) {
+ bytes += ppd->tp_snaplen;
+
+ if (ppd->tp_next_offset)
+ bytes_with_padding += ppd->tp_next_offset;
+ else
+ bytes_with_padding += ALIGN_8(ppd->tp_snaplen + ppd->tp_mac);
+
+ test_payload((uint8_t *) ppd + ppd->tp_mac, ppd->tp_snaplen);
+
+ status_bar_update();
+ total_packets++;
+
+ ppd = (struct tpacket3_hdr *) ((uint8_t *) ppd + ppd->tp_next_offset);
+ __sync_synchronize();
+ }
+
+ __v3_test_block_len(pbd, bytes_with_padding, block_num);
+ total_bytes += bytes;
+}
+
+void __v3_flush_block(struct block_desc *pbd)
+{
+ pbd->h1.block_status = TP_STATUS_KERNEL;
+ __sync_synchronize();
+}
+
+static void walk_v3_rx(int sock, struct ring *ring)
+{
+ unsigned int block_num = 0;
+ struct pollfd pfd;
+ struct block_desc *pbd;
+ int udp_sock[2];
+
+ bug_on(ring->type != PACKET_RX_RING);
+
+ pair_udp_open(udp_sock, PORT_BASE);
+ pair_udp_setfilter(sock);
+
+ memset(&pfd, 0, sizeof(pfd));
+ pfd.fd = sock;
+ pfd.events = POLLIN | POLLERR;
+ pfd.revents = 0;
+
+ pair_udp_send(udp_sock, NUM_PACKETS);
+
+ while (total_packets < NUM_PACKETS * 2) {
+ pbd = (struct block_desc *) ring->rd[block_num].iov_base;
+
+ while ((pbd->h1.block_status & TP_STATUS_USER) == 0)
+ poll(&pfd, 1, 1);
+
+ __v3_walk_block(pbd, block_num);
+ __v3_flush_block(pbd);
+
+ block_num = (block_num + 1) % ring->rd_num;
+ }
+
+ pair_udp_close(udp_sock);
+
+ if (total_packets != 2 * NUM_PACKETS) {
+ fprintf(stderr, "walk_v3_rx: received %u out of %u pkts\n",
+ total_packets, NUM_PACKETS);
+ exit(1);
+ }
+
+ fprintf(stderr, " %u pkts (%u bytes)", NUM_PACKETS, total_bytes >> 1);
+}
+
+static void walk_v3(int sock, struct ring *ring)
+{
+ if (ring->type == PACKET_RX_RING)
+ walk_v3_rx(sock, ring);
+ else
+ bug_on(1);
+}
+
+static void __v1_v2_fill(struct ring *ring, unsigned int blocks)
+{
+ ring->req.tp_block_size = getpagesize() << 2;
+ ring->req.tp_frame_size = TPACKET_ALIGNMENT << 7;
+ ring->req.tp_block_nr = blocks;
+
+ ring->req.tp_frame_nr = ring->req.tp_block_size /
+ ring->req.tp_frame_size *
+ ring->req.tp_block_nr;
+
+ ring->mm_len = ring->req.tp_block_size * ring->req.tp_block_nr;
+ ring->walk = walk_v1_v2;
+ ring->rd_num = ring->req.tp_frame_nr;
+ ring->flen = ring->req.tp_frame_size;
+}
+
+static void __v3_fill(struct ring *ring, unsigned int blocks)
+{
+ ring->req3.tp_retire_blk_tov = 64;
+ ring->req3.tp_sizeof_priv = 0;
+ ring->req3.tp_feature_req_word = TP_FT_REQ_FILL_RXHASH;
+
+ ring->req3.tp_block_size = getpagesize() << 2;
+ ring->req3.tp_frame_size = TPACKET_ALIGNMENT << 7;
+ ring->req3.tp_block_nr = blocks;
+
+ ring->req3.tp_frame_nr = ring->req3.tp_block_size /
+ ring->req3.tp_frame_size *
+ ring->req3.tp_block_nr;
+
+ ring->mm_len = ring->req3.tp_block_size * ring->req3.tp_block_nr;
+ ring->walk = walk_v3;
+ ring->rd_num = ring->req3.tp_block_nr;
+ ring->flen = ring->req3.tp_block_size;
+}
+
+static void setup_ring(int sock, struct ring *ring, int version, int type)
+{
+ int ret = 0;
+ unsigned int blocks = 256;
+
+ ring->type = type;
+ ring->version = version;
+
+ switch (version) {
+ case TPACKET_V1:
+ case TPACKET_V2:
+ if (type == PACKET_TX_RING)
+ __v1_v2_set_packet_loss_discard(sock);
+ __v1_v2_fill(ring, blocks);
+ ret = setsockopt(sock, SOL_PACKET, type, &ring->req,
+ sizeof(ring->req));
+ break;
+
+ case TPACKET_V3:
+ __v3_fill(ring, blocks);
+ ret = setsockopt(sock, SOL_PACKET, type, &ring->req3,
+ sizeof(ring->req3));
+ break;
+ }
+
+ if (ret == -1) {
+ perror("setsockopt");
+ exit(1);
+ }
+
+ ring->rd_len = ring->rd_num * sizeof(*ring->rd);
+ ring->rd = malloc(ring->rd_len);
+ if (ring->rd == NULL) {
+ perror("malloc");
+ exit(1);
+ }
+
+ total_packets = 0;
+ total_bytes = 0;
+}
+
+static void mmap_ring(int sock, struct ring *ring)
+{
+ int i;
+
+ ring->mm_space = mmap(0, ring->mm_len, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_LOCKED | MAP_POPULATE, sock, 0);
+ if (ring->mm_space == MAP_FAILED) {
+ perror("mmap");
+ exit(1);
+ }
+
+ memset(ring->rd, 0, ring->rd_len);
+ for (i = 0; i < ring->rd_num; ++i) {
+ ring->rd[i].iov_base = ring->mm_space + (i * ring->flen);
+ ring->rd[i].iov_len = ring->flen;
+ }
+}
+
+static void bind_ring(int sock, struct ring *ring)
+{
+ int ret;
+
+ ring->ll.sll_family = PF_PACKET;
+ ring->ll.sll_protocol = htons(ETH_P_ALL);
+ ring->ll.sll_ifindex = if_nametoindex("lo");
+ ring->ll.sll_hatype = 0;
+ ring->ll.sll_pkttype = 0;
+ ring->ll.sll_halen = 0;
+
+ ret = bind(sock, (struct sockaddr *) &ring->ll, sizeof(ring->ll));
+ if (ret == -1) {
+ perror("bind");
+ exit(1);
+ }
+}
+
+static void walk_ring(int sock, struct ring *ring)
+{
+ ring->walk(sock, ring);
+}
+
+static void unmap_ring(int sock, struct ring *ring)
+{
+ munmap(ring->mm_space, ring->mm_len);
+ free(ring->rd);
+}
+
+static int test_kernel_bit_width(void)
+{
+ char in[512], *ptr;
+ int num = 0, fd;
+ ssize_t ret;
+
+ fd = open("/proc/kallsyms", O_RDONLY);
+ if (fd == -1) {
+ perror("open");
+ exit(1);
+ }
+
+ ret = read(fd, in, sizeof(in));
+ if (ret <= 0) {
+ perror("read");
+ exit(1);
+ }
+
+ close(fd);
+
+ ptr = in;
+ while(!isspace(*ptr)) {
+ num++;
+ ptr++;
+ }
+
+ return num * 4;
+}
+
+static int test_user_bit_width(void)
+{
+ return __WORDSIZE;
+}
+
+static const char *tpacket_str[] = {
+ [TPACKET_V1] = "TPACKET_V1",
+ [TPACKET_V2] = "TPACKET_V2",
+ [TPACKET_V3] = "TPACKET_V3",
+};
+
+static const char *type_str[] = {
+ [PACKET_RX_RING] = "PACKET_RX_RING",
+ [PACKET_TX_RING] = "PACKET_TX_RING",
+};
+
+static int test_tpacket(int version, int type)
+{
+ int sock;
+ struct ring ring;
+
+ fprintf(stderr, "test: %s with %s ", tpacket_str[version],
+ type_str[type]);
+ fflush(stderr);
+
+ if (version == TPACKET_V1 &&
+ test_kernel_bit_width() != test_user_bit_width()) {
+ fprintf(stderr, "test: skip %s %s since user and kernel "
+ "space have different bit width\n",
+ tpacket_str[version], type_str[type]);
+ return 0;
+ }
+
+ sock = pfsocket(version);
+ memset(&ring, 0, sizeof(ring));
+ setup_ring(sock, &ring, version, type);
+ mmap_ring(sock, &ring);
+ bind_ring(sock, &ring);
+ walk_ring(sock, &ring);
+ unmap_ring(sock, &ring);
+ close(sock);
+
+ fprintf(stderr, "\n");
+ return 0;
+}
+
+int main(void)
+{
+ int ret = 0;
+
+ ret |= test_tpacket(TPACKET_V1, PACKET_RX_RING);
+ ret |= test_tpacket(TPACKET_V1, PACKET_TX_RING);
+
+ ret |= test_tpacket(TPACKET_V2, PACKET_RX_RING);
+ ret |= test_tpacket(TPACKET_V2, PACKET_TX_RING);
+
+ ret |= test_tpacket(TPACKET_V3, PACKET_RX_RING);
+
+ if (ret)
+ return 1;
+
+ printf("OK. All tests passed\n");
+ return 0;
+}
diff --git a/tools/testing/selftests/net/run_afpackettests b/tools/testing/selftests/net/run_afpackettests
new file mode 100644
index 00000000000..5246e782d6e
--- /dev/null
+++ b/tools/testing/selftests/net/run_afpackettests
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+if [ $(id -u) != 0 ]; then
+ echo $msg must be run as root >&2
+ exit 0
+fi
+
+echo "--------------------"
+echo "running psock_fanout test"
+echo "--------------------"
+./psock_fanout
+if [ $? -ne 0 ]; then
+ echo "[FAIL]"
+else
+ echo "[PASS]"
+fi
+
+echo "--------------------"
+echo "running psock_tpacket test"
+echo "--------------------"
+./psock_tpacket
+if [ $? -ne 0 ]; then
+ echo "[FAIL]"
+else
+ echo "[PASS]"
+fi
diff --git a/tools/testing/selftests/net/run_netsocktests b/tools/testing/selftests/net/run_netsocktests
new file mode 100644
index 00000000000..c09a682df56
--- /dev/null
+++ b/tools/testing/selftests/net/run_netsocktests
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+echo "--------------------"
+echo "running socket test"
+echo "--------------------"
+./socket
+if [ $? -ne 0 ]; then
+ echo "[FAIL]"
+else
+ echo "[PASS]"
+fi
+
diff --git a/tools/testing/selftests/net/socket.c b/tools/testing/selftests/net/socket.c
new file mode 100644
index 00000000000..0f227f2f9be
--- /dev/null
+++ b/tools/testing/selftests/net/socket.c
@@ -0,0 +1,92 @@
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+struct socket_testcase {
+ int domain;
+ int type;
+ int protocol;
+
+ /* 0 = valid file descriptor
+ * -foo = error foo
+ */
+ int expect;
+
+ /* If non-zero, accept EAFNOSUPPORT to handle the case
+ * of the protocol not being configured into the kernel.
+ */
+ int nosupport_ok;
+};
+
+static struct socket_testcase tests[] = {
+ { AF_MAX, 0, 0, -EAFNOSUPPORT, 0 },
+ { AF_INET, SOCK_STREAM, IPPROTO_TCP, 0, 1 },
+ { AF_INET, SOCK_DGRAM, IPPROTO_TCP, -EPROTONOSUPPORT, 1 },
+ { AF_INET, SOCK_DGRAM, IPPROTO_UDP, 0, 1 },
+ { AF_INET, SOCK_STREAM, IPPROTO_UDP, -EPROTONOSUPPORT, 1 },
+};
+
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0]))
+#define ERR_STRING_SZ 64
+
+static int run_tests(void)
+{
+ char err_string1[ERR_STRING_SZ];
+ char err_string2[ERR_STRING_SZ];
+ int i, err;
+
+ err = 0;
+ for (i = 0; i < ARRAY_SIZE(tests); i++) {
+ struct socket_testcase *s = &tests[i];
+ int fd;
+
+ fd = socket(s->domain, s->type, s->protocol);
+ if (fd < 0) {
+ if (s->nosupport_ok &&
+ errno == EAFNOSUPPORT)
+ continue;
+
+ if (s->expect < 0 &&
+ errno == -s->expect)
+ continue;
+
+ strerror_r(-s->expect, err_string1, ERR_STRING_SZ);
+ strerror_r(errno, err_string2, ERR_STRING_SZ);
+
+ fprintf(stderr, "socket(%d, %d, %d) expected "
+ "err (%s) got (%s)\n",
+ s->domain, s->type, s->protocol,
+ err_string1, err_string2);
+
+ err = -1;
+ break;
+ } else {
+ close(fd);
+
+ if (s->expect < 0) {
+ strerror_r(errno, err_string1, ERR_STRING_SZ);
+
+ fprintf(stderr, "socket(%d, %d, %d) expected "
+ "success got err (%s)\n",
+ s->domain, s->type, s->protocol,
+ err_string1);
+
+ err = -1;
+ break;
+ }
+ }
+ }
+
+ return err;
+}
+
+int main(void)
+{
+ int err = run_tests();
+
+ return err;
+}
diff --git a/tools/testing/selftests/powerpc/Makefile b/tools/testing/selftests/powerpc/Makefile
new file mode 100644
index 00000000000..54833a791a4
--- /dev/null
+++ b/tools/testing/selftests/powerpc/Makefile
@@ -0,0 +1,39 @@
+# Makefile for powerpc selftests
+
+# ARCH can be overridden by the user for cross compiling
+ARCH ?= $(shell uname -m)
+ARCH := $(shell echo $(ARCH) | sed -e s/ppc.*/powerpc/)
+
+ifeq ($(ARCH),powerpc)
+
+GIT_VERSION = $(shell git describe --always --long --dirty || echo "unknown")
+
+CC := $(CROSS_COMPILE)$(CC)
+CFLAGS := -Wall -O2 -flto -Wall -Werror -DGIT_VERSION='"$(GIT_VERSION)"' -I$(CURDIR) $(CFLAGS)
+
+export CC CFLAGS
+
+TARGETS = pmu copyloops mm tm
+
+endif
+
+all:
+ @for TARGET in $(TARGETS); do \
+ $(MAKE) -C $$TARGET all; \
+ done;
+
+run_tests: all
+ @for TARGET in $(TARGETS); do \
+ $(MAKE) -C $$TARGET run_tests; \
+ done;
+
+clean:
+ @for TARGET in $(TARGETS); do \
+ $(MAKE) -C $$TARGET clean; \
+ done;
+ rm -f tags
+
+tags:
+ find . -name '*.c' -o -name '*.h' | xargs ctags
+
+.PHONY: all run_tests clean tags
diff --git a/tools/testing/selftests/powerpc/copyloops/Makefile b/tools/testing/selftests/powerpc/copyloops/Makefile
new file mode 100644
index 00000000000..6f2d3be227f
--- /dev/null
+++ b/tools/testing/selftests/powerpc/copyloops/Makefile
@@ -0,0 +1,29 @@
+# The loops are all 64-bit code
+CFLAGS += -m64
+CFLAGS += -I$(CURDIR)
+CFLAGS += -D SELFTEST
+
+# Use our CFLAGS for the implicit .S rule
+ASFLAGS = $(CFLAGS)
+
+PROGS := copyuser_64 copyuser_power7 memcpy_64 memcpy_power7
+EXTRA_SOURCES := validate.c ../harness.c
+
+all: $(PROGS)
+
+copyuser_64: CPPFLAGS += -D COPY_LOOP=test___copy_tofrom_user_base
+copyuser_power7: CPPFLAGS += -D COPY_LOOP=test___copy_tofrom_user_power7
+memcpy_64: CPPFLAGS += -D COPY_LOOP=test_memcpy
+memcpy_power7: CPPFLAGS += -D COPY_LOOP=test_memcpy_power7
+
+$(PROGS): $(EXTRA_SOURCES)
+
+run_tests: all
+ @-for PROG in $(PROGS); do \
+ ./$$PROG; \
+ done;
+
+clean:
+ rm -f $(PROGS) *.o
+
+.PHONY: all run_tests clean
diff --git a/tools/testing/selftests/powerpc/copyloops/asm/ppc_asm.h b/tools/testing/selftests/powerpc/copyloops/asm/ppc_asm.h
new file mode 100644
index 00000000000..d1dc3742551
--- /dev/null
+++ b/tools/testing/selftests/powerpc/copyloops/asm/ppc_asm.h
@@ -0,0 +1,89 @@
+#include <ppc-asm.h>
+
+#define CONFIG_ALTIVEC
+
+#define r1 1
+
+#define vr0 0
+#define vr1 1
+#define vr2 2
+#define vr3 3
+#define vr4 4
+#define vr5 5
+#define vr6 6
+#define vr7 7
+#define vr8 8
+#define vr9 9
+#define vr10 10
+#define vr11 11
+#define vr12 12
+#define vr13 13
+#define vr14 14
+#define vr15 15
+#define vr16 16
+#define vr17 17
+#define vr18 18
+#define vr19 19
+#define vr20 20
+#define vr21 21
+#define vr22 22
+#define vr23 23
+#define vr24 24
+#define vr25 25
+#define vr26 26
+#define vr27 27
+#define vr28 28
+#define vr29 29
+#define vr30 30
+#define vr31 31
+
+#define R14 r14
+#define R15 r15
+#define R16 r16
+#define R17 r17
+#define R18 r18
+#define R19 r19
+#define R20 r20
+#define R21 r21
+#define R22 r22
+#define R29 r29
+#define R30 r30
+#define R31 r31
+
+#define STACKFRAMESIZE 256
+#define STK_REG(i) (112 + ((i)-14)*8)
+
+#define _GLOBAL(A) FUNC_START(test_ ## A)
+#define _GLOBAL_TOC(A) _GLOBAL(A)
+
+#define PPC_MTOCRF(A, B) mtocrf A, B
+
+FUNC_START(enter_vmx_usercopy)
+ li r3,1
+ blr
+
+FUNC_START(exit_vmx_usercopy)
+ li r3,0
+ blr
+
+FUNC_START(enter_vmx_copy)
+ li r3,1
+ blr
+
+FUNC_START(exit_vmx_copy)
+ blr
+
+FUNC_START(memcpy_power7)
+ blr
+
+FUNC_START(__copy_tofrom_user_power7)
+ blr
+
+FUNC_START(__copy_tofrom_user_base)
+ blr
+
+#define BEGIN_FTR_SECTION
+#define FTR_SECTION_ELSE
+#define ALT_FTR_SECTION_END_IFCLR(x)
+#define ALT_FTR_SECTION_END(x, y)
+#define END_FTR_SECTION_IFCLR(x)
diff --git a/tools/testing/selftests/powerpc/copyloops/asm/processor.h b/tools/testing/selftests/powerpc/copyloops/asm/processor.h
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/tools/testing/selftests/powerpc/copyloops/asm/processor.h
diff --git a/tools/testing/selftests/powerpc/copyloops/copyuser_64.S b/tools/testing/selftests/powerpc/copyloops/copyuser_64.S
new file mode 120000
index 00000000000..f1c418a2521
--- /dev/null
+++ b/tools/testing/selftests/powerpc/copyloops/copyuser_64.S
@@ -0,0 +1 @@
+../../../../../arch/powerpc/lib/copyuser_64.S \ No newline at end of file
diff --git a/tools/testing/selftests/powerpc/copyloops/copyuser_power7.S b/tools/testing/selftests/powerpc/copyloops/copyuser_power7.S
new file mode 120000
index 00000000000..47868959829
--- /dev/null
+++ b/tools/testing/selftests/powerpc/copyloops/copyuser_power7.S
@@ -0,0 +1 @@
+../../../../../arch/powerpc/lib/copyuser_power7.S \ No newline at end of file
diff --git a/tools/testing/selftests/powerpc/copyloops/memcpy_64.S b/tools/testing/selftests/powerpc/copyloops/memcpy_64.S
new file mode 120000
index 00000000000..cce33fb6f9d
--- /dev/null
+++ b/tools/testing/selftests/powerpc/copyloops/memcpy_64.S
@@ -0,0 +1 @@
+../../../../../arch/powerpc/lib/memcpy_64.S \ No newline at end of file
diff --git a/tools/testing/selftests/powerpc/copyloops/memcpy_power7.S b/tools/testing/selftests/powerpc/copyloops/memcpy_power7.S
new file mode 120000
index 00000000000..0d6fbfaf3d5
--- /dev/null
+++ b/tools/testing/selftests/powerpc/copyloops/memcpy_power7.S
@@ -0,0 +1 @@
+../../../../../arch/powerpc/lib/memcpy_power7.S \ No newline at end of file
diff --git a/tools/testing/selftests/powerpc/copyloops/validate.c b/tools/testing/selftests/powerpc/copyloops/validate.c
new file mode 100644
index 00000000000..1750ff57ee5
--- /dev/null
+++ b/tools/testing/selftests/powerpc/copyloops/validate.c
@@ -0,0 +1,99 @@
+#include <malloc.h>
+#include <string.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "../utils.h"
+
+#define MAX_LEN 8192
+#define MAX_OFFSET 16
+#define MIN_REDZONE 128
+#define BUFLEN (MAX_LEN+MAX_OFFSET+2*MIN_REDZONE)
+#define POISON 0xa5
+
+unsigned long COPY_LOOP(void *to, const void *from, unsigned long size);
+
+static void do_one(char *src, char *dst, unsigned long src_off,
+ unsigned long dst_off, unsigned long len, void *redzone,
+ void *fill)
+{
+ char *srcp, *dstp;
+ unsigned long ret;
+ unsigned long i;
+
+ srcp = src + MIN_REDZONE + src_off;
+ dstp = dst + MIN_REDZONE + dst_off;
+
+ memset(src, POISON, BUFLEN);
+ memset(dst, POISON, BUFLEN);
+ memcpy(srcp, fill, len);
+
+ ret = COPY_LOOP(dstp, srcp, len);
+ if (ret && ret != (unsigned long)dstp) {
+ printf("(%p,%p,%ld) returned %ld\n", dstp, srcp, len, ret);
+ abort();
+ }
+
+ if (memcmp(dstp, srcp, len)) {
+ printf("(%p,%p,%ld) miscompare\n", dstp, srcp, len);
+ printf("src: ");
+ for (i = 0; i < len; i++)
+ printf("%02x ", srcp[i]);
+ printf("\ndst: ");
+ for (i = 0; i < len; i++)
+ printf("%02x ", dstp[i]);
+ printf("\n");
+ abort();
+ }
+
+ if (memcmp(dst, redzone, dstp - dst)) {
+ printf("(%p,%p,%ld) redzone before corrupted\n",
+ dstp, srcp, len);
+ abort();
+ }
+
+ if (memcmp(dstp+len, redzone, dst+BUFLEN-(dstp+len))) {
+ printf("(%p,%p,%ld) redzone after corrupted\n",
+ dstp, srcp, len);
+ abort();
+ }
+}
+
+int test_copy_loop(void)
+{
+ char *src, *dst, *redzone, *fill;
+ unsigned long len, src_off, dst_off;
+ unsigned long i;
+
+ src = memalign(BUFLEN, BUFLEN);
+ dst = memalign(BUFLEN, BUFLEN);
+ redzone = malloc(BUFLEN);
+ fill = malloc(BUFLEN);
+
+ if (!src || !dst || !redzone || !fill) {
+ fprintf(stderr, "malloc failed\n");
+ exit(1);
+ }
+
+ memset(redzone, POISON, BUFLEN);
+
+ /* Fill with sequential bytes */
+ for (i = 0; i < BUFLEN; i++)
+ fill[i] = i & 0xff;
+
+ for (len = 1; len < MAX_LEN; len++) {
+ for (src_off = 0; src_off < MAX_OFFSET; src_off++) {
+ for (dst_off = 0; dst_off < MAX_OFFSET; dst_off++) {
+ do_one(src, dst, src_off, dst_off, len,
+ redzone, fill);
+ }
+ }
+ }
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(test_copy_loop, str(COPY_LOOP));
+}
diff --git a/tools/testing/selftests/powerpc/harness.c b/tools/testing/selftests/powerpc/harness.c
new file mode 100644
index 00000000000..8ebc58a0931
--- /dev/null
+++ b/tools/testing/selftests/powerpc/harness.c
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2013, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <errno.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "subunit.h"
+#include "utils.h"
+
+#define TIMEOUT 120
+#define KILL_TIMEOUT 5
+
+
+int run_test(int (test_function)(void), char *name)
+{
+ bool terminated;
+ int rc, status;
+ pid_t pid;
+
+ /* Make sure output is flushed before forking */
+ fflush(stdout);
+
+ pid = fork();
+ if (pid == 0) {
+ setpgid(0, 0);
+ exit(test_function());
+ } else if (pid == -1) {
+ perror("fork");
+ return 1;
+ }
+
+ setpgid(pid, pid);
+
+ /* Wake us up in timeout seconds */
+ alarm(TIMEOUT);
+ terminated = false;
+
+wait:
+ rc = waitpid(pid, &status, 0);
+ if (rc == -1) {
+ if (errno != EINTR) {
+ printf("unknown error from waitpid\n");
+ return 1;
+ }
+
+ if (terminated) {
+ printf("!! force killing %s\n", name);
+ kill(-pid, SIGKILL);
+ return 1;
+ } else {
+ printf("!! killing %s\n", name);
+ kill(-pid, SIGTERM);
+ terminated = true;
+ alarm(KILL_TIMEOUT);
+ goto wait;
+ }
+ }
+
+ /* Kill anything else in the process group that is still running */
+ kill(-pid, SIGTERM);
+
+ if (WIFEXITED(status))
+ status = WEXITSTATUS(status);
+ else {
+ if (WIFSIGNALED(status))
+ printf("!! child died by signal %d\n", WTERMSIG(status));
+ else
+ printf("!! child died by unknown cause\n");
+
+ status = 1; /* Signal or other */
+ }
+
+ return status;
+}
+
+static void alarm_handler(int signum)
+{
+ /* Jut wake us up from waitpid */
+}
+
+static struct sigaction alarm_action = {
+ .sa_handler = alarm_handler,
+};
+
+int test_harness(int (test_function)(void), char *name)
+{
+ int rc;
+
+ test_start(name);
+ test_set_git_version(GIT_VERSION);
+
+ if (sigaction(SIGALRM, &alarm_action, NULL)) {
+ perror("sigaction");
+ test_error(name);
+ return 1;
+ }
+
+ rc = run_test(test_function, name);
+
+ if (rc == MAGIC_SKIP_RETURN_VALUE)
+ test_skip(name);
+ else
+ test_finish(name, rc);
+
+ return rc;
+}
diff --git a/tools/testing/selftests/powerpc/mm/Makefile b/tools/testing/selftests/powerpc/mm/Makefile
new file mode 100644
index 00000000000..357ccbd6bad
--- /dev/null
+++ b/tools/testing/selftests/powerpc/mm/Makefile
@@ -0,0 +1,18 @@
+noarg:
+ $(MAKE) -C ../
+
+PROGS := hugetlb_vs_thp_test
+
+all: $(PROGS)
+
+$(PROGS): ../harness.c
+
+run_tests: all
+ @-for PROG in $(PROGS); do \
+ ./$$PROG; \
+ done;
+
+clean:
+ rm -f $(PROGS)
+
+.PHONY: all run_tests clean
diff --git a/tools/testing/selftests/powerpc/mm/hugetlb_vs_thp_test.c b/tools/testing/selftests/powerpc/mm/hugetlb_vs_thp_test.c
new file mode 100644
index 00000000000..3d8e5b033e1
--- /dev/null
+++ b/tools/testing/selftests/powerpc/mm/hugetlb_vs_thp_test.c
@@ -0,0 +1,72 @@
+#include <stdio.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#include "utils.h"
+
+/* This must match the huge page & THP size */
+#define SIZE (16 * 1024 * 1024)
+
+static int test_body(void)
+{
+ void *addr;
+ char *p;
+
+ addr = (void *)0xa0000000;
+
+ p = mmap(addr, SIZE, PROT_READ | PROT_WRITE,
+ MAP_HUGETLB | MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (p != MAP_FAILED) {
+ /*
+ * Typically the mmap will fail because no huge pages are
+ * allocated on the system. But if there are huge pages
+ * allocated the mmap will succeed. That's fine too, we just
+ * munmap here before continuing.
+ */
+ munmap(addr, SIZE);
+ }
+
+ p = mmap(addr, SIZE, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (p == MAP_FAILED) {
+ printf("Mapping failed @ %p\n", addr);
+ perror("mmap");
+ return 1;
+ }
+
+ /*
+ * Either a user or kernel access is sufficient to trigger the bug.
+ * A kernel access is easier to spot & debug, as it will trigger the
+ * softlockup or RCU stall detectors, and when the system is kicked
+ * into xmon we get a backtrace in the kernel.
+ *
+ * A good option is:
+ * getcwd(p, SIZE);
+ *
+ * For the purposes of this testcase it's preferable to spin in
+ * userspace, so the harness can kill us if we get stuck. That way we
+ * see a test failure rather than a dead system.
+ */
+ *p = 0xf;
+
+ munmap(addr, SIZE);
+
+ return 0;
+}
+
+static int test_main(void)
+{
+ int i;
+
+ /* 10,000 because it's a "bunch", and completes reasonably quickly */
+ for (i = 0; i < 10000; i++)
+ if (test_body())
+ return 1;
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(test_main, "hugetlb_vs_thp");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/Makefile b/tools/testing/selftests/powerpc/pmu/Makefile
new file mode 100644
index 00000000000..b9ff0db42c7
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/Makefile
@@ -0,0 +1,41 @@
+noarg:
+ $(MAKE) -C ../
+
+PROGS := count_instructions
+EXTRA_SOURCES := ../harness.c event.c
+
+all: $(PROGS) sub_all
+
+$(PROGS): $(EXTRA_SOURCES)
+
+# loop.S can only be built 64-bit
+count_instructions: loop.S count_instructions.c $(EXTRA_SOURCES)
+ $(CC) $(CFLAGS) -m64 -o $@ $^
+
+run_tests: all sub_run_tests
+ @-for PROG in $(PROGS); do \
+ ./$$PROG; \
+ done;
+
+clean: sub_clean
+ rm -f $(PROGS) loop.o
+
+
+SUB_TARGETS = ebb
+
+sub_all:
+ @for TARGET in $(SUB_TARGETS); do \
+ $(MAKE) -C $$TARGET all; \
+ done;
+
+sub_run_tests: all
+ @for TARGET in $(SUB_TARGETS); do \
+ $(MAKE) -C $$TARGET run_tests; \
+ done;
+
+sub_clean:
+ @for TARGET in $(SUB_TARGETS); do \
+ $(MAKE) -C $$TARGET clean; \
+ done;
+
+.PHONY: all run_tests clean sub_all sub_run_tests sub_clean
diff --git a/tools/testing/selftests/powerpc/pmu/count_instructions.c b/tools/testing/selftests/powerpc/pmu/count_instructions.c
new file mode 100644
index 00000000000..312b4f0fd27
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/count_instructions.c
@@ -0,0 +1,135 @@
+/*
+ * Copyright 2013, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/prctl.h>
+
+#include "event.h"
+#include "utils.h"
+
+extern void thirty_two_instruction_loop(u64 loops);
+
+static void setup_event(struct event *e, u64 config, char *name)
+{
+ event_init_opts(e, config, PERF_TYPE_HARDWARE, name);
+
+ e->attr.disabled = 1;
+ e->attr.exclude_kernel = 1;
+ e->attr.exclude_hv = 1;
+ e->attr.exclude_idle = 1;
+}
+
+static int do_count_loop(struct event *events, u64 instructions,
+ u64 overhead, bool report)
+{
+ s64 difference, expected;
+ double percentage;
+
+ prctl(PR_TASK_PERF_EVENTS_ENABLE);
+
+ /* Run for 1M instructions */
+ thirty_two_instruction_loop(instructions >> 5);
+
+ prctl(PR_TASK_PERF_EVENTS_DISABLE);
+
+ event_read(&events[0]);
+ event_read(&events[1]);
+
+ expected = instructions + overhead;
+ difference = events[0].result.value - expected;
+ percentage = (double)difference / events[0].result.value * 100;
+
+ if (report) {
+ event_report(&events[0]);
+ event_report(&events[1]);
+
+ printf("Looped for %llu instructions, overhead %llu\n", instructions, overhead);
+ printf("Expected %llu\n", expected);
+ printf("Actual %llu\n", events[0].result.value);
+ printf("Delta %lld, %f%%\n", difference, percentage);
+ }
+
+ event_reset(&events[0]);
+ event_reset(&events[1]);
+
+ if (difference < 0)
+ difference = -difference;
+
+ /* Tolerate a difference below 0.0001 % */
+ difference *= 10000 * 100;
+ if (difference / events[0].result.value)
+ return -1;
+
+ return 0;
+}
+
+/* Count how many instructions it takes to do a null loop */
+static u64 determine_overhead(struct event *events)
+{
+ u64 current, overhead;
+ int i;
+
+ do_count_loop(events, 0, 0, false);
+ overhead = events[0].result.value;
+
+ for (i = 0; i < 100; i++) {
+ do_count_loop(events, 0, 0, false);
+ current = events[0].result.value;
+ if (current < overhead) {
+ printf("Replacing overhead %llu with %llu\n", overhead, current);
+ overhead = current;
+ }
+ }
+
+ return overhead;
+}
+
+static int count_instructions(void)
+{
+ struct event events[2];
+ u64 overhead;
+
+ setup_event(&events[0], PERF_COUNT_HW_INSTRUCTIONS, "instructions");
+ setup_event(&events[1], PERF_COUNT_HW_CPU_CYCLES, "cycles");
+
+ if (event_open(&events[0])) {
+ perror("perf_event_open");
+ return -1;
+ }
+
+ if (event_open_with_group(&events[1], events[0].fd)) {
+ perror("perf_event_open");
+ return -1;
+ }
+
+ overhead = determine_overhead(events);
+ printf("Overhead of null loop: %llu instructions\n", overhead);
+
+ /* Run for 1M instructions */
+ FAIL_IF(do_count_loop(events, 0x100000, overhead, true));
+
+ /* Run for 10M instructions */
+ FAIL_IF(do_count_loop(events, 0xa00000, overhead, true));
+
+ /* Run for 100M instructions */
+ FAIL_IF(do_count_loop(events, 0x6400000, overhead, true));
+
+ /* Run for 1G instructions */
+ FAIL_IF(do_count_loop(events, 0x40000000, overhead, true));
+
+ event_close(&events[0]);
+ event_close(&events[1]);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(count_instructions, "count_instructions");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/Makefile b/tools/testing/selftests/powerpc/pmu/ebb/Makefile
new file mode 100644
index 00000000000..edbba2affc2
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/Makefile
@@ -0,0 +1,32 @@
+noarg:
+ $(MAKE) -C ../../
+
+# The EBB handler is 64-bit code and everything links against it
+CFLAGS += -m64
+
+PROGS := reg_access_test event_attributes_test cycles_test \
+ cycles_with_freeze_test pmc56_overflow_test \
+ ebb_vs_cpu_event_test cpu_event_vs_ebb_test \
+ cpu_event_pinned_vs_ebb_test task_event_vs_ebb_test \
+ task_event_pinned_vs_ebb_test multi_ebb_procs_test \
+ multi_counter_test pmae_handling_test \
+ close_clears_pmcc_test instruction_count_test \
+ fork_cleanup_test ebb_on_child_test \
+ ebb_on_willing_child_test back_to_back_ebbs_test \
+ lost_exception_test no_handler_test
+
+all: $(PROGS)
+
+$(PROGS): ../../harness.c ../event.c ../lib.c ebb.c ebb_handler.S trace.c
+
+instruction_count_test: ../loop.S
+
+lost_exception_test: ../lib.c
+
+run_tests: all
+ @-for PROG in $(PROGS); do \
+ ./$$PROG; \
+ done;
+
+clean:
+ rm -f $(PROGS)
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/back_to_back_ebbs_test.c b/tools/testing/selftests/powerpc/pmu/ebb/back_to_back_ebbs_test.c
new file mode 100644
index 00000000000..66ea765c0e7
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/back_to_back_ebbs_test.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ebb.h"
+
+
+#define NUMBER_OF_EBBS 50
+
+/*
+ * Test that if we overflow the counter while in the EBB handler, we take
+ * another EBB on exiting from the handler.
+ *
+ * We do this by counting with a stupidly low sample period, causing us to
+ * overflow the PMU while we're still in the EBB handler, leading to another
+ * EBB.
+ *
+ * We get out of what would otherwise be an infinite loop by leaving the
+ * counter frozen once we've taken enough EBBs.
+ */
+
+static void ebb_callee(void)
+{
+ uint64_t siar, val;
+
+ val = mfspr(SPRN_BESCR);
+ if (!(val & BESCR_PMEO)) {
+ ebb_state.stats.spurious++;
+ goto out;
+ }
+
+ ebb_state.stats.ebb_count++;
+ trace_log_counter(ebb_state.trace, ebb_state.stats.ebb_count);
+
+ /* Resets the PMC */
+ count_pmc(1, sample_period);
+
+out:
+ if (ebb_state.stats.ebb_count == NUMBER_OF_EBBS)
+ /* Reset but leave counters frozen */
+ reset_ebb_with_clear_mask(MMCR0_PMAO);
+ else
+ /* Unfreezes */
+ reset_ebb();
+
+ /* Do some stuff to chew some cycles and pop the counter */
+ siar = mfspr(SPRN_SIAR);
+ trace_log_reg(ebb_state.trace, SPRN_SIAR, siar);
+
+ val = mfspr(SPRN_PMC1);
+ trace_log_reg(ebb_state.trace, SPRN_PMC1, val);
+
+ val = mfspr(SPRN_MMCR0);
+ trace_log_reg(ebb_state.trace, SPRN_MMCR0, val);
+}
+
+int back_to_back_ebbs(void)
+{
+ struct event event;
+
+ event_init_named(&event, 0x1001e, "cycles");
+ event_leader_ebb_init(&event);
+
+ event.attr.exclude_kernel = 1;
+ event.attr.exclude_hv = 1;
+ event.attr.exclude_idle = 1;
+
+ FAIL_IF(event_open(&event));
+
+ setup_ebb_handler(ebb_callee);
+
+ FAIL_IF(ebb_event_enable(&event));
+
+ sample_period = 5;
+
+ ebb_freeze_pmcs();
+ mtspr(SPRN_PMC1, pmc_sample_period(sample_period));
+ ebb_global_enable();
+ ebb_unfreeze_pmcs();
+
+ while (ebb_state.stats.ebb_count < NUMBER_OF_EBBS)
+ FAIL_IF(core_busy_loop());
+
+ ebb_global_disable();
+ ebb_freeze_pmcs();
+
+ count_pmc(1, sample_period);
+
+ dump_ebb_state();
+
+ event_close(&event);
+
+ FAIL_IF(ebb_state.stats.ebb_count != NUMBER_OF_EBBS);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(back_to_back_ebbs, "back_to_back_ebbs");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/close_clears_pmcc_test.c b/tools/testing/selftests/powerpc/pmu/ebb/close_clears_pmcc_test.c
new file mode 100644
index 00000000000..0f0423dba18
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/close_clears_pmcc_test.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <signal.h>
+
+#include "ebb.h"
+
+
+/*
+ * Test that closing the EBB event clears MMCR0_PMCC, preventing further access
+ * by userspace to the PMU hardware.
+ */
+
+int close_clears_pmcc(void)
+{
+ struct event event;
+
+ event_init_named(&event, 0x1001e, "cycles");
+ event_leader_ebb_init(&event);
+
+ FAIL_IF(event_open(&event));
+
+ ebb_enable_pmc_counting(1);
+ setup_ebb_handler(standard_ebb_callee);
+ ebb_global_enable();
+ FAIL_IF(ebb_event_enable(&event));
+
+ mtspr(SPRN_PMC1, pmc_sample_period(sample_period));
+
+ while (ebb_state.stats.ebb_count < 1)
+ FAIL_IF(core_busy_loop());
+
+ ebb_global_disable();
+ event_close(&event);
+
+ FAIL_IF(ebb_state.stats.ebb_count == 0);
+
+ /* The real test is here, do we take a SIGILL when writing PMU regs now
+ * that we have closed the event. We expect that we will. */
+
+ FAIL_IF(catch_sigill(write_pmc1));
+
+ /* We should still be able to read EBB regs though */
+ mfspr(SPRN_EBBHR);
+ mfspr(SPRN_EBBRR);
+ mfspr(SPRN_BESCR);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(close_clears_pmcc, "close_clears_pmcc");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_pinned_vs_ebb_test.c b/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_pinned_vs_ebb_test.c
new file mode 100644
index 00000000000..d3ed64d5d6c
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_pinned_vs_ebb_test.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "ebb.h"
+
+
+/*
+ * Tests a pinned cpu event vs an EBB - in that order. The pinned cpu event
+ * should remain and the EBB event should fail to enable.
+ */
+
+static int setup_cpu_event(struct event *event, int cpu)
+{
+ event_init_named(event, 0x400FA, "PM_RUN_INST_CMPL");
+
+ event->attr.pinned = 1;
+
+ event->attr.exclude_kernel = 1;
+ event->attr.exclude_hv = 1;
+ event->attr.exclude_idle = 1;
+
+ SKIP_IF(require_paranoia_below(1));
+ FAIL_IF(event_open_with_cpu(event, cpu));
+ FAIL_IF(event_enable(event));
+
+ return 0;
+}
+
+int cpu_event_pinned_vs_ebb(void)
+{
+ union pipe read_pipe, write_pipe;
+ struct event event;
+ int cpu, rc;
+ pid_t pid;
+
+ cpu = pick_online_cpu();
+ FAIL_IF(cpu < 0);
+ FAIL_IF(bind_to_cpu(cpu));
+
+ FAIL_IF(pipe(read_pipe.fds) == -1);
+ FAIL_IF(pipe(write_pipe.fds) == -1);
+
+ pid = fork();
+ if (pid == 0) {
+ /* NB order of pipes looks reversed */
+ exit(ebb_child(write_pipe, read_pipe));
+ }
+
+ /* We setup the cpu event first */
+ rc = setup_cpu_event(&event, cpu);
+ if (rc) {
+ kill_child_and_wait(pid);
+ return rc;
+ }
+
+ /* Signal the child to install its EBB event and wait */
+ if (sync_with_child(read_pipe, write_pipe))
+ /* If it fails, wait for it to exit */
+ goto wait;
+
+ /* Signal the child to run */
+ FAIL_IF(sync_with_child(read_pipe, write_pipe));
+
+wait:
+ /* We expect it to fail to read the event */
+ FAIL_IF(wait_for_child(pid) != 2);
+
+ FAIL_IF(event_disable(&event));
+ FAIL_IF(event_read(&event));
+
+ event_report(&event);
+
+ /* The cpu event should have run */
+ FAIL_IF(event.result.value == 0);
+ FAIL_IF(event.result.enabled != event.result.running);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(cpu_event_pinned_vs_ebb, "cpu_event_pinned_vs_ebb");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_vs_ebb_test.c b/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_vs_ebb_test.c
new file mode 100644
index 00000000000..8b972c2aa39
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/cpu_event_vs_ebb_test.c
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "ebb.h"
+
+
+/*
+ * Tests a cpu event vs an EBB - in that order. The EBB should force the cpu
+ * event off the PMU.
+ */
+
+static int setup_cpu_event(struct event *event, int cpu)
+{
+ event_init_named(event, 0x400FA, "PM_RUN_INST_CMPL");
+
+ event->attr.exclude_kernel = 1;
+ event->attr.exclude_hv = 1;
+ event->attr.exclude_idle = 1;
+
+ SKIP_IF(require_paranoia_below(1));
+ FAIL_IF(event_open_with_cpu(event, cpu));
+ FAIL_IF(event_enable(event));
+
+ return 0;
+}
+
+int cpu_event_vs_ebb(void)
+{
+ union pipe read_pipe, write_pipe;
+ struct event event;
+ int cpu, rc;
+ pid_t pid;
+
+ cpu = pick_online_cpu();
+ FAIL_IF(cpu < 0);
+ FAIL_IF(bind_to_cpu(cpu));
+
+ FAIL_IF(pipe(read_pipe.fds) == -1);
+ FAIL_IF(pipe(write_pipe.fds) == -1);
+
+ pid = fork();
+ if (pid == 0) {
+ /* NB order of pipes looks reversed */
+ exit(ebb_child(write_pipe, read_pipe));
+ }
+
+ /* We setup the cpu event first */
+ rc = setup_cpu_event(&event, cpu);
+ if (rc) {
+ kill_child_and_wait(pid);
+ return rc;
+ }
+
+ /* Signal the child to install its EBB event and wait */
+ if (sync_with_child(read_pipe, write_pipe))
+ /* If it fails, wait for it to exit */
+ goto wait;
+
+ /* Signal the child to run */
+ FAIL_IF(sync_with_child(read_pipe, write_pipe));
+
+wait:
+ /* We expect the child to succeed */
+ FAIL_IF(wait_for_child(pid));
+
+ FAIL_IF(event_disable(&event));
+ FAIL_IF(event_read(&event));
+
+ event_report(&event);
+
+ /* The cpu event may have run */
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(cpu_event_vs_ebb, "cpu_event_vs_ebb");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/cycles_test.c b/tools/testing/selftests/powerpc/pmu/ebb/cycles_test.c
new file mode 100644
index 00000000000..8590fc1bfc0
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/cycles_test.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ebb.h"
+
+
+/*
+ * Basic test that counts user cycles and takes EBBs.
+ */
+int cycles(void)
+{
+ struct event event;
+
+ event_init_named(&event, 0x1001e, "cycles");
+ event_leader_ebb_init(&event);
+
+ event.attr.exclude_kernel = 1;
+ event.attr.exclude_hv = 1;
+ event.attr.exclude_idle = 1;
+
+ FAIL_IF(event_open(&event));
+
+ ebb_enable_pmc_counting(1);
+ setup_ebb_handler(standard_ebb_callee);
+ ebb_global_enable();
+ FAIL_IF(ebb_event_enable(&event));
+
+ mtspr(SPRN_PMC1, pmc_sample_period(sample_period));
+
+ while (ebb_state.stats.ebb_count < 10) {
+ FAIL_IF(core_busy_loop());
+ FAIL_IF(ebb_check_mmcr0());
+ }
+
+ ebb_global_disable();
+ ebb_freeze_pmcs();
+
+ count_pmc(1, sample_period);
+
+ dump_ebb_state();
+
+ event_close(&event);
+
+ FAIL_IF(ebb_state.stats.ebb_count == 0);
+ FAIL_IF(!ebb_check_count(1, sample_period, 100));
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(cycles, "cycles");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/cycles_with_freeze_test.c b/tools/testing/selftests/powerpc/pmu/ebb/cycles_with_freeze_test.c
new file mode 100644
index 00000000000..754b3f2008d
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/cycles_with_freeze_test.c
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+
+#include "ebb.h"
+
+
+/*
+ * Test of counting cycles while using MMCR0_FC (freeze counters) to only count
+ * parts of the code. This is complicated by the fact that FC is set by the
+ * hardware when the event overflows. We may take the EBB after we have set FC,
+ * so we have to be careful about whether we clear FC at the end of the EBB
+ * handler or not.
+ */
+
+static bool counters_frozen = false;
+static int ebbs_while_frozen = 0;
+
+static void ebb_callee(void)
+{
+ uint64_t mask, val;
+
+ mask = MMCR0_PMAO | MMCR0_FC;
+
+ val = mfspr(SPRN_BESCR);
+ if (!(val & BESCR_PMEO)) {
+ ebb_state.stats.spurious++;
+ goto out;
+ }
+
+ ebb_state.stats.ebb_count++;
+ trace_log_counter(ebb_state.trace, ebb_state.stats.ebb_count);
+
+ val = mfspr(SPRN_MMCR0);
+ trace_log_reg(ebb_state.trace, SPRN_MMCR0, val);
+
+ if (counters_frozen) {
+ trace_log_string(ebb_state.trace, "frozen");
+ ebbs_while_frozen++;
+ mask &= ~MMCR0_FC;
+ }
+
+ count_pmc(1, sample_period);
+out:
+ reset_ebb_with_clear_mask(mask);
+}
+
+int cycles_with_freeze(void)
+{
+ struct event event;
+ uint64_t val;
+ bool fc_cleared;
+
+ event_init_named(&event, 0x1001e, "cycles");
+ event_leader_ebb_init(&event);
+
+ event.attr.exclude_kernel = 1;
+ event.attr.exclude_hv = 1;
+ event.attr.exclude_idle = 1;
+
+ FAIL_IF(event_open(&event));
+
+ setup_ebb_handler(ebb_callee);
+ ebb_global_enable();
+ FAIL_IF(ebb_event_enable(&event));
+
+ mtspr(SPRN_PMC1, pmc_sample_period(sample_period));
+
+ fc_cleared = false;
+
+ /* Make sure we loop until we take at least one EBB */
+ while ((ebb_state.stats.ebb_count < 20 && !fc_cleared) ||
+ ebb_state.stats.ebb_count < 1)
+ {
+ counters_frozen = false;
+ mb();
+ mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) & ~MMCR0_FC);
+
+ FAIL_IF(core_busy_loop());
+
+ counters_frozen = true;
+ mb();
+ mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) | MMCR0_FC);
+
+ val = mfspr(SPRN_MMCR0);
+ if (! (val & MMCR0_FC)) {
+ printf("Outside of loop, FC NOT set MMCR0 0x%lx\n", val);
+ fc_cleared = true;
+ }
+ }
+
+ ebb_global_disable();
+ ebb_freeze_pmcs();
+
+ count_pmc(1, sample_period);
+
+ dump_ebb_state();
+
+ printf("EBBs while frozen %d\n", ebbs_while_frozen);
+
+ event_close(&event);
+
+ FAIL_IF(ebb_state.stats.ebb_count == 0);
+ FAIL_IF(fc_cleared);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(cycles_with_freeze, "cycles_with_freeze");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/ebb.c b/tools/testing/selftests/powerpc/pmu/ebb/ebb.c
new file mode 100644
index 00000000000..1b46be94b64
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/ebb.c
@@ -0,0 +1,727 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#define _GNU_SOURCE /* For CPU_ZERO etc. */
+
+#include <sched.h>
+#include <sys/wait.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+
+#include "trace.h"
+#include "reg.h"
+#include "ebb.h"
+
+
+void (*ebb_user_func)(void);
+
+void ebb_hook(void)
+{
+ if (ebb_user_func)
+ ebb_user_func();
+}
+
+struct ebb_state ebb_state;
+
+u64 sample_period = 0x40000000ull;
+
+void reset_ebb_with_clear_mask(unsigned long mmcr0_clear_mask)
+{
+ u64 val;
+
+ /* 2) clear MMCR0[PMAO] - docs say BESCR[PMEO] should do this */
+ /* 3) set MMCR0[PMAE] - docs say BESCR[PME] should do this */
+ val = mfspr(SPRN_MMCR0);
+ mtspr(SPRN_MMCR0, (val & ~mmcr0_clear_mask) | MMCR0_PMAE);
+
+ /* 4) clear BESCR[PMEO] */
+ mtspr(SPRN_BESCRR, BESCR_PMEO);
+
+ /* 5) set BESCR[PME] */
+ mtspr(SPRN_BESCRS, BESCR_PME);
+
+ /* 6) rfebb 1 - done in our caller */
+}
+
+void reset_ebb(void)
+{
+ reset_ebb_with_clear_mask(MMCR0_PMAO | MMCR0_FC);
+}
+
+/* Called outside of the EBB handler to check MMCR0 is sane */
+int ebb_check_mmcr0(void)
+{
+ u64 val;
+
+ val = mfspr(SPRN_MMCR0);
+ if ((val & (MMCR0_FC | MMCR0_PMAO)) == MMCR0_FC) {
+ /* It's OK if we see FC & PMAO, but not FC by itself */
+ printf("Outside of loop, only FC set 0x%llx\n", val);
+ return 1;
+ }
+
+ return 0;
+}
+
+bool ebb_check_count(int pmc, u64 sample_period, int fudge)
+{
+ u64 count, upper, lower;
+
+ count = ebb_state.stats.pmc_count[PMC_INDEX(pmc)];
+
+ lower = ebb_state.stats.ebb_count * (sample_period - fudge);
+
+ if (count < lower) {
+ printf("PMC%d count (0x%llx) below lower limit 0x%llx (-0x%llx)\n",
+ pmc, count, lower, lower - count);
+ return false;
+ }
+
+ upper = ebb_state.stats.ebb_count * (sample_period + fudge);
+
+ if (count > upper) {
+ printf("PMC%d count (0x%llx) above upper limit 0x%llx (+0x%llx)\n",
+ pmc, count, upper, count - upper);
+ return false;
+ }
+
+ printf("PMC%d count (0x%llx) is between 0x%llx and 0x%llx delta +0x%llx/-0x%llx\n",
+ pmc, count, lower, upper, count - lower, upper - count);
+
+ return true;
+}
+
+void standard_ebb_callee(void)
+{
+ int found, i;
+ u64 val;
+
+ val = mfspr(SPRN_BESCR);
+ if (!(val & BESCR_PMEO)) {
+ ebb_state.stats.spurious++;
+ goto out;
+ }
+
+ ebb_state.stats.ebb_count++;
+ trace_log_counter(ebb_state.trace, ebb_state.stats.ebb_count);
+
+ val = mfspr(SPRN_MMCR0);
+ trace_log_reg(ebb_state.trace, SPRN_MMCR0, val);
+
+ found = 0;
+ for (i = 1; i <= 6; i++) {
+ if (ebb_state.pmc_enable[PMC_INDEX(i)])
+ found += count_pmc(i, sample_period);
+ }
+
+ if (!found)
+ ebb_state.stats.no_overflow++;
+
+out:
+ reset_ebb();
+}
+
+extern void ebb_handler(void);
+
+void setup_ebb_handler(void (*callee)(void))
+{
+ u64 entry;
+
+#if defined(_CALL_ELF) && _CALL_ELF == 2
+ entry = (u64)ebb_handler;
+#else
+ struct opd
+ {
+ u64 entry;
+ u64 toc;
+ } *opd;
+
+ opd = (struct opd *)ebb_handler;
+ entry = opd->entry;
+#endif
+ printf("EBB Handler is at %#llx\n", entry);
+
+ ebb_user_func = callee;
+
+ /* Ensure ebb_user_func is set before we set the handler */
+ mb();
+ mtspr(SPRN_EBBHR, entry);
+
+ /* Make sure the handler is set before we return */
+ mb();
+}
+
+void clear_ebb_stats(void)
+{
+ memset(&ebb_state.stats, 0, sizeof(ebb_state.stats));
+}
+
+void dump_summary_ebb_state(void)
+{
+ printf("ebb_state:\n" \
+ " ebb_count = %d\n" \
+ " spurious = %d\n" \
+ " negative = %d\n" \
+ " no_overflow = %d\n" \
+ " pmc[1] count = 0x%llx\n" \
+ " pmc[2] count = 0x%llx\n" \
+ " pmc[3] count = 0x%llx\n" \
+ " pmc[4] count = 0x%llx\n" \
+ " pmc[5] count = 0x%llx\n" \
+ " pmc[6] count = 0x%llx\n",
+ ebb_state.stats.ebb_count, ebb_state.stats.spurious,
+ ebb_state.stats.negative, ebb_state.stats.no_overflow,
+ ebb_state.stats.pmc_count[0], ebb_state.stats.pmc_count[1],
+ ebb_state.stats.pmc_count[2], ebb_state.stats.pmc_count[3],
+ ebb_state.stats.pmc_count[4], ebb_state.stats.pmc_count[5]);
+}
+
+static char *decode_mmcr0(u32 value)
+{
+ static char buf[16];
+
+ buf[0] = '\0';
+
+ if (value & (1 << 31))
+ strcat(buf, "FC ");
+ if (value & (1 << 26))
+ strcat(buf, "PMAE ");
+ if (value & (1 << 7))
+ strcat(buf, "PMAO ");
+
+ return buf;
+}
+
+static char *decode_bescr(u64 value)
+{
+ static char buf[16];
+
+ buf[0] = '\0';
+
+ if (value & (1ull << 63))
+ strcat(buf, "GE ");
+ if (value & (1ull << 32))
+ strcat(buf, "PMAE ");
+ if (value & 1)
+ strcat(buf, "PMAO ");
+
+ return buf;
+}
+
+void dump_ebb_hw_state(void)
+{
+ u64 bescr;
+ u32 mmcr0;
+
+ mmcr0 = mfspr(SPRN_MMCR0);
+ bescr = mfspr(SPRN_BESCR);
+
+ printf("HW state:\n" \
+ "MMCR0 0x%016x %s\n" \
+ "EBBHR 0x%016lx\n" \
+ "BESCR 0x%016llx %s\n" \
+ "PMC1 0x%016lx\n" \
+ "PMC2 0x%016lx\n" \
+ "PMC3 0x%016lx\n" \
+ "PMC4 0x%016lx\n" \
+ "PMC5 0x%016lx\n" \
+ "PMC6 0x%016lx\n" \
+ "SIAR 0x%016lx\n",
+ mmcr0, decode_mmcr0(mmcr0), mfspr(SPRN_EBBHR), bescr,
+ decode_bescr(bescr), mfspr(SPRN_PMC1), mfspr(SPRN_PMC2),
+ mfspr(SPRN_PMC3), mfspr(SPRN_PMC4), mfspr(SPRN_PMC5),
+ mfspr(SPRN_PMC6), mfspr(SPRN_SIAR));
+}
+
+void dump_ebb_state(void)
+{
+ dump_summary_ebb_state();
+
+ dump_ebb_hw_state();
+
+ trace_buffer_print(ebb_state.trace);
+}
+
+int count_pmc(int pmc, uint32_t sample_period)
+{
+ uint32_t start_value;
+ u64 val;
+
+ /* 0) Read PMC */
+ start_value = pmc_sample_period(sample_period);
+
+ val = read_pmc(pmc);
+ if (val < start_value)
+ ebb_state.stats.negative++;
+ else
+ ebb_state.stats.pmc_count[PMC_INDEX(pmc)] += val - start_value;
+
+ trace_log_reg(ebb_state.trace, SPRN_PMC1 + pmc - 1, val);
+
+ /* 1) Reset PMC */
+ write_pmc(pmc, start_value);
+
+ /* Report if we overflowed */
+ return val >= COUNTER_OVERFLOW;
+}
+
+int ebb_event_enable(struct event *e)
+{
+ int rc;
+
+ /* Ensure any SPR writes are ordered vs us */
+ mb();
+
+ rc = ioctl(e->fd, PERF_EVENT_IOC_ENABLE);
+ if (rc)
+ return rc;
+
+ rc = event_read(e);
+
+ /* Ditto */
+ mb();
+
+ return rc;
+}
+
+void ebb_freeze_pmcs(void)
+{
+ mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) | MMCR0_FC);
+ mb();
+}
+
+void ebb_unfreeze_pmcs(void)
+{
+ /* Unfreeze counters */
+ mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) & ~MMCR0_FC);
+ mb();
+}
+
+void ebb_global_enable(void)
+{
+ /* Enable EBBs globally and PMU EBBs */
+ mtspr(SPRN_BESCR, 0x8000000100000000ull);
+ mb();
+}
+
+void ebb_global_disable(void)
+{
+ /* Disable EBBs & freeze counters, events are still scheduled */
+ mtspr(SPRN_BESCRR, BESCR_PME);
+ mb();
+}
+
+void event_ebb_init(struct event *e)
+{
+ e->attr.config |= (1ull << 63);
+}
+
+void event_bhrb_init(struct event *e, unsigned ifm)
+{
+ e->attr.config |= (1ull << 62) | ((u64)ifm << 60);
+}
+
+void event_leader_ebb_init(struct event *e)
+{
+ event_ebb_init(e);
+
+ e->attr.exclusive = 1;
+ e->attr.pinned = 1;
+}
+
+int core_busy_loop(void)
+{
+ int rc;
+
+ asm volatile (
+ "li 3, 0x3030\n"
+ "std 3, -96(1)\n"
+ "li 4, 0x4040\n"
+ "std 4, -104(1)\n"
+ "li 5, 0x5050\n"
+ "std 5, -112(1)\n"
+ "li 6, 0x6060\n"
+ "std 6, -120(1)\n"
+ "li 7, 0x7070\n"
+ "std 7, -128(1)\n"
+ "li 8, 0x0808\n"
+ "std 8, -136(1)\n"
+ "li 9, 0x0909\n"
+ "std 9, -144(1)\n"
+ "li 10, 0x1010\n"
+ "std 10, -152(1)\n"
+ "li 11, 0x1111\n"
+ "std 11, -160(1)\n"
+ "li 14, 0x1414\n"
+ "std 14, -168(1)\n"
+ "li 15, 0x1515\n"
+ "std 15, -176(1)\n"
+ "li 16, 0x1616\n"
+ "std 16, -184(1)\n"
+ "li 17, 0x1717\n"
+ "std 17, -192(1)\n"
+ "li 18, 0x1818\n"
+ "std 18, -200(1)\n"
+ "li 19, 0x1919\n"
+ "std 19, -208(1)\n"
+ "li 20, 0x2020\n"
+ "std 20, -216(1)\n"
+ "li 21, 0x2121\n"
+ "std 21, -224(1)\n"
+ "li 22, 0x2222\n"
+ "std 22, -232(1)\n"
+ "li 23, 0x2323\n"
+ "std 23, -240(1)\n"
+ "li 24, 0x2424\n"
+ "std 24, -248(1)\n"
+ "li 25, 0x2525\n"
+ "std 25, -256(1)\n"
+ "li 26, 0x2626\n"
+ "std 26, -264(1)\n"
+ "li 27, 0x2727\n"
+ "std 27, -272(1)\n"
+ "li 28, 0x2828\n"
+ "std 28, -280(1)\n"
+ "li 29, 0x2929\n"
+ "std 29, -288(1)\n"
+ "li 30, 0x3030\n"
+ "li 31, 0x3131\n"
+
+ "li 3, 0\n"
+ "0: "
+ "addi 3, 3, 1\n"
+ "cmpwi 3, 100\n"
+ "blt 0b\n"
+
+ /* Return 1 (fail) unless we get through all the checks */
+ "li 0, 1\n"
+
+ /* Check none of our registers have been corrupted */
+ "cmpwi 4, 0x4040\n"
+ "bne 1f\n"
+ "cmpwi 5, 0x5050\n"
+ "bne 1f\n"
+ "cmpwi 6, 0x6060\n"
+ "bne 1f\n"
+ "cmpwi 7, 0x7070\n"
+ "bne 1f\n"
+ "cmpwi 8, 0x0808\n"
+ "bne 1f\n"
+ "cmpwi 9, 0x0909\n"
+ "bne 1f\n"
+ "cmpwi 10, 0x1010\n"
+ "bne 1f\n"
+ "cmpwi 11, 0x1111\n"
+ "bne 1f\n"
+ "cmpwi 14, 0x1414\n"
+ "bne 1f\n"
+ "cmpwi 15, 0x1515\n"
+ "bne 1f\n"
+ "cmpwi 16, 0x1616\n"
+ "bne 1f\n"
+ "cmpwi 17, 0x1717\n"
+ "bne 1f\n"
+ "cmpwi 18, 0x1818\n"
+ "bne 1f\n"
+ "cmpwi 19, 0x1919\n"
+ "bne 1f\n"
+ "cmpwi 20, 0x2020\n"
+ "bne 1f\n"
+ "cmpwi 21, 0x2121\n"
+ "bne 1f\n"
+ "cmpwi 22, 0x2222\n"
+ "bne 1f\n"
+ "cmpwi 23, 0x2323\n"
+ "bne 1f\n"
+ "cmpwi 24, 0x2424\n"
+ "bne 1f\n"
+ "cmpwi 25, 0x2525\n"
+ "bne 1f\n"
+ "cmpwi 26, 0x2626\n"
+ "bne 1f\n"
+ "cmpwi 27, 0x2727\n"
+ "bne 1f\n"
+ "cmpwi 28, 0x2828\n"
+ "bne 1f\n"
+ "cmpwi 29, 0x2929\n"
+ "bne 1f\n"
+ "cmpwi 30, 0x3030\n"
+ "bne 1f\n"
+ "cmpwi 31, 0x3131\n"
+ "bne 1f\n"
+
+ /* Load junk into all our registers before we reload them from the stack. */
+ "li 3, 0xde\n"
+ "li 4, 0xad\n"
+ "li 5, 0xbe\n"
+ "li 6, 0xef\n"
+ "li 7, 0xde\n"
+ "li 8, 0xad\n"
+ "li 9, 0xbe\n"
+ "li 10, 0xef\n"
+ "li 11, 0xde\n"
+ "li 14, 0xad\n"
+ "li 15, 0xbe\n"
+ "li 16, 0xef\n"
+ "li 17, 0xde\n"
+ "li 18, 0xad\n"
+ "li 19, 0xbe\n"
+ "li 20, 0xef\n"
+ "li 21, 0xde\n"
+ "li 22, 0xad\n"
+ "li 23, 0xbe\n"
+ "li 24, 0xef\n"
+ "li 25, 0xde\n"
+ "li 26, 0xad\n"
+ "li 27, 0xbe\n"
+ "li 28, 0xef\n"
+ "li 29, 0xdd\n"
+
+ "ld 3, -96(1)\n"
+ "cmpwi 3, 0x3030\n"
+ "bne 1f\n"
+ "ld 4, -104(1)\n"
+ "cmpwi 4, 0x4040\n"
+ "bne 1f\n"
+ "ld 5, -112(1)\n"
+ "cmpwi 5, 0x5050\n"
+ "bne 1f\n"
+ "ld 6, -120(1)\n"
+ "cmpwi 6, 0x6060\n"
+ "bne 1f\n"
+ "ld 7, -128(1)\n"
+ "cmpwi 7, 0x7070\n"
+ "bne 1f\n"
+ "ld 8, -136(1)\n"
+ "cmpwi 8, 0x0808\n"
+ "bne 1f\n"
+ "ld 9, -144(1)\n"
+ "cmpwi 9, 0x0909\n"
+ "bne 1f\n"
+ "ld 10, -152(1)\n"
+ "cmpwi 10, 0x1010\n"
+ "bne 1f\n"
+ "ld 11, -160(1)\n"
+ "cmpwi 11, 0x1111\n"
+ "bne 1f\n"
+ "ld 14, -168(1)\n"
+ "cmpwi 14, 0x1414\n"
+ "bne 1f\n"
+ "ld 15, -176(1)\n"
+ "cmpwi 15, 0x1515\n"
+ "bne 1f\n"
+ "ld 16, -184(1)\n"
+ "cmpwi 16, 0x1616\n"
+ "bne 1f\n"
+ "ld 17, -192(1)\n"
+ "cmpwi 17, 0x1717\n"
+ "bne 1f\n"
+ "ld 18, -200(1)\n"
+ "cmpwi 18, 0x1818\n"
+ "bne 1f\n"
+ "ld 19, -208(1)\n"
+ "cmpwi 19, 0x1919\n"
+ "bne 1f\n"
+ "ld 20, -216(1)\n"
+ "cmpwi 20, 0x2020\n"
+ "bne 1f\n"
+ "ld 21, -224(1)\n"
+ "cmpwi 21, 0x2121\n"
+ "bne 1f\n"
+ "ld 22, -232(1)\n"
+ "cmpwi 22, 0x2222\n"
+ "bne 1f\n"
+ "ld 23, -240(1)\n"
+ "cmpwi 23, 0x2323\n"
+ "bne 1f\n"
+ "ld 24, -248(1)\n"
+ "cmpwi 24, 0x2424\n"
+ "bne 1f\n"
+ "ld 25, -256(1)\n"
+ "cmpwi 25, 0x2525\n"
+ "bne 1f\n"
+ "ld 26, -264(1)\n"
+ "cmpwi 26, 0x2626\n"
+ "bne 1f\n"
+ "ld 27, -272(1)\n"
+ "cmpwi 27, 0x2727\n"
+ "bne 1f\n"
+ "ld 28, -280(1)\n"
+ "cmpwi 28, 0x2828\n"
+ "bne 1f\n"
+ "ld 29, -288(1)\n"
+ "cmpwi 29, 0x2929\n"
+ "bne 1f\n"
+
+ /* Load 0 (success) to return */
+ "li 0, 0\n"
+
+ "1: mr %0, 0\n"
+
+ : "=r" (rc)
+ : /* no inputs */
+ : "3", "4", "5", "6", "7", "8", "9", "10", "11", "14",
+ "15", "16", "17", "18", "19", "20", "21", "22", "23",
+ "24", "25", "26", "27", "28", "29", "30", "31",
+ "memory"
+ );
+
+ return rc;
+}
+
+int core_busy_loop_with_freeze(void)
+{
+ int rc;
+
+ mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) & ~MMCR0_FC);
+ rc = core_busy_loop();
+ mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) | MMCR0_FC);
+
+ return rc;
+}
+
+int ebb_child(union pipe read_pipe, union pipe write_pipe)
+{
+ struct event event;
+ uint64_t val;
+
+ FAIL_IF(wait_for_parent(read_pipe));
+
+ event_init_named(&event, 0x1001e, "cycles");
+ event_leader_ebb_init(&event);
+
+ event.attr.exclude_kernel = 1;
+ event.attr.exclude_hv = 1;
+ event.attr.exclude_idle = 1;
+
+ FAIL_IF(event_open(&event));
+
+ ebb_enable_pmc_counting(1);
+ setup_ebb_handler(standard_ebb_callee);
+ ebb_global_enable();
+
+ FAIL_IF(event_enable(&event));
+
+ if (event_read(&event)) {
+ /*
+ * Some tests expect to fail here, so don't report an error on
+ * this line, and return a distinguisable error code. Tell the
+ * parent an error happened.
+ */
+ notify_parent_of_error(write_pipe);
+ return 2;
+ }
+
+ mtspr(SPRN_PMC1, pmc_sample_period(sample_period));
+
+ FAIL_IF(notify_parent(write_pipe));
+ FAIL_IF(wait_for_parent(read_pipe));
+ FAIL_IF(notify_parent(write_pipe));
+
+ while (ebb_state.stats.ebb_count < 20) {
+ FAIL_IF(core_busy_loop());
+
+ /* To try and hit SIGILL case */
+ val = mfspr(SPRN_MMCRA);
+ val |= mfspr(SPRN_MMCR2);
+ val |= mfspr(SPRN_MMCR0);
+ }
+
+ ebb_global_disable();
+ ebb_freeze_pmcs();
+
+ count_pmc(1, sample_period);
+
+ dump_ebb_state();
+
+ event_close(&event);
+
+ FAIL_IF(ebb_state.stats.ebb_count == 0);
+
+ return 0;
+}
+
+static jmp_buf setjmp_env;
+
+static void sigill_handler(int signal)
+{
+ printf("Took sigill\n");
+ longjmp(setjmp_env, 1);
+}
+
+static struct sigaction sigill_action = {
+ .sa_handler = sigill_handler,
+};
+
+int catch_sigill(void (*func)(void))
+{
+ if (sigaction(SIGILL, &sigill_action, NULL)) {
+ perror("sigaction");
+ return 1;
+ }
+
+ if (setjmp(setjmp_env) == 0) {
+ func();
+ return 1;
+ }
+
+ return 0;
+}
+
+void write_pmc1(void)
+{
+ mtspr(SPRN_PMC1, 0);
+}
+
+void write_pmc(int pmc, u64 value)
+{
+ switch (pmc) {
+ case 1: mtspr(SPRN_PMC1, value); break;
+ case 2: mtspr(SPRN_PMC2, value); break;
+ case 3: mtspr(SPRN_PMC3, value); break;
+ case 4: mtspr(SPRN_PMC4, value); break;
+ case 5: mtspr(SPRN_PMC5, value); break;
+ case 6: mtspr(SPRN_PMC6, value); break;
+ }
+}
+
+u64 read_pmc(int pmc)
+{
+ switch (pmc) {
+ case 1: return mfspr(SPRN_PMC1);
+ case 2: return mfspr(SPRN_PMC2);
+ case 3: return mfspr(SPRN_PMC3);
+ case 4: return mfspr(SPRN_PMC4);
+ case 5: return mfspr(SPRN_PMC5);
+ case 6: return mfspr(SPRN_PMC6);
+ }
+
+ return 0;
+}
+
+static void term_handler(int signal)
+{
+ dump_summary_ebb_state();
+ dump_ebb_hw_state();
+ abort();
+}
+
+struct sigaction term_action = {
+ .sa_handler = term_handler,
+};
+
+static void __attribute__((constructor)) ebb_init(void)
+{
+ clear_ebb_stats();
+
+ if (sigaction(SIGTERM, &term_action, NULL))
+ perror("sigaction");
+
+ ebb_state.trace = trace_buffer_allocate(1 * 1024 * 1024);
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/ebb.h b/tools/testing/selftests/powerpc/pmu/ebb/ebb.h
new file mode 100644
index 00000000000..e62bde05bf7
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/ebb.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#ifndef _SELFTESTS_POWERPC_PMU_EBB_EBB_H
+#define _SELFTESTS_POWERPC_PMU_EBB_EBB_H
+
+#include "../event.h"
+#include "../lib.h"
+#include "trace.h"
+#include "reg.h"
+
+#define PMC_INDEX(pmc) ((pmc)-1)
+
+#define NUM_PMC_VALUES 128
+
+struct ebb_state
+{
+ struct {
+ u64 pmc_count[6];
+ volatile int ebb_count;
+ int spurious;
+ int negative;
+ int no_overflow;
+ } stats;
+
+ bool pmc_enable[6];
+ struct trace_buffer *trace;
+};
+
+extern struct ebb_state ebb_state;
+
+#define COUNTER_OVERFLOW 0x80000000ull
+
+static inline uint32_t pmc_sample_period(uint32_t value)
+{
+ return COUNTER_OVERFLOW - value;
+}
+
+static inline void ebb_enable_pmc_counting(int pmc)
+{
+ ebb_state.pmc_enable[PMC_INDEX(pmc)] = true;
+}
+
+bool ebb_check_count(int pmc, u64 sample_period, int fudge);
+void event_leader_ebb_init(struct event *e);
+void event_ebb_init(struct event *e);
+void event_bhrb_init(struct event *e, unsigned ifm);
+void setup_ebb_handler(void (*callee)(void));
+void standard_ebb_callee(void);
+int ebb_event_enable(struct event *e);
+void ebb_global_enable(void);
+void ebb_global_disable(void);
+void ebb_freeze_pmcs(void);
+void ebb_unfreeze_pmcs(void);
+void event_ebb_init(struct event *e);
+void event_leader_ebb_init(struct event *e);
+int count_pmc(int pmc, uint32_t sample_period);
+void dump_ebb_state(void);
+void dump_summary_ebb_state(void);
+void dump_ebb_hw_state(void);
+void clear_ebb_stats(void);
+void write_pmc(int pmc, u64 value);
+u64 read_pmc(int pmc);
+void reset_ebb_with_clear_mask(unsigned long mmcr0_clear_mask);
+void reset_ebb(void);
+int ebb_check_mmcr0(void);
+
+extern u64 sample_period;
+
+int core_busy_loop(void);
+int core_busy_loop_with_freeze(void);
+int ebb_child(union pipe read_pipe, union pipe write_pipe);
+int catch_sigill(void (*func)(void));
+void write_pmc1(void);
+
+#endif /* _SELFTESTS_POWERPC_PMU_EBB_EBB_H */
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/ebb_handler.S b/tools/testing/selftests/powerpc/pmu/ebb/ebb_handler.S
new file mode 100644
index 00000000000..14274ea206e
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/ebb_handler.S
@@ -0,0 +1,365 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <ppc-asm.h>
+#include "reg.h"
+
+
+/* ppc-asm.h defines most of the reg aliases, but not r1/r2. */
+#define r1 1
+#define r2 2
+
+#define RFEBB .long 0x4c000924
+
+/* Stack layout:
+ *
+ * ^
+ * User stack |
+ * Back chain ------+ <- r1 <-------+
+ * ... |
+ * Red zone / ABI Gap |
+ * ... |
+ * vr63 <+ |
+ * vr0 | |
+ * VSCR | |
+ * FSCR | |
+ * r31 | Save area |
+ * r0 | |
+ * XER | |
+ * CTR | |
+ * LR | |
+ * CCR <+ |
+ * ... <+ |
+ * LR | Caller frame |
+ * CCR | |
+ * Back chain <+ <- updated r1 --------+
+ *
+ */
+
+#if defined(_CALL_ELF) && _CALL_ELF == 2
+#define ABIGAP 512
+#else
+#define ABIGAP 288
+#endif
+
+#define NR_GPR 32
+#define NR_SPR 6
+#define NR_VSR 64
+
+#define SAVE_AREA ((NR_GPR + NR_SPR) * 8 + (NR_VSR * 16))
+#define CALLER_FRAME 112
+
+#define STACK_FRAME (ABIGAP + SAVE_AREA + CALLER_FRAME)
+
+#define CCR_SAVE (CALLER_FRAME)
+#define LR_SAVE (CCR_SAVE + 8)
+#define CTR_SAVE (LR_SAVE + 8)
+#define XER_SAVE (CTR_SAVE + 8)
+#define GPR_SAVE(n) (XER_SAVE + 8 + (8 * n))
+#define FSCR_SAVE (GPR_SAVE(31) + 8)
+#define VSCR_SAVE (FSCR_SAVE + 8)
+#define VSR_SAVE(n) (VSCR_SAVE + 8 + (16 * n))
+
+#define SAVE_GPR(n) std n,GPR_SAVE(n)(r1)
+#define REST_GPR(n) ld n,GPR_SAVE(n)(r1)
+#define TRASH_GPR(n) lis n,0xaaaa
+
+#define SAVE_VSR(n, b) li b, VSR_SAVE(n); stxvd2x n,b,r1
+#define LOAD_VSR(n, b) li b, VSR_SAVE(n); lxvd2x n,b,r1
+
+#define LOAD_REG_IMMEDIATE(reg,expr) \
+ lis reg,(expr)@highest; \
+ ori reg,reg,(expr)@higher; \
+ rldicr reg,reg,32,31; \
+ oris reg,reg,(expr)@h; \
+ ori reg,reg,(expr)@l;
+
+
+#if defined(_CALL_ELF) && _CALL_ELF == 2
+#define ENTRY_POINT(name) \
+ .type FUNC_NAME(name),@function; \
+ .globl FUNC_NAME(name); \
+ FUNC_NAME(name):
+
+#define RESTORE_TOC(name) \
+ /* Restore our TOC pointer using our entry point */ \
+ LOAD_REG_IMMEDIATE(r12, name) \
+0: addis r2,r12,(.TOC.-0b)@ha; \
+ addi r2,r2,(.TOC.-0b)@l;
+
+#else
+#define ENTRY_POINT(name) FUNC_START(name)
+#define RESTORE_TOC(name) \
+ /* Restore our TOC pointer via our opd entry */ \
+ LOAD_REG_IMMEDIATE(r2, name) \
+ ld r2,8(r2);
+#endif
+
+ .text
+
+ENTRY_POINT(ebb_handler)
+ stdu r1,-STACK_FRAME(r1)
+ SAVE_GPR(0)
+ mflr r0
+ std r0,LR_SAVE(r1)
+ mfcr r0
+ std r0,CCR_SAVE(r1)
+ mfctr r0
+ std r0,CTR_SAVE(r1)
+ mfxer r0
+ std r0,XER_SAVE(r1)
+ SAVE_GPR(2)
+ SAVE_GPR(3)
+ SAVE_GPR(4)
+ SAVE_GPR(5)
+ SAVE_GPR(6)
+ SAVE_GPR(7)
+ SAVE_GPR(8)
+ SAVE_GPR(9)
+ SAVE_GPR(10)
+ SAVE_GPR(11)
+ SAVE_GPR(12)
+ SAVE_GPR(13)
+ SAVE_GPR(14)
+ SAVE_GPR(15)
+ SAVE_GPR(16)
+ SAVE_GPR(17)
+ SAVE_GPR(18)
+ SAVE_GPR(19)
+ SAVE_GPR(20)
+ SAVE_GPR(21)
+ SAVE_GPR(22)
+ SAVE_GPR(23)
+ SAVE_GPR(24)
+ SAVE_GPR(25)
+ SAVE_GPR(26)
+ SAVE_GPR(27)
+ SAVE_GPR(28)
+ SAVE_GPR(29)
+ SAVE_GPR(30)
+ SAVE_GPR(31)
+ SAVE_VSR(0, r3)
+ mffs f0
+ stfd f0, FSCR_SAVE(r1)
+ mfvscr f0
+ stfd f0, VSCR_SAVE(r1)
+ SAVE_VSR(1, r3)
+ SAVE_VSR(2, r3)
+ SAVE_VSR(3, r3)
+ SAVE_VSR(4, r3)
+ SAVE_VSR(5, r3)
+ SAVE_VSR(6, r3)
+ SAVE_VSR(7, r3)
+ SAVE_VSR(8, r3)
+ SAVE_VSR(9, r3)
+ SAVE_VSR(10, r3)
+ SAVE_VSR(11, r3)
+ SAVE_VSR(12, r3)
+ SAVE_VSR(13, r3)
+ SAVE_VSR(14, r3)
+ SAVE_VSR(15, r3)
+ SAVE_VSR(16, r3)
+ SAVE_VSR(17, r3)
+ SAVE_VSR(18, r3)
+ SAVE_VSR(19, r3)
+ SAVE_VSR(20, r3)
+ SAVE_VSR(21, r3)
+ SAVE_VSR(22, r3)
+ SAVE_VSR(23, r3)
+ SAVE_VSR(24, r3)
+ SAVE_VSR(25, r3)
+ SAVE_VSR(26, r3)
+ SAVE_VSR(27, r3)
+ SAVE_VSR(28, r3)
+ SAVE_VSR(29, r3)
+ SAVE_VSR(30, r3)
+ SAVE_VSR(31, r3)
+ SAVE_VSR(32, r3)
+ SAVE_VSR(33, r3)
+ SAVE_VSR(34, r3)
+ SAVE_VSR(35, r3)
+ SAVE_VSR(36, r3)
+ SAVE_VSR(37, r3)
+ SAVE_VSR(38, r3)
+ SAVE_VSR(39, r3)
+ SAVE_VSR(40, r3)
+ SAVE_VSR(41, r3)
+ SAVE_VSR(42, r3)
+ SAVE_VSR(43, r3)
+ SAVE_VSR(44, r3)
+ SAVE_VSR(45, r3)
+ SAVE_VSR(46, r3)
+ SAVE_VSR(47, r3)
+ SAVE_VSR(48, r3)
+ SAVE_VSR(49, r3)
+ SAVE_VSR(50, r3)
+ SAVE_VSR(51, r3)
+ SAVE_VSR(52, r3)
+ SAVE_VSR(53, r3)
+ SAVE_VSR(54, r3)
+ SAVE_VSR(55, r3)
+ SAVE_VSR(56, r3)
+ SAVE_VSR(57, r3)
+ SAVE_VSR(58, r3)
+ SAVE_VSR(59, r3)
+ SAVE_VSR(60, r3)
+ SAVE_VSR(61, r3)
+ SAVE_VSR(62, r3)
+ SAVE_VSR(63, r3)
+
+ TRASH_GPR(2)
+ TRASH_GPR(3)
+ TRASH_GPR(4)
+ TRASH_GPR(5)
+ TRASH_GPR(6)
+ TRASH_GPR(7)
+ TRASH_GPR(8)
+ TRASH_GPR(9)
+ TRASH_GPR(10)
+ TRASH_GPR(11)
+ TRASH_GPR(12)
+ TRASH_GPR(14)
+ TRASH_GPR(15)
+ TRASH_GPR(16)
+ TRASH_GPR(17)
+ TRASH_GPR(18)
+ TRASH_GPR(19)
+ TRASH_GPR(20)
+ TRASH_GPR(21)
+ TRASH_GPR(22)
+ TRASH_GPR(23)
+ TRASH_GPR(24)
+ TRASH_GPR(25)
+ TRASH_GPR(26)
+ TRASH_GPR(27)
+ TRASH_GPR(28)
+ TRASH_GPR(29)
+ TRASH_GPR(30)
+ TRASH_GPR(31)
+
+ RESTORE_TOC(ebb_handler)
+
+ /*
+ * r13 is our TLS pointer. We leave whatever value was in there when the
+ * EBB fired. That seems to be OK because once set the TLS pointer is not
+ * changed - but presumably that could change in future.
+ */
+
+ bl ebb_hook
+ nop
+
+ /* r2 may be changed here but we don't care */
+
+ lfd f0, FSCR_SAVE(r1)
+ mtfsf 0xff,f0
+ lfd f0, VSCR_SAVE(r1)
+ mtvscr f0
+ LOAD_VSR(0, r3)
+ LOAD_VSR(1, r3)
+ LOAD_VSR(2, r3)
+ LOAD_VSR(3, r3)
+ LOAD_VSR(4, r3)
+ LOAD_VSR(5, r3)
+ LOAD_VSR(6, r3)
+ LOAD_VSR(7, r3)
+ LOAD_VSR(8, r3)
+ LOAD_VSR(9, r3)
+ LOAD_VSR(10, r3)
+ LOAD_VSR(11, r3)
+ LOAD_VSR(12, r3)
+ LOAD_VSR(13, r3)
+ LOAD_VSR(14, r3)
+ LOAD_VSR(15, r3)
+ LOAD_VSR(16, r3)
+ LOAD_VSR(17, r3)
+ LOAD_VSR(18, r3)
+ LOAD_VSR(19, r3)
+ LOAD_VSR(20, r3)
+ LOAD_VSR(21, r3)
+ LOAD_VSR(22, r3)
+ LOAD_VSR(23, r3)
+ LOAD_VSR(24, r3)
+ LOAD_VSR(25, r3)
+ LOAD_VSR(26, r3)
+ LOAD_VSR(27, r3)
+ LOAD_VSR(28, r3)
+ LOAD_VSR(29, r3)
+ LOAD_VSR(30, r3)
+ LOAD_VSR(31, r3)
+ LOAD_VSR(32, r3)
+ LOAD_VSR(33, r3)
+ LOAD_VSR(34, r3)
+ LOAD_VSR(35, r3)
+ LOAD_VSR(36, r3)
+ LOAD_VSR(37, r3)
+ LOAD_VSR(38, r3)
+ LOAD_VSR(39, r3)
+ LOAD_VSR(40, r3)
+ LOAD_VSR(41, r3)
+ LOAD_VSR(42, r3)
+ LOAD_VSR(43, r3)
+ LOAD_VSR(44, r3)
+ LOAD_VSR(45, r3)
+ LOAD_VSR(46, r3)
+ LOAD_VSR(47, r3)
+ LOAD_VSR(48, r3)
+ LOAD_VSR(49, r3)
+ LOAD_VSR(50, r3)
+ LOAD_VSR(51, r3)
+ LOAD_VSR(52, r3)
+ LOAD_VSR(53, r3)
+ LOAD_VSR(54, r3)
+ LOAD_VSR(55, r3)
+ LOAD_VSR(56, r3)
+ LOAD_VSR(57, r3)
+ LOAD_VSR(58, r3)
+ LOAD_VSR(59, r3)
+ LOAD_VSR(60, r3)
+ LOAD_VSR(61, r3)
+ LOAD_VSR(62, r3)
+ LOAD_VSR(63, r3)
+
+ ld r0,XER_SAVE(r1)
+ mtxer r0
+ ld r0,CTR_SAVE(r1)
+ mtctr r0
+ ld r0,LR_SAVE(r1)
+ mtlr r0
+ ld r0,CCR_SAVE(r1)
+ mtcr r0
+ REST_GPR(0)
+ REST_GPR(2)
+ REST_GPR(3)
+ REST_GPR(4)
+ REST_GPR(5)
+ REST_GPR(6)
+ REST_GPR(7)
+ REST_GPR(8)
+ REST_GPR(9)
+ REST_GPR(10)
+ REST_GPR(11)
+ REST_GPR(12)
+ REST_GPR(13)
+ REST_GPR(14)
+ REST_GPR(15)
+ REST_GPR(16)
+ REST_GPR(17)
+ REST_GPR(18)
+ REST_GPR(19)
+ REST_GPR(20)
+ REST_GPR(21)
+ REST_GPR(22)
+ REST_GPR(23)
+ REST_GPR(24)
+ REST_GPR(25)
+ REST_GPR(26)
+ REST_GPR(27)
+ REST_GPR(28)
+ REST_GPR(29)
+ REST_GPR(30)
+ REST_GPR(31)
+ addi r1,r1,STACK_FRAME
+ RFEBB
+FUNC_END(ebb_handler)
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/ebb_on_child_test.c b/tools/testing/selftests/powerpc/pmu/ebb/ebb_on_child_test.c
new file mode 100644
index 00000000000..c45f948148e
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/ebb_on_child_test.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "ebb.h"
+
+
+/*
+ * Tests we can setup an EBB on our child. Nothing interesting happens, because
+ * even though the event is enabled and running the child hasn't enabled the
+ * actual delivery of the EBBs.
+ */
+
+static int victim_child(union pipe read_pipe, union pipe write_pipe)
+{
+ int i;
+
+ FAIL_IF(wait_for_parent(read_pipe));
+ FAIL_IF(notify_parent(write_pipe));
+
+ /* Parent creates EBB event */
+
+ FAIL_IF(wait_for_parent(read_pipe));
+ FAIL_IF(notify_parent(write_pipe));
+
+ /* Check the EBB is enabled by writing PMC1 */
+ write_pmc1();
+
+ /* EBB event is enabled here */
+ for (i = 0; i < 1000000; i++) ;
+
+ return 0;
+}
+
+int ebb_on_child(void)
+{
+ union pipe read_pipe, write_pipe;
+ struct event event;
+ pid_t pid;
+
+ FAIL_IF(pipe(read_pipe.fds) == -1);
+ FAIL_IF(pipe(write_pipe.fds) == -1);
+
+ pid = fork();
+ if (pid == 0) {
+ /* NB order of pipes looks reversed */
+ exit(victim_child(write_pipe, read_pipe));
+ }
+
+ FAIL_IF(sync_with_child(read_pipe, write_pipe));
+
+ /* Child is running now */
+
+ event_init_named(&event, 0x1001e, "cycles");
+ event_leader_ebb_init(&event);
+
+ event.attr.exclude_kernel = 1;
+ event.attr.exclude_hv = 1;
+ event.attr.exclude_idle = 1;
+
+ FAIL_IF(event_open_with_pid(&event, pid));
+ FAIL_IF(ebb_event_enable(&event));
+
+ FAIL_IF(sync_with_child(read_pipe, write_pipe));
+
+ /* Child should just exit happily */
+ FAIL_IF(wait_for_child(pid));
+
+ event_close(&event);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(ebb_on_child, "ebb_on_child");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/ebb_on_willing_child_test.c b/tools/testing/selftests/powerpc/pmu/ebb/ebb_on_willing_child_test.c
new file mode 100644
index 00000000000..11acf1d55f8
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/ebb_on_willing_child_test.c
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "ebb.h"
+
+
+/*
+ * Tests we can setup an EBB on our child. The child expects this and enables
+ * EBBs, which are then delivered to the child, even though the event is
+ * created by the parent.
+ */
+
+static int victim_child(union pipe read_pipe, union pipe write_pipe)
+{
+ FAIL_IF(wait_for_parent(read_pipe));
+
+ /* Setup our EBB handler, before the EBB event is created */
+ ebb_enable_pmc_counting(1);
+ setup_ebb_handler(standard_ebb_callee);
+ ebb_global_enable();
+
+ FAIL_IF(notify_parent(write_pipe));
+
+ while (ebb_state.stats.ebb_count < 20) {
+ FAIL_IF(core_busy_loop());
+ }
+
+ ebb_global_disable();
+ ebb_freeze_pmcs();
+
+ count_pmc(1, sample_period);
+
+ dump_ebb_state();
+
+ FAIL_IF(ebb_state.stats.ebb_count == 0);
+
+ return 0;
+}
+
+/* Tests we can setup an EBB on our child - if it's expecting it */
+int ebb_on_willing_child(void)
+{
+ union pipe read_pipe, write_pipe;
+ struct event event;
+ pid_t pid;
+
+ FAIL_IF(pipe(read_pipe.fds) == -1);
+ FAIL_IF(pipe(write_pipe.fds) == -1);
+
+ pid = fork();
+ if (pid == 0) {
+ /* NB order of pipes looks reversed */
+ exit(victim_child(write_pipe, read_pipe));
+ }
+
+ /* Signal the child to setup its EBB handler */
+ FAIL_IF(sync_with_child(read_pipe, write_pipe));
+
+ /* Child is running now */
+
+ event_init_named(&event, 0x1001e, "cycles");
+ event_leader_ebb_init(&event);
+
+ event.attr.exclude_kernel = 1;
+ event.attr.exclude_hv = 1;
+ event.attr.exclude_idle = 1;
+
+ FAIL_IF(event_open_with_pid(&event, pid));
+ FAIL_IF(ebb_event_enable(&event));
+
+ /* Child show now take EBBs and then exit */
+ FAIL_IF(wait_for_child(pid));
+
+ event_close(&event);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(ebb_on_willing_child, "ebb_on_willing_child");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/ebb_vs_cpu_event_test.c b/tools/testing/selftests/powerpc/pmu/ebb/ebb_vs_cpu_event_test.c
new file mode 100644
index 00000000000..be4dd5a4e98
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/ebb_vs_cpu_event_test.c
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "ebb.h"
+
+
+/*
+ * Tests an EBB vs a cpu event - in that order. The EBB should force the cpu
+ * event off the PMU.
+ */
+
+static int setup_cpu_event(struct event *event, int cpu)
+{
+ event_init_named(event, 0x400FA, "PM_RUN_INST_CMPL");
+
+ event->attr.exclude_kernel = 1;
+ event->attr.exclude_hv = 1;
+ event->attr.exclude_idle = 1;
+
+ SKIP_IF(require_paranoia_below(1));
+ FAIL_IF(event_open_with_cpu(event, cpu));
+ FAIL_IF(event_enable(event));
+
+ return 0;
+}
+
+int ebb_vs_cpu_event(void)
+{
+ union pipe read_pipe, write_pipe;
+ struct event event;
+ int cpu, rc;
+ pid_t pid;
+
+ cpu = pick_online_cpu();
+ FAIL_IF(cpu < 0);
+ FAIL_IF(bind_to_cpu(cpu));
+
+ FAIL_IF(pipe(read_pipe.fds) == -1);
+ FAIL_IF(pipe(write_pipe.fds) == -1);
+
+ pid = fork();
+ if (pid == 0) {
+ /* NB order of pipes looks reversed */
+ exit(ebb_child(write_pipe, read_pipe));
+ }
+
+ /* Signal the child to install its EBB event and wait */
+ FAIL_IF(sync_with_child(read_pipe, write_pipe));
+
+ /* Now try to install our CPU event */
+ rc = setup_cpu_event(&event, cpu);
+ if (rc) {
+ kill_child_and_wait(pid);
+ return rc;
+ }
+
+ /* Signal the child to run */
+ FAIL_IF(sync_with_child(read_pipe, write_pipe));
+
+ /* .. and wait for it to complete */
+ FAIL_IF(wait_for_child(pid));
+ FAIL_IF(event_disable(&event));
+ FAIL_IF(event_read(&event));
+
+ event_report(&event);
+
+ /* The cpu event may have run, but we don't expect 100% */
+ FAIL_IF(event.result.enabled >= event.result.running);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(ebb_vs_cpu_event, "ebb_vs_cpu_event");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/event_attributes_test.c b/tools/testing/selftests/powerpc/pmu/ebb/event_attributes_test.c
new file mode 100644
index 00000000000..7e78153f08e
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/event_attributes_test.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ebb.h"
+
+
+/*
+ * Test various attributes of the EBB event are enforced.
+ */
+int event_attributes(void)
+{
+ struct event event, leader;
+
+ event_init(&event, 0x1001e);
+ event_leader_ebb_init(&event);
+ /* Expected to succeed */
+ FAIL_IF(event_open(&event));
+ event_close(&event);
+
+
+ event_init(&event, 0x001e); /* CYCLES - no PMC specified */
+ event_leader_ebb_init(&event);
+ /* Expected to fail, no PMC specified */
+ FAIL_IF(event_open(&event) == 0);
+
+
+ event_init(&event, 0x2001e);
+ event_leader_ebb_init(&event);
+ event.attr.exclusive = 0;
+ /* Expected to fail, not exclusive */
+ FAIL_IF(event_open(&event) == 0);
+
+
+ event_init(&event, 0x3001e);
+ event_leader_ebb_init(&event);
+ event.attr.freq = 1;
+ /* Expected to fail, sets freq */
+ FAIL_IF(event_open(&event) == 0);
+
+
+ event_init(&event, 0x4001e);
+ event_leader_ebb_init(&event);
+ event.attr.sample_period = 1;
+ /* Expected to fail, sets sample_period */
+ FAIL_IF(event_open(&event) == 0);
+
+
+ event_init(&event, 0x1001e);
+ event_leader_ebb_init(&event);
+ event.attr.enable_on_exec = 1;
+ /* Expected to fail, sets enable_on_exec */
+ FAIL_IF(event_open(&event) == 0);
+
+
+ event_init(&event, 0x1001e);
+ event_leader_ebb_init(&event);
+ event.attr.inherit = 1;
+ /* Expected to fail, sets inherit */
+ FAIL_IF(event_open(&event) == 0);
+
+
+ event_init(&leader, 0x1001e);
+ event_leader_ebb_init(&leader);
+ FAIL_IF(event_open(&leader));
+
+ event_init(&event, 0x20002);
+ event_ebb_init(&event);
+
+ /* Expected to succeed */
+ FAIL_IF(event_open_with_group(&event, leader.fd));
+ event_close(&leader);
+ event_close(&event);
+
+
+ event_init(&leader, 0x1001e);
+ event_leader_ebb_init(&leader);
+ FAIL_IF(event_open(&leader));
+
+ event_init(&event, 0x20002);
+
+ /* Expected to fail, event doesn't request EBB, leader does */
+ FAIL_IF(event_open_with_group(&event, leader.fd) == 0);
+ event_close(&leader);
+
+
+ event_init(&leader, 0x1001e);
+ event_leader_ebb_init(&leader);
+ /* Clear the EBB flag */
+ leader.attr.config &= ~(1ull << 63);
+
+ FAIL_IF(event_open(&leader));
+
+ event_init(&event, 0x20002);
+ event_ebb_init(&event);
+
+ /* Expected to fail, leader doesn't request EBB */
+ FAIL_IF(event_open_with_group(&event, leader.fd) == 0);
+ event_close(&leader);
+
+
+ event_init(&leader, 0x1001e);
+ event_leader_ebb_init(&leader);
+ leader.attr.exclusive = 0;
+ /* Expected to fail, leader isn't exclusive */
+ FAIL_IF(event_open(&leader) == 0);
+
+
+ event_init(&leader, 0x1001e);
+ event_leader_ebb_init(&leader);
+ leader.attr.pinned = 0;
+ /* Expected to fail, leader isn't pinned */
+ FAIL_IF(event_open(&leader) == 0);
+
+ event_init(&event, 0x1001e);
+ event_leader_ebb_init(&event);
+ /* Expected to fail, not a task event */
+ SKIP_IF(require_paranoia_below(1));
+ FAIL_IF(event_open_with_cpu(&event, 0) == 0);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(event_attributes, "event_attributes");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/fixed_instruction_loop.S b/tools/testing/selftests/powerpc/pmu/ebb/fixed_instruction_loop.S
new file mode 100644
index 00000000000..b866a0581d3
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/fixed_instruction_loop.S
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <ppc-asm.h>
+
+ .text
+
+FUNC_START(thirty_two_instruction_loop)
+ cmpwi r3,0
+ beqlr
+ addi r4,r3,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1 # 28 addi's
+ subi r3,r3,1
+ b FUNC_NAME(thirty_two_instruction_loop)
+FUNC_END(thirty_two_instruction_loop)
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/fork_cleanup_test.c b/tools/testing/selftests/powerpc/pmu/ebb/fork_cleanup_test.c
new file mode 100644
index 00000000000..9e7af6e7662
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/fork_cleanup_test.c
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+#include <setjmp.h>
+#include <signal.h>
+
+#include "ebb.h"
+
+
+/*
+ * Test that a fork clears the PMU state of the child. eg. BESCR/EBBHR/EBBRR
+ * are cleared, and MMCR0_PMCC is reset, preventing the child from accessing
+ * the PMU.
+ */
+
+static struct event event;
+
+static int child(void)
+{
+ /* Even though we have EBE=0 we can still see the EBB regs */
+ FAIL_IF(mfspr(SPRN_BESCR) != 0);
+ FAIL_IF(mfspr(SPRN_EBBHR) != 0);
+ FAIL_IF(mfspr(SPRN_EBBRR) != 0);
+
+ FAIL_IF(catch_sigill(write_pmc1));
+
+ /* We can still read from the event, though it is on our parent */
+ FAIL_IF(event_read(&event));
+
+ return 0;
+}
+
+/* Tests that fork clears EBB state */
+int fork_cleanup(void)
+{
+ pid_t pid;
+
+ event_init_named(&event, 0x1001e, "cycles");
+ event_leader_ebb_init(&event);
+
+ FAIL_IF(event_open(&event));
+
+ ebb_enable_pmc_counting(1);
+ setup_ebb_handler(standard_ebb_callee);
+ ebb_global_enable();
+
+ FAIL_IF(ebb_event_enable(&event));
+
+ mtspr(SPRN_MMCR0, MMCR0_FC);
+ mtspr(SPRN_PMC1, pmc_sample_period(sample_period));
+
+ /* Don't need to actually take any EBBs */
+
+ pid = fork();
+ if (pid == 0)
+ exit(child());
+
+ /* Child does the actual testing */
+ FAIL_IF(wait_for_child(pid));
+
+ /* After fork */
+ event_close(&event);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(fork_cleanup, "fork_cleanup");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/instruction_count_test.c b/tools/testing/selftests/powerpc/pmu/ebb/instruction_count_test.c
new file mode 100644
index 00000000000..f8190fa2959
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/instruction_count_test.c
@@ -0,0 +1,164 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#define _GNU_SOURCE
+
+#include <stdio.h>
+#include <stdbool.h>
+#include <string.h>
+#include <sys/prctl.h>
+
+#include "ebb.h"
+
+
+/*
+ * Run a calibrated instruction loop and count instructions executed using
+ * EBBs. Make sure the counts look right.
+ */
+
+extern void thirty_two_instruction_loop(uint64_t loops);
+
+static bool counters_frozen = true;
+
+static int do_count_loop(struct event *event, uint64_t instructions,
+ uint64_t overhead, bool report)
+{
+ int64_t difference, expected;
+ double percentage;
+
+ clear_ebb_stats();
+
+ counters_frozen = false;
+ mb();
+ mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) & ~MMCR0_FC);
+
+ thirty_two_instruction_loop(instructions >> 5);
+
+ counters_frozen = true;
+ mb();
+ mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) | MMCR0_FC);
+
+ count_pmc(4, sample_period);
+
+ event->result.value = ebb_state.stats.pmc_count[4-1];
+ expected = instructions + overhead;
+ difference = event->result.value - expected;
+ percentage = (double)difference / event->result.value * 100;
+
+ if (report) {
+ printf("Looped for %lu instructions, overhead %lu\n", instructions, overhead);
+ printf("Expected %lu\n", expected);
+ printf("Actual %llu\n", event->result.value);
+ printf("Error %ld, %f%%\n", difference, percentage);
+ printf("Took %d EBBs\n", ebb_state.stats.ebb_count);
+ }
+
+ if (difference < 0)
+ difference = -difference;
+
+ /* Tolerate a difference of up to 0.0001 % */
+ difference *= 10000 * 100;
+ if (difference / event->result.value)
+ return -1;
+
+ return 0;
+}
+
+/* Count how many instructions it takes to do a null loop */
+static uint64_t determine_overhead(struct event *event)
+{
+ uint64_t current, overhead;
+ int i;
+
+ do_count_loop(event, 0, 0, false);
+ overhead = event->result.value;
+
+ for (i = 0; i < 100; i++) {
+ do_count_loop(event, 0, 0, false);
+ current = event->result.value;
+ if (current < overhead) {
+ printf("Replacing overhead %lu with %lu\n", overhead, current);
+ overhead = current;
+ }
+ }
+
+ return overhead;
+}
+
+static void pmc4_ebb_callee(void)
+{
+ uint64_t val;
+
+ val = mfspr(SPRN_BESCR);
+ if (!(val & BESCR_PMEO)) {
+ ebb_state.stats.spurious++;
+ goto out;
+ }
+
+ ebb_state.stats.ebb_count++;
+ count_pmc(4, sample_period);
+out:
+ if (counters_frozen)
+ reset_ebb_with_clear_mask(MMCR0_PMAO);
+ else
+ reset_ebb();
+}
+
+int instruction_count(void)
+{
+ struct event event;
+ uint64_t overhead;
+
+ event_init_named(&event, 0x400FA, "PM_RUN_INST_CMPL");
+ event_leader_ebb_init(&event);
+ event.attr.exclude_kernel = 1;
+ event.attr.exclude_hv = 1;
+ event.attr.exclude_idle = 1;
+
+ FAIL_IF(event_open(&event));
+ FAIL_IF(ebb_event_enable(&event));
+
+ sample_period = COUNTER_OVERFLOW;
+
+ setup_ebb_handler(pmc4_ebb_callee);
+ mtspr(SPRN_MMCR0, mfspr(SPRN_MMCR0) & ~MMCR0_FC);
+ ebb_global_enable();
+
+ overhead = determine_overhead(&event);
+ printf("Overhead of null loop: %lu instructions\n", overhead);
+
+ /* Run for 1M instructions */
+ FAIL_IF(do_count_loop(&event, 0x100000, overhead, true));
+
+ /* Run for 10M instructions */
+ FAIL_IF(do_count_loop(&event, 0xa00000, overhead, true));
+
+ /* Run for 100M instructions */
+ FAIL_IF(do_count_loop(&event, 0x6400000, overhead, true));
+
+ /* Run for 1G instructions */
+ FAIL_IF(do_count_loop(&event, 0x40000000, overhead, true));
+
+ /* Run for 16G instructions */
+ FAIL_IF(do_count_loop(&event, 0x400000000, overhead, true));
+
+ /* Run for 64G instructions */
+ FAIL_IF(do_count_loop(&event, 0x1000000000, overhead, true));
+
+ /* Run for 128G instructions */
+ FAIL_IF(do_count_loop(&event, 0x2000000000, overhead, true));
+
+ ebb_global_disable();
+ event_close(&event);
+
+ printf("Finished OK\n");
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(instruction_count, "instruction_count");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c b/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c
new file mode 100644
index 00000000000..0c9dd9b2e39
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/lost_exception_test.c
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <sched.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/mman.h>
+
+#include "ebb.h"
+
+
+/*
+ * Test that tries to trigger CPU_FTR_PMAO_BUG. Which is a hardware defect
+ * where an exception triggers but we context switch before it is delivered and
+ * lose the exception.
+ */
+
+static int test_body(void)
+{
+ int i, orig_period, max_period;
+ struct event event;
+
+ /* We use PMC4 to make sure the kernel switches all counters correctly */
+ event_init_named(&event, 0x40002, "instructions");
+ event_leader_ebb_init(&event);
+
+ event.attr.exclude_kernel = 1;
+ event.attr.exclude_hv = 1;
+ event.attr.exclude_idle = 1;
+
+ FAIL_IF(event_open(&event));
+
+ ebb_enable_pmc_counting(4);
+ setup_ebb_handler(standard_ebb_callee);
+ ebb_global_enable();
+ FAIL_IF(ebb_event_enable(&event));
+
+ /*
+ * We want a low sample period, but we also want to get out of the EBB
+ * handler without tripping up again.
+ *
+ * This value picked after much experimentation.
+ */
+ orig_period = max_period = sample_period = 400;
+
+ mtspr(SPRN_PMC4, pmc_sample_period(sample_period));
+
+ while (ebb_state.stats.ebb_count < 1000000) {
+ /*
+ * We are trying to get the EBB exception to race exactly with
+ * us entering the kernel to do the syscall. We then need the
+ * kernel to decide our timeslice is up and context switch to
+ * the other thread. When we come back our EBB will have been
+ * lost and we'll spin in this while loop forever.
+ */
+
+ for (i = 0; i < 100000; i++)
+ sched_yield();
+
+ /* Change the sample period slightly to try and hit the race */
+ if (sample_period >= (orig_period + 200))
+ sample_period = orig_period;
+ else
+ sample_period++;
+
+ if (sample_period > max_period)
+ max_period = sample_period;
+ }
+
+ ebb_freeze_pmcs();
+ ebb_global_disable();
+
+ count_pmc(4, sample_period);
+ mtspr(SPRN_PMC4, 0xdead);
+
+ dump_summary_ebb_state();
+ dump_ebb_hw_state();
+
+ event_close(&event);
+
+ FAIL_IF(ebb_state.stats.ebb_count == 0);
+
+ /* We vary our sample period so we need extra fudge here */
+ FAIL_IF(!ebb_check_count(4, orig_period, 2 * (max_period - orig_period)));
+
+ return 0;
+}
+
+static int lost_exception(void)
+{
+ return eat_cpu(test_body);
+}
+
+int main(void)
+{
+ return test_harness(lost_exception, "lost_exception");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/multi_counter_test.c b/tools/testing/selftests/powerpc/pmu/ebb/multi_counter_test.c
new file mode 100644
index 00000000000..67d78af3284
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/multi_counter_test.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+
+#include "ebb.h"
+
+
+/*
+ * Test counting multiple events using EBBs.
+ */
+int multi_counter(void)
+{
+ struct event events[6];
+ int i, group_fd;
+
+ event_init_named(&events[0], 0x1001C, "PM_CMPLU_STALL_THRD");
+ event_init_named(&events[1], 0x2D016, "PM_CMPLU_STALL_FXU");
+ event_init_named(&events[2], 0x30006, "PM_CMPLU_STALL_OTHER_CMPL");
+ event_init_named(&events[3], 0x4000A, "PM_CMPLU_STALL");
+ event_init_named(&events[4], 0x600f4, "PM_RUN_CYC");
+ event_init_named(&events[5], 0x500fa, "PM_RUN_INST_CMPL");
+
+ event_leader_ebb_init(&events[0]);
+ for (i = 1; i < 6; i++)
+ event_ebb_init(&events[i]);
+
+ group_fd = -1;
+ for (i = 0; i < 6; i++) {
+ events[i].attr.exclude_kernel = 1;
+ events[i].attr.exclude_hv = 1;
+ events[i].attr.exclude_idle = 1;
+
+ FAIL_IF(event_open_with_group(&events[i], group_fd));
+ if (group_fd == -1)
+ group_fd = events[0].fd;
+ }
+
+ ebb_enable_pmc_counting(1);
+ ebb_enable_pmc_counting(2);
+ ebb_enable_pmc_counting(3);
+ ebb_enable_pmc_counting(4);
+ ebb_enable_pmc_counting(5);
+ ebb_enable_pmc_counting(6);
+ setup_ebb_handler(standard_ebb_callee);
+
+ FAIL_IF(ioctl(events[0].fd, PERF_EVENT_IOC_ENABLE, PERF_IOC_FLAG_GROUP));
+ FAIL_IF(event_read(&events[0]));
+
+ ebb_global_enable();
+
+ mtspr(SPRN_PMC1, pmc_sample_period(sample_period));
+ mtspr(SPRN_PMC2, pmc_sample_period(sample_period));
+ mtspr(SPRN_PMC3, pmc_sample_period(sample_period));
+ mtspr(SPRN_PMC4, pmc_sample_period(sample_period));
+ mtspr(SPRN_PMC5, pmc_sample_period(sample_period));
+ mtspr(SPRN_PMC6, pmc_sample_period(sample_period));
+
+ while (ebb_state.stats.ebb_count < 50) {
+ FAIL_IF(core_busy_loop());
+ FAIL_IF(ebb_check_mmcr0());
+ }
+
+ ebb_global_disable();
+ ebb_freeze_pmcs();
+
+ count_pmc(1, sample_period);
+ count_pmc(2, sample_period);
+ count_pmc(3, sample_period);
+ count_pmc(4, sample_period);
+ count_pmc(5, sample_period);
+ count_pmc(6, sample_period);
+
+ dump_ebb_state();
+
+ for (i = 0; i < 6; i++)
+ event_close(&events[i]);
+
+ FAIL_IF(ebb_state.stats.ebb_count == 0);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(multi_counter, "multi_counter");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/multi_ebb_procs_test.c b/tools/testing/selftests/powerpc/pmu/ebb/multi_ebb_procs_test.c
new file mode 100644
index 00000000000..b8dc371f933
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/multi_ebb_procs_test.c
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <signal.h>
+
+#include "ebb.h"
+
+
+/*
+ * Test running multiple EBB using processes at once on a single CPU. They
+ * should all run happily without interfering with each other.
+ */
+
+static bool child_should_exit;
+
+static void sigint_handler(int signal)
+{
+ child_should_exit = true;
+}
+
+struct sigaction sigint_action = {
+ .sa_handler = sigint_handler,
+};
+
+static int cycles_child(void)
+{
+ struct event event;
+
+ if (sigaction(SIGINT, &sigint_action, NULL)) {
+ perror("sigaction");
+ return 1;
+ }
+
+ event_init_named(&event, 0x1001e, "cycles");
+ event_leader_ebb_init(&event);
+
+ event.attr.exclude_kernel = 1;
+ event.attr.exclude_hv = 1;
+ event.attr.exclude_idle = 1;
+
+ FAIL_IF(event_open(&event));
+
+ ebb_enable_pmc_counting(1);
+ setup_ebb_handler(standard_ebb_callee);
+ ebb_global_enable();
+
+ FAIL_IF(ebb_event_enable(&event));
+
+ mtspr(SPRN_PMC1, pmc_sample_period(sample_period));
+
+ while (!child_should_exit) {
+ FAIL_IF(core_busy_loop());
+ FAIL_IF(ebb_check_mmcr0());
+ }
+
+ ebb_global_disable();
+ ebb_freeze_pmcs();
+
+ count_pmc(1, sample_period);
+
+ dump_summary_ebb_state();
+
+ event_close(&event);
+
+ FAIL_IF(ebb_state.stats.ebb_count == 0);
+
+ return 0;
+}
+
+#define NR_CHILDREN 4
+
+int multi_ebb_procs(void)
+{
+ pid_t pids[NR_CHILDREN];
+ int cpu, rc, i;
+
+ cpu = pick_online_cpu();
+ FAIL_IF(cpu < 0);
+ FAIL_IF(bind_to_cpu(cpu));
+
+ for (i = 0; i < NR_CHILDREN; i++) {
+ pids[i] = fork();
+ if (pids[i] == 0)
+ exit(cycles_child());
+ }
+
+ /* Have them all run for "a while" */
+ sleep(10);
+
+ rc = 0;
+ for (i = 0; i < NR_CHILDREN; i++) {
+ /* Tell them to stop */
+ kill(pids[i], SIGINT);
+ /* And wait */
+ rc |= wait_for_child(pids[i]);
+ }
+
+ return rc;
+}
+
+int main(void)
+{
+ return test_harness(multi_ebb_procs, "multi_ebb_procs");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/no_handler_test.c b/tools/testing/selftests/powerpc/pmu/ebb/no_handler_test.c
new file mode 100644
index 00000000000..2f9bf8edfa6
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/no_handler_test.c
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <setjmp.h>
+#include <signal.h>
+
+#include "ebb.h"
+
+
+/* Test that things work sanely if we have no handler */
+
+static int no_handler_test(void)
+{
+ struct event event;
+ u64 val;
+ int i;
+
+ event_init_named(&event, 0x1001e, "cycles");
+ event_leader_ebb_init(&event);
+
+ event.attr.exclude_kernel = 1;
+ event.attr.exclude_hv = 1;
+ event.attr.exclude_idle = 1;
+
+ FAIL_IF(event_open(&event));
+ FAIL_IF(ebb_event_enable(&event));
+
+ val = mfspr(SPRN_EBBHR);
+ FAIL_IF(val != 0);
+
+ /* Make sure it overflows quickly */
+ sample_period = 1000;
+ mtspr(SPRN_PMC1, pmc_sample_period(sample_period));
+
+ /* Spin to make sure the event has time to overflow */
+ for (i = 0; i < 1000; i++)
+ mb();
+
+ dump_ebb_state();
+
+ /* We expect to see the PMU frozen & PMAO set */
+ val = mfspr(SPRN_MMCR0);
+ FAIL_IF(val != 0x0000000080000080);
+
+ event_close(&event);
+
+ dump_ebb_state();
+
+ /* The real test is that we never took an EBB at 0x0 */
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(no_handler_test,"no_handler_test");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/pmae_handling_test.c b/tools/testing/selftests/powerpc/pmu/ebb/pmae_handling_test.c
new file mode 100644
index 00000000000..986500fd213
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/pmae_handling_test.c
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <sched.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ebb.h"
+
+
+/*
+ * Test that the kernel properly handles PMAE across context switches.
+ *
+ * We test this by calling into the kernel inside our EBB handler, where PMAE
+ * is clear. A cpu eater companion thread is running on the same CPU as us to
+ * encourage the scheduler to switch us.
+ *
+ * The kernel must make sure that when it context switches us back in, it
+ * honours the fact that we had PMAE clear.
+ *
+ * Observed to hit the failing case on the first EBB with a broken kernel.
+ */
+
+static bool mmcr0_mismatch;
+static uint64_t before, after;
+
+static void syscall_ebb_callee(void)
+{
+ uint64_t val;
+
+ val = mfspr(SPRN_BESCR);
+ if (!(val & BESCR_PMEO)) {
+ ebb_state.stats.spurious++;
+ goto out;
+ }
+
+ ebb_state.stats.ebb_count++;
+ count_pmc(1, sample_period);
+
+ before = mfspr(SPRN_MMCR0);
+
+ /* Try and get ourselves scheduled, to force a PMU context switch */
+ sched_yield();
+
+ after = mfspr(SPRN_MMCR0);
+ if (before != after)
+ mmcr0_mismatch = true;
+
+out:
+ reset_ebb();
+}
+
+static int test_body(void)
+{
+ struct event event;
+
+ event_init_named(&event, 0x1001e, "cycles");
+ event_leader_ebb_init(&event);
+
+ event.attr.exclude_kernel = 1;
+ event.attr.exclude_hv = 1;
+ event.attr.exclude_idle = 1;
+
+ FAIL_IF(event_open(&event));
+
+ setup_ebb_handler(syscall_ebb_callee);
+ ebb_global_enable();
+
+ FAIL_IF(ebb_event_enable(&event));
+
+ mtspr(SPRN_PMC1, pmc_sample_period(sample_period));
+
+ while (ebb_state.stats.ebb_count < 20 && !mmcr0_mismatch)
+ FAIL_IF(core_busy_loop());
+
+ ebb_global_disable();
+ ebb_freeze_pmcs();
+
+ count_pmc(1, sample_period);
+
+ dump_ebb_state();
+
+ if (mmcr0_mismatch)
+ printf("Saw MMCR0 before 0x%lx after 0x%lx\n", before, after);
+
+ event_close(&event);
+
+ FAIL_IF(ebb_state.stats.ebb_count == 0);
+ FAIL_IF(mmcr0_mismatch);
+
+ return 0;
+}
+
+int pmae_handling(void)
+{
+ return eat_cpu(test_body);
+}
+
+int main(void)
+{
+ return test_harness(pmae_handling, "pmae_handling");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/pmc56_overflow_test.c b/tools/testing/selftests/powerpc/pmu/ebb/pmc56_overflow_test.c
new file mode 100644
index 00000000000..a503fa70c95
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/pmc56_overflow_test.c
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ebb.h"
+
+
+/*
+ * Test that PMC5 & 6 are frozen (ie. don't overflow) when they are not being
+ * used. Tests the MMCR0_FC56 logic in the kernel.
+ */
+
+static int pmc56_overflowed;
+
+static void ebb_callee(void)
+{
+ uint64_t val;
+
+ val = mfspr(SPRN_BESCR);
+ if (!(val & BESCR_PMEO)) {
+ ebb_state.stats.spurious++;
+ goto out;
+ }
+
+ ebb_state.stats.ebb_count++;
+ count_pmc(2, sample_period);
+
+ val = mfspr(SPRN_PMC5);
+ if (val >= COUNTER_OVERFLOW)
+ pmc56_overflowed++;
+
+ count_pmc(5, COUNTER_OVERFLOW);
+
+ val = mfspr(SPRN_PMC6);
+ if (val >= COUNTER_OVERFLOW)
+ pmc56_overflowed++;
+
+ count_pmc(6, COUNTER_OVERFLOW);
+
+out:
+ reset_ebb();
+}
+
+int pmc56_overflow(void)
+{
+ struct event event;
+
+ /* Use PMC2 so we set PMCjCE, which enables PMC5/6 */
+ event_init(&event, 0x2001e);
+ event_leader_ebb_init(&event);
+
+ event.attr.exclude_kernel = 1;
+ event.attr.exclude_hv = 1;
+ event.attr.exclude_idle = 1;
+
+ FAIL_IF(event_open(&event));
+
+ setup_ebb_handler(ebb_callee);
+ ebb_global_enable();
+
+ FAIL_IF(ebb_event_enable(&event));
+
+ mtspr(SPRN_PMC1, pmc_sample_period(sample_period));
+ mtspr(SPRN_PMC5, 0);
+ mtspr(SPRN_PMC6, 0);
+
+ while (ebb_state.stats.ebb_count < 10)
+ FAIL_IF(core_busy_loop());
+
+ ebb_global_disable();
+ ebb_freeze_pmcs();
+
+ count_pmc(2, sample_period);
+
+ dump_ebb_state();
+
+ printf("PMC5/6 overflow %d\n", pmc56_overflowed);
+
+ event_close(&event);
+
+ FAIL_IF(ebb_state.stats.ebb_count == 0 || pmc56_overflowed != 0);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(pmc56_overflow, "pmc56_overflow");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/reg.h b/tools/testing/selftests/powerpc/pmu/ebb/reg.h
new file mode 100644
index 00000000000..5921b0dfe2e
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/reg.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#ifndef _SELFTESTS_POWERPC_REG_H
+#define _SELFTESTS_POWERPC_REG_H
+
+#define __stringify_1(x) #x
+#define __stringify(x) __stringify_1(x)
+
+#define mfspr(rn) ({unsigned long rval; \
+ asm volatile("mfspr %0," __stringify(rn) \
+ : "=r" (rval)); rval; })
+#define mtspr(rn, v) asm volatile("mtspr " __stringify(rn) ",%0" : \
+ : "r" ((unsigned long)(v)) \
+ : "memory")
+
+#define mb() asm volatile("sync" : : : "memory");
+
+#define SPRN_MMCR2 769
+#define SPRN_MMCRA 770
+#define SPRN_MMCR0 779
+#define MMCR0_PMAO 0x00000080
+#define MMCR0_PMAE 0x04000000
+#define MMCR0_FC 0x80000000
+#define SPRN_EBBHR 804
+#define SPRN_EBBRR 805
+#define SPRN_BESCR 806 /* Branch event status & control register */
+#define SPRN_BESCRS 800 /* Branch event status & control set (1 bits set to 1) */
+#define SPRN_BESCRSU 801 /* Branch event status & control set upper */
+#define SPRN_BESCRR 802 /* Branch event status & control REset (1 bits set to 0) */
+#define SPRN_BESCRRU 803 /* Branch event status & control REset upper */
+
+#define BESCR_PMEO 0x1 /* PMU Event-based exception Occurred */
+#define BESCR_PME (0x1ul << 32) /* PMU Event-based exception Enable */
+
+#define SPRN_PMC1 771
+#define SPRN_PMC2 772
+#define SPRN_PMC3 773
+#define SPRN_PMC4 774
+#define SPRN_PMC5 775
+#define SPRN_PMC6 776
+
+#define SPRN_SIAR 780
+#define SPRN_SDAR 781
+#define SPRN_SIER 768
+
+#endif /* _SELFTESTS_POWERPC_REG_H */
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/reg_access_test.c b/tools/testing/selftests/powerpc/pmu/ebb/reg_access_test.c
new file mode 100644
index 00000000000..0cae66f659a
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/reg_access_test.c
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "ebb.h"
+#include "reg.h"
+
+
+/*
+ * Test basic access to the EBB regs, they should be user accessible with no
+ * kernel interaction required.
+ */
+int reg_access(void)
+{
+ uint64_t val, expected;
+
+ expected = 0x8000000100000000ull;
+ mtspr(SPRN_BESCR, expected);
+ val = mfspr(SPRN_BESCR);
+
+ FAIL_IF(val != expected);
+
+ expected = 0x0000000001000000ull;
+ mtspr(SPRN_EBBHR, expected);
+ val = mfspr(SPRN_EBBHR);
+
+ FAIL_IF(val != expected);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(reg_access, "reg_access");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/task_event_pinned_vs_ebb_test.c b/tools/testing/selftests/powerpc/pmu/ebb/task_event_pinned_vs_ebb_test.c
new file mode 100644
index 00000000000..d56607e4ffa
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/task_event_pinned_vs_ebb_test.c
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "ebb.h"
+
+
+/*
+ * Tests a pinned per-task event vs an EBB - in that order. The pinned per-task
+ * event should prevent the EBB event from being enabled.
+ */
+
+static int setup_child_event(struct event *event, pid_t child_pid)
+{
+ event_init_named(event, 0x400FA, "PM_RUN_INST_CMPL");
+
+ event->attr.pinned = 1;
+
+ event->attr.exclude_kernel = 1;
+ event->attr.exclude_hv = 1;
+ event->attr.exclude_idle = 1;
+
+ FAIL_IF(event_open_with_pid(event, child_pid));
+ FAIL_IF(event_enable(event));
+
+ return 0;
+}
+
+int task_event_pinned_vs_ebb(void)
+{
+ union pipe read_pipe, write_pipe;
+ struct event event;
+ pid_t pid;
+ int rc;
+
+ FAIL_IF(pipe(read_pipe.fds) == -1);
+ FAIL_IF(pipe(write_pipe.fds) == -1);
+
+ pid = fork();
+ if (pid == 0) {
+ /* NB order of pipes looks reversed */
+ exit(ebb_child(write_pipe, read_pipe));
+ }
+
+ /* We setup the task event first */
+ rc = setup_child_event(&event, pid);
+ if (rc) {
+ kill_child_and_wait(pid);
+ return rc;
+ }
+
+ /* Signal the child to install its EBB event and wait */
+ if (sync_with_child(read_pipe, write_pipe))
+ /* If it fails, wait for it to exit */
+ goto wait;
+
+ /* Signal the child to run */
+ FAIL_IF(sync_with_child(read_pipe, write_pipe));
+
+wait:
+ /* We expect it to fail to read the event */
+ FAIL_IF(wait_for_child(pid) != 2);
+ FAIL_IF(event_disable(&event));
+ FAIL_IF(event_read(&event));
+
+ event_report(&event);
+
+ FAIL_IF(event.result.value == 0);
+ /*
+ * For reasons I don't understand enabled is usually just slightly
+ * lower than running. Would be good to confirm why.
+ */
+ FAIL_IF(event.result.enabled == 0);
+ FAIL_IF(event.result.running == 0);
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(task_event_pinned_vs_ebb, "task_event_pinned_vs_ebb");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/task_event_vs_ebb_test.c b/tools/testing/selftests/powerpc/pmu/ebb/task_event_vs_ebb_test.c
new file mode 100644
index 00000000000..eba32196dbb
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/task_event_vs_ebb_test.c
@@ -0,0 +1,83 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "ebb.h"
+
+
+/*
+ * Tests a per-task event vs an EBB - in that order. The EBB should push the
+ * per-task event off the PMU.
+ */
+
+static int setup_child_event(struct event *event, pid_t child_pid)
+{
+ event_init_named(event, 0x400FA, "PM_RUN_INST_CMPL");
+
+ event->attr.exclude_kernel = 1;
+ event->attr.exclude_hv = 1;
+ event->attr.exclude_idle = 1;
+
+ FAIL_IF(event_open_with_pid(event, child_pid));
+ FAIL_IF(event_enable(event));
+
+ return 0;
+}
+
+int task_event_vs_ebb(void)
+{
+ union pipe read_pipe, write_pipe;
+ struct event event;
+ pid_t pid;
+ int rc;
+
+ FAIL_IF(pipe(read_pipe.fds) == -1);
+ FAIL_IF(pipe(write_pipe.fds) == -1);
+
+ pid = fork();
+ if (pid == 0) {
+ /* NB order of pipes looks reversed */
+ exit(ebb_child(write_pipe, read_pipe));
+ }
+
+ /* We setup the task event first */
+ rc = setup_child_event(&event, pid);
+ if (rc) {
+ kill_child_and_wait(pid);
+ return rc;
+ }
+
+ /* Signal the child to install its EBB event and wait */
+ if (sync_with_child(read_pipe, write_pipe))
+ /* If it fails, wait for it to exit */
+ goto wait;
+
+ /* Signal the child to run */
+ FAIL_IF(sync_with_child(read_pipe, write_pipe));
+
+wait:
+ /* The EBB event should push the task event off so the child should succeed */
+ FAIL_IF(wait_for_child(pid));
+ FAIL_IF(event_disable(&event));
+ FAIL_IF(event_read(&event));
+
+ event_report(&event);
+
+ /* The task event may have run, or not so we can't assert anything about it */
+
+ return 0;
+}
+
+int main(void)
+{
+ return test_harness(task_event_vs_ebb, "task_event_vs_ebb");
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/trace.c b/tools/testing/selftests/powerpc/pmu/ebb/trace.c
new file mode 100644
index 00000000000..251e66ab2aa
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/trace.c
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+
+#include "trace.h"
+
+
+struct trace_buffer *trace_buffer_allocate(u64 size)
+{
+ struct trace_buffer *tb;
+
+ if (size < sizeof(*tb)) {
+ fprintf(stderr, "Error: trace buffer too small\n");
+ return NULL;
+ }
+
+ tb = mmap(NULL, size, PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+ if (tb == MAP_FAILED) {
+ perror("mmap");
+ return NULL;
+ }
+
+ tb->size = size;
+ tb->tail = tb->data;
+ tb->overflow = false;
+
+ return tb;
+}
+
+static bool trace_check_bounds(struct trace_buffer *tb, void *p)
+{
+ return p < ((void *)tb + tb->size);
+}
+
+static bool trace_check_alloc(struct trace_buffer *tb, void *p)
+{
+ /*
+ * If we ever overflowed don't allow any more input. This prevents us
+ * from dropping a large item and then later logging a small one. The
+ * buffer should just stop when overflow happened, not be patchy. If
+ * you're overflowing, make your buffer bigger.
+ */
+ if (tb->overflow)
+ return false;
+
+ if (!trace_check_bounds(tb, p)) {
+ tb->overflow = true;
+ return false;
+ }
+
+ return true;
+}
+
+static void *trace_alloc(struct trace_buffer *tb, int bytes)
+{
+ void *p, *newtail;
+
+ p = tb->tail;
+ newtail = tb->tail + bytes;
+ if (!trace_check_alloc(tb, newtail))
+ return NULL;
+
+ tb->tail = newtail;
+
+ return p;
+}
+
+static struct trace_entry *trace_alloc_entry(struct trace_buffer *tb, int payload_size)
+{
+ struct trace_entry *e;
+
+ e = trace_alloc(tb, sizeof(*e) + payload_size);
+ if (e)
+ e->length = payload_size;
+
+ return e;
+}
+
+int trace_log_reg(struct trace_buffer *tb, u64 reg, u64 value)
+{
+ struct trace_entry *e;
+ u64 *p;
+
+ e = trace_alloc_entry(tb, sizeof(reg) + sizeof(value));
+ if (!e)
+ return -ENOSPC;
+
+ e->type = TRACE_TYPE_REG;
+ p = (u64 *)e->data;
+ *p++ = reg;
+ *p++ = value;
+
+ return 0;
+}
+
+int trace_log_counter(struct trace_buffer *tb, u64 value)
+{
+ struct trace_entry *e;
+ u64 *p;
+
+ e = trace_alloc_entry(tb, sizeof(value));
+ if (!e)
+ return -ENOSPC;
+
+ e->type = TRACE_TYPE_COUNTER;
+ p = (u64 *)e->data;
+ *p++ = value;
+
+ return 0;
+}
+
+int trace_log_string(struct trace_buffer *tb, char *str)
+{
+ struct trace_entry *e;
+ char *p;
+ int len;
+
+ len = strlen(str);
+
+ /* We NULL terminate to make printing easier */
+ e = trace_alloc_entry(tb, len + 1);
+ if (!e)
+ return -ENOSPC;
+
+ e->type = TRACE_TYPE_STRING;
+ p = (char *)e->data;
+ memcpy(p, str, len);
+ p += len;
+ *p = '\0';
+
+ return 0;
+}
+
+int trace_log_indent(struct trace_buffer *tb)
+{
+ struct trace_entry *e;
+
+ e = trace_alloc_entry(tb, 0);
+ if (!e)
+ return -ENOSPC;
+
+ e->type = TRACE_TYPE_INDENT;
+
+ return 0;
+}
+
+int trace_log_outdent(struct trace_buffer *tb)
+{
+ struct trace_entry *e;
+
+ e = trace_alloc_entry(tb, 0);
+ if (!e)
+ return -ENOSPC;
+
+ e->type = TRACE_TYPE_OUTDENT;
+
+ return 0;
+}
+
+static void trace_print_header(int seq, int prefix)
+{
+ printf("%*s[%d]: ", prefix, "", seq);
+}
+
+static char *trace_decode_reg(int reg)
+{
+ switch (reg) {
+ case 769: return "SPRN_MMCR2"; break;
+ case 770: return "SPRN_MMCRA"; break;
+ case 779: return "SPRN_MMCR0"; break;
+ case 804: return "SPRN_EBBHR"; break;
+ case 805: return "SPRN_EBBRR"; break;
+ case 806: return "SPRN_BESCR"; break;
+ case 800: return "SPRN_BESCRS"; break;
+ case 801: return "SPRN_BESCRSU"; break;
+ case 802: return "SPRN_BESCRR"; break;
+ case 803: return "SPRN_BESCRRU"; break;
+ case 771: return "SPRN_PMC1"; break;
+ case 772: return "SPRN_PMC2"; break;
+ case 773: return "SPRN_PMC3"; break;
+ case 774: return "SPRN_PMC4"; break;
+ case 775: return "SPRN_PMC5"; break;
+ case 776: return "SPRN_PMC6"; break;
+ case 780: return "SPRN_SIAR"; break;
+ case 781: return "SPRN_SDAR"; break;
+ case 768: return "SPRN_SIER"; break;
+ }
+
+ return NULL;
+}
+
+static void trace_print_reg(struct trace_entry *e)
+{
+ u64 *p, *reg, *value;
+ char *name;
+
+ p = (u64 *)e->data;
+ reg = p++;
+ value = p;
+
+ name = trace_decode_reg(*reg);
+ if (name)
+ printf("register %-10s = 0x%016llx\n", name, *value);
+ else
+ printf("register %lld = 0x%016llx\n", *reg, *value);
+}
+
+static void trace_print_counter(struct trace_entry *e)
+{
+ u64 *value;
+
+ value = (u64 *)e->data;
+ printf("counter = %lld\n", *value);
+}
+
+static void trace_print_string(struct trace_entry *e)
+{
+ char *str;
+
+ str = (char *)e->data;
+ puts(str);
+}
+
+#define BASE_PREFIX 2
+#define PREFIX_DELTA 8
+
+static void trace_print_entry(struct trace_entry *e, int seq, int *prefix)
+{
+ switch (e->type) {
+ case TRACE_TYPE_REG:
+ trace_print_header(seq, *prefix);
+ trace_print_reg(e);
+ break;
+ case TRACE_TYPE_COUNTER:
+ trace_print_header(seq, *prefix);
+ trace_print_counter(e);
+ break;
+ case TRACE_TYPE_STRING:
+ trace_print_header(seq, *prefix);
+ trace_print_string(e);
+ break;
+ case TRACE_TYPE_INDENT:
+ trace_print_header(seq, *prefix);
+ puts("{");
+ *prefix += PREFIX_DELTA;
+ break;
+ case TRACE_TYPE_OUTDENT:
+ *prefix -= PREFIX_DELTA;
+ if (*prefix < BASE_PREFIX)
+ *prefix = BASE_PREFIX;
+ trace_print_header(seq, *prefix);
+ puts("}");
+ break;
+ default:
+ trace_print_header(seq, *prefix);
+ printf("entry @ %p type %d\n", e, e->type);
+ break;
+ }
+}
+
+void trace_buffer_print(struct trace_buffer *tb)
+{
+ struct trace_entry *e;
+ int i, prefix;
+ void *p;
+
+ printf("Trace buffer dump:\n");
+ printf(" address %p \n", tb);
+ printf(" tail %p\n", tb->tail);
+ printf(" size %llu\n", tb->size);
+ printf(" overflow %s\n", tb->overflow ? "TRUE" : "false");
+ printf(" Content:\n");
+
+ p = tb->data;
+
+ i = 0;
+ prefix = BASE_PREFIX;
+
+ while (trace_check_bounds(tb, p) && p < tb->tail) {
+ e = p;
+
+ trace_print_entry(e, i, &prefix);
+
+ i++;
+ p = (void *)e + sizeof(*e) + e->length;
+ }
+}
+
+void trace_print_location(struct trace_buffer *tb)
+{
+ printf("Trace buffer 0x%llx bytes @ %p\n", tb->size, tb);
+}
diff --git a/tools/testing/selftests/powerpc/pmu/ebb/trace.h b/tools/testing/selftests/powerpc/pmu/ebb/trace.h
new file mode 100644
index 00000000000..926458e28c8
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/ebb/trace.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#ifndef _SELFTESTS_POWERPC_PMU_EBB_TRACE_H
+#define _SELFTESTS_POWERPC_PMU_EBB_TRACE_H
+
+#include "utils.h"
+
+#define TRACE_TYPE_REG 1
+#define TRACE_TYPE_COUNTER 2
+#define TRACE_TYPE_STRING 3
+#define TRACE_TYPE_INDENT 4
+#define TRACE_TYPE_OUTDENT 5
+
+struct trace_entry
+{
+ u8 type;
+ u8 length;
+ u8 data[0];
+};
+
+struct trace_buffer
+{
+ u64 size;
+ bool overflow;
+ void *tail;
+ u8 data[0];
+};
+
+struct trace_buffer *trace_buffer_allocate(u64 size);
+int trace_log_reg(struct trace_buffer *tb, u64 reg, u64 value);
+int trace_log_counter(struct trace_buffer *tb, u64 value);
+int trace_log_string(struct trace_buffer *tb, char *str);
+int trace_log_indent(struct trace_buffer *tb);
+int trace_log_outdent(struct trace_buffer *tb);
+void trace_buffer_print(struct trace_buffer *tb);
+void trace_print_location(struct trace_buffer *tb);
+
+#endif /* _SELFTESTS_POWERPC_PMU_EBB_TRACE_H */
diff --git a/tools/testing/selftests/powerpc/pmu/event.c b/tools/testing/selftests/powerpc/pmu/event.c
new file mode 100644
index 00000000000..184b36807d4
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/event.c
@@ -0,0 +1,131 @@
+/*
+ * Copyright 2013, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <sys/syscall.h>
+#include <string.h>
+#include <stdio.h>
+#include <sys/ioctl.h>
+
+#include "event.h"
+
+
+int perf_event_open(struct perf_event_attr *attr, pid_t pid, int cpu,
+ int group_fd, unsigned long flags)
+{
+ return syscall(__NR_perf_event_open, attr, pid, cpu,
+ group_fd, flags);
+}
+
+void event_init_opts(struct event *e, u64 config, int type, char *name)
+{
+ memset(e, 0, sizeof(*e));
+
+ e->name = name;
+
+ e->attr.type = type;
+ e->attr.config = config;
+ e->attr.size = sizeof(e->attr);
+ /* This has to match the structure layout in the header */
+ e->attr.read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | \
+ PERF_FORMAT_TOTAL_TIME_RUNNING;
+}
+
+void event_init_named(struct event *e, u64 config, char *name)
+{
+ event_init_opts(e, config, PERF_TYPE_RAW, name);
+}
+
+void event_init(struct event *e, u64 config)
+{
+ event_init_opts(e, config, PERF_TYPE_RAW, "event");
+}
+
+#define PERF_CURRENT_PID 0
+#define PERF_NO_PID -1
+#define PERF_NO_CPU -1
+#define PERF_NO_GROUP -1
+
+int event_open_with_options(struct event *e, pid_t pid, int cpu, int group_fd)
+{
+ e->fd = perf_event_open(&e->attr, pid, cpu, group_fd, 0);
+ if (e->fd == -1) {
+ perror("perf_event_open");
+ return -1;
+ }
+
+ return 0;
+}
+
+int event_open_with_group(struct event *e, int group_fd)
+{
+ return event_open_with_options(e, PERF_CURRENT_PID, PERF_NO_CPU, group_fd);
+}
+
+int event_open_with_pid(struct event *e, pid_t pid)
+{
+ return event_open_with_options(e, pid, PERF_NO_CPU, PERF_NO_GROUP);
+}
+
+int event_open_with_cpu(struct event *e, int cpu)
+{
+ return event_open_with_options(e, PERF_NO_PID, cpu, PERF_NO_GROUP);
+}
+
+int event_open(struct event *e)
+{
+ return event_open_with_options(e, PERF_CURRENT_PID, PERF_NO_CPU, PERF_NO_GROUP);
+}
+
+void event_close(struct event *e)
+{
+ close(e->fd);
+}
+
+int event_enable(struct event *e)
+{
+ return ioctl(e->fd, PERF_EVENT_IOC_ENABLE);
+}
+
+int event_disable(struct event *e)
+{
+ return ioctl(e->fd, PERF_EVENT_IOC_DISABLE);
+}
+
+int event_reset(struct event *e)
+{
+ return ioctl(e->fd, PERF_EVENT_IOC_RESET);
+}
+
+int event_read(struct event *e)
+{
+ int rc;
+
+ rc = read(e->fd, &e->result, sizeof(e->result));
+ if (rc != sizeof(e->result)) {
+ fprintf(stderr, "read error on event %p!\n", e);
+ return -1;
+ }
+
+ return 0;
+}
+
+void event_report_justified(struct event *e, int name_width, int result_width)
+{
+ printf("%*s: result %*llu ", name_width, e->name, result_width,
+ e->result.value);
+
+ if (e->result.running == e->result.enabled)
+ printf("running/enabled %llu\n", e->result.running);
+ else
+ printf("running %llu enabled %llu\n", e->result.running,
+ e->result.enabled);
+}
+
+void event_report(struct event *e)
+{
+ event_report_justified(e, 0, 0);
+}
diff --git a/tools/testing/selftests/powerpc/pmu/event.h b/tools/testing/selftests/powerpc/pmu/event.h
new file mode 100644
index 00000000000..a0ea6b1eef7
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/event.h
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#ifndef _SELFTESTS_POWERPC_PMU_EVENT_H
+#define _SELFTESTS_POWERPC_PMU_EVENT_H
+
+#include <unistd.h>
+#include <linux/perf_event.h>
+
+#include "utils.h"
+
+
+struct event {
+ struct perf_event_attr attr;
+ char *name;
+ int fd;
+ /* This must match the read_format we use */
+ struct {
+ u64 value;
+ u64 running;
+ u64 enabled;
+ } result;
+};
+
+void event_init(struct event *e, u64 config);
+void event_init_named(struct event *e, u64 config, char *name);
+void event_init_opts(struct event *e, u64 config, int type, char *name);
+int event_open_with_options(struct event *e, pid_t pid, int cpu, int group_fd);
+int event_open_with_group(struct event *e, int group_fd);
+int event_open_with_pid(struct event *e, pid_t pid);
+int event_open_with_cpu(struct event *e, int cpu);
+int event_open(struct event *e);
+void event_close(struct event *e);
+int event_enable(struct event *e);
+int event_disable(struct event *e);
+int event_reset(struct event *e);
+int event_read(struct event *e);
+void event_report_justified(struct event *e, int name_width, int result_width);
+void event_report(struct event *e);
+
+#endif /* _SELFTESTS_POWERPC_PMU_EVENT_H */
diff --git a/tools/testing/selftests/powerpc/pmu/lib.c b/tools/testing/selftests/powerpc/pmu/lib.c
new file mode 100644
index 00000000000..0f6a4731d54
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/lib.c
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#define _GNU_SOURCE /* For CPU_ZERO etc. */
+
+#include <errno.h>
+#include <sched.h>
+#include <setjmp.h>
+#include <stdlib.h>
+#include <sys/wait.h>
+
+#include "utils.h"
+#include "lib.h"
+
+
+int pick_online_cpu(void)
+{
+ cpu_set_t mask;
+ int cpu;
+
+ CPU_ZERO(&mask);
+
+ if (sched_getaffinity(0, sizeof(mask), &mask)) {
+ perror("sched_getaffinity");
+ return -1;
+ }
+
+ /* We prefer a primary thread, but skip 0 */
+ for (cpu = 8; cpu < CPU_SETSIZE; cpu += 8)
+ if (CPU_ISSET(cpu, &mask))
+ return cpu;
+
+ /* Search for anything, but in reverse */
+ for (cpu = CPU_SETSIZE - 1; cpu >= 0; cpu--)
+ if (CPU_ISSET(cpu, &mask))
+ return cpu;
+
+ printf("No cpus in affinity mask?!\n");
+ return -1;
+}
+
+int bind_to_cpu(int cpu)
+{
+ cpu_set_t mask;
+
+ printf("Binding to cpu %d\n", cpu);
+
+ CPU_ZERO(&mask);
+ CPU_SET(cpu, &mask);
+
+ return sched_setaffinity(0, sizeof(mask), &mask);
+}
+
+#define PARENT_TOKEN 0xAA
+#define CHILD_TOKEN 0x55
+
+int sync_with_child(union pipe read_pipe, union pipe write_pipe)
+{
+ char c = PARENT_TOKEN;
+
+ FAIL_IF(write(write_pipe.write_fd, &c, 1) != 1);
+ FAIL_IF(read(read_pipe.read_fd, &c, 1) != 1);
+ if (c != CHILD_TOKEN) /* sometimes expected */
+ return 1;
+
+ return 0;
+}
+
+int wait_for_parent(union pipe read_pipe)
+{
+ char c;
+
+ FAIL_IF(read(read_pipe.read_fd, &c, 1) != 1);
+ FAIL_IF(c != PARENT_TOKEN);
+
+ return 0;
+}
+
+int notify_parent(union pipe write_pipe)
+{
+ char c = CHILD_TOKEN;
+
+ FAIL_IF(write(write_pipe.write_fd, &c, 1) != 1);
+
+ return 0;
+}
+
+int notify_parent_of_error(union pipe write_pipe)
+{
+ char c = ~CHILD_TOKEN;
+
+ FAIL_IF(write(write_pipe.write_fd, &c, 1) != 1);
+
+ return 0;
+}
+
+int wait_for_child(pid_t child_pid)
+{
+ int rc;
+
+ if (waitpid(child_pid, &rc, 0) == -1) {
+ perror("waitpid");
+ return 1;
+ }
+
+ if (WIFEXITED(rc))
+ rc = WEXITSTATUS(rc);
+ else
+ rc = 1; /* Signal or other */
+
+ return rc;
+}
+
+int kill_child_and_wait(pid_t child_pid)
+{
+ kill(child_pid, SIGTERM);
+
+ return wait_for_child(child_pid);
+}
+
+static int eat_cpu_child(union pipe read_pipe, union pipe write_pipe)
+{
+ volatile int i = 0;
+
+ /*
+ * We are just here to eat cpu and die. So make sure we can be killed,
+ * and also don't do any custom SIGTERM handling.
+ */
+ signal(SIGTERM, SIG_DFL);
+
+ notify_parent(write_pipe);
+ wait_for_parent(read_pipe);
+
+ /* Soak up cpu forever */
+ while (1) i++;
+
+ return 0;
+}
+
+pid_t eat_cpu(int (test_function)(void))
+{
+ union pipe read_pipe, write_pipe;
+ int cpu, rc;
+ pid_t pid;
+
+ cpu = pick_online_cpu();
+ FAIL_IF(cpu < 0);
+ FAIL_IF(bind_to_cpu(cpu));
+
+ if (pipe(read_pipe.fds) == -1)
+ return -1;
+
+ if (pipe(write_pipe.fds) == -1)
+ return -1;
+
+ pid = fork();
+ if (pid == 0)
+ exit(eat_cpu_child(write_pipe, read_pipe));
+
+ if (sync_with_child(read_pipe, write_pipe)) {
+ rc = -1;
+ goto out;
+ }
+
+ printf("main test running as pid %d\n", getpid());
+
+ rc = test_function();
+out:
+ kill(pid, SIGKILL);
+
+ return rc;
+}
+
+struct addr_range libc, vdso;
+
+int parse_proc_maps(void)
+{
+ char execute, name[128];
+ uint64_t start, end;
+ FILE *f;
+ int rc;
+
+ f = fopen("/proc/self/maps", "r");
+ if (!f) {
+ perror("fopen");
+ return -1;
+ }
+
+ do {
+ /* This skips line with no executable which is what we want */
+ rc = fscanf(f, "%lx-%lx %*c%*c%c%*c %*x %*d:%*d %*d %127s\n",
+ &start, &end, &execute, name);
+ if (rc <= 0)
+ break;
+
+ if (execute != 'x')
+ continue;
+
+ if (strstr(name, "libc")) {
+ libc.first = start;
+ libc.last = end - 1;
+ } else if (strstr(name, "[vdso]")) {
+ vdso.first = start;
+ vdso.last = end - 1;
+ }
+ } while(1);
+
+ fclose(f);
+
+ return 0;
+}
+
+#define PARANOID_PATH "/proc/sys/kernel/perf_event_paranoid"
+
+bool require_paranoia_below(int level)
+{
+ unsigned long current;
+ char *end, buf[16];
+ FILE *f;
+ int rc;
+
+ rc = -1;
+
+ f = fopen(PARANOID_PATH, "r");
+ if (!f) {
+ perror("fopen");
+ goto out;
+ }
+
+ if (!fgets(buf, sizeof(buf), f)) {
+ printf("Couldn't read " PARANOID_PATH "?\n");
+ goto out_close;
+ }
+
+ current = strtoul(buf, &end, 10);
+
+ if (end == buf) {
+ printf("Couldn't parse " PARANOID_PATH "?\n");
+ goto out_close;
+ }
+
+ if (current >= level)
+ goto out;
+
+ rc = 0;
+out_close:
+ fclose(f);
+out:
+ return rc;
+}
diff --git a/tools/testing/selftests/powerpc/pmu/lib.h b/tools/testing/selftests/powerpc/pmu/lib.h
new file mode 100644
index 00000000000..ca5d72ae3be
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/lib.h
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2014, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#ifndef __SELFTESTS_POWERPC_PMU_LIB_H
+#define __SELFTESTS_POWERPC_PMU_LIB_H
+
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <unistd.h>
+
+union pipe {
+ struct {
+ int read_fd;
+ int write_fd;
+ };
+ int fds[2];
+};
+
+extern int pick_online_cpu(void);
+extern int bind_to_cpu(int cpu);
+extern int kill_child_and_wait(pid_t child_pid);
+extern int wait_for_child(pid_t child_pid);
+extern int sync_with_child(union pipe read_pipe, union pipe write_pipe);
+extern int wait_for_parent(union pipe read_pipe);
+extern int notify_parent(union pipe write_pipe);
+extern int notify_parent_of_error(union pipe write_pipe);
+extern pid_t eat_cpu(int (test_function)(void));
+extern bool require_paranoia_below(int level);
+
+struct addr_range {
+ uint64_t first, last;
+};
+
+extern struct addr_range libc, vdso;
+
+int parse_proc_maps(void);
+
+#endif /* __SELFTESTS_POWERPC_PMU_LIB_H */
diff --git a/tools/testing/selftests/powerpc/pmu/loop.S b/tools/testing/selftests/powerpc/pmu/loop.S
new file mode 100644
index 00000000000..20c1f0876c4
--- /dev/null
+++ b/tools/testing/selftests/powerpc/pmu/loop.S
@@ -0,0 +1,43 @@
+/*
+ * Copyright 2013, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#include <ppc-asm.h>
+
+ .text
+
+FUNC_START(thirty_two_instruction_loop)
+ cmpdi r3,0
+ beqlr
+ addi r4,r3,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1
+ addi r4,r4,1 # 28 addi's
+ subi r3,r3,1
+ b FUNC_NAME(thirty_two_instruction_loop)
+FUNC_END(thirty_two_instruction_loop)
diff --git a/tools/testing/selftests/powerpc/subunit.h b/tools/testing/selftests/powerpc/subunit.h
new file mode 100644
index 00000000000..9c6c4e901ab
--- /dev/null
+++ b/tools/testing/selftests/powerpc/subunit.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2013, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#ifndef _SELFTESTS_POWERPC_SUBUNIT_H
+#define _SELFTESTS_POWERPC_SUBUNIT_H
+
+static inline void test_start(char *name)
+{
+ printf("test: %s\n", name);
+}
+
+static inline void test_failure_detail(char *name, char *detail)
+{
+ printf("failure: %s [%s]\n", name, detail);
+}
+
+static inline void test_failure(char *name)
+{
+ printf("failure: %s\n", name);
+}
+
+static inline void test_error(char *name)
+{
+ printf("error: %s\n", name);
+}
+
+static inline void test_skip(char *name)
+{
+ printf("skip: %s\n", name);
+}
+
+static inline void test_success(char *name)
+{
+ printf("success: %s\n", name);
+}
+
+static inline void test_finish(char *name, int status)
+{
+ if (status)
+ test_failure(name);
+ else
+ test_success(name);
+}
+
+static inline void test_set_git_version(char *value)
+{
+ printf("tags: git_version:%s\n", value);
+}
+
+#endif /* _SELFTESTS_POWERPC_SUBUNIT_H */
diff --git a/tools/testing/selftests/powerpc/tm/Makefile b/tools/testing/selftests/powerpc/tm/Makefile
new file mode 100644
index 00000000000..2cede239a07
--- /dev/null
+++ b/tools/testing/selftests/powerpc/tm/Makefile
@@ -0,0 +1,15 @@
+PROGS := tm-resched-dscr
+
+all: $(PROGS)
+
+$(PROGS): ../harness.c
+
+run_tests: all
+ @-for PROG in $(PROGS); do \
+ ./$$PROG; \
+ done;
+
+clean:
+ rm -f $(PROGS) *.o
+
+.PHONY: all run_tests clean
diff --git a/tools/testing/selftests/powerpc/tm/tm-resched-dscr.c b/tools/testing/selftests/powerpc/tm/tm-resched-dscr.c
new file mode 100644
index 00000000000..42d4c8caad8
--- /dev/null
+++ b/tools/testing/selftests/powerpc/tm/tm-resched-dscr.c
@@ -0,0 +1,98 @@
+/* Test context switching to see if the DSCR SPR is correctly preserved
+ * when within a transaction.
+ *
+ * Note: We assume that the DSCR has been left at the default value (0)
+ * for all CPUs.
+ *
+ * Method:
+ *
+ * Set a value into the DSCR.
+ *
+ * Start a transaction, and suspend it (*).
+ *
+ * Hard loop checking to see if the transaction has become doomed.
+ *
+ * Now that we *may* have been preempted, record the DSCR and TEXASR SPRS.
+ *
+ * If the abort was because of a context switch, check the DSCR value.
+ * Otherwise, try again.
+ *
+ * (*) If the transaction is not suspended we can't see the problem because
+ * the transaction abort handler will restore the DSCR to it's checkpointed
+ * value before we regain control.
+ */
+
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <asm/tm.h>
+
+#include "utils.h"
+
+#define TBEGIN ".long 0x7C00051D ;"
+#define TEND ".long 0x7C00055D ;"
+#define TCHECK ".long 0x7C00059C ;"
+#define TSUSPEND ".long 0x7C0005DD ;"
+#define TRESUME ".long 0x7C2005DD ;"
+#define SPRN_TEXASR 0x82
+#define SPRN_DSCR 0x03
+
+int test_body(void)
+{
+ uint64_t rv, dscr1 = 1, dscr2, texasr;
+
+ printf("Check DSCR TM context switch: ");
+ fflush(stdout);
+ for (;;) {
+ rv = 1;
+ asm __volatile__ (
+ /* set a known value into the DSCR */
+ "ld 3, %[dscr1];"
+ "mtspr %[sprn_dscr], 3;"
+
+ /* start and suspend a transaction */
+ TBEGIN
+ "beq 1f;"
+ TSUSPEND
+
+ /* hard loop until the transaction becomes doomed */
+ "2: ;"
+ TCHECK
+ "bc 4, 0, 2b;"
+
+ /* record DSCR and TEXASR */
+ "mfspr 3, %[sprn_dscr];"
+ "std 3, %[dscr2];"
+ "mfspr 3, %[sprn_texasr];"
+ "std 3, %[texasr];"
+
+ TRESUME
+ TEND
+ "li %[rv], 0;"
+ "1: ;"
+ : [rv]"=r"(rv), [dscr2]"=m"(dscr2), [texasr]"=m"(texasr)
+ : [dscr1]"m"(dscr1)
+ , [sprn_dscr]"i"(SPRN_DSCR), [sprn_texasr]"i"(SPRN_TEXASR)
+ : "memory", "r3"
+ );
+ assert(rv); /* make sure the transaction aborted */
+ if ((texasr >> 56) != TM_CAUSE_RESCHED) {
+ putchar('.');
+ fflush(stdout);
+ continue;
+ }
+ if (dscr2 != dscr1) {
+ printf(" FAIL\n");
+ return 1;
+ } else {
+ printf(" OK\n");
+ return 0;
+ }
+ }
+}
+
+int main(void)
+{
+ return test_harness(test_body, "tm_resched_dscr");
+}
diff --git a/tools/testing/selftests/powerpc/utils.h b/tools/testing/selftests/powerpc/utils.h
new file mode 100644
index 00000000000..a93777ae068
--- /dev/null
+++ b/tools/testing/selftests/powerpc/utils.h
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2013, Michael Ellerman, IBM Corp.
+ * Licensed under GPLv2.
+ */
+
+#ifndef _SELFTESTS_POWERPC_UTILS_H
+#define _SELFTESTS_POWERPC_UTILS_H
+
+#include <stdint.h>
+#include <stdbool.h>
+
+/* Avoid headaches with PRI?64 - just use %ll? always */
+typedef unsigned long long u64;
+typedef signed long long s64;
+
+/* Just for familiarity */
+typedef uint32_t u32;
+typedef uint8_t u8;
+
+
+int test_harness(int (test_function)(void), char *name);
+
+
+/* Yes, this is evil */
+#define FAIL_IF(x) \
+do { \
+ if ((x)) { \
+ fprintf(stderr, \
+ "[FAIL] Test FAILED on line %d\n", __LINE__); \
+ return 1; \
+ } \
+} while (0)
+
+/* The test harness uses this, yes it's gross */
+#define MAGIC_SKIP_RETURN_VALUE 99
+
+#define SKIP_IF(x) \
+do { \
+ if ((x)) { \
+ fprintf(stderr, \
+ "[SKIP] Test skipped on line %d\n", __LINE__); \
+ return MAGIC_SKIP_RETURN_VALUE; \
+ } \
+} while (0)
+
+#define _str(s) #s
+#define str(s) _str(s)
+
+#endif /* _SELFTESTS_POWERPC_UTILS_H */
diff --git a/tools/testing/selftests/ptrace/Makefile b/tools/testing/selftests/ptrace/Makefile
new file mode 100644
index 00000000000..47ae2d385ce
--- /dev/null
+++ b/tools/testing/selftests/ptrace/Makefile
@@ -0,0 +1,10 @@
+CFLAGS += -iquote../../../../include/uapi -Wall
+peeksiginfo: peeksiginfo.c
+
+all: peeksiginfo
+
+clean:
+ rm -f peeksiginfo
+
+run_tests: all
+ @./peeksiginfo || echo "peeksiginfo selftests: [FAIL]"
diff --git a/tools/testing/selftests/ptrace/peeksiginfo.c b/tools/testing/selftests/ptrace/peeksiginfo.c
new file mode 100644
index 00000000000..d46558b1f58
--- /dev/null
+++ b/tools/testing/selftests/ptrace/peeksiginfo.c
@@ -0,0 +1,214 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <signal.h>
+#include <unistd.h>
+#include <errno.h>
+#include <linux/types.h>
+#include <sys/wait.h>
+#include <sys/syscall.h>
+#include <sys/user.h>
+#include <sys/mman.h>
+
+#include "linux/ptrace.h"
+
+static int sys_rt_sigqueueinfo(pid_t tgid, int sig, siginfo_t *uinfo)
+{
+ return syscall(SYS_rt_sigqueueinfo, tgid, sig, uinfo);
+}
+
+static int sys_rt_tgsigqueueinfo(pid_t tgid, pid_t tid,
+ int sig, siginfo_t *uinfo)
+{
+ return syscall(SYS_rt_tgsigqueueinfo, tgid, tid, sig, uinfo);
+}
+
+static int sys_ptrace(int request, pid_t pid, void *addr, void *data)
+{
+ return syscall(SYS_ptrace, request, pid, addr, data);
+}
+
+#define SIGNR 10
+#define TEST_SICODE_PRIV -1
+#define TEST_SICODE_SHARE -2
+
+#define err(fmt, ...) \
+ fprintf(stderr, \
+ "Error (%s:%d): " fmt, \
+ __FILE__, __LINE__, ##__VA_ARGS__)
+
+static int check_error_paths(pid_t child)
+{
+ struct ptrace_peeksiginfo_args arg;
+ int ret, exit_code = -1;
+ void *addr_rw, *addr_ro;
+
+ /*
+ * Allocate two contiguous pages. The first one is for read-write,
+ * another is for read-only.
+ */
+ addr_rw = mmap(NULL, 2 * PAGE_SIZE, PROT_READ | PROT_WRITE,
+ MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ if (addr_rw == MAP_FAILED) {
+ err("mmap() failed: %m\n");
+ return 1;
+ }
+
+ addr_ro = mmap(addr_rw + PAGE_SIZE, PAGE_SIZE, PROT_READ,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
+ if (addr_ro == MAP_FAILED) {
+ err("mmap() failed: %m\n");
+ goto out;
+ }
+
+ arg.nr = SIGNR;
+ arg.off = 0;
+
+ /* Unsupported flags */
+ arg.flags = ~0;
+ ret = sys_ptrace(PTRACE_PEEKSIGINFO, child, &arg, addr_rw);
+ if (ret != -1 || errno != EINVAL) {
+ err("sys_ptrace() returns %d (expected -1),"
+ " errno %d (expected %d): %m\n",
+ ret, errno, EINVAL);
+ goto out;
+ }
+ arg.flags = 0;
+
+ /* A part of the buffer is read-only */
+ ret = sys_ptrace(PTRACE_PEEKSIGINFO, child, &arg,
+ addr_ro - sizeof(siginfo_t) * 2);
+ if (ret != 2) {
+ err("sys_ptrace() returns %d (expected 2): %m\n", ret);
+ goto out;
+ }
+
+ /* Read-only buffer */
+ ret = sys_ptrace(PTRACE_PEEKSIGINFO, child, &arg, addr_ro);
+ if (ret != -1 && errno != EFAULT) {
+ err("sys_ptrace() returns %d (expected -1),"
+ " errno %d (expected %d): %m\n",
+ ret, errno, EFAULT);
+ goto out;
+ }
+
+ exit_code = 0;
+out:
+ munmap(addr_rw, 2 * PAGE_SIZE);
+ return exit_code;
+}
+
+int check_direct_path(pid_t child, int shared, int nr)
+{
+ struct ptrace_peeksiginfo_args arg = {.flags = 0, .nr = nr, .off = 0};
+ int i, j, ret, exit_code = -1;
+ siginfo_t siginfo[SIGNR];
+ int si_code;
+
+ if (shared == 1) {
+ arg.flags = PTRACE_PEEKSIGINFO_SHARED;
+ si_code = TEST_SICODE_SHARE;
+ } else {
+ arg.flags = 0;
+ si_code = TEST_SICODE_PRIV;
+ }
+
+ for (i = 0; i < SIGNR; ) {
+ arg.off = i;
+ ret = sys_ptrace(PTRACE_PEEKSIGINFO, child, &arg, siginfo);
+ if (ret == -1) {
+ err("ptrace() failed: %m\n");
+ goto out;
+ }
+
+ if (ret == 0)
+ break;
+
+ for (j = 0; j < ret; j++, i++) {
+ if (siginfo[j].si_code == si_code &&
+ siginfo[j].si_int == i)
+ continue;
+
+ err("%d: Wrong siginfo i=%d si_code=%d si_int=%d\n",
+ shared, i, siginfo[j].si_code, siginfo[j].si_int);
+ goto out;
+ }
+ }
+
+ if (i != SIGNR) {
+ err("Only %d signals were read\n", i);
+ goto out;
+ }
+
+ exit_code = 0;
+out:
+ return exit_code;
+}
+
+int main(int argc, char *argv[])
+{
+ siginfo_t siginfo[SIGNR];
+ int i, exit_code = 1;
+ sigset_t blockmask;
+ pid_t child;
+
+ sigemptyset(&blockmask);
+ sigaddset(&blockmask, SIGRTMIN);
+ sigprocmask(SIG_BLOCK, &blockmask, NULL);
+
+ child = fork();
+ if (child == -1) {
+ err("fork() failed: %m");
+ return 1;
+ } else if (child == 0) {
+ pid_t ppid = getppid();
+ while (1) {
+ if (ppid != getppid())
+ break;
+ sleep(1);
+ }
+ return 1;
+ }
+
+ /* Send signals in process-wide and per-thread queues */
+ for (i = 0; i < SIGNR; i++) {
+ siginfo->si_code = TEST_SICODE_SHARE;
+ siginfo->si_int = i;
+ sys_rt_sigqueueinfo(child, SIGRTMIN, siginfo);
+
+ siginfo->si_code = TEST_SICODE_PRIV;
+ siginfo->si_int = i;
+ sys_rt_tgsigqueueinfo(child, child, SIGRTMIN, siginfo);
+ }
+
+ if (sys_ptrace(PTRACE_ATTACH, child, NULL, NULL) == -1)
+ return 1;
+
+ waitpid(child, NULL, 0);
+
+ /* Dump signals one by one*/
+ if (check_direct_path(child, 0, 1))
+ goto out;
+ /* Dump all signals for one call */
+ if (check_direct_path(child, 0, SIGNR))
+ goto out;
+
+ /*
+ * Dump signal from the process-wide queue.
+ * The number of signals is not multible to the buffer size
+ */
+ if (check_direct_path(child, 1, 3))
+ goto out;
+
+ if (check_error_paths(child))
+ goto out;
+
+ printf("PASS\n");
+ exit_code = 0;
+out:
+ if (sys_ptrace(PTRACE_KILL, child, NULL, NULL) == -1)
+ return 1;
+
+ waitpid(child, NULL, 0);
+
+ return exit_code;
+}
diff --git a/tools/testing/selftests/rcutorture/.gitignore b/tools/testing/selftests/rcutorture/.gitignore
new file mode 100644
index 00000000000..05838f6f2eb
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/.gitignore
@@ -0,0 +1,6 @@
+initrd
+linux-2.6
+b[0-9]*
+rcu-test-image
+res
+*.swp
diff --git a/tools/testing/selftests/rcutorture/bin/config2frag.sh b/tools/testing/selftests/rcutorture/bin/config2frag.sh
new file mode 100644
index 00000000000..9f9ffcd427d
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/config2frag.sh
@@ -0,0 +1,25 @@
+#!/bin/sh
+# Usage: sh config2frag.sh < .config > configfrag
+#
+# Converts the "# CONFIG_XXX is not set" to "CONFIG_XXX=n" so that the
+# resulting file becomes a legitimate Kconfig fragment.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2013
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+LANG=C sed -e 's/^# CONFIG_\([a-zA-Z0-9_]*\) is not set$/CONFIG_\1=n/'
diff --git a/tools/testing/selftests/rcutorture/bin/configNR_CPUS.sh b/tools/testing/selftests/rcutorture/bin/configNR_CPUS.sh
new file mode 100755
index 00000000000..43540f1828c
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/configNR_CPUS.sh
@@ -0,0 +1,45 @@
+#!/bin/bash
+#
+# Extract the number of CPUs expected from the specified Kconfig-file
+# fragment by checking CONFIG_SMP and CONFIG_NR_CPUS. If the specified
+# file gives no clue, base the number on the number of idle CPUs on
+# the system.
+#
+# Usage: configNR_CPUS.sh config-frag
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2013
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+cf=$1
+if test ! -r $cf
+then
+ echo Unreadable config fragment $cf 1>&2
+ exit -1
+fi
+if grep -q '^CONFIG_SMP=n$' $cf
+then
+ echo 1
+ exit 0
+fi
+if grep -q '^CONFIG_NR_CPUS=' $cf
+then
+ grep '^CONFIG_NR_CPUS=' $cf |
+ sed -e 's/^CONFIG_NR_CPUS=\([0-9]*\).*$/\1/'
+ exit 0
+fi
+cpus2use.sh
diff --git a/tools/testing/selftests/rcutorture/bin/configcheck.sh b/tools/testing/selftests/rcutorture/bin/configcheck.sh
new file mode 100755
index 00000000000..d686537dd55
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/configcheck.sh
@@ -0,0 +1,54 @@
+#!/bin/sh
+# Usage: sh configcheck.sh .config .config-template
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2011
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+T=/tmp/abat-chk-config.sh.$$
+trap 'rm -rf $T' 0
+mkdir $T
+
+cat $1 > $T/.config
+
+cat $2 | sed -e 's/\(.*\)=n/# \1 is not set/' -e 's/^#CHECK#//' |
+awk '
+BEGIN {
+ print "if grep -q \"" $0 "\" < '"$T/.config"'";
+ print "then";
+ print "\t:";
+ print "else";
+ if ($1 == "#") {
+ print "\tif grep -q \"" $2 "\" < '"$T/.config"'";
+ print "\tthen";
+ print "\t\tif test \"$firsttime\" = \"\""
+ print "\t\tthen"
+ print "\t\t\tfirsttime=1"
+ print "\t\tfi"
+ print "\t\techo \":" $2 ": improperly set\"";
+ print "\telse";
+ print "\t\t:";
+ print "\tfi";
+ } else {
+ print "\tif test \"$firsttime\" = \"\""
+ print "\tthen"
+ print "\t\tfirsttime=1"
+ print "\tfi"
+ print "\techo \":" $0 ": improperly set\"";
+ }
+ print "fi";
+ }' | sh
diff --git a/tools/testing/selftests/rcutorture/bin/configinit.sh b/tools/testing/selftests/rcutorture/bin/configinit.sh
new file mode 100755
index 00000000000..9c3f3d39b93
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/configinit.sh
@@ -0,0 +1,74 @@
+#!/bin/sh
+#
+# sh configinit.sh config-spec-file [ build output dir ]
+#
+# Create a .config file from the spec file. Run from the kernel source tree.
+# Exits with 0 if all went well, with 1 if all went well but the config
+# did not match, and some other number for other failures.
+#
+# The first argument is the .config specification file, which contains
+# desired settings, for example, "CONFIG_NO_HZ=y". For best results,
+# this should be a full pathname.
+#
+# The second argument is a optional path to a build output directory,
+# for example, "O=/tmp/foo". If this argument is omitted, the .config
+# file will be generated directly in the current directory.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2013
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+T=/tmp/configinit.sh.$$
+trap 'rm -rf $T' 0
+mkdir $T
+
+# Capture config spec file.
+
+c=$1
+buildloc=$2
+builddir=
+if test -n $buildloc
+then
+ if echo $buildloc | grep -q '^O='
+ then
+ builddir=`echo $buildloc | sed -e 's/^O=//'`
+ if test ! -d $builddir
+ then
+ mkdir $builddir
+ fi
+ else
+ echo Bad build directory: \"$builddir\"
+ exit 2
+ fi
+fi
+
+sed -e 's/^\(CONFIG[0-9A-Z_]*\)=.*$/grep -v "^# \1" |/' < $c > $T/u.sh
+sed -e 's/^\(CONFIG[0-9A-Z_]*=\).*$/grep -v \1 |/' < $c >> $T/u.sh
+grep '^grep' < $T/u.sh > $T/upd.sh
+echo "cat - $c" >> $T/upd.sh
+make mrproper
+make $buildloc distclean > $builddir/Make.distclean 2>&1
+make $buildloc $TORTURE_DEFCONFIG > $builddir/Make.defconfig.out 2>&1
+mv $builddir/.config $builddir/.config.sav
+sh $T/upd.sh < $builddir/.config.sav > $builddir/.config
+cp $builddir/.config $builddir/.config.new
+yes '' | make $buildloc oldconfig > $builddir/Make.modconfig.out 2>&1
+
+# verify new config matches specification.
+configcheck.sh $builddir/.config $c
+
+exit 0
diff --git a/tools/testing/selftests/rcutorture/bin/cpus2use.sh b/tools/testing/selftests/rcutorture/bin/cpus2use.sh
new file mode 100755
index 00000000000..abe14b7f36e
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/cpus2use.sh
@@ -0,0 +1,41 @@
+#!/bin/bash
+#
+# Get an estimate of how CPU-hoggy to be.
+#
+# Usage: cpus2use.sh
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2013
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+ncpus=`grep '^processor' /proc/cpuinfo | wc -l`
+idlecpus=`mpstat | tail -1 | \
+ awk -v ncpus=$ncpus '{ print ncpus * ($7 + $12) / 100 }'`
+awk -v ncpus=$ncpus -v idlecpus=$idlecpus < /dev/null '
+BEGIN {
+ cpus2use = idlecpus;
+ if (cpus2use < 1)
+ cpus2use = 1;
+ if (cpus2use < ncpus / 10)
+ cpus2use = ncpus / 10;
+ if (cpus2use == int(cpus2use))
+ cpus2use = int(cpus2use)
+ else
+ cpus2use = int(cpus2use) + 1
+ print cpus2use;
+}'
+
diff --git a/tools/testing/selftests/rcutorture/bin/functions.sh b/tools/testing/selftests/rcutorture/bin/functions.sh
new file mode 100644
index 00000000000..d01b865bb10
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/functions.sh
@@ -0,0 +1,223 @@
+#!/bin/bash
+#
+# Shell functions for the rest of the scripts.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2013
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+# bootparam_hotplug_cpu bootparam-string
+#
+# Returns 1 if the specified boot-parameter string tells rcutorture to
+# test CPU-hotplug operations.
+bootparam_hotplug_cpu () {
+ echo "$1" | grep -q "rcutorture\.onoff_"
+}
+
+# checkarg --argname argtype $# arg mustmatch cannotmatch
+#
+# Checks the specified argument "arg" against the mustmatch and cannotmatch
+# patterns.
+checkarg () {
+ if test $3 -le 1
+ then
+ echo $1 needs argument $2 matching \"$5\"
+ usage
+ fi
+ if echo "$4" | grep -q -e "$5"
+ then
+ :
+ else
+ echo $1 $2 \"$4\" must match \"$5\"
+ usage
+ fi
+ if echo "$4" | grep -q -e "$6"
+ then
+ echo $1 $2 \"$4\" must not match \"$6\"
+ usage
+ fi
+}
+
+# configfrag_boot_params bootparam-string config-fragment-file
+#
+# Adds boot parameters from the .boot file, if any.
+configfrag_boot_params () {
+ if test -r "$2.boot"
+ then
+ echo $1 `grep -v '^#' "$2.boot" | tr '\012' ' '`
+ else
+ echo $1
+ fi
+}
+
+# configfrag_hotplug_cpu config-fragment-file
+#
+# Returns 1 if the config fragment specifies hotplug CPU.
+configfrag_hotplug_cpu () {
+ if test ! -r "$1"
+ then
+ echo Unreadable config fragment "$1" 1>&2
+ exit -1
+ fi
+ grep -q '^CONFIG_HOTPLUG_CPU=y$' "$1"
+}
+
+# identify_boot_image qemu-cmd
+#
+# Returns the relative path to the kernel build image. This will be
+# arch/<arch>/boot/bzImage unless overridden with the TORTURE_BOOT_IMAGE
+# environment variable.
+identify_boot_image () {
+ if test -n "$TORTURE_BOOT_IMAGE"
+ then
+ echo $TORTURE_BOOT_IMAGE
+ else
+ case "$1" in
+ qemu-system-x86_64|qemu-system-i386)
+ echo arch/x86/boot/bzImage
+ ;;
+ qemu-system-ppc64)
+ echo arch/powerpc/boot/bzImage
+ ;;
+ *)
+ echo ""
+ ;;
+ esac
+ fi
+}
+
+# identify_qemu builddir
+#
+# Returns our best guess as to which qemu command is appropriate for
+# the kernel at hand. Override with the TORTURE_QEMU_CMD environment variable.
+identify_qemu () {
+ local u="`file "$1"`"
+ if test -n "$TORTURE_QEMU_CMD"
+ then
+ echo $TORTURE_QEMU_CMD
+ elif echo $u | grep -q x86-64
+ then
+ echo qemu-system-x86_64
+ elif echo $u | grep -q "Intel 80386"
+ then
+ echo qemu-system-i386
+ elif uname -a | grep -q ppc64
+ then
+ echo qemu-system-ppc64
+ else
+ echo Cannot figure out what qemu command to use! 1>&2
+ echo file $1 output: $u
+ # Usually this will be one of /usr/bin/qemu-system-*
+ # Use TORTURE_QEMU_CMD environment variable or appropriate
+ # argument to top-level script.
+ exit 1
+ fi
+}
+
+# identify_qemu_append qemu-cmd
+#
+# Output arguments for the qemu "-append" string based on CPU type
+# and the TORTURE_QEMU_INTERACTIVE environment variable.
+identify_qemu_append () {
+ case "$1" in
+ qemu-system-x86_64|qemu-system-i386)
+ echo noapic selinux=0 initcall_debug debug
+ ;;
+ esac
+ if test -n "$TORTURE_QEMU_INTERACTIVE"
+ then
+ echo root=/dev/sda
+ else
+ echo console=ttyS0
+ fi
+}
+
+# identify_qemu_args qemu-cmd serial-file
+#
+# Output arguments for qemu arguments based on the TORTURE_QEMU_MAC
+# and TORTURE_QEMU_INTERACTIVE environment variables.
+identify_qemu_args () {
+ case "$1" in
+ qemu-system-x86_64|qemu-system-i386)
+ ;;
+ qemu-system-ppc64)
+ echo -enable-kvm -M pseries -cpu POWER7 -nodefaults
+ echo -device spapr-vscsi
+ if test -n "$TORTURE_QEMU_INTERACTIVE" -a -n "$TORTURE_QEMU_MAC"
+ then
+ echo -device spapr-vlan,netdev=net0,mac=$TORTURE_QEMU_MAC
+ echo -netdev bridge,br=br0,id=net0
+ elif test -n "$TORTURE_QEMU_INTERACTIVE"
+ then
+ echo -net nic -net user
+ fi
+ ;;
+ esac
+ if test -n "$TORTURE_QEMU_INTERACTIVE"
+ then
+ echo -monitor stdio -serial pty -S
+ else
+ echo -serial file:$2
+ fi
+}
+
+# identify_qemu_vcpus
+#
+# Returns the number of virtual CPUs available to the aggregate of the
+# guest OSes.
+identify_qemu_vcpus () {
+ lscpu | grep '^CPU(s):' | sed -e 's/CPU(s)://'
+}
+
+# print_bug
+#
+# Prints "BUG: " in red followed by remaining arguments
+print_bug () {
+ printf '\033[031mBUG: \033[m'
+ echo $*
+}
+
+# print_warning
+#
+# Prints "WARNING: " in yellow followed by remaining arguments
+print_warning () {
+ printf '\033[033mWARNING: \033[m'
+ echo $*
+}
+
+# specify_qemu_cpus qemu-cmd qemu-args #cpus
+#
+# Appends a string containing "-smp XXX" to qemu-args, unless the incoming
+# qemu-args already contains "-smp".
+specify_qemu_cpus () {
+ local nt;
+
+ if echo $2 | grep -q -e -smp
+ then
+ echo $2
+ else
+ case "$1" in
+ qemu-system-x86_64|qemu-system-i386)
+ echo $2 -smp $3
+ ;;
+ qemu-system-ppc64)
+ nt="`lscpu | grep '^NUMA node0' | sed -e 's/^[^,]*,\([0-9]*\),.*$/\1/'`"
+ echo $2 -smp cores=`expr \( $3 + $nt - 1 \) / $nt`,threads=$nt
+ ;;
+ esac
+ fi
+}
diff --git a/tools/testing/selftests/rcutorture/bin/kvm-build.sh b/tools/testing/selftests/rcutorture/bin/kvm-build.sh
new file mode 100755
index 00000000000..7c1e56b46de
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/kvm-build.sh
@@ -0,0 +1,71 @@
+#!/bin/bash
+#
+# Build a kvm-ready Linux kernel from the tree in the current directory.
+#
+# Usage: sh kvm-build.sh config-template build-dir more-configs
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2011
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+config_template=${1}
+if test -z "$config_template" -o ! -f "$config_template" -o ! -r "$config_template"
+then
+ echo "kvm-build.sh :$config_template: Not a readable file"
+ exit 1
+fi
+builddir=${2}
+if test -z "$builddir" -o ! -d "$builddir" -o ! -w "$builddir"
+then
+ echo "kvm-build.sh :$builddir: Not a writable directory, cannot build into it"
+ exit 1
+fi
+moreconfigs=${3}
+if test -z "$moreconfigs" -o ! -r "$moreconfigs"
+then
+ echo "kvm-build.sh :$moreconfigs: Not a readable file"
+ exit 1
+fi
+
+T=/tmp/test-linux.sh.$$
+trap 'rm -rf $T' 0
+mkdir $T
+
+grep -v 'CONFIG_[A-Z]*_TORTURE_TEST' < ${config_template} > $T/config
+cat << ___EOF___ >> $T/config
+CONFIG_INITRAMFS_SOURCE="$TORTURE_INITRD"
+CONFIG_VIRTIO_PCI=y
+CONFIG_VIRTIO_CONSOLE=y
+___EOF___
+cat $moreconfigs >> $T/config
+
+configinit.sh $T/config O=$builddir
+retval=$?
+if test $retval -gt 1
+then
+ exit 2
+fi
+ncpus=`cpus2use.sh`
+make O=$builddir -j$ncpus $TORTURE_KMAKE_ARG > $builddir/Make.out 2>&1
+retval=$?
+if test $retval -ne 0 || grep "rcu[^/]*": < $builddir/Make.out | egrep -q "Stop|Error|error:|warning:" || egrep -q "Stop|Error|error:" < $builddir/Make.out
+then
+ echo Kernel build error
+ egrep "Stop|Error|error:|warning:" < $builddir/Make.out
+ echo Run aborted.
+ exit 3
+fi
diff --git a/tools/testing/selftests/rcutorture/bin/kvm-recheck-lock.sh b/tools/testing/selftests/rcutorture/bin/kvm-recheck-lock.sh
new file mode 100755
index 00000000000..7f1ff1a8fc4
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/kvm-recheck-lock.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+#
+# Analyze a given results directory for locktorture progress.
+#
+# Usage: sh kvm-recheck-lock.sh resdir
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2014
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+i="$1"
+if test -d $i
+then
+ :
+else
+ echo Unreadable results directory: $i
+ exit 1
+fi
+
+configfile=`echo $i | sed -e 's/^.*\///'`
+ncs=`grep "Writes: Total:" $i/console.log 2> /dev/null | tail -1 | sed -e 's/^.* Total: //' -e 's/ .*$//'`
+if test -z "$ncs"
+then
+ echo "$configfile -------"
+else
+ title="$configfile ------- $ncs acquisitions/releases"
+ dur=`sed -e 's/^.* locktorture.shutdown_secs=//' -e 's/ .*$//' < $i/qemu-cmd 2> /dev/null`
+ if test -z "$dur"
+ then
+ :
+ else
+ ncsps=`awk -v ncs=$ncs -v dur=$dur '
+ BEGIN { print ncs / dur }' < /dev/null`
+ title="$title ($ncsps per second)"
+ fi
+ echo $title
+fi
diff --git a/tools/testing/selftests/rcutorture/bin/kvm-recheck-rcu.sh b/tools/testing/selftests/rcutorture/bin/kvm-recheck-rcu.sh
new file mode 100755
index 00000000000..307c4b95f32
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/kvm-recheck-rcu.sh
@@ -0,0 +1,51 @@
+#!/bin/bash
+#
+# Analyze a given results directory for rcutorture progress.
+#
+# Usage: sh kvm-recheck-rcu.sh resdir
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2014
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+i="$1"
+if test -d $i
+then
+ :
+else
+ echo Unreadable results directory: $i
+ exit 1
+fi
+
+configfile=`echo $i | sed -e 's/^.*\///'`
+ngps=`grep ver: $i/console.log 2> /dev/null | tail -1 | sed -e 's/^.* ver: //' -e 's/ .*$//'`
+if test -z "$ngps"
+then
+ echo "$configfile -------"
+else
+ title="$configfile ------- $ngps grace periods"
+ dur=`sed -e 's/^.* rcutorture.shutdown_secs=//' -e 's/ .*$//' < $i/qemu-cmd 2> /dev/null`
+ if test -z "$dur"
+ then
+ :
+ else
+ ngpsps=`awk -v ngps=$ngps -v dur=$dur '
+ BEGIN { print ngps / dur }' < /dev/null`
+ title="$title ($ngpsps per second)"
+ fi
+ echo $title
+fi
diff --git a/tools/testing/selftests/rcutorture/bin/kvm-recheck.sh b/tools/testing/selftests/rcutorture/bin/kvm-recheck.sh
new file mode 100755
index 00000000000..ee1f6cae3d7
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/kvm-recheck.sh
@@ -0,0 +1,63 @@
+#!/bin/bash
+#
+# Given the results directories for previous KVM-based torture runs,
+# check the build and console output for errors. Given a directory
+# containing results directories, this recursively checks them all.
+#
+# Usage: sh kvm-recheck.sh resdir ...
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2011
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+PATH=`pwd`/tools/testing/selftests/rcutorture/bin:$PATH; export PATH
+. tools/testing/selftests/rcutorture/bin/functions.sh
+for rd in "$@"
+do
+ firsttime=1
+ dirs=`find $rd -name Make.defconfig.out -print | sort | sed -e 's,/[^/]*$,,' | sort -u`
+ for i in $dirs
+ do
+ if test -n "$firsttime"
+ then
+ firsttime=""
+ resdir=`echo $i | sed -e 's,/$,,' -e 's,/[^/]*$,,'`
+ head -1 $resdir/log
+ fi
+ TORTURE_SUITE="`cat $i/../TORTURE_SUITE`"
+ kvm-recheck-${TORTURE_SUITE}.sh $i
+ if test -f "$i/console.log"
+ then
+ configcheck.sh $i/.config $i/ConfigFragment
+ parse-build.sh $i/Make.out $configfile
+ parse-torture.sh $i/console.log $configfile
+ parse-console.sh $i/console.log $configfile
+ if test -r $i/Warnings
+ then
+ cat $i/Warnings
+ fi
+ else
+ if test -f "$i/qemu-cmd"
+ then
+ print_bug qemu failed
+ else
+ print_bug Build failed
+ fi
+ echo " $i"
+ fi
+ done
+done
diff --git a/tools/testing/selftests/rcutorture/bin/kvm-test-1-run.sh b/tools/testing/selftests/rcutorture/bin/kvm-test-1-run.sh
new file mode 100755
index 00000000000..27e544e2951
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/kvm-test-1-run.sh
@@ -0,0 +1,222 @@
+#!/bin/bash
+#
+# Run a kvm-based test of the specified tree on the specified configs.
+# Fully automated run and error checking, no graphics console.
+#
+# Execute this in the source tree. Do not run it as a background task
+# because qemu does not seem to like that much.
+#
+# Usage: sh kvm-test-1-run.sh config builddir resdir minutes qemu-args boot_args
+#
+# qemu-args defaults to "-nographic", along with arguments specifying the
+# number of CPUs and other options generated from
+# the underlying CPU architecture.
+# boot_args defaults to value returned by the per_version_boot_params
+# shell function.
+#
+# Anything you specify for either qemu-args or boot_args is appended to
+# the default values. The "-smp" value is deduced from the contents of
+# the config fragment.
+#
+# More sophisticated argument parsing is clearly needed.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2011
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+grace=120
+
+T=/tmp/kvm-test-1-run.sh.$$
+trap 'rm -rf $T' 0
+
+. $KVM/bin/functions.sh
+. $KVPATH/ver_functions.sh
+
+config_template=${1}
+config_dir=`echo $config_template | sed -e 's,/[^/]*$,,'`
+title=`echo $config_template | sed -e 's/^.*\///'`
+builddir=${2}
+if test -z "$builddir" -o ! -d "$builddir" -o ! -w "$builddir"
+then
+ echo "kvm-test-1-run.sh :$builddir: Not a writable directory, cannot build into it"
+ exit 1
+fi
+resdir=${3}
+if test -z "$resdir" -o ! -d "$resdir" -o ! -w "$resdir"
+then
+ echo "kvm-test-1-run.sh :$resdir: Not a writable directory, cannot store results into it"
+ exit 1
+fi
+cp $config_template $resdir/ConfigFragment
+echo ' ---' `date`: Starting build
+echo ' ---' Kconfig fragment at: $config_template >> $resdir/log
+if test -r "$config_dir/CFcommon"
+then
+ cat < $config_dir/CFcommon >> $T
+fi
+# Optimizations below this point
+# CONFIG_USB=n
+# CONFIG_SECURITY=n
+# CONFIG_NFS_FS=n
+# CONFIG_SOUND=n
+# CONFIG_INPUT_JOYSTICK=n
+# CONFIG_INPUT_TABLET=n
+# CONFIG_INPUT_TOUCHSCREEN=n
+# CONFIG_INPUT_MISC=n
+# CONFIG_INPUT_MOUSE=n
+# # CONFIG_NET=n # disables console access, so accept the slower build.
+# CONFIG_SCSI=n
+# CONFIG_ATA=n
+# CONFIG_FAT_FS=n
+# CONFIG_MSDOS_FS=n
+# CONFIG_VFAT_FS=n
+# CONFIG_ISO9660_FS=n
+# CONFIG_QUOTA=n
+# CONFIG_HID=n
+# CONFIG_CRYPTO=n
+# CONFIG_PCCARD=n
+# CONFIG_PCMCIA=n
+# CONFIG_CARDBUS=n
+# CONFIG_YENTA=n
+if kvm-build.sh $config_template $builddir $T
+then
+ QEMU="`identify_qemu $builddir/vmlinux`"
+ BOOT_IMAGE="`identify_boot_image $QEMU`"
+ cp $builddir/Make*.out $resdir
+ cp $builddir/.config $resdir
+ if test -n "$BOOT_IMAGE"
+ then
+ cp $builddir/$BOOT_IMAGE $resdir
+ else
+ echo No identifiable boot image, not running KVM, see $resdir.
+ echo Do the torture scripts know about your architecture?
+ fi
+ parse-build.sh $resdir/Make.out $title
+ if test -f $builddir.wait
+ then
+ mv $builddir.wait $builddir.ready
+ fi
+else
+ cp $builddir/Make*.out $resdir
+ cp $builddir/.config $resdir || :
+ echo Build failed, not running KVM, see $resdir.
+ if test -f $builddir.wait
+ then
+ mv $builddir.wait $builddir.ready
+ fi
+ exit 1
+fi
+while test -f $builddir.ready
+do
+ sleep 1
+done
+minutes=$4
+seconds=$(($minutes * 60))
+qemu_args=$5
+boot_args=$6
+
+cd $KVM
+kstarttime=`awk 'BEGIN { print systime() }' < /dev/null`
+echo ' ---' `date`: Starting kernel
+
+# Generate -smp qemu argument.
+qemu_args="-nographic $qemu_args"
+cpu_count=`configNR_CPUS.sh $config_template`
+vcpus=`identify_qemu_vcpus`
+if test $cpu_count -gt $vcpus
+then
+ echo CPU count limited from $cpu_count to $vcpus
+ touch $resdir/Warnings
+ echo CPU count limited from $cpu_count to $vcpus >> $resdir/Warnings
+ cpu_count=$vcpus
+fi
+qemu_args="`specify_qemu_cpus "$QEMU" "$qemu_args" "$cpu_count"`"
+
+# Generate architecture-specific and interaction-specific qemu arguments
+qemu_args="$qemu_args `identify_qemu_args "$QEMU" "$builddir/console.log"`"
+
+# Generate qemu -append arguments
+qemu_append="`identify_qemu_append "$QEMU"`"
+
+# Pull in Kconfig-fragment boot parameters
+boot_args="`configfrag_boot_params "$boot_args" "$config_template"`"
+# Generate kernel-version-specific boot parameters
+boot_args="`per_version_boot_params "$boot_args" $builddir/.config $seconds`"
+
+echo $QEMU $qemu_args -m 512 -kernel $builddir/$BOOT_IMAGE -append \"$qemu_append $boot_args\" > $resdir/qemu-cmd
+if test -n "$TORTURE_BUILDONLY"
+then
+ echo Build-only run specified, boot/test omitted.
+ exit 0
+fi
+( $QEMU $qemu_args -m 512 -kernel $builddir/$BOOT_IMAGE -append "$qemu_append $boot_args"; echo $? > $resdir/qemu-retval ) &
+qemu_pid=$!
+commandcompleted=0
+echo Monitoring qemu job at pid $qemu_pid
+while :
+do
+ kruntime=`awk 'BEGIN { print systime() - '"$kstarttime"' }' < /dev/null`
+ if kill -0 $qemu_pid > /dev/null 2>&1
+ then
+ if test $kruntime -ge $seconds
+ then
+ break;
+ fi
+ sleep 1
+ else
+ commandcompleted=1
+ if test $kruntime -lt $seconds
+ then
+ echo Completed in $kruntime vs. $seconds >> $resdir/Warnings 2>&1
+ grep "^(qemu) qemu:" $resdir/kvm-test-1-run.sh.out >> $resdir/Warnings 2>&1
+ killpid="`sed -n "s/^(qemu) qemu: terminating on signal [0-9]* from pid \([0-9]*\).*$/\1/p" $resdir/Warnings`"
+ if test -n "$killpid"
+ then
+ echo "ps -fp $killpid" >> $resdir/Warnings 2>&1
+ ps -fp $killpid >> $resdir/Warnings 2>&1
+ fi
+ else
+ echo ' ---' `date`: Kernel done
+ fi
+ break
+ fi
+done
+if test $commandcompleted -eq 0
+then
+ echo Grace period for qemu job at pid $qemu_pid
+ while :
+ do
+ kruntime=`awk 'BEGIN { print systime() - '"$kstarttime"' }' < /dev/null`
+ if kill -0 $qemu_pid > /dev/null 2>&1
+ then
+ :
+ else
+ break
+ fi
+ if test $kruntime -ge $((seconds + grace))
+ then
+ echo "!!! Hang at $kruntime vs. $seconds seconds" >> $resdir/Warnings 2>&1
+ kill -KILL $qemu_pid
+ break
+ fi
+ sleep 1
+ done
+fi
+
+cp $builddir/console.log $resdir
+parse-torture.sh $resdir/console.log $title
+parse-console.sh $resdir/console.log $title
diff --git a/tools/testing/selftests/rcutorture/bin/kvm.sh b/tools/testing/selftests/rcutorture/bin/kvm.sh
new file mode 100644
index 00000000000..40285c58653
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/kvm.sh
@@ -0,0 +1,410 @@
+#!/bin/bash
+#
+# Run a series of 14 tests under KVM. These are not particularly
+# well-selected or well-tuned, but are the current set. Run from the
+# top level of the source tree.
+#
+# Edit the definitions below to set the locations of the various directories,
+# as well as the test duration.
+#
+# Usage: sh kvm.sh [ options ]
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2011
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+scriptname=$0
+args="$*"
+
+T=/tmp/kvm.sh.$$
+trap 'rm -rf $T' 0
+mkdir $T
+
+dur=30
+dryrun=""
+KVM="`pwd`/tools/testing/selftests/rcutorture"; export KVM
+PATH=${KVM}/bin:$PATH; export PATH
+TORTURE_DEFCONFIG=defconfig
+TORTURE_BOOT_IMAGE=""
+TORTURE_INITRD="$KVM/initrd"; export TORTURE_INITRD
+TORTURE_KMAKE_ARG=""
+TORTURE_SUITE=rcu
+resdir=""
+configs=""
+cpus=0
+ds=`date +%Y.%m.%d-%H:%M:%S`
+kversion=""
+
+. functions.sh
+
+usage () {
+ echo "Usage: $scriptname optional arguments:"
+ echo " --bootargs kernel-boot-arguments"
+ echo " --bootimage relative-path-to-kernel-boot-image"
+ echo " --buildonly"
+ echo " --configs \"config-file list\""
+ echo " --cpus N"
+ echo " --datestamp string"
+ echo " --defconfig string"
+ echo " --dryrun sched|script"
+ echo " --duration minutes"
+ echo " --interactive"
+ echo " --kmake-arg kernel-make-arguments"
+ echo " --kversion vN.NN"
+ echo " --mac nn:nn:nn:nn:nn:nn"
+ echo " --no-initrd"
+ echo " --qemu-args qemu-system-..."
+ echo " --qemu-cmd qemu-system-..."
+ echo " --results absolute-pathname"
+ echo " --torture rcu"
+ exit 1
+}
+
+while test $# -gt 0
+do
+ case "$1" in
+ --bootargs)
+ checkarg --bootargs "(list of kernel boot arguments)" "$#" "$2" '.*' '^--'
+ TORTURE_BOOTARGS="$2"
+ shift
+ ;;
+ --bootimage)
+ checkarg --bootimage "(relative path to kernel boot image)" "$#" "$2" '[a-zA-Z0-9][a-zA-Z0-9_]*' '^--'
+ TORTURE_BOOT_IMAGE="$2"
+ shift
+ ;;
+ --buildonly)
+ TORTURE_BUILDONLY=1
+ ;;
+ --configs)
+ checkarg --configs "(list of config files)" "$#" "$2" '^[^/]*$' '^--'
+ configs="$2"
+ shift
+ ;;
+ --cpus)
+ checkarg --cpus "(number)" "$#" "$2" '^[0-9]*$' '^--'
+ cpus=$2
+ shift
+ ;;
+ --datestamp)
+ checkarg --datestamp "(relative pathname)" "$#" "$2" '^[^/]*$' '^--'
+ ds=$2
+ shift
+ ;;
+ --defconfig)
+ checkarg --defconfig "defconfigtype" "$#" "$2" '^[^/][^/]*$' '^--'
+ TORTURE_DEFCONFIG=$2
+ shift
+ ;;
+ --dryrun)
+ checkarg --dryrun "sched|script" $# "$2" 'sched\|script' '^--'
+ dryrun=$2
+ shift
+ ;;
+ --duration)
+ checkarg --duration "(minutes)" $# "$2" '^[0-9]*$' '^error'
+ dur=$2
+ shift
+ ;;
+ --interactive)
+ TORTURE_QEMU_INTERACTIVE=1; export TORTURE_QEMU_INTERACTIVE
+ ;;
+ --kmake-arg)
+ checkarg --kmake-arg "(kernel make arguments)" $# "$2" '.*' '^error$'
+ TORTURE_KMAKE_ARG="$2"
+ shift
+ ;;
+ --kversion)
+ checkarg --kversion "(kernel version)" $# "$2" '^v[0-9.]*$' '^error'
+ kversion=$2
+ shift
+ ;;
+ --mac)
+ checkarg --mac "(MAC address)" $# "$2" '^\([0-9a-fA-F]\{2\}:\)\{5\}[0-9a-fA-F]\{2\}$' error
+ TORTURE_QEMU_MAC=$2
+ shift
+ ;;
+ --no-initrd)
+ TORTURE_INITRD=""; export TORTURE_INITRD
+ ;;
+ --qemu-args)
+ checkarg --qemu-args "-qemu args" $# "$2" '^-' '^error'
+ TORTURE_QEMU_ARG="$2"
+ shift
+ ;;
+ --qemu-cmd)
+ checkarg --qemu-cmd "(qemu-system-...)" $# "$2" 'qemu-system-' '^--'
+ TORTURE_QEMU_CMD="$2"
+ shift
+ ;;
+ --results)
+ checkarg --results "(absolute pathname)" "$#" "$2" '^/' '^error'
+ resdir=$2
+ shift
+ ;;
+ --torture)
+ checkarg --torture "(suite name)" "$#" "$2" '^\(lock\|rcu\)$' '^--'
+ TORTURE_SUITE=$2
+ shift
+ ;;
+ *)
+ echo Unknown argument $1
+ usage
+ ;;
+ esac
+ shift
+done
+
+CONFIGFRAG=${KVM}/configs/${TORTURE_SUITE}; export CONFIGFRAG
+KVPATH=${CONFIGFRAG}/$kversion; export KVPATH
+
+if test -z "$configs"
+then
+ configs="`cat $CONFIGFRAG/$kversion/CFLIST`"
+fi
+
+if test -z "$resdir"
+then
+ resdir=$KVM/res
+fi
+
+# Create a file of test-name/#cpus pairs, sorted by decreasing #cpus.
+touch $T/cfgcpu
+for CF in $configs
+do
+ if test -f "$CONFIGFRAG/$kversion/$CF"
+ then
+ echo $CF `configNR_CPUS.sh $CONFIGFRAG/$kversion/$CF` >> $T/cfgcpu
+ else
+ echo "The --configs file $CF does not exist, terminating."
+ exit 1
+ fi
+done
+sort -k2nr $T/cfgcpu > $T/cfgcpu.sort
+
+# Use a greedy bin-packing algorithm, sorting the list accordingly.
+awk < $T/cfgcpu.sort > $T/cfgcpu.pack -v ncpus=$cpus '
+BEGIN {
+ njobs = 0;
+}
+
+{
+ # Read file of tests and corresponding required numbers of CPUs.
+ cf[njobs] = $1;
+ cpus[njobs] = $2;
+ njobs++;
+}
+
+END {
+ alldone = 0;
+ batch = 0;
+ nc = -1;
+
+ # Each pass through the following loop creates on test batch
+ # that can be executed concurrently given ncpus. Note that a
+ # given test that requires more than the available CPUs will run in
+ # their own batch. Such tests just have to make do with what
+ # is available.
+ while (nc != ncpus) {
+ batch++;
+ nc = ncpus;
+
+ # Each pass through the following loop considers one
+ # test for inclusion in the current batch.
+ for (i = 0; i < njobs; i++) {
+ if (done[i])
+ continue; # Already part of a batch.
+ if (nc >= cpus[i] || nc == ncpus) {
+
+ # This test fits into the current batch.
+ done[i] = batch;
+ nc -= cpus[i];
+ if (nc <= 0)
+ break; # Too-big test in its own batch.
+ }
+ }
+ }
+
+ # Dump out the tests in batch order.
+ for (b = 1; b <= batch; b++)
+ for (i = 0; i < njobs; i++)
+ if (done[i] == b)
+ print cf[i], cpus[i];
+}'
+
+# Generate a script to execute the tests in appropriate batches.
+cat << ___EOF___ > $T/script
+CONFIGFRAG="$CONFIGFRAG"; export CONFIGFRAG
+KVM="$KVM"; export KVM
+KVPATH="$KVPATH"; export KVPATH
+PATH="$PATH"; export PATH
+TORTURE_BOOT_IMAGE="$TORTURE_BOOT_IMAGE"; export TORTURE_BOOT_IMAGE
+TORTURE_BUILDONLY="$TORTURE_BUILDONLY"; export TORTURE_BUILDONLY
+TORTURE_DEFCONFIG="$TORTURE_DEFCONFIG"; export TORTURE_DEFCONFIG
+TORTURE_INITRD="$TORTURE_INITRD"; export TORTURE_INITRD
+TORTURE_KMAKE_ARG="$TORTURE_KMAKE_ARG"; export TORTURE_KMAKE_ARG
+TORTURE_QEMU_CMD="$TORTURE_QEMU_CMD"; export TORTURE_QEMU_CMD
+TORTURE_QEMU_INTERACTIVE="$TORTURE_QEMU_INTERACTIVE"; export TORTURE_QEMU_INTERACTIVE
+TORTURE_QEMU_MAC="$TORTURE_QEMU_MAC"; export TORTURE_QEMU_MAC
+TORTURE_SUITE="$TORTURE_SUITE"; export TORTURE_SUITE
+if ! test -e $resdir
+then
+ mkdir -p "$resdir" || :
+fi
+mkdir $resdir/$ds
+echo Results directory: $resdir/$ds
+echo $scriptname $args
+touch $resdir/$ds/log
+echo $scriptname $args >> $resdir/$ds/log
+echo ${TORTURE_SUITE} > $resdir/$ds/TORTURE_SUITE
+pwd > $resdir/$ds/testid.txt
+if test -d .git
+then
+ git status >> $resdir/$ds/testid.txt
+ git rev-parse HEAD >> $resdir/$ds/testid.txt
+ if ! git diff HEAD > $T/git-diff 2>&1
+ then
+ cp $T/git-diff $resdir/$ds
+ fi
+fi
+___EOF___
+awk < $T/cfgcpu.pack \
+ -v CONFIGDIR="$CONFIGFRAG/$kversion/" \
+ -v KVM="$KVM" \
+ -v ncpus=$cpus \
+ -v rd=$resdir/$ds/ \
+ -v dur=$dur \
+ -v TORTURE_QEMU_ARG="$TORTURE_QEMU_ARG" \
+ -v TORTURE_BOOTARGS="$TORTURE_BOOTARGS" \
+'BEGIN {
+ i = 0;
+}
+
+{
+ cf[i] = $1;
+ cpus[i] = $2;
+ i++;
+}
+
+# Dump out the scripting required to run one test batch.
+function dump(first, pastlast)
+{
+ print "echo ----Start batch: `date`";
+ print "echo ----Start batch: `date` >> " rd "/log";
+ jn=1
+ for (j = first; j < pastlast; j++) {
+ builddir=KVM "/b" jn
+ cpusr[jn] = cpus[j];
+ if (cfrep[cf[j]] == "") {
+ cfr[jn] = cf[j];
+ cfrep[cf[j]] = 1;
+ } else {
+ cfrep[cf[j]]++;
+ cfr[jn] = cf[j] "." cfrep[cf[j]];
+ }
+ if (cpusr[jn] > ncpus && ncpus != 0)
+ ovf = "(!)";
+ else
+ ovf = "";
+ print "echo ", cfr[jn], cpusr[jn] ovf ": Starting build. `date`";
+ print "echo ", cfr[jn], cpusr[jn] ovf ": Starting build. `date` >> " rd "/log";
+ print "rm -f " builddir ".*";
+ print "touch " builddir ".wait";
+ print "mkdir " builddir " > /dev/null 2>&1 || :";
+ print "mkdir " rd cfr[jn] " || :";
+ print "kvm-test-1-run.sh " CONFIGDIR cf[j], builddir, rd cfr[jn], dur " \"" TORTURE_QEMU_ARG "\" \"" TORTURE_BOOTARGS "\" > " rd cfr[jn] "/kvm-test-1-run.sh.out 2>&1 &"
+ print "echo ", cfr[jn], cpusr[jn] ovf ": Waiting for build to complete. `date`";
+ print "echo ", cfr[jn], cpusr[jn] ovf ": Waiting for build to complete. `date` >> " rd "/log";
+ print "while test -f " builddir ".wait"
+ print "do"
+ print "\tsleep 1"
+ print "done"
+ print "echo ", cfr[jn], cpusr[jn] ovf ": Build complete. `date`";
+ print "echo ", cfr[jn], cpusr[jn] ovf ": Build complete. `date` >> " rd "/log";
+ jn++;
+ }
+ for (j = 1; j < jn; j++) {
+ builddir=KVM "/b" j
+ print "rm -f " builddir ".ready"
+ print "echo ----", cfr[j], cpusr[j] ovf ": Starting kernel. `date`";
+ print "echo ----", cfr[j], cpusr[j] ovf ": Starting kernel. `date` >> " rd "/log";
+ }
+ print "wait"
+ print "echo ---- All kernel runs complete. `date`";
+ print "echo ---- All kernel runs complete. `date` >> " rd "/log";
+ for (j = 1; j < jn; j++) {
+ builddir=KVM "/b" j
+ print "echo ----", cfr[j], cpusr[j] ovf ": Build/run results:";
+ print "echo ----", cfr[j], cpusr[j] ovf ": Build/run results: >> " rd "/log";
+ print "cat " rd cfr[j] "/kvm-test-1-run.sh.out";
+ print "cat " rd cfr[j] "/kvm-test-1-run.sh.out >> " rd "/log";
+ }
+}
+
+END {
+ njobs = i;
+ nc = ncpus;
+ first = 0;
+
+ # Each pass through the following loop considers one test.
+ for (i = 0; i < njobs; i++) {
+ if (ncpus == 0) {
+ # Sequential test specified, each test its own batch.
+ dump(i, i + 1);
+ first = i;
+ } else if (nc < cpus[i] && i != 0) {
+ # Out of CPUs, dump out a batch.
+ dump(first, i);
+ first = i;
+ nc = ncpus;
+ }
+ # Account for the CPUs needed by the current test.
+ nc -= cpus[i];
+ }
+ # Dump the last batch.
+ if (ncpus != 0)
+ dump(first, i);
+}' >> $T/script
+
+cat << ___EOF___ >> $T/script
+echo
+echo
+echo " --- `date` Test summary:"
+echo Results directory: $resdir/$ds
+if test -z "$TORTURE_BUILDONLY"
+then
+ kvm-recheck.sh $resdir/$ds
+fi
+___EOF___
+
+if test "$dryrun" = script
+then
+ cat $T/script
+ exit 0
+elif test "$dryrun" = sched
+then
+ # Extract the test run schedule from the script.
+ egrep 'Start batch|Starting build\.' $T/script |
+ grep -v ">>" |
+ sed -e 's/:.*$//' -e 's/^echo //'
+ exit 0
+else
+ # Not a dryru, so run the script.
+ sh $T/script
+fi
+
+# Tracing: trace_event=rcu:rcu_grace_period,rcu:rcu_future_grace_period,rcu:rcu_grace_period_init,rcu:rcu_nocb_wake,rcu:rcu_preempt_task,rcu:rcu_unlock_preempted_task,rcu:rcu_quiescent_state_report,rcu:rcu_fqs,rcu:rcu_callback,rcu:rcu_kfree_callback,rcu:rcu_batch_start,rcu:rcu_invoke_callback,rcu:rcu_invoke_kfree_callback,rcu:rcu_batch_end,rcu:rcu_torture_read,rcu:rcu_barrier
diff --git a/tools/testing/selftests/rcutorture/bin/parse-build.sh b/tools/testing/selftests/rcutorture/bin/parse-build.sh
new file mode 100755
index 00000000000..543230951c3
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/parse-build.sh
@@ -0,0 +1,57 @@
+#!/bin/sh
+#
+# Check the build output from an rcutorture run for goodness.
+# The "file" is a pathname on the local system, and "title" is
+# a text string for error-message purposes.
+#
+# The file must contain kernel build output.
+#
+# Usage:
+# sh parse-build.sh file title
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2011
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+T=$1
+title=$2
+
+. functions.sh
+
+if grep -q CC < $T
+then
+ :
+else
+ print_bug $title no build
+ exit 1
+fi
+
+if grep -q "error:" < $T
+then
+ print_bug $title build errors:
+ grep "error:" < $T
+ exit 2
+fi
+exit 0
+
+if egrep -q "rcu[^/]*\.c.*warning:|rcu.*\.h.*warning:" < $T
+then
+ print_warning $title build errors:
+ egrep "rcu[^/]*\.c.*warning:|rcu.*\.h.*warning:" < $T
+ exit 2
+fi
+exit 0
diff --git a/tools/testing/selftests/rcutorture/bin/parse-console.sh b/tools/testing/selftests/rcutorture/bin/parse-console.sh
new file mode 100755
index 00000000000..4185d4cab32
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/parse-console.sh
@@ -0,0 +1,41 @@
+#!/bin/sh
+#
+# Check the console output from an rcutorture run for oopses.
+# The "file" is a pathname on the local system, and "title" is
+# a text string for error-message purposes.
+#
+# Usage:
+# sh parse-console.sh file title
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2011
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+T=/tmp/abat-chk-badness.sh.$$
+trap 'rm -f $T' 0
+
+file="$1"
+title="$2"
+
+. functions.sh
+
+egrep 'Badness|WARNING:|Warn|BUG|===========|Call Trace:|Oops:' < $file | grep -v 'ODEBUG: ' | grep -v 'Warning: unable to open an initial console' > $T
+if test -s $T
+then
+ print_warning Assertion failure in $file $title
+ cat $T
+fi
diff --git a/tools/testing/selftests/rcutorture/bin/parse-torture.sh b/tools/testing/selftests/rcutorture/bin/parse-torture.sh
new file mode 100755
index 00000000000..3455560ab4e
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/bin/parse-torture.sh
@@ -0,0 +1,106 @@
+#!/bin/sh
+#
+# Check the console output from a torture run for goodness.
+# The "file" is a pathname on the local system, and "title" is
+# a text string for error-message purposes.
+#
+# The file must contain torture output, but can be interspersed
+# with other dmesg text, as in console-log output.
+#
+# Usage:
+# sh parse-torture.sh file title
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2011
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+T=/tmp/parse-torture.sh.$$
+file="$1"
+title="$2"
+
+trap 'rm -f $T.seq' 0
+
+. functions.sh
+
+# check for presence of torture output file.
+
+if test -f "$file" -a -r "$file"
+then
+ :
+else
+ echo $title unreadable torture output file: $file
+ exit 1
+fi
+
+# check for abject failure
+
+if grep -q FAILURE $file || grep -q -e '-torture.*!!!' $file
+then
+ nerrs=`grep --binary-files=text '!!!' $file | tail -1 | awk '{for (i=NF-8;i<=NF;i++) sum+=$i; } END {print sum}'`
+ print_bug $title FAILURE, $nerrs instances
+ echo " " $url
+ exit
+fi
+
+grep --binary-files=text 'torture:.*ver:' $file | grep --binary-files=text -v '(null)' | sed -e 's/^(initramfs)[^]]*] //' -e 's/^\[[^]]*] //' |
+awk '
+BEGIN {
+ ver = 0;
+ badseq = 0;
+ }
+
+ {
+ if (!badseq && ($5 + 0 != $5 || $5 <= ver)) {
+ badseqno1 = ver;
+ badseqno2 = $5;
+ badseqnr = NR;
+ badseq = 1;
+ }
+ ver = $5
+ }
+
+END {
+ if (badseq) {
+ if (badseqno1 == badseqno2 && badseqno2 == ver)
+ print "GP HANG at " ver " torture stat " badseqnr;
+ else
+ print "BAD SEQ " badseqno1 ":" badseqno2 " last:" ver " version " badseqnr;
+ }
+ }' > $T.seq
+
+if grep -q SUCCESS $file
+then
+ if test -s $T.seq
+ then
+ print_warning $title $title `cat $T.seq`
+ echo " " $file
+ exit 2
+ fi
+else
+ if grep -q "_HOTPLUG:" $file
+ then
+ print_warning HOTPLUG FAILURES $title `cat $T.seq`
+ echo " " $file
+ exit 3
+ fi
+ echo $title no success message, `grep --binary-files=text 'ver:' $file | wc -l` successful version messages
+ if test -s $T.seq
+ then
+ print_warning $title `cat $T.seq`
+ fi
+ exit 2
+fi
diff --git a/tools/testing/selftests/rcutorture/configs/lock/BUSTED b/tools/testing/selftests/rcutorture/configs/lock/BUSTED
new file mode 100644
index 00000000000..1d1da1477fc
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/lock/BUSTED
@@ -0,0 +1,6 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=4
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
diff --git a/tools/testing/selftests/rcutorture/configs/lock/BUSTED.boot b/tools/testing/selftests/rcutorture/configs/lock/BUSTED.boot
new file mode 100644
index 00000000000..6386c15e977
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/lock/BUSTED.boot
@@ -0,0 +1 @@
+locktorture.torture_type=lock_busted
diff --git a/tools/testing/selftests/rcutorture/configs/lock/CFLIST b/tools/testing/selftests/rcutorture/configs/lock/CFLIST
new file mode 100644
index 00000000000..a061b22d189
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/lock/CFLIST
@@ -0,0 +1 @@
+LOCK01
diff --git a/tools/testing/selftests/rcutorture/configs/lock/CFcommon b/tools/testing/selftests/rcutorture/configs/lock/CFcommon
new file mode 100644
index 00000000000..e372dc26925
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/lock/CFcommon
@@ -0,0 +1,2 @@
+CONFIG_LOCK_TORTURE_TEST=y
+CONFIG_PRINTK_TIME=y
diff --git a/tools/testing/selftests/rcutorture/configs/lock/LOCK01 b/tools/testing/selftests/rcutorture/configs/lock/LOCK01
new file mode 100644
index 00000000000..a9625e3d6cd
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/lock/LOCK01
@@ -0,0 +1,6 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=8
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
diff --git a/tools/testing/selftests/rcutorture/configs/lock/ver_functions.sh b/tools/testing/selftests/rcutorture/configs/lock/ver_functions.sh
new file mode 100644
index 00000000000..9746ea1cd6c
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/lock/ver_functions.sh
@@ -0,0 +1,43 @@
+#!/bin/bash
+#
+# Kernel-version-dependent shell functions for the rest of the scripts.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2014
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+# locktorture_param_onoff bootparam-string config-file
+#
+# Adds onoff locktorture module parameters to kernels having it.
+locktorture_param_onoff () {
+ if ! bootparam_hotplug_cpu "$1" && configfrag_hotplug_cpu "$2"
+ then
+ echo CPU-hotplug kernel, adding locktorture onoff. 1>&2
+ echo locktorture.onoff_interval=3 locktorture.onoff_holdoff=30
+ fi
+}
+
+# per_version_boot_params bootparam-string config-file seconds
+#
+# Adds per-version torture-module parameters to kernels supporting them.
+per_version_boot_params () {
+ echo $1 `locktorture_param_onoff "$1" "$2"` \
+ locktorture.stat_interval=15 \
+ locktorture.shutdown_secs=$3 \
+ locktorture.locktorture_runnable=1 \
+ locktorture.verbose=1
+}
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/BUSTED b/tools/testing/selftests/rcutorture/configs/rcu/BUSTED
new file mode 100644
index 00000000000..48d8a245c7f
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/BUSTED
@@ -0,0 +1,7 @@
+CONFIG_RCU_TRACE=n
+CONFIG_SMP=y
+CONFIG_NR_CPUS=4
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/BUSTED.boot b/tools/testing/selftests/rcutorture/configs/rcu/BUSTED.boot
new file mode 100644
index 00000000000..6804f9dcfc1
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/BUSTED.boot
@@ -0,0 +1 @@
+rcutorture.torture_type=rcu_busted
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/CFLIST b/tools/testing/selftests/rcutorture/configs/rcu/CFLIST
new file mode 100644
index 00000000000..cd3d29cb0a4
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/CFLIST
@@ -0,0 +1,13 @@
+TREE01
+TREE02
+TREE03
+TREE04
+TREE05
+TREE06
+TREE07
+TREE08
+TREE09
+SRCU-N
+SRCU-P
+TINY01
+TINY02
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/CFcommon b/tools/testing/selftests/rcutorture/configs/rcu/CFcommon
new file mode 100644
index 00000000000..d2d2a86139d
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/CFcommon
@@ -0,0 +1,2 @@
+CONFIG_RCU_TORTURE_TEST=y
+CONFIG_PRINTK_TIME=y
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/SRCU-N b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-N
new file mode 100644
index 00000000000..9fbb41b9b31
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-N
@@ -0,0 +1,7 @@
+CONFIG_RCU_TRACE=n
+CONFIG_SMP=y
+CONFIG_NR_CPUS=4
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/SRCU-N.boot b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-N.boot
new file mode 100644
index 00000000000..238bfe3bd0c
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-N.boot
@@ -0,0 +1 @@
+rcutorture.torture_type=srcu
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/SRCU-P b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-P
new file mode 100644
index 00000000000..4b6f272dba2
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-P
@@ -0,0 +1,7 @@
+CONFIG_RCU_TRACE=n
+CONFIG_SMP=y
+CONFIG_NR_CPUS=8
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/SRCU-P.boot b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-P.boot
new file mode 100644
index 00000000000..238bfe3bd0c
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/SRCU-P.boot
@@ -0,0 +1 @@
+rcutorture.torture_type=srcu
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TINY01 b/tools/testing/selftests/rcutorture/configs/rcu/TINY01
new file mode 100644
index 00000000000..0a63e073a00
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TINY01
@@ -0,0 +1,12 @@
+CONFIG_SMP=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TINY_RCU=y
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NO_HZ_FULL=n
+CONFIG_RCU_TRACE=n
+CONFIG_DEBUG_LOCK_ALLOC=n
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
+CONFIG_PREEMPT_COUNT=n
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TINY02 b/tools/testing/selftests/rcutorture/configs/rcu/TINY02
new file mode 100644
index 00000000000..f4feaee4077
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TINY02
@@ -0,0 +1,12 @@
+CONFIG_SMP=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TINY_RCU=y
+CONFIG_HZ_PERIODIC=y
+CONFIG_NO_HZ_IDLE=n
+CONFIG_NO_HZ_FULL=n
+CONFIG_RCU_TRACE=y
+CONFIG_DEBUG_LOCK_ALLOC=y
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
+CONFIG_PREEMPT_COUNT=y
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE01 b/tools/testing/selftests/rcutorture/configs/rcu/TREE01
new file mode 100644
index 00000000000..9c827ec59a9
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE01
@@ -0,0 +1,22 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=8
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NO_HZ_FULL=n
+CONFIG_RCU_FAST_NO_HZ=y
+CONFIG_RCU_TRACE=y
+CONFIG_HOTPLUG_CPU=y
+CONFIG_RCU_FANOUT=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_RCU_NOCB_CPU=y
+CONFIG_RCU_NOCB_CPU_ZERO=y
+CONFIG_DEBUG_LOCK_ALLOC=n
+CONFIG_PROVE_RCU_DELAY=n
+CONFIG_RCU_CPU_STALL_INFO=n
+CONFIG_RCU_CPU_STALL_VERBOSE=n
+CONFIG_RCU_BOOST=n
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE01.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE01.boot
new file mode 100644
index 00000000000..0fc8a342893
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE01.boot
@@ -0,0 +1 @@
+rcutorture.torture_type=rcu_bh
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE02 b/tools/testing/selftests/rcutorture/configs/rcu/TREE02
new file mode 100644
index 00000000000..1a777b5f68b
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE02
@@ -0,0 +1,25 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=8
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NO_HZ_FULL=n
+CONFIG_RCU_FAST_NO_HZ=n
+CONFIG_RCU_TRACE=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_RCU_FANOUT=3
+CONFIG_RCU_FANOUT_LEAF=3
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_RCU_NOCB_CPU=n
+CONFIG_DEBUG_LOCK_ALLOC=y
+CONFIG_PROVE_LOCKING=n
+CONFIG_PROVE_RCU_DELAY=n
+CONFIG_RCU_CPU_STALL_INFO=n
+CONFIG_RCU_CPU_STALL_VERBOSE=y
+CONFIG_RCU_BOOST=n
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE02-T b/tools/testing/selftests/rcutorture/configs/rcu/TREE02-T
new file mode 100644
index 00000000000..61c8d9ce5bb
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE02-T
@@ -0,0 +1,25 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=8
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NO_HZ_FULL=n
+CONFIG_RCU_FAST_NO_HZ=n
+CONFIG_RCU_TRACE=y
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_RCU_FANOUT=3
+CONFIG_RCU_FANOUT_LEAF=3
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_RCU_NOCB_CPU=n
+CONFIG_DEBUG_LOCK_ALLOC=y
+CONFIG_PROVE_LOCKING=n
+CONFIG_PROVE_RCU_DELAY=n
+CONFIG_RCU_CPU_STALL_INFO=n
+CONFIG_RCU_CPU_STALL_VERBOSE=y
+CONFIG_RCU_BOOST=n
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE03 b/tools/testing/selftests/rcutorture/configs/rcu/TREE03
new file mode 100644
index 00000000000..c1f111c1561
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE03
@@ -0,0 +1,22 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=8
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_HZ_PERIODIC=y
+CONFIG_NO_HZ_IDLE=n
+CONFIG_NO_HZ_FULL=n
+CONFIG_RCU_TRACE=y
+CONFIG_HOTPLUG_CPU=y
+CONFIG_RCU_FANOUT=4
+CONFIG_RCU_FANOUT_LEAF=4
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_RCU_NOCB_CPU=n
+CONFIG_DEBUG_LOCK_ALLOC=n
+CONFIG_PROVE_RCU_DELAY=n
+CONFIG_RCU_CPU_STALL_INFO=n
+CONFIG_RCU_CPU_STALL_VERBOSE=n
+CONFIG_RCU_BOOST=y
+CONFIG_RCU_BOOST_PRIO=2
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE04 b/tools/testing/selftests/rcutorture/configs/rcu/TREE04
new file mode 100644
index 00000000000..7dbd27ce17a
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE04
@@ -0,0 +1,24 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=8
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=n
+CONFIG_NO_HZ_FULL=y
+CONFIG_NO_HZ_FULL_ALL=y
+CONFIG_RCU_FAST_NO_HZ=y
+CONFIG_RCU_TRACE=y
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_RCU_FANOUT=2
+CONFIG_RCU_FANOUT_LEAF=2
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_RCU_NOCB_CPU=n
+CONFIG_DEBUG_LOCK_ALLOC=n
+CONFIG_PROVE_RCU_DELAY=n
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_RCU_CPU_STALL_VERBOSE=y
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE04.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE04.boot
new file mode 100644
index 00000000000..0fc8a342893
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE04.boot
@@ -0,0 +1 @@
+rcutorture.torture_type=rcu_bh
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE05 b/tools/testing/selftests/rcutorture/configs/rcu/TREE05
new file mode 100644
index 00000000000..d0f32e57474
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE05
@@ -0,0 +1,24 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=8
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NO_HZ_FULL=n
+CONFIG_RCU_FAST_NO_HZ=n
+CONFIG_RCU_TRACE=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_RCU_FANOUT=6
+CONFIG_RCU_FANOUT_LEAF=6
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_RCU_NOCB_CPU=y
+CONFIG_RCU_NOCB_CPU_NONE=y
+CONFIG_DEBUG_LOCK_ALLOC=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_PROVE_RCU_DELAY=y
+CONFIG_RCU_CPU_STALL_INFO=n
+CONFIG_RCU_CPU_STALL_VERBOSE=n
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE05.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE05.boot
new file mode 100644
index 00000000000..3b42b8b033c
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE05.boot
@@ -0,0 +1 @@
+rcutorture.torture_type=sched
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE06 b/tools/testing/selftests/rcutorture/configs/rcu/TREE06
new file mode 100644
index 00000000000..2e477dfb9c5
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE06
@@ -0,0 +1,25 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=8
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NO_HZ_FULL=n
+CONFIG_RCU_FAST_NO_HZ=n
+CONFIG_RCU_TRACE=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_RCU_FANOUT=6
+CONFIG_RCU_FANOUT_LEAF=6
+CONFIG_RCU_FANOUT_EXACT=y
+CONFIG_RCU_NOCB_CPU=n
+CONFIG_DEBUG_LOCK_ALLOC=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_PROVE_RCU_DELAY=n
+CONFIG_RCU_CPU_STALL_INFO=n
+CONFIG_RCU_CPU_STALL_VERBOSE=n
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=y
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE07 b/tools/testing/selftests/rcutorture/configs/rcu/TREE07
new file mode 100644
index 00000000000..042f86ef362
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE07
@@ -0,0 +1,23 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=16
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=n
+CONFIG_NO_HZ_FULL=y
+CONFIG_NO_HZ_FULL_ALL=y
+CONFIG_NO_HZ_FULL_SYSIDLE=y
+CONFIG_RCU_FAST_NO_HZ=n
+CONFIG_RCU_TRACE=y
+CONFIG_HOTPLUG_CPU=y
+CONFIG_RCU_FANOUT=2
+CONFIG_RCU_FANOUT_LEAF=2
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_RCU_NOCB_CPU=n
+CONFIG_DEBUG_LOCK_ALLOC=n
+CONFIG_PROVE_RCU_DELAY=n
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_RCU_CPU_STALL_VERBOSE=n
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE08 b/tools/testing/selftests/rcutorture/configs/rcu/TREE08
new file mode 100644
index 00000000000..3438cee1e3c
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE08
@@ -0,0 +1,25 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=16
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NO_HZ_FULL=n
+CONFIG_RCU_FAST_NO_HZ=n
+CONFIG_RCU_TRACE=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_RCU_FANOUT=3
+CONFIG_RCU_FANOUT_EXACT=y
+CONFIG_RCU_FANOUT_LEAF=2
+CONFIG_RCU_NOCB_CPU=y
+CONFIG_RCU_NOCB_CPU_ALL=y
+CONFIG_DEBUG_LOCK_ALLOC=n
+CONFIG_PROVE_RCU_DELAY=n
+CONFIG_RCU_CPU_STALL_INFO=n
+CONFIG_RCU_CPU_STALL_VERBOSE=n
+CONFIG_RCU_BOOST=n
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE08-T b/tools/testing/selftests/rcutorture/configs/rcu/TREE08-T
new file mode 100644
index 00000000000..bf4523d3e44
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE08-T
@@ -0,0 +1,25 @@
+CONFIG_SMP=y
+CONFIG_NR_CPUS=16
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NO_HZ_FULL=n
+CONFIG_RCU_FAST_NO_HZ=n
+CONFIG_RCU_TRACE=y
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_RCU_FANOUT=3
+CONFIG_RCU_FANOUT_EXACT=y
+CONFIG_RCU_FANOUT_LEAF=2
+CONFIG_RCU_NOCB_CPU=y
+CONFIG_RCU_NOCB_CPU_ALL=y
+CONFIG_DEBUG_LOCK_ALLOC=n
+CONFIG_PROVE_RCU_DELAY=n
+CONFIG_RCU_CPU_STALL_INFO=n
+CONFIG_RCU_CPU_STALL_VERBOSE=n
+CONFIG_RCU_BOOST=n
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE08.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE08.boot
new file mode 100644
index 00000000000..3b42b8b033c
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE08.boot
@@ -0,0 +1 @@
+rcutorture.torture_type=sched
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE09 b/tools/testing/selftests/rcutorture/configs/rcu/TREE09
new file mode 100644
index 00000000000..81e4f7c0bf0
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE09
@@ -0,0 +1,20 @@
+CONFIG_SMP=n
+CONFIG_NR_CPUS=1
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_HZ_PERIODIC=n
+CONFIG_NO_HZ_IDLE=y
+CONFIG_NO_HZ_FULL=n
+CONFIG_RCU_TRACE=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_RCU_NOCB_CPU=n
+CONFIG_DEBUG_LOCK_ALLOC=n
+CONFIG_PROVE_RCU_DELAY=n
+CONFIG_RCU_CPU_STALL_INFO=n
+CONFIG_RCU_CPU_STALL_VERBOSE=n
+CONFIG_RCU_BOOST=n
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=n
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/CFLIST b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/CFLIST
new file mode 100644
index 00000000000..18223947bbc
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/CFLIST
@@ -0,0 +1,14 @@
+P1-S-T-NH-SD-SMP-HP
+P2-2-t-nh-sd-SMP-hp
+P3-3-T-nh-SD-SMP-hp
+P4-A-t-NH-sd-SMP-HP
+P5-U-T-NH-sd-SMP-hp
+N1-S-T-NH-SD-SMP-HP
+N2-2-t-nh-sd-SMP-hp
+N3-3-T-nh-SD-SMP-hp
+N4-A-t-NH-sd-SMP-HP
+N5-U-T-NH-sd-SMP-hp
+PT1-nh
+PT2-NH
+NT1-nh
+NT3-NH
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N1-S-T-NH-SD-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N1-S-T-NH-SD-SMP-HP
new file mode 100644
index 00000000000..d3ef873eb6e
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N1-S-T-NH-SD-SMP-HP
@@ -0,0 +1,18 @@
+CONFIG_RCU_TRACE=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=8
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N2-2-t-nh-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N2-2-t-nh-sd-SMP-hp
new file mode 100644
index 00000000000..02e418572b1
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N2-2-t-nh-sd-SMP-hp
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=4
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N3-3-T-nh-SD-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N3-3-T-nh-SD-SMP-hp
new file mode 100644
index 00000000000..b3100f69c8c
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N3-3-T-nh-SD-SMP-hp
@@ -0,0 +1,22 @@
+CONFIG_RCU_TRACE=y
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=2
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N4-A-t-NH-sd-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N4-A-t-NH-sd-SMP-HP
new file mode 100644
index 00000000000..c56b4453072
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N4-A-t-NH-sd-SMP-HP
@@ -0,0 +1,18 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N5-U-T-NH-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N5-U-T-NH-sd-SMP-hp
new file mode 100644
index 00000000000..90d924fea9e
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/N5-U-T-NH-sd-SMP-hp
@@ -0,0 +1,22 @@
+CONFIG_RCU_TRACE=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=y
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/NT1-nh b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/NT1-nh
new file mode 100644
index 00000000000..023f312a931
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/NT1-nh
@@ -0,0 +1,23 @@
+#CHECK#CONFIG_TINY_RCU=y
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=n
+#
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/NT3-NH b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/NT3-NH
new file mode 100644
index 00000000000..6fd0235dae7
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/NT3-NH
@@ -0,0 +1,20 @@
+#CHECK#CONFIG_TINY_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=y
+#
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P1-S-T-NH-SD-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P1-S-T-NH-SD-SMP-HP
new file mode 100644
index 00000000000..f72402d7c13
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P1-S-T-NH-SD-SMP-HP
@@ -0,0 +1,19 @@
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=8
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P2-2-t-nh-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P2-2-t-nh-sd-SMP-hp
new file mode 100644
index 00000000000..0f3b667d2a9
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P2-2-t-nh-sd-SMP-hp
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=4
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P3-3-T-nh-SD-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P3-3-T-nh-SD-SMP-hp
new file mode 100644
index 00000000000..b035e141bf2
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P3-3-T-nh-SD-SMP-hp
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=y
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=2
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P4-A-t-NH-sd-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P4-A-t-NH-sd-SMP-HP
new file mode 100644
index 00000000000..3ccf6a9447f
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P4-A-t-NH-sd-SMP-HP
@@ -0,0 +1,22 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_RT_MUTEXES=y
+CONFIG_RCU_BOOST=y
+CONFIG_RCU_BOOST_PRIO=2
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P5-U-T-NH-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P5-U-T-NH-sd-SMP-hp
new file mode 100644
index 00000000000..ef624ce73d8
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/P5-U-T-NH-sd-SMP-hp
@@ -0,0 +1,28 @@
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=y
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_PROVE_RCU_DELAY=y
+CONFIG_DEBUG_OBJECTS=y
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=y
+CONFIG_RT_MUTEXES=y
+CONFIG_RCU_BOOST=y
+CONFIG_RCU_BOOST_PRIO=2
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/PT1-nh b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/PT1-nh
new file mode 100644
index 00000000000..e3361c3894a
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/PT1-nh
@@ -0,0 +1,23 @@
+CONFIG_TINY_PREEMPT_RCU=y
+CONFIG_RCU_BOOST=y
+CONFIG_RCU_BOOST_PRIO=2
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=n
+#
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/PT2-NH b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/PT2-NH
new file mode 100644
index 00000000000..64abfc3b4d9
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/PT2-NH
@@ -0,0 +1,22 @@
+CONFIG_TINY_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=y
+#
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v0.0/ver_functions.sh b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/ver_functions.sh
new file mode 100644
index 00000000000..5ace37a8978
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v0.0/ver_functions.sh
@@ -0,0 +1,33 @@
+#!/bin/bash
+#
+# Kernel-version-dependent shell functions for the rest of the scripts.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2013
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+# per_version_boot_params bootparam-string config-file seconds
+#
+# Adds per-version torture-module parameters to kernels supporting them.
+# Which old kernels do not.
+per_version_boot_params () {
+ echo rcutorture.stat_interval=15 \
+ rcutorture.shutdown_secs=$3 \
+ rcutorture.rcutorture_runnable=1 \
+ rcutorture.test_no_idle_hz=1 \
+ rcutorture.verbose=1
+}
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/CFLIST b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/CFLIST
new file mode 100644
index 00000000000..da4cbc668f2
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/CFLIST
@@ -0,0 +1,17 @@
+sysidleY.2013.06.19a
+sysidleN.2013.06.19a
+P1-S-T-NH-SD-SMP-HP
+P2-2-t-nh-sd-SMP-hp
+P3-3-T-nh-SD-SMP-hp
+P4-A-t-NH-sd-SMP-HP
+P5-U-T-NH-sd-SMP-hp
+P6---t-nh-SD-smp-hp
+N1-S-T-NH-SD-SMP-HP
+N2-2-t-nh-sd-SMP-hp
+N3-3-T-nh-SD-SMP-hp
+N4-A-t-NH-sd-SMP-HP
+N5-U-T-NH-sd-SMP-hp
+PT1-nh
+PT2-NH
+NT1-nh
+NT3-NH
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N1-S-T-NH-SD-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N1-S-T-NH-SD-SMP-HP
new file mode 100644
index 00000000000..d81e11d280a
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N1-S-T-NH-SD-SMP-HP
@@ -0,0 +1,19 @@
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_FAST_NO_HZ=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=8
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N2-2-t-nh-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N2-2-t-nh-sd-SMP-hp
new file mode 100644
index 00000000000..02e418572b1
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N2-2-t-nh-sd-SMP-hp
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=4
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N3-3-T-nh-SD-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N3-3-T-nh-SD-SMP-hp
new file mode 100644
index 00000000000..b3100f69c8c
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N3-3-T-nh-SD-SMP-hp
@@ -0,0 +1,22 @@
+CONFIG_RCU_TRACE=y
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=2
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N4-A-t-NH-sd-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N4-A-t-NH-sd-SMP-HP
new file mode 100644
index 00000000000..c56b4453072
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N4-A-t-NH-sd-SMP-HP
@@ -0,0 +1,18 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N5-U-T-NH-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N5-U-T-NH-sd-SMP-hp
new file mode 100644
index 00000000000..90d924fea9e
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N5-U-T-NH-sd-SMP-hp
@@ -0,0 +1,22 @@
+CONFIG_RCU_TRACE=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=y
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N6---t-nh-SD-smp-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N6---t-nh-SD-smp-hp
new file mode 100644
index 00000000000..0ccc36d7273
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N6---t-nh-SD-smp-hp
@@ -0,0 +1,19 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_NR_CPUS=1
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N7-4-T-NH-SD-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N7-4-T-NH-SD-SMP-HP
new file mode 100644
index 00000000000..3f640cf8497
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N7-4-T-NH-SD-SMP-HP
@@ -0,0 +1,26 @@
+CONFIG_RCU_TRACE=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=2
+CONFIG_NR_CPUS=16
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_RCU_NOCB_CPU=y
+CONFIG_RCU_NOCB_CPU_NONE=y
+CONFIG_RCU_NOCB_CPU_ZERO=n
+CONFIG_RCU_NOCB_CPU_ALL=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N8-2-T-NH-SD-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N8-2-T-NH-SD-SMP-HP
new file mode 100644
index 00000000000..285da2dd8ac
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/N8-2-T-NH-SD-SMP-HP
@@ -0,0 +1,22 @@
+CONFIG_RCU_TRACE=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=14
+CONFIG_NR_CPUS=16
+CONFIG_RCU_FANOUT_EXACT=y
+CONFIG_HOTPLUG_CPU=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/NT1-nh b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/NT1-nh
new file mode 100644
index 00000000000..023f312a931
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/NT1-nh
@@ -0,0 +1,23 @@
+#CHECK#CONFIG_TINY_RCU=y
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=n
+#
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/NT3-NH b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/NT3-NH
new file mode 100644
index 00000000000..6fd0235dae7
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/NT3-NH
@@ -0,0 +1,20 @@
+#CHECK#CONFIG_TINY_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=y
+#
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P1-S-T-NH-SD-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P1-S-T-NH-SD-SMP-HP
new file mode 100644
index 00000000000..9647c44cf4b
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P1-S-T-NH-SD-SMP-HP
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_RCU_FAST_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=8
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P2-2-t-nh-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P2-2-t-nh-sd-SMP-hp
new file mode 100644
index 00000000000..0f3b667d2a9
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P2-2-t-nh-sd-SMP-hp
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=4
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P3-3-T-nh-SD-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P3-3-T-nh-SD-SMP-hp
new file mode 100644
index 00000000000..b035e141bf2
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P3-3-T-nh-SD-SMP-hp
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=y
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=2
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P4-A-t-NH-sd-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P4-A-t-NH-sd-SMP-HP
new file mode 100644
index 00000000000..3ccf6a9447f
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P4-A-t-NH-sd-SMP-HP
@@ -0,0 +1,22 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_RT_MUTEXES=y
+CONFIG_RCU_BOOST=y
+CONFIG_RCU_BOOST_PRIO=2
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P5-U-T-NH-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P5-U-T-NH-sd-SMP-hp
new file mode 100644
index 00000000000..ef624ce73d8
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P5-U-T-NH-sd-SMP-hp
@@ -0,0 +1,28 @@
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=y
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_PROVE_RCU_DELAY=y
+CONFIG_DEBUG_OBJECTS=y
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=y
+CONFIG_RT_MUTEXES=y
+CONFIG_RCU_BOOST=y
+CONFIG_RCU_BOOST_PRIO=2
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P6---t-nh-SD-smp-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P6---t-nh-SD-smp-hp
new file mode 100644
index 00000000000..f4c9175828b
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P6---t-nh-SD-smp-hp
@@ -0,0 +1,18 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=n
+CONFIG_SMP=n
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-HP
new file mode 100644
index 00000000000..77a8c5b7576
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-HP
@@ -0,0 +1,30 @@
+CONFIG_RCU_TRACE=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=2
+CONFIG_NR_CPUS=16
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_RCU_NOCB_CPU=y
+CONFIG_RCU_NOCB_CPU_NONE=n
+CONFIG_RCU_NOCB_CPU_ZERO=n
+CONFIG_RCU_NOCB_CPU_ALL=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_DEBUG_OBJECTS=y
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_SLUB=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-HP-all b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-HP-all
new file mode 100644
index 00000000000..0eecebc6e95
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-HP-all
@@ -0,0 +1,30 @@
+CONFIG_RCU_TRACE=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=2
+CONFIG_NR_CPUS=16
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_RCU_NOCB_CPU=y
+CONFIG_RCU_NOCB_CPU_NONE=y
+CONFIG_RCU_NOCB_CPU_ZERO=n
+CONFIG_RCU_NOCB_CPU_ALL=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_DEBUG_OBJECTS=y
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_SLUB=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-HP-none b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-HP-none
new file mode 100644
index 00000000000..0eecebc6e95
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-HP-none
@@ -0,0 +1,30 @@
+CONFIG_RCU_TRACE=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=2
+CONFIG_NR_CPUS=16
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_RCU_NOCB_CPU=y
+CONFIG_RCU_NOCB_CPU_NONE=y
+CONFIG_RCU_NOCB_CPU_ZERO=n
+CONFIG_RCU_NOCB_CPU_ALL=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_DEBUG_OBJECTS=y
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_SLUB=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-hp
new file mode 100644
index 00000000000..588bc70420c
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/P7-4-T-NH-SD-SMP-hp
@@ -0,0 +1,30 @@
+CONFIG_RCU_TRACE=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=2
+CONFIG_NR_CPUS=16
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_RCU_NOCB_CPU=y
+CONFIG_RCU_NOCB_CPU_NONE=n
+CONFIG_RCU_NOCB_CPU_ZERO=y
+CONFIG_RCU_NOCB_CPU_ALL=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_DEBUG_OBJECTS=y
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_SLUB=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/PT1-nh b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/PT1-nh
new file mode 100644
index 00000000000..e3361c3894a
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/PT1-nh
@@ -0,0 +1,23 @@
+CONFIG_TINY_PREEMPT_RCU=y
+CONFIG_RCU_BOOST=y
+CONFIG_RCU_BOOST_PRIO=2
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=n
+#
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.12/PT2-NH b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/PT2-NH
new file mode 100644
index 00000000000..64abfc3b4d9
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.12/PT2-NH
@@ -0,0 +1,22 @@
+CONFIG_TINY_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=y
+#
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/CFLIST b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/CFLIST
new file mode 100644
index 00000000000..18223947bbc
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/CFLIST
@@ -0,0 +1,14 @@
+P1-S-T-NH-SD-SMP-HP
+P2-2-t-nh-sd-SMP-hp
+P3-3-T-nh-SD-SMP-hp
+P4-A-t-NH-sd-SMP-HP
+P5-U-T-NH-sd-SMP-hp
+N1-S-T-NH-SD-SMP-HP
+N2-2-t-nh-sd-SMP-hp
+N3-3-T-nh-SD-SMP-hp
+N4-A-t-NH-sd-SMP-HP
+N5-U-T-NH-sd-SMP-hp
+PT1-nh
+PT2-NH
+NT1-nh
+NT3-NH
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N1-S-T-NH-SD-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N1-S-T-NH-SD-SMP-HP
new file mode 100644
index 00000000000..d81e11d280a
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N1-S-T-NH-SD-SMP-HP
@@ -0,0 +1,19 @@
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_FAST_NO_HZ=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=8
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N2-2-t-nh-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N2-2-t-nh-sd-SMP-hp
new file mode 100644
index 00000000000..02e418572b1
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N2-2-t-nh-sd-SMP-hp
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=4
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N3-3-T-nh-SD-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N3-3-T-nh-SD-SMP-hp
new file mode 100644
index 00000000000..b3100f69c8c
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N3-3-T-nh-SD-SMP-hp
@@ -0,0 +1,22 @@
+CONFIG_RCU_TRACE=y
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=2
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N4-A-t-NH-sd-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N4-A-t-NH-sd-SMP-HP
new file mode 100644
index 00000000000..c56b4453072
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N4-A-t-NH-sd-SMP-HP
@@ -0,0 +1,18 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N5-U-T-NH-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N5-U-T-NH-sd-SMP-hp
new file mode 100644
index 00000000000..90d924fea9e
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/N5-U-T-NH-sd-SMP-hp
@@ -0,0 +1,22 @@
+CONFIG_RCU_TRACE=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=y
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/NT1-nh b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/NT1-nh
new file mode 100644
index 00000000000..023f312a931
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/NT1-nh
@@ -0,0 +1,23 @@
+#CHECK#CONFIG_TINY_RCU=y
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=n
+#
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/NT3-NH b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/NT3-NH
new file mode 100644
index 00000000000..6fd0235dae7
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/NT3-NH
@@ -0,0 +1,20 @@
+#CHECK#CONFIG_TINY_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=y
+#
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P1-S-T-NH-SD-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P1-S-T-NH-SD-SMP-HP
new file mode 100644
index 00000000000..9647c44cf4b
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P1-S-T-NH-SD-SMP-HP
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_RCU_FAST_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=8
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P2-2-t-nh-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P2-2-t-nh-sd-SMP-hp
new file mode 100644
index 00000000000..0f3b667d2a9
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P2-2-t-nh-sd-SMP-hp
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=4
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P3-3-T-nh-SD-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P3-3-T-nh-SD-SMP-hp
new file mode 100644
index 00000000000..b035e141bf2
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P3-3-T-nh-SD-SMP-hp
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=y
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=2
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P4-A-t-NH-sd-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P4-A-t-NH-sd-SMP-HP
new file mode 100644
index 00000000000..3ccf6a9447f
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P4-A-t-NH-sd-SMP-HP
@@ -0,0 +1,22 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_RT_MUTEXES=y
+CONFIG_RCU_BOOST=y
+CONFIG_RCU_BOOST_PRIO=2
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P5-U-T-NH-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P5-U-T-NH-sd-SMP-hp
new file mode 100644
index 00000000000..ef624ce73d8
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/P5-U-T-NH-sd-SMP-hp
@@ -0,0 +1,28 @@
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=y
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_PROVE_RCU_DELAY=y
+CONFIG_DEBUG_OBJECTS=y
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=y
+CONFIG_RT_MUTEXES=y
+CONFIG_RCU_BOOST=y
+CONFIG_RCU_BOOST_PRIO=2
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/PT1-nh b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/PT1-nh
new file mode 100644
index 00000000000..e3361c3894a
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/PT1-nh
@@ -0,0 +1,23 @@
+CONFIG_TINY_PREEMPT_RCU=y
+CONFIG_RCU_BOOST=y
+CONFIG_RCU_BOOST_PRIO=2
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=n
+#
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/PT2-NH b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/PT2-NH
new file mode 100644
index 00000000000..64abfc3b4d9
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/PT2-NH
@@ -0,0 +1,22 @@
+CONFIG_TINY_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=y
+#
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.3/ver_functions.sh b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/ver_functions.sh
new file mode 100644
index 00000000000..bae55692ce6
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.3/ver_functions.sh
@@ -0,0 +1,44 @@
+#!/bin/bash
+#
+# Kernel-version-dependent shell functions for the rest of the scripts.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2013
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+# rcutorture_param_onoff bootparam-string config-file
+#
+# Adds onoff rcutorture module parameters to kernels having it.
+rcutorture_param_onoff () {
+ if ! bootparam_hotplug_cpu "$1" && configfrag_hotplug_cpu "$2"
+ then
+ echo CPU-hotplug kernel, adding rcutorture onoff. 1>&2
+ echo rcutorture.onoff_interval=3 rcutorture.onoff_holdoff=30
+ fi
+}
+
+# per_version_boot_params bootparam-string config-file seconds
+#
+# Adds per-version torture-module parameters to kernels supporting them.
+per_version_boot_params () {
+ echo $1 `rcutorture_param_onoff "$1" "$2"` \
+ rcutorture.stat_interval=15 \
+ rcutorture.shutdown_secs=$3 \
+ rcutorture.rcutorture_runnable=1 \
+ rcutorture.test_no_idle_hz=1 \
+ rcutorture.verbose=1
+}
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/CFLIST b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/CFLIST
new file mode 100644
index 00000000000..18223947bbc
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/CFLIST
@@ -0,0 +1,14 @@
+P1-S-T-NH-SD-SMP-HP
+P2-2-t-nh-sd-SMP-hp
+P3-3-T-nh-SD-SMP-hp
+P4-A-t-NH-sd-SMP-HP
+P5-U-T-NH-sd-SMP-hp
+N1-S-T-NH-SD-SMP-HP
+N2-2-t-nh-sd-SMP-hp
+N3-3-T-nh-SD-SMP-hp
+N4-A-t-NH-sd-SMP-HP
+N5-U-T-NH-sd-SMP-hp
+PT1-nh
+PT2-NH
+NT1-nh
+NT3-NH
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N1-S-T-NH-SD-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N1-S-T-NH-SD-SMP-HP
new file mode 100644
index 00000000000..d81e11d280a
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N1-S-T-NH-SD-SMP-HP
@@ -0,0 +1,19 @@
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_FAST_NO_HZ=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=8
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N2-2-t-nh-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N2-2-t-nh-sd-SMP-hp
new file mode 100644
index 00000000000..02e418572b1
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N2-2-t-nh-sd-SMP-hp
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=4
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N3-3-T-nh-SD-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N3-3-T-nh-SD-SMP-hp
new file mode 100644
index 00000000000..b3100f69c8c
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N3-3-T-nh-SD-SMP-hp
@@ -0,0 +1,22 @@
+CONFIG_RCU_TRACE=y
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=2
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N4-A-t-NH-sd-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N4-A-t-NH-sd-SMP-HP
new file mode 100644
index 00000000000..c56b4453072
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N4-A-t-NH-sd-SMP-HP
@@ -0,0 +1,18 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N5-U-T-NH-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N5-U-T-NH-sd-SMP-hp
new file mode 100644
index 00000000000..90d924fea9e
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/N5-U-T-NH-sd-SMP-hp
@@ -0,0 +1,22 @@
+CONFIG_RCU_TRACE=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=y
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+#CHECK#CONFIG_TREE_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/NT1-nh b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/NT1-nh
new file mode 100644
index 00000000000..023f312a931
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/NT1-nh
@@ -0,0 +1,23 @@
+#CHECK#CONFIG_TINY_RCU=y
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=n
+#
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/NT3-NH b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/NT3-NH
new file mode 100644
index 00000000000..6fd0235dae7
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/NT3-NH
@@ -0,0 +1,20 @@
+#CHECK#CONFIG_TINY_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=y
+#
+CONFIG_PREEMPT_NONE=y
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=n
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P1-S-T-NH-SD-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P1-S-T-NH-SD-SMP-HP
new file mode 100644
index 00000000000..9647c44cf4b
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P1-S-T-NH-SD-SMP-HP
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_RCU_FAST_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=8
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P2-2-t-nh-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P2-2-t-nh-sd-SMP-hp
new file mode 100644
index 00000000000..0f3b667d2a9
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P2-2-t-nh-sd-SMP-hp
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=4
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P3-3-T-nh-SD-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P3-3-T-nh-SD-SMP-hp
new file mode 100644
index 00000000000..b035e141bf2
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P3-3-T-nh-SD-SMP-hp
@@ -0,0 +1,20 @@
+CONFIG_RCU_TRACE=y
+CONFIG_NO_HZ=n
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=2
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P4-A-t-NH-sd-SMP-HP b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P4-A-t-NH-sd-SMP-HP
new file mode 100644
index 00000000000..3ccf6a9447f
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P4-A-t-NH-sd-SMP-HP
@@ -0,0 +1,22 @@
+CONFIG_RCU_TRACE=n
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=n
+CONFIG_HOTPLUG_CPU=y
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_RT_MUTEXES=y
+CONFIG_RCU_BOOST=y
+CONFIG_RCU_BOOST_PRIO=2
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P5-U-T-NH-sd-SMP-hp b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P5-U-T-NH-sd-SMP-hp
new file mode 100644
index 00000000000..ef624ce73d8
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/P5-U-T-NH-sd-SMP-hp
@@ -0,0 +1,28 @@
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_CPU_STALL_INFO=y
+CONFIG_NO_HZ=y
+CONFIG_SMP=y
+CONFIG_RCU_FANOUT=6
+CONFIG_NR_CPUS=8
+CONFIG_RCU_FANOUT_EXACT=y
+CONFIG_HOTPLUG_CPU=n
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+#CHECK#CONFIG_TREE_PREEMPT_RCU=y
+CONFIG_DEBUG_KERNEL=y
+CONFIG_PROVE_RCU_DELAY=y
+CONFIG_DEBUG_OBJECTS=y
+CONFIG_DEBUG_OBJECTS_RCU_HEAD=y
+CONFIG_RT_MUTEXES=y
+CONFIG_RCU_BOOST=y
+CONFIG_RCU_BOOST_PRIO=2
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/PT1-nh b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/PT1-nh
new file mode 100644
index 00000000000..e3361c3894a
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/PT1-nh
@@ -0,0 +1,23 @@
+CONFIG_TINY_PREEMPT_RCU=y
+CONFIG_RCU_BOOST=y
+CONFIG_RCU_BOOST_PRIO=2
+CONFIG_RCU_TRACE=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=n
+#
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/PT2-NH b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/PT2-NH
new file mode 100644
index 00000000000..64abfc3b4d9
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/PT2-NH
@@ -0,0 +1,22 @@
+CONFIG_TINY_PREEMPT_RCU=y
+CONFIG_RCU_TORTURE_TEST=m
+CONFIG_MODULE_UNLOAD=y
+CONFIG_SUSPEND=n
+CONFIG_HIBERNATION=n
+#
+CONFIG_SMP=n
+#
+CONFIG_HOTPLUG_CPU=n
+#
+CONFIG_NO_HZ=y
+#
+CONFIG_PREEMPT_NONE=n
+CONFIG_PREEMPT_VOLUNTARY=n
+CONFIG_PREEMPT=y
+CONFIG_PROVE_LOCKING=y
+CONFIG_PROVE_RCU=y
+CONFIG_SYSFS_DEPRECATED_V2=y
+CONFIG_IKCONFIG=y
+CONFIG_IKCONFIG_PROC=y
+CONFIG_PRINTK_TIME=y
+
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/v3.5/ver_functions.sh b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/ver_functions.sh
new file mode 100644
index 00000000000..8977d8d31b1
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/v3.5/ver_functions.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+#
+# Kernel-version-dependent shell functions for the rest of the scripts.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2013
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+# rcutorture_param_n_barrier_cbs bootparam-string
+#
+# Adds n_barrier_cbs rcutorture module parameter to kernels having it.
+rcutorture_param_n_barrier_cbs () {
+ if echo $1 | grep -q "rcutorture\.n_barrier_cbs"
+ then
+ :
+ else
+ echo rcutorture.n_barrier_cbs=4
+ fi
+}
+
+# rcutorture_param_onoff bootparam-string config-file
+#
+# Adds onoff rcutorture module parameters to kernels having it.
+rcutorture_param_onoff () {
+ if ! bootparam_hotplug_cpu "$1" && configfrag_hotplug_cpu "$2"
+ then
+ echo CPU-hotplug kernel, adding rcutorture onoff. 1>&2
+ echo rcutorture.onoff_interval=3 rcutorture.onoff_holdoff=30
+ fi
+}
+
+# per_version_boot_params bootparam-string config-file seconds
+#
+# Adds per-version torture-module parameters to kernels supporting them.
+per_version_boot_params () {
+ echo $1 `rcutorture_param_onoff "$1" "$2"` \
+ `rcutorture_param_n_barrier_cbs "$1"` \
+ rcutorture.stat_interval=15 \
+ rcutorture.shutdown_secs=$3 \
+ rcutorture.rcutorture_runnable=1 \
+ rcutorture.test_no_idle_hz=1 \
+ rcutorture.verbose=1
+}
diff --git a/tools/testing/selftests/rcutorture/configs/rcu/ver_functions.sh b/tools/testing/selftests/rcutorture/configs/rcu/ver_functions.sh
new file mode 100644
index 00000000000..8977d8d31b1
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/configs/rcu/ver_functions.sh
@@ -0,0 +1,57 @@
+#!/bin/bash
+#
+# Kernel-version-dependent shell functions for the rest of the scripts.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, you can access it online at
+# http://www.gnu.org/licenses/gpl-2.0.html.
+#
+# Copyright (C) IBM Corporation, 2013
+#
+# Authors: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
+
+# rcutorture_param_n_barrier_cbs bootparam-string
+#
+# Adds n_barrier_cbs rcutorture module parameter to kernels having it.
+rcutorture_param_n_barrier_cbs () {
+ if echo $1 | grep -q "rcutorture\.n_barrier_cbs"
+ then
+ :
+ else
+ echo rcutorture.n_barrier_cbs=4
+ fi
+}
+
+# rcutorture_param_onoff bootparam-string config-file
+#
+# Adds onoff rcutorture module parameters to kernels having it.
+rcutorture_param_onoff () {
+ if ! bootparam_hotplug_cpu "$1" && configfrag_hotplug_cpu "$2"
+ then
+ echo CPU-hotplug kernel, adding rcutorture onoff. 1>&2
+ echo rcutorture.onoff_interval=3 rcutorture.onoff_holdoff=30
+ fi
+}
+
+# per_version_boot_params bootparam-string config-file seconds
+#
+# Adds per-version torture-module parameters to kernels supporting them.
+per_version_boot_params () {
+ echo $1 `rcutorture_param_onoff "$1" "$2"` \
+ `rcutorture_param_n_barrier_cbs "$1"` \
+ rcutorture.stat_interval=15 \
+ rcutorture.shutdown_secs=$3 \
+ rcutorture.rcutorture_runnable=1 \
+ rcutorture.test_no_idle_hz=1 \
+ rcutorture.verbose=1
+}
diff --git a/tools/testing/selftests/rcutorture/doc/TINY_RCU.txt b/tools/testing/selftests/rcutorture/doc/TINY_RCU.txt
new file mode 100644
index 00000000000..28db67b54e5
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/doc/TINY_RCU.txt
@@ -0,0 +1,40 @@
+This document gives a brief rationale for the TINY_RCU test cases.
+
+
+Kconfig Parameters:
+
+CONFIG_DEBUG_LOCK_ALLOC -- Do all three and none of the three.
+CONFIG_PREEMPT_COUNT
+CONFIG_RCU_TRACE
+
+The theory here is that randconfig testing will hit the other six possible
+combinations of these parameters.
+
+
+Kconfig Parameters Ignored:
+
+CONFIG_DEBUG_OBJECTS_RCU_HEAD
+CONFIG_PROVE_RCU
+
+ In common code tested by TREE_RCU test cases.
+
+CONFIG_NO_HZ_FULL_SYSIDLE
+CONFIG_RCU_NOCB_CPU
+CONFIG_RCU_USER_QS
+
+ Meaningless for TINY_RCU.
+
+CONFIG_RCU_STALL_COMMON
+CONFIG_RCU_TORTURE_TEST
+
+ Redundant with CONFIG_RCU_TRACE.
+
+CONFIG_HOTPLUG_CPU
+CONFIG_PREEMPT
+CONFIG_PREEMPT_RCU
+CONFIG_SMP
+CONFIG_TINY_RCU
+CONFIG_TREE_PREEMPT_RCU
+CONFIG_TREE_RCU
+
+ All forced by CONFIG_TINY_RCU.
diff --git a/tools/testing/selftests/rcutorture/doc/TREE_RCU-kconfig.txt b/tools/testing/selftests/rcutorture/doc/TREE_RCU-kconfig.txt
new file mode 100644
index 00000000000..adbb76cffb4
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/doc/TREE_RCU-kconfig.txt
@@ -0,0 +1,95 @@
+This document gives a brief rationale for the TREE_RCU-related test
+cases, a group that includes TREE_PREEMPT_RCU.
+
+
+Kconfig Parameters:
+
+CONFIG_DEBUG_LOCK_ALLOC -- Do three, covering CONFIG_PROVE_LOCKING & not.
+CONFIG_DEBUG_OBJECTS_RCU_HEAD -- Do one.
+CONFIG_HOTPLUG_CPU -- Do half. (Every second.)
+CONFIG_HZ_PERIODIC -- Do one.
+CONFIG_NO_HZ_IDLE -- Do those not otherwise specified. (Groups of two.)
+CONFIG_NO_HZ_FULL -- Do two, one with CONFIG_NO_HZ_FULL_SYSIDLE.
+CONFIG_NO_HZ_FULL_SYSIDLE -- Do one.
+CONFIG_PREEMPT -- Do half. (First three and #8.)
+CONFIG_PROVE_LOCKING -- Do all but two, covering CONFIG_PROVE_RCU and not.
+CONFIG_PROVE_RCU -- Do all but one under CONFIG_PROVE_LOCKING.
+CONFIG_PROVE_RCU_DELAY -- Do one.
+CONFIG_RCU_BOOST -- one of TREE_PREEMPT_RCU.
+CONFIG_RCU_BOOST_PRIO -- set to 2 for _BOOST testing.
+CONFIG_RCU_CPU_STALL_INFO -- do one with and without _VERBOSE.
+CONFIG_RCU_CPU_STALL_VERBOSE -- do one with and without _INFO.
+CONFIG_RCU_FANOUT -- Cover hierarchy as currently, but overlap with others.
+CONFIG_RCU_FANOUT_EXACT -- Do one.
+CONFIG_RCU_FANOUT_LEAF -- Do one non-default.
+CONFIG_RCU_FAST_NO_HZ -- Do one, but not with CONFIG_RCU_NOCB_CPU_ALL.
+CONFIG_RCU_NOCB_CPU -- Do three, see below.
+CONFIG_RCU_NOCB_CPU_ALL -- Do one.
+CONFIG_RCU_NOCB_CPU_NONE -- Do one.
+CONFIG_RCU_NOCB_CPU_ZERO -- Do one.
+CONFIG_RCU_TRACE -- Do half.
+CONFIG_SMP -- Need one !SMP for TREE_PREEMPT_RCU.
+RCU-bh: Do one with PREEMPT and one with !PREEMPT.
+RCU-sched: Do one with PREEMPT but not BOOST.
+
+
+Hierarchy:
+
+TREE01. CONFIG_NR_CPUS=8, CONFIG_RCU_FANOUT=8, CONFIG_RCU_FANOUT_EXACT=n.
+TREE02. CONFIG_NR_CPUS=8, CONFIG_RCU_FANOUT=3, CONFIG_RCU_FANOUT_EXACT=n,
+ CONFIG_RCU_FANOUT_LEAF=3.
+TREE03. CONFIG_NR_CPUS=8, CONFIG_RCU_FANOUT=4, CONFIG_RCU_FANOUT_EXACT=n,
+ CONFIG_RCU_FANOUT_LEAF=4.
+TREE04. CONFIG_NR_CPUS=8, CONFIG_RCU_FANOUT=2, CONFIG_RCU_FANOUT_EXACT=n,
+ CONFIG_RCU_FANOUT_LEAF=2.
+TREE05. CONFIG_NR_CPUS=8, CONFIG_RCU_FANOUT=6, CONFIG_RCU_FANOUT_EXACT=n
+ CONFIG_RCU_FANOUT_LEAF=6.
+TREE06. CONFIG_NR_CPUS=8, CONFIG_RCU_FANOUT=6, CONFIG_RCU_FANOUT_EXACT=y
+ CONFIG_RCU_FANOUT_LEAF=6.
+TREE07. CONFIG_NR_CPUS=16, CONFIG_RCU_FANOUT=2, CONFIG_RCU_FANOUT_EXACT=n,
+ CONFIG_RCU_FANOUT_LEAF=2.
+TREE08. CONFIG_NR_CPUS=16, CONFIG_RCU_FANOUT=3, CONFIG_RCU_FANOUT_EXACT=y,
+ CONFIG_RCU_FANOUT_LEAF=2.
+TREE09. CONFIG_NR_CPUS=1.
+
+
+Kconfig Parameters Ignored:
+
+CONFIG_64BIT
+
+ Used only to check CONFIG_RCU_FANOUT value, inspection suffices.
+
+CONFIG_NO_HZ_FULL_SYSIDLE_SMALL
+
+ Defer until Frederic uses this.
+
+CONFIG_PREEMPT_COUNT
+CONFIG_PREEMPT_RCU
+
+ Redundant with CONFIG_PREEMPT, ignore.
+
+CONFIG_RCU_BOOST_DELAY
+
+ Inspection suffices, ignore.
+
+CONFIG_RCU_CPU_STALL_TIMEOUT
+
+ Inspection suffices, ignore.
+
+CONFIG_RCU_STALL_COMMON
+
+ Implied by TREE_RCU and TREE_PREEMPT_RCU.
+
+CONFIG_RCU_TORTURE_TEST
+CONFIG_RCU_TORTURE_TEST_RUNNABLE
+
+ Always used in KVM testing.
+
+CONFIG_RCU_USER_QS
+
+ Redundant with CONFIG_NO_HZ_FULL.
+
+CONFIG_TREE_PREEMPT_RCU
+CONFIG_TREE_RCU
+
+ These are controlled by CONFIG_PREEMPT.
diff --git a/tools/testing/selftests/rcutorture/doc/initrd.txt b/tools/testing/selftests/rcutorture/doc/initrd.txt
new file mode 100644
index 00000000000..49d134c25c0
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/doc/initrd.txt
@@ -0,0 +1,90 @@
+This document describes one way to create the initrd directory hierarchy
+in order to allow an initrd to be built into your kernel. The trick
+here is to steal the initrd file used on your Linux laptop, Ubuntu in
+this case. There are probably much better ways of doing this.
+
+That said, here are the commands:
+
+------------------------------------------------------------------------
+zcat /initrd.img > /tmp/initrd.img.zcat
+mkdir initrd
+cd initrd
+cpio -id < /tmp/initrd.img.zcat
+------------------------------------------------------------------------
+
+Interestingly enough, if you are running rcutorture, you don't really
+need userspace in many cases. Running without userspace has the
+advantage of allowing you to test your kernel independently of the
+distro in place, the root-filesystem layout, and so on. To make this
+happen, put the following script in the initrd's tree's "/init" file,
+with 0755 mode.
+
+------------------------------------------------------------------------
+#!/bin/sh
+
+[ -d /dev ] || mkdir -m 0755 /dev
+[ -d /root ] || mkdir -m 0700 /root
+[ -d /sys ] || mkdir /sys
+[ -d /proc ] || mkdir /proc
+[ -d /tmp ] || mkdir /tmp
+mkdir -p /var/lock
+mount -t sysfs -o nodev,noexec,nosuid sysfs /sys
+mount -t proc -o nodev,noexec,nosuid proc /proc
+# Some things don't work properly without /etc/mtab.
+ln -sf /proc/mounts /etc/mtab
+
+# Note that this only becomes /dev on the real filesystem if udev's scripts
+# are used; which they will be, but it's worth pointing out
+if ! mount -t devtmpfs -o mode=0755 udev /dev; then
+ echo "W: devtmpfs not available, falling back to tmpfs for /dev"
+ mount -t tmpfs -o mode=0755 udev /dev
+ [ -e /dev/console ] || mknod --mode=600 /dev/console c 5 1
+ [ -e /dev/kmsg ] || mknod --mode=644 /dev/kmsg c 1 11
+ [ -e /dev/null ] || mknod --mode=666 /dev/null c 1 3
+fi
+
+mkdir /dev/pts
+mount -t devpts -o noexec,nosuid,gid=5,mode=0620 devpts /dev/pts || true
+mount -t tmpfs -o "nosuid,size=20%,mode=0755" tmpfs /run
+mkdir /run/initramfs
+# compatibility symlink for the pre-oneiric locations
+ln -s /run/initramfs /dev/.initramfs
+
+# Export relevant variables
+export ROOT=
+export ROOTDELAY=
+export ROOTFLAGS=
+export ROOTFSTYPE=
+export IP=
+export BOOT=
+export BOOTIF=
+export UBIMTD=
+export break=
+export init=/sbin/init
+export quiet=n
+export readonly=y
+export rootmnt=/root
+export debug=
+export panic=
+export blacklist=
+export resume=
+export resume_offset=
+export recovery=
+
+for i in /sys/devices/system/cpu/cpu*/online
+do
+ case $i in
+ '/sys/devices/system/cpu/cpu0/online')
+ ;;
+ '/sys/devices/system/cpu/cpu*/online')
+ ;;
+ *)
+ echo 1 > $i
+ ;;
+ esac
+done
+
+while :
+do
+ sleep 10
+done
diff --git a/tools/testing/selftests/rcutorture/doc/rcu-test-image.txt b/tools/testing/selftests/rcutorture/doc/rcu-test-image.txt
new file mode 100644
index 00000000000..66efb59a1bd
--- /dev/null
+++ b/tools/testing/selftests/rcutorture/doc/rcu-test-image.txt
@@ -0,0 +1,42 @@
+This document describes one way to created the rcu-test-image file
+that contains the filesystem used by the guest-OS kernel. There are
+probably much better ways of doing this, and this filesystem could no
+doubt be smaller. It is probably also possible to simply download
+an appropriate image from any number of places.
+
+That said, here are the commands:
+
+------------------------------------------------------------------------
+dd if=/dev/zero of=rcu-test-image bs=400M count=1
+mkfs.ext3 ./rcu-test-image
+sudo mount -o loop ./rcu-test-image /mnt
+
+# Replace "precise" below with your favorite Ubuntu release.
+# Empirical evidence says this image will work for 64-bit, but...
+# Note that debootstrap does take a few minutes to run. Or longer.
+sudo debootstrap --verbose --arch i386 precise /mnt http://archive.ubuntu.com/ubuntu
+cat << '___EOF___' | sudo dd of=/mnt/etc/fstab
+# UNCONFIGURED FSTAB FOR BASE SYSTEM
+#
+/dev/vda / ext3 defaults 1 1
+dev /dev tmpfs rw 0 0
+tmpfs /dev/shm tmpfs defaults 0 0
+devpts /dev/pts devpts gid=5,mode=620 0 0
+sysfs /sys sysfs defaults 0 0
+proc /proc proc defaults 0 0
+___EOF___
+sudo umount /mnt
+------------------------------------------------------------------------
+
+
+References:
+
+ http://sripathikodi.blogspot.com/2010/02/creating-kvm-bootable-fedora-system.html
+ https://help.ubuntu.com/community/KVM/CreateGuests
+ https://help.ubuntu.com/community/JeOSVMBuilder
+ http://wiki.libvirt.org/page/UbuntuKVMWalkthrough
+ http://www.moe.co.uk/2011/01/07/pci_add_option_rom-failed-to-find-romfile-pxe-rtl8139-bin/ -- "apt-get install kvm-pxe"
+ http://www.landley.net/writing/rootfs-howto.html
+ http://en.wikipedia.org/wiki/Initrd
+ http://en.wikipedia.org/wiki/Cpio
+ http://wiki.libvirt.org/page/UbuntuKVMWalkthrough
diff --git a/tools/testing/selftests/sysctl/Makefile b/tools/testing/selftests/sysctl/Makefile
new file mode 100644
index 00000000000..0a92adaf086
--- /dev/null
+++ b/tools/testing/selftests/sysctl/Makefile
@@ -0,0 +1,19 @@
+# Makefile for sysctl selftests.
+# Expects kernel.sysctl_writes_strict=1.
+
+# No binaries, but make sure arg-less "make" doesn't trigger "run_tests".
+all:
+
+# Allow specific tests to be selected.
+test_num:
+ @/bin/sh ./run_numerictests
+
+test_string:
+ @/bin/sh ./run_stringtests
+
+run_tests: all test_num test_string
+
+# Nothing to clean up.
+clean:
+
+.PHONY: all run_tests clean test_num test_string
diff --git a/tools/testing/selftests/sysctl/common_tests b/tools/testing/selftests/sysctl/common_tests
new file mode 100644
index 00000000000..17d534b1b7b
--- /dev/null
+++ b/tools/testing/selftests/sysctl/common_tests
@@ -0,0 +1,109 @@
+#!/bin/sh
+
+TEST_FILE=$(mktemp)
+
+echo "== Testing sysctl behavior against ${TARGET} =="
+
+set_orig()
+{
+ echo "${ORIG}" > "${TARGET}"
+}
+
+set_test()
+{
+ echo "${TEST_STR}" > "${TARGET}"
+}
+
+verify()
+{
+ local seen
+ seen=$(cat "$1")
+ if [ "${seen}" != "${TEST_STR}" ]; then
+ return 1
+ fi
+ return 0
+}
+
+trap 'set_orig; rm -f "${TEST_FILE}"' EXIT
+
+rc=0
+
+echo -n "Writing test file ... "
+echo "${TEST_STR}" > "${TEST_FILE}"
+if ! verify "${TEST_FILE}"; then
+ echo "FAIL" >&2
+ exit 1
+else
+ echo "ok"
+fi
+
+echo -n "Checking sysctl is not set to test value ... "
+if verify "${TARGET}"; then
+ echo "FAIL" >&2
+ exit 1
+else
+ echo "ok"
+fi
+
+echo -n "Writing sysctl from shell ... "
+set_test
+if ! verify "${TARGET}"; then
+ echo "FAIL" >&2
+ exit 1
+else
+ echo "ok"
+fi
+
+echo -n "Resetting sysctl to original value ... "
+set_orig
+if verify "${TARGET}"; then
+ echo "FAIL" >&2
+ exit 1
+else
+ echo "ok"
+fi
+
+# Now that we've validated the sanity of "set_test" and "set_orig",
+# we can use those functions to set starting states before running
+# specific behavioral tests.
+
+echo -n "Writing entire sysctl in single write ... "
+set_orig
+dd if="${TEST_FILE}" of="${TARGET}" bs=4096 2>/dev/null
+if ! verify "${TARGET}"; then
+ echo "FAIL" >&2
+ rc=1
+else
+ echo "ok"
+fi
+
+echo -n "Writing middle of sysctl after synchronized seek ... "
+set_test
+dd if="${TEST_FILE}" of="${TARGET}" bs=1 seek=1 skip=1 2>/dev/null
+if ! verify "${TARGET}"; then
+ echo "FAIL" >&2
+ rc=1
+else
+ echo "ok"
+fi
+
+echo -n "Writing beyond end of sysctl ... "
+set_orig
+dd if="${TEST_FILE}" of="${TARGET}" bs=20 seek=2 2>/dev/null
+if verify "${TARGET}"; then
+ echo "FAIL" >&2
+ rc=1
+else
+ echo "ok"
+fi
+
+echo -n "Writing sysctl with multiple long writes ... "
+set_orig
+(perl -e 'print "A" x 50;'; echo "${TEST_STR}") | \
+ dd of="${TARGET}" bs=50 2>/dev/null
+if verify "${TARGET}"; then
+ echo "FAIL" >&2
+ rc=1
+else
+ echo "ok"
+fi
diff --git a/tools/testing/selftests/sysctl/run_numerictests b/tools/testing/selftests/sysctl/run_numerictests
new file mode 100644
index 00000000000..8510f93f2d1
--- /dev/null
+++ b/tools/testing/selftests/sysctl/run_numerictests
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+SYSCTL="/proc/sys"
+TARGET="${SYSCTL}/vm/swappiness"
+ORIG=$(cat "${TARGET}")
+TEST_STR=$(( $ORIG + 1 ))
+
+. ./common_tests
+
+exit $rc
diff --git a/tools/testing/selftests/sysctl/run_stringtests b/tools/testing/selftests/sysctl/run_stringtests
new file mode 100644
index 00000000000..90a9293d520
--- /dev/null
+++ b/tools/testing/selftests/sysctl/run_stringtests
@@ -0,0 +1,77 @@
+#!/bin/sh
+
+SYSCTL="/proc/sys"
+TARGET="${SYSCTL}/kernel/domainname"
+ORIG=$(cat "${TARGET}")
+TEST_STR="Testing sysctl"
+
+. ./common_tests
+
+# Only string sysctls support seeking/appending.
+MAXLEN=65
+
+echo -n "Writing entire sysctl in short writes ... "
+set_orig
+dd if="${TEST_FILE}" of="${TARGET}" bs=1 2>/dev/null
+if ! verify "${TARGET}"; then
+ echo "FAIL" >&2
+ rc=1
+else
+ echo "ok"
+fi
+
+echo -n "Writing middle of sysctl after unsynchronized seek ... "
+set_test
+dd if="${TEST_FILE}" of="${TARGET}" bs=1 seek=1 2>/dev/null
+if verify "${TARGET}"; then
+ echo "FAIL" >&2
+ rc=1
+else
+ echo "ok"
+fi
+
+echo -n "Checking sysctl maxlen is at least $MAXLEN ... "
+set_orig
+perl -e 'print "A" x ('"${MAXLEN}"'-2), "B";' | \
+ dd of="${TARGET}" bs="${MAXLEN}" 2>/dev/null
+if ! grep -q B "${TARGET}"; then
+ echo "FAIL" >&2
+ rc=1
+else
+ echo "ok"
+fi
+
+echo -n "Checking sysctl keeps original string on overflow append ... "
+set_orig
+perl -e 'print "A" x ('"${MAXLEN}"'-1), "B";' | \
+ dd of="${TARGET}" bs=$(( MAXLEN - 1 )) 2>/dev/null
+if grep -q B "${TARGET}"; then
+ echo "FAIL" >&2
+ rc=1
+else
+ echo "ok"
+fi
+
+echo -n "Checking sysctl stays NULL terminated on write ... "
+set_orig
+perl -e 'print "A" x ('"${MAXLEN}"'-1), "B";' | \
+ dd of="${TARGET}" bs="${MAXLEN}" 2>/dev/null
+if grep -q B "${TARGET}"; then
+ echo "FAIL" >&2
+ rc=1
+else
+ echo "ok"
+fi
+
+echo -n "Checking sysctl stays NULL terminated on overwrite ... "
+set_orig
+perl -e 'print "A" x ('"${MAXLEN}"'-1), "BB";' | \
+ dd of="${TARGET}" bs=$(( $MAXLEN + 1 )) 2>/dev/null
+if grep -q B "${TARGET}"; then
+ echo "FAIL" >&2
+ rc=1
+else
+ echo "ok"
+fi
+
+exit $rc
diff --git a/tools/testing/selftests/timers/Makefile b/tools/testing/selftests/timers/Makefile
new file mode 100644
index 00000000000..eb2859f4ad2
--- /dev/null
+++ b/tools/testing/selftests/timers/Makefile
@@ -0,0 +1,8 @@
+all:
+ gcc posix_timers.c -o posix_timers -lrt
+
+run_tests: all
+ ./posix_timers
+
+clean:
+ rm -f ./posix_timers
diff --git a/tools/testing/selftests/timers/posix_timers.c b/tools/testing/selftests/timers/posix_timers.c
new file mode 100644
index 00000000000..41bd85559d4
--- /dev/null
+++ b/tools/testing/selftests/timers/posix_timers.c
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2013 Red Hat, Inc., Frederic Weisbecker <fweisbec@redhat.com>
+ *
+ * Licensed under the terms of the GNU GPL License version 2
+ *
+ * Selftests for a few posix timers interface.
+ *
+ * Kernel loop code stolen from Steven Rostedt <srostedt@redhat.com>
+ */
+
+#include <sys/time.h>
+#include <stdio.h>
+#include <signal.h>
+#include <unistd.h>
+#include <time.h>
+#include <pthread.h>
+
+#define DELAY 2
+#define USECS_PER_SEC 1000000
+
+static volatile int done;
+
+/* Busy loop in userspace to elapse ITIMER_VIRTUAL */
+static void user_loop(void)
+{
+ while (!done);
+}
+
+/*
+ * Try to spend as much time as possible in kernelspace
+ * to elapse ITIMER_PROF.
+ */
+static void kernel_loop(void)
+{
+ void *addr = sbrk(0);
+
+ while (!done) {
+ brk(addr + 4096);
+ brk(addr);
+ }
+}
+
+/*
+ * Sleep until ITIMER_REAL expiration.
+ */
+static void idle_loop(void)
+{
+ pause();
+}
+
+static void sig_handler(int nr)
+{
+ done = 1;
+}
+
+/*
+ * Check the expected timer expiration matches the GTOD elapsed delta since
+ * we armed the timer. Keep a 0.5 sec error margin due to various jitter.
+ */
+static int check_diff(struct timeval start, struct timeval end)
+{
+ long long diff;
+
+ diff = end.tv_usec - start.tv_usec;
+ diff += (end.tv_sec - start.tv_sec) * USECS_PER_SEC;
+
+ if (abs(diff - DELAY * USECS_PER_SEC) > USECS_PER_SEC / 2) {
+ printf("Diff too high: %lld..", diff);
+ return -1;
+ }
+
+ return 0;
+}
+
+static int check_itimer(int which)
+{
+ int err;
+ struct timeval start, end;
+ struct itimerval val = {
+ .it_value.tv_sec = DELAY,
+ };
+
+ printf("Check itimer ");
+
+ if (which == ITIMER_VIRTUAL)
+ printf("virtual... ");
+ else if (which == ITIMER_PROF)
+ printf("prof... ");
+ else if (which == ITIMER_REAL)
+ printf("real... ");
+
+ fflush(stdout);
+
+ done = 0;
+
+ if (which == ITIMER_VIRTUAL)
+ signal(SIGVTALRM, sig_handler);
+ else if (which == ITIMER_PROF)
+ signal(SIGPROF, sig_handler);
+ else if (which == ITIMER_REAL)
+ signal(SIGALRM, sig_handler);
+
+ err = gettimeofday(&start, NULL);
+ if (err < 0) {
+ perror("Can't call gettimeofday()\n");
+ return -1;
+ }
+
+ err = setitimer(which, &val, NULL);
+ if (err < 0) {
+ perror("Can't set timer\n");
+ return -1;
+ }
+
+ if (which == ITIMER_VIRTUAL)
+ user_loop();
+ else if (which == ITIMER_PROF)
+ kernel_loop();
+ else if (which == ITIMER_REAL)
+ idle_loop();
+
+ gettimeofday(&end, NULL);
+ if (err < 0) {
+ perror("Can't call gettimeofday()\n");
+ return -1;
+ }
+
+ if (!check_diff(start, end))
+ printf("[OK]\n");
+ else
+ printf("[FAIL]\n");
+
+ return 0;
+}
+
+static int check_timer_create(int which)
+{
+ int err;
+ timer_t id;
+ struct timeval start, end;
+ struct itimerspec val = {
+ .it_value.tv_sec = DELAY,
+ };
+
+ printf("Check timer_create() ");
+ if (which == CLOCK_THREAD_CPUTIME_ID) {
+ printf("per thread... ");
+ } else if (which == CLOCK_PROCESS_CPUTIME_ID) {
+ printf("per process... ");
+ }
+ fflush(stdout);
+
+ done = 0;
+ err = timer_create(which, NULL, &id);
+ if (err < 0) {
+ perror("Can't create timer\n");
+ return -1;
+ }
+ signal(SIGALRM, sig_handler);
+
+ err = gettimeofday(&start, NULL);
+ if (err < 0) {
+ perror("Can't call gettimeofday()\n");
+ return -1;
+ }
+
+ err = timer_settime(id, 0, &val, NULL);
+ if (err < 0) {
+ perror("Can't set timer\n");
+ return -1;
+ }
+
+ user_loop();
+
+ gettimeofday(&end, NULL);
+ if (err < 0) {
+ perror("Can't call gettimeofday()\n");
+ return -1;
+ }
+
+ if (!check_diff(start, end))
+ printf("[OK]\n");
+ else
+ printf("[FAIL]\n");
+
+ return 0;
+}
+
+int main(int argc, char **argv)
+{
+ int err;
+
+ printf("Testing posix timers. False negative may happen on CPU execution \n");
+ printf("based timers if other threads run on the CPU...\n");
+
+ if (check_itimer(ITIMER_VIRTUAL) < 0)
+ return -1;
+
+ if (check_itimer(ITIMER_PROF) < 0)
+ return -1;
+
+ if (check_itimer(ITIMER_REAL) < 0)
+ return -1;
+
+ if (check_timer_create(CLOCK_THREAD_CPUTIME_ID) < 0)
+ return -1;
+
+ /*
+ * It's unfortunately hard to reliably test a timer expiration
+ * on parallel multithread cputime. We could arm it to expire
+ * on DELAY * nr_threads, with nr_threads busy looping, then wait
+ * the normal DELAY since the time is elapsing nr_threads faster.
+ * But for that we need to ensure we have real physical free CPUs
+ * to ensure true parallelism. So test only one thread until we
+ * find a better solution.
+ */
+ if (check_timer_create(CLOCK_PROCESS_CPUTIME_ID) < 0)
+ return -1;
+
+ return 0;
+}
diff --git a/tools/testing/selftests/user/Makefile b/tools/testing/selftests/user/Makefile
new file mode 100644
index 00000000000..396255bd720
--- /dev/null
+++ b/tools/testing/selftests/user/Makefile
@@ -0,0 +1,13 @@
+# Makefile for user memory selftests
+
+# No binaries, but make sure arg-less "make" doesn't trigger "run_tests"
+all:
+
+run_tests: all
+ @if /sbin/modprobe test_user_copy ; then \
+ rmmod test_user_copy; \
+ echo "user_copy: ok"; \
+ else \
+ echo "user_copy: [FAIL]"; \
+ exit 1; \
+ fi
diff --git a/tools/testing/selftests/vm/.gitignore b/tools/testing/selftests/vm/.gitignore
new file mode 100644
index 00000000000..ff1bb16cec4
--- /dev/null
+++ b/tools/testing/selftests/vm/.gitignore
@@ -0,0 +1,4 @@
+hugepage-mmap
+hugepage-shm
+map_hugetlb
+thuge-gen
diff --git a/tools/testing/selftests/vm/Makefile b/tools/testing/selftests/vm/Makefile
new file mode 100644
index 00000000000..3f94e1afd6c
--- /dev/null
+++ b/tools/testing/selftests/vm/Makefile
@@ -0,0 +1,15 @@
+# Makefile for vm selftests
+
+CC = $(CROSS_COMPILE)gcc
+CFLAGS = -Wall
+BINARIES = hugepage-mmap hugepage-shm map_hugetlb thuge-gen hugetlbfstest
+
+all: $(BINARIES)
+%: %.c
+ $(CC) $(CFLAGS) -o $@ $^
+
+run_tests: all
+ @/bin/sh ./run_vmtests || (echo "vmtests: [FAIL]"; exit 1)
+
+clean:
+ $(RM) $(BINARIES)
diff --git a/tools/testing/selftests/vm/hugepage-mmap.c b/tools/testing/selftests/vm/hugepage-mmap.c
new file mode 100644
index 00000000000..a10f310d236
--- /dev/null
+++ b/tools/testing/selftests/vm/hugepage-mmap.c
@@ -0,0 +1,92 @@
+/*
+ * hugepage-mmap:
+ *
+ * Example of using huge page memory in a user application using the mmap
+ * system call. Before running this application, make sure that the
+ * administrator has mounted the hugetlbfs filesystem (on some directory
+ * like /mnt) using the command mount -t hugetlbfs nodev /mnt. In this
+ * example, the app is requesting memory of size 256MB that is backed by
+ * huge pages.
+ *
+ * For the ia64 architecture, the Linux kernel reserves Region number 4 for
+ * huge pages. That means that if one requires a fixed address, a huge page
+ * aligned address starting with 0x800000... will be required. If a fixed
+ * address is not required, the kernel will select an address in the proper
+ * range.
+ * Other architectures, such as ppc64, i386 or x86_64 are not so constrained.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+
+#define FILE_NAME "huge/hugepagefile"
+#define LENGTH (256UL*1024*1024)
+#define PROTECTION (PROT_READ | PROT_WRITE)
+
+/* Only ia64 requires this */
+#ifdef __ia64__
+#define ADDR (void *)(0x8000000000000000UL)
+#define FLAGS (MAP_SHARED | MAP_FIXED)
+#else
+#define ADDR (void *)(0x0UL)
+#define FLAGS (MAP_SHARED)
+#endif
+
+static void check_bytes(char *addr)
+{
+ printf("First hex is %x\n", *((unsigned int *)addr));
+}
+
+static void write_bytes(char *addr)
+{
+ unsigned long i;
+
+ for (i = 0; i < LENGTH; i++)
+ *(addr + i) = (char)i;
+}
+
+static int read_bytes(char *addr)
+{
+ unsigned long i;
+
+ check_bytes(addr);
+ for (i = 0; i < LENGTH; i++)
+ if (*(addr + i) != (char)i) {
+ printf("Mismatch at %lu\n", i);
+ return 1;
+ }
+ return 0;
+}
+
+int main(void)
+{
+ void *addr;
+ int fd, ret;
+
+ fd = open(FILE_NAME, O_CREAT | O_RDWR, 0755);
+ if (fd < 0) {
+ perror("Open failed");
+ exit(1);
+ }
+
+ addr = mmap(ADDR, LENGTH, PROTECTION, FLAGS, fd, 0);
+ if (addr == MAP_FAILED) {
+ perror("mmap");
+ unlink(FILE_NAME);
+ exit(1);
+ }
+
+ printf("Returned address is %p\n", addr);
+ check_bytes(addr);
+ write_bytes(addr);
+ ret = read_bytes(addr);
+
+ munmap(addr, LENGTH);
+ close(fd);
+ unlink(FILE_NAME);
+
+ return ret;
+}
diff --git a/tools/testing/selftests/vm/hugepage-shm.c b/tools/testing/selftests/vm/hugepage-shm.c
new file mode 100644
index 00000000000..0d0ef4fc0c0
--- /dev/null
+++ b/tools/testing/selftests/vm/hugepage-shm.c
@@ -0,0 +1,100 @@
+/*
+ * hugepage-shm:
+ *
+ * Example of using huge page memory in a user application using Sys V shared
+ * memory system calls. In this example the app is requesting 256MB of
+ * memory that is backed by huge pages. The application uses the flag
+ * SHM_HUGETLB in the shmget system call to inform the kernel that it is
+ * requesting huge pages.
+ *
+ * For the ia64 architecture, the Linux kernel reserves Region number 4 for
+ * huge pages. That means that if one requires a fixed address, a huge page
+ * aligned address starting with 0x800000... will be required. If a fixed
+ * address is not required, the kernel will select an address in the proper
+ * range.
+ * Other architectures, such as ppc64, i386 or x86_64 are not so constrained.
+ *
+ * Note: The default shared memory limit is quite low on many kernels,
+ * you may need to increase it via:
+ *
+ * echo 268435456 > /proc/sys/kernel/shmmax
+ *
+ * This will increase the maximum size per shared memory segment to 256MB.
+ * The other limit that you will hit eventually is shmall which is the
+ * total amount of shared memory in pages. To set it to 16GB on a system
+ * with a 4kB pagesize do:
+ *
+ * echo 4194304 > /proc/sys/kernel/shmall
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/types.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <sys/mman.h>
+
+#ifndef SHM_HUGETLB
+#define SHM_HUGETLB 04000
+#endif
+
+#define LENGTH (256UL*1024*1024)
+
+#define dprintf(x) printf(x)
+
+/* Only ia64 requires this */
+#ifdef __ia64__
+#define ADDR (void *)(0x8000000000000000UL)
+#define SHMAT_FLAGS (SHM_RND)
+#else
+#define ADDR (void *)(0x0UL)
+#define SHMAT_FLAGS (0)
+#endif
+
+int main(void)
+{
+ int shmid;
+ unsigned long i;
+ char *shmaddr;
+
+ shmid = shmget(2, LENGTH, SHM_HUGETLB | IPC_CREAT | SHM_R | SHM_W);
+ if (shmid < 0) {
+ perror("shmget");
+ exit(1);
+ }
+ printf("shmid: 0x%x\n", shmid);
+
+ shmaddr = shmat(shmid, ADDR, SHMAT_FLAGS);
+ if (shmaddr == (char *)-1) {
+ perror("Shared memory attach failure");
+ shmctl(shmid, IPC_RMID, NULL);
+ exit(2);
+ }
+ printf("shmaddr: %p\n", shmaddr);
+
+ dprintf("Starting the writes:\n");
+ for (i = 0; i < LENGTH; i++) {
+ shmaddr[i] = (char)(i);
+ if (!(i % (1024 * 1024)))
+ dprintf(".");
+ }
+ dprintf("\n");
+
+ dprintf("Starting the Check...");
+ for (i = 0; i < LENGTH; i++)
+ if (shmaddr[i] != (char)i) {
+ printf("\nIndex %lu mismatched\n", i);
+ exit(3);
+ }
+ dprintf("Done.\n");
+
+ if (shmdt((const void *)shmaddr) != 0) {
+ perror("Detach failure");
+ shmctl(shmid, IPC_RMID, NULL);
+ exit(4);
+ }
+
+ shmctl(shmid, IPC_RMID, NULL);
+
+ return 0;
+}
diff --git a/tools/testing/selftests/vm/hugetlbfstest.c b/tools/testing/selftests/vm/hugetlbfstest.c
new file mode 100644
index 00000000000..ea40ff8c239
--- /dev/null
+++ b/tools/testing/selftests/vm/hugetlbfstest.c
@@ -0,0 +1,84 @@
+#define _GNU_SOURCE
+#include <assert.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+typedef unsigned long long u64;
+
+static size_t length = 1 << 24;
+
+static u64 read_rss(void)
+{
+ char buf[4096], *s = buf;
+ int i, fd;
+ u64 rss;
+
+ fd = open("/proc/self/statm", O_RDONLY);
+ assert(fd > 2);
+ memset(buf, 0, sizeof(buf));
+ read(fd, buf, sizeof(buf) - 1);
+ for (i = 0; i < 1; i++)
+ s = strchr(s, ' ') + 1;
+ rss = strtoull(s, NULL, 10);
+ return rss << 12; /* assumes 4k pagesize */
+}
+
+static void do_mmap(int fd, int extra_flags, int unmap)
+{
+ int *p;
+ int flags = MAP_PRIVATE | MAP_POPULATE | extra_flags;
+ u64 before, after;
+
+ before = read_rss();
+ p = mmap(NULL, length, PROT_READ | PROT_WRITE, flags, fd, 0);
+ assert(p != MAP_FAILED ||
+ !"mmap returned an unexpected error");
+ after = read_rss();
+ assert(llabs(after - before - length) < 0x40000 ||
+ !"rss didn't grow as expected");
+ if (!unmap)
+ return;
+ munmap(p, length);
+ after = read_rss();
+ assert(llabs(after - before) < 0x40000 ||
+ !"rss didn't shrink as expected");
+}
+
+static int open_file(const char *path)
+{
+ int fd, err;
+
+ unlink(path);
+ fd = open(path, O_CREAT | O_RDWR | O_TRUNC | O_EXCL
+ | O_LARGEFILE | O_CLOEXEC, 0600);
+ assert(fd > 2);
+ unlink(path);
+ err = ftruncate(fd, length);
+ assert(!err);
+ return fd;
+}
+
+int main(void)
+{
+ int hugefd, fd;
+
+ fd = open_file("/dev/shm/hugetlbhog");
+ hugefd = open_file("/hugepages/hugetlbhog");
+
+ system("echo 100 > /proc/sys/vm/nr_hugepages");
+ do_mmap(-1, MAP_ANONYMOUS, 1);
+ do_mmap(fd, 0, 1);
+ do_mmap(-1, MAP_ANONYMOUS | MAP_HUGETLB, 1);
+ do_mmap(hugefd, 0, 1);
+ do_mmap(hugefd, MAP_HUGETLB, 1);
+ /* Leak the last one to test do_exit() */
+ do_mmap(-1, MAP_ANONYMOUS | MAP_HUGETLB, 0);
+ printf("oll korrekt.\n");
+ return 0;
+}
diff --git a/tools/testing/selftests/vm/map_hugetlb.c b/tools/testing/selftests/vm/map_hugetlb.c
new file mode 100644
index 00000000000..ac56639dd4a
--- /dev/null
+++ b/tools/testing/selftests/vm/map_hugetlb.c
@@ -0,0 +1,79 @@
+/*
+ * Example of using hugepage memory in a user application using the mmap
+ * system call with MAP_HUGETLB flag. Before running this program make
+ * sure the administrator has allocated enough default sized huge pages
+ * to cover the 256 MB allocation.
+ *
+ * For ia64 architecture, Linux kernel reserves Region number 4 for hugepages.
+ * That means the addresses starting with 0x800000... will need to be
+ * specified. Specifying a fixed address is not required on ppc64, i386
+ * or x86_64.
+ */
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <sys/mman.h>
+#include <fcntl.h>
+
+#define LENGTH (256UL*1024*1024)
+#define PROTECTION (PROT_READ | PROT_WRITE)
+
+#ifndef MAP_HUGETLB
+#define MAP_HUGETLB 0x40000 /* arch specific */
+#endif
+
+/* Only ia64 requires this */
+#ifdef __ia64__
+#define ADDR (void *)(0x8000000000000000UL)
+#define FLAGS (MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB | MAP_FIXED)
+#else
+#define ADDR (void *)(0x0UL)
+#define FLAGS (MAP_PRIVATE | MAP_ANONYMOUS | MAP_HUGETLB)
+#endif
+
+static void check_bytes(char *addr)
+{
+ printf("First hex is %x\n", *((unsigned int *)addr));
+}
+
+static void write_bytes(char *addr)
+{
+ unsigned long i;
+
+ for (i = 0; i < LENGTH; i++)
+ *(addr + i) = (char)i;
+}
+
+static int read_bytes(char *addr)
+{
+ unsigned long i;
+
+ check_bytes(addr);
+ for (i = 0; i < LENGTH; i++)
+ if (*(addr + i) != (char)i) {
+ printf("Mismatch at %lu\n", i);
+ return 1;
+ }
+ return 0;
+}
+
+int main(void)
+{
+ void *addr;
+ int ret;
+
+ addr = mmap(ADDR, LENGTH, PROTECTION, FLAGS, 0, 0);
+ if (addr == MAP_FAILED) {
+ perror("mmap");
+ exit(1);
+ }
+
+ printf("Returned address is %p\n", addr);
+ check_bytes(addr);
+ write_bytes(addr);
+ ret = read_bytes(addr);
+
+ munmap(addr, LENGTH);
+
+ return ret;
+}
diff --git a/tools/testing/selftests/vm/run_vmtests b/tools/testing/selftests/vm/run_vmtests
new file mode 100644
index 00000000000..c87b6812300
--- /dev/null
+++ b/tools/testing/selftests/vm/run_vmtests
@@ -0,0 +1,93 @@
+#!/bin/bash
+#please run as root
+
+#we need 256M, below is the size in kB
+needmem=262144
+mnt=./huge
+exitcode=0
+
+#get pagesize and freepages from /proc/meminfo
+while read name size unit; do
+ if [ "$name" = "HugePages_Free:" ]; then
+ freepgs=$size
+ fi
+ if [ "$name" = "Hugepagesize:" ]; then
+ pgsize=$size
+ fi
+done < /proc/meminfo
+
+#set proper nr_hugepages
+if [ -n "$freepgs" ] && [ -n "$pgsize" ]; then
+ nr_hugepgs=`cat /proc/sys/vm/nr_hugepages`
+ needpgs=`expr $needmem / $pgsize`
+ if [ $freepgs -lt $needpgs ]; then
+ lackpgs=$(( $needpgs - $freepgs ))
+ echo $(( $lackpgs + $nr_hugepgs )) > /proc/sys/vm/nr_hugepages
+ if [ $? -ne 0 ]; then
+ echo "Please run this test as root"
+ exit 1
+ fi
+ fi
+else
+ echo "no hugetlbfs support in kernel?"
+ exit 1
+fi
+
+mkdir $mnt
+mount -t hugetlbfs none $mnt
+
+echo "--------------------"
+echo "running hugepage-mmap"
+echo "--------------------"
+./hugepage-mmap
+if [ $? -ne 0 ]; then
+ echo "[FAIL]"
+ exitcode=1
+else
+ echo "[PASS]"
+fi
+
+shmmax=`cat /proc/sys/kernel/shmmax`
+shmall=`cat /proc/sys/kernel/shmall`
+echo 268435456 > /proc/sys/kernel/shmmax
+echo 4194304 > /proc/sys/kernel/shmall
+echo "--------------------"
+echo "running hugepage-shm"
+echo "--------------------"
+./hugepage-shm
+if [ $? -ne 0 ]; then
+ echo "[FAIL]"
+ exitcode=1
+else
+ echo "[PASS]"
+fi
+echo $shmmax > /proc/sys/kernel/shmmax
+echo $shmall > /proc/sys/kernel/shmall
+
+echo "--------------------"
+echo "running map_hugetlb"
+echo "--------------------"
+./map_hugetlb
+if [ $? -ne 0 ]; then
+ echo "[FAIL]"
+ exitcode=1
+else
+ echo "[PASS]"
+fi
+
+echo "--------------------"
+echo "running hugetlbfstest"
+echo "--------------------"
+./hugetlbfstest
+if [ $? -ne 0 ]; then
+ echo "[FAIL]"
+ exitcode=1
+else
+ echo "[PASS]"
+fi
+
+#cleanup
+umount $mnt
+rm -rf $mnt
+echo $nr_hugepgs > /proc/sys/vm/nr_hugepages
+exit $exitcode
diff --git a/tools/testing/selftests/vm/thuge-gen.c b/tools/testing/selftests/vm/thuge-gen.c
new file mode 100644
index 00000000000..c87957295f7
--- /dev/null
+++ b/tools/testing/selftests/vm/thuge-gen.c
@@ -0,0 +1,254 @@
+/* Test selecting other page sizes for mmap/shmget.
+
+ Before running this huge pages for each huge page size must have been
+ reserved.
+ For large pages beyond MAX_ORDER (like 1GB on x86) boot options must be used.
+ Also shmmax must be increased.
+ And you need to run as root to work around some weird permissions in shm.
+ And nothing using huge pages should run in parallel.
+ When the program aborts you may need to clean up the shm segments with
+ ipcrm -m by hand, like this
+ sudo ipcs | awk '$1 == "0x00000000" {print $2}' | xargs -n1 sudo ipcrm -m
+ (warning this will remove all if someone else uses them) */
+
+#define _GNU_SOURCE 1
+#include <sys/mman.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/ipc.h>
+#include <sys/shm.h>
+#include <sys/stat.h>
+#include <glob.h>
+#include <assert.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <string.h>
+
+#define err(x) perror(x), exit(1)
+
+#define MAP_HUGE_2MB (21 << MAP_HUGE_SHIFT)
+#define MAP_HUGE_1GB (30 << MAP_HUGE_SHIFT)
+#define MAP_HUGE_SHIFT 26
+#define MAP_HUGE_MASK 0x3f
+#define MAP_HUGETLB 0x40000
+
+#define SHM_HUGETLB 04000 /* segment will use huge TLB pages */
+#define SHM_HUGE_SHIFT 26
+#define SHM_HUGE_MASK 0x3f
+#define SHM_HUGE_2MB (21 << SHM_HUGE_SHIFT)
+#define SHM_HUGE_1GB (30 << SHM_HUGE_SHIFT)
+
+#define NUM_PAGESIZES 5
+
+#define NUM_PAGES 4
+
+#define Dprintf(fmt...) // printf(fmt)
+
+unsigned long page_sizes[NUM_PAGESIZES];
+int num_page_sizes;
+
+int ilog2(unsigned long v)
+{
+ int l = 0;
+ while ((1UL << l) < v)
+ l++;
+ return l;
+}
+
+void find_pagesizes(void)
+{
+ glob_t g;
+ int i;
+ glob("/sys/kernel/mm/hugepages/hugepages-*kB", 0, NULL, &g);
+ assert(g.gl_pathc <= NUM_PAGESIZES);
+ for (i = 0; i < g.gl_pathc; i++) {
+ sscanf(g.gl_pathv[i], "/sys/kernel/mm/hugepages/hugepages-%lukB",
+ &page_sizes[i]);
+ page_sizes[i] <<= 10;
+ printf("Found %luMB\n", page_sizes[i] >> 20);
+ }
+ num_page_sizes = g.gl_pathc;
+ globfree(&g);
+}
+
+unsigned long default_huge_page_size(void)
+{
+ unsigned long hps = 0;
+ char *line = NULL;
+ size_t linelen = 0;
+ FILE *f = fopen("/proc/meminfo", "r");
+ if (!f)
+ return 0;
+ while (getline(&line, &linelen, f) > 0) {
+ if (sscanf(line, "Hugepagesize: %lu kB", &hps) == 1) {
+ hps <<= 10;
+ break;
+ }
+ }
+ free(line);
+ return hps;
+}
+
+void show(unsigned long ps)
+{
+ char buf[100];
+ if (ps == getpagesize())
+ return;
+ printf("%luMB: ", ps >> 20);
+ fflush(stdout);
+ snprintf(buf, sizeof buf,
+ "cat /sys/kernel/mm/hugepages/hugepages-%lukB/free_hugepages",
+ ps >> 10);
+ system(buf);
+}
+
+unsigned long read_sysfs(int warn, char *fmt, ...)
+{
+ char *line = NULL;
+ size_t linelen = 0;
+ char buf[100];
+ FILE *f;
+ va_list ap;
+ unsigned long val = 0;
+
+ va_start(ap, fmt);
+ vsnprintf(buf, sizeof buf, fmt, ap);
+ va_end(ap);
+
+ f = fopen(buf, "r");
+ if (!f) {
+ if (warn)
+ printf("missing %s\n", buf);
+ return 0;
+ }
+ if (getline(&line, &linelen, f) > 0) {
+ sscanf(line, "%lu", &val);
+ }
+ fclose(f);
+ free(line);
+ return val;
+}
+
+unsigned long read_free(unsigned long ps)
+{
+ return read_sysfs(ps != getpagesize(),
+ "/sys/kernel/mm/hugepages/hugepages-%lukB/free_hugepages",
+ ps >> 10);
+}
+
+void test_mmap(unsigned long size, unsigned flags)
+{
+ char *map;
+ unsigned long before, after;
+ int err;
+
+ before = read_free(size);
+ map = mmap(NULL, size*NUM_PAGES, PROT_READ|PROT_WRITE,
+ MAP_PRIVATE|MAP_ANONYMOUS|MAP_HUGETLB|flags, 0, 0);
+
+ if (map == (char *)-1) err("mmap");
+ memset(map, 0xff, size*NUM_PAGES);
+ after = read_free(size);
+ Dprintf("before %lu after %lu diff %ld size %lu\n",
+ before, after, before - after, size);
+ assert(size == getpagesize() || (before - after) == NUM_PAGES);
+ show(size);
+ err = munmap(map, size);
+ assert(!err);
+}
+
+void test_shmget(unsigned long size, unsigned flags)
+{
+ int id;
+ unsigned long before, after;
+ int err;
+
+ before = read_free(size);
+ id = shmget(IPC_PRIVATE, size * NUM_PAGES, IPC_CREAT|0600|flags);
+ if (id < 0) err("shmget");
+
+ struct shm_info i;
+ if (shmctl(id, SHM_INFO, (void *)&i) < 0) err("shmctl");
+ Dprintf("alloc %lu res %lu\n", i.shm_tot, i.shm_rss);
+
+
+ Dprintf("id %d\n", id);
+ char *map = shmat(id, NULL, 0600);
+ if (map == (char*)-1) err("shmat");
+
+ shmctl(id, IPC_RMID, NULL);
+
+ memset(map, 0xff, size*NUM_PAGES);
+ after = read_free(size);
+
+ Dprintf("before %lu after %lu diff %ld size %lu\n",
+ before, after, before - after, size);
+ assert(size == getpagesize() || (before - after) == NUM_PAGES);
+ show(size);
+ err = shmdt(map);
+ assert(!err);
+}
+
+void sanity_checks(void)
+{
+ int i;
+ unsigned long largest = getpagesize();
+
+ for (i = 0; i < num_page_sizes; i++) {
+ if (page_sizes[i] > largest)
+ largest = page_sizes[i];
+
+ if (read_free(page_sizes[i]) < NUM_PAGES) {
+ printf("Not enough huge pages for page size %lu MB, need %u\n",
+ page_sizes[i] >> 20,
+ NUM_PAGES);
+ exit(0);
+ }
+ }
+
+ if (read_sysfs(0, "/proc/sys/kernel/shmmax") < NUM_PAGES * largest) {
+ printf("Please do echo %lu > /proc/sys/kernel/shmmax", largest * NUM_PAGES);
+ exit(0);
+ }
+
+#if defined(__x86_64__)
+ if (largest != 1U<<30) {
+ printf("No GB pages available on x86-64\n"
+ "Please boot with hugepagesz=1G hugepages=%d\n", NUM_PAGES);
+ exit(0);
+ }
+#endif
+}
+
+int main(void)
+{
+ int i;
+ unsigned default_hps = default_huge_page_size();
+
+ find_pagesizes();
+
+ sanity_checks();
+
+ for (i = 0; i < num_page_sizes; i++) {
+ unsigned long ps = page_sizes[i];
+ int arg = ilog2(ps) << MAP_HUGE_SHIFT;
+ printf("Testing %luMB mmap with shift %x\n", ps >> 20, arg);
+ test_mmap(ps, MAP_HUGETLB | arg);
+ }
+ printf("Testing default huge mmap\n");
+ test_mmap(default_hps, SHM_HUGETLB);
+
+ puts("Testing non-huge shmget");
+ test_shmget(getpagesize(), 0);
+
+ for (i = 0; i < num_page_sizes; i++) {
+ unsigned long ps = page_sizes[i];
+ int arg = ilog2(ps) << SHM_HUGE_SHIFT;
+ printf("Testing %luMB shmget with shift %x\n", ps >> 20, arg);
+ test_shmget(ps, SHM_HUGETLB | arg);
+ }
+ puts("default huge shmget");
+ test_shmget(default_hps, SHM_HUGETLB);
+
+ return 0;
+}
diff --git a/tools/thermal/tmon/Makefile b/tools/thermal/tmon/Makefile
new file mode 100644
index 00000000000..e775adcbd29
--- /dev/null
+++ b/tools/thermal/tmon/Makefile
@@ -0,0 +1,47 @@
+VERSION = 1.0
+
+BINDIR=usr/bin
+WARNFLAGS=-Wall -Wshadow -W -Wformat -Wimplicit-function-declaration -Wimplicit-int
+CFLAGS= -O1 ${WARNFLAGS} -fstack-protector
+CC=gcc
+
+CFLAGS+=-D VERSION=\"$(VERSION)\"
+LDFLAGS+=
+TARGET=tmon
+
+INSTALL_PROGRAM=install -m 755 -p
+DEL_FILE=rm -f
+
+INSTALL_CONFIGFILE=install -m 644 -p
+CONFIG_FILE=
+CONFIG_PATH=
+
+
+OBJS = tmon.o tui.o sysfs.o pid.o
+OBJS +=
+
+tmon: $(OBJS) Makefile tmon.h
+ $(CC) ${CFLAGS} $(LDFLAGS) $(OBJS) -o $(TARGET) -lm -lpanel -lncursesw -ltinfo -lpthread
+
+valgrind: tmon
+ sudo valgrind -v --track-origins=yes --tool=memcheck --leak-check=yes --show-reachable=yes --num-callers=20 --track-fds=yes ./$(TARGET) 1> /dev/null
+
+install:
+ - mkdir -p $(INSTALL_ROOT)/$(BINDIR)
+ - $(INSTALL_PROGRAM) "$(TARGET)" "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
+ - mkdir -p $(INSTALL_ROOT)/$(CONFIG_PATH)
+ - $(INSTALL_CONFIGFILE) "$(CONFIG_FILE)" "$(INSTALL_ROOT)/$(CONFIG_PATH)"
+
+uninstall:
+ $(DEL_FILE) "$(INSTALL_ROOT)/$(BINDIR)/$(TARGET)"
+ $(CONFIG_FILE) "$(CONFIG_PATH)"
+
+
+clean:
+ find . -name "*.o" | xargs $(DEL_FILE)
+ rm -f $(TARGET)
+
+dist:
+ git tag v$(VERSION)
+ git archive --format=tar --prefix="$(TARGET)-$(VERSION)/" v$(VERSION) | \
+ gzip > $(TARGET)-$(VERSION).tar.gz
diff --git a/tools/thermal/tmon/README b/tools/thermal/tmon/README
new file mode 100644
index 00000000000..457949897a8
--- /dev/null
+++ b/tools/thermal/tmon/README
@@ -0,0 +1,50 @@
+TMON - A Monitoring and Testing Tool for Linux kernel thermal subsystem
+
+Why TMON?
+==========
+Increasingly, Linux is running on thermally constrained devices. The simple
+thermal relationship between processor and fan has become past for modern
+computers.
+
+As hardware vendors cope with the thermal constraints on their products, more
+and more sensors are added, new cooling capabilities are introduced. The
+complexity of the thermal relationship can grow exponentially among cooling
+devices, zones, sensors, and trip points. They can also change dynamically.
+
+To expose such relationship to the userspace, Linux generic thermal layer
+introduced sysfs entry at /sys/class/thermal with a matrix of symbolic
+links, trip point bindings, and device instances. To traverse such
+matrix by hand is not a trivial task. Testing is also difficult in that
+thermal conditions are often exception cases that hard to reach in
+normal operations.
+
+TMON is conceived as a tool to help visualize, tune, and test the
+complex thermal subsystem.
+
+Files
+=====
+ tmon.c : main function for set up and configurations.
+ tui.c : handles ncurses based user interface
+ sysfs.c : access to the generic thermal sysfs
+ pid.c : a proportional-integral-derivative (PID) controller
+ that can be used for thermal relationship training.
+
+Requirements
+============
+Depends on ncurses
+
+Build
+=========
+$ make
+$ sudo ./tmon -h
+Usage: tmon [OPTION...]
+ -c, --control cooling device in control
+ -d, --daemon run as daemon, no TUI
+ -l, --log log data to /var/tmp/tmon.log
+ -h, --help show this help message
+ -t, --time-interval set time interval for sampling
+ -v, --version show version
+ -g, --debug debug message in syslog
+
+1. For monitoring only:
+$ sudo ./tmon
diff --git a/tools/thermal/tmon/pid.c b/tools/thermal/tmon/pid.c
new file mode 100644
index 00000000000..fd7e9e9d6f4
--- /dev/null
+++ b/tools/thermal/tmon/pid.c
@@ -0,0 +1,131 @@
+/*
+ * pid.c PID controller for testing cooling devices
+ *
+ *
+ *
+ * Copyright (C) 2012 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 or later as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Author Name Jacob Pan <jacob.jun.pan@linux.intel.com>
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <libintl.h>
+#include <ctype.h>
+#include <assert.h>
+#include <time.h>
+#include <limits.h>
+#include <math.h>
+#include <sys/stat.h>
+#include <syslog.h>
+
+#include "tmon.h"
+
+/**************************************************************************
+ * PID (Proportional-Integral-Derivative) controller is commonly used in
+ * linear control system, consider the the process.
+ * G(s) = U(s)/E(s)
+ * kp = proportional gain
+ * ki = integral gain
+ * kd = derivative gain
+ * Ts
+ * We use type C Alan Bradley equation which takes set point off the
+ * output dependency in P and D term.
+ *
+ * y[k] = y[k-1] - kp*(x[k] - x[k-1]) + Ki*Ts*e[k] - Kd*(x[k]
+ * - 2*x[k-1]+x[k-2])/Ts
+ *
+ *
+ ***********************************************************************/
+struct pid_params p_param;
+/* cached data from previous loop */
+static double xk_1, xk_2; /* input temperature x[k-#] */
+
+/*
+ * TODO: make PID parameters tuned automatically,
+ * 1. use CPU burn to produce open loop unit step response
+ * 2. calculate PID based on Ziegler-Nichols rule
+ *
+ * add a flag for tuning PID
+ */
+int init_thermal_controller(void)
+{
+ int ret = 0;
+
+ /* init pid params */
+ p_param.ts = ticktime;
+ /* TODO: get it from TUI tuning tab */
+ p_param.kp = .36;
+ p_param.ki = 5.0;
+ p_param.kd = 0.19;
+
+ p_param.t_target = target_temp_user;
+
+ return ret;
+}
+
+void controller_reset(void)
+{
+ /* TODO: relax control data when not over thermal limit */
+ syslog(LOG_DEBUG, "TC inactive, relax p-state\n");
+ p_param.y_k = 0.0;
+ xk_1 = 0.0;
+ xk_2 = 0.0;
+ set_ctrl_state(0);
+}
+
+/* To be called at time interval Ts. Type C PID controller.
+ * y[k] = y[k-1] - kp*(x[k] - x[k-1]) + Ki*Ts*e[k] - Kd*(x[k]
+ * - 2*x[k-1]+x[k-2])/Ts
+ * TODO: add low pass filter for D term
+ */
+#define GUARD_BAND (2)
+void controller_handler(const double xk, double *yk)
+{
+ double ek;
+ double p_term, i_term, d_term;
+
+ ek = p_param.t_target - xk; /* error */
+ if (ek >= 3.0) {
+ syslog(LOG_DEBUG, "PID: %3.1f Below set point %3.1f, stop\n",
+ xk, p_param.t_target);
+ controller_reset();
+ *yk = 0.0;
+ return;
+ }
+ /* compute intermediate PID terms */
+ p_term = -p_param.kp * (xk - xk_1);
+ i_term = p_param.kp * p_param.ki * p_param.ts * ek;
+ d_term = -p_param.kp * p_param.kd * (xk - 2 * xk_1 + xk_2) / p_param.ts;
+ /* compute output */
+ *yk += p_term + i_term + d_term;
+ /* update sample data */
+ xk_1 = xk;
+ xk_2 = xk_1;
+
+ /* clamp output adjustment range */
+ if (*yk < -LIMIT_HIGH)
+ *yk = -LIMIT_HIGH;
+ else if (*yk > -LIMIT_LOW)
+ *yk = -LIMIT_LOW;
+
+ p_param.y_k = *yk;
+
+ set_ctrl_state(lround(fabs(p_param.y_k)));
+
+}
diff --git a/tools/thermal/tmon/sysfs.c b/tools/thermal/tmon/sysfs.c
new file mode 100644
index 00000000000..dfe454855cd
--- /dev/null
+++ b/tools/thermal/tmon/sysfs.c
@@ -0,0 +1,596 @@
+/*
+ * sysfs.c sysfs ABI access functions for TMON program
+ *
+ * Copyright (C) 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 or later as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Author: Jacob Pan <jacob.jun.pan@linux.intel.com>
+ *
+ */
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <dirent.h>
+#include <libintl.h>
+#include <ctype.h>
+#include <time.h>
+#include <syslog.h>
+#include <sys/time.h>
+#include <errno.h>
+
+#include "tmon.h"
+
+struct tmon_platform_data ptdata;
+const char *trip_type_name[] = {
+ "critical",
+ "hot",
+ "passive",
+ "active",
+};
+
+int sysfs_set_ulong(char *path, char *filename, unsigned long val)
+{
+ FILE *fd;
+ int ret = -1;
+ char filepath[256];
+
+ snprintf(filepath, 256, "%s/%s", path, filename);
+
+ fd = fopen(filepath, "w");
+ if (!fd) {
+ syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath);
+ return ret;
+ }
+ ret = fprintf(fd, "%lu", val);
+ fclose(fd);
+
+ return 0;
+}
+
+/* history of thermal data, used for control algo */
+#define NR_THERMAL_RECORDS 3
+struct thermal_data_record trec[NR_THERMAL_RECORDS];
+int cur_thermal_record; /* index to the trec array */
+
+static int sysfs_get_ulong(char *path, char *filename, unsigned long *p_ulong)
+{
+ FILE *fd;
+ int ret = -1;
+ char filepath[256];
+
+ snprintf(filepath, 256, "%s/%s", path, filename);
+
+ fd = fopen(filepath, "r");
+ if (!fd) {
+ syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath);
+ return ret;
+ }
+ ret = fscanf(fd, "%lu", p_ulong);
+ fclose(fd);
+
+ return 0;
+}
+
+static int sysfs_get_string(char *path, char *filename, char *str)
+{
+ FILE *fd;
+ int ret = -1;
+ char filepath[256];
+
+ snprintf(filepath, 256, "%s/%s", path, filename);
+
+ fd = fopen(filepath, "r");
+ if (!fd) {
+ syslog(LOG_ERR, "Err: open %s: %s\n", __func__, filepath);
+ return ret;
+ }
+ ret = fscanf(fd, "%256s", str);
+ fclose(fd);
+
+ return ret;
+}
+
+/* get states of the cooling device instance */
+static int probe_cdev(struct cdev_info *cdi, char *path)
+{
+ sysfs_get_string(path, "type", cdi->type);
+ sysfs_get_ulong(path, "max_state", &cdi->max_state);
+ sysfs_get_ulong(path, "cur_state", &cdi->cur_state);
+
+ syslog(LOG_INFO, "%s: %s: type %s, max %lu, curr %lu inst %d\n",
+ __func__, path,
+ cdi->type, cdi->max_state, cdi->cur_state, cdi->instance);
+
+ return 0;
+}
+
+static int str_to_trip_type(char *name)
+{
+ int i;
+
+ for (i = 0; i < NR_THERMAL_TRIP_TYPE; i++) {
+ if (!strcmp(name, trip_type_name[i]))
+ return i;
+ }
+
+ return -ENOENT;
+}
+
+/* scan and fill in trip point info for a thermal zone and trip point id */
+static int get_trip_point_data(char *tz_path, int tzid, int tpid)
+{
+ char filename[256];
+ char temp_str[256];
+ int trip_type;
+
+ if (tpid >= MAX_NR_TRIP)
+ return -EINVAL;
+ /* check trip point type */
+ snprintf(filename, sizeof(filename), "trip_point_%d_type", tpid);
+ sysfs_get_string(tz_path, filename, temp_str);
+ trip_type = str_to_trip_type(temp_str);
+ if (trip_type < 0) {
+ syslog(LOG_ERR, "%s:%s no matching type\n", __func__, temp_str);
+ return -ENOENT;
+ }
+ ptdata.tzi[tzid].tp[tpid].type = trip_type;
+ syslog(LOG_INFO, "%s:tz:%d tp:%d:type:%s type id %d\n", __func__, tzid,
+ tpid, temp_str, trip_type);
+
+ /* TODO: check attribute */
+
+ return 0;
+}
+
+/* return instance id for file format such as trip_point_4_temp */
+static int get_instance_id(char *name, int pos, int skip)
+{
+ char *ch;
+ int i = 0;
+
+ ch = strtok(name, "_");
+ while (ch != NULL) {
+ ++i;
+ syslog(LOG_INFO, "%s:%s:%s:%d", __func__, name, ch, i);
+ ch = strtok(NULL, "_");
+ if (pos == i)
+ return atol(ch + skip);
+ }
+
+ return -1;
+}
+
+/* Find trip point info of a thermal zone */
+static int find_tzone_tp(char *tz_name, char *d_name, struct tz_info *tzi,
+ int tz_id)
+{
+ int tp_id;
+ unsigned long temp_ulong;
+
+ if (strstr(d_name, "trip_point") &&
+ strstr(d_name, "temp")) {
+ /* check if trip point temp is non-zero
+ * ignore 0/invalid trip points
+ */
+ sysfs_get_ulong(tz_name, d_name, &temp_ulong);
+ if (temp_ulong < MAX_TEMP_KC) {
+ tzi->nr_trip_pts++;
+ /* found a valid trip point */
+ tp_id = get_instance_id(d_name, 2, 0);
+ syslog(LOG_DEBUG, "tzone %s trip %d temp %lu tpnode %s",
+ tz_name, tp_id, temp_ulong, d_name);
+ if (tp_id < 0 || tp_id >= MAX_NR_TRIP) {
+ syslog(LOG_ERR, "Failed to find TP inst %s\n",
+ d_name);
+ return -1;
+ }
+ get_trip_point_data(tz_name, tz_id, tp_id);
+ tzi->tp[tp_id].temp = temp_ulong;
+ }
+ }
+
+ return 0;
+}
+
+/* check cooling devices for binding info. */
+static int find_tzone_cdev(struct dirent *nl, char *tz_name,
+ struct tz_info *tzi, int tz_id, int cid)
+{
+ unsigned long trip_instance = 0;
+ char cdev_name_linked[256];
+ char cdev_name[256];
+ char cdev_trip_name[256];
+ int cdev_id;
+
+ if (nl->d_type == DT_LNK) {
+ syslog(LOG_DEBUG, "TZ%d: cdev: %s cid %d\n", tz_id, nl->d_name,
+ cid);
+ tzi->nr_cdev++;
+ if (tzi->nr_cdev > ptdata.nr_cooling_dev) {
+ syslog(LOG_ERR, "Err: Too many cdev? %d\n",
+ tzi->nr_cdev);
+ return -EINVAL;
+ }
+ /* find the link to real cooling device record binding */
+ snprintf(cdev_name, 256, "%s/%s", tz_name, nl->d_name);
+ memset(cdev_name_linked, 0, sizeof(cdev_name_linked));
+ if (readlink(cdev_name, cdev_name_linked,
+ sizeof(cdev_name_linked) - 1) != -1) {
+ cdev_id = get_instance_id(cdev_name_linked, 1,
+ sizeof("device") - 1);
+ syslog(LOG_DEBUG, "cdev %s linked to %s : %d\n",
+ cdev_name, cdev_name_linked, cdev_id);
+ tzi->cdev_binding |= (1 << cdev_id);
+
+ /* find the trip point in which the cdev is binded to
+ * in this tzone
+ */
+ snprintf(cdev_trip_name, 256, "%s%s", nl->d_name,
+ "_trip_point");
+ sysfs_get_ulong(tz_name, cdev_trip_name,
+ &trip_instance);
+ /* validate trip point range, e.g. trip could return -1
+ * when passive is enabled
+ */
+ if (trip_instance > MAX_NR_TRIP)
+ trip_instance = 0;
+ tzi->trip_binding[cdev_id] |= 1 << trip_instance;
+ syslog(LOG_DEBUG, "cdev %s -> trip:%lu: 0x%lx %d\n",
+ cdev_name, trip_instance,
+ tzi->trip_binding[cdev_id],
+ cdev_id);
+
+
+ }
+ return 0;
+ }
+
+ return -ENODEV;
+}
+
+
+
+/*****************************************************************************
+ * Before calling scan_tzones, thermal sysfs must be probed to determine
+ * the number of thermal zones and cooling devices.
+ * We loop through each thermal zone and fill in tz_info struct, i.e.
+ * ptdata.tzi[]
+root@jacob-chiefriver:~# tree -d /sys/class/thermal/thermal_zone0
+/sys/class/thermal/thermal_zone0
+|-- cdev0 -> ../cooling_device4
+|-- cdev1 -> ../cooling_device3
+|-- cdev10 -> ../cooling_device7
+|-- cdev11 -> ../cooling_device6
+|-- cdev12 -> ../cooling_device5
+|-- cdev2 -> ../cooling_device2
+|-- cdev3 -> ../cooling_device1
+|-- cdev4 -> ../cooling_device0
+|-- cdev5 -> ../cooling_device12
+|-- cdev6 -> ../cooling_device11
+|-- cdev7 -> ../cooling_device10
+|-- cdev8 -> ../cooling_device9
+|-- cdev9 -> ../cooling_device8
+|-- device -> ../../../LNXSYSTM:00/device:62/LNXTHERM:00
+|-- power
+`-- subsystem -> ../../../../class/thermal
+*****************************************************************************/
+static int scan_tzones(void)
+{
+ DIR *dir;
+ struct dirent **namelist;
+ char tz_name[256];
+ int i, j, n, k = 0;
+
+ if (!ptdata.nr_tz_sensor)
+ return -1;
+
+ for (i = 0; i <= ptdata.max_tz_instance; i++) {
+ memset(tz_name, 0, sizeof(tz_name));
+ snprintf(tz_name, 256, "%s/%s%d", THERMAL_SYSFS, TZONE, i);
+
+ dir = opendir(tz_name);
+ if (!dir) {
+ syslog(LOG_INFO, "Thermal zone %s skipped\n", tz_name);
+ continue;
+ }
+ /* keep track of valid tzones */
+ n = scandir(tz_name, &namelist, 0, alphasort);
+ if (n < 0)
+ syslog(LOG_ERR, "scandir failed in %s", tz_name);
+ else {
+ sysfs_get_string(tz_name, "type", ptdata.tzi[k].type);
+ ptdata.tzi[k].instance = i;
+ /* detect trip points and cdev attached to this tzone */
+ j = 0; /* index for cdev */
+ ptdata.tzi[k].nr_cdev = 0;
+ ptdata.tzi[k].nr_trip_pts = 0;
+ while (n--) {
+ char *temp_str;
+
+ if (find_tzone_tp(tz_name, namelist[n]->d_name,
+ &ptdata.tzi[k], k))
+ break;
+ temp_str = strstr(namelist[n]->d_name, "cdev");
+ if (!temp_str) {
+ free(namelist[n]);
+ continue;
+ }
+ if (!find_tzone_cdev(namelist[n], tz_name,
+ &ptdata.tzi[k], i, j))
+ j++; /* increment cdev index */
+ free(namelist[n]);
+ }
+ free(namelist);
+ }
+ /*TODO: reverse trip points */
+ closedir(dir);
+ syslog(LOG_INFO, "TZ %d has %d cdev\n", i,
+ ptdata.tzi[k].nr_cdev);
+ k++;
+ }
+
+ return 0;
+}
+
+static int scan_cdevs(void)
+{
+ DIR *dir;
+ struct dirent **namelist;
+ char cdev_name[256];
+ int i, n, k = 0;
+
+ if (!ptdata.nr_cooling_dev) {
+ fprintf(stderr, "No cooling devices found\n");
+ return 0;
+ }
+ for (i = 0; i <= ptdata.max_cdev_instance; i++) {
+ memset(cdev_name, 0, sizeof(cdev_name));
+ snprintf(cdev_name, 256, "%s/%s%d", THERMAL_SYSFS, CDEV, i);
+
+ dir = opendir(cdev_name);
+ if (!dir) {
+ syslog(LOG_INFO, "Cooling dev %s skipped\n", cdev_name);
+ /* there is a gap in cooling device id, check again
+ * for the same index.
+ */
+ continue;
+ }
+
+ n = scandir(cdev_name, &namelist, 0, alphasort);
+ if (n < 0)
+ syslog(LOG_ERR, "scandir failed in %s", cdev_name);
+ else {
+ sysfs_get_string(cdev_name, "type", ptdata.cdi[k].type);
+ ptdata.cdi[k].instance = i;
+ if (strstr(ptdata.cdi[k].type, ctrl_cdev)) {
+ ptdata.cdi[k].flag |= CDEV_FLAG_IN_CONTROL;
+ syslog(LOG_DEBUG, "control cdev id %d\n", i);
+ }
+ while (n--)
+ free(namelist[n]);
+ free(namelist);
+ }
+ closedir(dir);
+ k++;
+ }
+ return 0;
+}
+
+
+int probe_thermal_sysfs(void)
+{
+ DIR *dir;
+ struct dirent **namelist;
+ int n;
+
+ dir = opendir(THERMAL_SYSFS);
+ if (!dir) {
+ fprintf(stderr, "\nNo thermal sysfs, exit\n");
+ return -1;
+ }
+ n = scandir(THERMAL_SYSFS, &namelist, 0, alphasort);
+ if (n < 0)
+ syslog(LOG_ERR, "scandir failed in thermal sysfs");
+ else {
+ /* detect number of thermal zones and cooling devices */
+ while (n--) {
+ int inst;
+
+ if (strstr(namelist[n]->d_name, CDEV)) {
+ inst = get_instance_id(namelist[n]->d_name, 1,
+ sizeof("device") - 1);
+ /* keep track of the max cooling device since
+ * there may be gaps.
+ */
+ if (inst > ptdata.max_cdev_instance)
+ ptdata.max_cdev_instance = inst;
+
+ syslog(LOG_DEBUG, "found cdev: %s %d %d\n",
+ namelist[n]->d_name,
+ ptdata.nr_cooling_dev,
+ ptdata.max_cdev_instance);
+ ptdata.nr_cooling_dev++;
+ } else if (strstr(namelist[n]->d_name, TZONE)) {
+ inst = get_instance_id(namelist[n]->d_name, 1,
+ sizeof("zone") - 1);
+ if (inst > ptdata.max_tz_instance)
+ ptdata.max_tz_instance = inst;
+
+ syslog(LOG_DEBUG, "found tzone: %s %d %d\n",
+ namelist[n]->d_name,
+ ptdata.nr_tz_sensor,
+ ptdata.max_tz_instance);
+ ptdata.nr_tz_sensor++;
+ }
+ free(namelist[n]);
+ }
+ free(namelist);
+ }
+ syslog(LOG_INFO, "found %d tzone(s), %d cdev(s), target zone %d\n",
+ ptdata.nr_tz_sensor, ptdata.nr_cooling_dev,
+ target_thermal_zone);
+ closedir(dir);
+
+ if (!ptdata.nr_tz_sensor) {
+ fprintf(stderr, "\nNo thermal zones found, exit\n\n");
+ return -1;
+ }
+
+ ptdata.tzi = calloc(sizeof(struct tz_info), ptdata.max_tz_instance+1);
+ if (!ptdata.tzi) {
+ fprintf(stderr, "Err: allocate tz_info\n");
+ return -1;
+ }
+
+ /* we still show thermal zone information if there is no cdev */
+ if (ptdata.nr_cooling_dev) {
+ ptdata.cdi = calloc(sizeof(struct cdev_info),
+ ptdata.max_cdev_instance + 1);
+ if (!ptdata.cdi) {
+ free(ptdata.tzi);
+ fprintf(stderr, "Err: allocate cdev_info\n");
+ return -1;
+ }
+ }
+
+ /* now probe tzones */
+ if (scan_tzones())
+ return -1;
+ if (scan_cdevs())
+ return -1;
+ return 0;
+}
+
+/* convert sysfs zone instance to zone array index */
+int zone_instance_to_index(int zone_inst)
+{
+ int i;
+
+ for (i = 0; i < ptdata.nr_tz_sensor; i++)
+ if (ptdata.tzi[i].instance == zone_inst)
+ return i;
+ return -ENOENT;
+}
+
+/* read temperature of all thermal zones */
+int update_thermal_data()
+{
+ int i;
+ char tz_name[256];
+ static unsigned long samples;
+
+ if (!ptdata.nr_tz_sensor) {
+ syslog(LOG_ERR, "No thermal zones found!\n");
+ return -1;
+ }
+
+ /* circular buffer for keeping historic data */
+ if (cur_thermal_record >= NR_THERMAL_RECORDS)
+ cur_thermal_record = 0;
+ gettimeofday(&trec[cur_thermal_record].tv, NULL);
+ if (tmon_log) {
+ fprintf(tmon_log, "%lu ", ++samples);
+ fprintf(tmon_log, "%3.1f ", p_param.t_target);
+ }
+ for (i = 0; i < ptdata.nr_tz_sensor; i++) {
+ memset(tz_name, 0, sizeof(tz_name));
+ snprintf(tz_name, 256, "%s/%s%d", THERMAL_SYSFS, TZONE,
+ ptdata.tzi[i].instance);
+ sysfs_get_ulong(tz_name, "temp",
+ &trec[cur_thermal_record].temp[i]);
+ if (tmon_log)
+ fprintf(tmon_log, "%lu ",
+ trec[cur_thermal_record].temp[i]/1000);
+ }
+ for (i = 0; i < ptdata.nr_cooling_dev; i++) {
+ char cdev_name[256];
+ unsigned long val;
+
+ snprintf(cdev_name, 256, "%s/%s%d", THERMAL_SYSFS, CDEV,
+ ptdata.cdi[i].instance);
+ probe_cdev(&ptdata.cdi[i], cdev_name);
+ val = ptdata.cdi[i].cur_state;
+ if (val > 1000000)
+ val = 0;
+ if (tmon_log)
+ fprintf(tmon_log, "%lu ", val);
+ }
+
+ if (tmon_log) {
+ fprintf(tmon_log, "\n");
+ fflush(tmon_log);
+ }
+
+ return 0;
+}
+
+void set_ctrl_state(unsigned long state)
+{
+ char ctrl_cdev_path[256];
+ int i;
+ unsigned long cdev_state;
+
+ if (no_control)
+ return;
+ /* set all ctrl cdev to the same state */
+ for (i = 0; i < ptdata.nr_cooling_dev; i++) {
+ if (ptdata.cdi[i].flag & CDEV_FLAG_IN_CONTROL) {
+ if (ptdata.cdi[i].max_state < 10) {
+ strcpy(ctrl_cdev, "None.");
+ return;
+ }
+ /* scale to percentage of max_state */
+ cdev_state = state * ptdata.cdi[i].max_state/100;
+ syslog(LOG_DEBUG,
+ "ctrl cdev %d set state %lu scaled to %lu\n",
+ ptdata.cdi[i].instance, state, cdev_state);
+ snprintf(ctrl_cdev_path, 256, "%s/%s%d", THERMAL_SYSFS,
+ CDEV, ptdata.cdi[i].instance);
+ syslog(LOG_DEBUG, "ctrl cdev path %s", ctrl_cdev_path);
+ sysfs_set_ulong(ctrl_cdev_path, "cur_state",
+ cdev_state);
+ }
+ }
+}
+
+void get_ctrl_state(unsigned long *state)
+{
+ char ctrl_cdev_path[256];
+ int ctrl_cdev_id = -1;
+ int i;
+
+ /* TODO: take average of all ctrl types. also consider change based on
+ * uevent. Take the first reading for now.
+ */
+ for (i = 0; i < ptdata.nr_cooling_dev; i++) {
+ if (ptdata.cdi[i].flag & CDEV_FLAG_IN_CONTROL) {
+ ctrl_cdev_id = ptdata.cdi[i].instance;
+ syslog(LOG_INFO, "ctrl cdev %d get state\n",
+ ptdata.cdi[i].instance);
+ break;
+ }
+ }
+ if (ctrl_cdev_id == -1) {
+ *state = 0;
+ return;
+ }
+ snprintf(ctrl_cdev_path, 256, "%s/%s%d", THERMAL_SYSFS,
+ CDEV, ctrl_cdev_id);
+ sysfs_get_ulong(ctrl_cdev_path, "cur_state", state);
+}
+
+void free_thermal_data(void)
+{
+ free(ptdata.tzi);
+ free(ptdata.cdi);
+}
diff --git a/tools/thermal/tmon/tmon.8 b/tools/thermal/tmon/tmon.8
new file mode 100644
index 00000000000..0be727cb989
--- /dev/null
+++ b/tools/thermal/tmon/tmon.8
@@ -0,0 +1,142 @@
+.TH TMON 8
+.SH NAME
+\fBtmon\fP - A monitoring and testing tool for Linux kernel thermal subsystem
+
+.SH SYNOPSIS
+.ft B
+.B tmon
+.RB [ Options ]
+.br
+.SH DESCRIPTION
+\fBtmon \fP can be used to visualize thermal relationship and
+real-time thermal data; tune
+and test cooling devices and sensors; collect thermal data for offline
+analysis and plot. \fBtmon\fP must be run as root in order to control device
+states via sysfs.
+.PP
+\fBFunctions\fP
+.PP
+.nf
+1. Thermal relationships:
+- show thermal zone information
+- show cooling device information
+- show trip point binding within each thermal zone
+- show trip point and cooling device instance bindings
+.PP
+2. Real time data display
+- show temperature of all thermal zones w.r.t. its trip points and types
+- show states of all cooling devices
+.PP
+3. Thermal relationship learning and device tuning
+- with a built-in Proportional Integral Derivative (\fBPID\fP)
+controller, user can pair a cooling device to a thermal sensor for
+testing the effectiveness and learn about the thermal distance between the two
+- allow manual control of cooling device states and target temperature
+.PP
+4. Data logging in /var/tmp/tmon.log
+- contains thermal configuration data, i.e. cooling device, thermal
+ zones, and trip points. Can be used for data collection in remote
+ debugging.
+- log real-time thermal data into space separated format that can be
+ directly consumed by plotting tools such as Rscript.
+
+.SS Options
+.PP
+The \fB-c --control\fP option sets a cooling device type to control temperature
+of a thermal zone
+.PP
+The \fB-d --daemon\fP option runs \fBtmon \fP as daemon without user interface
+.PP
+The \fB-g --debug\fP option allow debug messages to be stored in syslog
+.PP
+The \fB-h --help\fP option shows help message
+.PP
+The \fB-l --log\fP option write data to /var/tmp/tmon.log
+.PP
+The \fB-t --time-interval\fP option sets the polling interval in seconds
+.PP
+The \fB-v --version\fP option shows the version of \fBtmon \fP
+.PP
+The \fB-z --zone\fP option sets the target therma zone instance to be controlled
+.PP
+
+.SH FIELD DESCRIPTIONS
+.nf
+.PP
+\fBP \fP passive cooling trip point type
+\fBA \fP active cooling trip point type (fan)
+\fBC \fP critical trip point type
+\fBA \fP hot trip point type
+\fBkp \fP proportional gain of \fBPID\fP controller
+\fBki \fP integral gain of \fBPID\fP controller
+\fBkd \fP derivative gain of \fBPID\fP controller
+
+.SH REQUIREMENT
+Build depends on ncurses
+.PP
+Runtime depends on window size large enough to show the number of
+devices found on the system.
+
+.PP
+
+.SH INTERACTIVE COMMANDS
+.pp
+.nf
+\fBCtrl-C, q/Q\fP stops \fBtmon\fP
+\fBTAB\fP shows tuning pop up panel, choose a letter to modify
+
+.SH EXAMPLES
+Without any parameters, tmon is in monitoring only mode and refresh
+screen every 1 second.
+.PP
+1. For monitoring only:
+.nf
+$ sudo ./tmon
+
+2. Use Processor cooling device to control thermal zone 0 at default 65C.
+$ sudo ./tmon -c Processor -z 0
+
+3. Use intel_powerclamp(idle injection) cooling device to control thermal zone 1
+$ sudo ./tmon -c intel_powerclamp -z 1
+
+4. Turn on debug and collect data log at /var/tmp/tmon.log
+$ sudo ./tmon -g -l
+
+For example, the log below shows PID controller was adjusting current states
+for all cooling devices with "Processor" type such that thermal zone 0
+can stay below 65 dC.
+
+#---------- THERMAL DATA LOG STARTED -----------
+Samples TargetTemp acpitz0 acpitz1 Fan0 Fan1 Fan2 Fan3 Fan4 Fan5
+Fan6 Fan7 Fan8 Fan9 Processor10 Processor11 Processor12 Processor13
+LCD14 intel_powerclamp15 1 65.0 65 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0 2
+65.0 66 65 0 0 0 0 0 0 0 0 0 0 4 4 4 4 6 0 3 65.0 60 54 0 0 0 0 0 0 0 0
+0 0 4 4 4 4 6 0 4 65.0 53 53 0 0 0 0 0 0 0 0 0 0 4 4 4 4 6 0
+5 65.0 52 52 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0
+6 65.0 53 65 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0
+7 65.0 68 70 0 0 0 0 0 0 0 0 0 0 0 0 0 0 6 0
+8 65.0 68 68 0 0 0 0 0 0 0 0 0 0 5 5 5 5 6 0
+9 65.0 68 68 0 0 0 0 0 0 0 0 0 0 6 6 6 6 6 0
+10 65.0 67 67 0 0 0 0 0 0 0 0 0 0 7 7 7 7 6 0
+11 65.0 67 67 0 0 0 0 0 0 0 0 0 0 8 8 8 8 6 0
+12 65.0 67 67 0 0 0 0 0 0 0 0 0 0 8 8 8 8 6 0
+13 65.0 67 67 0 0 0 0 0 0 0 0 0 0 9 9 9 9 6 0
+14 65.0 66 66 0 0 0 0 0 0 0 0 0 0 10 10 10 10 6 0
+15 65.0 66 67 0 0 0 0 0 0 0 0 0 0 10 10 10 10 6 0
+16 65.0 66 66 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0
+17 65.0 66 66 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0
+18 65.0 64 61 0 0 0 0 0 0 0 0 0 0 11 11 11 11 6 0
+19 65.0 60 59 0 0 0 0 0 0 0 0 0 0 12 12 12 12 6 0
+
+Data can be read directly into an array by an example R-script below:
+
+#!/usr/bin/Rscript
+tdata <- read.table("/var/tmp/tmon.log", header=T, comment.char="#")
+attach(tdata)
+jpeg("tmon.jpg")
+X11()
+g_range <- range(0, intel_powerclamp15, TargetTemp, acpitz0)
+plot( Samples, intel_powerclamp15, col="blue", ylim=g_range, axes=FALSE, ann=FALSE)
+par(new=TRUE)
+lines(TargetTemp, type="o", pch=22, lty=2, col="red")
+dev.off()
diff --git a/tools/thermal/tmon/tmon.c b/tools/thermal/tmon/tmon.c
new file mode 100644
index 00000000000..09b7c321833
--- /dev/null
+++ b/tools/thermal/tmon/tmon.c
@@ -0,0 +1,376 @@
+/*
+ * tmon.c Thermal Monitor (TMON) main function and entry point
+ *
+ * Copyright (C) 2012 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 or later as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Author: Jacob Pan <jacob.jun.pan@linux.intel.com>
+ *
+ */
+
+#include <getopt.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <ncurses.h>
+#include <ctype.h>
+#include <time.h>
+#include <signal.h>
+#include <limits.h>
+#include <sys/time.h>
+#include <pthread.h>
+#include <math.h>
+#include <stdarg.h>
+#include <syslog.h>
+
+#include "tmon.h"
+
+unsigned long ticktime = 1; /* seconds */
+unsigned long no_control = 1; /* monitoring only or use cooling device for
+ * temperature control.
+ */
+double time_elapsed = 0.0;
+unsigned long target_temp_user = 65; /* can be select by tui later */
+int dialogue_on;
+int tmon_exit;
+static short daemon_mode;
+static int logging; /* for recording thermal data to a file */
+static int debug_on;
+FILE *tmon_log;
+/*cooling device used for the PID controller */
+char ctrl_cdev[CDEV_NAME_SIZE] = "None";
+int target_thermal_zone; /* user selected target zone instance */
+static void start_daemon_mode(void);
+
+pthread_t event_tid;
+pthread_mutex_t input_lock;
+void usage()
+{
+ printf("Usage: tmon [OPTION...]\n");
+ printf(" -c, --control cooling device in control\n");
+ printf(" -d, --daemon run as daemon, no TUI\n");
+ printf(" -g, --debug debug message in syslog\n");
+ printf(" -h, --help show this help message\n");
+ printf(" -l, --log log data to /var/tmp/tmon.log\n");
+ printf(" -t, --time-interval sampling time interval, > 1 sec.\n");
+ printf(" -v, --version show version\n");
+ printf(" -z, --zone target thermal zone id\n");
+
+ exit(0);
+}
+
+void version()
+{
+ printf("TMON version %s\n", VERSION);
+ exit(EXIT_SUCCESS);
+}
+
+static void tmon_cleanup(void)
+{
+
+ syslog(LOG_INFO, "TMON exit cleanup\n");
+ fflush(stdout);
+ refresh();
+ if (tmon_log)
+ fclose(tmon_log);
+ if (event_tid) {
+ pthread_mutex_lock(&input_lock);
+ pthread_cancel(event_tid);
+ pthread_mutex_unlock(&input_lock);
+ pthread_mutex_destroy(&input_lock);
+ }
+ closelog();
+ /* relax control knobs, undo throttling */
+ set_ctrl_state(0);
+
+ keypad(stdscr, FALSE);
+ echo();
+ nocbreak();
+ close_windows();
+ endwin();
+ free_thermal_data();
+
+ exit(1);
+}
+
+
+static void tmon_sig_handler(int sig)
+{
+ syslog(LOG_INFO, "TMON caught signal %d\n", sig);
+ refresh();
+ switch (sig) {
+ case SIGTERM:
+ printf("sigterm, exit and clean up\n");
+ fflush(stdout);
+ break;
+ case SIGKILL:
+ printf("sigkill, exit and clean up\n");
+ fflush(stdout);
+ break;
+ case SIGINT:
+ printf("ctrl-c, exit and clean up\n");
+ fflush(stdout);
+ break;
+ default:
+ break;
+ }
+ tmon_exit = true;
+}
+
+
+static void start_syslog(void)
+{
+ if (debug_on)
+ setlogmask(LOG_UPTO(LOG_DEBUG));
+ else
+ setlogmask(LOG_UPTO(LOG_ERR));
+ openlog("tmon.log", LOG_CONS | LOG_PID | LOG_NDELAY, LOG_LOCAL0);
+ syslog(LOG_NOTICE, "TMON started by User %d", getuid());
+}
+
+static void prepare_logging(void)
+{
+ int i;
+ struct stat logstat;
+
+ if (!logging)
+ return;
+ /* open local data log file */
+ tmon_log = fopen(TMON_LOG_FILE, "w+");
+ if (!tmon_log) {
+ syslog(LOG_ERR, "failed to open log file %s\n", TMON_LOG_FILE);
+ return;
+ }
+
+ if (lstat(TMON_LOG_FILE, &logstat) < 0) {
+ syslog(LOG_ERR, "Unable to stat log file %s\n", TMON_LOG_FILE);
+ fclose(tmon_log);
+ tmon_log = NULL;
+ return;
+ }
+
+ /* The log file must be a regular file owned by us */
+ if (S_ISLNK(logstat.st_mode)) {
+ syslog(LOG_ERR, "Log file is a symlink. Will not log\n");
+ fclose(tmon_log);
+ tmon_log = NULL;
+ return;
+ }
+
+ if (logstat.st_uid != getuid()) {
+ syslog(LOG_ERR, "We don't own the log file. Not logging\n");
+ fclose(tmon_log);
+ tmon_log = NULL;
+ return;
+ }
+
+
+ fprintf(tmon_log, "#----------- THERMAL SYSTEM CONFIG -------------\n");
+ for (i = 0; i < ptdata.nr_tz_sensor; i++) {
+ char binding_str[33]; /* size of long + 1 */
+ int j;
+
+ memset(binding_str, 0, sizeof(binding_str));
+ for (j = 0; j < 32; j++)
+ binding_str[j] = (ptdata.tzi[i].cdev_binding & 1<<j) ?
+ '1' : '0';
+
+ fprintf(tmon_log, "#thermal zone %s%02d cdevs binding: %32s\n",
+ ptdata.tzi[i].type,
+ ptdata.tzi[i].instance,
+ binding_str);
+ for (j = 0; j < ptdata.tzi[i].nr_trip_pts; j++) {
+ fprintf(tmon_log, "#\tTP%02d type:%s, temp:%lu\n", j,
+ trip_type_name[ptdata.tzi[i].tp[j].type],
+ ptdata.tzi[i].tp[j].temp);
+ }
+
+ }
+
+ for (i = 0; i < ptdata.nr_cooling_dev; i++)
+ fprintf(tmon_log, "#cooling devices%02d: %s\n",
+ i, ptdata.cdi[i].type);
+
+ fprintf(tmon_log, "#---------- THERMAL DATA LOG STARTED -----------\n");
+ fprintf(tmon_log, "Samples TargetTemp ");
+ for (i = 0; i < ptdata.nr_tz_sensor; i++) {
+ fprintf(tmon_log, "%s%d ", ptdata.tzi[i].type,
+ ptdata.tzi[i].instance);
+ }
+ for (i = 0; i < ptdata.nr_cooling_dev; i++)
+ fprintf(tmon_log, "%s%d ", ptdata.cdi[i].type,
+ ptdata.cdi[i].instance);
+
+ fprintf(tmon_log, "\n");
+}
+
+static struct option opts[] = {
+ { "control", 1, NULL, 'c' },
+ { "daemon", 0, NULL, 'd' },
+ { "time-interval", 1, NULL, 't' },
+ { "log", 0, NULL, 'l' },
+ { "help", 0, NULL, 'h' },
+ { "version", 0, NULL, 'v' },
+ { "debug", 0, NULL, 'g' },
+ { 0, 0, NULL, 0 }
+};
+
+
+int main(int argc, char **argv)
+{
+ int err = 0;
+ int id2 = 0, c;
+ double yk = 0.0; /* controller output */
+ int target_tz_index;
+
+ if (geteuid() != 0) {
+ printf("TMON needs to be run as root\n");
+ exit(EXIT_FAILURE);
+ }
+
+ while ((c = getopt_long(argc, argv, "c:dlht:vgz:", opts, &id2)) != -1) {
+ switch (c) {
+ case 'c':
+ no_control = 0;
+ strncpy(ctrl_cdev, optarg, CDEV_NAME_SIZE);
+ break;
+ case 'd':
+ start_daemon_mode();
+ printf("Run TMON in daemon mode\n");
+ break;
+ case 't':
+ ticktime = strtod(optarg, NULL);
+ if (ticktime < 1)
+ ticktime = 1;
+ break;
+ case 'l':
+ printf("Logging data to /var/tmp/tmon.log\n");
+ logging = 1;
+ break;
+ case 'h':
+ usage();
+ break;
+ case 'v':
+ version();
+ break;
+ case 'g':
+ debug_on = 1;
+ break;
+ case 'z':
+ target_thermal_zone = strtod(optarg, NULL);
+ break;
+ default:
+ break;
+ }
+ }
+ if (pthread_mutex_init(&input_lock, NULL) != 0) {
+ fprintf(stderr, "\n mutex init failed, exit\n");
+ return 1;
+ }
+ start_syslog();
+ if (signal(SIGINT, tmon_sig_handler) == SIG_ERR)
+ syslog(LOG_DEBUG, "Cannot handle SIGINT\n");
+ if (signal(SIGTERM, tmon_sig_handler) == SIG_ERR)
+ syslog(LOG_DEBUG, "Cannot handle SIGINT\n");
+
+ if (probe_thermal_sysfs()) {
+ pthread_mutex_destroy(&input_lock);
+ closelog();
+ return -1;
+ }
+ initialize_curses();
+ setup_windows();
+ signal(SIGWINCH, resize_handler);
+ show_title_bar();
+ show_sensors_w();
+ show_cooling_device();
+ update_thermal_data();
+ show_data_w();
+ prepare_logging();
+ init_thermal_controller();
+
+ nodelay(stdscr, TRUE);
+ err = pthread_create(&event_tid, NULL, &handle_tui_events, NULL);
+ if (err != 0) {
+ printf("\ncan't create thread :[%s]", strerror(err));
+ tmon_cleanup();
+ exit(EXIT_FAILURE);
+ }
+
+ /* validate range of user selected target zone, default to the first
+ * instance if out of range
+ */
+ target_tz_index = zone_instance_to_index(target_thermal_zone);
+ if (target_tz_index < 0) {
+ target_thermal_zone = ptdata.tzi[0].instance;
+ syslog(LOG_ERR, "target zone is not found, default to %d\n",
+ target_thermal_zone);
+ }
+ while (1) {
+ sleep(ticktime);
+ show_title_bar();
+ show_sensors_w();
+ update_thermal_data();
+ if (!dialogue_on) {
+ show_data_w();
+ show_cooling_device();
+ }
+ cur_thermal_record++;
+ time_elapsed += ticktime;
+ controller_handler(trec[0].temp[target_tz_index] / 1000,
+ &yk);
+ trec[0].pid_out_pct = yk;
+ if (!dialogue_on)
+ show_control_w();
+ if (tmon_exit)
+ break;
+ }
+ tmon_cleanup();
+ return 0;
+}
+
+static void start_daemon_mode()
+{
+ daemon_mode = 1;
+ /* fork */
+ pid_t sid, pid = fork();
+ if (pid < 0) {
+ exit(EXIT_FAILURE);
+ } else if (pid > 0)
+ /* kill parent */
+ exit(EXIT_SUCCESS);
+
+ /* disable TUI, it may not be necessary, but saves some resource */
+ disable_tui();
+
+ /* change the file mode mask */
+ umask(S_IWGRP | S_IWOTH);
+
+ /* new SID for the daemon process */
+ sid = setsid();
+ if (sid < 0)
+ exit(EXIT_FAILURE);
+
+ /* change working directory */
+ if ((chdir("/")) < 0)
+ exit(EXIT_FAILURE);
+
+
+ sleep(10);
+
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+
+}
diff --git a/tools/thermal/tmon/tmon.h b/tools/thermal/tmon/tmon.h
new file mode 100644
index 00000000000..9e3c49c547a
--- /dev/null
+++ b/tools/thermal/tmon/tmon.h
@@ -0,0 +1,204 @@
+/*
+ * tmon.h contains data structures and constants used by TMON
+ *
+ * Copyright (C) 2012 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 or later as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Author Name Jacob Pan <jacob.jun.pan@linux.intel.com>
+ *
+ */
+
+#ifndef TMON_H
+#define TMON_H
+
+#define MAX_DISP_TEMP 125
+#define MAX_CTRL_TEMP 105
+#define MIN_CTRL_TEMP 40
+#define MAX_NR_TZONE 16
+#define MAX_NR_CDEV 32
+#define MAX_NR_TRIP 16
+#define MAX_NR_CDEV_TRIP 12 /* number of cooling devices that can bind
+ * to a thermal zone trip.
+ */
+#define MAX_TEMP_KC 140000
+/* starting char position to draw sensor data, such as tz names
+ * trip point list, etc.
+ */
+#define DATA_LEFT_ALIGN 10
+#define NR_LINES_TZDATA 1
+#define TMON_LOG_FILE "/var/tmp/tmon.log"
+
+extern unsigned long ticktime;
+extern double time_elapsed;
+extern unsigned long target_temp_user;
+extern int dialogue_on;
+extern char ctrl_cdev[];
+extern pthread_mutex_t input_lock;
+extern int tmon_exit;
+extern int target_thermal_zone;
+/* use fixed size record to simplify data processing and transfer
+ * TBD: more info to be added, e.g. programmable trip point data.
+*/
+struct thermal_data_record {
+ struct timeval tv;
+ unsigned long temp[MAX_NR_TZONE];
+ double pid_out_pct;
+};
+
+struct cdev_info {
+ char type[64];
+ int instance;
+ unsigned long max_state;
+ unsigned long cur_state;
+ unsigned long flag;
+};
+
+enum trip_type {
+ THERMAL_TRIP_CRITICAL,
+ THERMAL_TRIP_HOT,
+ THERMAL_TRIP_PASSIVE,
+ THERMAL_TRIP_ACTIVE,
+ NR_THERMAL_TRIP_TYPE,
+};
+
+struct trip_point {
+ enum trip_type type;
+ unsigned long temp;
+ unsigned long hysteresis;
+ int attribute; /* programmability etc. */
+};
+
+/* thermal zone configuration information, binding with cooling devices could
+ * change at runtime.
+ */
+struct tz_info {
+ char type[256]; /* e.g. acpitz */
+ int instance;
+ int passive; /* active zone has passive node to force passive mode */
+ int nr_cdev; /* number of cooling device binded */
+ int nr_trip_pts;
+ struct trip_point tp[MAX_NR_TRIP];
+ unsigned long cdev_binding; /* bitmap for attached cdevs */
+ /* cdev bind trip points, allow one cdev bind to multiple trips */
+ unsigned long trip_binding[MAX_NR_CDEV];
+};
+
+struct tmon_platform_data {
+ int nr_tz_sensor;
+ int nr_cooling_dev;
+ /* keep track of instance ids since there might be gaps */
+ int max_tz_instance;
+ int max_cdev_instance;
+ struct tz_info *tzi;
+ struct cdev_info *cdi;
+};
+
+struct control_ops {
+ void (*set_ratio)(unsigned long ratio);
+ unsigned long (*get_ratio)(unsigned long ratio);
+
+};
+
+enum cdev_types {
+ CDEV_TYPE_PROC,
+ CDEV_TYPE_FAN,
+ CDEV_TYPE_MEM,
+ CDEV_TYPE_NR,
+};
+
+/* REVISIT: the idea is to group sensors if possible, e.g. on intel mid
+ * we have "skin0", "skin1", "sys", "msicdie"
+ * on DPTF enabled systems, we might have PCH, TSKN, TAMB, etc.
+ */
+enum tzone_types {
+ TZONE_TYPE_ACPI,
+ TZONE_TYPE_PCH,
+ TZONE_TYPE_NR,
+};
+
+/* limit the output of PID controller adjustment */
+#define LIMIT_HIGH (95)
+#define LIMIT_LOW (2)
+
+struct pid_params {
+ double kp; /* Controller gain from Dialog Box */
+ double ki; /* Time-constant for I action from Dialog Box */
+ double kd; /* Time-constant for D action from Dialog Box */
+ double ts;
+ double k_lpf;
+
+ double t_target;
+ double y_k;
+};
+
+extern int init_thermal_controller(void);
+extern void controller_handler(const double xk, double *yk);
+
+extern struct tmon_platform_data ptdata;
+extern struct pid_params p_param;
+
+extern FILE *tmon_log;
+extern int cur_thermal_record; /* index to the trec array */
+extern struct thermal_data_record trec[];
+extern const char *trip_type_name[];
+extern unsigned long no_control;
+
+extern void initialize_curses(void);
+extern void show_controller_stats(char *line);
+extern void show_title_bar(void);
+extern void setup_windows(void);
+extern void disable_tui(void);
+extern void show_sensors_w(void);
+extern void show_data_w(void);
+extern void write_status_bar(int x, char *line);
+extern void show_control_w();
+
+extern void show_cooling_device(void);
+extern void show_dialogue(void);
+extern int update_thermal_data(void);
+
+extern int probe_thermal_sysfs(void);
+extern void free_thermal_data(void);
+extern void resize_handler(int sig);
+extern void set_ctrl_state(unsigned long state);
+extern void get_ctrl_state(unsigned long *state);
+extern void *handle_tui_events(void *arg);
+extern int sysfs_set_ulong(char *path, char *filename, unsigned long val);
+extern int zone_instance_to_index(int zone_inst);
+extern void close_windows(void);
+
+#define PT_COLOR_DEFAULT 1
+#define PT_COLOR_HEADER_BAR 2
+#define PT_COLOR_ERROR 3
+#define PT_COLOR_RED 4
+#define PT_COLOR_YELLOW 5
+#define PT_COLOR_GREEN 6
+#define PT_COLOR_BRIGHT 7
+#define PT_COLOR_BLUE 8
+
+/* each thermal zone uses 12 chars, 8 for name, 2 for instance, 2 space
+ * also used to list trip points in forms of AAAC, which represents
+ * A: Active
+ * C: Critical
+ */
+#define TZONE_RECORD_SIZE 12
+#define TZ_LEFT_ALIGN 32
+#define CDEV_NAME_SIZE 20
+#define CDEV_FLAG_IN_CONTROL (1 << 0)
+
+/* dialogue box starts */
+#define DIAG_X 48
+#define DIAG_Y 8
+#define THERMAL_SYSFS "/sys/class/thermal"
+#define CDEV "cooling_device"
+#define TZONE "thermal_zone"
+#define TDATA_LEFT 16
+#endif /* TMON_H */
diff --git a/tools/thermal/tmon/tui.c b/tools/thermal/tmon/tui.c
new file mode 100644
index 00000000000..89f8ef0e15c
--- /dev/null
+++ b/tools/thermal/tmon/tui.c
@@ -0,0 +1,638 @@
+/*
+ * tui.c ncurses text user interface for TMON program
+ *
+ * Copyright (C) 2013 Intel Corporation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License version
+ * 2 or later as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * Author: Jacob Pan <jacob.jun.pan@linux.intel.com>
+ *
+ */
+
+#include <unistd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdint.h>
+#include <ncurses.h>
+#include <time.h>
+#include <syslog.h>
+#include <panel.h>
+#include <pthread.h>
+#include <signal.h>
+
+#include "tmon.h"
+
+static PANEL *data_panel;
+static PANEL *dialogue_panel;
+static PANEL *top;
+
+static WINDOW *title_bar_window;
+static WINDOW *tz_sensor_window;
+static WINDOW *cooling_device_window;
+static WINDOW *control_window;
+static WINDOW *status_bar_window;
+static WINDOW *thermal_data_window;
+static WINDOW *dialogue_window;
+
+char status_bar_slots[10][40];
+static void draw_hbar(WINDOW *win, int y, int start, int len,
+ unsigned long pattern, bool end);
+
+static int maxx, maxy;
+static int maxwidth = 200;
+
+#define TITLE_BAR_HIGHT 1
+#define SENSOR_WIN_HIGHT 4 /* one row for tz name, one for trip points */
+
+
+/* daemon mode flag (set by startup parameter -d) */
+static int tui_disabled;
+
+static void close_panel(PANEL *p)
+{
+ if (p) {
+ del_panel(p);
+ p = NULL;
+ }
+}
+
+static void close_window(WINDOW *win)
+{
+ if (win) {
+ delwin(win);
+ win = NULL;
+ }
+}
+
+void close_windows(void)
+{
+ if (tui_disabled)
+ return;
+ /* must delete panels before their attached windows */
+ if (dialogue_window)
+ close_panel(dialogue_panel);
+ if (cooling_device_window)
+ close_panel(data_panel);
+
+ close_window(title_bar_window);
+ close_window(tz_sensor_window);
+ close_window(status_bar_window);
+ close_window(cooling_device_window);
+ close_window(control_window);
+ close_window(thermal_data_window);
+ close_window(dialogue_window);
+
+}
+
+void write_status_bar(int x, char *line)
+{
+ mvwprintw(status_bar_window, 0, x, "%s", line);
+ wrefresh(status_bar_window);
+}
+
+void setup_windows(void)
+{
+ int y_begin = 1;
+
+ if (tui_disabled)
+ return;
+
+ getmaxyx(stdscr, maxy, maxx);
+ resizeterm(maxy, maxx);
+
+ title_bar_window = subwin(stdscr, TITLE_BAR_HIGHT, maxx, 0, 0);
+ y_begin += TITLE_BAR_HIGHT;
+
+ tz_sensor_window = subwin(stdscr, SENSOR_WIN_HIGHT, maxx, y_begin, 0);
+ y_begin += SENSOR_WIN_HIGHT;
+
+ cooling_device_window = subwin(stdscr, ptdata.nr_cooling_dev + 3, maxx,
+ y_begin, 0);
+ y_begin += ptdata.nr_cooling_dev + 3; /* 2 lines for border */
+ /* two lines to show borders, one line per tz show trip point position
+ * and value.
+ * dialogue window is a pop-up, when needed it lays on top of cdev win
+ */
+
+ dialogue_window = subwin(stdscr, ptdata.nr_cooling_dev+5, maxx-50,
+ DIAG_Y, DIAG_X);
+
+ thermal_data_window = subwin(stdscr, ptdata.nr_tz_sensor *
+ NR_LINES_TZDATA + 3, maxx, y_begin, 0);
+ y_begin += ptdata.nr_tz_sensor * NR_LINES_TZDATA + 3;
+ control_window = subwin(stdscr, 4, maxx, y_begin, 0);
+
+ scrollok(cooling_device_window, TRUE);
+ maxwidth = maxx - 18;
+ status_bar_window = subwin(stdscr, 1, maxx, maxy-1, 0);
+
+ strcpy(status_bar_slots[0], " Ctrl-c - Quit ");
+ strcpy(status_bar_slots[1], " TAB - Tuning ");
+ wmove(status_bar_window, 1, 30);
+
+ /* prepare panels for dialogue, if panel already created then we must
+ * be doing resizing, so just replace windows with new ones, old ones
+ * should have been deleted by close_window
+ */
+ data_panel = new_panel(cooling_device_window);
+ if (!data_panel)
+ syslog(LOG_DEBUG, "No data panel\n");
+ else {
+ if (dialogue_window) {
+ dialogue_panel = new_panel(dialogue_window);
+ if (!dialogue_panel)
+ syslog(LOG_DEBUG, "No dialogue panel\n");
+ else {
+ /* Set up the user pointer to the next panel*/
+ set_panel_userptr(data_panel, dialogue_panel);
+ set_panel_userptr(dialogue_panel, data_panel);
+ top = data_panel;
+ }
+ } else
+ syslog(LOG_INFO, "no dialogue win, term too small\n");
+ }
+ doupdate();
+ werase(stdscr);
+ refresh();
+}
+
+void resize_handler(int sig)
+{
+ /* start over when term gets resized, but first we clean up */
+ close_windows();
+ endwin();
+ refresh();
+ clear();
+ getmaxyx(stdscr, maxy, maxx); /* get the new screen size */
+ setup_windows();
+ /* rate limit */
+ sleep(1);
+ syslog(LOG_DEBUG, "SIG %d, term resized to %d x %d\n",
+ sig, maxy, maxx);
+ signal(SIGWINCH, resize_handler);
+}
+
+const char cdev_title[] = " COOLING DEVICES ";
+void show_cooling_device(void)
+{
+ int i, j, x, y = 0;
+
+ if (tui_disabled || !cooling_device_window)
+ return;
+
+ werase(cooling_device_window);
+ wattron(cooling_device_window, A_BOLD);
+ mvwprintw(cooling_device_window, 1, 1,
+ "ID Cooling Dev Cur Max Thermal Zone Binding");
+ wattroff(cooling_device_window, A_BOLD);
+ for (j = 0; j < ptdata.nr_cooling_dev; j++) {
+ /* draw cooling device list on the left in the order of
+ * cooling device instances. skip unused idr.
+ */
+ mvwprintw(cooling_device_window, j + 2, 1,
+ "%02d %12.12s%6d %6d",
+ ptdata.cdi[j].instance,
+ ptdata.cdi[j].type,
+ ptdata.cdi[j].cur_state,
+ ptdata.cdi[j].max_state);
+ }
+
+ /* show cdev binding, y is the global cooling device instance */
+ for (i = 0; i < ptdata.nr_tz_sensor; i++) {
+ int tz_inst = ptdata.tzi[i].instance;
+ for (j = 0; j < ptdata.nr_cooling_dev; j++) {
+ int cdev_inst;
+ y = j;
+ x = tz_inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN;
+
+ draw_hbar(cooling_device_window, y+2, x,
+ TZONE_RECORD_SIZE-1, ACS_VLINE, false);
+
+ /* draw a column of spaces to separate thermal zones */
+ mvwprintw(cooling_device_window, y+2, x-1, " ");
+ if (ptdata.tzi[i].cdev_binding) {
+ cdev_inst = ptdata.cdi[j].instance;
+ unsigned long trip_binding =
+ ptdata.tzi[i].trip_binding[cdev_inst];
+ int k = 0; /* per zone trip point id that
+ * binded to this cdev, one to
+ * many possible based on the
+ * binding bitmask.
+ */
+ syslog(LOG_DEBUG,
+ "bind tz%d cdev%d tp%lx %d cdev%lx\n",
+ i, j, trip_binding, y,
+ ptdata.tzi[i].cdev_binding);
+ /* draw each trip binding for the cdev */
+ while (trip_binding >>= 1) {
+ k++;
+ if (!(trip_binding & 1))
+ continue;
+ /* draw '*' to show binding */
+ mvwprintw(cooling_device_window,
+ y + 2,
+ x + ptdata.tzi[i].nr_trip_pts -
+ k - 1, "*");
+ }
+ }
+ }
+ }
+ /* draw border after data so that border will not be messed up
+ * even there is not enough space for all the data to be shown
+ */
+ wborder(cooling_device_window, 0, 0, 0, 0, 0, 0, 0, 0);
+ wattron(cooling_device_window, A_BOLD);
+ mvwprintw(cooling_device_window, 0, maxx/2 - sizeof(cdev_title),
+ cdev_title);
+ wattroff(cooling_device_window, A_BOLD);
+
+ wrefresh(cooling_device_window);
+}
+
+const char DIAG_TITLE[] = "[ TUNABLES ]";
+#define DIAG_DEV_ROWS 5
+void show_dialogue(void)
+{
+ int j, x = 0, y = 0;
+ WINDOW *w = dialogue_window;
+
+ if (tui_disabled || !w)
+ return;
+
+ werase(w);
+ box(w, 0, 0);
+ mvwprintw(w, 0, maxx/4, DIAG_TITLE);
+ /* list all the available tunables */
+ for (j = 0; j <= ptdata.nr_cooling_dev; j++) {
+ y = j % DIAG_DEV_ROWS;
+ if (y == 0 && j != 0)
+ x += 20;
+ if (j == ptdata.nr_cooling_dev)
+ /* save last choice for target temp */
+ mvwprintw(w, y+1, x+1, "%C-%.12s", 'A'+j, "Set Temp");
+ else
+ mvwprintw(w, y+1, x+1, "%C-%.10s-%2d", 'A'+j,
+ ptdata.cdi[j].type, ptdata.cdi[j].instance);
+ }
+ wattron(w, A_BOLD);
+ mvwprintw(w, DIAG_DEV_ROWS+1, 1, "Enter Choice [A-Z]?");
+ wattroff(w, A_BOLD);
+ /* y size of dialogue win is nr cdev + 5, so print legend
+ * at the bottom line
+ */
+ mvwprintw(w, ptdata.nr_cooling_dev+3, 1,
+ "Legend: A=Active, P=Passive, C=Critical");
+
+ wrefresh(dialogue_window);
+}
+
+void write_dialogue_win(char *buf, int y, int x)
+{
+ WINDOW *w = dialogue_window;
+
+ mvwprintw(w, y, x, "%s", buf);
+}
+
+const char control_title[] = " CONTROLS ";
+void show_control_w(void)
+{
+ unsigned long state;
+
+ get_ctrl_state(&state);
+
+ if (tui_disabled || !control_window)
+ return;
+
+ werase(control_window);
+ mvwprintw(control_window, 1, 1,
+ "PID gain: kp=%2.2f ki=%2.2f kd=%2.2f Output %2.2f",
+ p_param.kp, p_param.ki, p_param.kd, p_param.y_k);
+
+ mvwprintw(control_window, 2, 1,
+ "Target Temp: %2.1fC, Zone: %d, Control Device: %.12s",
+ p_param.t_target, target_thermal_zone, ctrl_cdev);
+
+ /* draw border last such that everything is within boundary */
+ wborder(control_window, 0, 0, 0, 0, 0, 0, 0, 0);
+ wattron(control_window, A_BOLD);
+ mvwprintw(control_window, 0, maxx/2 - sizeof(control_title),
+ control_title);
+ wattroff(control_window, A_BOLD);
+
+ wrefresh(control_window);
+}
+
+void initialize_curses(void)
+{
+ if (tui_disabled)
+ return;
+
+ initscr();
+ start_color();
+ keypad(stdscr, TRUE); /* enable keyboard mapping */
+ nonl(); /* tell curses not to do NL->CR/NL on output */
+ cbreak(); /* take input chars one at a time */
+ noecho(); /* dont echo input */
+ curs_set(0); /* turn off cursor */
+ use_default_colors();
+
+ init_pair(PT_COLOR_DEFAULT, COLOR_WHITE, COLOR_BLACK);
+ init_pair(PT_COLOR_HEADER_BAR, COLOR_BLACK, COLOR_WHITE);
+ init_pair(PT_COLOR_ERROR, COLOR_BLACK, COLOR_RED);
+ init_pair(PT_COLOR_RED, COLOR_WHITE, COLOR_RED);
+ init_pair(PT_COLOR_YELLOW, COLOR_WHITE, COLOR_YELLOW);
+ init_pair(PT_COLOR_GREEN, COLOR_WHITE, COLOR_GREEN);
+ init_pair(PT_COLOR_BLUE, COLOR_WHITE, COLOR_BLUE);
+ init_pair(PT_COLOR_BRIGHT, COLOR_WHITE, COLOR_BLACK);
+
+}
+
+void show_title_bar(void)
+{
+ int i;
+ int x = 0;
+
+ if (tui_disabled || !title_bar_window)
+ return;
+
+ wattrset(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR));
+ wbkgd(title_bar_window, COLOR_PAIR(PT_COLOR_HEADER_BAR));
+ werase(title_bar_window);
+
+ mvwprintw(title_bar_window, 0, 0,
+ " TMON v%s", VERSION);
+
+ wrefresh(title_bar_window);
+
+ werase(status_bar_window);
+
+ for (i = 0; i < 10; i++) {
+ if (strlen(status_bar_slots[i]) == 0)
+ continue;
+ wattron(status_bar_window, A_REVERSE);
+ mvwprintw(status_bar_window, 0, x, "%s", status_bar_slots[i]);
+ wattroff(status_bar_window, A_REVERSE);
+ x += strlen(status_bar_slots[i]) + 1;
+ }
+ wrefresh(status_bar_window);
+}
+
+static void handle_input_val(int ch)
+{
+ char buf[32];
+ int val;
+ char path[256];
+ WINDOW *w = dialogue_window;
+
+ echo();
+ keypad(w, TRUE);
+ wgetnstr(w, buf, 31);
+ val = atoi(buf);
+
+ if (ch == ptdata.nr_cooling_dev) {
+ snprintf(buf, 31, "Invalid Temp %d! %d-%d", val,
+ MIN_CTRL_TEMP, MAX_CTRL_TEMP);
+ if (val < MIN_CTRL_TEMP || val > MAX_CTRL_TEMP)
+ write_status_bar(40, buf);
+ else {
+ p_param.t_target = val;
+ snprintf(buf, 31, "Set New Target Temp %d", val);
+ write_status_bar(40, buf);
+ }
+ } else {
+ snprintf(path, 256, "%s/%s%d", THERMAL_SYSFS,
+ CDEV, ptdata.cdi[ch].instance);
+ sysfs_set_ulong(path, "cur_state", val);
+ }
+ noecho();
+ dialogue_on = 0;
+ show_data_w();
+ show_control_w();
+
+ top = (PANEL *)panel_userptr(top);
+ top_panel(top);
+}
+
+static void handle_input_choice(int ch)
+{
+ char buf[48];
+ int base = 0;
+ int cdev_id = 0;
+
+ if ((ch >= 'A' && ch <= 'A' + ptdata.nr_cooling_dev) ||
+ (ch >= 'a' && ch <= 'a' + ptdata.nr_cooling_dev)) {
+ base = (ch < 'a') ? 'A' : 'a';
+ cdev_id = ch - base;
+ if (ptdata.nr_cooling_dev == cdev_id)
+ snprintf(buf, sizeof(buf), "New Target Temp:");
+ else
+ snprintf(buf, sizeof(buf), "New Value for %.10s-%2d: ",
+ ptdata.cdi[cdev_id].type,
+ ptdata.cdi[cdev_id].instance);
+ write_dialogue_win(buf, DIAG_DEV_ROWS+2, 2);
+ handle_input_val(cdev_id);
+ } else {
+ snprintf(buf, sizeof(buf), "Invalid selection %d", ch);
+ write_dialogue_win(buf, 8, 2);
+ }
+}
+
+void *handle_tui_events(void *arg)
+{
+ int ch;
+
+ keypad(cooling_device_window, TRUE);
+ while ((ch = wgetch(cooling_device_window)) != EOF) {
+ if (tmon_exit)
+ break;
+ /* when term size is too small, no dialogue panels are set.
+ * we need to filter out such cases.
+ */
+ if (!data_panel || !dialogue_panel ||
+ !cooling_device_window ||
+ !dialogue_window) {
+
+ continue;
+ }
+ pthread_mutex_lock(&input_lock);
+ if (dialogue_on) {
+ handle_input_choice(ch);
+ /* top panel filter */
+ if (ch == 'q' || ch == 'Q')
+ ch = 0;
+ }
+ switch (ch) {
+ case KEY_LEFT:
+ box(cooling_device_window, 10, 0);
+ break;
+ case 9: /* TAB */
+ top = (PANEL *)panel_userptr(top);
+ top_panel(top);
+ if (top == dialogue_panel) {
+ dialogue_on = 1;
+ show_dialogue();
+ } else {
+ dialogue_on = 0;
+ /* force refresh */
+ show_data_w();
+ show_control_w();
+ }
+ break;
+ case 'q':
+ case 'Q':
+ tmon_exit = 1;
+ break;
+ }
+ update_panels();
+ doupdate();
+ pthread_mutex_unlock(&input_lock);
+ }
+
+ if (arg)
+ *(int *)arg = 0; /* make gcc happy */
+
+ return NULL;
+}
+
+/* draw a horizontal bar in given pattern */
+static void draw_hbar(WINDOW *win, int y, int start, int len, unsigned long ptn,
+ bool end)
+{
+ mvwaddch(win, y, start, ptn);
+ whline(win, ptn, len);
+ if (end)
+ mvwaddch(win, y, MAX_DISP_TEMP+TDATA_LEFT, ']');
+}
+
+static char trip_type_to_char(int type)
+{
+ switch (type) {
+ case THERMAL_TRIP_CRITICAL: return 'C';
+ case THERMAL_TRIP_HOT: return 'H';
+ case THERMAL_TRIP_PASSIVE: return 'P';
+ case THERMAL_TRIP_ACTIVE: return 'A';
+ default:
+ return '?';
+ }
+}
+
+/* fill a string with trip point type and value in one line
+ * e.g. P(56) C(106)
+ * maintain the distance one degree per char
+ */
+static void draw_tp_line(int tz, int y)
+{
+ int j;
+ int x;
+
+ for (j = 0; j < ptdata.tzi[tz].nr_trip_pts; j++) {
+ x = ptdata.tzi[tz].tp[j].temp / 1000;
+ mvwprintw(thermal_data_window, y + 0, x + TDATA_LEFT,
+ "%c%d", trip_type_to_char(ptdata.tzi[tz].tp[j].type),
+ x);
+ syslog(LOG_INFO, "%s:tz %d tp %d temp = %lu\n", __func__,
+ tz, j, ptdata.tzi[tz].tp[j].temp);
+ }
+}
+
+const char data_win_title[] = " THERMAL DATA ";
+void show_data_w(void)
+{
+ int i;
+
+
+ if (tui_disabled || !thermal_data_window)
+ return;
+
+ werase(thermal_data_window);
+ wattron(thermal_data_window, A_BOLD);
+ mvwprintw(thermal_data_window, 0, maxx/2 - sizeof(data_win_title),
+ data_win_title);
+ wattroff(thermal_data_window, A_BOLD);
+ /* draw a line as ruler */
+ for (i = 10; i < MAX_DISP_TEMP; i += 10)
+ mvwprintw(thermal_data_window, 1, i+TDATA_LEFT, "%2d", i);
+
+ for (i = 0; i < ptdata.nr_tz_sensor; i++) {
+ int temp = trec[cur_thermal_record].temp[i] / 1000;
+ int y = 0;
+
+ y = i * NR_LINES_TZDATA + 2;
+ /* y at tz temp data line */
+ mvwprintw(thermal_data_window, y, 1, "%6.6s%2d:[%3d][",
+ ptdata.tzi[i].type,
+ ptdata.tzi[i].instance, temp);
+ draw_hbar(thermal_data_window, y, TDATA_LEFT, temp, ACS_RARROW,
+ true);
+ draw_tp_line(i, y);
+ }
+ wborder(thermal_data_window, 0, 0, 0, 0, 0, 0, 0, 0);
+ wrefresh(thermal_data_window);
+}
+
+const char tz_title[] = "THERMAL ZONES(SENSORS)";
+
+void show_sensors_w(void)
+{
+ int i, j;
+ char buffer[512];
+
+ if (tui_disabled || !tz_sensor_window)
+ return;
+
+ werase(tz_sensor_window);
+
+ memset(buffer, 0, sizeof(buffer));
+ wattron(tz_sensor_window, A_BOLD);
+ mvwprintw(tz_sensor_window, 1, 1, "Thermal Zones:");
+ wattroff(tz_sensor_window, A_BOLD);
+
+ mvwprintw(tz_sensor_window, 1, TZ_LEFT_ALIGN, "%s", buffer);
+ /* fill trip points for each tzone */
+ wattron(tz_sensor_window, A_BOLD);
+ mvwprintw(tz_sensor_window, 2, 1, "Trip Points:");
+ wattroff(tz_sensor_window, A_BOLD);
+
+ /* draw trip point from low to high for each tz */
+ for (i = 0; i < ptdata.nr_tz_sensor; i++) {
+ int inst = ptdata.tzi[i].instance;
+
+ mvwprintw(tz_sensor_window, 1,
+ TZ_LEFT_ALIGN+TZONE_RECORD_SIZE * inst, "%.9s%02d",
+ ptdata.tzi[i].type, ptdata.tzi[i].instance);
+ for (j = ptdata.tzi[i].nr_trip_pts - 1; j >= 0; j--) {
+ /* loop through all trip points */
+ char type;
+ int tp_pos;
+ /* reverse the order here since trips are sorted
+ * in ascending order in terms of temperature.
+ */
+ tp_pos = ptdata.tzi[i].nr_trip_pts - j - 1;
+
+ type = trip_type_to_char(ptdata.tzi[i].tp[j].type);
+ mvwaddch(tz_sensor_window, 2,
+ inst * TZONE_RECORD_SIZE + TZ_LEFT_ALIGN +
+ tp_pos, type);
+ syslog(LOG_DEBUG, "draw tz %d tp %d ch:%c\n",
+ inst, j, type);
+ }
+ }
+ wborder(tz_sensor_window, 0, 0, 0, 0, 0, 0, 0, 0);
+ wattron(tz_sensor_window, A_BOLD);
+ mvwprintw(tz_sensor_window, 0, maxx/2 - sizeof(tz_title), tz_title);
+ wattroff(tz_sensor_window, A_BOLD);
+ wrefresh(tz_sensor_window);
+}
+
+void disable_tui(void)
+{
+ tui_disabled = 1;
+}
diff --git a/tools/usb/Makefile b/tools/usb/Makefile
new file mode 100644
index 00000000000..acf2165c04e
--- /dev/null
+++ b/tools/usb/Makefile
@@ -0,0 +1,14 @@
+# Makefile for USB tools
+
+CC = $(CROSS_COMPILE)gcc
+PTHREAD_LIBS = -lpthread
+WARNINGS = -Wall -Wextra
+CFLAGS = $(WARNINGS) -g -I../include
+LDFLAGS = $(PTHREAD_LIBS)
+
+all: testusb ffs-test
+%: %.c
+ $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+clean:
+ $(RM) testusb ffs-test
diff --git a/tools/usb/ffs-aio-example/multibuff/device_app/aio_multibuff.c b/tools/usb/ffs-aio-example/multibuff/device_app/aio_multibuff.c
new file mode 100644
index 00000000000..87216a0c4a8
--- /dev/null
+++ b/tools/usb/ffs-aio-example/multibuff/device_app/aio_multibuff.c
@@ -0,0 +1,349 @@
+#define _BSD_SOURCE /* for endian.h */
+
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/poll.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <sys/eventfd.h>
+
+#include "libaio.h"
+#define IOCB_FLAG_RESFD (1 << 0)
+
+#include <linux/usb/functionfs.h>
+
+#define BUF_LEN 8192
+#define BUFS_MAX 128
+#define AIO_MAX (BUFS_MAX*2)
+
+/******************** Descriptors and Strings *******************************/
+
+static const struct {
+ struct usb_functionfs_descs_head header;
+ struct {
+ struct usb_interface_descriptor intf;
+ struct usb_endpoint_descriptor_no_audio bulk_sink;
+ struct usb_endpoint_descriptor_no_audio bulk_source;
+ } __attribute__ ((__packed__)) fs_descs, hs_descs;
+} __attribute__ ((__packed__)) descriptors = {
+ .header = {
+ .magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC),
+ .length = htole32(sizeof(descriptors)),
+ .fs_count = 3,
+ .hs_count = 3,
+ },
+ .fs_descs = {
+ .intf = {
+ .bLength = sizeof(descriptors.fs_descs.intf),
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .iInterface = 1,
+ },
+ .bulk_sink = {
+ .bLength = sizeof(descriptors.fs_descs.bulk_sink),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 1 | USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ },
+ .bulk_source = {
+ .bLength = sizeof(descriptors.fs_descs.bulk_source),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 2 | USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ },
+ },
+ .hs_descs = {
+ .intf = {
+ .bLength = sizeof(descriptors.hs_descs.intf),
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .iInterface = 1,
+ },
+ .bulk_sink = {
+ .bLength = sizeof(descriptors.hs_descs.bulk_sink),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 1 | USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = htole16(512),
+ },
+ .bulk_source = {
+ .bLength = sizeof(descriptors.hs_descs.bulk_source),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 2 | USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = htole16(512),
+ },
+ },
+};
+
+#define STR_INTERFACE "AIO Test"
+
+static const struct {
+ struct usb_functionfs_strings_head header;
+ struct {
+ __le16 code;
+ const char str1[sizeof(STR_INTERFACE)];
+ } __attribute__ ((__packed__)) lang0;
+} __attribute__ ((__packed__)) strings = {
+ .header = {
+ .magic = htole32(FUNCTIONFS_STRINGS_MAGIC),
+ .length = htole32(sizeof(strings)),
+ .str_count = htole32(1),
+ .lang_count = htole32(1),
+ },
+ .lang0 = {
+ htole16(0x0409), /* en-us */
+ STR_INTERFACE,
+ },
+};
+
+/********************** Buffer structure *******************************/
+
+struct io_buffer {
+ struct iocb **iocb;
+ unsigned char **buf;
+ unsigned cnt;
+ unsigned len;
+ unsigned requested;
+};
+
+/******************** Endpoints handling *******************************/
+
+static void display_event(struct usb_functionfs_event *event)
+{
+ static const char *const names[] = {
+ [FUNCTIONFS_BIND] = "BIND",
+ [FUNCTIONFS_UNBIND] = "UNBIND",
+ [FUNCTIONFS_ENABLE] = "ENABLE",
+ [FUNCTIONFS_DISABLE] = "DISABLE",
+ [FUNCTIONFS_SETUP] = "SETUP",
+ [FUNCTIONFS_SUSPEND] = "SUSPEND",
+ [FUNCTIONFS_RESUME] = "RESUME",
+ };
+ switch (event->type) {
+ case FUNCTIONFS_BIND:
+ case FUNCTIONFS_UNBIND:
+ case FUNCTIONFS_ENABLE:
+ case FUNCTIONFS_DISABLE:
+ case FUNCTIONFS_SETUP:
+ case FUNCTIONFS_SUSPEND:
+ case FUNCTIONFS_RESUME:
+ printf("Event %s\n", names[event->type]);
+ }
+}
+
+static void handle_ep0(int ep0, bool *ready)
+{
+ int ret;
+ struct usb_functionfs_event event;
+
+ ret = read(ep0, &event, sizeof(event));
+ if (!ret) {
+ perror("unable to read event from ep0");
+ return;
+ }
+ display_event(&event);
+ switch (event.type) {
+ case FUNCTIONFS_SETUP:
+ if (event.u.setup.bRequestType & USB_DIR_IN)
+ write(ep0, NULL, 0);
+ else
+ read(ep0, NULL, 0);
+ break;
+
+ case FUNCTIONFS_ENABLE:
+ *ready = true;
+ break;
+
+ case FUNCTIONFS_DISABLE:
+ *ready = false;
+ break;
+
+ default:
+ break;
+ }
+}
+
+void init_bufs(struct io_buffer *iobuf, unsigned n, unsigned len)
+{
+ unsigned i;
+ iobuf->buf = malloc(n*sizeof(*iobuf->buf));
+ iobuf->iocb = malloc(n*sizeof(*iobuf->iocb));
+ iobuf->cnt = n;
+ iobuf->len = len;
+ iobuf->requested = 0;
+ for (i = 0; i < n; ++i) {
+ iobuf->buf[i] = malloc(len*sizeof(**iobuf->buf));
+ iobuf->iocb[i] = malloc(sizeof(**iobuf->iocb));
+ }
+ iobuf->cnt = n;
+}
+
+void delete_bufs(struct io_buffer *iobuf)
+{
+ unsigned i;
+ for (i = 0; i < iobuf->cnt; ++i) {
+ free(iobuf->buf[i]);
+ free(iobuf->iocb[i]);
+ }
+ free(iobuf->buf);
+ free(iobuf->iocb);
+}
+
+int main(int argc, char *argv[])
+{
+ int ret;
+ unsigned i, j;
+ char *ep_path;
+
+ int ep0, ep1;
+
+ io_context_t ctx;
+
+ int evfd;
+ fd_set rfds;
+
+ struct io_buffer iobuf[2];
+ int actual = 0;
+ bool ready;
+
+ if (argc != 2) {
+ printf("ffs directory not specified!\n");
+ return 1;
+ }
+
+ ep_path = malloc(strlen(argv[1]) + 4 /* "/ep#" */ + 1 /* '\0' */);
+ if (!ep_path) {
+ perror("malloc");
+ return 1;
+ }
+
+ /* open endpoint files */
+ sprintf(ep_path, "%s/ep0", argv[1]);
+ ep0 = open(ep_path, O_RDWR);
+ if (ep0 < 0) {
+ perror("unable to open ep0");
+ return 1;
+ }
+ if (write(ep0, &descriptors, sizeof(descriptors)) < 0) {
+ perror("unable do write descriptors");
+ return 1;
+ }
+ if (write(ep0, &strings, sizeof(strings)) < 0) {
+ perror("unable to write strings");
+ return 1;
+ }
+ sprintf(ep_path, "%s/ep1", argv[1]);
+ ep1 = open(ep_path, O_RDWR);
+ if (ep1 < 0) {
+ perror("unable to open ep1");
+ return 1;
+ }
+
+ free(ep_path);
+
+ memset(&ctx, 0, sizeof(ctx));
+ /* setup aio context to handle up to AIO_MAX requests */
+ if (io_setup(AIO_MAX, &ctx) < 0) {
+ perror("unable to setup aio");
+ return 1;
+ }
+
+ evfd = eventfd(0, 0);
+ if (evfd < 0) {
+ perror("unable to open eventfd");
+ return 1;
+ }
+
+ for (i = 0; i < sizeof(iobuf)/sizeof(*iobuf); ++i)
+ init_bufs(&iobuf[i], BUFS_MAX, BUF_LEN);
+
+ while (1) {
+ FD_ZERO(&rfds);
+ FD_SET(ep0, &rfds);
+ FD_SET(evfd, &rfds);
+
+ ret = select(((ep0 > evfd) ? ep0 : evfd)+1,
+ &rfds, NULL, NULL, NULL);
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("select");
+ break;
+ }
+
+ if (FD_ISSET(ep0, &rfds))
+ handle_ep0(ep0, &ready);
+
+ /* we are waiting for function ENABLE */
+ if (!ready)
+ continue;
+
+ /*
+ * when we're preparing new data to submit,
+ * second buffer being transmitted
+ */
+ for (i = 0; i < sizeof(iobuf)/sizeof(*iobuf); ++i) {
+ if (iobuf[i].requested)
+ continue;
+ /* prepare requests */
+ for (j = 0; j < iobuf[i].cnt; ++j) {
+ io_prep_pwrite(iobuf[i].iocb[j], ep1,
+ iobuf[i].buf[j],
+ iobuf[i].len, 0);
+ /* enable eventfd notification */
+ iobuf[i].iocb[j]->u.c.flags |= IOCB_FLAG_RESFD;
+ iobuf[i].iocb[j]->u.c.resfd = evfd;
+ }
+ /* submit table of requests */
+ ret = io_submit(ctx, iobuf[i].cnt, iobuf[i].iocb);
+ if (ret >= 0) {
+ iobuf[i].requested = ret;
+ printf("submit: %d requests buf: %d\n", ret, i);
+ } else
+ perror("unable to submit reqests");
+ }
+
+ /* if event is ready to read */
+ if (!FD_ISSET(evfd, &rfds))
+ continue;
+
+ uint64_t ev_cnt;
+ ret = read(evfd, &ev_cnt, sizeof(ev_cnt));
+ if (ret < 0) {
+ perror("unable to read eventfd");
+ break;
+ }
+
+ struct io_event e[BUFS_MAX];
+ /* we read aio events */
+ ret = io_getevents(ctx, 1, BUFS_MAX, e, NULL);
+ if (ret > 0) /* if we got events */
+ iobuf[actual].requested -= ret;
+
+ /* if all req's from iocb completed */
+ if (!iobuf[actual].requested)
+ actual = (actual + 1)%(sizeof(iobuf)/sizeof(*iobuf));
+ }
+
+ /* free resources */
+
+ for (i = 0; i < sizeof(iobuf)/sizeof(*iobuf); ++i)
+ delete_bufs(&iobuf[i]);
+ io_destroy(ctx);
+
+ close(ep1);
+ close(ep0);
+
+ return 0;
+}
diff --git a/tools/usb/ffs-aio-example/multibuff/host_app/Makefile b/tools/usb/ffs-aio-example/multibuff/host_app/Makefile
new file mode 100644
index 00000000000..8c4a6f0aa82
--- /dev/null
+++ b/tools/usb/ffs-aio-example/multibuff/host_app/Makefile
@@ -0,0 +1,13 @@
+CC = gcc
+LIBUSB_CFLAGS = $(shell pkg-config --cflags libusb-1.0)
+LIBUSB_LIBS = $(shell pkg-config --libs libusb-1.0)
+WARNINGS = -Wall -Wextra
+CFLAGS = $(LIBUSB_CFLAGS) $(WARNINGS)
+LDFLAGS = $(LIBUSB_LIBS)
+
+all: test
+%: %.c
+ $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+clean:
+ $(RM) test
diff --git a/tools/usb/ffs-aio-example/multibuff/host_app/test.c b/tools/usb/ffs-aio-example/multibuff/host_app/test.c
new file mode 100644
index 00000000000..b0ad8747d03
--- /dev/null
+++ b/tools/usb/ffs-aio-example/multibuff/host_app/test.c
@@ -0,0 +1,146 @@
+#include <libusb.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define VENDOR 0x1d6b
+#define PRODUCT 0x0105
+
+/* endpoints indexes */
+
+#define EP_BULK_IN (1 | LIBUSB_ENDPOINT_IN)
+#define EP_BULK_OUT (2 | LIBUSB_ENDPOINT_OUT)
+
+#define BUF_LEN 8192
+
+/*
+ * struct test_state - describes test program state
+ * @list: list of devices returned by libusb_get_device_list function
+ * @found: pointer to struct describing tested device
+ * @ctx: context, set to NULL
+ * @handle: handle of tested device
+ * @attached: indicates that device was attached to kernel, and has to be
+ * reattached at the end of test program
+ */
+
+struct test_state {
+ libusb_device *found;
+ libusb_context *ctx;
+ libusb_device_handle *handle;
+ int attached;
+};
+
+/*
+ * test_init - initialize test program
+ */
+
+int test_init(struct test_state *state)
+{
+ int i, ret;
+ ssize_t cnt;
+ libusb_device **list;
+
+ state->found = NULL;
+ state->ctx = NULL;
+ state->handle = NULL;
+ state->attached = 0;
+
+ ret = libusb_init(&state->ctx);
+ if (ret) {
+ printf("cannot init libusb: %s\n", libusb_error_name(ret));
+ return 1;
+ }
+
+ cnt = libusb_get_device_list(state->ctx, &list);
+ if (cnt <= 0) {
+ printf("no devices found\n");
+ goto error1;
+ }
+
+ for (i = 0; i < cnt; ++i) {
+ libusb_device *dev = list[i];
+ struct libusb_device_descriptor desc;
+ ret = libusb_get_device_descriptor(dev, &desc);
+ if (ret) {
+ printf("unable to get device descriptor: %s\n",
+ libusb_error_name(ret));
+ goto error2;
+ }
+ if (desc.idVendor == VENDOR && desc.idProduct == PRODUCT) {
+ state->found = dev;
+ break;
+ }
+ }
+
+ if (!state->found) {
+ printf("no devices found\n");
+ goto error2;
+ }
+
+ ret = libusb_open(state->found, &state->handle);
+ if (ret) {
+ printf("cannot open device: %s\n", libusb_error_name(ret));
+ goto error2;
+ }
+
+ if (libusb_claim_interface(state->handle, 0)) {
+ ret = libusb_detach_kernel_driver(state->handle, 0);
+ if (ret) {
+ printf("unable to detach kernel driver: %s\n",
+ libusb_error_name(ret));
+ goto error3;
+ }
+ state->attached = 1;
+ ret = libusb_claim_interface(state->handle, 0);
+ if (ret) {
+ printf("cannot claim interface: %s\n",
+ libusb_error_name(ret));
+ goto error4;
+ }
+ }
+
+ return 0;
+
+error4:
+ if (state->attached == 1)
+ libusb_attach_kernel_driver(state->handle, 0);
+
+error3:
+ libusb_close(state->handle);
+
+error2:
+ libusb_free_device_list(list, 1);
+
+error1:
+ libusb_exit(state->ctx);
+ return 1;
+}
+
+/*
+ * test_exit - cleanup test program
+ */
+
+void test_exit(struct test_state *state)
+{
+ libusb_release_interface(state->handle, 0);
+ if (state->attached == 1)
+ libusb_attach_kernel_driver(state->handle, 0);
+ libusb_close(state->handle);
+ libusb_exit(state->ctx);
+}
+
+int main(void)
+{
+ struct test_state state;
+
+ if (test_init(&state))
+ return 1;
+
+ while (1) {
+ static unsigned char buffer[BUF_LEN];
+ int bytes;
+ libusb_bulk_transfer(state.handle, EP_BULK_IN, buffer, BUF_LEN,
+ &bytes, 500);
+ }
+ test_exit(&state);
+}
diff --git a/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c b/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c
new file mode 100644
index 00000000000..f558664a331
--- /dev/null
+++ b/tools/usb/ffs-aio-example/simple/device_app/aio_simple.c
@@ -0,0 +1,335 @@
+#define _BSD_SOURCE /* for endian.h */
+
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/poll.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <sys/eventfd.h>
+
+#include "libaio.h"
+#define IOCB_FLAG_RESFD (1 << 0)
+
+#include <linux/usb/functionfs.h>
+
+#define BUF_LEN 8192
+
+/******************** Descriptors and Strings *******************************/
+
+static const struct {
+ struct usb_functionfs_descs_head header;
+ struct {
+ struct usb_interface_descriptor intf;
+ struct usb_endpoint_descriptor_no_audio bulk_sink;
+ struct usb_endpoint_descriptor_no_audio bulk_source;
+ } __attribute__ ((__packed__)) fs_descs, hs_descs;
+} __attribute__ ((__packed__)) descriptors = {
+ .header = {
+ .magic = htole32(FUNCTIONFS_DESCRIPTORS_MAGIC),
+ .length = htole32(sizeof(descriptors)),
+ .fs_count = 3,
+ .hs_count = 3,
+ },
+ .fs_descs = {
+ .intf = {
+ .bLength = sizeof(descriptors.fs_descs.intf),
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .iInterface = 1,
+ },
+ .bulk_sink = {
+ .bLength = sizeof(descriptors.fs_descs.bulk_sink),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 1 | USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ },
+ .bulk_source = {
+ .bLength = sizeof(descriptors.fs_descs.bulk_source),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 2 | USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ },
+ },
+ .hs_descs = {
+ .intf = {
+ .bLength = sizeof(descriptors.hs_descs.intf),
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .iInterface = 1,
+ },
+ .bulk_sink = {
+ .bLength = sizeof(descriptors.hs_descs.bulk_sink),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 1 | USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ },
+ .bulk_source = {
+ .bLength = sizeof(descriptors.hs_descs.bulk_source),
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 2 | USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ },
+ },
+};
+
+#define STR_INTERFACE "AIO Test"
+
+static const struct {
+ struct usb_functionfs_strings_head header;
+ struct {
+ __le16 code;
+ const char str1[sizeof(STR_INTERFACE)];
+ } __attribute__ ((__packed__)) lang0;
+} __attribute__ ((__packed__)) strings = {
+ .header = {
+ .magic = htole32(FUNCTIONFS_STRINGS_MAGIC),
+ .length = htole32(sizeof(strings)),
+ .str_count = htole32(1),
+ .lang_count = htole32(1),
+ },
+ .lang0 = {
+ htole16(0x0409), /* en-us */
+ STR_INTERFACE,
+ },
+};
+
+/******************** Endpoints handling *******************************/
+
+static void display_event(struct usb_functionfs_event *event)
+{
+ static const char *const names[] = {
+ [FUNCTIONFS_BIND] = "BIND",
+ [FUNCTIONFS_UNBIND] = "UNBIND",
+ [FUNCTIONFS_ENABLE] = "ENABLE",
+ [FUNCTIONFS_DISABLE] = "DISABLE",
+ [FUNCTIONFS_SETUP] = "SETUP",
+ [FUNCTIONFS_SUSPEND] = "SUSPEND",
+ [FUNCTIONFS_RESUME] = "RESUME",
+ };
+ switch (event->type) {
+ case FUNCTIONFS_BIND:
+ case FUNCTIONFS_UNBIND:
+ case FUNCTIONFS_ENABLE:
+ case FUNCTIONFS_DISABLE:
+ case FUNCTIONFS_SETUP:
+ case FUNCTIONFS_SUSPEND:
+ case FUNCTIONFS_RESUME:
+ printf("Event %s\n", names[event->type]);
+ }
+}
+
+static void handle_ep0(int ep0, bool *ready)
+{
+ struct usb_functionfs_event event;
+ int ret;
+
+ struct pollfd pfds[1];
+ pfds[0].fd = ep0;
+ pfds[0].events = POLLIN;
+
+ ret = poll(pfds, 1, 0);
+
+ if (ret && (pfds[0].revents & POLLIN)) {
+ ret = read(ep0, &event, sizeof(event));
+ if (!ret) {
+ perror("unable to read event from ep0");
+ return;
+ }
+ display_event(&event);
+ switch (event.type) {
+ case FUNCTIONFS_SETUP:
+ if (event.u.setup.bRequestType & USB_DIR_IN)
+ write(ep0, NULL, 0);
+ else
+ read(ep0, NULL, 0);
+ break;
+
+ case FUNCTIONFS_ENABLE:
+ *ready = true;
+ break;
+
+ case FUNCTIONFS_DISABLE:
+ *ready = false;
+ break;
+
+ default:
+ break;
+ }
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ int i, ret;
+ char *ep_path;
+
+ int ep0;
+ int ep[2];
+
+ io_context_t ctx;
+
+ int evfd;
+ fd_set rfds;
+
+ char *buf_in, *buf_out;
+ struct iocb *iocb_in, *iocb_out;
+ int req_in = 0, req_out = 0;
+ bool ready;
+
+ if (argc != 2) {
+ printf("ffs directory not specified!\n");
+ return 1;
+ }
+
+ ep_path = malloc(strlen(argv[1]) + 4 /* "/ep#" */ + 1 /* '\0' */);
+ if (!ep_path) {
+ perror("malloc");
+ return 1;
+ }
+
+ /* open endpoint files */
+ sprintf(ep_path, "%s/ep0", argv[1]);
+ ep0 = open(ep_path, O_RDWR);
+ if (ep0 < 0) {
+ perror("unable to open ep0");
+ return 1;
+ }
+ if (write(ep0, &descriptors, sizeof(descriptors)) < 0) {
+ perror("unable do write descriptors");
+ return 1;
+ }
+ if (write(ep0, &strings, sizeof(strings)) < 0) {
+ perror("unable to write strings");
+ return 1;
+ }
+ for (i = 0; i < 2; ++i) {
+ sprintf(ep_path, "%s/ep%d", argv[1], i+1);
+ ep[i] = open(ep_path, O_RDWR);
+ if (ep[i] < 0) {
+ printf("unable to open ep%d: %s\n", i+1,
+ strerror(errno));
+ return 1;
+ }
+ }
+
+ free(ep_path);
+
+ memset(&ctx, 0, sizeof(ctx));
+ /* setup aio context to handle up to 2 requests */
+ if (io_setup(2, &ctx) < 0) {
+ perror("unable to setup aio");
+ return 1;
+ }
+
+ evfd = eventfd(0, 0);
+ if (evfd < 0) {
+ perror("unable to open eventfd");
+ return 1;
+ }
+
+ /* alloc buffers and requests */
+ buf_in = malloc(BUF_LEN);
+ buf_out = malloc(BUF_LEN);
+ iocb_in = malloc(sizeof(*iocb_in));
+ iocb_out = malloc(sizeof(*iocb_out));
+
+ while (1) {
+ FD_ZERO(&rfds);
+ FD_SET(ep0, &rfds);
+ FD_SET(evfd, &rfds);
+
+ ret = select(((ep0 > evfd) ? ep0 : evfd)+1,
+ &rfds, NULL, NULL, NULL);
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ perror("select");
+ break;
+ }
+
+ if (FD_ISSET(ep0, &rfds))
+ handle_ep0(ep0, &ready);
+
+ /* we are waiting for function ENABLE */
+ if (!ready)
+ continue;
+
+ /* if something was submitted we wait for event */
+ if (FD_ISSET(evfd, &rfds)) {
+ uint64_t ev_cnt;
+ ret = read(evfd, &ev_cnt, sizeof(ev_cnt));
+ if (ret < 0) {
+ perror("unable to read eventfd");
+ break;
+ }
+
+ struct io_event e[2];
+ /* we wait for one event */
+ ret = io_getevents(ctx, 1, 2, e, NULL);
+ /* if we got event */
+ for (i = 0; i < ret; ++i) {
+ if (e[i].obj->aio_fildes == ep[0]) {
+ printf("ev=in; ret=%lu\n", e[i].res);
+ req_in = 0;
+ } else if (e[i].obj->aio_fildes == ep[1]) {
+ printf("ev=out; ret=%lu\n", e[i].res);
+ req_out = 0;
+ }
+ }
+ }
+
+ if (!req_in) { /* if IN transfer not requested*/
+ /* prepare write request */
+ io_prep_pwrite(iocb_in, ep[0], buf_in, BUF_LEN, 0);
+ /* enable eventfd notification */
+ iocb_in->u.c.flags |= IOCB_FLAG_RESFD;
+ iocb_in->u.c.resfd = evfd;
+ /* submit table of requests */
+ ret = io_submit(ctx, 1, &iocb_in);
+ if (ret >= 0) { /* if ret > 0 request is queued */
+ req_in = 1;
+ printf("submit: in\n");
+ } else
+ perror("unable to submit request");
+ }
+ if (!req_out) { /* if OUT transfer not requested */
+ /* prepare read request */
+ io_prep_pread(iocb_out, ep[1], buf_out, BUF_LEN, 0);
+ /* enable eventfs notification */
+ iocb_out->u.c.flags |= IOCB_FLAG_RESFD;
+ iocb_out->u.c.resfd = evfd;
+ /* submit table of requests */
+ ret = io_submit(ctx, 1, &iocb_out);
+ if (ret >= 0) { /* if ret > 0 request is queued */
+ req_out = 1;
+ printf("submit: out\n");
+ } else
+ perror("unable to submit request");
+ }
+ }
+
+ /* free resources */
+
+ io_destroy(ctx);
+
+ free(buf_in);
+ free(buf_out);
+ free(iocb_in);
+ free(iocb_out);
+
+ for (i = 0; i < 2; ++i)
+ close(ep[i]);
+ close(ep0);
+
+ return 0;
+}
diff --git a/tools/usb/ffs-aio-example/simple/host_app/Makefile b/tools/usb/ffs-aio-example/simple/host_app/Makefile
new file mode 100644
index 00000000000..8c4a6f0aa82
--- /dev/null
+++ b/tools/usb/ffs-aio-example/simple/host_app/Makefile
@@ -0,0 +1,13 @@
+CC = gcc
+LIBUSB_CFLAGS = $(shell pkg-config --cflags libusb-1.0)
+LIBUSB_LIBS = $(shell pkg-config --libs libusb-1.0)
+WARNINGS = -Wall -Wextra
+CFLAGS = $(LIBUSB_CFLAGS) $(WARNINGS)
+LDFLAGS = $(LIBUSB_LIBS)
+
+all: test
+%: %.c
+ $(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
+
+clean:
+ $(RM) test
diff --git a/tools/usb/ffs-aio-example/simple/host_app/test.c b/tools/usb/ffs-aio-example/simple/host_app/test.c
new file mode 100644
index 00000000000..64b6a57d8ca
--- /dev/null
+++ b/tools/usb/ffs-aio-example/simple/host_app/test.c
@@ -0,0 +1,148 @@
+#include <libusb.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#define VENDOR 0x1d6b
+#define PRODUCT 0x0105
+
+/* endpoints indexes */
+
+#define EP_BULK_IN (1 | LIBUSB_ENDPOINT_IN)
+#define EP_BULK_OUT (2 | LIBUSB_ENDPOINT_OUT)
+
+#define BUF_LEN 8192
+
+/*
+ * struct test_state - describes test program state
+ * @list: list of devices returned by libusb_get_device_list function
+ * @found: pointer to struct describing tested device
+ * @ctx: context, set to NULL
+ * @handle: handle of tested device
+ * @attached: indicates that device was attached to kernel, and has to be
+ * reattached at the end of test program
+ */
+
+struct test_state {
+ libusb_device *found;
+ libusb_context *ctx;
+ libusb_device_handle *handle;
+ int attached;
+};
+
+/*
+ * test_init - initialize test program
+ */
+
+int test_init(struct test_state *state)
+{
+ int i, ret;
+ ssize_t cnt;
+ libusb_device **list;
+
+ state->found = NULL;
+ state->ctx = NULL;
+ state->handle = NULL;
+ state->attached = 0;
+
+ ret = libusb_init(&state->ctx);
+ if (ret) {
+ printf("cannot init libusb: %s\n", libusb_error_name(ret));
+ return 1;
+ }
+
+ cnt = libusb_get_device_list(state->ctx, &list);
+ if (cnt <= 0) {
+ printf("no devices found\n");
+ goto error1;
+ }
+
+ for (i = 0; i < cnt; ++i) {
+ libusb_device *dev = list[i];
+ struct libusb_device_descriptor desc;
+ ret = libusb_get_device_descriptor(dev, &desc);
+ if (ret) {
+ printf("unable to get device descriptor: %s\n",
+ libusb_error_name(ret));
+ goto error2;
+ }
+ if (desc.idVendor == VENDOR && desc.idProduct == PRODUCT) {
+ state->found = dev;
+ break;
+ }
+ }
+
+ if (!state->found) {
+ printf("no devices found\n");
+ goto error2;
+ }
+
+ ret = libusb_open(state->found, &state->handle);
+ if (ret) {
+ printf("cannot open device: %s\n", libusb_error_name(ret));
+ goto error2;
+ }
+
+ if (libusb_claim_interface(state->handle, 0)) {
+ ret = libusb_detach_kernel_driver(state->handle, 0);
+ if (ret) {
+ printf("unable to detach kernel driver: %s\n",
+ libusb_error_name(ret));
+ goto error3;
+ }
+ state->attached = 1;
+ ret = libusb_claim_interface(state->handle, 0);
+ if (ret) {
+ printf("cannot claim interface: %s\n",
+ libusb_error_name(ret));
+ goto error4;
+ }
+ }
+
+ return 0;
+
+error4:
+ if (state->attached == 1)
+ libusb_attach_kernel_driver(state->handle, 0);
+
+error3:
+ libusb_close(state->handle);
+
+error2:
+ libusb_free_device_list(list, 1);
+
+error1:
+ libusb_exit(state->ctx);
+ return 1;
+}
+
+/*
+ * test_exit - cleanup test program
+ */
+
+void test_exit(struct test_state *state)
+{
+ libusb_release_interface(state->handle, 0);
+ if (state->attached == 1)
+ libusb_attach_kernel_driver(state->handle, 0);
+ libusb_close(state->handle);
+ libusb_exit(state->ctx);
+}
+
+int main(void)
+{
+ struct test_state state;
+
+ if (test_init(&state))
+ return 1;
+
+ while (1) {
+ static unsigned char buffer[BUF_LEN];
+ int bytes;
+ libusb_bulk_transfer(state.handle, EP_BULK_IN, buffer, BUF_LEN,
+ &bytes, 500);
+ libusb_bulk_transfer(state.handle, EP_BULK_OUT, buffer, BUF_LEN,
+ &bytes, 500);
+ }
+ test_exit(&state);
+}
diff --git a/tools/usb/ffs-test.c b/tools/usb/ffs-test.c
new file mode 100644
index 00000000000..a87e99f37c5
--- /dev/null
+++ b/tools/usb/ffs-test.c
@@ -0,0 +1,527 @@
+/*
+ * ffs-test.c.c -- user mode filesystem api for usb composite function
+ *
+ * Copyright (C) 2010 Samsung Electronics
+ * Author: Michal Nazarewicz <mina86@mina86.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+ */
+
+/* $(CROSS_COMPILE)cc -Wall -Wextra -g -o ffs-test ffs-test.c -lpthread */
+
+
+#define _BSD_SOURCE /* for endian.h */
+
+#include <endian.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <pthread.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <tools/le_byteshift.h>
+
+#include "../../include/uapi/linux/usb/functionfs.h"
+
+
+/******************** Little Endian Handling ********************************/
+
+#define cpu_to_le16(x) htole16(x)
+#define cpu_to_le32(x) htole32(x)
+#define le32_to_cpu(x) le32toh(x)
+#define le16_to_cpu(x) le16toh(x)
+
+
+/******************** Messages and Errors ***********************************/
+
+static const char argv0[] = "ffs-test";
+
+static unsigned verbosity = 7;
+
+static void _msg(unsigned level, const char *fmt, ...)
+{
+ if (level < 2)
+ level = 2;
+ else if (level > 7)
+ level = 7;
+
+ if (level <= verbosity) {
+ static const char levels[8][6] = {
+ [2] = "crit:",
+ [3] = "err: ",
+ [4] = "warn:",
+ [5] = "note:",
+ [6] = "info:",
+ [7] = "dbg: "
+ };
+
+ int _errno = errno;
+ va_list ap;
+
+ fprintf(stderr, "%s: %s ", argv0, levels[level]);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+
+ if (fmt[strlen(fmt) - 1] != '\n') {
+ char buffer[128];
+ strerror_r(_errno, buffer, sizeof buffer);
+ fprintf(stderr, ": (-%d) %s\n", _errno, buffer);
+ }
+
+ fflush(stderr);
+ }
+}
+
+#define die(...) (_msg(2, __VA_ARGS__), exit(1))
+#define err(...) _msg(3, __VA_ARGS__)
+#define warn(...) _msg(4, __VA_ARGS__)
+#define note(...) _msg(5, __VA_ARGS__)
+#define info(...) _msg(6, __VA_ARGS__)
+#define debug(...) _msg(7, __VA_ARGS__)
+
+#define die_on(cond, ...) do { \
+ if (cond) \
+ die(__VA_ARGS__); \
+ } while (0)
+
+
+/******************** Descriptors and Strings *******************************/
+
+static const struct {
+ struct usb_functionfs_descs_head header;
+ struct {
+ struct usb_interface_descriptor intf;
+ struct usb_endpoint_descriptor_no_audio sink;
+ struct usb_endpoint_descriptor_no_audio source;
+ } __attribute__((packed)) fs_descs, hs_descs;
+} __attribute__((packed)) descriptors = {
+ .header = {
+ .magic = cpu_to_le32(FUNCTIONFS_DESCRIPTORS_MAGIC),
+ .length = cpu_to_le32(sizeof descriptors),
+ .fs_count = cpu_to_le32(3),
+ .hs_count = cpu_to_le32(3),
+ },
+ .fs_descs = {
+ .intf = {
+ .bLength = sizeof descriptors.fs_descs.intf,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .iInterface = 1,
+ },
+ .sink = {
+ .bLength = sizeof descriptors.fs_descs.sink,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 1 | USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ /* .wMaxPacketSize = autoconfiguration (kernel) */
+ },
+ .source = {
+ .bLength = sizeof descriptors.fs_descs.source,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 2 | USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ /* .wMaxPacketSize = autoconfiguration (kernel) */
+ },
+ },
+ .hs_descs = {
+ .intf = {
+ .bLength = sizeof descriptors.fs_descs.intf,
+ .bDescriptorType = USB_DT_INTERFACE,
+ .bNumEndpoints = 2,
+ .bInterfaceClass = USB_CLASS_VENDOR_SPEC,
+ .iInterface = 1,
+ },
+ .sink = {
+ .bLength = sizeof descriptors.hs_descs.sink,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 1 | USB_DIR_IN,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+ },
+ .source = {
+ .bLength = sizeof descriptors.hs_descs.source,
+ .bDescriptorType = USB_DT_ENDPOINT,
+ .bEndpointAddress = 2 | USB_DIR_OUT,
+ .bmAttributes = USB_ENDPOINT_XFER_BULK,
+ .wMaxPacketSize = cpu_to_le16(512),
+ .bInterval = 1, /* NAK every 1 uframe */
+ },
+ },
+};
+
+
+#define STR_INTERFACE_ "Source/Sink"
+
+static const struct {
+ struct usb_functionfs_strings_head header;
+ struct {
+ __le16 code;
+ const char str1[sizeof STR_INTERFACE_];
+ } __attribute__((packed)) lang0;
+} __attribute__((packed)) strings = {
+ .header = {
+ .magic = cpu_to_le32(FUNCTIONFS_STRINGS_MAGIC),
+ .length = cpu_to_le32(sizeof strings),
+ .str_count = cpu_to_le32(1),
+ .lang_count = cpu_to_le32(1),
+ },
+ .lang0 = {
+ cpu_to_le16(0x0409), /* en-us */
+ STR_INTERFACE_,
+ },
+};
+
+#define STR_INTERFACE strings.lang0.str1
+
+
+/******************** Files and Threads Handling ****************************/
+
+struct thread;
+
+static ssize_t read_wrap(struct thread *t, void *buf, size_t nbytes);
+static ssize_t write_wrap(struct thread *t, const void *buf, size_t nbytes);
+static ssize_t ep0_consume(struct thread *t, const void *buf, size_t nbytes);
+static ssize_t fill_in_buf(struct thread *t, void *buf, size_t nbytes);
+static ssize_t empty_out_buf(struct thread *t, const void *buf, size_t nbytes);
+
+
+static struct thread {
+ const char *const filename;
+ size_t buf_size;
+
+ ssize_t (*in)(struct thread *, void *, size_t);
+ const char *const in_name;
+
+ ssize_t (*out)(struct thread *, const void *, size_t);
+ const char *const out_name;
+
+ int fd;
+ pthread_t id;
+ void *buf;
+ ssize_t status;
+} threads[] = {
+ {
+ "ep0", 4 * sizeof(struct usb_functionfs_event),
+ read_wrap, NULL,
+ ep0_consume, "<consume>",
+ 0, 0, NULL, 0
+ },
+ {
+ "ep1", 8 * 1024,
+ fill_in_buf, "<in>",
+ write_wrap, NULL,
+ 0, 0, NULL, 0
+ },
+ {
+ "ep2", 8 * 1024,
+ read_wrap, NULL,
+ empty_out_buf, "<out>",
+ 0, 0, NULL, 0
+ },
+};
+
+
+static void init_thread(struct thread *t)
+{
+ t->buf = malloc(t->buf_size);
+ die_on(!t->buf, "malloc");
+
+ t->fd = open(t->filename, O_RDWR);
+ die_on(t->fd < 0, "%s", t->filename);
+}
+
+static void cleanup_thread(void *arg)
+{
+ struct thread *t = arg;
+ int ret, fd;
+
+ fd = t->fd;
+ if (t->fd < 0)
+ return;
+ t->fd = -1;
+
+ /* test the FIFO ioctls (non-ep0 code paths) */
+ if (t != threads) {
+ ret = ioctl(fd, FUNCTIONFS_FIFO_STATUS);
+ if (ret < 0) {
+ /* ENODEV reported after disconnect */
+ if (errno != ENODEV)
+ err("%s: get fifo status", t->filename);
+ } else if (ret) {
+ warn("%s: unclaimed = %d\n", t->filename, ret);
+ if (ioctl(fd, FUNCTIONFS_FIFO_FLUSH) < 0)
+ err("%s: fifo flush", t->filename);
+ }
+ }
+
+ if (close(fd) < 0)
+ err("%s: close", t->filename);
+
+ free(t->buf);
+ t->buf = NULL;
+}
+
+static void *start_thread_helper(void *arg)
+{
+ const char *name, *op, *in_name, *out_name;
+ struct thread *t = arg;
+ ssize_t ret;
+
+ info("%s: starts\n", t->filename);
+ in_name = t->in_name ? t->in_name : t->filename;
+ out_name = t->out_name ? t->out_name : t->filename;
+
+ pthread_cleanup_push(cleanup_thread, arg);
+
+ for (;;) {
+ pthread_testcancel();
+
+ ret = t->in(t, t->buf, t->buf_size);
+ if (ret > 0) {
+ ret = t->out(t, t->buf, ret);
+ name = out_name;
+ op = "write";
+ } else {
+ name = in_name;
+ op = "read";
+ }
+
+ if (ret > 0) {
+ /* nop */
+ } else if (!ret) {
+ debug("%s: %s: EOF", name, op);
+ break;
+ } else if (errno == EINTR || errno == EAGAIN) {
+ debug("%s: %s", name, op);
+ } else {
+ warn("%s: %s", name, op);
+ break;
+ }
+ }
+
+ pthread_cleanup_pop(1);
+
+ t->status = ret;
+ info("%s: ends\n", t->filename);
+ return NULL;
+}
+
+static void start_thread(struct thread *t)
+{
+ debug("%s: starting\n", t->filename);
+
+ die_on(pthread_create(&t->id, NULL, start_thread_helper, t) < 0,
+ "pthread_create(%s)", t->filename);
+}
+
+static void join_thread(struct thread *t)
+{
+ int ret = pthread_join(t->id, NULL);
+
+ if (ret < 0)
+ err("%s: joining thread", t->filename);
+ else
+ debug("%s: joined\n", t->filename);
+}
+
+
+static ssize_t read_wrap(struct thread *t, void *buf, size_t nbytes)
+{
+ return read(t->fd, buf, nbytes);
+}
+
+static ssize_t write_wrap(struct thread *t, const void *buf, size_t nbytes)
+{
+ return write(t->fd, buf, nbytes);
+}
+
+
+/******************** Empty/Fill buffer routines ****************************/
+
+/* 0 -- stream of zeros, 1 -- i % 63, 2 -- pipe */
+enum pattern { PAT_ZERO, PAT_SEQ, PAT_PIPE };
+static enum pattern pattern;
+
+static ssize_t
+fill_in_buf(struct thread *ignore, void *buf, size_t nbytes)
+{
+ size_t i;
+ __u8 *p;
+
+ (void)ignore;
+
+ switch (pattern) {
+ case PAT_ZERO:
+ memset(buf, 0, nbytes);
+ break;
+
+ case PAT_SEQ:
+ for (p = buf, i = 0; i < nbytes; ++i, ++p)
+ *p = i % 63;
+ break;
+
+ case PAT_PIPE:
+ return fread(buf, 1, nbytes, stdin);
+ }
+
+ return nbytes;
+}
+
+static ssize_t
+empty_out_buf(struct thread *ignore, const void *buf, size_t nbytes)
+{
+ const __u8 *p;
+ __u8 expected;
+ ssize_t ret;
+ size_t len;
+
+ (void)ignore;
+
+ switch (pattern) {
+ case PAT_ZERO:
+ expected = 0;
+ for (p = buf, len = 0; len < nbytes; ++p, ++len)
+ if (*p)
+ goto invalid;
+ break;
+
+ case PAT_SEQ:
+ for (p = buf, len = 0; len < nbytes; ++p, ++len)
+ if (*p != len % 63) {
+ expected = len % 63;
+ goto invalid;
+ }
+ break;
+
+ case PAT_PIPE:
+ ret = fwrite(buf, nbytes, 1, stdout);
+ if (ret > 0)
+ fflush(stdout);
+ break;
+
+invalid:
+ err("bad OUT byte %zd, expected %02x got %02x\n",
+ len, expected, *p);
+ for (p = buf, len = 0; len < nbytes; ++p, ++len) {
+ if (0 == (len % 32))
+ fprintf(stderr, "%4zd:", len);
+ fprintf(stderr, " %02x", *p);
+ if (31 == (len % 32))
+ fprintf(stderr, "\n");
+ }
+ fflush(stderr);
+ errno = EILSEQ;
+ return -1;
+ }
+
+ return len;
+}
+
+
+/******************** Endpoints routines ************************************/
+
+static void handle_setup(const struct usb_ctrlrequest *setup)
+{
+ printf("bRequestType = %d\n", setup->bRequestType);
+ printf("bRequest = %d\n", setup->bRequest);
+ printf("wValue = %d\n", le16_to_cpu(setup->wValue));
+ printf("wIndex = %d\n", le16_to_cpu(setup->wIndex));
+ printf("wLength = %d\n", le16_to_cpu(setup->wLength));
+}
+
+static ssize_t
+ep0_consume(struct thread *ignore, const void *buf, size_t nbytes)
+{
+ static const char *const names[] = {
+ [FUNCTIONFS_BIND] = "BIND",
+ [FUNCTIONFS_UNBIND] = "UNBIND",
+ [FUNCTIONFS_ENABLE] = "ENABLE",
+ [FUNCTIONFS_DISABLE] = "DISABLE",
+ [FUNCTIONFS_SETUP] = "SETUP",
+ [FUNCTIONFS_SUSPEND] = "SUSPEND",
+ [FUNCTIONFS_RESUME] = "RESUME",
+ };
+
+ const struct usb_functionfs_event *event = buf;
+ size_t n;
+
+ (void)ignore;
+
+ for (n = nbytes / sizeof *event; n; --n, ++event)
+ switch (event->type) {
+ case FUNCTIONFS_BIND:
+ case FUNCTIONFS_UNBIND:
+ case FUNCTIONFS_ENABLE:
+ case FUNCTIONFS_DISABLE:
+ case FUNCTIONFS_SETUP:
+ case FUNCTIONFS_SUSPEND:
+ case FUNCTIONFS_RESUME:
+ printf("Event %s\n", names[event->type]);
+ if (event->type == FUNCTIONFS_SETUP)
+ handle_setup(&event->u.setup);
+ break;
+
+ default:
+ printf("Event %03u (unknown)\n", event->type);
+ }
+
+ return nbytes;
+}
+
+static void ep0_init(struct thread *t)
+{
+ ssize_t ret;
+
+ info("%s: writing descriptors\n", t->filename);
+ ret = write(t->fd, &descriptors, sizeof descriptors);
+ die_on(ret < 0, "%s: write: descriptors", t->filename);
+
+ info("%s: writing strings\n", t->filename);
+ ret = write(t->fd, &strings, sizeof strings);
+ die_on(ret < 0, "%s: write: strings", t->filename);
+}
+
+
+/******************** Main **************************************************/
+
+int main(void)
+{
+ unsigned i;
+
+ /* XXX TODO: Argument parsing missing */
+
+ init_thread(threads);
+ ep0_init(threads);
+
+ for (i = 1; i < sizeof threads / sizeof *threads; ++i)
+ init_thread(threads + i);
+
+ for (i = 1; i < sizeof threads / sizeof *threads; ++i)
+ start_thread(threads + i);
+
+ start_thread_helper(threads);
+
+ for (i = 1; i < sizeof threads / sizeof *threads; ++i)
+ join_thread(threads + i);
+
+ return 0;
+}
diff --git a/tools/usb/hcd-tests.sh b/tools/usb/hcd-tests.sh
new file mode 100644
index 00000000000..b30b3dc4c78
--- /dev/null
+++ b/tools/usb/hcd-tests.sh
@@ -0,0 +1,275 @@
+#!/bin/sh
+#
+# test types can be passed on the command line:
+#
+# - control: any device can do this
+# - out, in: out needs 'bulk sink' firmware, in needs 'bulk src'
+# - iso-out, iso-in: out needs 'iso sink' firmware, in needs 'iso src'
+# - halt: needs bulk sink+src, tests halt set/clear from host
+# - unlink: needs bulk sink and/or src, test HCD unlink processing
+# - loop: needs firmware that will buffer N transfers
+#
+# run it for hours, days, weeks.
+#
+
+#
+# this default provides a steady test load for a bulk device
+#
+TYPES='control out in'
+#TYPES='control out in halt'
+
+#
+# to test HCD code
+#
+# - include unlink tests
+# - add some ${RANDOM}ness
+# - connect several devices concurrently (same HC)
+# - keep HC's IRQ lines busy with unrelated traffic (IDE, net, ...)
+# - add other concurrent system loads
+#
+
+declare -i COUNT BUFLEN
+
+COUNT=50000
+BUFLEN=2048
+
+# NOTE: the 'in' and 'out' cases are usually bulk, but can be
+# set up to use interrupt transfers by 'usbtest' module options
+
+
+if [ "$DEVICE" = "" ]; then
+ echo "testing ALL recognized usbtest devices"
+ echo ""
+ TEST_ARGS="-a"
+else
+ TEST_ARGS=""
+fi
+
+do_test ()
+{
+ if ! ./testusb $TEST_ARGS -s $BUFLEN -c $COUNT $* 2>/dev/null
+ then
+ echo "FAIL"
+ exit 1
+ fi
+}
+
+ARGS="$*"
+
+if [ "$ARGS" = "" ];
+then
+ ARGS="$TYPES"
+fi
+
+# FIXME use /sys/bus/usb/device/$THIS/bConfigurationValue to
+# check and change configs
+
+CONFIG=''
+
+check_config ()
+{
+ if [ "$CONFIG" = "" ]; then
+ CONFIG=$1
+ echo "assuming $CONFIG configuration"
+ return
+ fi
+ if [ "$CONFIG" = $1 ]; then
+ return
+ fi
+
+ echo "** device must be in $1 config, but it's $CONFIG instead"
+ exit 1
+}
+
+
+echo "TESTING: $ARGS"
+
+while : true
+do
+ echo $(date)
+
+ for TYPE in $ARGS
+ do
+ # restore defaults
+ COUNT=5000
+ BUFLEN=2048
+
+ # FIXME automatically multiply COUNT by 10 when
+ # /sys/bus/usb/device/$THIS/speed == "480"
+
+# COUNT=50000
+
+ case $TYPE in
+ control)
+ # any device, in any configuration, can use this.
+ echo '** Control test cases:'
+
+ echo "test 9: ch9 postconfig"
+ do_test -t 9 -c 5000
+ echo "test 10: control queueing"
+ do_test -t 10 -c 5000
+
+ # this relies on some vendor-specific commands
+ echo "test 14: control writes"
+ do_test -t 14 -c 15000 -s 256 -v 1
+
+ echo "test 21: control writes, unaligned"
+ do_test -t 21 -c 100 -s 256 -v 1
+
+ ;;
+
+ out)
+ check_config sink-src
+ echo '** Host Write (OUT) test cases:'
+
+ echo "test 1: $COUNT transfers, same size"
+ do_test -t 1
+ echo "test 3: $COUNT transfers, variable/short size"
+ do_test -t 3 -v 421
+
+ COUNT=100
+ echo "test 17: $COUNT transfers, unaligned DMA map by core"
+ do_test -t 17
+
+ echo "test 19: $COUNT transfers, unaligned DMA map by usb_alloc_coherent"
+ do_test -t 19
+
+ COUNT=2000
+ echo "test 5: $COUNT scatterlists, same size entries"
+ do_test -t 5
+
+ # try to trigger short OUT processing bugs
+ echo "test 7a: $COUNT scatterlists, variable size/short entries"
+ do_test -t 7 -v 579
+ BUFLEN=4096
+ echo "test 7b: $COUNT scatterlists, variable size/bigger entries"
+ do_test -t 7 -v 41
+ BUFLEN=64
+ echo "test 7c: $COUNT scatterlists, variable size/micro entries"
+ do_test -t 7 -v 63
+ ;;
+
+ iso-out)
+ check_config sink-src
+ echo '** Host ISOCHRONOUS Write (OUT) test cases:'
+
+ # at peak iso transfer rates:
+ # - usb 2.0 high bandwidth, this is one frame.
+ # - usb 1.1, it's twenty-four frames.
+ BUFLEN=24500
+
+ COUNT=1000
+
+# COUNT=10000
+
+ echo "test 15: $COUNT transfers, same size"
+ # do_test -t 15 -g 3 -v 0
+ BUFLEN=32768
+ do_test -t 15 -g 8 -v 0
+
+ # FIXME it'd make sense to have an iso OUT test issuing
+ # short writes on more packets than the last one
+
+ COUNT=100
+ echo "test 22: $COUNT transfers, non aligned"
+ do_test -t 22 -g 8 -v 0
+
+ ;;
+
+ in)
+ check_config sink-src
+ echo '** Host Read (IN) test cases:'
+
+ # NOTE: these "variable size" reads are just multiples
+ # of 512 bytes, no EOVERFLOW testing is done yet
+
+ echo "test 2: $COUNT transfers, same size"
+ do_test -t 2
+ echo "test 4: $COUNT transfers, variable size"
+ do_test -t 4
+
+ COUNT=100
+ echo "test 18: $COUNT transfers, unaligned DMA map by core"
+ do_test -t 18
+
+ echo "test 20: $COUNT transfers, unaligned DMA map by usb_alloc_coherent"
+ do_test -t 20
+
+ COUNT=2000
+ echo "test 6: $COUNT scatterlists, same size entries"
+ do_test -t 6
+ echo "test 8: $COUNT scatterlists, variable size entries"
+ do_test -t 8
+ ;;
+
+ iso-in)
+ check_config sink-src
+ echo '** Host ISOCHRONOUS Read (IN) test cases:'
+
+ # at peak iso transfer rates:
+ # - usb 2.0 high bandwidth, this is one frame.
+ # - usb 1.1, it's twenty-four frames.
+ BUFLEN=24500
+
+ COUNT=1000
+
+# COUNT=10000
+
+ echo "test 16: $COUNT transfers, same size"
+ # do_test -t 16 -g 3 -v 0
+ BUFLEN=32768
+ do_test -t 16 -g 8 -v 0
+
+ # FIXME since iso expects faults, it'd make sense
+ # to have an iso IN test issuing short reads ...
+
+ COUNT=100
+ echo "test 23: $COUNT transfers, unaligned"
+ do_test -t 23 -g 8 -v 0
+
+ ;;
+
+ halt)
+ # NOTE: sometimes hardware doesn't cooperate well with halting
+ # endpoints from the host side. so long as mass-storage class
+ # firmware can halt them from the device, don't worry much if
+ # you can't make this test work on your device.
+ COUNT=2000
+ echo "test 13: $COUNT halt set/clear"
+ do_test -t 13
+ ;;
+
+ unlink)
+ COUNT=2000
+ echo "test 11: $COUNT read unlinks"
+ do_test -t 11
+
+ echo "test 12: $COUNT write unlinks"
+ do_test -t 12
+ ;;
+
+ loop)
+ # defaults need too much buffering for ez-usb devices
+ BUFLEN=2048
+ COUNT=32
+
+ # modprobe g_zero qlen=$COUNT buflen=$BUFLEN loopdefault
+ check_config loopback
+
+ # FIXME someone needs to write and merge a version of this
+
+ echo "write $COUNT buffers of $BUFLEN bytes, read them back"
+
+ echo "write $COUNT variable size buffers, read them back"
+
+ ;;
+
+ *)
+ echo "Don't understand test type $TYPE"
+ exit 1;
+ esac
+ echo ''
+ done
+done
+
+# vim: sw=4
diff --git a/tools/usb/testusb.c b/tools/usb/testusb.c
new file mode 100644
index 00000000000..879f9870a6b
--- /dev/null
+++ b/tools/usb/testusb.c
@@ -0,0 +1,537 @@
+/* $(CROSS_COMPILE)cc -Wall -Wextra -g -lpthread -o testusb testusb.c */
+
+/*
+ * Copyright (c) 2002 by David Brownell
+ * Copyright (c) 2010 by Samsung Electronics
+ * Author: Michal Nazarewicz <mina86@mina86.com>
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but
+ * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
+ * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * This program issues ioctls to perform the tests implemented by the
+ * kernel driver. It can generate a variety of transfer patterns; you
+ * should make sure to test both regular streaming and mixes of
+ * transfer sizes (including short transfers).
+ *
+ * For more information on how this can be used and on USB testing
+ * refer to <URL:http://www.linux-usb.org/usbtest/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <ftw.h>
+#include <stdlib.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <errno.h>
+#include <limits.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#include <sys/ioctl.h>
+#include <linux/usbdevice_fs.h>
+
+/*-------------------------------------------------------------------------*/
+
+#define TEST_CASES 30
+
+// FIXME make these public somewhere; usbdevfs.h?
+
+struct usbtest_param {
+ // inputs
+ unsigned test_num; /* 0..(TEST_CASES-1) */
+ unsigned iterations;
+ unsigned length;
+ unsigned vary;
+ unsigned sglen;
+
+ // outputs
+ struct timeval duration;
+};
+#define USBTEST_REQUEST _IOWR('U', 100, struct usbtest_param)
+
+/*-------------------------------------------------------------------------*/
+
+/* #include <linux/usb_ch9.h> */
+
+#define USB_DT_DEVICE 0x01
+#define USB_DT_INTERFACE 0x04
+
+#define USB_CLASS_PER_INTERFACE 0 /* for DeviceClass */
+#define USB_CLASS_VENDOR_SPEC 0xff
+
+
+struct usb_device_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+ __u16 bcdUSB;
+ __u8 bDeviceClass;
+ __u8 bDeviceSubClass;
+ __u8 bDeviceProtocol;
+ __u8 bMaxPacketSize0;
+ __u16 idVendor;
+ __u16 idProduct;
+ __u16 bcdDevice;
+ __u8 iManufacturer;
+ __u8 iProduct;
+ __u8 iSerialNumber;
+ __u8 bNumConfigurations;
+} __attribute__ ((packed));
+
+struct usb_interface_descriptor {
+ __u8 bLength;
+ __u8 bDescriptorType;
+
+ __u8 bInterfaceNumber;
+ __u8 bAlternateSetting;
+ __u8 bNumEndpoints;
+ __u8 bInterfaceClass;
+ __u8 bInterfaceSubClass;
+ __u8 bInterfaceProtocol;
+ __u8 iInterface;
+} __attribute__ ((packed));
+
+enum usb_device_speed {
+ USB_SPEED_UNKNOWN = 0, /* enumerating */
+ USB_SPEED_LOW, USB_SPEED_FULL, /* usb 1.1 */
+ USB_SPEED_HIGH /* usb 2.0 */
+};
+
+/*-------------------------------------------------------------------------*/
+
+static char *speed (enum usb_device_speed s)
+{
+ switch (s) {
+ case USB_SPEED_UNKNOWN: return "unknown";
+ case USB_SPEED_LOW: return "low";
+ case USB_SPEED_FULL: return "full";
+ case USB_SPEED_HIGH: return "high";
+ default: return "??";
+ }
+}
+
+struct testdev {
+ struct testdev *next;
+ char *name;
+ pthread_t thread;
+ enum usb_device_speed speed;
+ unsigned ifnum : 8;
+ unsigned forever : 1;
+ int test;
+
+ struct usbtest_param param;
+};
+static struct testdev *testdevs;
+
+static int testdev_ffs_ifnum(FILE *fd)
+{
+ union {
+ char buf[255];
+ struct usb_interface_descriptor intf;
+ } u;
+
+ for (;;) {
+ if (fread(u.buf, 1, 1, fd) != 1)
+ return -1;
+ if (fread(u.buf + 1, (unsigned char)u.buf[0] - 1, 1, fd) != 1)
+ return -1;
+
+ if (u.intf.bLength == sizeof u.intf
+ && u.intf.bDescriptorType == USB_DT_INTERFACE
+ && u.intf.bNumEndpoints == 2
+ && u.intf.bInterfaceClass == USB_CLASS_VENDOR_SPEC
+ && u.intf.bInterfaceSubClass == 0
+ && u.intf.bInterfaceProtocol == 0)
+ return (unsigned char)u.intf.bInterfaceNumber;
+ }
+}
+
+static int testdev_ifnum(FILE *fd)
+{
+ struct usb_device_descriptor dev;
+
+ if (fread(&dev, sizeof dev, 1, fd) != 1)
+ return -1;
+
+ if (dev.bLength != sizeof dev || dev.bDescriptorType != USB_DT_DEVICE)
+ return -1;
+
+ /* FX2 with (tweaked) bulksrc firmware */
+ if (dev.idVendor == 0x0547 && dev.idProduct == 0x1002)
+ return 0;
+
+ /*----------------------------------------------------*/
+
+ /* devices that start up using the EZ-USB default device and
+ * which we can use after loading simple firmware. hotplug
+ * can fxload it, and then run this test driver.
+ *
+ * we return false positives in two cases:
+ * - the device has a "real" driver (maybe usb-serial) that
+ * renumerates. the device should vanish quickly.
+ * - the device doesn't have the test firmware installed.
+ */
+
+ /* generic EZ-USB FX controller */
+ if (dev.idVendor == 0x0547 && dev.idProduct == 0x2235)
+ return 0;
+
+ /* generic EZ-USB FX2 controller */
+ if (dev.idVendor == 0x04b4 && dev.idProduct == 0x8613)
+ return 0;
+
+ /* CY3671 development board with EZ-USB FX */
+ if (dev.idVendor == 0x0547 && dev.idProduct == 0x0080)
+ return 0;
+
+ /* Keyspan 19Qi uses an21xx (original EZ-USB) */
+ if (dev.idVendor == 0x06cd && dev.idProduct == 0x010b)
+ return 0;
+
+ /*----------------------------------------------------*/
+
+ /* "gadget zero", Linux-USB test software */
+ if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4a0)
+ return 0;
+
+ /* user mode subset of that */
+ if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4a4)
+ return testdev_ffs_ifnum(fd);
+ /* return 0; */
+
+ /* iso version of usermode code */
+ if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4a3)
+ return 0;
+
+ /* some GPL'd test firmware uses these IDs */
+
+ if (dev.idVendor == 0xfff0 && dev.idProduct == 0xfff0)
+ return 0;
+
+ /*----------------------------------------------------*/
+
+ /* iBOT2 high speed webcam */
+ if (dev.idVendor == 0x0b62 && dev.idProduct == 0x0059)
+ return 0;
+
+ /*----------------------------------------------------*/
+
+ /* the FunctionFS gadget can have the source/sink interface
+ * anywhere. We look for an interface descriptor that match
+ * what we expect. We ignore configuratiens thou. */
+
+ if (dev.idVendor == 0x0525 && dev.idProduct == 0xa4ac
+ && (dev.bDeviceClass == USB_CLASS_PER_INTERFACE
+ || dev.bDeviceClass == USB_CLASS_VENDOR_SPEC))
+ return testdev_ffs_ifnum(fd);
+
+ return -1;
+}
+
+static int find_testdev(const char *name, const struct stat *sb, int flag)
+{
+ FILE *fd;
+ int ifnum;
+ struct testdev *entry;
+
+ (void)sb; /* unused */
+
+ if (flag != FTW_F)
+ return 0;
+
+ fd = fopen(name, "rb");
+ if (!fd) {
+ perror(name);
+ return 0;
+ }
+
+ ifnum = testdev_ifnum(fd);
+ fclose(fd);
+ if (ifnum < 0)
+ return 0;
+
+ entry = calloc(1, sizeof *entry);
+ if (!entry)
+ goto nomem;
+
+ entry->name = strdup(name);
+ if (!entry->name) {
+ free(entry);
+nomem:
+ perror("malloc");
+ return 0;
+ }
+
+ entry->ifnum = ifnum;
+
+ /* FIXME update USBDEVFS_CONNECTINFO so it tells about high speed etc */
+
+ fprintf(stderr, "%s speed\t%s\t%u\n",
+ speed(entry->speed), entry->name, entry->ifnum);
+
+ entry->next = testdevs;
+ testdevs = entry;
+ return 0;
+}
+
+static int
+usbdev_ioctl (int fd, int ifno, unsigned request, void *param)
+{
+ struct usbdevfs_ioctl wrapper;
+
+ wrapper.ifno = ifno;
+ wrapper.ioctl_code = request;
+ wrapper.data = param;
+
+ return ioctl (fd, USBDEVFS_IOCTL, &wrapper);
+}
+
+static void *handle_testdev (void *arg)
+{
+ struct testdev *dev = arg;
+ int fd, i;
+ int status;
+
+ if ((fd = open (dev->name, O_RDWR)) < 0) {
+ perror ("can't open dev file r/w");
+ return 0;
+ }
+
+restart:
+ for (i = 0; i < TEST_CASES; i++) {
+ if (dev->test != -1 && dev->test != i)
+ continue;
+ dev->param.test_num = i;
+
+ status = usbdev_ioctl (fd, dev->ifnum,
+ USBTEST_REQUEST, &dev->param);
+ if (status < 0 && errno == EOPNOTSUPP)
+ continue;
+
+ /* FIXME need a "syslog it" option for background testing */
+
+ /* NOTE: each thread emits complete lines; no fragments! */
+ if (status < 0) {
+ char buf [80];
+ int err = errno;
+
+ if (strerror_r (errno, buf, sizeof buf)) {
+ snprintf (buf, sizeof buf, "error %d", err);
+ errno = err;
+ }
+ printf ("%s test %d --> %d (%s)\n",
+ dev->name, i, errno, buf);
+ } else
+ printf ("%s test %d, %4d.%.06d secs\n", dev->name, i,
+ (int) dev->param.duration.tv_sec,
+ (int) dev->param.duration.tv_usec);
+
+ fflush (stdout);
+ }
+ if (dev->forever)
+ goto restart;
+
+ close (fd);
+ return arg;
+}
+
+static const char *usb_dir_find(void)
+{
+ static char udev_usb_path[] = "/dev/bus/usb";
+
+ if (access(udev_usb_path, F_OK) == 0)
+ return udev_usb_path;
+
+ return NULL;
+}
+
+static int parse_num(unsigned *num, const char *str)
+{
+ unsigned long val;
+ char *end;
+
+ errno = 0;
+ val = strtoul(str, &end, 0);
+ if (errno || *end || val > UINT_MAX)
+ return -1;
+ *num = val;
+ return 0;
+}
+
+int main (int argc, char **argv)
+{
+
+ int c;
+ struct testdev *entry;
+ char *device;
+ const char *usb_dir = NULL;
+ int all = 0, forever = 0, not = 0;
+ int test = -1 /* all */;
+ struct usbtest_param param;
+
+ /* pick defaults that works with all speeds, without short packets.
+ *
+ * Best per-frame data rates:
+ * high speed, bulk 512 * 13 * 8 = 53248
+ * interrupt 1024 * 3 * 8 = 24576
+ * full speed, bulk/intr 64 * 19 = 1216
+ * interrupt 64 * 1 = 64
+ * low speed, interrupt 8 * 1 = 8
+ */
+ param.iterations = 1000;
+ param.length = 512;
+ param.vary = 512;
+ param.sglen = 32;
+
+ /* for easy use when hotplugging */
+ device = getenv ("DEVICE");
+
+ while ((c = getopt (argc, argv, "D:aA:c:g:hlns:t:v:")) != EOF)
+ switch (c) {
+ case 'D': /* device, if only one */
+ device = optarg;
+ continue;
+ case 'A': /* use all devices with specified USB dir */
+ usb_dir = optarg;
+ /* FALL THROUGH */
+ case 'a': /* use all devices */
+ device = NULL;
+ all = 1;
+ continue;
+ case 'c': /* count iterations */
+ if (parse_num(&param.iterations, optarg))
+ goto usage;
+ continue;
+ case 'g': /* scatter/gather entries */
+ if (parse_num(&param.sglen, optarg))
+ goto usage;
+ continue;
+ case 'l': /* loop forever */
+ forever = 1;
+ continue;
+ case 'n': /* no test running! */
+ not = 1;
+ continue;
+ case 's': /* size of packet */
+ if (parse_num(&param.length, optarg))
+ goto usage;
+ continue;
+ case 't': /* run just one test */
+ test = atoi (optarg);
+ if (test < 0)
+ goto usage;
+ continue;
+ case 'v': /* vary packet size by ... */
+ if (parse_num(&param.vary, optarg))
+ goto usage;
+ continue;
+ case '?':
+ case 'h':
+ default:
+usage:
+ fprintf (stderr,
+ "usage: %s [options]\n"
+ "Options:\n"
+ "\t-D dev only test specific device\n"
+ "\t-A usb-dir\n"
+ "\t-a test all recognized devices\n"
+ "\t-l loop forever(for stress test)\n"
+ "\t-t testnum only run specified case\n"
+ "\t-n no test running, show devices to be tested\n"
+ "Case arguments:\n"
+ "\t-c iterations default 1000\n"
+ "\t-s packetsize default 512\n"
+ "\t-g sglen default 32\n"
+ "\t-v vary default 512\n",
+ argv[0]);
+ return 1;
+ }
+ if (optind != argc)
+ goto usage;
+ if (!all && !device) {
+ fprintf (stderr, "must specify '-a' or '-D dev', "
+ "or DEVICE=/dev/bus/usb/BBB/DDD in env\n");
+ goto usage;
+ }
+
+ /* Find usb device subdirectory */
+ if (!usb_dir) {
+ usb_dir = usb_dir_find();
+ if (!usb_dir) {
+ fputs ("USB device files are missing\n", stderr);
+ return -1;
+ }
+ }
+
+ /* collect and list the test devices */
+ if (ftw (usb_dir, find_testdev, 3) != 0) {
+ fputs ("ftw failed; are USB device files missing?\n", stderr);
+ return -1;
+ }
+
+ /* quit, run single test, or create test threads */
+ if (!testdevs && !device) {
+ fputs ("no test devices recognized\n", stderr);
+ return -1;
+ }
+ if (not)
+ return 0;
+ if (testdevs && testdevs->next == 0 && !device)
+ device = testdevs->name;
+ for (entry = testdevs; entry; entry = entry->next) {
+ int status;
+
+ entry->param = param;
+ entry->forever = forever;
+ entry->test = test;
+
+ if (device) {
+ if (strcmp (entry->name, device))
+ continue;
+ return handle_testdev (entry) != entry;
+ }
+ status = pthread_create (&entry->thread, 0, handle_testdev, entry);
+ if (status)
+ perror ("pthread_create");
+ }
+ if (device) {
+ struct testdev dev;
+
+ /* kernel can recognize test devices we don't */
+ fprintf (stderr, "%s: %s may see only control tests\n",
+ argv [0], device);
+
+ memset (&dev, 0, sizeof dev);
+ dev.name = device;
+ dev.param = param;
+ dev.forever = forever;
+ dev.test = test;
+ return handle_testdev (&dev) != &dev;
+ }
+
+ /* wait for tests to complete */
+ for (entry = testdevs; entry; entry = entry->next) {
+ void *retval;
+
+ if (pthread_join (entry->thread, &retval))
+ perror ("pthread_join");
+ /* testing errors discarded! */
+ }
+
+ return 0;
+}
diff --git a/tools/virtio/.gitignore b/tools/virtio/.gitignore
new file mode 100644
index 00000000000..1cfbb0157a4
--- /dev/null
+++ b/tools/virtio/.gitignore
@@ -0,0 +1,3 @@
+*.d
+virtio_test
+vringh_test
diff --git a/tools/virtio/Makefile b/tools/virtio/Makefile
new file mode 100644
index 00000000000..9325f469382
--- /dev/null
+++ b/tools/virtio/Makefile
@@ -0,0 +1,14 @@
+all: test mod
+test: virtio_test vringh_test
+virtio_test: virtio_ring.o virtio_test.o
+vringh_test: vringh_test.o vringh.o virtio_ring.o
+
+CFLAGS += -g -O2 -Wall -I. -I../include/ -I ../../usr/include/ -Wno-pointer-sign -fno-strict-overflow -fno-strict-aliasing -fno-common -MMD -U_FORTIFY_SOURCE
+vpath %.c ../../drivers/virtio ../../drivers/vhost
+mod:
+ ${MAKE} -C `pwd`/../.. M=`pwd`/vhost_test
+.PHONY: all test mod clean
+clean:
+ ${RM} *.o vringh_test virtio_test vhost_test/*.o vhost_test/.*.cmd \
+ vhost_test/Module.symvers vhost_test/modules.order *.d
+-include *.d
diff --git a/tools/virtio/asm/barrier.h b/tools/virtio/asm/barrier.h
new file mode 100644
index 00000000000..aff61e13306
--- /dev/null
+++ b/tools/virtio/asm/barrier.h
@@ -0,0 +1,14 @@
+#if defined(__i386__) || defined(__x86_64__)
+#define barrier() asm volatile("" ::: "memory")
+#define mb() __sync_synchronize()
+
+#define smp_mb() mb()
+# define smp_rmb() barrier()
+# define smp_wmb() barrier()
+/* Weak barriers should be used. If not - it's a bug */
+# define rmb() abort()
+# define wmb() abort()
+#else
+#error Please fill in barrier macros
+#endif
+
diff --git a/tools/virtio/linux/bug.h b/tools/virtio/linux/bug.h
new file mode 100644
index 00000000000..fb94f0787c4
--- /dev/null
+++ b/tools/virtio/linux/bug.h
@@ -0,0 +1,10 @@
+#ifndef BUG_H
+#define BUG_H
+
+#define BUG_ON(__BUG_ON_cond) assert(!(__BUG_ON_cond))
+
+#define BUILD_BUG_ON(x)
+
+#define BUG() abort()
+
+#endif /* BUG_H */
diff --git a/tools/virtio/linux/device.h b/tools/virtio/linux/device.h
new file mode 100644
index 00000000000..4ad7e1df0db
--- /dev/null
+++ b/tools/virtio/linux/device.h
@@ -0,0 +1,2 @@
+#ifndef LINUX_DEVICE_H
+#endif
diff --git a/tools/virtio/linux/err.h b/tools/virtio/linux/err.h
new file mode 100644
index 00000000000..e32eff8b2a1
--- /dev/null
+++ b/tools/virtio/linux/err.h
@@ -0,0 +1,26 @@
+#ifndef ERR_H
+#define ERR_H
+#define MAX_ERRNO 4095
+
+#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)
+
+static inline void * __must_check ERR_PTR(long error)
+{
+ return (void *) error;
+}
+
+static inline long __must_check PTR_ERR(const void *ptr)
+{
+ return (long) ptr;
+}
+
+static inline long __must_check IS_ERR(const void *ptr)
+{
+ return IS_ERR_VALUE((unsigned long)ptr);
+}
+
+static inline long __must_check IS_ERR_OR_NULL(const void *ptr)
+{
+ return !ptr || IS_ERR_VALUE((unsigned long)ptr);
+}
+#endif /* ERR_H */
diff --git a/tools/virtio/linux/hrtimer.h b/tools/virtio/linux/hrtimer.h
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/tools/virtio/linux/hrtimer.h
diff --git a/tools/virtio/linux/irqreturn.h b/tools/virtio/linux/irqreturn.h
new file mode 100644
index 00000000000..a3c4e7be708
--- /dev/null
+++ b/tools/virtio/linux/irqreturn.h
@@ -0,0 +1 @@
+#include "../../../include/linux/irqreturn.h"
diff --git a/tools/virtio/linux/kernel.h b/tools/virtio/linux/kernel.h
new file mode 100644
index 00000000000..1e8ce6979c1
--- /dev/null
+++ b/tools/virtio/linux/kernel.h
@@ -0,0 +1,105 @@
+#ifndef KERNEL_H
+#define KERNEL_H
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+#include <stdarg.h>
+
+#include <linux/types.h>
+#include <linux/printk.h>
+#include <linux/bug.h>
+#include <errno.h>
+#include <unistd.h>
+#include <asm/barrier.h>
+
+#define CONFIG_SMP
+
+#define PAGE_SIZE getpagesize()
+#define PAGE_MASK (~(PAGE_SIZE-1))
+
+typedef unsigned long long dma_addr_t;
+typedef size_t __kernel_size_t;
+
+struct page {
+ unsigned long long dummy;
+};
+
+/* Physical == Virtual */
+#define virt_to_phys(p) ((unsigned long)p)
+#define phys_to_virt(a) ((void *)(unsigned long)(a))
+/* Page address: Virtual / 4K */
+#define page_to_phys(p) ((dma_addr_t)(unsigned long)(p))
+#define virt_to_page(p) ((struct page *)((unsigned long)p & PAGE_MASK))
+
+#define offset_in_page(p) (((unsigned long)p) % PAGE_SIZE)
+
+#define __printf(a,b) __attribute__((format(printf,a,b)))
+
+#define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0]))
+
+extern void *__kmalloc_fake, *__kfree_ignore_start, *__kfree_ignore_end;
+static inline void *kmalloc(size_t s, gfp_t gfp)
+{
+ if (__kmalloc_fake)
+ return __kmalloc_fake;
+ return malloc(s);
+}
+
+static inline void kfree(void *p)
+{
+ if (p >= __kfree_ignore_start && p < __kfree_ignore_end)
+ return;
+ free(p);
+}
+
+static inline void *krealloc(void *p, size_t s, gfp_t gfp)
+{
+ return realloc(p, s);
+}
+
+
+static inline unsigned long __get_free_page(gfp_t gfp)
+{
+ void *p;
+
+ posix_memalign(&p, PAGE_SIZE, PAGE_SIZE);
+ return (unsigned long)p;
+}
+
+static inline void free_page(unsigned long addr)
+{
+ free((void *)addr);
+}
+
+#define container_of(ptr, type, member) ({ \
+ const typeof( ((type *)0)->member ) *__mptr = (ptr); \
+ (type *)( (char *)__mptr - offsetof(type,member) );})
+
+#define uninitialized_var(x) x = x
+
+# ifndef likely
+# define likely(x) (__builtin_expect(!!(x), 1))
+# endif
+# ifndef unlikely
+# define unlikely(x) (__builtin_expect(!!(x), 0))
+# endif
+
+#define pr_err(format, ...) fprintf (stderr, format, ## __VA_ARGS__)
+#ifdef DEBUG
+#define pr_debug(format, ...) fprintf (stderr, format, ## __VA_ARGS__)
+#else
+#define pr_debug(format, ...) do {} while (0)
+#endif
+#define dev_err(dev, format, ...) fprintf (stderr, format, ## __VA_ARGS__)
+#define dev_warn(dev, format, ...) fprintf (stderr, format, ## __VA_ARGS__)
+
+#define min(x, y) ({ \
+ typeof(x) _min1 = (x); \
+ typeof(y) _min2 = (y); \
+ (void) (&_min1 == &_min2); \
+ _min1 < _min2 ? _min1 : _min2; })
+
+#endif /* KERNEL_H */
diff --git a/tools/virtio/linux/kmemleak.h b/tools/virtio/linux/kmemleak.h
new file mode 100644
index 00000000000..c07072270e2
--- /dev/null
+++ b/tools/virtio/linux/kmemleak.h
@@ -0,0 +1,3 @@
+static inline void kmemleak_ignore(const void *ptr)
+{
+}
diff --git a/tools/virtio/linux/module.h b/tools/virtio/linux/module.h
new file mode 100644
index 00000000000..28ce95a0599
--- /dev/null
+++ b/tools/virtio/linux/module.h
@@ -0,0 +1,6 @@
+#include <linux/export.h>
+
+#define MODULE_LICENSE(__MODULE_LICENSE_value) \
+ static __attribute__((unused)) const char *__MODULE_LICENSE_name = \
+ __MODULE_LICENSE_value
+
diff --git a/tools/virtio/linux/printk.h b/tools/virtio/linux/printk.h
new file mode 100644
index 00000000000..9f2423bd89c
--- /dev/null
+++ b/tools/virtio/linux/printk.h
@@ -0,0 +1,4 @@
+#include "../../../include/linux/kern_levels.h"
+
+#define printk printf
+#define vprintk vprintf
diff --git a/tools/virtio/linux/ratelimit.h b/tools/virtio/linux/ratelimit.h
new file mode 100644
index 00000000000..dcce1725f90
--- /dev/null
+++ b/tools/virtio/linux/ratelimit.h
@@ -0,0 +1,4 @@
+#define DEFINE_RATELIMIT_STATE(name, interval_init, burst_init) int name = 0
+
+#define __ratelimit(x) (*(x))
+
diff --git a/tools/virtio/linux/scatterlist.h b/tools/virtio/linux/scatterlist.h
new file mode 100644
index 00000000000..68c9e2adc99
--- /dev/null
+++ b/tools/virtio/linux/scatterlist.h
@@ -0,0 +1,189 @@
+#ifndef SCATTERLIST_H
+#define SCATTERLIST_H
+#include <linux/kernel.h>
+
+struct scatterlist {
+ unsigned long page_link;
+ unsigned int offset;
+ unsigned int length;
+ dma_addr_t dma_address;
+};
+
+/* Scatterlist helpers, stolen from linux/scatterlist.h */
+#define sg_is_chain(sg) ((sg)->page_link & 0x01)
+#define sg_is_last(sg) ((sg)->page_link & 0x02)
+#define sg_chain_ptr(sg) \
+ ((struct scatterlist *) ((sg)->page_link & ~0x03))
+
+/**
+ * sg_assign_page - Assign a given page to an SG entry
+ * @sg: SG entry
+ * @page: The page
+ *
+ * Description:
+ * Assign page to sg entry. Also see sg_set_page(), the most commonly used
+ * variant.
+ *
+ **/
+static inline void sg_assign_page(struct scatterlist *sg, struct page *page)
+{
+ unsigned long page_link = sg->page_link & 0x3;
+
+ /*
+ * In order for the low bit stealing approach to work, pages
+ * must be aligned at a 32-bit boundary as a minimum.
+ */
+ BUG_ON((unsigned long) page & 0x03);
+#ifdef CONFIG_DEBUG_SG
+ BUG_ON(sg->sg_magic != SG_MAGIC);
+ BUG_ON(sg_is_chain(sg));
+#endif
+ sg->page_link = page_link | (unsigned long) page;
+}
+
+/**
+ * sg_set_page - Set sg entry to point at given page
+ * @sg: SG entry
+ * @page: The page
+ * @len: Length of data
+ * @offset: Offset into page
+ *
+ * Description:
+ * Use this function to set an sg entry pointing at a page, never assign
+ * the page directly. We encode sg table information in the lower bits
+ * of the page pointer. See sg_page() for looking up the page belonging
+ * to an sg entry.
+ *
+ **/
+static inline void sg_set_page(struct scatterlist *sg, struct page *page,
+ unsigned int len, unsigned int offset)
+{
+ sg_assign_page(sg, page);
+ sg->offset = offset;
+ sg->length = len;
+}
+
+static inline struct page *sg_page(struct scatterlist *sg)
+{
+#ifdef CONFIG_DEBUG_SG
+ BUG_ON(sg->sg_magic != SG_MAGIC);
+ BUG_ON(sg_is_chain(sg));
+#endif
+ return (struct page *)((sg)->page_link & ~0x3);
+}
+
+/*
+ * Loop over each sg element, following the pointer to a new list if necessary
+ */
+#define for_each_sg(sglist, sg, nr, __i) \
+ for (__i = 0, sg = (sglist); __i < (nr); __i++, sg = sg_next(sg))
+
+/**
+ * sg_chain - Chain two sglists together
+ * @prv: First scatterlist
+ * @prv_nents: Number of entries in prv
+ * @sgl: Second scatterlist
+ *
+ * Description:
+ * Links @prv@ and @sgl@ together, to form a longer scatterlist.
+ *
+ **/
+static inline void sg_chain(struct scatterlist *prv, unsigned int prv_nents,
+ struct scatterlist *sgl)
+{
+ /*
+ * offset and length are unused for chain entry. Clear them.
+ */
+ prv[prv_nents - 1].offset = 0;
+ prv[prv_nents - 1].length = 0;
+
+ /*
+ * Set lowest bit to indicate a link pointer, and make sure to clear
+ * the termination bit if it happens to be set.
+ */
+ prv[prv_nents - 1].page_link = ((unsigned long) sgl | 0x01) & ~0x02;
+}
+
+/**
+ * sg_mark_end - Mark the end of the scatterlist
+ * @sg: SG entryScatterlist
+ *
+ * Description:
+ * Marks the passed in sg entry as the termination point for the sg
+ * table. A call to sg_next() on this entry will return NULL.
+ *
+ **/
+static inline void sg_mark_end(struct scatterlist *sg)
+{
+#ifdef CONFIG_DEBUG_SG
+ BUG_ON(sg->sg_magic != SG_MAGIC);
+#endif
+ /*
+ * Set termination bit, clear potential chain bit
+ */
+ sg->page_link |= 0x02;
+ sg->page_link &= ~0x01;
+}
+
+/**
+ * sg_unmark_end - Undo setting the end of the scatterlist
+ * @sg: SG entryScatterlist
+ *
+ * Description:
+ * Removes the termination marker from the given entry of the scatterlist.
+ *
+ **/
+static inline void sg_unmark_end(struct scatterlist *sg)
+{
+#ifdef CONFIG_DEBUG_SG
+ BUG_ON(sg->sg_magic != SG_MAGIC);
+#endif
+ sg->page_link &= ~0x02;
+}
+
+static inline struct scatterlist *sg_next(struct scatterlist *sg)
+{
+#ifdef CONFIG_DEBUG_SG
+ BUG_ON(sg->sg_magic != SG_MAGIC);
+#endif
+ if (sg_is_last(sg))
+ return NULL;
+
+ sg++;
+ if (unlikely(sg_is_chain(sg)))
+ sg = sg_chain_ptr(sg);
+
+ return sg;
+}
+
+static inline void sg_init_table(struct scatterlist *sgl, unsigned int nents)
+{
+ memset(sgl, 0, sizeof(*sgl) * nents);
+#ifdef CONFIG_DEBUG_SG
+ {
+ unsigned int i;
+ for (i = 0; i < nents; i++)
+ sgl[i].sg_magic = SG_MAGIC;
+ }
+#endif
+ sg_mark_end(&sgl[nents - 1]);
+}
+
+static inline dma_addr_t sg_phys(struct scatterlist *sg)
+{
+ return page_to_phys(sg_page(sg)) + sg->offset;
+}
+
+static inline void sg_set_buf(struct scatterlist *sg, const void *buf,
+ unsigned int buflen)
+{
+ sg_set_page(sg, virt_to_page(buf), buflen, offset_in_page(buf));
+}
+
+static inline void sg_init_one(struct scatterlist *sg,
+ const void *buf, unsigned int buflen)
+{
+ sg_init_table(sg, 1);
+ sg_set_buf(sg, buf, buflen);
+}
+#endif /* SCATTERLIST_H */
diff --git a/tools/virtio/linux/slab.h b/tools/virtio/linux/slab.h
new file mode 100644
index 00000000000..81baeac8ae4
--- /dev/null
+++ b/tools/virtio/linux/slab.h
@@ -0,0 +1,2 @@
+#ifndef LINUX_SLAB_H
+#endif
diff --git a/tools/virtio/linux/uaccess.h b/tools/virtio/linux/uaccess.h
new file mode 100644
index 00000000000..0a578fe1865
--- /dev/null
+++ b/tools/virtio/linux/uaccess.h
@@ -0,0 +1,50 @@
+#ifndef UACCESS_H
+#define UACCESS_H
+extern void *__user_addr_min, *__user_addr_max;
+
+#define ACCESS_ONCE(x) (*(volatile typeof(x) *)&(x))
+
+static inline void __chk_user_ptr(const volatile void *p, size_t size)
+{
+ assert(p >= __user_addr_min && p + size <= __user_addr_max);
+}
+
+#define put_user(x, ptr) \
+({ \
+ typeof(ptr) __pu_ptr = (ptr); \
+ __chk_user_ptr(__pu_ptr, sizeof(*__pu_ptr)); \
+ ACCESS_ONCE(*(__pu_ptr)) = x; \
+ 0; \
+})
+
+#define get_user(x, ptr) \
+({ \
+ typeof(ptr) __pu_ptr = (ptr); \
+ __chk_user_ptr(__pu_ptr, sizeof(*__pu_ptr)); \
+ x = ACCESS_ONCE(*(__pu_ptr)); \
+ 0; \
+})
+
+static void volatile_memcpy(volatile char *to, const volatile char *from,
+ unsigned long n)
+{
+ while (n--)
+ *(to++) = *(from++);
+}
+
+static inline int copy_from_user(void *to, const void __user volatile *from,
+ unsigned long n)
+{
+ __chk_user_ptr(from, n);
+ volatile_memcpy(to, from, n);
+ return 0;
+}
+
+static inline int copy_to_user(void __user volatile *to, const void *from,
+ unsigned long n)
+{
+ __chk_user_ptr(to, n);
+ volatile_memcpy(to, from, n);
+ return 0;
+}
+#endif /* UACCESS_H */
diff --git a/tools/virtio/linux/uio.h b/tools/virtio/linux/uio.h
new file mode 100644
index 00000000000..cd20f0ba308
--- /dev/null
+++ b/tools/virtio/linux/uio.h
@@ -0,0 +1,3 @@
+#include <linux/kernel.h>
+
+#include "../../../include/linux/uio.h"
diff --git a/tools/virtio/linux/virtio.h b/tools/virtio/linux/virtio.h
new file mode 100644
index 00000000000..5a2d1f0f6bc
--- /dev/null
+++ b/tools/virtio/linux/virtio.h
@@ -0,0 +1,87 @@
+#ifndef LINUX_VIRTIO_H
+#define LINUX_VIRTIO_H
+#include <linux/scatterlist.h>
+#include <linux/kernel.h>
+
+/* TODO: empty stubs for now. Broken but enough for virtio_ring.c */
+#define list_add_tail(a, b) do {} while (0)
+#define list_del(a) do {} while (0)
+
+#define BIT_WORD(nr) ((nr) / BITS_PER_LONG)
+#define BITS_PER_BYTE 8
+#define BITS_PER_LONG (sizeof(long) * BITS_PER_BYTE)
+#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG))
+
+/* TODO: Not atomic as it should be:
+ * we don't use this for anything important. */
+static inline void clear_bit(int nr, volatile unsigned long *addr)
+{
+ unsigned long mask = BIT_MASK(nr);
+ unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr);
+
+ *p &= ~mask;
+}
+
+static inline int test_bit(int nr, const volatile unsigned long *addr)
+{
+ return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1)));
+}
+/* end of stubs */
+
+struct virtio_device {
+ void *dev;
+ unsigned long features[1];
+};
+
+struct virtqueue {
+ /* TODO: commented as list macros are empty stubs for now.
+ * Broken but enough for virtio_ring.c
+ * struct list_head list; */
+ void (*callback)(struct virtqueue *vq);
+ const char *name;
+ struct virtio_device *vdev;
+ unsigned int index;
+ unsigned int num_free;
+ void *priv;
+};
+
+/* Interfaces exported by virtio_ring. */
+int virtqueue_add_sgs(struct virtqueue *vq,
+ struct scatterlist *sgs[],
+ unsigned int out_sgs,
+ unsigned int in_sgs,
+ void *data,
+ gfp_t gfp);
+
+int virtqueue_add_outbuf(struct virtqueue *vq,
+ struct scatterlist sg[], unsigned int num,
+ void *data,
+ gfp_t gfp);
+
+int virtqueue_add_inbuf(struct virtqueue *vq,
+ struct scatterlist sg[], unsigned int num,
+ void *data,
+ gfp_t gfp);
+
+bool virtqueue_kick(struct virtqueue *vq);
+
+void *virtqueue_get_buf(struct virtqueue *vq, unsigned int *len);
+
+void virtqueue_disable_cb(struct virtqueue *vq);
+
+bool virtqueue_enable_cb(struct virtqueue *vq);
+bool virtqueue_enable_cb_delayed(struct virtqueue *vq);
+
+void *virtqueue_detach_unused_buf(struct virtqueue *vq);
+struct virtqueue *vring_new_virtqueue(unsigned int index,
+ unsigned int num,
+ unsigned int vring_align,
+ struct virtio_device *vdev,
+ bool weak_barriers,
+ void *pages,
+ bool (*notify)(struct virtqueue *vq),
+ void (*callback)(struct virtqueue *vq),
+ const char *name);
+void vring_del_virtqueue(struct virtqueue *vq);
+
+#endif
diff --git a/tools/virtio/linux/virtio_config.h b/tools/virtio/linux/virtio_config.h
new file mode 100644
index 00000000000..5049967f99f
--- /dev/null
+++ b/tools/virtio/linux/virtio_config.h
@@ -0,0 +1,6 @@
+#define VIRTIO_TRANSPORT_F_START 28
+#define VIRTIO_TRANSPORT_F_END 32
+
+#define virtio_has_feature(dev, feature) \
+ test_bit((feature), (dev)->features)
+
diff --git a/tools/virtio/linux/virtio_ring.h b/tools/virtio/linux/virtio_ring.h
new file mode 100644
index 00000000000..8949c4e2772
--- /dev/null
+++ b/tools/virtio/linux/virtio_ring.h
@@ -0,0 +1 @@
+#include "../../../include/linux/virtio_ring.h"
diff --git a/tools/virtio/linux/vringh.h b/tools/virtio/linux/vringh.h
new file mode 100644
index 00000000000..9348957be56
--- /dev/null
+++ b/tools/virtio/linux/vringh.h
@@ -0,0 +1 @@
+#include "../../../include/linux/vringh.h"
diff --git a/tools/virtio/uapi/linux/uio.h b/tools/virtio/uapi/linux/uio.h
new file mode 100644
index 00000000000..7230e900220
--- /dev/null
+++ b/tools/virtio/uapi/linux/uio.h
@@ -0,0 +1 @@
+#include <sys/uio.h>
diff --git a/tools/virtio/uapi/linux/virtio_config.h b/tools/virtio/uapi/linux/virtio_config.h
new file mode 100644
index 00000000000..4c86675f015
--- /dev/null
+++ b/tools/virtio/uapi/linux/virtio_config.h
@@ -0,0 +1 @@
+#include "../../../../include/uapi/linux/virtio_config.h"
diff --git a/tools/virtio/uapi/linux/virtio_ring.h b/tools/virtio/uapi/linux/virtio_ring.h
new file mode 100644
index 00000000000..4d99c78234d
--- /dev/null
+++ b/tools/virtio/uapi/linux/virtio_ring.h
@@ -0,0 +1,4 @@
+#ifndef VIRTIO_RING_H
+#define VIRTIO_RING_H
+#include "../../../../include/uapi/linux/virtio_ring.h"
+#endif /* VIRTIO_RING_H */
diff --git a/tools/virtio/vhost_test/Makefile b/tools/virtio/vhost_test/Makefile
new file mode 100644
index 00000000000..a1d35b81b31
--- /dev/null
+++ b/tools/virtio/vhost_test/Makefile
@@ -0,0 +1,2 @@
+obj-m += vhost_test.o
+EXTRA_CFLAGS += -Idrivers/vhost
diff --git a/tools/virtio/vhost_test/vhost_test.c b/tools/virtio/vhost_test/vhost_test.c
new file mode 100644
index 00000000000..18735189e62
--- /dev/null
+++ b/tools/virtio/vhost_test/vhost_test.c
@@ -0,0 +1 @@
+#include "test.c"
diff --git a/tools/virtio/virtio-trace/Makefile b/tools/virtio/virtio-trace/Makefile
new file mode 100644
index 00000000000..0d238163347
--- /dev/null
+++ b/tools/virtio/virtio-trace/Makefile
@@ -0,0 +1,13 @@
+CC = gcc
+CFLAGS = -O2 -Wall -pthread
+
+all: trace-agent
+
+.c.o:
+ $(CC) $(CFLAGS) -c $^ -o $@
+
+trace-agent: trace-agent.o trace-agent-ctl.o trace-agent-rw.o
+ $(CC) $(CFLAGS) -o $@ $^
+
+clean:
+ rm -f *.o trace-agent
diff --git a/tools/virtio/virtio-trace/README b/tools/virtio/virtio-trace/README
new file mode 100644
index 00000000000..b64845b823a
--- /dev/null
+++ b/tools/virtio/virtio-trace/README
@@ -0,0 +1,118 @@
+Trace Agent for virtio-trace
+============================
+
+Trace agent is a user tool for sending trace data of a guest to a Host in low
+overhead. Trace agent has the following functions:
+ - splice a page of ring-buffer to read_pipe without memory copying
+ - splice the page from write_pipe to virtio-console without memory copying
+ - write trace data to stdout by using -o option
+ - controlled by start/stop orders from a Host
+
+The trace agent operates as follows:
+ 1) Initialize all structures.
+ 2) Create a read/write thread per CPU. Each thread is bound to a CPU.
+ The read/write threads hold it.
+ 3) A controller thread does poll() for a start order of a host.
+ 4) After the controller of the trace agent receives a start order from a host,
+ the controller wake read/write threads.
+ 5) The read/write threads start to read trace data from ring-buffers and
+ write the data to virtio-serial.
+ 6) If the controller receives a stop order from a host, the read/write threads
+ stop to read trace data.
+
+
+Files
+=====
+
+README: this file
+Makefile: Makefile of trace agent for virtio-trace
+trace-agent.c: includes main function, sets up for operating trace agent
+trace-agent.h: includes all structures and some macros
+trace-agent-ctl.c: includes controller function for read/write threads
+trace-agent-rw.c: includes read/write threads function
+
+
+Setup
+=====
+
+To use this trace agent for virtio-trace, we need to prepare some virtio-serial
+I/Fs.
+
+1) Make FIFO in a host
+ virtio-trace uses virtio-serial pipe as trace data paths as to the number
+of CPUs and a control path, so FIFO (named pipe) should be created as follows:
+ # mkdir /tmp/virtio-trace/
+ # mkfifo /tmp/virtio-trace/trace-path-cpu{0,1,2,...,X}.{in,out}
+ # mkfifo /tmp/virtio-trace/agent-ctl-path.{in,out}
+
+For example, if a guest use three CPUs, the names are
+ trace-path-cpu{0,1,2}.{in.out}
+and
+ agent-ctl-path.{in,out}.
+
+2) Set up of virtio-serial pipe in a host
+ Add qemu option to use virtio-serial pipe.
+
+ ##virtio-serial device##
+ -device virtio-serial-pci,id=virtio-serial0\
+ ##control path##
+ -chardev pipe,id=charchannel0,path=/tmp/virtio-trace/agent-ctl-path\
+ -device virtserialport,bus=virtio-serial0.0,nr=1,chardev=charchannel0,\
+ id=channel0,name=agent-ctl-path\
+ ##data path##
+ -chardev pipe,id=charchannel1,path=/tmp/virtio-trace/trace-path-cpu0\
+ -device virtserialport,bus=virtio-serial0.0,nr=2,chardev=charchannel0,\
+ id=channel1,name=trace-path-cpu0\
+ ...
+
+If you manage guests with libvirt, add the following tags to domain XML files.
+Then, libvirt passes the same command option to qemu.
+
+ <channel type='pipe'>
+ <source path='/tmp/virtio-trace/agent-ctl-path'/>
+ <target type='virtio' name='agent-ctl-path'/>
+ <address type='virtio-serial' controller='0' bus='0' port='0'/>
+ </channel>
+ <channel type='pipe'>
+ <source path='/tmp/virtio-trace/trace-path-cpu0'/>
+ <target type='virtio' name='trace-path-cpu0'/>
+ <address type='virtio-serial' controller='0' bus='0' port='1'/>
+ </channel>
+ ...
+Here, chardev names are restricted to trace-path-cpuX and agent-ctl-path. For
+example, if a guest use three CPUs, chardev names should be trace-path-cpu0,
+trace-path-cpu1, trace-path-cpu2, and agent-ctl-path.
+
+3) Boot the guest
+ You can find some chardev in /dev/virtio-ports/ in the guest.
+
+
+Run
+===
+
+0) Build trace agent in a guest
+ $ make
+
+1) Enable ftrace in the guest
+ <Example>
+ # echo 1 > /sys/kernel/debug/tracing/events/sched/enable
+
+2) Run trace agent in the guest
+ This agent must be operated as root.
+ # ./trace-agent
+read/write threads in the agent wait for start order from host. If you add -o
+option, trace data are output via stdout in the guest.
+
+3) Open FIFO in a host
+ # cat /tmp/virtio-trace/trace-path-cpu0.out
+If a host does not open these, trace data get stuck in buffers of virtio. Then,
+the guest will stop by specification of chardev in QEMU. This blocking mode may
+be solved in the future.
+
+4) Start to read trace data by ordering from a host
+ A host injects read start order to the guest via virtio-serial.
+ # echo 1 > /tmp/virtio-trace/agent-ctl-path.in
+
+5) Stop to read trace data by ordering from a host
+ A host injects read stop order to the guest via virtio-serial.
+ # echo 0 > /tmp/virtio-trace/agent-ctl-path.in
diff --git a/tools/virtio/virtio-trace/trace-agent-ctl.c b/tools/virtio/virtio-trace/trace-agent-ctl.c
new file mode 100644
index 00000000000..a2d0403c4f9
--- /dev/null
+++ b/tools/virtio/virtio-trace/trace-agent-ctl.c
@@ -0,0 +1,137 @@
+/*
+ * Controller of read/write threads for virtio-trace
+ *
+ * Copyright (C) 2012 Hitachi, Ltd.
+ * Created by Yoshihiro Yunomae <yoshihiro.yunomae.ez@hitachi.com>
+ * Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
+ *
+ * Licensed under GPL version 2 only.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <poll.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "trace-agent.h"
+
+#define HOST_MSG_SIZE 256
+#define EVENT_WAIT_MSEC 100
+
+static volatile sig_atomic_t global_signal_val;
+bool global_sig_receive; /* default false */
+bool global_run_operation; /* default false*/
+
+/* Handle SIGTERM/SIGINT/SIGQUIT to exit */
+static void signal_handler(int sig)
+{
+ global_signal_val = sig;
+}
+
+int rw_ctl_init(const char *ctl_path)
+{
+ int ctl_fd;
+
+ ctl_fd = open(ctl_path, O_RDONLY);
+ if (ctl_fd == -1) {
+ pr_err("Cannot open ctl_fd\n");
+ goto error;
+ }
+
+ return ctl_fd;
+
+error:
+ exit(EXIT_FAILURE);
+}
+
+static int wait_order(int ctl_fd)
+{
+ struct pollfd poll_fd;
+ int ret = 0;
+
+ while (!global_sig_receive) {
+ poll_fd.fd = ctl_fd;
+ poll_fd.events = POLLIN;
+
+ ret = poll(&poll_fd, 1, EVENT_WAIT_MSEC);
+
+ if (global_signal_val) {
+ global_sig_receive = true;
+ pr_info("Receive interrupt %d\n", global_signal_val);
+
+ /* Wakes rw-threads when they are sleeping */
+ if (!global_run_operation)
+ pthread_cond_broadcast(&cond_wakeup);
+
+ ret = -1;
+ break;
+ }
+
+ if (ret < 0) {
+ pr_err("Polling error\n");
+ goto error;
+ }
+
+ if (ret)
+ break;
+ };
+
+ return ret;
+
+error:
+ exit(EXIT_FAILURE);
+}
+
+/*
+ * contol read/write threads by handling global_run_operation
+ */
+void *rw_ctl_loop(int ctl_fd)
+{
+ ssize_t rlen;
+ char buf[HOST_MSG_SIZE];
+ int ret;
+
+ /* Setup signal handlers */
+ signal(SIGTERM, signal_handler);
+ signal(SIGINT, signal_handler);
+ signal(SIGQUIT, signal_handler);
+
+ while (!global_sig_receive) {
+
+ ret = wait_order(ctl_fd);
+ if (ret < 0)
+ break;
+
+ rlen = read(ctl_fd, buf, sizeof(buf));
+ if (rlen < 0) {
+ pr_err("read data error in ctl thread\n");
+ goto error;
+ }
+
+ if (rlen == 2 && buf[0] == '1') {
+ /*
+ * If host writes '1' to a control path,
+ * this controller wakes all read/write threads.
+ */
+ global_run_operation = true;
+ pthread_cond_broadcast(&cond_wakeup);
+ pr_debug("Wake up all read/write threads\n");
+ } else if (rlen == 2 && buf[0] == '0') {
+ /*
+ * If host writes '0' to a control path, read/write
+ * threads will wait for notification from Host.
+ */
+ global_run_operation = false;
+ pr_debug("Stop all read/write threads\n");
+ } else
+ pr_info("Invalid host notification: %s\n", buf);
+ }
+
+ return NULL;
+
+error:
+ exit(EXIT_FAILURE);
+}
diff --git a/tools/virtio/virtio-trace/trace-agent-rw.c b/tools/virtio/virtio-trace/trace-agent-rw.c
new file mode 100644
index 00000000000..3aace5ea484
--- /dev/null
+++ b/tools/virtio/virtio-trace/trace-agent-rw.c
@@ -0,0 +1,192 @@
+/*
+ * Read/write thread of a guest agent for virtio-trace
+ *
+ * Copyright (C) 2012 Hitachi, Ltd.
+ * Created by Yoshihiro Yunomae <yoshihiro.yunomae.ez@hitachi.com>
+ * Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
+ *
+ * Licensed under GPL version 2 only.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <sys/syscall.h>
+#include "trace-agent.h"
+
+#define READ_WAIT_USEC 100000
+
+void *rw_thread_info_new(void)
+{
+ struct rw_thread_info *rw_ti;
+
+ rw_ti = zalloc(sizeof(struct rw_thread_info));
+ if (rw_ti == NULL) {
+ pr_err("rw_thread_info zalloc error\n");
+ exit(EXIT_FAILURE);
+ }
+
+ rw_ti->cpu_num = -1;
+ rw_ti->in_fd = -1;
+ rw_ti->out_fd = -1;
+ rw_ti->read_pipe = -1;
+ rw_ti->write_pipe = -1;
+ rw_ti->pipe_size = PIPE_INIT;
+
+ return rw_ti;
+}
+
+void *rw_thread_init(int cpu, const char *in_path, const char *out_path,
+ bool stdout_flag, unsigned long pipe_size,
+ struct rw_thread_info *rw_ti)
+{
+ int data_pipe[2];
+
+ rw_ti->cpu_num = cpu;
+
+ /* set read(input) fd */
+ rw_ti->in_fd = open(in_path, O_RDONLY);
+ if (rw_ti->in_fd == -1) {
+ pr_err("Could not open in_fd (CPU:%d)\n", cpu);
+ goto error;
+ }
+
+ /* set write(output) fd */
+ if (!stdout_flag) {
+ /* virtio-serial output mode */
+ rw_ti->out_fd = open(out_path, O_WRONLY);
+ if (rw_ti->out_fd == -1) {
+ pr_err("Could not open out_fd (CPU:%d)\n", cpu);
+ goto error;
+ }
+ } else
+ /* stdout mode */
+ rw_ti->out_fd = STDOUT_FILENO;
+
+ if (pipe2(data_pipe, O_NONBLOCK) < 0) {
+ pr_err("Could not create pipe in rw-thread(%d)\n", cpu);
+ goto error;
+ }
+
+ /*
+ * Size of pipe is 64kB in default based on fs/pipe.c.
+ * To read/write trace data speedy, pipe size is changed.
+ */
+ if (fcntl(*data_pipe, F_SETPIPE_SZ, pipe_size) < 0) {
+ pr_err("Could not change pipe size in rw-thread(%d)\n", cpu);
+ goto error;
+ }
+
+ rw_ti->read_pipe = data_pipe[1];
+ rw_ti->write_pipe = data_pipe[0];
+ rw_ti->pipe_size = pipe_size;
+
+ return NULL;
+
+error:
+ exit(EXIT_FAILURE);
+}
+
+/* Bind a thread to a cpu */
+static void bind_cpu(int cpu_num)
+{
+ cpu_set_t mask;
+
+ CPU_ZERO(&mask);
+ CPU_SET(cpu_num, &mask);
+
+ /* bind my thread to cpu_num by assigning zero to the first argument */
+ if (sched_setaffinity(0, sizeof(mask), &mask) == -1)
+ pr_err("Could not set CPU#%d affinity\n", (int)cpu_num);
+}
+
+static void *rw_thread_main(void *thread_info)
+{
+ ssize_t rlen, wlen;
+ ssize_t ret;
+ struct rw_thread_info *ts = (struct rw_thread_info *)thread_info;
+
+ bind_cpu(ts->cpu_num);
+
+ while (1) {
+ /* Wait for a read order of trace data by Host OS */
+ if (!global_run_operation) {
+ pthread_mutex_lock(&mutex_notify);
+ pthread_cond_wait(&cond_wakeup, &mutex_notify);
+ pthread_mutex_unlock(&mutex_notify);
+ }
+
+ if (global_sig_receive)
+ break;
+
+ /*
+ * Each thread read trace_pipe_raw of each cpu bounding the
+ * thread, so contention of multi-threads does not occur.
+ */
+ rlen = splice(ts->in_fd, NULL, ts->read_pipe, NULL,
+ ts->pipe_size, SPLICE_F_MOVE | SPLICE_F_MORE);
+
+ if (rlen < 0) {
+ pr_err("Splice_read in rw-thread(%d)\n", ts->cpu_num);
+ goto error;
+ } else if (rlen == 0) {
+ /*
+ * If trace data do not exist or are unreadable not
+ * for exceeding the page size, splice_read returns
+ * NULL. Then, this waits for being filled the data in a
+ * ring-buffer.
+ */
+ usleep(READ_WAIT_USEC);
+ pr_debug("Read retry(cpu:%d)\n", ts->cpu_num);
+ continue;
+ }
+
+ wlen = 0;
+
+ do {
+ ret = splice(ts->write_pipe, NULL, ts->out_fd, NULL,
+ rlen - wlen,
+ SPLICE_F_MOVE | SPLICE_F_MORE);
+
+ if (ret < 0) {
+ pr_err("Splice_write in rw-thread(%d)\n",
+ ts->cpu_num);
+ goto error;
+ } else if (ret == 0)
+ /*
+ * When host reader is not in time for reading
+ * trace data, guest will be stopped. This is
+ * because char dev in QEMU is not supported
+ * non-blocking mode. Then, writer might be
+ * sleep in that case.
+ * This sleep will be removed by supporting
+ * non-blocking mode.
+ */
+ sleep(1);
+ wlen += ret;
+ } while (wlen < rlen);
+ }
+
+ return NULL;
+
+error:
+ exit(EXIT_FAILURE);
+}
+
+
+pthread_t rw_thread_run(struct rw_thread_info *rw_ti)
+{
+ int ret;
+ pthread_t rw_thread_per_cpu;
+
+ ret = pthread_create(&rw_thread_per_cpu, NULL, rw_thread_main, rw_ti);
+ if (ret != 0) {
+ pr_err("Could not create a rw thread(%d)\n", rw_ti->cpu_num);
+ exit(EXIT_FAILURE);
+ }
+
+ return rw_thread_per_cpu;
+}
diff --git a/tools/virtio/virtio-trace/trace-agent.c b/tools/virtio/virtio-trace/trace-agent.c
new file mode 100644
index 00000000000..0a0a7dd4eff
--- /dev/null
+++ b/tools/virtio/virtio-trace/trace-agent.c
@@ -0,0 +1,270 @@
+/*
+ * Guest agent for virtio-trace
+ *
+ * Copyright (C) 2012 Hitachi, Ltd.
+ * Created by Yoshihiro Yunomae <yoshihiro.yunomae.ez@hitachi.com>
+ * Masami Hiramatsu <masami.hiramatsu.pt@hitachi.com>
+ *
+ * Licensed under GPL version 2 only.
+ *
+ */
+
+#define _GNU_SOURCE
+#include <limits.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include "trace-agent.h"
+
+#define PAGE_SIZE (sysconf(_SC_PAGE_SIZE))
+#define PIPE_DEF_BUFS 16
+#define PIPE_MIN_SIZE (PAGE_SIZE*PIPE_DEF_BUFS)
+#define PIPE_MAX_SIZE (1024*1024)
+#define READ_PATH_FMT \
+ "/sys/kernel/debug/tracing/per_cpu/cpu%d/trace_pipe_raw"
+#define WRITE_PATH_FMT "/dev/virtio-ports/trace-path-cpu%d"
+#define CTL_PATH "/dev/virtio-ports/agent-ctl-path"
+
+pthread_mutex_t mutex_notify = PTHREAD_MUTEX_INITIALIZER;
+pthread_cond_t cond_wakeup = PTHREAD_COND_INITIALIZER;
+
+static int get_total_cpus(void)
+{
+ int nr_cpus = (int)sysconf(_SC_NPROCESSORS_CONF);
+
+ if (nr_cpus <= 0) {
+ pr_err("Could not read cpus\n");
+ goto error;
+ } else if (nr_cpus > MAX_CPUS) {
+ pr_err("Exceed max cpus(%d)\n", (int)MAX_CPUS);
+ goto error;
+ }
+
+ return nr_cpus;
+
+error:
+ exit(EXIT_FAILURE);
+}
+
+static void *agent_info_new(void)
+{
+ struct agent_info *s;
+ int i;
+
+ s = zalloc(sizeof(struct agent_info));
+ if (s == NULL) {
+ pr_err("agent_info zalloc error\n");
+ exit(EXIT_FAILURE);
+ }
+
+ s->pipe_size = PIPE_INIT;
+ s->use_stdout = false;
+ s->cpus = get_total_cpus();
+ s->ctl_fd = -1;
+
+ /* read/write threads init */
+ for (i = 0; i < s->cpus; i++)
+ s->rw_ti[i] = rw_thread_info_new();
+
+ return s;
+}
+
+static unsigned long parse_size(const char *arg)
+{
+ unsigned long value, round;
+ char *ptr;
+
+ value = strtoul(arg, &ptr, 10);
+ switch (*ptr) {
+ case 'K': case 'k':
+ value <<= 10;
+ break;
+ case 'M': case 'm':
+ value <<= 20;
+ break;
+ default:
+ break;
+ }
+
+ if (value > PIPE_MAX_SIZE) {
+ pr_err("Pipe size must be less than 1MB\n");
+ goto error;
+ } else if (value < PIPE_MIN_SIZE) {
+ pr_err("Pipe size must be over 64KB\n");
+ goto error;
+ }
+
+ /* Align buffer size with page unit */
+ round = value & (PAGE_SIZE - 1);
+ value = value - round;
+
+ return value;
+error:
+ return 0;
+}
+
+static void usage(char const *prg)
+{
+ pr_err("usage: %s [-h] [-o] [-s <size of pipe>]\n", prg);
+}
+
+static const char *make_path(int cpu_num, bool this_is_write_path)
+{
+ int ret;
+ char *buf;
+
+ buf = zalloc(PATH_MAX);
+ if (buf == NULL) {
+ pr_err("Could not allocate buffer\n");
+ goto error;
+ }
+
+ if (this_is_write_path)
+ /* write(output) path */
+ ret = snprintf(buf, PATH_MAX, WRITE_PATH_FMT, cpu_num);
+ else
+ /* read(input) path */
+ ret = snprintf(buf, PATH_MAX, READ_PATH_FMT, cpu_num);
+
+ if (ret <= 0) {
+ pr_err("Failed to generate %s path(CPU#%d):%d\n",
+ this_is_write_path ? "read" : "write", cpu_num, ret);
+ goto error;
+ }
+
+ return buf;
+
+error:
+ free(buf);
+ return NULL;
+}
+
+static const char *make_input_path(int cpu_num)
+{
+ return make_path(cpu_num, false);
+}
+
+static const char *make_output_path(int cpu_num)
+{
+ return make_path(cpu_num, true);
+}
+
+static void *agent_info_init(struct agent_info *s)
+{
+ int cpu;
+ const char *in_path = NULL;
+ const char *out_path = NULL;
+
+ /* init read/write threads */
+ for (cpu = 0; cpu < s->cpus; cpu++) {
+ /* set read(input) path per read/write thread */
+ in_path = make_input_path(cpu);
+ if (in_path == NULL)
+ goto error;
+
+ /* set write(output) path per read/write thread*/
+ if (!s->use_stdout) {
+ out_path = make_output_path(cpu);
+ if (out_path == NULL)
+ goto error;
+ } else
+ /* stdout mode */
+ pr_debug("stdout mode\n");
+
+ rw_thread_init(cpu, in_path, out_path, s->use_stdout,
+ s->pipe_size, s->rw_ti[cpu]);
+ }
+
+ /* init controller of read/write threads */
+ s->ctl_fd = rw_ctl_init((const char *)CTL_PATH);
+
+ return NULL;
+
+error:
+ exit(EXIT_FAILURE);
+}
+
+static void *parse_args(int argc, char *argv[], struct agent_info *s)
+{
+ int cmd;
+ unsigned long size;
+
+ while ((cmd = getopt(argc, argv, "hos:")) != -1) {
+ switch (cmd) {
+ /* stdout mode */
+ case 'o':
+ s->use_stdout = true;
+ break;
+ /* size of pipe */
+ case 's':
+ size = parse_size(optarg);
+ if (size == 0)
+ goto error;
+ s->pipe_size = size;
+ break;
+ case 'h':
+ default:
+ usage(argv[0]);
+ goto error;
+ }
+ }
+
+ agent_info_init(s);
+
+ return NULL;
+
+error:
+ exit(EXIT_FAILURE);
+}
+
+static void agent_main_loop(struct agent_info *s)
+{
+ int cpu;
+ pthread_t rw_thread_per_cpu[MAX_CPUS];
+
+ /* Start all read/write threads */
+ for (cpu = 0; cpu < s->cpus; cpu++)
+ rw_thread_per_cpu[cpu] = rw_thread_run(s->rw_ti[cpu]);
+
+ rw_ctl_loop(s->ctl_fd);
+
+ /* Finish all read/write threads */
+ for (cpu = 0; cpu < s->cpus; cpu++) {
+ int ret;
+
+ ret = pthread_join(rw_thread_per_cpu[cpu], NULL);
+ if (ret != 0) {
+ pr_err("pthread_join() error:%d (cpu %d)\n", ret, cpu);
+ exit(EXIT_FAILURE);
+ }
+ }
+}
+
+static void agent_info_free(struct agent_info *s)
+{
+ int i;
+
+ close(s->ctl_fd);
+ for (i = 0; i < s->cpus; i++) {
+ close(s->rw_ti[i]->in_fd);
+ close(s->rw_ti[i]->out_fd);
+ close(s->rw_ti[i]->read_pipe);
+ close(s->rw_ti[i]->write_pipe);
+ free(s->rw_ti[i]);
+ }
+ free(s);
+}
+
+int main(int argc, char *argv[])
+{
+ struct agent_info *s = NULL;
+
+ s = agent_info_new();
+ parse_args(argc, argv, s);
+
+ agent_main_loop(s);
+
+ agent_info_free(s);
+
+ return 0;
+}
diff --git a/tools/virtio/virtio-trace/trace-agent.h b/tools/virtio/virtio-trace/trace-agent.h
new file mode 100644
index 00000000000..8de79bfeaa7
--- /dev/null
+++ b/tools/virtio/virtio-trace/trace-agent.h
@@ -0,0 +1,75 @@
+#ifndef __TRACE_AGENT_H__
+#define __TRACE_AGENT_H__
+#include <pthread.h>
+#include <stdbool.h>
+
+#define MAX_CPUS 256
+#define PIPE_INIT (1024*1024)
+
+/*
+ * agent_info - structure managing total information of guest agent
+ * @pipe_size: size of pipe (default 1MB)
+ * @use_stdout: set to true when o option is added (default false)
+ * @cpus: total number of CPUs
+ * @ctl_fd: fd of control path, /dev/virtio-ports/agent-ctl-path
+ * @rw_ti: structure managing information of read/write threads
+ */
+struct agent_info {
+ unsigned long pipe_size;
+ bool use_stdout;
+ int cpus;
+ int ctl_fd;
+ struct rw_thread_info *rw_ti[MAX_CPUS];
+};
+
+/*
+ * rw_thread_info - structure managing a read/write thread a cpu
+ * @cpu_num: cpu number operating this read/write thread
+ * @in_fd: fd of reading trace data path in cpu_num
+ * @out_fd: fd of writing trace data path in cpu_num
+ * @read_pipe: fd of read pipe
+ * @write_pipe: fd of write pipe
+ * @pipe_size: size of pipe (default 1MB)
+ */
+struct rw_thread_info {
+ int cpu_num;
+ int in_fd;
+ int out_fd;
+ int read_pipe;
+ int write_pipe;
+ unsigned long pipe_size;
+};
+
+/* use for stopping rw threads */
+extern bool global_sig_receive;
+
+/* use for notification */
+extern bool global_run_operation;
+extern pthread_mutex_t mutex_notify;
+extern pthread_cond_t cond_wakeup;
+
+/* for controller of read/write threads */
+extern int rw_ctl_init(const char *ctl_path);
+extern void *rw_ctl_loop(int ctl_fd);
+
+/* for trace read/write thread */
+extern void *rw_thread_info_new(void);
+extern void *rw_thread_init(int cpu, const char *in_path, const char *out_path,
+ bool stdout_flag, unsigned long pipe_size,
+ struct rw_thread_info *rw_ti);
+extern pthread_t rw_thread_run(struct rw_thread_info *rw_ti);
+
+static inline void *zalloc(size_t size)
+{
+ return calloc(1, size);
+}
+
+#define pr_err(format, ...) fprintf(stderr, format, ## __VA_ARGS__)
+#define pr_info(format, ...) fprintf(stdout, format, ## __VA_ARGS__)
+#ifdef DEBUG
+#define pr_debug(format, ...) fprintf(stderr, format, ## __VA_ARGS__)
+#else
+#define pr_debug(format, ...) do {} while (0)
+#endif
+
+#endif /*__TRACE_AGENT_H__*/
diff --git a/tools/virtio/virtio_test.c b/tools/virtio/virtio_test.c
new file mode 100644
index 00000000000..00ea679b382
--- /dev/null
+++ b/tools/virtio/virtio_test.c
@@ -0,0 +1,290 @@
+#define _GNU_SOURCE
+#include <getopt.h>
+#include <string.h>
+#include <poll.h>
+#include <sys/eventfd.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <unistd.h>
+#include <sys/ioctl.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <linux/vhost.h>
+#include <linux/virtio.h>
+#include <linux/virtio_ring.h>
+#include "../../drivers/vhost/test.h"
+
+/* Unused */
+void *__kmalloc_fake, *__kfree_ignore_start, *__kfree_ignore_end;
+
+struct vq_info {
+ int kick;
+ int call;
+ int num;
+ int idx;
+ void *ring;
+ /* copy used for control */
+ struct vring vring;
+ struct virtqueue *vq;
+};
+
+struct vdev_info {
+ struct virtio_device vdev;
+ int control;
+ struct pollfd fds[1];
+ struct vq_info vqs[1];
+ int nvqs;
+ void *buf;
+ size_t buf_size;
+ struct vhost_memory *mem;
+};
+
+bool vq_notify(struct virtqueue *vq)
+{
+ struct vq_info *info = vq->priv;
+ unsigned long long v = 1;
+ int r;
+ r = write(info->kick, &v, sizeof v);
+ assert(r == sizeof v);
+ return true;
+}
+
+void vq_callback(struct virtqueue *vq)
+{
+}
+
+
+void vhost_vq_setup(struct vdev_info *dev, struct vq_info *info)
+{
+ struct vhost_vring_state state = { .index = info->idx };
+ struct vhost_vring_file file = { .index = info->idx };
+ unsigned long long features = dev->vdev.features[0];
+ struct vhost_vring_addr addr = {
+ .index = info->idx,
+ .desc_user_addr = (uint64_t)(unsigned long)info->vring.desc,
+ .avail_user_addr = (uint64_t)(unsigned long)info->vring.avail,
+ .used_user_addr = (uint64_t)(unsigned long)info->vring.used,
+ };
+ int r;
+ r = ioctl(dev->control, VHOST_SET_FEATURES, &features);
+ assert(r >= 0);
+ state.num = info->vring.num;
+ r = ioctl(dev->control, VHOST_SET_VRING_NUM, &state);
+ assert(r >= 0);
+ state.num = 0;
+ r = ioctl(dev->control, VHOST_SET_VRING_BASE, &state);
+ assert(r >= 0);
+ r = ioctl(dev->control, VHOST_SET_VRING_ADDR, &addr);
+ assert(r >= 0);
+ file.fd = info->kick;
+ r = ioctl(dev->control, VHOST_SET_VRING_KICK, &file);
+ assert(r >= 0);
+ file.fd = info->call;
+ r = ioctl(dev->control, VHOST_SET_VRING_CALL, &file);
+ assert(r >= 0);
+}
+
+static void vq_info_add(struct vdev_info *dev, int num)
+{
+ struct vq_info *info = &dev->vqs[dev->nvqs];
+ int r;
+ info->idx = dev->nvqs;
+ info->kick = eventfd(0, EFD_NONBLOCK);
+ info->call = eventfd(0, EFD_NONBLOCK);
+ r = posix_memalign(&info->ring, 4096, vring_size(num, 4096));
+ assert(r >= 0);
+ memset(info->ring, 0, vring_size(num, 4096));
+ vring_init(&info->vring, num, info->ring, 4096);
+ info->vq = vring_new_virtqueue(info->idx,
+ info->vring.num, 4096, &dev->vdev,
+ true, info->ring,
+ vq_notify, vq_callback, "test");
+ assert(info->vq);
+ info->vq->priv = info;
+ vhost_vq_setup(dev, info);
+ dev->fds[info->idx].fd = info->call;
+ dev->fds[info->idx].events = POLLIN;
+ dev->nvqs++;
+}
+
+static void vdev_info_init(struct vdev_info* dev, unsigned long long features)
+{
+ int r;
+ memset(dev, 0, sizeof *dev);
+ dev->vdev.features[0] = features;
+ dev->vdev.features[1] = features >> 32;
+ dev->buf_size = 1024;
+ dev->buf = malloc(dev->buf_size);
+ assert(dev->buf);
+ dev->control = open("/dev/vhost-test", O_RDWR);
+ assert(dev->control >= 0);
+ r = ioctl(dev->control, VHOST_SET_OWNER, NULL);
+ assert(r >= 0);
+ dev->mem = malloc(offsetof(struct vhost_memory, regions) +
+ sizeof dev->mem->regions[0]);
+ assert(dev->mem);
+ memset(dev->mem, 0, offsetof(struct vhost_memory, regions) +
+ sizeof dev->mem->regions[0]);
+ dev->mem->nregions = 1;
+ dev->mem->regions[0].guest_phys_addr = (long)dev->buf;
+ dev->mem->regions[0].userspace_addr = (long)dev->buf;
+ dev->mem->regions[0].memory_size = dev->buf_size;
+ r = ioctl(dev->control, VHOST_SET_MEM_TABLE, dev->mem);
+ assert(r >= 0);
+}
+
+/* TODO: this is pretty bad: we get a cache line bounce
+ * for the wait queue on poll and another one on read,
+ * plus the read which is there just to clear the
+ * current state. */
+static void wait_for_interrupt(struct vdev_info *dev)
+{
+ int i;
+ unsigned long long val;
+ poll(dev->fds, dev->nvqs, -1);
+ for (i = 0; i < dev->nvqs; ++i)
+ if (dev->fds[i].revents & POLLIN) {
+ read(dev->fds[i].fd, &val, sizeof val);
+ }
+}
+
+static void run_test(struct vdev_info *dev, struct vq_info *vq,
+ bool delayed, int bufs)
+{
+ struct scatterlist sl;
+ long started = 0, completed = 0;
+ long completed_before;
+ int r, test = 1;
+ unsigned len;
+ long long spurious = 0;
+ r = ioctl(dev->control, VHOST_TEST_RUN, &test);
+ assert(r >= 0);
+ for (;;) {
+ virtqueue_disable_cb(vq->vq);
+ completed_before = completed;
+ do {
+ if (started < bufs) {
+ sg_init_one(&sl, dev->buf, dev->buf_size);
+ r = virtqueue_add_outbuf(vq->vq, &sl, 1,
+ dev->buf + started,
+ GFP_ATOMIC);
+ if (likely(r == 0)) {
+ ++started;
+ if (unlikely(!virtqueue_kick(vq->vq)))
+ r = -1;
+ }
+ } else
+ r = -1;
+
+ /* Flush out completed bufs if any */
+ if (virtqueue_get_buf(vq->vq, &len)) {
+ ++completed;
+ r = 0;
+ }
+
+ } while (r == 0);
+ if (completed == completed_before)
+ ++spurious;
+ assert(completed <= bufs);
+ assert(started <= bufs);
+ if (completed == bufs)
+ break;
+ if (delayed) {
+ if (virtqueue_enable_cb_delayed(vq->vq))
+ wait_for_interrupt(dev);
+ } else {
+ if (virtqueue_enable_cb(vq->vq))
+ wait_for_interrupt(dev);
+ }
+ }
+ test = 0;
+ r = ioctl(dev->control, VHOST_TEST_RUN, &test);
+ assert(r >= 0);
+ fprintf(stderr, "spurious wakeus: 0x%llx\n", spurious);
+}
+
+const char optstring[] = "h";
+const struct option longopts[] = {
+ {
+ .name = "help",
+ .val = 'h',
+ },
+ {
+ .name = "event-idx",
+ .val = 'E',
+ },
+ {
+ .name = "no-event-idx",
+ .val = 'e',
+ },
+ {
+ .name = "indirect",
+ .val = 'I',
+ },
+ {
+ .name = "no-indirect",
+ .val = 'i',
+ },
+ {
+ .name = "delayed-interrupt",
+ .val = 'D',
+ },
+ {
+ .name = "no-delayed-interrupt",
+ .val = 'd',
+ },
+ {
+ }
+};
+
+static void help(void)
+{
+ fprintf(stderr, "Usage: virtio_test [--help]"
+ " [--no-indirect]"
+ " [--no-event-idx]"
+ " [--delayed-interrupt]"
+ "\n");
+}
+
+int main(int argc, char **argv)
+{
+ struct vdev_info dev;
+ unsigned long long features = (1ULL << VIRTIO_RING_F_INDIRECT_DESC) |
+ (1ULL << VIRTIO_RING_F_EVENT_IDX);
+ int o;
+ bool delayed = false;
+
+ for (;;) {
+ o = getopt_long(argc, argv, optstring, longopts, NULL);
+ switch (o) {
+ case -1:
+ goto done;
+ case '?':
+ help();
+ exit(2);
+ case 'e':
+ features &= ~(1ULL << VIRTIO_RING_F_EVENT_IDX);
+ break;
+ case 'h':
+ help();
+ goto done;
+ case 'i':
+ features &= ~(1ULL << VIRTIO_RING_F_INDIRECT_DESC);
+ break;
+ case 'D':
+ delayed = true;
+ break;
+ default:
+ assert(0);
+ break;
+ }
+ }
+
+done:
+ vdev_info_init(&dev, features);
+ vq_info_add(&dev, 256);
+ run_test(&dev, &dev.vqs[0], delayed, 0x100000);
+ return 0;
+}
diff --git a/tools/virtio/vringh_test.c b/tools/virtio/vringh_test.c
new file mode 100644
index 00000000000..14a4f4cab5b
--- /dev/null
+++ b/tools/virtio/vringh_test.c
@@ -0,0 +1,746 @@
+/* Simple test of virtio code, entirely in userpsace. */
+#define _GNU_SOURCE
+#include <sched.h>
+#include <err.h>
+#include <linux/kernel.h>
+#include <linux/err.h>
+#include <linux/virtio.h>
+#include <linux/vringh.h>
+#include <linux/virtio_ring.h>
+#include <linux/uaccess.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+
+#define USER_MEM (1024*1024)
+void *__user_addr_min, *__user_addr_max;
+void *__kmalloc_fake, *__kfree_ignore_start, *__kfree_ignore_end;
+static u64 user_addr_offset;
+
+#define RINGSIZE 256
+#define ALIGN 4096
+
+static bool never_notify_host(struct virtqueue *vq)
+{
+ abort();
+}
+
+static void never_callback_guest(struct virtqueue *vq)
+{
+ abort();
+}
+
+static bool getrange_iov(struct vringh *vrh, u64 addr, struct vringh_range *r)
+{
+ if (addr < (u64)(unsigned long)__user_addr_min - user_addr_offset)
+ return false;
+ if (addr >= (u64)(unsigned long)__user_addr_max - user_addr_offset)
+ return false;
+
+ r->start = (u64)(unsigned long)__user_addr_min - user_addr_offset;
+ r->end_incl = (u64)(unsigned long)__user_addr_max - 1 - user_addr_offset;
+ r->offset = user_addr_offset;
+ return true;
+}
+
+/* We return single byte ranges. */
+static bool getrange_slow(struct vringh *vrh, u64 addr, struct vringh_range *r)
+{
+ if (addr < (u64)(unsigned long)__user_addr_min - user_addr_offset)
+ return false;
+ if (addr >= (u64)(unsigned long)__user_addr_max - user_addr_offset)
+ return false;
+
+ r->start = addr;
+ r->end_incl = r->start;
+ r->offset = user_addr_offset;
+ return true;
+}
+
+struct guest_virtio_device {
+ struct virtio_device vdev;
+ int to_host_fd;
+ unsigned long notifies;
+};
+
+static bool parallel_notify_host(struct virtqueue *vq)
+{
+ int rc;
+ struct guest_virtio_device *gvdev;
+
+ gvdev = container_of(vq->vdev, struct guest_virtio_device, vdev);
+ rc = write(gvdev->to_host_fd, "", 1);
+ if (rc < 0)
+ return false;
+ gvdev->notifies++;
+ return true;
+}
+
+static bool no_notify_host(struct virtqueue *vq)
+{
+ return true;
+}
+
+#define NUM_XFERS (10000000)
+
+/* We aim for two "distant" cpus. */
+static void find_cpus(unsigned int *first, unsigned int *last)
+{
+ unsigned int i;
+
+ *first = -1U;
+ *last = 0;
+ for (i = 0; i < 4096; i++) {
+ cpu_set_t set;
+ CPU_ZERO(&set);
+ CPU_SET(i, &set);
+ if (sched_setaffinity(getpid(), sizeof(set), &set) == 0) {
+ if (i < *first)
+ *first = i;
+ if (i > *last)
+ *last = i;
+ }
+ }
+}
+
+/* Opencoded version for fast mode */
+static inline int vringh_get_head(struct vringh *vrh, u16 *head)
+{
+ u16 avail_idx, i;
+ int err;
+
+ err = get_user(avail_idx, &vrh->vring.avail->idx);
+ if (err)
+ return err;
+
+ if (vrh->last_avail_idx == avail_idx)
+ return 0;
+
+ /* Only get avail ring entries after they have been exposed by guest. */
+ virtio_rmb(vrh->weak_barriers);
+
+ i = vrh->last_avail_idx & (vrh->vring.num - 1);
+
+ err = get_user(*head, &vrh->vring.avail->ring[i]);
+ if (err)
+ return err;
+
+ vrh->last_avail_idx++;
+ return 1;
+}
+
+static int parallel_test(unsigned long features,
+ bool (*getrange)(struct vringh *vrh,
+ u64 addr, struct vringh_range *r),
+ bool fast_vringh)
+{
+ void *host_map, *guest_map;
+ int fd, mapsize, to_guest[2], to_host[2];
+ unsigned long xfers = 0, notifies = 0, receives = 0;
+ unsigned int first_cpu, last_cpu;
+ cpu_set_t cpu_set;
+ char buf[128];
+
+ /* Create real file to mmap. */
+ fd = open("/tmp/vringh_test-file", O_RDWR|O_CREAT|O_TRUNC, 0600);
+ if (fd < 0)
+ err(1, "Opening /tmp/vringh_test-file");
+
+ /* Extra room at the end for some data, and indirects */
+ mapsize = vring_size(RINGSIZE, ALIGN)
+ + RINGSIZE * 2 * sizeof(int)
+ + RINGSIZE * 6 * sizeof(struct vring_desc);
+ mapsize = (mapsize + getpagesize() - 1) & ~(getpagesize() - 1);
+ ftruncate(fd, mapsize);
+
+ /* Parent and child use separate addresses, to check our mapping logic! */
+ host_map = mmap(NULL, mapsize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+ guest_map = mmap(NULL, mapsize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
+
+ pipe(to_guest);
+ pipe(to_host);
+
+ CPU_ZERO(&cpu_set);
+ find_cpus(&first_cpu, &last_cpu);
+ printf("Using CPUS %u and %u\n", first_cpu, last_cpu);
+ fflush(stdout);
+
+ if (fork() != 0) {
+ struct vringh vrh;
+ int status, err, rlen = 0;
+ char rbuf[5];
+
+ /* We are the host: never access guest addresses! */
+ munmap(guest_map, mapsize);
+
+ __user_addr_min = host_map;
+ __user_addr_max = __user_addr_min + mapsize;
+ user_addr_offset = host_map - guest_map;
+ assert(user_addr_offset);
+
+ close(to_guest[0]);
+ close(to_host[1]);
+
+ vring_init(&vrh.vring, RINGSIZE, host_map, ALIGN);
+ vringh_init_user(&vrh, features, RINGSIZE, true,
+ vrh.vring.desc, vrh.vring.avail, vrh.vring.used);
+ CPU_SET(first_cpu, &cpu_set);
+ if (sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set))
+ errx(1, "Could not set affinity to cpu %u", first_cpu);
+
+ while (xfers < NUM_XFERS) {
+ struct iovec host_riov[2], host_wiov[2];
+ struct vringh_iov riov, wiov;
+ u16 head, written;
+
+ if (fast_vringh) {
+ for (;;) {
+ err = vringh_get_head(&vrh, &head);
+ if (err != 0)
+ break;
+ err = vringh_need_notify_user(&vrh);
+ if (err < 0)
+ errx(1, "vringh_need_notify_user: %i",
+ err);
+ if (err) {
+ write(to_guest[1], "", 1);
+ notifies++;
+ }
+ }
+ if (err != 1)
+ errx(1, "vringh_get_head");
+ written = 0;
+ goto complete;
+ } else {
+ vringh_iov_init(&riov,
+ host_riov,
+ ARRAY_SIZE(host_riov));
+ vringh_iov_init(&wiov,
+ host_wiov,
+ ARRAY_SIZE(host_wiov));
+
+ err = vringh_getdesc_user(&vrh, &riov, &wiov,
+ getrange, &head);
+ }
+ if (err == 0) {
+ err = vringh_need_notify_user(&vrh);
+ if (err < 0)
+ errx(1, "vringh_need_notify_user: %i",
+ err);
+ if (err) {
+ write(to_guest[1], "", 1);
+ notifies++;
+ }
+
+ if (!vringh_notify_enable_user(&vrh))
+ continue;
+
+ /* Swallow all notifies at once. */
+ if (read(to_host[0], buf, sizeof(buf)) < 1)
+ break;
+
+ vringh_notify_disable_user(&vrh);
+ receives++;
+ continue;
+ }
+ if (err != 1)
+ errx(1, "vringh_getdesc_user: %i", err);
+
+ /* We simply copy bytes. */
+ if (riov.used) {
+ rlen = vringh_iov_pull_user(&riov, rbuf,
+ sizeof(rbuf));
+ if (rlen != 4)
+ errx(1, "vringh_iov_pull_user: %i",
+ rlen);
+ assert(riov.i == riov.used);
+ written = 0;
+ } else {
+ err = vringh_iov_push_user(&wiov, rbuf, rlen);
+ if (err != rlen)
+ errx(1, "vringh_iov_push_user: %i",
+ err);
+ assert(wiov.i == wiov.used);
+ written = err;
+ }
+ complete:
+ xfers++;
+
+ err = vringh_complete_user(&vrh, head, written);
+ if (err != 0)
+ errx(1, "vringh_complete_user: %i", err);
+ }
+
+ err = vringh_need_notify_user(&vrh);
+ if (err < 0)
+ errx(1, "vringh_need_notify_user: %i", err);
+ if (err) {
+ write(to_guest[1], "", 1);
+ notifies++;
+ }
+ wait(&status);
+ if (!WIFEXITED(status))
+ errx(1, "Child died with signal %i?", WTERMSIG(status));
+ if (WEXITSTATUS(status) != 0)
+ errx(1, "Child exited %i?", WEXITSTATUS(status));
+ printf("Host: notified %lu, pinged %lu\n", notifies, receives);
+ return 0;
+ } else {
+ struct guest_virtio_device gvdev;
+ struct virtqueue *vq;
+ unsigned int *data;
+ struct vring_desc *indirects;
+ unsigned int finished = 0;
+
+ /* We pass sg[]s pointing into here, but we need RINGSIZE+1 */
+ data = guest_map + vring_size(RINGSIZE, ALIGN);
+ indirects = (void *)data + (RINGSIZE + 1) * 2 * sizeof(int);
+
+ /* We are the guest. */
+ munmap(host_map, mapsize);
+
+ close(to_guest[1]);
+ close(to_host[0]);
+
+ gvdev.vdev.features[0] = features;
+ gvdev.to_host_fd = to_host[1];
+ gvdev.notifies = 0;
+
+ CPU_SET(first_cpu, &cpu_set);
+ if (sched_setaffinity(getpid(), sizeof(cpu_set), &cpu_set))
+ err(1, "Could not set affinity to cpu %u", first_cpu);
+
+ vq = vring_new_virtqueue(0, RINGSIZE, ALIGN, &gvdev.vdev, true,
+ guest_map, fast_vringh ? no_notify_host
+ : parallel_notify_host,
+ never_callback_guest, "guest vq");
+
+ /* Don't kfree indirects. */
+ __kfree_ignore_start = indirects;
+ __kfree_ignore_end = indirects + RINGSIZE * 6;
+
+ while (xfers < NUM_XFERS) {
+ struct scatterlist sg[4];
+ unsigned int num_sg, len;
+ int *dbuf, err;
+ bool output = !(xfers % 2);
+
+ /* Consume bufs. */
+ while ((dbuf = virtqueue_get_buf(vq, &len)) != NULL) {
+ if (len == 4)
+ assert(*dbuf == finished - 1);
+ else if (!fast_vringh)
+ assert(*dbuf == finished);
+ finished++;
+ }
+
+ /* Produce a buffer. */
+ dbuf = data + (xfers % (RINGSIZE + 1));
+
+ if (output)
+ *dbuf = xfers;
+ else
+ *dbuf = -1;
+
+ switch ((xfers / sizeof(*dbuf)) % 4) {
+ case 0:
+ /* Nasty three-element sg list. */
+ sg_init_table(sg, num_sg = 3);
+ sg_set_buf(&sg[0], (void *)dbuf, 1);
+ sg_set_buf(&sg[1], (void *)dbuf + 1, 2);
+ sg_set_buf(&sg[2], (void *)dbuf + 3, 1);
+ break;
+ case 1:
+ sg_init_table(sg, num_sg = 2);
+ sg_set_buf(&sg[0], (void *)dbuf, 1);
+ sg_set_buf(&sg[1], (void *)dbuf + 1, 3);
+ break;
+ case 2:
+ sg_init_table(sg, num_sg = 1);
+ sg_set_buf(&sg[0], (void *)dbuf, 4);
+ break;
+ case 3:
+ sg_init_table(sg, num_sg = 4);
+ sg_set_buf(&sg[0], (void *)dbuf, 1);
+ sg_set_buf(&sg[1], (void *)dbuf + 1, 1);
+ sg_set_buf(&sg[2], (void *)dbuf + 2, 1);
+ sg_set_buf(&sg[3], (void *)dbuf + 3, 1);
+ break;
+ }
+
+ /* May allocate an indirect, so force it to allocate
+ * user addr */
+ __kmalloc_fake = indirects + (xfers % RINGSIZE) * 4;
+ if (output)
+ err = virtqueue_add_outbuf(vq, sg, num_sg, dbuf,
+ GFP_KERNEL);
+ else
+ err = virtqueue_add_inbuf(vq, sg, num_sg,
+ dbuf, GFP_KERNEL);
+
+ if (err == -ENOSPC) {
+ if (!virtqueue_enable_cb_delayed(vq))
+ continue;
+ /* Swallow all notifies at once. */
+ if (read(to_guest[0], buf, sizeof(buf)) < 1)
+ break;
+
+ receives++;
+ virtqueue_disable_cb(vq);
+ continue;
+ }
+
+ if (err)
+ errx(1, "virtqueue_add_in/outbuf: %i", err);
+
+ xfers++;
+ virtqueue_kick(vq);
+ }
+
+ /* Any extra? */
+ while (finished != xfers) {
+ int *dbuf;
+ unsigned int len;
+
+ /* Consume bufs. */
+ dbuf = virtqueue_get_buf(vq, &len);
+ if (dbuf) {
+ if (len == 4)
+ assert(*dbuf == finished - 1);
+ else
+ assert(len == 0);
+ finished++;
+ continue;
+ }
+
+ if (!virtqueue_enable_cb_delayed(vq))
+ continue;
+ if (read(to_guest[0], buf, sizeof(buf)) < 1)
+ break;
+
+ receives++;
+ virtqueue_disable_cb(vq);
+ }
+
+ printf("Guest: notified %lu, pinged %lu\n",
+ gvdev.notifies, receives);
+ vring_del_virtqueue(vq);
+ return 0;
+ }
+}
+
+int main(int argc, char *argv[])
+{
+ struct virtio_device vdev;
+ struct virtqueue *vq;
+ struct vringh vrh;
+ struct scatterlist guest_sg[RINGSIZE], *sgs[2];
+ struct iovec host_riov[2], host_wiov[2];
+ struct vringh_iov riov, wiov;
+ struct vring_used_elem used[RINGSIZE];
+ char buf[28];
+ u16 head;
+ int err;
+ unsigned i;
+ void *ret;
+ bool (*getrange)(struct vringh *vrh, u64 addr, struct vringh_range *r);
+ bool fast_vringh = false, parallel = false;
+
+ getrange = getrange_iov;
+ vdev.features[0] = 0;
+
+ while (argv[1]) {
+ if (strcmp(argv[1], "--indirect") == 0)
+ vdev.features[0] |= (1 << VIRTIO_RING_F_INDIRECT_DESC);
+ else if (strcmp(argv[1], "--eventidx") == 0)
+ vdev.features[0] |= (1 << VIRTIO_RING_F_EVENT_IDX);
+ else if (strcmp(argv[1], "--slow-range") == 0)
+ getrange = getrange_slow;
+ else if (strcmp(argv[1], "--fast-vringh") == 0)
+ fast_vringh = true;
+ else if (strcmp(argv[1], "--parallel") == 0)
+ parallel = true;
+ else
+ errx(1, "Unknown arg %s", argv[1]);
+ argv++;
+ }
+
+ if (parallel)
+ return parallel_test(vdev.features[0], getrange, fast_vringh);
+
+ if (posix_memalign(&__user_addr_min, PAGE_SIZE, USER_MEM) != 0)
+ abort();
+ __user_addr_max = __user_addr_min + USER_MEM;
+ memset(__user_addr_min, 0, vring_size(RINGSIZE, ALIGN));
+
+ /* Set up guest side. */
+ vq = vring_new_virtqueue(0, RINGSIZE, ALIGN, &vdev, true,
+ __user_addr_min,
+ never_notify_host, never_callback_guest,
+ "guest vq");
+
+ /* Set up host side. */
+ vring_init(&vrh.vring, RINGSIZE, __user_addr_min, ALIGN);
+ vringh_init_user(&vrh, vdev.features[0], RINGSIZE, true,
+ vrh.vring.desc, vrh.vring.avail, vrh.vring.used);
+
+ /* No descriptor to get yet... */
+ err = vringh_getdesc_user(&vrh, &riov, &wiov, getrange, &head);
+ if (err != 0)
+ errx(1, "vringh_getdesc_user: %i", err);
+
+ /* Guest puts in a descriptor. */
+ memcpy(__user_addr_max - 1, "a", 1);
+ sg_init_table(guest_sg, 1);
+ sg_set_buf(&guest_sg[0], __user_addr_max - 1, 1);
+ sg_init_table(guest_sg+1, 1);
+ sg_set_buf(&guest_sg[1], __user_addr_max - 3, 2);
+ sgs[0] = &guest_sg[0];
+ sgs[1] = &guest_sg[1];
+
+ /* May allocate an indirect, so force it to allocate user addr */
+ __kmalloc_fake = __user_addr_min + vring_size(RINGSIZE, ALIGN);
+ err = virtqueue_add_sgs(vq, sgs, 1, 1, &err, GFP_KERNEL);
+ if (err)
+ errx(1, "virtqueue_add_sgs: %i", err);
+ __kmalloc_fake = NULL;
+
+ /* Host retreives it. */
+ vringh_iov_init(&riov, host_riov, ARRAY_SIZE(host_riov));
+ vringh_iov_init(&wiov, host_wiov, ARRAY_SIZE(host_wiov));
+
+ err = vringh_getdesc_user(&vrh, &riov, &wiov, getrange, &head);
+ if (err != 1)
+ errx(1, "vringh_getdesc_user: %i", err);
+
+ assert(riov.used == 1);
+ assert(riov.iov[0].iov_base == __user_addr_max - 1);
+ assert(riov.iov[0].iov_len == 1);
+ if (getrange != getrange_slow) {
+ assert(wiov.used == 1);
+ assert(wiov.iov[0].iov_base == __user_addr_max - 3);
+ assert(wiov.iov[0].iov_len == 2);
+ } else {
+ assert(wiov.used == 2);
+ assert(wiov.iov[0].iov_base == __user_addr_max - 3);
+ assert(wiov.iov[0].iov_len == 1);
+ assert(wiov.iov[1].iov_base == __user_addr_max - 2);
+ assert(wiov.iov[1].iov_len == 1);
+ }
+
+ err = vringh_iov_pull_user(&riov, buf, 5);
+ if (err != 1)
+ errx(1, "vringh_iov_pull_user: %i", err);
+ assert(buf[0] == 'a');
+ assert(riov.i == 1);
+ assert(vringh_iov_pull_user(&riov, buf, 5) == 0);
+
+ memcpy(buf, "bcdef", 5);
+ err = vringh_iov_push_user(&wiov, buf, 5);
+ if (err != 2)
+ errx(1, "vringh_iov_push_user: %i", err);
+ assert(memcmp(__user_addr_max - 3, "bc", 2) == 0);
+ assert(wiov.i == wiov.used);
+ assert(vringh_iov_push_user(&wiov, buf, 5) == 0);
+
+ /* Host is done. */
+ err = vringh_complete_user(&vrh, head, err);
+ if (err != 0)
+ errx(1, "vringh_complete_user: %i", err);
+
+ /* Guest should see used token now. */
+ __kfree_ignore_start = __user_addr_min + vring_size(RINGSIZE, ALIGN);
+ __kfree_ignore_end = __kfree_ignore_start + 1;
+ ret = virtqueue_get_buf(vq, &i);
+ if (ret != &err)
+ errx(1, "virtqueue_get_buf: %p", ret);
+ assert(i == 2);
+
+ /* Guest puts in a huge descriptor. */
+ sg_init_table(guest_sg, RINGSIZE);
+ for (i = 0; i < RINGSIZE; i++) {
+ sg_set_buf(&guest_sg[i],
+ __user_addr_max - USER_MEM/4, USER_MEM/4);
+ }
+
+ /* Fill contents with recognisable garbage. */
+ for (i = 0; i < USER_MEM/4; i++)
+ ((char *)__user_addr_max - USER_MEM/4)[i] = i;
+
+ /* This will allocate an indirect, so force it to allocate user addr */
+ __kmalloc_fake = __user_addr_min + vring_size(RINGSIZE, ALIGN);
+ err = virtqueue_add_outbuf(vq, guest_sg, RINGSIZE, &err, GFP_KERNEL);
+ if (err)
+ errx(1, "virtqueue_add_outbuf (large): %i", err);
+ __kmalloc_fake = NULL;
+
+ /* Host picks it up (allocates new iov). */
+ vringh_iov_init(&riov, host_riov, ARRAY_SIZE(host_riov));
+ vringh_iov_init(&wiov, host_wiov, ARRAY_SIZE(host_wiov));
+
+ err = vringh_getdesc_user(&vrh, &riov, &wiov, getrange, &head);
+ if (err != 1)
+ errx(1, "vringh_getdesc_user: %i", err);
+
+ assert(riov.max_num & VRINGH_IOV_ALLOCATED);
+ assert(riov.iov != host_riov);
+ if (getrange != getrange_slow)
+ assert(riov.used == RINGSIZE);
+ else
+ assert(riov.used == RINGSIZE * USER_MEM/4);
+
+ assert(!(wiov.max_num & VRINGH_IOV_ALLOCATED));
+ assert(wiov.used == 0);
+
+ /* Pull data back out (in odd chunks), should be as expected. */
+ for (i = 0; i < RINGSIZE * USER_MEM/4; i += 3) {
+ err = vringh_iov_pull_user(&riov, buf, 3);
+ if (err != 3 && i + err != RINGSIZE * USER_MEM/4)
+ errx(1, "vringh_iov_pull_user large: %i", err);
+ assert(buf[0] == (char)i);
+ assert(err < 2 || buf[1] == (char)(i + 1));
+ assert(err < 3 || buf[2] == (char)(i + 2));
+ }
+ assert(riov.i == riov.used);
+ vringh_iov_cleanup(&riov);
+ vringh_iov_cleanup(&wiov);
+
+ /* Complete using multi interface, just because we can. */
+ used[0].id = head;
+ used[0].len = 0;
+ err = vringh_complete_multi_user(&vrh, used, 1);
+ if (err)
+ errx(1, "vringh_complete_multi_user(1): %i", err);
+
+ /* Free up those descriptors. */
+ ret = virtqueue_get_buf(vq, &i);
+ if (ret != &err)
+ errx(1, "virtqueue_get_buf: %p", ret);
+
+ /* Add lots of descriptors. */
+ sg_init_table(guest_sg, 1);
+ sg_set_buf(&guest_sg[0], __user_addr_max - 1, 1);
+ for (i = 0; i < RINGSIZE; i++) {
+ err = virtqueue_add_outbuf(vq, guest_sg, 1, &err, GFP_KERNEL);
+ if (err)
+ errx(1, "virtqueue_add_outbuf (multiple): %i", err);
+ }
+
+ /* Now get many, and consume them all at once. */
+ vringh_iov_init(&riov, host_riov, ARRAY_SIZE(host_riov));
+ vringh_iov_init(&wiov, host_wiov, ARRAY_SIZE(host_wiov));
+
+ for (i = 0; i < RINGSIZE; i++) {
+ err = vringh_getdesc_user(&vrh, &riov, &wiov, getrange, &head);
+ if (err != 1)
+ errx(1, "vringh_getdesc_user: %i", err);
+ used[i].id = head;
+ used[i].len = 0;
+ }
+ /* Make sure it wraps around ring, to test! */
+ assert(vrh.vring.used->idx % RINGSIZE != 0);
+ err = vringh_complete_multi_user(&vrh, used, RINGSIZE);
+ if (err)
+ errx(1, "vringh_complete_multi_user: %i", err);
+
+ /* Free those buffers. */
+ for (i = 0; i < RINGSIZE; i++) {
+ unsigned len;
+ assert(virtqueue_get_buf(vq, &len) != NULL);
+ }
+
+ /* Test weird (but legal!) indirect. */
+ if (vdev.features[0] & (1 << VIRTIO_RING_F_INDIRECT_DESC)) {
+ char *data = __user_addr_max - USER_MEM/4;
+ struct vring_desc *d = __user_addr_max - USER_MEM/2;
+ struct vring vring;
+
+ /* Force creation of direct, which we modify. */
+ vdev.features[0] &= ~(1 << VIRTIO_RING_F_INDIRECT_DESC);
+ vq = vring_new_virtqueue(0, RINGSIZE, ALIGN, &vdev, true,
+ __user_addr_min,
+ never_notify_host,
+ never_callback_guest,
+ "guest vq");
+
+ sg_init_table(guest_sg, 4);
+ sg_set_buf(&guest_sg[0], d, sizeof(*d)*2);
+ sg_set_buf(&guest_sg[1], d + 2, sizeof(*d)*1);
+ sg_set_buf(&guest_sg[2], data + 6, 4);
+ sg_set_buf(&guest_sg[3], d + 3, sizeof(*d)*3);
+
+ err = virtqueue_add_outbuf(vq, guest_sg, 4, &err, GFP_KERNEL);
+ if (err)
+ errx(1, "virtqueue_add_outbuf (indirect): %i", err);
+
+ vring_init(&vring, RINGSIZE, __user_addr_min, ALIGN);
+
+ /* They're used in order, but double-check... */
+ assert(vring.desc[0].addr == (unsigned long)d);
+ assert(vring.desc[1].addr == (unsigned long)(d+2));
+ assert(vring.desc[2].addr == (unsigned long)data + 6);
+ assert(vring.desc[3].addr == (unsigned long)(d+3));
+ vring.desc[0].flags |= VRING_DESC_F_INDIRECT;
+ vring.desc[1].flags |= VRING_DESC_F_INDIRECT;
+ vring.desc[3].flags |= VRING_DESC_F_INDIRECT;
+
+ /* First indirect */
+ d[0].addr = (unsigned long)data;
+ d[0].len = 1;
+ d[0].flags = VRING_DESC_F_NEXT;
+ d[0].next = 1;
+ d[1].addr = (unsigned long)data + 1;
+ d[1].len = 2;
+ d[1].flags = 0;
+
+ /* Second indirect */
+ d[2].addr = (unsigned long)data + 3;
+ d[2].len = 3;
+ d[2].flags = 0;
+
+ /* Third indirect */
+ d[3].addr = (unsigned long)data + 10;
+ d[3].len = 5;
+ d[3].flags = VRING_DESC_F_NEXT;
+ d[3].next = 1;
+ d[4].addr = (unsigned long)data + 15;
+ d[4].len = 6;
+ d[4].flags = VRING_DESC_F_NEXT;
+ d[4].next = 2;
+ d[5].addr = (unsigned long)data + 21;
+ d[5].len = 7;
+ d[5].flags = 0;
+
+ /* Host picks it up (allocates new iov). */
+ vringh_iov_init(&riov, host_riov, ARRAY_SIZE(host_riov));
+ vringh_iov_init(&wiov, host_wiov, ARRAY_SIZE(host_wiov));
+
+ err = vringh_getdesc_user(&vrh, &riov, &wiov, getrange, &head);
+ if (err != 1)
+ errx(1, "vringh_getdesc_user: %i", err);
+
+ if (head != 0)
+ errx(1, "vringh_getdesc_user: head %i not 0", head);
+
+ assert(riov.max_num & VRINGH_IOV_ALLOCATED);
+ if (getrange != getrange_slow)
+ assert(riov.used == 7);
+ else
+ assert(riov.used == 28);
+ err = vringh_iov_pull_user(&riov, buf, 29);
+ assert(err == 28);
+
+ /* Data should be linear. */
+ for (i = 0; i < err; i++)
+ assert(buf[i] == i);
+ vringh_iov_cleanup(&riov);
+ }
+
+ /* Don't leak memory... */
+ vring_del_virtqueue(vq);
+ free(__user_addr_min);
+
+ return 0;
+}
diff --git a/tools/vm/.gitignore b/tools/vm/.gitignore
new file mode 100644
index 00000000000..44f095fa260
--- /dev/null
+++ b/tools/vm/.gitignore
@@ -0,0 +1,2 @@
+slabinfo
+page-types
diff --git a/tools/vm/Makefile b/tools/vm/Makefile
new file mode 100644
index 00000000000..3d907dacf2a
--- /dev/null
+++ b/tools/vm/Makefile
@@ -0,0 +1,22 @@
+# Makefile for vm tools
+#
+TARGETS=page-types slabinfo
+
+LIB_DIR = ../lib/api
+LIBS = $(LIB_DIR)/libapikfs.a
+
+CC = $(CROSS_COMPILE)gcc
+CFLAGS = -Wall -Wextra -I../lib/
+LDFLAGS = $(LIBS)
+
+$(TARGETS): $(LIBS)
+
+$(LIBS):
+ make -C $(LIB_DIR)
+
+%: %.c
+ $(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
+
+clean:
+ $(RM) page-types slabinfo
+ make -C $(LIB_DIR) clean
diff --git a/tools/vm/page-types.c b/tools/vm/page-types.c
new file mode 100644
index 00000000000..c4d6d2e20e0
--- /dev/null
+++ b/tools/vm/page-types.c
@@ -0,0 +1,1176 @@
+/*
+ * page-types: Tool for querying page flags
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should find a copy of v2 of the GNU General Public License somewhere on
+ * your Linux system; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+ *
+ * Copyright (C) 2009 Intel corporation
+ *
+ * Authors: Wu Fengguang <fengguang.wu@intel.com>
+ */
+
+#define _FILE_OFFSET_BITS 64
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdint.h>
+#include <stdarg.h>
+#include <string.h>
+#include <getopt.h>
+#include <limits.h>
+#include <assert.h>
+#include <ftw.h>
+#include <time.h>
+#include <setjmp.h>
+#include <signal.h>
+#include <sys/types.h>
+#include <sys/errno.h>
+#include <sys/fcntl.h>
+#include <sys/mount.h>
+#include <sys/statfs.h>
+#include <sys/mman.h>
+#include "../../include/uapi/linux/magic.h"
+#include "../../include/uapi/linux/kernel-page-flags.h"
+#include <api/fs/debugfs.h>
+
+#ifndef MAX_PATH
+# define MAX_PATH 256
+#endif
+
+#ifndef STR
+# define _STR(x) #x
+# define STR(x) _STR(x)
+#endif
+
+/*
+ * pagemap kernel ABI bits
+ */
+
+#define PM_ENTRY_BYTES sizeof(uint64_t)
+#define PM_STATUS_BITS 3
+#define PM_STATUS_OFFSET (64 - PM_STATUS_BITS)
+#define PM_STATUS_MASK (((1LL << PM_STATUS_BITS) - 1) << PM_STATUS_OFFSET)
+#define PM_STATUS(nr) (((nr) << PM_STATUS_OFFSET) & PM_STATUS_MASK)
+#define PM_PSHIFT_BITS 6
+#define PM_PSHIFT_OFFSET (PM_STATUS_OFFSET - PM_PSHIFT_BITS)
+#define PM_PSHIFT_MASK (((1LL << PM_PSHIFT_BITS) - 1) << PM_PSHIFT_OFFSET)
+#define __PM_PSHIFT(x) (((uint64_t) (x) << PM_PSHIFT_OFFSET) & PM_PSHIFT_MASK)
+#define PM_PFRAME_MASK ((1LL << PM_PSHIFT_OFFSET) - 1)
+#define PM_PFRAME(x) ((x) & PM_PFRAME_MASK)
+
+#define __PM_SOFT_DIRTY (1LL)
+#define PM_PRESENT PM_STATUS(4LL)
+#define PM_SWAP PM_STATUS(2LL)
+#define PM_SOFT_DIRTY __PM_PSHIFT(__PM_SOFT_DIRTY)
+
+
+/*
+ * kernel page flags
+ */
+
+#define KPF_BYTES 8
+#define PROC_KPAGEFLAGS "/proc/kpageflags"
+
+/* [32-] kernel hacking assistances */
+#define KPF_RESERVED 32
+#define KPF_MLOCKED 33
+#define KPF_MAPPEDTODISK 34
+#define KPF_PRIVATE 35
+#define KPF_PRIVATE_2 36
+#define KPF_OWNER_PRIVATE 37
+#define KPF_ARCH 38
+#define KPF_UNCACHED 39
+#define KPF_SOFTDIRTY 40
+
+/* [48-] take some arbitrary free slots for expanding overloaded flags
+ * not part of kernel API
+ */
+#define KPF_READAHEAD 48
+#define KPF_SLOB_FREE 49
+#define KPF_SLUB_FROZEN 50
+#define KPF_SLUB_DEBUG 51
+
+#define KPF_ALL_BITS ((uint64_t)~0ULL)
+#define KPF_HACKERS_BITS (0xffffULL << 32)
+#define KPF_OVERLOADED_BITS (0xffffULL << 48)
+#define BIT(name) (1ULL << KPF_##name)
+#define BITS_COMPOUND (BIT(COMPOUND_HEAD) | BIT(COMPOUND_TAIL))
+
+static const char * const page_flag_names[] = {
+ [KPF_LOCKED] = "L:locked",
+ [KPF_ERROR] = "E:error",
+ [KPF_REFERENCED] = "R:referenced",
+ [KPF_UPTODATE] = "U:uptodate",
+ [KPF_DIRTY] = "D:dirty",
+ [KPF_LRU] = "l:lru",
+ [KPF_ACTIVE] = "A:active",
+ [KPF_SLAB] = "S:slab",
+ [KPF_WRITEBACK] = "W:writeback",
+ [KPF_RECLAIM] = "I:reclaim",
+ [KPF_BUDDY] = "B:buddy",
+
+ [KPF_MMAP] = "M:mmap",
+ [KPF_ANON] = "a:anonymous",
+ [KPF_SWAPCACHE] = "s:swapcache",
+ [KPF_SWAPBACKED] = "b:swapbacked",
+ [KPF_COMPOUND_HEAD] = "H:compound_head",
+ [KPF_COMPOUND_TAIL] = "T:compound_tail",
+ [KPF_HUGE] = "G:huge",
+ [KPF_UNEVICTABLE] = "u:unevictable",
+ [KPF_HWPOISON] = "X:hwpoison",
+ [KPF_NOPAGE] = "n:nopage",
+ [KPF_KSM] = "x:ksm",
+ [KPF_THP] = "t:thp",
+
+ [KPF_RESERVED] = "r:reserved",
+ [KPF_MLOCKED] = "m:mlocked",
+ [KPF_MAPPEDTODISK] = "d:mappedtodisk",
+ [KPF_PRIVATE] = "P:private",
+ [KPF_PRIVATE_2] = "p:private_2",
+ [KPF_OWNER_PRIVATE] = "O:owner_private",
+ [KPF_ARCH] = "h:arch",
+ [KPF_UNCACHED] = "c:uncached",
+ [KPF_SOFTDIRTY] = "f:softdirty",
+
+ [KPF_READAHEAD] = "I:readahead",
+ [KPF_SLOB_FREE] = "P:slob_free",
+ [KPF_SLUB_FROZEN] = "A:slub_frozen",
+ [KPF_SLUB_DEBUG] = "E:slub_debug",
+};
+
+
+static const char * const debugfs_known_mountpoints[] = {
+ "/sys/kernel/debug",
+ "/debug",
+ 0,
+};
+
+/*
+ * data structures
+ */
+
+static int opt_raw; /* for kernel developers */
+static int opt_list; /* list pages (in ranges) */
+static int opt_no_summary; /* don't show summary */
+static pid_t opt_pid; /* process to walk */
+const char * opt_file;
+
+#define MAX_ADDR_RANGES 1024
+static int nr_addr_ranges;
+static unsigned long opt_offset[MAX_ADDR_RANGES];
+static unsigned long opt_size[MAX_ADDR_RANGES];
+
+#define MAX_VMAS 10240
+static int nr_vmas;
+static unsigned long pg_start[MAX_VMAS];
+static unsigned long pg_end[MAX_VMAS];
+
+#define MAX_BIT_FILTERS 64
+static int nr_bit_filters;
+static uint64_t opt_mask[MAX_BIT_FILTERS];
+static uint64_t opt_bits[MAX_BIT_FILTERS];
+
+static int page_size;
+
+static int pagemap_fd;
+static int kpageflags_fd;
+
+static int opt_hwpoison;
+static int opt_unpoison;
+
+static char *hwpoison_debug_fs;
+static int hwpoison_inject_fd;
+static int hwpoison_forget_fd;
+
+#define HASH_SHIFT 13
+#define HASH_SIZE (1 << HASH_SHIFT)
+#define HASH_MASK (HASH_SIZE - 1)
+#define HASH_KEY(flags) (flags & HASH_MASK)
+
+static unsigned long total_pages;
+static unsigned long nr_pages[HASH_SIZE];
+static uint64_t page_flags[HASH_SIZE];
+
+
+/*
+ * helper functions
+ */
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+
+#define min_t(type, x, y) ({ \
+ type __min1 = (x); \
+ type __min2 = (y); \
+ __min1 < __min2 ? __min1 : __min2; })
+
+#define max_t(type, x, y) ({ \
+ type __max1 = (x); \
+ type __max2 = (y); \
+ __max1 > __max2 ? __max1 : __max2; })
+
+static unsigned long pages2mb(unsigned long pages)
+{
+ return (pages * page_size) >> 20;
+}
+
+static void fatal(const char *x, ...)
+{
+ va_list ap;
+
+ va_start(ap, x);
+ vfprintf(stderr, x, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+static int checked_open(const char *pathname, int flags)
+{
+ int fd = open(pathname, flags);
+
+ if (fd < 0) {
+ perror(pathname);
+ exit(EXIT_FAILURE);
+ }
+
+ return fd;
+}
+
+/*
+ * pagemap/kpageflags routines
+ */
+
+static unsigned long do_u64_read(int fd, char *name,
+ uint64_t *buf,
+ unsigned long index,
+ unsigned long count)
+{
+ long bytes;
+
+ if (index > ULONG_MAX / 8)
+ fatal("index overflow: %lu\n", index);
+
+ bytes = pread(fd, buf, count * 8, (off_t)index * 8);
+ if (bytes < 0) {
+ perror(name);
+ exit(EXIT_FAILURE);
+ }
+ if (bytes % 8)
+ fatal("partial read: %lu bytes\n", bytes);
+
+ return bytes / 8;
+}
+
+static unsigned long kpageflags_read(uint64_t *buf,
+ unsigned long index,
+ unsigned long pages)
+{
+ return do_u64_read(kpageflags_fd, PROC_KPAGEFLAGS, buf, index, pages);
+}
+
+static unsigned long pagemap_read(uint64_t *buf,
+ unsigned long index,
+ unsigned long pages)
+{
+ return do_u64_read(pagemap_fd, "/proc/pid/pagemap", buf, index, pages);
+}
+
+static unsigned long pagemap_pfn(uint64_t val)
+{
+ unsigned long pfn;
+
+ if (val & PM_PRESENT)
+ pfn = PM_PFRAME(val);
+ else
+ pfn = 0;
+
+ return pfn;
+}
+
+
+/*
+ * page flag names
+ */
+
+static char *page_flag_name(uint64_t flags)
+{
+ static char buf[65];
+ int present;
+ size_t i, j;
+
+ for (i = 0, j = 0; i < ARRAY_SIZE(page_flag_names); i++) {
+ present = (flags >> i) & 1;
+ if (!page_flag_names[i]) {
+ if (present)
+ fatal("unknown flag bit %d\n", i);
+ continue;
+ }
+ buf[j++] = present ? page_flag_names[i][0] : '_';
+ }
+
+ return buf;
+}
+
+static char *page_flag_longname(uint64_t flags)
+{
+ static char buf[1024];
+ size_t i, n;
+
+ for (i = 0, n = 0; i < ARRAY_SIZE(page_flag_names); i++) {
+ if (!page_flag_names[i])
+ continue;
+ if ((flags >> i) & 1)
+ n += snprintf(buf + n, sizeof(buf) - n, "%s,",
+ page_flag_names[i] + 2);
+ }
+ if (n)
+ n--;
+ buf[n] = '\0';
+
+ return buf;
+}
+
+
+/*
+ * page list and summary
+ */
+
+static void show_page_range(unsigned long voffset, unsigned long offset,
+ unsigned long size, uint64_t flags)
+{
+ static uint64_t flags0;
+ static unsigned long voff;
+ static unsigned long index;
+ static unsigned long count;
+
+ if (flags == flags0 && offset == index + count &&
+ size && voffset == voff + count) {
+ count += size;
+ return;
+ }
+
+ if (count) {
+ if (opt_pid)
+ printf("%lx\t", voff);
+ if (opt_file)
+ printf("%lu\t", voff);
+ printf("%lx\t%lx\t%s\n",
+ index, count, page_flag_name(flags0));
+ }
+
+ flags0 = flags;
+ index = offset;
+ voff = voffset;
+ count = size;
+}
+
+static void flush_page_range(void)
+{
+ show_page_range(0, 0, 0, 0);
+}
+
+static void show_page(unsigned long voffset,
+ unsigned long offset, uint64_t flags)
+{
+ if (opt_pid)
+ printf("%lx\t", voffset);
+ if (opt_file)
+ printf("%lu\t", voffset);
+ printf("%lx\t%s\n", offset, page_flag_name(flags));
+}
+
+static void show_summary(void)
+{
+ size_t i;
+
+ printf(" flags\tpage-count MB"
+ " symbolic-flags\t\t\tlong-symbolic-flags\n");
+
+ for (i = 0; i < ARRAY_SIZE(nr_pages); i++) {
+ if (nr_pages[i])
+ printf("0x%016llx\t%10lu %8lu %s\t%s\n",
+ (unsigned long long)page_flags[i],
+ nr_pages[i],
+ pages2mb(nr_pages[i]),
+ page_flag_name(page_flags[i]),
+ page_flag_longname(page_flags[i]));
+ }
+
+ printf(" total\t%10lu %8lu\n",
+ total_pages, pages2mb(total_pages));
+}
+
+
+/*
+ * page flag filters
+ */
+
+static int bit_mask_ok(uint64_t flags)
+{
+ int i;
+
+ for (i = 0; i < nr_bit_filters; i++) {
+ if (opt_bits[i] == KPF_ALL_BITS) {
+ if ((flags & opt_mask[i]) == 0)
+ return 0;
+ } else {
+ if ((flags & opt_mask[i]) != opt_bits[i])
+ return 0;
+ }
+ }
+
+ return 1;
+}
+
+static uint64_t expand_overloaded_flags(uint64_t flags, uint64_t pme)
+{
+ /* SLOB/SLUB overload several page flags */
+ if (flags & BIT(SLAB)) {
+ if (flags & BIT(PRIVATE))
+ flags ^= BIT(PRIVATE) | BIT(SLOB_FREE);
+ if (flags & BIT(ACTIVE))
+ flags ^= BIT(ACTIVE) | BIT(SLUB_FROZEN);
+ if (flags & BIT(ERROR))
+ flags ^= BIT(ERROR) | BIT(SLUB_DEBUG);
+ }
+
+ /* PG_reclaim is overloaded as PG_readahead in the read path */
+ if ((flags & (BIT(RECLAIM) | BIT(WRITEBACK))) == BIT(RECLAIM))
+ flags ^= BIT(RECLAIM) | BIT(READAHEAD);
+
+ if (pme & PM_SOFT_DIRTY)
+ flags |= BIT(SOFTDIRTY);
+
+ return flags;
+}
+
+static uint64_t well_known_flags(uint64_t flags)
+{
+ /* hide flags intended only for kernel hacker */
+ flags &= ~KPF_HACKERS_BITS;
+
+ /* hide non-hugeTLB compound pages */
+ if ((flags & BITS_COMPOUND) && !(flags & BIT(HUGE)))
+ flags &= ~BITS_COMPOUND;
+
+ return flags;
+}
+
+static uint64_t kpageflags_flags(uint64_t flags, uint64_t pme)
+{
+ if (opt_raw)
+ flags = expand_overloaded_flags(flags, pme);
+ else
+ flags = well_known_flags(flags);
+
+ return flags;
+}
+
+/*
+ * page actions
+ */
+
+static void prepare_hwpoison_fd(void)
+{
+ char buf[MAX_PATH + 1];
+
+ hwpoison_debug_fs = debugfs_mount(NULL);
+ if (!hwpoison_debug_fs) {
+ perror("mount debugfs");
+ exit(EXIT_FAILURE);
+ }
+
+ if (opt_hwpoison && !hwpoison_inject_fd) {
+ snprintf(buf, MAX_PATH, "%s/hwpoison/corrupt-pfn",
+ hwpoison_debug_fs);
+ hwpoison_inject_fd = checked_open(buf, O_WRONLY);
+ }
+
+ if (opt_unpoison && !hwpoison_forget_fd) {
+ snprintf(buf, MAX_PATH, "%s/hwpoison/unpoison-pfn",
+ hwpoison_debug_fs);
+ hwpoison_forget_fd = checked_open(buf, O_WRONLY);
+ }
+}
+
+static int hwpoison_page(unsigned long offset)
+{
+ char buf[100];
+ int len;
+
+ len = sprintf(buf, "0x%lx\n", offset);
+ len = write(hwpoison_inject_fd, buf, len);
+ if (len < 0) {
+ perror("hwpoison inject");
+ return len;
+ }
+ return 0;
+}
+
+static int unpoison_page(unsigned long offset)
+{
+ char buf[100];
+ int len;
+
+ len = sprintf(buf, "0x%lx\n", offset);
+ len = write(hwpoison_forget_fd, buf, len);
+ if (len < 0) {
+ perror("hwpoison forget");
+ return len;
+ }
+ return 0;
+}
+
+/*
+ * page frame walker
+ */
+
+static size_t hash_slot(uint64_t flags)
+{
+ size_t k = HASH_KEY(flags);
+ size_t i;
+
+ /* Explicitly reserve slot 0 for flags 0: the following logic
+ * cannot distinguish an unoccupied slot from slot (flags==0).
+ */
+ if (flags == 0)
+ return 0;
+
+ /* search through the remaining (HASH_SIZE-1) slots */
+ for (i = 1; i < ARRAY_SIZE(page_flags); i++, k++) {
+ if (!k || k >= ARRAY_SIZE(page_flags))
+ k = 1;
+ if (page_flags[k] == 0) {
+ page_flags[k] = flags;
+ return k;
+ }
+ if (page_flags[k] == flags)
+ return k;
+ }
+
+ fatal("hash table full: bump up HASH_SHIFT?\n");
+ exit(EXIT_FAILURE);
+}
+
+static void add_page(unsigned long voffset,
+ unsigned long offset, uint64_t flags, uint64_t pme)
+{
+ flags = kpageflags_flags(flags, pme);
+
+ if (!bit_mask_ok(flags))
+ return;
+
+ if (opt_hwpoison)
+ hwpoison_page(offset);
+ if (opt_unpoison)
+ unpoison_page(offset);
+
+ if (opt_list == 1)
+ show_page_range(voffset, offset, 1, flags);
+ else if (opt_list == 2)
+ show_page(voffset, offset, flags);
+
+ nr_pages[hash_slot(flags)]++;
+ total_pages++;
+}
+
+#define KPAGEFLAGS_BATCH (64 << 10) /* 64k pages */
+static void walk_pfn(unsigned long voffset,
+ unsigned long index,
+ unsigned long count,
+ uint64_t pme)
+{
+ uint64_t buf[KPAGEFLAGS_BATCH];
+ unsigned long batch;
+ unsigned long pages;
+ unsigned long i;
+
+ while (count) {
+ batch = min_t(unsigned long, count, KPAGEFLAGS_BATCH);
+ pages = kpageflags_read(buf, index, batch);
+ if (pages == 0)
+ break;
+
+ for (i = 0; i < pages; i++)
+ add_page(voffset + i, index + i, buf[i], pme);
+
+ index += pages;
+ count -= pages;
+ }
+}
+
+#define PAGEMAP_BATCH (64 << 10)
+static void walk_vma(unsigned long index, unsigned long count)
+{
+ uint64_t buf[PAGEMAP_BATCH];
+ unsigned long batch;
+ unsigned long pages;
+ unsigned long pfn;
+ unsigned long i;
+
+ while (count) {
+ batch = min_t(unsigned long, count, PAGEMAP_BATCH);
+ pages = pagemap_read(buf, index, batch);
+ if (pages == 0)
+ break;
+
+ for (i = 0; i < pages; i++) {
+ pfn = pagemap_pfn(buf[i]);
+ if (pfn)
+ walk_pfn(index + i, pfn, 1, buf[i]);
+ }
+
+ index += pages;
+ count -= pages;
+ }
+}
+
+static void walk_task(unsigned long index, unsigned long count)
+{
+ const unsigned long end = index + count;
+ unsigned long start;
+ int i = 0;
+
+ while (index < end) {
+
+ while (pg_end[i] <= index)
+ if (++i >= nr_vmas)
+ return;
+ if (pg_start[i] >= end)
+ return;
+
+ start = max_t(unsigned long, pg_start[i], index);
+ index = min_t(unsigned long, pg_end[i], end);
+
+ assert(start < index);
+ walk_vma(start, index - start);
+ }
+}
+
+static void add_addr_range(unsigned long offset, unsigned long size)
+{
+ if (nr_addr_ranges >= MAX_ADDR_RANGES)
+ fatal("too many addr ranges\n");
+
+ opt_offset[nr_addr_ranges] = offset;
+ opt_size[nr_addr_ranges] = min_t(unsigned long, size, ULONG_MAX-offset);
+ nr_addr_ranges++;
+}
+
+static void walk_addr_ranges(void)
+{
+ int i;
+
+ kpageflags_fd = checked_open(PROC_KPAGEFLAGS, O_RDONLY);
+
+ if (!nr_addr_ranges)
+ add_addr_range(0, ULONG_MAX);
+
+ for (i = 0; i < nr_addr_ranges; i++)
+ if (!opt_pid)
+ walk_pfn(opt_offset[i], opt_offset[i], opt_size[i], 0);
+ else
+ walk_task(opt_offset[i], opt_size[i]);
+
+ close(kpageflags_fd);
+}
+
+
+/*
+ * user interface
+ */
+
+static const char *page_flag_type(uint64_t flag)
+{
+ if (flag & KPF_HACKERS_BITS)
+ return "(r)";
+ if (flag & KPF_OVERLOADED_BITS)
+ return "(o)";
+ return " ";
+}
+
+static void usage(void)
+{
+ size_t i, j;
+
+ printf(
+"page-types [options]\n"
+" -r|--raw Raw mode, for kernel developers\n"
+" -d|--describe flags Describe flags\n"
+" -a|--addr addr-spec Walk a range of pages\n"
+" -b|--bits bits-spec Walk pages with specified bits\n"
+" -p|--pid pid Walk process address space\n"
+" -f|--file filename Walk file address space\n"
+" -l|--list Show page details in ranges\n"
+" -L|--list-each Show page details one by one\n"
+" -N|--no-summary Don't show summary info\n"
+" -X|--hwpoison hwpoison pages\n"
+" -x|--unpoison unpoison pages\n"
+" -h|--help Show this usage message\n"
+"flags:\n"
+" 0x10 bitfield format, e.g.\n"
+" anon bit-name, e.g.\n"
+" 0x10,anon comma-separated list, e.g.\n"
+"addr-spec:\n"
+" N one page at offset N (unit: pages)\n"
+" N+M pages range from N to N+M-1\n"
+" N,M pages range from N to M-1\n"
+" N, pages range from N to end\n"
+" ,M pages range from 0 to M-1\n"
+"bits-spec:\n"
+" bit1,bit2 (flags & (bit1|bit2)) != 0\n"
+" bit1,bit2=bit1 (flags & (bit1|bit2)) == bit1\n"
+" bit1,~bit2 (flags & (bit1|bit2)) == bit1\n"
+" =bit1,bit2 flags == (bit1|bit2)\n"
+"bit-names:\n"
+ );
+
+ for (i = 0, j = 0; i < ARRAY_SIZE(page_flag_names); i++) {
+ if (!page_flag_names[i])
+ continue;
+ printf("%16s%s", page_flag_names[i] + 2,
+ page_flag_type(1ULL << i));
+ if (++j > 3) {
+ j = 0;
+ putchar('\n');
+ }
+ }
+ printf("\n "
+ "(r) raw mode bits (o) overloaded bits\n");
+}
+
+static unsigned long long parse_number(const char *str)
+{
+ unsigned long long n;
+
+ n = strtoll(str, NULL, 0);
+
+ if (n == 0 && str[0] != '0')
+ fatal("invalid name or number: %s\n", str);
+
+ return n;
+}
+
+static void parse_pid(const char *str)
+{
+ FILE *file;
+ char buf[5000];
+
+ opt_pid = parse_number(str);
+
+ sprintf(buf, "/proc/%d/pagemap", opt_pid);
+ pagemap_fd = checked_open(buf, O_RDONLY);
+
+ sprintf(buf, "/proc/%d/maps", opt_pid);
+ file = fopen(buf, "r");
+ if (!file) {
+ perror(buf);
+ exit(EXIT_FAILURE);
+ }
+
+ while (fgets(buf, sizeof(buf), file) != NULL) {
+ unsigned long vm_start;
+ unsigned long vm_end;
+ unsigned long long pgoff;
+ int major, minor;
+ char r, w, x, s;
+ unsigned long ino;
+ int n;
+
+ n = sscanf(buf, "%lx-%lx %c%c%c%c %llx %x:%x %lu",
+ &vm_start,
+ &vm_end,
+ &r, &w, &x, &s,
+ &pgoff,
+ &major, &minor,
+ &ino);
+ if (n < 10) {
+ fprintf(stderr, "unexpected line: %s\n", buf);
+ continue;
+ }
+ pg_start[nr_vmas] = vm_start / page_size;
+ pg_end[nr_vmas] = vm_end / page_size;
+ if (++nr_vmas >= MAX_VMAS) {
+ fprintf(stderr, "too many VMAs\n");
+ break;
+ }
+ }
+ fclose(file);
+}
+
+static void show_file(const char *name, const struct stat *st)
+{
+ unsigned long long size = st->st_size;
+ char atime[64], mtime[64];
+ long now = time(NULL);
+
+ printf("%s\tInode: %u\tSize: %llu (%llu pages)\n",
+ name, (unsigned)st->st_ino,
+ size, (size + page_size - 1) / page_size);
+
+ strftime(atime, sizeof(atime), "%c", localtime(&st->st_atime));
+ strftime(mtime, sizeof(mtime), "%c", localtime(&st->st_mtime));
+
+ printf("Modify: %s (%ld seconds ago)\nAccess: %s (%ld seconds ago)\n",
+ mtime, now - st->st_mtime,
+ atime, now - st->st_atime);
+}
+
+static sigjmp_buf sigbus_jmp;
+
+static void * volatile sigbus_addr;
+
+static void sigbus_handler(int sig, siginfo_t *info, void *ucontex)
+{
+ (void)sig;
+ (void)ucontex;
+ sigbus_addr = info ? info->si_addr : NULL;
+ siglongjmp(sigbus_jmp, 1);
+}
+
+static struct sigaction sigbus_action = {
+ .sa_sigaction = sigbus_handler,
+ .sa_flags = SA_SIGINFO,
+};
+
+static void walk_file(const char *name, const struct stat *st)
+{
+ uint8_t vec[PAGEMAP_BATCH];
+ uint64_t buf[PAGEMAP_BATCH], flags;
+ unsigned long nr_pages, pfn, i;
+ off_t off, end = st->st_size;
+ int fd;
+ ssize_t len;
+ void *ptr;
+ int first = 1;
+
+ fd = checked_open(name, O_RDONLY|O_NOATIME|O_NOFOLLOW);
+
+ for (off = 0; off < end; off += len) {
+ nr_pages = (end - off + page_size - 1) / page_size;
+ if (nr_pages > PAGEMAP_BATCH)
+ nr_pages = PAGEMAP_BATCH;
+ len = nr_pages * page_size;
+
+ ptr = mmap(NULL, len, PROT_READ, MAP_SHARED, fd, off);
+ if (ptr == MAP_FAILED)
+ fatal("mmap failed: %s", name);
+
+ /* determine cached pages */
+ if (mincore(ptr, len, vec))
+ fatal("mincore failed: %s", name);
+
+ /* turn off readahead */
+ if (madvise(ptr, len, MADV_RANDOM))
+ fatal("madvice failed: %s", name);
+
+ if (sigsetjmp(sigbus_jmp, 1)) {
+ end = off + sigbus_addr ? sigbus_addr - ptr : 0;
+ fprintf(stderr, "got sigbus at offset %lld: %s\n",
+ (long long)end, name);
+ goto got_sigbus;
+ }
+
+ /* populate ptes */
+ for (i = 0; i < nr_pages ; i++) {
+ if (vec[i] & 1)
+ (void)*(volatile int *)(ptr + i * page_size);
+ }
+got_sigbus:
+
+ /* turn off harvesting reference bits */
+ if (madvise(ptr, len, MADV_SEQUENTIAL))
+ fatal("madvice failed: %s", name);
+
+ if (pagemap_read(buf, (unsigned long)ptr / page_size,
+ nr_pages) != nr_pages)
+ fatal("cannot read pagemap");
+
+ munmap(ptr, len);
+
+ for (i = 0; i < nr_pages; i++) {
+ pfn = pagemap_pfn(buf[i]);
+ if (!pfn)
+ continue;
+ if (!kpageflags_read(&flags, pfn, 1))
+ continue;
+ if (first && opt_list) {
+ first = 0;
+ flush_page_range();
+ show_file(name, st);
+ }
+ add_page(off / page_size + i, pfn, flags, buf[i]);
+ }
+ }
+
+ close(fd);
+}
+
+int walk_tree(const char *name, const struct stat *st, int type, struct FTW *f)
+{
+ (void)f;
+ switch (type) {
+ case FTW_F:
+ if (S_ISREG(st->st_mode))
+ walk_file(name, st);
+ break;
+ case FTW_DNR:
+ fprintf(stderr, "cannot read dir: %s\n", name);
+ break;
+ }
+ return 0;
+}
+
+static void walk_page_cache(void)
+{
+ struct stat st;
+
+ kpageflags_fd = checked_open(PROC_KPAGEFLAGS, O_RDONLY);
+ pagemap_fd = checked_open("/proc/self/pagemap", O_RDONLY);
+ sigaction(SIGBUS, &sigbus_action, NULL);
+
+ if (stat(opt_file, &st))
+ fatal("stat failed: %s\n", opt_file);
+
+ if (S_ISREG(st.st_mode)) {
+ walk_file(opt_file, &st);
+ } else if (S_ISDIR(st.st_mode)) {
+ /* do not follow symlinks and mountpoints */
+ if (nftw(opt_file, walk_tree, 64, FTW_MOUNT | FTW_PHYS) < 0)
+ fatal("nftw failed: %s\n", opt_file);
+ } else
+ fatal("unhandled file type: %s\n", opt_file);
+
+ close(kpageflags_fd);
+ close(pagemap_fd);
+ signal(SIGBUS, SIG_DFL);
+}
+
+static void parse_file(const char *name)
+{
+ opt_file = name;
+}
+
+static void parse_addr_range(const char *optarg)
+{
+ unsigned long offset;
+ unsigned long size;
+ char *p;
+
+ p = strchr(optarg, ',');
+ if (!p)
+ p = strchr(optarg, '+');
+
+ if (p == optarg) {
+ offset = 0;
+ size = parse_number(p + 1);
+ } else if (p) {
+ offset = parse_number(optarg);
+ if (p[1] == '\0')
+ size = ULONG_MAX;
+ else {
+ size = parse_number(p + 1);
+ if (*p == ',') {
+ if (size < offset)
+ fatal("invalid range: %lu,%lu\n",
+ offset, size);
+ size -= offset;
+ }
+ }
+ } else {
+ offset = parse_number(optarg);
+ size = 1;
+ }
+
+ add_addr_range(offset, size);
+}
+
+static void add_bits_filter(uint64_t mask, uint64_t bits)
+{
+ if (nr_bit_filters >= MAX_BIT_FILTERS)
+ fatal("too much bit filters\n");
+
+ opt_mask[nr_bit_filters] = mask;
+ opt_bits[nr_bit_filters] = bits;
+ nr_bit_filters++;
+}
+
+static uint64_t parse_flag_name(const char *str, int len)
+{
+ size_t i;
+
+ if (!*str || !len)
+ return 0;
+
+ if (len <= 8 && !strncmp(str, "compound", len))
+ return BITS_COMPOUND;
+
+ for (i = 0; i < ARRAY_SIZE(page_flag_names); i++) {
+ if (!page_flag_names[i])
+ continue;
+ if (!strncmp(str, page_flag_names[i] + 2, len))
+ return 1ULL << i;
+ }
+
+ return parse_number(str);
+}
+
+static uint64_t parse_flag_names(const char *str, int all)
+{
+ const char *p = str;
+ uint64_t flags = 0;
+
+ while (1) {
+ if (*p == ',' || *p == '=' || *p == '\0') {
+ if ((*str != '~') || (*str == '~' && all && *++str))
+ flags |= parse_flag_name(str, p - str);
+ if (*p != ',')
+ break;
+ str = p + 1;
+ }
+ p++;
+ }
+
+ return flags;
+}
+
+static void parse_bits_mask(const char *optarg)
+{
+ uint64_t mask;
+ uint64_t bits;
+ const char *p;
+
+ p = strchr(optarg, '=');
+ if (p == optarg) {
+ mask = KPF_ALL_BITS;
+ bits = parse_flag_names(p + 1, 0);
+ } else if (p) {
+ mask = parse_flag_names(optarg, 0);
+ bits = parse_flag_names(p + 1, 0);
+ } else if (strchr(optarg, '~')) {
+ mask = parse_flag_names(optarg, 1);
+ bits = parse_flag_names(optarg, 0);
+ } else {
+ mask = parse_flag_names(optarg, 0);
+ bits = KPF_ALL_BITS;
+ }
+
+ add_bits_filter(mask, bits);
+}
+
+static void describe_flags(const char *optarg)
+{
+ uint64_t flags = parse_flag_names(optarg, 0);
+
+ printf("0x%016llx\t%s\t%s\n",
+ (unsigned long long)flags,
+ page_flag_name(flags),
+ page_flag_longname(flags));
+}
+
+static const struct option opts[] = {
+ { "raw" , 0, NULL, 'r' },
+ { "pid" , 1, NULL, 'p' },
+ { "file" , 1, NULL, 'f' },
+ { "addr" , 1, NULL, 'a' },
+ { "bits" , 1, NULL, 'b' },
+ { "describe" , 1, NULL, 'd' },
+ { "list" , 0, NULL, 'l' },
+ { "list-each" , 0, NULL, 'L' },
+ { "no-summary", 0, NULL, 'N' },
+ { "hwpoison" , 0, NULL, 'X' },
+ { "unpoison" , 0, NULL, 'x' },
+ { "help" , 0, NULL, 'h' },
+ { NULL , 0, NULL, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+ int c;
+
+ page_size = getpagesize();
+
+ while ((c = getopt_long(argc, argv,
+ "rp:f:a:b:d:lLNXxh", opts, NULL)) != -1) {
+ switch (c) {
+ case 'r':
+ opt_raw = 1;
+ break;
+ case 'p':
+ parse_pid(optarg);
+ break;
+ case 'f':
+ parse_file(optarg);
+ break;
+ case 'a':
+ parse_addr_range(optarg);
+ break;
+ case 'b':
+ parse_bits_mask(optarg);
+ break;
+ case 'd':
+ describe_flags(optarg);
+ exit(0);
+ case 'l':
+ opt_list = 1;
+ break;
+ case 'L':
+ opt_list = 2;
+ break;
+ case 'N':
+ opt_no_summary = 1;
+ break;
+ case 'X':
+ opt_hwpoison = 1;
+ prepare_hwpoison_fd();
+ break;
+ case 'x':
+ opt_unpoison = 1;
+ prepare_hwpoison_fd();
+ break;
+ case 'h':
+ usage();
+ exit(0);
+ default:
+ usage();
+ exit(1);
+ }
+ }
+
+ if (opt_list && opt_pid)
+ printf("voffset\t");
+ if (opt_list && opt_file)
+ printf("foffset\t");
+ if (opt_list == 1)
+ printf("offset\tlen\tflags\n");
+ if (opt_list == 2)
+ printf("offset\tflags\n");
+
+ if (opt_file)
+ walk_page_cache();
+ else
+ walk_addr_ranges();
+
+ if (opt_list == 1)
+ flush_page_range();
+
+ if (opt_no_summary)
+ return 0;
+
+ if (opt_list)
+ printf("\n\n");
+
+ show_summary();
+
+ return 0;
+}
diff --git a/tools/vm/slabinfo.c b/tools/vm/slabinfo.c
new file mode 100644
index 00000000000..808d5a9d5dc
--- /dev/null
+++ b/tools/vm/slabinfo.c
@@ -0,0 +1,1393 @@
+/*
+ * Slabinfo: Tool to get reports about slabs
+ *
+ * (C) 2007 sgi, Christoph Lameter
+ * (C) 2011 Linux Foundation, Christoph Lameter
+ *
+ * Compile with:
+ *
+ * gcc -o slabinfo slabinfo.c
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <dirent.h>
+#include <strings.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdarg.h>
+#include <getopt.h>
+#include <regex.h>
+#include <errno.h>
+
+#define MAX_SLABS 500
+#define MAX_ALIASES 500
+#define MAX_NODES 1024
+
+struct slabinfo {
+ char *name;
+ int alias;
+ int refs;
+ int aliases, align, cache_dma, cpu_slabs, destroy_by_rcu;
+ int hwcache_align, object_size, objs_per_slab;
+ int sanity_checks, slab_size, store_user, trace;
+ int order, poison, reclaim_account, red_zone;
+ unsigned long partial, objects, slabs, objects_partial, objects_total;
+ unsigned long alloc_fastpath, alloc_slowpath;
+ unsigned long free_fastpath, free_slowpath;
+ unsigned long free_frozen, free_add_partial, free_remove_partial;
+ unsigned long alloc_from_partial, alloc_slab, free_slab, alloc_refill;
+ unsigned long cpuslab_flush, deactivate_full, deactivate_empty;
+ unsigned long deactivate_to_head, deactivate_to_tail;
+ unsigned long deactivate_remote_frees, order_fallback;
+ unsigned long cmpxchg_double_cpu_fail, cmpxchg_double_fail;
+ unsigned long alloc_node_mismatch, deactivate_bypass;
+ unsigned long cpu_partial_alloc, cpu_partial_free;
+ int numa[MAX_NODES];
+ int numa_partial[MAX_NODES];
+} slabinfo[MAX_SLABS];
+
+struct aliasinfo {
+ char *name;
+ char *ref;
+ struct slabinfo *slab;
+} aliasinfo[MAX_ALIASES];
+
+int slabs = 0;
+int actual_slabs = 0;
+int aliases = 0;
+int alias_targets = 0;
+int highest_node = 0;
+
+char buffer[4096];
+
+int show_empty = 0;
+int show_report = 0;
+int show_alias = 0;
+int show_slab = 0;
+int skip_zero = 1;
+int show_numa = 0;
+int show_track = 0;
+int show_first_alias = 0;
+int validate = 0;
+int shrink = 0;
+int show_inverted = 0;
+int show_single_ref = 0;
+int show_totals = 0;
+int sort_size = 0;
+int sort_active = 0;
+int set_debug = 0;
+int show_ops = 0;
+int show_activity = 0;
+
+/* Debug options */
+int sanity = 0;
+int redzone = 0;
+int poison = 0;
+int tracking = 0;
+int tracing = 0;
+
+int page_size;
+
+regex_t pattern;
+
+static void fatal(const char *x, ...)
+{
+ va_list ap;
+
+ va_start(ap, x);
+ vfprintf(stderr, x, ap);
+ va_end(ap);
+ exit(EXIT_FAILURE);
+}
+
+static void usage(void)
+{
+ printf("slabinfo 4/15/2011. (c) 2007 sgi/(c) 2011 Linux Foundation.\n\n"
+ "slabinfo [-ahnpvtsz] [-d debugopts] [slab-regexp]\n"
+ "-a|--aliases Show aliases\n"
+ "-A|--activity Most active slabs first\n"
+ "-d<options>|--debug=<options> Set/Clear Debug options\n"
+ "-D|--display-active Switch line format to activity\n"
+ "-e|--empty Show empty slabs\n"
+ "-f|--first-alias Show first alias\n"
+ "-h|--help Show usage information\n"
+ "-i|--inverted Inverted list\n"
+ "-l|--slabs Show slabs\n"
+ "-n|--numa Show NUMA information\n"
+ "-o|--ops Show kmem_cache_ops\n"
+ "-s|--shrink Shrink slabs\n"
+ "-r|--report Detailed report on single slabs\n"
+ "-S|--Size Sort by size\n"
+ "-t|--tracking Show alloc/free information\n"
+ "-T|--Totals Show summary information\n"
+ "-v|--validate Validate slabs\n"
+ "-z|--zero Include empty slabs\n"
+ "-1|--1ref Single reference\n"
+ "\nValid debug options (FZPUT may be combined)\n"
+ "a / A Switch on all debug options (=FZUP)\n"
+ "- Switch off all debug options\n"
+ "f / F Sanity Checks (SLAB_DEBUG_FREE)\n"
+ "z / Z Redzoning\n"
+ "p / P Poisoning\n"
+ "u / U Tracking\n"
+ "t / T Tracing\n"
+ );
+}
+
+static unsigned long read_obj(const char *name)
+{
+ FILE *f = fopen(name, "r");
+
+ if (!f)
+ buffer[0] = 0;
+ else {
+ if (!fgets(buffer, sizeof(buffer), f))
+ buffer[0] = 0;
+ fclose(f);
+ if (buffer[strlen(buffer)] == '\n')
+ buffer[strlen(buffer)] = 0;
+ }
+ return strlen(buffer);
+}
+
+
+/*
+ * Get the contents of an attribute
+ */
+static unsigned long get_obj(const char *name)
+{
+ if (!read_obj(name))
+ return 0;
+
+ return atol(buffer);
+}
+
+static unsigned long get_obj_and_str(const char *name, char **x)
+{
+ unsigned long result = 0;
+ char *p;
+
+ *x = NULL;
+
+ if (!read_obj(name)) {
+ x = NULL;
+ return 0;
+ }
+ result = strtoul(buffer, &p, 10);
+ while (*p == ' ')
+ p++;
+ if (*p)
+ *x = strdup(p);
+ return result;
+}
+
+static void set_obj(struct slabinfo *s, const char *name, int n)
+{
+ char x[100];
+ FILE *f;
+
+ snprintf(x, 100, "%s/%s", s->name, name);
+ f = fopen(x, "w");
+ if (!f)
+ fatal("Cannot write to %s\n", x);
+
+ fprintf(f, "%d\n", n);
+ fclose(f);
+}
+
+static unsigned long read_slab_obj(struct slabinfo *s, const char *name)
+{
+ char x[100];
+ FILE *f;
+ size_t l;
+
+ snprintf(x, 100, "%s/%s", s->name, name);
+ f = fopen(x, "r");
+ if (!f) {
+ buffer[0] = 0;
+ l = 0;
+ } else {
+ l = fread(buffer, 1, sizeof(buffer), f);
+ buffer[l] = 0;
+ fclose(f);
+ }
+ return l;
+}
+
+
+/*
+ * Put a size string together
+ */
+static int store_size(char *buffer, unsigned long value)
+{
+ unsigned long divisor = 1;
+ char trailer = 0;
+ int n;
+
+ if (value > 1000000000UL) {
+ divisor = 100000000UL;
+ trailer = 'G';
+ } else if (value > 1000000UL) {
+ divisor = 100000UL;
+ trailer = 'M';
+ } else if (value > 1000UL) {
+ divisor = 100;
+ trailer = 'K';
+ }
+
+ value /= divisor;
+ n = sprintf(buffer, "%ld",value);
+ if (trailer) {
+ buffer[n] = trailer;
+ n++;
+ buffer[n] = 0;
+ }
+ if (divisor != 1) {
+ memmove(buffer + n - 2, buffer + n - 3, 4);
+ buffer[n-2] = '.';
+ n++;
+ }
+ return n;
+}
+
+static void decode_numa_list(int *numa, char *t)
+{
+ int node;
+ int nr;
+
+ memset(numa, 0, MAX_NODES * sizeof(int));
+
+ if (!t)
+ return;
+
+ while (*t == 'N') {
+ t++;
+ node = strtoul(t, &t, 10);
+ if (*t == '=') {
+ t++;
+ nr = strtoul(t, &t, 10);
+ numa[node] = nr;
+ if (node > highest_node)
+ highest_node = node;
+ }
+ while (*t == ' ')
+ t++;
+ }
+}
+
+static void slab_validate(struct slabinfo *s)
+{
+ if (strcmp(s->name, "*") == 0)
+ return;
+
+ set_obj(s, "validate", 1);
+}
+
+static void slab_shrink(struct slabinfo *s)
+{
+ if (strcmp(s->name, "*") == 0)
+ return;
+
+ set_obj(s, "shrink", 1);
+}
+
+int line = 0;
+
+static void first_line(void)
+{
+ if (show_activity)
+ printf("Name Objects Alloc Free %%Fast Fallb O CmpX UL\n");
+ else
+ printf("Name Objects Objsize Space "
+ "Slabs/Part/Cpu O/S O %%Fr %%Ef Flg\n");
+}
+
+/*
+ * Find the shortest alias of a slab
+ */
+static struct aliasinfo *find_one_alias(struct slabinfo *find)
+{
+ struct aliasinfo *a;
+ struct aliasinfo *best = NULL;
+
+ for(a = aliasinfo;a < aliasinfo + aliases; a++) {
+ if (a->slab == find &&
+ (!best || strlen(best->name) < strlen(a->name))) {
+ best = a;
+ if (strncmp(a->name,"kmall", 5) == 0)
+ return best;
+ }
+ }
+ return best;
+}
+
+static unsigned long slab_size(struct slabinfo *s)
+{
+ return s->slabs * (page_size << s->order);
+}
+
+static unsigned long slab_activity(struct slabinfo *s)
+{
+ return s->alloc_fastpath + s->free_fastpath +
+ s->alloc_slowpath + s->free_slowpath;
+}
+
+static void slab_numa(struct slabinfo *s, int mode)
+{
+ int node;
+
+ if (strcmp(s->name, "*") == 0)
+ return;
+
+ if (!highest_node) {
+ printf("\n%s: No NUMA information available.\n", s->name);
+ return;
+ }
+
+ if (skip_zero && !s->slabs)
+ return;
+
+ if (!line) {
+ printf("\n%-21s:", mode ? "NUMA nodes" : "Slab");
+ for(node = 0; node <= highest_node; node++)
+ printf(" %4d", node);
+ printf("\n----------------------");
+ for(node = 0; node <= highest_node; node++)
+ printf("-----");
+ printf("\n");
+ }
+ printf("%-21s ", mode ? "All slabs" : s->name);
+ for(node = 0; node <= highest_node; node++) {
+ char b[20];
+
+ store_size(b, s->numa[node]);
+ printf(" %4s", b);
+ }
+ printf("\n");
+ if (mode) {
+ printf("%-21s ", "Partial slabs");
+ for(node = 0; node <= highest_node; node++) {
+ char b[20];
+
+ store_size(b, s->numa_partial[node]);
+ printf(" %4s", b);
+ }
+ printf("\n");
+ }
+ line++;
+}
+
+static void show_tracking(struct slabinfo *s)
+{
+ printf("\n%s: Kernel object allocation\n", s->name);
+ printf("-----------------------------------------------------------------------\n");
+ if (read_slab_obj(s, "alloc_calls"))
+ printf("%s", buffer);
+ else
+ printf("No Data\n");
+
+ printf("\n%s: Kernel object freeing\n", s->name);
+ printf("------------------------------------------------------------------------\n");
+ if (read_slab_obj(s, "free_calls"))
+ printf("%s", buffer);
+ else
+ printf("No Data\n");
+
+}
+
+static void ops(struct slabinfo *s)
+{
+ if (strcmp(s->name, "*") == 0)
+ return;
+
+ if (read_slab_obj(s, "ops")) {
+ printf("\n%s: kmem_cache operations\n", s->name);
+ printf("--------------------------------------------\n");
+ printf("%s", buffer);
+ } else
+ printf("\n%s has no kmem_cache operations\n", s->name);
+}
+
+static const char *onoff(int x)
+{
+ if (x)
+ return "On ";
+ return "Off";
+}
+
+static void slab_stats(struct slabinfo *s)
+{
+ unsigned long total_alloc;
+ unsigned long total_free;
+ unsigned long total;
+
+ if (!s->alloc_slab)
+ return;
+
+ total_alloc = s->alloc_fastpath + s->alloc_slowpath;
+ total_free = s->free_fastpath + s->free_slowpath;
+
+ if (!total_alloc)
+ return;
+
+ printf("\n");
+ printf("Slab Perf Counter Alloc Free %%Al %%Fr\n");
+ printf("--------------------------------------------------\n");
+ printf("Fastpath %8lu %8lu %3lu %3lu\n",
+ s->alloc_fastpath, s->free_fastpath,
+ s->alloc_fastpath * 100 / total_alloc,
+ total_free ? s->free_fastpath * 100 / total_free : 0);
+ printf("Slowpath %8lu %8lu %3lu %3lu\n",
+ total_alloc - s->alloc_fastpath, s->free_slowpath,
+ (total_alloc - s->alloc_fastpath) * 100 / total_alloc,
+ total_free ? s->free_slowpath * 100 / total_free : 0);
+ printf("Page Alloc %8lu %8lu %3lu %3lu\n",
+ s->alloc_slab, s->free_slab,
+ s->alloc_slab * 100 / total_alloc,
+ total_free ? s->free_slab * 100 / total_free : 0);
+ printf("Add partial %8lu %8lu %3lu %3lu\n",
+ s->deactivate_to_head + s->deactivate_to_tail,
+ s->free_add_partial,
+ (s->deactivate_to_head + s->deactivate_to_tail) * 100 / total_alloc,
+ total_free ? s->free_add_partial * 100 / total_free : 0);
+ printf("Remove partial %8lu %8lu %3lu %3lu\n",
+ s->alloc_from_partial, s->free_remove_partial,
+ s->alloc_from_partial * 100 / total_alloc,
+ total_free ? s->free_remove_partial * 100 / total_free : 0);
+
+ printf("Cpu partial list %8lu %8lu %3lu %3lu\n",
+ s->cpu_partial_alloc, s->cpu_partial_free,
+ s->cpu_partial_alloc * 100 / total_alloc,
+ total_free ? s->cpu_partial_free * 100 / total_free : 0);
+
+ printf("RemoteObj/SlabFrozen %8lu %8lu %3lu %3lu\n",
+ s->deactivate_remote_frees, s->free_frozen,
+ s->deactivate_remote_frees * 100 / total_alloc,
+ total_free ? s->free_frozen * 100 / total_free : 0);
+
+ printf("Total %8lu %8lu\n\n", total_alloc, total_free);
+
+ if (s->cpuslab_flush)
+ printf("Flushes %8lu\n", s->cpuslab_flush);
+
+ total = s->deactivate_full + s->deactivate_empty +
+ s->deactivate_to_head + s->deactivate_to_tail + s->deactivate_bypass;
+
+ if (total) {
+ printf("\nSlab Deactivation Ocurrences %%\n");
+ printf("-------------------------------------------------\n");
+ printf("Slab full %7lu %3lu%%\n",
+ s->deactivate_full, (s->deactivate_full * 100) / total);
+ printf("Slab empty %7lu %3lu%%\n",
+ s->deactivate_empty, (s->deactivate_empty * 100) / total);
+ printf("Moved to head of partial list %7lu %3lu%%\n",
+ s->deactivate_to_head, (s->deactivate_to_head * 100) / total);
+ printf("Moved to tail of partial list %7lu %3lu%%\n",
+ s->deactivate_to_tail, (s->deactivate_to_tail * 100) / total);
+ printf("Deactivation bypass %7lu %3lu%%\n",
+ s->deactivate_bypass, (s->deactivate_bypass * 100) / total);
+ printf("Refilled from foreign frees %7lu %3lu%%\n",
+ s->alloc_refill, (s->alloc_refill * 100) / total);
+ printf("Node mismatch %7lu %3lu%%\n",
+ s->alloc_node_mismatch, (s->alloc_node_mismatch * 100) / total);
+ }
+
+ if (s->cmpxchg_double_fail || s->cmpxchg_double_cpu_fail)
+ printf("\nCmpxchg_double Looping\n------------------------\n");
+ printf("Locked Cmpxchg Double redos %lu\nUnlocked Cmpxchg Double redos %lu\n",
+ s->cmpxchg_double_fail, s->cmpxchg_double_cpu_fail);
+}
+
+static void report(struct slabinfo *s)
+{
+ if (strcmp(s->name, "*") == 0)
+ return;
+
+ printf("\nSlabcache: %-20s Aliases: %2d Order : %2d Objects: %lu\n",
+ s->name, s->aliases, s->order, s->objects);
+ if (s->hwcache_align)
+ printf("** Hardware cacheline aligned\n");
+ if (s->cache_dma)
+ printf("** Memory is allocated in a special DMA zone\n");
+ if (s->destroy_by_rcu)
+ printf("** Slabs are destroyed via RCU\n");
+ if (s->reclaim_account)
+ printf("** Reclaim accounting active\n");
+
+ printf("\nSizes (bytes) Slabs Debug Memory\n");
+ printf("------------------------------------------------------------------------\n");
+ printf("Object : %7d Total : %7ld Sanity Checks : %s Total: %7ld\n",
+ s->object_size, s->slabs, onoff(s->sanity_checks),
+ s->slabs * (page_size << s->order));
+ printf("SlabObj: %7d Full : %7ld Redzoning : %s Used : %7ld\n",
+ s->slab_size, s->slabs - s->partial - s->cpu_slabs,
+ onoff(s->red_zone), s->objects * s->object_size);
+ printf("SlabSiz: %7d Partial: %7ld Poisoning : %s Loss : %7ld\n",
+ page_size << s->order, s->partial, onoff(s->poison),
+ s->slabs * (page_size << s->order) - s->objects * s->object_size);
+ printf("Loss : %7d CpuSlab: %7d Tracking : %s Lalig: %7ld\n",
+ s->slab_size - s->object_size, s->cpu_slabs, onoff(s->store_user),
+ (s->slab_size - s->object_size) * s->objects);
+ printf("Align : %7d Objects: %7d Tracing : %s Lpadd: %7ld\n",
+ s->align, s->objs_per_slab, onoff(s->trace),
+ ((page_size << s->order) - s->objs_per_slab * s->slab_size) *
+ s->slabs);
+
+ ops(s);
+ show_tracking(s);
+ slab_numa(s, 1);
+ slab_stats(s);
+}
+
+static void slabcache(struct slabinfo *s)
+{
+ char size_str[20];
+ char dist_str[40];
+ char flags[20];
+ char *p = flags;
+
+ if (strcmp(s->name, "*") == 0)
+ return;
+
+ if (actual_slabs == 1) {
+ report(s);
+ return;
+ }
+
+ if (skip_zero && !show_empty && !s->slabs)
+ return;
+
+ if (show_empty && s->slabs)
+ return;
+
+ store_size(size_str, slab_size(s));
+ snprintf(dist_str, 40, "%lu/%lu/%d", s->slabs - s->cpu_slabs,
+ s->partial, s->cpu_slabs);
+
+ if (!line++)
+ first_line();
+
+ if (s->aliases)
+ *p++ = '*';
+ if (s->cache_dma)
+ *p++ = 'd';
+ if (s->hwcache_align)
+ *p++ = 'A';
+ if (s->poison)
+ *p++ = 'P';
+ if (s->reclaim_account)
+ *p++ = 'a';
+ if (s->red_zone)
+ *p++ = 'Z';
+ if (s->sanity_checks)
+ *p++ = 'F';
+ if (s->store_user)
+ *p++ = 'U';
+ if (s->trace)
+ *p++ = 'T';
+
+ *p = 0;
+ if (show_activity) {
+ unsigned long total_alloc;
+ unsigned long total_free;
+
+ total_alloc = s->alloc_fastpath + s->alloc_slowpath;
+ total_free = s->free_fastpath + s->free_slowpath;
+
+ printf("%-21s %8ld %10ld %10ld %3ld %3ld %5ld %1d %4ld %4ld\n",
+ s->name, s->objects,
+ total_alloc, total_free,
+ total_alloc ? (s->alloc_fastpath * 100 / total_alloc) : 0,
+ total_free ? (s->free_fastpath * 100 / total_free) : 0,
+ s->order_fallback, s->order, s->cmpxchg_double_fail,
+ s->cmpxchg_double_cpu_fail);
+ }
+ else
+ printf("%-21s %8ld %7d %8s %14s %4d %1d %3ld %3ld %s\n",
+ s->name, s->objects, s->object_size, size_str, dist_str,
+ s->objs_per_slab, s->order,
+ s->slabs ? (s->partial * 100) / s->slabs : 100,
+ s->slabs ? (s->objects * s->object_size * 100) /
+ (s->slabs * (page_size << s->order)) : 100,
+ flags);
+}
+
+/*
+ * Analyze debug options. Return false if something is amiss.
+ */
+static int debug_opt_scan(char *opt)
+{
+ if (!opt || !opt[0] || strcmp(opt, "-") == 0)
+ return 1;
+
+ if (strcasecmp(opt, "a") == 0) {
+ sanity = 1;
+ poison = 1;
+ redzone = 1;
+ tracking = 1;
+ return 1;
+ }
+
+ for ( ; *opt; opt++)
+ switch (*opt) {
+ case 'F' : case 'f':
+ if (sanity)
+ return 0;
+ sanity = 1;
+ break;
+ case 'P' : case 'p':
+ if (poison)
+ return 0;
+ poison = 1;
+ break;
+
+ case 'Z' : case 'z':
+ if (redzone)
+ return 0;
+ redzone = 1;
+ break;
+
+ case 'U' : case 'u':
+ if (tracking)
+ return 0;
+ tracking = 1;
+ break;
+
+ case 'T' : case 't':
+ if (tracing)
+ return 0;
+ tracing = 1;
+ break;
+ default:
+ return 0;
+ }
+ return 1;
+}
+
+static int slab_empty(struct slabinfo *s)
+{
+ if (s->objects > 0)
+ return 0;
+
+ /*
+ * We may still have slabs even if there are no objects. Shrinking will
+ * remove them.
+ */
+ if (s->slabs != 0)
+ set_obj(s, "shrink", 1);
+
+ return 1;
+}
+
+static void slab_debug(struct slabinfo *s)
+{
+ if (strcmp(s->name, "*") == 0)
+ return;
+
+ if (sanity && !s->sanity_checks) {
+ set_obj(s, "sanity", 1);
+ }
+ if (!sanity && s->sanity_checks) {
+ if (slab_empty(s))
+ set_obj(s, "sanity", 0);
+ else
+ fprintf(stderr, "%s not empty cannot disable sanity checks\n", s->name);
+ }
+ if (redzone && !s->red_zone) {
+ if (slab_empty(s))
+ set_obj(s, "red_zone", 1);
+ else
+ fprintf(stderr, "%s not empty cannot enable redzoning\n", s->name);
+ }
+ if (!redzone && s->red_zone) {
+ if (slab_empty(s))
+ set_obj(s, "red_zone", 0);
+ else
+ fprintf(stderr, "%s not empty cannot disable redzoning\n", s->name);
+ }
+ if (poison && !s->poison) {
+ if (slab_empty(s))
+ set_obj(s, "poison", 1);
+ else
+ fprintf(stderr, "%s not empty cannot enable poisoning\n", s->name);
+ }
+ if (!poison && s->poison) {
+ if (slab_empty(s))
+ set_obj(s, "poison", 0);
+ else
+ fprintf(stderr, "%s not empty cannot disable poisoning\n", s->name);
+ }
+ if (tracking && !s->store_user) {
+ if (slab_empty(s))
+ set_obj(s, "store_user", 1);
+ else
+ fprintf(stderr, "%s not empty cannot enable tracking\n", s->name);
+ }
+ if (!tracking && s->store_user) {
+ if (slab_empty(s))
+ set_obj(s, "store_user", 0);
+ else
+ fprintf(stderr, "%s not empty cannot disable tracking\n", s->name);
+ }
+ if (tracing && !s->trace) {
+ if (slabs == 1)
+ set_obj(s, "trace", 1);
+ else
+ fprintf(stderr, "%s can only enable trace for one slab at a time\n", s->name);
+ }
+ if (!tracing && s->trace)
+ set_obj(s, "trace", 1);
+}
+
+static void totals(void)
+{
+ struct slabinfo *s;
+
+ int used_slabs = 0;
+ char b1[20], b2[20], b3[20], b4[20];
+ unsigned long long max = 1ULL << 63;
+
+ /* Object size */
+ unsigned long long min_objsize = max, max_objsize = 0, avg_objsize;
+
+ /* Number of partial slabs in a slabcache */
+ unsigned long long min_partial = max, max_partial = 0,
+ avg_partial, total_partial = 0;
+
+ /* Number of slabs in a slab cache */
+ unsigned long long min_slabs = max, max_slabs = 0,
+ avg_slabs, total_slabs = 0;
+
+ /* Size of the whole slab */
+ unsigned long long min_size = max, max_size = 0,
+ avg_size, total_size = 0;
+
+ /* Bytes used for object storage in a slab */
+ unsigned long long min_used = max, max_used = 0,
+ avg_used, total_used = 0;
+
+ /* Waste: Bytes used for alignment and padding */
+ unsigned long long min_waste = max, max_waste = 0,
+ avg_waste, total_waste = 0;
+ /* Number of objects in a slab */
+ unsigned long long min_objects = max, max_objects = 0,
+ avg_objects, total_objects = 0;
+ /* Waste per object */
+ unsigned long long min_objwaste = max,
+ max_objwaste = 0, avg_objwaste,
+ total_objwaste = 0;
+
+ /* Memory per object */
+ unsigned long long min_memobj = max,
+ max_memobj = 0, avg_memobj,
+ total_objsize = 0;
+
+ /* Percentage of partial slabs per slab */
+ unsigned long min_ppart = 100, max_ppart = 0,
+ avg_ppart, total_ppart = 0;
+
+ /* Number of objects in partial slabs */
+ unsigned long min_partobj = max, max_partobj = 0,
+ avg_partobj, total_partobj = 0;
+
+ /* Percentage of partial objects of all objects in a slab */
+ unsigned long min_ppartobj = 100, max_ppartobj = 0,
+ avg_ppartobj, total_ppartobj = 0;
+
+
+ for (s = slabinfo; s < slabinfo + slabs; s++) {
+ unsigned long long size;
+ unsigned long used;
+ unsigned long long wasted;
+ unsigned long long objwaste;
+ unsigned long percentage_partial_slabs;
+ unsigned long percentage_partial_objs;
+
+ if (!s->slabs || !s->objects)
+ continue;
+
+ used_slabs++;
+
+ size = slab_size(s);
+ used = s->objects * s->object_size;
+ wasted = size - used;
+ objwaste = s->slab_size - s->object_size;
+
+ percentage_partial_slabs = s->partial * 100 / s->slabs;
+ if (percentage_partial_slabs > 100)
+ percentage_partial_slabs = 100;
+
+ percentage_partial_objs = s->objects_partial * 100
+ / s->objects;
+
+ if (percentage_partial_objs > 100)
+ percentage_partial_objs = 100;
+
+ if (s->object_size < min_objsize)
+ min_objsize = s->object_size;
+ if (s->partial < min_partial)
+ min_partial = s->partial;
+ if (s->slabs < min_slabs)
+ min_slabs = s->slabs;
+ if (size < min_size)
+ min_size = size;
+ if (wasted < min_waste)
+ min_waste = wasted;
+ if (objwaste < min_objwaste)
+ min_objwaste = objwaste;
+ if (s->objects < min_objects)
+ min_objects = s->objects;
+ if (used < min_used)
+ min_used = used;
+ if (s->objects_partial < min_partobj)
+ min_partobj = s->objects_partial;
+ if (percentage_partial_slabs < min_ppart)
+ min_ppart = percentage_partial_slabs;
+ if (percentage_partial_objs < min_ppartobj)
+ min_ppartobj = percentage_partial_objs;
+ if (s->slab_size < min_memobj)
+ min_memobj = s->slab_size;
+
+ if (s->object_size > max_objsize)
+ max_objsize = s->object_size;
+ if (s->partial > max_partial)
+ max_partial = s->partial;
+ if (s->slabs > max_slabs)
+ max_slabs = s->slabs;
+ if (size > max_size)
+ max_size = size;
+ if (wasted > max_waste)
+ max_waste = wasted;
+ if (objwaste > max_objwaste)
+ max_objwaste = objwaste;
+ if (s->objects > max_objects)
+ max_objects = s->objects;
+ if (used > max_used)
+ max_used = used;
+ if (s->objects_partial > max_partobj)
+ max_partobj = s->objects_partial;
+ if (percentage_partial_slabs > max_ppart)
+ max_ppart = percentage_partial_slabs;
+ if (percentage_partial_objs > max_ppartobj)
+ max_ppartobj = percentage_partial_objs;
+ if (s->slab_size > max_memobj)
+ max_memobj = s->slab_size;
+
+ total_partial += s->partial;
+ total_slabs += s->slabs;
+ total_size += size;
+ total_waste += wasted;
+
+ total_objects += s->objects;
+ total_used += used;
+ total_partobj += s->objects_partial;
+ total_ppart += percentage_partial_slabs;
+ total_ppartobj += percentage_partial_objs;
+
+ total_objwaste += s->objects * objwaste;
+ total_objsize += s->objects * s->slab_size;
+ }
+
+ if (!total_objects) {
+ printf("No objects\n");
+ return;
+ }
+ if (!used_slabs) {
+ printf("No slabs\n");
+ return;
+ }
+
+ /* Per slab averages */
+ avg_partial = total_partial / used_slabs;
+ avg_slabs = total_slabs / used_slabs;
+ avg_size = total_size / used_slabs;
+ avg_waste = total_waste / used_slabs;
+
+ avg_objects = total_objects / used_slabs;
+ avg_used = total_used / used_slabs;
+ avg_partobj = total_partobj / used_slabs;
+ avg_ppart = total_ppart / used_slabs;
+ avg_ppartobj = total_ppartobj / used_slabs;
+
+ /* Per object object sizes */
+ avg_objsize = total_used / total_objects;
+ avg_objwaste = total_objwaste / total_objects;
+ avg_partobj = total_partobj * 100 / total_objects;
+ avg_memobj = total_objsize / total_objects;
+
+ printf("Slabcache Totals\n");
+ printf("----------------\n");
+ printf("Slabcaches : %3d Aliases : %3d->%-3d Active: %3d\n",
+ slabs, aliases, alias_targets, used_slabs);
+
+ store_size(b1, total_size);store_size(b2, total_waste);
+ store_size(b3, total_waste * 100 / total_used);
+ printf("Memory used: %6s # Loss : %6s MRatio:%6s%%\n", b1, b2, b3);
+
+ store_size(b1, total_objects);store_size(b2, total_partobj);
+ store_size(b3, total_partobj * 100 / total_objects);
+ printf("# Objects : %6s # PartObj: %6s ORatio:%6s%%\n", b1, b2, b3);
+
+ printf("\n");
+ printf("Per Cache Average Min Max Total\n");
+ printf("---------------------------------------------------------\n");
+
+ store_size(b1, avg_objects);store_size(b2, min_objects);
+ store_size(b3, max_objects);store_size(b4, total_objects);
+ printf("#Objects %10s %10s %10s %10s\n",
+ b1, b2, b3, b4);
+
+ store_size(b1, avg_slabs);store_size(b2, min_slabs);
+ store_size(b3, max_slabs);store_size(b4, total_slabs);
+ printf("#Slabs %10s %10s %10s %10s\n",
+ b1, b2, b3, b4);
+
+ store_size(b1, avg_partial);store_size(b2, min_partial);
+ store_size(b3, max_partial);store_size(b4, total_partial);
+ printf("#PartSlab %10s %10s %10s %10s\n",
+ b1, b2, b3, b4);
+ store_size(b1, avg_ppart);store_size(b2, min_ppart);
+ store_size(b3, max_ppart);
+ store_size(b4, total_partial * 100 / total_slabs);
+ printf("%%PartSlab%10s%% %10s%% %10s%% %10s%%\n",
+ b1, b2, b3, b4);
+
+ store_size(b1, avg_partobj);store_size(b2, min_partobj);
+ store_size(b3, max_partobj);
+ store_size(b4, total_partobj);
+ printf("PartObjs %10s %10s %10s %10s\n",
+ b1, b2, b3, b4);
+
+ store_size(b1, avg_ppartobj);store_size(b2, min_ppartobj);
+ store_size(b3, max_ppartobj);
+ store_size(b4, total_partobj * 100 / total_objects);
+ printf("%% PartObj%10s%% %10s%% %10s%% %10s%%\n",
+ b1, b2, b3, b4);
+
+ store_size(b1, avg_size);store_size(b2, min_size);
+ store_size(b3, max_size);store_size(b4, total_size);
+ printf("Memory %10s %10s %10s %10s\n",
+ b1, b2, b3, b4);
+
+ store_size(b1, avg_used);store_size(b2, min_used);
+ store_size(b3, max_used);store_size(b4, total_used);
+ printf("Used %10s %10s %10s %10s\n",
+ b1, b2, b3, b4);
+
+ store_size(b1, avg_waste);store_size(b2, min_waste);
+ store_size(b3, max_waste);store_size(b4, total_waste);
+ printf("Loss %10s %10s %10s %10s\n",
+ b1, b2, b3, b4);
+
+ printf("\n");
+ printf("Per Object Average Min Max\n");
+ printf("---------------------------------------------\n");
+
+ store_size(b1, avg_memobj);store_size(b2, min_memobj);
+ store_size(b3, max_memobj);
+ printf("Memory %10s %10s %10s\n",
+ b1, b2, b3);
+ store_size(b1, avg_objsize);store_size(b2, min_objsize);
+ store_size(b3, max_objsize);
+ printf("User %10s %10s %10s\n",
+ b1, b2, b3);
+
+ store_size(b1, avg_objwaste);store_size(b2, min_objwaste);
+ store_size(b3, max_objwaste);
+ printf("Loss %10s %10s %10s\n",
+ b1, b2, b3);
+}
+
+static void sort_slabs(void)
+{
+ struct slabinfo *s1,*s2;
+
+ for (s1 = slabinfo; s1 < slabinfo + slabs; s1++) {
+ for (s2 = s1 + 1; s2 < slabinfo + slabs; s2++) {
+ int result;
+
+ if (sort_size)
+ result = slab_size(s1) < slab_size(s2);
+ else if (sort_active)
+ result = slab_activity(s1) < slab_activity(s2);
+ else
+ result = strcasecmp(s1->name, s2->name);
+
+ if (show_inverted)
+ result = -result;
+
+ if (result > 0) {
+ struct slabinfo t;
+
+ memcpy(&t, s1, sizeof(struct slabinfo));
+ memcpy(s1, s2, sizeof(struct slabinfo));
+ memcpy(s2, &t, sizeof(struct slabinfo));
+ }
+ }
+ }
+}
+
+static void sort_aliases(void)
+{
+ struct aliasinfo *a1,*a2;
+
+ for (a1 = aliasinfo; a1 < aliasinfo + aliases; a1++) {
+ for (a2 = a1 + 1; a2 < aliasinfo + aliases; a2++) {
+ char *n1, *n2;
+
+ n1 = a1->name;
+ n2 = a2->name;
+ if (show_alias && !show_inverted) {
+ n1 = a1->ref;
+ n2 = a2->ref;
+ }
+ if (strcasecmp(n1, n2) > 0) {
+ struct aliasinfo t;
+
+ memcpy(&t, a1, sizeof(struct aliasinfo));
+ memcpy(a1, a2, sizeof(struct aliasinfo));
+ memcpy(a2, &t, sizeof(struct aliasinfo));
+ }
+ }
+ }
+}
+
+static void link_slabs(void)
+{
+ struct aliasinfo *a;
+ struct slabinfo *s;
+
+ for (a = aliasinfo; a < aliasinfo + aliases; a++) {
+
+ for (s = slabinfo; s < slabinfo + slabs; s++)
+ if (strcmp(a->ref, s->name) == 0) {
+ a->slab = s;
+ s->refs++;
+ break;
+ }
+ if (s == slabinfo + slabs)
+ fatal("Unresolved alias %s\n", a->ref);
+ }
+}
+
+static void alias(void)
+{
+ struct aliasinfo *a;
+ char *active = NULL;
+
+ sort_aliases();
+ link_slabs();
+
+ for(a = aliasinfo; a < aliasinfo + aliases; a++) {
+
+ if (!show_single_ref && a->slab->refs == 1)
+ continue;
+
+ if (!show_inverted) {
+ if (active) {
+ if (strcmp(a->slab->name, active) == 0) {
+ printf(" %s", a->name);
+ continue;
+ }
+ }
+ printf("\n%-12s <- %s", a->slab->name, a->name);
+ active = a->slab->name;
+ }
+ else
+ printf("%-20s -> %s\n", a->name, a->slab->name);
+ }
+ if (active)
+ printf("\n");
+}
+
+
+static void rename_slabs(void)
+{
+ struct slabinfo *s;
+ struct aliasinfo *a;
+
+ for (s = slabinfo; s < slabinfo + slabs; s++) {
+ if (*s->name != ':')
+ continue;
+
+ if (s->refs > 1 && !show_first_alias)
+ continue;
+
+ a = find_one_alias(s);
+
+ if (a)
+ s->name = a->name;
+ else {
+ s->name = "*";
+ actual_slabs--;
+ }
+ }
+}
+
+static int slab_mismatch(char *slab)
+{
+ return regexec(&pattern, slab, 0, NULL, 0);
+}
+
+static void read_slab_dir(void)
+{
+ DIR *dir;
+ struct dirent *de;
+ struct slabinfo *slab = slabinfo;
+ struct aliasinfo *alias = aliasinfo;
+ char *p;
+ char *t;
+ int count;
+
+ if (chdir("/sys/kernel/slab") && chdir("/sys/slab"))
+ fatal("SYSFS support for SLUB not active\n");
+
+ dir = opendir(".");
+ while ((de = readdir(dir))) {
+ if (de->d_name[0] == '.' ||
+ (de->d_name[0] != ':' && slab_mismatch(de->d_name)))
+ continue;
+ switch (de->d_type) {
+ case DT_LNK:
+ alias->name = strdup(de->d_name);
+ count = readlink(de->d_name, buffer, sizeof(buffer)-1);
+
+ if (count < 0)
+ fatal("Cannot read symlink %s\n", de->d_name);
+
+ buffer[count] = 0;
+ p = buffer + count;
+ while (p > buffer && p[-1] != '/')
+ p--;
+ alias->ref = strdup(p);
+ alias++;
+ break;
+ case DT_DIR:
+ if (chdir(de->d_name))
+ fatal("Unable to access slab %s\n", slab->name);
+ slab->name = strdup(de->d_name);
+ slab->alias = 0;
+ slab->refs = 0;
+ slab->aliases = get_obj("aliases");
+ slab->align = get_obj("align");
+ slab->cache_dma = get_obj("cache_dma");
+ slab->cpu_slabs = get_obj("cpu_slabs");
+ slab->destroy_by_rcu = get_obj("destroy_by_rcu");
+ slab->hwcache_align = get_obj("hwcache_align");
+ slab->object_size = get_obj("object_size");
+ slab->objects = get_obj("objects");
+ slab->objects_partial = get_obj("objects_partial");
+ slab->objects_total = get_obj("objects_total");
+ slab->objs_per_slab = get_obj("objs_per_slab");
+ slab->order = get_obj("order");
+ slab->partial = get_obj("partial");
+ slab->partial = get_obj_and_str("partial", &t);
+ decode_numa_list(slab->numa_partial, t);
+ free(t);
+ slab->poison = get_obj("poison");
+ slab->reclaim_account = get_obj("reclaim_account");
+ slab->red_zone = get_obj("red_zone");
+ slab->sanity_checks = get_obj("sanity_checks");
+ slab->slab_size = get_obj("slab_size");
+ slab->slabs = get_obj_and_str("slabs", &t);
+ decode_numa_list(slab->numa, t);
+ free(t);
+ slab->store_user = get_obj("store_user");
+ slab->trace = get_obj("trace");
+ slab->alloc_fastpath = get_obj("alloc_fastpath");
+ slab->alloc_slowpath = get_obj("alloc_slowpath");
+ slab->free_fastpath = get_obj("free_fastpath");
+ slab->free_slowpath = get_obj("free_slowpath");
+ slab->free_frozen= get_obj("free_frozen");
+ slab->free_add_partial = get_obj("free_add_partial");
+ slab->free_remove_partial = get_obj("free_remove_partial");
+ slab->alloc_from_partial = get_obj("alloc_from_partial");
+ slab->alloc_slab = get_obj("alloc_slab");
+ slab->alloc_refill = get_obj("alloc_refill");
+ slab->free_slab = get_obj("free_slab");
+ slab->cpuslab_flush = get_obj("cpuslab_flush");
+ slab->deactivate_full = get_obj("deactivate_full");
+ slab->deactivate_empty = get_obj("deactivate_empty");
+ slab->deactivate_to_head = get_obj("deactivate_to_head");
+ slab->deactivate_to_tail = get_obj("deactivate_to_tail");
+ slab->deactivate_remote_frees = get_obj("deactivate_remote_frees");
+ slab->order_fallback = get_obj("order_fallback");
+ slab->cmpxchg_double_cpu_fail = get_obj("cmpxchg_double_cpu_fail");
+ slab->cmpxchg_double_fail = get_obj("cmpxchg_double_fail");
+ slab->cpu_partial_alloc = get_obj("cpu_partial_alloc");
+ slab->cpu_partial_free = get_obj("cpu_partial_free");
+ slab->alloc_node_mismatch = get_obj("alloc_node_mismatch");
+ slab->deactivate_bypass = get_obj("deactivate_bypass");
+ chdir("..");
+ if (slab->name[0] == ':')
+ alias_targets++;
+ slab++;
+ break;
+ default :
+ fatal("Unknown file type %lx\n", de->d_type);
+ }
+ }
+ closedir(dir);
+ slabs = slab - slabinfo;
+ actual_slabs = slabs;
+ aliases = alias - aliasinfo;
+ if (slabs > MAX_SLABS)
+ fatal("Too many slabs\n");
+ if (aliases > MAX_ALIASES)
+ fatal("Too many aliases\n");
+}
+
+static void output_slabs(void)
+{
+ struct slabinfo *slab;
+
+ for (slab = slabinfo; slab < slabinfo + slabs; slab++) {
+
+ if (slab->alias)
+ continue;
+
+
+ if (show_numa)
+ slab_numa(slab, 0);
+ else if (show_track)
+ show_tracking(slab);
+ else if (validate)
+ slab_validate(slab);
+ else if (shrink)
+ slab_shrink(slab);
+ else if (set_debug)
+ slab_debug(slab);
+ else if (show_ops)
+ ops(slab);
+ else if (show_slab)
+ slabcache(slab);
+ else if (show_report)
+ report(slab);
+ }
+}
+
+struct option opts[] = {
+ { "aliases", 0, NULL, 'a' },
+ { "activity", 0, NULL, 'A' },
+ { "debug", 2, NULL, 'd' },
+ { "display-activity", 0, NULL, 'D' },
+ { "empty", 0, NULL, 'e' },
+ { "first-alias", 0, NULL, 'f' },
+ { "help", 0, NULL, 'h' },
+ { "inverted", 0, NULL, 'i'},
+ { "numa", 0, NULL, 'n' },
+ { "ops", 0, NULL, 'o' },
+ { "report", 0, NULL, 'r' },
+ { "shrink", 0, NULL, 's' },
+ { "slabs", 0, NULL, 'l' },
+ { "track", 0, NULL, 't'},
+ { "validate", 0, NULL, 'v' },
+ { "zero", 0, NULL, 'z' },
+ { "1ref", 0, NULL, '1'},
+ { NULL, 0, NULL, 0 }
+};
+
+int main(int argc, char *argv[])
+{
+ int c;
+ int err;
+ char *pattern_source;
+
+ page_size = getpagesize();
+
+ while ((c = getopt_long(argc, argv, "aAd::Defhil1noprstvzTS",
+ opts, NULL)) != -1)
+ switch (c) {
+ case '1':
+ show_single_ref = 1;
+ break;
+ case 'a':
+ show_alias = 1;
+ break;
+ case 'A':
+ sort_active = 1;
+ break;
+ case 'd':
+ set_debug = 1;
+ if (!debug_opt_scan(optarg))
+ fatal("Invalid debug option '%s'\n", optarg);
+ break;
+ case 'D':
+ show_activity = 1;
+ break;
+ case 'e':
+ show_empty = 1;
+ break;
+ case 'f':
+ show_first_alias = 1;
+ break;
+ case 'h':
+ usage();
+ return 0;
+ case 'i':
+ show_inverted = 1;
+ break;
+ case 'n':
+ show_numa = 1;
+ break;
+ case 'o':
+ show_ops = 1;
+ break;
+ case 'r':
+ show_report = 1;
+ break;
+ case 's':
+ shrink = 1;
+ break;
+ case 'l':
+ show_slab = 1;
+ break;
+ case 't':
+ show_track = 1;
+ break;
+ case 'v':
+ validate = 1;
+ break;
+ case 'z':
+ skip_zero = 0;
+ break;
+ case 'T':
+ show_totals = 1;
+ break;
+ case 'S':
+ sort_size = 1;
+ break;
+
+ default:
+ fatal("%s: Invalid option '%c'\n", argv[0], optopt);
+
+ }
+
+ if (!show_slab && !show_alias && !show_track && !show_report
+ && !validate && !shrink && !set_debug && !show_ops)
+ show_slab = 1;
+
+ if (argc > optind)
+ pattern_source = argv[optind];
+ else
+ pattern_source = ".*";
+
+ err = regcomp(&pattern, pattern_source, REG_ICASE|REG_NOSUB);
+ if (err)
+ fatal("%s: Invalid pattern '%s' code %d\n",
+ argv[0], pattern_source, err);
+ read_slab_dir();
+ if (show_alias)
+ alias();
+ else
+ if (show_totals)
+ totals();
+ else {
+ link_slabs();
+ rename_slabs();
+ sort_slabs();
+ output_slabs();
+ }
+ return 0;
+}