mirror of
https://github.com/hardkernel/linux.git
synced 2026-06-07 19:30:30 +09:00
Merge "ODROID-COMMON: add gator driver" into odroidn2-4.9.y
This commit is contained in:
@@ -4518,6 +4518,11 @@ CONFIG_MALI_EXPERT=y
|
||||
# CONFIG_MALI_PWRSOFT_765 is not set
|
||||
CONFIG_MALI_KUTF=m
|
||||
|
||||
#
|
||||
# Gator module for ARM streamline
|
||||
#
|
||||
# CONFIG_GATOR is not set
|
||||
|
||||
#
|
||||
# Firmware Drivers
|
||||
#
|
||||
|
||||
@@ -207,4 +207,6 @@ source "drivers/fpga/Kconfig"
|
||||
source "drivers/tee/Kconfig"
|
||||
|
||||
source "drivers/gpu/arm/Kconfig"
|
||||
|
||||
source "drivers/gator/Kconfig"
|
||||
endmenu
|
||||
|
||||
@@ -177,3 +177,4 @@ obj-$(CONFIG_NVMEM) += nvmem/
|
||||
obj-$(CONFIG_FPGA) += fpga/
|
||||
obj-$(CONFIG_TEE) += tee/
|
||||
obj-$(CONFIG_AMLOGIC_DRIVER) += amlogic/
|
||||
obj-$(CONFIG_GATOR) += gator/
|
||||
|
||||
339
drivers/gator/COPYING
Normal file
339
drivers/gator/COPYING
Normal file
@@ -0,0 +1,339 @@
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
Version 2, June 1991
|
||||
|
||||
Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
Everyone is permitted to copy and distribute verbatim copies
|
||||
of this license document, but changing it is not allowed.
|
||||
|
||||
Preamble
|
||||
|
||||
The licenses for most software are designed to take away your
|
||||
freedom to share and change it. By contrast, the GNU General Public
|
||||
License is intended to guarantee your freedom to share and change free
|
||||
software--to make sure the software is free for all its users. This
|
||||
General Public License applies to most of the Free Software
|
||||
Foundation's software and to any other program whose authors commit to
|
||||
using it. (Some other Free Software Foundation software is covered by
|
||||
the GNU Lesser General Public License instead.) You can apply it to
|
||||
your programs, too.
|
||||
|
||||
When we speak of free software, we are referring to freedom, not
|
||||
price. Our General Public Licenses are designed to make sure that you
|
||||
have the freedom to distribute copies of free software (and charge for
|
||||
this service if you wish), that you receive source code or can get it
|
||||
if you want it, that you can change the software or use pieces of it
|
||||
in new free programs; and that you know you can do these things.
|
||||
|
||||
To protect your rights, we need to make restrictions that forbid
|
||||
anyone to deny you these rights or to ask you to surrender the rights.
|
||||
These restrictions translate to certain responsibilities for you if you
|
||||
distribute copies of the software, or if you modify it.
|
||||
|
||||
For example, if you distribute copies of such a program, whether
|
||||
gratis or for a fee, you must give the recipients all the rights that
|
||||
you have. You must make sure that they, too, receive or can get the
|
||||
source code. And you must show them these terms so they know their
|
||||
rights.
|
||||
|
||||
We protect your rights with two steps: (1) copyright the software, and
|
||||
(2) offer you this license which gives you legal permission to copy,
|
||||
distribute and/or modify the software.
|
||||
|
||||
Also, for each author's protection and ours, we want to make certain
|
||||
that everyone understands that there is no warranty for this free
|
||||
software. If the software is modified by someone else and passed on, we
|
||||
want its recipients to know that what they have is not the original, so
|
||||
that any problems introduced by others will not reflect on the original
|
||||
authors' reputations.
|
||||
|
||||
Finally, any free program is threatened constantly by software
|
||||
patents. We wish to avoid the danger that redistributors of a free
|
||||
program will individually obtain patent licenses, in effect making the
|
||||
program proprietary. To prevent this, we have made it clear that any
|
||||
patent must be licensed for everyone's free use or not licensed at all.
|
||||
|
||||
The precise terms and conditions for copying, distribution and
|
||||
modification follow.
|
||||
|
||||
GNU GENERAL PUBLIC LICENSE
|
||||
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
|
||||
|
||||
0. This License applies to any program or other work which contains
|
||||
a notice placed by the copyright holder saying it may be distributed
|
||||
under the terms of this General Public License. The "Program", below,
|
||||
refers to any such program or work, and a "work based on the Program"
|
||||
means either the Program or any derivative work under copyright law:
|
||||
that is to say, a work containing the Program or a portion of it,
|
||||
either verbatim or with modifications and/or translated into another
|
||||
language. (Hereinafter, translation is included without limitation in
|
||||
the term "modification".) Each licensee is addressed as "you".
|
||||
|
||||
Activities other than copying, distribution and modification are not
|
||||
covered by this License; they are outside its scope. The act of
|
||||
running the Program is not restricted, and the output from the Program
|
||||
is covered only if its contents constitute a work based on the
|
||||
Program (independent of having been made by running the Program).
|
||||
Whether that is true depends on what the Program does.
|
||||
|
||||
1. You may copy and distribute verbatim copies of the Program's
|
||||
source code as you receive it, in any medium, provided that you
|
||||
conspicuously and appropriately publish on each copy an appropriate
|
||||
copyright notice and disclaimer of warranty; keep intact all the
|
||||
notices that refer to this License and to the absence of any warranty;
|
||||
and give any other recipients of the Program a copy of this License
|
||||
along with the Program.
|
||||
|
||||
You may charge a fee for the physical act of transferring a copy, and
|
||||
you may at your option offer warranty protection in exchange for a fee.
|
||||
|
||||
2. You may modify your copy or copies of the Program or any portion
|
||||
of it, thus forming a work based on the Program, and copy and
|
||||
distribute such modifications or work under the terms of Section 1
|
||||
above, provided that you also meet all of these conditions:
|
||||
|
||||
a) You must cause the modified files to carry prominent notices
|
||||
stating that you changed the files and the date of any change.
|
||||
|
||||
b) You must cause any work that you distribute or publish, that in
|
||||
whole or in part contains or is derived from the Program or any
|
||||
part thereof, to be licensed as a whole at no charge to all third
|
||||
parties under the terms of this License.
|
||||
|
||||
c) If the modified program normally reads commands interactively
|
||||
when run, you must cause it, when started running for such
|
||||
interactive use in the most ordinary way, to print or display an
|
||||
announcement including an appropriate copyright notice and a
|
||||
notice that there is no warranty (or else, saying that you provide
|
||||
a warranty) and that users may redistribute the program under
|
||||
these conditions, and telling the user how to view a copy of this
|
||||
License. (Exception: if the Program itself is interactive but
|
||||
does not normally print such an announcement, your work based on
|
||||
the Program is not required to print an announcement.)
|
||||
|
||||
These requirements apply to the modified work as a whole. If
|
||||
identifiable sections of that work are not derived from the Program,
|
||||
and can be reasonably considered independent and separate works in
|
||||
themselves, then this License, and its terms, do not apply to those
|
||||
sections when you distribute them as separate works. But when you
|
||||
distribute the same sections as part of a whole which is a work based
|
||||
on the Program, the distribution of the whole must be on the terms of
|
||||
this License, whose permissions for other licensees extend to the
|
||||
entire whole, and thus to each and every part regardless of who wrote it.
|
||||
|
||||
Thus, it is not the intent of this section to claim rights or contest
|
||||
your rights to work written entirely by you; rather, the intent is to
|
||||
exercise the right to control the distribution of derivative or
|
||||
collective works based on the Program.
|
||||
|
||||
In addition, mere aggregation of another work not based on the Program
|
||||
with the Program (or with a work based on the Program) on a volume of
|
||||
a storage or distribution medium does not bring the other work under
|
||||
the scope of this License.
|
||||
|
||||
3. You may copy and distribute the Program (or a work based on it,
|
||||
under Section 2) in object code or executable form under the terms of
|
||||
Sections 1 and 2 above provided that you also do one of the following:
|
||||
|
||||
a) Accompany it with the complete corresponding machine-readable
|
||||
source code, which must be distributed under the terms of Sections
|
||||
1 and 2 above on a medium customarily used for software interchange; or,
|
||||
|
||||
b) Accompany it with a written offer, valid for at least three
|
||||
years, to give any third party, for a charge no more than your
|
||||
cost of physically performing source distribution, a complete
|
||||
machine-readable copy of the corresponding source code, to be
|
||||
distributed under the terms of Sections 1 and 2 above on a medium
|
||||
customarily used for software interchange; or,
|
||||
|
||||
c) Accompany it with the information you received as to the offer
|
||||
to distribute corresponding source code. (This alternative is
|
||||
allowed only for noncommercial distribution and only if you
|
||||
received the program in object code or executable form with such
|
||||
an offer, in accord with Subsection b above.)
|
||||
|
||||
The source code for a work means the preferred form of the work for
|
||||
making modifications to it. For an executable work, complete source
|
||||
code means all the source code for all modules it contains, plus any
|
||||
associated interface definition files, plus the scripts used to
|
||||
control compilation and installation of the executable. However, as a
|
||||
special exception, the source code distributed need not include
|
||||
anything that is normally distributed (in either source or binary
|
||||
form) with the major components (compiler, kernel, and so on) of the
|
||||
operating system on which the executable runs, unless that component
|
||||
itself accompanies the executable.
|
||||
|
||||
If distribution of executable or object code is made by offering
|
||||
access to copy from a designated place, then offering equivalent
|
||||
access to copy the source code from the same place counts as
|
||||
distribution of the source code, even though third parties are not
|
||||
compelled to copy the source along with the object code.
|
||||
|
||||
4. You may not copy, modify, sublicense, or distribute the Program
|
||||
except as expressly provided under this License. Any attempt
|
||||
otherwise to copy, modify, sublicense or distribute the Program is
|
||||
void, and will automatically terminate your rights under this License.
|
||||
However, parties who have received copies, or rights, from you under
|
||||
this License will not have their licenses terminated so long as such
|
||||
parties remain in full compliance.
|
||||
|
||||
5. You are not required to accept this License, since you have not
|
||||
signed it. However, nothing else grants you permission to modify or
|
||||
distribute the Program or its derivative works. These actions are
|
||||
prohibited by law if you do not accept this License. Therefore, by
|
||||
modifying or distributing the Program (or any work based on the
|
||||
Program), you indicate your acceptance of this License to do so, and
|
||||
all its terms and conditions for copying, distributing or modifying
|
||||
the Program or works based on it.
|
||||
|
||||
6. Each time you redistribute the Program (or any work based on the
|
||||
Program), the recipient automatically receives a license from the
|
||||
original licensor to copy, distribute or modify the Program subject to
|
||||
these terms and conditions. You may not impose any further
|
||||
restrictions on the recipients' exercise of the rights granted herein.
|
||||
You are not responsible for enforcing compliance by third parties to
|
||||
this License.
|
||||
|
||||
7. If, as a consequence of a court judgment or allegation of patent
|
||||
infringement or for any other reason (not limited to patent issues),
|
||||
conditions are imposed on you (whether by court order, agreement or
|
||||
otherwise) that contradict the conditions of this License, they do not
|
||||
excuse you from the conditions of this License. If you cannot
|
||||
distribute so as to satisfy simultaneously your obligations under this
|
||||
License and any other pertinent obligations, then as a consequence you
|
||||
may not distribute the Program at all. For example, if a patent
|
||||
license would not permit royalty-free redistribution of the Program by
|
||||
all those who receive copies directly or indirectly through you, then
|
||||
the only way you could satisfy both it and this License would be to
|
||||
refrain entirely from distribution of the Program.
|
||||
|
||||
If any portion of this section is held invalid or unenforceable under
|
||||
any particular circumstance, the balance of the section is intended to
|
||||
apply and the section as a whole is intended to apply in other
|
||||
circumstances.
|
||||
|
||||
It is not the purpose of this section to induce you to infringe any
|
||||
patents or other property right claims or to contest validity of any
|
||||
such claims; this section has the sole purpose of protecting the
|
||||
integrity of the free software distribution system, which is
|
||||
implemented by public license practices. Many people have made
|
||||
generous contributions to the wide range of software distributed
|
||||
through that system in reliance on consistent application of that
|
||||
system; it is up to the author/donor to decide if he or she is willing
|
||||
to distribute software through any other system and a licensee cannot
|
||||
impose that choice.
|
||||
|
||||
This section is intended to make thoroughly clear what is believed to
|
||||
be a consequence of the rest of this License.
|
||||
|
||||
8. If the distribution and/or use of the Program is restricted in
|
||||
certain countries either by patents or by copyrighted interfaces, the
|
||||
original copyright holder who places the Program under this License
|
||||
may add an explicit geographical distribution limitation excluding
|
||||
those countries, so that distribution is permitted only in or among
|
||||
countries not thus excluded. In such case, this License incorporates
|
||||
the limitation as if written in the body of this License.
|
||||
|
||||
9. The Free Software Foundation may publish revised and/or new versions
|
||||
of the General Public License from time to time. Such new versions will
|
||||
be similar in spirit to the present version, but may differ in detail to
|
||||
address new problems or concerns.
|
||||
|
||||
Each version is given a distinguishing version number. If the Program
|
||||
specifies a version number of this License which applies to it and "any
|
||||
later version", you have the option of following the terms and conditions
|
||||
either of that version or of any later version published by the Free
|
||||
Software Foundation. If the Program does not specify a version number of
|
||||
this License, you may choose any version ever published by the Free Software
|
||||
Foundation.
|
||||
|
||||
10. If you wish to incorporate parts of the Program into other free
|
||||
programs whose distribution conditions are different, write to the author
|
||||
to ask for permission. For software which is copyrighted by the Free
|
||||
Software Foundation, write to the Free Software Foundation; we sometimes
|
||||
make exceptions for this. Our decision will be guided by the two goals
|
||||
of preserving the free status of all derivatives of our free software and
|
||||
of promoting the sharing and reuse of software generally.
|
||||
|
||||
NO WARRANTY
|
||||
|
||||
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
|
||||
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
|
||||
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
|
||||
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
|
||||
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
|
||||
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
|
||||
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
|
||||
REPAIR OR CORRECTION.
|
||||
|
||||
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
|
||||
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
|
||||
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
|
||||
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
|
||||
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
|
||||
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
|
||||
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
|
||||
POSSIBILITY OF SUCH DAMAGES.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
How to Apply These Terms to Your New Programs
|
||||
|
||||
If you develop a new program, and you want it to be of the greatest
|
||||
possible use to the public, the best way to achieve this is to make it
|
||||
free software which everyone can redistribute and change under these terms.
|
||||
|
||||
To do so, attach the following notices to the program. It is safest
|
||||
to attach them to the start of each source file to most effectively
|
||||
convey the exclusion of warranty; and each file should have at least
|
||||
the "copyright" line and a pointer to where the full notice is found.
|
||||
|
||||
<one line to give the program's name and a brief idea of what it does.>
|
||||
Copyright (C) <year> <name of author>
|
||||
|
||||
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.,
|
||||
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
|
||||
|
||||
Also add information on how to contact you by electronic and paper mail.
|
||||
|
||||
If the program is interactive, make it output a short notice like this
|
||||
when it starts in an interactive mode:
|
||||
|
||||
Gnomovision version 69, Copyright (C) year name of author
|
||||
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||
This is free software, and you are welcome to redistribute it
|
||||
under certain conditions; type `show c' for details.
|
||||
|
||||
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||
parts of the General Public License. Of course, the commands you use may
|
||||
be called something other than `show w' and `show c'; they could even be
|
||||
mouse-clicks or menu items--whatever suits your program.
|
||||
|
||||
You should also get your employer (if you work as a programmer) or your
|
||||
school, if any, to sign a "copyright disclaimer" for the program, if
|
||||
necessary. Here is a sample; alter the names:
|
||||
|
||||
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
|
||||
`Gnomovision' (which makes passes at compilers) written by James Hacker.
|
||||
|
||||
<signature of Ty Coon>, 1 April 1989
|
||||
Ty Coon, President of Vice
|
||||
|
||||
This General Public License does not permit incorporating your program into
|
||||
proprietary programs. If your program is a subroutine library, you may
|
||||
consider it more useful to permit linking proprietary applications with the
|
||||
library. If this is what you want to do, use the GNU Lesser General
|
||||
Public License instead of this License.
|
||||
66
drivers/gator/Kconfig
Normal file
66
drivers/gator/Kconfig
Normal file
@@ -0,0 +1,66 @@
|
||||
menu "Gator module for ARM streamline"
|
||||
|
||||
config GATOR
|
||||
tristate "Gator module for Arm's Streamline Performance Analyzer"
|
||||
default m if (ARM || ARM64)
|
||||
depends on PROFILING
|
||||
depends on HIGH_RES_TIMERS
|
||||
depends on LOCAL_TIMERS || !(ARM && SMP)
|
||||
depends on PERF_EVENTS
|
||||
depends on HW_PERF_EVENTS || !(ARM || ARM64)
|
||||
select TRACING
|
||||
help
|
||||
Gator module for Arm's Streamline Performance Analyzer
|
||||
|
||||
config GATOR_DO_NOT_ONLINE_CORES_AT_STARTUP
|
||||
bool "Disable onlining all CPU cores by default when module loads"
|
||||
default n
|
||||
depends on GATOR
|
||||
help
|
||||
Prevent gator from online all CPU cores when it first loads.
|
||||
The default option is to online all CPU cores when gator first
|
||||
loads. This is safe and necessary in most cases as it allows gator to correctly
|
||||
identify all cores in the system.
|
||||
Most users should leave this option disabled.
|
||||
Even if the option is left disabled, it is possible to disable onlining
|
||||
behaviour at module load time by setting the disable_cpu_onlining
|
||||
module parameter to true.
|
||||
|
||||
config GATOR_WITH_MALI_SUPPORT
|
||||
bool
|
||||
depends on GATOR
|
||||
|
||||
choice
|
||||
prompt "Enable Mali GPU support in Gator"
|
||||
depends on GATOR
|
||||
optional
|
||||
help
|
||||
Enable Mali GPU support in Gator
|
||||
|
||||
config GATOR_MALI_4XXMP
|
||||
bool "Mali-400MP or Mali-450MP"
|
||||
select GATOR_WITH_MALI_SUPPORT
|
||||
|
||||
config GATOR_MALI_MIDGARD
|
||||
bool "Mali-T60x, Mali-T62x, Mali-T72x or Mali-T76x"
|
||||
select GATOR_WITH_MALI_SUPPORT
|
||||
|
||||
endchoice
|
||||
|
||||
config GATOR_MALI_4XXMP_PATH
|
||||
string "Path to Mali driver"
|
||||
depends on GATOR_MALI_4XXMP
|
||||
default "drivers/gpu/arm/mali400mp"
|
||||
help
|
||||
The gator code adds this to its include path so it can get the Mali
|
||||
trace headers with: #include "linux/mali_linux_trace.h"
|
||||
|
||||
config GATOR_MALI_MIDGARD_PATH
|
||||
string "Path to Mali driver"
|
||||
depends on GATOR_MALI_MIDGARD
|
||||
default "drivers/gpu/arm/midgard"
|
||||
help
|
||||
The gator code adds this to its include path so it can get the Mali
|
||||
trace headers with: #include "linux/mali_linux_trace.h"
|
||||
|
||||
endmenu
|
||||
107
drivers/gator/Makefile
Normal file
107
drivers/gator/Makefile
Normal file
@@ -0,0 +1,107 @@
|
||||
ifneq ($(KERNELRELEASE),)
|
||||
|
||||
# Uncomment the following line to enable kernel stack unwinding within gator, or update gator_backtrace.c
|
||||
# EXTRA_CFLAGS += -DGATOR_KERNEL_STACK_UNWINDING
|
||||
|
||||
CONFIG_GATOR ?= m
|
||||
obj-$(CONFIG_GATOR) := gator.o
|
||||
|
||||
gator-y := gator_main.o \
|
||||
gator_events_block.o \
|
||||
gator_events_irq.o \
|
||||
gator_events_meminfo.o \
|
||||
gator_events_mmapped.o \
|
||||
gator_events_net.o \
|
||||
gator_events_sched.o
|
||||
|
||||
chk_events.h = :
|
||||
quiet_chk_events.h = echo ' CHK $@'
|
||||
silent_chk_events.h = :
|
||||
|
||||
# Convert the old GATOR_WITH_MALI_SUPPORT to the new kernel flags
|
||||
ifneq ($(GATOR_WITH_MALI_SUPPORT),)
|
||||
CONFIG_GATOR_WITH_MALI_SUPPORT := y
|
||||
ifeq ($(GATOR_WITH_MALI_SUPPORT),MALI_MIDGARD)
|
||||
CONFIG_GATOR_MALI_4XXMP := n
|
||||
CONFIG_GATOR_MALI_MIDGARD := y
|
||||
EXTRA_CFLAGS += -DMALI_SUPPORT=MALI_MIDGARD_OR_BIFROST
|
||||
else
|
||||
ifeq ($(GATOR_WITH_MALI_SUPPORT),MALI_BIFROST)
|
||||
CONFIG_GATOR_MALI_4XXMP := n
|
||||
CONFIG_GATOR_MALI_MIDGARD := y
|
||||
EXTRA_CFLAGS += -DMALI_SUPPORT=MALI_MIDGARD_OR_BIFROST
|
||||
else
|
||||
# Neither Midgard, nor Bifrost, must be 4xx then
|
||||
CONFIG_GATOR_MALI_4XXMP := y
|
||||
CONFIG_GATOR_MALI_MIDGARD := n
|
||||
EXTRA_CFLAGS += -DMALI_SUPPORT=$(GATOR_WITH_MALI_SUPPORT)
|
||||
endif
|
||||
endif
|
||||
ifneq ($(GATOR_MALI_INTERFACE_STYLE),)
|
||||
EXTRA_CFLAGS += -DGATOR_MALI_INTERFACE_STYLE=$(GATOR_MALI_INTERFACE_STYLE)
|
||||
endif
|
||||
ifneq ($(GATOR_MALI_4XXMP_PATH),)
|
||||
CONFIG_GATOR_MALI_4XXMP_PATH = $(GATOR_MALI_4XXMP_PATH)
|
||||
endif
|
||||
CONFIG_GATOR_MALI_4XXMP_PATH ?= drivers/gpu/arm/mali400mp
|
||||
ifneq ($(GATOR_MALI_MIDGARD_PATH),)
|
||||
CONFIG_GATOR_MALI_MIDGARD_PATH = $(GATOR_MALI_MIDGARD_PATH)
|
||||
endif
|
||||
CONFIG_GATOR_MALI_MIDGARD_PATH ?= drivers/gpu/arm/midgard
|
||||
endif
|
||||
|
||||
ifeq ($(CONFIG_GATOR_WITH_MALI_SUPPORT),y)
|
||||
ifeq ($(CONFIG_GATOR_MALI_MIDGARD),y)
|
||||
gator-y += gator_events_mali_midgard.o \
|
||||
gator_events_mali_midgard_hw.o
|
||||
include $(src)/mali_midgard.mk
|
||||
else
|
||||
gator-y += gator_events_mali_4xx.o
|
||||
endif
|
||||
gator-y += gator_events_mali_common.o
|
||||
|
||||
ifneq ($(CONFIG_GATOR_MALI_4XXMP_PATH),)
|
||||
ccflags-$(CONFIG_GATOR_MALI_4XXMP) += -I$(CONFIG_GATOR_MALI_4XXMP_PATH)
|
||||
endif
|
||||
ifneq ($(CONFIG_GATOR_MALI_MIDGARD_PATH),)
|
||||
ccflags-$(CONFIG_GATOR_MALI_MIDGARD) += -I$(CONFIG_GATOR_MALI_MIDGARD_PATH)
|
||||
endif
|
||||
ccflags-$(CONFIG_GATOR_MALI_4XXMP) += -DMALI_SUPPORT=MALI_4xx
|
||||
ccflags-$(CONFIG_GATOR_MALI_MIDGARD) += -DMALI_SUPPORT=MALI_MIDGARD_OR_BIFROST
|
||||
endif
|
||||
|
||||
gator-additional-dependencies := $(obj)/generated_gator_src_md5.h
|
||||
clean-files := generated_gator_src_md5.h
|
||||
|
||||
# Should the original or new block_rq_complete API be used?
|
||||
OLD_BLOCK_RQ_COMPLETE := $(shell grep -A3 block_rq_complete $(srctree)/include/trace/events/block.h | grep nr_bytes -q; echo $$?)
|
||||
EXTRA_CFLAGS += -DOLD_BLOCK_RQ_COMPLETE=$(OLD_BLOCK_RQ_COMPLETE)
|
||||
|
||||
gator-$(CONFIG_ARM) += gator_events_l2c-310.o
|
||||
|
||||
gator-$(CONFIG_ARM64) +=
|
||||
|
||||
$(obj)/gator_main.o: $(gator-additional-dependencies)
|
||||
|
||||
# Note, in the recipe below we use "cd $(srctree) && cd $(src)" rather than
|
||||
# "cd $(srctree)/$(src)" because under DKMS $(src) is an absolute path, and we
|
||||
# can't just use $(src) because for normal kernel builds this is relative to
|
||||
# $(srctree)
|
||||
$(obj)/generated_gator_src_md5.h: $(wildcard $(src)/gator_*.h $(src)/gator_*.c $(src)/mali/*.h)
|
||||
@$($(quiet)chk_events.h)
|
||||
$(Q)cd $(srctree) && cd $(src) ; $(CONFIG_SHELL) -c "echo 'static char * gator_src_md5 = \"'\`ls gator_*.c gator_*.h mali/*.h | grep -Ev '^(generated_gator_src_md5\.h|gator\.mod\.c)$$' | LC_ALL=C sort | xargs cat | md5sum | cut -b 1-32\`'\";'" > $(abspath $@)
|
||||
|
||||
else
|
||||
|
||||
all:
|
||||
@echo
|
||||
@echo "usage:"
|
||||
@echo " make -C <kernel_build_dir> M=\`pwd\` ARCH=arm CROSS_COMPILE=<...> modules"
|
||||
@echo
|
||||
$(error)
|
||||
|
||||
clean:
|
||||
rm -f *.o .*.cmd generated_gator_src_md5.h modules.order Module.symvers gator.ko gator.mod.c
|
||||
rm -rf .tmp_versions
|
||||
|
||||
endif
|
||||
143
drivers/gator/gator.h
Normal file
143
drivers/gator/gator.h
Normal file
@@ -0,0 +1,143 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef GATOR_H_
|
||||
#define GATOR_H_
|
||||
|
||||
#include <linux/version.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/list.h>
|
||||
|
||||
#define GATOR_PERF_PMU_SUPPORT (defined(CONFIG_PERF_EVENTS) && (!(defined(__arm__) || defined(__aarch64__)) || defined(CONFIG_HW_PERF_EVENTS)))
|
||||
#define GATOR_CPU_FREQ_SUPPORT defined(CONFIG_CPU_FREQ)
|
||||
#define GATOR_IKS_SUPPORT defined(CONFIG_BL_SWITCHER)
|
||||
|
||||
/* cpu ids */
|
||||
#define CORTEX_A5 0x41c05
|
||||
#define CORTEX_A9 0x41c09
|
||||
#define OTHER 0xfffff
|
||||
|
||||
#define MAXSIZE_CORE_NAME 32
|
||||
|
||||
struct gator_cpu {
|
||||
struct list_head list;
|
||||
unsigned long cpuid;
|
||||
unsigned long pmnc_counters;
|
||||
/* Human readable name */
|
||||
char core_name[MAXSIZE_CORE_NAME];
|
||||
/* gatorfs event and Perf PMU name */
|
||||
char pmnc_name[MAXSIZE_CORE_NAME];
|
||||
/* compatible from Documentation/devicetree/bindings/arm/cpus.txt */
|
||||
char dt_name[MAXSIZE_CORE_NAME];
|
||||
};
|
||||
|
||||
/* clusters */
|
||||
#define GATOR_CLUSTER_COUNT 4
|
||||
|
||||
extern const struct gator_cpu *gator_clusters[GATOR_CLUSTER_COUNT];
|
||||
extern int gator_clusterids[NR_CPUS];
|
||||
extern int gator_cluster_count;
|
||||
|
||||
/* gpu enums */
|
||||
#define MALI_4xx 1
|
||||
#define MALI_MIDGARD_OR_BIFROST 2
|
||||
|
||||
/******************************************************************************
|
||||
* Filesystem
|
||||
******************************************************************************/
|
||||
struct dentry *gatorfs_mkdir(struct super_block *sb, struct dentry *root,
|
||||
char const *name);
|
||||
|
||||
int gatorfs_create_ulong(struct super_block *sb, struct dentry *root,
|
||||
char const *name, unsigned long *val);
|
||||
|
||||
int gatorfs_create_ro_ulong(struct super_block *sb, struct dentry *root,
|
||||
char const *name, unsigned long *val);
|
||||
|
||||
/******************************************************************************
|
||||
* Tracepoints
|
||||
******************************************************************************/
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 4, 0)
|
||||
# error Kernels prior to 3.4 not supported. DS-5 v5.21 and earlier supported 2.6.32 and later.
|
||||
#elif LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0)
|
||||
# define GATOR_DEFINE_PROBE(probe_name, proto) \
|
||||
static void probe_##probe_name(void *data, PARAMS(proto))
|
||||
# define GATOR_REGISTER_TRACE(probe_name) \
|
||||
register_trace_##probe_name(probe_##probe_name, NULL)
|
||||
# define GATOR_UNREGISTER_TRACE(probe_name) \
|
||||
unregister_trace_##probe_name(probe_##probe_name, NULL)
|
||||
#else
|
||||
# define GATOR_DEFINE_PROBE(probe_name, proto) \
|
||||
extern struct tracepoint *gator_tracepoint_##probe_name; \
|
||||
static void probe_##probe_name(void *data, PARAMS(proto))
|
||||
# define GATOR_REGISTER_TRACE(probe_name) \
|
||||
((gator_tracepoint_##probe_name == NULL) || tracepoint_probe_register(gator_tracepoint_##probe_name, probe_##probe_name, NULL))
|
||||
# define GATOR_UNREGISTER_TRACE(probe_name) \
|
||||
tracepoint_probe_unregister(gator_tracepoint_##probe_name, probe_##probe_name, NULL)
|
||||
#endif
|
||||
|
||||
/******************************************************************************
|
||||
* Events
|
||||
******************************************************************************/
|
||||
struct gator_interface {
|
||||
const char *const name;
|
||||
/* Complementary function to init */
|
||||
void (*shutdown)(void);
|
||||
int (*create_files)(struct super_block *sb, struct dentry *root);
|
||||
int (*start)(void);
|
||||
/* Complementary function to start */
|
||||
void (*stop)(void);
|
||||
int (*online)(int **buffer, bool migrate);
|
||||
int (*offline)(int **buffer, bool migrate);
|
||||
/* called in process context but may not be running on core 'cpu' */
|
||||
void (*online_dispatch)(int cpu, bool migrate);
|
||||
/* called in process context but may not be running on core 'cpu' */
|
||||
void (*offline_dispatch)(int cpu, bool migrate);
|
||||
int (*read)(int **buffer, bool sched_switch);
|
||||
int (*read64)(long long **buffer, bool sched_switch);
|
||||
int (*read_proc)(long long **buffer, struct task_struct *);
|
||||
struct list_head list;
|
||||
};
|
||||
|
||||
u64 gator_get_time(void);
|
||||
int gator_events_install(struct gator_interface *interface);
|
||||
int gator_events_get_key(void);
|
||||
u32 gator_cpuid(void);
|
||||
|
||||
void gator_marshal_activity_switch(int core, int key, int activity, int pid);
|
||||
|
||||
#if !GATOR_IKS_SUPPORT
|
||||
|
||||
#define get_physical_cpu() smp_processor_id()
|
||||
#define lcpu_to_pcpu(lcpu) lcpu
|
||||
#define pcpu_to_lcpu(pcpu) pcpu
|
||||
|
||||
#else
|
||||
|
||||
#define get_physical_cpu() lcpu_to_pcpu(get_logical_cpu())
|
||||
int lcpu_to_pcpu(const int lcpu);
|
||||
int pcpu_to_lcpu(const int pcpu);
|
||||
|
||||
#endif
|
||||
|
||||
#define get_logical_cpu() smp_processor_id()
|
||||
#define on_primary_core() (get_logical_cpu() == 0)
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 15, 0)
|
||||
# define setup_deferrable_timer_on_stack(TIMER, HANDLER, VALUE) \
|
||||
timer_setup((TIMER), (HANDLER), TIMER_DEFERRABLE)
|
||||
# define destroy_timer_on_stack(TIMER)
|
||||
# define DECLARE_TIMER_HANDLER(NAME) \
|
||||
void NAME (struct timer_list * timers)
|
||||
#else
|
||||
# define DECLARE_TIMER_HANDLER(NAME) \
|
||||
void NAME (unsigned long arg)
|
||||
#endif
|
||||
|
||||
#endif /* GATOR_H_ */
|
||||
189
drivers/gator/gator_annotate.c
Normal file
189
drivers/gator/gator_annotate.c
Normal file
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/mm.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/uaccess.h>
|
||||
#include <asm/current.h>
|
||||
#include <linux/spinlock.h>
|
||||
|
||||
static DEFINE_SPINLOCK(annotate_lock);
|
||||
static bool collect_annotations;
|
||||
|
||||
static int annotate_copy(struct file *file, char const __user *buf, size_t count)
|
||||
{
|
||||
int cpu = 0;
|
||||
int write = per_cpu(gator_buffer_write, cpu)[ANNOTATE_BUF];
|
||||
|
||||
if (file == NULL) {
|
||||
/* copy from kernel */
|
||||
memcpy(&per_cpu(gator_buffer, cpu)[ANNOTATE_BUF][write], buf, count);
|
||||
} else {
|
||||
/* copy from user space */
|
||||
if (copy_from_user(&per_cpu(gator_buffer, cpu)[ANNOTATE_BUF][write], buf, count) != 0)
|
||||
return -1;
|
||||
}
|
||||
per_cpu(gator_buffer_write, cpu)[ANNOTATE_BUF] = (write + count) & gator_buffer_mask[ANNOTATE_BUF];
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t annotate_write(struct file *file, char const __user *buf, size_t count_orig, loff_t *offset)
|
||||
{
|
||||
int pid, cpu, header_size, available, contiguous, length1, length2, size, count = count_orig & 0x7fffffff;
|
||||
bool interrupt_context;
|
||||
|
||||
if (*offset)
|
||||
return -EINVAL;
|
||||
|
||||
interrupt_context = in_interrupt();
|
||||
/* Annotations are not supported in interrupt context, but may work
|
||||
* if you comment out the the next four lines of code. By doing so,
|
||||
* annotations in interrupt context can result in deadlocks and lost
|
||||
* data.
|
||||
*/
|
||||
if (interrupt_context) {
|
||||
pr_warning("gator: Annotations are not supported in interrupt context. Edit gator_annotate.c in the gator driver to enable annotations in interrupt context.\n");
|
||||
return -EINVAL;
|
||||
}
|
||||
|
||||
retry:
|
||||
/* synchronize between cores and with collect_annotations */
|
||||
spin_lock(&annotate_lock);
|
||||
|
||||
if (!collect_annotations) {
|
||||
/* Not collecting annotations, tell the caller everything was written */
|
||||
size = count_orig;
|
||||
goto annotate_write_out;
|
||||
}
|
||||
|
||||
/* Annotation only uses a single per-cpu buffer as the data must be in order to the engine */
|
||||
cpu = 0;
|
||||
|
||||
if (current == NULL)
|
||||
pid = 0;
|
||||
else
|
||||
pid = current->pid;
|
||||
|
||||
/* determine total size of the payload */
|
||||
header_size = MAXSIZE_PACK32 * 3 + MAXSIZE_PACK64;
|
||||
available = buffer_bytes_available(cpu, ANNOTATE_BUF) - header_size;
|
||||
size = count < available ? count : available;
|
||||
|
||||
if (size <= 0) {
|
||||
/* Buffer is full, wait until space is available */
|
||||
spin_unlock(&annotate_lock);
|
||||
|
||||
/* Drop the annotation as blocking is not allowed in interrupt context */
|
||||
if (interrupt_context)
|
||||
return -EINVAL;
|
||||
|
||||
wait_event_interruptible(gator_annotate_wait, buffer_bytes_available(cpu, ANNOTATE_BUF) > header_size || !collect_annotations);
|
||||
|
||||
/* Check to see if a signal is pending */
|
||||
if (signal_pending(current))
|
||||
return -EINTR;
|
||||
|
||||
goto retry;
|
||||
}
|
||||
|
||||
/* synchronize shared variables annotateBuf and annotatePos */
|
||||
if (per_cpu(gator_buffer, cpu)[ANNOTATE_BUF]) {
|
||||
u64 time = gator_get_time();
|
||||
|
||||
gator_buffer_write_packed_int(cpu, ANNOTATE_BUF, get_physical_cpu());
|
||||
gator_buffer_write_packed_int(cpu, ANNOTATE_BUF, pid);
|
||||
gator_buffer_write_packed_int64(cpu, ANNOTATE_BUF, time);
|
||||
gator_buffer_write_packed_int(cpu, ANNOTATE_BUF, size);
|
||||
|
||||
/* determine the sizes to capture, length1 + length2 will equal size */
|
||||
contiguous = contiguous_space_available(cpu, ANNOTATE_BUF);
|
||||
if (size < contiguous) {
|
||||
length1 = size;
|
||||
length2 = 0;
|
||||
} else {
|
||||
length1 = contiguous;
|
||||
length2 = size - contiguous;
|
||||
}
|
||||
|
||||
if (annotate_copy(file, buf, length1) != 0) {
|
||||
size = -EINVAL;
|
||||
goto annotate_write_out;
|
||||
}
|
||||
|
||||
if (length2 > 0 && annotate_copy(file, &buf[length1], length2) != 0) {
|
||||
size = -EINVAL;
|
||||
goto annotate_write_out;
|
||||
}
|
||||
|
||||
/* Check and commit; commit is set to occur once buffer is 3/4 full */
|
||||
buffer_check(cpu, ANNOTATE_BUF, time);
|
||||
}
|
||||
|
||||
annotate_write_out:
|
||||
spin_unlock(&annotate_lock);
|
||||
|
||||
/* return the number of bytes written */
|
||||
return size;
|
||||
}
|
||||
|
||||
#include "gator_annotate_kernel.c"
|
||||
|
||||
static int annotate_release(struct inode *inode, struct file *file)
|
||||
{
|
||||
int cpu = 0;
|
||||
|
||||
/* synchronize between cores */
|
||||
spin_lock(&annotate_lock);
|
||||
|
||||
if (per_cpu(gator_buffer, cpu)[ANNOTATE_BUF] && buffer_check_space(cpu, ANNOTATE_BUF, MAXSIZE_PACK64 + 3 * MAXSIZE_PACK32)) {
|
||||
uint32_t pid = current->pid;
|
||||
|
||||
gator_buffer_write_packed_int(cpu, ANNOTATE_BUF, get_physical_cpu());
|
||||
gator_buffer_write_packed_int(cpu, ANNOTATE_BUF, pid);
|
||||
/* time */
|
||||
gator_buffer_write_packed_int64(cpu, ANNOTATE_BUF, 0);
|
||||
/* size */
|
||||
gator_buffer_write_packed_int(cpu, ANNOTATE_BUF, 0);
|
||||
}
|
||||
|
||||
/* Check and commit; commit is set to occur once buffer is 3/4 full */
|
||||
buffer_check(cpu, ANNOTATE_BUF, gator_get_time());
|
||||
|
||||
spin_unlock(&annotate_lock);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations annotate_fops = {
|
||||
.write = annotate_write,
|
||||
.release = annotate_release
|
||||
};
|
||||
|
||||
static int gator_annotate_create_files(struct super_block *sb, struct dentry *root)
|
||||
{
|
||||
return gatorfs_create_file_perm(sb, root, "annotate", &annotate_fops, 0666);
|
||||
}
|
||||
|
||||
static int gator_annotate_start(void)
|
||||
{
|
||||
collect_annotations = true;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gator_annotate_stop(void)
|
||||
{
|
||||
/* the spinlock here will ensure that when this function exits, we are not in the middle of an annotation */
|
||||
spin_lock(&annotate_lock);
|
||||
collect_annotations = false;
|
||||
wake_up(&gator_annotate_wait);
|
||||
spin_unlock(&annotate_lock);
|
||||
}
|
||||
200
drivers/gator/gator_annotate_kernel.c
Normal file
200
drivers/gator/gator_annotate_kernel.c
Normal file
@@ -0,0 +1,200 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2012-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#define ESCAPE_CODE 0x1c
|
||||
#define STRING_ANNOTATION 0x06
|
||||
#define NAME_CHANNEL_ANNOTATION 0x07
|
||||
#define NAME_GROUP_ANNOTATION 0x08
|
||||
#define VISUAL_ANNOTATION 0x04
|
||||
#define MARKER_ANNOTATION 0x05
|
||||
|
||||
static void kannotate_write(const char *ptr, unsigned int size)
|
||||
{
|
||||
int retval;
|
||||
int pos = 0;
|
||||
loff_t offset = 0;
|
||||
|
||||
while (pos < size) {
|
||||
retval = annotate_write(NULL, &ptr[pos], size - pos, &offset);
|
||||
if (retval < 0) {
|
||||
pr_warning("gator: kannotate_write failed with return value %d\n", retval);
|
||||
return;
|
||||
}
|
||||
pos += retval;
|
||||
}
|
||||
}
|
||||
|
||||
static void marshal_u16(char *buf, u16 val)
|
||||
{
|
||||
buf[0] = val & 0xff;
|
||||
buf[1] = (val >> 8) & 0xff;
|
||||
}
|
||||
|
||||
static void marshal_u32(char *buf, u32 val)
|
||||
{
|
||||
buf[0] = val & 0xff;
|
||||
buf[1] = (val >> 8) & 0xff;
|
||||
buf[2] = (val >> 16) & 0xff;
|
||||
buf[3] = (val >> 24) & 0xff;
|
||||
}
|
||||
|
||||
void gator_annotate_channel(int channel, const char *str)
|
||||
{
|
||||
const u16 str_size = strlen(str) & 0xffff;
|
||||
char header[8];
|
||||
|
||||
header[0] = ESCAPE_CODE;
|
||||
header[1] = STRING_ANNOTATION;
|
||||
marshal_u32(header + 2, channel);
|
||||
marshal_u16(header + 6, str_size);
|
||||
kannotate_write(header, sizeof(header));
|
||||
kannotate_write(str, str_size);
|
||||
}
|
||||
EXPORT_SYMBOL(gator_annotate_channel);
|
||||
|
||||
void gator_annotate(const char *str)
|
||||
{
|
||||
gator_annotate_channel(0, str);
|
||||
}
|
||||
EXPORT_SYMBOL(gator_annotate);
|
||||
|
||||
void gator_annotate_channel_color(int channel, int color, const char *str)
|
||||
{
|
||||
const u16 str_size = (strlen(str) + 4) & 0xffff;
|
||||
char header[12];
|
||||
|
||||
header[0] = ESCAPE_CODE;
|
||||
header[1] = STRING_ANNOTATION;
|
||||
marshal_u32(header + 2, channel);
|
||||
marshal_u16(header + 6, str_size);
|
||||
marshal_u32(header + 8, color);
|
||||
kannotate_write(header, sizeof(header));
|
||||
kannotate_write(str, str_size - 4);
|
||||
}
|
||||
EXPORT_SYMBOL(gator_annotate_channel_color);
|
||||
|
||||
void gator_annotate_color(int color, const char *str)
|
||||
{
|
||||
gator_annotate_channel_color(0, color, str);
|
||||
}
|
||||
EXPORT_SYMBOL(gator_annotate_color);
|
||||
|
||||
void gator_annotate_channel_end(int channel)
|
||||
{
|
||||
char header[8];
|
||||
|
||||
header[0] = ESCAPE_CODE;
|
||||
header[1] = STRING_ANNOTATION;
|
||||
marshal_u32(header + 2, channel);
|
||||
marshal_u16(header + 6, 0);
|
||||
kannotate_write(header, sizeof(header));
|
||||
}
|
||||
EXPORT_SYMBOL(gator_annotate_channel_end);
|
||||
|
||||
void gator_annotate_end(void)
|
||||
{
|
||||
gator_annotate_channel_end(0);
|
||||
}
|
||||
EXPORT_SYMBOL(gator_annotate_end);
|
||||
|
||||
void gator_annotate_name_channel(int channel, int group, const char *str)
|
||||
{
|
||||
const u16 str_size = strlen(str) & 0xffff;
|
||||
char header[12];
|
||||
|
||||
header[0] = ESCAPE_CODE;
|
||||
header[1] = NAME_CHANNEL_ANNOTATION;
|
||||
marshal_u32(header + 2, channel);
|
||||
marshal_u32(header + 6, group);
|
||||
marshal_u16(header + 10, str_size);
|
||||
kannotate_write(header, sizeof(header));
|
||||
kannotate_write(str, str_size);
|
||||
}
|
||||
EXPORT_SYMBOL(gator_annotate_name_channel);
|
||||
|
||||
void gator_annotate_name_group(int group, const char *str)
|
||||
{
|
||||
const u16 str_size = strlen(str) & 0xffff;
|
||||
char header[8];
|
||||
|
||||
header[0] = ESCAPE_CODE;
|
||||
header[1] = NAME_GROUP_ANNOTATION;
|
||||
marshal_u32(header + 2, group);
|
||||
marshal_u16(header + 6, str_size);
|
||||
kannotate_write(header, sizeof(header));
|
||||
kannotate_write(str, str_size);
|
||||
}
|
||||
EXPORT_SYMBOL(gator_annotate_name_group);
|
||||
|
||||
void gator_annotate_visual(const char *data, unsigned int length, const char *str)
|
||||
{
|
||||
const u16 str_size = strlen(str) & 0xffff;
|
||||
char header[4];
|
||||
char header_length[4];
|
||||
|
||||
header[0] = ESCAPE_CODE;
|
||||
header[1] = VISUAL_ANNOTATION;
|
||||
marshal_u16(header + 2, str_size);
|
||||
marshal_u32(header_length, length);
|
||||
kannotate_write(header, sizeof(header));
|
||||
kannotate_write(str, str_size);
|
||||
kannotate_write(header_length, sizeof(header_length));
|
||||
kannotate_write(data, length);
|
||||
}
|
||||
EXPORT_SYMBOL(gator_annotate_visual);
|
||||
|
||||
void gator_annotate_marker(void)
|
||||
{
|
||||
char header[4];
|
||||
|
||||
header[0] = ESCAPE_CODE;
|
||||
header[1] = MARKER_ANNOTATION;
|
||||
marshal_u16(header + 2, 0);
|
||||
kannotate_write(header, sizeof(header));
|
||||
}
|
||||
EXPORT_SYMBOL(gator_annotate_marker);
|
||||
|
||||
void gator_annotate_marker_str(const char *str)
|
||||
{
|
||||
const u16 str_size = strlen(str) & 0xffff;
|
||||
char header[4];
|
||||
|
||||
header[0] = ESCAPE_CODE;
|
||||
header[1] = MARKER_ANNOTATION;
|
||||
marshal_u16(header + 2, str_size);
|
||||
kannotate_write(header, sizeof(header));
|
||||
kannotate_write(str, str_size);
|
||||
}
|
||||
EXPORT_SYMBOL(gator_annotate_marker_str);
|
||||
|
||||
void gator_annotate_marker_color(int color)
|
||||
{
|
||||
char header[8];
|
||||
|
||||
header[0] = ESCAPE_CODE;
|
||||
header[1] = MARKER_ANNOTATION;
|
||||
marshal_u16(header + 2, 4);
|
||||
marshal_u32(header + 4, color);
|
||||
kannotate_write(header, sizeof(header));
|
||||
}
|
||||
EXPORT_SYMBOL(gator_annotate_marker_color);
|
||||
|
||||
void gator_annotate_marker_color_str(int color, const char *str)
|
||||
{
|
||||
const u16 str_size = (strlen(str) + 4) & 0xffff;
|
||||
char header[8];
|
||||
|
||||
header[0] = ESCAPE_CODE;
|
||||
header[1] = MARKER_ANNOTATION;
|
||||
marshal_u16(header + 2, str_size);
|
||||
marshal_u32(header + 4, color);
|
||||
kannotate_write(header, sizeof(header));
|
||||
kannotate_write(str, str_size - 4);
|
||||
}
|
||||
EXPORT_SYMBOL(gator_annotate_marker_color_str);
|
||||
330
drivers/gator/gator_backtrace.c
Normal file
330
drivers/gator/gator_backtrace.c
Normal file
@@ -0,0 +1,330 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#if defined(__arm__) || defined(__aarch64__)
|
||||
#define GATOR_KERNEL_UNWINDING 1
|
||||
#define GATOR_USER_UNWINDING 1
|
||||
#else
|
||||
#define GATOR_KERNEL_UNWINDING 0
|
||||
#define GATOR_USER_UNWINDING 0
|
||||
#endif
|
||||
|
||||
/* on 4.9 walk_stackframe was unexported so use save_stack_trace instead */
|
||||
#if defined(MODULE) && defined(__aarch64__) && (LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0))
|
||||
#define GATOR_KERNEL_UNWINDING_USE_WALK_STACKFRAME 0
|
||||
#else
|
||||
#define GATOR_KERNEL_UNWINDING_USE_WALK_STACKFRAME 1
|
||||
#endif
|
||||
|
||||
#if (!GATOR_KERNEL_UNWINDING_USE_WALK_STACKFRAME) && !defined(CONFIG_STACKTRACE)
|
||||
#error "CONFIG_STACKTRACE is required for kernel unwinding"
|
||||
#endif
|
||||
|
||||
/* Uncomment the following line to enable kernel stack unwinding within gator, note it can also be defined from the Makefile */
|
||||
/* #define GATOR_KERNEL_STACK_UNWINDING */
|
||||
|
||||
#if GATOR_KERNEL_UNWINDING
|
||||
|
||||
#include <asm/stacktrace.h>
|
||||
#if !GATOR_KERNEL_UNWINDING_USE_WALK_STACKFRAME
|
||||
#include <linux/stacktrace.h>
|
||||
#endif
|
||||
|
||||
#if !defined(GATOR_KERNEL_STACK_UNWINDING)
|
||||
/* Disabled by default */
|
||||
MODULE_PARM_DESC(kernel_stack_unwinding, "Allow kernel stack unwinding.");
|
||||
static bool kernel_stack_unwinding;
|
||||
module_param(kernel_stack_unwinding, bool, 0644);
|
||||
#endif
|
||||
|
||||
#if GATOR_KERNEL_UNWINDING_USE_WALK_STACKFRAME
|
||||
|
||||
/* -------------------------- KERNEL UNWINDING USING walk_stackframe -------------------------- */
|
||||
|
||||
static int report_trace(struct stackframe *frame, void *d)
|
||||
{
|
||||
unsigned int *depth = d, cookie = NO_COOKIE;
|
||||
unsigned long addr = frame->pc;
|
||||
|
||||
if (*depth) {
|
||||
#if defined(MODULE)
|
||||
unsigned int cpu = get_physical_cpu();
|
||||
struct module *mod = __module_address(addr);
|
||||
|
||||
if (mod) {
|
||||
cookie = get_cookie(cpu, current, mod->name, false);
|
||||
addr = addr -
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0)
|
||||
(unsigned long)mod->module_core;
|
||||
#else
|
||||
(unsigned long)mod->core_layout.base;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
cookie = NO_COOKIE;
|
||||
}
|
||||
#endif
|
||||
marshal_backtrace(addr & ~1, cookie, 1);
|
||||
(*depth)--;
|
||||
}
|
||||
|
||||
return *depth == 0;
|
||||
}
|
||||
|
||||
static void kernel_backtrace(int cpu, struct pt_regs *const regs)
|
||||
{
|
||||
#ifdef GATOR_KERNEL_STACK_UNWINDING
|
||||
int depth = gator_backtrace_depth;
|
||||
#else
|
||||
int depth = (kernel_stack_unwinding ? gator_backtrace_depth : 1);
|
||||
#endif
|
||||
struct stackframe frame;
|
||||
|
||||
if (depth == 0)
|
||||
depth = 1;
|
||||
#if defined(__arm__)
|
||||
frame.fp = regs->ARM_fp;
|
||||
frame.sp = regs->ARM_sp;
|
||||
frame.lr = regs->ARM_lr;
|
||||
frame.pc = regs->ARM_pc;
|
||||
#else
|
||||
frame.fp = regs->regs[29];
|
||||
frame.sp = regs->sp;
|
||||
frame.pc = regs->pc;
|
||||
#endif
|
||||
|
||||
#if defined(__aarch64__) && LINUX_VERSION_CODE >= KERNEL_VERSION(4, 5, 0)
|
||||
walk_stackframe(current, &frame, report_trace, &depth);
|
||||
#else
|
||||
walk_stackframe(&frame, report_trace, &depth);
|
||||
#endif
|
||||
}
|
||||
|
||||
#else /* GATOR_KERNEL_UNWINDING_USE_WALK_STACKFRAME */
|
||||
|
||||
/* -------------------------- KERNEL UNWINDING USING save_stack_trace_regs -------------------------- */
|
||||
|
||||
#define GATOR_KMOD_STACK_MAX_SIZE 32
|
||||
struct gator_kmod_stack { unsigned long addresses[GATOR_KMOD_STACK_MAX_SIZE]; };
|
||||
static DEFINE_PER_CPU(struct gator_kmod_stack, gator_kmod_stack);
|
||||
|
||||
static void report_trace(unsigned int cpu, struct stack_trace * trace)
|
||||
{
|
||||
unsigned int cookie = NO_COOKIE;
|
||||
unsigned int index;
|
||||
|
||||
for (index = 0; index < trace->nr_entries; ++index) {
|
||||
unsigned long addr = trace->entries[index];
|
||||
#if defined(MODULE)
|
||||
struct module * mod = __module_address(addr);
|
||||
if (mod) {
|
||||
cookie = get_cookie(cpu, current, mod->name, false);
|
||||
addr = addr -
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0)
|
||||
(unsigned long) mod->module_core;
|
||||
#else
|
||||
(unsigned long) mod->core_layout.base;
|
||||
#endif
|
||||
}
|
||||
else {
|
||||
cookie = NO_COOKIE;
|
||||
}
|
||||
#endif
|
||||
marshal_backtrace(addr & ~1, cookie, 1);
|
||||
}
|
||||
}
|
||||
|
||||
static void kernel_backtrace(int cpu, struct pt_regs *const regs)
|
||||
{
|
||||
struct stack_trace trace;
|
||||
trace.skip = 0;
|
||||
trace.nr_entries = 0;
|
||||
trace.entries = per_cpu(gator_kmod_stack, cpu).addresses;
|
||||
#ifdef GATOR_KERNEL_STACK_UNWINDING
|
||||
trace.max_entries = gator_backtrace_depth;
|
||||
#else
|
||||
trace.max_entries = (kernel_stack_unwinding ? gator_backtrace_depth : 1);
|
||||
#endif
|
||||
if (trace.max_entries < 1) {
|
||||
trace.max_entries = 1;
|
||||
}
|
||||
else if (trace.max_entries > GATOR_KMOD_STACK_MAX_SIZE) {
|
||||
trace.max_entries = GATOR_KMOD_STACK_MAX_SIZE;
|
||||
}
|
||||
|
||||
save_stack_trace(&trace);
|
||||
|
||||
report_trace(cpu, &trace);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
#else /* GATOR_KERNEL_UNWINDING */
|
||||
|
||||
/* -------------------------- NO KERNEL UNWINDING -------------------------- */
|
||||
|
||||
static void kernel_backtrace(int cpu, struct pt_regs *const regs)
|
||||
{
|
||||
marshal_backtrace(PC_REG & ~1, NO_COOKIE, 1);
|
||||
}
|
||||
|
||||
#endif /* GATOR_KERNEL_UNWINDING */
|
||||
|
||||
|
||||
/* -------------------------- USER SPACE UNWINDING -------------------------- */
|
||||
|
||||
static void gator_add_trace(int cpu, unsigned long address)
|
||||
{
|
||||
off_t offset = 0;
|
||||
unsigned long cookie = get_address_cookie(cpu, current, address & ~1, &offset);
|
||||
|
||||
if (cookie == NO_COOKIE || cookie == UNRESOLVED_COOKIE)
|
||||
offset = address;
|
||||
|
||||
marshal_backtrace(offset & ~1, cookie, 0);
|
||||
}
|
||||
|
||||
#if GATOR_USER_UNWINDING
|
||||
|
||||
/*
|
||||
* EABI backtrace stores {fp,lr} on the stack.
|
||||
*/
|
||||
struct stack_frame_eabi {
|
||||
union {
|
||||
struct {
|
||||
unsigned long fp;
|
||||
/* May be the fp in the case of a leaf function or clang */
|
||||
unsigned long lr;
|
||||
/* If lr is really the fp, lr2 is the corresponding lr */
|
||||
unsigned long lr2;
|
||||
};
|
||||
/* Used to read 32 bit fp/lr from a 64 bit kernel */
|
||||
struct {
|
||||
u32 fp_32;
|
||||
/* same as lr above */
|
||||
u32 lr_32;
|
||||
/* same as lr2 above */
|
||||
u32 lr2_32;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
static void arm_backtrace_eabi(int cpu, struct pt_regs *const regs, unsigned int depth)
|
||||
{
|
||||
struct stack_frame_eabi *curr;
|
||||
struct stack_frame_eabi bufcurr;
|
||||
#if defined(__arm__)
|
||||
const bool is_compat = false;
|
||||
unsigned long fp = regs->ARM_fp;
|
||||
unsigned long sp = regs->ARM_sp;
|
||||
unsigned long lr = regs->ARM_lr;
|
||||
const int gcc_frame_offset = sizeof(unsigned long);
|
||||
#else
|
||||
/* Is userspace aarch32 (32 bit) */
|
||||
const bool is_compat = compat_user_mode(regs);
|
||||
unsigned long fp = (is_compat ? regs->regs[11] : regs->regs[29]);
|
||||
unsigned long sp = (is_compat ? regs->compat_sp : regs->sp);
|
||||
unsigned long lr = (is_compat ? regs->compat_lr : regs->regs[30]);
|
||||
const int gcc_frame_offset = (is_compat ? sizeof(u32) : 0);
|
||||
#endif
|
||||
/* clang frame offset is always zero */
|
||||
int is_user_mode = user_mode(regs);
|
||||
|
||||
/* pc (current function) has already been added */
|
||||
|
||||
if (!is_user_mode)
|
||||
return;
|
||||
|
||||
/* Add the lr (parent function), entry preamble may not have
|
||||
* executed
|
||||
*/
|
||||
gator_add_trace(cpu, lr);
|
||||
|
||||
/* check fp is valid */
|
||||
if (fp == 0 || fp < sp)
|
||||
return;
|
||||
|
||||
/* Get the current stack frame */
|
||||
curr = (struct stack_frame_eabi *)(fp - gcc_frame_offset);
|
||||
if ((unsigned long)curr & 3)
|
||||
return;
|
||||
|
||||
while (depth-- && curr) {
|
||||
if (!access_ok(VERIFY_READ, curr, sizeof(struct stack_frame_eabi)) ||
|
||||
__copy_from_user_inatomic(&bufcurr, curr, sizeof(struct stack_frame_eabi))) {
|
||||
return;
|
||||
}
|
||||
|
||||
fp = (is_compat ? bufcurr.fp_32 : bufcurr.fp);
|
||||
lr = (is_compat ? bufcurr.lr_32 : bufcurr.lr);
|
||||
|
||||
#define calc_next(reg) ((reg) - gcc_frame_offset)
|
||||
/* Returns true if reg is a valid fp */
|
||||
#define validate_next(reg, curr) \
|
||||
((reg) != 0 && (calc_next(reg) & 3) == 0 && (unsigned long)(curr) < calc_next(reg))
|
||||
|
||||
/* Try lr from the stack as the fp because gcc leaf functions do
|
||||
* not push lr. If gcc_frame_offset is non-zero, the lr will also
|
||||
* be the clang fp. This assumes code is at a lower address than
|
||||
* the stack
|
||||
*/
|
||||
if (validate_next(lr, curr)) {
|
||||
fp = lr;
|
||||
lr = (is_compat ? bufcurr.lr2_32 : bufcurr.lr2);
|
||||
}
|
||||
|
||||
gator_add_trace(cpu, lr);
|
||||
|
||||
if (!validate_next(fp, curr))
|
||||
return;
|
||||
|
||||
/* Move to the next stack frame */
|
||||
curr = (struct stack_frame_eabi *)calc_next(fp);
|
||||
}
|
||||
}
|
||||
|
||||
#else /* GATOR_USER_UNWINDING */
|
||||
|
||||
static void arm_backtrace_eabi(int cpu, struct pt_regs *const regs, unsigned int depth)
|
||||
{
|
||||
/* NO OP */
|
||||
}
|
||||
|
||||
#endif /* GATOR_USER_UNWINDING */
|
||||
|
||||
/* -------------------------- STACK SAMPLING -------------------------- */
|
||||
|
||||
static void gator_add_sample(int cpu, struct pt_regs *const regs, u64 time)
|
||||
{
|
||||
bool in_kernel;
|
||||
unsigned long exec_cookie;
|
||||
|
||||
if (!regs)
|
||||
return;
|
||||
|
||||
in_kernel = !user_mode(regs);
|
||||
exec_cookie = get_exec_cookie(cpu, current);
|
||||
|
||||
if (!marshal_backtrace_header(exec_cookie, current->tgid, current->pid, time))
|
||||
return;
|
||||
|
||||
if (in_kernel) {
|
||||
kernel_backtrace(cpu, regs);
|
||||
} else {
|
||||
/* Cookie+PC */
|
||||
gator_add_trace(cpu, PC_REG);
|
||||
|
||||
/* Backtrace */
|
||||
if (gator_backtrace_depth)
|
||||
arm_backtrace_eabi(cpu, regs, gator_backtrace_depth);
|
||||
}
|
||||
|
||||
marshal_backtrace_footer(time);
|
||||
}
|
||||
171
drivers/gator/gator_buffer.c
Normal file
171
drivers/gator/gator_buffer.c
Normal file
@@ -0,0 +1,171 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
static void marshal_frame(int cpu, int buftype)
|
||||
{
|
||||
int frame;
|
||||
bool write_cpu;
|
||||
|
||||
if (!per_cpu(gator_buffer, cpu)[buftype])
|
||||
return;
|
||||
|
||||
switch (buftype) {
|
||||
case SUMMARY_BUF:
|
||||
write_cpu = false;
|
||||
frame = FRAME_SUMMARY;
|
||||
break;
|
||||
case BACKTRACE_BUF:
|
||||
write_cpu = true;
|
||||
frame = FRAME_BACKTRACE;
|
||||
break;
|
||||
case NAME_BUF:
|
||||
write_cpu = true;
|
||||
frame = FRAME_NAME;
|
||||
break;
|
||||
case COUNTER_BUF:
|
||||
write_cpu = false;
|
||||
frame = FRAME_COUNTER;
|
||||
break;
|
||||
case BLOCK_COUNTER_BUF:
|
||||
write_cpu = true;
|
||||
frame = FRAME_BLOCK_COUNTER;
|
||||
break;
|
||||
case ANNOTATE_BUF:
|
||||
write_cpu = false;
|
||||
frame = FRAME_ANNOTATE;
|
||||
break;
|
||||
case SCHED_TRACE_BUF:
|
||||
write_cpu = true;
|
||||
frame = FRAME_SCHED_TRACE;
|
||||
break;
|
||||
case IDLE_BUF:
|
||||
write_cpu = false;
|
||||
frame = FRAME_IDLE;
|
||||
break;
|
||||
case ACTIVITY_BUF:
|
||||
write_cpu = false;
|
||||
frame = FRAME_ACTIVITY;
|
||||
break;
|
||||
default:
|
||||
write_cpu = false;
|
||||
frame = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
/* add response type */
|
||||
if (gator_response_type > 0)
|
||||
gator_buffer_write_packed_int(cpu, buftype, gator_response_type);
|
||||
|
||||
/* leave space for 4-byte unpacked length */
|
||||
per_cpu(gator_buffer_write, cpu)[buftype] = (per_cpu(gator_buffer_write, cpu)[buftype] + sizeof(s32)) & gator_buffer_mask[buftype];
|
||||
|
||||
/* add frame type and core number */
|
||||
gator_buffer_write_packed_int(cpu, buftype, frame);
|
||||
if (write_cpu)
|
||||
gator_buffer_write_packed_int(cpu, buftype, cpu);
|
||||
}
|
||||
|
||||
static int buffer_bytes_available(int cpu, int buftype)
|
||||
{
|
||||
int remaining, filled;
|
||||
|
||||
filled = per_cpu(gator_buffer_write, cpu)[buftype] - per_cpu(gator_buffer_read, cpu)[buftype];
|
||||
if (filled < 0)
|
||||
filled += gator_buffer_size[buftype];
|
||||
|
||||
remaining = gator_buffer_size[buftype] - filled;
|
||||
|
||||
if (per_cpu(buffer_space_available, cpu)[buftype])
|
||||
/* Give some extra room; also allows space to insert the overflow error packet */
|
||||
remaining -= 200;
|
||||
else
|
||||
/* Hysteresis, prevents multiple overflow messages */
|
||||
remaining -= 2000;
|
||||
|
||||
return remaining;
|
||||
}
|
||||
|
||||
static bool buffer_check_space(int cpu, int buftype, int bytes)
|
||||
{
|
||||
int remaining = buffer_bytes_available(cpu, buftype);
|
||||
|
||||
if (remaining < bytes)
|
||||
per_cpu(buffer_space_available, cpu)[buftype] = false;
|
||||
else
|
||||
per_cpu(buffer_space_available, cpu)[buftype] = true;
|
||||
|
||||
return per_cpu(buffer_space_available, cpu)[buftype];
|
||||
}
|
||||
|
||||
static int contiguous_space_available(int cpu, int buftype)
|
||||
{
|
||||
int remaining = buffer_bytes_available(cpu, buftype);
|
||||
int contiguous = gator_buffer_size[buftype] - per_cpu(gator_buffer_write, cpu)[buftype];
|
||||
|
||||
if (remaining < contiguous)
|
||||
return remaining;
|
||||
return contiguous;
|
||||
}
|
||||
|
||||
static void gator_commit_buffer(int cpu, int buftype, u64 time)
|
||||
{
|
||||
int type_length, commit, length, byte;
|
||||
unsigned long flags;
|
||||
|
||||
if (!per_cpu(gator_buffer, cpu)[buftype])
|
||||
return;
|
||||
|
||||
/* post-populate the length, which does not include the response type length nor the length itself, i.e. only the length of the payload */
|
||||
local_irq_save(flags);
|
||||
type_length = gator_response_type ? 1 : 0;
|
||||
commit = per_cpu(gator_buffer_commit, cpu)[buftype];
|
||||
length = per_cpu(gator_buffer_write, cpu)[buftype] - commit;
|
||||
if (length < 0)
|
||||
length += gator_buffer_size[buftype];
|
||||
length = length - type_length - sizeof(s32);
|
||||
|
||||
if (length <= FRAME_HEADER_SIZE) {
|
||||
/* Nothing to write, only the frame header is present */
|
||||
local_irq_restore(flags);
|
||||
return;
|
||||
}
|
||||
|
||||
for (byte = 0; byte < sizeof(s32); byte++)
|
||||
per_cpu(gator_buffer, cpu)[buftype][(commit + type_length + byte) & gator_buffer_mask[buftype]] = (length >> byte * 8) & 0xFF;
|
||||
|
||||
per_cpu(gator_buffer_commit, cpu)[buftype] = per_cpu(gator_buffer_write, cpu)[buftype];
|
||||
|
||||
if (gator_live_rate > 0) {
|
||||
while (time > per_cpu(gator_buffer_commit_time, cpu))
|
||||
per_cpu(gator_buffer_commit_time, cpu) += gator_live_rate;
|
||||
}
|
||||
|
||||
marshal_frame(cpu, buftype);
|
||||
local_irq_restore(flags);
|
||||
|
||||
/* had to delay scheduling work as attempting to schedule work during the context switch is illegal in kernel versions 3.5 and greater */
|
||||
if (per_cpu(in_scheduler_context, cpu)) {
|
||||
#ifndef CONFIG_PREEMPT_RT_FULL
|
||||
/* mod_timer can not be used in interrupt context in RT-Preempt full */
|
||||
mod_timer(&gator_buffer_wake_up_timer, jiffies + 1);
|
||||
#endif
|
||||
} else {
|
||||
up(&gator_buffer_wake_sem);
|
||||
}
|
||||
}
|
||||
|
||||
static void buffer_check(int cpu, int buftype, u64 time)
|
||||
{
|
||||
int filled = per_cpu(gator_buffer_write, cpu)[buftype] - per_cpu(gator_buffer_commit, cpu)[buftype];
|
||||
|
||||
if (filled < 0)
|
||||
filled += gator_buffer_size[buftype];
|
||||
if (filled >= ((gator_buffer_size[buftype] * 3) / 4))
|
||||
gator_commit_buffer(cpu, buftype, time);
|
||||
}
|
||||
83
drivers/gator/gator_buffer_write.c
Normal file
83
drivers/gator/gator_buffer_write.c
Normal file
@@ -0,0 +1,83 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
static void gator_buffer_write_packed_int(int cpu, int buftype, int x)
|
||||
{
|
||||
uint32_t write = per_cpu(gator_buffer_write, cpu)[buftype];
|
||||
uint32_t mask = gator_buffer_mask[buftype];
|
||||
char *buffer = per_cpu(gator_buffer, cpu)[buftype];
|
||||
int packedBytes = 0;
|
||||
int more = true;
|
||||
|
||||
while (more) {
|
||||
/* low order 7 bits of x */
|
||||
char b = x & 0x7f;
|
||||
|
||||
x >>= 7;
|
||||
|
||||
if ((x == 0 && (b & 0x40) == 0) || (x == -1 && (b & 0x40) != 0))
|
||||
more = false;
|
||||
else
|
||||
b |= 0x80;
|
||||
|
||||
buffer[(write + packedBytes) & mask] = b;
|
||||
packedBytes++;
|
||||
}
|
||||
|
||||
per_cpu(gator_buffer_write, cpu)[buftype] = (write + packedBytes) & mask;
|
||||
}
|
||||
|
||||
static void gator_buffer_write_packed_int64(int cpu, int buftype, long long x)
|
||||
{
|
||||
uint32_t write = per_cpu(gator_buffer_write, cpu)[buftype];
|
||||
uint32_t mask = gator_buffer_mask[buftype];
|
||||
char *buffer = per_cpu(gator_buffer, cpu)[buftype];
|
||||
int packedBytes = 0;
|
||||
int more = true;
|
||||
|
||||
while (more) {
|
||||
/* low order 7 bits of x */
|
||||
char b = x & 0x7f;
|
||||
|
||||
x >>= 7;
|
||||
|
||||
if ((x == 0 && (b & 0x40) == 0) || (x == -1 && (b & 0x40) != 0))
|
||||
more = false;
|
||||
else
|
||||
b |= 0x80;
|
||||
|
||||
buffer[(write + packedBytes) & mask] = b;
|
||||
packedBytes++;
|
||||
}
|
||||
|
||||
per_cpu(gator_buffer_write, cpu)[buftype] = (write + packedBytes) & mask;
|
||||
}
|
||||
|
||||
static void gator_buffer_write_bytes(int cpu, int buftype, const char *x, int len)
|
||||
{
|
||||
int i;
|
||||
u32 write = per_cpu(gator_buffer_write, cpu)[buftype];
|
||||
u32 mask = gator_buffer_mask[buftype];
|
||||
char *buffer = per_cpu(gator_buffer, cpu)[buftype];
|
||||
|
||||
for (i = 0; i < len; i++) {
|
||||
buffer[write] = x[i];
|
||||
write = (write + 1) & mask;
|
||||
}
|
||||
|
||||
per_cpu(gator_buffer_write, cpu)[buftype] = write;
|
||||
}
|
||||
|
||||
static void gator_buffer_write_string(int cpu, int buftype, const char *x)
|
||||
{
|
||||
int len = strlen(x);
|
||||
|
||||
gator_buffer_write_packed_int(cpu, buftype, len);
|
||||
gator_buffer_write_bytes(cpu, buftype, x, len);
|
||||
}
|
||||
550
drivers/gator/gator_cookies.c
Normal file
550
drivers/gator/gator_cookies.c
Normal file
@@ -0,0 +1,550 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/mount.h>
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 11, 0)
|
||||
# include <linux/sched/mm.h>
|
||||
#endif
|
||||
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 10, 0)
|
||||
/* Kernel version 4.10.0 adds locked argument
|
||||
(See https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/include/linux/mm.h?id=5b56d49fc31dbb0487e14ead790fc81ca9fb2c99) */
|
||||
# define get_user_pages_remote(tsk,mm,start,nr_pages,write,force,pages,vmas) get_user_pages_remote(tsk,mm,start,nr_pages,((write) ? FOLL_WRITE : 0) | ((force) ? FOLL_FORCE : 0),pages,vmas,NULL)
|
||||
#elif LINUX_VERSION_CODE >= KERNEL_VERSION(4, 9, 0)
|
||||
/* Kernel version 4.9.0 removes write and force arguments from get_user_pages_remote and replaces with gup_flags instead
|
||||
(See https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/include/linux/mm.h?id=9beae1ea89305a9667ceaab6d0bf46a045ad71e7) */
|
||||
# define get_user_pages_remote(tsk,mm,start,nr_pages,write,force,pages,vmas) get_user_pages_remote(tsk,mm,start,nr_pages,((write) ? FOLL_WRITE : 0) | ((force) ? FOLL_FORCE : 0),pages,vmas)
|
||||
#elif LINUX_VERSION_CODE < KERNEL_VERSION(4, 6, 0)
|
||||
/* Kernel version 4.6.0 removes get_user_pages macro. We should use get_user_pages_remote anyway.
|
||||
(See https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit/include/linux/mm.h?id=c12d2da56d0e07d230968ee2305aaa86b93a6832) */
|
||||
# define get_user_pages_remote(tsk,mm,start,nr_pages,write,force,pages,vmas) get_user_pages(tsk,mm,start,nr_pages,write,force,pages,vmas)
|
||||
#endif
|
||||
|
||||
struct mount {
|
||||
struct mount *mnt_parent;
|
||||
struct dentry *mnt_mountpoint;
|
||||
struct vfsmount mnt;
|
||||
};
|
||||
|
||||
static inline struct mount *real_mount(struct vfsmount *mnt)
|
||||
{
|
||||
return container_of(mnt, struct mount, mnt);
|
||||
}
|
||||
|
||||
#define GET_MNT_ROOT(mount) ((mount)->mnt.mnt_root)
|
||||
|
||||
/* must be power of 2 */
|
||||
#define COOKIEMAP_ENTRIES 1024
|
||||
/* must be a power of 2 - 512/4 = 128 entries */
|
||||
#define TRANSLATE_BUFFER_SIZE 512
|
||||
#define TRANSLATE_TEXT_SIZE 256
|
||||
#define MAX_COLLISIONS 2
|
||||
|
||||
static uint32_t *gator_crc32_table;
|
||||
static unsigned int translate_buffer_mask;
|
||||
|
||||
struct cookie_args {
|
||||
struct task_struct *task;
|
||||
const char *text;
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(char *, translate_text);
|
||||
static DEFINE_PER_CPU(char *, scratch);
|
||||
static DEFINE_PER_CPU(uint32_t, cookie_next_key);
|
||||
static DEFINE_PER_CPU(uint64_t *, cookie_keys);
|
||||
static DEFINE_PER_CPU(uint32_t *, cookie_values);
|
||||
static DEFINE_PER_CPU(int, translate_buffer_read);
|
||||
static DEFINE_PER_CPU(int, translate_buffer_write);
|
||||
static DEFINE_PER_CPU(struct cookie_args *, translate_buffer);
|
||||
|
||||
static uint32_t get_cookie(int cpu, struct task_struct *task, const char *text, bool from_wq);
|
||||
static void wq_cookie_handler(struct work_struct *unused);
|
||||
static DECLARE_WORK(cookie_work, wq_cookie_handler);
|
||||
static struct timer_list app_process_wake_up_timer;
|
||||
static DECLARE_TIMER_HANDLER(app_process_wake_up_handler);
|
||||
|
||||
static uint32_t cookiemap_code(uint64_t value64)
|
||||
{
|
||||
uint32_t value = (uint32_t)((value64 >> 32) + value64);
|
||||
uint32_t cookiecode = (value >> 24) & 0xff;
|
||||
|
||||
cookiecode = cookiecode * 31 + ((value >> 16) & 0xff);
|
||||
cookiecode = cookiecode * 31 + ((value >> 8) & 0xff);
|
||||
cookiecode = cookiecode * 31 + ((value >> 0) & 0xff);
|
||||
cookiecode &= (COOKIEMAP_ENTRIES - 1);
|
||||
return cookiecode * MAX_COLLISIONS;
|
||||
}
|
||||
|
||||
static uint32_t gator_chksum_crc32(const char *data)
|
||||
{
|
||||
register unsigned long crc;
|
||||
const unsigned char *block = data;
|
||||
int i, length = strlen(data);
|
||||
|
||||
crc = 0xFFFFFFFF;
|
||||
for (i = 0; i < length; i++)
|
||||
crc = ((crc >> 8) & 0x00FFFFFF) ^ gator_crc32_table[(crc ^ *block++) & 0xFF];
|
||||
|
||||
return (crc ^ 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
/*
|
||||
* Exists
|
||||
* Pre: [0][1][v][3]..[n-1]
|
||||
* Post: [v][0][1][3]..[n-1]
|
||||
*/
|
||||
static uint32_t cookiemap_exists(uint64_t key)
|
||||
{
|
||||
unsigned long x, flags, retval = 0;
|
||||
int cpu = get_physical_cpu();
|
||||
uint32_t cookiecode = cookiemap_code(key);
|
||||
uint64_t *keys = &(per_cpu(cookie_keys, cpu)[cookiecode]);
|
||||
uint32_t *values = &(per_cpu(cookie_values, cpu)[cookiecode]);
|
||||
|
||||
/* Can be called from interrupt handler or from work queue */
|
||||
local_irq_save(flags);
|
||||
for (x = 0; x < MAX_COLLISIONS; x++) {
|
||||
if (keys[x] == key) {
|
||||
uint32_t value = values[x];
|
||||
|
||||
for (; x > 0; x--) {
|
||||
keys[x] = keys[x - 1];
|
||||
values[x] = values[x - 1];
|
||||
}
|
||||
keys[0] = key;
|
||||
values[0] = value;
|
||||
retval = value;
|
||||
break;
|
||||
}
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*
|
||||
* Add
|
||||
* Pre: [0][1][2][3]..[n-1]
|
||||
* Post: [v][0][1][2]..[n-2]
|
||||
*/
|
||||
static void cookiemap_add(uint64_t key, uint32_t value)
|
||||
{
|
||||
int cpu = get_physical_cpu();
|
||||
int cookiecode = cookiemap_code(key);
|
||||
uint64_t *keys = &(per_cpu(cookie_keys, cpu)[cookiecode]);
|
||||
uint32_t *values = &(per_cpu(cookie_values, cpu)[cookiecode]);
|
||||
int x;
|
||||
|
||||
for (x = MAX_COLLISIONS - 1; x > 0; x--) {
|
||||
keys[x] = keys[x - 1];
|
||||
values[x] = values[x - 1];
|
||||
}
|
||||
keys[0] = key;
|
||||
values[0] = value;
|
||||
}
|
||||
|
||||
#ifndef CONFIG_PREEMPT_RT_FULL
|
||||
static void translate_buffer_write_args(int cpu, struct task_struct *task, const char *text)
|
||||
{
|
||||
unsigned long flags;
|
||||
int write;
|
||||
int next_write;
|
||||
struct cookie_args *args;
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
write = per_cpu(translate_buffer_write, cpu);
|
||||
next_write = (write + 1) & translate_buffer_mask;
|
||||
|
||||
/* At least one entry must always remain available as when read == write, the queue is empty not full */
|
||||
if (next_write != per_cpu(translate_buffer_read, cpu)) {
|
||||
args = &per_cpu(translate_buffer, cpu)[write];
|
||||
args->task = task;
|
||||
args->text = text;
|
||||
get_task_struct(task);
|
||||
per_cpu(translate_buffer_write, cpu) = next_write;
|
||||
}
|
||||
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
#endif
|
||||
|
||||
static void translate_buffer_read_args(int cpu, struct cookie_args *args)
|
||||
{
|
||||
unsigned long flags;
|
||||
int read;
|
||||
|
||||
local_irq_save(flags);
|
||||
|
||||
read = per_cpu(translate_buffer_read, cpu);
|
||||
*args = per_cpu(translate_buffer, cpu)[read];
|
||||
per_cpu(translate_buffer_read, cpu) = (read + 1) & translate_buffer_mask;
|
||||
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
static void wq_cookie_handler(struct work_struct *unused)
|
||||
{
|
||||
struct cookie_args args;
|
||||
int cpu = get_physical_cpu(), cookie;
|
||||
|
||||
mutex_lock(&start_mutex);
|
||||
|
||||
if (gator_started != 0) {
|
||||
while (per_cpu(translate_buffer_read, cpu) != per_cpu(translate_buffer_write, cpu)) {
|
||||
translate_buffer_read_args(cpu, &args);
|
||||
cookie = get_cookie(cpu, args.task, args.text, true);
|
||||
marshal_link(cookie, args.task->tgid, args.task->pid);
|
||||
put_task_struct(args.task);
|
||||
}
|
||||
}
|
||||
|
||||
mutex_unlock(&start_mutex);
|
||||
}
|
||||
|
||||
static DECLARE_TIMER_HANDLER(app_process_wake_up_handler)
|
||||
{
|
||||
/* had to delay scheduling work as attempting to schedule work during the context switch is illegal in kernel versions 3.5 and greater */
|
||||
schedule_work(&cookie_work);
|
||||
}
|
||||
|
||||
/* Retrieve full name from proc/pid/cmdline for java processes on Android */
|
||||
static int translate_app_process(const char **text, int cpu, struct task_struct *task, bool from_wq)
|
||||
{
|
||||
void *maddr;
|
||||
unsigned int len;
|
||||
unsigned long addr;
|
||||
struct mm_struct *mm;
|
||||
struct page *page = NULL;
|
||||
struct vm_area_struct *page_vma;
|
||||
int bytes, offset, retval = 0;
|
||||
char *buf = per_cpu(translate_text, cpu);
|
||||
|
||||
#ifndef CONFIG_PREEMPT_RT_FULL
|
||||
/* Push work into a work queue if in atomic context as the kernel
|
||||
* functions below might sleep. Rely on the in_interrupt variable
|
||||
* rather than in_irq() or in_interrupt() kernel functions, as the
|
||||
* value of these functions seems inconsistent during a context
|
||||
* switch between android/linux versions
|
||||
*/
|
||||
if (!from_wq) {
|
||||
/* Check if already in buffer */
|
||||
int pos = per_cpu(translate_buffer_read, cpu);
|
||||
|
||||
while (pos != per_cpu(translate_buffer_write, cpu)) {
|
||||
if (per_cpu(translate_buffer, cpu)[pos].task == task)
|
||||
goto out;
|
||||
pos = (pos + 1) & translate_buffer_mask;
|
||||
}
|
||||
|
||||
translate_buffer_write_args(cpu, task, *text);
|
||||
|
||||
/* Not safe to call in RT-Preempt full in schedule switch context */
|
||||
mod_timer(&app_process_wake_up_timer, jiffies + 1);
|
||||
goto out;
|
||||
}
|
||||
#endif
|
||||
|
||||
mm = get_task_mm(task);
|
||||
if (!mm)
|
||||
goto out;
|
||||
if (!mm->arg_end)
|
||||
goto outmm;
|
||||
addr = mm->arg_start;
|
||||
len = mm->arg_end - mm->arg_start;
|
||||
|
||||
if (len > TRANSLATE_TEXT_SIZE)
|
||||
len = TRANSLATE_TEXT_SIZE;
|
||||
|
||||
down_read(&mm->mmap_sem);
|
||||
while (len) {
|
||||
if (get_user_pages_remote(task, mm, addr, 1, 0, 1, &page, &page_vma) <= 0)
|
||||
goto outsem;
|
||||
|
||||
maddr = kmap(page);
|
||||
offset = addr & (PAGE_SIZE - 1);
|
||||
bytes = len;
|
||||
if (bytes > PAGE_SIZE - offset)
|
||||
bytes = PAGE_SIZE - offset;
|
||||
|
||||
copy_from_user_page(page_vma, page, addr, buf, maddr + offset, bytes);
|
||||
|
||||
/* release page allocated by get_user_pages_remote() */
|
||||
kunmap(page);
|
||||
page_cache_release(page);
|
||||
|
||||
len -= bytes;
|
||||
buf += bytes;
|
||||
addr += bytes;
|
||||
|
||||
*text = per_cpu(translate_text, cpu);
|
||||
retval = 1;
|
||||
}
|
||||
|
||||
/* On app_process startup, /proc/pid/cmdline is initially "zygote" then "<pre-initialized>" but changes after an initial startup period */
|
||||
if (strcmp(*text, "zygote") == 0 || strcmp(*text, "<pre-initialized>") == 0)
|
||||
retval = 0;
|
||||
|
||||
outsem:
|
||||
up_read(&mm->mmap_sem);
|
||||
outmm:
|
||||
mmput(mm);
|
||||
out:
|
||||
return retval;
|
||||
}
|
||||
|
||||
static const char APP_PROCESS[] = "app_process";
|
||||
|
||||
static uint32_t get_cookie(int cpu, struct task_struct *task, const char *text, bool from_wq)
|
||||
{
|
||||
unsigned long flags, cookie;
|
||||
uint64_t key;
|
||||
|
||||
key = gator_chksum_crc32(text);
|
||||
key = (key << 32) | (uint32_t)task->tgid;
|
||||
|
||||
cookie = cookiemap_exists(key);
|
||||
if (cookie)
|
||||
return cookie;
|
||||
|
||||
/* On 64-bit android app_process can be app_process32 or app_process64 */
|
||||
if (strstr(text, APP_PROCESS) != NULL) {
|
||||
if (!translate_app_process(&text, cpu, task, from_wq))
|
||||
return UNRESOLVED_COOKIE;
|
||||
}
|
||||
|
||||
/* Can be called from interrupt handler or from work queue or from scheduler trace */
|
||||
local_irq_save(flags);
|
||||
|
||||
cookie = UNRESOLVED_COOKIE;
|
||||
if (marshal_cookie_header(text)) {
|
||||
cookie = per_cpu(cookie_next_key, cpu) += nr_cpu_ids;
|
||||
cookiemap_add(key, cookie);
|
||||
marshal_cookie(cookie, text);
|
||||
}
|
||||
|
||||
local_irq_restore(flags);
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
/* Can't call d_path in interrupt context so create something similar */
|
||||
static const char *gator_d_path(const struct path *path, char *buf, int buflen)
|
||||
{
|
||||
struct dentry *dentry = path->dentry;
|
||||
struct mount *mount = real_mount(path->mnt);
|
||||
int pos = buflen - 1;
|
||||
int len;
|
||||
|
||||
buf[pos] = '\0';
|
||||
|
||||
for (;;) {
|
||||
if (dentry == NULL) {
|
||||
pr_err("gator: dentry is null!\n");
|
||||
break;
|
||||
}
|
||||
if (dentry->d_name.name[0] == '\0') {
|
||||
pr_err("gator: path is empty string\n");
|
||||
break;
|
||||
}
|
||||
if (dentry->d_name.name[0] == '/' && dentry->d_name.name[1] == '\0') {
|
||||
/* Normal operation */
|
||||
/* pr_err("gator: path is /\n"); */
|
||||
break;
|
||||
}
|
||||
|
||||
len = strlen(dentry->d_name.name);
|
||||
if (pos < len) {
|
||||
pr_err("gator: path is too long\n");
|
||||
break;
|
||||
}
|
||||
pos -= len;
|
||||
memcpy(buf + pos, dentry->d_name.name, len);
|
||||
|
||||
if (pos == 0) {
|
||||
pr_err("gator: no room for slash\n");
|
||||
/* Fall back to name only */
|
||||
return path->dentry->d_name.name;
|
||||
}
|
||||
--pos;
|
||||
buf[pos] = '/';
|
||||
|
||||
if (dentry->d_parent == GET_MNT_ROOT(mount)) {
|
||||
/* pr_err("gator: filesystem is complete, moving to next '%s'\n", buf + pos); */
|
||||
dentry = mount->mnt_mountpoint;
|
||||
mount = mount->mnt_parent;
|
||||
continue;
|
||||
}
|
||||
if (dentry == dentry->d_parent) {
|
||||
/* Normal operation, at least for ashmem */
|
||||
/* pr_err("gator: parent is self\n"); */
|
||||
break;
|
||||
}
|
||||
dentry = dentry->d_parent;
|
||||
}
|
||||
|
||||
if (pos < 0) {
|
||||
pr_err("gator: pos is somenow negative\n");
|
||||
/* Fall back to name only */
|
||||
return path->dentry->d_name.name;
|
||||
}
|
||||
|
||||
return buf + pos;
|
||||
}
|
||||
|
||||
#define d_path gator_d_path
|
||||
|
||||
static int get_exec_cookie(int cpu, struct task_struct *task)
|
||||
{
|
||||
struct mm_struct *mm = task->mm;
|
||||
const char *text;
|
||||
|
||||
/* kernel threads have no address space */
|
||||
if (!mm)
|
||||
return NO_COOKIE;
|
||||
|
||||
if (task && task->mm && task->mm->exe_file) {
|
||||
text = d_path(&task->mm->exe_file->f_path, per_cpu(scratch, get_physical_cpu()), PAGE_SIZE);
|
||||
return get_cookie(cpu, task, text, false);
|
||||
}
|
||||
|
||||
return UNRESOLVED_COOKIE;
|
||||
}
|
||||
|
||||
static unsigned long get_address_cookie(int cpu, struct task_struct *task, unsigned long addr, off_t *offset)
|
||||
{
|
||||
unsigned long cookie = NO_COOKIE;
|
||||
struct mm_struct *mm = task->mm;
|
||||
struct vm_area_struct *vma;
|
||||
const char *text;
|
||||
|
||||
if (!mm)
|
||||
return cookie;
|
||||
|
||||
for (vma = find_vma(mm, addr); vma; vma = vma->vm_next) {
|
||||
if (addr < vma->vm_start || addr >= vma->vm_end)
|
||||
continue;
|
||||
|
||||
if (vma->vm_file) {
|
||||
text = d_path(&vma->vm_file->f_path, per_cpu(scratch, get_physical_cpu()), PAGE_SIZE);
|
||||
cookie = get_cookie(cpu, task, text, false);
|
||||
*offset = (vma->vm_pgoff << PAGE_SHIFT) + addr - vma->vm_start;
|
||||
} else {
|
||||
/* must be an anonymous map */
|
||||
*offset = addr;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (!vma)
|
||||
cookie = UNRESOLVED_COOKIE;
|
||||
|
||||
return cookie;
|
||||
}
|
||||
|
||||
static int cookies_initialize(void)
|
||||
{
|
||||
uint32_t crc, poly;
|
||||
int i, j, cpu, size, err = 0;
|
||||
|
||||
translate_buffer_mask = TRANSLATE_BUFFER_SIZE / sizeof(per_cpu(translate_buffer, 0)[0]) - 1;
|
||||
|
||||
for_each_present_cpu(cpu) {
|
||||
per_cpu(cookie_next_key, cpu) = nr_cpu_ids + cpu;
|
||||
|
||||
size = COOKIEMAP_ENTRIES * MAX_COLLISIONS * sizeof(uint64_t);
|
||||
per_cpu(cookie_keys, cpu) = kmalloc(size, GFP_KERNEL);
|
||||
if (!per_cpu(cookie_keys, cpu)) {
|
||||
err = -ENOMEM;
|
||||
goto cookie_setup_error;
|
||||
}
|
||||
memset(per_cpu(cookie_keys, cpu), 0, size);
|
||||
|
||||
size = COOKIEMAP_ENTRIES * MAX_COLLISIONS * sizeof(uint32_t);
|
||||
per_cpu(cookie_values, cpu) = kmalloc(size, GFP_KERNEL);
|
||||
if (!per_cpu(cookie_values, cpu)) {
|
||||
err = -ENOMEM;
|
||||
goto cookie_setup_error;
|
||||
}
|
||||
memset(per_cpu(cookie_values, cpu), 0, size);
|
||||
|
||||
per_cpu(translate_buffer, cpu) = kmalloc(TRANSLATE_BUFFER_SIZE, GFP_KERNEL);
|
||||
if (!per_cpu(translate_buffer, cpu)) {
|
||||
err = -ENOMEM;
|
||||
goto cookie_setup_error;
|
||||
}
|
||||
|
||||
per_cpu(translate_buffer_write, cpu) = 0;
|
||||
per_cpu(translate_buffer_read, cpu) = 0;
|
||||
|
||||
per_cpu(translate_text, cpu) = kmalloc(TRANSLATE_TEXT_SIZE, GFP_KERNEL);
|
||||
if (!per_cpu(translate_text, cpu)) {
|
||||
err = -ENOMEM;
|
||||
goto cookie_setup_error;
|
||||
}
|
||||
|
||||
per_cpu(scratch, cpu) = kmalloc(PAGE_SIZE, GFP_KERNEL);
|
||||
if (!per_cpu(scratch, cpu)) {
|
||||
err = -ENOMEM;
|
||||
goto cookie_setup_error;
|
||||
}
|
||||
}
|
||||
|
||||
/* build CRC32 table */
|
||||
poly = 0x04c11db7;
|
||||
gator_crc32_table = kmalloc(256 * sizeof(*gator_crc32_table), GFP_KERNEL);
|
||||
if (!gator_crc32_table) {
|
||||
err = -ENOMEM;
|
||||
goto cookie_setup_error;
|
||||
}
|
||||
for (i = 0; i < 256; i++) {
|
||||
crc = i;
|
||||
for (j = 8; j > 0; j--) {
|
||||
if (crc & 1)
|
||||
crc = (crc >> 1) ^ poly;
|
||||
else
|
||||
crc >>= 1;
|
||||
}
|
||||
gator_crc32_table[i] = crc;
|
||||
}
|
||||
|
||||
setup_deferrable_timer_on_stack(&app_process_wake_up_timer, app_process_wake_up_handler, 0);
|
||||
|
||||
cookie_setup_error:
|
||||
return err;
|
||||
}
|
||||
|
||||
static void cookies_release(void)
|
||||
{
|
||||
int cpu;
|
||||
|
||||
for_each_present_cpu(cpu) {
|
||||
kfree(per_cpu(cookie_keys, cpu));
|
||||
per_cpu(cookie_keys, cpu) = NULL;
|
||||
|
||||
kfree(per_cpu(cookie_values, cpu));
|
||||
per_cpu(cookie_values, cpu) = NULL;
|
||||
|
||||
kfree(per_cpu(translate_buffer, cpu));
|
||||
per_cpu(translate_buffer, cpu) = NULL;
|
||||
per_cpu(translate_buffer_read, cpu) = 0;
|
||||
per_cpu(translate_buffer_write, cpu) = 0;
|
||||
|
||||
kfree(per_cpu(translate_text, cpu));
|
||||
per_cpu(translate_text, cpu) = NULL;
|
||||
|
||||
kfree(per_cpu(scratch, cpu));
|
||||
per_cpu(scratch, cpu) = NULL;
|
||||
}
|
||||
|
||||
del_timer_sync(&app_process_wake_up_timer);
|
||||
kfree(gator_crc32_table);
|
||||
gator_crc32_table = NULL;
|
||||
}
|
||||
162
drivers/gator/gator_events_block.c
Normal file
162
drivers/gator/gator_events_block.c
Normal file
@@ -0,0 +1,162 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gator.h"
|
||||
#include <trace/events/block.h>
|
||||
|
||||
#define BLOCK_RQ_WR 0
|
||||
#define BLOCK_RQ_RD 1
|
||||
|
||||
#define BLOCK_TOTAL (BLOCK_RQ_RD+1)
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 8, 0)
|
||||
#define GATOR_REQ_IS_OP_WRITE(rq) (rq->cmd_flags & REQ_WRITE)
|
||||
#else
|
||||
#define GATOR_REQ_IS_OP_WRITE(rq) (req_op(rq) & REQ_OP_WRITE)
|
||||
#endif
|
||||
|
||||
static ulong block_rq_wr_enabled;
|
||||
static ulong block_rq_rd_enabled;
|
||||
static ulong block_rq_wr_key;
|
||||
static ulong block_rq_rd_key;
|
||||
static atomic_t blockCnt[BLOCK_TOTAL];
|
||||
static int blockGet[BLOCK_TOTAL * 4];
|
||||
|
||||
/* Tracepoint changed in 3.15 backported to older kernels. The Makefile tries to autodetect the correct value, but if it fails change the #if below */
|
||||
#if OLD_BLOCK_RQ_COMPLETE
|
||||
GATOR_DEFINE_PROBE(block_rq_complete, TP_PROTO(struct request_queue *q, struct request *rq))
|
||||
#else
|
||||
GATOR_DEFINE_PROBE(block_rq_complete, TP_PROTO(struct request_queue *q, struct request *rq, unsigned int nr_bytes))
|
||||
#endif
|
||||
{
|
||||
int write;
|
||||
unsigned int size;
|
||||
|
||||
if (!rq)
|
||||
return;
|
||||
|
||||
write = GATOR_REQ_IS_OP_WRITE(rq);
|
||||
|
||||
#if OLD_BLOCK_RQ_COMPLETE
|
||||
size = rq->resid_len;
|
||||
#else
|
||||
size = nr_bytes;
|
||||
#endif
|
||||
|
||||
if (!size)
|
||||
return;
|
||||
|
||||
if (write) {
|
||||
if (block_rq_wr_enabled)
|
||||
atomic_add(size, &blockCnt[BLOCK_RQ_WR]);
|
||||
} else {
|
||||
if (block_rq_rd_enabled)
|
||||
atomic_add(size, &blockCnt[BLOCK_RQ_RD]);
|
||||
}
|
||||
}
|
||||
|
||||
static int gator_events_block_create_files(struct super_block *sb, struct dentry *root)
|
||||
{
|
||||
struct dentry *dir;
|
||||
|
||||
/* block_complete_wr */
|
||||
dir = gatorfs_mkdir(sb, root, "Linux_block_rq_wr");
|
||||
if (!dir)
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &block_rq_wr_enabled);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &block_rq_wr_key);
|
||||
|
||||
/* block_complete_rd */
|
||||
dir = gatorfs_mkdir(sb, root, "Linux_block_rq_rd");
|
||||
if (!dir)
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &block_rq_rd_enabled);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &block_rq_rd_key);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gator_events_block_start(void)
|
||||
{
|
||||
/* register tracepoints */
|
||||
if (block_rq_wr_enabled || block_rq_rd_enabled)
|
||||
if (GATOR_REGISTER_TRACE(block_rq_complete))
|
||||
goto fail_block_rq_exit;
|
||||
pr_debug("gator: registered block event tracepoints\n");
|
||||
|
||||
return 0;
|
||||
|
||||
/* unregister tracepoints on error */
|
||||
fail_block_rq_exit:
|
||||
pr_err("gator: block event tracepoints failed to activate, please verify that tracepoints are enabled in the linux kernel\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void gator_events_block_stop(void)
|
||||
{
|
||||
if (block_rq_wr_enabled || block_rq_rd_enabled)
|
||||
GATOR_UNREGISTER_TRACE(block_rq_complete);
|
||||
pr_debug("gator: unregistered block event tracepoints\n");
|
||||
|
||||
block_rq_wr_enabled = 0;
|
||||
block_rq_rd_enabled = 0;
|
||||
}
|
||||
|
||||
static int gator_events_block_read(int **buffer, bool sched_switch)
|
||||
{
|
||||
int len, value, data = 0;
|
||||
|
||||
if (!on_primary_core())
|
||||
return 0;
|
||||
|
||||
len = 0;
|
||||
if (block_rq_wr_enabled && (value = atomic_read(&blockCnt[BLOCK_RQ_WR])) > 0) {
|
||||
atomic_sub(value, &blockCnt[BLOCK_RQ_WR]);
|
||||
blockGet[len++] = block_rq_wr_key;
|
||||
/* Indicates to Streamline that value bytes were written now, not since the last message */
|
||||
blockGet[len++] = 0;
|
||||
blockGet[len++] = block_rq_wr_key;
|
||||
blockGet[len++] = value;
|
||||
data += value;
|
||||
}
|
||||
if (block_rq_rd_enabled && (value = atomic_read(&blockCnt[BLOCK_RQ_RD])) > 0) {
|
||||
atomic_sub(value, &blockCnt[BLOCK_RQ_RD]);
|
||||
blockGet[len++] = block_rq_rd_key;
|
||||
/* Indicates to Streamline that value bytes were read now, not since the last message */
|
||||
blockGet[len++] = 0;
|
||||
blockGet[len++] = block_rq_rd_key;
|
||||
blockGet[len++] = value;
|
||||
data += value;
|
||||
}
|
||||
|
||||
if (buffer)
|
||||
*buffer = blockGet;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct gator_interface gator_events_block_interface = {
|
||||
.name = "block",
|
||||
.create_files = gator_events_block_create_files,
|
||||
.start = gator_events_block_start,
|
||||
.stop = gator_events_block_stop,
|
||||
.read = gator_events_block_read,
|
||||
};
|
||||
|
||||
int gator_events_block_init(void)
|
||||
{
|
||||
block_rq_wr_enabled = 0;
|
||||
block_rq_rd_enabled = 0;
|
||||
|
||||
block_rq_wr_key = gator_events_get_key();
|
||||
block_rq_rd_key = gator_events_get_key();
|
||||
|
||||
return gator_events_install(&gator_events_block_interface);
|
||||
}
|
||||
189
drivers/gator/gator_events_irq.c
Normal file
189
drivers/gator/gator_events_irq.c
Normal file
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gator.h"
|
||||
#include <trace/events/irq.h>
|
||||
|
||||
#define HARDIRQ 0
|
||||
#define SOFTIRQ 1
|
||||
#define TOTALIRQ (SOFTIRQ+1)
|
||||
|
||||
static ulong hardirq_enabled[GATOR_CLUSTER_COUNT];
|
||||
static bool hardirq_enabled_any;
|
||||
static ulong softirq_enabled[GATOR_CLUSTER_COUNT];
|
||||
static bool softirq_enabled_any;
|
||||
static ulong hardirq_key[GATOR_CLUSTER_COUNT];
|
||||
static ulong softirq_key[GATOR_CLUSTER_COUNT];
|
||||
static DEFINE_PER_CPU(atomic_t[TOTALIRQ], irqCnt);
|
||||
static DEFINE_PER_CPU(int[TOTALIRQ * 2], irqGet);
|
||||
|
||||
GATOR_DEFINE_PROBE(irq_handler_exit,
|
||||
TP_PROTO(int irq, struct irqaction *action, int ret))
|
||||
{
|
||||
atomic_inc(&per_cpu(irqCnt, get_physical_cpu())[HARDIRQ]);
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(softirq_exit, TP_PROTO(unsigned int vec_nr))
|
||||
{
|
||||
atomic_inc(&per_cpu(irqCnt, get_physical_cpu())[SOFTIRQ]);
|
||||
}
|
||||
|
||||
static int gator_events_irq_create_files(struct super_block *sb, struct dentry *root)
|
||||
{
|
||||
struct dentry *dir;
|
||||
int i;
|
||||
char buf[40];
|
||||
|
||||
/* irq */
|
||||
for (i = 0; i < gator_cluster_count; i++) {
|
||||
snprintf(buf, sizeof(buf), "%s_irq", gator_clusters[i]->pmnc_name);
|
||||
dir = gatorfs_mkdir(sb, root, buf);
|
||||
if (!dir)
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &hardirq_enabled[i]);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &hardirq_key[i]);
|
||||
}
|
||||
|
||||
/* soft irq */
|
||||
for (i = 0; i < gator_cluster_count; i++) {
|
||||
snprintf(buf, sizeof(buf), "%s_softirq", gator_clusters[i]->pmnc_name);
|
||||
dir = gatorfs_mkdir(sb, root, buf);
|
||||
if (!dir)
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &softirq_enabled[i]);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &softirq_key[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gator_events_irq_online(int **buffer, bool migrate)
|
||||
{
|
||||
int len = 0, cpu = get_physical_cpu();
|
||||
int cluster = gator_clusterids[cpu];
|
||||
|
||||
/* synchronization with the irq_exit functions is not necessary as the values are being reset */
|
||||
if (hardirq_enabled[cluster]) {
|
||||
atomic_set(&per_cpu(irqCnt, cpu)[HARDIRQ], 0);
|
||||
per_cpu(irqGet, cpu)[len++] = hardirq_key[cluster];
|
||||
per_cpu(irqGet, cpu)[len++] = 0;
|
||||
}
|
||||
|
||||
if (softirq_enabled[cluster]) {
|
||||
atomic_set(&per_cpu(irqCnt, cpu)[SOFTIRQ], 0);
|
||||
per_cpu(irqGet, cpu)[len++] = softirq_key[cluster];
|
||||
per_cpu(irqGet, cpu)[len++] = 0;
|
||||
}
|
||||
|
||||
if (buffer)
|
||||
*buffer = per_cpu(irqGet, cpu);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static int gator_events_irq_start(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
hardirq_enabled_any = false;
|
||||
softirq_enabled_any = false;
|
||||
for (i = 0; i < gator_cluster_count; i++) {
|
||||
if (hardirq_enabled[i])
|
||||
hardirq_enabled_any = true;
|
||||
if (softirq_enabled[i])
|
||||
softirq_enabled_any = true;
|
||||
}
|
||||
|
||||
/* register tracepoints */
|
||||
if (hardirq_enabled_any)
|
||||
if (GATOR_REGISTER_TRACE(irq_handler_exit))
|
||||
goto fail_hardirq_exit;
|
||||
if (softirq_enabled_any)
|
||||
if (GATOR_REGISTER_TRACE(softirq_exit))
|
||||
goto fail_softirq_exit;
|
||||
pr_debug("gator: registered irq tracepoints\n");
|
||||
|
||||
return 0;
|
||||
|
||||
/* unregister tracepoints on error */
|
||||
fail_softirq_exit:
|
||||
if (hardirq_enabled_any)
|
||||
GATOR_UNREGISTER_TRACE(irq_handler_exit);
|
||||
fail_hardirq_exit:
|
||||
pr_err("gator: irq tracepoints failed to activate, please verify that tracepoints are enabled in the linux kernel\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void gator_events_irq_stop(void)
|
||||
{
|
||||
if (hardirq_enabled_any)
|
||||
GATOR_UNREGISTER_TRACE(irq_handler_exit);
|
||||
if (softirq_enabled_any)
|
||||
GATOR_UNREGISTER_TRACE(softirq_exit);
|
||||
pr_debug("gator: unregistered irq tracepoints\n");
|
||||
|
||||
hardirq_enabled_any = false;
|
||||
memset(hardirq_enabled, 0, sizeof(hardirq_enabled));
|
||||
softirq_enabled_any = false;
|
||||
memset(softirq_enabled, 0, sizeof(softirq_enabled));
|
||||
}
|
||||
|
||||
static int gator_events_irq_read(int **buffer, bool sched_switch)
|
||||
{
|
||||
int len, value;
|
||||
int cpu = get_physical_cpu();
|
||||
int cluster = gator_clusterids[cpu];
|
||||
|
||||
len = 0;
|
||||
if (hardirq_enabled[cluster]) {
|
||||
value = atomic_read(&per_cpu(irqCnt, cpu)[HARDIRQ]);
|
||||
atomic_sub(value, &per_cpu(irqCnt, cpu)[HARDIRQ]);
|
||||
|
||||
per_cpu(irqGet, cpu)[len++] = hardirq_key[cluster];
|
||||
per_cpu(irqGet, cpu)[len++] = value;
|
||||
}
|
||||
|
||||
if (softirq_enabled[cluster]) {
|
||||
value = atomic_read(&per_cpu(irqCnt, cpu)[SOFTIRQ]);
|
||||
atomic_sub(value, &per_cpu(irqCnt, cpu)[SOFTIRQ]);
|
||||
|
||||
per_cpu(irqGet, cpu)[len++] = softirq_key[cluster];
|
||||
per_cpu(irqGet, cpu)[len++] = value;
|
||||
}
|
||||
|
||||
if (buffer)
|
||||
*buffer = per_cpu(irqGet, cpu);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct gator_interface gator_events_irq_interface = {
|
||||
.name = "irq",
|
||||
.create_files = gator_events_irq_create_files,
|
||||
.online = gator_events_irq_online,
|
||||
.start = gator_events_irq_start,
|
||||
.stop = gator_events_irq_stop,
|
||||
.read = gator_events_irq_read,
|
||||
};
|
||||
|
||||
int gator_events_irq_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < gator_cluster_count; i++) {
|
||||
hardirq_key[i] = gator_events_get_key();
|
||||
softirq_key[i] = gator_events_get_key();
|
||||
|
||||
hardirq_enabled[i] = 0;
|
||||
softirq_enabled[i] = 0;
|
||||
}
|
||||
|
||||
return gator_events_install(&gator_events_irq_interface);
|
||||
}
|
||||
209
drivers/gator/gator_events_l2c-310.c
Normal file
209
drivers/gator/gator_events_l2c-310.c
Normal file
@@ -0,0 +1,209 @@
|
||||
/**
|
||||
* l2c310 (L2 Cache Controller) event counters for gator
|
||||
*
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/module.h>
|
||||
#if defined(CONFIG_OF)
|
||||
#include <linux/of.h>
|
||||
#include <linux/of_address.h>
|
||||
#endif
|
||||
#include <asm/hardware/cache-l2x0.h>
|
||||
|
||||
#include "gator.h"
|
||||
|
||||
#define L2C310_COUNTERS_NUM 2
|
||||
|
||||
static struct {
|
||||
unsigned long enabled;
|
||||
unsigned long event;
|
||||
unsigned long key;
|
||||
} l2c310_counters[L2C310_COUNTERS_NUM];
|
||||
|
||||
static int l2c310_buffer[L2C310_COUNTERS_NUM * 2];
|
||||
|
||||
static void __iomem *l2c310_base;
|
||||
|
||||
static void gator_events_l2c310_reset_counters(void)
|
||||
{
|
||||
u32 val = readl(l2c310_base + L2X0_EVENT_CNT_CTRL);
|
||||
|
||||
val |= ((1 << L2C310_COUNTERS_NUM) - 1) << 1;
|
||||
|
||||
writel(val, l2c310_base + L2X0_EVENT_CNT_CTRL);
|
||||
}
|
||||
|
||||
static int gator_events_l2c310_create_files(struct super_block *sb,
|
||||
struct dentry *root)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < L2C310_COUNTERS_NUM; i++) {
|
||||
char buf[16];
|
||||
struct dentry *dir;
|
||||
|
||||
snprintf(buf, sizeof(buf), "l2c_310_cnt%d", i);
|
||||
dir = gatorfs_mkdir(sb, root, buf);
|
||||
if (WARN_ON(!dir))
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled",
|
||||
&l2c310_counters[i].enabled);
|
||||
gatorfs_create_ulong(sb, dir, "event",
|
||||
&l2c310_counters[i].event);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key",
|
||||
&l2c310_counters[i].key);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gator_events_l2c310_start(void)
|
||||
{
|
||||
static const unsigned long l2x0_event_cntx_cfg[L2C310_COUNTERS_NUM] = {
|
||||
L2X0_EVENT_CNT0_CFG,
|
||||
L2X0_EVENT_CNT1_CFG,
|
||||
};
|
||||
int i;
|
||||
|
||||
/* Counter event sources */
|
||||
for (i = 0; i < L2C310_COUNTERS_NUM; i++)
|
||||
writel((l2c310_counters[i].event & 0xf) << 2,
|
||||
l2c310_base + l2x0_event_cntx_cfg[i]);
|
||||
|
||||
gator_events_l2c310_reset_counters();
|
||||
|
||||
/* Event counter enable */
|
||||
writel(1, l2c310_base + L2X0_EVENT_CNT_CTRL);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gator_events_l2c310_stop(void)
|
||||
{
|
||||
/* Event counter disable */
|
||||
writel(0, l2c310_base + L2X0_EVENT_CNT_CTRL);
|
||||
}
|
||||
|
||||
static int gator_events_l2c310_read(int **buffer, bool sched_switch)
|
||||
{
|
||||
static const unsigned long l2x0_event_cntx_val[L2C310_COUNTERS_NUM] = {
|
||||
L2X0_EVENT_CNT0_VAL,
|
||||
L2X0_EVENT_CNT1_VAL,
|
||||
};
|
||||
int i;
|
||||
int len = 0;
|
||||
|
||||
if (!on_primary_core())
|
||||
return 0;
|
||||
|
||||
for (i = 0; i < L2C310_COUNTERS_NUM; i++) {
|
||||
if (l2c310_counters[i].enabled) {
|
||||
l2c310_buffer[len++] = l2c310_counters[i].key;
|
||||
l2c310_buffer[len++] = readl(l2c310_base +
|
||||
l2x0_event_cntx_val[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* l2c310 counters are saturating, not wrapping in case of overflow */
|
||||
gator_events_l2c310_reset_counters();
|
||||
|
||||
if (buffer)
|
||||
*buffer = l2c310_buffer;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct gator_interface gator_events_l2c310_interface = {
|
||||
.name = "l2c-310",
|
||||
.create_files = gator_events_l2c310_create_files,
|
||||
.start = gator_events_l2c310_start,
|
||||
.stop = gator_events_l2c310_stop,
|
||||
.read = gator_events_l2c310_read,
|
||||
};
|
||||
|
||||
#define L2C310_ADDR_PROBE (~0)
|
||||
|
||||
MODULE_PARM_DESC(l2c310_addr, "L2C310 physical base address (0 to disable)");
|
||||
static unsigned long l2c310_addr = L2C310_ADDR_PROBE;
|
||||
module_param(l2c310_addr, ulong, 0444);
|
||||
|
||||
static void __iomem *gator_events_l2c310_probe(void)
|
||||
{
|
||||
phys_addr_t variants[] = {
|
||||
#if defined(CONFIG_ARCH_EXYNOS4) || defined(CONFIG_ARCH_S5PV310)
|
||||
0x10502000,
|
||||
#endif
|
||||
#if defined(CONFIG_ARCH_OMAP4)
|
||||
0x48242000,
|
||||
#endif
|
||||
#if defined(CONFIG_ARCH_TEGRA)
|
||||
0x50043000,
|
||||
#endif
|
||||
#if defined(CONFIG_ARCH_U8500)
|
||||
0xa0412000,
|
||||
#endif
|
||||
#if defined(CONFIG_ARCH_VEXPRESS)
|
||||
0x1e00a000, /* A9x4 core tile (HBI-0191) */
|
||||
0x2c0f0000, /* New memory map tiles */
|
||||
#endif
|
||||
};
|
||||
int i;
|
||||
void __iomem *base;
|
||||
#if defined(CONFIG_OF)
|
||||
struct device_node *node = of_find_all_nodes(NULL);
|
||||
|
||||
if (node) {
|
||||
of_node_put(node);
|
||||
|
||||
node = of_find_compatible_node(NULL, NULL, "arm,pl310-cache");
|
||||
base = of_iomap(node, 0);
|
||||
of_node_put(node);
|
||||
|
||||
return base;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(variants); i++) {
|
||||
base = ioremap(variants[i], SZ_4K);
|
||||
if (base) {
|
||||
u32 cache_id = readl(base + L2X0_CACHE_ID);
|
||||
|
||||
if ((cache_id & 0xff0003c0) == 0x410000c0)
|
||||
return base;
|
||||
|
||||
iounmap(base);
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int gator_events_l2c310_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (gator_cpuid() != CORTEX_A5 && gator_cpuid() != CORTEX_A9)
|
||||
return -1;
|
||||
|
||||
if (l2c310_addr == L2C310_ADDR_PROBE)
|
||||
l2c310_base = gator_events_l2c310_probe();
|
||||
else if (l2c310_addr)
|
||||
l2c310_base = ioremap(l2c310_addr, SZ_4K);
|
||||
|
||||
if (!l2c310_base)
|
||||
return -1;
|
||||
|
||||
for (i = 0; i < L2C310_COUNTERS_NUM; i++) {
|
||||
l2c310_counters[i].enabled = 0;
|
||||
l2c310_counters[i].key = gator_events_get_key();
|
||||
}
|
||||
|
||||
return gator_events_install(&gator_events_l2c310_interface);
|
||||
}
|
||||
624
drivers/gator/gator_events_mali_4xx.c
Normal file
624
drivers/gator/gator_events_mali_4xx.c
Normal file
@@ -0,0 +1,624 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include "gator.h"
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/math64.h>
|
||||
|
||||
#include "linux/mali_linux_trace.h"
|
||||
|
||||
#include "gator_events_mali_common.h"
|
||||
#include "gator_events_mali_4xx.h"
|
||||
|
||||
/*
|
||||
* There have been four different variants of the comms between gator and Mali depending on driver version:
|
||||
* # | DDK vsn range | Support | Notes
|
||||
*
|
||||
* 1 | (obsolete) | No software counter support | Obsolete patches
|
||||
* 2 | (obsolete) | Tracepoint called for each separate s/w counter value as it appears | Obsolete patches
|
||||
* 3 | r3p0-04rel0 - r3p2-01rel2 | Single tracepoint for all s/w counters in a bundle. |
|
||||
* 4 | r3p2-01rel3 - date | As above but with extensions for MP devices (Mali-450) | At least r4p0-00rel1
|
||||
*/
|
||||
|
||||
#if !defined(GATOR_MALI_INTERFACE_STYLE)
|
||||
#define GATOR_MALI_INTERFACE_STYLE (4)
|
||||
#endif
|
||||
|
||||
#if GATOR_MALI_INTERFACE_STYLE == 1
|
||||
#error GATOR_MALI_INTERFACE_STYLE 1 is obsolete
|
||||
#elif GATOR_MALI_INTERFACE_STYLE == 2
|
||||
#error GATOR_MALI_INTERFACE_STYLE 2 is obsolete
|
||||
#elif GATOR_MALI_INTERFACE_STYLE >= 3
|
||||
/* Valid GATOR_MALI_INTERFACE_STYLE */
|
||||
#else
|
||||
#error Unknown GATOR_MALI_INTERFACE_STYLE option.
|
||||
#endif
|
||||
|
||||
#if GATOR_MALI_INTERFACE_STYLE < 4
|
||||
#include "mali/mali_mjollnir_profiling_gator_api.h"
|
||||
#else
|
||||
#include "mali/mali_utgard_profiling_gator_api.h"
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Check that the MALI_SUPPORT define is set to one of the allowable device codes.
|
||||
*/
|
||||
#if (MALI_SUPPORT != MALI_4xx)
|
||||
#error MALI_SUPPORT set to an invalid device code: expecting MALI_4xx
|
||||
#endif
|
||||
|
||||
static const char mali_name[] = "4xx";
|
||||
|
||||
/* gatorfs variables for counter enable state,
|
||||
* the event the counter should count and the
|
||||
* 'key' (a unique id set by gatord and returned
|
||||
* by gator.ko)
|
||||
*/
|
||||
static unsigned long counter_enabled[NUMBER_OF_EVENTS];
|
||||
static unsigned long counter_event[NUMBER_OF_EVENTS];
|
||||
static unsigned long counter_key[NUMBER_OF_EVENTS];
|
||||
|
||||
/* The data we have recorded */
|
||||
static atomic_t counter_data[NUMBER_OF_EVENTS];
|
||||
|
||||
/* An array used to return the data we recorded
|
||||
* as key,value pairs hence the *2
|
||||
*/
|
||||
static int counter_dump[NUMBER_OF_EVENTS * 2];
|
||||
static int counter_prev[NUMBER_OF_EVENTS];
|
||||
static bool prev_set[NUMBER_OF_EVENTS];
|
||||
|
||||
/* Note whether tracepoints have been registered */
|
||||
static int trace_registered;
|
||||
|
||||
/*
|
||||
* These numbers define the actual numbers of each block type that exist in the system. Initially
|
||||
* these are set to the maxima defined above; if the driver is capable of being queried (newer
|
||||
* drivers only) then the values may be revised.
|
||||
*/
|
||||
static unsigned int n_vp_cores = MAX_NUM_VP_CORES;
|
||||
static unsigned int n_l2_cores = MAX_NUM_L2_CACHE_CORES;
|
||||
static unsigned int n_fp_cores = MAX_NUM_FP_CORES;
|
||||
|
||||
extern struct mali_counter mali_activity[2];
|
||||
static const char *const mali_activity_names[] = {
|
||||
"fragment",
|
||||
"vertex",
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns non-zero if the given counter ID is an activity counter.
|
||||
*/
|
||||
static inline int is_activity_counter(unsigned int event_id)
|
||||
{
|
||||
return (event_id >= FIRST_ACTIVITY_EVENT &&
|
||||
event_id <= LAST_ACTIVITY_EVENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns non-zero if the given counter ID is a hardware counter.
|
||||
*/
|
||||
static inline int is_hw_counter(unsigned int event_id)
|
||||
{
|
||||
return (event_id >= FIRST_HW_COUNTER && event_id <= LAST_HW_COUNTER);
|
||||
}
|
||||
|
||||
/* Probe for hardware counter events */
|
||||
GATOR_DEFINE_PROBE(mali_hw_counter, TP_PROTO(unsigned int event_id, unsigned int value))
|
||||
{
|
||||
if (is_hw_counter(event_id))
|
||||
atomic_add(value, &counter_data[event_id]);
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(mali_sw_counters, TP_PROTO(pid_t pid, pid_t tid, void *surface_id, unsigned int *counters))
|
||||
{
|
||||
u32 i;
|
||||
|
||||
/* Copy over the values for those counters which are enabled. */
|
||||
for (i = FIRST_SW_COUNTER; i <= LAST_SW_COUNTER; i++) {
|
||||
if (counter_enabled[i])
|
||||
atomic_add(counters[i - FIRST_SW_COUNTER], &counter_data[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a single filesystem entry for a specified event.
|
||||
* @param sb the superblock
|
||||
* @param root Filesystem root
|
||||
* @param name The name of the entry to create
|
||||
* @param event The ID of the event
|
||||
* @param create_event_item boolean indicating whether to create an 'event' filesystem entry. True to create.
|
||||
*
|
||||
* @return 0 if ok, non-zero if the create failed.
|
||||
*/
|
||||
static int create_fs_entry(struct super_block *sb, struct dentry *root, const char *name, int event, int create_event_item)
|
||||
{
|
||||
struct dentry *dir;
|
||||
|
||||
dir = gatorfs_mkdir(sb, root, name);
|
||||
|
||||
if (!dir)
|
||||
return -1;
|
||||
|
||||
if (create_event_item)
|
||||
gatorfs_create_ulong(sb, dir, "event", &counter_event[event]);
|
||||
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &counter_enabled[event]);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &counter_key[event]);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if GATOR_MALI_INTERFACE_STYLE > 3
|
||||
/*
|
||||
* Read the version info structure if available
|
||||
*/
|
||||
static void initialise_version_info(void)
|
||||
{
|
||||
void (*mali_profiling_get_mali_version_symbol)(struct _mali_profiling_mali_version *values);
|
||||
|
||||
mali_profiling_get_mali_version_symbol = symbol_get(_mali_profiling_get_mali_version);
|
||||
|
||||
if (mali_profiling_get_mali_version_symbol) {
|
||||
struct _mali_profiling_mali_version version_info;
|
||||
|
||||
pr_debug("gator: mali online _mali_profiling_get_mali_version symbol @ %p\n",
|
||||
mali_profiling_get_mali_version_symbol);
|
||||
|
||||
/*
|
||||
* Revise the number of each different core type using information derived from the DDK.
|
||||
*/
|
||||
mali_profiling_get_mali_version_symbol(&version_info);
|
||||
|
||||
n_fp_cores = version_info.num_of_fp_cores;
|
||||
n_vp_cores = version_info.num_of_vp_cores;
|
||||
n_l2_cores = version_info.num_of_l2_cores;
|
||||
|
||||
/* Release the function - we're done with it. */
|
||||
symbol_put(_mali_profiling_get_mali_version);
|
||||
} else {
|
||||
pr_err("gator: mali online _mali_profiling_get_mali_version symbol not found\n");
|
||||
pr_err("gator: check your Mali DDK version versus the GATOR_MALI_INTERFACE_STYLE setting\n");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int create_files(struct super_block *sb, struct dentry *root)
|
||||
{
|
||||
int event;
|
||||
|
||||
char buf[40];
|
||||
int core_id;
|
||||
int counter_number;
|
||||
|
||||
pr_debug("gator: Initialising counters with style = %d\n", GATOR_MALI_INTERFACE_STYLE);
|
||||
|
||||
#if GATOR_MALI_INTERFACE_STYLE > 3
|
||||
/*
|
||||
* Initialise first: this sets up the number of cores available (on compatible DDK versions).
|
||||
* Ideally this would not need guarding but other parts of the code depend on the interface style being set
|
||||
* correctly; if it is not then the system can enter an inconsistent state.
|
||||
*/
|
||||
initialise_version_info();
|
||||
#endif
|
||||
|
||||
mali_activity[0].cores = n_fp_cores;
|
||||
mali_activity[1].cores = n_vp_cores;
|
||||
for (event = 0; event < ARRAY_SIZE(mali_activity); event++) {
|
||||
if (gator_mali_create_file_system(mali_name, mali_activity_names[event], sb, root, &mali_activity[event], NULL) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Vertex processor counters */
|
||||
for (core_id = 0; core_id < n_vp_cores; core_id++) {
|
||||
int activity_counter_id = ACTIVITY_VP_0;
|
||||
|
||||
snprintf(buf, sizeof(buf), "ARM_Mali-%s_VP_%d_active", mali_name, core_id);
|
||||
if (create_fs_entry(sb, root, buf, activity_counter_id, 0) != 0)
|
||||
return -1;
|
||||
|
||||
for (counter_number = 0; counter_number < 2; counter_number++) {
|
||||
int counter_id = COUNTER_VP_0_C0 + (2 * core_id) + counter_number;
|
||||
|
||||
snprintf(buf, sizeof(buf), "ARM_Mali-%s_VP_%d_cnt%d", mali_name, core_id, counter_number);
|
||||
if (create_fs_entry(sb, root, buf, counter_id, 1) != 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Fragment processors' counters */
|
||||
for (core_id = 0; core_id < n_fp_cores; core_id++) {
|
||||
int activity_counter_id = ACTIVITY_FP_0 + core_id;
|
||||
|
||||
snprintf(buf, sizeof(buf), "ARM_Mali-%s_FP_%d_active", mali_name, core_id);
|
||||
if (create_fs_entry(sb, root, buf, activity_counter_id, 0) != 0)
|
||||
return -1;
|
||||
|
||||
for (counter_number = 0; counter_number < 2; counter_number++) {
|
||||
int counter_id = COUNTER_FP_0_C0 + (2 * core_id) + counter_number;
|
||||
|
||||
snprintf(buf, sizeof(buf), "ARM_Mali-%s_FP_%d_cnt%d", mali_name, core_id, counter_number);
|
||||
if (create_fs_entry(sb, root, buf, counter_id, 1) != 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* L2 Cache counters */
|
||||
for (core_id = 0; core_id < n_l2_cores; core_id++) {
|
||||
for (counter_number = 0; counter_number < 2; counter_number++) {
|
||||
int counter_id = COUNTER_L2_0_C0 + (2 * core_id) + counter_number;
|
||||
|
||||
snprintf(buf, sizeof(buf), "ARM_Mali-%s_L2_%d_cnt%d", mali_name, core_id, counter_number);
|
||||
if (create_fs_entry(sb, root, buf, counter_id, 1) != 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
/* Now set up the software counter entries */
|
||||
for (event = FIRST_SW_COUNTER; event <= LAST_SW_COUNTER; event++) {
|
||||
snprintf(buf, sizeof(buf), "ARM_Mali-%s_SW_%d", mali_name, event - FIRST_SW_COUNTER);
|
||||
|
||||
if (create_fs_entry(sb, root, buf, event, 0) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* Now set up the special counter entries */
|
||||
snprintf(buf, sizeof(buf), "ARM_Mali-%s_Filmstrip_cnt0", mali_name);
|
||||
if (create_fs_entry(sb, root, buf, COUNTER_FILMSTRIP, 1) != 0)
|
||||
return -1;
|
||||
|
||||
#ifdef DVFS_REPORTED_BY_DDK
|
||||
snprintf(buf, sizeof(buf), "ARM_Mali-%s_Frequency", mali_name);
|
||||
if (create_fs_entry(sb, root, buf, COUNTER_FREQUENCY, 1) != 0)
|
||||
return -1;
|
||||
|
||||
snprintf(buf, sizeof(buf), "ARM_Mali-%s_Voltage", mali_name);
|
||||
if (create_fs_entry(sb, root, buf, COUNTER_VOLTAGE, 1) != 0)
|
||||
return -1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Local store for the get_counters entry point into the DDK.
|
||||
* This is stored here since it is used very regularly.
|
||||
*/
|
||||
static void (*mali_get_counters)(unsigned int *, unsigned int *, unsigned int *, unsigned int *);
|
||||
static u32 (*mali_get_l2_counters)(struct _mali_profiling_l2_counter_values *values);
|
||||
|
||||
/*
|
||||
* Examine list of counters between two index limits and determine if any one is enabled.
|
||||
* Returns 1 if any counter is enabled, 0 if none is.
|
||||
*/
|
||||
static int is_any_counter_enabled(unsigned int first_counter, unsigned int last_counter)
|
||||
{
|
||||
unsigned int i;
|
||||
|
||||
for (i = first_counter; i <= last_counter; i++) {
|
||||
if (counter_enabled[i])
|
||||
return 1; /* At least one counter is enabled */
|
||||
}
|
||||
|
||||
return 0; /* No s/w counters enabled */
|
||||
}
|
||||
|
||||
static void init_counters(unsigned int from_counter, unsigned int to_counter)
|
||||
{
|
||||
unsigned int counter_id;
|
||||
|
||||
/* If a Mali driver is present and exporting the appropriate symbol
|
||||
* then we can request the HW counters (of which there are only 2)
|
||||
* be configured to count the desired events
|
||||
*/
|
||||
mali_profiling_set_event_type *mali_set_hw_event;
|
||||
|
||||
mali_set_hw_event = symbol_get(_mali_profiling_set_event);
|
||||
|
||||
if (mali_set_hw_event) {
|
||||
pr_debug("gator: mali online _mali_profiling_set_event symbol @ %p\n", mali_set_hw_event);
|
||||
|
||||
for (counter_id = from_counter; counter_id <= to_counter; counter_id++) {
|
||||
if (counter_enabled[counter_id])
|
||||
mali_set_hw_event(counter_id, counter_event[counter_id]);
|
||||
else
|
||||
mali_set_hw_event(counter_id, 0xFFFFFFFF);
|
||||
}
|
||||
|
||||
symbol_put(_mali_profiling_set_event);
|
||||
} else {
|
||||
pr_err("gator: mali online _mali_profiling_set_event symbol not found\n");
|
||||
}
|
||||
}
|
||||
|
||||
static void mali_counter_initialize(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
mali_profiling_control_type *mali_control;
|
||||
|
||||
init_counters(COUNTER_L2_0_C0, COUNTER_L2_0_C0 + (2 * n_l2_cores) - 1);
|
||||
init_counters(COUNTER_VP_0_C0, COUNTER_VP_0_C0 + (2 * n_vp_cores) - 1);
|
||||
init_counters(COUNTER_FP_0_C0, COUNTER_FP_0_C0 + (2 * n_fp_cores) - 1);
|
||||
|
||||
/* Generic control interface for Mali DDK. */
|
||||
mali_control = symbol_get(_mali_profiling_control);
|
||||
if (mali_control) {
|
||||
/* The event attribute in the XML file keeps the actual frame rate. */
|
||||
unsigned int rate = counter_event[COUNTER_FILMSTRIP] & 0xff;
|
||||
unsigned int resize_factor = (counter_event[COUNTER_FILMSTRIP] >> 8) & 0xff;
|
||||
|
||||
pr_debug("gator: mali online _mali_profiling_control symbol @ %p\n", mali_control);
|
||||
|
||||
mali_control(SW_COUNTER_ENABLE, (is_any_counter_enabled(FIRST_SW_COUNTER, LAST_SW_COUNTER) ? 1 : 0));
|
||||
mali_control(FBDUMP_CONTROL_ENABLE, (counter_enabled[COUNTER_FILMSTRIP] ? 1 : 0));
|
||||
mali_control(FBDUMP_CONTROL_RATE, rate);
|
||||
mali_control(FBDUMP_CONTROL_RESIZE_FACTOR, resize_factor);
|
||||
|
||||
pr_debug("gator: sent mali_control enabled=%d, rate=%d\n", (counter_enabled[COUNTER_FILMSTRIP] ? 1 : 0), rate);
|
||||
|
||||
symbol_put(_mali_profiling_control);
|
||||
} else {
|
||||
pr_err("gator: mali online _mali_profiling_control symbol not found\n");
|
||||
}
|
||||
|
||||
mali_get_counters = symbol_get(_mali_profiling_get_counters);
|
||||
if (mali_get_counters)
|
||||
pr_debug("gator: mali online _mali_profiling_get_counters symbol @ %p\n", mali_get_counters);
|
||||
else
|
||||
pr_debug("gator: mali _mali_profiling_get_counters symbol not defined\n");
|
||||
|
||||
mali_get_l2_counters = symbol_get(_mali_profiling_get_l2_counters);
|
||||
if (mali_get_l2_counters)
|
||||
pr_debug("gator: mali online _mali_profiling_get_l2_counters symbol @ %p\n", mali_get_l2_counters);
|
||||
else
|
||||
pr_debug("gator: mali _mali_profiling_get_l2_counters symbol not defined\n");
|
||||
|
||||
if (!mali_get_counters && !mali_get_l2_counters) {
|
||||
pr_err("gator: no L2 counters available\n");
|
||||
n_l2_cores = 0;
|
||||
}
|
||||
|
||||
/* Clear counters in the start */
|
||||
for (i = 0; i < NUMBER_OF_EVENTS; i++) {
|
||||
atomic_set(&counter_data[i], 0);
|
||||
prev_set[i] = false;
|
||||
}
|
||||
}
|
||||
|
||||
static void mali_counter_deinitialize(void)
|
||||
{
|
||||
mali_profiling_set_event_type *mali_set_hw_event;
|
||||
mali_profiling_control_type *mali_control;
|
||||
|
||||
mali_set_hw_event = symbol_get(_mali_profiling_set_event);
|
||||
|
||||
if (mali_set_hw_event) {
|
||||
int i;
|
||||
|
||||
pr_debug("gator: mali offline _mali_profiling_set_event symbol @ %p\n", mali_set_hw_event);
|
||||
for (i = FIRST_HW_COUNTER; i <= LAST_HW_COUNTER; i++)
|
||||
mali_set_hw_event(i, 0xFFFFFFFF);
|
||||
|
||||
symbol_put(_mali_profiling_set_event);
|
||||
} else {
|
||||
pr_err("gator: mali offline _mali_profiling_set_event symbol not found\n");
|
||||
}
|
||||
|
||||
/* Generic control interface for Mali DDK. */
|
||||
mali_control = symbol_get(_mali_profiling_control);
|
||||
|
||||
if (mali_control) {
|
||||
pr_debug("gator: mali offline _mali_profiling_control symbol @ %p\n", mali_control);
|
||||
|
||||
/* Reset the DDK state - disable counter collection */
|
||||
mali_control(SW_COUNTER_ENABLE, 0);
|
||||
|
||||
mali_control(FBDUMP_CONTROL_ENABLE, 0);
|
||||
|
||||
symbol_put(_mali_profiling_control);
|
||||
} else {
|
||||
pr_err("gator: mali offline _mali_profiling_control symbol not found\n");
|
||||
}
|
||||
|
||||
if (mali_get_counters)
|
||||
symbol_put(_mali_profiling_get_counters);
|
||||
|
||||
if (mali_get_l2_counters)
|
||||
symbol_put(_mali_profiling_get_l2_counters);
|
||||
}
|
||||
|
||||
static int start(void)
|
||||
{
|
||||
/* register tracepoints */
|
||||
if (GATOR_REGISTER_TRACE(mali_hw_counter)) {
|
||||
pr_err("gator: mali_hw_counter tracepoint failed to activate\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* For Mali drivers with built-in support. */
|
||||
if (GATOR_REGISTER_TRACE(mali_sw_counters)) {
|
||||
pr_err("gator: mali_sw_counters tracepoint failed to activate\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
trace_registered = 1;
|
||||
|
||||
mali_counter_initialize();
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stop(void)
|
||||
{
|
||||
unsigned int cnt;
|
||||
|
||||
pr_debug("gator: mali stop\n");
|
||||
|
||||
if (trace_registered) {
|
||||
GATOR_UNREGISTER_TRACE(mali_hw_counter);
|
||||
|
||||
/* For Mali drivers with built-in support. */
|
||||
GATOR_UNREGISTER_TRACE(mali_sw_counters);
|
||||
|
||||
pr_debug("gator: mali timeline tracepoint deactivated\n");
|
||||
|
||||
trace_registered = 0;
|
||||
}
|
||||
|
||||
for (cnt = 0; cnt < NUMBER_OF_EVENTS; cnt++) {
|
||||
counter_enabled[cnt] = 0;
|
||||
counter_event[cnt] = 0;
|
||||
}
|
||||
|
||||
mali_counter_deinitialize();
|
||||
}
|
||||
|
||||
static void dump_counters(unsigned int from_counter, unsigned int to_counter, unsigned int *len)
|
||||
{
|
||||
unsigned int counter_id;
|
||||
|
||||
for (counter_id = from_counter; counter_id <= to_counter; counter_id++) {
|
||||
if (counter_enabled[counter_id]) {
|
||||
unsigned int value;
|
||||
|
||||
counter_dump[(*len)++] = counter_key[counter_id];
|
||||
value = atomic_read(&counter_data[counter_id]);
|
||||
atomic_sub(value, &counter_data[counter_id]);
|
||||
counter_dump[(*len)++] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int read(int **buffer, bool sched_switch)
|
||||
{
|
||||
int len = 0;
|
||||
|
||||
if (!on_primary_core())
|
||||
return 0;
|
||||
|
||||
/* Read the L2 C0 and C1 here. */
|
||||
if (n_l2_cores > 0 && is_any_counter_enabled(COUNTER_L2_0_C0, COUNTER_L2_0_C0 + (2 * n_l2_cores))) {
|
||||
unsigned int unavailable_l2_caches = 0;
|
||||
struct _mali_profiling_l2_counter_values cache_values;
|
||||
unsigned int cache_id;
|
||||
struct _mali_profiling_core_counters *per_core;
|
||||
|
||||
/* Poke the driver to get the counter values - older style; only one L2 cache */
|
||||
if (mali_get_l2_counters) {
|
||||
unavailable_l2_caches = mali_get_l2_counters(&cache_values);
|
||||
} else if (mali_get_counters) {
|
||||
per_core = &cache_values.cores[0];
|
||||
mali_get_counters(&per_core->source0, &per_core->value0, &per_core->source1, &per_core->value1);
|
||||
} else {
|
||||
/* This should never happen, as n_l2_caches is only set > 0 if one of the above functions is found. */
|
||||
}
|
||||
|
||||
/* Fill in the two cache counter values for each cache block. */
|
||||
for (cache_id = 0; cache_id < n_l2_cores; cache_id++) {
|
||||
unsigned int counter_id_0 = COUNTER_L2_0_C0 + (2 * cache_id);
|
||||
unsigned int counter_id_1 = counter_id_0 + 1;
|
||||
|
||||
if ((1 << cache_id) & unavailable_l2_caches)
|
||||
continue; /* This cache is unavailable (powered-off, possibly). */
|
||||
|
||||
per_core = &cache_values.cores[cache_id];
|
||||
|
||||
if (counter_enabled[counter_id_0] && prev_set[counter_id_0]) {
|
||||
/* Calculate and save src0's counter val0 */
|
||||
counter_dump[len++] = counter_key[counter_id_0];
|
||||
counter_dump[len++] = per_core->value0 - counter_prev[counter_id_0];
|
||||
}
|
||||
|
||||
if (counter_enabled[counter_id_1] && prev_set[counter_id_1]) {
|
||||
/* Calculate and save src1's counter val1 */
|
||||
counter_dump[len++] = counter_key[counter_id_1];
|
||||
counter_dump[len++] = per_core->value1 - counter_prev[counter_id_1];
|
||||
}
|
||||
|
||||
/* Save the previous values for the counters. */
|
||||
counter_prev[counter_id_0] = per_core->value0;
|
||||
prev_set[counter_id_0] = true;
|
||||
counter_prev[counter_id_1] = per_core->value1;
|
||||
prev_set[counter_id_1] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/* Process other (non-timeline) counters. */
|
||||
dump_counters(COUNTER_VP_0_C0, COUNTER_VP_0_C0 + (2 * n_vp_cores) - 1, &len);
|
||||
dump_counters(COUNTER_FP_0_C0, COUNTER_FP_0_C0 + (2 * n_fp_cores) - 1, &len);
|
||||
|
||||
dump_counters(FIRST_SW_COUNTER, LAST_SW_COUNTER, &len);
|
||||
|
||||
#ifdef DVFS_REPORTED_BY_DDK
|
||||
{
|
||||
int cnt;
|
||||
/*
|
||||
* Add in the voltage and frequency counters if enabled. Note
|
||||
* that, since these are actually passed as events, the counter
|
||||
* value should not be cleared.
|
||||
*
|
||||
* Intentionally use atomic_read as these are absolute counters
|
||||
*/
|
||||
cnt = COUNTER_FREQUENCY;
|
||||
if (counter_enabled[cnt]) {
|
||||
counter_dump[len++] = counter_key[cnt];
|
||||
counter_dump[len++] = atomic_read(&counter_data[cnt]);
|
||||
}
|
||||
|
||||
cnt = COUNTER_VOLTAGE;
|
||||
if (counter_enabled[cnt]) {
|
||||
counter_dump[len++] = counter_key[cnt];
|
||||
counter_dump[len++] = atomic_read(&counter_data[cnt]);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (buffer)
|
||||
*buffer = counter_dump;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct gator_interface gator_events_mali_interface = {
|
||||
.name = "mali_4xx",
|
||||
.create_files = create_files,
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.read = read,
|
||||
};
|
||||
|
||||
extern void gator_events_mali_log_dvfs_event(unsigned int frequency_mhz, unsigned int voltage_mv)
|
||||
{
|
||||
#ifdef DVFS_REPORTED_BY_DDK
|
||||
/* Intentionally use atomic_set as these are absolute counters */
|
||||
atomic_set(&counter_data[COUNTER_FREQUENCY], frequency_mhz);
|
||||
atomic_set(&counter_data[COUNTER_VOLTAGE], voltage_mv);
|
||||
#endif
|
||||
}
|
||||
|
||||
int gator_events_mali_init(void)
|
||||
{
|
||||
unsigned int cnt;
|
||||
|
||||
pr_debug("gator: mali init\n");
|
||||
|
||||
gator_mali_initialise_counters(mali_activity, ARRAY_SIZE(mali_activity));
|
||||
|
||||
for (cnt = 0; cnt < NUMBER_OF_EVENTS; cnt++) {
|
||||
counter_enabled[cnt] = 0;
|
||||
counter_event[cnt] = 0;
|
||||
counter_key[cnt] = gator_events_get_key();
|
||||
atomic_set(&counter_data[cnt], 0);
|
||||
}
|
||||
|
||||
trace_registered = 0;
|
||||
|
||||
return gator_events_install(&gator_events_mali_interface);
|
||||
}
|
||||
18
drivers/gator/gator_events_mali_4xx.h
Normal file
18
drivers/gator/gator_events_mali_4xx.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2011-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
/*
|
||||
* Header contains common definitions for the Mali-4xx processors.
|
||||
*/
|
||||
#if !defined(GATOR_EVENTS_MALI_4xx_H)
|
||||
#define GATOR_EVENTS_MALI_4xx_H
|
||||
|
||||
extern void gator_events_mali_log_dvfs_event(unsigned int d0, unsigned int d1);
|
||||
|
||||
#endif /* GATOR_EVENTS_MALI_4xx_H */
|
||||
72
drivers/gator/gator_events_mali_common.c
Normal file
72
drivers/gator/gator_events_mali_common.c
Normal file
@@ -0,0 +1,72 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2012-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
#include "gator_events_mali_common.h"
|
||||
|
||||
extern int gator_mali_create_file_system(const char *mali_name, const char *event_name, struct super_block *sb, struct dentry *root, struct mali_counter *counter, unsigned long *event)
|
||||
{
|
||||
int err;
|
||||
char buf[50];
|
||||
struct dentry *dir;
|
||||
|
||||
/* If the counter name is empty ignore it */
|
||||
if (strlen(event_name) != 0) {
|
||||
/* Set up the filesystem entry for this event. */
|
||||
if (mali_name == NULL)
|
||||
snprintf(buf, sizeof(buf), "ARM_Mali-%s", event_name);
|
||||
else
|
||||
snprintf(buf, sizeof(buf), "ARM_Mali-%s_%s", mali_name, event_name);
|
||||
|
||||
dir = gatorfs_mkdir(sb, root, buf);
|
||||
|
||||
if (dir == NULL) {
|
||||
pr_err("gator: %s: error creating file system for: %s (%s)\n", mali_name, event_name, buf);
|
||||
return -1;
|
||||
}
|
||||
|
||||
err = gatorfs_create_ulong(sb, dir, "enabled", &counter->enabled);
|
||||
if (err != 0) {
|
||||
pr_err("gator: %s: error calling gatorfs_create_ulong for: %s (%s)\n", mali_name, event_name, buf);
|
||||
return -1;
|
||||
}
|
||||
err = gatorfs_create_ro_ulong(sb, dir, "key", &counter->key);
|
||||
if (err != 0) {
|
||||
pr_err("gator: %s: error calling gatorfs_create_ro_ulong for: %s (%s)\n", mali_name, event_name, buf);
|
||||
return -1;
|
||||
}
|
||||
if (counter->cores != -1) {
|
||||
err = gatorfs_create_ro_ulong(sb, dir, "cores", &counter->cores);
|
||||
if (err != 0) {
|
||||
pr_err("gator: %s: error calling gatorfs_create_ro_ulong for: %s (%s)\n", mali_name, event_name, buf);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
if (event != NULL) {
|
||||
err = gatorfs_create_ulong(sb, dir, "event", event);
|
||||
if (err != 0) {
|
||||
pr_err("gator: %s: error calling gatorfs_create_ro_ulong for: %s (%s)\n", mali_name, event_name, buf);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
extern void gator_mali_initialise_counters(struct mali_counter counters[], unsigned int n_counters)
|
||||
{
|
||||
unsigned int cnt;
|
||||
|
||||
for (cnt = 0; cnt < n_counters; cnt++) {
|
||||
struct mali_counter *counter = &counters[cnt];
|
||||
|
||||
counter->key = gator_events_get_key();
|
||||
counter->enabled = 0;
|
||||
counter->cores = -1;
|
||||
}
|
||||
}
|
||||
77
drivers/gator/gator_events_mali_common.h
Normal file
77
drivers/gator/gator_events_mali_common.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2012-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#if !defined(GATOR_EVENTS_MALI_COMMON_H)
|
||||
#define GATOR_EVENTS_MALI_COMMON_H
|
||||
|
||||
#include "gator.h"
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
/* Ensure that MALI_SUPPORT has been defined to something. */
|
||||
#ifndef MALI_SUPPORT
|
||||
#error MALI_SUPPORT not defined!
|
||||
#endif
|
||||
|
||||
/* Values for the supported activity event types */
|
||||
#define ACTIVITY_START (1)
|
||||
#define ACTIVITY_STOP (2)
|
||||
|
||||
/*
|
||||
* Runtime state information for a counter.
|
||||
*/
|
||||
struct mali_counter {
|
||||
/* 'key' (a unique id set by gatord and returned by gator.ko) */
|
||||
unsigned long key;
|
||||
/* counter enable state */
|
||||
unsigned long enabled;
|
||||
/* for activity counters, the number of cores, otherwise -1 */
|
||||
unsigned long cores;
|
||||
};
|
||||
|
||||
/*
|
||||
* Mali-4xx
|
||||
*/
|
||||
typedef int mali_profiling_set_event_type(unsigned int, int);
|
||||
typedef void mali_profiling_control_type(unsigned int, unsigned int);
|
||||
|
||||
/*
|
||||
* Driver entry points for functions called directly by gator.
|
||||
*/
|
||||
extern int _mali_profiling_set_event(unsigned int, int);
|
||||
extern void _mali_profiling_control(unsigned int, unsigned int);
|
||||
extern void _mali_profiling_get_counters(unsigned int *, unsigned int *, unsigned int *, unsigned int *);
|
||||
|
||||
/**
|
||||
* Creates a filesystem entry under /dev/gator relating to the specified event name and key, and
|
||||
* associate the key/enable values with this entry point.
|
||||
*
|
||||
* @param event_name The name of the event.
|
||||
* @param sb Linux super block
|
||||
* @param root Directory under which the entry will be created.
|
||||
* @param counter_key Ptr to location which will be associated with the counter key.
|
||||
* @param counter_enabled Ptr to location which will be associated with the counter enable state.
|
||||
*
|
||||
* @return 0 if entry point was created, non-zero if not.
|
||||
*/
|
||||
extern int gator_mali_create_file_system(const char *mali_name, const char *event_name, struct super_block *sb, struct dentry *root, struct mali_counter *counter, unsigned long *event);
|
||||
|
||||
/**
|
||||
* Initializes the counter array.
|
||||
*
|
||||
* @param keys The array of counters
|
||||
* @param n_counters The number of entries in each of the arrays.
|
||||
*/
|
||||
extern void gator_mali_initialise_counters(struct mali_counter counters[], unsigned int n_counters);
|
||||
|
||||
#endif /* GATOR_EVENTS_MALI_COMMON_H */
|
||||
570
drivers/gator/gator_events_mali_midgard.c
Normal file
570
drivers/gator/gator_events_mali_midgard.c
Normal file
@@ -0,0 +1,570 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2011-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gator.h"
|
||||
|
||||
#include <linux/module.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/math64.h>
|
||||
#include <linux/slab.h>
|
||||
#include <linux/io.h>
|
||||
|
||||
#include "gator_events_mali_midgard.h"
|
||||
|
||||
#ifdef MALI_DIR_MIDGARD
|
||||
/* New DDK Directory structure with kernel/drivers/gpu/arm/midgard*/
|
||||
#include "mali_linux_trace.h"
|
||||
#else
|
||||
/* Old DDK Directory structure with kernel/drivers/gpu/arm/t6xx*/
|
||||
#include "linux/mali_linux_trace.h"
|
||||
#endif
|
||||
|
||||
#include "gator_events_mali_common.h"
|
||||
|
||||
/*
|
||||
* Check that the MALI_SUPPORT define is set to one of the allowable device codes.
|
||||
*/
|
||||
#if (MALI_SUPPORT != MALI_MIDGARD_OR_BIFROST)
|
||||
#error MALI_SUPPORT set to an invalid device code: expecting MALI_MIDGARD_OR_BIFROST
|
||||
#endif
|
||||
|
||||
// This name has to be set in the runtime, since it might be Midgard or Bifrost, depending on circumstances.
|
||||
const char* mali_name = NULL;
|
||||
|
||||
/* Counters for Mali-Midgard/Bifrost:
|
||||
*
|
||||
* - Timeline events
|
||||
* They are tracepoints, but instead of reporting a number they report a START/STOP event.
|
||||
* They are reported in Streamline as number of microseconds while that particular counter was active.
|
||||
*
|
||||
* - SW counters
|
||||
* They are tracepoints reporting a particular number.
|
||||
* They are accumulated in sw_counter_data array until they are passed to Streamline, then they are zeroed.
|
||||
*
|
||||
* - Accumulators
|
||||
* They are the same as software counters but their value is not zeroed.
|
||||
*/
|
||||
|
||||
/* Timeline (start/stop) activity */
|
||||
static const char *const timeline_event_names[] = {
|
||||
"PM_SHADER_0",
|
||||
"PM_SHADER_1",
|
||||
"PM_SHADER_2",
|
||||
"PM_SHADER_3",
|
||||
"PM_SHADER_4",
|
||||
"PM_SHADER_5",
|
||||
"PM_SHADER_6",
|
||||
"PM_SHADER_7",
|
||||
"PM_TILER_0",
|
||||
"PM_L2_0",
|
||||
"PM_L2_1",
|
||||
"MMU_AS_0",
|
||||
"MMU_AS_1",
|
||||
"MMU_AS_2",
|
||||
"MMU_AS_3"
|
||||
};
|
||||
|
||||
enum {
|
||||
PM_SHADER_0 = 0,
|
||||
PM_SHADER_1,
|
||||
PM_SHADER_2,
|
||||
PM_SHADER_3,
|
||||
PM_SHADER_4,
|
||||
PM_SHADER_5,
|
||||
PM_SHADER_6,
|
||||
PM_SHADER_7,
|
||||
PM_TILER_0,
|
||||
PM_L2_0,
|
||||
PM_L2_1,
|
||||
MMU_AS_0,
|
||||
MMU_AS_1,
|
||||
MMU_AS_2,
|
||||
MMU_AS_3
|
||||
};
|
||||
/* The number of shader blocks in the enum above */
|
||||
#define NUM_PM_SHADER (8)
|
||||
|
||||
/* Software Counters */
|
||||
static const char *const software_counter_names[] = {
|
||||
"MMU_PAGE_FAULT_0",
|
||||
"MMU_PAGE_FAULT_1",
|
||||
"MMU_PAGE_FAULT_2",
|
||||
"MMU_PAGE_FAULT_3"
|
||||
};
|
||||
|
||||
enum {
|
||||
MMU_PAGE_FAULT_0 = 0,
|
||||
MMU_PAGE_FAULT_1,
|
||||
MMU_PAGE_FAULT_2,
|
||||
MMU_PAGE_FAULT_3
|
||||
};
|
||||
|
||||
/* Software Counters */
|
||||
static const char *const accumulators_names[] = {
|
||||
"TOTAL_ALLOC_PAGES"
|
||||
};
|
||||
|
||||
enum {
|
||||
TOTAL_ALLOC_PAGES = 0
|
||||
};
|
||||
|
||||
#define FIRST_TIMELINE_EVENT (0)
|
||||
#define NUMBER_OF_TIMELINE_EVENTS (sizeof(timeline_event_names) / sizeof(timeline_event_names[0]))
|
||||
#define FIRST_SOFTWARE_COUNTER (FIRST_TIMELINE_EVENT + NUMBER_OF_TIMELINE_EVENTS)
|
||||
#define NUMBER_OF_SOFTWARE_COUNTERS (sizeof(software_counter_names) / sizeof(software_counter_names[0]))
|
||||
#define FIRST_ACCUMULATOR (FIRST_SOFTWARE_COUNTER + NUMBER_OF_SOFTWARE_COUNTERS)
|
||||
#define NUMBER_OF_ACCUMULATORS (sizeof(accumulators_names) / sizeof(accumulators_names[0]))
|
||||
#define FILMSTRIP (NUMBER_OF_TIMELINE_EVENTS + NUMBER_OF_SOFTWARE_COUNTERS + NUMBER_OF_ACCUMULATORS)
|
||||
#define NUMBER_OF_EVENTS (NUMBER_OF_TIMELINE_EVENTS + NUMBER_OF_SOFTWARE_COUNTERS + NUMBER_OF_ACCUMULATORS + 1)
|
||||
|
||||
/*
|
||||
* gatorfs variables for counter enable state
|
||||
*/
|
||||
static struct mali_counter counters[NUMBER_OF_EVENTS];
|
||||
static unsigned long filmstrip_event;
|
||||
|
||||
/* An array used to return the data we recorded
|
||||
* as key,value pairs hence the *2
|
||||
*/
|
||||
static int counter_dump[NUMBER_OF_EVENTS * 2];
|
||||
|
||||
/*
|
||||
* Array holding counter start times (in ns) for each counter. A zero
|
||||
* here indicates that the activity monitored by this counter is not
|
||||
* running.
|
||||
*/
|
||||
static struct timespec timeline_event_starttime[NUMBER_OF_TIMELINE_EVENTS];
|
||||
|
||||
/* The data we have recorded */
|
||||
static unsigned int timeline_data[NUMBER_OF_TIMELINE_EVENTS];
|
||||
static unsigned int sw_counter_data[NUMBER_OF_SOFTWARE_COUNTERS];
|
||||
static unsigned int accumulators_data[NUMBER_OF_ACCUMULATORS];
|
||||
|
||||
/* Hold the previous timestamp, used to calculate the sample interval. */
|
||||
static struct timespec prev_timestamp;
|
||||
|
||||
static unsigned long long previous_shader_bitmask;
|
||||
static unsigned long long previous_tiler_bitmask;
|
||||
static unsigned long long previous_l2_bitmask;
|
||||
|
||||
/**
|
||||
* Returns the timespan (in microseconds) between the two specified timestamps.
|
||||
*
|
||||
* @param start Ptr to the start timestamp
|
||||
* @param end Ptr to the end timestamp
|
||||
*
|
||||
* @return Number of microseconds between the two timestamps (can be negative if start follows end).
|
||||
*/
|
||||
static inline long get_duration_us(const struct timespec *start, const struct timespec *end)
|
||||
{
|
||||
long event_duration_us = (end->tv_nsec - start->tv_nsec) / 1000;
|
||||
|
||||
event_duration_us += (end->tv_sec - start->tv_sec) * 1000000;
|
||||
|
||||
return event_duration_us;
|
||||
}
|
||||
|
||||
static void record_timeline_event(unsigned int timeline_index, unsigned int type)
|
||||
{
|
||||
struct timespec event_timestamp;
|
||||
struct timespec *event_start = &timeline_event_starttime[timeline_index];
|
||||
|
||||
switch (type) {
|
||||
case ACTIVITY_START:
|
||||
/* Get the event time... */
|
||||
getnstimeofday(&event_timestamp);
|
||||
|
||||
/* Remember the start time if the activity is not already started */
|
||||
if (event_start->tv_sec == 0)
|
||||
*event_start = event_timestamp; /* Structure copy */
|
||||
break;
|
||||
|
||||
case ACTIVITY_STOP:
|
||||
/* if the counter was started... */
|
||||
if (event_start->tv_sec != 0) {
|
||||
/* Get the event time... */
|
||||
getnstimeofday(&event_timestamp);
|
||||
|
||||
/* Accumulate the duration in us */
|
||||
timeline_data[timeline_index] += get_duration_us(event_start, &event_timestamp);
|
||||
|
||||
/* Reset the start time to indicate the activity is stopped. */
|
||||
event_start->tv_sec = 0;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
/* Other activity events are ignored. */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Documentation about the following tracepoints is in mali_linux_trace.h
|
||||
*/
|
||||
|
||||
GATOR_DEFINE_PROBE(mali_pm_status, TP_PROTO(unsigned int event_id, unsigned long long value))
|
||||
{
|
||||
#define SHADER_PRESENT_LO 0x100 /* (RO) Shader core present bitmap, low word */
|
||||
#define TILER_PRESENT_LO 0x110 /* (RO) Tiler core present bitmap, low word */
|
||||
#define L2_PRESENT_LO 0x120 /* (RO) Level 2 cache present bitmap, low word */
|
||||
#define BIT_AT(value, pos) ((value >> pos) & 1)
|
||||
|
||||
switch (event_id) {
|
||||
case SHADER_PRESENT_LO:
|
||||
{
|
||||
unsigned long long changed_bitmask = previous_shader_bitmask ^ value;
|
||||
int pos;
|
||||
|
||||
for (pos = 0; pos < NUM_PM_SHADER; ++pos) {
|
||||
if (BIT_AT(changed_bitmask, pos))
|
||||
record_timeline_event(PM_SHADER_0 + pos, BIT_AT(value, pos) ? ACTIVITY_START : ACTIVITY_STOP);
|
||||
}
|
||||
|
||||
previous_shader_bitmask = value;
|
||||
break;
|
||||
}
|
||||
|
||||
case TILER_PRESENT_LO:
|
||||
{
|
||||
unsigned long long changed = previous_tiler_bitmask ^ value;
|
||||
|
||||
if (BIT_AT(changed, 0))
|
||||
record_timeline_event(PM_TILER_0, BIT_AT(value, 0) ? ACTIVITY_START : ACTIVITY_STOP);
|
||||
|
||||
previous_tiler_bitmask = value;
|
||||
break;
|
||||
}
|
||||
|
||||
case L2_PRESENT_LO:
|
||||
{
|
||||
unsigned long long changed = previous_l2_bitmask ^ value;
|
||||
|
||||
if (BIT_AT(changed, 0))
|
||||
record_timeline_event(PM_L2_0, BIT_AT(value, 0) ? ACTIVITY_START : ACTIVITY_STOP);
|
||||
if (BIT_AT(changed, 4))
|
||||
record_timeline_event(PM_L2_1, BIT_AT(value, 4) ? ACTIVITY_START : ACTIVITY_STOP);
|
||||
|
||||
previous_l2_bitmask = value;
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
/* No other blocks are supported at present */
|
||||
break;
|
||||
}
|
||||
|
||||
#undef SHADER_PRESENT_LO
|
||||
#undef TILER_PRESENT_LO
|
||||
#undef L2_PRESENT_LO
|
||||
#undef BIT_AT
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(mali_page_fault_insert_pages, TP_PROTO(int event_id, unsigned long value))
|
||||
{
|
||||
/* We add to the previous since we may receive many tracepoints in one sample period */
|
||||
sw_counter_data[MMU_PAGE_FAULT_0 + event_id] += value;
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(mali_mmu_as_in_use, TP_PROTO(int event_id))
|
||||
{
|
||||
record_timeline_event(MMU_AS_0 + event_id, ACTIVITY_START);
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(mali_mmu_as_released, TP_PROTO(int event_id))
|
||||
{
|
||||
record_timeline_event(MMU_AS_0 + event_id, ACTIVITY_STOP);
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(mali_total_alloc_pages_change, TP_PROTO(long long int event_id))
|
||||
{
|
||||
accumulators_data[TOTAL_ALLOC_PAGES] = event_id;
|
||||
}
|
||||
|
||||
static int create_files(struct super_block *sb, struct dentry *root)
|
||||
{
|
||||
int event;
|
||||
/*
|
||||
* Create the filesystem for all events
|
||||
*/
|
||||
int counter_index = 0;
|
||||
mali_profiling_control_type *mali_control;
|
||||
|
||||
for (event = FIRST_TIMELINE_EVENT; event < FIRST_TIMELINE_EVENT + NUMBER_OF_TIMELINE_EVENTS; event++) {
|
||||
if (gator_mali_create_file_system(mali_name, timeline_event_names[counter_index], sb, root, &counters[event], NULL) != 0)
|
||||
return -1;
|
||||
counter_index++;
|
||||
}
|
||||
counter_index = 0;
|
||||
for (event = FIRST_SOFTWARE_COUNTER; event < FIRST_SOFTWARE_COUNTER + NUMBER_OF_SOFTWARE_COUNTERS; event++) {
|
||||
if (gator_mali_create_file_system(mali_name, software_counter_names[counter_index], sb, root, &counters[event], NULL) != 0)
|
||||
return -1;
|
||||
counter_index++;
|
||||
}
|
||||
counter_index = 0;
|
||||
for (event = FIRST_ACCUMULATOR; event < FIRST_ACCUMULATOR + NUMBER_OF_ACCUMULATORS; event++) {
|
||||
if (gator_mali_create_file_system(mali_name, accumulators_names[counter_index], sb, root, &counters[event], NULL) != 0)
|
||||
return -1;
|
||||
counter_index++;
|
||||
}
|
||||
|
||||
mali_control = symbol_get(_mali_profiling_control);
|
||||
if (mali_control) {
|
||||
if (gator_mali_create_file_system(mali_name, "Filmstrip_cnt0", sb, root, &counters[FILMSTRIP], &filmstrip_event) != 0)
|
||||
return -1;
|
||||
symbol_put(_mali_profiling_control);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int register_tracepoints(void)
|
||||
{
|
||||
if (GATOR_REGISTER_TRACE(mali_pm_status)) {
|
||||
pr_err("gator: %s: mali_pm_status tracepoint failed to activate\n", mali_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (GATOR_REGISTER_TRACE(mali_page_fault_insert_pages)) {
|
||||
pr_err("gator: %s: mali_page_fault_insert_pages tracepoint failed to activate\n", mali_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (GATOR_REGISTER_TRACE(mali_mmu_as_in_use)) {
|
||||
pr_err("gator: %s: mali_mmu_as_in_use tracepoint failed to activate\n", mali_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (GATOR_REGISTER_TRACE(mali_mmu_as_released)) {
|
||||
pr_err("gator: %s: mali_mmu_as_released tracepoint failed to activate\n", mali_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (GATOR_REGISTER_TRACE(mali_total_alloc_pages_change)) {
|
||||
pr_err("gator: %s: mali_total_alloc_pages_change tracepoint failed to activate\n", mali_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
pr_debug("gator: %s: start\n", mali_name);
|
||||
pr_debug("gator: %s: mali_pm_status probe is at %p\n", mali_name, &probe_mali_pm_status);
|
||||
pr_debug("gator: %s: mali_page_fault_insert_pages probe is at %p\n", mali_name, &probe_mali_page_fault_insert_pages);
|
||||
pr_debug("gator: %s: mali_mmu_as_in_use probe is at %p\n", mali_name, &probe_mali_mmu_as_in_use);
|
||||
pr_debug("gator: %s: mali_mmu_as_released probe is at %p\n", mali_name, &probe_mali_mmu_as_released);
|
||||
pr_debug("gator: %s: mali_total_alloc_pages_change probe is at %p\n", mali_name, &probe_mali_total_alloc_pages_change);
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int start(void)
|
||||
{
|
||||
unsigned int cnt;
|
||||
mali_profiling_control_type *mali_control;
|
||||
|
||||
previous_shader_bitmask = 0;
|
||||
previous_tiler_bitmask = 0;
|
||||
previous_l2_bitmask = 0;
|
||||
|
||||
/* Clean all data for the next capture */
|
||||
for (cnt = 0; cnt < NUMBER_OF_TIMELINE_EVENTS; cnt++) {
|
||||
timeline_event_starttime[cnt].tv_sec = timeline_event_starttime[cnt].tv_nsec = 0;
|
||||
timeline_data[cnt] = 0;
|
||||
}
|
||||
|
||||
for (cnt = 0; cnt < NUMBER_OF_SOFTWARE_COUNTERS; cnt++)
|
||||
sw_counter_data[cnt] = 0;
|
||||
|
||||
for (cnt = 0; cnt < NUMBER_OF_ACCUMULATORS; cnt++)
|
||||
accumulators_data[cnt] = 0;
|
||||
|
||||
/* Register tracepoints */
|
||||
if (register_tracepoints() == 0)
|
||||
return -1;
|
||||
|
||||
/* Generic control interface for Mali DDK. */
|
||||
mali_control = symbol_get(_mali_profiling_control);
|
||||
if (mali_control) {
|
||||
/* The event attribute in the XML file keeps the actual frame rate. */
|
||||
unsigned int enabled = counters[FILMSTRIP].enabled ? 1 : 0;
|
||||
unsigned int rate = filmstrip_event & 0xff;
|
||||
unsigned int resize_factor = (filmstrip_event >> 8) & 0xff;
|
||||
|
||||
pr_debug("gator: mali online _mali_profiling_control symbol @ %p\n", mali_control);
|
||||
|
||||
#define FBDUMP_CONTROL_ENABLE (1)
|
||||
#define FBDUMP_CONTROL_RATE (2)
|
||||
#define FBDUMP_CONTROL_RESIZE_FACTOR (4)
|
||||
mali_control(FBDUMP_CONTROL_ENABLE, enabled);
|
||||
mali_control(FBDUMP_CONTROL_RATE, rate);
|
||||
mali_control(FBDUMP_CONTROL_RESIZE_FACTOR, resize_factor);
|
||||
|
||||
pr_debug("gator: sent mali_control enabled=%d, rate=%d, resize_factor=%d\n", enabled, rate, resize_factor);
|
||||
|
||||
symbol_put(_mali_profiling_control);
|
||||
} else {
|
||||
pr_err("gator: mali online _mali_profiling_control symbol not found\n");
|
||||
}
|
||||
|
||||
/*
|
||||
* Set the first timestamp for calculating the sample interval. The first interval could be quite long,
|
||||
* since it will be the time between 'start' and the first 'read'.
|
||||
* This means that timeline values will be divided by a big number for the first sample.
|
||||
*/
|
||||
getnstimeofday(&prev_timestamp);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void stop(void)
|
||||
{
|
||||
mali_profiling_control_type *mali_control;
|
||||
|
||||
pr_debug("gator: %s: stop\n", mali_name);
|
||||
|
||||
/*
|
||||
* It is safe to unregister traces even if they were not successfully
|
||||
* registered, so no need to check.
|
||||
*/
|
||||
GATOR_UNREGISTER_TRACE(mali_pm_status);
|
||||
pr_debug("gator: %s: mali_pm_status tracepoint deactivated\n", mali_name);
|
||||
|
||||
GATOR_UNREGISTER_TRACE(mali_page_fault_insert_pages);
|
||||
pr_debug("gator: %s: mali_page_fault_insert_pages tracepoint deactivated\n", mali_name);
|
||||
|
||||
GATOR_UNREGISTER_TRACE(mali_mmu_as_in_use);
|
||||
pr_debug("gator: %s: mali_mmu_as_in_use tracepoint deactivated\n", mali_name);
|
||||
|
||||
GATOR_UNREGISTER_TRACE(mali_mmu_as_released);
|
||||
pr_debug("gator: %s: mali_mmu_as_released tracepoint deactivated\n", mali_name);
|
||||
|
||||
GATOR_UNREGISTER_TRACE(mali_total_alloc_pages_change);
|
||||
pr_debug("gator: %s: mali_total_alloc_pages_change tracepoint deactivated\n", mali_name);
|
||||
|
||||
/* Generic control interface for Mali DDK. */
|
||||
mali_control = symbol_get(_mali_profiling_control);
|
||||
if (mali_control) {
|
||||
pr_debug("gator: mali offline _mali_profiling_control symbol @ %p\n", mali_control);
|
||||
|
||||
mali_control(FBDUMP_CONTROL_ENABLE, 0);
|
||||
|
||||
symbol_put(_mali_profiling_control);
|
||||
} else {
|
||||
pr_err("gator: mali offline _mali_profiling_control symbol not found\n");
|
||||
}
|
||||
}
|
||||
|
||||
static int read(int **buffer, bool sched_switch)
|
||||
{
|
||||
int cnt;
|
||||
int len = 0;
|
||||
long sample_interval_us = 0;
|
||||
struct timespec read_timestamp;
|
||||
|
||||
if (!on_primary_core())
|
||||
return 0;
|
||||
|
||||
/* Get the start of this sample period. */
|
||||
getnstimeofday(&read_timestamp);
|
||||
|
||||
/*
|
||||
* Calculate the sample interval if the previous sample time is valid.
|
||||
* We use tv_sec since it will not be 0.
|
||||
*/
|
||||
if (prev_timestamp.tv_sec != 0)
|
||||
sample_interval_us = get_duration_us(&prev_timestamp, &read_timestamp);
|
||||
|
||||
/* Structure copy. Update the previous timestamp. */
|
||||
prev_timestamp = read_timestamp;
|
||||
|
||||
/*
|
||||
* Report the timeline counters (ACTIVITY_START/STOP)
|
||||
*/
|
||||
for (cnt = FIRST_TIMELINE_EVENT; cnt < (FIRST_TIMELINE_EVENT + NUMBER_OF_TIMELINE_EVENTS); cnt++) {
|
||||
struct mali_counter *counter = &counters[cnt];
|
||||
|
||||
if (counter->enabled) {
|
||||
const int index = cnt - FIRST_TIMELINE_EVENT;
|
||||
unsigned int value;
|
||||
|
||||
/* If the activity is still running, reset its start time to the
|
||||
* start of this sample period to correct the count. Add the
|
||||
* time up to the end of the sample onto the count.
|
||||
*/
|
||||
if (timeline_event_starttime[index].tv_sec != 0) {
|
||||
const long event_duration = get_duration_us(&timeline_event_starttime[index], &read_timestamp);
|
||||
|
||||
timeline_data[index] += event_duration;
|
||||
timeline_event_starttime[index] = read_timestamp; /* Activity is still running. */
|
||||
}
|
||||
|
||||
if (sample_interval_us != 0) {
|
||||
/* Convert the counter to a percent-of-sample value */
|
||||
value = (timeline_data[index] * 100) / sample_interval_us;
|
||||
} else {
|
||||
pr_debug("gator: %s: setting value to zero\n", mali_name);
|
||||
value = 0;
|
||||
}
|
||||
|
||||
/* Clear the counter value ready for the next sample. */
|
||||
timeline_data[index] = 0;
|
||||
|
||||
counter_dump[len++] = counter->key;
|
||||
counter_dump[len++] = value;
|
||||
}
|
||||
}
|
||||
|
||||
/* Report the software counters */
|
||||
for (cnt = FIRST_SOFTWARE_COUNTER; cnt < (FIRST_SOFTWARE_COUNTER + NUMBER_OF_SOFTWARE_COUNTERS); cnt++) {
|
||||
const struct mali_counter *counter = &counters[cnt];
|
||||
|
||||
if (counter->enabled) {
|
||||
const int index = cnt - FIRST_SOFTWARE_COUNTER;
|
||||
|
||||
counter_dump[len++] = counter->key;
|
||||
counter_dump[len++] = sw_counter_data[index];
|
||||
/* Set the value to zero for the next time */
|
||||
sw_counter_data[index] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* Report the accumulators */
|
||||
for (cnt = FIRST_ACCUMULATOR; cnt < (FIRST_ACCUMULATOR + NUMBER_OF_ACCUMULATORS); cnt++) {
|
||||
const struct mali_counter *counter = &counters[cnt];
|
||||
|
||||
if (counter->enabled) {
|
||||
const int index = cnt - FIRST_ACCUMULATOR;
|
||||
|
||||
counter_dump[len++] = counter->key;
|
||||
counter_dump[len++] = accumulators_data[index];
|
||||
/* Do not zero the accumulator */
|
||||
}
|
||||
}
|
||||
|
||||
/* Update the buffer */
|
||||
if (buffer)
|
||||
*buffer = counter_dump;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct gator_interface gator_events_mali_midgard_interface = {
|
||||
.name = "mali_sw_counters",
|
||||
.create_files = create_files,
|
||||
.start = start,
|
||||
.stop = stop,
|
||||
.read = read
|
||||
};
|
||||
|
||||
extern int gator_events_mali_midgard_init(void)
|
||||
{
|
||||
pr_debug("gator: Mali-%s: sw_counters init\n", mali_name);
|
||||
|
||||
gator_mali_initialise_counters(counters, NUMBER_OF_EVENTS);
|
||||
|
||||
return gator_events_install(&gator_events_mali_midgard_interface);
|
||||
}
|
||||
38
drivers/gator/gator_events_mali_midgard.h
Normal file
38
drivers/gator/gator_events_mali_midgard.h
Normal file
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2013-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#ifndef GATOR_EVENTS_MALI_MIDGARD_H_
|
||||
#define GATOR_EVENTS_MALI_MIDGARD_H_
|
||||
|
||||
#define SYMBOL_GET(FUNCTION, ERROR_COUNT) \
|
||||
do { \
|
||||
if (FUNCTION ## _symbol) { \
|
||||
pr_err("gator: mali " #FUNCTION " symbol was already registered\n"); \
|
||||
(ERROR_COUNT)++; \
|
||||
} else { \
|
||||
FUNCTION ## _symbol = symbol_get(FUNCTION); \
|
||||
if (!FUNCTION ## _symbol) { \
|
||||
pr_err("gator: mali online " #FUNCTION " symbol not found\n"); \
|
||||
(ERROR_COUNT)++; \
|
||||
} \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
#define SYMBOL_CLEANUP(FUNCTION) \
|
||||
do { \
|
||||
if (FUNCTION ## _symbol) { \
|
||||
symbol_put(FUNCTION); \
|
||||
FUNCTION ## _symbol = NULL; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
// We're using a single place where name for mali GPU architecture is defined.
|
||||
extern const char* mali_name;
|
||||
|
||||
#endif /* GATOR_EVENTS_MALI_MIDGARD_H_ */
|
||||
1026
drivers/gator/gator_events_mali_midgard_hw.c
Normal file
1026
drivers/gator/gator_events_mali_midgard_hw.c
Normal file
File diff suppressed because it is too large
Load Diff
435
drivers/gator/gator_events_meminfo.c
Normal file
435
drivers/gator/gator_events_meminfo.c
Normal file
@@ -0,0 +1,435 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gator.h"
|
||||
|
||||
#include <linux/hardirq.h>
|
||||
#include <linux/kthread.h>
|
||||
#include <linux/sched.h>
|
||||
#include <linux/semaphore.h>
|
||||
#include <linux/swap.h>
|
||||
#include <linux/workqueue.h>
|
||||
#include <trace/events/kmem.h>
|
||||
|
||||
#define USE_THREAD defined(CONFIG_PREEMPT_RT_FULL)
|
||||
|
||||
/*
|
||||
* Handle rename of global_page_state "c41f012ade0b95b0a6e25c7150673e0554736165 mm: rename global_page_state to global_zone_page_state"
|
||||
*/
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 14, 0)
|
||||
#define GLOBAL_ZONE_PAGE_STATE(item) global_page_state(item)
|
||||
#else
|
||||
#define GLOBAL_ZONE_PAGE_STATE(item) global_zone_page_state(item)
|
||||
#endif
|
||||
|
||||
enum {
|
||||
MEMINFO_MEMFREE,
|
||||
MEMINFO_MEMUSED,
|
||||
MEMINFO_BUFFERRAM,
|
||||
MEMINFO_CACHED,
|
||||
MEMINFO_SLAB,
|
||||
MEMINFO_TOTAL,
|
||||
};
|
||||
|
||||
enum {
|
||||
PROC_SIZE,
|
||||
PROC_SHARE,
|
||||
PROC_TEXT,
|
||||
PROC_DATA,
|
||||
PROC_COUNT,
|
||||
};
|
||||
|
||||
static const char * const meminfo_names[] = {
|
||||
"Linux_meminfo_memfree",
|
||||
"Linux_meminfo_memused",
|
||||
"Linux_meminfo_bufferram",
|
||||
"Linux_meminfo_cached",
|
||||
"Linux_meminfo_slab",
|
||||
};
|
||||
|
||||
static const char * const proc_names[] = {
|
||||
"Linux_proc_statm_size",
|
||||
"Linux_proc_statm_share",
|
||||
"Linux_proc_statm_text",
|
||||
"Linux_proc_statm_data",
|
||||
};
|
||||
|
||||
static bool meminfo_global_enabled;
|
||||
static ulong meminfo_enabled[MEMINFO_TOTAL];
|
||||
static ulong meminfo_keys[MEMINFO_TOTAL];
|
||||
static long long meminfo_buffer[2 * (MEMINFO_TOTAL + 2)];
|
||||
static int meminfo_length;
|
||||
static bool new_data_avail;
|
||||
|
||||
static bool proc_global_enabled;
|
||||
static ulong proc_enabled[PROC_COUNT];
|
||||
static ulong proc_keys[PROC_COUNT];
|
||||
static DEFINE_PER_CPU(long long, proc_buffer[2 * (PROC_COUNT + 3)]);
|
||||
|
||||
static void do_read(void);
|
||||
|
||||
#if USE_THREAD
|
||||
|
||||
static int gator_meminfo_func(void *data);
|
||||
static bool gator_meminfo_run;
|
||||
/* Initialize semaphore unlocked to initialize memory values */
|
||||
static DEFINE_SEMAPHORE(gator_meminfo_sem);
|
||||
|
||||
static void notify(void)
|
||||
{
|
||||
up(&gator_meminfo_sem);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static unsigned int mem_event;
|
||||
static void wq_sched_handler(struct work_struct *wsptr);
|
||||
static DECLARE_WORK(work, wq_sched_handler);
|
||||
static struct timer_list meminfo_wake_up_timer;
|
||||
static DECLARE_TIMER_HANDLER(meminfo_wake_up_handler);
|
||||
|
||||
static void notify(void)
|
||||
{
|
||||
mem_event++;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
GATOR_DEFINE_PROBE(mm_page_free, TP_PROTO(struct page *page, unsigned int order))
|
||||
{
|
||||
notify();
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(mm_page_free_batched, TP_PROTO(struct page *page, int cold))
|
||||
{
|
||||
notify();
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(mm_page_alloc, TP_PROTO(struct page *page, unsigned int order, gfp_t gfp_flags, int migratetype))
|
||||
{
|
||||
notify();
|
||||
}
|
||||
|
||||
static int gator_events_meminfo_create_files(struct super_block *sb, struct dentry *root)
|
||||
{
|
||||
struct dentry *dir;
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MEMINFO_TOTAL; i++) {
|
||||
dir = gatorfs_mkdir(sb, root, meminfo_names[i]);
|
||||
if (!dir)
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &meminfo_enabled[i]);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &meminfo_keys[i]);
|
||||
}
|
||||
|
||||
for (i = 0; i < PROC_COUNT; ++i) {
|
||||
dir = gatorfs_mkdir(sb, root, proc_names[i]);
|
||||
if (!dir)
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &proc_enabled[i]);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &proc_keys[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gator_events_meminfo_start(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
new_data_avail = false;
|
||||
meminfo_global_enabled = 0;
|
||||
for (i = 0; i < MEMINFO_TOTAL; i++) {
|
||||
if (meminfo_enabled[i]) {
|
||||
meminfo_global_enabled = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
proc_global_enabled = 0;
|
||||
for (i = 0; i < PROC_COUNT; ++i) {
|
||||
if (proc_enabled[i]) {
|
||||
proc_global_enabled = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (meminfo_enabled[MEMINFO_MEMUSED])
|
||||
proc_global_enabled = 1;
|
||||
|
||||
if (meminfo_global_enabled == 0)
|
||||
return 0;
|
||||
|
||||
if (GATOR_REGISTER_TRACE(mm_page_free))
|
||||
goto mm_page_free_exit;
|
||||
if (GATOR_REGISTER_TRACE(mm_page_free_batched))
|
||||
goto mm_page_free_batched_exit;
|
||||
if (GATOR_REGISTER_TRACE(mm_page_alloc))
|
||||
goto mm_page_alloc_exit;
|
||||
|
||||
do_read();
|
||||
#if USE_THREAD
|
||||
/* Start worker thread */
|
||||
gator_meminfo_run = true;
|
||||
/* Since the mutex starts unlocked, memory values will be initialized */
|
||||
if (IS_ERR(kthread_run(gator_meminfo_func, NULL, "gator_meminfo")))
|
||||
goto kthread_run_exit;
|
||||
#else
|
||||
setup_deferrable_timer_on_stack(&meminfo_wake_up_timer, meminfo_wake_up_handler, 0);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
|
||||
#if USE_THREAD
|
||||
kthread_run_exit:
|
||||
GATOR_UNREGISTER_TRACE(mm_page_alloc);
|
||||
#endif
|
||||
mm_page_alloc_exit:
|
||||
GATOR_UNREGISTER_TRACE(mm_page_free_batched);
|
||||
mm_page_free_batched_exit:
|
||||
GATOR_UNREGISTER_TRACE(mm_page_free);
|
||||
mm_page_free_exit:
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void gator_events_meminfo_stop(void)
|
||||
{
|
||||
if (meminfo_global_enabled) {
|
||||
GATOR_UNREGISTER_TRACE(mm_page_free);
|
||||
GATOR_UNREGISTER_TRACE(mm_page_free_batched);
|
||||
GATOR_UNREGISTER_TRACE(mm_page_alloc);
|
||||
|
||||
#if USE_THREAD
|
||||
/* Stop worker thread */
|
||||
gator_meminfo_run = false;
|
||||
up(&gator_meminfo_sem);
|
||||
#else
|
||||
del_timer_sync(&meminfo_wake_up_timer);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
static void do_read(void)
|
||||
{
|
||||
struct sysinfo info;
|
||||
int i, len;
|
||||
unsigned long long value;
|
||||
|
||||
meminfo_length = len = 0;
|
||||
|
||||
si_meminfo(&info);
|
||||
for (i = 0; i < MEMINFO_TOTAL; i++) {
|
||||
if (meminfo_enabled[i]) {
|
||||
switch (i) {
|
||||
case MEMINFO_MEMFREE:
|
||||
value = info.freeram * PAGE_SIZE;
|
||||
break;
|
||||
case MEMINFO_MEMUSED:
|
||||
/* pid -1 means system wide */
|
||||
meminfo_buffer[len++] = 1;
|
||||
meminfo_buffer[len++] = -1;
|
||||
/* Emit value */
|
||||
meminfo_buffer[len++] = meminfo_keys[MEMINFO_MEMUSED];
|
||||
meminfo_buffer[len++] = (info.totalram - info.freeram) * PAGE_SIZE;
|
||||
/* Clear pid */
|
||||
meminfo_buffer[len++] = 1;
|
||||
meminfo_buffer[len++] = 0;
|
||||
continue;
|
||||
case MEMINFO_BUFFERRAM:
|
||||
value = info.bufferram * PAGE_SIZE;
|
||||
break;
|
||||
case MEMINFO_CACHED:
|
||||
// total_swapcache_pages is not exported so the result is slightly different, but hopefully not too much
|
||||
value = (GLOBAL_ZONE_PAGE_STATE(NR_FILE_PAGES) /*- total_swapcache_pages()*/ - info.bufferram) * PAGE_SIZE;
|
||||
break;
|
||||
case MEMINFO_SLAB:
|
||||
value = (GLOBAL_ZONE_PAGE_STATE(NR_SLAB_RECLAIMABLE) + GLOBAL_ZONE_PAGE_STATE(NR_SLAB_UNRECLAIMABLE)) * PAGE_SIZE;
|
||||
break;
|
||||
default:
|
||||
value = 0;
|
||||
break;
|
||||
}
|
||||
meminfo_buffer[len++] = meminfo_keys[i];
|
||||
meminfo_buffer[len++] = value;
|
||||
}
|
||||
}
|
||||
|
||||
meminfo_length = len;
|
||||
new_data_avail = true;
|
||||
}
|
||||
|
||||
#if USE_THREAD
|
||||
|
||||
static int gator_meminfo_func(void *data)
|
||||
{
|
||||
for (;;) {
|
||||
if (down_killable(&gator_meminfo_sem))
|
||||
break;
|
||||
|
||||
/* Eat up any pending events */
|
||||
while (!down_trylock(&gator_meminfo_sem))
|
||||
;
|
||||
|
||||
if (!gator_meminfo_run)
|
||||
break;
|
||||
|
||||
do_read();
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/* Must be run in process context as the kernel function si_meminfo() can sleep */
|
||||
static void wq_sched_handler(struct work_struct *wsptr)
|
||||
{
|
||||
do_read();
|
||||
}
|
||||
|
||||
static DECLARE_TIMER_HANDLER(meminfo_wake_up_handler)
|
||||
{
|
||||
/* had to delay scheduling work as attempting to schedule work during the context switch is illegal in kernel versions 3.5 and greater */
|
||||
schedule_work(&work);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static int gator_events_meminfo_read(long long **buffer, bool sched_switch)
|
||||
{
|
||||
#if !USE_THREAD
|
||||
static unsigned int last_mem_event;
|
||||
#endif
|
||||
|
||||
if (!on_primary_core() || !meminfo_global_enabled)
|
||||
return 0;
|
||||
|
||||
#if !USE_THREAD
|
||||
if (last_mem_event != mem_event) {
|
||||
last_mem_event = mem_event;
|
||||
mod_timer(&meminfo_wake_up_timer, jiffies + 1);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!new_data_avail)
|
||||
return 0;
|
||||
|
||||
new_data_avail = false;
|
||||
|
||||
if (buffer)
|
||||
*buffer = meminfo_buffer;
|
||||
|
||||
return meminfo_length;
|
||||
}
|
||||
|
||||
static int gator_events_meminfo_read_proc(long long **buffer, struct task_struct *task)
|
||||
{
|
||||
struct mm_struct *mm;
|
||||
u64 share = 0;
|
||||
int i;
|
||||
long long value;
|
||||
int len = 0;
|
||||
int cpu = get_physical_cpu();
|
||||
long long *buf = per_cpu(proc_buffer, cpu);
|
||||
|
||||
if (!proc_global_enabled)
|
||||
return 0;
|
||||
|
||||
/* Collect the memory stats of the process instead of the thread */
|
||||
if (task->group_leader != NULL)
|
||||
task = task->group_leader;
|
||||
|
||||
/* get_task_mm/mmput is not needed in this context because the task and it's mm are required as part of the sched_switch */
|
||||
mm = task->mm;
|
||||
if (mm == NULL)
|
||||
return 0;
|
||||
|
||||
/* Derived from task_statm in fs/proc/task_mmu.c */
|
||||
if (meminfo_enabled[MEMINFO_MEMUSED] || proc_enabled[PROC_SHARE]) {
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0)
|
||||
share = get_mm_counter(mm, MM_FILEPAGES);
|
||||
#else
|
||||
share = get_mm_counter(mm, MM_FILEPAGES) + get_mm_counter(mm, MM_SHMEMPAGES);
|
||||
#endif
|
||||
}
|
||||
|
||||
/* key of 1 indicates a pid */
|
||||
buf[len++] = 1;
|
||||
buf[len++] = task->pid;
|
||||
|
||||
for (i = 0; i < PROC_COUNT; ++i) {
|
||||
if (proc_enabled[i]) {
|
||||
switch (i) {
|
||||
case PROC_SIZE:
|
||||
value = mm->total_vm;
|
||||
break;
|
||||
case PROC_SHARE:
|
||||
value = share;
|
||||
break;
|
||||
case PROC_TEXT:
|
||||
value = (PAGE_ALIGN(mm->end_code) - (mm->start_code & PAGE_MASK)) >> PAGE_SHIFT;
|
||||
break;
|
||||
case PROC_DATA:
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 5, 0)
|
||||
value = mm->total_vm - mm->shared_vm;
|
||||
#else
|
||||
value = mm->total_vm - mm->stack_vm;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
buf[len++] = proc_keys[i];
|
||||
buf[len++] = value * PAGE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
if (meminfo_enabled[MEMINFO_MEMUSED]) {
|
||||
value = share + get_mm_counter(mm, MM_ANONPAGES);
|
||||
/* Send resident for this pid */
|
||||
buf[len++] = meminfo_keys[MEMINFO_MEMUSED];
|
||||
buf[len++] = value * PAGE_SIZE;
|
||||
}
|
||||
|
||||
/* Clear pid */
|
||||
buf[len++] = 1;
|
||||
buf[len++] = 0;
|
||||
|
||||
if (buffer)
|
||||
*buffer = buf;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct gator_interface gator_events_meminfo_interface = {
|
||||
.name = "meminfo",
|
||||
.create_files = gator_events_meminfo_create_files,
|
||||
.start = gator_events_meminfo_start,
|
||||
.stop = gator_events_meminfo_stop,
|
||||
.read64 = gator_events_meminfo_read,
|
||||
.read_proc = gator_events_meminfo_read_proc,
|
||||
};
|
||||
|
||||
int gator_events_meminfo_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
meminfo_global_enabled = 0;
|
||||
for (i = 0; i < MEMINFO_TOTAL; i++) {
|
||||
meminfo_enabled[i] = 0;
|
||||
meminfo_keys[i] = gator_events_get_key();
|
||||
}
|
||||
|
||||
proc_global_enabled = 0;
|
||||
for (i = 0; i < PROC_COUNT; ++i) {
|
||||
proc_enabled[i] = 0;
|
||||
proc_keys[i] = gator_events_get_key();
|
||||
}
|
||||
|
||||
return gator_events_install(&gator_events_meminfo_interface);
|
||||
}
|
||||
210
drivers/gator/gator_events_mmapped.c
Normal file
210
drivers/gator/gator_events_mmapped.c
Normal file
@@ -0,0 +1,210 @@
|
||||
/*
|
||||
* Example events provider
|
||||
*
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
* Similar entries to those below must be present in the events.xml file.
|
||||
* To add them to the events.xml, create an events-mmap.xml with the
|
||||
* following contents and rebuild gatord:
|
||||
*
|
||||
* <category name="mmapped">
|
||||
* <event counter="mmapped_cnt0" title="Simulated1" name="Sine" display="maximum" class="absolute" description="Sort-of-sine"/>
|
||||
* <event counter="mmapped_cnt1" title="Simulated2" name="Triangle" display="maximum" class="absolute" description="Triangular wave"/>
|
||||
* <event counter="mmapped_cnt2" title="Simulated3" name="PWM" display="maximum" class="absolute" description="PWM Signal"/>
|
||||
* </category>
|
||||
*
|
||||
* When adding custom events, be sure to do the following:
|
||||
* - add any needed .c files to the gator driver Makefile
|
||||
* - call gator_events_install in the events init function
|
||||
* - add the init function to GATOR_EVENTS_LIST in gator_main.c
|
||||
* - add a new events-*.xml file to the gator daemon and rebuild
|
||||
*
|
||||
* Troubleshooting:
|
||||
* - verify the new events are part of events.xml, which is created when building the daemon
|
||||
* - verify the new events exist at /dev/gator/events/ once gatord is launched
|
||||
* - verify the counter name in the XML matches the name at /dev/gator/events
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/io.h>
|
||||
#include <linux/ratelimit.h>
|
||||
|
||||
#include "gator.h"
|
||||
|
||||
#define MMAPPED_COUNTERS_NUM 3
|
||||
|
||||
static int mmapped_global_enabled;
|
||||
|
||||
static struct {
|
||||
unsigned long enabled;
|
||||
unsigned long key;
|
||||
} mmapped_counters[MMAPPED_COUNTERS_NUM];
|
||||
|
||||
static int mmapped_buffer[MMAPPED_COUNTERS_NUM * 2];
|
||||
|
||||
static s64 prev_time;
|
||||
|
||||
/* Adds mmapped_cntX directories and enabled, event, and key files to /dev/gator/events */
|
||||
static int gator_events_mmapped_create_files(struct super_block *sb,
|
||||
struct dentry *root)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MMAPPED_COUNTERS_NUM; i++) {
|
||||
char buf[16];
|
||||
struct dentry *dir;
|
||||
|
||||
snprintf(buf, sizeof(buf), "mmapped_cnt%d", i);
|
||||
dir = gatorfs_mkdir(sb, root, buf);
|
||||
if (WARN_ON(!dir))
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled",
|
||||
&mmapped_counters[i].enabled);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key",
|
||||
&mmapped_counters[i].key);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gator_events_mmapped_start(void)
|
||||
{
|
||||
int i;
|
||||
struct timespec ts;
|
||||
|
||||
getnstimeofday(&ts);
|
||||
prev_time = timespec_to_ns(&ts);
|
||||
|
||||
mmapped_global_enabled = 0;
|
||||
for (i = 0; i < MMAPPED_COUNTERS_NUM; i++) {
|
||||
if (mmapped_counters[i].enabled) {
|
||||
mmapped_global_enabled = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gator_events_mmapped_stop(void)
|
||||
{
|
||||
}
|
||||
|
||||
/* This function "simulates" counters, generating values of fancy
|
||||
* functions like sine or triangle... */
|
||||
static int mmapped_simulate(int counter, int delta_in_us)
|
||||
{
|
||||
int result = 0;
|
||||
|
||||
switch (counter) {
|
||||
case 0: /* sort-of-sine */
|
||||
{
|
||||
static int t;
|
||||
int x;
|
||||
|
||||
t += delta_in_us;
|
||||
if (t > 2048000)
|
||||
t = 0;
|
||||
|
||||
if (t % 1024000 < 512000)
|
||||
x = 512000 - (t % 512000);
|
||||
else
|
||||
x = t % 512000;
|
||||
|
||||
result = 32 * x / 512000;
|
||||
result = result * result;
|
||||
|
||||
if (t < 1024000)
|
||||
result = 1922 - result;
|
||||
}
|
||||
break;
|
||||
case 1: /* triangle */
|
||||
{
|
||||
static int v, d = 1;
|
||||
|
||||
v = v + d * delta_in_us;
|
||||
if (v < 0) {
|
||||
v = 0;
|
||||
d = 1;
|
||||
} else if (v > 1000000) {
|
||||
v = 1000000;
|
||||
d = -1;
|
||||
}
|
||||
|
||||
result = v;
|
||||
}
|
||||
break;
|
||||
case 2: /* PWM signal */
|
||||
{
|
||||
static int dc, x, t;
|
||||
|
||||
t += delta_in_us;
|
||||
if (t > 1000000)
|
||||
t = 0;
|
||||
if (x / 1000000 != (x + delta_in_us) / 1000000)
|
||||
dc = (dc + 100000) % 1000000;
|
||||
x += delta_in_us;
|
||||
|
||||
result = t < dc ? 0 : 10;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
static int gator_events_mmapped_read(int **buffer, bool sched_switch)
|
||||
{
|
||||
int i;
|
||||
int len = 0;
|
||||
int delta_in_us;
|
||||
struct timespec ts;
|
||||
s64 time;
|
||||
|
||||
/* System wide counters - read from one core only */
|
||||
if (!on_primary_core() || !mmapped_global_enabled)
|
||||
return 0;
|
||||
|
||||
getnstimeofday(&ts);
|
||||
time = timespec_to_ns(&ts);
|
||||
delta_in_us = (int)(time - prev_time) / 1000;
|
||||
prev_time = time;
|
||||
|
||||
for (i = 0; i < MMAPPED_COUNTERS_NUM; i++) {
|
||||
if (mmapped_counters[i].enabled) {
|
||||
mmapped_buffer[len++] = mmapped_counters[i].key;
|
||||
mmapped_buffer[len++] =
|
||||
mmapped_simulate(i, delta_in_us);
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer)
|
||||
*buffer = mmapped_buffer;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct gator_interface gator_events_mmapped_interface = {
|
||||
.name = "mmapped",
|
||||
.create_files = gator_events_mmapped_create_files,
|
||||
.start = gator_events_mmapped_start,
|
||||
.stop = gator_events_mmapped_stop,
|
||||
.read = gator_events_mmapped_read,
|
||||
};
|
||||
|
||||
/* Must not be static! */
|
||||
int gator_events_mmapped_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < MMAPPED_COUNTERS_NUM; i++) {
|
||||
mmapped_counters[i].enabled = 0;
|
||||
mmapped_counters[i].key = gator_events_get_key();
|
||||
}
|
||||
|
||||
return gator_events_install(&gator_events_mmapped_interface);
|
||||
}
|
||||
165
drivers/gator/gator_events_net.c
Normal file
165
drivers/gator/gator_events_net.c
Normal file
@@ -0,0 +1,165 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gator.h"
|
||||
#include <linux/netdevice.h>
|
||||
#include <linux/hardirq.h>
|
||||
|
||||
#define NETRX 0
|
||||
#define NETTX 1
|
||||
#define TOTALNET 2
|
||||
|
||||
static ulong netrx_enabled;
|
||||
static ulong nettx_enabled;
|
||||
static ulong netrx_key;
|
||||
static ulong nettx_key;
|
||||
static int rx_total, tx_total;
|
||||
static ulong netPrev[TOTALNET];
|
||||
static int netGet[TOTALNET * 4];
|
||||
|
||||
static struct timer_list net_wake_up_timer;
|
||||
|
||||
/* Must be run in process context as the kernel function dev_get_stats() can sleep */
|
||||
static void get_network_stats(struct work_struct *wsptr)
|
||||
{
|
||||
int rx = 0, tx = 0;
|
||||
struct net_device *dev;
|
||||
|
||||
for_each_netdev(&init_net, dev) {
|
||||
struct rtnl_link_stats64 temp;
|
||||
const struct rtnl_link_stats64 *stats = dev_get_stats(dev, &temp);
|
||||
rx += stats->rx_bytes;
|
||||
tx += stats->tx_bytes;
|
||||
}
|
||||
rx_total = rx;
|
||||
tx_total = tx;
|
||||
}
|
||||
|
||||
static DECLARE_WORK(wq_get_stats, get_network_stats);
|
||||
|
||||
static DECLARE_TIMER_HANDLER(net_wake_up_handler)
|
||||
{
|
||||
/* had to delay scheduling work as attempting to schedule work during the context switch is illegal in kernel versions 3.5 and greater */
|
||||
schedule_work(&wq_get_stats);
|
||||
}
|
||||
|
||||
static void calculate_delta(int *rx, int *tx)
|
||||
{
|
||||
int rx_calc, tx_calc;
|
||||
|
||||
rx_calc = (int)(rx_total - netPrev[NETRX]);
|
||||
if (rx_calc < 0)
|
||||
rx_calc = 0;
|
||||
netPrev[NETRX] += rx_calc;
|
||||
|
||||
tx_calc = (int)(tx_total - netPrev[NETTX]);
|
||||
if (tx_calc < 0)
|
||||
tx_calc = 0;
|
||||
netPrev[NETTX] += tx_calc;
|
||||
|
||||
*rx = rx_calc;
|
||||
*tx = tx_calc;
|
||||
}
|
||||
|
||||
static int gator_events_net_create_files(struct super_block *sb, struct dentry *root)
|
||||
{
|
||||
/* Network counters are not currently supported in RT-Preempt full because mod_timer is used */
|
||||
#ifndef CONFIG_PREEMPT_RT_FULL
|
||||
struct dentry *dir;
|
||||
|
||||
dir = gatorfs_mkdir(sb, root, "Linux_net_rx");
|
||||
if (!dir)
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &netrx_enabled);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &netrx_key);
|
||||
|
||||
dir = gatorfs_mkdir(sb, root, "Linux_net_tx");
|
||||
if (!dir)
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &nettx_enabled);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &nettx_key);
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gator_events_net_start(void)
|
||||
{
|
||||
get_network_stats(NULL);
|
||||
netPrev[NETRX] = rx_total;
|
||||
netPrev[NETTX] = tx_total;
|
||||
setup_deferrable_timer_on_stack(&net_wake_up_timer, net_wake_up_handler, 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gator_events_net_stop(void)
|
||||
{
|
||||
del_timer_sync(&net_wake_up_timer);
|
||||
netrx_enabled = 0;
|
||||
nettx_enabled = 0;
|
||||
}
|
||||
|
||||
static int gator_events_net_read(int **buffer, bool sched_switch)
|
||||
{
|
||||
int len, rx_delta, tx_delta;
|
||||
static int last_rx_delta, last_tx_delta;
|
||||
|
||||
if (!on_primary_core())
|
||||
return 0;
|
||||
|
||||
if (!netrx_enabled && !nettx_enabled)
|
||||
return 0;
|
||||
|
||||
mod_timer(&net_wake_up_timer, jiffies + 1);
|
||||
|
||||
calculate_delta(&rx_delta, &tx_delta);
|
||||
|
||||
len = 0;
|
||||
if (netrx_enabled && last_rx_delta != rx_delta) {
|
||||
last_rx_delta = rx_delta;
|
||||
netGet[len++] = netrx_key;
|
||||
/* indicates to Streamline that rx_delta bytes were transmitted now, not since the last message */
|
||||
netGet[len++] = 0;
|
||||
netGet[len++] = netrx_key;
|
||||
netGet[len++] = rx_delta;
|
||||
}
|
||||
|
||||
if (nettx_enabled && last_tx_delta != tx_delta) {
|
||||
last_tx_delta = tx_delta;
|
||||
netGet[len++] = nettx_key;
|
||||
/* indicates to Streamline that tx_delta bytes were transmitted now, not since the last message */
|
||||
netGet[len++] = 0;
|
||||
netGet[len++] = nettx_key;
|
||||
netGet[len++] = tx_delta;
|
||||
}
|
||||
|
||||
if (buffer)
|
||||
*buffer = netGet;
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct gator_interface gator_events_net_interface = {
|
||||
.name = "net",
|
||||
.create_files = gator_events_net_create_files,
|
||||
.start = gator_events_net_start,
|
||||
.stop = gator_events_net_stop,
|
||||
.read = gator_events_net_read,
|
||||
};
|
||||
|
||||
int gator_events_net_init(void)
|
||||
{
|
||||
netrx_key = gator_events_get_key();
|
||||
nettx_key = gator_events_get_key();
|
||||
|
||||
netrx_enabled = 0;
|
||||
nettx_enabled = 0;
|
||||
|
||||
return gator_events_install(&gator_events_net_interface);
|
||||
}
|
||||
581
drivers/gator/gator_events_perf_pmu.c
Normal file
581
drivers/gator/gator_events_perf_pmu.c
Normal file
@@ -0,0 +1,581 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include "gator.h"
|
||||
|
||||
/* gator_events_armvX.c is used for Linux 2.6.x */
|
||||
#if GATOR_PERF_PMU_SUPPORT
|
||||
|
||||
#include <linux/io.h>
|
||||
#include <linux/perf_event.h>
|
||||
#include <linux/slab.h>
|
||||
|
||||
/* Maximum number of per-core counters - currently reserves enough space for two full hardware PMUs for big.LITTLE */
|
||||
#define CNTMAX 16
|
||||
/* Maximum number of uncore counters */
|
||||
#define UCCNT 32
|
||||
|
||||
/* Default to 0 if unable to probe the revision which was the previous behavior */
|
||||
#define DEFAULT_CCI_REVISION 0
|
||||
|
||||
/* A gator_attr is needed for every counter */
|
||||
struct gator_attr {
|
||||
/* Set once in gator_events_perf_pmu_*_init - the name of the event in the gatorfs */
|
||||
char name[40];
|
||||
/* Exposed in gatorfs - set by gatord to enable this counter */
|
||||
unsigned long enabled;
|
||||
/* Set once in gator_events_perf_pmu_*_init - the perf type to use, see perf_type_id in the perf_event.h header file. */
|
||||
unsigned long type;
|
||||
/* Exposed in gatorfs - set by gatord to select the event to collect */
|
||||
unsigned long event;
|
||||
/* Exposed in gatorfs - set by gatord with the sample period to use and enable EBS for this counter */
|
||||
unsigned long count;
|
||||
/* Exposed as read only in gatorfs - set once in __attr_init as the key to use in the APC data */
|
||||
unsigned long key;
|
||||
/* only one of the two may ever be set */
|
||||
/* The gator_cpu object that it belongs to */
|
||||
const struct gator_cpu * gator_cpu;
|
||||
/* The uncore_pmu object that it belongs to */
|
||||
const struct uncore_pmu * uncore_pmu;
|
||||
};
|
||||
|
||||
/* Per-core counter attributes */
|
||||
static struct gator_attr attrs[CNTMAX];
|
||||
/* Number of initialized per-core counters */
|
||||
static int attr_count;
|
||||
/* Uncore counter attributes */
|
||||
static struct gator_attr uc_attrs[UCCNT];
|
||||
/* Number of initialized uncore counters */
|
||||
static int uc_attr_count;
|
||||
/* Mapping from CPU to gator_cpu object */
|
||||
static const struct gator_cpu * gator_cpus_per_core[ARRAY_SIZE(gator_cpuids)];
|
||||
|
||||
struct gator_event {
|
||||
uint32_t curr;
|
||||
uint32_t prev;
|
||||
uint32_t prev_delta;
|
||||
bool zero;
|
||||
struct perf_event *pevent;
|
||||
struct perf_event_attr *pevent_attr;
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(struct gator_event[CNTMAX], events);
|
||||
static struct gator_event uc_events[UCCNT];
|
||||
static DEFINE_PER_CPU(int[(CNTMAX + UCCNT)*2], perf_cnt);
|
||||
|
||||
static void gator_events_perf_pmu_stop(void);
|
||||
|
||||
static int __create_files(struct super_block *sb, struct dentry *root, struct gator_attr *const attr)
|
||||
{
|
||||
struct dentry *dir;
|
||||
|
||||
if (attr->name[0] == '\0')
|
||||
return 0;
|
||||
dir = gatorfs_mkdir(sb, root, attr->name);
|
||||
if (!dir)
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &attr->enabled);
|
||||
gatorfs_create_ulong(sb, dir, "count", &attr->count);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &attr->key);
|
||||
gatorfs_create_ulong(sb, dir, "event", &attr->event);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gator_events_perf_pmu_create_files(struct super_block *sb, struct dentry *root)
|
||||
{
|
||||
int cnt;
|
||||
|
||||
for (cnt = 0; cnt < attr_count; cnt++) {
|
||||
if (__create_files(sb, root, &attrs[cnt]) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (cnt = 0; cnt < uc_attr_count; cnt++) {
|
||||
if (__create_files(sb, root, &uc_attrs[cnt]) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void ebs_overflow_handler(struct perf_event *event, struct perf_sample_data *data, struct pt_regs *regs)
|
||||
{
|
||||
gator_backtrace_handler(regs);
|
||||
}
|
||||
|
||||
static void dummy_handler(struct perf_event *event, struct perf_sample_data *data, struct pt_regs *regs)
|
||||
{
|
||||
/* Required as perf_event_create_kernel_counter() requires an overflow handler, even though all we do is poll */
|
||||
}
|
||||
|
||||
static int gator_events_perf_pmu_read(int **buffer, bool sched_switch);
|
||||
|
||||
static int gator_events_perf_pmu_online(int **buffer, bool migrate)
|
||||
{
|
||||
return gator_events_perf_pmu_read(buffer, false);
|
||||
}
|
||||
|
||||
static void __online_dispatch(int cpu, bool migrate, struct gator_attr *const attr, struct gator_event *const event)
|
||||
{
|
||||
const char * const cpu_core_name = (gator_cpus_per_core[cpu] != NULL ? gator_cpus_per_core[cpu]->core_name : "Unknown");
|
||||
const u32 cpu_cpuid = (gator_cpus_per_core[cpu] != NULL ? gator_cpus_per_core[cpu]->cpuid : gator_cpuids[cpu]);
|
||||
|
||||
perf_overflow_handler_t handler;
|
||||
struct perf_event *pevent;
|
||||
|
||||
event->zero = true;
|
||||
|
||||
if (event->pevent != NULL || event->pevent_attr == NULL || migrate)
|
||||
return;
|
||||
|
||||
if ((gator_cpus_per_core[cpu] == NULL) || ((gator_cpus_per_core[cpu] != attr->gator_cpu) && (attr->gator_cpu != NULL))) {
|
||||
pr_debug("gator: Counter %s does not apply to core %i (Core: %s 0x%x, Counter: %s 0x%lx)\n",
|
||||
attr->name, cpu, cpu_core_name, cpu_cpuid, attr->gator_cpu->core_name, attr->gator_cpu->cpuid);
|
||||
return;
|
||||
}
|
||||
else {
|
||||
pr_debug("gator: Counter %s applies to core %i (%s 0x%x)\n", attr->name, cpu, cpu_core_name, cpu_cpuid);
|
||||
}
|
||||
|
||||
if (attr->count > 0)
|
||||
handler = ebs_overflow_handler;
|
||||
else
|
||||
handler = dummy_handler;
|
||||
|
||||
pevent = perf_event_create_kernel_counter(event->pevent_attr, cpu, NULL, handler, NULL);
|
||||
if (IS_ERR(pevent)) {
|
||||
pr_err("gator: unable to online a counter on cpu %d\n", cpu);
|
||||
return;
|
||||
}
|
||||
|
||||
if (pevent->state != PERF_EVENT_STATE_ACTIVE) {
|
||||
pr_err("gator: inactive counter on cpu %d\n", cpu);
|
||||
perf_event_release_kernel(pevent);
|
||||
return;
|
||||
}
|
||||
|
||||
event->pevent = pevent;
|
||||
}
|
||||
|
||||
#define GATOR_IF_UNCORE_CPUMASK(uncore_pmu, cpu, cpumask) \
|
||||
(cpumask) = (struct cpumask *) GATOR_ATOMIC_READ(&((uncore_pmu)->cpumask_atomic)); \
|
||||
if (((!(cpumask)) && ((cpu) == 0)) || ((cpumask) && cpumask_test_cpu((cpu), (cpumask))))
|
||||
|
||||
static void gator_events_perf_pmu_online_dispatch(int cpu, bool migrate)
|
||||
{
|
||||
int cnt;
|
||||
|
||||
cpu = pcpu_to_lcpu(cpu);
|
||||
|
||||
for (cnt = 0; cnt < attr_count; cnt++) {
|
||||
__online_dispatch(cpu, migrate, &attrs[cnt], &per_cpu(events, cpu)[cnt]);
|
||||
}
|
||||
|
||||
for (cnt = 0; cnt < uc_attr_count; cnt++) {
|
||||
struct cpumask * cpumask;
|
||||
GATOR_IF_UNCORE_CPUMASK(uc_attrs[cnt].uncore_pmu, cpu, cpumask) {
|
||||
__online_dispatch(cpu, migrate, &uc_attrs[cnt], &uc_events[cnt]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void __offline_dispatch(int cpu, struct gator_event *const event)
|
||||
{
|
||||
struct perf_event *pe = NULL;
|
||||
|
||||
if (event->pevent) {
|
||||
pe = event->pevent;
|
||||
event->pevent = NULL;
|
||||
}
|
||||
|
||||
if (pe)
|
||||
perf_event_release_kernel(pe);
|
||||
}
|
||||
|
||||
static void gator_events_perf_pmu_offline_dispatch(int cpu, bool migrate)
|
||||
{
|
||||
int cnt;
|
||||
|
||||
if (migrate)
|
||||
return;
|
||||
cpu = pcpu_to_lcpu(cpu);
|
||||
|
||||
for (cnt = 0; cnt < attr_count; cnt++)
|
||||
__offline_dispatch(cpu, &per_cpu(events, cpu)[cnt]);
|
||||
|
||||
for (cnt = 0; cnt < uc_attr_count; cnt++) {
|
||||
struct cpumask * cpumask;
|
||||
GATOR_IF_UNCORE_CPUMASK(uc_attrs[cnt].uncore_pmu, cpu, cpumask) {
|
||||
__offline_dispatch(cpu, &uc_events[cnt]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int __check_ebs(struct gator_attr *const attr)
|
||||
{
|
||||
if (attr->count > 0) {
|
||||
if (!event_based_sampling) {
|
||||
event_based_sampling = true;
|
||||
} else {
|
||||
pr_warning("gator: Only one ebs counter is allowed\n");
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int __start(struct gator_attr *const attr, struct gator_event *const event)
|
||||
{
|
||||
u32 size = sizeof(struct perf_event_attr);
|
||||
|
||||
event->pevent = NULL;
|
||||
/* Skip disabled counters */
|
||||
if (!attr->enabled)
|
||||
return 0;
|
||||
|
||||
event->prev = 0;
|
||||
event->curr = 0;
|
||||
event->prev_delta = 0;
|
||||
event->pevent_attr = kmalloc(size, GFP_KERNEL);
|
||||
if (!event->pevent_attr) {
|
||||
gator_events_perf_pmu_stop();
|
||||
return -1;
|
||||
}
|
||||
|
||||
memset(event->pevent_attr, 0, size);
|
||||
event->pevent_attr->type = attr->type;
|
||||
event->pevent_attr->size = size;
|
||||
event->pevent_attr->config = attr->event;
|
||||
event->pevent_attr->sample_period = attr->count;
|
||||
event->pevent_attr->pinned = 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gator_events_perf_pmu_start(void)
|
||||
{
|
||||
int cnt, cpu;
|
||||
|
||||
event_based_sampling = false;
|
||||
for (cnt = 0; cnt < attr_count; cnt++) {
|
||||
if (__check_ebs(&attrs[cnt]) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (cnt = 0; cnt < uc_attr_count; cnt++) {
|
||||
if (__check_ebs(&uc_attrs[cnt]) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
for_each_present_cpu(cpu) {
|
||||
for (cnt = 0; cnt < attr_count; cnt++) {
|
||||
if (__start(&attrs[cnt], &per_cpu(events, cpu)[cnt]) != 0)
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
for (cnt = 0; cnt < uc_attr_count; cnt++) {
|
||||
if (__start(&uc_attrs[cnt], &uc_events[cnt]) != 0)
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void __event_stop(struct gator_event *const event)
|
||||
{
|
||||
kfree(event->pevent_attr);
|
||||
event->pevent_attr = NULL;
|
||||
}
|
||||
|
||||
static void __attr_stop(struct gator_attr *const attr)
|
||||
{
|
||||
attr->enabled = 0;
|
||||
attr->event = 0;
|
||||
attr->count = 0;
|
||||
}
|
||||
|
||||
static void gator_events_perf_pmu_stop(void)
|
||||
{
|
||||
unsigned int cnt, cpu;
|
||||
|
||||
for_each_present_cpu(cpu) {
|
||||
for (cnt = 0; cnt < attr_count; cnt++)
|
||||
__event_stop(&per_cpu(events, cpu)[cnt]);
|
||||
}
|
||||
|
||||
for (cnt = 0; cnt < uc_attr_count; cnt++)
|
||||
__event_stop(&uc_events[cnt]);
|
||||
|
||||
for (cnt = 0; cnt < attr_count; cnt++)
|
||||
__attr_stop(&attrs[cnt]);
|
||||
|
||||
for (cnt = 0; cnt < uc_attr_count; cnt++)
|
||||
__attr_stop(&uc_attrs[cnt]);
|
||||
}
|
||||
|
||||
static void __read(int *const len, int cpu, struct gator_attr *const attr, struct gator_event *const event)
|
||||
{
|
||||
uint32_t delta;
|
||||
struct perf_event *const ev = event->pevent;
|
||||
|
||||
if (ev != NULL && ev->state == PERF_EVENT_STATE_ACTIVE) {
|
||||
/* After creating the perf counter in __online_dispatch, there
|
||||
* is a race condition between gator_events_perf_pmu_online and
|
||||
* gator_events_perf_pmu_read. So have
|
||||
* gator_events_perf_pmu_online call gator_events_perf_pmu_read
|
||||
* and in __read check to see if it's the first call after
|
||||
* __online_dispatch and if so, run the online code.
|
||||
*/
|
||||
if (event->zero) {
|
||||
ev->pmu->read(ev);
|
||||
event->prev = event->curr = local64_read(&ev->count);
|
||||
event->prev_delta = 0;
|
||||
per_cpu(perf_cnt, cpu)[(*len)++] = attr->key;
|
||||
per_cpu(perf_cnt, cpu)[(*len)++] = 0;
|
||||
event->zero = false;
|
||||
} else {
|
||||
ev->pmu->read(ev);
|
||||
event->curr = local64_read(&ev->count);
|
||||
delta = event->curr - event->prev;
|
||||
if (delta != 0 || delta != event->prev_delta) {
|
||||
event->prev_delta = delta;
|
||||
event->prev = event->curr;
|
||||
per_cpu(perf_cnt, cpu)[(*len)++] = attr->key;
|
||||
per_cpu(perf_cnt, cpu)[(*len)++] = delta;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int gator_events_perf_pmu_read(int **buffer, bool sched_switch)
|
||||
{
|
||||
int cnt, len = 0;
|
||||
const int cpu = get_logical_cpu();
|
||||
|
||||
for (cnt = 0; cnt < attr_count; cnt++)
|
||||
__read(&len, cpu, &attrs[cnt], &per_cpu(events, cpu)[cnt]);
|
||||
|
||||
for (cnt = 0; cnt < uc_attr_count; cnt++) {
|
||||
struct cpumask * cpumask;
|
||||
GATOR_IF_UNCORE_CPUMASK(uc_attrs[cnt].uncore_pmu, cpu, cpumask) {
|
||||
__read(&len, cpu, &uc_attrs[cnt], &uc_events[cnt]);
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer)
|
||||
*buffer = per_cpu(perf_cnt, cpu);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct gator_interface gator_events_perf_pmu_interface = {
|
||||
.name = "perf_pmu",
|
||||
.start = gator_events_perf_pmu_start,
|
||||
.stop = gator_events_perf_pmu_stop,
|
||||
.online = gator_events_perf_pmu_online,
|
||||
.online_dispatch = gator_events_perf_pmu_online_dispatch,
|
||||
.offline_dispatch = gator_events_perf_pmu_offline_dispatch,
|
||||
.read = gator_events_perf_pmu_read,
|
||||
};
|
||||
|
||||
static void __attr_init(struct gator_attr *const attr)
|
||||
{
|
||||
attr->name[0] = '\0';
|
||||
attr->enabled = 0;
|
||||
attr->type = 0;
|
||||
attr->event = 0;
|
||||
attr->count = 0;
|
||||
attr->key = gator_events_get_key();
|
||||
attr->gator_cpu = NULL;
|
||||
attr->uncore_pmu = NULL;
|
||||
}
|
||||
|
||||
static void gator_events_perf_pmu_uncore_init(const struct uncore_pmu *const uncore_pmu, const int type)
|
||||
{
|
||||
int cnt;
|
||||
|
||||
if (uncore_pmu->has_cycles_counter) {
|
||||
if (uc_attr_count < ARRAY_SIZE(uc_attrs)) {
|
||||
snprintf(uc_attrs[uc_attr_count].name, sizeof(uc_attrs[uc_attr_count].name), "%s_ccnt", uncore_pmu->core_name);
|
||||
uc_attrs[uc_attr_count].type = type;
|
||||
uc_attrs[uc_attr_count].gator_cpu = NULL;
|
||||
uc_attrs[uc_attr_count].uncore_pmu = uncore_pmu;
|
||||
}
|
||||
++uc_attr_count;
|
||||
}
|
||||
|
||||
for (cnt = 0; cnt < uncore_pmu->pmnc_counters; ++cnt, ++uc_attr_count) {
|
||||
struct gator_attr *const attr = &uc_attrs[uc_attr_count];
|
||||
|
||||
if (uc_attr_count < ARRAY_SIZE(uc_attrs)) {
|
||||
snprintf(attr->name, sizeof(attr->name), "%s_cnt%d", uncore_pmu->core_name, cnt);
|
||||
attr->type = type;
|
||||
attr->gator_cpu = NULL;
|
||||
attr->uncore_pmu = uncore_pmu;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void gator_events_perf_pmu_cpu_init(const struct gator_cpu *const gator_cpu, const int type)
|
||||
{
|
||||
int cnt;
|
||||
|
||||
if (gator_cluster_count < ARRAY_SIZE(gator_clusters)) {
|
||||
gator_clusters[gator_cluster_count++] = gator_cpu;
|
||||
|
||||
if (attr_count < ARRAY_SIZE(attrs)) {
|
||||
snprintf(attrs[attr_count].name, sizeof(attrs[attr_count].name), "%s_ccnt", gator_cpu->pmnc_name);
|
||||
attrs[attr_count].type = type;
|
||||
attrs[attr_count].gator_cpu = gator_cpu;
|
||||
attrs[uc_attr_count].uncore_pmu = NULL;
|
||||
}
|
||||
++attr_count;
|
||||
|
||||
for (cnt = 0; cnt < gator_cpu->pmnc_counters; ++cnt, ++attr_count) {
|
||||
struct gator_attr *const attr = &attrs[attr_count];
|
||||
|
||||
if (attr_count < ARRAY_SIZE(attrs)) {
|
||||
snprintf(attr->name, sizeof(attr->name), "%s_cnt%d", gator_cpu->pmnc_name, cnt);
|
||||
attr->type = type;
|
||||
attr->gator_cpu = gator_cpu;
|
||||
attr->uncore_pmu = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static int gator_events_perf_pmu_reread(void)
|
||||
{
|
||||
struct perf_event_attr pea;
|
||||
struct perf_event *pe;
|
||||
const struct gator_cpu *gator_cpu;
|
||||
const struct uncore_pmu *uncore_pmu;
|
||||
int type;
|
||||
int cpu;
|
||||
int cnt;
|
||||
|
||||
for (cnt = 0; cnt < ARRAY_SIZE(attrs); cnt++)
|
||||
__attr_init(&attrs[cnt]);
|
||||
for (cnt = 0; cnt < ARRAY_SIZE(uc_attrs); cnt++)
|
||||
__attr_init(&uc_attrs[cnt]);
|
||||
for (cnt = 0; cnt < ARRAY_SIZE(gator_cpus_per_core); cnt++)
|
||||
gator_cpus_per_core[cnt] = NULL;
|
||||
|
||||
memset(&pea, 0, sizeof(pea));
|
||||
pea.size = sizeof(pea);
|
||||
pea.config = 0xFF;
|
||||
attr_count = 0;
|
||||
uc_attr_count = 0;
|
||||
for (type = PERF_TYPE_MAX; type < 0x20; ++type) {
|
||||
pea.type = type;
|
||||
|
||||
/* A particular PMU may work on some but not all cores, so try on each core */
|
||||
pe = NULL;
|
||||
for_each_present_cpu(cpu) {
|
||||
pe = perf_event_create_kernel_counter(&pea, cpu, NULL, dummy_handler, NULL);
|
||||
if (!IS_ERR(pe))
|
||||
break;
|
||||
}
|
||||
/* Assume that valid PMUs are contiguous */
|
||||
if (IS_ERR(pe)) {
|
||||
pea.config = 0xff00;
|
||||
pe = perf_event_create_kernel_counter(&pea, 0, NULL, dummy_handler, NULL);
|
||||
if (IS_ERR(pe))
|
||||
break;
|
||||
}
|
||||
|
||||
if (pe->pmu != NULL && type == pe->pmu->type) {
|
||||
pr_notice("gator: perf pmu: %s\n", pe->pmu->name);
|
||||
if ((uncore_pmu = gator_find_uncore_pmu(pe->pmu->name)) != NULL) {
|
||||
pr_notice("gator: Adding uncore counters for %s (%s) with type %i\n", uncore_pmu->core_name, uncore_pmu->pmnc_name, type);
|
||||
gator_events_perf_pmu_uncore_init(uncore_pmu, type);
|
||||
} else if ((gator_cpu = gator_find_cpu_by_pmu_name(pe->pmu->name)) != NULL) {
|
||||
pr_notice("gator: Adding cpu counters for %s (%s) with type %i\n", gator_cpu->core_name, gator_cpu->pmnc_name, type);
|
||||
gator_events_perf_pmu_cpu_init(gator_cpu, type);
|
||||
}
|
||||
/* Initialize gator_attrs for dynamic PMUs here */
|
||||
}
|
||||
|
||||
perf_event_release_kernel(pe);
|
||||
}
|
||||
|
||||
/* Now use CPUID to create CPU->PMU mapping, and add any missing PMUs using PERF_TYPE_RAW */
|
||||
for (cpu = 0; cpu < ARRAY_SIZE(gator_cpuids); ++cpu) {
|
||||
if (gator_cpuids[cpu] != ((u32) -1)) {
|
||||
bool found_cpu = false;
|
||||
const struct gator_cpu *gator_cpu = gator_find_cpu_by_cpuid(gator_cpuids[cpu]);
|
||||
|
||||
#if defined(__arm__) || defined(__aarch64__)
|
||||
if (gator_cpu == NULL) {
|
||||
pr_err("gator: This CPU is not recognized, using the Arm architected counters\n");
|
||||
gator_cpu = &gator_pmu_other;
|
||||
}
|
||||
#else
|
||||
if (gator_cpu == NULL) {
|
||||
pr_err("gator: This CPU is not recognized\n");
|
||||
return -1;
|
||||
}
|
||||
#endif
|
||||
for (cnt = 0; cnt < gator_cluster_count; ++cnt) {
|
||||
if (gator_clusters[cnt] == gator_cpu) {
|
||||
found_cpu = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found_cpu) {
|
||||
pr_notice("gator: Adding cpu counters (based on cpuid) for %s\n", gator_cpu->core_name);
|
||||
gator_events_perf_pmu_cpu_init(gator_cpu, PERF_TYPE_RAW);
|
||||
}
|
||||
|
||||
gator_cpus_per_core[cpu] = gator_cpu;
|
||||
}
|
||||
}
|
||||
|
||||
/* Log the PMUs used per core */
|
||||
for (cpu = 0; cpu < ARRAY_SIZE(gator_cpuids); ++cpu) {
|
||||
if (gator_cpus_per_core[cpu] != NULL) {
|
||||
pr_notice("gator: Using %s (0x%lx) for cpu %i\n", gator_cpus_per_core[cpu]->core_name, gator_cpus_per_core[cpu]->cpuid, cpu);
|
||||
}
|
||||
}
|
||||
|
||||
/* Initialize gator_attrs for non-dynamic PMUs here */
|
||||
if (attr_count > CNTMAX) {
|
||||
pr_err("gator: Too many perf counters, please increase CNTMAX\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (uc_attr_count > UCCNT) {
|
||||
pr_err("gator: Too many perf uncore counters, please increase UCCNT\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gator_events_perf_pmu_init(void)
|
||||
{
|
||||
return gator_events_install(&gator_events_perf_pmu_interface);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
static int gator_events_perf_pmu_reread(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gator_events_perf_pmu_create_files(struct super_block *sb, struct dentry *root)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
135
drivers/gator/gator_events_sched.c
Normal file
135
drivers/gator/gator_events_sched.c
Normal file
@@ -0,0 +1,135 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "gator.h"
|
||||
#include <trace/events/sched.h>
|
||||
|
||||
#define SCHED_SWITCH 0
|
||||
#define SCHED_TOTAL (SCHED_SWITCH+1)
|
||||
|
||||
static ulong sched_switch_enabled[GATOR_CLUSTER_COUNT];
|
||||
static bool sched_switch_enabled_any;
|
||||
static ulong sched_switch_key[GATOR_CLUSTER_COUNT];
|
||||
static DEFINE_PER_CPU(int[SCHED_TOTAL], schedCnt);
|
||||
static DEFINE_PER_CPU(int[SCHED_TOTAL * 2], schedGet);
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0)
|
||||
GATOR_DEFINE_PROBE(sched_switch, TP_PROTO(struct task_struct *prev, struct task_struct *next))
|
||||
#else
|
||||
GATOR_DEFINE_PROBE(sched_switch, TP_PROTO(bool preempt, struct task_struct *prev, struct task_struct *next))
|
||||
#endif
|
||||
{
|
||||
unsigned long flags;
|
||||
|
||||
/* disable interrupts to synchronize with gator_events_sched_read()
|
||||
* spinlocks not needed since percpu buffers are used
|
||||
*/
|
||||
local_irq_save(flags);
|
||||
per_cpu(schedCnt, get_physical_cpu())[SCHED_SWITCH]++;
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
static int gator_events_sched_create_files(struct super_block *sb, struct dentry *root)
|
||||
{
|
||||
struct dentry *dir;
|
||||
int i;
|
||||
char buf[40];
|
||||
|
||||
/* switch */
|
||||
for (i = 0; i < gator_cluster_count; i++) {
|
||||
snprintf(buf, sizeof(buf), "%s_switch", gator_clusters[i]->pmnc_name);
|
||||
dir = gatorfs_mkdir(sb, root, buf);
|
||||
if (!dir)
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &sched_switch_enabled[i]);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &sched_switch_key[i]);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gator_events_sched_start(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
sched_switch_enabled_any = false;
|
||||
for (i = 0; i < gator_cluster_count; i++) {
|
||||
if (sched_switch_enabled[i]) {
|
||||
sched_switch_enabled_any = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* register tracepoints */
|
||||
if (sched_switch_enabled_any)
|
||||
if (GATOR_REGISTER_TRACE(sched_switch))
|
||||
goto sched_switch_exit;
|
||||
pr_debug("gator: registered scheduler event tracepoints\n");
|
||||
|
||||
return 0;
|
||||
|
||||
/* unregister tracepoints on error */
|
||||
sched_switch_exit:
|
||||
pr_err("gator: scheduler event tracepoints failed to activate, please verify that tracepoints are enabled in the linux kernel\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void gator_events_sched_stop(void)
|
||||
{
|
||||
if (sched_switch_enabled_any)
|
||||
GATOR_UNREGISTER_TRACE(sched_switch);
|
||||
pr_debug("gator: unregistered scheduler event tracepoints\n");
|
||||
|
||||
sched_switch_enabled_any = false;
|
||||
memset(sched_switch_enabled, 0, sizeof(sched_switch_enabled));
|
||||
}
|
||||
|
||||
static int gator_events_sched_read(int **buffer, bool sched_switch)
|
||||
{
|
||||
unsigned long flags;
|
||||
int len, value;
|
||||
int cpu = get_physical_cpu();
|
||||
int cluster = gator_clusterids[cpu];
|
||||
|
||||
len = 0;
|
||||
if (sched_switch_enabled[cluster]) {
|
||||
local_irq_save(flags);
|
||||
value = per_cpu(schedCnt, cpu)[SCHED_SWITCH];
|
||||
per_cpu(schedCnt, cpu)[SCHED_SWITCH] = 0;
|
||||
local_irq_restore(flags);
|
||||
per_cpu(schedGet, cpu)[len++] = sched_switch_key[cluster];
|
||||
per_cpu(schedGet, cpu)[len++] = value;
|
||||
}
|
||||
|
||||
if (buffer)
|
||||
*buffer = per_cpu(schedGet, cpu);
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
static struct gator_interface gator_events_sched_interface = {
|
||||
.name = "sched",
|
||||
.create_files = gator_events_sched_create_files,
|
||||
.start = gator_events_sched_start,
|
||||
.stop = gator_events_sched_stop,
|
||||
.read = gator_events_sched_read,
|
||||
};
|
||||
|
||||
int gator_events_sched_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < gator_cluster_count; i++) {
|
||||
sched_switch_enabled[i] = 0;
|
||||
sched_switch_key[i] = gator_events_get_key();
|
||||
}
|
||||
|
||||
return gator_events_install(&gator_events_sched_interface);
|
||||
}
|
||||
375
drivers/gator/gator_fs.c
Normal file
375
drivers/gator/gator_fs.c
Normal file
@@ -0,0 +1,375 @@
|
||||
/**
|
||||
* @file gatorfs.c
|
||||
*
|
||||
* @remark Copyright 2002 OProfile authors
|
||||
* @remark Read the file COPYING
|
||||
*
|
||||
* @author John Levon
|
||||
*
|
||||
* A simple filesystem for configuration and
|
||||
* access of oprofile.
|
||||
*/
|
||||
|
||||
#include <linux/init.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/fs.h>
|
||||
#include <linux/pagemap.h>
|
||||
#include <linux/uaccess.h>
|
||||
|
||||
|
||||
/* Kernel version 4.6.0 removes page_cache_{get,release} and related defines. Add them back here.
|
||||
(See https://git.kernel.org/cgit/linux/kernel/git/torvalds/linux.git/commit?id=1fa64f198b9f8d6ec0f7aec7c18dc94684391140) */
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 6, 0)
|
||||
# define PAGE_CACHE_SHIFT PAGE_SHIFT
|
||||
# define PAGE_CACHE_SIZE PAGE_SIZE
|
||||
# define PAGE_CACHE_MASK PAGE_MASK
|
||||
# define PAGE_CACHE_ALIGN(addr) (((addr)+PAGE_CACHE_SIZE-1)&PAGE_CACHE_MASK)
|
||||
# define page_cache_get(page) get_page(page)
|
||||
# define page_cache_release(page) put_page(page)
|
||||
#endif
|
||||
|
||||
|
||||
#define gatorfs_MAGIC 0x24051020
|
||||
#define TMPBUFSIZE 50
|
||||
static DEFINE_SPINLOCK(gatorfs_lock);
|
||||
|
||||
static struct inode *gatorfs_get_inode(struct super_block *sb, int mode)
|
||||
{
|
||||
struct inode *inode = new_inode(sb);
|
||||
|
||||
if (inode) {
|
||||
inode->i_ino = get_next_ino();
|
||||
inode->i_mode = mode;
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 12, 0)
|
||||
inode->i_atime = inode->i_mtime = inode->i_ctime = CURRENT_TIME;
|
||||
#else
|
||||
inode->i_atime = inode->i_mtime = inode->i_ctime = current_time(inode);
|
||||
#endif
|
||||
}
|
||||
return inode;
|
||||
}
|
||||
|
||||
static const struct super_operations s_ops = {
|
||||
.statfs = simple_statfs,
|
||||
.drop_inode = generic_delete_inode,
|
||||
};
|
||||
|
||||
static ssize_t gatorfs_ulong_to_user(unsigned long val, char __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
char tmpbuf[TMPBUFSIZE];
|
||||
size_t maxlen = snprintf(tmpbuf, TMPBUFSIZE, "%lu\n", val);
|
||||
|
||||
if (maxlen > TMPBUFSIZE)
|
||||
maxlen = TMPBUFSIZE;
|
||||
return simple_read_from_buffer(buf, count, offset, tmpbuf, maxlen);
|
||||
}
|
||||
|
||||
static ssize_t gatorfs_u64_to_user(u64 val, char __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
char tmpbuf[TMPBUFSIZE];
|
||||
size_t maxlen = snprintf(tmpbuf, TMPBUFSIZE, "%llu\n", val);
|
||||
|
||||
if (maxlen > TMPBUFSIZE)
|
||||
maxlen = TMPBUFSIZE;
|
||||
return simple_read_from_buffer(buf, count, offset, tmpbuf, maxlen);
|
||||
}
|
||||
|
||||
static int gatorfs_ulong_from_user(unsigned long *val, char const __user *buf, size_t count)
|
||||
{
|
||||
char tmpbuf[TMPBUFSIZE];
|
||||
unsigned long flags;
|
||||
|
||||
if (!count)
|
||||
return 0;
|
||||
|
||||
if (count > TMPBUFSIZE - 1)
|
||||
return -EINVAL;
|
||||
|
||||
memset(tmpbuf, 0x0, TMPBUFSIZE);
|
||||
|
||||
if (copy_from_user(tmpbuf, buf, count))
|
||||
return -EFAULT;
|
||||
|
||||
spin_lock_irqsave(&gatorfs_lock, flags);
|
||||
*val = simple_strtoul(tmpbuf, NULL, 0);
|
||||
spin_unlock_irqrestore(&gatorfs_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gatorfs_u64_from_user(u64 *val, char const __user *buf, size_t count)
|
||||
{
|
||||
char tmpbuf[TMPBUFSIZE];
|
||||
unsigned long flags;
|
||||
|
||||
if (!count)
|
||||
return 0;
|
||||
|
||||
if (count > TMPBUFSIZE - 1)
|
||||
return -EINVAL;
|
||||
|
||||
memset(tmpbuf, 0x0, TMPBUFSIZE);
|
||||
|
||||
if (copy_from_user(tmpbuf, buf, count))
|
||||
return -EFAULT;
|
||||
|
||||
spin_lock_irqsave(&gatorfs_lock, flags);
|
||||
*val = simple_strtoull(tmpbuf, NULL, 0);
|
||||
spin_unlock_irqrestore(&gatorfs_lock, flags);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t ulong_read_file(struct file *file, char __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
unsigned long *val = file->private_data;
|
||||
|
||||
return gatorfs_ulong_to_user(*val, buf, count, offset);
|
||||
}
|
||||
|
||||
static ssize_t u64_read_file(struct file *file, char __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
u64 *val = file->private_data;
|
||||
|
||||
return gatorfs_u64_to_user(*val, buf, count, offset);
|
||||
}
|
||||
|
||||
static ssize_t ulong_write_file(struct file *file, char const __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
unsigned long *value = file->private_data;
|
||||
int retval;
|
||||
|
||||
if (*offset)
|
||||
return -EINVAL;
|
||||
|
||||
retval = gatorfs_ulong_from_user(value, buf, count);
|
||||
|
||||
if (retval)
|
||||
return retval;
|
||||
return count;
|
||||
}
|
||||
|
||||
static ssize_t u64_write_file(struct file *file, char const __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
u64 *value = file->private_data;
|
||||
int retval;
|
||||
|
||||
if (*offset)
|
||||
return -EINVAL;
|
||||
|
||||
retval = gatorfs_u64_from_user(value, buf, count);
|
||||
|
||||
if (retval)
|
||||
return retval;
|
||||
return count;
|
||||
}
|
||||
|
||||
static int default_open(struct inode *inode, struct file *filp)
|
||||
{
|
||||
if (inode->i_private)
|
||||
filp->private_data = inode->i_private;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static const struct file_operations ulong_fops = {
|
||||
.read = ulong_read_file,
|
||||
.write = ulong_write_file,
|
||||
.open = default_open,
|
||||
};
|
||||
|
||||
static const struct file_operations u64_fops = {
|
||||
.read = u64_read_file,
|
||||
.write = u64_write_file,
|
||||
.open = default_open,
|
||||
};
|
||||
|
||||
static const struct file_operations ulong_ro_fops = {
|
||||
.read = ulong_read_file,
|
||||
.open = default_open,
|
||||
};
|
||||
|
||||
static const struct file_operations u64_ro_fops = {
|
||||
.read = u64_read_file,
|
||||
.open = default_open,
|
||||
};
|
||||
|
||||
static struct dentry *__gatorfs_create_file(struct super_block *sb,
|
||||
struct dentry *root,
|
||||
char const *name,
|
||||
const struct file_operations *fops,
|
||||
int perm)
|
||||
{
|
||||
struct dentry *dentry;
|
||||
struct inode *inode;
|
||||
|
||||
dentry = d_alloc_name(root, name);
|
||||
if (!dentry)
|
||||
return NULL;
|
||||
inode = gatorfs_get_inode(sb, S_IFREG | perm);
|
||||
if (!inode) {
|
||||
dput(dentry);
|
||||
return NULL;
|
||||
}
|
||||
inode->i_fop = fops;
|
||||
d_add(dentry, inode);
|
||||
return dentry;
|
||||
}
|
||||
|
||||
int gatorfs_create_ulong(struct super_block *sb, struct dentry *root,
|
||||
char const *name, unsigned long *val)
|
||||
{
|
||||
struct dentry *d = __gatorfs_create_file(sb, root, name,
|
||||
&ulong_fops, 0644);
|
||||
if (!d)
|
||||
return -EFAULT;
|
||||
|
||||
d->d_inode->i_private = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gatorfs_create_u64(struct super_block *sb, struct dentry *root,
|
||||
char const *name, u64 *val)
|
||||
{
|
||||
struct dentry *d = __gatorfs_create_file(sb, root, name,
|
||||
&u64_fops, 0644);
|
||||
if (!d)
|
||||
return -EFAULT;
|
||||
|
||||
d->d_inode->i_private = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int gatorfs_create_ro_ulong(struct super_block *sb, struct dentry *root,
|
||||
char const *name, unsigned long *val)
|
||||
{
|
||||
struct dentry *d = __gatorfs_create_file(sb, root, name,
|
||||
&ulong_ro_fops, 0444);
|
||||
if (!d)
|
||||
return -EFAULT;
|
||||
|
||||
d->d_inode->i_private = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gatorfs_create_ro_u64(struct super_block *sb, struct dentry *root,
|
||||
char const *name, u64 *val)
|
||||
{
|
||||
struct dentry *d =
|
||||
__gatorfs_create_file(sb, root, name, &u64_ro_fops, 0444);
|
||||
if (!d)
|
||||
return -EFAULT;
|
||||
|
||||
d->d_inode->i_private = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static ssize_t atomic_read_file(struct file *file, char __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
atomic_t *val = file->private_data;
|
||||
|
||||
return gatorfs_ulong_to_user(atomic_read(val), buf, count, offset);
|
||||
}
|
||||
|
||||
static const struct file_operations atomic_ro_fops = {
|
||||
.read = atomic_read_file,
|
||||
.open = default_open,
|
||||
};
|
||||
|
||||
static int gatorfs_create_file_data(struct super_block *sb, struct dentry *root,
|
||||
char const *name, const struct file_operations *fops,
|
||||
void * private_data)
|
||||
{
|
||||
struct dentry * dentry = __gatorfs_create_file(sb, root, name, fops, 0644);
|
||||
if (!dentry)
|
||||
return -EFAULT;
|
||||
|
||||
dentry->d_inode->i_private = private_data;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int gatorfs_create_file(struct super_block *sb, struct dentry *root,
|
||||
char const *name, const struct file_operations *fops)
|
||||
{
|
||||
return gatorfs_create_file_data(sb, root, name, fops, NULL);
|
||||
}
|
||||
|
||||
static int gatorfs_create_file_perm(struct super_block *sb, struct dentry *root,
|
||||
char const *name,
|
||||
const struct file_operations *fops, int perm)
|
||||
{
|
||||
if (!__gatorfs_create_file(sb, root, name, fops, perm))
|
||||
return -EFAULT;
|
||||
return 0;
|
||||
}
|
||||
|
||||
struct dentry *gatorfs_mkdir(struct super_block *sb,
|
||||
struct dentry *root, char const *name)
|
||||
{
|
||||
struct dentry *dentry;
|
||||
struct inode *inode;
|
||||
|
||||
dentry = d_alloc_name(root, name);
|
||||
if (!dentry)
|
||||
return NULL;
|
||||
inode = gatorfs_get_inode(sb, S_IFDIR | 0755);
|
||||
if (!inode) {
|
||||
dput(dentry);
|
||||
return NULL;
|
||||
}
|
||||
inode->i_op = &simple_dir_inode_operations;
|
||||
inode->i_fop = &simple_dir_operations;
|
||||
d_add(dentry, inode);
|
||||
return dentry;
|
||||
}
|
||||
|
||||
static int gatorfs_fill_super(struct super_block *sb, void *data, int silent)
|
||||
{
|
||||
struct inode *root_inode;
|
||||
struct dentry *root_dentry;
|
||||
|
||||
sb->s_blocksize = PAGE_CACHE_SIZE;
|
||||
sb->s_blocksize_bits = PAGE_CACHE_SHIFT;
|
||||
sb->s_magic = gatorfs_MAGIC;
|
||||
sb->s_op = &s_ops;
|
||||
sb->s_time_gran = 1;
|
||||
|
||||
root_inode = gatorfs_get_inode(sb, S_IFDIR | 0755);
|
||||
if (!root_inode)
|
||||
return -ENOMEM;
|
||||
root_inode->i_op = &simple_dir_inode_operations;
|
||||
root_inode->i_fop = &simple_dir_operations;
|
||||
|
||||
root_dentry = d_make_root(root_inode);
|
||||
|
||||
if (!root_dentry) {
|
||||
return -ENOMEM;
|
||||
}
|
||||
|
||||
sb->s_root = root_dentry;
|
||||
|
||||
gator_op_create_files(sb, root_dentry);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static struct dentry *gatorfs_mount(struct file_system_type *fs_type,
|
||||
int flags, const char *dev_name, void *data)
|
||||
{
|
||||
return mount_nodev(fs_type, flags, data, gatorfs_fill_super);
|
||||
}
|
||||
|
||||
static struct file_system_type gatorfs_type = {
|
||||
.owner = THIS_MODULE,
|
||||
.name = "gatorfs",
|
||||
.mount = gatorfs_mount,
|
||||
|
||||
.kill_sb = kill_litter_super,
|
||||
};
|
||||
|
||||
static int __init gatorfs_register(void)
|
||||
{
|
||||
return register_filesystem(&gatorfs_type);
|
||||
}
|
||||
|
||||
static void gatorfs_unregister(void)
|
||||
{
|
||||
unregister_filesystem(&gatorfs_type);
|
||||
}
|
||||
80
drivers/gator/gator_hrtimer_gator.c
Normal file
80
drivers/gator/gator_hrtimer_gator.c
Normal file
@@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2011-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
static void (*callback)(void);
|
||||
static DEFINE_PER_CPU(struct hrtimer, percpu_hrtimer);
|
||||
static DEFINE_PER_CPU(ktime_t, hrtimer_expire);
|
||||
static DEFINE_PER_CPU(int, hrtimer_is_active);
|
||||
static ktime_t profiling_interval;
|
||||
static void gator_hrtimer_online(void);
|
||||
static void gator_hrtimer_offline(void);
|
||||
|
||||
static enum hrtimer_restart gator_hrtimer_notify(struct hrtimer *hrtimer)
|
||||
{
|
||||
int cpu = get_logical_cpu();
|
||||
|
||||
hrtimer_forward(hrtimer, per_cpu(hrtimer_expire, cpu), profiling_interval);
|
||||
per_cpu(hrtimer_expire, cpu) = ktime_add(per_cpu(hrtimer_expire, cpu), profiling_interval);
|
||||
(*callback)();
|
||||
return HRTIMER_RESTART;
|
||||
}
|
||||
|
||||
static void gator_hrtimer_online(void)
|
||||
{
|
||||
int cpu = get_logical_cpu();
|
||||
struct hrtimer *hrtimer = &per_cpu(percpu_hrtimer, cpu);
|
||||
|
||||
if (per_cpu(hrtimer_is_active, cpu) || (ktime_to_ns(profiling_interval) == 0))
|
||||
return;
|
||||
|
||||
per_cpu(hrtimer_is_active, cpu) = 1;
|
||||
hrtimer_init(hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
|
||||
hrtimer->function = gator_hrtimer_notify;
|
||||
#ifdef CONFIG_PREEMPT_RT_BASE
|
||||
hrtimer->irqsafe = 1;
|
||||
#endif
|
||||
per_cpu(hrtimer_expire, cpu) = ktime_add(hrtimer->base->get_time(), profiling_interval);
|
||||
hrtimer_start(hrtimer, per_cpu(hrtimer_expire, cpu), HRTIMER_MODE_ABS_PINNED);
|
||||
}
|
||||
|
||||
static void gator_hrtimer_offline(void)
|
||||
{
|
||||
int cpu = get_logical_cpu();
|
||||
struct hrtimer *hrtimer = &per_cpu(percpu_hrtimer, cpu);
|
||||
|
||||
if (!per_cpu(hrtimer_is_active, cpu))
|
||||
return;
|
||||
|
||||
per_cpu(hrtimer_is_active, cpu) = 0;
|
||||
hrtimer_cancel(hrtimer);
|
||||
}
|
||||
|
||||
static int gator_hrtimer_init(int interval, void (*func)(void))
|
||||
{
|
||||
int cpu;
|
||||
|
||||
(callback) = (func);
|
||||
|
||||
for_each_present_cpu(cpu) {
|
||||
per_cpu(hrtimer_is_active, cpu) = 0;
|
||||
}
|
||||
|
||||
/* calculate profiling interval */
|
||||
if (interval > 0)
|
||||
profiling_interval = ns_to_ktime(1000000000UL / interval);
|
||||
else
|
||||
profiling_interval = ns_to_ktime(0);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gator_hrtimer_shutdown(void)
|
||||
{
|
||||
/* empty */
|
||||
}
|
||||
195
drivers/gator/gator_iks.c
Normal file
195
drivers/gator/gator_iks.c
Normal file
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2013-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#if GATOR_IKS_SUPPORT
|
||||
|
||||
#include <linux/of.h>
|
||||
#include <asm/bL_switcher.h>
|
||||
#include <asm/smp_plat.h>
|
||||
#include <trace/events/power_cpu_migrate.h>
|
||||
|
||||
static bool map_cpuids;
|
||||
static int mpidr_cpuids[NR_CPUS];
|
||||
static const struct gator_cpu *mpidr_cpus[NR_CPUS];
|
||||
static int __lcpu_to_pcpu[NR_CPUS];
|
||||
|
||||
static const struct gator_cpu *gator_find_cpu_by_dt_name(const char *const name)
|
||||
{
|
||||
const struct gator_cpu *gator_cpu;
|
||||
|
||||
list_for_each_entry(gator_cpu, &gator_cpus, list) {
|
||||
if (gator_cpu->dt_name != NULL && strcmp(gator_cpu->dt_name, name) == 0)
|
||||
return gator_cpu;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void calc_first_cluster_size(void)
|
||||
{
|
||||
int len;
|
||||
const u32 *val;
|
||||
const char *compatible;
|
||||
struct device_node *cn = NULL;
|
||||
int mpidr_cpuids_count = 0;
|
||||
|
||||
/* Zero is a valid cpuid, so initialize the array to 0xff's */
|
||||
memset(&mpidr_cpuids, 0xff, sizeof(mpidr_cpuids));
|
||||
memset(&mpidr_cpus, 0, sizeof(mpidr_cpus));
|
||||
|
||||
while ((cn = of_find_node_by_type(cn, "cpu"))) {
|
||||
BUG_ON(mpidr_cpuids_count >= NR_CPUS);
|
||||
|
||||
val = of_get_property(cn, "reg", &len);
|
||||
if (!val || len != 4) {
|
||||
pr_err("%s missing reg property\n", cn->full_name);
|
||||
continue;
|
||||
}
|
||||
compatible = of_get_property(cn, "compatible", NULL);
|
||||
if (compatible == NULL) {
|
||||
pr_err("%s missing compatible property\n", cn->full_name);
|
||||
continue;
|
||||
}
|
||||
|
||||
mpidr_cpuids[mpidr_cpuids_count] = be32_to_cpup(val);
|
||||
mpidr_cpus[mpidr_cpuids_count] = gator_find_cpu_by_dt_name(compatible);
|
||||
++mpidr_cpuids_count;
|
||||
}
|
||||
|
||||
map_cpuids = (mpidr_cpuids_count == nr_cpu_ids);
|
||||
}
|
||||
|
||||
static int linearize_mpidr(int mpidr)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < nr_cpu_ids; ++i) {
|
||||
if (mpidr_cpuids[i] == mpidr)
|
||||
return i;
|
||||
}
|
||||
|
||||
BUG();
|
||||
}
|
||||
|
||||
int lcpu_to_pcpu(const int lcpu)
|
||||
{
|
||||
int pcpu;
|
||||
|
||||
if (!map_cpuids)
|
||||
return lcpu;
|
||||
|
||||
BUG_ON(lcpu >= nr_cpu_ids || lcpu < 0);
|
||||
pcpu = __lcpu_to_pcpu[lcpu];
|
||||
BUG_ON(pcpu >= nr_cpu_ids || pcpu < 0);
|
||||
return pcpu;
|
||||
}
|
||||
|
||||
int pcpu_to_lcpu(const int pcpu)
|
||||
{
|
||||
int lcpu;
|
||||
|
||||
if (!map_cpuids)
|
||||
return pcpu;
|
||||
|
||||
BUG_ON(pcpu >= nr_cpu_ids || pcpu < 0);
|
||||
for (lcpu = 0; lcpu < nr_cpu_ids; ++lcpu) {
|
||||
if (__lcpu_to_pcpu[lcpu] == pcpu) {
|
||||
BUG_ON(lcpu >= nr_cpu_ids || lcpu < 0);
|
||||
return lcpu;
|
||||
}
|
||||
}
|
||||
BUG();
|
||||
}
|
||||
|
||||
static void gator_update_cpu_mapping(u32 cpu_hwid)
|
||||
{
|
||||
int lcpu = smp_processor_id();
|
||||
int pcpu = linearize_mpidr(cpu_hwid & MPIDR_HWID_BITMASK);
|
||||
|
||||
BUG_ON(lcpu >= nr_cpu_ids || lcpu < 0);
|
||||
BUG_ON(pcpu >= nr_cpu_ids || pcpu < 0);
|
||||
__lcpu_to_pcpu[lcpu] = pcpu;
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(cpu_migrate_begin, TP_PROTO(u64 timestamp, u32 cpu_hwid))
|
||||
{
|
||||
const int cpu = get_physical_cpu();
|
||||
|
||||
gator_timer_offline((void *)1);
|
||||
gator_timer_offline_dispatch(cpu, true);
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(cpu_migrate_finish, TP_PROTO(u64 timestamp, u32 cpu_hwid))
|
||||
{
|
||||
int cpu;
|
||||
|
||||
gator_update_cpu_mapping(cpu_hwid);
|
||||
|
||||
/* get_physical_cpu must be called after gator_update_cpu_mapping */
|
||||
cpu = get_physical_cpu();
|
||||
gator_timer_online_dispatch(cpu, true);
|
||||
gator_timer_online((void *)1);
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(cpu_migrate_current, TP_PROTO(u64 timestamp, u32 cpu_hwid))
|
||||
{
|
||||
gator_update_cpu_mapping(cpu_hwid);
|
||||
}
|
||||
|
||||
static void gator_send_iks_core_names(void)
|
||||
{
|
||||
int cpu;
|
||||
/* Send the cpu names */
|
||||
preempt_disable();
|
||||
for (cpu = 0; cpu < nr_cpu_ids; ++cpu) {
|
||||
if (mpidr_cpus[cpu] != NULL)
|
||||
gator_send_core_name(cpu, mpidr_cpus[cpu]->cpuid);
|
||||
}
|
||||
preempt_enable();
|
||||
}
|
||||
|
||||
static int gator_migrate_start(void)
|
||||
{
|
||||
int retval = 0;
|
||||
|
||||
if (!map_cpuids)
|
||||
return retval;
|
||||
|
||||
if (retval == 0)
|
||||
retval = GATOR_REGISTER_TRACE(cpu_migrate_begin);
|
||||
if (retval == 0)
|
||||
retval = GATOR_REGISTER_TRACE(cpu_migrate_finish);
|
||||
if (retval == 0)
|
||||
retval = GATOR_REGISTER_TRACE(cpu_migrate_current);
|
||||
if (retval == 0) {
|
||||
/* Initialize the logical to physical cpu mapping */
|
||||
memset(&__lcpu_to_pcpu, 0xff, sizeof(__lcpu_to_pcpu));
|
||||
bL_switcher_trace_trigger();
|
||||
}
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void gator_migrate_stop(void)
|
||||
{
|
||||
if (!map_cpuids)
|
||||
return;
|
||||
|
||||
GATOR_UNREGISTER_TRACE(cpu_migrate_current);
|
||||
GATOR_UNREGISTER_TRACE(cpu_migrate_finish);
|
||||
GATOR_UNREGISTER_TRACE(cpu_migrate_begin);
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#define calc_first_cluster_size()
|
||||
#define gator_send_iks_core_names()
|
||||
#define gator_migrate_start() 0
|
||||
#define gator_migrate_stop()
|
||||
|
||||
#endif
|
||||
1501
drivers/gator/gator_main.c
Normal file
1501
drivers/gator/gator_main.c
Normal file
File diff suppressed because it is too large
Load Diff
379
drivers/gator/gator_marshaling.c
Normal file
379
drivers/gator/gator_marshaling.c
Normal file
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2012-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#define NEWLINE_CANARY \
|
||||
/* Unix */ \
|
||||
"1\n" \
|
||||
/* Windows */ \
|
||||
"2\r\n" \
|
||||
/* Mac OS */ \
|
||||
"3\r" \
|
||||
/* RISC OS */ \
|
||||
"4\n\r" \
|
||||
/* Add another character so the length isn't 0x0a bytes */ \
|
||||
"5"
|
||||
|
||||
#ifdef MALI_SUPPORT
|
||||
#include "gator_events_mali_common.h"
|
||||
#endif
|
||||
|
||||
static void marshal_summary(long long timestamp, long long uptime, long long monotonic_delta, const char *uname)
|
||||
{
|
||||
unsigned long flags;
|
||||
int cpu = 0;
|
||||
char buf[32];
|
||||
|
||||
local_irq_save(flags);
|
||||
gator_buffer_write_packed_int(cpu, SUMMARY_BUF, MESSAGE_SUMMARY);
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, NEWLINE_CANARY);
|
||||
gator_buffer_write_packed_int64(cpu, SUMMARY_BUF, timestamp);
|
||||
gator_buffer_write_packed_int64(cpu, SUMMARY_BUF, uptime);
|
||||
gator_buffer_write_packed_int64(cpu, SUMMARY_BUF, monotonic_delta);
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "uname");
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, uname);
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "PAGESIZE");
|
||||
snprintf(buf, sizeof(buf), "%lu", PAGE_SIZE);
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, buf);
|
||||
#if GATOR_IKS_SUPPORT
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "iks");
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "");
|
||||
#endif
|
||||
#ifdef CONFIG_PREEMPT_RTB
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "preempt_rtb");
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "");
|
||||
#endif
|
||||
#ifdef CONFIG_PREEMPT_RT_FULL
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "preempt_rt_full");
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "");
|
||||
#endif
|
||||
/* Let Streamline know which GPU is used so that it can label the GPU Activity appropriately. This is a temporary fix, to be improved in a future release. */
|
||||
#ifdef MALI_SUPPORT
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "mali_type");
|
||||
#if (MALI_SUPPORT == MALI_4xx)
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "4xx");
|
||||
#elif (MALI_SUPPORT == MALI_MIDGARD_OR_BIFROST)
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "6xx");
|
||||
#else
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "unknown");
|
||||
#endif
|
||||
#endif
|
||||
#if LINUX_VERSION_CODE >= KERNEL_VERSION(4, 2, 0)
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "nosync");
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "");
|
||||
#endif
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, "");
|
||||
/* Commit the buffer now so it can be one of the first frames read by Streamline */
|
||||
local_irq_restore(flags);
|
||||
gator_commit_buffer(cpu, SUMMARY_BUF, gator_get_time());
|
||||
}
|
||||
|
||||
static bool marshal_cookie_header(const char *text)
|
||||
{
|
||||
int cpu = get_physical_cpu();
|
||||
|
||||
return buffer_check_space(cpu, NAME_BUF, strlen(text) + 3 * MAXSIZE_PACK32);
|
||||
}
|
||||
|
||||
static void marshal_cookie(int cookie, const char *text)
|
||||
{
|
||||
int cpu = get_physical_cpu();
|
||||
/* buffer_check_space already called by marshal_cookie_header */
|
||||
gator_buffer_write_packed_int(cpu, NAME_BUF, MESSAGE_COOKIE);
|
||||
gator_buffer_write_packed_int(cpu, NAME_BUF, cookie);
|
||||
gator_buffer_write_string(cpu, NAME_BUF, text);
|
||||
buffer_check(cpu, NAME_BUF, gator_get_time());
|
||||
}
|
||||
|
||||
static void marshal_thread_name(int pid, char *name)
|
||||
{
|
||||
unsigned long flags, cpu;
|
||||
u64 time;
|
||||
|
||||
local_irq_save(flags);
|
||||
cpu = get_physical_cpu();
|
||||
time = gator_get_time();
|
||||
if (buffer_check_space(cpu, NAME_BUF, TASK_COMM_LEN + 3 * MAXSIZE_PACK32 + MAXSIZE_PACK64)) {
|
||||
gator_buffer_write_packed_int(cpu, NAME_BUF, MESSAGE_THREAD_NAME);
|
||||
gator_buffer_write_packed_int64(cpu, NAME_BUF, time);
|
||||
gator_buffer_write_packed_int(cpu, NAME_BUF, pid);
|
||||
gator_buffer_write_string(cpu, NAME_BUF, name);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
buffer_check(cpu, NAME_BUF, time);
|
||||
}
|
||||
|
||||
static void marshal_link(int cookie, int tgid, int pid)
|
||||
{
|
||||
unsigned long cpu = get_physical_cpu(), flags;
|
||||
u64 time;
|
||||
|
||||
local_irq_save(flags);
|
||||
time = gator_get_time();
|
||||
if (buffer_check_space(cpu, ACTIVITY_BUF, MAXSIZE_PACK64 + 5 * MAXSIZE_PACK32)) {
|
||||
gator_buffer_write_packed_int(cpu, ACTIVITY_BUF, MESSAGE_LINK);
|
||||
gator_buffer_write_packed_int64(cpu, ACTIVITY_BUF, time);
|
||||
gator_buffer_write_packed_int(cpu, ACTIVITY_BUF, cookie);
|
||||
gator_buffer_write_packed_int(cpu, ACTIVITY_BUF, tgid);
|
||||
gator_buffer_write_packed_int(cpu, ACTIVITY_BUF, pid);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
/* Check and commit; commit is set to occur once buffer is 3/4 full */
|
||||
buffer_check(cpu, ACTIVITY_BUF, time);
|
||||
}
|
||||
|
||||
static bool marshal_backtrace_header(int exec_cookie, int tgid, int pid, u64 time)
|
||||
{
|
||||
int cpu = get_physical_cpu();
|
||||
|
||||
if (!buffer_check_space(cpu, BACKTRACE_BUF, MAXSIZE_PACK64 + 5 * MAXSIZE_PACK32 + gator_backtrace_depth * 2 * MAXSIZE_PACK32)) {
|
||||
/* Check and commit; commit is set to occur once buffer is 3/4 full */
|
||||
buffer_check(cpu, BACKTRACE_BUF, time);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
gator_buffer_write_packed_int64(cpu, BACKTRACE_BUF, time);
|
||||
gator_buffer_write_packed_int(cpu, BACKTRACE_BUF, exec_cookie);
|
||||
gator_buffer_write_packed_int(cpu, BACKTRACE_BUF, tgid);
|
||||
gator_buffer_write_packed_int(cpu, BACKTRACE_BUF, pid);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static void marshal_backtrace(unsigned long address, int cookie, int in_kernel)
|
||||
{
|
||||
int cpu = get_physical_cpu();
|
||||
|
||||
if (cookie == 0 && !in_kernel)
|
||||
cookie = UNRESOLVED_COOKIE;
|
||||
gator_buffer_write_packed_int(cpu, BACKTRACE_BUF, cookie);
|
||||
gator_buffer_write_packed_int64(cpu, BACKTRACE_BUF, address);
|
||||
}
|
||||
|
||||
static void marshal_backtrace_footer(u64 time)
|
||||
{
|
||||
int cpu = get_physical_cpu();
|
||||
|
||||
gator_buffer_write_packed_int(cpu, BACKTRACE_BUF, MESSAGE_END_BACKTRACE);
|
||||
|
||||
/* Check and commit; commit is set to occur once buffer is 3/4 full */
|
||||
buffer_check(cpu, BACKTRACE_BUF, time);
|
||||
}
|
||||
|
||||
static bool marshal_event_header(u64 time)
|
||||
{
|
||||
unsigned long flags, cpu = get_physical_cpu();
|
||||
bool retval = false;
|
||||
|
||||
local_irq_save(flags);
|
||||
if (buffer_check_space(cpu, BLOCK_COUNTER_BUF, MAXSIZE_PACK32 + MAXSIZE_PACK64)) {
|
||||
gator_buffer_write_packed_int(cpu, BLOCK_COUNTER_BUF, 0); /* key of zero indicates a timestamp */
|
||||
gator_buffer_write_packed_int64(cpu, BLOCK_COUNTER_BUF, time);
|
||||
retval = true;
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
static void marshal_event(int len, int *buffer)
|
||||
{
|
||||
unsigned long i, flags, cpu = get_physical_cpu();
|
||||
|
||||
if (len <= 0)
|
||||
return;
|
||||
|
||||
/* length must be even since all data is a (key, value) pair */
|
||||
if (len & 0x1) {
|
||||
pr_err("gator: invalid counter data detected and discarded\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* events must be written in key,value pairs */
|
||||
local_irq_save(flags);
|
||||
for (i = 0; i < len; i += 2) {
|
||||
if (!buffer_check_space(cpu, BLOCK_COUNTER_BUF, 2 * MAXSIZE_PACK32))
|
||||
break;
|
||||
gator_buffer_write_packed_int(cpu, BLOCK_COUNTER_BUF, buffer[i]);
|
||||
gator_buffer_write_packed_int(cpu, BLOCK_COUNTER_BUF, buffer[i + 1]);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
static void marshal_event64(int len, long long *buffer64)
|
||||
{
|
||||
unsigned long i, flags, cpu = get_physical_cpu();
|
||||
|
||||
if (len <= 0)
|
||||
return;
|
||||
|
||||
/* length must be even since all data is a (key, value) pair */
|
||||
if (len & 0x1) {
|
||||
pr_err("gator: invalid counter data detected and discarded\n");
|
||||
return;
|
||||
}
|
||||
|
||||
/* events must be written in key,value pairs */
|
||||
local_irq_save(flags);
|
||||
for (i = 0; i < len; i += 2) {
|
||||
if (!buffer_check_space(cpu, BLOCK_COUNTER_BUF, 2 * MAXSIZE_PACK64))
|
||||
break;
|
||||
gator_buffer_write_packed_int64(cpu, BLOCK_COUNTER_BUF, buffer64[i]);
|
||||
gator_buffer_write_packed_int64(cpu, BLOCK_COUNTER_BUF, buffer64[i + 1]);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
}
|
||||
|
||||
static void __maybe_unused marshal_event_single(int core, int key, int value)
|
||||
{
|
||||
unsigned long flags, cpu;
|
||||
u64 time;
|
||||
|
||||
local_irq_save(flags);
|
||||
cpu = get_physical_cpu();
|
||||
time = gator_get_time();
|
||||
if (buffer_check_space(cpu, COUNTER_BUF, MAXSIZE_PACK64 + 3 * MAXSIZE_PACK32)) {
|
||||
gator_buffer_write_packed_int64(cpu, COUNTER_BUF, time);
|
||||
gator_buffer_write_packed_int(cpu, COUNTER_BUF, core);
|
||||
gator_buffer_write_packed_int(cpu, COUNTER_BUF, key);
|
||||
gator_buffer_write_packed_int(cpu, COUNTER_BUF, value);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
/* Check and commit; commit is set to occur once buffer is 3/4 full */
|
||||
buffer_check(cpu, COUNTER_BUF, time);
|
||||
}
|
||||
|
||||
static void __maybe_unused marshal_event_single64(int core, int key, long long value)
|
||||
{
|
||||
unsigned long flags, cpu;
|
||||
u64 time;
|
||||
|
||||
local_irq_save(flags);
|
||||
cpu = get_physical_cpu();
|
||||
time = gator_get_time();
|
||||
if (buffer_check_space(cpu, COUNTER_BUF, 2 * MAXSIZE_PACK64 + 2 * MAXSIZE_PACK32)) {
|
||||
gator_buffer_write_packed_int64(cpu, COUNTER_BUF, time);
|
||||
gator_buffer_write_packed_int(cpu, COUNTER_BUF, core);
|
||||
gator_buffer_write_packed_int(cpu, COUNTER_BUF, key);
|
||||
gator_buffer_write_packed_int64(cpu, COUNTER_BUF, value);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
/* Check and commit; commit is set to occur once buffer is 3/4 full */
|
||||
buffer_check(cpu, COUNTER_BUF, time);
|
||||
}
|
||||
|
||||
static void marshal_sched_trace_switch(int pid, int state)
|
||||
{
|
||||
unsigned long cpu = get_physical_cpu(), flags;
|
||||
u64 time;
|
||||
|
||||
if (!per_cpu(gator_buffer, cpu)[SCHED_TRACE_BUF])
|
||||
return;
|
||||
|
||||
local_irq_save(flags);
|
||||
time = gator_get_time();
|
||||
if (buffer_check_space(cpu, SCHED_TRACE_BUF, MAXSIZE_PACK64 + 5 * MAXSIZE_PACK32)) {
|
||||
gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, MESSAGE_SCHED_SWITCH);
|
||||
gator_buffer_write_packed_int64(cpu, SCHED_TRACE_BUF, time);
|
||||
gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, pid);
|
||||
gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, state);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
/* Check and commit; commit is set to occur once buffer is 3/4 full */
|
||||
buffer_check(cpu, SCHED_TRACE_BUF, time);
|
||||
}
|
||||
|
||||
static void marshal_sched_trace_exit(int tgid, int pid)
|
||||
{
|
||||
unsigned long cpu = get_physical_cpu(), flags;
|
||||
u64 time;
|
||||
|
||||
if (!per_cpu(gator_buffer, cpu)[SCHED_TRACE_BUF])
|
||||
return;
|
||||
|
||||
local_irq_save(flags);
|
||||
time = gator_get_time();
|
||||
if (buffer_check_space(cpu, SCHED_TRACE_BUF, MAXSIZE_PACK64 + 2 * MAXSIZE_PACK32)) {
|
||||
gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, MESSAGE_SCHED_EXIT);
|
||||
gator_buffer_write_packed_int64(cpu, SCHED_TRACE_BUF, time);
|
||||
gator_buffer_write_packed_int(cpu, SCHED_TRACE_BUF, pid);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
/* Check and commit; commit is set to occur once buffer is 3/4 full */
|
||||
buffer_check(cpu, SCHED_TRACE_BUF, time);
|
||||
}
|
||||
|
||||
#if GATOR_CPU_FREQ_SUPPORT
|
||||
static void marshal_idle(int core, int state)
|
||||
{
|
||||
unsigned long flags, cpu;
|
||||
u64 time;
|
||||
|
||||
local_irq_save(flags);
|
||||
cpu = get_physical_cpu();
|
||||
time = gator_get_time();
|
||||
if (buffer_check_space(cpu, IDLE_BUF, MAXSIZE_PACK64 + 2 * MAXSIZE_PACK32)) {
|
||||
gator_buffer_write_packed_int(cpu, IDLE_BUF, state);
|
||||
gator_buffer_write_packed_int64(cpu, IDLE_BUF, time);
|
||||
gator_buffer_write_packed_int(cpu, IDLE_BUF, core);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
/* Check and commit; commit is set to occur once buffer is 3/4 full */
|
||||
buffer_check(cpu, IDLE_BUF, time);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(__arm__) || defined(__aarch64__)
|
||||
static void marshal_core_name(const int core, const int cpuid, const char *name)
|
||||
{
|
||||
int cpu = get_physical_cpu();
|
||||
unsigned long flags;
|
||||
|
||||
local_irq_save(flags);
|
||||
if (buffer_check_space(cpu, SUMMARY_BUF, MAXSIZE_PACK32 + MAXSIZE_CORE_NAME)) {
|
||||
gator_buffer_write_packed_int(cpu, SUMMARY_BUF, MESSAGE_CORE_NAME);
|
||||
gator_buffer_write_packed_int(cpu, SUMMARY_BUF, core);
|
||||
gator_buffer_write_packed_int(cpu, SUMMARY_BUF, cpuid);
|
||||
gator_buffer_write_string(cpu, SUMMARY_BUF, name);
|
||||
}
|
||||
/* Commit core names now so that they can show up in live */
|
||||
local_irq_restore(flags);
|
||||
gator_commit_buffer(cpu, SUMMARY_BUF, gator_get_time());
|
||||
}
|
||||
#endif
|
||||
|
||||
static void marshal_activity_switch(int core, int key, int activity, int pid, int state)
|
||||
{
|
||||
unsigned long cpu = get_physical_cpu(), flags;
|
||||
u64 time;
|
||||
|
||||
if (!per_cpu(gator_buffer, cpu)[ACTIVITY_BUF])
|
||||
return;
|
||||
|
||||
local_irq_save(flags);
|
||||
time = gator_get_time();
|
||||
if (buffer_check_space(cpu, ACTIVITY_BUF, MAXSIZE_PACK64 + 5 * MAXSIZE_PACK32)) {
|
||||
gator_buffer_write_packed_int(cpu, ACTIVITY_BUF, MESSAGE_SWITCH);
|
||||
gator_buffer_write_packed_int64(cpu, ACTIVITY_BUF, time);
|
||||
gator_buffer_write_packed_int(cpu, ACTIVITY_BUF, core);
|
||||
gator_buffer_write_packed_int(cpu, ACTIVITY_BUF, key);
|
||||
gator_buffer_write_packed_int(cpu, ACTIVITY_BUF, activity);
|
||||
gator_buffer_write_packed_int(cpu, ACTIVITY_BUF, pid);
|
||||
gator_buffer_write_packed_int(cpu, ACTIVITY_BUF, state);
|
||||
}
|
||||
local_irq_restore(flags);
|
||||
/* Check and commit; commit is set to occur once buffer is 3/4 full */
|
||||
buffer_check(cpu, ACTIVITY_BUF, time);
|
||||
}
|
||||
|
||||
void gator_marshal_activity_switch(int core, int key, int activity, int pid)
|
||||
{
|
||||
/* state is reserved for cpu use only */
|
||||
marshal_activity_switch(core, key, activity, pid, 0);
|
||||
}
|
||||
379
drivers/gator/gator_pmu.c
Normal file
379
drivers/gator/gator_pmu.c
Normal file
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2015-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include <linux/cpumask.h>
|
||||
|
||||
#ifdef CONFIG_64BIT
|
||||
# define GATOR_ATOMIC_T atomic64_t
|
||||
# define GATOR_ATOMIC_CMPXCHG atomic64_cmpxchg
|
||||
# define GATOR_ATOMIC_READ atomic64_read
|
||||
#else
|
||||
# define GATOR_ATOMIC_T atomic_t
|
||||
# define GATOR_ATOMIC_CMPXCHG atomic_cmpxchg
|
||||
# define GATOR_ATOMIC_READ atomic_read
|
||||
#endif
|
||||
|
||||
#define GATOR_ATOMIC_PTR_TO_COUNTER(ptr) ((unsigned long) (ptr))
|
||||
|
||||
struct uncore_pmu {
|
||||
struct list_head list;
|
||||
unsigned long pmnc_counters;
|
||||
unsigned long has_cycles_counter;
|
||||
/* Perf PMU name */
|
||||
char pmnc_name[MAXSIZE_CORE_NAME];
|
||||
/* gatorfs event name */
|
||||
char core_name[MAXSIZE_CORE_NAME];
|
||||
/* cpumask - actually an atomic so we can CAS the pointer once without lock*/
|
||||
GATOR_ATOMIC_T cpumask_atomic;
|
||||
};
|
||||
|
||||
static LIST_HEAD(uncore_pmus);
|
||||
static LIST_HEAD(gator_cpus);
|
||||
static DEFINE_MUTEX(pmu_mutex);
|
||||
|
||||
static struct super_block *gator_sb;
|
||||
static struct dentry *gator_events_dir;
|
||||
|
||||
static const struct gator_cpu gator_pmu_other = {
|
||||
.pmnc_name = "Other",
|
||||
.cpuid = 0xfffff,
|
||||
.core_name = "Other",
|
||||
.pmnc_counters = 6,
|
||||
};
|
||||
|
||||
const struct gator_cpu *gator_clusters[GATOR_CLUSTER_COUNT];
|
||||
int gator_cluster_count;
|
||||
static ulong gator_cluster_ids[GATOR_CLUSTER_COUNT];
|
||||
|
||||
static const struct gator_cpu *gator_find_cpu_by_cpuid(const u32 cpuid)
|
||||
{
|
||||
const struct gator_cpu *gator_cpu;
|
||||
|
||||
list_for_each_entry(gator_cpu, &gator_cpus, list) {
|
||||
if (gator_cpu->cpuid == cpuid)
|
||||
return gator_cpu;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static const char OLD_PMU_PREFIX[] = "ARMv7 Cortex-";
|
||||
static const char NEW_PMU_PREFIX[] = "ARMv7_Cortex_";
|
||||
|
||||
__maybe_unused
|
||||
static const struct gator_cpu *gator_find_cpu_by_pmu_name(const char *const name)
|
||||
{
|
||||
const struct gator_cpu *gator_cpu;
|
||||
|
||||
list_for_each_entry(gator_cpu, &gator_cpus, list) {
|
||||
if (gator_cpu->pmnc_name != NULL &&
|
||||
/* Do the names match exactly? */
|
||||
(strcasecmp(gator_cpu->pmnc_name, name) == 0 ||
|
||||
/* Do these names match but have the old vs new prefix? */
|
||||
((strncasecmp(name, OLD_PMU_PREFIX, sizeof(OLD_PMU_PREFIX) - 1) == 0 &&
|
||||
strncasecmp(gator_cpu->pmnc_name, NEW_PMU_PREFIX, sizeof(NEW_PMU_PREFIX) - 1) == 0 &&
|
||||
strcasecmp(name + sizeof(OLD_PMU_PREFIX) - 1, gator_cpu->pmnc_name + sizeof(NEW_PMU_PREFIX) - 1) == 0))))
|
||||
return gator_cpu;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
__maybe_unused
|
||||
static const struct uncore_pmu *gator_find_uncore_pmu(const char *const name)
|
||||
{
|
||||
const struct uncore_pmu *uncore_pmu;
|
||||
|
||||
list_for_each_entry(uncore_pmu, &uncore_pmus, list) {
|
||||
if (uncore_pmu->pmnc_name != NULL && strcasecmp(uncore_pmu->pmnc_name, name) == 0)
|
||||
return uncore_pmu;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static ssize_t gator_pmu_init_write(struct file *file, char const __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
struct gator_interface *gi;
|
||||
int i;
|
||||
|
||||
if (gator_events_perf_pmu_reread() != 0 ||
|
||||
gator_events_perf_pmu_create_files(gator_sb, gator_events_dir) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
if (gator_cluster_count == 0)
|
||||
gator_clusters[gator_cluster_count++] = &gator_pmu_other;
|
||||
|
||||
/* cluster information */
|
||||
{
|
||||
struct dentry *dir;
|
||||
|
||||
dir = gatorfs_mkdir(gator_sb, file->f_path.dentry->d_parent, "clusters");
|
||||
for (i = 0; i < gator_cluster_count; i++) {
|
||||
gator_cluster_ids[i] = i;
|
||||
gatorfs_create_ro_ulong(gator_sb, dir, gator_clusters[i]->pmnc_name, &gator_cluster_ids[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/* needs PMU info, so initialize afterwards */
|
||||
gator_trace_power_init();
|
||||
if (gator_trace_power_create_files(gator_sb, gator_events_dir) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
gator_trace_sched_init();
|
||||
if (sched_trace_create_files(gator_sb, gator_events_dir) != 0)
|
||||
return -EINVAL;
|
||||
|
||||
/* events sources */
|
||||
for (i = 0; i < ARRAY_SIZE(gator_events_list); i++)
|
||||
if (gator_events_list[i])
|
||||
gator_events_list[i]();
|
||||
|
||||
list_for_each_entry(gi, &gator_events, list)
|
||||
if (gi->create_files)
|
||||
if (gi->create_files(gator_sb, gator_events_dir) != 0)
|
||||
pr_err("gator: create_files failed for %s\n", gi->name);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations gator_pmu_init_fops = {
|
||||
.write = gator_pmu_init_write,
|
||||
};
|
||||
|
||||
static ssize_t gator_pmu_str_read_file(struct file *file, char __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
char *const val = file->private_data;
|
||||
|
||||
return simple_read_from_buffer(buf, count, offset, val, strlen(val));
|
||||
}
|
||||
|
||||
static ssize_t gator_pmu_str_write_file(struct file *file, char const __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
char *value = file->private_data;
|
||||
|
||||
if (*offset)
|
||||
return -EINVAL;
|
||||
|
||||
if (count >= MAXSIZE_CORE_NAME)
|
||||
return -EINVAL;
|
||||
if (copy_from_user(value, buf, count))
|
||||
return -EFAULT;
|
||||
value[count] = 0;
|
||||
value = strstrip(value);
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations gator_pmu_str_fops = {
|
||||
.read = gator_pmu_str_read_file,
|
||||
.write = gator_pmu_str_write_file,
|
||||
.open = default_open,
|
||||
};
|
||||
|
||||
static int gator_pmu_create_str(struct super_block *sb, struct dentry *root, char const *name, char *const val)
|
||||
{
|
||||
struct dentry *d = __gatorfs_create_file(sb, root, name, &gator_pmu_str_fops, 0644);
|
||||
if (!d)
|
||||
return -EFAULT;
|
||||
|
||||
d->d_inode->i_private = val;
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define GATOR_NONE_STRING "(none)"
|
||||
|
||||
static ssize_t gator_pmu_cpumask_read(struct file *file, char __user *buf, size_t count, loff_t *offset)
|
||||
{
|
||||
struct uncore_pmu * uncore_pmu = NULL;
|
||||
struct cpumask * cpumask = NULL;
|
||||
int cpu = 0;
|
||||
int bpos = 0;
|
||||
char buffer[128];
|
||||
|
||||
uncore_pmu = (struct uncore_pmu *) file->private_data;
|
||||
if (!uncore_pmu)
|
||||
return -EFAULT;
|
||||
|
||||
cpumask = (struct cpumask *) GATOR_ATOMIC_READ(&(uncore_pmu->cpumask_atomic));
|
||||
if (!cpumask)
|
||||
return simple_read_from_buffer(buf, count, offset, GATOR_NONE_STRING, strlen(GATOR_NONE_STRING));
|
||||
|
||||
for_each_cpu(cpu, cpumask) {
|
||||
bpos = snprintf(buffer + bpos, sizeof(buffer) - bpos, (bpos > 0 ? ",%d" : "%d"), cpu);
|
||||
if (bpos >= (sizeof(buffer) - 1))
|
||||
break;
|
||||
}
|
||||
|
||||
return simple_read_from_buffer(buf, count, offset, buffer, strlen(buffer));
|
||||
}
|
||||
|
||||
static ssize_t gator_pmu_cpumask_write(struct file *file, char const __user *ubuf, size_t count, loff_t *offset)
|
||||
{
|
||||
struct uncore_pmu * uncore_pmu = NULL;
|
||||
struct cpumask * cpumask = NULL;
|
||||
unsigned long value = 0;
|
||||
int retval = 0;
|
||||
|
||||
uncore_pmu = (struct uncore_pmu *) file->private_data;
|
||||
if (!uncore_pmu)
|
||||
return -EFAULT;
|
||||
|
||||
/* validate args */
|
||||
if (*offset)
|
||||
return -EINVAL;
|
||||
|
||||
/* parse ulong */
|
||||
retval = gatorfs_ulong_from_user(&value, ubuf, count);
|
||||
if (retval)
|
||||
return retval;
|
||||
if (value >= nr_cpu_ids)
|
||||
return -EINVAL;
|
||||
|
||||
/* allocate the mask if it does not already exist */
|
||||
cpumask = (struct cpumask *) GATOR_ATOMIC_READ(&(uncore_pmu->cpumask_atomic));
|
||||
if (!cpumask) {
|
||||
struct cpumask * ret = NULL;
|
||||
|
||||
cpumask = kzalloc(cpumask_size(), GFP_KERNEL);
|
||||
if (!cpumask)
|
||||
return -ENOMEM;
|
||||
|
||||
ret = (struct cpumask * ) GATOR_ATOMIC_CMPXCHG(&(uncore_pmu->cpumask_atomic), GATOR_ATOMIC_PTR_TO_COUNTER(NULL), GATOR_ATOMIC_PTR_TO_COUNTER(cpumask));
|
||||
if (ret != NULL) {
|
||||
/* someone else got there first, free the one we allocated and use the existing instead */
|
||||
kfree(cpumask);
|
||||
cpumask = ret;
|
||||
}
|
||||
}
|
||||
|
||||
cpumask_set_cpu(value, cpumask);
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations cpumask_fops = {
|
||||
.read = gator_pmu_cpumask_read,
|
||||
.write = gator_pmu_cpumask_write,
|
||||
.open = default_open,
|
||||
};
|
||||
|
||||
static ssize_t gator_pmu_export_write(struct file *file, char const __user *ubuf, size_t count, loff_t *offset)
|
||||
{
|
||||
struct dentry *dir;
|
||||
struct dentry *parent;
|
||||
char buf[MAXSIZE_CORE_NAME];
|
||||
const char *str;
|
||||
|
||||
if (*offset)
|
||||
return -EINVAL;
|
||||
|
||||
if (count >= sizeof(buf))
|
||||
return -EINVAL;
|
||||
if (copy_from_user(&buf, ubuf, count))
|
||||
return -EFAULT;
|
||||
buf[count] = 0;
|
||||
str = strstrip(buf);
|
||||
|
||||
parent = file->f_path.dentry->d_parent;
|
||||
dir = gatorfs_mkdir(gator_sb, parent, buf);
|
||||
if (!dir)
|
||||
return -EINVAL;
|
||||
|
||||
if (strcmp("pmu", parent->d_name.name) == 0) {
|
||||
struct gator_cpu *gator_cpu;
|
||||
|
||||
gator_cpu = kmalloc(sizeof(*gator_cpu), GFP_KERNEL);
|
||||
if (gator_cpu == NULL)
|
||||
return -ENOMEM;
|
||||
memset(gator_cpu, 0, sizeof(*gator_cpu));
|
||||
|
||||
gatorfs_create_ulong(gator_sb, dir, "cpuid", &gator_cpu->cpuid);
|
||||
gator_pmu_create_str(gator_sb, dir, "core_name", gator_cpu->core_name);
|
||||
strcpy(gator_cpu->pmnc_name, str);
|
||||
gator_pmu_create_str(gator_sb, dir, "dt_name", gator_cpu->dt_name);
|
||||
gatorfs_create_ulong(gator_sb, dir, "pmnc_counters", &gator_cpu->pmnc_counters);
|
||||
|
||||
mutex_lock(&pmu_mutex);
|
||||
list_add_tail(&gator_cpu->list, &gator_cpus); /* mutex */
|
||||
mutex_unlock(&pmu_mutex);
|
||||
} else {
|
||||
struct uncore_pmu *uncore_pmu;
|
||||
|
||||
uncore_pmu = kmalloc(sizeof(*uncore_pmu), GFP_KERNEL);
|
||||
if (uncore_pmu == NULL)
|
||||
return -ENOMEM;
|
||||
memset(uncore_pmu, 0, sizeof(*uncore_pmu));
|
||||
|
||||
strcpy(uncore_pmu->pmnc_name, str);
|
||||
gator_pmu_create_str(gator_sb, dir, "core_name", uncore_pmu->core_name);
|
||||
gatorfs_create_ulong(gator_sb, dir, "pmnc_counters", &uncore_pmu->pmnc_counters);
|
||||
gatorfs_create_ulong(gator_sb, dir, "has_cycles_counter", &uncore_pmu->has_cycles_counter);
|
||||
gatorfs_create_file_data(gator_sb, dir, "cpumask", &cpumask_fops, uncore_pmu);
|
||||
|
||||
mutex_lock(&pmu_mutex);
|
||||
list_add_tail(&uncore_pmu->list, &uncore_pmus); /* mutex */
|
||||
mutex_unlock(&pmu_mutex);
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
static const struct file_operations export_fops = {
|
||||
.write = gator_pmu_export_write,
|
||||
};
|
||||
|
||||
static int gator_pmu_create_files(struct super_block *sb, struct dentry *root, struct dentry *events)
|
||||
{
|
||||
struct dentry *dir;
|
||||
|
||||
gator_sb = sb;
|
||||
gator_events_dir = events;
|
||||
|
||||
gatorfs_create_file(sb, root, "pmu_init", &gator_pmu_init_fops);
|
||||
|
||||
dir = gatorfs_mkdir(sb, root, "pmu");
|
||||
if (!dir)
|
||||
return -1;
|
||||
|
||||
gatorfs_create_file(sb, dir, "export", &export_fops);
|
||||
|
||||
dir = gatorfs_mkdir(sb, root, "uncore_pmu");
|
||||
if (!dir)
|
||||
return -1;
|
||||
|
||||
gatorfs_create_file(sb, dir, "export", &export_fops);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gator_pmu_exit(void)
|
||||
{
|
||||
mutex_lock(&pmu_mutex);
|
||||
{
|
||||
struct gator_cpu *gator_cpu;
|
||||
struct gator_cpu *next;
|
||||
|
||||
list_for_each_entry_safe(gator_cpu, next, &gator_cpus, list) {
|
||||
kfree(gator_cpu);
|
||||
}
|
||||
}
|
||||
{
|
||||
struct uncore_pmu *uncore_pmu;
|
||||
struct uncore_pmu *next;
|
||||
|
||||
list_for_each_entry_safe(uncore_pmu, next, &uncore_pmus, list) {
|
||||
struct cpumask * cpumask = (struct cpumask *) GATOR_ATOMIC_READ(&(uncore_pmu->cpumask_atomic));
|
||||
if (cpumask) {
|
||||
kfree(cpumask);
|
||||
}
|
||||
|
||||
kfree(uncore_pmu);
|
||||
}
|
||||
}
|
||||
mutex_unlock(&pmu_mutex);
|
||||
}
|
||||
330
drivers/gator/gator_trace_gpu.c
Normal file
330
drivers/gator/gator_trace_gpu.c
Normal file
@@ -0,0 +1,330 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*/
|
||||
|
||||
#include "gator.h"
|
||||
|
||||
#include <linux/slab.h>
|
||||
#include <linux/module.h>
|
||||
#include <linux/time.h>
|
||||
#include <linux/math64.h>
|
||||
|
||||
#ifdef MALI_SUPPORT
|
||||
#ifdef MALI_DIR_MIDGARD
|
||||
/* New DDK Directory structure with kernel/drivers/gpu/arm/midgard*/
|
||||
#include "mali_linux_trace.h"
|
||||
#else
|
||||
/* Old DDK Directory structure with kernel/drivers/gpu/arm/t6xx*/
|
||||
#include "linux/mali_linux_trace.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Taken from MALI_PROFILING_EVENT_TYPE_* items in Mali DDK.
|
||||
*/
|
||||
#define EVENT_TYPE_SINGLE 0
|
||||
#define EVENT_TYPE_START 1
|
||||
#define EVENT_TYPE_STOP 2
|
||||
#define EVENT_TYPE_SUSPEND 3
|
||||
#define EVENT_TYPE_RESUME 4
|
||||
|
||||
/* Note whether tracepoints have been registered */
|
||||
static int mali_timeline_trace_registered;
|
||||
static int mali_job_slots_trace_registered;
|
||||
|
||||
enum {
|
||||
GPU_UNIT_NONE = 0,
|
||||
GPU_UNIT_VP,
|
||||
GPU_UNIT_FP,
|
||||
GPU_UNIT_CL,
|
||||
NUMBER_OF_GPU_UNITS
|
||||
};
|
||||
|
||||
#if defined(MALI_SUPPORT)
|
||||
|
||||
struct mali_activity {
|
||||
int core;
|
||||
int key;
|
||||
int count;
|
||||
int last_activity;
|
||||
int last_pid;
|
||||
};
|
||||
|
||||
// Midgard can have up to 16 cores, but Bifrost can have up to 32, so reserving the max.
|
||||
#define NUMBER_OF_GPU_CORES 32
|
||||
|
||||
static struct mali_activity mali_activities[NUMBER_OF_GPU_UNITS*NUMBER_OF_GPU_CORES];
|
||||
static DEFINE_SPINLOCK(mali_activities_lock);
|
||||
|
||||
/* Only one event should be running on a unit and core at a time (ie,
|
||||
* a start event can only be followed by a stop and vice versa), but
|
||||
* because the kernel only knows when a job is enqueued and not
|
||||
* started, it is possible for a start1, start2, stop1, stop2. Change
|
||||
* it back into start1, stop1, start2, stop2 by queueing up start2 and
|
||||
* releasing it when stop1 is received.
|
||||
*/
|
||||
|
||||
static int mali_activity_index(int core, int key)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(mali_activities); ++i) {
|
||||
if ((mali_activities[i].core == core) && (mali_activities[i].key == key))
|
||||
break;
|
||||
if ((mali_activities[i].core == 0) && (mali_activities[i].key == 0)) {
|
||||
mali_activities[i].core = core;
|
||||
mali_activities[i].key = key;
|
||||
break;
|
||||
}
|
||||
}
|
||||
BUG_ON(i >= ARRAY_SIZE(mali_activities));
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
static void mali_activity_enqueue(int core, int key, int activity, int pid)
|
||||
{
|
||||
int i;
|
||||
int count;
|
||||
|
||||
spin_lock(&mali_activities_lock);
|
||||
i = mali_activity_index(core, key);
|
||||
|
||||
count = mali_activities[i].count;
|
||||
BUG_ON(count < 0 || count > 2);
|
||||
if (count > 1)
|
||||
/*
|
||||
* The last value is about to be overwritten, send it now. This
|
||||
* may happen if a stop message is lost.
|
||||
*/
|
||||
gator_marshal_activity_switch(core, key, mali_activities[i].last_activity, mali_activities[i].last_pid);
|
||||
else
|
||||
++mali_activities[i].count;
|
||||
if (count) {
|
||||
mali_activities[i].last_activity = activity;
|
||||
mali_activities[i].last_pid = pid;
|
||||
}
|
||||
spin_unlock(&mali_activities_lock);
|
||||
|
||||
if (!count)
|
||||
gator_marshal_activity_switch(core, key, activity, pid);
|
||||
}
|
||||
|
||||
static void mali_activity_stop(int core, int key)
|
||||
{
|
||||
int i;
|
||||
int count;
|
||||
int last_activity = 0;
|
||||
int last_pid = 0;
|
||||
|
||||
spin_lock(&mali_activities_lock);
|
||||
i = mali_activity_index(core, key);
|
||||
|
||||
if (mali_activities[i].count == 0) {
|
||||
spin_unlock(&mali_activities_lock);
|
||||
return;
|
||||
}
|
||||
--mali_activities[i].count;
|
||||
count = mali_activities[i].count;
|
||||
if (count) {
|
||||
last_activity = mali_activities[i].last_activity;
|
||||
last_pid = mali_activities[i].last_pid;
|
||||
}
|
||||
spin_unlock(&mali_activities_lock);
|
||||
|
||||
gator_marshal_activity_switch(core, key, 0, 0);
|
||||
if (count)
|
||||
gator_marshal_activity_switch(core, key, last_activity, last_pid);
|
||||
}
|
||||
|
||||
static void mali_activity_clear(struct mali_counter mali_activity[], size_t mali_activity_size)
|
||||
{
|
||||
int activity;
|
||||
int cores;
|
||||
int core;
|
||||
|
||||
for (activity = 0; activity < mali_activity_size; ++activity) {
|
||||
cores = mali_activity[activity].cores;
|
||||
if (cores < 0)
|
||||
cores = 1;
|
||||
for (core = 0; core < cores; ++core) {
|
||||
if (mali_activity[activity].enabled) {
|
||||
preempt_disable();
|
||||
gator_marshal_activity_switch(core, mali_activity[activity].key, 0, 0);
|
||||
preempt_enable();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if defined(MALI_SUPPORT) && (MALI_SUPPORT != MALI_MIDGARD_OR_BIFROST)
|
||||
#include "gator_events_mali_4xx.h"
|
||||
|
||||
/*
|
||||
* Taken from MALI_PROFILING_EVENT_CHANNEL_* in Mali DDK.
|
||||
*/
|
||||
enum {
|
||||
EVENT_CHANNEL_SOFTWARE = 0,
|
||||
EVENT_CHANNEL_VP0 = 1,
|
||||
EVENT_CHANNEL_FP0 = 5,
|
||||
EVENT_CHANNEL_FP1,
|
||||
EVENT_CHANNEL_FP2,
|
||||
EVENT_CHANNEL_FP3,
|
||||
EVENT_CHANNEL_FP4,
|
||||
EVENT_CHANNEL_FP5,
|
||||
EVENT_CHANNEL_FP6,
|
||||
EVENT_CHANNEL_FP7,
|
||||
EVENT_CHANNEL_GPU = 21
|
||||
};
|
||||
|
||||
/**
|
||||
* These events are applicable when the type MALI_PROFILING_EVENT_TYPE_SINGLE is used from the GPU channel
|
||||
*/
|
||||
enum {
|
||||
EVENT_REASON_SINGLE_GPU_NONE = 0,
|
||||
EVENT_REASON_SINGLE_GPU_FREQ_VOLT_CHANGE = 1,
|
||||
};
|
||||
|
||||
struct mali_counter mali_activity[2];
|
||||
|
||||
GATOR_DEFINE_PROBE(mali_timeline_event, TP_PROTO(unsigned int event_id, unsigned int d0, unsigned int d1, unsigned int d2, unsigned int d3, unsigned int d4))
|
||||
{
|
||||
unsigned int component, state;
|
||||
|
||||
/* do as much work as possible before disabling interrupts */
|
||||
component = (event_id >> 16) & 0xFF; /* component is an 8-bit field */
|
||||
state = (event_id >> 24) & 0xF; /* state is a 4-bit field */
|
||||
|
||||
switch (state) {
|
||||
case EVENT_TYPE_START:
|
||||
if (component == EVENT_CHANNEL_VP0) {
|
||||
/* tgid = d0; pid = d1; */
|
||||
if (mali_activity[1].enabled)
|
||||
mali_activity_enqueue(0, mali_activity[1].key, 1, d1);
|
||||
} else if (component >= EVENT_CHANNEL_FP0 && component <= EVENT_CHANNEL_FP7) {
|
||||
/* tgid = d0; pid = d1; */
|
||||
if (mali_activity[0].enabled)
|
||||
mali_activity_enqueue(component - EVENT_CHANNEL_FP0, mali_activity[0].key, 1, d1);
|
||||
}
|
||||
break;
|
||||
|
||||
case EVENT_TYPE_STOP:
|
||||
if (component == EVENT_CHANNEL_VP0) {
|
||||
if (mali_activity[1].enabled)
|
||||
mali_activity_stop(0, mali_activity[1].key);
|
||||
} else if (component >= EVENT_CHANNEL_FP0 && component <= EVENT_CHANNEL_FP7) {
|
||||
if (mali_activity[0].enabled)
|
||||
mali_activity_stop(component - EVENT_CHANNEL_FP0, mali_activity[0].key);
|
||||
}
|
||||
break;
|
||||
|
||||
case EVENT_TYPE_SINGLE:
|
||||
if (component == EVENT_CHANNEL_GPU) {
|
||||
unsigned int reason = (event_id & 0xffff);
|
||||
|
||||
if (reason == EVENT_REASON_SINGLE_GPU_FREQ_VOLT_CHANGE)
|
||||
gator_events_mali_log_dvfs_event(d0, d1);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#if defined(MALI_SUPPORT) && (MALI_SUPPORT == MALI_MIDGARD_OR_BIFROST)
|
||||
|
||||
struct mali_counter mali_activity[3];
|
||||
|
||||
#if defined(MALI_JOB_SLOTS_EVENT_CHANGED)
|
||||
GATOR_DEFINE_PROBE(mali_job_slots_event, TP_PROTO(unsigned int event_id, unsigned int tgid, unsigned int pid, unsigned char job_id))
|
||||
#else
|
||||
GATOR_DEFINE_PROBE(mali_job_slots_event, TP_PROTO(unsigned int event_id, unsigned int tgid, unsigned int pid))
|
||||
#endif
|
||||
{
|
||||
unsigned int component, state, unit;
|
||||
#if !defined(MALI_JOB_SLOTS_EVENT_CHANGED)
|
||||
unsigned char job_id = 0;
|
||||
#endif
|
||||
|
||||
component = (event_id >> 16) & 0xFF; /* component is an 8-bit field */
|
||||
state = (event_id >> 24) & 0xF; /* state is a 4-bit field */
|
||||
|
||||
switch (component) {
|
||||
case 0:
|
||||
unit = GPU_UNIT_FP;
|
||||
break;
|
||||
case 1:
|
||||
unit = GPU_UNIT_VP;
|
||||
break;
|
||||
case 2:
|
||||
unit = GPU_UNIT_CL;
|
||||
break;
|
||||
default:
|
||||
unit = GPU_UNIT_NONE;
|
||||
}
|
||||
|
||||
if (unit != GPU_UNIT_NONE) {
|
||||
switch (state) {
|
||||
case EVENT_TYPE_START:
|
||||
if (mali_activity[component].enabled)
|
||||
mali_activity_enqueue(0, mali_activity[component].key, 1, (pid != 0 ? pid : tgid));
|
||||
break;
|
||||
case EVENT_TYPE_STOP:
|
||||
default: /* Some jobs can be soft-stopped, so ensure that this terminates the activity trace. */
|
||||
if (mali_activity[component].enabled)
|
||||
mali_activity_stop(0, mali_activity[component].key);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int gator_trace_gpu_start(void)
|
||||
{
|
||||
/*
|
||||
* Returns nonzero for installation failed
|
||||
* Absence of gpu trace points is not an error
|
||||
*/
|
||||
|
||||
#if defined(MALI_SUPPORT)
|
||||
memset(&mali_activities, 0, sizeof(mali_activities));
|
||||
#endif
|
||||
mali_timeline_trace_registered = mali_job_slots_trace_registered = 0;
|
||||
|
||||
#if defined(MALI_SUPPORT) && (MALI_SUPPORT != MALI_MIDGARD_OR_BIFROST)
|
||||
mali_activity_clear(mali_activity, ARRAY_SIZE(mali_activity));
|
||||
if (!GATOR_REGISTER_TRACE(mali_timeline_event))
|
||||
mali_timeline_trace_registered = 1;
|
||||
#endif
|
||||
|
||||
#if defined(MALI_SUPPORT) && (MALI_SUPPORT == MALI_MIDGARD_OR_BIFROST)
|
||||
mali_activity_clear(mali_activity, ARRAY_SIZE(mali_activity));
|
||||
if (!GATOR_REGISTER_TRACE(mali_job_slots_event))
|
||||
mali_job_slots_trace_registered = 1;
|
||||
#endif
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gator_trace_gpu_stop(void)
|
||||
{
|
||||
#if defined(MALI_SUPPORT) && (MALI_SUPPORT != MALI_MIDGARD_OR_BIFROST)
|
||||
if (mali_timeline_trace_registered)
|
||||
GATOR_UNREGISTER_TRACE(mali_timeline_event);
|
||||
#endif
|
||||
|
||||
#if defined(MALI_SUPPORT) && (MALI_SUPPORT == MALI_MIDGARD_OR_BIFROST)
|
||||
if (mali_job_slots_trace_registered)
|
||||
GATOR_UNREGISTER_TRACE(mali_job_slots_event);
|
||||
#endif
|
||||
|
||||
mali_timeline_trace_registered = mali_job_slots_trace_registered = 0;
|
||||
}
|
||||
195
drivers/gator/gator_trace_power.c
Normal file
195
drivers/gator/gator_trace_power.c
Normal file
@@ -0,0 +1,195 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2011-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <linux/cpufreq.h>
|
||||
#include <trace/events/power.h>
|
||||
|
||||
#if defined(__arm__)
|
||||
|
||||
#include <asm/mach-types.h>
|
||||
|
||||
#define implements_wfi() (!machine_is_omap3_beagle())
|
||||
|
||||
#else
|
||||
|
||||
#define implements_wfi() false
|
||||
|
||||
#endif
|
||||
|
||||
/* cpu_frequency and cpu_idle trace points were introduced in Linux
|
||||
* kernel v2.6.38 the now deprecated power_frequency trace point was
|
||||
* available prior to 2.6.38, but only for x86
|
||||
*/
|
||||
#if GATOR_CPU_FREQ_SUPPORT
|
||||
|
||||
static DEFINE_PER_CPU(ulong, idle_prev_state);
|
||||
static ulong power_cpu_enabled[GATOR_CLUSTER_COUNT];
|
||||
static bool power_cpu_enabled_any;
|
||||
static ulong power_cpu_key[GATOR_CLUSTER_COUNT];
|
||||
|
||||
static int gator_trace_power_create_files(struct super_block *sb, struct dentry *root)
|
||||
{
|
||||
struct dentry *dir;
|
||||
int cpu;
|
||||
bool found_nonzero_freq = false;
|
||||
|
||||
/* Even if CONFIG_CPU_FREQ is defined, it still may not be
|
||||
* used. Check for non-zero values from cpufreq_quick_get
|
||||
*/
|
||||
for_each_online_cpu(cpu) {
|
||||
if (cpufreq_quick_get(cpu) > 0) {
|
||||
found_nonzero_freq = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (found_nonzero_freq) {
|
||||
char buf[40];
|
||||
int i;
|
||||
|
||||
/* cpu_frequency */
|
||||
for (i = 0; i < gator_cluster_count; i++) {
|
||||
snprintf(buf, sizeof(buf), "%s_freq", gator_clusters[i]->pmnc_name);
|
||||
dir = gatorfs_mkdir(sb, root, buf);
|
||||
if (!dir)
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &power_cpu_enabled[i]);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &power_cpu_key[i]);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* 'cpu' may not equal smp_processor_id(), i.e. may not be running on the core that is having the freq/idle state change */
|
||||
GATOR_DEFINE_PROBE(cpu_frequency, TP_PROTO(unsigned int frequency, unsigned int cpu))
|
||||
{
|
||||
cpu = lcpu_to_pcpu(cpu);
|
||||
marshal_event_single64(cpu, power_cpu_key[gator_clusterids[cpu]], frequency * 1000L);
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(cpu_idle, TP_PROTO(unsigned int state, unsigned int cpu))
|
||||
{
|
||||
cpu = lcpu_to_pcpu(cpu);
|
||||
|
||||
if (state == per_cpu(idle_prev_state, cpu))
|
||||
return;
|
||||
|
||||
if (implements_wfi())
|
||||
marshal_idle(cpu, state);
|
||||
|
||||
per_cpu(idle_prev_state, cpu) = state;
|
||||
}
|
||||
|
||||
static void gator_trace_power_online(void)
|
||||
{
|
||||
int pcpu = get_physical_cpu();
|
||||
int lcpu = get_logical_cpu();
|
||||
|
||||
if (power_cpu_enabled[gator_clusterids[pcpu]])
|
||||
marshal_event_single64(pcpu, power_cpu_key[gator_clusterids[pcpu]], cpufreq_quick_get(lcpu) * 1000L);
|
||||
}
|
||||
|
||||
static void gator_trace_power_offline(void)
|
||||
{
|
||||
/* Set frequency to zero on an offline */
|
||||
int cpu = get_physical_cpu();
|
||||
|
||||
if (power_cpu_enabled[gator_clusterids[cpu]])
|
||||
marshal_event_single(cpu, power_cpu_key[gator_clusterids[cpu]], 0);
|
||||
}
|
||||
|
||||
static int gator_trace_power_start(void)
|
||||
{
|
||||
int cpu;
|
||||
int i;
|
||||
|
||||
power_cpu_enabled_any = false;
|
||||
for (i = 0; i < gator_cluster_count; i++) {
|
||||
if (power_cpu_enabled[i]) {
|
||||
power_cpu_enabled_any = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* register tracepoints */
|
||||
if (power_cpu_enabled_any)
|
||||
if (GATOR_REGISTER_TRACE(cpu_frequency))
|
||||
goto fail_cpu_frequency_exit;
|
||||
|
||||
/* Always register for cpu_idle for detecting WFI */
|
||||
if (GATOR_REGISTER_TRACE(cpu_idle))
|
||||
goto fail_cpu_idle_exit;
|
||||
pr_debug("gator: registered power event tracepoints\n");
|
||||
|
||||
for_each_present_cpu(cpu) {
|
||||
per_cpu(idle_prev_state, cpu) = 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
||||
/* unregister tracepoints on error */
|
||||
fail_cpu_idle_exit:
|
||||
if (power_cpu_enabled_any)
|
||||
GATOR_UNREGISTER_TRACE(cpu_frequency);
|
||||
fail_cpu_frequency_exit:
|
||||
pr_err("gator: power event tracepoints failed to activate, please verify that tracepoints are enabled in the linux kernel\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void gator_trace_power_stop(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
if (power_cpu_enabled_any)
|
||||
GATOR_UNREGISTER_TRACE(cpu_frequency);
|
||||
GATOR_UNREGISTER_TRACE(cpu_idle);
|
||||
pr_debug("gator: unregistered power event tracepoints\n");
|
||||
|
||||
for (i = 0; i < gator_cluster_count; i++)
|
||||
power_cpu_enabled[i] = 0;
|
||||
}
|
||||
|
||||
static void gator_trace_power_init(void)
|
||||
{
|
||||
int i;
|
||||
|
||||
for (i = 0; i < gator_cluster_count; i++) {
|
||||
power_cpu_enabled[i] = 0;
|
||||
power_cpu_key[i] = gator_events_get_key();
|
||||
}
|
||||
}
|
||||
#else
|
||||
static int gator_trace_power_create_files(struct super_block *sb, struct dentry *root)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gator_trace_power_online(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void gator_trace_power_offline(void)
|
||||
{
|
||||
}
|
||||
|
||||
static int gator_trace_power_start(void)
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void gator_trace_power_stop(void)
|
||||
{
|
||||
}
|
||||
|
||||
static void gator_trace_power_init(void)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
341
drivers/gator/gator_trace_sched.c
Normal file
341
drivers/gator/gator_trace_sched.c
Normal file
@@ -0,0 +1,341 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2010-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#include <trace/events/sched.h>
|
||||
#include <trace/events/task.h>
|
||||
|
||||
#include "gator.h"
|
||||
|
||||
#define TASK_MAP_ENTRIES 1024 /* must be power of 2 */
|
||||
#define TASK_MAX_COLLISIONS 2
|
||||
|
||||
enum {
|
||||
STATE_WAIT_ON_OTHER = 0,
|
||||
STATE_CONTENTION,
|
||||
STATE_WAIT_ON_IO,
|
||||
CPU_WAIT_TOTAL
|
||||
};
|
||||
|
||||
static DEFINE_PER_CPU(uint64_t *, taskname_keys);
|
||||
static DEFINE_PER_CPU(int, collecting);
|
||||
|
||||
/* this array is never read as the cpu charts are derived
|
||||
* counters the files are needed, nonetheless, to show that these
|
||||
* counters are available
|
||||
*/
|
||||
static const char *sched_wait_event_names[] = {
|
||||
"Linux_cpu_wait_contention",
|
||||
"Linux_cpu_wait_io",
|
||||
};
|
||||
static ulong sched_wait_enabled[ARRAY_SIZE(sched_wait_event_names)];
|
||||
static ulong sched_wait_keys[ARRAY_SIZE(sched_wait_event_names)];
|
||||
|
||||
static const char *sched_activity_event_names[] = {
|
||||
"system",
|
||||
"user",
|
||||
};
|
||||
static ulong sched_activity_enabled[ARRAY_SIZE(sched_activity_event_names)][GATOR_CLUSTER_COUNT];
|
||||
static ulong sched_activity_keys[ARRAY_SIZE(sched_activity_event_names)][GATOR_CLUSTER_COUNT];
|
||||
|
||||
static int sched_trace_create_files(struct super_block *sb, struct dentry *root)
|
||||
{
|
||||
struct dentry *dir;
|
||||
int i;
|
||||
int j;
|
||||
char buf[40];
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sched_wait_event_names); ++i) {
|
||||
dir = gatorfs_mkdir(sb, root, sched_wait_event_names[i]);
|
||||
if (!dir)
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &sched_wait_enabled[i]);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &sched_wait_keys[i]);
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sched_activity_event_names); ++i) {
|
||||
for (j = 0; j < gator_cluster_count; j++) {
|
||||
snprintf(buf, sizeof(buf), "%s_%s", gator_clusters[j]->pmnc_name, sched_activity_event_names[i]);
|
||||
dir = gatorfs_mkdir(sb, root, buf);
|
||||
if (!dir)
|
||||
return -1;
|
||||
gatorfs_create_ulong(sb, dir, "enabled", &sched_activity_enabled[i][j]);
|
||||
gatorfs_create_ro_ulong(sb, dir, "key", &sched_activity_keys[i][j]);
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void emit_pid_name(const char *comm, struct task_struct *task)
|
||||
{
|
||||
bool found = false;
|
||||
char taskcomm[TASK_COMM_LEN + 3];
|
||||
unsigned long x, cpu = get_physical_cpu();
|
||||
uint64_t *keys = &(per_cpu(taskname_keys, cpu)[(task->pid & 0xFF) * TASK_MAX_COLLISIONS]);
|
||||
uint64_t value;
|
||||
|
||||
value = gator_chksum_crc32(comm);
|
||||
value = (value << 32) | (uint32_t)task->pid;
|
||||
|
||||
/* determine if the thread name was emitted already */
|
||||
for (x = 0; x < TASK_MAX_COLLISIONS; x++) {
|
||||
if (keys[x] == value) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
/* shift values, new value always in front */
|
||||
uint64_t oldv, newv = value;
|
||||
|
||||
for (x = 0; x < TASK_MAX_COLLISIONS; x++) {
|
||||
oldv = keys[x];
|
||||
keys[x] = newv;
|
||||
newv = oldv;
|
||||
}
|
||||
|
||||
/* emit pid names, cannot use get_task_comm, as it's not exported on all kernel versions */
|
||||
if (strlcpy(taskcomm, comm, TASK_COMM_LEN) == TASK_COMM_LEN - 1) {
|
||||
/* append ellipses if comm has length of TASK_COMM_LEN - 1 */
|
||||
strcat(taskcomm, "...");
|
||||
}
|
||||
|
||||
marshal_thread_name(task->pid, taskcomm);
|
||||
}
|
||||
}
|
||||
|
||||
static void collect_counters(u64 time, struct task_struct *task, bool sched_switch)
|
||||
{
|
||||
int *buffer, len, cpu = get_physical_cpu();
|
||||
long long *buffer64;
|
||||
struct gator_interface *gi;
|
||||
|
||||
if (marshal_event_header(time)) {
|
||||
list_for_each_entry(gi, &gator_events, list) {
|
||||
if (gi->read) {
|
||||
len = gi->read(&buffer, sched_switch);
|
||||
if (len < 0)
|
||||
pr_err("gator: read failed for %s\n", gi->name);
|
||||
marshal_event(len, buffer);
|
||||
} else if (gi->read64) {
|
||||
len = gi->read64(&buffer64, sched_switch);
|
||||
if (len < 0)
|
||||
pr_err("gator: read64 failed for %s\n", gi->name);
|
||||
marshal_event64(len, buffer64);
|
||||
}
|
||||
if (gi->read_proc && task != NULL) {
|
||||
len = gi->read_proc(&buffer64, task);
|
||||
if (len < 0)
|
||||
pr_err("gator: read_proc failed for %s\n", gi->name);
|
||||
marshal_event64(len, buffer64);
|
||||
}
|
||||
}
|
||||
if (cpu == 0)
|
||||
gator_emit_perf_time(time);
|
||||
/* Only check after writing all counters so that time and corresponding counters appear in the same frame */
|
||||
buffer_check(cpu, BLOCK_COUNTER_BUF, time);
|
||||
|
||||
/* Commit buffers on timeout */
|
||||
if (gator_live_rate > 0 && time >= per_cpu(gator_buffer_commit_time, cpu)) {
|
||||
static const int buftypes[] = { NAME_BUF, COUNTER_BUF, BLOCK_COUNTER_BUF, SCHED_TRACE_BUF, ACTIVITY_BUF };
|
||||
int i;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(buftypes); ++i)
|
||||
gator_commit_buffer(cpu, buftypes[i], time);
|
||||
|
||||
/* spinlocks are noops on uniprocessor machines and mutexes do
|
||||
* not work in sched_switch context in RT-Preempt full, so
|
||||
* disable proactive flushing of the annotate frame on
|
||||
* uniprocessor machines.
|
||||
*/
|
||||
#ifdef CONFIG_SMP
|
||||
/* Try to preemptively flush the annotate buffer to reduce the chance of the buffer being full */
|
||||
if (on_primary_core() && spin_trylock(&annotate_lock)) {
|
||||
gator_commit_buffer(0, ANNOTATE_BUF, time);
|
||||
spin_unlock(&annotate_lock);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* special case used during a suspend of the system */
|
||||
static void trace_sched_insert_idle(void)
|
||||
{
|
||||
marshal_sched_trace_switch(0, 0);
|
||||
}
|
||||
|
||||
static void gator_trace_emit_link(struct task_struct *p)
|
||||
{
|
||||
int cookie;
|
||||
int cpu = get_physical_cpu();
|
||||
|
||||
cookie = get_exec_cookie(cpu, p);
|
||||
emit_pid_name(p->comm, p);
|
||||
|
||||
marshal_link(cookie, p->tgid, p->pid);
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(sched_process_fork, TP_PROTO(struct task_struct *parent, struct task_struct *child))
|
||||
{
|
||||
gator_trace_emit_link(child);
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(sched_process_exec, TP_PROTO(struct task_struct *p, pid_t old_pid, struct linux_binprm *bprm))
|
||||
{
|
||||
gator_trace_emit_link(p);
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 15, 0)
|
||||
GATOR_DEFINE_PROBE(task_rename, TP_PROTO(struct task_struct *task, char *comm))
|
||||
#else
|
||||
GATOR_DEFINE_PROBE(task_rename, TP_PROTO(struct task_struct *task, const char *comm))
|
||||
#endif
|
||||
{
|
||||
emit_pid_name(comm, task);
|
||||
}
|
||||
|
||||
#if LINUX_VERSION_CODE < KERNEL_VERSION(4, 4, 0)
|
||||
GATOR_DEFINE_PROBE(sched_switch, TP_PROTO(struct task_struct *prev, struct task_struct *next))
|
||||
#else
|
||||
GATOR_DEFINE_PROBE(sched_switch, TP_PROTO(bool preempt, struct task_struct *prev, struct task_struct *next))
|
||||
#endif
|
||||
{
|
||||
int state;
|
||||
int cpu = get_physical_cpu();
|
||||
|
||||
per_cpu(in_scheduler_context, cpu) = true;
|
||||
|
||||
/* do as much work as possible before disabling interrupts */
|
||||
if (prev->state == TASK_RUNNING)
|
||||
state = STATE_CONTENTION;
|
||||
else if (prev->in_iowait)
|
||||
state = STATE_WAIT_ON_IO;
|
||||
else
|
||||
state = STATE_WAIT_ON_OTHER;
|
||||
|
||||
per_cpu(collecting, cpu) = 1;
|
||||
collect_counters(gator_get_time(), prev, true);
|
||||
per_cpu(collecting, cpu) = 0;
|
||||
|
||||
marshal_sched_trace_switch(next->pid, state);
|
||||
|
||||
per_cpu(in_scheduler_context, cpu) = false;
|
||||
}
|
||||
|
||||
GATOR_DEFINE_PROBE(sched_process_free, TP_PROTO(struct task_struct *p))
|
||||
{
|
||||
marshal_sched_trace_exit(p->tgid, p->pid);
|
||||
}
|
||||
|
||||
static void do_nothing(void *info)
|
||||
{
|
||||
/* Intentionally do nothing */
|
||||
(void)info;
|
||||
}
|
||||
|
||||
static int register_scheduler_tracepoints(void)
|
||||
{
|
||||
/* register tracepoints */
|
||||
if (GATOR_REGISTER_TRACE(sched_process_fork))
|
||||
goto fail_sched_process_fork;
|
||||
if (GATOR_REGISTER_TRACE(sched_process_exec))
|
||||
goto fail_sched_process_exec;
|
||||
if (GATOR_REGISTER_TRACE(task_rename))
|
||||
goto fail_task_rename;
|
||||
if (GATOR_REGISTER_TRACE(sched_switch))
|
||||
goto fail_sched_switch;
|
||||
if (GATOR_REGISTER_TRACE(sched_process_free))
|
||||
goto fail_sched_process_free;
|
||||
pr_debug("gator: registered tracepoints\n");
|
||||
|
||||
/* Now that the scheduler tracepoint is registered, force a context
|
||||
* switch on all cpus to capture what is currently running.
|
||||
*/
|
||||
on_each_cpu(do_nothing, NULL, 0);
|
||||
|
||||
return 0;
|
||||
|
||||
/* unregister tracepoints on error */
|
||||
fail_sched_process_free:
|
||||
GATOR_UNREGISTER_TRACE(sched_switch);
|
||||
fail_sched_switch:
|
||||
GATOR_UNREGISTER_TRACE(task_rename);
|
||||
fail_task_rename:
|
||||
GATOR_UNREGISTER_TRACE(sched_process_exec);
|
||||
fail_sched_process_exec:
|
||||
GATOR_UNREGISTER_TRACE(sched_process_fork);
|
||||
fail_sched_process_fork:
|
||||
pr_err("gator: tracepoints failed to activate, please verify that tracepoints are enabled in the linux kernel\n");
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void unregister_scheduler_tracepoints(void)
|
||||
{
|
||||
GATOR_UNREGISTER_TRACE(sched_process_fork);
|
||||
GATOR_UNREGISTER_TRACE(sched_process_exec);
|
||||
GATOR_UNREGISTER_TRACE(task_rename);
|
||||
GATOR_UNREGISTER_TRACE(sched_switch);
|
||||
GATOR_UNREGISTER_TRACE(sched_process_free);
|
||||
pr_debug("gator: unregistered tracepoints\n");
|
||||
}
|
||||
|
||||
static void gator_trace_sched_stop(void)
|
||||
{
|
||||
int cpu;
|
||||
|
||||
unregister_scheduler_tracepoints();
|
||||
|
||||
for_each_present_cpu(cpu) {
|
||||
kfree(per_cpu(taskname_keys, cpu));
|
||||
}
|
||||
}
|
||||
|
||||
static int gator_trace_sched_start(void)
|
||||
{
|
||||
int cpu, size;
|
||||
int ret;
|
||||
|
||||
for_each_present_cpu(cpu) {
|
||||
size = TASK_MAP_ENTRIES * TASK_MAX_COLLISIONS * sizeof(uint64_t);
|
||||
per_cpu(taskname_keys, cpu) = kmalloc(size, GFP_KERNEL);
|
||||
if (!per_cpu(taskname_keys, cpu))
|
||||
return -1;
|
||||
memset(per_cpu(taskname_keys, cpu), 0, size);
|
||||
}
|
||||
|
||||
ret = register_scheduler_tracepoints();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
static void gator_trace_sched_offline(void)
|
||||
{
|
||||
trace_sched_insert_idle();
|
||||
}
|
||||
|
||||
static void gator_trace_sched_init(void)
|
||||
{
|
||||
int i;
|
||||
int j;
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sched_wait_enabled); i++) {
|
||||
sched_wait_enabled[i] = 0;
|
||||
sched_wait_keys[i] = gator_events_get_key();
|
||||
}
|
||||
|
||||
for (i = 0; i < ARRAY_SIZE(sched_activity_enabled); i++) {
|
||||
for (j = 0; j < gator_cluster_count; j++) {
|
||||
sched_activity_enabled[i][j] = 0;
|
||||
sched_activity_keys[i][j] = gator_events_get_key();
|
||||
}
|
||||
}
|
||||
}
|
||||
159
drivers/gator/mali/mali_mjollnir_profiling_gator_api.h
Normal file
159
drivers/gator/mali/mali_mjollnir_profiling_gator_api.h
Normal file
@@ -0,0 +1,159 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2013-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MALI_MJOLLNIR_PROFILING_GATOR_API_H__
|
||||
#define __MALI_MJOLLNIR_PROFILING_GATOR_API_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
|
||||
/*
|
||||
* The number of processor cores. Update to suit your hardware implementation.
|
||||
*/
|
||||
#define MAX_NUM_FP_CORES (4)
|
||||
#define MAX_NUM_VP_CORES (1)
|
||||
#define MAX_NUM_L2_CACHE_CORES (1)
|
||||
|
||||
enum counters {
|
||||
/* Timeline activity */
|
||||
ACTIVITY_VP_0 = 0,
|
||||
ACTIVITY_FP_0,
|
||||
ACTIVITY_FP_1,
|
||||
ACTIVITY_FP_2,
|
||||
ACTIVITY_FP_3,
|
||||
|
||||
/* L2 cache counters */
|
||||
COUNTER_L2_0_C0,
|
||||
COUNTER_L2_0_C1,
|
||||
|
||||
/* Vertex processor counters */
|
||||
COUNTER_VP_0_C0,
|
||||
COUNTER_VP_0_C1,
|
||||
|
||||
/* Fragment processor counters */
|
||||
COUNTER_FP_0_C0,
|
||||
COUNTER_FP_0_C1,
|
||||
COUNTER_FP_1_C0,
|
||||
COUNTER_FP_1_C1,
|
||||
COUNTER_FP_2_C0,
|
||||
COUNTER_FP_2_C1,
|
||||
COUNTER_FP_3_C0,
|
||||
COUNTER_FP_3_C1,
|
||||
|
||||
/* EGL Software Counters */
|
||||
COUNTER_EGL_BLIT_TIME,
|
||||
|
||||
/* GLES Software Counters */
|
||||
COUNTER_GLES_DRAW_ELEMENTS_CALLS,
|
||||
COUNTER_GLES_DRAW_ELEMENTS_NUM_INDICES,
|
||||
COUNTER_GLES_DRAW_ELEMENTS_NUM_TRANSFORMED,
|
||||
COUNTER_GLES_DRAW_ARRAYS_CALLS,
|
||||
COUNTER_GLES_DRAW_ARRAYS_NUM_TRANSFORMED,
|
||||
COUNTER_GLES_DRAW_POINTS,
|
||||
COUNTER_GLES_DRAW_LINES,
|
||||
COUNTER_GLES_DRAW_LINE_LOOP,
|
||||
COUNTER_GLES_DRAW_LINE_STRIP,
|
||||
COUNTER_GLES_DRAW_TRIANGLES,
|
||||
COUNTER_GLES_DRAW_TRIANGLE_STRIP,
|
||||
COUNTER_GLES_DRAW_TRIANGLE_FAN,
|
||||
COUNTER_GLES_NON_VBO_DATA_COPY_TIME,
|
||||
COUNTER_GLES_UNIFORM_BYTES_COPIED_TO_MALI,
|
||||
COUNTER_GLES_UPLOAD_TEXTURE_TIME,
|
||||
COUNTER_GLES_UPLOAD_VBO_TIME,
|
||||
COUNTER_GLES_NUM_FLUSHES,
|
||||
COUNTER_GLES_NUM_VSHADERS_GENERATED,
|
||||
COUNTER_GLES_NUM_FSHADERS_GENERATED,
|
||||
COUNTER_GLES_VSHADER_GEN_TIME,
|
||||
COUNTER_GLES_FSHADER_GEN_TIME,
|
||||
COUNTER_GLES_INPUT_TRIANGLES,
|
||||
COUNTER_GLES_VXCACHE_HIT,
|
||||
COUNTER_GLES_VXCACHE_MISS,
|
||||
COUNTER_GLES_VXCACHE_COLLISION,
|
||||
COUNTER_GLES_CULLED_TRIANGLES,
|
||||
COUNTER_GLES_CULLED_LINES,
|
||||
COUNTER_GLES_BACKFACE_TRIANGLES,
|
||||
COUNTER_GLES_GBCLIP_TRIANGLES,
|
||||
COUNTER_GLES_GBCLIP_LINES,
|
||||
COUNTER_GLES_TRIANGLES_DRAWN,
|
||||
COUNTER_GLES_DRAWCALL_TIME,
|
||||
COUNTER_GLES_TRIANGLES_COUNT,
|
||||
COUNTER_GLES_INDEPENDENT_TRIANGLES_COUNT,
|
||||
COUNTER_GLES_STRIP_TRIANGLES_COUNT,
|
||||
COUNTER_GLES_FAN_TRIANGLES_COUNT,
|
||||
COUNTER_GLES_LINES_COUNT,
|
||||
COUNTER_GLES_INDEPENDENT_LINES_COUNT,
|
||||
COUNTER_GLES_STRIP_LINES_COUNT,
|
||||
COUNTER_GLES_LOOP_LINES_COUNT,
|
||||
|
||||
COUNTER_FILMSTRIP,
|
||||
COUNTER_FREQUENCY,
|
||||
COUNTER_VOLTAGE,
|
||||
|
||||
NUMBER_OF_EVENTS
|
||||
};
|
||||
|
||||
#define FIRST_ACTIVITY_EVENT ACTIVITY_VP_0
|
||||
#define LAST_ACTIVITY_EVENT ACTIVITY_FP_3
|
||||
|
||||
#define FIRST_HW_COUNTER COUNTER_L2_0_C0
|
||||
#define LAST_HW_COUNTER COUNTER_FP_3_C1
|
||||
|
||||
#define FIRST_SW_COUNTER COUNTER_EGL_BLIT_TIME
|
||||
#define LAST_SW_COUNTER COUNTER_GLES_LOOP_LINES_COUNT
|
||||
|
||||
/* Signifies that the system is able to report voltage and frequency numbers. */
|
||||
#define DVFS_REPORTED_BY_DDK 1
|
||||
|
||||
/**
|
||||
* Structure to pass performance counter data of a Mali core
|
||||
*/
|
||||
struct _mali_profiling_core_counters {
|
||||
u32 source0;
|
||||
u32 value0;
|
||||
u32 source1;
|
||||
u32 value1;
|
||||
};
|
||||
|
||||
/*
|
||||
* For compatibility with utgard.
|
||||
*/
|
||||
struct _mali_profiling_l2_counter_values {
|
||||
struct _mali_profiling_core_counters cores[MAX_NUM_L2_CACHE_CORES];
|
||||
};
|
||||
|
||||
struct _mali_profiling_mali_version {
|
||||
u32 mali_product_id;
|
||||
u32 mali_version_major;
|
||||
u32 mali_version_minor;
|
||||
u32 num_of_l2_cores;
|
||||
u32 num_of_fp_cores;
|
||||
u32 num_of_vp_cores;
|
||||
};
|
||||
|
||||
extern void _mali_profiling_get_mali_version(struct _mali_profiling_mali_version *values);
|
||||
extern u32 _mali_profiling_get_l2_counters(struct _mali_profiling_l2_counter_values *values);
|
||||
|
||||
/*
|
||||
* List of possible actions allowing DDK to be controlled by Streamline.
|
||||
* The following numbers are used by DDK to control the frame buffer dumping.
|
||||
*/
|
||||
#define FBDUMP_CONTROL_ENABLE (1)
|
||||
#define FBDUMP_CONTROL_RATE (2)
|
||||
#define SW_COUNTER_ENABLE (3)
|
||||
#define FBDUMP_CONTROL_RESIZE_FACTOR (4)
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __MALI_MJOLLNIR_PROFILING_GATOR_API_H__ */
|
||||
197
drivers/gator/mali/mali_utgard_profiling_gator_api.h
Normal file
197
drivers/gator/mali/mali_utgard_profiling_gator_api.h
Normal file
@@ -0,0 +1,197 @@
|
||||
/**
|
||||
* Copyright (C) Arm Limited 2013-2016. 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 as
|
||||
* published by the Free Software Foundation.
|
||||
*
|
||||
*/
|
||||
|
||||
#ifndef __MALI_UTGARD_PROFILING_GATOR_API_H__
|
||||
#define __MALI_UTGARD_PROFILING_GATOR_API_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
#define MALI_PROFILING_API_VERSION 4
|
||||
|
||||
#define MAX_NUM_L2_CACHE_CORES 3
|
||||
#define MAX_NUM_FP_CORES 8
|
||||
#define MAX_NUM_VP_CORES 1
|
||||
|
||||
/** The list of events supported by the Mali DDK. */
|
||||
enum {
|
||||
/* Vertex processor activity */
|
||||
ACTIVITY_VP_0 = 0,
|
||||
|
||||
/* Fragment processor activity */
|
||||
ACTIVITY_FP_0, /* 1 */
|
||||
ACTIVITY_FP_1,
|
||||
ACTIVITY_FP_2,
|
||||
ACTIVITY_FP_3,
|
||||
ACTIVITY_FP_4,
|
||||
ACTIVITY_FP_5,
|
||||
ACTIVITY_FP_6,
|
||||
ACTIVITY_FP_7,
|
||||
|
||||
/* L2 cache counters */
|
||||
COUNTER_L2_0_C0,
|
||||
COUNTER_L2_0_C1,
|
||||
COUNTER_L2_1_C0,
|
||||
COUNTER_L2_1_C1,
|
||||
COUNTER_L2_2_C0,
|
||||
COUNTER_L2_2_C1,
|
||||
|
||||
/* Vertex processor counters */
|
||||
COUNTER_VP_0_C0, /*15*/
|
||||
COUNTER_VP_0_C1,
|
||||
|
||||
/* Fragment processor counters */
|
||||
COUNTER_FP_0_C0,
|
||||
COUNTER_FP_0_C1,
|
||||
COUNTER_FP_1_C0,
|
||||
COUNTER_FP_1_C1,
|
||||
COUNTER_FP_2_C0,
|
||||
COUNTER_FP_2_C1,
|
||||
COUNTER_FP_3_C0,
|
||||
COUNTER_FP_3_C1,
|
||||
COUNTER_FP_4_C0,
|
||||
COUNTER_FP_4_C1,
|
||||
COUNTER_FP_5_C0,
|
||||
COUNTER_FP_5_C1,
|
||||
COUNTER_FP_6_C0,
|
||||
COUNTER_FP_6_C1,
|
||||
COUNTER_FP_7_C0,
|
||||
COUNTER_FP_7_C1, /* 32 */
|
||||
|
||||
/*
|
||||
* If more hardware counters are added, the _mali_osk_hw_counter_table
|
||||
* below should also be updated.
|
||||
*/
|
||||
|
||||
/* EGL software counters */
|
||||
COUNTER_EGL_BLIT_TIME,
|
||||
|
||||
/* GLES software counters */
|
||||
COUNTER_GLES_DRAW_ELEMENTS_CALLS,
|
||||
COUNTER_GLES_DRAW_ELEMENTS_NUM_INDICES,
|
||||
COUNTER_GLES_DRAW_ELEMENTS_NUM_TRANSFORMED,
|
||||
COUNTER_GLES_DRAW_ARRAYS_CALLS,
|
||||
COUNTER_GLES_DRAW_ARRAYS_NUM_TRANSFORMED,
|
||||
COUNTER_GLES_DRAW_POINTS,
|
||||
COUNTER_GLES_DRAW_LINES,
|
||||
COUNTER_GLES_DRAW_LINE_LOOP,
|
||||
COUNTER_GLES_DRAW_LINE_STRIP,
|
||||
COUNTER_GLES_DRAW_TRIANGLES,
|
||||
COUNTER_GLES_DRAW_TRIANGLE_STRIP,
|
||||
COUNTER_GLES_DRAW_TRIANGLE_FAN,
|
||||
COUNTER_GLES_NON_VBO_DATA_COPY_TIME,
|
||||
COUNTER_GLES_UNIFORM_BYTES_COPIED_TO_MALI,
|
||||
COUNTER_GLES_UPLOAD_TEXTURE_TIME,
|
||||
COUNTER_GLES_UPLOAD_VBO_TIME,
|
||||
COUNTER_GLES_NUM_FLUSHES,
|
||||
COUNTER_GLES_NUM_VSHADERS_GENERATED,
|
||||
COUNTER_GLES_NUM_FSHADERS_GENERATED,
|
||||
COUNTER_GLES_VSHADER_GEN_TIME,
|
||||
COUNTER_GLES_FSHADER_GEN_TIME,
|
||||
COUNTER_GLES_INPUT_TRIANGLES,
|
||||
COUNTER_GLES_VXCACHE_HIT,
|
||||
COUNTER_GLES_VXCACHE_MISS,
|
||||
COUNTER_GLES_VXCACHE_COLLISION,
|
||||
COUNTER_GLES_CULLED_TRIANGLES,
|
||||
COUNTER_GLES_CULLED_LINES,
|
||||
COUNTER_GLES_BACKFACE_TRIANGLES,
|
||||
COUNTER_GLES_GBCLIP_TRIANGLES,
|
||||
COUNTER_GLES_GBCLIP_LINES,
|
||||
COUNTER_GLES_TRIANGLES_DRAWN,
|
||||
COUNTER_GLES_DRAWCALL_TIME,
|
||||
COUNTER_GLES_TRIANGLES_COUNT,
|
||||
COUNTER_GLES_INDEPENDENT_TRIANGLES_COUNT,
|
||||
COUNTER_GLES_STRIP_TRIANGLES_COUNT,
|
||||
COUNTER_GLES_FAN_TRIANGLES_COUNT,
|
||||
COUNTER_GLES_LINES_COUNT,
|
||||
COUNTER_GLES_INDEPENDENT_LINES_COUNT,
|
||||
COUNTER_GLES_STRIP_LINES_COUNT,
|
||||
COUNTER_GLES_LOOP_LINES_COUNT,
|
||||
|
||||
/* Framebuffer capture pseudo-counter */
|
||||
COUNTER_FILMSTRIP,
|
||||
|
||||
NUMBER_OF_EVENTS
|
||||
} _mali_osk_counter_id;
|
||||
|
||||
#define FIRST_ACTIVITY_EVENT ACTIVITY_VP_0
|
||||
#define LAST_ACTIVITY_EVENT ACTIVITY_FP_7
|
||||
|
||||
#define FIRST_HW_COUNTER COUNTER_L2_0_C0
|
||||
#define LAST_HW_COUNTER COUNTER_FP_7_C1
|
||||
|
||||
#define FIRST_SW_COUNTER COUNTER_EGL_BLIT_TIME
|
||||
#define LAST_SW_COUNTER COUNTER_GLES_LOOP_LINES_COUNT
|
||||
|
||||
#define FIRST_SPECIAL_COUNTER COUNTER_FILMSTRIP
|
||||
#define LAST_SPECIAL_COUNTER COUNTER_FILMSTRIP
|
||||
|
||||
/**
|
||||
* Structure to pass performance counter data of a Mali core
|
||||
*/
|
||||
struct _mali_profiling_core_counters {
|
||||
u32 source0;
|
||||
u32 value0;
|
||||
u32 source1;
|
||||
u32 value1;
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure to pass performance counter data of Mali L2 cache cores
|
||||
*/
|
||||
struct _mali_profiling_l2_counter_values {
|
||||
struct _mali_profiling_core_counters cores[MAX_NUM_L2_CACHE_CORES];
|
||||
};
|
||||
|
||||
/**
|
||||
* Structure to pass data defining Mali instance in use:
|
||||
*
|
||||
* mali_product_id - Mali product id
|
||||
* mali_version_major - Mali version major number
|
||||
* mali_version_minor - Mali version minor number
|
||||
* num_of_l2_cores - number of L2 cache cores
|
||||
* num_of_fp_cores - number of fragment processor cores
|
||||
* num_of_vp_cores - number of vertex processor cores
|
||||
*/
|
||||
struct _mali_profiling_mali_version {
|
||||
u32 mali_product_id;
|
||||
u32 mali_version_major;
|
||||
u32 mali_version_minor;
|
||||
u32 num_of_l2_cores;
|
||||
u32 num_of_fp_cores;
|
||||
u32 num_of_vp_cores;
|
||||
};
|
||||
|
||||
/*
|
||||
* List of possible actions to be controlled by Streamline.
|
||||
* The following numbers are used by gator to control the frame buffer dumping and s/w counter reporting.
|
||||
* We cannot use the enums in mali_uk_types.h because they are unknown inside gator.
|
||||
*/
|
||||
#define FBDUMP_CONTROL_ENABLE (1)
|
||||
#define FBDUMP_CONTROL_RATE (2)
|
||||
#define SW_COUNTER_ENABLE (3)
|
||||
#define FBDUMP_CONTROL_RESIZE_FACTOR (4)
|
||||
|
||||
void _mali_profiling_control(u32 action, u32 value);
|
||||
|
||||
u32 _mali_profiling_get_l2_counters(struct _mali_profiling_l2_counter_values *values);
|
||||
|
||||
int _mali_profiling_set_event(u32 counter_id, s32 event_id);
|
||||
|
||||
u32 _mali_profiling_get_api_version(void);
|
||||
|
||||
void _mali_profiling_get_mali_version(struct _mali_profiling_mali_version *values);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __MALI_UTGARD_PROFILING_GATOR_API_H__ */
|
||||
49
drivers/gator/mali_midgard.mk
Normal file
49
drivers/gator/mali_midgard.mk
Normal file
@@ -0,0 +1,49 @@
|
||||
# Defines for Mali-Midgard/Bifrost driver
|
||||
EXTRA_CFLAGS += -DMALI_USE_UMP=1 \
|
||||
-DMALI_LICENSE_IS_GPL=1 \
|
||||
-DMALI_BASE_TRACK_MEMLEAK=0 \
|
||||
-DMALI_DEBUG=0 \
|
||||
-DMALI_ERROR_INJECT_ON=0 \
|
||||
-DMALI_CUSTOMER_RELEASE=1 \
|
||||
-DMALI_UNIT_TEST=0 \
|
||||
-DMALI_BACKEND_KERNEL=1 \
|
||||
-DMALI_NO_MALI=0
|
||||
|
||||
DDK_DIR ?= .
|
||||
|
||||
GATOR_MALI_MIDGARD_PATH := $(shell echo $(CONFIG_GATOR_MALI_MIDGARD_PATH))
|
||||
|
||||
ifneq ($(wildcard $(DDK_DIR)/$(GATOR_MALI_MIDGARD_PATH)/mali_kbase_gator_api.h),)
|
||||
# r5p0/Fluorine - ...
|
||||
EXTRA_CFLAGS += -DMALI_SIMPLE_API=1 \
|
||||
-DMALI_DIR_MIDGARD=1 \
|
||||
-I$(DDK_DIR)/$(GATOR_MALI_MIDGARD_PATH) \
|
||||
|
||||
else
|
||||
ifneq ($(wildcard $(DDK_DIR)/$(GATOR_MALI_MIDGARD_PATH)/kbase),)
|
||||
# ? - r3p0
|
||||
KBASE_DIR = $(DDK_DIR)/$(GATOR_MALI_MIDGARD_PATH)/kbase
|
||||
OSK_DIR = $(DDK_DIR)/$(GATOR_MALI_MIDGARD_PATH)/kbase/osk
|
||||
else
|
||||
ifneq ($(wildcard $(DDK_DIR)/$(GATOR_MALI_MIDGARD_PATH)),)
|
||||
# r4p0/Europium - r4p1/Europium-Inc
|
||||
KBASE_DIR = $(DDK_DIR)/$(GATOR_MALI_MIDGARD_PATH)
|
||||
OSK_DIR = $(DDK_DIR)/$(GATOR_MALI_MIDGARD_PATH)/osk
|
||||
EXTRA_CFLAGS += -DMALI_DIR_MIDGARD=1
|
||||
endif
|
||||
endif
|
||||
|
||||
UMP_DIR = $(DDK_DIR)/include/linux
|
||||
|
||||
# Include directories in the DDK
|
||||
EXTRA_CFLAGS += -I$(KBASE_DIR)/ \
|
||||
-I$(KBASE_DIR)/.. \
|
||||
-I$(OSK_DIR)/.. \
|
||||
-I$(UMP_DIR)/.. \
|
||||
-I$(DDK_DIR)/include \
|
||||
-I$(KBASE_DIR)/osk/src/linux/include \
|
||||
-I$(KBASE_DIR)/platform_dummy \
|
||||
-I$(KBASE_DIR)/src \
|
||||
-Idrivers/staging/android \
|
||||
|
||||
endif
|
||||
Reference in New Issue
Block a user